Commit 291baf5a authored by Diego Louzán's avatar Diego Louzán Committed by Bob Van Landuyt

Admin mode for Sidekiq jobs

Add client & server middlewares to support admin mode in Sidekiq jobs
Extend CurrentUserMode to enable setting admin user in RequestStore
parent e01f24f8
......@@ -37,6 +37,7 @@ class ApplicationController < ActionController::Base
around_action :set_current_context
around_action :set_locale
around_action :set_session_storage
around_action :set_current_admin
after_action :set_page_title_header, if: :json_request?
after_action :limit_session_time, if: -> { !current_user }
......@@ -473,6 +474,13 @@ class ApplicationController < ActionController::Base
response.headers['Page-Title'] = URI.escape(page_title('GitLab'))
end
def set_current_admin(&block)
return yield unless Feature.enabled?(:user_mode_in_session)
return yield unless current_user
Gitlab::Auth::CurrentUserMode.with_current_admin(current_user, &block)
end
def html_request?
request.format.html?
end
......
---
title: Admin mode support in sidekiq jobs
merge_request: 21792
author: Diego Louzán
type: changed
......@@ -10,12 +10,54 @@ module Gitlab
class CurrentUserMode
NotRequestedError = Class.new(StandardError)
# RequestStore entries
CURRENT_REQUEST_BYPASS_SESSION_ADMIN_ID_RS_KEY = { res: :current_user_mode, data: :bypass_session_admin_id }.freeze
CURRENT_REQUEST_ADMIN_MODE_USER_RS_KEY = { res: :current_user_mode, data: :current_admin }.freeze
# SessionStore entries
SESSION_STORE_KEY = :current_user_mode
ADMIN_MODE_START_TIME_KEY = 'admin_mode'
ADMIN_MODE_REQUESTED_TIME_KEY = 'admin_mode_requested'
ADMIN_MODE_START_TIME_KEY = :admin_mode
ADMIN_MODE_REQUESTED_TIME_KEY = :admin_mode_requested
MAX_ADMIN_MODE_TIME = 6.hours
ADMIN_MODE_REQUESTED_GRACE_PERIOD = 5.minutes
class << self
# Admin mode activation requires storing a flag in the user session. Using this
# method when scheduling jobs in Sidekiq will bypass the session check for a
# user that was already in admin mode
def bypass_session!(admin_id)
Gitlab::SafeRequestStore[CURRENT_REQUEST_BYPASS_SESSION_ADMIN_ID_RS_KEY] = admin_id
Gitlab::AppLogger.debug("Bypassing session in admin mode for: #{admin_id}")
yield
ensure
Gitlab::SafeRequestStore.delete(CURRENT_REQUEST_BYPASS_SESSION_ADMIN_ID_RS_KEY)
end
def bypass_session_admin_id
Gitlab::SafeRequestStore[CURRENT_REQUEST_BYPASS_SESSION_ADMIN_ID_RS_KEY]
end
# Store in the current request the provided user model (only if in admin mode)
# and yield
def with_current_admin(admin)
return yield unless self.new(admin).admin_mode?
Gitlab::SafeRequestStore[CURRENT_REQUEST_ADMIN_MODE_USER_RS_KEY] = admin
Gitlab::AppLogger.debug("Admin mode active for: #{admin.username}")
yield
ensure
Gitlab::SafeRequestStore.delete(CURRENT_REQUEST_ADMIN_MODE_USER_RS_KEY)
end
def current_admin
Gitlab::SafeRequestStore[CURRENT_REQUEST_ADMIN_MODE_USER_RS_KEY]
end
end
def initialize(user)
@user = user
end
......@@ -42,7 +84,7 @@ module Gitlab
raise NotRequestedError unless admin_mode_requested?
reset_request_store
reset_request_store_cache_entries
current_session_data[ADMIN_MODE_REQUESTED_TIME_KEY] = nil
current_session_data[ADMIN_MODE_START_TIME_KEY] = Time.now
......@@ -55,7 +97,7 @@ module Gitlab
def disable_admin_mode!
return unless user&.admin?
reset_request_store
reset_request_store_cache_entries
current_session_data[ADMIN_MODE_REQUESTED_TIME_KEY] = nil
current_session_data[ADMIN_MODE_START_TIME_KEY] = nil
......@@ -64,7 +106,7 @@ module Gitlab
def request_admin_mode!
return unless user&.admin?
reset_request_store
reset_request_store_cache_entries
current_session_data[ADMIN_MODE_REQUESTED_TIME_KEY] = Time.now
end
......@@ -73,10 +115,12 @@ module Gitlab
attr_reader :user
# RequestStore entry to cache #admin_mode? result
def admin_mode_rs_key
@admin_mode_rs_key ||= { res: :current_user_mode, user: user.id, method: :admin_mode? }
end
# RequestStore entry to cache #admin_mode_requested? result
def admin_mode_requested_rs_key
@admin_mode_requested_rs_key ||= { res: :current_user_mode, user: user.id, method: :admin_mode_requested? }
end
......@@ -86,6 +130,7 @@ module Gitlab
end
def any_session_with_admin_mode?
return true if bypass_session?
return true if current_session_data.initiated? && current_session_data[ADMIN_MODE_START_TIME_KEY].to_i > MAX_ADMIN_MODE_TIME.ago.to_i
all_sessions.any? do |session|
......@@ -103,7 +148,11 @@ module Gitlab
current_session_data[ADMIN_MODE_REQUESTED_TIME_KEY].to_i > ADMIN_MODE_REQUESTED_GRACE_PERIOD.ago.to_i
end
def reset_request_store
def bypass_session?
user&.id && user.id == self.class.bypass_session_admin_id
end
def reset_request_store_cache_entries
Gitlab::SafeRequestStore.delete(admin_mode_rs_key)
Gitlab::SafeRequestStore.delete(admin_mode_requested_rs_key)
end
......
......@@ -17,6 +17,7 @@ module Gitlab
chain.add Gitlab::SidekiqMiddleware::BatchLoader
chain.add Labkit::Middleware::Sidekiq::Server
chain.add Gitlab::SidekiqMiddleware::InstrumentationLogger
chain.add Gitlab::SidekiqMiddleware::AdminMode::Server
chain.add Gitlab::SidekiqStatus::ServerMiddleware
chain.add Gitlab::SidekiqMiddleware::WorkerContext::Server
end
......@@ -31,6 +32,7 @@ module Gitlab
chain.add Gitlab::SidekiqMiddleware::ClientMetrics
chain.add Gitlab::SidekiqMiddleware::WorkerContext::Client # needs to be before the Labkit middleware
chain.add Labkit::Middleware::Sidekiq::Client
chain.add Gitlab::SidekiqMiddleware::AdminMode::Client
end
end
end
......
# frozen_string_literal: true
module Gitlab
module SidekiqMiddleware
module AdminMode
# Checks if admin mode is enabled for the request creating the sidekiq job
# by examining if admin mode has been enabled for the user
# If enabled then it injects a job field that persists through the job execution
class Client
def call(_worker_class, job, _queue, _redis_pool)
return yield unless Feature.enabled?(:user_mode_in_session)
# Admin mode enabled in the original request or in a nested sidekiq job
admin_mode_user_id = find_admin_user_id
if admin_mode_user_id
job['admin_mode_user_id'] ||= admin_mode_user_id
Gitlab::AppLogger.debug("AdminMode::Client injected admin mode for job: #{job.inspect}")
end
yield
end
private
def find_admin_user_id
Gitlab::Auth::CurrentUserMode.current_admin&.id ||
Gitlab::Auth::CurrentUserMode.bypass_session_admin_id
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module SidekiqMiddleware
module AdminMode
class Server
def call(_worker, job, _queue)
return yield unless Feature.enabled?(:user_mode_in_session)
admin_mode_user_id = job['admin_mode_user_id']
# Do not bypass session if this job was not enabled with admin mode on
return yield unless admin_mode_user_id
Gitlab::Auth::CurrentUserMode.bypass_session!(admin_mode_user_id) do
Gitlab::AppLogger.debug("AdminMode::Server bypasses session for admin mode in job: #{job.inspect}")
yield
end
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
# Test an operation that triggers background jobs requiring administrative rights
describe 'Admin mode for workers', :do_not_mock_admin_mode, :request_store, :clean_gitlab_redis_shared_state do
let(:user) { create(:user) }
let(:user_to_delete) { create(:user) }
before do
add_sidekiq_middleware
sign_in(user)
end
context 'as a regular user' do
it 'cannot delete user' do
visit admin_user_path(user_to_delete)
expect(page).to have_gitlab_http_status(:not_found)
end
end
context 'as an admin user' do
let(:user) { create(:admin) }
context 'when admin mode disabled' do
it 'cannot delete user', :js do
visit admin_user_path(user_to_delete)
expect(page).to have_content('Re-authentication required')
end
end
context 'when admin mode enabled', :delete do
before do
gitlab_enable_admin_mode_sign_in(user)
end
it 'can delete user', :js do
visit admin_user_path(user_to_delete)
click_button 'Delete user'
page.within '.modal-dialog' do
find("input[name='username']").send_keys(user_to_delete.name)
click_button 'Delete user'
wait_for_requests
end
expect(page).to have_content('The user is being deleted.')
# Perform jobs while logged out so that admin mode is only enabled in job metadata
execute_jobs_signed_out(user)
visit admin_user_path(user_to_delete)
expect(page).to have_title('Not Found')
end
end
end
def add_sidekiq_middleware
Sidekiq::Testing.server_middleware do |chain|
chain.add Gitlab::SidekiqMiddleware::AdminMode::Server
end
end
def execute_jobs_signed_out(user)
gitlab_sign_out
Sidekiq::Worker.drain_all
sign_in(user)
gitlab_enable_admin_mode_sign_in(user)
end
end
......@@ -2,46 +2,64 @@
require 'spec_helper'
describe 'Admin uses repository checks' do
describe 'Admin uses repository checks', :request_store, :clean_gitlab_redis_shared_state, :do_not_mock_admin_mode do
include StubENV
let(:admin) { create(:admin) }
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
sign_in(create(:admin))
sign_in(admin)
end
it 'to trigger a single check' do
project = create(:project)
visit_admin_project_page(project)
context 'when admin mode is disabled' do
it 'admin project page requires admin mode' do
project = create(:project)
visit_admin_project_page(project)
page.within('.repository-check') do
click_button 'Trigger repository check'
expect(page).not_to have_css('.repository-check')
expect(page).to have_content('Enter Admin Mode')
end
expect(page).to have_content('Repository check was triggered')
end
it 'to see a single failed repository check', :js do
project = create(:project)
project.update_columns(
last_repository_check_failed: true,
last_repository_check_at: Time.now
)
visit_admin_project_page(project)
context 'when admin mode is enabled' do
before do
gitlab_enable_admin_mode_sign_in(admin)
end
it 'to trigger a single check', :js do
project = create(:project)
visit_admin_project_page(project)
page.within('.repository-check') do
click_button 'Trigger repository check'
end
page.within('.alert') do
expect(page.text).to match(/Last repository check \(just now\) failed/)
expect(page).to have_content('Repository check was triggered')
end
end
it 'to clear all repository checks', :js do
visit repository_admin_application_settings_path
it 'to see a single failed repository check', :js do
project = create(:project)
project.update_columns(
last_repository_check_failed: true,
last_repository_check_at: Time.now
)
visit_admin_project_page(project)
page.within('.alert') do
expect(page.text).to match(/Last repository check \(just now\) failed/)
end
end
expect(RepositoryCheck::ClearWorker).to receive(:perform_async)
it 'to clear all repository checks', :js do
visit repository_admin_application_settings_path
accept_confirm { find(:link, 'Clear all repository checks').send_keys(:return) }
expect(RepositoryCheck::ClearWorker).to receive(:perform_async)
expect(page).to have_content('Started asynchronous removal of all repository check states.')
accept_confirm { find(:link, 'Clear all repository checks').send_keys(:return) }
expect(page).to have_content('Started asynchronous removal of all repository check states.')
end
end
def visit_admin_project_page(project)
......
......@@ -2,10 +2,10 @@
require 'spec_helper'
describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode do
describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode, :request_store do
include_context 'custom session'
let(:user) { build(:user) }
let(:user) { build_stubbed(:user) }
subject { described_class.new(user) }
......@@ -13,54 +13,66 @@ describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode do
allow(ActiveSession).to receive(:list_sessions).with(user).and_return([session])
end
describe '#admin_mode?', :request_store do
context 'when the user is a regular user' do
it 'is false by default' do
expect(subject.admin_mode?).to be(false)
end
shared_examples 'admin mode cannot be enabled' do
it 'is false by default' do
expect(subject.admin_mode?).to be(false)
end
it 'cannot be enabled with a valid password' do
subject.enable_admin_mode!(password: user.password)
it 'cannot be enabled with a valid password' do
subject.enable_admin_mode!(password: user.password)
expect(subject.admin_mode?).to be(false)
end
expect(subject.admin_mode?).to be(false)
end
it 'cannot be enabled with an invalid password' do
subject.enable_admin_mode!(password: nil)
it 'cannot be enabled with an invalid password' do
subject.enable_admin_mode!(password: nil)
expect(subject.admin_mode?).to be(false)
end
expect(subject.admin_mode?).to be(false)
end
it 'cannot be enabled with empty params' do
subject.enable_admin_mode!
it 'cannot be enabled with empty params' do
subject.enable_admin_mode!
expect(subject.admin_mode?).to be(false)
end
expect(subject.admin_mode?).to be(false)
end
it 'disable has no effect' do
subject.enable_admin_mode!
subject.disable_admin_mode!
it 'disable has no effect' do
subject.enable_admin_mode!
subject.disable_admin_mode!
expect(subject.admin_mode?).to be(false)
end
context 'skipping password validation' do
it 'cannot be enabled with a valid password' do
subject.enable_admin_mode!(password: user.password, skip_password_validation: true)
expect(subject.admin_mode?).to be(false)
end
context 'skipping password validation' do
it 'cannot be enabled with a valid password' do
subject.enable_admin_mode!(password: user.password, skip_password_validation: true)
it 'cannot be enabled with an invalid password' do
subject.enable_admin_mode!(skip_password_validation: true)
expect(subject.admin_mode?).to be(false)
end
expect(subject.admin_mode?).to be(false)
end
end
end
it 'cannot be enabled with an invalid password' do
subject.enable_admin_mode!(skip_password_validation: true)
describe '#admin_mode?' do
context 'when the user is a regular user' do
it_behaves_like 'admin mode cannot be enabled'
expect(subject.admin_mode?).to be(false)
context 'bypassing session' do
it_behaves_like 'admin mode cannot be enabled' do
around do |example|
described_class.bypass_session!(user.id) { example.run }
end
end
end
end
context 'when the user is an admin' do
let(:user) { build(:user, :admin) }
let(:user) { build_stubbed(:user, :admin) }
context 'when admin mode not requested' do
it 'is false by default' do
......@@ -148,11 +160,36 @@ describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode do
end
end
end
context 'bypassing session' do
it 'is active by default' do
described_class.bypass_session!(user.id) do
expect(subject.admin_mode?).to be(true)
end
end
it 'enable has no effect' do
described_class.bypass_session!(user.id) do
subject.request_admin_mode!
subject.enable_admin_mode!(password: user.password)
expect(subject.admin_mode?).to be(true)
end
end
it 'disable has no effect' do
described_class.bypass_session!(user.id) do
subject.disable_admin_mode!
expect(subject.admin_mode?).to be(true)
end
end
end
end
end
describe '#enable_admin_mode!' do
let(:user) { build(:user, :admin) }
let(:user) { build_stubbed(:user, :admin) }
it 'creates a timestamp in the session' do
subject.request_admin_mode!
......@@ -163,7 +200,7 @@ describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode do
end
describe '#enable_sessionless_admin_mode!' do
let(:user) { build(:user, :admin) }
let(:user) { build_stubbed(:user, :admin) }
it 'enabled admin mode without password' do
subject.enable_sessionless_admin_mode!
......@@ -173,7 +210,7 @@ describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode do
end
describe '#disable_admin_mode!' do
let(:user) { build(:user, :admin) }
let(:user) { build_stubbed(:user, :admin) }
it 'sets the session timestamp to nil' do
subject.request_admin_mode!
......@@ -183,6 +220,73 @@ describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode do
end
end
describe '.bypass_session!' do
context 'with a regular user' do
it 'admin mode is false' do
described_class.bypass_session!(user.id) do
expect(subject.admin_mode?).to be(false)
expect(described_class.bypass_session_admin_id).to be(user.id)
end
expect(described_class.bypass_session_admin_id).to be_nil
end
end
context 'with an admin user' do
let(:user) { build_stubbed(:user, :admin) }
it 'admin mode is true' do
described_class.bypass_session!(user.id) do
expect(subject.admin_mode?).to be(true)
expect(described_class.bypass_session_admin_id).to be(user.id)
end
expect(described_class.bypass_session_admin_id).to be_nil
end
end
end
describe '.with_current_request_admin_mode' do
context 'with a regular user' do
it 'user is not available inside nor outside the yielded block' do
described_class.with_current_admin(user) do
expect(described_class.current_admin).to be_nil
end
expect(described_class.bypass_session_admin_id).to be_nil
end
end
context 'with an admin user' do
let(:user) { build_stubbed(:user, :admin) }
context 'admin mode is disabled' do
it 'user is not available inside nor outside the yielded block' do
described_class.with_current_admin(user) do
expect(described_class.current_admin).to be_nil
end
expect(described_class.bypass_session_admin_id).to be_nil
end
end
context 'admin mode is enabled' do
before do
subject.request_admin_mode!
subject.enable_admin_mode!(password: user.password)
end
it 'user is available only inside the yielded block' do
described_class.with_current_admin(user) do
expect(described_class.current_admin).to be(user)
end
expect(described_class.current_admin).to be_nil
end
end
end
end
def expected_session_entry(value_matcher)
{
Gitlab::Auth::CurrentUserMode::SESSION_STORE_KEY => a_hash_including(
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::SidekiqMiddleware::AdminMode::Client, :do_not_mock_admin_mode, :request_store do
include AdminModeHelper
let(:worker) do
Class.new do
def perform; end
end
end
let(:job) { {} }
let(:queue) { :test }
it 'yields block' do
expect do |b|
subject.call(worker, job, queue, nil, &b)
end.to yield_control.once
end
context 'user is a regular user' do
it 'no admin mode field in payload' do
subject.call(worker, job, queue, nil) { nil }
expect(job).not_to include('admin_mode_user_id')
end
end
context 'user is an administrator' do
let(:admin) { create(:admin) }
context 'admin mode disabled' do
it 'no admin mode field in payload' do
subject.call(worker, job, queue, nil) { nil }
expect(job).not_to include('admin_mode_user_id')
end
end
context 'admin mode enabled' do
before do
enable_admin_mode!(admin)
end
context 'when sidekiq required context not set' do
it 'no admin mode field in payload' do
subject.call(worker, job, queue, nil) { nil }
expect(job).not_to include('admin_mode_user_id')
end
end
context 'when user stored in current request' do
it 'has admin mode field in payload' do
Gitlab::Auth::CurrentUserMode.with_current_admin(admin) do
subject.call(worker, job, queue, nil) { nil }
expect(job).to include('admin_mode_user_id' => admin.id)
end
end
end
context 'when bypassing session' do
it 'has admin mode field in payload' do
Gitlab::Auth::CurrentUserMode.bypass_session!(admin.id) do
subject.call(worker, job, queue, nil) { nil }
expect(job).to include('admin_mode_user_id' => admin.id)
end
end
end
end
end
context 'admin mode feature disabled' do
before do
stub_feature_flags(user_mode_in_session: false)
end
it 'yields block' do
expect do |b|
subject.call(worker, job, queue, nil, &b)
end.to yield_control.once
end
it 'no admin mode field in payload' do
subject.call(worker, job, queue, nil) { nil }
expect(job).not_to include('admin_mode_user_id')
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::SidekiqMiddleware::AdminMode::Server, :do_not_mock_admin_mode, :request_store do
include AdminModeHelper
let(:worker) do
Class.new do
def perform; end
end
end
let(:job) { {} }
let(:queue) { :test }
it 'yields block' do
expect do |b|
subject.call(worker, job, queue, &b)
end.to yield_control.once
end
context 'job has no admin mode field' do
it 'session is not bypassed' do
subject.call(worker, job, queue) do
expect(Gitlab::Auth::CurrentUserMode.bypass_session_admin_id).to be_nil
end
end
end
context 'job has admin mode field' do
let(:admin) { create(:admin) }
context 'nil admin mode id' do
let(:job) { { 'admin_mode_user_id' => nil } }
it 'session is not bypassed' do
subject.call(worker, job, queue) do
expect(Gitlab::Auth::CurrentUserMode.bypass_session_admin_id).to be_nil
end
end
end
context 'valid admin mode id' do
let(:job) { { 'admin_mode_user_id' => admin.id } }
it 'session is bypassed' do
subject.call(worker, job, queue) do
expect(Gitlab::Auth::CurrentUserMode.bypass_session_admin_id).to be(admin.id)
end
end
end
end
context 'admin mode feature disabled' do
before do
stub_feature_flags(user_mode_in_session: false)
end
it 'yields block' do
expect do |b|
subject.call(worker, job, queue, &b)
end.to yield_control.once
end
it 'session is not bypassed' do
subject.call(worker, job, queue) do
expect(Gitlab::Auth::CurrentUserMode.bypass_session_admin_id).to be_nil
end
end
end
end
......@@ -45,7 +45,8 @@ describe Gitlab::SidekiqMiddleware do
Gitlab::SidekiqMiddleware::ArgumentsLogger,
Gitlab::SidekiqMiddleware::MemoryKiller,
Gitlab::SidekiqMiddleware::RequestStoreMiddleware,
Gitlab::SidekiqMiddleware::WorkerContext::Server
Gitlab::SidekiqMiddleware::WorkerContext::Server,
Gitlab::SidekiqMiddleware::AdminMode::Server
]
end
let(:enabled_sidekiq_middlewares) { all_sidekiq_middlewares - disabled_sidekiq_middlewares }
......@@ -115,7 +116,8 @@ describe Gitlab::SidekiqMiddleware do
Gitlab::SidekiqStatus::ClientMiddleware,
Gitlab::SidekiqMiddleware::ClientMetrics,
Gitlab::SidekiqMiddleware::WorkerContext::Client,
Labkit::Middleware::Sidekiq::Client
Labkit::Middleware::Sidekiq::Client,
Gitlab::SidekiqMiddleware::AdminMode::Client
]
end
......
......@@ -2985,9 +2985,9 @@ describe User, :do_not_mock_admin_mode do
end
end
describe '#can_read_all_resources?' do
describe '#can_read_all_resources?', :request_store do
it 'returns false for regular user' do
user = build(:user)
user = build_stubbed(:user)
expect(user.can_read_all_resources?).to be_falsy
end
......@@ -2995,7 +2995,7 @@ describe User, :do_not_mock_admin_mode do
context 'for admin user' do
include_context 'custom session'
let(:user) { build(:user, :admin) }
let(:user) { build_stubbed(:user, :admin) }
context 'when admin mode is disabled' do
it 'returns false' do
......
......@@ -23,8 +23,8 @@ describe BasePolicy, :do_not_mock_admin_mode do
end
describe 'read cross project' do
let(:current_user) { create(:user) }
let(:user) { create(:user) }
let(:current_user) { build_stubbed(:user) }
let(:user) { build_stubbed(:user) }
subject { described_class.new(current_user, [user]) }
......@@ -38,7 +38,7 @@ describe BasePolicy, :do_not_mock_admin_mode do
it { is_expected.not_to be_allowed(:read_cross_project) }
context 'for admins' do
let(:current_user) { build(:admin) }
let(:current_user) { build_stubbed(:admin) }
subject { described_class.new(current_user, nil) }
......@@ -56,14 +56,14 @@ describe BasePolicy, :do_not_mock_admin_mode do
end
describe 'full private access' do
let(:current_user) { create(:user) }
let(:current_user) { build_stubbed(:user) }
subject { described_class.new(current_user, nil) }
it { is_expected.not_to be_allowed(:read_all_resources) }
context 'for admins' do
let(:current_user) { build(:admin) }
let(:current_user) { build_stubbed(:admin) }
it 'allowed when in admin mode' do
enable_admin_mode!(current_user)
......
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