Allow login through OpenID Connect (#16221)
* added OpenID Connect as an SSO option * minor fixes * added comments, removed an option that shouldn't be set * fixed Gemfile.lock * added newline to end of Gemfile.lock * removed tab from Gemfile.lock * remove chomp * codeclimate changes and small name change to make function's purpose clearer * codeclimate fix * added SSO buttons to /about page * minor refactor * minor style change * removed spurious change * removed unecessary conditional from ensure_valid_username and added support for auth.info.name in user_params_from_auth * minor changes
This commit is contained in:
		
							parent
							
								
									9c2275d15f
								
							
						
					
					
						commit
						ce5bebf108
					
				
					 5 changed files with 97 additions and 15 deletions
				
			
		
							
								
								
									
										1
									
								
								Gemfile
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								Gemfile
									
									
									
									
									
								
							|  | @ -40,6 +40,7 @@ end | |||
| gem 'net-ldap', '~> 0.17' | ||||
| gem 'omniauth-cas', '~> 2.0' | ||||
| gem 'omniauth-saml', '~> 1.10' | ||||
| gem 'gitlab-omniauth-openid-connect', '~>0.5.0', require: 'omniauth_openid_connect' | ||||
| gem 'omniauth', '~> 1.9' | ||||
| gem 'omniauth-rails_csrf_protection', '~> 0.1' | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										41
									
								
								Gemfile.lock
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								Gemfile.lock
									
									
									
									
									
								
							|  | @ -68,6 +68,7 @@ GEM | |||
|       zeitwerk (~> 2.3) | ||||
|     addressable (2.8.0) | ||||
|       public_suffix (>= 2.0.2, < 5.0) | ||||
|     aes_key_wrap (1.1.0) | ||||
|     airbrussh (1.4.0) | ||||
|       sshkit (>= 1.6.1, != 1.7.0) | ||||
|     android_key_attestation (0.3.0) | ||||
|  | @ -77,6 +78,7 @@ GEM | |||
|     ast (2.4.2) | ||||
|     attr_encrypted (3.1.0) | ||||
|       encryptor (~> 3.0.0) | ||||
|     attr_required (1.0.1) | ||||
|     awrence (1.1.1) | ||||
|     aws-eventstream (1.2.0) | ||||
|     aws-partitions (1.558.0) | ||||
|  | @ -260,6 +262,10 @@ GEM | |||
|     fuubar (2.5.1) | ||||
|       rspec-core (~> 3.0) | ||||
|       ruby-progressbar (~> 1.4) | ||||
|     gitlab-omniauth-openid-connect (0.5.0) | ||||
|       addressable (~> 2.7) | ||||
|       omniauth (~> 1.9) | ||||
|       openid_connect (~> 1.2) | ||||
|     globalid (1.0.0) | ||||
|       activesupport (>= 5.0) | ||||
|     hamlit (2.13.0) | ||||
|  | @ -286,6 +292,7 @@ GEM | |||
|       domain_name (~> 0.5) | ||||
|     http-form_data (2.3.0) | ||||
|     http_accept_language (2.1.1) | ||||
|     httpclient (2.8.3) | ||||
|     httplog (1.5.0) | ||||
|       rack (>= 1.0) | ||||
|       rainbow (>= 2.0.0) | ||||
|  | @ -306,6 +313,10 @@ GEM | |||
|     jmespath (1.6.0) | ||||
|     json (2.5.1) | ||||
|     json-canonicalization (0.3.0) | ||||
|     json-jwt (1.13.0) | ||||
|       activesupport (>= 4.2) | ||||
|       aes_key_wrap | ||||
|       bindata | ||||
|     json-ld (3.2.0) | ||||
|       htmlentities (~> 4.3) | ||||
|       json-canonicalization (~> 0.3) | ||||
|  | @ -406,6 +417,16 @@ GEM | |||
|     omniauth-saml (1.10.3) | ||||
|       omniauth (~> 1.3, >= 1.3.2) | ||||
|       ruby-saml (~> 1.9) | ||||
|     openid_connect (1.2.0) | ||||
|       activemodel | ||||
|       attr_required (>= 1.0.0) | ||||
|       json-jwt (>= 1.5.0) | ||||
|       rack-oauth2 (>= 1.6.1) | ||||
|       swd (>= 1.0.0) | ||||
|       tzinfo | ||||
|       validate_email | ||||
|       validate_url | ||||
|       webfinger (>= 1.0.1) | ||||
|     openssl (2.2.0) | ||||
|     openssl-signature_algorithm (0.4.0) | ||||
|     orm_adapter (0.5.0) | ||||
|  | @ -449,6 +470,12 @@ GEM | |||
|       rack (>= 1.0, < 3) | ||||
|     rack-cors (1.1.1) | ||||
|       rack (>= 2.0.0) | ||||
|     rack-oauth2 (1.16.0) | ||||
|       activesupport | ||||
|       attr_required | ||||
|       httpclient | ||||
|       json-jwt (>= 1.11.0) | ||||
|       rack (>= 2.1.0) | ||||
|     rack-proxy (0.7.0) | ||||
|       rack | ||||
|     rack-test (1.1.0) | ||||
|  | @ -608,6 +635,10 @@ GEM | |||
|     stoplight (2.2.1) | ||||
|     strong_migrations (0.7.9) | ||||
|       activerecord (>= 5) | ||||
|     swd (1.2.0) | ||||
|       activesupport (>= 3) | ||||
|       attr_required (>= 0.0.5) | ||||
|       httpclient (>= 2.4) | ||||
|     temple (0.8.2) | ||||
|     terminal-table (3.0.2) | ||||
|       unicode-display_width (>= 1.1.1, < 3) | ||||
|  | @ -642,6 +673,12 @@ GEM | |||
|     unf_ext (0.0.8) | ||||
|     unicode-display_width (2.1.0) | ||||
|     uniform_notifier (1.14.2) | ||||
|     validate_email (0.1.6) | ||||
|       activemodel (>= 3.0) | ||||
|       mail (>= 2.2.5) | ||||
|     validate_url (1.0.13) | ||||
|       activemodel (>= 3.0.0) | ||||
|       public_suffix | ||||
|     warden (1.2.9) | ||||
|       rack (>= 2.0.9) | ||||
|     webauthn (3.0.0.alpha1) | ||||
|  | @ -654,6 +691,9 @@ GEM | |||
|       safety_net_attestation (~> 0.4.0) | ||||
|       securecompare (~> 1.0) | ||||
|       tpm-key_attestation (~> 0.9.0) | ||||
|     webfinger (1.1.0) | ||||
|       activesupport | ||||
|       httpclient (>= 2.4) | ||||
|     webmock (3.14.0) | ||||
|       addressable (>= 2.8.0) | ||||
|       crack (>= 0.3.2) | ||||
|  | @ -717,6 +757,7 @@ DEPENDENCIES | |||
|   fog-core (<= 2.1.0) | ||||
|   fog-openstack (~> 0.3) | ||||
|   fuubar (~> 2.5) | ||||
|   gitlab-omniauth-openid-connect (~> 0.5.0) | ||||
|   hamlit-rails (~> 0.2) | ||||
|   hiredis (~> 0.6) | ||||
|   htmlentities (~> 4.3) | ||||
|  |  | |||
|  | @ -4,8 +4,6 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController | |||
|   skip_before_action :verify_authenticity_token | ||||
| 
 | ||||
|   def self.provides_callback_for(provider) | ||||
|     provider_id = provider.to_s.chomp '_oauth2' | ||||
| 
 | ||||
|     define_method provider do | ||||
|       @user = User.find_for_oauth(request.env['omniauth.auth'], current_user) | ||||
| 
 | ||||
|  | @ -20,7 +18,7 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController | |||
|         ) | ||||
| 
 | ||||
