%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 password 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 { $authority_id eq "" } { # 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 { $authority_id eq "" } { 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 { $authority_id eq "" } { 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 pages 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 { [parameter::get -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_returnredirect $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_returnredirect $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) eq "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 retrieve 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 { $authority_id eq "" } { 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 { [parameter::get -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 { $authority_id eq "" } { 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 [export_vars -base [ad_url]/register/email-confirm { row_id }] with_catch errmsg { acs_mail_lite::send \ -to_addr $email \ -from_addr $notification_address \ -subject "[_ acs-subsite.lt_Welcome_to_system_nam]" \ -body "[_ 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 1] } { with_catch errmsg { acs_mail_lite::send \ -to_addr $email \ -from_addr $notification_address \ -subject "[_ acs-subsite.lt_Welcome_to_system_nam]" \ -body "[_ 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 {[parameter::get -parameter NotifyAdminOfNewRegistrationsP -default 0]} { with_catch errmsg { acs_mail_lite::send \ -to_addr $notification_address \ -from_addr $email \ -subject "[_ acs-subsite.lt_New_registration_at_s]" \ -body "[_ 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 {acs_mail_lite::send -to_addr $email -from_addr $system_owner -subject $subject -body $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 {acs_mail_lite::send -to_addr $email -from_addr $system_owner -subject $subject -body $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 pages 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 a 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:required} } { 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: