0006-oauth-Use-Redmine-s-permissions-as-OAuth2-scopes.patch

Jens Krämer, 2020-07-21 13:05

Download (10.1 KB)

View differences:

app/controllers/application_controller.rb
127 127
        # Oauth
128 128
        if access_token.accessible?
129 129
          user = User.active.find_by_id(access_token.resource_owner_id)
130
          user.oauth_scope = access_token.scopes.all.map(&:to_sym)
130 131
        else
131 132
          doorkeeper_render_error
132 133
        end
app/controllers/oauth2_applications_controller.rb
1
class Oauth2ApplicationsController < Doorkeeper::ApplicationsController
2

  
3
  private
4

  
5
  def application_params
6
    params[:doorkeeper_application] ||= {}
7
    params[:doorkeeper_application][:scopes] ||= []
8

  
9
    scopes = Redmine::AccessControl.public_permissions.map{|p| p.name.to_s}
10

  
11
    if params[:doorkeeper_application][:scopes].is_a?(Array)
12
      scopes |= params[:doorkeeper_application][:scopes]
13
    else
14
      scopes |= params[:doorkeeper_application][:scopes].split(/\s+/)
15
    end
16
    params[:doorkeeper_application][:scopes] = scopes.join(' ')
17
    super
18
  end
19
end
app/models/user.rb
100 100
  attr_accessor :password, :password_confirmation, :generate_password
101 101
  attr_accessor :last_before_login_on
102 102
  attr_accessor :remote_ip
103
  attr_writer   :oauth_scope
103 104

  
104 105
  LOGIN_LENGTH_LIMIT = 60
105 106
  MAIL_LENGTH_LIMIT = 60
......
704 705
  def allowed_to?(action, context, options={}, &block)
705 706
    if context && context.is_a?(Project)
706 707
      return false unless context.allows_to?(action)
707
      # Admin users are authorized for anything else
708
      return true if admin?
708

  
709
      # Admin users are authorized for anything or what their oauth scope prescribes
710
      if admin?
711
        if @oauth_scope
712
          return Role.new(permissions: @oauth_scope).allowed_to?(action, @oauth_scope)
713
        else
714
          return true
715
        end
716
      end
709 717

  
710 718
      roles = roles_for_project(context)
711 719
      return false unless roles
712 720

  
713 721
      roles.any? {|role|
714 722
        (context.is_public? || role.member?) &&
715
        role.allowed_to?(action) &&
723
        role.allowed_to?(action, @oauth_scope) &&
716 724
        (block_given? ? yield(role, self) : true)
717 725
      }
718 726
    elsif context && context.is_a?(Array)
......
725 733
    elsif context
726 734
      raise ArgumentError.new("#allowed_to? context argument must be a Project, an Array of projects or nil")
727 735
    elsif options[:global]
728
      # Admin users are always authorized
729
      return true if admin?
736
      # Admin users are always authorized, only limited by their oauth scope
737
      if admin?
738
        if @oauth_scope
739
          return Role.new(permissions: @oauth_scope).allowed_to?(action, @oauth_scope)
740
        else
741
          return true
742
        end
743
      end
730 744

  
731 745
      # authorize if user has at least one role that has this permission
732 746
      roles = self.roles.to_a | [builtin_role]
733 747
      roles.any? {|role|
734
        role.allowed_to?(action) &&
748
        role.allowed_to?(action, @oauth_scope) &&
735 749
        (block_given? ? yield(role, self) : true)
736 750
      }
737 751
    else
app/views/doorkeeper/applications/_form.html.erb
12 12
      <% end %>
13 13
    </em>
14 14
  </p>
15
</div>
15 16

  
16
  <p>
17
    <%= f.text_field :scopes, :size => 60, :label => :'activerecord.attributes.doorkeeper/application.scopes'  %>
18
    <em class="info">
19
      <%= t('doorkeeper.applications.help.scopes') %>
20
    </em>
21
  </p>
22

  
23

  
24

  
17
<h3><%= l(:'activerecord.attributes.doorkeeper/application.scopes') %></h3>
18
<div class="box tabular" id="scopes">
19
<% perms_by_module = Redmine::AccessControl.permissions.group_by {|p| p.project_module.to_s} %>
20
<% perms_by_module.keys.sort.each do |mod| %>
21
    <fieldset><legend><%= mod.blank? ? l(:label_project) : l_or_humanize(mod, :prefix => 'project_module_') %></legend>
22
    <% perms_by_module[mod].each do |permission| %>
23
        <label class="floating">
24
        <%= check_box_tag 'doorkeeper_application[scopes][]', permission.name.to_s, (permission.public? || @application.scopes.include?( permission.name.to_s)),
25
              :id => "doorkeeper_application_scopes_#{permission.name}",
26
              :data => {:shows => ".#{permission.name}_shown"},
27
              :disabled => permission.public? %>
28
        <%= l_or_humanize(permission.name, :prefix => 'permission_') %>
29
        </label>
30
    <% end %>
31
    </fieldset>
32
<% end %>
33
<br /><%= check_all_links 'scopes' %>
34
<%= hidden_field_tag 'doorkeeper_application[scopes][]', '' %>
25 35
</div>
app/views/doorkeeper/applications/index.html.erb
18 18
    <tr id="application_<%= application.id %>" class="<%= cycle("odd", "even") %>">
19 19
      <td class="name"><span><%= link_to application.name, oauth_application_path(application) %></span></td>
20 20
      <td class="description"><%= truncate application.redirect_uri.split.join(', '), length: 50 %></td>
21
      <td class="description"><%= h application.scopes %></td>
21
      <td class="description"><%= safe_join application.scopes.map{|scope| h l_or_humanize(scope, prefix: 'permission_')}, ", " %></td>
22 22
      <td class="buttons">
23 23
        <%= link_to t('doorkeeper.applications.buttons.edit'), edit_oauth_application_path(application), class: 'icon icon-edit' %>
24 24
        <%= link_to t('doorkeeper.applications.buttons.destroy'), oauth_application_path(application), :data => {:confirm => t('doorkeeper.applications.confirmations.destroy')}, :method => :delete, :class => 'icon icon-del' %>
app/views/doorkeeper/applications/show.html.erb
17 17
  </p>
18 18
  <p>
19 19
    <span class="label"><%= t('.scopes') %>:</span>
20
    <code><%= h @application.scopes %></code>
20
    <code><%= safe_join @application.scopes.map{|scope| h l_or_humanize(scope, prefix: 'permission_')}, ", " %></code>
21 21
  </p>
22 22
</div>
23 23

  
app/views/doorkeeper/authorizations/new.html.erb
10 10
    <p><%= t('.able_to') %>:
11 11
    <ul>
12 12
      <% @pre_auth.scopes.each do |scope| %>
13
        <li><%= t scope, scope: [:doorkeeper, :scopes] %></li>
13
        <li><%= l_or_humanize(scope, prefix: 'permission_') %></li>
14 14
      <% end %>
15 15
    </ul>
16 16
    </p>
config/initializers/doorkeeper.rb
3 3
  reuse_access_token
4 4
  realm           Redmine::Info.app_name
5 5
  base_controller 'ApplicationController'
6
  default_scopes  :public
6
  default_scopes  *Redmine::AccessControl.public_permissions.map(&:name)
7
  optional_scopes *Redmine::AccessControl.permissions.map(&:name)
7 8

  
8 9
  resource_owner_authenticator do
9 10
    if require_login
config/routes.rb
19 19

  
20 20
Rails.application.routes.draw do
21 21

  
22
  use_doorkeeper
22
  use_doorkeeper do
23
    controllers :applications => 'oauth2_applications'
24
  end
23 25

  
24 26
  root :to => 'welcome#index'
25 27
  root :to => 'welcome#index', :as => 'home'
lib/redmine.rb
267 267
            :html => {:class => 'icon icon-settings'}
268 268
  menu.push :ldap_authentication, {:controller => 'auth_sources', :action => 'index'},
269 269
            :html => {:class => 'icon icon-server-authentication'}
270
  menu.push :applications, {:controller => 'doorkeeper/applications', :action => 'index'},
270
  menu.push :applications, {:controller => 'oauth2_applications', :action => 'index'},
271 271
            :if => Proc.new { Setting.rest_api_enabled? },
272 272
            :caption => :'doorkeeper.layouts.admin.nav.applications',
273 273
            :html => {:class => 'icon icon-applications'}
274
-