Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] SSO proof of concept #12404

Open
wants to merge 15 commits into
base: 2.10
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/api/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ gem 'deep_cloneable', '~> 2.4.0'
# Server-side datatables
gem 'ajax-datatables-rails'

# SSO
gem 'omniauth'
gem 'omniauth-gitlab'
gem 'omniauth-github'
gem 'omniauth_openid_connect', git: 'https://github.com/m0n9oose/omniauth_openid_connect', ref: '88ae46e968dec5e66d4f92773b52babbe72d41fe'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason for pinning this gem to a commit? Is it a lack of a recent release?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, at the time when this was written, there was no compatible release.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did some work with omniauth_openid_connect as a part of paste-o-o, and there is a compatible version now, so we should be good to use the latest version

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes — after I kicked a bunch of people who did omniauth development, they’ve all got together, moved omniauth_openid_connect under the OmniAuth umbrella and made a new release 🙂

gem 'omniauth-rails_csrf_protection'

group :development, :production do
# to have the delayed job daemon
gem 'daemons'
Expand Down
105 changes: 105 additions & 0 deletions src/api/Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
GIT
remote: https://github.com/m0n9oose/omniauth_openid_connect
revision: 88ae46e968dec5e66d4f92773b52babbe72d41fe
ref: 88ae46e968dec5e66d4f92773b52babbe72d41fe
specs:
omniauth_openid_connect (0.3.5)
addressable (~> 2.5)
omniauth (>= 1.9, < 3)
openid_connect (~> 1.1)

GEM
remote: https://rubygems.org/
specs:
Expand Down Expand Up @@ -52,6 +62,7 @@ GEM
activerecord (>= 3.0.0)
addressable (2.6.0)
public_suffix (>= 2.0.2, < 4.0)
aes_key_wrap (1.1.0)
airbrake (8.0.1)
airbrake-ruby (~> 3.0)
airbrake-ruby (3.1.0)
Expand All @@ -62,9 +73,11 @@ GEM
ansi (1.5.0)
arel (9.0.0)
ast (2.4.0)
attr_required (1.0.1)
autoprefixer-rails (9.6.0)
execjs
bcrypt (3.1.13)
bindata (2.4.10)
bootstrap (4.3.1)
autoprefixer-rails (>= 9.1.0)
popper_js (>= 1.14.3, < 2)
Expand Down Expand Up @@ -154,6 +167,29 @@ GEM
tty-pager (~> 0.12.0)
tty-screen (~> 0.6.5)
tty-tree (~> 0.3.0)
faraday (1.9.3)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0)
faraday-multipart (~> 1.0)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.0)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
faraday-retry (~> 1.0)
ruby2_keywords (>= 0.0.4)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.0.3)
multipart-post (>= 1.2, < 3)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
feature (1.4.0)
ffi (1.11.1)
flot-rails (0.0.7)
Expand All @@ -180,11 +216,13 @@ GEM
rubocop (>= 0.50.0)
sysexits (~> 1.1)
hashdiff (0.4.0)
hashie (5.0.0)
html2haml (2.2.0)
erubis (~> 2.7.0)
haml (>= 4.0, < 6)
nokogiri (>= 1.6.0)
ruby_parser (~> 3.5)
httpclient (2.8.3)
i18n (1.8.10)
concurrent-ruby (~> 1.0)
influxdb (0.7.0)
Expand All @@ -200,6 +238,11 @@ GEM
jquery-ui-rails (4.2.1)
railties (>= 3.2.16)
json (2.5.1)
json-jwt (1.13.0)
activesupport (>= 4.2)
aes_key_wrap
bindata
jwt (2.3.0)
kaminari (1.2.1)
activesupport (>= 4.1.0)
kaminari-actionview (= 1.2.1)
Expand Down Expand Up @@ -245,13 +288,48 @@ GEM
momentjs-rails (2.20.1)
railties (>= 3.1)
mousetrap-rails (1.4.6)
multi_json (1.15.0)
multi_xml (0.6.0)
multipart-post (2.1.1)
mysql2 (0.5.2)
nio4r (2.5.7)
nokogiri (1.11.2)
mini_portile2 (~> 2.5.0)
racc (~> 1.4)
nokogumbo (2.0.1)
nokogiri (~> 1.8, >= 1.8.4)
oauth2 (1.4.7)
faraday (>= 0.8, < 2.0)
jwt (>= 1.0, < 3.0)
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (>= 1.2, < 3)
omniauth (2.0.4)
hashie (>= 3.4.6)
rack (>= 1.6.2, < 3)
rack-protection
omniauth-github (2.0.0)
omniauth (~> 2.0)
omniauth-oauth2 (~> 1.7.1)
omniauth-gitlab (3.0.0)
omniauth (~> 2.0)
omniauth-oauth2 (~> 1.7.1)
omniauth-oauth2 (1.7.2)
oauth2 (~> 1.4)
omniauth (>= 1.9, < 3)
omniauth-rails_csrf_protection (1.0.0)
actionpack (>= 4.2)
omniauth (~> 2.0)
openid_connect (1.3.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)
parallel (1.17.0)
parser (2.6.3.0)
ast (~> 2.4.0)
Expand Down Expand Up @@ -290,6 +368,14 @@ GEM
activesupport (>= 3.0.0)
racc (1.5.2)
rack (2.2.3)
rack-oauth2 (1.19.0)
activesupport
attr_required
httpclient
json-jwt (>= 1.11.0)
rack (>= 2.1.0)
rack-protection (2.1.0)
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
rails (5.2.5)
Expand Down Expand Up @@ -377,6 +463,7 @@ GEM
rubocop (>= 0.60.0)
ruby-ldap (0.9.20)
ruby-progressbar (1.10.1)
ruby2_keywords (0.0.5)
ruby_parser (3.13.1)
sexp_processor (~> 4.9)
rubyzip (2.0.0)
Expand Down Expand Up @@ -420,6 +507,10 @@ GEM
unicode-display_width (~> 1.5)
unicode_utils (~> 1.4)
strings-ansi (0.1.0)
swd (1.3.0)
activesupport (>= 3)
attr_required (>= 0.0.5)
httpclient (>= 2.4)
sysexits (1.2.0)
tdigest (0.1.1)
rbtree (~> 0.4.2)
Expand Down Expand Up @@ -455,9 +546,18 @@ GEM
unicode-display_width (1.6.0)
unicode_utils (1.4.0)
uniform_notifier (1.12.1)
validate_email (0.1.6)
activemodel (>= 3.0)
mail (>= 2.2.5)
validate_url (1.0.13)
activemodel (>= 3.0.0)
public_suffix
vcr (5.0.0)
voight_kampff (1.1.3)
rack (>= 1.4, < 3.0)
webfinger (1.2.0)
activesupport
httpclient (>= 2.4)
webmock (3.6.0)
addressable (>= 2.3.6)
crack (>= 0.3.2)
Expand Down Expand Up @@ -532,6 +632,11 @@ DEPENDENCIES
mousetrap-rails
mysql2
nokogiri
omniauth
omniauth-github
omniauth-gitlab
omniauth-rails_csrf_protection
omniauth_openid_connect!
peek
peek-dalli
peek-host
Expand Down
120 changes: 116 additions & 4 deletions src/api/app/controllers/webui/session_controller.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
class Webui::SessionController < Webui::WebuiController
before_action :kerberos_auth, only: [:new]

