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
d17fb70131
commit
a6ed6845c9
5 changed files with 97 additions and 15 deletions
1
Gemfile
1
Gemfile
|
@ -40,6 +40,7 @@ end
|
||||||
gem 'net-ldap', '~> 0.17'
|
gem 'net-ldap', '~> 0.17'
|
||||||
gem 'omniauth-cas', '~> 2.0'
|
gem 'omniauth-cas', '~> 2.0'
|
||||||
gem 'omniauth-saml', '~> 1.10'
|
gem 'omniauth-saml', '~> 1.10'
|
||||||
|
gem 'gitlab-omniauth-openid-connect', '~>0.5.0', require: 'omniauth_openid_connect'
|
||||||
gem 'omniauth', '~> 1.9'
|
gem 'omniauth', '~> 1.9'
|
||||||
gem 'omniauth-rails_csrf_protection', '~> 0.1'
|
gem 'omniauth-rails_csrf_protection', '~> 0.1'
|
||||||
|
|
||||||
|
|
41
Gemfile.lock
41
Gemfile.lock
|
@ -68,6 +68,7 @@ GEM
|
||||||
zeitwerk (~> 2.3)
|
zeitwerk (~> 2.3)
|
||||||
addressable (2.8.0)
|
addressable (2.8.0)
|
||||||
public_suffix (>= 2.0.2, < 5.0)
|
public_suffix (>= 2.0.2, < 5.0)
|
||||||
|
aes_key_wrap (1.1.0)
|
||||||
airbrussh (1.4.0)
|
airbrussh (1.4.0)
|
||||||
sshkit (>= 1.6.1, != 1.7.0)
|
sshkit (>= 1.6.1, != 1.7.0)
|
||||||
android_key_attestation (0.3.0)
|
android_key_attestation (0.3.0)
|
||||||
|
@ -77,6 +78,7 @@ GEM
|
||||||
ast (2.4.2)
|
ast (2.4.2)
|
||||||
attr_encrypted (3.1.0)
|
attr_encrypted (3.1.0)
|
||||||
encryptor (~> 3.0.0)
|
encryptor (~> 3.0.0)
|
||||||
|
attr_required (1.0.1)
|
||||||
awrence (1.1.1)
|
awrence (1.1.1)
|
||||||
aws-eventstream (1.2.0)
|
aws-eventstream (1.2.0)
|
||||||
aws-partitions (1.558.0)
|
aws-partitions (1.558.0)
|
||||||
|
@ -260,6 +262,10 @@ GEM
|
||||||
fuubar (2.5.1)
|
fuubar (2.5.1)
|
||||||
rspec-core (~> 3.0)
|
rspec-core (~> 3.0)
|
||||||
ruby-progressbar (~> 1.4)
|
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)
|
globalid (1.0.0)
|
||||||
activesupport (>= 5.0)
|
activesupport (>= 5.0)
|
||||||
hamlit (2.13.0)
|
hamlit (2.13.0)
|
||||||
|
@ -286,6 +292,7 @@ GEM
|
||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
http-form_data (2.3.0)
|
http-form_data (2.3.0)
|
||||||
http_accept_language (2.1.1)
|
http_accept_language (2.1.1)
|
||||||
|
httpclient (2.8.3)
|
||||||
httplog (1.5.0)
|
httplog (1.5.0)
|
||||||
rack (>= 1.0)
|
rack (>= 1.0)
|
||||||
rainbow (>= 2.0.0)
|
rainbow (>= 2.0.0)
|
||||||
|
@ -306,6 +313,10 @@ GEM
|
||||||
jmespath (1.6.0)
|
jmespath (1.6.0)
|
||||||
json (2.5.1)
|
json (2.5.1)
|
||||||
json-canonicalization (0.3.0)
|
json-canonicalization (0.3.0)
|
||||||
|
json-jwt (1.13.0)
|
||||||
|
activesupport (>= 4.2)
|
||||||
|
aes_key_wrap
|
||||||
|
bindata
|
||||||
json-ld (3.2.0)
|
json-ld (3.2.0)
|
||||||
htmlentities (~> 4.3)
|
htmlentities (~> 4.3)
|
||||||
json-canonicalization (~> 0.3)
|
json-canonicalization (~> 0.3)
|
||||||
|
@ -406,6 +417,16 @@ GEM
|
||||||
omniauth-saml (1.10.3)
|
omniauth-saml (1.10.3)
|
||||||
omniauth (~> 1.3, >= 1.3.2)
|
omniauth (~> 1.3, >= 1.3.2)
|
||||||
ruby-saml (~> 1.9)
|
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 (2.2.0)
|
||||||
openssl-signature_algorithm (0.4.0)
|
openssl-signature_algorithm (0.4.0)
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
|
@ -449,6 +470,12 @@ GEM
|
||||||
rack (>= 1.0, < 3)
|
rack (>= 1.0, < 3)
|
||||||
rack-cors (1.1.1)
|
rack-cors (1.1.1)
|
||||||
rack (>= 2.0.0)
|
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-proxy (0.7.0)
|
||||||
rack
|
rack
|
||||||
rack-test (1.1.0)
|
rack-test (1.1.0)
|
||||||
|
@ -608,6 +635,10 @@ GEM
|
||||||
stoplight (2.2.1)
|
stoplight (2.2.1)
|
||||||
strong_migrations (0.7.9)
|
strong_migrations (0.7.9)
|
||||||
activerecord (>= 5)
|
activerecord (>= 5)
|
||||||
|
swd (1.2.0)
|
||||||
|
activesupport (>= 3)
|
||||||
|
attr_required (>= 0.0.5)
|
||||||
|
httpclient (>= 2.4)
|
||||||
temple (0.8.2)
|
temple (0.8.2)
|
||||||
terminal-table (3.0.2)
|
terminal-table (3.0.2)
|
||||||
unicode-display_width (>= 1.1.1, < 3)
|
unicode-display_width (>= 1.1.1, < 3)
|
||||||
|
@ -642,6 +673,12 @@ GEM
|
||||||
unf_ext (0.0.8)
|
unf_ext (0.0.8)
|
||||||
unicode-display_width (2.1.0)
|
unicode-display_width (2.1.0)
|
||||||
uniform_notifier (1.14.2)
|
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)
|
warden (1.2.9)
|
||||||
rack (>= 2.0.9)
|
rack (>= 2.0.9)
|
||||||
webauthn (3.0.0.alpha1)
|
webauthn (3.0.0.alpha1)
|
||||||
|
@ -654,6 +691,9 @@ GEM
|
||||||
safety_net_attestation (~> 0.4.0)
|
safety_net_attestation (~> 0.4.0)
|
||||||
securecompare (~> 1.0)
|
securecompare (~> 1.0)
|
||||||
tpm-key_attestation (~> 0.9.0)
|
tpm-key_attestation (~> 0.9.0)
|
||||||
|
webfinger (1.1.0)
|
||||||
|
activesupport
|
||||||
|
httpclient (>= 2.4)
|
||||||
webmock (3.14.0)
|
webmock (3.14.0)
|
||||||
addressable (>= 2.8.0)
|
addressable (>= 2.8.0)
|
||||||
crack (>= 0.3.2)
|
crack (>= 0.3.2)
|
||||||
|
@ -717,6 +757,7 @@ DEPENDENCIES
|
||||||
fog-core (<= 2.1.0)
|
fog-core (<= 2.1.0)
|
||||||
fog-openstack (~> 0.3)
|
fog-openstack (~> 0.3)
|
||||||
fuubar (~> 2.5)
|
fuubar (~> 2.5)
|
||||||
|
gitlab-omniauth-openid-connect (~> 0.5.0)
|
||||||
hamlit-rails (~> 0.2)
|
hamlit-rails (~> 0.2)
|
||||||
hiredis (~> 0.6)
|
hiredis (~> 0.6)
|
||||||
htmlentities (~> 4.3)
|
htmlentities (~> 4.3)
|
||||||
|
|
|
@ -4,8 +4,6 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||||
skip_before_action :verify_authenticity_token
|
skip_before_action :verify_authenticity_token
|
||||||
|
|
||||||
def self.provides_callback_for(provider)
|
def self.provides_callback_for(provider)
|
||||||
provider_id = provider.to_s.chomp '_oauth2'
|
|
||||||
|
|
||||||
define_method provider do
|
define_method provider do
|
||||||
@user = User.find_for_oauth(request.env['omniauth.auth'], current_user)
|
@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
|
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
|
else
|
||||||
session["devise.#{provider}_data"] = request.env['omniauth.auth']
|
session["devise.#{provider}_data"] = request.env['omniauth.auth']
|
||||||
redirect_to new_user_registration_url
|
redirect_to new_user_registration_url
|
||||||
|
@ -33,7 +31,7 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||||
end
|
end
|
||||||
|
|
||||||
def after_sign_in_path_for(resource)
|
def after_sign_in_path_for(resource)
|
||||||
if resource.email_verified?
|
if resource.email_present?
|
||||||
root_path
|
root_path
|
||||||
else
|
else
|
||||||
auth_setup_path(missing_email: '1')
|
auth_setup_path(missing_email: '1')
|
||||||
|
|
|
@ -13,7 +13,7 @@ module Omniauthable
|
||||||
Devise.omniauth_configs.keys
|
Devise.omniauth_configs.keys
|
||||||
end
|
end
|
||||||
|
|
||||||
def email_verified?
|
def email_present?
|
||||||
email && email !~ TEMP_EMAIL_REGEX
|
email && email !~ TEMP_EMAIL_REGEX
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -40,16 +40,14 @@ module Omniauthable
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_for_oauth(auth)
|
def create_for_oauth(auth)
|
||||||
# Check if the user exists with provided email if the provider gives us a
|
# Check if the user exists with provided email. If no email was provided,
|
||||||
# verified email. If no verified email was provided or the user already
|
# we assign a temporary email and ask the user to verify it on
|
||||||
# exists, we assign a temporary email and ask the user to verify it on
|
|
||||||
# the next step via Auth::SetupController.show
|
# the next step via Auth::SetupController.show
|
||||||
|
|
||||||
strategy = Devise.omniauth_configs[auth.provider.to_sym].strategy
|
strategy = Devise.omniauth_configs[auth.provider.to_sym].strategy
|
||||||
assume_verified = strategy&.security&.assume_email_is_verified
|
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 = auth.info.verified_email || auth.info.email
|
||||||
email = nil unless email_is_verified
|
|
||||||
|
|
||||||
user = User.find_by(email: email) if 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 = 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.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.save!
|
||||||
user
|
user
|
||||||
end
|
end
|
||||||
|
@ -71,8 +69,8 @@ module Omniauthable
|
||||||
agreement: true,
|
agreement: true,
|
||||||
external: true,
|
external: true,
|
||||||
account_attributes: {
|
account_attributes: {
|
||||||
username: ensure_unique_username(auth.uid),
|
username: ensure_unique_username(ensure_valid_username(auth.uid)),
|
||||||
display_name: auth.info.full_name || [auth.info.first_name, auth.info.last_name].join(' '),
|
display_name: auth.info.full_name || auth.info.name || [auth.info.first_name, auth.info.last_name].join(' '),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
@ -88,5 +86,12 @@ module Omniauthable
|
||||||
|
|
||||||
username
|
username
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,7 +8,8 @@ Devise.setup do |config|
|
||||||
|
|
||||||
# CAS strategy
|
# CAS strategy
|
||||||
if ENV['CAS_ENABLED'] == 'true'
|
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[:url] = ENV['CAS_URL'] if ENV['CAS_URL']
|
||||||
cas_options[:host] = ENV['CAS_HOST'] if ENV['CAS_HOST']
|
cas_options[:host] = ENV['CAS_HOST'] if ENV['CAS_HOST']
|
||||||
cas_options[:port] = ENV['CAS_PORT'] if ENV['CAS_PORT']
|
cas_options[:port] = ENV['CAS_PORT'] if ENV['CAS_PORT']
|
||||||
|
@ -36,7 +37,8 @@ Devise.setup do |config|
|
||||||
|
|
||||||
# SAML strategy
|
# SAML strategy
|
||||||
if ENV['SAML_ENABLED'] == 'true'
|
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[: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[: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']
|
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']
|
saml_options[:allowed_clock_drift] = ENV['SAML_ALLOWED_CLOCK_DRIFT'] if ENV['SAML_ALLOWED_CLOCK_DRIFT']
|
||||||
config.omniauth :saml, saml_options
|
config.omniauth :saml, saml_options
|
||||||
end
|
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
|
end
|
||||||
|
|
Loading…
Reference in a new issue