|         sign_in_and_redirect @user, event: :authentication | ||||
|         set_flash_message(:notice, :success, kind: provider_id.capitalize) if is_navigational_format? | ||||
|         set_flash_message(:notice, :success, kind: Devise.omniauth_configs[provider].strategy.display_name.capitalize) if is_navigational_format? | ||||
|       else | ||||
|         session["devise.#{provider}_data"] = request.env['omniauth.auth'] | ||||
|         redirect_to new_user_registration_url | ||||
|  | @ -33,7 +31,7 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController | |||
|   end | ||||
| 
 | ||||
|   def after_sign_in_path_for(resource) | ||||
|     if resource.email_verified? | ||||
|     if resource.email_present? | ||||
|       root_path | ||||
|     else | ||||
|       auth_setup_path(missing_email: '1') | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ module Omniauthable | |||
|       Devise.omniauth_configs.keys | ||||
|     end | ||||
| 
 | ||||
|     def email_verified? | ||||
|     def email_present? | ||||
|       email && email !~ TEMP_EMAIL_REGEX | ||||
|     end | ||||
|   end | ||||
|  | @ -40,16 +40,14 @@ module Omniauthable | |||
|     end | ||||
| 
 | ||||
|     def create_for_oauth(auth) | ||||
|       # Check if the user exists with provided email if the provider gives us a | ||||
|       # verified email.  If no verified email was provided or the user already | ||||
|       # exists, we assign a temporary email and ask the user to verify it on | ||||
|       # Check if the user exists with provided email. If no email was provided, | ||||
|       # we assign a temporary email and ask the user to verify it on | ||||
|       # the next step via Auth::SetupController.show | ||||
| 
 | ||||
