• Publicity: Public Only All

local-procs.tcl

Procs for local authentication.

Location:
packages/acs-authentication/tcl/local-procs.tcl
Created:
2003-05-13
Author:
Lars Pind <lars@collaobraid.biz>
CVS Identification:
$Id: local-procs.tcl,v 1.47.2.8 2022/09/19 13:24:22 antoniop Exp $

Procedures in this file

Detailed information

[ hide source ] | [ make this the default ]

Content File Source

ad_library {
    Procs for local authentication.

    @author Lars Pind (lars@collaobraid.biz)
    @creation-date 2003-05-13
    @cvs-id $Id: local-procs.tcl,v 1.47.2.8 2022/09/19 13:24:22 antoniop Exp $
}

namespace eval auth {}
namespace eval auth::local {}
namespace eval auth::local::authentication {}
namespace eval auth::local::password {}
namespace eval auth::local::registration {}
namespace eval auth::local::user_info {}
namespace eval auth::local::search {}

#####
#
# auth::local
#
#####

ad_proc -private auth::local::install {} {
    Register local service contract implementations,
    and update the local authority with live information.
} {
    db_transaction {
        # Register the local service contract implementations
        set row(auth_impl_id) [auth::local::authentication::register_impl]
        set row(pwd_impl_id) [auth::local::password::register_impl]
        set row(register_impl_id) [auth::local::registration::register_impl]
        set row(user_info_impl_id) [auth::local::user_info::register_impl]

        # Set the authority pretty-name to be the system name
        set row(pretty_name) [ad_system_name]

        auth::authority::edit \
            -authority_id [auth::authority::local] \
            -array row
    }
}

ad_proc -private auth::local::uninstall {} {
    Unregister the local service contract implementation, and update the
    local authority to reflect that.
} {
    db_transaction {
        # Update the local authority to reflect the loss of the implementations
        set row(auth_impl_id) {}
        set row(pwd_impl_id) {}
        set row(register_impl_id) {}

        auth::authority::edit \
            -authority_id [auth::authority::local] \
            -array row

        # Unregister the implementations
        auth::local::authentication::unregister_impl
        auth::local::password::unregister_impl
        auth::local::registration::unregister_impl
    }
}




#####
#
# auth::local::authentication
#
#####
#
# The 'auth_authentication' service contract implementation
#

ad_proc -private auth::local::authentication::register_impl {} {
    Register the 'local' implementation of the 'auth_authentication' service contract.

    @return impl_id of the newly created implementation.
} {
    set spec {
        contract_name "auth_authentication"
        owner "acs-authentication"
        name "local"
        pretty_name "Local"
        aliases {
            MergeUser auth::local::authentication::MergeUser
            Authenticate auth::local::authentication::Authenticate
            GetParameters auth::local::authentication::GetParameters
        }
    }

    return [acs_sc::impl::new_from_spec -spec $spec]
}

ad_proc -private auth::local::authentication::unregister_impl {} {
    Unregister the 'local' implementation of the 'auth_authentication' service contract.
} {
    acs_sc::impl::delete -contract_name "auth_authentication" -impl_name "local"
}

d_proc -private auth::local::authentication::MergeUser {
    from_user_id
    to_user_id
    {authority_id ""}
} {
    Merge Implementation of local authentication. This will
    merge the names, emails, usernames, permissions, etc
    of the two users to merge.
} {
    ns_log Notice "Starting auth::local::authentication::MergeUser"
    db_transaction {
        ns_log Notice "  Merging user portraits"

        ns_log notice "  Merging username, email and basic info in general"

        set new_username "merged_$from_user_id"
        append new_username "_$to_user_id"

        # Shall we keep the domain for email?
        # Actually, the username 'merged_xxx_yyy'
        # won't be an email, so we will keep it without
        # domain
        set new_email $new_username

        set rel_id [db_string getrelid {}]
        membership_rel::change_state -rel_id $rel_id -state "merged"

        acs_user::update -user_id $from_user_id -username "$new_username" -screen_name "$new_username"
        party::update -party_id $from_user_id -email "$new_email"

    }
    ns_log notice "Finishing auth::local::authentication::MergeUser"
}


d_proc -private auth::local::authentication::Authenticate {
    username
    password
    {parameters {}}
    {authority_id {}}
} {
    Implements the Authenticate operation of the auth_authentication
    service contract for the local account implementation.
} {
    array set auth_info [list]

    if {$authority_id eq ""} {
        set authority_id [auth::authority::local]
    }

    set user_id [acs_user::get_by_username -authority_id $authority_id -username $username]
    if { $user_id eq "" } {
        set result(auth_status) "no_account"
        return [array get result]
    }

    if { [ad_check_password $user_id $password] } {
        set auth_info(auth_status) "ok"
    } else {
        set auth_info(auth_status) "bad_password"
        set auth_info(auth_message) [_ acs-authentication.Invalid_username_or_password]
        return [array get auth_info]
    }

    # We set 'external' account status to 'ok', because the
    # local account status will be checked anyways by the framework
    set auth_info(account_status) ok

    return [array get auth_info]
}

