From e6cbe3a953d338c5f8de0bb52cfd3866dd6ac28f Mon Sep 17 00:00:00 2001 From: Jan Schulz-Hofen Date: Tue, 17 Jan 2017 17:00:38 +0100 Subject: [PATCH 6/6] =?UTF-8?q?Use=20Redmine=E2=80=99s=20permissions=20as?= =?UTF-8?q?=20OAuth2=20scopes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User#allowed_to? will deny any actions performed by OAuth2 apps which aren’t authorized for the required scope/permission, regardless of the user’s actual role/membership. --- app/controllers/application_controller.rb | 1 + app/controllers/oauth2_applications_controller.rb | 19 +++++++++++++++ app/models/user.rb | 21 ++++++++++++----- app/views/doorkeeper/applications/_form.html.erb | 28 +++++++++++++++-------- config/initializers/doorkeeper.rb | 3 ++- config/routes.rb | 4 +++- lib/redmine.rb | 2 +- 7 files changed, 60 insertions(+), 18 deletions(-) create mode 100644 app/controllers/oauth2_applications_controller.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 3fa0d2e..21648bf 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -115,6 +115,7 @@ class ApplicationController < ActionController::Base elsif access_token = Doorkeeper.authenticate(request) if access_token.accessible? user = User.active.find_by_id(access_token.resource_owner_id) + user.oauth_scope = access_token.scopes.all.map(&:to_sym) else doorkeeper_render_error end diff --git a/app/controllers/oauth2_applications_controller.rb b/app/controllers/oauth2_applications_controller.rb new file mode 100644 index 0000000..a921886 --- /dev/null +++ b/app/controllers/oauth2_applications_controller.rb @@ -0,0 +1,19 @@ +class Oauth2ApplicationsController < Doorkeeper::ApplicationsController + + private + + def application_params + params[:doorkeeper_application] ||= {} + params[:doorkeeper_application][:scopes] ||= [] + + scopes = Redmine::AccessControl.public_permissions.map{|p| p.name.to_s} + + if params[:doorkeeper_application][:scopes].is_a?(Array) + scopes |= params[:doorkeeper_application][:scopes] + else + scopes |= params[:doorkeeper_application][:scopes].split(/\s+/) + end + params[:doorkeeper_application][:scopes] = scopes.join(' ') + super + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 150cc27..17839fc 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -98,6 +98,7 @@ class User < Principal attr_accessor :password, :password_confirmation, :generate_password attr_accessor :last_before_login_on attr_accessor :remote_ip + attr_writer :oauth_scope # Prevents unauthorized assignments attr_protected :password, :password_confirmation, :hashed_password @@ -654,14 +655,18 @@ class User < Principal def allowed_to?(action, context, options={}, &block) if context && context.is_a?(Project) return false unless context.allows_to?(action) - # Admin users are authorized for anything else - return true if admin? + # Admin users are authorized for anything or what their oauth scope prescribes + if admin? && @oauth_scope.present? + Role.new(permissions: @oauth_scope).allowed_to?(action, @oauth_scope) + elsif admin? + return true + end roles = roles_for_project(context) return false unless roles roles.any? {|role| (context.is_public? || role.member?) && - role.allowed_to?(action) && + role.allowed_to?(action, @oauth_scope) && (block_given? ? yield(role, self) : true) } elsif context && context.is_a?(Array) @@ -674,13 +679,17 @@ class User < Principal elsif context raise ArgumentError.new("#allowed_to? context argument must be a Project, an Array of projects or nil") elsif options[:global] - # Admin users are always authorized - return true if admin? + # Admin users are always authorized, only limited by their oauth scope + if admin? && @oauth_scope.present? + Role.new(permissions: @oauth_scope).allowed_to?(action, @oauth_scope) + elsif admin? + return true + end # authorize if user has at least one role that has this permission roles = self.roles.to_a | [builtin_role] roles.any? {|role| - role.allowed_to?(action) && + role.allowed_to?(action, @oauth_scope) && (block_given? ? yield(role, self) : true) } else diff --git a/app/views/doorkeeper/applications/_form.html.erb b/app/views/doorkeeper/applications/_form.html.erb index 8b111b3..c3398b9 100644 --- a/app/views/doorkeeper/applications/_form.html.erb +++ b/app/views/doorkeeper/applications/_form.html.erb @@ -12,14 +12,24 @@ <% end %>

+ -

- <%= f.text_field :scopes, :size => 60, :label => :'activerecord.attributes.doorkeeper/application.scopes' %> - - <%= t('doorkeeper.applications.help.scopes') %> - -

- - - +

<%= l(:'activerecord.attributes.doorkeeper/application.scopes') %>

+
+<% perms_by_module = Redmine::AccessControl.permissions.group_by {|p| p.project_module.to_s} %> +<% perms_by_module.keys.sort.each do |mod| %> +
<%= mod.blank? ? l(:label_project) : l_or_humanize(mod, :prefix => 'project_module_') %> + <% perms_by_module[mod].each do |permission| %> + + <% end %> +
+<% end %> +
<%= check_all_links 'scopes' %> +<%= hidden_field_tag 'doorkeeper_application[scopes][]', '' %>
diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb index 83b2aac..b7e408d 100644 --- a/config/initializers/doorkeeper.rb +++ b/config/initializers/doorkeeper.rb @@ -3,7 +3,8 @@ Doorkeeper.configure do reuse_access_token realm Redmine::Info.app_name base_controller 'ApplicationController' - default_scopes :public + default_scopes *Redmine::AccessControl.public_permissions.map(&:name) + optional_scopes *Redmine::AccessControl.permissions.map(&:name) resource_owner_authenticator do if require_login diff --git a/config/routes.rb b/config/routes.rb index bc2af07..4e4eed8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -17,7 +17,9 @@ Rails.application.routes.draw do - use_doorkeeper + use_doorkeeper do + controllers :applications => 'oauth2_applications' + end root :to => 'welcome#index' root :to => 'welcome#index', :as => 'home' diff --git a/lib/redmine.rb b/lib/redmine.rb index 92c80d9..808232f 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -245,7 +245,7 @@ Redmine::MenuManager.map :admin_menu do |menu| :html => {:class => 'icon icon-server-authentication'} menu.push :plugins, {:controller => 'admin', :action => 'plugins'}, :last => true, :html => {:class => 'icon icon-plugins'} - menu.push :applications, {:controller => 'doorkeeper/applications', :action => 'index'}, :last => true, + menu.push :applications, {:controller => 'oauth2_applications', :action => 'index'}, :last => true, :if => Proc.new { Setting.rest_api_enabled? }, :caption => :'doorkeeper.layouts.admin.nav.applications', :html => {:class => 'icon icon-applications'} -- 2.7.2