|       strategy          = Devise.omniauth_configs[auth.provider.to_sym].strategy | ||||
|       assume_verified   = strategy&.security&.assume_email_is_verified | ||||
|       email_is_verified = auth.info.verified || auth.info.verified_email || assume_verified | ||||
|       email_is_verified = auth.info.verified || auth.info.verified_email || auth.info.email_verified || assume_verified | ||||
|       email             = auth.info.verified_email || auth.info.email | ||||
|       email             = nil unless email_is_verified | ||||
| 
 | ||||
|       user = User.find_by(email: email) if email_is_verified | ||||
| 
 | ||||
|  | @ -58,7 +56,7 @@ module Omniauthable | |||
|       user = User.new(user_params_from_auth(email, auth)) | ||||
| 
 | ||||
|       user.account.avatar_remote_url = auth.info.image if /\A#{URI::DEFAULT_PARSER.make_regexp(%w(http https))}\z/.match?(auth.info.image) | ||||
|       user.skip_confirmation! | ||||
|       user.skip_confirmation! if email_is_verified | ||||
|       user.save! | ||||
|       user | ||||
|     end | ||||
|  | @ -71,8 +69,8 @@ module Omniauthable | |||
|         agreement: true, | ||||
|         external: true, | ||||
|         account_attributes: { | ||||
|           username: ensure_unique_username(auth.uid), | ||||
|           display_name: auth.info.full_name || [auth.info.first_name, auth.info.last_name].join(' '), | ||||
|           username: ensure_unique_username(ensure_valid_username(auth.uid)), | ||||
|           display_name: auth.info.full_name || auth.info.name || [auth.info.first_name, auth.info.last_name].join(' '), | ||||
|         }, | ||||
|       } | ||||
|     end | ||||
|  | @ -88,5 +86,12 @@ module Omniauthable | |||
| 
 | ||||
|       username | ||||
|     end | ||||
| 
 | ||||
|     def ensure_valid_username(starting_username) | ||||
|       starting_username = starting_username.split('@')[0] | ||||
|       temp_username = starting_username.gsub(/[^a-z0-9_]+/i, '') | ||||
|       validated_username = temp_username.truncate(30, omission: '') | ||||
|       validated_username | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -8,7 +8,8 @@ Devise.setup do |config| | |||
| 
 | ||||
|   # CAS strategy | ||||
|   if ENV['CAS_ENABLED'] == 'true' | ||||
|     cas_options = options | ||||
|     cas_options = {} | ||||
|     cas_options[:display_name] = ENV['CAS_DISPLAY_NAME'] || 'cas' | ||||
|     cas_options[:url] = ENV['CAS_URL'] if ENV['CAS_URL'] | ||||
|     cas_options[:host] = ENV['CAS_HOST'] if ENV['CAS_HOST'] | ||||
|     cas_options[:port] = ENV['CAS_PORT'] if ENV['CAS_PORT'] | ||||
|  | @ -36,7 +37,8 @@ Devise.setup do |config| | |||
| 
 | ||||
|   # SAML strategy | ||||
|   if ENV['SAML_ENABLED'] == 'true' | ||||
|     saml_options = options | ||||
|     saml_options = {} | ||||
|     saml_options[:display_name] = ENV['SAML_DISPLAY_NAME'] || 'saml' | ||||
|     saml_options[:assertion_consumer_service_url] = ENV['SAML_ACS_URL'] if ENV['SAML_ACS_URL'] | ||||
|     saml_options[:issuer] = ENV['SAML_ISSUER'] if ENV['SAML_ISSUER'] | ||||
|     saml_options[:idp_sso_target_url] = ENV['SAML_IDP_SSO_TARGET_URL'] if ENV['SAML_IDP_SSO_TARGET_URL'] | ||||
|  | @ -64,4 +66,39 @@ Devise.setup do |config| | |||
|     saml_options[:allowed_clock_drift] = ENV['SAML_ALLOWED_CLOCK_DRIFT'] if ENV['SAML_ALLOWED_CLOCK_DRIFT'] | ||||
|     config.omniauth :saml, saml_options | ||||
|   end | ||||
| 
 | ||||
