commit
8e3ba17ae5
@ -0,0 +1,85 @@ |
||||
module Accounts::AuthenticationStages |
||||
def stage_success |
||||
stage = session[:authentication_stages]&.first |
||||
|
||||
if stage && stage.to_s == params[:stage] |
||||
if params[:secret] == stage_secrets[stage] |
||||
session[:authentication_stages] = session[:authentication_stages].drop(1) |
||||
|
||||
successful_authentication User.find(session[:authenticated_user_id]), reset_stages: false |
||||
else |
||||
flash[:error] = I18n.t :notice_auth_stage_verification_error, stage: stage |
||||
|
||||
redirect_to signin_path |
||||
end |
||||
else |
||||
flash[:error] = I18n.t( |
||||
:notice_auth_stage_wrong_stage, |
||||
expected: stage || '(none)', |
||||
actual: params[:stage] |
||||
) |
||||
|
||||
redirect_to signin_path |
||||
end |
||||
end |
||||
|
||||
def stage_failure |
||||
flash[:error] = flash[:error] || I18n.t(:notice_auth_stage_error, stage: params[:stage]) |
||||
|
||||
redirect_to signin_path |
||||
end |
||||
|
||||
private |
||||
|
||||
def authentication_stages(after_activation: false, reset: true) |
||||
if OpenProject::Authentication::Stage.stages.select(&:active?).any? |
||||
session.delete [:authentication_stages, :stage_secrets, :back_url] if reset |
||||
|
||||
if session.include?(:authentication_stages) |
||||
lookup_authentication_stages |
||||
else |
||||
init_authentication_stages after_activation: after_activation |
||||
end |
||||
else |
||||
[] |
||||
end |
||||
end |
||||
|
||||
def lookup_authentication_stages |
||||
OpenProject::Authentication::Stage.find_all session[:authentication_stages] |
||||
end |
||||
|
||||
def init_authentication_stages(after_activation:) |
||||
stages = active_stages after_activation |
||||
|
||||
session[:authentication_stages] = stages.map(&:identifier) |
||||
session[:stage_secrets] = new_stage_secrets |
||||
|
||||
# Remember back_url from params since we're redirecting |
||||
# but don't use the referer |
||||
session[:back_url] = params[:back_url] |
||||
|
||||
stages |
||||
end |
||||
|
||||
def active_stages(after_activation) |
||||
OpenProject::Authentication::Stage |
||||
.stages |
||||
.select(&:active?) |
||||
.select { |s| s.run_after_activation? || !after_activation } |
||||
end |
||||
|
||||
def stage_secrets |
||||
Hash(session[:stage_secrets]) |
||||
end |
||||
|
||||
def new_stage_secrets |
||||
session[:authentication_stages] |
||||
.map { |ident| [ident, stage_secret(ident)] } |
||||
.to_h |
||||
end |
||||
|
||||
def stage_secret(ident) |
||||
SecureRandom.hex(16) |
||||
end |
||||
end |
@ -1,139 +1,137 @@ |
||||
module Concerns |
||||
## |
||||
# If OPENPROJECT_AUTH__SOURCE__SSO_HEADER and OPENPROJECT_AUTH__SOURCE__SSO_SECRET are |
||||
# configured OpenProject will login the user given in the HTTP header with the given name |
||||
# together with the secret in the form of `login:$secret`. |
||||
module AuthSourceSSO |
||||
def find_current_user |
||||
user = super |
||||
## |
||||
# If OPENPROJECT_AUTH__SOURCE__SSO_HEADER and OPENPROJECT_AUTH__SOURCE__SSO_SECRET are |
||||
# configured OpenProject will login the user given in the HTTP header with the given name |
||||
# together with the secret in the form of `login:$secret`. |
||||
module AuthSourceSSO |
||||
def find_current_user |
||||
user = super |
||||
|
||||
return user if user || sso_in_progress! |
||||
return user if user || sso_in_progress! |
||||
|
||||
if login = read_sso_login |
||||
user = find_user_from_auth_source(login) || create_user_from_auth_source(login) |
||||
if login = read_sso_login |
||||
user = find_user_from_auth_source(login) || create_user_from_auth_source(login) |
||||
|
||||
handle_sso_for! user, login |
||||
end |
||||
handle_sso_for! user, login |
||||
end |
||||
end |
||||
|
||||
def read_sso_login |
||||
return nil unless header_name && secret |
||||
def read_sso_login |
||||
return nil unless header_name && secret |
||||
|
||||
login, given_secret = String(request.headers[header_name]).split(":") |
||||
login, given_secret = String(request.headers[header_name]).split(":") |
||||
|
||||
login if valid_credentials? login, given_secret |
||||
end |
||||
login if valid_credentials? login, given_secret |
||||
end |
||||
|
||||
def sso_config |
||||
@sso_config ||= OpenProject::Configuration.auth_source_sso.try(:with_indifferent_access) |
||||
end |
||||
def sso_config |
||||
@sso_config ||= OpenProject::Configuration.auth_source_sso.try(:with_indifferent_access) |
||||
end |
||||
|
||||
def header_name |
||||
sso_config && sso_config[:header] |
||||
end |
||||
def header_name |
||||
sso_config && sso_config[:header] |
||||
end |
||||
|
||||
def secret |
||||
sso_config && sso_config[:secret] |
||||
end |
||||
def secret |
||||
sso_config && sso_config[:secret] |
||||
end |
||||
|
||||
def valid_credentials?(login, secret) |
||||
!invalid_credentials?(login, secret) |
||||
end |
||||
def valid_credentials?(login, secret) |
||||
!invalid_credentials?(login, secret) |
||||
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 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]})" |
||||
) |
||||
|
||||
true |
||||
elsif login.nil? |
||||
Rails.logger.error( |
||||
"No login contained in auth source SSO header. " + |
||||
"(#{header_name}: #{request.headers[header_name]})" |
||||
) |
||||
true |
||||
elsif login.nil? |
||||
Rails.logger.error( |
||||
"No login contained in auth source SSO header. " + |
||||
"(#{header_name}: #{request.headers[header_name]})" |
||||
) |
||||
|
||||
true |
||||
end |
||||
true |
||||
end |
||||
end |
||||
|
||||
def find_user_from_auth_source(login) |
||||
User.where(login: login).where.not(auth_source_id: nil).first |
||||
end |
||||
def find_user_from_auth_source(login) |
||||
User.where(login: login).where.not(auth_source_id: nil).first |
||||
end |
||||
|
||||
def create_user_from_auth_source(login) |
||||
if attrs = AuthSource.find_user(login) |
||||
# login is both safe and protected in chilis core code |
||||
# in case it's intentional we keep it that way |
||||
user = User.new attrs.except(:login) |
||||
user.login = login |
||||
user.language = Setting.default_language |
||||
def create_user_from_auth_source(login) |
||||
if attrs = AuthSource.find_user(login) |
||||
# login is both safe and protected in chilis core code |
||||
# in case it's intentional we keep it that way |
||||
user = User.new attrs.except(:login) |
||||
user.login = login |
||||
user.language = Setting.default_language |
||||
|
||||
save_user! user |
||||
save_user! user |
||||
|
||||
user |
||||
end |
||||
user |
||||
end |
||||
end |
||||
|
||||
def save_user!(user) |
||||
if user.save |
||||
user.reload |
||||
def save_user!(user) |
||||
if user.save |
||||
user.reload |
||||
|
||||
if logger && user.auth_source |
||||
logger.info( |
||||
"User '#{user.login}' created from external auth source: " + |
||||
"#{user.auth_source.type} - #{user.auth_source.name}" |
||||
) |
||||
end |
||||
if logger && user.auth_source |
||||
logger.info( |
||||
"User '#{user.login}' created from external auth source: " + |
||||
"#{user.auth_source.type} - #{user.auth_source.name}" |
||||
) |
||||
end |
||||
end |
||||
end |
||||
|
||||
def sso_in_progress! |
||||
sso_failure_in_progress! || session[:auth_source_registration] |
||||
end |
||||
def sso_in_progress! |
||||
sso_failure_in_progress! || session[:auth_source_registration] |
||||
end |
||||
|
||||
def sso_failure_in_progress! |
||||
failure = session[:auth_source_sso_failure] |
||||
def sso_failure_in_progress! |
||||
failure = session[:auth_source_sso_failure] |
||||
|
||||
if failure |
||||
if failure[:ttl] > 0 |
||||
session[:auth_source_sso_failure] = failure.merge(ttl: failure[:ttl] - 1) |
||||
else |
||||
session.delete :auth_source_sso_failure |
||||
if failure |
||||
if failure[:ttl] > 0 |
||||
session[:auth_source_sso_failure] = failure.merge(ttl: failure[:ttl] - 1) |
||||
else |
||||
session.delete :auth_source_sso_failure |
||||
|
||||
nil |
||||
end |
||||
nil |
||||
end |
||||
end |
||||
end |
||||
|
||||
def sso_login_failed?(user) |
||||
user.nil? || user.new_record? || !user.active? |
||||
end |
||||
def sso_login_failed?(user) |
||||
user.nil? || user.new_record? || !user.active? |
||||
end |
||||
|
||||
def handle_sso_for!(user, login) |
||||
if sso_login_failed?(user) |
||||
handle_sso_failure! user, login |
||||
else # valid user |
||||
handle_sso_success user |
||||
end |
||||
def handle_sso_for!(user, login) |
||||
if sso_login_failed?(user) |
||||
handle_sso_failure! user, login |
||||
else # valid user |
||||
handle_sso_success user |
||||
end |
||||
end |
||||
|
||||
def handle_sso_success(user) |
||||
session[:user_id] = user.id |
||||
def handle_sso_success(user) |
||||
session[:user_id] = user.id |
||||
|
||||
user |
||||
end |
||||
user |
||||
end |
||||
|
||||
def handle_sso_failure!(user, login) |
||||
session[:auth_source_sso_failure] = { |
||||
user: user, |
||||
login: login, |
||||
back_url: request.base_url + request.original_fullpath, |
||||
ttl: 1 |
||||
} |
||||
def handle_sso_failure!(user, login) |
||||
session[:auth_source_sso_failure] = { |
||||
user: user, |
||||
login: login, |
||||
back_url: request.base_url + request.original_fullpath, |
||||
ttl: 1 |
||||
} |
||||
|
||||
redirect_to sso_failure_path |
||||
end |
||||
redirect_to sso_failure_path |
||||
end |
||||
end |
||||
|
@ -1,87 +0,0 @@ |
||||
module Concerns |
||||
module AuthenticationStages |
||||
def stage_success |
||||
stage = session[:authentication_stages]&.first |
||||
|
||||
if stage && stage.to_s == params[:stage] |
||||
if params[:secret] == stage_secrets[stage] |
||||
session[:authentication_stages] = session[:authentication_stages].drop(1) |
||||
|
||||
successful_authentication User.find(session[:authenticated_user_id]), reset_stages: false |
||||
else |
||||
flash[:error] = I18n.t :notice_auth_stage_verification_error, stage: stage |
||||
|
||||
redirect_to signin_path |
||||
end |
||||
else |
||||
flash[:error] = I18n.t( |
||||
:notice_auth_stage_wrong_stage, |
||||
expected: stage || '(none)', |
||||
actual: params[:stage] |
||||
) |
||||
|
||||
redirect_to signin_path |
||||
end |
||||
end |
||||
|
||||
def stage_failure |
||||
flash[:error] = flash[:error] || I18n.t(:notice_auth_stage_error, stage: params[:stage]) |
||||
|
||||
redirect_to signin_path |
||||
end |
||||
|
||||
private |
||||
|
||||
def authentication_stages(after_activation: false, reset: true) |
||||
if OpenProject::Authentication::Stage.stages.select(&:active?).any? |
||||
session.delete [:authentication_stages, :stage_secrets, :back_url] if reset |
||||
|
||||
if session.include?(:authentication_stages) |
||||
lookup_authentication_stages |
||||
else |
||||
init_authentication_stages after_activation: after_activation |
||||
end |
||||
else |
||||
[] |
||||
end |
||||
end |
||||
|
||||
def lookup_authentication_stages |
||||
OpenProject::Authentication::Stage.find_all session[:authentication_stages] |
||||
end |
||||
|
||||
def init_authentication_stages(after_activation:) |
||||
stages = active_stages after_activation |
||||
|
||||
session[:authentication_stages] = stages.map(&:identifier) |
||||
session[:stage_secrets] = new_stage_secrets |
||||
|
||||
# Remember back_url from params since we're redirecting |
||||
# but don't use the referer |
||||
session[:back_url] = params[:back_url] |
||||
|
||||
stages |
||||
end |
||||
|
||||
def active_stages(after_activation) |
||||
OpenProject::Authentication::Stage |
||||
.stages |
||||
.select(&:active?) |
||||
.select { |s| s.run_after_activation? || !after_activation } |
||||
end |
||||
|
||||
def stage_secrets |
||||
Hash(session[:stage_secrets]) |
||||
end |
||||
|
||||
def new_stage_secrets |
||||
session[:authentication_stages] |
||||
.map { |ident| [ident, stage_secret(ident)] } |
||||
.to_h |
||||
end |
||||
|
||||
def stage_secret(ident) |
||||
SecureRandom.hex(16) |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,29 @@ |
||||
class AnonymousUser < User |
||||
validate :validate_unique_anonymous_user, on: :create |
||||
|
||||
# There should be only one AnonymousUser in the database |
||||
def validate_unique_anonymous_user |
||||
errors.add :base, 'An anonymous user already exists.' if AnonymousUser.any? |
||||
end |
||||
|
||||
def available_custom_fields |
||||
[] |
||||
end |
||||
|
||||
# Overrides a few properties |
||||
def logged?; false end |
||||
|
||||
def builtin?; true end |
||||
|
||||
def admin; false end |
||||
|
||||
def name(*_args); I18n.t(:label_user_anonymous) end |
||||
|
||||
def mail; nil end |
||||
|
||||
def time_zone; nil end |
||||
|
||||
def rss_key; nil end |
||||
|
||||
def destroy; false end |
||||
end |
@ -0,0 +1,29 @@ |
||||
class DeletedUser < User |
||||
validate :validate_unique_deleted_user, on: :create |
||||
|
||||
# There should be only one DeletedUser in the database |
||||
def validate_unique_deleted_user |
||||
errors.add :base, 'A DeletedUser already exists.' if DeletedUser.any? |
||||
end |
||||
|
||||
def self.first |
||||
super || create(type: to_s, status: STATUSES[:locked]) |
||||
end |
||||
|
||||
# Overrides a few properties |
||||
def logged?; false end |
||||
|
||||
def builtin?; true end |
||||
|
||||
def admin; false end |
||||
|
||||
def name(*_args); I18n.t('user.deleted') end |
||||
|
||||
def mail; nil end |
||||
|
||||
def time_zone; nil end |
||||
|
||||
def rss_key; nil end |
||||
|
||||
def destroy; false end |
||||
end |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue