diff --git a/BrainPortal/app/controllers/sessions_controller.rb b/BrainPortal/app/controllers/sessions_controller.rb index 8410ca194..a12442ec1 100644 --- a/BrainPortal/app/controllers/sessions_controller.rb +++ b/BrainPortal/app/controllers/sessions_controller.rb @@ -45,7 +45,8 @@ def new #:nodoc: @browser_version = ua.browser_version || "(unknown browser version)" @globus_uri = globus_login_uri(globus_url) # can be nil - + @oidc_client = (RemoteResource.current_resource.meta[:oidc_client] || "Globus").capitalize + respond_to do |format| format.html format.any { head :unauthorized } diff --git a/BrainPortal/app/controllers/users_controller.rb b/BrainPortal/app/controllers/users_controller.rb index bc35092c1..d62d158f8 100644 --- a/BrainPortal/app/controllers/users_controller.rb +++ b/BrainPortal/app/controllers/users_controller.rb @@ -75,7 +75,8 @@ def index #:nodoc: # GET /user/1.xml # GET /user/1.json def show #:nodoc: - @user = User.find(params[:id]) + @user = User.find(params[:id]) + @oidc_client = (RemoteResource.current_resource.meta[:oidc_client] || "Globus").capitalize cb_error "You don't have permission to view this user.", :redirect => start_page_path unless edit_permission?(@user) diff --git a/BrainPortal/app/views/sessions/new.html.erb b/BrainPortal/app/views/sessions/new.html.erb index 076fb4980..2acab054b 100644 --- a/BrainPortal/app/views/sessions/new.html.erb +++ b/BrainPortal/app/views/sessions/new.html.erb @@ -79,10 +79,10 @@ OR - <%= link_to 'Sign In With Globus', @globus_uri, :class => 'button globus_button' %> + <%= link_to "Sign In With #{@oidc_client}", @globus_uri, :class => 'button globus_button' %>
(Only available if you have already linked your
- CBRAIN account to a Globus identity) + CBRAIN account to a <%= @oidc_client %> identity)
diff --git a/BrainPortal/app/views/users/show.html.erb b/BrainPortal/app/views/users/show.html.erb index 03546fded..7da7138f8 100644 --- a/BrainPortal/app/views/users/show.html.erb +++ b/BrainPortal/app/views/users/show.html.erb @@ -118,10 +118,10 @@ <% end %> - <% t.edit_cell('meta[allowed_globus_provider_names]', :header => 'Forced Globus Identity Providers', :content => @user.meta['allowed_globus_provider_names'] || '') do %> + <% t.edit_cell('meta[allowed_globus_provider_names]', :header => "Forced #{@oidc_client} Identity Providers", :content => @user.meta['allowed_globus_provider_names'] || '') do %> <%= text_field_tag "meta[allowed_globus_provider_names]", @user.meta['allowed_globus_provider_names'], :size => 40 %>
- If set, must be a list of Globus identity provider names separated by commas. A single '*' is + If set, must be a list of <%= @oidc_client %> identity provider names separated by commas. A single '*' is also allowed to mean any provider name.
<% end %> @@ -172,22 +172,22 @@ %> <% if @globus_uri %> - <% t.cell "Globus Provider", :show_width => 2 do %> + <% t.cell "#{@oidc_client} Provider", :show_width => 2 do %> <% if prov_id %> Provider name: <%= prov_name %>
Provider user: <%= prov_user %>
<% if @user.id == current_user.id # show button on user own page, hide on other users pages %> - <%= link_to('Unlink this Globus identity', unlink_globus_path, + <%= link_to("Unlink this #{@oidc_client} identity", unlink_globus_path, :class => "button", :method => :post, - :data => { :confirm => "Are you sure you want to unlink your account with this Globus identity?" } + :data => { :confirm => "Are you sure you want to unlink your account with this #{@oidc_client} identity?" } ) %> <% end %> <% else %> - (No Globus identity linked to your account)
+ (No <%= @oidc_client %> identity linked to your account)
<% if @user.id == current_user.id # Globus button works only on user own account %> - <%= link_to 'Link a Globus identity', @globus_uri, :class => 'button globus_button' %> + <%= link_to "Link a #{@oidc_client} identity", @globus_uri, :class => 'button globus_button' %> <% end %> <% end %> <% end %> diff --git a/BrainPortal/lib/globus_helpers.rb b/BrainPortal/lib/globus_helpers.rb index ca159df93..c41f81f13 100644 --- a/BrainPortal/lib/globus_helpers.rb +++ b/BrainPortal/lib/globus_helpers.rb @@ -25,25 +25,36 @@ module GlobusHelpers Revision_info=CbrainFileRevision[__FILE__] #:nodoc: - # GLOBUS authentication URL constants + # OIDC authentication URL constants # Maybe should be made configurable. - GLOBUS_AUTHORIZE_URI = "https://auth.globus.org/v2/oauth2/authorize" # will be issued a GET with params - GLOBUS_TOKEN_URI = "https://auth.globus.org/v2/oauth2/token" # will be issued a POST with a single code - GLOBUS_LOGOUT_URI = "https://auth.globus.org/v2/web/logout" # for pages that provide this link - # Returns the URI to send users to the GLOBUS authentication page. + # will be issued a GET with params + GLOBUS_AUTHORIZE_URI = RemoteResource.current_resource.meta[:oidc_authorize_uri] || "https://auth.globus.org/v2/oauth2/authorize" + # will be issued a POST with a single code + GLOBUS_TOKEN_URI = RemoteResource.current_resource.meta[:oidc_token_uri] || "https://auth.globus.org/v2/oauth2/token" + # for pages that provide this link + GLOBUS_LOGOUT_URI = RemoteResource.current_resource.meta[:oidc_logout_uri] || "https://auth.globus.org/v2/web/logout" + + + # Define OIDC scope + $scope,$oidc_name = (RemoteResource.current_resource.meta[:oidc_client] || "").casecmp?("keycloak") ? + ["openid email profile", "Keycloak"] : + ["urn:globus:auth:scope:auth.globus.org:view_identities openid email profile", "Globus"] + + + # Returns the URI to send users to the OIDC authentication page. # The parameter globus_action_url should be the URL to the controller # action here in CBRAIN that will received the POST response. def globus_login_uri(globus_action_url) return nil if api_request? return nil unless globus_auth_configured? - # Create the URI to authenticate with GLOBUS + # Create the URI to authenticate with OIDC globus_params = { :client_id => globus_client_id, :response_type => 'code', - :scope => "urn:globus:auth:scope:auth.globus.org:view_identities openid email profile", - :redirect_uri => globus_action_url, # generated from Rails routes + :scope => $scope, + :redirect_uri => globus_action_url, # generated from Rails routes :state => globus_current_state, # method is below } GLOBUS_AUTHORIZE_URI + '?' + globus_params.to_query @@ -54,9 +65,9 @@ def globus_logout_uri end def globus_fetch_token(code, globus_action_url) - # Query Globus; this returns all the info we need at the same time. + # Query OIDC; this returns all the info we need at the same time. auth_header = globus_basic_auth_header # method is below - response = Typhoeus.post(GLOBUS_TOKEN_URI, + response = Typhoeus.post(GLOBUS_TOKEN_URI, :body => { :code => code, :redirect_uri => globus_action_url, @@ -75,7 +86,7 @@ def globus_fetch_token(code, globus_action_url) return identity_struct rescue => ex - Rails.logger.error "GLOBUS token request failed: #{ex.class} #{ex.message}" + Rails.logger.error "#{$oidc_name} token request failed: #{ex.class} #{ex.message}" return nil end @@ -90,14 +101,14 @@ def globus_basic_auth_header end # Returns a string that should stay constants during the entire - # globus negotiations. The current Rails session_id, encoded, will do + # OIDC negotiations. The current Rails session_id, encoded, will do # the trick. The Rails session is maintained by a cookie already # created and maintained, at this point. def globus_current_state Digest::MD5.hexdigest( request.session_options[:id] ) end - # Return the registered globus endpoint client ID. + # Return the registered OIDC endpoint client ID. # This value must be configured by the CBRAIN admin # in the meta data of the portal. Returns nil if unset. def globus_client_id @@ -105,7 +116,7 @@ def globus_client_id myself.meta[:globus_client_id].presence.try(:strip) end - # Return the registered globus endpoint client secret. + # Return the registered OIDC endpoint client secret. # This value must be configured by the CBRAIN admin # in the meta data of the portal. Returns nil if unset. def globus_client_secret @@ -114,7 +125,7 @@ def globus_client_secret end # Returns true if the CBRAIN system is configured for - # globus auth. + # OIDC auth. def globus_auth_configured? myself = RemoteResource.current_resource site_uri = myself.site_url_prefix.presence @@ -125,43 +136,43 @@ def globus_auth_configured? true end - # Record the globus identity for the current user. + # Record the OIDC identity for the current user. # (This maybe should be made into a User model method) def record_globus_identity(user, globus_identity) # In the case where a user must auth with a specific set of - # globus providers, we find the first identity that + # OIDC providers, we find the first identity that # matches a name of that set. identity = set_of_identities(globus_identity).detect do |idstruct| user_can_link_to_globus_identity?(user, idstruct) end - provider_id = identity['identity_provider'] || cb_error("Globus: No identity provider") - provider_name = identity['identity_provider_display_name'] || cb_error("Globus: No identity provider name") + provider_id = identity['identity_provider'] || identity['aud'] || cb_error("#{$oidc_name}: No identity provider") + provider_name = identity['identity_provider_display_name'] || identity['azp'] || cb_error("#{$oidc_name}: No identity provider name") pref_username = identity['preferred_username'] || - identity['username'] || cb_error("Globus: No preferred username") + identity['username'] || cb_error("#{$oidc_name}: No preferred username") # Special case for ORCID, because we already have fields for that provider - # We do NOT do this in the case where the user is forced to auth with globus. + # We do NOT do this in the case where the user is forced to auth with OIDC. if provider_name == 'ORCID' && ! user_must_link_to_globus?(user) orcid = pref_username.sub(/@.*/, "") user.meta['orcid'] = orcid - user.addlog("Linked to ORCID identity: '#{orcid}' through Globus") + user.addlog("Linked to ORCID identity: '#{orcid}' through #{$oidc_name}") return end user.meta[:globus_provider_id] = provider_id user.meta[:globus_provider_name] = provider_name # used in show page user.meta[:globus_preferred_username] = pref_username - user.addlog("Linked to Globus identity: '#{pref_username}' on provider '#{provider_name}'") + user.addlog("Linked to #{$oidc_name} identity: '#{pref_username}' on provider '#{provider_name}'") end - # Removes the recorded globus identity for +user+ + # Removes the recorded OIDC identity for +user+ def unlink_globus_identity(user) user.meta[:globus_provider_id] = nil user.meta[:globus_provider_name] = nil user.meta[:globus_preferred_username] = nil - user.addlog("Unlinked Globus identity") + user.addlog("Unlinked #{$oidc_name} identity") end def set_of_identities(globus_identity) @@ -201,19 +212,19 @@ def user_must_link_to_globus?(user) end def wipe_user_password_after_globus_link(user) - user.update_attribute(:crypted_password, 'Wiped-By-Globus-Link-' + User.random_string) - user.update_attribute(:salt , 'Wiped-By-Globus-Link-' + User.random_string) + user.update_attribute(:crypted_password, "Wiped-By-#{$oidc_name}-Link-" + User.random_string) + user.update_attribute(:salt , "Wiped-By-#{$oidc_name}-Link-" + User.random_string) user.update_attribute(:password_reset , false) end - # Given a globus identity structure, find the user that matches it. + # Given a OIDC identity structure, find the user that matches it. # Returns the user object if found; returns a string error message otherwise. def find_user_with_globus_identity(globus_identity) provider_name = globus_identity['identity_provider_display_name'] pref_username = globus_identity['preferred_username'] || globus_identity['username'] - id_set = set_of_identities(globus_identity) # a globus record can contain several identities + id_set = set_of_identities(globus_identity) # an OIDC record can contain several identities # For each present identity, find all users that have it. # We only allow ONE cbrain user to link to any of the identities. @@ -222,14 +233,14 @@ def find_user_with_globus_identity(globus_identity) end if users.size == 0 - Rails.logger.error "GLOBUS warning: no CBRAIN accounts found for identity '#{pref_username}' on provider '#{provider_name}'" - return "No CBRAIN user matches your Globus identity. Create a CBRAIN account or link your existing CBRAIN account to your Globus provider." + Rails.logger.error "#{$oidc_name.upcase} warning: no CBRAIN accounts found for identity '#{pref_username}' on provider '#{provider_name}'" + return "No CBRAIN user matches your #{$oidc_name} identity. Create a CBRAIN account or link your existing CBRAIN account to your #{$oidc_name} provider." end if users.size > 1 loginnames = users.map(&:login).join(", ") - Rails.logger.error "GLOBUS error: multiple CBRAIN accounts (#{loginnames}) found for identity '#{pref_username}' on provider '#{provider_name}'" - return "Several CBRAIN user accounts match your Globus identity. Please contact the CBRAIN admins." + Rails.logger.error "#{$oidc_name.upcase} error: multiple CBRAIN accounts (#{loginnames}) found for identity '#{pref_username}' on provider '#{provider_name}'" + return "Several CBRAIN user accounts match your #{$oidc_name} identity. Please contact the CBRAIN admins." end # The one lucky user @@ -241,17 +252,17 @@ def find_user_with_globus_identity(globus_identity) # be empty (no such users) or contain more than one # user (an account management error). def find_users_with_specific_identity(identity) - provider_id = identity['identity_provider'] || cb_error("Globus: No identity provider") - provider_name = identity['identity_provider_display_name'] || cb_error("Globus: No identity provider name") + provider_id = identity['identity_provider'] || identity['aud'] || cb_error("#{$oidc_name}: No identity provider") + provider_name = identity['identity_provider_display_name'] || identity['azp'] || cb_error("#{$oidc_name}: No identity provider name") pref_username = identity['preferred_username'] || - identity['username'] || cb_error("Globus: No preferred username") + identity['username'] || cb_error("#{$oidc_name}: No preferred username") # Special case for ORCID, because we already have fields for that provider if provider_name == 'ORCID' orcid = pref_username.sub(/@.*/, "") users = User.find_all_by_meta_data(:orcid, orcid).to_a return users if users.present? - # otherwise we fall through to detect users who linked with ORCID through Globus + # otherwise we fall through to detect users who linked with ORCID through OIDC end # All other globus providers