Class ::xo::Authorize (public)

 ::nx::Class ::xo::Authorize[i]

Defined in packages/xooauth/tcl/authorize-procs.tcl

Base class to support OAuth authorization API

Testcases:
No testcase defined.
Source code:
        :property {pretty_name}
        :property {base_url}
        :property {responder_url}

        :property {after_successful_login_url /pvt/}
        :property {login_failure_url /}

        :property {create_not_registered_users:switch false}
        :property {create_with_dotlrn_role ""}

        :property {scope}
        :property {debug:switch false}

        :method qualified {partial_url} {
            return [util_current_location]$partial_url
        }

        :method encoded_state {{-return_url ""}} {
            set state [::xo::oauth::nonce]
            append state . [ns_base64urlencode $return_url]
            return $state
        }

        :method decoded_state {state} {
            lassign [split $state .] nonce encoded_url
            return [list  nonce $nonce  return_url [ns_base64urldecode $encoded_url]]
        }


        :public method login_url {
            {-return_url ""}
            {-login}
        } {
            #
            # Returns the URL for log-in
            #
            set base ${:base_url}/authorize
            set client_id ${:client_id}
            set scope ${:scope}
            set state [:encoded_state -return_url $return_url]
            set redirect_uri [:qualified ${:responder_url}]

            return [export_vars -no_empty -base $base {
                client_id redirect_uri state scope login
            }]
        }

        :method redeem_code {code} {
            set client_id ${:client_id}
            set client_secret ${:client_secret}
            set redirect_uri [:qualified ${:responder_url}]
            set url [export_vars -no_empty -base ${:base_url}/access_token {
                client_id client_secret code redirect_uri
            }]
            set data [ns_http run $url]
            if {[dict get $data status] ne 200} {
                dict set data error oacs-cant_redeem_code
                dict set data error_description $data
            } else {
                set form_data [ns_set array [ns_parsequery [dict get $data body]]]
                ns_log notice "[self] redeem_code formdata has keys: [lsort [dict keys $form_data]]"
                if {![dict exists $form_data access_token]} {
                    if {[dict exists $form_data error]} {
                        dict set data error [dict get $form_data error]
                        dict set data error_description [dict get $form_data error_description]
                    } else {
                        dict set data error oacs-no_access_token
                        dict set data error_description $form_data
                    }
                } else {
                    dict set data access_token [dict get $form_data access_token]
                }
            }
            ns_log notice "[self] redeem_code returns $data"
            return $data
        }

        :public method logout {} {
            #
            # Perform logout operation from oauth in the background
            # (i.e. without a redirect) when the logout_url is
            # nonempty.
            #
            set url [:logout_url]
            if {$url ne ""} {
                ns_http run $url
            }
        }

        :public method name {} {
            #
            # @return instance name
            #
            return [expr {[info exists :pretty_name]
                          ? ${:pretty_name}
                          : [namespace tail [self]]}]
        }


        :method lookup_user_id {-email} -returns integer {
            set user_id [party::get_by_email -email [string tolower $email]]
            if {$user_id eq ""} {
                #
                # Here one could do some more checks or alternative lookups
                #
            }
            return [expr {$user_id eq "" ? 0 : $user_id}]
        }

        :method required_fields {} {
            return [expr {${:create_not_registered_users}
                          ? "email given_name family_name"
                          : "email"}]
        }

        :method get_required_fields {
            {-claims:required}
            {-mapped_fields:required}
        } {
            #
            # Check, if required fields are provided in the claims and
            # perform the name mapping between what was provided from
            # the identity provided and what we need in OpenACS.
            #
            set result ""

            set fields {}
            foreach pair $mapped_fields {
                lassign $pair field target
                if {[dict exists $claims $field]} {
                    dict set fields $target [dict get $claims $field]
                }
            }
            dict set result fields $fields
            foreach field [:required_fields] {
                if {![dict exists $fields $field]
                    || [dict get $fields $field] in {"" "null"}
                } {
                    set not_enough_data $field
                    break
                }
            }

            if {[info exists not_enough_data]} {
                ns_log warning "[self] get_user_data: not enough data:"  $not_enough_data "is missing"  "($field -> $target, claims: $claims)"
                dict set result error oacs-not_enough_data
            }
            return $result
        }

        :method record_oauth_registration {user_id} {
            #
            # Record the fact that this user_id was created via an
            # OAuth identity provider.
            #
            set auth_obj [self]
            db_dml _ {
                INSERT INTO xooauth_authorized_users (user_id, auth_obj)
                VALUES (:user_id, :auth_obj)
            }
        }

        :method register_new_user {
            {-first_names}
            {-last_name}
            {-email}
        } -returns integer {
            #
            # Register the user and return the user_id. In case, the
            # registration of the new user fails, raise an exception.
            #
            # not tested
            #
            db_transaction {
                set user_info(first_names) $first_names
                set user_info(last_name) $last_name
                if {![util_email_unique_p $email]} {
                    error "Email is not unique: $email"
                }
                set user_info(email) $email
                array set creation_info [auth::create_local_account  -authority_id [auth::authority::local]  -username $email  -array user_info]
                if {$creation_info(creation_status) ne "ok"} {
                    set errorMsg ""
                    error [append errorMsg "Error when creating user: "  $creation_info(creation_status) " "  $creation_info(element_messages)]
                }

                set user_id $creation_info(user_id)
                :record_oauth_registration $user_id

                if {[apm_package_installed_p dotlrn] && ${:create_with_dotlrn_role} ne ""} {
                    #
                    # We have DotLRN installed, and we want to create
                    # for this register object the new users in the
                    # provided role. Note that one can define
                    # different instances of this class behaving
                    # differently.
                    #
                    dotlrn::user_add  -type ${:create_with_dotlrn_role}  -can_browse=1  -id $email  -user_id $user_id

                    ::permission::grant  -party_id $user_id  -object_id [dotlrn::get_package_id]  -privilege read_private_data
                }
            } on_error {
                ns_log error "OAuth Login (Error during user creation): $errmsg ($email)"
                error "OAuth user creation failed: $errmsg"
            }
            return $user_id
        }

        :public method perform_login {-token {-state ""}} {
            #
            # Get the provided claims from the identity provider and
            # perform an OpenACS login, when the user exists.
            #
            # In case the user does not exist, create it optionally (when
            # "create_not_registered_users" is activated.
            #
            # When the user is created, and dotlrn is installed, the
            # new user might be added optionally as a dotlrn user with
            # the role as specified in "create_with_dotlrn_role".
            #
            set data [:get_user_data -token $token]
            if {[dict exists $data error]} {
                #
                # There was already an error in the steps leading to
                # this.
                #
                ns_log warning "[self] OAuth login failed:"  [dict get $data error] "\n$data"

            } elseif {![dict exists $data email]} {
                #
                # No error and no email in result... actually, this
                # should not happen.
                #
                dict set data error oacs-no_email_in_result
                ns_log warning "OAuth login failed strangely: "  [dict get $data error] "\n$data"

            } else {
                dict set data decoded_state [:decoded_state $state]
                set user_id [:lookup_user_id -email [dict get $data email]]
                if {!${:debug}
                    && $user_id == 0
                    && ${:create_not_registered_users}
                } {
                    try {
                        :register_new_user  -first_names [dict get $data given_name]  -last_name [dict get $data family_name]  -email [dict get $data email]
                    } on ok {result} {
                        set user_id $result
                    } on error {errorMsg} {
                        dict set data error oacs-register_failed
                        dict set data error_description $errorMsg
                    }
                }
                dict set data user_id $user_id
                if {$user_id != 0} {
                    #
                    # The lookup of the user_id was successful. We can
                    # login as this user.... but only, when no "debug"
                    # is activated.
                    #
                    if {!${:debug}} {
                        ad_user_login -external_registry [self$user_id
                    }
                } else {
                    #
                    # For the time being, just report data back to the
                    # calling script.
                    #
                    dict set data error "oacs-no_such_user"
                }
            }
            return $data
        }
XQL Not present:
Generic, PostgreSQL, Oracle
[ hide source ] | [ make this the default ]
Show another procedure: