From 7e40c11ca40280d8b7102a15e4346a936c4956da Mon Sep 17 00:00:00 2001 From: Markus Kahl Date: Thu, 7 May 2015 23:39:57 +0100 Subject: [PATCH] moved strategies; refactoring --- config/initializers/warden.rb | 18 +++-- .../strategies/warden/basic_auth_failure.rb | 16 ++++ .../strategies/warden/global_basic_auth.rb | 78 +++++++++++++++++++ .../strategies/warden/session.rb | 30 +++++++ .../strategies/warden/user_basic_auth.rb | 34 ++++++++ lib/warden/strategies/basic_auth_failure.rb | 12 --- lib/warden/strategies/global_basic_auth.rb | 62 --------------- lib/warden/strategies/session.rb | 26 ------- lib/warden/strategies/user_basic_auth.rb | 30 ------- spec/lib/api/v3/authentication_spec.rb | 21 ++--- .../warden/global_basic_auth_spec.rb | 41 ++++++++++ 11 files changed, 215 insertions(+), 153 deletions(-) create mode 100644 lib/open_project/authentication/strategies/warden/basic_auth_failure.rb create mode 100644 lib/open_project/authentication/strategies/warden/global_basic_auth.rb create mode 100644 lib/open_project/authentication/strategies/warden/session.rb create mode 100644 lib/open_project/authentication/strategies/warden/user_basic_auth.rb delete mode 100644 lib/warden/strategies/basic_auth_failure.rb delete mode 100644 lib/warden/strategies/global_basic_auth.rb delete mode 100644 lib/warden/strategies/session.rb delete mode 100644 lib/warden/strategies/user_basic_auth.rb create mode 100644 spec/lib/open_project/authentication/strategies/warden/global_basic_auth_spec.rb diff --git a/config/initializers/warden.rb b/config/initializers/warden.rb index 99ffe61291..b28f24321f 100644 --- a/config/initializers/warden.rb +++ b/config/initializers/warden.rb @@ -1,14 +1,16 @@ +require 'open_project/authentication' + # Strategies provided by OpenProject: -require 'warden/strategies/basic_auth_failure' -require 'warden/strategies/global_basic_auth' -require 'warden/strategies/user_basic_auth' -require 'warden/strategies/session' +require 'open_project/authentication/strategies/warden/basic_auth_failure' +require 'open_project/authentication/strategies/warden/global_basic_auth' +require 'open_project/authentication/strategies/warden/user_basic_auth' +require 'open_project/authentication/strategies/warden/session' strategies = [ - [:basic_auth_failure, Warden::Strategies::BasicAuthFailure], - [:global_basic_auth, Warden::Strategies::GlobalBasicAuth], - [:user_basic_auth, Warden::Strategies::UserBasicAuth], - [:session, Warden::Strategies::Session] + [:basic_auth_failure, OpenProject::Authentication::Strategies::Warden::BasicAuthFailure], + [:global_basic_auth, OpenProject::Authentication::Strategies::Warden::GlobalBasicAuth], + [:user_basic_auth, OpenProject::Authentication::Strategies::Warden::UserBasicAuth], + [:session, OpenProject::Authentication::Strategies::Warden::Session] ] strategies.each do |name, clazz| diff --git a/lib/open_project/authentication/strategies/warden/basic_auth_failure.rb b/lib/open_project/authentication/strategies/warden/basic_auth_failure.rb new file mode 100644 index 0000000000..05bc805336 --- /dev/null +++ b/lib/open_project/authentication/strategies/warden/basic_auth_failure.rb @@ -0,0 +1,16 @@ +module OpenProject + module Authentication + module Strategies + module Warden + ## + # This strategy is inserted after optional basic auth strategies to + # indicate that invalid basic auth credentials were provided. + class BasicAuthFailure < ::Warden::Strategies::BasicAuth + def authenticate_user(_username, _password) + nil # always fails + end + end + end + end + end +end diff --git a/lib/open_project/authentication/strategies/warden/global_basic_auth.rb b/lib/open_project/authentication/strategies/warden/global_basic_auth.rb new file mode 100644 index 0000000000..8357725ab2 --- /dev/null +++ b/lib/open_project/authentication/strategies/warden/global_basic_auth.rb @@ -0,0 +1,78 @@ +require 'warden/basic_auth' + +module OpenProject + module Authentication + module Strategies + module Warden + ## + # Allows authentication via a singular set of basic auth credentials for admin access. + # + # The credentials must be configured in `config/configuration.yml` like this: + # + # production: + # authentication: + # global_basic_auth: + # user: admin + # password: 123456 + # + # The strategy will only be triggered when the configured user name is sent. + # Meaning that this strategy is skipped if a basic auth attempt involving any + # other user name is made. + class GlobalBasicAuth < ::Warden::Strategies::BasicAuth + def self.configuration + @configuration ||= configure! + end + + ## + # Updates the configuration for this strategy. It's usually called only once, at startup. + # + # @param [Hash] config The configuration to be used. Must contain :user and :password. + # @raise [ArgumentError] Raises an error if the configured user name collides with the + # user name used for UserBasicAuth (apikey). + def self.configure!(config = openproject_config) + if config[:user] == UserBasicAuth.user + raise ArgumentError, "global user must not be '#{UserBasicAuth.user}'" + end + + @configuration = config + end + + ## + # Reads the configuration for this strategy from OpenProject's `configuration.yml`. + def self.openproject_config + config = OpenProject::Configuration + %w(authentication global_basic_auth).inject(config) do |acc, key| + HashWithIndifferentAccess.new acc[key] + end + end + + def self.configuration? + user && password + end + + def self.user + configuration[:user] + end + + def self.password + configuration[:password] + end + + ## + # Only valid if global basic auth is configured and tried. + def valid? + self.class.configuration? && super && username == self.class.user + end + + def authenticate_user(username, password) + if username == self.class.user && password == self.class.password + User.system.tap do |user| + user.admin = true + end + end + end + end + end + end + end +end diff --git a/lib/open_project/authentication/strategies/warden/session.rb b/lib/open_project/authentication/strategies/warden/session.rb new file mode 100644 index 0000000000..1048b27450 --- /dev/null +++ b/lib/open_project/authentication/strategies/warden/session.rb @@ -0,0 +1,30 @@ +module OpenProject + module Authentication + module Strategies + module Warden + ## + # Temporary strategy necessary as long as the OpenProject authentication has not been unified + # in terms of Warden strategies and is only locally applied to the API v3. + class Session < ::Warden::Strategies::Base + def valid? + session + end + + def authenticate! + user = user_id ? User.find(user_id) : User.anonymous + + success! user + end + + def user_id + Hash(session)['user_id'] + end + + def session + env['rack.session'] + end + end + end + end + end +end diff --git a/lib/open_project/authentication/strategies/warden/user_basic_auth.rb b/lib/open_project/authentication/strategies/warden/user_basic_auth.rb new file mode 100644 index 0000000000..83ec5df077 --- /dev/null +++ b/lib/open_project/authentication/strategies/warden/user_basic_auth.rb @@ -0,0 +1,34 @@ +require 'warden/basic_auth' + +module OpenProject + module Authentication + module Strategies + module Warden + ## + # Allows users to authenticate using their API key via basic auth. + # Note that in order for a user to be able to generate one + # `Setting.rest_api_enabled` has to be `1`. + # + # The basic auth credentials are expected to contain the literal 'apikey' + # as the user name and the API key as the password. + class UserBasicAuth < ::Warden::Strategies::BasicAuth + def self.user + 'apikey' + end + + def valid? + super && username == self.class.user + end + + def authenticate_user(_, api_key) + token(api_key).try(:user) + end + + def token(value) + Token.where(action: 'api', value: value).first + end + end + end + end + end +end diff --git a/lib/warden/strategies/basic_auth_failure.rb b/lib/warden/strategies/basic_auth_failure.rb deleted file mode 100644 index c4d2a72750..0000000000 --- a/lib/warden/strategies/basic_auth_failure.rb +++ /dev/null @@ -1,12 +0,0 @@ -module Warden - module Strategies - ## - # This strategy is inserted after optional basic auth strategies to - # indicate that invalid basic auth credentials were provided. - class BasicAuthFailure < BasicAuth - def authenticate_user(_username, _password) - nil # always fails - end - end - end -end diff --git a/lib/warden/strategies/global_basic_auth.rb b/lib/warden/strategies/global_basic_auth.rb deleted file mode 100644 index 9badc54f8e..0000000000 --- a/lib/warden/strategies/global_basic_auth.rb +++ /dev/null @@ -1,62 +0,0 @@ -require 'warden/basic_auth' - -module Warden - module Strategies - ## - # Allows authentication via a singular set of basic auth credentials for admin access. - # - # The credentials must be configured in `config/configuration.yml` like this: - # - # production: - # authentication: - # global_basic_auth: - # user: admin - # password: 123456 - # - # The strategy will only be triggered when the configured user name is sent. - # Meaning that this strategy is skipped if a basic auth attempt involving any - # other user name is made. - class GlobalBasicAuth < BasicAuth - def self.configuration - @configuration ||= configuration! - end - - def self.configuration! - path = %w(authentication global_basic_auth) - @configuration = path.inject(OpenProject::Configuration) { |acc, key| Hash(acc[key]) } - - if user == UserBasicAuth.user - raise ArgumentError, "global user must not be '#{UserBasicAuth.user}'" - end - - @configuration - end - - def self.configuration? - user && password - end - - def self.user - configuration['user'] - end - - def self.password - configuration['password'] - end - - ## - # Only valid if global basic auth is configured and tried. - def valid? - self.class.configuration && super && username == self.class.user - end - - def authenticate_user(username, password) - if username == self.class.user && password == self.class.password - User.system.tap do |user| - user.admin = true - end - end - end - end - end -end diff --git a/lib/warden/strategies/session.rb b/lib/warden/strategies/session.rb deleted file mode 100644 index 4b65d65668..0000000000 --- a/lib/warden/strategies/session.rb +++ /dev/null @@ -1,26 +0,0 @@ -module Warden - module Strategies - ## - # Temporary strategy necessary as long as the OpenProject authentication has not been unified - # in terms of Warden strategies and is only locally applied to the API v3. - class Session < Base - def valid? - session - end - - def authenticate! - user = user_id ? User.find(user_id) : User.anonymous - - success! user - end - - def user_id - Hash(session)['user_id'] - end - - def session - env['rack.session'] - end - end - end -end diff --git a/lib/warden/strategies/user_basic_auth.rb b/lib/warden/strategies/user_basic_auth.rb deleted file mode 100644 index be3e81ffcb..0000000000 --- a/lib/warden/strategies/user_basic_auth.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'warden/basic_auth' - -module Warden - module Strategies - ## - # Allows users to authenticate using their API key via basic auth. - # Note that in order for a user to be able to generate one - # `Setting.rest_api_enabled` has to be `1`. - # - # The basic auth credentials are expected to contain the literal 'apikey' - # as the user name and the API key as the password. - class UserBasicAuth < BasicAuth - def self.user - 'apikey' - end - - def valid? - super && username == self.class.user - end - - def authenticate_user(_, api_key) - token(api_key).try(:user) - end - - def token(value) - Token.where(action: 'api', value: value).first - end - end - end -end diff --git a/spec/lib/api/v3/authentication_spec.rb b/spec/lib/api/v3/authentication_spec.rb index 832680f244..1e65ffc30c 100644 --- a/spec/lib/api/v3/authentication_spec.rb +++ b/spec/lib/api/v3/authentication_spec.rb @@ -33,9 +33,11 @@ describe API::V3, type: :request do let(:user) { FactoryGirl.create :user } let(:resource) { "/api/v3/users/#{user.id}"} + Strategies = OpenProject::Authentication::Strategies::Warden + def basic_auth(user, password) credentials = ActionController::HttpAuthentication::Basic.encode_credentials user, password - {'HTTP_AUTHORIZATION' => credentials} + { 'HTTP_AUTHORIZATION' => credentials } end shared_examples 'it is basic auth protected' do @@ -80,13 +82,7 @@ describe API::V3, type: :request do let(:password) { 'toor' } before do - authentication = { - 'global_basic_auth' => { - 'user' => 'root', - 'password' => 'toor' - } - } - OpenProject::Configuration['authentication'] = authentication + Strategies::GlobalBasicAuth.configure! user: 'root', password: 'toor' end it_behaves_like 'it is basic auth protected' @@ -116,13 +112,8 @@ describe API::V3, type: :request do let(:api_key) { FactoryGirl.create :api_key, user: api_user } before do - authentication = { - 'global_basic_auth' => { - 'user' => 'global_account', - 'password' => 'global_password' - } - } - OpenProject::Configuration['authentication'] = authentication + config = { user: 'global_account', password: 'global_password' } + Strategies::GlobalBasicAuth.configure! config end context 'without credentials' do diff --git a/spec/lib/open_project/authentication/strategies/warden/global_basic_auth_spec.rb b/spec/lib/open_project/authentication/strategies/warden/global_basic_auth_spec.rb new file mode 100644 index 0000000000..ca789a87f2 --- /dev/null +++ b/spec/lib/open_project/authentication/strategies/warden/global_basic_auth_spec.rb @@ -0,0 +1,41 @@ +#-- copyright +# OpenProject is a project management system. +# Copyright (C) 2012-2015 the OpenProject Foundation (OPF) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See doc/COPYRIGHT.rdoc for more details. +#++ + +require 'spec_helper' + +describe OpenProject::Authentication::Strategies::Warden::GlobalBasicAuth do + it 'does not allow the UserBasicAuth user name' do + configuration = lambda do + user = OpenProject::Authentication::Strategies::Warden::UserBasicAuth.user + OpenProject::Authentication::Strategies::Warden::GlobalBasicAuth.configure!( + user: user, password: 'foo') + end + + expect(configuration).to raise_error(ArgumentError) + end +end