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| %>
+
+<% 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