Commit 8158e6f2 authored by Imre Farkas's avatar Imre Farkas

Merge branch 'feat/group-applications' into 'master'

Group applications [RUN ALL RSPEC] [RUN AS-IF-FOSS]

See merge request gitlab-org/gitlab!55152
parents 51393d6a ace5a69c
# frozen_string_literal: true
module Groups
module Settings
class ApplicationsController < Groups::ApplicationController
include OauthApplications
prepend_before_action :authorize_admin_group!
before_action :set_application, only: [:show, :edit, :update, :destroy]
before_action :load_scopes, only: [:index, :create, :edit, :update]
feature_category :authentication_and_authorization
def index
set_index_vars
end
def show
end
def edit
end
def create
@application = Applications::CreateService.new(current_user, application_params).execute(request)
if @application.persisted?
flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create])
redirect_to group_settings_application_url(@group, @application)
else
set_index_vars
render :index
end
end
def update
if @application.update(application_params)
redirect_to group_settings_application_path(@group, @application), notice: _('Application was successfully updated.')
else
render :edit
end
end
def destroy
@application.destroy
redirect_to group_settings_applications_url(@group), status: :found, notice: _('Application was successfully destroyed.')
end
private
def set_index_vars
# TODO: Remove limit(100) and implement pagination
# https://gitlab.com/gitlab-org/gitlab/-/issues/324187
@applications = @group.oauth_applications.limit(100)
# Don't overwrite a value possibly set by `create`
@application ||= Doorkeeper::Application.new
end
def set_application
@application = @group.oauth_applications.find(params[:id])
end
def application_params
params
.require(:doorkeeper_application)
.permit(:name, :redirect_uri, :trusted, :scopes, :confidential)
.tap do |params|
params[:owner] = @group
end
end
end
end
end
...@@ -17,6 +17,6 @@ class ApplicationsFinder ...@@ -17,6 +17,6 @@ class ApplicationsFinder
def by_id(applications) def by_id(applications)
return applications unless params[:id] return applications unless params[:id]
Doorkeeper::Application.find_by(id: params[:id]) # rubocop: disable CodeReuse/ActiveRecord applications.find_by(id: params[:id]) # rubocop: disable CodeReuse/ActiveRecord
end end
end end
...@@ -22,6 +22,9 @@ module GroupsHelper ...@@ -22,6 +22,9 @@ module GroupsHelper
ldap_group_links#index ldap_group_links#index
hooks#index hooks#index
pipeline_quota#index pipeline_quota#index
applications#index
applications#show
applications#edit
packages_and_registries#index packages_and_registries#index
] ]
end end
......
...@@ -70,6 +70,7 @@ class Group < Namespace ...@@ -70,6 +70,7 @@ class Group < Namespace
has_many :group_deploy_keys, through: :group_deploy_keys_groups has_many :group_deploy_keys, through: :group_deploy_keys_groups
has_many :group_deploy_tokens has_many :group_deploy_tokens
has_many :deploy_tokens, through: :group_deploy_tokens has_many :deploy_tokens, through: :group_deploy_tokens
has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_one :dependency_proxy_setting, class_name: 'DependencyProxy::GroupSetting' has_one :dependency_proxy_setting, class_name: 'DependencyProxy::GroupSetting'
has_many :dependency_proxy_blobs, class_name: 'DependencyProxy::Blob' has_many :dependency_proxy_blobs, class_name: 'DependencyProxy::Blob'
......
- page_title _("Edit"), @application.name, _("Applications") - page_title _("Edit"), @application.name, _("Applications")
- @content_class = "limit-container-width" unless fluid_layout - @content_class = "limit-container-width" unless fluid_layout
%h3.page-title= _('Edit application') %h3.page-title= _('Edit application')
= render 'form', application: @application = render 'shared/doorkeeper/applications/form', url: doorkeeper_submit_path(@application)
- page_title _("Applications") - page_title _("Applications")
- @content_class = "limit-container-width" unless fluid_layout
.row.gl-mt-3 = render 'shared/doorkeeper/applications/index',
.col-lg-4.profile-settings-sidebar oauth_applications_enabled: user_oauth_applications?,
%h4.gl-mt-0 oauth_authorized_applications_enabled: true,
= page_title form_url: doorkeeper_submit_path(@application),
%p application_url: ->(application) { oauth_application_path(application) },
- if user_oauth_applications? edit_application_url: ->(application) { edit_oauth_application_path(application) }
= _("Manage applications that can use GitLab as an OAuth provider, and applications that you've authorized to use your account.")
- else
= _("Manage applications that you've authorized to use your account.")
.col-lg-8
- if user_oauth_applications?
%h5.gl-mt-0
= _('Add new application')
= render 'form', application: @application
%hr
- else
.bs-callout.bs-callout-disabled
= _('Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission')
- if user_oauth_applications?
.oauth-applications
%h5
= _("Your applications (%{size})") % { size: @applications.size }
- if @applications.any?
.table-responsive
%table.table
%thead
%tr
%th= _('Name')
%th= _('Callback URL')
%th= _('Clients')
%th.last-heading
%tbody
- @applications.each do |application|
%tr{ id: "application_#{application.id}" }
%td= link_to application.name, oauth_application_path(application)
%td
- application.redirect_uri.split.each do |uri|
%div= uri
%td= application.access_tokens.count
%td.gl-display-flex
= link_to edit_oauth_application_path(application), class: "gl-button btn btn-default gl-mr-2" do
%span.sr-only
= _('Edit')
= sprite_icon('pencil')
= render 'delete_form', application: application, small: true
- else
.settings-message.text-center
= _("You don't have any applications")
.oauth-authorized-applications.prepend-top-20.gl-mb-3
- if user_oauth_applications?
%h5
= _("Authorized applications (%{size})") % { size: @authorized_apps.size + @authorized_anonymous_tokens.size }
- if @authorized_tokens.any?
.table-responsive
%table.table.table-striped
%thead
%tr
%th= _('Name')
%th= _('Authorized At')
%th= _('Scope')
%th
%tbody
- @authorized_apps.each do |app|
- token = app.authorized_tokens.order('created_at desc').first # rubocop: disable CodeReuse/ActiveRecord
%tr{ id: "application_#{app.id}" }
%td= app.name
%td= token.created_at
%td= token.scopes
%td= render 'doorkeeper/authorized_applications/delete_form', application: app
- @authorized_anonymous_tokens.each do |token|
%tr
%td
= _('Anonymous')
.form-text.text-muted
%em= _("Authorization was granted by entering your username and password in the application.")
%td= token.created_at
%td= token.scopes
%td= render 'doorkeeper/authorized_applications/delete_form', token: token
- else
.settings-message.text-center
= _("You don't have any authorized applications")
...@@ -6,42 +6,4 @@ ...@@ -6,42 +6,4 @@
%h3.page-title %h3.page-title
= _("Application: %{name}") % { name: @application.name } = _("Application: %{name}") % { name: @application.name }
.table-holder.oauth-application-show = render 'shared/doorkeeper/applications/show', edit_path: edit_oauth_application_path(@application), delete_path: oauth_application_path(@application)
%table.table
%tr
%td
= _('Application ID')
%td
.clipboard-group
.input-group
%input.label.label-monospace.monospace{ id: "application_id", type: "text", autocomplete: 'off', value: @application.uid, readonly: true }
.input-group-append
= clipboard_button(target: '#application_id', title: _("Copy ID"), class: "gl-button btn btn-default")
%tr
%td
= _('Secret')
%td
.clipboard-group
.input-group
%input.label.label-monospace.monospace{ id: "secret", type: "text", autocomplete: 'off', value: @application.secret, readonly: true }
.input-group-append
= clipboard_button(target: '#secret', title: _("Copy secret"), class: "gl-button btn btn-default")
%tr
%td
= _('Callback URL')
%td
- @application.redirect_uri.split.each do |uri|
%div
%span.monospace= uri
%tr
%td
= _('Confidential')
%td
= @application.confidential? ? _('Yes') : _('No')
= render "shared/tokens/scopes_list", token: @application
.form-actions
= link_to _('Edit'), edit_oauth_application_path(@application), class: 'gl-button btn btn-confirm wide float-left'
= render 'delete_form', application: @application, submit_btn_css: 'gl-button btn btn-danger gl-ml-3'
- page_title _("Edit"), @application.name, _("Group applications")
- @content_class = "limit-container-width" unless fluid_layout
%h3.page-title= _('Edit group application')
= render 'shared/doorkeeper/applications/form', url: group_settings_application_path(@group, @application)
- page_title _("Group applications")
= render 'shared/doorkeeper/applications/index',
oauth_applications_enabled: user_oauth_applications?,
oauth_authorized_applications_enabled: false,
form_url: group_settings_applications_path(@group),
application_url: ->(application) { group_settings_application_path(@group, application) },
edit_application_url: ->(application) { edit_group_settings_application_path(@group, application) }
- add_to_breadcrumbs _("Group applications"), group_settings_applications_path(@group)
- breadcrumb_title @application.name
- page_title @application.name, _("Group applications")
- @content_class = "limit-container-width" unless fluid_layout
%h3.page-title
= _("Group application: %{name}") % { name: @application.name }
= render 'shared/doorkeeper/applications/show', edit_path: edit_group_settings_application_path(@group, @application), delete_path: group_settings_application_path(@group, @application)
...@@ -145,7 +145,7 @@ ...@@ -145,7 +145,7 @@
%span.nav-item-name.qa-group-settings-item %span.nav-item-name.qa-group-settings-item
= _('Settings') = _('Settings')
%ul.sidebar-sub-level-items.qa-group-sidebar-submenu{ data: { testid: 'group-settings-menu' } } %ul.sidebar-sub-level-items.qa-group-sidebar-submenu{ data: { testid: 'group-settings-menu' } }
= nav_link(path: %w[groups#projects groups#edit badges#index ci_cd#show], html_options: { class: "fly-out-top-item" } ) do = nav_link(path: %w[groups#projects groups#edit badges#index ci_cd#show groups/applications#index], html_options: { class: "fly-out-top-item" } ) do
= link_to edit_group_path(@group) do = link_to edit_group_path(@group) do
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
= _('Settings') = _('Settings')
...@@ -175,6 +175,11 @@ ...@@ -175,6 +175,11 @@
%span %span
= _('CI/CD') = _('CI/CD')
= nav_link(controller: :applications) do
= link_to group_settings_applications_path(@group), title: _('Applications') do
%span
= _('Applications')
= render 'groups/sidebar/packages_settings' = render 'groups/sidebar/packages_settings'
= render_if_exists "groups/ee/settings_nav" = render_if_exists "groups/ee/settings_nav"
......
- submit_btn_css ||= 'gl-button btn btn-danger btn-sm' - submit_btn_css ||= 'gl-button btn btn-danger btn-sm'
= form_tag oauth_application_path(application) do = form_tag path do
%input{ :name => "_method", :type => "hidden", :value => "delete" }/ %input{ :name => "_method", :type => "hidden", :value => "delete" }
- if defined? small - if defined? small
= button_tag type: "submit", class: "gl-button btn btn-default", data: { confirm: _("Are you sure?") } do = button_tag type: "submit", class: "gl-button btn btn-default", data: { confirm: _("Are you sure?") } do
%span.sr-only %span.sr-only
......
= form_for application, url: doorkeeper_submit_path(application), html: { role: 'form', class: 'doorkeeper-app-form' } do |f| = form_for @application, url: url, html: { role: 'form', class: 'doorkeeper-app-form' } do |f|
= form_errors(application) = form_errors(@application)
.form-group .form-group
= f.label :name, class: 'label-bold' = f.label :name, class: 'label-bold'
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
.form-group .form-group
= f.label :scopes, class: 'label-bold' = f.label :scopes, class: 'label-bold'
= render 'shared/tokens/scopes_form', prefix: 'doorkeeper_application', token: application, scopes: @scopes = render 'shared/tokens/scopes_form', prefix: 'doorkeeper_application', token: @application, scopes: @scopes
.gl-mt-3 .gl-mt-3
= f.submit _('Save application'), class: "gl-button btn btn-confirm" = f.submit _('Save application'), class: "gl-button btn btn-confirm"
- @content_class = "limit-container-width" unless fluid_layout
.row.gl-mt-3
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0
= page_title
%p
- if oauth_applications_enabled
- if oauth_authorized_applications_enabled
= _("Manage applications that can use GitLab as an OAuth provider, and applications that you've authorized to use your account.")
- else
= _("Manage applications that can use GitLab as an OAuth provider.")
- else
= _("Manage applications that you've authorized to use your account.")
.col-lg-8
- if oauth_applications_enabled
%h5.gl-mt-0
= _('Add new application')
= render 'shared/doorkeeper/applications/form', url: form_url
%hr
- else
.bs-callout.bs-callout-disabled
= _('Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission')
- if oauth_applications_enabled
.oauth-applications
%h5
= _("Your applications (%{size})") % { size: @applications.size }
- if @applications.any?
.table-responsive
%table.table
%thead
%tr
%th= _('Name')
%th= _('Callback URL')
%th= _('Clients')
%th.last-heading
%tbody
- @applications.each do |application|
%tr{ id: "application_#{application.id}" }
%td= link_to application.name, application_url.call(application)
%td
- application.redirect_uri.split.each do |uri|
%div= uri
%td= application.access_tokens.count
%td.gl-display-flex
= link_to edit_application_url.call(application), class: "gl-button btn btn-default gl-mr-2" do
%span.sr-only
= _('Edit')
= sprite_icon('pencil')
= render 'shared/doorkeeper/applications/delete_form', path: application_url.call(application), small: true
- else
.settings-message.text-center
= _("You don't have any applications")
- if oauth_authorized_applications_enabled
.oauth-authorized-applications.prepend-top-20.gl-mb-3
- if oauth_applications_enabled
%h5
= _("Authorized applications (%{size})") % { size: @authorized_apps.size + @authorized_anonymous_tokens.size }
- if @authorized_tokens.any?
.table-responsive
%table.table.table-striped
%thead
%tr
%th= _('Name')
%th= _('Authorized At')
%th= _('Scope')
%th
%tbody
- @authorized_apps.each do |app|
- token = app.authorized_tokens.order('created_at desc').first # rubocop: disable CodeReuse/ActiveRecord
%tr{ id: "application_#{app.id}" }
%td= app.name
%td= token.created_at
%td= token.scopes
%td= render 'doorkeeper/authorized_applications/delete_form', application: app
- @authorized_anonymous_tokens.each do |token|
%tr
%td
= _('Anonymous')
.form-text.text-muted
%em= _("Authorization was granted by entering your username and password in the application.")
%td= token.created_at
%td= token.scopes
%td= render 'doorkeeper/authorized_applications/delete_form', token: token
- else
.settings-message.text-center
= _("You don't have any authorized applications")
.table-holder.oauth-application-show
%table.table
%tr
%td
= _('Application ID')
%td
.clipboard-group
.input-group
%input.label.label-monospace.monospace{ id: "application_id", type: "text", autocomplete: 'off', value: @application.uid, readonly: true }
.input-group-append
= clipboard_button(target: '#application_id', title: _("Copy ID"), class: "gl-button btn btn-default")
%tr
%td
= _('Secret')
%td
.clipboard-group
.input-group
%input.label.label-monospace.monospace{ id: "secret", type: "text", autocomplete: 'off', value: @application.secret, readonly: true }
.input-group-append
= clipboard_button(target: '#secret', title: _("Copy secret"), class: "gl-button btn btn-default")
%tr
%td
= _('Callback URL')
%td
- @application.redirect_uri.split.each do |uri|
%div
%span.monospace= uri
%tr
%td
= _('Confidential')
%td
= @application.confidential? ? _('Yes') : _('No')
= render "shared/tokens/scopes_list", token: @application
.form-actions
= link_to _('Edit'), edit_path, class: 'gl-button btn btn-confirm wide float-left'
= render 'shared/doorkeeper/applications/delete_form', path: delete_path, submit_btn_css: 'gl-button btn btn-danger gl-ml-3'
---
title: Support group applications
merge_request: 55152
author: Jonas Wälter @wwwjon, Bastian Blank
type: added
...@@ -50,6 +50,8 @@ constraints(::Constraints::GroupUrlConstrainer.new) do ...@@ -50,6 +50,8 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
end end
end end
resources :applications
resources :packages_and_registries, only: [:index] resources :packages_and_registries, only: [:index]
end end
......
...@@ -407,7 +407,7 @@ Pages access control is disabled by default. To enable it: ...@@ -407,7 +407,7 @@ Pages access control is disabled by default. To enable it:
``` ```
1. [Restart GitLab](../restart_gitlab.md#installations-from-source). 1. [Restart GitLab](../restart_gitlab.md#installations-from-source).
1. Create a new [system OAuth application](../../integration/oauth_provider.md#add-an-application-through-the-profile). 1. Create a new [system OAuth application](../../integration/oauth_provider.md#user-owned-applications).
This should be called `GitLab Pages` and have a `Redirect URL` of This should be called `GitLab Pages` and have a `Redirect URL` of
`https://projects.example.io/auth`. It does not need to be a "trusted" `https://projects.example.io/auth`. It does not need to be a "trusted"
application, but it does need the `api` scope. application, but it does need the `api` scope.
......
...@@ -8,11 +8,13 @@ info: To determine the technical writer assigned to the Stage/Group associated w ...@@ -8,11 +8,13 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/8160) in GitLab 10.5. > [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/8160) in GitLab 10.5.
Applications API operates on OAuth applications for: The Applications API operates on instance-wide OAuth applications for:
- [Using GitLab as an authentication provider](../integration/oauth_provider.md). - [Using GitLab as an authentication provider](../integration/oauth_provider.md).
- [Allowing access to GitLab resources on a user's behalf](oauth2.md). - [Allowing access to GitLab resources on a user's behalf](oauth2.md).
The Applications API cannot be used to manage group applications or applications of individual users.
NOTE: NOTE:
Only administrator users can use the Applications API. Only administrator users can use the Applications API.
......
...@@ -33,17 +33,18 @@ OAuth 2 can be used: ...@@ -33,17 +33,18 @@ OAuth 2 can be used:
The 'GitLab Importer' feature also uses OAuth 2 to give access The 'GitLab Importer' feature also uses OAuth 2 to give access
to repositories without sharing user credentials to your GitLab.com account. to repositories without sharing user credentials to your GitLab.com account.
GitLab supports two ways of adding a new OAuth 2 application to an instance: GitLab supports several ways of adding a new OAuth 2 application to an instance:
- As a regular user, for applications owned by an individual. - [User owned applications](#user-owned-applications)
- Through the Admin Area menu for instance-wide apps. - [Group owned applications](#group-owned-applications)
- [Instance-wide applications](#instance-wide-applications)
The only difference between these two methods is the [permission](../user/permissions.md) The only difference between these methods is the [permission](../user/permissions.md)
levels. The default callback URL is `http://your-gitlab.example.com/users/auth/gitlab/callback`. levels. The default callback URL is `http://your-gitlab.example.com/users/auth/gitlab/callback`.
## Add an application through the profile ## User owned applications
To add a new application via your profile: To add a new application for your user:
1. In the top-right corner, select your avatar. 1. In the top-right corner, select your avatar.
1. Select **Edit profile**. 1. Select **Edit profile**.
...@@ -55,7 +56,20 @@ To add a new application via your profile: ...@@ -55,7 +56,20 @@ To add a new application via your profile:
- Application ID: OAuth 2 Client ID. - Application ID: OAuth 2 Client ID.
- Secret: OAuth 2 Client Secret. - Secret: OAuth 2 Client Secret.
## OAuth applications in the Admin Area ## Group owned applications
To add a new application for a group:
1. Navigate to the desired group.
1. In the left sidebar, select **Settings > Applications**.
1. Enter a **Name**, **Redirect URI** and OAuth 2 scopes as defined in [Authorized Applications](#authorized-applications).
The **Redirect URI** is the URL where users are sent after they authorize with GitLab.
1. Select **Save application**. GitLab displays:
- Application ID: OAuth 2 Client ID.
- Secret: OAuth 2 Client Secret.
## Instance-wide applications
To create an application for your GitLab instance, select To create an application for your GitLab instance, select
**Admin Area > Applications > New application**. **Admin Area > Applications > New application**.
......
...@@ -8,15 +8,16 @@ module EE ...@@ -8,15 +8,16 @@ module EE
override :execute override :execute
def execute(request) def execute(request)
super.tap do |application| super.tap do |application|
audit_event_service(request.remote_ip).for_user(full_path: application.name, entity_id: application.id).security_event entity = application.owner || current_user
audit_event_service(entity, request.remote_ip).for_user(full_path: application.name, entity_id: application.id).security_event
end end
end end
def audit_event_service(ip_address) def audit_event_service(entity, ip_address)
::AuditEventService.new(current_user, ::AuditEventService.new(current_user,
current_user, entity,
action: :custom, action: :custom,
custom_message: 'OAuth access granted', custom_message: 'OAuth application added',
ip_address: ip_address) ip_address: ip_address)
end end
end end
......
...@@ -3,15 +3,33 @@ ...@@ -3,15 +3,33 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe ::Applications::CreateService do RSpec.describe ::Applications::CreateService do
include TestRequestHelpers
let(:user) { create(:user) } let(:user) { create(:user) }
let(:group) { create(:group) }
let(:params) { attributes_for(:application) } let(:params) { attributes_for(:application) }
let(:request) { ActionController::TestRequest.new({ remote_ip: "127.0.0.1" }, ActionController::TestSession.new, nil) }
subject { described_class.new(user, params) } subject { described_class.new(user, params) }
it 'creates an AuditEvent' do describe '#audit_event_service' do
stub_licensed_features(extended_audit_events: true) using RSpec::Parameterized::TableSyntax
where(:case_name, :owner, :entity_type) do
'instance application' | nil | 'User'
'group application' | group | 'Group'
'user application' | user | 'User'
end
with_them do
before do
stub_licensed_features(extended_audit_events: true)
params[:owner] = owner
end
expect { subject.execute(request) }.to change { AuditEvent.count }.by(1) it 'creates AuditEvent with correct entity type' do
expect { subject.execute(test_request) }.to change(AuditEvent, :count).by(1)
expect(AuditEvent.last.entity_type).to eq(entity_type)
end
end
end end
end end
...@@ -41,6 +41,8 @@ module API ...@@ -41,6 +41,8 @@ module API
desc 'Delete an application' desc 'Delete an application'
delete ':id' do delete ':id' do
application = ApplicationsFinder.new(params).execute application = ApplicationsFinder.new(params).execute
break not_found!('Application') unless application
application.destroy application.destroy
no_content! no_content!
......
...@@ -11203,6 +11203,9 @@ msgstr "" ...@@ -11203,6 +11203,9 @@ msgstr ""
msgid "Edit fork in Web IDE" msgid "Edit fork in Web IDE"
msgstr "" msgstr ""
msgid "Edit group application"
msgstr ""
msgid "Edit group: %{group_name}" msgid "Edit group: %{group_name}"
msgstr "" msgstr ""
...@@ -14553,6 +14556,12 @@ msgstr "" ...@@ -14553,6 +14556,12 @@ msgstr ""
msgid "Group Wikis" msgid "Group Wikis"
msgstr "" msgstr ""
msgid "Group application: %{name}"
msgstr ""
msgid "Group applications"
msgstr ""
msgid "Group avatar" msgid "Group avatar"
msgstr "" msgstr ""
...@@ -18550,6 +18559,9 @@ msgstr "" ...@@ -18550,6 +18559,9 @@ msgstr ""
msgid "Manage applications that can use GitLab as an OAuth provider, and applications that you've authorized to use your account." msgid "Manage applications that can use GitLab as an OAuth provider, and applications that you've authorized to use your account."
msgstr "" msgstr ""
msgid "Manage applications that can use GitLab as an OAuth provider."
msgstr ""
msgid "Manage applications that you've authorized to use your account." msgid "Manage applications that you've authorized to use your account."
msgstr "" msgstr ""
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Groups::Settings::ApplicationsController do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:application) { create(:oauth_application, owner_id: group.id, owner_type: 'Namespace') }
before do
sign_in(user)
end
describe 'GET #index' do
context 'when user is owner' do
before do
group.add_owner(user)
end
it 'renders the application form' do
get :index, params: { group_id: group }
expect(response).to render_template :index
expect(assigns[:scopes]).to be_kind_of(Doorkeeper::OAuth::Scopes)
end
end
context 'when user is not owner' do
before do
group.add_maintainer(user)
end
it 'renders a 404' do
get :index, params: { group_id: group }
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
describe 'GET #edit' do
context 'when user is owner' do
before do
group.add_owner(user)
end
it 'renders the application form' do
get :edit, params: { group_id: group, id: application.id }
expect(response).to render_template :edit
expect(assigns[:scopes]).to be_kind_of(Doorkeeper::OAuth::Scopes)
end
end
context 'when user is not owner' do
before do
group.add_maintainer(user)
end
it 'renders a 404' do
get :edit, params: { group_id: group, id: application.id }
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
describe 'POST #create' do
context 'when user is owner' do
before do
group.add_owner(user)
end
it 'creates the application' do
create_params = attributes_for(:application, trusted: true, confidential: false, scopes: ['api'])
expect do
post :create, params: { group_id: group, doorkeeper_application: create_params }
end.to change { Doorkeeper::Application.count }.by(1)
application = Doorkeeper::Application.last
expect(response).to redirect_to(group_settings_application_path(group, application))
expect(application).to have_attributes(create_params.except(:uid, :owner_type))
end
it 'renders the application form on errors' do
expect do
post :create, params: { group_id: group, doorkeeper_application: attributes_for(:application).merge(redirect_uri: nil) }
end.not_to change { Doorkeeper::Application.count }
expect(response).to render_template :index
expect(assigns[:scopes]).to be_kind_of(Doorkeeper::OAuth::Scopes)
end
context 'when the params are for a confidential application' do
it 'creates a confidential application' do
create_params = attributes_for(:application, confidential: true, scopes: ['read_user'])
expect do
post :create, params: { group_id: group, doorkeeper_application: create_params }
end.to change { Doorkeeper::Application.count }.by(1)
application = Doorkeeper::Application.last
expect(response).to redirect_to(group_settings_application_path(group, application))
expect(application).to have_attributes(create_params.except(:uid, :owner_type))
end
end
context 'when scopes are not present' do
it 'renders the application form on errors' do
create_params = attributes_for(:application, trusted: true, confidential: false)
expect do
post :create, params: { group_id: group, doorkeeper_application: create_params }
end.not_to change { Doorkeeper::Application.count }
expect(response).to render_template :index
end
end
end
context 'when user is not owner' do
before do
group.add_maintainer(user)
end
it 'renders a 404' do
create_params = attributes_for(:application, trusted: true, confidential: false, scopes: ['api'])
post :create, params: { group_id: group, doorkeeper_application: create_params }
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
describe 'PATCH #update' do
context 'when user is owner' do
before do
group.add_owner(user)
end
it 'updates the application' do
doorkeeper_params = { redirect_uri: 'http://example.com/', trusted: true, confidential: false }
patch :update, params: { group_id: group, id: application.id, doorkeeper_application: doorkeeper_params }
application.reload
expect(response).to redirect_to(group_settings_application_path(group, application))
expect(application)
.to have_attributes(redirect_uri: 'http://example.com/', trusted: true, confidential: false)
end
it 'renders the application form on errors' do
patch :update, params: { group_id: group, id: application.id, doorkeeper_application: { redirect_uri: nil } }
expect(response).to render_template :edit
expect(assigns[:scopes]).to be_kind_of(Doorkeeper::OAuth::Scopes)
end
context 'when updating the application to be confidential' do
it 'successfully sets the application to confidential' do
doorkeeper_params = { confidential: true }
patch :update, params: { group_id: group, id: application.id, doorkeeper_application: doorkeeper_params }
application.reload
expect(response).to redirect_to(group_settings_application_path(group, application))
expect(application).to be_confidential
end
end
end
context 'when user is not owner' do
before do
group.add_maintainer(user)
end
it 'renders a 404' do
doorkeeper_params = { redirect_uri: 'http://example.com/', trusted: true, confidential: false }
patch :update, params: { group_id: group, id: application.id, doorkeeper_application: doorkeeper_params }
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
describe 'DELETE #destroy' do
context 'when user is owner' do
before do
group.add_owner(user)
end
it 'deletes the application' do
delete :destroy, params: { group_id: group, id: application.id }
expect(Doorkeeper::Application.exists?(application.id)).to be_falsy
expect(response).to redirect_to(group_settings_applications_url(group))
end
end
context 'when user is not owner' do
before do
group.add_maintainer(user)
end
it 'renders a 404' do
delete :destroy, params: { group_id: group, id: application.id }
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end
...@@ -5,18 +5,48 @@ require 'spec_helper' ...@@ -5,18 +5,48 @@ require 'spec_helper'
RSpec.describe ApplicationsFinder do RSpec.describe ApplicationsFinder do
let(:application1) { create(:application, name: 'some_application', owner: nil, redirect_uri: 'http://some_application.url', scopes: '') } let(:application1) { create(:application, name: 'some_application', owner: nil, redirect_uri: 'http://some_application.url', scopes: '') }
let(:application2) { create(:application, name: 'another_application', owner: nil, redirect_uri: 'http://other_application.url', scopes: '') } let(:application2) { create(:application, name: 'another_application', owner: nil, redirect_uri: 'http://other_application.url', scopes: '') }
let(:user_application) { create(:application, name: 'user_application', owner: create(:user), redirect_uri: 'http://user_application.url', scopes: '') }
let(:group_application) { create(:application, name: 'group_application', owner: create(:group), redirect_uri: 'http://group_application.url', scopes: '') }
describe '#execute' do describe '#execute' do
it 'returns an array of applications' do it 'returns an array of instance applications' do
found = described_class.new.execute found = described_class.new.execute
expect(found).to match_array([application1, application2]) expect(found).to match_array([application1, application2])
end end
it 'returns the application by id' do
params = { id: application1.id }
found = described_class.new(params).execute
expect(found).to match(application1) context 'by_id' do
context 'with existing id' do
it 'returns the application' do
params = { id: application1.id }
found = described_class.new(params).execute
expect(found).to match(application1)
end
end
context 'with invalid id' do
it 'returns nil for user application' do
params = { id: user_application.id }
found = described_class.new(params).execute
expect(found).to be_nil
end
it 'returns nil for group application' do
params = { id: group_application.id }
found = described_class.new(params).execute
expect(found).to be_nil
end
it 'returns nil for non-existing application' do
params = { id: non_existing_record_id }
found = described_class.new(params).execute
expect(found).to be_nil
end
end
end end
end end
end end
...@@ -5,7 +5,7 @@ require 'spec_helper' ...@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe API::Applications, :api do RSpec.describe API::Applications, :api do
let(:admin_user) { create(:user, admin: true) } let(:admin_user) { create(:user, admin: true) }
let(:user) { create(:user, admin: false) } let(:user) { create(:user, admin: false) }
let!(:application) { create(:application, name: 'another_application', redirect_uri: 'http://other_application.url', scopes: '') } let!(:application) { create(:application, name: 'another_application', owner: nil, redirect_uri: 'http://other_application.url', scopes: '') }
describe 'POST /applications' do describe 'POST /applications' do
context 'authenticated and authorized user' do context 'authenticated and authorized user' do
...@@ -143,6 +143,12 @@ RSpec.describe API::Applications, :api do ...@@ -143,6 +143,12 @@ RSpec.describe API::Applications, :api do
expect(response).to have_gitlab_http_status(:no_content) expect(response).to have_gitlab_http_status(:no_content)
end end
it 'cannot delete non-existing application' do
delete api("/applications/#{non_existing_record_id}", admin_user)
expect(response).to have_gitlab_http_status(:not_found)
end
end end
context 'authorized user without authorization' do context 'authorized user without authorization' do
......
...@@ -139,6 +139,7 @@ RSpec.shared_context 'group navbar structure' do ...@@ -139,6 +139,7 @@ RSpec.shared_context 'group navbar structure' do
_('Projects'), _('Projects'),
_('Repository'), _('Repository'),
_('CI/CD'), _('CI/CD'),
_('Applications'),
_('Packages & Registries'), _('Packages & Registries'),
_('Webhooks') _('Webhooks')
] ]
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment