Allow an optional auth_source_sso flag

pull/8357/head
Oliver Günther 5 years ago
parent 9d98c1c506
commit e36f8b389c
No known key found for this signature in database
GPG Key ID: A3A8BDAD7C0C552C
  1. 64
      app/controllers/concerns/auth_source_sso.rb
  2. 2
      docs/installation-and-operations/configuration/README.md
  3. 8
      docs/system-admin-guide/authentication/kerberos/README.md
  4. 26
      spec/controllers/concerns/auth_source_sso_spec.rb

@ -7,20 +7,23 @@ module AuthSourceSSO
user = super
return user if user || sso_in_progress!
return nil unless header_name
perform_header_sso
end
def perform_header_sso
if login = read_sso_login
user = find_user_from_auth_source(login) || create_user_from_auth_source(login)
handle_sso_for! user, login
else
handle_sso_failure!
end
end
def read_sso_login
return nil unless header_name && secret
login, given_secret = String(request.headers[header_name]).split(":")
login if valid_credentials? login, given_secret
get_validated_login! String(request.headers[header_name])
end
def sso_config
@ -35,25 +38,34 @@ module AuthSourceSSO
sso_config && sso_config[:secret]
end
def valid_credentials?(login, secret)
!invalid_credentials?(login, secret)
def optional?
sso_config && sso_config[:optional]
end
def invalid_credentials?(login, secret)
if secret != self.secret.to_s
Rails.logger.error(
"Secret contained in auth source SSO header not valid. " +
"(#{header_name}: #{request.headers[header_name]})"
)
def get_validated_login!(value)
login, valid_secret = extract_from_header(value)
true
elsif login.nil?
Rails.logger.error(
"No login contained in auth source SSO header. " +
"(#{header_name}: #{request.headers[header_name]})"
)
unless valid_secret
Rails.logger.error("Secret contained in auth source SSO header #{header_name} is not valid.")
return nil
end
true
unless login.present?
Rails.logger.error("Secret contained in auth source SSO header #{header_name} is not valid.")
return nil
end
login
end
def extract_from_header(value)
if self.secret.present?
valid_secret = value.end_with?(":#{self.secret}")
login = value.gsub(/:#{Regexp.escape(self.secret)}\z/, '')
[login, valid_secret]
else
[value, true]
end
end
@ -112,7 +124,7 @@ module AuthSourceSSO
def handle_sso_for!(user, login)
if sso_login_failed?(user)
handle_sso_failure! user, login
handle_sso_failure!({ user: user, login: login })
else # valid user
handle_sso_success user
end
@ -124,13 +136,13 @@ module AuthSourceSSO
user
end
def handle_sso_failure!(user, login)
session[:auth_source_sso_failure] = {
user: user,
login: login,
def handle_sso_failure!(session_args = {})
return if optional?
session[:auth_source_sso_failure] = session_args.merge(
back_url: request.base_url + request.original_fullpath,
ttl: 1
}
)
redirect_to sso_failure_path
end

@ -77,6 +77,8 @@ Example:
auth_source_sso:
header: X-Remote-User
secret: s3cr3t
# Uncomment to make the header optional.
# optional: true
Can be used to automatically login a user defined through a custom header
sent by a load balancer or reverse proxy in front of OpenProject,

@ -114,6 +114,8 @@ production:
# You can comment it out to disable if your outer server
# fully controls this header value and you trust it.
secret: MyPassword
# Uncomment to make the header optional.
# optional: true
```
@ -142,6 +144,12 @@ openproject config:set OPENPROJECT_AUTH__SOURCE__SSO_HEADER="X-Authenticated-Use
openproject config:set OPENPROJECT_AUTH__SOURCE__SSO_SECRET="MyPassword"
```
In case you want to make the header optional, i.e. the header may or may not be present for a subset of users going through Apache, you can set the following value:
```bash
openproject config:set OPENPROJECT_AUTH__SOURCE__SSO_OPTIONAL=true
```
Please note that every underscore (`_`) in the original configuration key has to be replaced by a duplicate underscore
(`__`) in the environment variable as the single underscore denotes namespaces.

@ -58,7 +58,8 @@ describe MyController, type: :controller do
auth_source # create auth source
user # create user
request.headers[header] = "#{login}:#{secret}"
separator = secret ? ':' : ''
request.headers[header] = "#{login}#{separator}#{secret}"
get :account
end
@ -67,6 +68,15 @@ describe MyController, type: :controller do
expect(response.body.squish).to have_content("Username h.wurst")
end
context 'when the secret being null' do
let(:secret) { nil }
it "should log in given user" do
expect(response.body.squish).to have_content("Username h.wurst")
end
end
shared_examples "auth source sso failure" do
def attrs(user)
user.attributes.slice(:login, :mail, :auth_source_id)
@ -84,6 +94,20 @@ describe MyController, type: :controller do
expect(failure[:back_url]).to eq "http://test.host/my/account"
expect(failure[:ttl]).to eq 1
end
context 'when the config is marked optional' do
let(:sso_config) do
{
header: header,
secret: secret,
optional: true
}
end
it "should redirect to login" do
expect(response).to redirect_to("/login?back_url=http%3A%2F%2Ftest.host%2Fmy%2Faccount")
end
end
end
context "with no auth source sso configured" do

Loading…
Cancel
Save