OpenProject is the leading open source project management software.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openproject/lib/open_project/authentication.rb

140 lines
4.8 KiB

require 'open_project/authentication/manager'
module OpenProject
##
# OpenProject uses Warden strategies for request authentication.
module Authentication
class << self
def add_strategy(name, clazz, auth_scheme)
Warden::Strategies.add name, clazz
info = Manager.auth_scheme auth_scheme
info.strategies << name
end
##
# Updates the used warden strategies for a given scope. The strategies will be tried
10 years ago
# in the order they are set here. Plugins can call this to add or remove strategies.
# For available scopes please refer to `OpenProject::Authentication::Scope`.
#
# @param [Symbol] scope The scope for which to update the used warden strategies.
# @param [Hash] opts Options for that scope.
# @option opts [Boolean] :store Indicates whether the user should be stored in the session
# for this scope. Optional. If not given, the current store
# flag for this strategy will remain unchanged what ever it is.
# @option opts [String] :realm The WWW-Authenticate realm used for authentication challenges
# for this scope. The default value ()
#
# @yield [strategies] A block returning the strategies to be used for this scope.
# @yieldparam [Array] strategies The strategies currently used by this scope. May be empty.
# @yieldreturn [Array] The strategies to be used by this scope.
def update_strategies(scope, opts = {}, &block)
raise ArgumentError, "invalid scope: #{scope}" unless Scope.values.include? scope
config = Manager.scope_config scope
current_strategies = Array(config.strategies)
config.store = opts[:store] if opts.include? :store
config.realm = opts[:realm] if opts.include? :realm
config.strategies = block.call current_strategies if block_given?
end
##
# Allows to handle an authentication failure with a custom response.
#
# @param [Symbol] scope The scope for which to set the custom failure handler. Optional.
# If omitted the default failure handler is set.
#
# @yield [failure_handler] A block returning a custom failure response.
# @yieldparam [Warden::Proxy] warden Warden instance giving access to the would-be
# result message and headers.
# @yieldparam [Hash] warden_options Warden options including the scope of the failed
# strategy and the attempted request path.
# @yieldreturn [Array] A rack standard response such as `[401, {}, ['unauthenticated']]`.
def handle_failure(scope: nil, &block)
Manager.failure_handlers[scope] = block
end
end
##
# This module is only there to declare all used scopes. Technically a scope can be an
# arbitrary symbol. But we declare them here not to lose sight of them.
#
# Plugins can declare new scopes by declaring new constants in this module.
module Scope
API_V3 = :api_v3
class << self
def values
constants.map do |name|
const_get name
end
end
end
end
##
# Options used in the WWW-Authenticate header returned to the user
# in case authentication failed (401).
module WWWAuthenticate
module_function
def pick_auth_scheme(supported_schemes, default_scheme, request_headers = {})
req_scheme = request_headers['X-Authentication-Scheme']
if supported_schemes.include? req_scheme
req_scheme
else
default_scheme
end
end
def default_auth_scheme
'Basic'
end
def default_realm
'OpenProject'
end
def scope_realm(scope = nil)
Manager.scope_config(scope).realm || default_realm
end
def response_header(
default_auth_scheme: self.default_auth_scheme,
scope: nil,
request_headers: {}
)
scheme = pick_auth_scheme auth_schemes(scope), default_auth_scheme, request_headers
"#{scheme} realm=\"#{scope_realm(scope)}\""
end
def auth_schemes(scope)
strategies = Array(Manager.scope_config(scope).strategies)
Manager.auth_schemes
.select { |_, info| scope.nil? or not (info.strategies & strategies).empty? }
.keys
end
end
module AuthHeaders
include WWWAuthenticate
# #scope available from Warden::Strategies::BasicAuth
def auth_scheme
pick_auth_scheme auth_schemes(scope), default_auth_scheme, env
end
def realm
scope_realm scope
end
end
end
end
Warden::Strategies::BasicAuth.prepend OpenProject::Authentication::AuthHeaders