|   # OpenID Connect Strategy | ||||
|   if ENV['OIDC_ENABLED'] == 'true' | ||||
|     oidc_options = {} | ||||
|     oidc_options[:display_name] = ENV['OIDC_DISPLAY_NAME'] || 'openid_connect' #OPTIONAL | ||||
|     oidc_options[:issuer] = ENV['OIDC_ISSUER'] if ENV['OIDC_ISSUER'] #NEED | ||||
|     oidc_options[:discovery] = ENV['OIDC_DISCOVERY'] == 'true' if ENV['OIDC_DISCOVERY'] #OPTIONAL (default: false) | ||||
|     oidc_options[:client_auth_method] =  ENV['OIDC_CLIENT_AUTH_METHOD'] if ENV['OIDC_CLIENT_AUTH_METHOD'] #OPTIONAL (default: basic) | ||||
|     scope_string = ENV['OIDC_SCOPE'] if ENV['OIDC_SCOPE'] #NEED | ||||
|     scopes = scope_string.split(',') | ||||
|     oidc_options[:scope] = scopes.map { |x| x.to_sym } | ||||
|     oidc_options[:response_type] = ENV['OIDC_RESPONSE_TYPE'] if ENV['OIDC_RESPONSE_TYPE'] #OPTIONAL (default: code) | ||||
|     oidc_options[:response_mode] = ENV['OIDC_RESPONSE_MODE'] if ENV['OIDC_RESPONSE_MODE'] #OPTIONAL (default: query) | ||||
|     oidc_options[:display] = ENV['OIDC_DISPLAY'] if ENV['OIDC_DISPLAY'] #OPTIONAL (default: page) | ||||
|     oidc_options[:prompt] = ENV['OIDC_PROMPT'] if ENV['OIDC_PROMPT'] #OPTIONAL | ||||
|     oidc_options[:send_nonce] = ENV['OIDC_SEND_NONCE'] == 'true' if ENV['OIDC_SEND_NONCE'] #OPTIONAL (default: true) | ||||
|     oidc_options[:send_scope_to_token_endpoint] = ENV['OIDC_SEND_SCOPE_TO_TOKEN_ENDPOINT'] == 'true' if ENV['OIDC_SEND_SCOPE_TO_TOKEN_ENDPOINT'] #OPTIONAL (default: true) | ||||
|     oidc_options[:post_logout_redirect_uri] = ENV['OIDC_IDP_LOGOUT_REDIRECT_URI'] if ENV['OIDC_IDP_LOGOUT_REDIRECT_URI'] #OPTIONAL | ||||
|     oidc_options[:uid_field] = ENV['OIDC_UID_FIELD'] if ENV['OIDC_UID_FIELD'] #NEED | ||||
|     oidc_options[:client_options] = {} | ||||
|     oidc_options[:client_options][:identifier] = ENV['OIDC_CLIENT_ID'] if ENV['OIDC_CLIENT_ID'] #NEED | ||||
|     oidc_options[:client_options][:secret] = ENV['OIDC_CLIENT_SECRET'] if ENV['OIDC_CLIENT_SECRET'] #NEED | ||||
|     oidc_options[:client_options][:redirect_uri] = ENV['OIDC_REDIRECT_URI'] if ENV['OIDC_REDIRECT_URI'] #NEED | ||||
|     oidc_options[:client_options][:scheme] = ENV['OIDC_HTTP_SCHEME'] if ENV['OIDC_HTTP_SCHEME'] #OPTIONAL (default: https) | ||||
|     oidc_options[:client_options][:host] = ENV['OIDC_HOST'] if ENV['OIDC_HOST'] #OPTIONAL | ||||
|     oidc_options[:client_options][:port] = ENV['OIDC_PORT'] if ENV['OIDC_PORT'] #OPTIONAL | ||||
|     oidc_options[:client_options][:authorization_endpoint] = ENV['OIDC_AUTH_ENDPOINT'] if ENV['OIDC_AUTH_ENDPOINT'] #NEED when discovery != true | ||||
|     oidc_options[:client_options][:token_endpoint] = ENV['OIDC_TOKEN_ENDPOINT'] if ENV['OIDC_TOKEN_ENDPOINT'] #NEED when discovery != true | ||||
|     oidc_options[:client_options][:userinfo_endpoint] = ENV['OIDC_USER_INFO_ENDPOINT'] if ENV['OIDC_USER_INFO_ENDPOINT'] #NEED when discovery != true | ||||
|     oidc_options[:client_options][:jwks_uri] = ENV['OIDC_JWKS_URI'] if ENV['OIDC_JWKS_URI'] #NEED when discovery != true | ||||
|     oidc_options[:client_options][:end_session_endpoint] = ENV['OIDC_END_SESSION_ENDPOINT'] if ENV['OIDC_END_SESSION_ENDPOINT'] #OPTIONAL | ||||
|     oidc_options[:security] = {} | ||||
|     oidc_options[:security][:assume_email_is_verified] = ENV['OIDC_SECURITY_ASSUME_EMAIL_IS_VERIFIED'] == 'true' #OPTIONAL | ||||
|     config.omniauth :openid_connect, oidc_options | ||||
|   end | ||||
| end | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue