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. }
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
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. }
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]] }
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.
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.
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
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
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
proc "ad_change_password" updates a user's password.
Other password update/retrieval/reset functionality is hard-coded in the pages.
Decide on generic "failed-to-connect" and "connected, but auth server returned unknown error".
Test cases
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.
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. }
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 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.
#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
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?)
Which one or two of the following are emphasised in this design?
Performance: availability and efficiency
Reliability and robustness
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.
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 } }
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 }
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".
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] }
creation_status
creation_message
element_messages
account_status
account_message
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] }
When a user authenticates against an authority which uses PAM, the PAM authentication driver will be invoked.
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.
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.
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.
When a user authenticates against an authority which uses LDAP, the LDAP authentication driver will be invoked.
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.
Set up an LDAP server, and configure authentication against that.
Using LDAP driver without having the ns_ldap C module loaded.
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) );
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.
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.
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.
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.
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.
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.
User visits /pvt/home
Clicks "Change my Password"
Enters password
User is redirected to /pvt/home
Email goes out
()
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 }
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.
Merge changes from oacs-4-6 onto HEAD. (Commits made around June 6).
Sort out the upgrade script sequence.
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?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.
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.)
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
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
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.
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.
add authorities: auth::authority::new
delete authorities: auth::authority::delete
and edit authorities: auth::authority::edit
Here goes:
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 } }
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
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.
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 .
Schedule nightly recreation
Make sure the install script invokes acs-automated-testing tests and checks results
Make sure emails go out to appropriate people
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.
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
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.
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.