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

auth::authenticate
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

auth::register
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:

  1. The login page will have a link to assistance with forgotten passwords ("Forgot your password?"). The URL of that link is determined as follows:

    1. auth_authority.forgotten_pwd_url

    2. 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.

    3. Otherwise, no forgotten password link.

    4. 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.

    5. 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).

    6. User changes password scenario:

      1. User visits "My Account"

      2. "My Account" will provide a link to changing the user's password as follows:

        1. If the authority has a 'change_pwd_url' defined, we'll offer a "Change my password" link that goes there.

        2. 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.

        3. Otherwise, no "Change my password" link.

        4. 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

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

EXT-AUTH #8: Automating Batch Synchronization

Execution Story

  1. User goes to Administration.

  2. Visits site-wide admin pages.

  3. Link says "Authentication Management".

  4. The link goes to a list of known domains.

  5. 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

Peter Marklund

EXT-AUTH-17: Account Creation Service Contract (1 hour)

by

Peter Marklund

Peter Marklund

EXT-AUTH-18: Authority Configuration Data Model (2 hours)

by

Peter Marklund

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

  1. Install SSL on a development server and integration server.

  2. Try setting RestrictToSSL to "admin/* register/*" and test what happens.

  3. 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

  1. Merge changes from oacs-4-6 onto HEAD. (Commits made around June 6).

  2. 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:

  1. 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.

  2. 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.

  3. 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

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:

View comments on this page at openacs.org