Index: openacs-4/packages/xooauth/xooauth.info =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/xooauth/xooauth.info,v diff -u -r1.1.2.8 -r1.1.2.9 --- openacs-4/packages/xooauth/xooauth.info 3 May 2023 12:41:51 -0000 1.1.2.8 +++ openacs-4/packages/xooauth/xooauth.info 8 May 2023 17:37:52 -0000 1.1.2.9 @@ -9,7 +9,7 @@ f f - + Michael Aram XOTcl based OAuth implementation and OAuth based REST interfaces for OpenACS @@ -28,7 +28,7 @@ 0 - + Fisheye: Tag 1.1 refers to a dead (removed) revision in file `openacs-4/packages/xooauth/tcl/authorize-procs.tcl'. Fisheye: No comparison available. Pass `N' to diff? Index: openacs-4/packages/xooauth/tcl/ms-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/xooauth/tcl/ms-procs.tcl,v diff -u -r1.1.2.16 -r1.1.2.17 --- openacs-4/packages/xooauth/tcl/ms-procs.tcl 7 May 2023 15:22:56 -0000 1.1.2.16 +++ openacs-4/packages/xooauth/tcl/ms-procs.tcl 8 May 2023 17:37:52 -0000 1.1.2.17 @@ -1087,16 +1087,12 @@ # https://learn.microsoft.com/en-us/azure/active-directory/develop/id-tokens ########################################################### - nx::Class create Authorize -superclasses ::xo::REST { + nx::Class create ::ms::Authorize -superclasses ::xo::Authorize { + :property {pretty_name "Azure"} :property {tenant} + :property {version ""} :property {responder_url /oauth/azure-login-handler} - :property {after_successful_login_url /} - :property {login_failure_url /} - - :property {create_not_registered_users:switch false} - :property {create_with_dotlrn_role ""} - :property {version ""} :property {scope {openid offline_access profile}} :property {response_type {code id_token}} @@ -1116,7 +1112,7 @@ # here: # https://learn.microsoft.com/en-us/azure/active-directory/develop/id-tokens # - if {${:version} eq ""} { + if {${:version} in {"" "v1.0"}} { set base https://login.microsoftonline.com/common/oauth2/authorize } else { # @@ -1161,27 +1157,28 @@ # Perform logout operation form MS in the background # (i.e. without a redirect). # - ns_http run [ms::azure logout_url] + ns_http run [:logout_url] } - :method get_user_data {{required_fields {upn family_name given_name}}} { + :method get_user_data { + -token + } { # - # Get data from the query variables "id_token", "error" - # and "token". In case of an error or incomplete data, - # add this information the result dict. Per default, we - # require "upn" (user principal name), "family_name" and - # "given_name". See here for AD claim sets + # Get data via the provided token (which comes from the + # "id_token"). In case of an error or incomplete data, + # add this information the result dict. + # + # See here for AD claim sets # https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-optional-claims # - # # The error codes returned by Azure are defined here: # https://learn.microsoft.com/en-us/azure/active-directory/develop/reference-error-codes # Extra errors for OpenACS are prefixed with "oacs-" # # @return return a dict containing the extracted fields set result {} - lassign [split [ns_queryget id_token] .] jwt_header jwt_claims jwt_signature + lassign [split $token .] jwt_header jwt_claims jwt_signature #ns_log notice "[self]: jwt_header <[:json_to_dict [encoding convertfrom "utf-8" [ns_base64decode -binary -- $jwt_header]]]>" @@ -1190,147 +1187,25 @@ return $result } - set claims [:json_to_dict [encoding convertfrom "utf-8" [ns_base64decode -binary -- $jwt_claims]]] + set claims [:json_to_dict \ + [encoding convertfrom "utf-8" \ + [ns_base64decode -binary -- $jwt_claims]]] dict set result claims $claims - foreach field $required_fields { - if {![dict exists $claims $field] || [dict get $claims $field] eq ""} { - set not_enough_data $field - break - } - } - if {[info exists not_enough_data]} { - dict set result error oacs-not_enough_data - } - return $result - } - - :method lookup_user_id {data} -returns integer { - set email [dict get $data claims upn] - 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 register_new_user {data} -returns integer { - # - # Register the user and return the user_id. In case, the - # registration of the new user failes, raise an exception. - # - # not tested - # - db_transaction { - set user_info(first_names) [dict get $data given_name] - set user_info(last_name) [dict get $data family_name] - if {![util_email_unique_p [dict get $data upn]]} { - error "Email is not unique: [dict get $data upn]" - } - set user_info(email) [dict get $data upn] - array set creation_info [auth::create_local_account \ - -authority_id [auth::authority::local] \ - -username [dict get $data upn] \ - -array user_info] - if {$creation_info(creation_status) ne "ok"} { - error "Error when creating user: $creation_info(creation_status) $creation_info(element_messages)" - } - set user_id $creation_info(user_id) - set email [dict get $data upn] - #db_dml _ "INSERT INTO azure_users VALUES (:user_id)" - #db_dml _ "INSERT INTO azure_user_mails (user_id, email) VALUES (:user_id, :email)" - - 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 - - acs_privacy::set_user_read_private_data \ - -user_id $user_id \ - -object_id [dotlrn::get_package_id] \ - -value 1 - } - } on_error { - ns_log error "Azure Login (Error during user creation): $errmsg ($email)" - # - # logout the user from Azure and report an error message. - # - # For the time being the error is reported via - # util_user_message. We could as well define an extra - # error page and pass the error message and/or some - # error code to it. - # - error "Azure user creation failed: $errmsg" - } - return $user_id - } - - :public method perform_login {} { - # - # Get the provided claims from the Azure response 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 for this - # object). - # - # 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] + set data [:get_required_fields \ + -claims $claims \ + -required_fields { + {upn email} + {family_name last_name} + {given_name first_names} + }] if {[dict exists $data error]} { - ns_log warning "Azure login failed: [dict get $data error]\n$data" - #util_user_message -message "Azure login failed: [dict get $data error]" - #ad_returnredirect ${:login_failure_url} - #ad_script_abort + set result [dict merge $data $result] } else { - set user_id [:lookup_user_id $data] - if {$user_id == 0 && ${:create_not_registered_users}} { - try { - :register_new_user $data - } 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} { - # - # Still to do: - # - Sync/check validity of the token vs. the validity - # of the login cookie. - # - maybe set an extra cookie to perform token refresh - # from Azure when the login expires. - # - ad_user_login -external_registry [self] $user_id - #ad_returnredirect ${:after_successful_login_url} - #ad_script_abort - - } else { - # - # For the time being, just report data back to the - # calling script. - # - dict set data error "oacs-no_such_user" - } + set result [dict merge $result [dict get $data fields]] } - return $data + return $result } - } # Index: openacs-4/packages/xooauth/www/azure-login-handler.adp =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/xooauth/www/azure-login-handler.adp,v diff -u -r1.1.2.2 -r1.1.2.3 --- openacs-4/packages/xooauth/www/azure-login-handler.adp 4 May 2023 17:09:03 -0000 1.1.2.2 +++ openacs-4/packages/xooauth/www/azure-login-handler.adp 8 May 2023 17:37:52 -0000 1.1.2.3 @@ -1,11 +1,11 @@ @title;literal@ -

