1
0
mirror of https://github.com/mastodon/mastodon synced 2024-12-12 21:58:20 +09:00

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:
chandrn7 2022-03-09 06:07:35 -05:00 committed by GitHub
parent d17fb70131
commit a6ed6845c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 97 additions and 15 deletions

View File

@ -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'

View File

@ -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)

View File

@ -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')

View File

@ -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

View File

@ -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