summaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/images/icons.svg11
-rw-r--r--app/assets/stylesheets/application.css5
-rw-r--r--app/controllers/application_controller.rb8
-rw-r--r--app/controllers/oauth2_applications_controller.rb38
-rw-r--r--app/helpers/application_helper.rb2
-rw-r--r--app/helpers/queries_helper.rb2
-rw-r--r--app/models/issue.rb2
-rw-r--r--app/models/role.rb26
-rw-r--r--app/models/user.rb19
-rw-r--r--app/models/user_preference.rb2
-rw-r--r--app/views/doorkeeper/applications/_form.html.erb39
-rw-r--r--app/views/doorkeeper/applications/edit.html.erb6
-rw-r--r--app/views/doorkeeper/applications/index.html.erb33
-rw-r--r--app/views/doorkeeper/applications/new.html.erb6
-rw-r--r--app/views/doorkeeper/applications/show.html.erb54
-rw-r--r--app/views/doorkeeper/authorizations/error.html.erb6
-rw-r--r--app/views/doorkeeper/authorizations/new.html.erb48
-rw-r--r--app/views/doorkeeper/authorizations/show.html.erb8
-rw-r--r--app/views/doorkeeper/authorized_applications/index.html.erb31
-rw-r--r--app/views/my/account.html.erb1
-rw-r--r--app/views/users/show.api.rsb2
21 files changed, 334 insertions, 15 deletions
diff --git a/app/assets/images/icons.svg b/app/assets/images/icons.svg
index e30ef5446..6283537ce 100644
--- a/app/assets/images/icons.svg
+++ b/app/assets/images/icons.svg
@@ -59,6 +59,13 @@
<path d="M12 15v6"/>
<path d="M5 15h3l-3 6h3"/>
</symbol>
+ <symbol viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" id="icon--apps">
+ <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"/>
+ <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"/>
+ <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"/>
+ <path d="M14 7l6 0"/>
+ <path d="M17 4l0 6"/>
+ </symbol>
<symbol viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" id="icon--arrow-right">
<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"/>
</symbol>
@@ -398,6 +405,10 @@
<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"/>
<path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0"/>
</symbol>
+ <symbol viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" id="icon--shield-check">
+ <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"/>
+ <path d="M15 19l2 2l4 -4"/>
+ </symbol>
<symbol viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" id="icon--stats">
<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"/>
<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"/>
diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css
index 4e894c1f5..833e998f8 100644
--- a/app/assets/stylesheets/application.css
+++ b/app/assets/stylesheets/application.css
@@ -359,7 +359,7 @@ table.list td.buttons a, div.buttons a, table.list td.buttons span.icon-only { m
table.list td.buttons a:last-child, div.buttons a:last-child { margin-right: 0; }
table.list td.buttons img, div.buttons img {vertical-align:middle;}
table.list td.reorder {width:15%; white-space:nowrap; text-align:center; }
-table.list table.progress td {padding-right:0px;}
+table.list table.progress td {padding-right:0; border-top: none;}
table.list caption { text-align: left; padding: 0.5em 0.5em 0.5em 0; }
table.list tr.overdue td.due_date { color: #c22; }
table.list thead.related-issues th { background-color: inherit; font-size: 11px; border: none; }
@@ -1316,6 +1316,9 @@ div.flash.warning svg.icon-svg, .conflict svg.icon-svg {
color: #A6750C;
}
+.warning .oauth-permissions { display:inline-block;text-align:left; }
+.warning .oauth-permissions p { margin-top:0;-webkit-margin-before:0;}
+
#errorExplanation ul { font-size: 0.9em;}
#errorExplanation h2, #errorExplanation p { display: none; }
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 074392709..a01d5c75f 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -131,6 +131,14 @@ class ApplicationController < ActionController::Base
if (key = api_key_from_request)
# Use API key
user = User.find_by_api_key(key)
+ elsif access_token = Doorkeeper.authenticate(request)
+ # Oauth
+ 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
elsif /\ABasic /i.match?(request.authorization.to_s)
# HTTP Basic, either username/password or API key/random
authenticate_with_http_basic do |username, password|
diff --git a/app/controllers/oauth2_applications_controller.rb b/app/controllers/oauth2_applications_controller.rb
new file mode 100644
index 000000000..107af2ec0
--- /dev/null
+++ b/app/controllers/oauth2_applications_controller.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+#
+# Redmine - project management software
+# Copyright (C) 2006- Jean-Philippe Lang
+#
+# 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.
+#
+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/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 99a760c1d..285528422 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -518,6 +518,8 @@ module ApplicationHelper
def render_flash_messages
s = +''
flash.each do |k, v|
+ next unless v.is_a?(String)
+
s << content_tag('div', notice_icon(k) + v.html_safe, :class => "flash #{k}", :id => "flash_#{k}")
end
s.html_safe
diff --git a/app/helpers/queries_helper.rb b/app/helpers/queries_helper.rb
index ca7168f27..3aef7083a 100644
--- a/app/helpers/queries_helper.rb
+++ b/app/helpers/queries_helper.rb
@@ -169,7 +169,7 @@ module QueriesHelper
group_name = format_object(group)
end
group_name ||= ""
- group_count = result_count_by_group ? result_count_by_group[group] : nil
+ group_count = result_count_by_group&.[](group)
group_totals = totals_by_group.map {|column, t| total_tag(column, t[group] || 0)}.join(" ").html_safe
end
end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index bfef3533a..576840843 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -1175,7 +1175,7 @@ class Issue < ApplicationRecord
if leaf?
spent_hours
else
- self_and_descendants.joins(:time_entries).sum("#{TimeEntry.table_name}.hours").to_f || 0.0
+ self_and_descendants.joins(:time_entries).sum("#{TimeEntry.table_name}.hours").to_f
end
end
diff --git a/app/models/role.rb b/app/models/role.rb
index 3ca4f92a1..870bbe945 100644
--- a/app/models/role.rb
+++ b/app/models/role.rb
@@ -198,11 +198,14 @@ class Role < ApplicationRecord
# action can be:
# * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
# * a permission Symbol (eg. :edit_project)
- def allowed_to?(action)
+ # scope can be:
+ # * an array of permissions which will be used as filter (logical AND)
+
+ def allowed_to?(action, scope=nil)
if action.is_a? Hash
- allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
+ allowed_actions(scope).include? "#{action[:controller]}/#{action[:action]}"
else
- allowed_permissions.include? action
+ allowed_permissions(scope).include? action
end
end
@@ -298,13 +301,20 @@ class Role < ApplicationRecord
private
- def allowed_permissions
- @allowed_permissions ||= permissions + Redmine::AccessControl.public_permissions.collect {|p| p.name}
+ def allowed_permissions(scope = nil)
+ scope = scope.sort if scope.present? # to maintain stable cache keys
+ @allowed_permissions ||= {}
+ @allowed_permissions[scope] ||= begin
+ unscoped = permissions + Redmine::AccessControl.public_permissions.collect {|p| p.name}
+ scope.present? ? unscoped & scope : unscoped
+ end
end
- def allowed_actions
- @actions_allowed ||=
- allowed_permissions.inject([]) do |actions, permission|
+ def allowed_actions(scope = nil)
+ scope = scope.sort if scope.present? # to maintain stable cache keys
+ @actions_allowed ||= {}
+ @actions_allowed[scope] ||=
+ allowed_permissions(scope).inject([]) do |actions, permission|
actions += Redmine::AccessControl.allowed_actions(permission)
end.flatten
end
diff --git a/app/models/user.rb b/app/models/user.rb
index c1a860f5a..496084ceb 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -112,6 +112,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
LOGIN_LENGTH_LIMIT = 60
MAIL_LENGTH_LIMIT = 254
@@ -732,6 +733,20 @@ class User < Principal
end
end
+ def admin?
+ if authorized_by_oauth?
+ # when signed in via oauth, the user only acts as admin when the admin scope is set
+ super and @oauth_scope.include?(:admin)
+ else
+ super
+ end
+ end
+
+ # true if the user has signed in via oauth
+ def authorized_by_oauth?
+ !@oauth_scope.nil?
+ end
+
# Return true if the user is allowed to do the specified action on a specific context
# Action can be:
# * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
@@ -752,7 +767,7 @@ class User < Principal
roles.any? do |role|
(context.is_public? || role.member?) &&
- role.allowed_to?(action) &&
+ role.allowed_to?(action, @oauth_scope) &&
(block ? yield(role, self) : true)
end
elsif context && context.is_a?(Array)
@@ -771,7 +786,7 @@ class User < Principal
# authorize if user has at least one role that has this permission
roles = self.roles.to_a | [builtin_role]
roles.any? do |role|
- role.allowed_to?(action) &&
+ role.allowed_to?(action, @oauth_scope) &&
(block ? yield(role, self) : true)
end
else
diff --git a/app/models/user_preference.rb b/app/models/user_preference.rb
index 8b19d9a5a..e1842b131 100644
--- a/app/models/user_preference.rb
+++ b/app/models/user_preference.rb
@@ -73,7 +73,7 @@ class UserPreference < ApplicationRecord
if has_attribute? attr_name
super
else
- others ? others[attr_name] : nil
+ others&.[](attr_name)
end
end
diff --git a/app/views/doorkeeper/applications/_form.html.erb b/app/views/doorkeeper/applications/_form.html.erb
new file mode 100644
index 000000000..e4f778f63
--- /dev/null
+++ b/app/views/doorkeeper/applications/_form.html.erb
@@ -0,0 +1,39 @@
+<%= error_messages_for 'application' %>
+<div class="box tabular">
+ <p><%= f.text_field :name, :required => true %></p>
+
+ <p>
+ <%= f.text_area :redirect_uri, :required => true, :size => 60, :label => :'activerecord.attributes.doorkeeper/application.redirect_uri' %>
+ <em class="info">
+ <%= t('doorkeeper.applications.help.redirect_uri') %>
+ </em>
+ </p>
+</div>
+
+<h3><%= l(:'activerecord.attributes.doorkeeper/application.scopes') %></h3>
+<p><em class="info"><%= l :text_oauth_info_scopes %></em></p>
+<div class="box tabular" id="scopes">
+<fieldset><legend><%= l :label_oauth_admin_access %></legend>
+ <label class="floating" style="width: auto;">
+ <%= check_box_tag 'doorkeeper_application[scopes][]', 'admin', @application.scopes.include?('admin'),
+ :id => "doorkeeper_application_scopes_admin"
+ %>
+ <%= l :text_oauth_admin_permission %>
+ </label>
+</fieldset>
+<% perms_by_module = Redmine::AccessControl.permissions.group_by {|p| p.project_module.to_s} %>
+<% perms_by_module.keys.sort.each do |mod| %>
+ <fieldset><legend><%= mod.blank? ? l(:label_project) : l_or_humanize(mod, :prefix => 'project_module_') %></legend>
+ <% perms_by_module[mod].each do |permission| %>
+ <label class="floating">
+ <%= check_box_tag 'doorkeeper_application[scopes][]', permission.name.to_s, (permission.public? || @application.scopes.include?( permission.name.to_s)),
+ :id => "doorkeeper_application_scopes_#{permission.name}",
+ :disabled => permission.public? %>
+ <%= l_or_humanize(permission.name, :prefix => 'permission_') %>
+ </label>
+ <% end %>
+ </fieldset>
+<% end %>
+<br /><%= check_all_links 'scopes' %>
+<%= hidden_field_tag 'doorkeeper_application[scopes][]', '' %>
+</div>
diff --git a/app/views/doorkeeper/applications/edit.html.erb b/app/views/doorkeeper/applications/edit.html.erb
new file mode 100644
index 000000000..aebc1a841
--- /dev/null
+++ b/app/views/doorkeeper/applications/edit.html.erb
@@ -0,0 +1,6 @@
+<%= title [l('label_oauth_application_plural'), oauth_applications_path], @application.name %>
+
+<%= labelled_form_for @application, url: doorkeeper_submit_path(@application) do |f| %>
+ <%= render :partial => 'form', :locals => {:f => f} %>
+ <%= submit_tag l(:button_save) %>
+<% end %>
diff --git a/app/views/doorkeeper/applications/index.html.erb b/app/views/doorkeeper/applications/index.html.erb
new file mode 100644
index 000000000..0ba31c0e8
--- /dev/null
+++ b/app/views/doorkeeper/applications/index.html.erb
@@ -0,0 +1,33 @@
+<div class="contextual">
+<%= link_to sprite_icon('add', t('.new')), new_oauth_application_path, :class => 'icon icon-add' %>
+</div>
+
+<%= title l 'label_oauth_application_plural' %>
+
+<% if @applications.any? %>
+<div class="autoscroll">
+<table class="list">
+ <thead><tr>
+ <th><%= t('.name') %></th>
+ <th><%= t('.callback_url') %></th>
+ <th><%= t('.scopes') %></th>
+ <th></th>
+ </tr></thead>
+ <tbody>
+ <% @applications.each do |application| %>
+ <tr id="application_<%= application.id %>" class="<%= cycle("odd", "even") %>">
+ <td class="name"><span><%= link_to application.name, oauth_application_path(application) %></span></td>
+ <td class="description"><%= truncate application.redirect_uri.split.join(', '), length: 50 %></td>
+ <td class="description"><%= safe_join application.scopes.map{|scope| h l_or_humanize(scope, prefix: 'permission_')}, ", " %></td>
+ <td class="buttons">
+ <%= link_to sprite_icon('edit', t('doorkeeper.applications.buttons.edit')), edit_oauth_application_path(application), class: 'icon icon-edit' %>
+ <%= 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' %>
+ </td>
+ </tr>
+ <% end %>
+ </tbody>
+</table>
+</div>
+<% else %>
+ <p class="nodata"><%= l(:label_no_data) %></p>
+<% end %>
diff --git a/app/views/doorkeeper/applications/new.html.erb b/app/views/doorkeeper/applications/new.html.erb
new file mode 100644
index 000000000..e2a39ac93
--- /dev/null
+++ b/app/views/doorkeeper/applications/new.html.erb
@@ -0,0 +1,6 @@
+<%= title [l('label_oauth_application_plural'), oauth_applications_path], t('.title') %>
+
+<%= labelled_form_for @application, url: doorkeeper_submit_path(@application) do |f| %>
+<%= render :partial => 'form', :locals => { :f => f } %>
+<%= submit_tag l(:button_create) %>
+<% end %>
diff --git a/app/views/doorkeeper/applications/show.html.erb b/app/views/doorkeeper/applications/show.html.erb
new file mode 100644
index 000000000..c98e7d29c
--- /dev/null
+++ b/app/views/doorkeeper/applications/show.html.erb
@@ -0,0 +1,54 @@
+<div class="contextual">
+<%= link_to sprite_icon('edit', t('doorkeeper.applications.buttons.edit')), edit_oauth_application_path(@application), :accesskey => accesskey(:edit), class: 'icon icon-edit' %>
+<%= 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' %>
+</div>
+
+<%= title [l('label_oauth_application_plural'), oauth_applications_path], @application.name %>
+
+<div class="box">
+ <h3 class="icon icon-passwd"><%= sprite_icon('key', l(:label_information_plural)) %></h3>
+ <p>
+ <span class="label"><%= t('.application_id') %>:</span>
+ <code><%= h @application.uid %></code>
+ </p>
+ <p>
+ <span class="label"><%= t('.secret') %>:</span>
+ <code>
+ <% secret = flash[:application_secret].presence || @application.plaintext_secret %>
+ <% flash.delete :application_secret %>
+ <% if secret.blank? && Doorkeeper.config.application_secret_hashed? %>
+ <%= t('.secret_hashed') %>
+ <% else %>
+ <%= secret %>
+ <% end %>
+ </code>
+ <% if secret.present? && Doorkeeper.config.application_secret_hashed? %>
+ <strong><%= t "text_oauth_copy_secret_now" %></strong>
+ <% end %>
+ </p>
+ <p>
+ <span class="label"><%= t('.scopes') %>:</span>
+ <code><%= safe_join @application.scopes.map{|scope| h l_or_humanize(scope, prefix: 'permission_')}, ", " %></code>
+ </p>
+</div>
+
+<h3><%= t('.callback_urls') %></h3>
+
+<div class="autoscroll">
+<table class="list">
+ <thead><tr>
+ <th><%= t('.callback_url') %></th>
+ <th></th>
+ </tr></thead>
+ <tbody>
+ <% @application.redirect_uri.split.each do |uri| %>
+ <tr class="<%= cycle("odd", "even") %>">
+ <td class="name"><span><%= uri %></span></td>
+ <td class="buttons">
+ <%= 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' %>
+ </td>
+ </tr>
+ <% end %>
+ </tbody>
+</table>
+</div>
diff --git a/app/views/doorkeeper/authorizations/error.html.erb b/app/views/doorkeeper/authorizations/error.html.erb
new file mode 100644
index 000000000..59cedf8f3
--- /dev/null
+++ b/app/views/doorkeeper/authorizations/error.html.erb
@@ -0,0 +1,6 @@
+<h2><%= t('doorkeeper.authorizations.error.title') %></h2>
+
+<p id="errorExplanation"><%= @pre_auth.error_response.body[:error_description] %></p>
+<p><a href="javascript:history.back()"><%= l(:button_back) %></a></p>
+
+<% html_title t('doorkeeper.authorizations.error.title') %>
diff --git a/app/views/doorkeeper/authorizations/new.html.erb b/app/views/doorkeeper/authorizations/new.html.erb
new file mode 100644
index 000000000..898f2e645
--- /dev/null
+++ b/app/views/doorkeeper/authorizations/new.html.erb
@@ -0,0 +1,48 @@
+<%= title t('.title') %>
+
+<div class="warning">
+<p><strong><%=h @pre_auth.client.name %></strong></p>
+
+<p><%= raw t('.prompt', client_name: content_tag(:strong, class: "text-info") { @pre_auth.client.name }) %></p>
+
+<div class="oauth-permissions">
+ <p><%= t('.able_to') %>:</p>
+ <ul>
+ <li><%= l :text_oauth_implicit_permissions %></li>
+ <% @pre_auth.scopes.each do |scope| %>
+ <% if scope == 'admin' %>
+ <li><%= l :label_oauth_permission_admin %></li>
+ <% else %>
+ <li><%= l_or_humanize(scope, prefix: 'permission_') %></li>
+ <% end %>
+ <% end %>
+ </ul>
+</div>
+
+<% if @pre_auth.scopes.include?('admin') %>
+ <p><%= l :text_oauth_admin_permission_info %></p>
+<% end %>
+</div>
+
+<p>
+ <%= form_tag oauth_authorization_path, method: :post do %>
+ <%= hidden_field_tag :client_id, @pre_auth.client.uid %>
+ <%= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri %>
+ <%= hidden_field_tag :state, @pre_auth.state %>
+ <%= hidden_field_tag :response_type, @pre_auth.response_type %>
+ <%= hidden_field_tag :scope, @pre_auth.scope %>
+ <%= hidden_field_tag :code_challenge, @pre_auth.code_challenge %>
+ <%= hidden_field_tag :code_challenge_method, @pre_auth.code_challenge_method %>
+ <%= submit_tag t('doorkeeper.authorizations.buttons.authorize') %>
+ <% end %>
+ <%= form_tag oauth_authorization_path, method: :delete do %>
+ <%= hidden_field_tag :client_id, @pre_auth.client.uid %>
+ <%= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri %>
+ <%= hidden_field_tag :state, @pre_auth.state %>
+ <%= hidden_field_tag :response_type, @pre_auth.response_type %>
+ <%= hidden_field_tag :scope, @pre_auth.scope %>
+ <%= hidden_field_tag :code_challenge, @pre_auth.code_challenge %>
+ <%= hidden_field_tag :code_challenge_method, @pre_auth.code_challenge_method %>
+ <%= submit_tag t('doorkeeper.authorizations.buttons.deny') %>
+ <% end %>
+</p>
diff --git a/app/views/doorkeeper/authorizations/show.html.erb b/app/views/doorkeeper/authorizations/show.html.erb
new file mode 100644
index 000000000..25ee88a87
--- /dev/null
+++ b/app/views/doorkeeper/authorizations/show.html.erb
@@ -0,0 +1,8 @@
+<%= title [l('label_oauth_authorized_application_plural'), oauth_authorized_applications_path] %>
+
+<fieldset class="tabular"><legend><%= l(:label_information_plural) %></legend>
+ <p>
+ <label><%= t('.title') %>:</label>
+ <code><%= params[:code] %></code>
+ </p>
+</fieldset>
diff --git a/app/views/doorkeeper/authorized_applications/index.html.erb b/app/views/doorkeeper/authorized_applications/index.html.erb
new file mode 100644
index 000000000..0a1fc8a00
--- /dev/null
+++ b/app/views/doorkeeper/authorized_applications/index.html.erb
@@ -0,0 +1,31 @@
+<%= title [t(:label_my_account), my_account_path], l('label_oauth_authorized_application_plural') %>
+
+<% if @applications.any? %>
+<div class="autoscroll">
+<table class="list">
+ <thead><tr>
+ <th><%= t('doorkeeper.authorized_applications.index.application') %></th>
+ <th><%= t('doorkeeper.authorized_applications.index.created_at') %></th>
+ <th></th>
+ </tr></thead>
+ <tbody>
+ <% @applications.each do |application| %>
+ <tr id="application_<%= application.id %>" class="<%= cycle("odd", "even") %>">
+ <td class="name"><span><%= application.name %></span></td>
+ <td ><%= format_date application.created_at %></td>
+ <td class="buttons">
+ <%= 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' %>
+ </td>
+ </tr>
+ <% end %>
+ </tbody>
+</table>
+</div>
+<% else %>
+ <p class="nodata"><%= l(:label_no_data) %></p>
+<% end %>
+
+<% content_for :sidebar do %>
+<% @user = User.current %>
+<%= render :partial => 'my/sidebar' %>
+<% end %>
diff --git a/app/views/my/account.html.erb b/app/views/my/account.html.erb
index c8706a5f5..95afbabac 100644
--- a/app/views/my/account.html.erb
+++ b/app/views/my/account.html.erb
@@ -1,6 +1,7 @@
<div class="contextual">
<%= additional_emails_link(@user) %>
<%= link_to(sprite_icon('key', l(:button_change_password)), { :action => 'password'}, :class => 'icon icon-passwd') if @user.change_password_allowed? %>
+<%= link_to(sprite_icon('apps', l('label_oauth_authorized_application_plural')), oauth_authorized_applications_path, :class => 'icon icon-applications') if Setting.rest_api_enabled? %>
<%= call_hook(:view_my_account_contextual, :user => @user)%>
</div>
diff --git a/app/views/users/show.api.rsb b/app/views/users/show.api.rsb
index bf415795d..0681903b8 100644
--- a/app/views/users/show.api.rsb
+++ b/app/views/users/show.api.rsb
@@ -11,7 +11,7 @@ api.user do
api.passwd_changed_on @user.passwd_changed_on
api.avatar_url gravatar_url(@user.mail, {rating: nil, size: nil, default: Setting.gravatar_default}) if @user.mail && Setting.gravatar_enabled?
api.twofa_scheme @user.twofa_scheme if User.current.admin? || (User.current == @user)
- api.api_key @user.api_key if User.current.admin? || (User.current == @user)
+ api.api_key @user.api_key if (User.current.admin? || (User.current == @user && !User.current.authorized_by_oauth?))
api.status @user.status if User.current.admin?
render_api_custom_values @user.visible_custom_field_values, api