Azure Interface

+

@name@ Authorization

@@ -25,4 +25,4 @@
 @cooked_data@
 
-
\ No newline at end of file + Index: openacs-4/packages/xooauth/www/azure-login-handler.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/xooauth/www/azure-login-handler.tcl,v diff -u -r1.1.2.2 -r1.1.2.3 --- openacs-4/packages/xooauth/www/azure-login-handler.tcl 4 May 2023 17:09:03 -0000 1.1.2.2 +++ openacs-4/packages/xooauth/www/azure-login-handler.tcl 8 May 2023 17:37:52 -0000 1.1.2.3 @@ -1,27 +1,47 @@ -set title "Azure Interface" +ad_page_contract { + Azure landing page for OAuth authorization + + @author Gustaf Neumann +} + +set auth_obj ::ms::azure + +if {![nsf::is object $auth_obj]} { + set error "Authorization object '$auth_obj' was not configured" + set name Azure + set title "$name Authorization" + return +} + set swa_p [acs_user::site_wide_admin_p] +set name [$auth_obj name] +set title "$name Authorization" -set login_url [ms::azure login_url] -set logout_url [ms::azure logout_url] +set login_url [$auth_obj login_url] +set logout_url [$auth_obj logout_url] +set data "" if {[ns_queryget id_token] ne ""} { - set data [ms::azure perform_login] + set data [$auth_obj perform_login -token [ns_queryget id_token]] +} - if {[dict exists $data user_id] && [dict get $data user_id] > 0} { - # - # Login was performed, just redirect to the right place. - # - set redirect_url [ns_queryget state \ - [ms::azure cget -after_successful_login_url]] - if {[string range $redirect_url 0 0] eq "/"} { - ad_returnredirect $redirect_url - } else { - ns_log warning "Azure redirect URL looks suspicious: '$redirect_url'" - } - ad_script_abort +if {![$auth_obj cget -debug] + && [dict exists $data user_id] + && [dict get $data user_id] > 0 +} { + # + # Login was performed, just redirect to the right place. + # + # We can use "state" on Azure as redirect URL (since it has a + # nonce) + set redirect_url [ns_queryget state \ + [$auth_obj cget -after_successful_login_url]] + if {[string range $redirect_url 0 0] eq "/"} { + ad_returnredirect $redirect_url + } else { + ns_log warning "Azure redirect URL looks suspicious: '$redirect_url'" } -} else { - set data "" + ad_script_abort } if {1 || $swa_p} { @@ -35,10 +55,11 @@ if {[dict exists $data claims]} { set claims [dict get $data claims] + dict unset data claims } - set form_data [ns_set array [ns_conn form]] - set cooked_data [join [lmap {k v} $form_data { set _ "$k: $v" }] "\n"] + set data [dict merge $data [ns_set array [ns_conn form]]] + set cooked_data [join [lmap {k v} $data { set _ "$k: $v" }] "\n"] } if {[dict exists $data error]} { @@ -47,7 +68,7 @@ # error cases. # #ad_returnredirect -allow_complete_url [:logout_url] - ns_http run [ms::azure logout_url] + $auth_obj logout set error [dict get $data error] } Fisheye: Tag 1.1 refers to a dead (removed) revision in file `openacs-4/packages/xooauth/www/github-login-handler.adp'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 1.1 refers to a dead (removed) revision in file `openacs-4/packages/xooauth/www/github-login-handler.tcl'. Fisheye: No comparison available. Pass `N' to diff?