ad_proc -private auth::local::authentication::GetParameters {} {
    Implements the GetParameters operation of the auth_authentication
    service contract for the local account implementation.
} {
    # No parameters
    return [list]
}


#####
#
# auth::local::password
#
#####
#
# The 'auth_password' service contract implementation
#

ad_proc -private auth::local::password::register_impl {} {
    Register the 'local' implementation of the 'auth_password' service contract.

    @return impl_id of the newly created implementation.
} {
    set spec {
        contract_name "auth_password"
        owner "acs-authentication"
        name "local"
        pretty_name "Local"
        aliases {
            CanChangePassword auth::local::password::CanChangePassword
            ChangePassword auth::local::password::ChangePassword
            CanRetrievePassword auth::local::password::CanRetrievePassword
            RetrievePassword auth::local::password::RetrievePassword
            CanResetPassword auth::local::password::CanResetPassword
            ResetPassword auth::local::password::ResetPassword
            GetParameters auth::local::password::GetParameters
        }
    }
    return [acs_sc::impl::new_from_spec -spec $spec]
}

ad_proc -private auth::local::password::unregister_impl {} {
    Unregister the 'local' implementation of the 'auth_password' service contract.
} {
    acs_sc::impl::delete -contract_name "auth_password" -impl_name "local"
}


d_proc -private auth::local::password::CanChangePassword {
    {parameters ""}
} {
    Implements the CanChangePassword operation of the auth_password
    service contract for the local account implementation.
} {
    # Yeah, we can change your password
    return 1
}

d_proc -private auth::local::password::CanRetrievePassword {
    {parameters ""}
} {
    Implements the CanRetrievePassword operation of the auth_password
    service contract for the local account implementation.
} {
    # passwords are stored hashed, so we send the hash and let the user choose a new password
    return 1
}

d_proc -private auth::local::password::CanResetPassword {
    {parameters ""}
} {
    Implements the CanResetPassword operation of the auth_password
    service contract for the local account implementation.
} {
    # Yeah, we can reset for you.
    return 1
}

d_proc -private auth::local::password::ChangePassword {
    username
    new_password
    {old_password ""}
    {parameters {}}
    {authority_id {}}
} {
    Implements the ChangePassword operation of the auth_password
    service contract for the local account implementation.
} {
    array set result {
        password_status {}
        password_message {}
    }

    set user_id [acs_user::get_by_username -authority_id $authority_id -username $username]
    if { $user_id eq "" } {
        set result(password_status) "no_account"
        return [array get result]
    }

    if { $old_password ne "" } {
        if { ![ad_check_password $user_id $old_password] } {
            set result(password_status) "old_password_bad"
            return [array get result]
        }
    }

    ad_try {
        ad_change_password $user_id $new_password
    } on error {errorMsg} {
        set result(password_status) "change_error"
        ad_log Error "Error changing local password for username $username, user_id $user_id: $errorMsg"
        return [array get result]
    }

    set result(password_status) "ok"

    if { [parameter::get -parameter EmailAccountOwnerOnPasswordChangeP -package_id [ad_acs_kernel_id] -default 1] } {
        ad_try {
            set user_id [acs_user::get_by_username \
                             -username $username \
                             -authority_id $authority_id]
            set user_email [party::get -party_id $user_id -element email]

            set system_name [ad_system_name]
            set pvt_home_name [ad_pvt_home_name]
            set password_update_link_text [_ acs-subsite.Change_my_Password]

            if { [auth::UseEmailForLoginP] } {
                set account_id_label [_ acs-subsite.Email]
                set account_id $user_email
            } else {
                set account_id_label [_ acs-subsite.Username]
                set account_id [acs_user::get_user_info \
                                    -user_id $user_id \
                                    -element username]
            }

            set subject [_ acs-subsite.Password_changed_subject]
            set body [_ acs-subsite.Password_changed_body]

            acs_mail_lite::send \
                -send_immediately \
                -to_addr $user_email \
                -from_addr [ad_outgoing_sender] \
                -subject $subject \
                -body $body
        } on error {errorMsg} {
            ad_log Error "Error sending out password changed notification to account owner with user_id $user_id, email $user_email: $errorMsg"
        }
    }

    return [array get result]
}