skip_before_action :check_anonymous, only: [:create]
skip_before_action :check_anonymous, only: [:new, :create, :sso, :sso_callback, :sso_confirm, :do_sso_confirm]

def new
switch_to_webui2
Expand Down Expand Up @@ -40,10 +40,109 @@ def destroy
redirect_on_logout
end

def sso
switch_to_webui2
end

def sso_callback
@auth_hash = request.env['omniauth.auth']
# Rails.logger.info "auth hash: #{@auth_hash}"
user = User.find_with_omniauth(@auth_hash['info'])

unless user
session[:auth] = @auth_hash['info']
session[:auth]['provider'] = @auth_hash['provider']
redirect_to(sso_confirm_path)
return
end

unless user.is_active?
RabbitmqBus.send_to_bus('metrics', 'login,access_point=webui,failure=disabled value=1')
redirect_to(root_path, error: 'Your account is disabled. Please contact the administrator for details.')
return
end

User.session = user
session[:login] = user.login
Rails.logger.debug "Authenticated user '#{user.login}'"

redirect_on_login
end

def sso_confirm
switch_to_webui2
auth_info = session[:auth]

if !auth_info
redirect_to sso_path
return
end

# Try to derive a username from the information available,
# falling back to full name if nothing else works
@derived_username = auth_info['username'] ||
auth_info['nickname'] ||
auth_info['email'] ||
auth_info['name']

# Some providers set username or nickname to an email address
# Derive the username from the local part of the email address,
# if possible. The full name with spaces replaced by underscores
# is the last resort fallback.
@derived_username = @derived_username.rpartition("@")[0] if @derived_username.include? "@"
@derived_username = @derived_username.gsub(' ', '_')
end

def do_sso_confirm
required_parameters :login
auth_info = session[:auth]

if !auth_info
redirect_to sso_path
return
end

existing_user = User.find_by_login(params[:login])
if existing_user
flash[:error] = "Username #{params[:login]} is already taken, choose a different one"
redirect_to sso_confirm_path
return
end

begin
user = User.create_with_omniauth(auth_info, params[:login])
rescue ActiveRecord::ActiveRecordError
flash[:error] = "Invalid username, please try a different one"
redirect_to sso_confirm_path
return
end

unless user
flash[:error] = "Cannot create user"
redirect_to root_path
return
end

unless user.is_active?
RabbitmqBus.send_to_bus('metrics', 'login,access_point=webui,failure=disabled value=1')
redirect_to(root_path, error: 'Your account needs to be confirmed by the administrator.')
return
end

User.session = user
session[:login] = user.login
Rails.logger.debug "Authenticated user '#{user.login}'"

redirect_on_login
end


private

def redirect_on_login
if referer_was_login?
if !referer_was_ours?
redirect_to root_path
elsif referer_was_login?
redirect_to user_show_path(User.session!)
else
redirect_back(fallback_location: root_path)
Expand All @@ -54,11 +153,24 @@ def redirect_on_logout
if CONFIG['proxy_auth_mode'] == :on
redirect_to CONFIG['proxy_auth_logout_page']
else
redirect_back(fallback_location: root_path)
redirect_to root_path
end
end

def referer_was_ours?
return false unless request.referer

parsed = URI.parse(request.referer)
parsed.host == request.host and parsed.port == request.port
end

def referer_was_login?
request.referer && request.referer.end_with?(session_new_path)
return false unless request.referer

parsed = URI.parse(request.referer)
return false unless parsed.host == request.host
return false unless parsed.port == request.port

parsed.path == session_new_path or parsed.path.starts_with?(sso_path)
end
end
3 changes: 2 additions & 1 deletion src/api/app/controllers/webui/user_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,10 @@ def change_password
return
end

if user.authenticate(params[:password])
if user.authenticate(params[:password]) or user.password_invalid?
user.password = params[:new_password]
user.password_confirmation = params[:repeat_password]
user.deprecated_password_hash_type = nil

if user.save
flash[:success] = 'Your password has been changed successfully.'
Expand Down
Loading