Index: openacs-4/packages/acs-core-docs/www/xml/kernel/ext-auth-design.xml =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-core-docs/www/xml/kernel/ext-auth-design.xml,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/acs-core-docs/www/xml/kernel/ext-auth-design.xml 21 Aug 2003 08:04:31 -0000 1.1 @@ -0,0 +1,2629 @@ + + +%myvars; +]> + + + External Authentication Design + + + EXT-AUTH-1: Authentication and Account Status API (4 hours) + + by + +Peter +Marklund + + + + Current Design + + Procedures to support this feature have already been written by + Lars. We are using the existing procedure ad_user_login and are + deprecating ad_maybe_redirect_for_registration and making it invoke + auth::require_login. + + + Execution Story + + The auth::authenticate procedure will be called by the login page + and the auth::require_login procedure at the beginning of any + application pages that require login. + + + Tradeoffs: + + For this feature reliability and testing are the prime concerns. + + + External Design + + The authtentication API has the following public methods: + +ad_proc -public auth::require_login {} { + If the current session is not authenticated, redirect to the + login page, and aborts the current page script. + Otherwise, returns the user_id of the user logged in. + Use this in a page script to ensure that only registered and authenticated + users can execute the page, for example for posting to a forum. + @return user_id of user, if the user is logged in. Otherwise will issue an ad_script_abort. + @see ad_script_abort +} + + + + +ad_proc -public auth::authenticate { + {-authority_id ""} + {-username:required} + {-password:required} +} { + Try to authenticate and login the user forever by validating the username/password combination, + and return authentication and account status codes. + + @param authority_id The ID of the authority to ask to verify the user. Defaults to local authority. + @param username Authority specific username of the user. + @param passowrd The password as the user entered it. + +} + + + + + + Testcases + + + Procedure +auth::authenticate + + Need to stub ns_sendmail? + Test auth_status not "ok" for: + + + + Invalid password + + + + Invalid username + + + + Invalid authority + + + + + Test account_status "closed" for + + + + Member state in banned, rejected, needs approval, and +deleted. + + + + + Error handling (requires stubbing the Authenticate service +contract): + + + + The Authenticate service contract call hangs and we +should time out. Can we implement this in Tcl? + + + + The Authenticate call returns comm_error. + + + + The Authenticate call returns auth_error. + + + + The Authenticate service contract call does not return +the variables that it should + + + + + + + + Page Flow + + None + + + Data Model + + None + + + TODO + + + + Write test cases + + + + Deprecate ad_maybe_redirect_for_registration and make it +invoke auth::require_login + + + + New proc acs_user::get_by_username + + + + + + + + + EXT-AUTH-3: Account Creation API (5 hours) + + + + + EXT-AUTH-3: +Account Creation API (5 hours) + + by + +Peter +Marklund + + + + Current API + + +ad_proc ad_user_new {email first_names last_name password password_question password_answer + {url ""} {email_verified_p "t"} {member_state "approved"} {user_id ""} } { + Creates a new user in the system. The user_id can be specified as an argument to enable double click protection. + If this procedure succeeds, returns the new user_id. Otherwise, returns 0. +} + + + + + + New API + + TODO: Make a auth::create_user return values from +auth::registration::Register. + + TODO: Make the auth::create_user proc honor site-wide setting +for which external authority to create account in. Holding off on +this feature for now. + + TODO: New procs: auth::registration::get_required_attributes +auth::registration::get_optional_attributes + + +ad_proc -public auth::create_user { + {-username:required} + {-password:required} + {-first_names ""} + {-last_name ""} + {-email ""} + {-url ""} + {-secret_question ""} + {-secret_answer ""} +} { + @param authority_id The id of the authority to create the user in. Defaults to + the authority with lowest sort_order that has register_p set to true. +} { + set authorities_list [list] + + # Always register the user locally + lappend authorities_list [auth::authority::local] + # Default authority_id if none was provided + if { [empty_string_p $authority_id] } { + # Pick the first authority that can create users + set authority_id [db_string first_registering_authority { + select authority_id + from auth_authorities + where register_p = 't' + and sort_order = (select max(sort_order) + from auth_authorities + where register_p = 't' + ) + } -default ""] + if { [empty_string_p $authority_id] } { + error "No authority_id provided and could not find an authority that can create users" + } + lappend authorities_list $authority_id + } + # Register the user both with the local authority and the external one + db_transaction { + foreach authority_id $authorities_list { + auth::registration::Register \ + -authority_id $authority_id \ + -username $user_name \ + -password $password \ + -first_names $first_names \ + -last_name $last_name \ + -email $email \ + -url $url \ + -secret_question $secret_question \ + -secret_answer $secret_answer + } + } +} + + + + +ad_proc -private auth::registration::Register { + {-authority_id:required} + {-username:required} + {-password:required} + {-first_names ""} + {-last_name ""} + {-email ""} + {-url ""} + {-secret_question ""} + {-secret_answer ""} +} { + Invoke the Register service contract operation for the given authority. + @authority_id Id of the authority. Defaults to local authority. + @url Any URL (homepage) associated with the new user + @secret_question Question to ask on forgotten password + @secret_answer Answer to forgotten password question +} { + if { [empty_string_p $authority_id] } { + set authority_id [auth::authority::local] + } + # TODO: + # Implement parameters + return [acs_sc::invoke \ + -contract "auth_registration" \ + -impl [auth::authority::get_element -authority_id $authority_id -element "auth_impl_name"] \ + -operation Register \ + -call_args [list [list] \ + $username \ + $authority_id \ + $first_names \ + $last_name \ + $email \ + $url \ + $password \ + $secret_question \ + $secret_answer]] +} + + + + + + + + EXT-AUTH #4: Rewrite login & register pagse to use +APIs + + + + + EXT-AUTH +#4: Rewrite login & register pages to use APIs + + + Current Design + + Login is handled by acs-subsite/www/register/index.tcl and +user-login.tcl without use of an API. All the logic is in the +pages. + + Registration is handled by +acs-subsite/www/register/user-new.tcl. Again, all logic is in the +pages. + + + + + External Design + + We will have to rewrite the following pages: + + + User login: /register/index + + + + User registration: /register/user-add + + + + Admin: /acs-admin/users/user-add, which includes user-new +(which can then be included on other pages as well) + + + + Bulk: /acs-admin/users/user-batch-add + + + + Authentication of users is handled by the +auth::authenticate + proc and registration by the +auth::local::register proc. + + + The code to handle the login process in /register/index.tcl would +look like this: + +ad_form -name user_add -form { + {authority_id:integer(hidden)} + {username:text} + {email:text} + {password} +} -on_submit { + if { [ad_parameter UsernameIsEmail] == 't' } { + set email $username + } + array set auth_info [auth::authenticate \ + -authority_id $authority_id \ + -username $username \ + -password $password] + # Handle authentication problems + switch $auth_info(auth_status) { + ok { + # Continue below + } + bad_password { + } + no_account { + } + auth_error { + } + default { + } + } + # TODO: form builder validation of auth_status + # Handle account status + switch $auth_info(account_status) { + ok { + # Continue below + } + default { + } + } + # TODO: form builder validation of account_status + # We're logged in + ad_returnrediredt $return_url + ad_script_abort +} + + + The code to handle the registration process is in +user-new.tcl would look like this: + + +array set registratoin_info [auth::register \ + -authority_id $authority_id \ + -username $username \ + -password $password \ + -first_names $first_names \ + -last_name $last_name \ + -email $email \ + -url $url \ + -secret_question $secret_question \ + -secret_answer $secret_answer] +# Handle registration problems +switch $registratoin_info(reg_status) { + ok { + # Continue below + } + default { + } +} +# User is registered and logged in +ad_returnrediredt $return_url + + + + Page Flow + + User is redirected to /register/index.tcl if login is required + (i.e. auth::require is specified) and the login is taken care of by + user-login.tcl. If user not registered (i.e. + $auth_info(account_status) == "no_account" +), + user is redirected to user-register.tcl. + + + Error Handling + + + +<literal>auth::authenticate +</literal> + + Returns the array element auth_status, which can either be: + + + ok: login was successful, continue + + + + bad_password: Username is correct, password is wrong, +redirect to bad-password, display auth_message + + + + no_account: Username not found, redirect to user-new, +display auth_message + + + + auth_error: Authentication failed, display auth_message, +display auth_message + + + + failed_to_connect: The driver didn't return anything +meaningful, display error message + + + + + + Also account_status is returned, which can either be: + + + ok: login was successful, continue + + + + closed,permanently: account permanently closed, redirect +to account_closed, display error message + + + + No match: account_status doesn't match any of the above, +display error message + + + + + + +<literal>auth::register +</literal> + + Returns the array element reg_status, which can either be: + + + ok: Registration was successful, continue + + + + failed: Registration failed, display +reg_message + + + + No match: reg_status doesn't match any of the above, +display error message + + + + + + + Estimate + + Original estimate: + + Hours spent: + + Estimated hours remaining: + + Risk: + + + + + + + EXT-AUTH #5: +Password Management API + + + Current Design + + proc "ad_change_password" updates a user's password. + + Other password update/retrieval/reset functionality is +hard-coded in the pages. + + + + TODO + + + + Decide on generic "failed-to-connect" and "connected, but +auth server returned unknown error". + + + + Test cases + + + + + + + Execution Story + User login scenario: + + + The login page will have a link to assistance with +forgotten passwords ("Forgot your password?"). The URL of that link +is determined as follows: + + + auth_authority.forgotten_pwd_url + + + + Otherwise, if the authority's pwd mgmt driver's +CanRetrievePassword or CanRetrievePassword returns "Yes", we offer +a link to the OpenACS forgotten-password page, query vars +authority_id, maybe username. + + + + Otherwise, no forgotten password link. + + + + The OpenACS forgotten-password page may require the user +to fill in question/answer fields. These will be the OpenACS +question/answers only, we do not support question/answers from +external authentication servers. + + + + The email is reset or retrieved. The new password will be +emailed to the user, either by the driver or by the authentication +API (i.e., from one or the other side of the service +contract). + + + + User changes password scenario: + + + User visits "My Account" + + + + "My Account" will provide a link to changing the user's +password as follows: + + + If the authority has a 'change_pwd_url' defined, we'll +offer a "Change my password" link that goes there. + + + + Otherwise, if the authority driver's CanChangePassword +returns "Yes", we'll offer a "Change my password" link that goes to +the normal OpenACS change password page. + + + + Otherwise, no "Change my password" link. + + + + + The change password page will call the Password +Management API to change the password. + + + + + + + + + + + + + External Design + + +ad_proc -public auth::password::get_change_url { + {-user_id:required} +} { + Returns the URL to redirect to for changing passwords. If the + user's authority has a "change_pwd_url" set, it'll return that, + otherwise it'll return a link to /user/password-update under the + nearest subsite. + @param user_id The ID of the user whose password you want to change. + @return A URL that can be linked to for changing password. +} - +ad_proc -public auth::password::can_change_p { + {-user_id:required} +} { + Returns whether the given user change password. + This depends on the user's authority and the configuration of that authority. + + @param user_id The ID of the user whose password you want to change. + @return 1 if the user can change password, 0 otherwise. +} { + # Implementation note: + # Calls auth::password::CanChangePassword(authority_id) for the user's authority. +} +ad_proc -public auth::password::change { + {-user_id:required} + {-old_password:required} + {-new_password:required} +} { + Change the user's password. + @param user_id The ID of the user whose password you want to change. + @param old_password The current password of that user. This is required for security purposes. + + @param new_password The desired new password of the user. + @return An array list with the following entries: + <ul> + <li> password_status: "ok", "no_account", "old_password_bad", + "new_password_bad", "error" </li> + <li> password_message: A human-readable description of what + went wrong. </li> + </ul> +} { + # Implementation note + # Calls auth::password::ChangePassword(authority_id, username, old_password, new_password) for the user's authority. +} +ad_proc -public auth::password::get_forgotten_url { + {-authority_id:required} +} { + Returns the URL to redirect to for forgotten passwords. If the + user's authority has a "forgotten_pwd_url" set, it'll return that, + otherwise it'll return a link to /register/email-password under the + nearest subsite. + + @param authority_id The ID of the authority that the user is trying to log into. + @return A URL that can be linked to when the user has forgotten his/her password. +} - +ad_proc -public auth::password::can_retrieve_p { + {-authority_id:required} +} { + Returns whether the given authority can retrive forgotten passwords. + + @param authority_id The ID of the authority that the user is trying to log into. + @return 1 if the authority allows retrieving passwords, 0 otherwise. +} { + # Implementation note + # Calls auth::password::CanRetrievePassword(authority_id) for the user's authority. +} +ad_proc -public auth::password::retrieve { + {-user_id:required} +} { + Retrieve the user's password. + @param user_id The ID of the user whose password you want to retrieve. + @return An array list with the following entries: + <ul> + <li> password_status: "ok", "no_account", "not_implemented", + "error" </li> + <li> password_message: A human-readable description of what + went wrong, if password_status is not "ok". Not set if + password_status is "ok". </li> + <li> password: The retrieved password. </li> + </ul> +} { + # Implementation note + # Calls auth::password::RetrievePassword(authority_id, username) for the user's authority. +} +ad_proc -public auth::password::can_reset_p { + {-authority_id:required} +} { + Returns whether the given authority can reset forgotten passwords. + + @param authority_id The ID of the authority that the user is trying to log into. + @return 1 if the authority allows resetting passwords, 0 otherwise. +} { + # Implementation note + # Calls auth::password::CanResetPassword(authority_id) for the user's authority. +} +ad_proc -public auth::password::reset { + {-user_id:required} +} { + Reset the user's password, which means setting it to a new + randomly generated password and informing the user of that new + password. + @param user_id The ID of the user whose password you want to reset. + @return An array list with the following entries: + <ul> + <li> password_status: "ok", "no_account", "not_implemented", + "error" </li> + <li> password_message: A human-readable description of what + went wrong, if password_status is not "ok". Not set if + password_status is "ok". </li> + <li> password: The new, automatically generated password. If no + password is included in the return array, that means the new + password has already been sent to the user somehow. If it is + returned, it means that caller is responsible for informing the + user of his/her new password.</li> + </ul> +} { + # Implementation note + # Calls auth::password::ResetPassword(authority_id, username) for the user's authority. +} + + + + + Implementation +Details + + Create auth::password::CanChangePassword and friends wrappers +that take authority_id plus specific parameters for the service +contract call, finds the relevant service contract implementation, +and calls that. + + + + Error Handling + + Error codes are defined in the API stubs above. + + This API should check for bad return codes, drivers throwing +Tcl errors, and timeout, and replace with "failed-to-connect" +error. + + + + Related Stories + + #19 covers password recovery pages (and should also include +change password pages). Info about these pages belong in that +story. This section belongs there: + + Pages: + + + + acs-subsite/www/register/bad-password + + + + acs-subsite/www/register/email-password (-2, +-3) + + + + acs-subsite/www/user/password-update (-2) + + + + + #29, service contract for password mgmt, will have to change +as implied by the return values of this API. + + #9, local authentication driver, should take this section +into account: + + Parameters: + + + + acs-subsite.EmailForgottenPasswordP + + + + acs-subsite.RequireQuestionForPasswordReset + + + + acs-subsite.UseCustomQuestionForPasswordReset + + + + + + + Time Tracking + + + + Design: 2 hours + + + + Estimated hours remaining: 4 hours + + + + + + + + + + EXT-AUTH +#8: Automating Batch Synchronization + + + Execution Story + + + + User goes to Administration. + + + + Visits site-wide admin pages. + + + + Link says "Authentication Management". + + + + The link goes to a list of known domains. + + + + For each domain, a page: + + + + + + + Local Authority Administration ... other stuff ... +Enable/Disable nightly batch Status of last run B: history of +previous runs (30 days?) + + + + + + + + + Tradeoffs + + Which one or two of the following are emphasised in this +design? + + + + Performance: availability and efficiency + + + + Reliability and robustness + + + + + + External Design + + + Administration + + +auth::authority::enable_batch_sync -authority_id +integer +. This API toggles the value of +batch_sync_enabled_p column in ?some_table?. Returns 1 or 0. + + + + Scheduled proc + + auth::batch_sync_sweeper +. Runs every night. Runs + through all enabled and batch-enabled authorities and calls + auth::authority::batch_sync -authority_id +integer +. + + + + Internal +Design + + Administration + +ad_proc -public auth::authority::enable_batch_sync { + -authority_id +} { + db_dml toggle_enbaled_p { + update some_table + set batch_sync_enabled_p = 't' + where authority_id = :authority_id + } +} + + + Scheduled proc: + +ad_proc -public auth::batch_sync_sweeper {} { + db_foreach select_authorities { + select authority_id + from auth_authorities + where active_p = 't' + and batch_sync_enabled_p = 't' + } { + auth::authority::batch_sync -authority_id $authority_id + } +} +ad_proc -public auth::authority::batch_sync { + -authority_id +} { + set driver [auth::get_driver -authority $authority] + acs_sc::invoke $driver Synchronize -authority $authority +} + + + + Estimate + + Original estimate: + + Hours spent: + + Estimated hours remaining: + + Risk: + + + + + + + EXT-AUTH +#9: Create Authentication drivers for Local Authority + + + External Design + + auth::authenticate + calls + auth::authentication::Authenticate + which invokes + the service contract implementation for the authority. Returns: + + + auth_status: "ok", "bad_password", "no_account", +"auth_error", "failed_to_connect" + + + + auth_message: Message to the user. + + + + account_status: "ok", "closed" + + + + account_message: Message to the user. + + + + account_status and account_message are only set if +auth_status is "ok". + + + + + + Internal Design + + +ad_proc -private auth::authentication::Authenticate { + {-authority_id ""} + {-username:required} + {-password:required} +} { + Invoke the Authenticate service contract operation for the given authority. + @param username Username of the user. + @param password The password as the user entered it. + + @param authority_id The ID of the authority to ask to verify the user. Leave blank for local authority. +} { + if { [empty_string_p $authority_id] } { + set authority_id [auth::authority::local] + } + # TODO: + # Implement parameters + return [acs_sc::invoke \ + -contract "auth_authentication" \ + -impl [auth::authority::get_element -authority_id $authority_id -element "auth_impl_name"] \ + -operation Authenticate \ + -call_args [list $username $password [list]]] +} + + + + +ad_proc -private auth::local::authentication::Authenticate { + username + password + {parameters {}} +} { + Implements the Authenticate operation of the auth_authentication + service contract for the local account implementation. +} { + array set auth_info [list] + # TODO: username = email parameter ... + set username [string tolower $username] + + set authority_id [auth::authority::local] + set account_exists_p [db_0or1row select_user_info { + select user_id + from cc_users + where username = :username + and authority_id = :authority_id + }] + + if { !$account_exists_p } { + set auth_info(auth_status) "no_account" + return [array get auth_info] + } + + if { [ad_check_password $user_id $password] } { + set auth_info(auth_status) "ok" + } else { + if { [ad_parameter EmailForgottenPasswordP] == 't' } { + set auth_info(auth_status) "bad_password" + ... display link... + } else { + set auth_info(auth_status) "bad_password" + } + return [array get auth_info] + } + # We set 'external' account status to 'ok', because the + # local account status will be checked anyways + set auth_info(account_status) ok + return [array get auth_info] +} + + + + + Estimate + + Original estimate: + + Hours spent: + + Estimated hours remaining: + + Risk: + + + + + + + EXT-AUTH +#10: Create Account Creation drivers for Local Authority + + + External Design + + auth::registration::Register + returns: + + + creation_status + + + + creation_message + + + + element_messages + + + + account_status + + + + account_message + + + + + + Internal Design + + +ad_proc -private auth::registration::Register { + {-authority_id:required} + {-username:required} + {-password:required} + {-first_names ""} + {-last_name ""} + {-email ""} + {-url ""} + {-secret_question ""} + {-secret_answer ""} + {-parameters ""} +} { + Invoke the Register service contract operation for the given authority. + +} { + if { [empty_string_p $authority_id] } { + set authority_id [auth::authority::local] + } + + return [acs_sc::invoke \ + -contract "auth_registration" \ + -impl ??? \ + -operation Register \ + -call_args [list ???]] +} + + + +ad_proc -private auth::local::registration::Register { + parameters + username + authority_id + first_names + last_name + email + url + password + secret_question + secret_answer +} { + Implements the Register operation of the auth_register + service contract for the local account implementation. +} { + array set result { + creation_status "reg_error" + creation_message {} + element_messages {} + account_status "ok" + account_message {} + } + # TODO: email = username + # TODO: Add catch + set user_id [ad_user_new \ + $email \ + $first_names \ + $last_name \ + $password \ + $question \ + $answer \ + $url \ + $email_verified_p \ + $member_state \ + "" \ + $username \ + $authority_id] + if { !$user_id } { + set result(creation_status) "fail" + set result(creation_message) "We experienced an error while trying to register an account for you." + return [array get result] + } + + # Creation succeeded + set result(creation_status) "ok" + # TODO: validate data (see user-new-2.tcl) + # TODO: double-click protection + # Get whether they requre some sort of approval + if { [parameter::get -parameter RegistrationRequiresApprovalP -default 0] } { + set member_state "needs approval" + set result(account_status) "closed" + set result(account_message) [_ acs-subsite.lt_Your_registration_is_] + } else { + set member_state "approved" + } + set notification_address [parameter::get -parameter NewRegistrationEmailAddress -default [ad_system_owner]] + if { [parameter::get -parameter RegistrationRequiresEmailVerificationP -default 0] } { + set email_verified_p "f" + set result(account_status) "closed" + set result(account_message) "[_ acs-subsite.lt_Registration_informat_1][_ acs-subsite.lt_Please_read_and_follo]" + set row_id [db_string rowid_for_email { + select rowid from users where user_id = :user_id + }] + # Send email verification email to user + set confirmation_url "[ad_url]/register/email-confirm?[export_vars { row_id }]" + with_catch errmsg { + ns_sendmail \ + $email \ + $notification_address \ + "[_ acs-subsite.lt_Welcome_to_system_nam]" \ + "[_ acs-subsite.lt_To_confirm_your_regis]" + } { + ns_returnerror "500" "$errmsg" + ns_log Warning "Error sending email verification email to $email. Error: $errmsg" + } + } else { + set email_verified_p "t" + } + # Send password/confirmail email to user + if { [parameter::get -parameter RegistrationProvidesRandomPasswordP -default 0] || \ + [parameter::get -parameter EmailRegistrationConfirmationToUserP -default 0] } { + with_catch errmsg { + ns_sendmail \ + $email \ + $notification_address \ + "[_ acs-subsite.lt_Welcome_to_system_nam]" \ + "[_ acs-subsite.lt_Thank_you_for_visitin]" + } { + ns_returnerror "500" "$errmsg" + ns_log Warning "Error sending registration confirmation to $email. Error: $errmsg" + } + } + # Notify admin on new registration + if {[ad_parameter NotifyAdminOfNewRegistrationsP "security" 0]} { + with_catch errmsg { + ns_sendmail \ + $notification_address \ + $email \ + "[_ acs-subsite.lt_New_registration_at_s]" \ + "[_ acs-subsite.lt_first_names_last_name]" + } { + ns_returnerror "500" "$errmsg" + ns_log Warning "Error sending admin notification to $notification_address. Error: $errmsg" + } + } + + return [array get result] +} + + + + + Estimate + + Original estimate: + + Hours spent: + + Estimated hours remaining: + + Risk: + + + + + + + EXT AUTH #11: Create Auth driver for PAM + + + Execution Story + + When a user authenticates against an authority which uses +PAM, the PAM authentication driver will be invoked. + + + + Tradeoffs + + Reliability, robustness, portability. + + + + External Design + + Will implement the authentication service contract. + + Parameters: Don't know. + + + + Internal Design + + Mat Kovach will implement a thread-safe ns_pam AOLserver +module in C, which will provide a Tcl interface to PAM. + + We'll write the service contract implementation in Tcl as a +wrapper for the ns_pam calls. + + + + Test Cases + + Set up authentication against /etc/passwd on cph02 and test +that we can log in with our cph02 usernames and passwords. + + Using PAM driver without having the ns_pam C module +loaded. + + + + Error Handling + + Need to catch timeouts and communications errors and pass +them on to the caller. + + + + Estimate + + Mat says: 20 hours x USD 50/hr = USD 1,000. We need to sort +out with Mat what happens if it takes him longer (or +shorter). + + Adding the service contract wrappers for authentication and +password management: 8 hours. + + + + + + + EXT +AUTH #14: Create authentication driver for LDAP + + + Status + + ON HOLD awaiting info from clients on whether we can use PAM +instead of talking directly to the LDAP server, or how we should +implement this. + + + + Execution Story + + When a user authenticates against an authority which uses +LDAP, the LDAP authentication driver will be invoked. + + + + Tradeoffs + + Reliability, robustness, portability. + + + + External Design + + Will implement the authentication service contract. + + Parameters: Don't know. + + + + Internal Design + + We'd look at the extisting ns_ldap module, or we'd find +someone to implement a new thread-safe ns_ldap AOLserver module in +C, which will provide a Tcl interface to the parts of LDAP which we +need. + + We'll write the service contract implementation in Tcl as a +wrapper for the ns_ldap calls. + + + + Test Cases + + Set up an LDAP server, and configure authentication against +that. + + Using LDAP driver without having the ns_ldap C module +loaded. + + + + Error Handling + + Need to catch timeouts and communications errors and pass +them on to the caller. + + + + Estimate + + Implementing ns_ldap: unknown. + + Adding the service contract wrappers for authentication and +password management: 8 hours. + + + + + + + EXT-AUTH-16: +Authentication Service Contract (1 hour) + + by + +Peter +Marklund + + + Already done by Lars. We should ocument which messages can/should + be HTML and which should be plain text and in general try to + document expected values of return variables more clearly. + by + +Peter +Marklund + + + + + + EXT-AUTH-17: +Account Creation Service Contract (1 hour) + + by + +Peter +Marklund + + + Already done by Lars. Todo: improve documentation of return values. + by + +Peter +Marklund + + + + + + EXT-AUTH-18: +Authority Configuration Data Model (2 hours) + + by + +Peter +Marklund + + + The table auth_authorities already exists in acs-kernel for Oracle. + We need to create the table for PostgreSQL and provide upgrade + scripts. Rename column auth_p authenticate_p for readability and + clarity? Change column name active_p to enabled_p. + TODO: new column: help_contact_text with contact information +(phone, email, etc.) to be displayed as a last resort when people +are having problems with an authority. + + +create table auth_authorities ( + authority_id integer + constraint auth_authorities_pk + primary key, + short_name varchar2(255) + constraint auth_authority_short_name_un + unique, + pretty_name varchar2(4000), + active_p char(1) default 't' + constraint auth_authority_active_p_nn + not null + constraint auth_authority_active_p_ck + check (active_p in ('t','f')), + sort_order integer not null, + -- authentication + auth_impl_id integer + constraint auth_authority_auth_impl_fk + references acs_sc_impls(impl_id), + auth_p char(1) default 't' + constraint auth_authority_auth_p_ck + check (auth_p in ('t','f')) + constraint auth_authority_auth_p_nn + not null, + -- password management + pwd_impl_id integer + constraint auth_authority_pwd_impl_fk + references acs_sc_impls(impl_id), + -- Any username in this url must be on the syntax username={username} + -- and {username} will be replaced with the real username + forgotten_pwd_url varchar2(4000), + change_pwd_url varchar2(4000), + -- registration + register_impl_id integer + constraint auth_authority_reg_impl_fk + references acs_sc_impls(impl_id), + register_p char(1) default 't' + constraint auth_authority_register_p_ck + check (register_p in ('t','f')) + constraint auth_authority_register_p_nn + not null, + register_url varchar2(4000) +); + + + + + EXT-AUTH +#19: Rewrite password recovery to use API + + + Current Design + + Password recovery is currently handled by + /register/email-password.tcl, email-password-2.tcl and + email-password-3.tcl. All logic is placed in the pages. + + + Execution Story + + + + User is prompted for login, but types in a bad +password. + + + + The CanRetrievePassword service contract is called if +retrieving passwords is allowed user is redirected to +bad-password.tcl + + + + Here the RetrievePassword service contract is called, +which returns successful_p, password, message + + + + If password is empty and successful_p is true, the +authority server has send out the verification email. + + + + If successful and password is not empty, we email the +user and ask for verification. + + + + + + External Design + + +auth::password::CanResetPassword +.Input: +driver Output: 1 or 0 + + +auth::password::ResetPassword +.Input: +username, parameters.Output: successful_p: boolean, password (or +blank, if the server sends the user its new password directly), +message: To be relayed to the user. + + +auth::password::CanRetrievePassword +.Input: +driver Output: 1 or 0 + + +auth::password::RetrievePassword +.Input: +usernameOutput: retrievable_p: boolean, message: Instruct the user +what to do if not. + + +auth::password::CanChangePassword +Input: +driverOutput:True or false. + + +auth::password::ChangePassword +Input: +username, old_password, new_passwordOutput: new_password + + The logic of bad-password will be moved into /register/index as + part of ad_form, but the logic should look like something along the + lines of: + +set user_id [ad_conn] +set authority_id [auth::authority -user_id $user_id] +set driver [auth::get_driver -authority $authority] +set retrieve_password_p [auth::password::CanRetrievePassword -driver $driver] +set reset_password_p [auth::password::CanResetPassword -driver $driver] + + If $retrieve_password_p and $reset_password_p is true, this text + will be displayed: + "If you've forgotten your password, you can +ask this server to +reset your password and email a new randomly generated password to +you +." + + And email-password, should look something like this: + +# Fetch the username. What proc should we use? +set username [auth::username] +# Reset password +auth::password::ResetPassword -username $username +set subject "[_ acs-subsite.lt_Your_forgotten_passwo]" +# SIMON: how does the password get inserted here? +# Should make use of auth::password::RetrievePassword +set body "[_ acs-subsite.lt_Please_follow_the_fol]" +# Send email +if [catch {ns_sendmail $email $system_owner $subject $body} errmsg] { + ad_return_error \ + "[_ acs-subsite.Error_sending_mail]" \ + "[_ acs-subsite.lt_Now_were_really_in_tr] +<blockquote> + <pre> + $errmsg + </pre> +</blockquote> +[_ acs-subsite.lt_when_trying_to_send_y] +<blockquote> + <pre> +[_ acs-subsite.Subject] $subject +$body + </pre> +</blockquote> +" + return +} + + We'll want to add a check for CanChangePassword in /pvt/home. + + + Estimate + + Original estimate: + + Hours spent: + + Estimated hours remaining: + + Risk: + + + + + + + EXT AUTH +#20: Login pages over HTTPS + + + Current Design + + Current login pages are over HTTP. Just bashing them to be +HTTPS has these issues: + + + + Browsers will not send cookies over HTTP that were +received over HTTPS. + + + + If images on the login page are over HTTP and not HTTPS, +browsers will warn that you're seeing unsecure items as part of a +secure web page, which is annoying and unprofessional. + + + + Browsers may also give a warning when redirecting back to +the normal pages not over HTTPS. + + + + + + Execution Story + + Beginning with a human being using a computer, describe how +the feature is triggered and what it does. As this story becomes +more detailed, move pieces to appropriate parts of the +document. + + + + Tradeoffs + + Security, Reliability and robustness. + + + + External Design + + Parameters: + + + + acs-kernel.RegisterRestrictToSSLFilters: If set to 0, we +don't restrict any URLs to HTTPs. + + + + acs-subsite.RestrictToSSL: A Tcl list of URL patterns +under the given subsite to restrict to SSL, e.g. "admin/* +register/*". Currently defaults to "admin/*". Only takes effect if +SSL is installed, and acs-kernel.RegisterRestrictToSSLFilters is +set to 1. + + + + + + To do + + + + Install SSL on a development server and integration +server. + + + + Try setting RestrictToSSL to "admin/* register/*" and +test what happens. + + + + Identify and fix issues that show up. + + + + + + Time Estimate + + Original estimate: 8 hours. + + Hours spent: 0.25 + + Estimated hours remaining: + + Risk: + + + + + + + EXT-AUTH +#24: Email on password change + + + Execution story + + User: + + + User visits /pvt/home + + + + Clicks "Change my Password" + + + + Enters password + + + + User is redirected to /pvt/home + + + + Email goes out + + + + Admin: + + + () + + + + + Internal Design + + We'll first want to check whether changing the password is allowed: + + +set user_id [ad_conn user_id] +set authority [auth::authority -user_id $user_id] +set driver [auth::get_driver -authority $authority] +set change_password_p [auth::password::CanChangePassword -driver $driver] + + If $change_password_p is true, we'll display the "Change my + password" link on /pvt/home. update-password would look something + like this: + +if {![db_0or1row select_email {}]} { + db_release_unused_handles + ad_return_error "[_ acs-subsite.lt_Couldnt_find_user_use]" "[_ acs-subsite.lt_Couldnt_find_user_use_1]" + return +} +set system_owner [ad_system_owner] +set subject "some other i18n key msg" +set body "some other i18n key msg" +# Send email +if [catch {ns_sendmail $email $system_owner $subject $body} errmsg] { + ad_return_error \ + "[_ acs-subsite.Error_sending_mail]" \ + "[_ acs-subsite.lt_Now_were_really_in_tr] +<blockquote> + <pre> + $errmsg + </pre> +</blockquote> +[_ acs-subsite.lt_when_trying_to_send_y] +<blockquote> + <pre> +[_ acs-subsite.Subject] $subject +$body + </pre> +</blockquote> +" + return +} + + + + + Estimate + + Original estimate: + + Hours spent: + + Estimated hours remaining: + + Risk: + + + + + + + EXT AUTH #25: +Password Policy + + + Current Design + + This has already been implemented on oacs-4-6 branch. + + + + Expiration of passwords: Password must be changed after a +certain number of days. No password history, though. + + + + Approval expiration: If a user is approved but doesn't +log on before a certain number of days, his approval will +expire. + + + + + + To do + + + + Merge changes from oacs-4-6 onto HEAD. (Commits made +around June 6). + + + + Sort out the upgrade script sequence. + + + + + + Time Estimate + + Original estimate: 6 hours. + + Hours spent: + + Estimated hours remaining: + + Risk: + + + + + + + Who's online list + + + Execution Story + + A page showing who has requested a page during the last 5 minutes. + Could be integrated with a chat or instant messaging service. + + + Internal Design + + We keep a record of which authenticated users have requested +pags on the site in the last x minutes (typically about 5), and +thus are considered to be currently online. We've already made the +changes necessary to security-procs.tcl to do this on an earlier +project, but haven't quite finished the work and put it back into +the tree. + + Lars? + + + Estimate + + Original estimate: + + Hours spent: + + Estimated hours remaining: + + Risk: + + + + + + + EXT +AUTH #28: Create Service Contract for Batch Sync + + + Status + + NOTE: We'll need to keep a pretty close transaction log of +any action taken during batch sync, and also offer a mechanism for +storing transactions that couldn't be completed for some reason, +e.g. email address already taken, etc., email the admin, offer a +list of transactions that failed for manual resolution by an +admin. + + + + Design To +Do/Notes + + Performance criteria: Nightly batch run should be able to +complete within 3 hours with 10,000 users. + + We'll want to design an API for an incremental or snapshot +run to call, which takes care of all the updating, logging, error +handling, emailing admins, etc. + + We need examples of how the communication would be done from +our clients. + + We might need a source/ID column in the users table to +identify where they're imported from for doing updates, +particularly if importing from multiple sources (or when some users +are local.) + + + + Current Design + + None. + + + + Execution Story + + This is executed in background thread, typically +nightly. + + There's also a link from the authority administration page to +run the batch synchronization immediately. + + The process runs like this: + + + + OpenACS retrieves a document from the enterprise server +containing user information. Example mechanisms: + + + A file is delivered to an agreed-on location in the file +system at an agreed-on point in time. + + + + OpenACS does a normal HTTP request to a remote server, +which returns the document. + + + + OpenACS does a SOAP (Web Service) request to a remote +server, which returns the document. + + + + OpenACS retrieves a file from an SMB server or an FTP +server. + + + + + + + The document will contain either the complete user list +(IMS: "snapshot"), or an incremental user list (IMS: "Event Driven" +-- contains only adds, edits, updates). You could for example do a +complete transfer once a month, and incrementals every night. The +invocation should decide which type is returned. + + + + The document will be in an agreed-on format, e.g. an XML +format based on the IMS Enterprise +Specification (example +XML document + + + + + Tradeoffs + + The design should favor interoperability, reliability and +robustness. + + + + External Design + + NOTE: Do we really want to do this as service contracts? We +might be better off getting one implementation running, and only do +the service contract when we're certain what it would look +like. + + TODO: Look at how Blackboard and other systems implements +this, specifically how do they get the data out of the other +system. Which enterprise servers already support the IMS Enterprise +specification? + + TODO: Find out if Greenpeace needs an different exchange +format from IMS Enterprise, given that they're not in the +University business. + + Service contract for retrieving the document: + + + GetDocument ( + type: 0 = snapshot + 1 = incremental + since: date that you want the incremental update since? + parameters: values of implementation-specific parameters + ): document as string + Performs the request necessary to get a document containing + enterprise information. + GetParameters ( + ): list of parameters specific to this implementation. + Parameters would typically be the URL to make a request to. + + + Service contract for processing the document: + + + ProcessDocument ( + type: 0 = snapshot + 1 = incremental + since: date that you want the incremental update since? + document: the document containing either incremental or snapshot of enterprise data. + parameters: values of implementation-specific parameters + ): document as string + Processes the document and updates the OpenACS users and other tables appropriately. + GetParameters ( + ): list of parameters specific to this implementation. + Not sure what parameters would be. + + + It looks like we'll use the IMS standard for formatting of +the doucment, but not so + + + + Standards + + + + +Consolidation +before the leap; IMS Enterprise 1.1 +: This sect2 says that +IMS Enterprise 1.1 (current version) does not address the +communication model, which is critically missing for real seamless +interoperability. IMS Enterprise 2.0 will address this, but +Blackboard, who's influential in the IMS committee, is adopting +OKI's programming interrfaces for this. + + + + +IMS +and OKI, the wire and the socket + + + + + + + Page Flow + + For features with UI, a map of all pages, showing which pages +can call others, which are includeable, etc. For each page, show +all UI elements on the page (text blocks, forms, form controls). +Include administration functionality. + + + + Internal Design + + Describe key algorithms, including pseudo-code. + + + Data Model + + Describe data model changes. + + + Error Handling + + What error codes or error conditions could result? How are they + handled? + + + Upgrade + + Describe in pseudo-code how upgrade will be implemented. + + + + + EXT-AUTH-29: +Password Management Service Contract (1 hour) + + by + +Peter +Marklund + + + Already done by Lars. Todo: improve documentation of return values. + by + +Peter +Marklund + + + + + + EXT-AUTH +#30: Create Authority Management API + + + External Design + + We'll want an API that lets us + + + add authorities: auth::authority::new + + + + delete authorities: auth::authority::delete + + + + and edit authorities: auth::authority::edit + + + + authorities. + Here goes: + + + + Internal Design + + +ad_proc -public auth::authority::new { + {-authority_id:required} + {-short_name:required} + {-pretty_name:required} + {-sort_order:required} + {-auth_impl_id:required} + {-auth_p:required} + {-pwd_impl_id:required} + {-forgotten_pwd_url:required} + {-change_pwd_url:required} + {-register_impl_id:required} + {-register_p:required} + {-register_url:required} +} { + db_dml new_authority { + insert into auth_authorities ( + authority_id, + short_name, + pretty_name, + active_p, + sort_order, + auth_impl_id, + auth_p, + pwd_impl_id, + forgotten_pwd_url, + change_pwd_url, + register_impl_id, + register_p, + register_url + ) values ( + :authority_id, + :short_name, + :pretty_name, + 1, + :sort_order, + :auth_impl_id, + :auth_p, + :pwd_impl_id, + :forgotten_pwd_url, + :change_pwd_url, + :register_impl_id, + :register_p, + :register_url + ) + } +} +ad_proc -public auth::authority::delete { + {-authority_id:requied} +} { + db_exec delete_authority { + delete from auth_authorities + where authority_id = :authority_id + } +} +ad_proc -public auth::authority::edit { + {-authority_id:required} + {-short_name:required} + {-pretty_name:required} + {-active_p:required} + {-sort_order:required} + {-auth_impl_id:required} + {-auth_p:required} + {-pwd_impl_id:required} + {-forgotten_pwd_url:required} + {-change_pwd_url:required} + {-register_impl_id:required} + {-register_p:required} + {-register_url:required} +} { + db_exec edit_authority { + update auth_authorities + set short_name = :short_name, + pretty_name = :pretty_name, + active_p = :active_p, + sort_order = :sort_order, + auth_impl_id = :auth_impl_id, + auth_p = :auth_p, + pwd_impl_id = :pwd_impl_id, + forgotten_pwd_url = :forgotten_pwd_url, + change_pwd_url = :change_pwd_url, + register_impl_id = :register_impl_id, + register_p = :register_p, + register_url = :register_url + where authority_id = :authority_id + } +} + + + + + Estimate + + Original estimate: + + Hours spent: + + Estimated hours remaining: + + Risk: + + + + + + + EXT-AUTH-31: +External Authentication Datamodel (2 hours) + + by + +Peter +Marklund + + + The columns authority_id and username have been added to the users + table for Oracle. We need to add them for PostgreSQL and provide + upgrade scripts. + +create table users ( + user_id not null + constraint users_user_id_fk + references persons (person_id) + constraint users_pk primary key, + authority_id integer + constraint users_auth_authorities_fk + references auth_authorities(authority_id), + username varchar2(100) + constraint users_username_nn + not null, + screen_name varchar2(100) + constraint users_screen_name_un + unique, + priv_name integer default 0 not null, + priv_email integer default 5 not null, + email_verified_p char(1) default 't' + constraint users_email_verified_p_ck + check (email_verified_p in ('t', 'f')), + email_bouncing_p char(1) default 'f' not null + constraint users_email_bouncing_p_ck + check (email_bouncing_p in ('t','f')), + no_alerts_until date, + last_visit date, + second_to_last_visit date, + n_sessions integer default 1 not null, + -- local authentication information + password char(40), + salt char(40), + password_question varchar2(1000), + password_answer varchar2(1000), + -- table constraints + constraint users_authority_username_un + unique (authority_id, username) +); + + + + by + +Peter +Marklund + + + + + + EXT +AUTH #32: Service Contract Implementation Parameters + + + Execution Story + + When the administrator configures an authority to use a +specific service contract implementation, e.g. PAM, that +implementation can say which parameters it supports, e.g. Host, +Port, Root node, etc. + + The administrator will specify values for these parameters as +part of configuring the authority. + + These parameter values will be passed along to the service +contract implementation when its methods get called. + + + + Tradeoffs + + Flexibility, usability. + + + + External Design + + We're considering whether to implement a very simple solution +a' la current package parameters, or a general configuration +solution as outlined in the +Configurator +Spec +. + + + + Data Model + + Simple solution: A table with key/value pairs attached to the +auth_authorities table. + + + + Time Estimate + + Original estimate, simple solution: 8 hours + + Original estimate, complex solution: 50 hours + + Hours spent: + + Estimated hours remaining: + + Risk: + + + + + + + OACS-COL-1: +Automate install and self-test (5 hours) + + by + +Peter +Marklund + + + I need to make sure the install scripts work. I then need to: + + + Schedule nightly recreation + + + + Make sure the install script invokes +acs-automated-testing tests and checks results + + + + Make sure emails go out to appropriate people + + + + by + +Peter +Marklund + + + + + + EXT AUTH #x: Title +of feature + + + Current Design + + Describe how any functionality to be replaced works. + + + + Execution Story + + Beginning with a human being using a computer, describe how +the feature is triggered and what it does. As this story becomes +more detailed, move pieces to appropriate parts of the +document. + + + + Tradeoffs + + Which one or two of the following are emphasised in this +design? + + + + Performance: availability and efficiency + + + + Flexibility + + + + Interoperability + + + + Reliability and robustness + + + + Usability + + + + Maintainability + + + + Portability + + + + Reusability + + + + Testability + + + + + + External Design + + For a feature with a public API, write the stub for each +procedure, showing all parameters, a preliminary description of +instructions, and expected return. + + For a feature without a public API, describe how the feature +is invoked. + + Include admin-configurable parameters. + + + + Page Flow + + For features with UI, a map of all pages, showing which pages +can call others, which are includeable, etc. For each page, show +all UI elements on the page (text blocks, forms, form controls). +Include administration functionality. + + + + Internal Design + + Describe key algorithms, including pseudo-code. + + + + Data Model + + Describe data model changes. + + + + Error Handling + + What error codes or error conditions could result? How are +they handled? + + + + Upgrade + + Describe in pseudo-code how upgrade will be +implemented. + + + + Time Estimate + + Original estimate: + + Hours spent: + + Estimated hours remaining: + + Risk: + + + + + \ No newline at end of file