d_proc -private auth::local::password::RetrievePassword {
    username
    parameters
} {
    Implements the RetrievePassword operation of the auth_password
    service contract for the local account implementation.
} {
    set result(password_status) "ok"
    set result(password_message) [_ acs-subsite.Request_Change_Password_token_email]

    db_1row get_usr_id_and_password_hash {SELECT user_id, password as password_hash FROM users WHERE username = :username}

    set email [party::email -party_id $user_id]
    # TODO: This email message text should go in the recipient user language, english or every language supported
    set subject "[ad_system_name]: [_ acs-subsite.change_password_email_subject] $username"
    set body "[_ acs-subsite.change_password_email_body_0]\n\n[export_vars -base "[ad_url]/user/password-reset" {user_id password_hash}]\n\n[_ acs-subsite.change_password_email_body_1]"

    acs_mail_lite::send \
        -send_immediately \
        -to_addr $email \
        -from_addr [ad_outgoing_sender] \
        -subject $subject \
        -body $body

    return [array get result]
}

d_proc -private auth::local::password::ResetPassword {
    username
    parameters
    {authority_id {}}
} {
    Implements the ResetPassword operation of the auth_password
    service contract for the local account implementation.
} {
    array set result {
        password_status ok
        password_message {}
    }

    set user_id [acs_user::get_by_username -authority_id $authority_id -username $username]
    if { $user_id eq "" } {
        set result(password_status) "no_account"
        return [array get result]
    }

    # Reset the password
    set password [ad_generate_random_string]

    ad_change_password $user_id $password

    # We return the new password here and let the OpenACS framework send the email with the new password
    set result(password) $password
    return [array get result]
}

ad_proc -private auth::local::password::GetParameters {} {
    Implements the GetParameters operation of the auth_password
    service contract for the local account implementation.
} {
    # No parameters
    return [list]
}


#####
#
# auth::local::register
#



#####
#
# The 'auth_registration' service contract implementation
#

ad_proc -private auth::local::registration::register_impl {} {
    Register the 'local' implementation of the 'auth_registration' service contract.

    @return impl_id of the newly created implementation.
} {
    set spec {
        contract_name "auth_registration"
        owner "acs-authentication"
        name "local"
        pretty_name "Local"
        aliases {
            GetElements auth::local::registration::GetElements
            Register auth::local::registration::Register
            GetParameters auth::local::registration::GetParameters
        }
    }
    return [acs_sc::impl::new_from_spec -spec $spec]
}

ad_proc -private auth::local::registration::unregister_impl {} {
    Unregister the 'local' implementation of the 'auth_register' service contract.
} {
    acs_sc::impl::delete -contract_name "auth_registration" -impl_name "local"
}

d_proc -private auth::local::registration::GetElements {
    {parameters ""}
} {
    Implements the GetElements operation of the auth_registration
    service contract for the local account implementation.
} {
    set result(required) {}
    if { ![auth::UseEmailForLoginP] } {
        set result(required) username
    }

    lappend result(required) email first_names last_name
    set result(optional) { url }

    if { ![parameter::get -package_id [ad_conn subsite_id] -parameter RegistrationProvidesRandomPasswordP -default 0] } {
        lappend result(optional) password
    }

    if { [parameter::get -package_id [ad_acs_kernel_id] -parameter RequireQuestionForPasswordResetP -default 0] &&
         [parameter::get -package_id [ad_acs_kernel_id] -parameter UseCustomQuestionForPasswordReset -default 0] } {
        lappend result(required) secret_question secret_answer
    }

    return [array get result]
}

d_proc -private auth::local::registration::Register {
    parameters
    username
    authority_id
    first_names
    last_name
    screen_name
    email
    url
    password
    secret_question
    secret_answer
} {
    Implements the Register operation of the auth_registration
    service contract for the local account implementation.
} {
    array set result {
        creation_status "ok"
        creation_message {}
        element_messages {}
        account_status "ok"
        account_message {}
        generated_pwd_p 0
        password {}
    }

    # We don't create anything here, so creation always succeeds
    # And we don't check local account, either

    set subsite_id [ad_conn subsite_id]

    # LARS TODO: Move this out of the local driver and into the auth framework
    # Generate random password?
    set generated_pwd_p 0
    if { $password eq ""
         || [parameter::get \
                 -package_id $subsite_id \
                 -parameter RegistrationProvidesRandomPasswordP \
                 -default 0]
     } {
        set password [ad_generate_random_string]
        set generated_pwd_p 1
    }
    set result(generated_pwd_p) $generated_pwd_p
    set result(password) $password

    # Set user's password
    set user_id [acs_user::get_by_username -authority_id $authority_id -username $username]
    ad_change_password $user_id $password

    # Used in messages below
    set system_name [ad_system_name]
    set system_url [ad_url]

    # LARS TODO: Move this out of the local driver and into the auth framework
    # Send password confirmation email to user
    if { [parameter::get \
              -parameter EmailRegistrationConfirmationToUserP \
              -package_id $subsite_id -default 1] } {
        ad_try {
            auth::password::email_password \
                -username $username \
                -authority_id $authority_id \
                -password $password \
                -from [parameter::get \
                           -parameter NewRegistrationEmailAddress \
                           -package_id $subsite_id \
                           -default [ad_system_owner]] \
                -subject_msg_key "acs-subsite.email_subject_Registration_password" \
                -body_msg_key "acs-subsite.email_body_Registration_password"
        } on error {errorMsg} {
            # We don't fail hard here, just log an error
            ad_log Error "Error sending registration confirmation to $email: $errorMsg"
        }
    }

    # LARS TODO: Move this out of the local driver and into the auth framework
    # Notify admin on new registration
    if { [parameter::get \
              -parameter NotifyAdminOfNewRegistrationsP \
              -package_id $subsite_id \
              -default 0] } {
        ad_try {
            set admin_email [parameter::get \
                                 -parameter NewRegistrationEmailAddress \
                                 -package_id $subsite_id \
                                 -default [ad_system_owner]]
            set admin_id [party::get_by_email -email $admin_email]
            if { $admin_id eq "" } {
                set admin_locale [lang::system::site_wide_locale]
            } else {
                set admin_locale [lang::user::locale -user_id $admin_id]
            }

            set system_url [ad_url]

            acs_mail_lite::send \
                -send_immediately \
                -to_addr $admin_email \
                -from_addr [ad_outgoing_sender] \
                -subject [lang::message::lookup $admin_locale acs-subsite.lt_New_registration_at_s] \
                -body [lang::message::lookup $admin_locale acs-subsite.lt_first_names_last_name]
        } on error {errorMsg} {
            # We don't fail hard here, just log an error
            ad_log Error "Error sending admin notification to $admin_email: $errorMsg"
        }
    }

    return [array get result]
}

ad_proc -private auth::local::registration::GetParameters {} {
    Implements the GetParameters operation of the auth_registration
    service contract for the local account implementation.
} {
    # No parameters
    return [list]
}

#####
#
# The 'auth_user_info' service contract implementation
#

ad_proc -private auth::local::user_info::register_impl {} {
    Register the 'local' implementation of the 'auth_user_info' service contract.

    @return impl_id of the newly created implementation.
} {
    set spec {
        contract_name "auth_user_info"
        owner "acs-authentication"
        name "local"
        pretty_name "Local"
        aliases {
            GetUserInfo auth::local::user_info::GetUserInfo
            GetParameters auth::local::user_info::GetParameters
        }
    }
    return [acs_sc::impl::new_from_spec -spec $spec]
}

ad_proc -private auth::local::user_info::unregister_impl {} {
    Unregister the 'local' implementation of the 'auth_user_info' service contract.
} {
    acs_sc::impl::delete -contract_name "auth_user_info" -impl_name "local"
}

d_proc -private auth::local::user_info::GetUserInfo {
    username
    {parameters ""}
} {
    Implements the GetUserInfo operation of the auth_user_info
    service contract for the local account implementation.
} {
    set user_id [acs_user::get_by_username -username $username]
    set result(info_status) [auth::get_local_account_status -user_id $user_id]
    set result(info_message) ""
    db_1row get_user_info {} -column_array user_info
    set result(user_info) [array get user_info]

    return [array get result]
}

ad_proc -private auth::local::user_info::GetParameters {} {
    Implements the GetParameters operation of the auth_user_info
    service contract for the local account implementation.
} {
    # No parameters
    return [list]
}

d_proc -private auth::local::search::Search {
    search_text
    {parameters ""}
} {
    Implements the Search operation of the auth_search
    service contract for the local account implementation.
} {
    set authority_id [auth::authority::local]
    return [db_list user_search {
        select distinct username
        from   cc_users u
        where  authority_id = :authority_id
               and upper(coalesce(u.first_names || ' ', '')  ||
                         coalesce(u.last_name || ' ', '') ||
                         u.email || ' ' ||
                         u.username || ' ' ||
                         coalesce(u.screen_name, '')) like upper('%'||:search_text||'%')
        order  by username
    }]
}

ad_proc -private auth::local::search::GetParameters {} {
    Implements the GetParameters operation of the auth_search
    service contract for the local account implementation.
} {
    # No parameters
    return [list]
}

# Local variables:
#    mode: tcl
#    tcl-indent-level: 4
#    indent-tabs-mode: nil
# End: