Feature #24808 » 0001-Oauth-provider.patch
Gemfile | ||
---|---|---|
19 | 19 |
gem "stimulus-rails", "~> 1.3" |
20 | 20 |
gem "importmap-rails", "~> 2.0" |
21 | 21 |
gem 'commonmarker', '~> 2.3.0' |
22 |
gem "doorkeeper", "~> 5.8.2" |
|
23 |
gem "bcrypt", require: false |
|
24 |
gem "doorkeeper-i18n", "~> 5.2" |
|
22 | 25 | |
23 | 26 |
# Ruby Standard Gems |
24 | 27 |
gem 'csv', '~> 3.3.2' |
... | ... | |
115 | 118 |
gem 'rubocop-performance', '~> 1.25.0', require: false |
116 | 119 |
gem 'rubocop-rails', '~> 2.31.0', require: false |
117 | 120 |
gem 'bundle-audit', require: false |
121 |
# for testing oauth provider capabilities |
|
122 |
gem 'oauth2' |
|
123 |
gem 'rest-client' |
|
124 |
gem 'webrick' |
|
118 | 125 |
end |
119 | 126 | |
120 | 127 |
local_gemfile = File.join(File.dirname(__FILE__), "Gemfile.local") |
app/assets/images/icons.svg | ||
---|---|---|
59 | 59 |
<path d="M12 15v6"/> |
60 | 60 |
<path d="M5 15h3l-3 6h3"/> |
61 | 61 |
</symbol> |
62 |
<symbol viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" id="icon--apps"> |
|
63 |
<path d="M4 4m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"/> |
|
64 |
<path d="M4 14m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"/> |
|
65 |
<path d="M14 14m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"/> |
|
66 |
<path d="M14 7l6 0"/> |
|
67 |
<path d="M17 4l0 6"/> |
|
68 |
</symbol> |
|
62 | 69 |
<symbol viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" id="icon--arrow-right"> |
63 | 70 |
<path d="M4 9h8v-3.586a1 1 0 0 1 1.707 -.707l6.586 6.586a1 1 0 0 1 0 1.414l-6.586 6.586a1 1 0 0 1 -1.707 -.707v-3.586h-8a1 1 0 0 1 -1 -1v-4a1 1 0 0 1 1 -1z"/> |
64 | 71 |
</symbol> |
... | ... | |
394 | 401 |
<path d="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065z"/> |
395 | 402 |
<path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0"/> |
396 | 403 |
</symbol> |
404 |
<symbol viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" id="icon--shield-check"> |
|
405 |
<path d="M11.46 20.846a12 12 0 0 1 -7.96 -14.846a12 12 0 0 0 8.5 -3a12 12 0 0 0 8.5 3a12 12 0 0 1 -.09 7.06"/> |
|
406 |
<path d="M15 19l2 2l4 -4"/> |
|
407 |
</symbol> |
|
397 | 408 |
<symbol viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" id="icon--stats"> |
398 | 409 |
<path d="M3 13a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v6a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"/> |
399 | 410 |
<path d="M15 9a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v10a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"/> |
app/assets/stylesheets/application.css | ||
---|---|---|
1285 | 1285 |
color: #A6750C; |
1286 | 1286 |
} |
1287 | 1287 | |
1288 |
.warning .oauth-permissions { display:inline-block;text-align:left; } |
|
1289 |
.warning .oauth-permissions p { margin-top:0;-webkit-margin-before:0;} |
|
1290 | ||
1288 | 1291 |
#errorExplanation ul { font-size: 0.9em;} |
1289 | 1292 |
#errorExplanation h2, #errorExplanation p { display: none; } |
1290 | 1293 |
app/controllers/application_controller.rb | ||
---|---|---|
131 | 131 |
if (key = api_key_from_request) |
132 | 132 |
# Use API key |
133 | 133 |
user = User.find_by_api_key(key) |
134 |
elsif access_token = Doorkeeper.authenticate(request) |
|
135 |
# Oauth |
|
136 |
if access_token.accessible? |
|
137 |
user = User.active.find_by_id(access_token.resource_owner_id) |
|
138 |
user.oauth_scope = access_token.scopes.all.map(&:to_sym) |
|
139 |
else |
|
140 |
doorkeeper_render_error |
|
141 |
end |
|
134 | 142 |
elsif /\ABasic /i.match?(request.authorization.to_s) |
135 | 143 |
# HTTP Basic, either username/password or API key/random |
136 | 144 |
authenticate_with_http_basic do |username, password| |
app/controllers/oauth2_applications_controller.rb | ||
---|---|---|
1 |
# frozen_string_literal: true |
|
2 | ||
3 |
# |
|
4 |
# Redmine - project management software |
|
5 |
# Copyright (C) 2006- Jean-Philippe Lang |
|
6 |
# |
|
7 |
# This program is free software; you can redistribute it and/or |
|
8 |
# modify it under the terms of the GNU General Public License |
|
9 |
# as published by the Free Software Foundation; either version 2 |
|
10 |
# of the License, or (at your option) any later version. |
|
11 |
# |
|
12 |
# This program is distributed in the hope that it will be useful, |
|
13 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
14 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
15 |
# GNU General Public License for more details. |
|
16 |
# |
|
17 |
# You should have received a copy of the GNU General Public License |
|
18 |
# along with this program; if not, write to the Free Software |
|
19 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
20 |
# |
|
21 |
class Oauth2ApplicationsController < Doorkeeper::ApplicationsController |
|
22 |
private |
|
23 | ||
24 |
def application_params |
|
25 |
params[:doorkeeper_application] ||= {} |
|
26 |
params[:doorkeeper_application][:scopes] ||= [] |
|
27 | ||
28 |
scopes = Redmine::AccessControl.public_permissions.map{|p| p.name.to_s} |
|
29 | ||
30 |
if params[:doorkeeper_application][:scopes].is_a?(Array) |
|
31 |
scopes |= params[:doorkeeper_application][:scopes] |
|
32 |
else |
|
33 |
scopes |= params[:doorkeeper_application][:scopes].split(/\s+/) |
|
34 |
end |
|
35 |
params[:doorkeeper_application][:scopes] = scopes.join(' ') |
|
36 |
super |
|
37 |
end |
|
38 |
end |
app/models/role.rb | ||
---|---|---|
198 | 198 |
# action can be: |
199 | 199 |
# * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit') |
200 | 200 |
# * a permission Symbol (eg. :edit_project) |
201 |
def allowed_to?(action) |
|
201 |
# scope can be: |
|
202 |
# * an array of permissions which will be used as filter (logical AND) |
|
203 | ||
204 |
def allowed_to?(action, scope=nil) |
|
202 | 205 |
if action.is_a? Hash |
203 |
allowed_actions.include? "#{action[:controller]}/#{action[:action]}" |
|
206 |
allowed_actions(scope).include? "#{action[:controller]}/#{action[:action]}"
|
|
204 | 207 |
else |
205 |
allowed_permissions.include? action |
|
208 |
allowed_permissions(scope).include? action
|
|
206 | 209 |
end |
207 | 210 |
end |
208 | 211 | |
... | ... | |
298 | 301 | |
299 | 302 |
private |
300 | 303 | |
301 |
def allowed_permissions |
|
302 |
@allowed_permissions ||= permissions + Redmine::AccessControl.public_permissions.collect {|p| p.name} |
|
304 |
def allowed_permissions(scope = nil) |
|
305 |
scope = scope.sort if scope.present? # to maintain stable cache keys |
|
306 |
@allowed_permissions ||= {} |
|
307 |
@allowed_permissions[scope] ||= begin |
|
308 |
unscoped = permissions + Redmine::AccessControl.public_permissions.collect {|p| p.name} |
|
309 |
scope.present? ? unscoped & scope : unscoped |
|
310 |
end |
|
303 | 311 |
end |
304 | 312 | |
305 |
def allowed_actions |
|
306 |
@actions_allowed ||= |
|
307 |
allowed_permissions.inject([]) do |actions, permission| |
|
313 |
def allowed_actions(scope = nil) |
|
314 |
scope = scope.sort if scope.present? # to maintain stable cache keys |
|
315 |
@actions_allowed ||= {} |
|
316 |
@actions_allowed[scope] ||= |
|
317 |
allowed_permissions(scope).inject([]) do |actions, permission| |
|
308 | 318 |
actions += Redmine::AccessControl.allowed_actions(permission) |
309 | 319 |
end.flatten |
310 | 320 |
end |
app/models/user.rb | ||
---|---|---|
112 | 112 |
attr_accessor :password, :password_confirmation, :generate_password |
113 | 113 |
attr_accessor :last_before_login_on |
114 | 114 |
attr_accessor :remote_ip |
115 |
attr_writer :oauth_scope |
|
115 | 116 | |
116 | 117 |
LOGIN_LENGTH_LIMIT = 60 |
117 | 118 |
MAIL_LENGTH_LIMIT = 254 |
... | ... | |
732 | 733 |
end |
733 | 734 |
end |
734 | 735 | |
736 |
def admin? |
|
737 |
if authorized_by_oauth? |
|
738 |
# when signed in via oauth, the user only acts as admin when the admin scope is set |
|
739 |
super and @oauth_scope.include?(:admin) |
|
740 |
else |
|
741 |
super |
|
742 |
end |
|
743 |
end |
|
744 | ||
745 |
# true if the user has signed in via oauth |
|
746 |
def authorized_by_oauth? |
|
747 |
!@oauth_scope.nil? |
|
748 |
end |
|
749 | ||
735 | 750 |
# Return true if the user is allowed to do the specified action on a specific context |
736 | 751 |
# Action can be: |
737 | 752 |
# * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit') |
... | ... | |
752 | 767 | |
753 | 768 |
roles.any? do |role| |
754 | 769 |
(context.is_public? || role.member?) && |
755 |
role.allowed_to?(action) && |
|
770 |
role.allowed_to?(action, @oauth_scope) &&
|
|
756 | 771 |
(block ? yield(role, self) : true) |
757 | 772 |
end |
758 | 773 |
elsif context && context.is_a?(Array) |
... | ... | |
771 | 786 |
# authorize if user has at least one role that has this permission |
772 | 787 |
roles = self.roles.to_a | [builtin_role] |
773 | 788 |
roles.any? do |role| |
774 |
role.allowed_to?(action) && |
|
789 |
role.allowed_to?(action, @oauth_scope) &&
|
|
775 | 790 |
(block ? yield(role, self) : true) |
776 | 791 |
end |
777 | 792 |
else |
app/views/doorkeeper/applications/_form.html.erb | ||
---|---|---|
1 |
<%= error_messages_for 'application' %> |
|
2 |
<div class="box tabular"> |
|
3 |
<p><%= f.text_field :name, :required => true %></p> |
|
4 | ||
5 |
<p> |
|
6 |
<%= f.text_area :redirect_uri, :required => true, :size => 60, :label => :'activerecord.attributes.doorkeeper/application.redirect_uri' %> |
|
7 |
<em class="info"> |
|
8 |
<%= t('doorkeeper.applications.help.redirect_uri') %> |
|
9 |
</em> |
|
10 |
</p> |
|
11 |
</div> |
|
12 | ||
13 |
<h3><%= l(:'activerecord.attributes.doorkeeper/application.scopes') %></h3> |
|
14 |
<p><em class="info"><%= l :text_oauth_info_scopes %></em></p> |
|
15 |
<div class="box tabular" id="scopes"> |
|
16 |
<fieldset><legend><%= l :label_oauth_admin_access %></legend> |
|
17 |
<label class="floating" style="width: auto;"> |
|
18 |
<%= check_box_tag 'doorkeeper_application[scopes][]', 'admin', @application.scopes.include?('admin'), |
|
19 |
:id => "doorkeeper_application_scopes_admin" |
|
20 |
%> |
|
21 |
<%= l :text_oauth_admin_permission %> |
|
22 |
</label> |
|
23 |
</fieldset> |
|
24 |
<% perms_by_module = Redmine::AccessControl.permissions.group_by {|p| p.project_module.to_s} %> |
|
25 |
<% perms_by_module.keys.sort.each do |mod| %> |
|
26 |
<fieldset><legend><%= mod.blank? ? l(:label_project) : l_or_humanize(mod, :prefix => 'project_module_') %></legend> |
|
27 |
<% perms_by_module[mod].each do |permission| %> |
|
28 |
<label class="floating"> |
|
29 |
<%= check_box_tag 'doorkeeper_application[scopes][]', permission.name.to_s, (permission.public? || @application.scopes.include?( permission.name.to_s)), |
|
30 |
:id => "doorkeeper_application_scopes_#{permission.name}", |
|
31 |
:disabled => permission.public? %> |
|
32 |
<%= l_or_humanize(permission.name, :prefix => 'permission_') %> |
|
33 |
</label> |
|
34 |
<% end %> |
|
35 |
</fieldset> |
|
36 |
<% end %> |
|
37 |
<br /><%= check_all_links 'scopes' %> |
|
38 |
<%= hidden_field_tag 'doorkeeper_application[scopes][]', '' %> |
|
39 |
</div> |
app/views/doorkeeper/applications/edit.html.erb | ||
---|---|---|
1 |
<%= title [l('label_oauth_application_plural'), oauth_applications_path], @application.name %> |
|
2 | ||
3 |
<%= labelled_form_for @application, url: doorkeeper_submit_path(@application) do |f| %> |
|
4 |
<%= render :partial => 'form', :locals => {:f => f} %> |
|
5 |
<%= submit_tag l(:button_save) %> |
|
6 |
<% end %> |
app/views/doorkeeper/applications/index.html.erb | ||
---|---|---|
1 |
<div class="contextual"> |
|
2 |
<%= link_to sprite_icon('add', t('.new')), new_oauth_application_path, :class => 'icon icon-add' %> |
|
3 |
</div> |
|
4 | ||
5 |
<%= title l 'label_oauth_application_plural' %> |
|
6 | ||
7 |
<% if @applications.any? %> |
|
8 |
<div class="autoscroll"> |
|
9 |
<table class="list"> |
|
10 |
<thead><tr> |
|
11 |
<th><%= t('.name') %></th> |
|
12 |
<th><%= t('.callback_url') %></th> |
|
13 |
<th><%= t('.scopes') %></th> |
|
14 |
<th></th> |
|
15 |
</tr></thead> |
|
16 |
<tbody> |
|
17 |
<% @applications.each do |application| %> |
|
18 |
<tr id="application_<%= application.id %>" class="<%= cycle("odd", "even") %>"> |
|
19 |
<td class="name"><span><%= link_to application.name, oauth_application_path(application) %></span></td> |
|
20 |
<td class="description"><%= truncate application.redirect_uri.split.join(', '), length: 50 %></td> |
|
21 |
<td class="description"><%= safe_join application.scopes.map{|scope| h l_or_humanize(scope, prefix: 'permission_')}, ", " %></td> |
|
22 |
<td class="buttons"> |
|
23 |
<%= link_to sprite_icon('edit', t('doorkeeper.applications.buttons.edit')), edit_oauth_application_path(application), class: 'icon icon-edit' %> |
|
24 |
<%= link_to sprite_icon('del', t('doorkeeper.applications.buttons.destroy')), oauth_application_path(application), :data => {:confirm => t('doorkeeper.applications.confirmations.destroy')}, :method => :delete, :class => 'icon icon-del' %> |
|
25 |
</td> |
|
26 |
</tr> |
|
27 |
<% end %> |
|
28 |
</tbody> |
|
29 |
</table> |
|
30 |
</div> |
|
31 |
<% else %> |
|
32 |
<p class="nodata"><%= l(:label_no_data) %></p> |
|
33 |
<% end %> |
app/views/doorkeeper/applications/new.html.erb | ||
---|---|---|
1 |
<%= title [l('label_oauth_application_plural'), oauth_applications_path], t('.title') %> |
|
2 | ||
3 |
<%= labelled_form_for @application, url: doorkeeper_submit_path(@application) do |f| %> |
|
4 |
<%= render :partial => 'form', :locals => { :f => f } %> |
|
5 |
<%= submit_tag l(:button_create) %> |
|
6 |
<% end %> |
app/views/doorkeeper/applications/show.html.erb | ||
---|---|---|
1 |
<div class="contextual"> |
|
2 |
<%= link_to sprite_icon('edit', t('doorkeeper.applications.buttons.edit')), edit_oauth_application_path(@application), :accesskey => accesskey(:edit), class: 'icon icon-edit' %> |
|
3 |
<%= link_to sprite_icon('del', t('doorkeeper.applications.buttons.destroy')), oauth_application_path(@application), :data => {:confirm => t('doorkeeper.applications.confirmations.destroy')}, :method => :delete, :class => 'icon icon-del' %> |
|
4 |
</div> |
|
5 | ||
6 |
<%= title [l('label_oauth_application_plural'), oauth_applications_path], @application.name %> |
|
7 | ||
8 |
<div class="box"> |
|
9 |
<h3 class="icon icon-passwd"><%= sprite_icon('key', l(:label_information_plural)) %></h3> |
|
10 |
<p> |
|
11 |
<span class="label"><%= t('.application_id') %>:</span> |
|
12 |
<code><%= h @application.uid %></code> |
|
13 |
</p> |
|
14 |
<p> |
|
15 |
<span class="label"><%= t('.secret') %>:</span> |
|
16 |
<code> |
|
17 |
<% secret = flash[:application_secret].presence || @application.plaintext_secret %> |
|
18 |
<% flash.delete :application_secret %> |
|
19 |
<% if secret.blank? && Doorkeeper.config.application_secret_hashed? %> |
|
20 |
<%= t('.secret_hashed') %> |
|
21 |
<% else %> |
|
22 |
<%= secret %> |
|
23 |
<% end %> |
|
24 |
</code> |
|
25 |
<% if secret.present? && Doorkeeper.config.application_secret_hashed? %> |
|
26 |
<strong><%= t "text_oauth_copy_secret_now" %></strong> |
|
27 |
<% end %> |
|
28 |
</p> |
|
29 |
<p> |
|
30 |
<span class="label"><%= t('.scopes') %>:</span> |
|
31 |
<code><%= safe_join @application.scopes.map{|scope| h l_or_humanize(scope, prefix: 'permission_')}, ", " %></code> |
|
32 |
</p> |
|
33 |
</div> |
|
34 | ||
35 |
<h3><%= t('.callback_urls') %></h3> |
|
36 | ||
37 |
<div class="autoscroll"> |
|
38 |
<table class="list"> |
|
39 |
<thead><tr> |
|
40 |
<th><%= t('.callback_url') %></th> |
|
41 |
<th></th> |
|
42 |
</tr></thead> |
|
43 |
<tbody> |
|
44 |
<% @application.redirect_uri.split.each do |uri| %> |
|
45 |
<tr class="<%= cycle("odd", "even") %>"> |
|
46 |
<td class="name"><span><%= uri %></span></td> |
|
47 |
<td class="buttons"> |
|
48 |
<%= link_to sprite_icon('shield-check', t('doorkeeper.applications.buttons.authorize')), oauth_authorization_path(client_id: @application.uid, redirect_uri: uri, response_type: 'code', scope: @application.scopes), class: 'icon icon-authorize', target: '_blank' %> |
|
49 |
</td> |
|
50 |
</tr> |
|
51 |
<% end %> |
|
52 |
</tbody> |
|
53 |
</table> |
|
54 |
</div> |
app/views/doorkeeper/authorizations/error.html.erb | ||
---|---|---|
1 |
<h2><%= t('doorkeeper.authorizations.error.title') %></h2> |
|
2 | ||
3 |
<p id="errorExplanation"><%= @pre_auth.error_response.body[:error_description] %></p> |
|
4 |
<p><a href="javascript:history.back()"><%= l(:button_back) %></a></p> |
|
5 | ||
6 |
<% html_title t('doorkeeper.authorizations.error.title') %> |
app/views/doorkeeper/authorizations/new.html.erb | ||
---|---|---|
1 |
<%= title t('.title') %> |
|
2 | ||
3 |
<div class="warning"> |
|
4 |
<p><strong><%=h @pre_auth.client.name %></strong></p> |
|
5 | ||
6 |
<p><%= raw t('.prompt', client_name: content_tag(:strong, class: "text-info") { @pre_auth.client.name }) %></p> |
|
7 | ||
8 |
<div class="oauth-permissions"> |
|
9 |
<p><%= t('.able_to') %>:</p> |
|
10 |
<ul> |
|
11 |
<li><%= l :text_oauth_implicit_permissions %></li> |
|
12 |
<% @pre_auth.scopes.each do |scope| %> |
|
13 |
<% if scope == 'admin' %> |
|
14 |
<li><%= l :label_oauth_permission_admin %></li> |
|
15 |
<% else %> |
|
16 |
<li><%= l_or_humanize(scope, prefix: 'permission_') %></li> |
|
17 |
<% end %> |
|
18 |
<% end %> |
|
19 |
</ul> |
|
20 |
</div> |
|
21 | ||
22 |
<% if @pre_auth.scopes.include?('admin') %> |
|
23 |
<p><%= l :text_oauth_admin_permission_info %></p> |
|
24 |
<% end %> |
|
25 |
</div> |
|
26 | ||
27 |
<p> |
|
28 |
<%= form_tag oauth_authorization_path, method: :post do %> |
|
29 |
<%= hidden_field_tag :client_id, @pre_auth.client.uid %> |
|
30 |
<%= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri %> |
|
31 |
<%= hidden_field_tag :state, @pre_auth.state %> |
|
32 |
<%= hidden_field_tag :response_type, @pre_auth.response_type %> |
|
33 |
<%= hidden_field_tag :scope, @pre_auth.scope %> |
|
34 |
<%= hidden_field_tag :code_challenge, @pre_auth.code_challenge %> |
|
35 |
<%= hidden_field_tag :code_challenge_method, @pre_auth.code_challenge_method %> |
|
36 |
<%= submit_tag t('doorkeeper.authorizations.buttons.authorize') %> |
|
37 |
<% end %> |
|
38 |
<%= form_tag oauth_authorization_path, method: :delete do %> |
|
39 |
<%= hidden_field_tag :client_id, @pre_auth.client.uid %> |
|
40 |
<%= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri %> |
|
41 |
<%= hidden_field_tag :state, @pre_auth.state %> |
|
42 |
<%= hidden_field_tag :response_type, @pre_auth.response_type %> |
|
43 |
<%= hidden_field_tag :scope, @pre_auth.scope %> |
|
44 |
<%= hidden_field_tag :code_challenge, @pre_auth.code_challenge %> |
|
45 |
<%= hidden_field_tag :code_challenge_method, @pre_auth.code_challenge_method %> |
|
46 |
<%= submit_tag t('doorkeeper.authorizations.buttons.deny') %> |
|
47 |
<% end %> |
|
48 |
</p> |
app/views/doorkeeper/authorizations/show.html.erb | ||
---|---|---|
1 |
<%= title [l('label_oauth_authorized_application_plural'), oauth_authorized_applications_path] %> |
|
2 | ||
3 |
<fieldset class="tabular"><legend><%= l(:label_information_plural) %></legend> |
|
4 |
<p> |
|
5 |
<label><%= t('.title') %>:</label> |
|
6 |
<code><%= params[:code] %></code> |
|
7 |
</p> |
|
8 |
</fieldset> |
app/views/doorkeeper/authorized_applications/index.html.erb | ||
---|---|---|
1 |
<%= title [t(:label_my_account), my_account_path], l('label_oauth_authorized_application_plural') %> |
|
2 | ||
3 |
<% if @applications.any? %> |
|
4 |
<div class="autoscroll"> |
|
5 |
<table class="list"> |
|
6 |
<thead><tr> |
|
7 |
<th><%= t('doorkeeper.authorized_applications.index.application') %></th> |
|
8 |
<th><%= t('doorkeeper.authorized_applications.index.created_at') %></th> |
|
9 |
<th></th> |
|
10 |
</tr></thead> |
|
11 |
<tbody> |
|
12 |
<% @applications.each do |application| %> |
|
13 |
<tr id="application_<%= application.id %>" class="<%= cycle("odd", "even") %>"> |
|
14 |
<td class="name"><span><%= application.name %></span></td> |
|
15 |
<td ><%= format_date application.created_at %></td> |
|
16 |
<td class="buttons"> |
|
17 |
<%= link_to sprite_icon('del', t('doorkeeper.authorized_applications.buttons.revoke')), oauth_authorized_application_path(application), :data => {:confirm => t('doorkeeper.authorized_applications.confirmations.revoke')}, :method => :delete, :class => 'icon icon-del' %> |
|
18 |
</td> |
|
19 |
</tr> |
|
20 |
<% end %> |
|
21 |
</tbody> |
|
22 |
</table> |
|
23 |
</div> |
|
24 |
<% else %> |
|
25 |
<p class="nodata"><%= l(:label_no_data) %></p> |
|
26 |
<% end %> |
|
27 | ||
28 |
<% content_for :sidebar do %> |
|
29 |
<% @user = User.current %> |
|
30 |
<%= render :partial => 'my/sidebar' %> |
|
31 |
<% end %> |
app/views/my/account.html.erb | ||
---|---|---|
1 | 1 |
<div class="contextual"> |
2 | 2 |
<%= additional_emails_link(@user) %> |
3 | 3 |
<%= link_to(sprite_icon('key', l(:button_change_password)), { :action => 'password'}, :class => 'icon icon-passwd') if @user.change_password_allowed? %> |
4 |
<%= link_to(sprite_icon('apps', l('label_oauth_authorized_application_plural')), oauth_authorized_applications_path, :class => 'icon icon-applications') if Setting.rest_api_enabled? %> |
|
4 | 5 |
<%= call_hook(:view_my_account_contextual, :user => @user)%> |
5 | 6 |
</div> |
6 | 7 |
app/views/users/show.api.rsb | ||
---|---|---|
11 | 11 |
api.passwd_changed_on @user.passwd_changed_on |
12 | 12 |
api.avatar_url gravatar_url(@user.mail, {rating: nil, size: nil, default: Setting.gravatar_default}) if @user.mail && Setting.gravatar_enabled? |
13 | 13 |
api.twofa_scheme @user.twofa_scheme if User.current.admin? || (User.current == @user) |
14 |
api.api_key @user.api_key if User.current.admin? || (User.current == @user)
|
|
14 |
api.api_key @user.api_key if (User.current.admin? || (User.current == @user && !User.current.authorized_by_oauth?))
|
|
15 | 15 |
api.status @user.status if User.current.admin? |
16 | 16 | |
17 | 17 |
render_api_custom_values @user.visible_custom_field_values, api |
config/icon_source.yml | ||
---|---|---|
233 | 233 |
svg: bulb |
234 | 234 |
- name: message-report |
235 | 235 |
svg: message-report |
236 |
- name: apps |
|
237 |
svg: apps |
|
238 |
- name: shield-check |
|
239 |
svg: shield-check |
config/initializers/30-redmine.rb | ||
---|---|---|
12 | 12 |
ActiveSupport::XmlMini.backend = 'Nokogiri' |
13 | 13 | |
14 | 14 |
Redmine::Preparation.prepare |
15 | ||
16 |
Doorkeeper.configure do |
|
17 |
orm :active_record |
|
18 | ||
19 |
# Issue access tokens with refresh token |
|
20 |
use_refresh_token |
|
21 | ||
22 |
# Authorization Code expiration time (default: 10 minutes). |
|
23 |
# |
|
24 |
# authorization_code_expires_in 10.minutes |
|
25 | ||
26 |
# Access token expiration time (default: 2 hours). |
|
27 |
# If you want to disable expiration, set this to `nil`. |
|
28 |
# |
|
29 |
# access_token_expires_in 2.hours |
|
30 | ||
31 |
# Hash access and refresh tokens before persisting them. |
|
32 |
# https://doorkeeper.gitbook.io/guides/security/token-and-application-secrets |
|
33 |
hash_token_secrets |
|
34 | ||
35 |
# Hash application secrets before persisting them. |
|
36 |
hash_application_secrets using: '::Doorkeeper::SecretStoring::BCrypt' |
|
37 | ||
38 |
# limit supported flows to Auth code |
|
39 |
grant_flows ['authorization_code'] |
|
40 | ||
41 |
realm Redmine::Info.app_name |
|
42 |
base_controller 'ApplicationController' |
|
43 |
default_scopes(*Redmine::AccessControl.public_permissions.map(&:name)) |
|
44 |
optional_scopes(*(Redmine::AccessControl.permissions.map(&:name) << :admin)) |
|
45 | ||
46 |
# Forbids creating/updating applications with arbitrary scopes that are |
|
47 |
# not in configuration, i.e. +default_scopes+ or +optional_scopes+. |
|
48 |
enforce_configured_scopes |
|
49 | ||
50 |
allow_token_introspection false |
|
51 | ||
52 |
# allow http loopback redirect URIs but require https for all others |
|
53 |
force_ssl_in_redirect_uri { |uri| !%w[localhost 127.0.0.1 web localohst:8080].include?(uri.host) } |
|
54 | ||
55 |
# Specify what redirect URI's you want to block during Application creation. |
|
56 |
forbid_redirect_uri { |uri| %w[data vbscript javascript].include?(uri.scheme.to_s.downcase) } |
|
57 | ||
58 |
resource_owner_authenticator do |
|
59 |
if require_login |
|
60 |
if Setting.rest_api_enabled? |
|
61 |
User.current |
|
62 |
else |
|
63 |
deny_access |
|
64 |
end |
|
65 |
end |
|
66 |
end |
|
67 | ||
68 |
admin_authenticator do |_routes| |
|
69 |
if !Setting.rest_api_enabled? || !User.current.admin? |
|
70 |
deny_access |
|
71 |
end |
|
72 |
end |
|
73 |
end |
|
74 | ||
75 |
# Use Redmine standard layouts and helpers for Doorkeeper OAuth2 screens |
|
76 |
Doorkeeper::ApplicationsController.layout "admin" |
|
77 |
Doorkeeper::ApplicationsController.main_menu = false |
|
78 |
Doorkeeper::AuthorizationsController.layout "base" |
|
79 |
Doorkeeper::AuthorizedApplicationsController.layout "base" |
|
80 |
Doorkeeper::AuthorizedApplicationsController.main_menu = false |
|
15 | 81 |
end |
16 | 82 | |
17 | 83 |
# Load the secret token from the Redmine configuration file |
... | ... | |
42 | 108 |
paths = theme.asset_paths |
43 | 109 |
Rails.application.config.assets.redmine_extension_paths << paths if paths.present? |
44 | 110 |
end |
111 | ||
112 |
Doorkeeper::ApplicationsController.class_eval do |
|
113 |
require_sudo_mode :create, :show, :update, :destroy |
|
114 |
end |
|
115 | ||
116 |
Doorkeeper::AuthorizationsController.class_eval do |
|
117 |
require_sudo_mode :create, :destroy |
|
118 |
end |
|
45 | 119 |
end |
46 | 120 | |
47 | 121 |
Rails.application.deprecators[:redmine] = ActiveSupport::Deprecation.new('7.0', 'Redmine') |
config/initializers/doorkeeper.rb | ||
---|---|---|
1 |
# frozen_string_literal: true |
|
2 | ||
3 |
# rubocop:disable Lint/EmptyBlock |
|
4 |
Doorkeeper.configure do |
|
5 |
end |
|
6 | ||
7 |
Rails.application.config.to_prepare do |
|
8 |
end |
|
9 |
# rubocop:enable Lint/EmptyBlock |
config/locales/de.yml | ||
---|---|---|
971 | 971 |
permission_view_time_entries: Gebuchte Aufwände ansehen |
972 | 972 |
permission_view_wiki_edits: Wiki-Versionsgeschichte ansehen |
973 | 973 |
permission_view_wiki_pages: Wiki ansehen |
974 |
permission_view_project: Projekte ansehen |
|
975 |
permission_search_project: Projekte suchen |
|
976 |
permission_view_members: Projektmitglieder anzeigen |
|
974 | 977 | |
975 | 978 |
project_module_boards: Foren |
976 | 979 |
project_module_calendar: Kalender |
... | ... | |
1477 | 1480 |
other: "%{count} others" |
1478 | 1481 |
text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> |
1479 | 1482 |
to generate their avatars. |
1483 |
label_oauth_permission_admin: Administrator-Zugriff |
|
1484 |
label_oauth_admin_access: Administration |
|
1485 |
label_oauth_application_plural: Applikationen |
|
1486 |
label_oauth_authorized_application_plural: Autorisierte Applikationen |
|
1487 |
text_oauth_admin_permission: Voller Admin-Zugriff. Wenn diese Applikation durch einen Administrator autorisiert wird, kann sie alle Daten lesen und schreiben, auch im Namen anderer Benutzer. |
|
1488 |
text_oauth_admin_permission_info: Diese Applikation verlangt vollen Administrator-Zugriff. Wenn Sie ein Administrator sind (oder in Zukunft Administrator werden), wird sie in der Lage sein, alle Daten zu lesen und zu schreiben, auch im Namen anderer Benutzer. Dies kann vermieden werden, indem die Applikation mit einem anderen Benutzerkonto ohne Administrator-Privileg autorisiert wird. |
|
1489 |
text_oauth_copy_secret_now: Das Geheimnis bitte jetzt an einen sicheren Ort kopieren, es kann nicht erneut angezeigt werden. |
|
1490 |
text_oauth_implicit_permissions: Zugriff auf Benutzername, Login sowie auf die primäre Email-Adresse |
|
1491 |
text_oauth_info_scopes: Scopes für die Applikation auswählen. Die Applikation wird niemals mehr Rechte haben als hier ausgewählt. Sie wird außerdem auf die Rollen und Projektmitgliedschaften des Benutzers, der sie autorisiert hat, beschränkt sein. |
config/locales/en.yml | ||
---|---|---|
139 | 139 |
must_contain_special_chars: "must contain special characters (!, $, %, ...)" |
140 | 140 |
domain_not_allowed: "contains a domain not allowed (%{domain})" |
141 | 141 |
too_simple: "is too simple" |
142 |
attributes: |
|
143 |
doorkeeper/application: |
|
144 |
scopes: Scopes |
|
142 | 145 | |
143 | 146 |
actionview_instancetag_blank_option: Please select |
144 | 147 | |
... | ... | |
605 | 608 |
permission_manage_related_issues: Manage related issues |
606 | 609 |
permission_import_issues: Import issues |
607 | 610 |
permission_log_time_for_other_users: Log spent time for other users |
611 |
permission_view_project: View projects |
|
612 |
permission_search_project: Search projects |
|
613 |
permission_view_members: View project members |
|
614 | ||
608 | 615 | |
609 | 616 |
project_module_issue_tracking: Issue tracking |
610 | 617 |
project_module_time_tracking: Time tracking |
... | ... | |
1158 | 1165 |
label_time_by_author: "%{time} by %{author}" |
1159 | 1166 |
label_involved_principals: Author / Previous assignee |
1160 | 1167 |
label_progressbar: Progress bar |
1168 |
label_oauth_permission_admin: Administrate this Redmine |
|
1169 |
label_oauth_admin_access: Administration |
|
1170 |
label_oauth_application_plural: Applications |
|
1171 |
label_oauth_authorized_application_plural: Authorized applications |
|
1161 | 1172 | |
1162 | 1173 |
button_login: Login |
1163 | 1174 |
button_submit: Submit |
... | ... | |
1343 | 1354 |
text_allowed_queries_to_select: Public (to any users) queries only selectable |
1344 | 1355 |
text_setting_config_change: You can configure the behaviour in config/configuration.yml. Please restart the application after editing it. |
1345 | 1356 |
text_setting_gravatar_default_initials_html: Users' initials are sent to <a href="https://www.gravatar.com">https://www.gravatar.com</a> to generate their avatars. |
1357 |
text_oauth_admin_permission: Full administrative access. When authorized by an Administrator, this application will be able to read and write all data and impersonate other users. |
|
1358 |
text_oauth_admin_permission_info: This application requests full administrative access. If you are an Administrator (or become one in the future), it will be able to read and write all data and impersonate other users on your behalf. If you want to avoid this, authorize it as a user without Administrator privileges instead. |
|
1359 |
text_oauth_copy_secret_now: Copy the secret to a safe place now, it will not be shown again. |
|
1360 |
text_oauth_implicit_permissions: View your name, login and primary email address |
|
1361 |
text_oauth_info_scopes: Select the scopes this application may request. The application will not be allowed to do more than what is selected here. It will also always be limited by the roles and project memberships of the user who authorized it. |
|
1346 | 1362 | |
1347 | 1363 |
default_role_manager: Manager |
1348 | 1364 |
default_role_developer: Developer |
config/routes.rb | ||
---|---|---|
18 | 18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
19 | 19 | |
20 | 20 |
Rails.application.routes.draw do |
21 |
use_doorkeeper do |
|
22 |
controllers :applications => 'oauth2_applications' |
|
23 |
end |
|
24 | ||
25 |
root :to => 'welcome#index' |
|
21 | 26 |
root :to => 'welcome#index', :as => 'home' |
22 | 27 | |
23 | 28 |
match 'login', :to => 'account#login', :as => 'signin', :via => [:get, :post] |
db/migrate/20170107092155_create_doorkeeper_tables.rb | ||
---|---|---|
1 |
class CreateDoorkeeperTables < ActiveRecord::Migration[4.2] |
|
2 |
def change |
|
3 |
create_table :oauth_applications do |t| |
|
4 |
t.string :name, null: false |
|
5 |
t.string :uid, null: false |
|
6 |
t.string :secret, null: false |
|
7 |
t.text :redirect_uri, null: false |
|
8 |
t.text :scopes, null: false |
|
9 |
t.boolean :confidential, null: false, default: true |
|
10 |
t.timestamps null: false |
|
11 |
end |
|
12 | ||
13 |
add_index :oauth_applications, :uid, unique: true |
|
14 | ||
15 |
create_table :oauth_access_grants do |t| |
|
16 |
t.integer :resource_owner_id, null: false |
|
17 |
t.references :application, null: false |
|
18 |
t.string :token, null: false |
|
19 |
t.integer :expires_in, null: false |
|
20 |
t.text :redirect_uri, null: false |
|
21 |
t.datetime :created_at, null: false |
|
22 |
t.datetime :revoked_at |
|
23 |
t.text :scopes |
|
24 |
end |
|
25 | ||
26 |
add_index :oauth_access_grants, :token, unique: true |
|
27 |
add_foreign_key( |
|
28 |
:oauth_access_grants, |
|
29 |
:oauth_applications, |
|
30 |
column: :application_id |
|
31 |
) |
|
32 |
add_foreign_key( |
|
33 |
:oauth_access_grants, |
|
34 |
:users, |
|
35 |
column: :resource_owner_id |
|
36 |
) |
|
37 | ||
38 |
create_table :oauth_access_tokens do |t| |
|
39 |
t.integer :resource_owner_id |
|
40 |
t.references :application |
|
41 | ||
42 |
t.string :token, null: false |
|
43 | ||
44 |
t.string :refresh_token |
|
45 |
t.integer :expires_in |
|
46 |
t.datetime :revoked_at |
|
47 |
t.datetime :created_at, null: false |
|
48 |
t.text :scopes |
|
49 | ||
50 |
t.string :previous_refresh_token, null: false, default: "" |
|
51 |
end |
|
52 | ||
53 |
add_index :oauth_access_tokens, :token, unique: true |
|
54 |
add_index :oauth_access_tokens, :resource_owner_id |
|
55 |
add_index :oauth_access_tokens, :refresh_token, unique: true |
|
56 | ||
57 |
add_foreign_key( |
|
58 |
:oauth_access_tokens, |
|
59 |
:oauth_applications, |
|
60 |
column: :application_id |
|
61 |
) |
|
62 |
add_foreign_key( |
|
63 |
:oauth_access_tokens, |
|
64 |
:users, |
|
65 |
column: :resource_owner_id |
|
66 |
) |
|
67 |
end |
|
68 |
end |
db/migrate/20200812065227_enable_pkce.rb | ||
---|---|---|
1 |
# frozen_string_literal: true |
|
2 | ||
3 |
class EnablePkce < ActiveRecord::Migration[5.2] |
|
4 |
def change |
|
5 |
add_column :oauth_access_grants, :code_challenge, :string, null: true |
|
6 |
add_column :oauth_access_grants, :code_challenge_method, :string, null: true |
|
7 |
end |
|
8 |
end |
lib/redmine/preparation.rb | ||
---|---|---|
280 | 280 |
{:controller => 'auth_sources', :action => 'index'}, |
281 | 281 |
:icon => 'server-authentication', |
282 | 282 |
:html => {:class => 'icon icon-server-authentication'} |
283 |
menu.push :applications, {:controller => 'oauth2_applications', :action => 'index'}, |
|
284 |
:if => Proc.new { Setting.rest_api_enabled? }, |
|
285 |
:caption => :'doorkeeper.layouts.admin.nav.applications', |
|
286 |
:icon => 'apps', |
|
287 |
:html => {:class => 'icon icon-applications'} |
|
283 | 288 |
menu.push :plugins, {:controller => 'admin', :action => 'plugins'}, |
284 | 289 |
:last => true, |
285 | 290 |
:icon => 'plugins', |
test/system/oauth_provider_test.rb | ||
---|---|---|
1 |
# frozen_string_literal: true |
|
2 | ||
3 |
require File.expand_path('../application_system_test_case', __dir__) |
|
4 |
require 'oauth2' |
|
5 |
require 'webrick' |
|
6 | ||
7 |
class OauthProviderSystemTest < ApplicationSystemTestCase |
|
8 |
fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles, |
|
9 |
:trackers, :projects_trackers, :enabled_modules, :issue_statuses, :issues, |
|
10 |
:enumerations, :custom_fields, :custom_values, :custom_fields_trackers, |
|
11 |
:watchers, :journals, :journal_details, :versions, |
|
12 |
:workflows |
|
13 |
test 'application creation and authorization' do |
|
14 |
# |
|
15 |
# admin creates the application, granting permissions and generating a uuid |
|
16 |
# and secret. |
|
17 |
# |
|
18 |
log_user 'admin', 'admin' |
|
19 |
with_settings rest_api_enabled: 1 do |
|
20 |
visit '/admin' |
|
21 |
within 'div#admin-menu ul' do |
|
22 |
click_link 'Applications' |
|
23 |
end |
|
24 |
click_link 'New Application' |
|
25 |
fill_in 'Name', with: 'Oauth Test' |
|
26 | ||
27 |
# as per https://tools.ietf.org/html/rfc8252#section-7.3, the port can be |
|
28 |
# anything when the redirect URI's host is 127.0.0.1. |
|
29 |
fill_in 'Redirect URI', with: 'http://127.0.0.1' |
|
30 | ||
31 |
check 'View Issues' |
|
32 |
click_button 'Create' |
|
33 |
end |
|
34 | ||
35 |
assert app = Doorkeeper::Application.find_by_name('Oauth Test') |
|
36 | ||
37 |
find 'h2', visible: true, text: /Oauth Test/ |
|
38 |
find 'p code', visible: true, text: app.uid |
|
39 |
find 'p strong', visible: true, text: /will not be shown again/ |
|
40 |
find 'p code', visible: true, text: /View Issues/ |
|
41 | ||
42 |
# scrape the clear text secret from the page |
|
43 |
app_secret = all(:css, 'p code')[1].text |
|
44 | ||
45 |
click_link 'Sign out' |
|
46 | ||
47 |
# |
|
48 |
# regular user authorizes the application |
|
49 |
# |
|
50 |
client = OAuth2::Client.new(app.uid, app_secret, site: "http://127.0.0.1:#{test_port}/") |
|
51 | ||
52 |
# set up a dummy http listener to handle the redirect |
|
53 |
port = rand 10000..20000 |
|
54 |
redirect_uri = "http://127.0.0.1:#{port}" |
|
55 |
# the request handler below will set this to the auth token |
|
56 |
token = nil |
|
57 | ||
58 |
# launches webrick, listening for the redirect with the auth code. |
|
59 |
launch_client_app(port: port) do |req, res| |
|
60 |
# get access code from code url param |
|
61 |
if code = req.query['code'].presence |
|
62 |
# exchange it for token |
|
63 |
token = client.auth_code.get_token(code, redirect_uri: redirect_uri) |
|
64 |
res.body = "<html><body><p>Authorization succeeded, you may close this window now.</p></body></html>" |
|
65 |
end |
|
66 |
end |
|
67 | ||
68 |
log_user 'jsmith', 'jsmith' |
|
69 |
with_settings rest_api_enabled: 1 do |
|
70 |
visit '/my/account' |
|
71 |
click_link 'Authorized applications' |
|
72 |
find 'p.nodata', visible: true |
|
73 | ||
74 |
# an oauth client would send the user to this url to request permission |
|
75 |
url = client.auth_code.authorize_url redirect_uri: redirect_uri, scope: 'view_issues view_project' |
|
76 |
uri = URI.parse url |
|
77 |
visit uri.path + '?' + uri.query |
|
78 | ||
79 |
find 'h2', visible: true, text: 'Authorization required' |
|
80 |
find 'p', visible: true, text: /Authorize Oauth Test/ |
|
81 |
find '.oauth-permissions', visible: true, text: /View Issues/ |
|
82 |
find '.oauth-permissions', visible: true, text: /View project/ |
|
83 | ||
84 |
click_button 'Authorize' |
|
85 | ||
86 |
assert grant = app.access_grants.last |
|
87 |
assert_equal 'view_issues view_project', grant.scopes.to_s |
|
88 | ||
89 |
# check for output defined above in the request handler |
|
90 |
find 'p', visible: true, text: /Authorization succeeded/ |
|
91 |
assert token.present? |
|
92 | ||
93 |
visit '/my/account' |
|
94 |
click_link 'Authorized applications' |
|
95 |
find 'td', visible: true, text: /Oauth Test/ |
|
96 |
click_link 'Sign out' |
|
97 | ||
98 |
# Now, use the token for some API requests |
|
99 |
assert_raise(RestClient::Unauthorized) do |
|
100 |
RestClient.get "http://localhost:#{test_port}/projects/onlinestore/issues.json" |
|
101 |
end |
|
102 | ||
103 |
headers = { 'Authorization' => "Bearer #{token.token}" } |
|
104 |
r = RestClient.get "http://localhost:#{test_port}/projects/onlinestore/issues.json", headers |
|
105 |
issues = JSON.parse(r.body)['issues'] |
|
106 |
assert issues.any? |
|
107 | ||
108 |
# time entries access is not part of the granted scopes |
|
109 |
assert_raise(RestClient::Forbidden) do |
|
110 |
RestClient.get "http://localhost:#{test_port}/projects/onlinestore/time_entries.json", headers |
|
111 |
end |
|
112 |
end |
|
113 |
end |
|
114 | ||
115 |
private |
|
116 | ||
117 |
def launch_client_app(port: 12345, path: '/', &block) |
|
118 |
server = WEBrick::HTTPServer.new Port: port |
|
119 |
trap('INT') { server.shutdown } |
|
120 |
server.mount_proc(path, block) |
|
121 |
Thread.new { server.start } |
|
122 |
port |
|
123 |
end |
|
124 | ||
125 |
def test_port |
|
126 |
Capybara.current_session.server.port |
|
127 |
end |
|
128 |
end |
test/unit/role_test.rb | ||
---|---|---|
175 | 175 |
assert_equal false, role.permissions_tracker_ids?(:view_issues, 1) |
176 | 176 |
end |
177 | 177 | |
178 |
def test_allowed_to_with_symbol |
|
179 |
role = Role.create!(:name => 'Test', :permissions => [:view_issues]) |
|
180 |
assert_equal true, role.allowed_to?(:view_issues) |
|
181 |
assert_equal false, role.allowed_to?(:add_issues) |
|
182 |
end |
|
183 | ||
184 |
def test_allowed_to_with_symbol_and_scope |
|
185 |
role = Role.create!(:name => 'Test', :permissions => [:view_issues, :delete_issues]) |
|
186 |
assert_equal true, role.allowed_to?(:view_issues, [:view_issues, :add_issues]) |
|
187 |
assert_equal false, role.allowed_to?(:add_issues, [:view_issues, :add_issues]) |
|
188 |
assert_equal false, role.allowed_to?(:delete_issues, [:view_issues, :add_issues]) |
|
189 |
end |
|
190 | ||
191 |
def test_allowed_to_with_hash |
|
192 |
role = Role.create!(:name => 'Test', :permissions => [:view_issues]) |
|
193 |
assert_equal true, role.allowed_to?(:controller => 'issues', :action => 'show') |
|
194 |
assert_equal false, role.allowed_to?(:controller => 'issues', :action => 'create') |
|
195 |
end |
|
196 | ||
197 |
def test_allowed_to_with_hash_and_scope |
|
198 |
role = Role.create!(:name => 'Test', :permissions => [:view_issues, :delete_issues]) |
|
199 |
assert_equal true, role.allowed_to?({:controller => 'issues', :action => 'show'}, [:view_issues, :add_issues]) |
|
200 |
assert_equal false, role.allowed_to?({:controller => 'issues', :action => 'create'}, [:view_issues, :add_issues]) |
|
201 |
assert_equal false, role.allowed_to?({:controller => 'issues', :action => 'destroy'}, [:view_issues, :add_issues]) |
|
202 |
end |
|
203 | ||
178 | 204 |
def test_has_permission_without_permissions |
179 | 205 |
role = Role.create!(:name => 'Test') |
180 | 206 |
assert_equal false, role.has_permission?(:delete_issues) |
test/unit/user_test.rb | ||
---|---|---|
1398 | 1398 |
end |
1399 | 1399 |
end |
1400 | 1400 | |
1401 |
def test_should_recognize_authorized_by_oauth |
|
1402 |
u = User.find 2 |
|
1403 |
assert_not u.authorized_by_oauth? |
|
1404 |
u.oauth_scope = [:add_issues, :view_issues] |
|
1405 |
assert u.authorized_by_oauth? |
|
1406 |
end |
|
1407 | ||
1408 |
def test_admin_should_be_limited_by_oauth_scope |
|
1409 |
u = User.find_by_admin(true) |
|
1410 |
assert u.admin? |
|
1411 | ||
1412 |
u.oauth_scope = [:add_issues, :view_issues] |
|
1413 |
assert_not u.admin? |
|
1414 | ||
1415 |
u.oauth_scope = [:add_issues, :view_issues, :admin] |
|
1416 |
assert u.admin? |
|
1417 | ||
1418 |
u = User.find_by_admin(false) |
|
1419 |
assert_not u.admin? |
|
1420 |
u.oauth_scope = [:add_issues, :view_issues, :admin] |
|
1421 |
assert_not u.admin? |
|
1422 |
end |
|
1423 | ||
1424 |
def test_oauth_scope_should_limit_global_user_permissions |
|
1425 |
admin = User.find 1 |
|
1426 |
user = User.find 2 |
|
1427 |
[admin, user].each do |u| |
|
1428 |
assert u.allowed_to?(:add_issues, nil, global: true) |
|
1429 |
assert u.allowed_to?(:view_issues, nil, global: true) |
|
1430 |
u.oauth_scope = [:view_issues] |
|
1431 |
assert_not u.allowed_to?(:add_issues, nil, global: true) |
|
1432 |
assert u.allowed_to?(:view_issues, nil, global: true) |
|
1433 |
end |
|
1434 |
end |
|
1435 | ||
1436 |
def test_oauth_scope_should_limit_project_user_permissions |
|
1437 |
admin = User.find 1 |
|
1438 |
project = Project.find 5 |
|
1439 |
assert admin.allowed_to?(:add_issues, project) |
|
1440 |
assert admin.allowed_to?(:view_issues, project) |
|
1441 |
admin.oauth_scope = [:view_issues] |
|
1442 |
assert_not admin.allowed_to?(:add_issues, project) |
|
1443 |
assert admin.allowed_to?(:view_issues, project) |
|
1444 | ||
1445 |
admin.oauth_scope = [:view_issues, :admin] |
|
1446 |
assert admin.allowed_to?(:add_issues, project) |
|
1447 |
assert admin.allowed_to?(:view_issues, project) |
|
1448 | ||
1449 |
user = User.find 2 |
|
1450 |
project = Project.find 1 |
|
1451 |
assert user.allowed_to?(:add_issues, project) |
|
1452 |
assert user.allowed_to?(:view_issues, project) |
|
1453 |
user.oauth_scope = [:view_issues] |
|
1454 |
assert_not user.allowed_to?(:add_issues, project) |
|
1455 |
assert user.allowed_to?(:view_issues, project) |
|
1456 | ||
1457 |
user.oauth_scope = [:view_issues, :admin] |
|
1458 |
assert_not user.allowed_to?(:add_issues, project) |
|
1459 |
assert user.allowed_to?(:view_issues, project) |
|
1460 |
end |
|
1461 | ||
1401 | 1462 |
def test_destroy_should_delete_associated_reactions |
1402 | 1463 |
users(:users_004).reactions.create!( |
1403 | 1464 |
[ |
- « Previous
- 1
- …
- 25
- 26
- 27
- Next »