Commit 007c3c55 authored by Jan Provaznik's avatar Jan Provaznik

Merge branch '38133-credential-inventory-for-group-managed-accounts' into 'master'

Resolve "Credentials inventory for group managed accounts"

Closes #38133

See merge request gitlab-org/gitlab!23944
parents dd5500f1 7b9e13ba
...@@ -8,16 +8,13 @@ class KeysFinder ...@@ -8,16 +8,13 @@ class KeysFinder
'md5' => 'fingerprint' 'md5' => 'fingerprint'
}.freeze }.freeze
def initialize(current_user, params) def initialize(params)
@current_user = current_user
@params = params @params = params
end end
def execute def execute
raise GitLabAccessDeniedError unless current_user.admin?
keys = by_key_type keys = by_key_type
keys = by_user(keys) keys = by_users(keys)
keys = sort(keys) keys = sort(keys)
by_fingerprint(keys) by_fingerprint(keys)
...@@ -25,7 +22,7 @@ class KeysFinder ...@@ -25,7 +22,7 @@ class KeysFinder
private private
attr_reader :current_user, :params attr_reader :params
def by_key_type def by_key_type
if params[:key_type] == 'ssh' if params[:key_type] == 'ssh'
...@@ -39,10 +36,10 @@ class KeysFinder ...@@ -39,10 +36,10 @@ class KeysFinder
keys.order_last_used_at_desc keys.order_last_used_at_desc
end end
def by_user(keys) def by_users(keys)
return keys unless params[:user] return keys unless params[:users]
keys.for_user(params[:user]) keys.for_user(params[:users])
end end
def by_fingerprint(keys) def by_fingerprint(keys)
......
...@@ -81,6 +81,20 @@ Since use of the group-managed account requires the use of SSO, users of group-m ...@@ -81,6 +81,20 @@ Since use of the group-managed account requires the use of SSO, users of group-m
- The user will be unable to access the group (their credentials will no longer work on the identity provider when prompted to SSO). - The user will be unable to access the group (their credentials will no longer work on the identity provider when prompted to SSO).
- Contributions in the group (e.g. issues, merge requests) will remain intact. - Contributions in the group (e.g. issues, merge requests) will remain intact.
##### Credentials inventory for Group-managed accounts **(ULTIMATE)**
> [Introduced in GitLab 12.8](https://gitlab.com/gitlab-org/gitlab/issues/38133)
Owners who manage user accounts in a group can view the following details of personal access tokens and SSH keys:
- Owners
- Scopes
- Usage patterns
To access the Credentials inventory of a group, navigate to **{shield}** **Security & Compliance > Credentials** in your group's sidebar.
This feature is similar to the [Credentials inventory for self-managed instances](../../admin_area/credentials_inventory.md).
#### Assertions #### Assertions
When using group-managed accounts, the following user details need to be passed to GitLab as SAML When using group-managed accounts, the following user details need to be passed to GitLab as SAML
......
# frozen_string_literal: true # frozen_string_literal: true
class Admin::CredentialsController < Admin::ApplicationController class Admin::CredentialsController < Admin::ApplicationController
extend ::Gitlab::Utils::Override
include CredentialsInventoryActions include CredentialsInventoryActions
helper_method :credentials_inventory_path, :user_detail_path helper_method :credentials_inventory_path, :user_detail_path
before_action :check_license_credentials_inventory_available!, only: [:index]
private private
def check_license_credentials_inventory_available!
render_404 unless credentials_inventory_feature_available?
end
override :credentials_inventory_path
def credentials_inventory_path(args) def credentials_inventory_path(args)
admin_credentials_path(args) admin_credentials_path(args)
end end
override :user_detail_path
def user_detail_path(user) def user_detail_path(user)
admin_user_path(user) admin_user_path(user)
end end
def user override :users
def users
nil nil
end end
end end
...@@ -4,10 +4,6 @@ module CredentialsInventoryActions ...@@ -4,10 +4,6 @@ module CredentialsInventoryActions
extend ActiveSupport::Concern extend ActiveSupport::Concern
include CredentialsInventoryHelper include CredentialsInventoryHelper
included do
before_action :check_license_credentials_inventory_available!, only: [:index]
end
def index def index
@credentials = filter_credentials.page(params[:page]).preload_users.without_count # rubocop:disable Gitlab/ModuleWithInstanceVariables @credentials = filter_credentials.page(params[:page]).preload_users.without_count # rubocop:disable Gitlab/ModuleWithInstanceVariables
...@@ -18,17 +14,13 @@ module CredentialsInventoryActions ...@@ -18,17 +14,13 @@ module CredentialsInventoryActions
def filter_credentials def filter_credentials
if show_personal_access_tokens? if show_personal_access_tokens?
::PersonalAccessTokensFinder.new({ user: user, impersonation: false, state: 'active', sort: 'id_desc' }).execute ::PersonalAccessTokensFinder.new({ user: users, impersonation: false, state: 'active', sort: 'id_desc' }).execute
elsif show_ssh_keys? elsif show_ssh_keys?
::KeysFinder.new(current_user, { user: user, key_type: 'ssh' }).execute ::KeysFinder.new({ users: users, key_type: 'ssh' }).execute
end end
end end
def check_license_credentials_inventory_available! def users
render_404 unless credentials_inventory_feature_available?
end
def user
raise NotImplementedError, "#{self.class} does not implement #{__method__}" raise NotImplementedError, "#{self.class} does not implement #{__method__}"
end end
end end
# frozen_string_literal: true # frozen_string_literal: true
class Groups::Security::ComplianceDashboardsController < Groups::ApplicationController class Groups::Security::ComplianceDashboardsController < Groups::ApplicationController
include Groups::SecurityFeaturesHelper
layout 'group' layout 'group'
before_action :authorize_compliance_dashboard! before_action :authorize_compliance_dashboard!
...@@ -13,7 +15,6 @@ class Groups::Security::ComplianceDashboardsController < Groups::ApplicationCont ...@@ -13,7 +15,6 @@ class Groups::Security::ComplianceDashboardsController < Groups::ApplicationCont
private private
def authorize_compliance_dashboard! def authorize_compliance_dashboard!
render_404 unless group.feature_available?(:group_level_compliance_dashboard) && render_404 unless group_level_compliance_dashboard_available?(group)
can?(current_user, :read_group_compliance_dashboard, group)
end end
end end
# frozen_string_literal: true
class Groups::Security::CredentialsController < Groups::ApplicationController
layout 'group'
extend ::Gitlab::Utils::Override
include CredentialsInventoryActions
include Groups::SecurityFeaturesHelper
helper_method :credentials_inventory_path, :user_detail_path
before_action :validate_group_level_credentials_inventory_available!, only: [:index]
private
def validate_group_level_credentials_inventory_available!
render_404 unless group_level_credentials_inventory_available?(group)
end
override :credentials_inventory_path
def credentials_inventory_path(args)
group_security_credentials_path(args)
end
override :user_detail_path
def user_detail_path(user)
user_path(user)
end
override :users
def users
group.managed_users
end
end
# frozen_string_literal: true
module Groups::SecurityFeaturesHelper
def group_level_security_dashboard_available?(group)
group.feature_available?(:security_dashboard)
end
def group_level_compliance_dashboard_available?(group)
group.feature_available?(:group_level_compliance_dashboard) &&
can?(current_user, :read_group_compliance_dashboard, group)
end
def group_level_credentials_inventory_available?(group)
can?(current_user, :read_group_credentials_inventory, group) &&
group.feature_available?(:credentials_inventory) &&
group.enforced_group_managed_accounts?
end
def primary_group_level_security_feature_path(group)
if group_level_security_dashboard_available?(group)
group_security_dashboard_path(group)
elsif group_level_compliance_dashboard_available?(group)
group_security_compliance_dashboard_path(group)
elsif group_level_credentials_inventory_available?(group)
group_security_credentials_path(group)
end
end
end
...@@ -45,6 +45,7 @@ module EE ...@@ -45,6 +45,7 @@ module EE
has_one :deletion_schedule, class_name: 'GroupDeletionSchedule' has_one :deletion_schedule, class_name: 'GroupDeletionSchedule'
delegate :deleting_user, :marked_for_deletion_on, to: :deletion_schedule, allow_nil: true delegate :deleting_user, :marked_for_deletion_on, to: :deletion_schedule, allow_nil: true
delegate :enforced_group_managed_accounts?, to: :saml_provider, allow_nil: true
belongs_to :file_template_project, class_name: "Project" belongs_to :file_template_project, class_name: "Project"
......
...@@ -135,6 +135,7 @@ module EE ...@@ -135,6 +135,7 @@ module EE
rule { admin | owner }.policy do rule { admin | owner }.policy do
enable :read_group_compliance_dashboard enable :read_group_compliance_dashboard
enable :read_group_credentials_inventory
end end
rule { needs_new_sso_session }.policy do rule { needs_new_sso_session }.policy do
......
- compliance_dashboard_available = @group.feature_available?(:group_level_compliance_dashboard) - main_path = primary_group_level_security_feature_path(@group)
- security_dashboard_available = @group.feature_available?(:security_dashboard) - if main_path.present?
- if compliance_dashboard_available || security_dashboard_available = nav_link(path: %w[dashboard#show compliance_dashboards#show credentials#index]) do
- main_path = compliance_dashboard_available ? group_security_compliance_dashboard_path(@group) : group_security_dashboard_path(@group)
= nav_link(path: %w[groups/security/compliance_dashboards#show groups/security/dashboard#show]) do
= link_to main_path, data: { qa_selector: 'security_compliance_link' } do = link_to main_path, data: { qa_selector: 'security_compliance_link' } do
.nav-icon-container .nav-icon-container
= sprite_icon('shield') = sprite_icon('shield')
%span.nav-item-name %span.nav-item-name
= _('Security & Compliance') = _('Security & Compliance')
%ul.sidebar-sub-level-items{ data: { qa_selector: 'group_secure_submenu' } } %ul.sidebar-sub-level-items{ data: { qa_selector: 'group_secure_submenu' } }
- if security_dashboard_available - if group_level_security_dashboard_available?(@group)
= nav_link(path: 'groups/security/dashboard#show') do = nav_link(path: 'dashboard#show') do
= link_to group_security_dashboard_path(@group), title: _('Security'), data: { qa_selector: 'security_dashboard_link' } do = link_to group_security_dashboard_path(@group), title: _('Security'), data: { qa_selector: 'security_dashboard_link' } do
%span= _('Security') %span= _('Security')
- if compliance_dashboard_available - if group_level_compliance_dashboard_available?(@group)
= nav_link(path: 'groups/security/compliance_dashboards#show') do = nav_link(path: 'compliance_dashboards#show') do
= link_to group_security_compliance_dashboard_path(@group), title: _('Compliance') do = link_to group_security_compliance_dashboard_path(@group), title: _('Compliance') do
%span= _('Compliance') %span= _('Compliance')
- if group_level_credentials_inventory_available?(@group)
= nav_link(path: 'credentials#index') do
= link_to group_security_credentials_path(@group), title: _('Credentials') do
%span= _('Credentials')
- elsif show_discover_group_security?(@group) - elsif show_discover_group_security?(@group)
= nav_link(path: group_security_discover_path(@group)) do = nav_link(path: group_security_discover_path(@group)) do
= link_to group_security_discover_path(@group) do = link_to group_security_discover_path(@group) do
......
---
title: Introduce Credentials Inventory for Groups that enforce Group Managed Accounts
merge_request: 23944
author:
type: added
...@@ -119,6 +119,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do ...@@ -119,6 +119,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
resource :compliance_dashboard, only: [:show] resource :compliance_dashboard, only: [:show]
resources :vulnerable_projects, only: [:index] resources :vulnerable_projects, only: [:index]
resource :discover, only: [:show], controller: :discover resource :discover, only: [:show], controller: :discover
resources :credentials, only: [:index]
resources :vulnerability_findings, only: [:index] do resources :vulnerability_findings, only: [:index] do
collection do collection do
......
# frozen_string_literal: true
require 'spec_helper'
describe Groups::Security::CredentialsController do
let_it_be(:group_with_managed_accounts) { create(:group_with_managed_accounts, :private) }
let_it_be(:managed_users) { create_list(:user, 2, :group_managed, managing_group: group_with_managed_accounts) }
before do
allow_next_instance_of(Gitlab::Auth::GroupSaml::SsoEnforcer) do |sso_enforcer|
allow(sso_enforcer).to receive(:active_session?).and_return(true)
end
owner = managed_users.first
group_with_managed_accounts.add_owner(owner)
sign_in(owner)
end
describe 'GET #index' do
let(:filter) {}
let(:group_id) { group_with_managed_accounts.to_param }
subject { get :index, params: { group_id: group_id.to_param, filter: filter } }
context 'when `credentials_inventory` feature is enabled' do
before do
stub_licensed_features(credentials_inventory: true)
end
context 'for a group that enforces group managed accounts' do
context 'for a user with access to view credentials inventory' do
it 'responds with 200' do
subject
expect(response).to have_gitlab_http_status(:ok)
end
context 'filtering by type of credential' do
before do
managed_users.each do |user|
create(:personal_access_token, user: user)
end
end
shared_examples_for 'filtering by `personal_access_tokens`' do
it do
subject
expect(assigns(:credentials)).to match_array(PersonalAccessToken.where(user: managed_users))
end
end
context 'no credential type specified' do
let(:filter) { nil }
it_behaves_like 'filtering by `personal_access_tokens`'
end
context 'non-existent credential type specified' do
let(:filter) { 'non_existent_credential_type' }
it_behaves_like 'filtering by `personal_access_tokens`'
end
context 'credential type specified as `personal_access_tokens`' do
let(:filter) { 'personal_access_tokens' }
it_behaves_like 'filtering by `personal_access_tokens`'
end
context 'user scope' do
it 'does not show the credentials of a user outside the group' do
personal_access_token = create(:personal_access_token, user: create(:user))
subject
expect(assigns(:credentials)).not_to include(personal_access_token)
end
end
context 'credential type specified as `ssh_keys`' do
let(:filter) { 'ssh_keys' }
before do
managed_users.each do |user|
create(:personal_key, user: user)
end
end
it 'filters by ssh keys' do
subject
expect(assigns(:credentials)).to match_array(Key.regular_keys.where(user: managed_users))
end
end
end
context 'for a user without access to view credentials inventory' do
before do
maintainer = managed_users.last
group_with_managed_accounts.add_maintainer(maintainer)
sign_in(maintainer)
end
it 'responds with 404' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end
context 'for a group that does not enforce group managed accounts' do
let(:group_id) { create(:group).to_param }
it 'responds with 404' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
context 'when `credentials_inventory` feature is disabled' do
before do
stub_licensed_features(credentials_inventory: false)
end
it 'returns 404' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end
...@@ -52,4 +52,12 @@ FactoryBot.define do ...@@ -52,4 +52,12 @@ FactoryBot.define do
) )
end end
end end
factory :group_with_managed_accounts, parent: :group do
after(:create) do |group, evaluator|
create(:saml_provider,
:enforced_group_managed_accounts,
group: group)
end
end
end end
...@@ -7,7 +7,14 @@ FactoryBot.modify do ...@@ -7,7 +7,14 @@ FactoryBot.modify do
end end
trait :group_managed do trait :group_managed do
association :managing_group, factory: :group association :managing_group, factory: :group_with_managed_accounts
after(:create) do |user, evaluator|
create(:group_saml_identity,
user: user,
saml_provider: user.managing_group.saml_provider
)
end
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
describe 'Groups::Security::Credentials' do
include Spec::Support::Helpers::Features::ResponsiveTableHelpers
let_it_be(:group_with_managed_accounts) { create(:group_with_managed_accounts, :private) }
let_it_be(:managed_user) { create(:user, :group_managed, managing_group: group_with_managed_accounts, name: 'David') }
let(:group_id) { group_with_managed_accounts.to_param }
before do
allow_next_instance_of(Gitlab::Auth::GroupSaml::SsoEnforcer) do |sso_enforcer|
allow(sso_enforcer).to receive(:active_session?).and_return(true)
end
group_with_managed_accounts.add_owner(managed_user)
sign_in(managed_user)
end
context 'licensed' do
before do
stub_licensed_features(credentials_inventory: true)
end
context 'links' do
before do
visit group_security_credentials_path(group_id: group_id)
end
it 'has Credentials Inventory link in sidebar' do
expect(page).to have_link('Credentials', href: group_security_credentials_path(group_id: group_id))
end
context 'tabs' do
it 'contains the relevant filter tabs' do
expect(page).to have_link('Personal Access Tokens', href: group_security_credentials_path(group_id: group_id, filter: 'personal_access_tokens'))
expect(page).to have_link('SSH Keys', href: group_security_credentials_path(group_id: group_id, filter: 'ssh_keys'))
end
end
end
context 'filtering' do
context 'by Personal Access Tokens' do
before do
create(:personal_access_token,
user: managed_user,
created_at: '2019-12-10',
expires_at: nil)
visit group_security_credentials_path(group_id: group_id, filter: 'personal_access_tokens')
end
it 'shows details of personal access tokens' do
expect(first_row.text).to include('David')
expect(first_row.text).to include('api')
expect(first_row.text).to include('2019-12-10')
expect(first_row.text).to include('Never')
end
end
context 'by SSH Keys' do
before do
create(:personal_key,
user: managed_user,
created_at: '2019-12-09',
last_used_at: '2019-12-10')
visit group_security_credentials_path(group_id: group_id, filter: 'ssh_keys')
end
it 'shows details of ssh keys' do
expect(first_row.text).to include('David')
expect(first_row.text).to include('2019-12-09')
expect(first_row.text).to include('2019-12-10')
end
end
end
end
context 'unlicensed' do
before do
stub_licensed_features(credentials_inventory: false)
end
it 'returns 400' do
visit group_security_credentials_path(group_id: group_id)
expect(page.status_code).to eq(404)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Groups::SecurityFeaturesHelper do
using RSpec::Parameterized::TableSyntax
let_it_be(:group, refind: true) { create(:group) }
let_it_be(:user, refind: true) { create(:user) }
before do
allow(helper).to receive(:current_user).and_return(user)
end
describe '#group_level_security_dashboard_available?' do
where(:security_dashboard_feature_enabled, :result) do
true | true
false | false
end
with_them do
before do
stub_licensed_features(security_dashboard: security_dashboard_feature_enabled)
end
it 'returns the expected result' do
expect(helper.group_level_security_dashboard_available?(group)).to eq(result)
end
end
end
describe '#group_level_security_dashboard_available?' do
where(:group_level_compliance_dashboard_enabled, :read_group_compliance_dashboard_permission, :result) do
false | false | false
true | false | false
false | true | false
true | true | true
end
with_them do
before do
stub_licensed_features(group_level_compliance_dashboard: group_level_compliance_dashboard_enabled)
allow(helper).to receive(:can?).with(user, :read_group_compliance_dashboard, group).and_return(read_group_compliance_dashboard_permission)
end
it 'returns the expected result' do
expect(helper.group_level_compliance_dashboard_available?(group)).to eq(result)
end
end
end
describe '#group_level_credentials_inventory_available?' do
where(:credentials_inventory_feature_enabled, :enforced_group_managed_accounts, :read_group_credentials_inventory_permission, :result) do
true | false | false | false
true | true | false | false
true | false | true | false
true | true | true | true
false | false | false | false
false | false | false | false
false | false | true | false
false | true | true | false
end
with_them do
before do
stub_licensed_features(credentials_inventory: credentials_inventory_feature_enabled)
allow(group).to receive(:enforced_group_managed_accounts?).and_return(enforced_group_managed_accounts)
allow(helper).to receive(:can?).with(user, :read_group_credentials_inventory, group).and_return(read_group_credentials_inventory_permission)
end
it 'returns the expected result' do
expect(helper.group_level_credentials_inventory_available?(group)).to eq(result)
end
end
end
describe '#primary_group_level_security_feature_path' do
subject { helper.primary_group_level_security_feature_path(group) }
context 'group_level_security_dashboard is available' do
before do
allow(helper).to receive(:group_level_security_dashboard_available?).with(group).and_return(true)
end
it 'returns path to security dashboard' do
expect(subject).to eq(group_security_dashboard_path(group))
end
end
context 'group_level_compliance_dashboard is available' do
before do
allow(helper).to receive(:group_level_compliance_dashboard_available?).with(group).and_return(true)
end
it 'returns path to compliance dashboard' do
expect(subject).to eq(group_security_compliance_dashboard_path(group))
end
end
context 'group_level_credentials_inventory is available' do
before do
allow(helper).to receive(:group_level_credentials_inventory_available?).with(group).and_return(true)
end
it 'returns path to credentials inventory dashboard' do
expect(subject).to eq(group_security_credentials_path(group))
end
end
context 'when no security features are available' do
before do
allow(helper).to receive(:group_level_security_dashboard_available?).with(group).and_return(false)
allow(helper).to receive(:group_level_compliance_dashboard_available?).with(group).and_return(false)
allow(helper).to receive(:group_level_credentials_inventory_available?).with(group).and_return(false)
end
it 'returns nil' do
expect(subject).to be_nil
end
end
end
end
...@@ -371,6 +371,64 @@ describe GroupPolicy do ...@@ -371,6 +371,64 @@ describe GroupPolicy do
end end
end end
describe 'read_group_credentials_inventory' do
context 'with admin' do
let(:current_user) { admin }
it { is_expected.to be_allowed(:read_group_credentials_inventory) }
end
context 'with owner' do
let(:current_user) { owner }
it { is_expected.to be_allowed(:read_group_credentials_inventory) }
end
context 'with maintainer' do
let(:current_user) { maintainer }
it { is_expected.to be_disallowed(:read_group_credentials_inventory) }
end
context 'with developer' do
let(:current_user) { developer }
it { is_expected.to be_disallowed(:read_group_credentials_inventory) }
context 'when security dashboard features is not available' do
before do
stub_licensed_features(security_dashboard: false)
end
it { is_expected.to be_disallowed(:read_group_credentials_inventory) }
end
end
context 'with reporter' do
let(:current_user) { reporter }
it { is_expected.to be_disallowed(:read_group_credentials_inventory) }
end
context 'with guest' do
let(:current_user) { guest }
it { is_expected.to be_disallowed(:read_group_credentials_inventory) }
end
context 'with non member' do
let(:current_user) { create(:user) }
it { is_expected.to be_disallowed(:read_group_credentials_inventory) }
end
context 'with anonymous' do
let(:current_user) { nil }
it { is_expected.to be_disallowed(:read_group_credentials_inventory) }
end
end
describe 'read_group_security_dashboard' do describe 'read_group_security_dashboard' do
before do before do
stub_licensed_features(security_dashboard: true) stub_licensed_features(security_dashboard: true)
......
...@@ -8,6 +8,7 @@ describe 'layouts/nav/sidebar/_group' do ...@@ -8,6 +8,7 @@ describe 'layouts/nav/sidebar/_group' do
end end
let(:group) { create(:group) } let(:group) { create(:group) }
let(:user) { create(:user) }
describe 'contribution analytics tab' do describe 'contribution analytics tab' do
it 'is not visible when there is no valid license and we dont show promotions' do it 'is not visible when there is no valid license and we dont show promotions' do
...@@ -96,11 +97,70 @@ describe 'layouts/nav/sidebar/_group' do ...@@ -96,11 +97,70 @@ describe 'layouts/nav/sidebar/_group' do
stub_licensed_features(group_level_compliance_dashboard: true) stub_licensed_features(group_level_compliance_dashboard: true)
end end
it 'is visible' do context 'when the user does not have access to Compliance dashboard' do
render it 'is not visible' do
render
expect(rendered).to have_link 'Security & Compliance' expect(rendered).not_to have_link 'Security & Compliance'
expect(rendered).to have_link 'Compliance' expect(rendered).not_to have_link 'Compliance'
end
end
context 'when the user has access to Compliance dashboard' do
before do
group.add_owner(user)
allow(view).to receive(:current_user).and_return(user)
end
it 'is visible' do
render
expect(rendered).to have_link 'Security & Compliance'
expect(rendered).to have_link 'Compliance'
end
end
end
context 'when credentials inventory feature is enabled' do
shared_examples_for 'Credentials tab is not visible' do
it 'does not show the `Credentials` tab' do
render
expect(rendered).not_to have_link 'Security & Compliance'
expect(rendered).not_to have_link 'Credentials'
end
end
before do
stub_licensed_features(credentials_inventory: true)
end
context 'when the group does not enforce managed accounts' do
it_behaves_like 'Credentials tab is not visible'
end
context 'when the group enforces managed accounts' do
before do
allow(group).to receive(:enforced_group_managed_accounts?).and_return(true)
end
context 'when the user has privileges to view Credentials' do
before do
group.add_owner(user)
allow(view).to receive(:current_user).and_return(user)
end
it 'is visible' do
render
expect(rendered).to have_link 'Security & Compliance'
expect(rendered).to have_link 'Credentials'
end
end
context 'when the user does not have privileges to view Credentials' do
it_behaves_like 'Credentials tab is not visible'
end
end end
end end
......
...@@ -26,7 +26,7 @@ module API ...@@ -26,7 +26,7 @@ module API
get do get do
authenticated_with_can_read_all_resources! authenticated_with_can_read_all_resources!
key = KeysFinder.new(current_user, params).execute key = KeysFinder.new(params).execute
not_found!('Key') unless key not_found!('Key') unless key
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
require 'spec_helper' require 'spec_helper'
describe KeysFinder do describe KeysFinder do
subject { described_class.new(user, params).execute } subject { described_class.new(params).execute }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:params) { {} } let(:params) { {} }
...@@ -20,159 +20,149 @@ describe KeysFinder do ...@@ -20,159 +20,149 @@ describe KeysFinder do
let!(:key_2) { create(:personal_key, last_used_at: nil, user: user) } let!(:key_2) { create(:personal_key, last_used_at: nil, user: user) }
let!(:key_3) { create(:personal_key, last_used_at: 2.days.ago) } let!(:key_3) { create(:personal_key, last_used_at: 2.days.ago) }
context 'with a regular user' do context 'key_type' do
it 'raises GitLabAccessDeniedError' do let!(:deploy_key) { create(:deploy_key) }
expect { subject }.to raise_error(KeysFinder::GitLabAccessDeniedError)
end
end
context 'with an admin user' do context 'when `key_type` is `ssh`' do
let(:user) {create(:admin)} before do
params[:key_type] = 'ssh'
end
it 'returns only SSH keys' do
expect(subject).to contain_exactly(key_1, key_2, key_3)
end
end
context 'key_type' do context 'when `key_type` is not specified' do
let!(:deploy_key) { create(:deploy_key) } it 'returns all types of keys' do
expect(subject).to contain_exactly(key_1, key_2, key_3, deploy_key)
end
end
end
context 'when `key_type` is `ssh`' do context 'fingerprint' do
context 'with invalid fingerprint' do
context 'with invalid MD5 fingerprint' do
before do before do
params[:key_type] = 'ssh' params[:fingerprint] = '11:11:11:11'
end end
it 'returns only SSH keys' do it 'raises InvalidFingerprint' do
expect(subject).to contain_exactly(key_1, key_2, key_3) expect { subject }.to raise_error(KeysFinder::InvalidFingerprint)
end end
end end
context 'when `key_type` is not specified' do context 'with invalid SHA fingerprint' do
it 'returns all types of keys' do before do
expect(subject).to contain_exactly(key_1, key_2, key_3, deploy_key) params[:fingerprint] = 'nUhzNyftwAAKs7HufskYTte2g'
end
it 'raises InvalidFingerprint' do
expect { subject }.to raise_error(KeysFinder::InvalidFingerprint)
end end
end end
end end
context 'fingerprint' do context 'with valid fingerprints' do
context 'with invalid fingerprint' do let!(:deploy_key) do
context 'with invalid MD5 fingerprint' do create(:deploy_key,
user: user,
key: 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt1017k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=',
fingerprint: '8a:4a:12:92:0b:50:47:02:d4:5a:8e:a9:44:4e:08:b4',
fingerprint_sha256: '4DPHOVNh53i9dHb5PpY2vjfyf5qniTx1/pBFPoZLDdk')
end
context 'personal key with valid MD5 params' do
context 'with an existent fingerprint' do
before do before do
params[:fingerprint] = '11:11:11:11' params[:fingerprint] = 'ba:81:59:68:d7:6c:cd:02:02:bf:6a:9b:55:4e:af:d1'
end end
it 'raises InvalidFingerprint' do it 'returns the key' do
expect { subject }.to raise_error(KeysFinder::InvalidFingerprint) expect(subject).to eq(key_1)
expect(subject.user).to eq(user)
end end
end end
context 'with invalid SHA fingerprint' do context 'deploy key with an existent fingerprint' do
before do before do
params[:fingerprint] = 'nUhzNyftwAAKs7HufskYTte2g' params[:fingerprint] = '8a:4a:12:92:0b:50:47:02:d4:5a:8e:a9:44:4e:08:b4'
end end
it 'raises InvalidFingerprint' do it 'returns the key' do
expect { subject }.to raise_error(KeysFinder::InvalidFingerprint) expect(subject).to eq(deploy_key)
expect(subject.user).to eq(user)
end end
end end
end
context 'with valid fingerprints' do
let!(:deploy_key) do
create(:deploy_key,
user: user,
key: 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt1017k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=',
fingerprint: '8a:4a:12:92:0b:50:47:02:d4:5a:8e:a9:44:4e:08:b4',
fingerprint_sha256: '4DPHOVNh53i9dHb5PpY2vjfyf5qniTx1/pBFPoZLDdk')
end
context 'personal key with valid MD5 params' do
context 'with an existent fingerprint' do
before do
params[:fingerprint] = 'ba:81:59:68:d7:6c:cd:02:02:bf:6a:9b:55:4e:af:d1'
end
it 'returns the key' do context 'with a non-existent fingerprint' do
expect(subject).to eq(key_1) before do
expect(subject.user).to eq(user) params[:fingerprint] = 'bb:81:59:68:d7:6c:cd:02:02:bf:6a:9b:55:4e:af:d2'
end
end end
context 'deploy key with an existent fingerprint' do it 'returns nil' do
before do expect(subject).to be_nil
params[:fingerprint] = '8a:4a:12:92:0b:50:47:02:d4:5a:8e:a9:44:4e:08:b4'
end
it 'returns the key' do
expect(subject).to eq(deploy_key)
expect(subject.user).to eq(user)
end
end end
end
end
context 'with a non-existent fingerprint' do context 'personal key with valid SHA256 params' do
before do context 'with an existent fingerprint' do
params[:fingerprint] = 'bb:81:59:68:d7:6c:cd:02:02:bf:6a:9b:55:4e:af:d2' before do
end params[:fingerprint] = 'SHA256:nUhzNyftwADy8AH3wFY31tAKs7HufskYTte2aXo/lCg'
end
it 'returns nil' do it 'returns key' do
expect(subject).to be_nil expect(subject).to eq(key_1)
end expect(subject.user).to eq(user)
end end
end end
context 'personal key with valid SHA256 params' do context 'deploy key with an existent fingerprint' do
context 'with an existent fingerprint' do before do
before do params[:fingerprint] = 'SHA256:4DPHOVNh53i9dHb5PpY2vjfyf5qniTx1/pBFPoZLDdk'
params[:fingerprint] = 'SHA256:nUhzNyftwADy8AH3wFY31tAKs7HufskYTte2aXo/lCg'
end
it 'returns key' do
expect(subject).to eq(key_1)
expect(subject.user).to eq(user)
end
end end
context 'deploy key with an existent fingerprint' do it 'returns key' do
before do expect(subject).to eq(deploy_key)
params[:fingerprint] = 'SHA256:4DPHOVNh53i9dHb5PpY2vjfyf5qniTx1/pBFPoZLDdk' expect(subject.user).to eq(user)
end
it 'returns key' do
expect(subject).to eq(deploy_key)
expect(subject.user).to eq(user)
end
end end
end
context 'with a non-existent fingerprint' do context 'with a non-existent fingerprint' do
before do before do
params[:fingerprint] = 'SHA256:xTjuFqftwADy8AH3wFY31tAKs7HufskYTte2aXi/mNp' params[:fingerprint] = 'SHA256:xTjuFqftwADy8AH3wFY31tAKs7HufskYTte2aXi/mNp'
end end
it 'returns nil' do it 'returns nil' do
expect(subject).to be_nil expect(subject).to be_nil
end
end end
end end
end end
end end
end
context 'user' do context 'user' do
context 'without user' do context 'without user' do
it 'contains ssh_keys of all users in the system' do it 'contains ssh_keys of all users in the system' do
expect(subject).to contain_exactly(key_1, key_2, key_3) expect(subject).to contain_exactly(key_1, key_2, key_3)
end
end end
end
context 'with user' do context 'with user' do
before do before do
params[:user] = user params[:users] = user
end end
it 'contains ssh_keys of only the specified users' do it 'contains ssh_keys of only the specified users' do
expect(subject).to contain_exactly(key_1, key_2) expect(subject).to contain_exactly(key_1, key_2)
end
end end
end end
end
context 'sort order' do context 'sort order' do
it 'sorts in last_used_at_desc order' do it 'sorts in last_used_at_desc order' do
expect(subject).to eq([key_3, key_1, key_2]) expect(subject).to eq([key_3, key_1, key_2])
end
end end
end end
end end
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