authentication-procs.tcl
Does not contain a contract.
- Location:
- /packages/acs-authentication/tcl/authentication-procs.tcl
Related Files
- packages/acs-authentication/tcl/authentication-procs.xql
- packages/acs-authentication/tcl/authentication-procs.tcl
- packages/acs-authentication/tcl/authentication-procs-postgresql.xql
- packages/acs-authentication/tcl/authentication-procs-oracle.xql
[ hide source ] | [ make this the default ]
File Contents
ad_library { Tcl API for authentication, account management, and account registration. @author Lars Pind (lars@collaobraid.biz) @creation-date 2003-05-13 @cvs-id $Id: authentication-procs.tcl,v 1.114.2.32 2024/08/28 10:06:11 gustafn Exp $ } namespace eval auth {} namespace eval auth::authentication {} namespace eval auth::registration {} namespace eval auth::user_info {} namespace eval auth::login_attempts {} ##### # # auth namespace public procs # ##### d_proc -public auth::require_login { {-level ok} {-account_status ok} } { 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 a returnredirect and abort the current page. @see ad_script_abort } { set user_id [auth::get_user_id \ -level $level \ -account_status $account_status] if { $user_id != 0 } { # # The user is in fact logged in, return her user_id. # return $user_id } set message "" if {[ad_conn auth_level] eq "expired"} { # # The login has expired. # set message [_ acs-subsite.lt_Your_login_has_expire] # # If the login was issued from an external_registry, use this # as well for refreshing. # set external_registry [sec_login_get_external_registry] } else { set external_registry "" } # # The -return switch causes the URL to return to the current page. # set return_url [ad_get_login_url -return -external_registry $external_registry] # Long URLs (slightly above 4000 bytes) can kill aolserver-4.0.10, causing # a restart. They lead to empty Browser-windows with AOLserver 4.5 (but no # crash so far). May browsers have length limitations for URLs. E.g. # 2083 is the documented maximal length of MSIE. # # Long URLs will be generated e.g. when # a) a user edits a form with text entries # b) before submitting the form logs out of OpenACS from a different browser window # c) submits the form. # When submitting needs authentication, OpenACS generates the redirect to # /register with the form-data coded into the URL to continue there..... # set user_agent [string tolower [ns_set iget [ns_conn headers] User-Agent]] # ns_log notice "URL have url, len=[string length $return_url] $user_agent" if {[string length $return_url] > 2083} { set message "[_ acs-authentication.Login_expired_url_too_long]" append message "[_ acs-authentication.Editing_form_text]" set return_url [ad_get_login_url] } # If the login was issued from an external_registry, # we have to allow the redirect to a complete url ad_returnredirect -allow_complete_url=[expr {$external_registry ne ""}] -message $message -- $return_url ad_script_abort } ad_proc -public auth::refresh_login {} { If there currently is a user associated with this session, but the user's authentication is expired, redirect the user to refresh his/her login. This allows for users to not be logged in, but if the user is logged in, then we require that the authentication is not expired. @return user_id of user, if the user is logged in and auth_status is not expired, or 0 if the user is not logged in. If user's auth_status is expired, this proc will issue a returnredirect and abort the current page. @see ad_script_abort } { if { [ad_conn auth_level] ne "expired" } { return [ad_conn user_id] } # # The -return switch causes the URL to return to the current page # ad_returnredirect [ad_get_login_url -return \ -external_registry [sec_login_get_external_registry]] ad_script_abort } ad_proc -public auth::self_registration {} { Check AllowSelfRegister parameter and set user message if self registration not allowed. } { if { [string is false [parameter::get_from_package_key \ -package_key acs-authentication \ -parameter AllowSelfRegister]] } { if {[ad_conn session_id] ne ""} { util_user_message -message "Self registration is not allowed" } else { ns_log notice "auth::self_registration: cannot set user_message 'Self registration is not allowed'" } auth::require_login } } d_proc -public auth::get_user_id { {-level ok} {-account_status ok} } { Get the current user_id with at least the level of security specified. If no user is logged in, or the user is not logged in at a sufficiently high security level, return 0. @return user_id of user, if the user is logged in, 0 otherwise. @see ad_script_abort } { set untrusted_user_id [ad_conn untrusted_user_id] # Do we have any user_id at all? if { $untrusted_user_id == 0 } { return 0 } # Check account status if { $account_status eq "ok" && [ad_conn account_status] ne "ok" } { return 0 } array set levelv { none 0 expired 1 ok 2 secure 3 } # If HTTPS isn't available, we can't require secure authentication if { ![security::https_available_p] } { set levelv(secure) 2 } # Check if auth_level is sufficiently high if { $levelv([ad_conn auth_level]) < $levelv($level) } { return 0 } return $untrusted_user_id } ad_proc -public auth::UseEmailForLoginP {} { Do we use email address for login? } { return [parameter::get -boolean -parameter UseEmailForLoginP -package_id [ad_acs_kernel_id] -default 1] } d_proc -public auth::authenticate { {-return_url ""} {-authority_id ""} {-username ""} {-email ""} {-password:required} {-persistent:boolean} {-no_cookie:boolean} {-first_names ""} {-last_name ""} {-host_node_id ""} } { Try to authenticate and login the user forever by validating the username/password combination, and return authentication and account status codes. @param return_url If specified, this can be included in account status messages. @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 email User's email address. You must supply either username or email. @param password The password as the user entered it. @param persistent Set this if you want a permanent login cookie @param no_cookie Set this if you don't want to issue a login cookie @param host_node_id Optional parameter used to determine the cookie domain from the host_node_map @return Array list with the following entries: <ul> <li> auth_status: Whether authentication succeeded. ok, no_account, bad_password, auth_error, failed_to_connect <li> auth_message: Human-readable message about what went wrong. Guaranteed to be set if auth_status is not ok. Should be ignored if auth_status is ok. May contain HTML. <li> account_status: Account status from authentication server. ok, closed. <li> account_url: A URL to redirect the user to. Could e.g. ask the user to update his password. <li> account_message: Human-readable message about account status. Guaranteed to be set if auth_status is not ok and account_url is empty. If nonempty, must be relayed to the user regardless of account_status. May contain HTML. This proc is responsible for concatenating any remote and/or local account messages into one single message which can be displayed to the user. <li> user_id: Set to local user_id if auth_status is ok. </ul> } { # Login Brute Force Prevention set login_attempt_key "[ad_conn peeraddr]-[ad_conn subsite_id]" if { [::auth::login_attempts::threshold_reached_p -login_attempt_key $login_attempt_key] } { set auth_message [_ acs-authentication.Too_many_failed_login_attempts] return [list auth_status "failed_to_connect" \ auth_message $auth_message \ account_status "closed" \ account_message "[_ acs-subsite.Auth_internal_error]"] } # record login attempt ::auth::login_attempts::record -login_attempt_key $login_attempt_key if { $username eq "" } { if { $email eq "" } { set result(auth_status) "auth_error" if { [auth::UseEmailForLoginP] } { set result(auth_message) [_ acs-subsite.Email_required] } else { set result(auth_message) [_ acs-subsite.Username_required] } return [array get result] } set user_id [party::get_by_email -email $email] if { $user_id eq "" || ![acs_user::registered_user_p -user_id $user_id] } { set result(auth_status) "no_account" set result(auth_message) [_ acs-subsite.Unknown_email] return [array get result] } set user [acs_user::get_user_info -user_id $user_id] set authority_id [dict get $user authority_id] set username [dict get $user username] } else { # Default to local authority if { $authority_id eq "" } { set authority_id [auth::authority::local] } } # # initialize result with authentication and account keys # array set result {auth_status "n/a" auth_message "" account_status "n/a" account_message ""} ad_try { array set result [auth::authentication::authenticate \ -username $username \ -authority_id $authority_id \ -password $password] } on error {errorMsg} { set result(auth_status) failed_to_connect set result(auth_message) $errorMsg ad_log Error "auth::authenticate: error '$errorMsg' invoking authentication driver for authority_id = $authority_id: $::errorInfo" } # Returns: # result(auth_status) # result(auth_message) # result(account_status) # result(account_message) # Verify result/auth_message return codes switch $result(auth_status) { ok { # reset/unset failed login attempts counter after a successful authentication ::auth::login_attempts::reset -login_attempt_key $login_attempt_key # Continue below } no_account - bad_password - auth_error - failed_to_connect { if { $result(auth_message) eq "" } { array set default_auth_message { no_account {Unknown username} bad_password {Bad password} auth_error {Invalid username/password} failed_to_connect {Error communicating with authentication server} } set result(auth_message) $default_auth_message($result(auth_status)) } return [array get result] } default { ns_log Error "auth::authenticate: Illegal auth_status code '$result(auth_status)' returned from authentication driver for authority_id $authority_id ([auth::authority::get_element -authority_id $authority_id -element pretty_name])" set result(auth_status) "failed_to_connect" set result(auth_message) [_ acs-subsite.Auth_internal_error] return [array get result] } } # Verify remote account_info/account_message return codes switch $result(account_status) { ok { # Continue below } closed { if { $result(account_message) eq "" } { set result(account_message) [_ acs-subsite.Account_not_avail_now] } } default { ns_log Error "auth::authenticate: Illegal account_status code '$result(account_status)' returned from authentication driver for authority_id $authority_id ([auth::authority::get_element -authority_id $authority_id -element pretty_name])" set result(account_status) "closed" set result(account_message) [_ acs-subsite.Auth_internal_error] } } # # Save the remote account information for later # set remote_account_status $result(account_status) set remote_account_message $result(account_message) # # Clear out remote account_status and account_message # and initialize it with values that we can relay on later. # array set result {account_url "" account_status "" account_message "" user_id ""} # Map to row in local users table array set result [auth::get_local_account \ -return_url $return_url \ -username $username \ -authority_id $authority_id \ -email $email \ -first_names $first_names \ -last_name $last_name] # Returns: # result(account_status) # result(account_message) # result(account_url) # result(user_id) # Verify local account_info/account_message return codes switch $result(account_status) { ok { # Continue below } closed { if { $result(account_message) eq "" } { set result(account_message) [_ acs-subsite.Account_not_avail_now] } } default { ns_log Error "auth::authenticate: Illegal account_status code '$result(account_status)' returned from auth::get_local_account for authority_id $authority_id ([auth::authority::get_element -authority_id $authority_id -element pretty_name])" set result(account_status) "closed" set result(account_message) [_ acs-subsite.Auth_internal_error] } } # If the remote account was closed, the whole account is closed, regardless of local account status if {$remote_account_status eq "closed"} { set result(account_status) closed } if { $remote_account_message ne "" } { if { $result(account_message) ne "" } { # Concatenate local and remote account messages set local_account_message [auth::authority::get_element \ -authority_id $authority_id \ -element pretty_name] set result(account_message) [subst { <p>$local_account_message: $remote_account_message</p> <p>[ad_system_name]: $result(account_message)</p> }] } else { set result(account_message) $remote_account_message } } # # Issue login cookie if login was successful # and everything is ok with the account. # if { $result(auth_status) eq "ok" && !$no_cookie_p && $result(user_id) ne "" && $result(account_status) eq "ok" } { if {$host_node_id ne ""} { set cookie_domain [db_string get_mapped_host { select host from host_node_map where node_id = :host_node_id } -default ""] if {$cookie_domain eq ""} { ns_log warning "auth::authenticate: host_node_id $host_node_id was provided but is apparently not mapped" } } else { set cookie_domain "" } ns_log notice "auth::authenticate receives host_node_id $host_node_id domain <$cookie_domain>" ad_user_login \ -account_status $result(account_status) \ -cookie_domain $cookie_domain \ -forever=$persistent_p \ $result(user_id) } return [array get result] } d_proc -deprecated auth::issue_login { {-user_id:required} {-account_status "ok"} {-cookie_domain ""} {-persistent:boolean} } { Issue the login cookie. DEPRECATED: just a trivial wrapper of ad_user_login @see ad_user_login } { ad_user_login \ -account_status $account_status \ -cookie_domain $cookie_domain \ -forever=$persistent_p \ $user_id } d_proc -public auth::get_register_authority { } { Get the ID of the authority in which accounts get created. Is based on the RegisterAuthority parameter but will default to the local authority if that parameter has an invalid value. } { set parameter_value [parameter::get_from_package_key -parameter RegisterAuthority -package_key "acs-authentication"] # Catch the case where somebody has set the parameter to some non-existent authority if {$parameter_value in [auth::authority::get_short_names]} { # The authority exists set authority_id [auth::authority::get_id -short_name $parameter_value] # Check that the authority has a register implementation auth::authority::get -authority_id $authority_id -array authority if { $authority(register_impl_id) eq "" } { ns_log Error "auth::get_register_authority: parameter value for RegisterAuthority is an authority without registration driver, defaulting to local authority" set authority_id [auth::authority::local] } } else { # The authority doesn't exist - use the local authority ns_log Error "auth::get_register_authority: parameter RegisterAuthority has the invalid value $parameter_value. Defaulting to local authority" set authority_id [auth::authority::local] } return $authority_id } d_proc -public auth::create_user { {-verify_password_confirm:boolean} {-user_id ""} {-username ""} {-email:required} {-first_names ""} {-last_name ""} {-screen_name ""} {-password ""} {-password_confirm ""} {-url ""} {-secret_question ""} {-secret_answer ""} {-email_verified_p ""} {-nologin:boolean} {-authority_id ""} } { Create a user, and return creation status and account status. @param email_verified_p Whether the local account considers the email to be verified or not. @param verify_password_confirm Set this flag if you want the proc to verify that password and password_confirm match for you. @param authority_id create user in the specified authority. Defaults to the register authority of the subsite. @return Array list containing the following entries: <ul> <li> creation_status: ok, data_error, reg_error, failed_to_connect. Says whether user creation succeeded. <li> creation_message: Information about the problem, to be relayed to the user. If creation_status is not ok, then either creation_message or element_messages is guaranteed to be nonempty, and both are guaranteed to be in the array list. May contain HTML. <li> element_messages: list of (element_name, message, element_name, message, ...) of errors on the individual registration elements. to be relayed on to the user. If creation_status is not ok, then either creation_message or element_messages is guaranteed to be nonempty, and both are guaranteed to be in the array list. Cannot contain HTML. <li> account_status: ok, closed. Only set if creation_status was ok, this says whether the newly created account is ready for use or not. For example, we may require approval, in which case the account would be created but closed. <li> account_message: A human-readable explanation of why the account was closed. May include HTML, and thus shouldn't be quoted. Guaranteed to be nonempty if account_status is not ok. <li> user_id: The user_id of the created user. Only when creation_status is ok. </ul> @see auth::get_all_registration_elements } { if {$authority_id eq ""} { set authority_id [auth::get_register_authority] } # This holds element error messages array set element_messages [list] ##### # # Create local account # ##### if { $verify_password_confirm_p } { if { $password ne $password_confirm } { return [list \ creation_status data_error \ creation_message [_ acs-subsite.Passwords_dont_match] \ element_messages [list \ password_confirm [_ acs-subsite.Passwords_dont_match] ]] } } set email [string trim $email] set username [string trim $username] foreach elm [get_all_registration_elements] { if { [info exists $elm] } { set user_info($elm) [set $elm] } } # email_verified_p set user_info(email_verified_p) $email_verified_p db_transaction { array set creation_info [auth::create_local_account \ -user_id $user_id \ -authority_id $authority_id \ -username $username \ -array user_info] # Returns: # creation_info(creation_status) # creation_info(creation_message) # creation_info(element_messages) # creation_info(account_status) # creation_info(account_message) # creation_info(user_id) # We don't do any fancy error checking here, because # create_local_account is not a service contract so we control # it 100% # Local account creation ok? if {$creation_info(creation_status) eq "ok"} { # Need to find out which username was set set username $creation_info(username) # Save the local account information for later set local_account_status $creation_info(account_status) set local_account_message $creation_info(account_message) # Clear out remote creation_info array for reuse array set creation_info { creation_status {} creation_message {} element_messages {} account_status {} account_message {} } ##### # # Create remote account # ##### array set creation_info [auth::registration::Register \ -authority_id $authority_id \ -username $username \ -password $password \ -first_names $first_names \ -last_name $last_name \ -screen_name $screen_name \ -email $email \ -url $url \ -secret_question $secret_question \ -secret_answer $secret_answer] # Returns: # creation_info(creation_status) # creation_info(creation_message) # creation_info(element_messages) # creation_info(account_status) # creation_info(account_message) # Verify creation_info/creation_message return codes array set default_creation_message { data_error {Problem with user data} reg_error {Unknown registration error} failed_to_connect {Error communicating with account server} } switch $creation_info(creation_status) { ok { # Continue below } data_error - reg_error - failed_to_connect { if { $creation_info(creation_message) eq "" } { set creation_info(creation_message) $default_creation_message($creation_info(creation_status)) } if { ![info exists creation_info(element_messages)] } { set creation_info(element_messages) {} } return [array get creation_info] } default { set creation_info(creation_status) "failed_to_connect" set creation_info(creation_message) "Illegal error code returned from account creation driver" return [array get creation_info] } } # Verify remote account_info/account_message return codes switch $creation_info(account_status) { ok { # Continue below set creation_info(account_message) {} } closed { if { $creation_info(account_message) eq "" } { set creation_info(account_message) [_ acs-subsite.Account_not_avail_now] } } default { set creation_info(account_status) "closed" set creation_info(account_message) "Illegal error code returned from creationentication driver" } } } } on_error { set creation_info(creation_status) failed_to_connect set creation_info(creation_message) $errmsg ad_log Error "auth::create_user: Error invoking account registration driver for authority_id = $authority_id" } if { $creation_info(creation_status) ne "ok" } { return [array get creation_info] } ##### # # Clean up, concat account messages, issue login cookie # ##### # If the local account was closed, the whole account is closed, regardless of remote account status if {$local_account_status eq "closed"} { set creation_info(account_status) closed } if { [info exists local_account_message] && $local_account_message ne "" } { if { [info exists creation_info(account_message)] && $creation_info(account_message) ne "" } { # Concatenate local and remote account messages set creation_info(account_message) "<p>[auth::authority::get_element -authority_id $authority_id -element pretty_name]: $creation_info(account_message)</p> <p>[ad_system_name]: $local_account_message</p>" } else { set creation_info(account_message) $local_account_message } } # Unless nologin was specified, issue login cookie if login was successful if { !$nologin_p && $creation_info(creation_status) eq "ok" && $creation_info(account_status) eq "ok" && [ad_conn user_id] == 0 } { ad_user_login $creation_info(user_id) } return [array get creation_info] } d_proc -public auth::get_registration_elements { } { Get the list of required/optional elements for user registration. @return Array-list with two entries <ul> <li> required: a list of required elements <li> optional: a list of optional elements </ul> @see auth::get_all_registration_elements } { set authority_id [auth::get_register_authority] array set element_info [auth::registration::GetElements -authority_id $authority_id] if { ![info exists element_info(required)] } { set element_info(required) {} } if { ![info exists element_info(optional)] } { set element_info(optional) {} } set local_required_elms { first_names last_name email } set local_optional_elms {} switch [acs_user::ScreenName] { require { lappend local_required_elms "screen_name" } solicit { lappend local_optional_elms "screen_name" } } # Handle required elements for local account foreach elm $local_required_elms { # Add to required if { $elm ni $element_info(required) } { lappend element_info(required) $elm } # Remove from optional set index [lsearch $element_info(optional) $elm] if { $index != -1 } { set element_info(optional) [lreplace $element_info(optional) $index $index] } } foreach elm $local_optional_elms { # Add to required if { $elm ni $element_info(required) && $elm ni $element_info(optional) } { lappend element_info(optional) $elm } } return [array get element_info] } d_proc -public auth::get_all_registration_elements { {-include_password_confirm:boolean} } { Get the list of possible registration elements. } { if { $include_password_confirm_p } { return { email username first_names last_name password password_confirm screen_name url secret_question secret_answer } } else { return { email username first_names last_name password screen_name url secret_question secret_answer } } } d_proc -public auth::get_registration_form_elements { } { Returns a list of elements to be included in the -form chunk of an ad_form form. All possible elements will always be present, but those that shouldn't be displayed will be hidden and have a hard-coded empty string value. } { array set data_types { username text email text first_names text last_name text screen_name text url text password text password_confirm text secret_question text secret_answer text } array set widgets { username text email email first_names text last_name text screen_name text url url password password password_confirm password secret_question text secret_answer text } array set labels [list \ username [_ acs-subsite.Username] \ email [_ acs-subsite.Email] \ first_names [_ acs-subsite.First_names] \ last_name [_ acs-subsite.Last_name] \ screen_name [_ acs-subsite.Screen_name] \ url [_ acs-subsite.lt_Personal_Home_Page_UR] \ password [_ acs-subsite.Password] \ password_confirm [_ acs-subsite.lt_Password_Confirmation] \ secret_question [_ acs-subsite.Question] \ secret_answer [_ acs-subsite.Answer]] array set html { username {size 30} email {size 30} first_names {size 30} last_name {size 30} screen_name {size 30} url {size 80 value ""} password {size 20} password_confirm {size 20} secret_question {size 30} secret_answer {size 30} } array set element_info [auth::get_registration_elements] # provide default help texts, might be refined later. array set help_text { username {} email {} first_names {} last_name {} screen_name {} url {} password {} password_confirm {} secret_question {} secret_answer {} } if {"password" in $element_info(required)} { lappend element_info(required) password_confirm } if {"password" in $element_info(optional)} { lappend element_info(optional) password_confirm } # required_p will have 1 if required, 0 if optional, and unset if not in the form array set required_p [list] foreach element $element_info(required) { set required_p($element) 1 } foreach element $element_info(optional) { set required_p($element) 0 } set form_elements [list] foreach element [auth::get_all_registration_elements -include_password_confirm] { if { [info exists required_p($element)] } { set form_element [list] # The header with name, datatype, and widget set form_element_header "${element}:$data_types($element)($widgets($element))" if { !$required_p($element) } { append form_element_header ",optional" } lappend form_element $form_element_header # The label lappend form_element [list label $labels($element)] # HTML lappend form_element [list html $html($element)] # Help Text lappend form_element [list help_text $help_text($element)] # The form element is finished - add it to the list lappend form_elements $form_element } else { lappend form_elements "${element}:text(hidden),optional {value {}}" } } return $form_elements } d_proc -public auth::create_local_account { {-user_id ""} {-authority_id:required} {-username ""} {-array:required} } { Create the local account for a user. @param array Name of an array containing the registration elements to update. Fields are specified by auth::get_all_registration_elements @see auth::get_all_registration_elements @return Array list containing the following entries: <ul> <li> creation_status: ok, data_error, reg_error, failed_to_connect. Says whether user creation succeeded. <li> creation_message: Information about the problem, to be relayed to the user. If creation_status is not ok, then either creation_message or element_messages is guaranteed to be nonempty, and both are guaranteed to be in the array list. May contain HTML. <li> element_messages: list of (element_name, message, element_name, message, ...) of errors on the individual registration elements. to be relayed on to the user. If creation_status is not ok, then either creation_message or element_messages is guaranteed to be nonempty, and both are guaranteed to be in the array list. Cannot contain HTML. <li> account_status: ok, closed. Only set if creation_status was ok, this says whether the newly created account is ready for use or not. For example, we may require approval, in which case the account would be created but closed. <li> account_message: A human-readable explanation of why the account was closed. May include HTML, and thus shouldn't be quoted. Guaranteed to be nonempty if account_status is not ok. </ul> All entries are guaranteed to always be set, but may be empty. } { upvar 1 $array user_info array set result { creation_status reg_error creation_message {} element_messages {} account_status ok account_message {} user_id {} } # Default all elements to the empty string foreach elm [get_all_registration_elements] { if { ![info exists user_info($elm)] } { set user_info($elm) {} } } # Validate data auth::validate_account_info \ -authority_id $authority_id \ -username $username \ -user_array user_info \ -message_array element_messages # Handle validation errors if { [array size element_messages] > 0 } { return [list \ creation_status "data_error" \ creation_message {} \ element_messages [array get element_messages] \ ] } # Admin approval set system_name [ad_system_name] set subsite_id [expr {[ns_conn isconnected] ? [ad_conn subsite_id] : [subsite::main_site_id]}] if { [parameter::get -package_id $subsite_id -parameter RegistrationRequiresApprovalP -default 0] } { set member_state "needs approval" set result(account_status) "closed" set result(account_message) [_ acs-subsite.Registration_Approval_Notice] } else { set member_state "approved" } set registration_requires_email_verification_p \ [parameter::get \ -package_id $subsite_id \ -parameter RegistrationRequiresEmailVerificationP \ -default 0] if { ![info exists user_info(email_verified_p)] || $user_info(email_verified_p) eq "" } { if {$registration_requires_email_verification_p} { set user_info(email_verified_p) "f" } else { set user_info(email_verified_p) "t" } } # We can generate a username ourselves when this is missing and # the system is configured to do so, but only if the account is # managed locally. if { $username eq "" && [auth::UseEmailForLoginP] } { set local_authority_id [auth::authority::local] set local_auth_impl_id [auth::authority::get_element \ -authority_id $local_authority_id \ -element "auth_impl_id"] set auth_impl_id [auth::authority::get_element \ -authority_id $authority_id \ -element "auth_impl_id"] set generate_username_p [expr {$local_auth_impl_id == $auth_impl_id}] } else { set generate_username_p false } if { $generate_username_p } { # Generate a username that is guaranteed to be unique. # Rather much work, but that's the best I could think of # Default to email set username [string tolower $user_info(email)] # Check if it already exists set existing_user_id [acs_user::get_by_username -authority_id $authority_id -username $username] # If so, add -2 or -3 or ... to make it unique if { $existing_user_id ne "" } { set match "${username}-%" set existing_usernames [db_list select_existing_usernames { select username from users where authority_id = :authority_id and username like :match }] set number 2 foreach existing_username $existing_usernames { if { [regexp "^${username}-(\\d+)\$" $existing_username match existing_number] } { # matches the foo-123 pattern if { $existing_number >= $number } { set number [expr {$existing_number + 1}] } } } set username "$username-$number" ns_log Notice "auth::create_local_account: user's email was already used as someone else's username, setting username to $username" } } set error_p 0 ad_try { # We create the user without a password # If it's a local account, that'll get set later set user_id [auth::create_local_account_helper \ $user_info(email) \ $user_info(first_names) \ $user_info(last_name) \ {} \ $user_info(secret_question) \ $user_info(secret_answer) \ $user_info(url) \ $user_info(email_verified_p) \ $member_state \ $user_id \ $username \ $user_info(authority_id) \ $user_info(screen_name)] # Update person.bio if { [info exists user_info(bio)] } { person::update \ -person_id $user_id \ -bio $user_info(bio) } } on error {errorMsg} { set error_p 1 } if { $error_p || $user_id == 0 } { set result(creation_status) "failed_to_connect" set result(creation_message) [_ acs-subsite.Error_trying_to_register] ad_log Error "auth::create_local_account: Error creating local account." return [array get result] } set result(user_id) $user_id if { $username eq "" } { set username [acs_user::get_user_info \ -user_id $user_id -element username] } set result(username) $username # Creation succeeded set result(creation_status) "ok" if {$registration_requires_email_verification_p} { set email $user_info(email) set result(account_status) "closed" set result(account_message) "<p>[_ acs-subsite.lt_Registration_informat_1]</p><p>[_ acs-subsite.lt_Please_read_and_follo]</p>" ad_try { auth::send_email_verification_email -user_id $user_id } on error {errorMsg} { ad_log Error "auth::create_local_account: Error sending out email verification email to email $email: $errorMsg" set auth_info(account_message) [_ acs-subsite.Error_sending_verification_mail] } } return [array get result] } d_proc -private auth::create_local_account_helper { email first_names last_name password password_question password_answer {url ""} {email_verified_p "t"} {member_state "approved"} {user_id ""} {username ""} {authority_id ""} {screen_name ""} } { 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. @see auth::create_user @see auth::create_local_account } { if { $user_id eq "" } { set user_id [db_nextval acs_object_id_seq] } set creation_user "" set peeraddr "" # This may fail, either because there's no connection, or because # we're in the bootstrap-installer, at which point [ad_conn user_id] is undefined. ad_try { set creation_user [ad_conn user_id] set peeraddr [ad_conn peeraddr] } on error {errorMsg} { ns_log warning "auth::create_local_account_helper $errorMsg" } set salt [sec_random_token] set hashed_password [ns_sha1 "$password$salt"] set error_p 0 db_transaction { set user_id [db_exec_plsql user_insert {}] # set password_question, password_answer db_dml update_question_answer {} ad_try { # Call the extension acs_user_extension::user_new -user_id $user_id } on error {errorMsg} { # At this point, we don't want the user addition to fail # if some extension is screwing things up ns_log warning "acs_user_extension::user_new -user_id $user_id failed: $errorMsg" } } on_error { # we got an error. log it and signal failure. ad_log Error "Problem creating a new user" set error_p 1 } if { $error_p } { return 0 } # success. return $user_id } d_proc -public auth::update_local_account { {-authority_id:required} {-username:required} {-array:required} } { Update the local account for a user. @param array Name of an array containing the registration elements to update. @return Array list containing the following entries: <ul> <li> update_status: ok, data_error, update_error, failed_to_connect. Says whether user update succeeded. <li> update_message: Information about the problem, to be relayed to the user. If update_status is not ok, then either update_message or element_messages is guaranteed to be nonempty, and both are guaranteed to be in the array list. May contain HTML. <li> element_messages: list of (element_name, message, element_name, message, ...) of errors on the individual registration elements. to be relayed on to the user. If update_status is not ok, then either update_message or element_messages is guaranteed to be nonempty, and both are guaranteed to be in the array list. Cannot contain HTML. </ul> All entries are guaranteed to always be set, but may be empty. } { upvar 1 $array user_info array set result { update_status update_error update_message {} element_messages {} user_id {} } # Validate data auth::validate_account_info \ -update \ -authority_id $authority_id \ -username $username \ -user_array user_info \ -message_array element_messages # Handle validation errors if { [array size element_messages] > 0 } { return [list \ update_status "data_error" \ update_message {} \ element_messages [array get element_messages] \ ] } # We get user_id from validate_account_info above, and set it in the result array so our caller can get it set user_id $user_info(user_id) set result(user_id) $user_id ad_try { db_transaction { # Update persons: first_names, last_name if { [info exists user_info(first_names)] } { # We know that validate_account_info will not let us update only one of the two person::update \ -person_id $user_id \ -first_names $user_info(first_names) \ -last_name $user_info(last_name) } # Update person's bio if { [info exists user_info(bio)] } { person::update \ -person_id $user_id \ -bio $user_info(bio) } # Update parties: email, url if { [info exists user_info(email)] } { party::update \ -party_id $user_id \ -email $user_info(email) } if { [info exists user_info(url)] } { party::update \ -party_id $user_id \ -url $user_info(url) } # Update users: email_verified_p if { [info exists user_info(email_verified_p)] } { acs_user::update \ -user_id $user_id \ -email_verified_p $user_info(email_verified_p) } # Update users: screen_name if { [info exists user_info(screen_name)] } { acs_user::update \ -user_id $user_id \ -screen_name $user_info(screen_name) } if { [info exists user_info(username)] } { acs_user::update \ -user_id $user_id \ -username $user_info(username) } if { [info exists user_info(authority_id)] } { acs_user::update \ -user_id $user_id \ -authority_id $user_info(authority_id) } # TODO: Portrait } } on error {errorMsg} { set result(update_status) "failed_to_connect" set result(update_message) [_ acs-subsite.Error_update_account_info] ad_log Error "Error updating local account: $errorMsg" return [array get result] } # Update succeeded set result(update_status) "ok" return [array get result] } d_proc -public auth::delete_local_account { {-authority_id:required} {-username:required} } { Delete the local account for a user. @return Array list containing the following entries: <ul> <li> delete_status: ok, delete_error, failed_to_connect. Says whether user deletion succeeded. <li> delete_message: Information about the problem, to be relayed to the user. If delete_status is not ok, then delete_message is guaranteed to be nonempty. May contain HTML. </ul> All entries are guaranteed to always be set, but may be empty. } { array set result { delete_status ok delete_message {} user_id {} } set user_id [acs_user::get_by_username \ -authority_id $authority_id \ -username $username] if { $user_id eq "" } { set result(delete_status) "delete_error" set result(delete_message) [_ acs-subsite.No_user_with_this_username] return [array get result] } # Mark the account banned acs_user::ban -user_id $user_id set result(user_id) $user_id return [array get result] } d_proc -public auth::set_email_verified { {-user_id:required} } { Update an OpenACS record with the fact that the email address on record was verified. } { acs_user::update \ -user_id $user_id \ -email_verified_p "t" } ad_proc -public auth::verify_account_status {} { Verify the account status of the current user, and set [ad_conn account_status] appropriately. } { # Just recheck the authentication cookie, and it'll do the verification for us sec_login_handler } ##### # # auth namespace private procs # ##### d_proc -private auth::get_local_account { {-return_url ""} {-username:required} {-authority_id ""} {-email ""} {-first_names ""} {-last_name ""} } { Get the user_id of the local account for the given username and domain combination. @param username The username to find @param authority_id The ID of the authority to ask to verify the user. Leave blank for local authority. } { array set auth_info [list] # Will return: # auth_info(account_status) # auth_info(account_message) # auth_info(user_id) if { $authority_id eq "" } { set authority_id [auth::authority::local] } #ns_log notice "auth::get_local_account authority_id = '${authority_id}' local = [auth::authority::local]" ad_try { acs_user::get -authority_id $authority_id -username $username -array user set account_found_p 1 } on error {errorMsg} { set account_found_p 0 } if { !$account_found_p } { # Try for an on-demand sync array set info_result [auth::user_info::GetUserInfo \ -authority_id $authority_id \ -username $username] if {$info_result(info_status) eq "ok"} { array set user $info_result(user_info) if {$email ne "" && (![info exists user(email)] || $user(email) eq "") } { set user(email) $email } if {$first_names ne "" && (![info exists user(first_names)] || $user(first_names) eq "") } { set user(first_names) $first_names } if {$last_name ne "" && (![info exists user(last_name)] || $user(last_name) eq "") } { set user(last_name) $last_name } array set creation_info [auth::create_local_account \ -authority_id $authority_id \ -username $username \ -array user] if {$creation_info(creation_status) ne "ok"} { set auth_info(account_status) "closed" # Used to get help contact info auth::authority::get -authority_id $authority_id -array authority set system_name [ad_system_name] set auth_info(account_message) "You have successfully authenticated, but we were unable to create an account for you on $system_name. " set auth_info(element_messages) $creation_info(element_messages) append auth_info(account_message) "The error was: $creation_info(element_messages). Please contact the system administrator." if { $authority(help_contact_text) ne "" } { append auth_info(account_message) "<p><h3>Help Information</h3>" append auth_info(account_message) [ad_html_text_convert \ -from $authority(help_contact_text_format) \ -to "text/html" -- $authority(help_contact_text)] } return [array get auth_info] } } else { # Local user account doesn't exist set auth_info(account_status) "closed" # Used to get help contact info auth::authority::get -authority_id $authority_id -array authority set system_name [ad_system_name] set auth_info(account_message) [_ acs-subsite.Success_but_no_account_yet] if { $authority(help_contact_text) ne "" } { append auth_info(account_message) [_ acs-subsite.Help_information] append auth_info(account_message) [ad_html_text_convert \ -from $authority(help_contact_text_format) \ -to "text/html" -- $authority(help_contact_text)] } return [array get auth_info] } } set user_id [acs_user::get_by_username \ -authority_id $authority_id -username $username] set user_info [acs_user::get_user_info -user_id $user_id] set party_info [party::get -party_id $user_id] # Check local account status array set auth_info [auth::check_local_account_status \ -user_id $user_id \ -authority_id [dict get $user_info authority_id] \ -member_state [dict get $user_info member_state] \ -email [dict get $party_info email] \ -email_verified_p [dict get $user_info email_verified_p] \ -screen_name [dict get $user_info screen_name] \ -password_age_days [dict get $user_info password_age_days] \ -return_url $return_url] # Return user_id set auth_info(user_id) $user_id return [array get auth_info] } d_proc -private auth::check_local_account_status { {-return_url ""} {-no_dialogue:boolean} {-user_id:required} {-authority_id:required} {-member_state:required} {-email:required} {-email_verified_p:required} {-screen_name:required} {-password_age_days:required} } { Check the account status of a user with the given parameters. @param no_dialogue If specified, will not send out email or in other ways converse with the user @return An array-list with account_status, account_url and account_message } { # Initialize to 'closed', because most cases below mean the account is closed set result(account_status) "closed" # system_name and email is used in some of the I18N messages set system_name [ad_system_name] switch $member_state { approved { set PasswordExpirationDays [parameter::get \ -parameter PasswordExpirationDays \ -package_id [ad_acs_kernel_id] \ -default 0] if { $email_verified_p == "f" } { if { !$no_dialogue_p } { set result(account_message) [subst { <p>[_ acs-subsite.lt_Registration_informat]</p> <p>[_ acs-subsite.lt_Please_read_and_follo]</p> }] ad_try { auth::send_email_verification_email -user_id $user_id } on error {errorMsg} { ad_log Error "auth::check_local_account_status: Error sending out email verification email to email $email: $errorMsg" set result(account_message) [_ acs-subsite.Error_sending_verification_mail] } } } elseif { [acs_user::ScreenName] eq "require" && $screen_name eq "" } { set message "Please enter a screen name now." set result(account_url) [export_vars -no_empty \ -base "[subsite::get_element -element url]user/basic-info-update" { message return_url {edit_p 1} }] } elseif { $PasswordExpirationDays > 0 && ($password_age_days eq "" || $password_age_days > $PasswordExpirationDays) } { set message [_ acs-subsite.Password_regular_change_now] set result(account_url) [export_vars -base "[subsite::get_element -element url]user/password-update" { return_url message }] } else { set result(account_status) "ok" } } banned { set result(account_message) [_ acs-subsite.lt_Sorry_but_it_seems_th] } deleted { set restore_url [export_vars -base "restore-user" { return_url }] set result(account_message) [_ acs-subsite.Account_closed] } rejected - "needs approval" { set result(account_message) \ "<p>[_ acs-subsite.lt_registration_request_submitted]</p><p>[_ acs-subsite.Thank_you]</p>" } default { set result(account_message) [_ acs-subsite.Problem_auth_no_memb] ns_log Error "auth::check_local_account_status: problem with registration state machine: user_id $user_id has member_state '$member_state'" } } return [array get result] } d_proc -public auth::get_local_account_status { {-user_id:required} } { Return 'ok', 'closed', or 'no_account' } { set result no_account ad_try { set user [acs_user::get_user_info -user_id $user_id] set party_info [party::get -party_id $user_id] set check_result [auth::check_local_account_status \ -user_id $user_id \ -authority_id [dict get $user authority_id] \ -member_state [dict get $user member_state] \ -email_verified_p [dict get $user email_verified_p] \ -email [dict get $party_info email] \ -screen_name [dict get $user screen_name] \ -password_age_days [dict get $user password_age_days]] set result [dict get $check_result account_status] } on error {errorMsg} { ns_log notice "auth::get_local_account_status returned: $errorMsg" } return $result } d_proc -public auth::get_user_secret_token { -user_id:required } { Get a secret token for the user. Can be used for email verification purposes. } { return [ns_sha1 "${user_id}[sec_get_token 1]"] } d_proc -private auth::send_email_verification_email { -user_id:required } { Sends out an email to the user that lets them verify their email. Throws an error if we couldn't send out the email. } { # These are used in the messages below set token [auth::get_user_secret_token -user_id $user_id] set to_addr [party::get -party_id $user_id -element email] set subsite_url [site_node::get_url -node_id [ad_conn subsite_node_id]] set confirmation_url [export_vars -base "[ad_url]$subsite_url/register/email-confirm" { token user_id }] set system_name [ad_system_name] acs_mail_lite::send -send_immediately \ -to_addr $to_addr \ -from_addr "\"$system_name\" <[parameter::get -parameter NewRegistrationEmailAddress -default [ad_system_owner]]>" \ -subject [_ acs-subsite.lt_Welcome_to_system_nam] \ -body [_ acs-subsite.lt_To_confirm_your_regis] } d_proc -private auth::validate_account_info { {-update:boolean} {-authority_id:required} {-username:required} {-user_array:required} {-message_array:required} } { Validates user info and returns errors, if any. @param update Set this flag if you're updating an existing record, meaning we shouldn't check for duplicates. @param user_array Name of an array in the caller's namespace which contains the registration elements. @param message_array Name of an array where you want the validation errors stored, keyed by element name. } { upvar 1 $user_array user upvar 1 $message_array element_messages set required_elms {} if { !$update_p } { lappend required_elms first_names last_name email } foreach elm $required_elms { if { ![info exists user($elm)] || $user($elm) eq "" } { set element_messages($elm) "Required" } } if { [info exists user(email)] } { set user(email) [string trim $user(email)] } if { [info exists user(username)] } { set user(username) [string trim $user(username)] } if { $update_p } { set user(user_id) [acs_user::get_by_username \ -authority_id $authority_id \ -username $username] if { $user(user_id) eq "" } { set this_authority [auth::authority::get_element -authority_id $authority_id -element pretty_name] set element_messages(username) [_ acs-subsite.Username_not_found_for_authority] } } else { set user(username) $username set user(authority_id) $authority_id } # TODO: When doing RBM's parameter, make sure that we still require both first_names and last_names, or none of them if { [info exists user(first_names)] && $user(first_names) ne "" && [string first "<" $user(first_names)] != -1 } { set element_messages(first_names) [_ acs-subsite.lt_You_cant_have_a_lt_in] } if { [info exists user(last_name)] && $user(last_name) ne "" && [string first "<" $user(last_name)] != -1 } { set element_messages(last_name) [_ acs-subsite.lt_You_cant_have_a_lt_in_1] } if { [info exists user(email)] && $user(email) ne "" } { if { ![util_email_valid_p $user(email)] } { set element_messages(email) [_ acs-subsite.Not_valid_email_addr] } else { set user(email) [string tolower $user(email)] } } if { [info exists user(url)] } { if { $user(url) eq "" || $user(url) eq "http://" } { # The user left the default hint for the url set user(url) {} } elseif { ![util_url_valid_p $user(url)] } { set valid_url_example "http://openacs.org/" set element_messages(url) [_ acs-subsite.lt_Your_URL_doesnt_have_] } } if { [info exists user(screen_name)] && $user(screen_name) ne "none" } { set screen_name_user_id [acs_user::get_user_id_by_screen_name -screen_name $user(screen_name)] if { $screen_name_user_id ne "" && (!$update_p || $screen_name_user_id != $user(user_id)) } { set element_messages(screen_name) [_ acs-subsite.screen_name_already_taken] # We could do the same logic as below with 'stealing' the # screen_name of an old, banned user. } } if { [info exists user(email)] && $user(email) ne "" } { # Check that email is unique set email $user(email) set email_party_id [party::get_by_email -email $user(email)] if { $email_party_id ne "" && (!$update_p || $email_party_id != $user(user_id)) } { # We found a user with this email, and either we're not updating, # or it's not the same user_id as the one we're updating if { [acs_object_type $email_party_id] ne "user" } { set element_messages(email) [_ acs-subsite.Have_group_mail] } else { set email_member_state [acs_user::get_user_info \ -user_id $email_party_id \ -element member_state] switch $email_member_state { banned { set element_messages(email) [_ acs-subsite.lt_This_user_is_deleted] } default { set element_messages(email) [_ acs-subsite.Have_user_mail] } } } } } # They're trying to set the username if { [info exists user(username)] && $user(username) ne "" } { # Check that username is unique set username_user_id [acs_user::get_by_username -authority_id $authority_id -username $user(username)] if { $username_user_id ne "" && (!$update_p || $username_user_id != $user(user_id)) } { # We already have a user with this username, and either # we're not updating, or it's not the same user_id as the # one we're updating set username_member_state [acs_user::get_user_info \ -user_id $username_user_id \ -element member_state] switch $username_member_state { banned { set element_messages(username) [_ acs-subsite.lt_This_user_is_deleted] } default { set element_messages(username) [_ acs-subsite.Have_user_name] } } } } } d_proc -public auth::can_admin_system_without_authority_p { {-authority_id:required} } { Before disabling or deleting an authority we need to check that there is at least one site-wide admin in a different authority that can administer the system. @return boolean @author Peter Marklund } { # # Is there a user from other authorities having swa admins (having # admin rights on the magic object 'security_context_root')? # return [db_0or1row admins_left_p { select 1 from dual where exists ( select 1 from acs_permissions p, party_approved_member_map m, acs_magic_objects amo, cc_users u where amo.name = 'security_context_root' and p.object_id = amo.object_id and p.grantee_id = m.party_id and u.user_id = m.member_id and u.member_state = 'approved' and u.authority_id <> :authority_id and acs_permission.permission_p(amo.object_id, u.user_id, 'admin') = 't' ) }] } ##### # # auth::authentication # ##### d_proc -public auth::authentication::authenticate { {-authority_id:required} {-username:required} {-password:required} } { Invoke the Authenticate service contract operation for the given authority. @param authority_id The ID of the authority to ask to verify the user. @param username Username of the user. @param password The password as the user entered it. } { set impl_id [auth::authority::get_element -authority_id $authority_id -element "auth_impl_id"] if { $impl_id eq "" } { # No implementation of authentication set authority_pretty_name [auth::authority::get_element -authority_id $authority_id -element "pretty_name"] error "The authority '$authority_pretty_name' doesn't support authentication" } set parameters [auth::driver::get_parameter_values \ -authority_id $authority_id \ -impl_id $impl_id] return [acs_sc::invoke \ -error \ -impl_id $impl_id \ -operation Authenticate \ -call_args [list $username $password $parameters $authority_id]] } # ad_proc -deprecated auth::authentication::Authenticate args { # Invoke the Authenticate service contract operation for the given authority. # DEPRECATED: this used to be a private api, however, it could be # made public, as it calls only public api itself and provides some # convenience. Unfortunately, it has been named in camelcase, so we # have to create a new alias and deprecate this one. # @see auth::authentication::authenticate # @param authority_id The ID of the authority to ask to verify the user. # @param username Username of the user. # @param password The password as the user entered it. # } { # return [auth::authentication::authenticate {*}$args] # } ##### # # auth::registration # ##### d_proc -private auth::registration::Register { {-authority_id:required} {-username ""} {-password ""} {-first_names ""} {-last_name ""} {-screen_name ""} {-email ""} {-url ""} {-secret_question ""} {-secret_answer ""} } { Invoke the Register service contract operation for the given authority. @param authority_id Id of the authority. } { set impl_id [auth::authority::get_element -authority_id $authority_id -element "register_impl_id"] if { $impl_id eq "" } { # No implementation of authentication set authority_pretty_name [auth::authority::get_element -authority_id $authority_id -element "pretty_name"] error "The authority '$authority_pretty_name' doesn't support account registration" } set parameters [auth::driver::get_parameter_values \ -authority_id $authority_id \ -impl_id $impl_id] return [acs_sc::invoke \ -error \ -impl_id $impl_id \ -operation Register \ -call_args [list $parameters \ $username \ $authority_id \ $first_names \ $last_name \ $screen_name \ $email \ $url \ $password \ $secret_question \ $secret_answer]] } d_proc -private auth::registration::GetElements { {-authority_id:required} } { @author Peter Marklund } { set impl_id [auth::authority::get_element -authority_id $authority_id -element "register_impl_id"] if { $impl_id eq "" } { # No implementation of authentication set authority_pretty_name [auth::authority::get_element -authority_id $authority_id -element "pretty_name"] error "The authority '$authority_pretty_name' doesn't support account registration" } set parameters [auth::driver::get_parameter_values \ -authority_id $authority_id \ -impl_id $impl_id] return [acs_sc::invoke \ -error \ -impl_id $impl_id \ -operation GetElements \ -call_args [list $parameters]] } ##### # # auth::user_info # ##### d_proc -private auth::user_info::GetUserInfo { {-authority_id:required} {-username:required} } { Invoke the Register service contract operation for the given authority. @param authority_id Id of the authority. } { set impl_id [auth::authority::get_element -authority_id $authority_id -element "user_info_impl_id"] if { $impl_id eq "" } { # No implementation of authentication return { info_status no_account } } set parameters [auth::driver::get_parameter_values \ -authority_id $authority_id \ -impl_id $impl_id] return [acs_sc::invoke \ -error \ -impl_id $impl_id \ -operation GetUserInfo \ -call_args [list $username $parameters]] } ##### # # auth::login_attempts # ##### # Prevent/slowdown brute force attacks on login by counting the number of # failed consecutive failed login attempts based on the ip-address and subsite. # # After the maximum number of consecutive failed login attempts # has been exceeded, all further login attempts will be automatically rejected # for a specified lock-out/cool-down time, even if the correct credentials have been # provided. Every successful login before reaching the threshold resets the # counter to 0 again. Beware, the counting is done via caching and is # therefore not persistent. # # Configure this feature via the following acs-authentication parameters: # # MaxConsecutiveFailedLoginAttempts: max number of consecutive failed login attempts; # Default: 0 (= infinite attempts) # # MaxConsecutiveFailedLoginAttemptsLockoutTime : Timespan in seconds # for which every new login attempt is rejected after the threshold has been reached. # Default: 21600 seconds (six hours) # d_proc -private ::auth::login_attempts::threshold_reached_p { {-login_attempt_key "[ad_conn peeraddr]-[ad_conn subsite_id]"} } { Check if the maximum number of consecutive failed login attempts has been reached @param login_attempt_key Identifier of this login attempt. Defaults to "[ad_conn peeraddr]-[ad_conn subsite]" @return 1 if limit has been reached otherwise 0 } { set max_failed_login_attempts [parameter::get_from_package_key \ -parameter "MaxConsecutiveFailedLoginAttempts" \ -package_key "acs-authentication" \ -default 0] if {$max_failed_login_attempts > 0 && [::auth::login_attempts::get -key $login_attempt_key] > $max_failed_login_attempts } { return 1 } else { return 0 } } d_proc -private ::auth::login_attempts::record { {-login_attempt_key "[ad_conn peeraddr]-[ad_conn subsite_id]"} } { Record a failed login attempt @param login_attempt_key Identifier of this login attempt. Defaults to "[ad_conn peeraddr]-[ad_conn subsite]" } { if { [parameter::get_from_package_key -parameter "MaxConsecutiveFailedLoginAttempts" -package_key "acs-authentication" -default 0] } { set max_age [parameter::get_from_package_key \ -parameter "MaxConsecutiveFailedLoginAttemptsLockoutTime" \ -package_key "acs-authentication" \ -default 21600] ::auth::login_attempts::login_attempt_incr -key $login_attempt_key -max_age $max_age } } d_proc -public ::auth::login_attempts::reset { {-login_attempt_key "[ad_conn peeraddr]-[ad_conn subsite_id]"} } { Flush the recorded failed login attempt for the provided login_attempt_key @param login_attempt_key Identifier of this login attempt. Defaults to "[ad_conn peeraddr]-[ad_conn subsite]" } { ::auth::login_attempts::login_attempt_flush -key $login_attempt_key } ad_proc -public ::auth::login_attempts::reset_all {} { Flush all recorded failed login attempts } { ::auth::login_attempts::flush_all } ad_proc -public ::auth::login_attempts::get_all {} { Get all failed login attempts } { ::auth::login_attempts::all_entries } # Local variables: # mode: tcl # tcl-indent-level: 4 # indent-tabs-mode: nil # End: