Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Boxiang Sun
gitlab-ce
Commits
37c40143
Commit
37c40143
authored
Apr 06, 2017
by
http://jneen.net/
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
convert all the policies to DeclarativePolicy
parent
e5aad75a
Changes
30
Show whitespace changes
Inline
Side-by-side
Showing
30 changed files
with
629 additions
and
662 deletions
+629
-662
app/models/ability.rb
app/models/ability.rb
+16
-14
app/models/project_feature.rb
app/models/project_feature.rb
+5
-2
app/policies/base_policy.rb
app/policies/base_policy.rb
+9
-123
app/policies/ci/build_policy.rb
app/policies/ci/build_policy.rb
+6
-22
app/policies/ci/pipeline_policy.rb
app/policies/ci/pipeline_policy.rb
+1
-3
app/policies/ci/runner_policy.rb
app/policies/ci/runner_policy.rb
+9
-6
app/policies/ci/trigger_policy.rb
app/policies/ci/trigger_policy.rb
+12
-9
app/policies/commit_status_policy.rb
app/policies/commit_status_policy.rb
+4
-2
app/policies/deploy_key_policy.rb
app/policies/deploy_key_policy.rb
+7
-7
app/policies/deployment_policy.rb
app/policies/deployment_policy.rb
+1
-3
app/policies/environment_policy.rb
app/policies/environment_policy.rb
+4
-12
app/policies/external_issue_policy.rb
app/policies/external_issue_policy.rb
+1
-3
app/policies/global_policy.rb
app/policies/global_policy.rb
+35
-11
app/policies/group_label_policy.rb
app/policies/group_label_policy.rb
+1
-3
app/policies/group_member_policy.rb
app/policies/group_member_policy.rb
+13
-16
app/policies/group_policy.rb
app/policies/group_policy.rb
+52
-44
app/policies/issuable_policy.rb
app/policies/issuable_policy.rb
+10
-9
app/policies/issue_policy.rb
app/policies/issue_policy.rb
+9
-17
app/policies/namespace_policy.rb
app/policies/namespace_policy.rb
+6
-6
app/policies/nil_policy.rb
app/policies/nil_policy.rb
+3
-0
app/policies/note_policy.rb
app/policies/note_policy.rb
+18
-13
app/policies/personal_snippet_policy.rb
app/policies/personal_snippet_policy.rb
+21
-20
app/policies/project_label_policy.rb
app/policies/project_label_policy.rb
+1
-3
app/policies/project_member_policy.rb
app/policies/project_member_policy.rb
+10
-16
app/policies/project_policy.rb
app/policies/project_policy.rb
+315
-259
app/policies/project_snippet_policy.rb
app/policies/project_snippet_policy.rb
+42
-22
app/policies/user_policy.rb
app/policies/user_policy.rb
+13
-12
lib/gitlab/allowable.rb
lib/gitlab/allowable.rb
+2
-2
spec/policies/project_policy_spec.rb
spec/policies/project_policy_spec.rb
+2
-2
spec/policies/project_snippet_policy_spec.rb
spec/policies/project_snippet_policy_spec.rb
+1
-1
No files found.
app/models/ability.rb
View file @
37c40143
...
...
@@ -56,24 +56,26 @@ class Ability
end
end
def
allowed?
(
user
,
action
,
subject
=
:global
)
allowed
(
user
,
subject
).
include?
(
action
)
def
allowed?
(
user
,
action
,
subject
=
:global
,
opts
=
{})
if
subject
.
is_a?
(
Hash
)
opts
,
subject
=
subject
,
:global
end
def
allowed
(
user
,
subject
=
:global
)
return
BasePolicy
::
RuleSet
.
none
if
subject
.
nil?
return
uncached_allowed
(
user
,
subject
)
unless
RequestStore
.
active?
policy
=
policy_for
(
user
,
subject
)
user_key
=
user
?
user
.
id
:
'anonymous'
subject_key
=
subject
==
:global
?
'global'
:
"
#{
subject
.
class
.
name
}
/
#{
subject
.
id
}
"
key
=
"/ability/
#{
user_key
}
/
#{
subject_key
}
"
RequestStore
[
key
]
||=
uncached_allowed
(
user
,
subject
).
freeze
case
opts
[
:scope
]
when
:user
DeclarativePolicy
.
user_scope
{
policy
.
can?
(
action
)
}
when
:subject
DeclarativePolicy
.
subject_scope
{
policy
.
can?
(
action
)
}
else
policy
.
can?
(
action
)
end
end
private
def
uncached_allowed
(
user
,
subject
)
BasePolicy
.
class_for
(
subject
).
abilities
(
user
,
subject
)
def
policy_for
(
user
,
subject
=
:global
)
cache
=
RequestStore
.
active?
?
RequestStore
:
{}
DeclarativePolicy
.
policy_for
(
user
,
subject
,
cache:
cache
)
end
end
end
app/models/project_feature.rb
View file @
37c40143
...
...
@@ -51,8 +51,11 @@ class ProjectFeature < ActiveRecord::Base
default_value_for
:repository_access_level
,
value:
ENABLED
,
allows_nil:
false
def
feature_available?
(
feature
,
user
)
access_level
=
public_send
(
ProjectFeature
.
access_level_attribute
(
feature
))
get_permission
(
user
,
access_level
)
get_permission
(
user
,
access_level
(
feature
))
end
def
access_level
(
feature
)
public_send
(
ProjectFeature
.
access_level_attribute
(
feature
))
end
def
builds_enabled?
...
...
app/policies/base_policy.rb
View file @
37c40143
class
BasePolicy
class
RuleSet
attr_reader
:can_set
,
:cannot_set
def
initialize
(
can_set
,
cannot_set
)
@can_set
=
can_set
@cannot_set
=
cannot_set
end
require
'declarative_policy'
delegate
:size
,
to: :to_set
class
BasePolicy
<
DeclarativePolicy
::
Base
desc
"User is an instance admin"
with_options
scope: :user
,
score:
0
condition
(
:admin
)
{
@user
&
.
admin?
}
def
self
.
empty
new
(
Set
.
new
,
Set
.
new
)
end
with_options
scope: :user
,
score:
0
condition
(
:external_user
)
{
@user
.
nil?
||
@user
.
external?
}
def
self
.
none
empty
.
freeze
end
def
can?
(
ability
)
@can_set
.
include?
(
ability
)
&&
!
@cannot_set
.
include?
(
ability
)
end
def
include?
(
ability
)
can?
(
ability
)
end
def
to_set
@can_set
-
@cannot_set
end
def
merge
(
other
)
@can_set
.
merge
(
other
.
can_set
)
@cannot_set
.
merge
(
other
.
cannot_set
)
end
def
can!
(
*
abilities
)
@can_set
.
merge
(
abilities
)
end
def
cannot!
(
*
abilities
)
@cannot_set
.
merge
(
abilities
)
end
def
freeze
@can_set
.
freeze
@cannot_set
.
freeze
super
end
end
def
self
.
abilities
(
user
,
subject
)
new
(
user
,
subject
).
abilities
end
def
self
.
class_for
(
subject
)
return
GlobalPolicy
if
subject
==
:global
raise
ArgumentError
,
'no policy for nil'
if
subject
.
nil?
if
subject
.
class
.
try
(
:presenter?
)
subject
=
subject
.
subject
end
subject
.
class
.
ancestors
.
each
do
|
klass
|
next
unless
klass
.
name
begin
policy_class
=
"
#{
klass
.
name
}
Policy"
.
constantize
# NOTE: the < operator here tests whether policy_class
# inherits from BasePolicy
return
policy_class
if
policy_class
<
BasePolicy
rescue
NameError
nil
end
end
raise
"no policy for
#{
subject
.
class
.
name
}
"
end
attr_reader
:user
,
:subject
def
initialize
(
user
,
subject
)
@user
=
user
@subject
=
subject
end
def
abilities
return
RuleSet
.
none
if
@user
&&
@user
.
blocked?
return
anonymous_abilities
if
@user
.
nil?
collect_rules
{
rules
}
end
def
anonymous_abilities
collect_rules
{
anonymous_rules
}
end
def
anonymous_rules
rules
end
def
rules
raise
NotImplementedError
end
def
delegate!
(
new_subject
)
@rule_set
.
merge
(
Ability
.
allowed
(
@user
,
new_subject
))
end
def
can?
(
rule
)
@rule_set
.
can?
(
rule
)
end
def
can!
(
*
rules
)
@rule_set
.
can!
(
*
rules
)
end
def
cannot!
(
*
rules
)
@rule_set
.
cannot!
(
*
rules
)
end
private
def
collect_rules
(
&
b
)
@rule_set
=
RuleSet
.
empty
yield
@rule_set
end
with_options
scope: :user
,
score:
0
condition
(
:can_create_group
)
{
@user
&
.
can_create_group
}
end
app/policies/ci/build_policy.rb
View file @
37c40143
module
Ci
class
BuildPolicy
<
CommitStatusPolicy
alias_method
:build
,
:subject
def
rules
super
# If we can't read build we should also not have that
# ability when looking at this in context of commit_status
%w[read create update admin]
.
each
do
|
rule
|
cannot!
:"
#{
rule
}
_commit_status"
unless
can?
:"
#{
rule
}
_build"
end
if
can?
(
:update_build
)
&&
protected_action?
cannot!
:update_build
end
end
private
def
protected_action?
return
false
unless
build
.
action?
condition
(
:protected_action
)
do
next
false
unless
@subject
.
action?
!::
Gitlab
::
UserAccess
.
new
(
user
,
project:
build
.
project
)
.
can_merge_to_branch?
(
build
.
ref
)
.
new
(
@user
,
project:
@subject
.
project
)
.
can_merge_to_branch?
(
@subject
.
ref
)
end
rule
{
protected_action
}.
prevent
:update_build
end
end
app/policies/ci/pipeline_policy.rb
View file @
37c40143
module
Ci
class
PipelinePolicy
<
BasePolicy
def
rules
delegate!
@subject
.
project
end
delegate
{
@subject
.
project
}
end
end
app/policies/ci/runner_policy.rb
View file @
37c40143
module
Ci
class
RunnerPolicy
<
BasePolicy
def
rules
return
unless
@user
with_options
scope: :subject
,
score:
0
condition
(
:shared
)
{
@subject
.
is_shared?
}
can!
:assign_runner
if
@user
.
admin?
with_options
scope: :subject
,
score:
0
condition
(
:locked
,
scope: :subject
)
{
@subject
.
locked?
}
return
if
@subject
.
is_shared?
||
@subject
.
locked?
condition
(
:authorized_runner
)
{
@user
.
ci_authorized_runners
.
include?
(
@subject
)
}
can!
:assign_runner
if
@user
.
ci_authorized_runners
.
include?
(
@subject
)
end
rule
{
anonymous
}.
prevent_all
rule
{
admin
|
authorized_runner
}.
enable
:assign_runner
rule
{
~
admin
&
shared
}.
prevent
:assign_runner
rule
{
~
admin
&
locked
}.
prevent
:assign_runner
end
end
app/policies/ci/trigger_policy.rb
View file @
37c40143
module
Ci
class
TriggerPolicy
<
BasePolicy
def
rules
delegate!
@subject
.
project
delegate
{
@subject
.
project
}
if
can?
(
:admin_build
)
can!
:admin_trigger
if
@subject
.
owner
.
blank?
||
@subject
.
owner
==
@user
can!
:manage_trigger
end
end
with_options
scope: :subject
,
score:
0
condition
(
:legacy
)
{
@subject
.
legacy?
}
with_score
0
condition
(
:is_owner
)
{
@user
&&
@subject
.
owner_id
==
@user
.
id
}
rule
{
~
can?
(
:admin_build
)
}.
prevent
:admin_trigger
rule
{
legacy
|
is_owner
}.
enable
:admin_trigger
rule
{
can?
(
:admin_build
)
}.
enable
:manage_trigger
end
end
app/policies/commit_status_policy.rb
View file @
37c40143
class
CommitStatusPolicy
<
BasePolicy
def
rules
delegate!
@subject
.
project
delegate
{
@subject
.
project
}
%w[read create update admin]
.
each
do
|
action
|
rule
{
~
can?
(
:"
#{
action
}
_commit_status"
)
}.
prevent
:"
#{
action
}
_build"
end
end
app/policies/deploy_key_policy.rb
View file @
37c40143
class
DeployKeyPolicy
<
BasePolicy
def
rules
return
unless
@user
with_options
scope: :subject
,
score:
0
condition
(
:private_deploy_key
)
{
@subject
.
private?
}
can!
:update_deploy_key
if
@user
.
admin?
condition
(
:has_deploy_key
)
{
@user
.
project_deploy_keys
.
exists?
(
id:
@subject
.
id
)
}
if
@subject
.
private?
&&
@user
.
project_deploy_keys
.
exists?
(
id:
@subject
.
id
)
can!
:update_deploy_key
end
end
rule
{
anonymous
}.
prevent_all
rule
{
admin
}.
enable
:update_deploy_key
rule
{
private_deploy_key
&
has_deploy_key
}.
enable
:update_deploy_key
end
app/policies/deployment_policy.rb
View file @
37c40143
class
DeploymentPolicy
<
BasePolicy
def
rules
delegate!
@subject
.
project
end
delegate
{
@subject
.
project
}
end
app/policies/environment_policy.rb
View file @
37c40143
class
EnvironmentPolicy
<
BasePolicy
alias_method
:environment
,
:subject
delegate
{
@subject
.
project
}
def
rules
delegate!
environment
.
project
if
can?
(
:create_deployment
)
&&
environment
.
stop_action?
can!
:stop_environment
if
can_play_stop_action?
end
condition
(
:stop_action_allowed
)
do
@subject
.
stop_action?
&&
can?
(
:update_build
,
@subject
.
stop_action
)
end
private
def
can_play_stop_action?
Ability
.
allowed?
(
user
,
:update_build
,
environment
.
stop_action
)
end
rule
{
can?
(
:create_deployment
)
&
stop_action_allowed
}.
enable
:stop_environment
end
app/policies/external_issue_policy.rb
View file @
37c40143
class
ExternalIssuePolicy
<
BasePolicy
def
rules
delegate!
@subject
.
project
end
delegate
{
@subject
.
project
}
end
app/policies/global_policy.rb
View file @
37c40143
class
GlobalPolicy
<
BasePolicy
def
rules
return
unless
@user
desc
"User is blocked"
with_options
scope: :user
,
score:
0
condition
(
:blocked
)
{
@user
.
blocked?
}
can!
:create_group
if
@user
.
can_create_group
can!
:read_users_list
desc
"User is an internal user"
with_options
scope: :user
,
score:
0
condition
(
:internal
)
{
@user
.
internal?
}
unless
@user
.
blocked?
||
@user
.
internal?
can!
:log_in
unless
@user
.
access_locked?
can!
:access_api
can!
:access_git
can!
:receive_notifications
can!
:use_quick_actions
desc
"User's access has been locked"
with_options
scope: :user
,
score:
0
condition
(
:access_locked
)
{
@user
.
access_locked?
}
rule
{
anonymous
}.
prevent_all
rule
{
default
}.
policy
do
enable
:read_users_list
enable
:log_in
enable
:access_api
enable
:access_git
enable
:receive_notifications
enable
:use_quick_actions
end
rule
{
blocked
|
internal
}.
policy
do
prevent
:log_in
prevent
:access_api
prevent
:access_git
prevent
:receive_notifications
prevent
:use_quick_actions
end
rule
{
can_create_group
}.
policy
do
enable
:create_group
end
rule
{
access_locked
}.
policy
do
prevent
:log_in
end
end
app/policies/group_label_policy.rb
View file @
37c40143
class
GroupLabelPolicy
<
BasePolicy
def
rules
delegate!
@subject
.
group
end
delegate
{
@subject
.
group
}
end
app/policies/group_member_policy.rb
View file @
37c40143
class
GroupMemberPolicy
<
BasePolicy
def
rules
return
unless
@user
delegate
:group
target_user
=
@subject
.
user
group
=
@subject
.
group
with_scope
:subject
condition
(
:last_owner
)
{
@subject
.
group
.
last_owner?
(
@subject
.
user
)
}
return
if
group
.
last_owner?
(
target_user
)
desc
"Membership is users' own"
with_score
0
condition
(
:is_target_user
)
{
@user
&&
@subject
.
user_id
==
@user
.
id
}
can_manage
=
Ability
.
allowed?
(
@user
,
:admin_group_member
,
group
)
rule
{
anonymous
}.
prevent_all
rule
{
last_owner
}.
prevent_all
if
can_manage
can!
:update_group_member
can!
:destroy_group_member
elsif
@user
==
target_user
can!
:destroy_group_member
rule
{
can?
(
:admin_group_member
)
}.
policy
do
enable
:update_group_member
enable
:destroy_group_member
end
additional_rules!
end
def
additional_rules!
# This is meant to be overriden in EE
rule
{
is_target_user
}.
policy
do
enable
:destroy_group_member
end
end
app/policies/group_policy.rb
View file @
37c40143
class
GroupPolicy
<
BasePolicy
def
rules
can!
:read_group
if
@subject
.
public?
return
unless
@user
globally_viewable
=
@subject
.
public?
||
(
@subject
.
internal?
&&
!
@user
.
external?
)
access_level
=
@subject
.
max_member_access_for_user
(
@user
)
owner
=
access_level
>=
GroupMember
::
OWNER
master
=
access_level
>=
GroupMember
::
MASTER
reporter
=
access_level
>=
GroupMember
::
REPORTER
can_read
=
false
can_read
||=
globally_viewable
can_read
||=
access_level
>=
GroupMember
::
GUEST
can_read
||=
GroupProjectsFinder
.
new
(
group:
@subject
,
current_user:
@user
).
execute
.
any?
can!
:read_group
if
can_read
if
reporter
can!
:admin_label
end
desc
"Group is public"
with_options
scope: :subject
,
score:
0
condition
(
:public_group
)
{
@subject
.
public?
}
# Only group masters and group owners can create new projects
if
master
can!
:create_projects
can!
:admin_milestones
end
with_score
0
condition
(
:logged_in_viewable
)
{
@user
&&
@subject
.
internal?
&&
!
@user
.
external?
}
condition
(
:has_access
)
{
access_level
!=
GroupMember
::
NO_ACCESS
}
condition
(
:guest
)
{
access_level
>=
GroupMember
::
GUEST
}
condition
(
:owner
)
{
access_level
>=
GroupMember
::
OWNER
}
condition
(
:master
)
{
access_level
>=
GroupMember
::
MASTER
}
condition
(
:reporter
)
{
access_level
>=
GroupMember
::
REPORTER
}
# Only group owner and administrators can admin group
if
owner
can!
:admin_group
can!
:admin_namespace
can!
:admin_group_member
can!
:change_visibility_level
can!
:create_subgroup
if
@user
.
can_create_group
condition
(
:has_projects
)
do
GroupProjectsFinder
.
new
(
group:
@subject
,
current_user:
@user
).
execute
.
any?
end
if
globally_viewable
&&
@subject
.
request_access_enabled
&&
access_level
==
GroupMember
::
NO_ACCESS
can!
:request_access
with_options
scope: :subject
,
score:
0
condition
(
:request_access_enabled
)
{
@subject
.
request_access_enabled
}
rule
{
public_group
}
.
enable
:read_group
rule
{
logged_in_viewable
}.
enable
:read_group
rule
{
guest
}
.
enable
:read_group
rule
{
admin
}
.
enable
:read_group
rule
{
has_projects
}
.
enable
:read_group
rule
{
reporter
}.
enable
:admin_label
rule
{
master
}.
policy
do
enable
:create_projects
enable
:admin_milestones
end
rule
{
owner
}.
policy
do
enable
:admin_group
enable
:admin_namespace
enable
:admin_group_member
enable
:change_visibility_level
end
def
can_read_group?
return
true
if
@subject
.
public?
return
true
if
@user
.
admin?
return
true
if
@subject
.
internal?
&&
!
@user
.
external?
return
true
if
@subject
.
users
.
include?
(
@user
)
rule
{
owner
&
can_create_group
}.
enable
:create_subgroup
GroupProjectsFinder
.
new
(
group:
@subject
,
current_user:
@user
).
execute
.
any?
rule
{
public_group
|
logged_in_viewable
}.
enable
:view_globally
rule
{
default
}.
enable
(
:request_access
)
rule
{
~
request_access_enabled
}.
prevent
:request_access
rule
{
~
can?
(
:view_globally
)
}.
prevent
:request_access
rule
{
has_access
}.
prevent
:request_access
def
access_level
return
GroupMember
::
NO_ACCESS
if
@user
.
nil?
@access_level
||=
@subject
.
max_member_access_for_user
(
@user
)
end
end
app/policies/issuable_policy.rb
View file @
37c40143
class
IssuablePolicy
<
BasePolicy
def
action_name
@subject
.
class
.
name
.
underscore
end
delegate
{
@subject
.
project
}
def
rules
if
@user
&&
@subject
.
assignee_or_author?
(
@user
)
can!
:"read_
#{
action_name
}
"
can!
:"update_
#{
action_name
}
"
desc
"User is the assignee or author"
condition
(
:assignee_or_author
)
do
@user
&&
@subject
.
assignee_or_author?
(
@user
)
end
delegate!
@subject
.
project
rule
{
assignee_or_author
}.
policy
do
enable
:read_issue
enable
:update_issue
enable
:read_merge_request
enable
:update_merge_request
end
end
app/policies/issue_policy.rb
View file @
37c40143
...
...
@@ -3,25 +3,17 @@ class IssuePolicy < IssuablePolicy
# Make sure to sync this class checks with issue.rb to avoid security problems.
# Check commit 002ad215818450d2cbbc5fa065850a953dc7ada8 for more information.
def
issue
@subject
desc
"User can read confidential issues"
condition
(
:can_read_confidential
)
do
@user
&&
IssueCollection
.
new
([
@subject
]).
visible_to
(
@user
).
any?
end
de
f
rules
super
de
sc
"Issue is confidential"
condition
(
:confidential
,
scope: :subject
)
{
@subject
.
confidential?
}
if
@subject
.
confidential?
&&
!
can_read_confidential?
cannot!
:read_issue
cannot!
:update_issue
cannot!
:admin_issue
end
end
private
def
can_read_confidential?
return
false
unless
@user
IssueCollection
.
new
([
@subject
]).
visible_to
(
@user
).
any?
rule
{
confidential
&
~
can_read_confidential
}.
policy
do
prevent
:read_issue
prevent
:update_issue
prevent
:admin_issue
end
end
app/policies/namespace_policy.rb
View file @
37c40143
class
NamespacePolicy
<
BasePolicy
def
rules
return
unless
@user
rule
{
anonymous
}.
prevent_all
if
@subject
.
owner
==
@user
||
@user
.
admin?
can!
:create_projects
can!
:admin_namespace
end
condition
(
:owner
)
{
@subject
.
owner
==
@user
}
rule
{
owner
|
admin
}.
policy
do
enable
:create_projects
enable
:admin_namespace
end
end
app/policies/nil_policy.rb
0 → 100644
View file @
37c40143
class
NilPolicy
<
BasePolicy
rule
{
default
}.
prevent_all
end
app/policies/note_policy.rb
View file @
37c40143
class
NotePolicy
<
BasePolicy
def
rules
delegate!
@subject
.
project
delegate
{
@subject
.
project
}
return
unless
@user
condition
(
:is_author
)
{
@user
&&
@subject
.
author
==
@user
}
condition
(
:for_merge_request
,
scope: :subject
)
{
@subject
.
for_merge_request?
}
condition
(
:is_noteable_author
)
{
@user
&&
@subject
.
noteable
.
author_id
==
@user
.
id
}
if
@subject
.
author
==
@user
can!
:read_note
can!
:update_note
can!
:admin_note
can!
:resolve_note
end
condition
(
:editable
,
scope: :subject
)
{
@subject
.
editable?
}
rule
{
~
editable
|
anonymous
}.
prevent
:edit_note
rule
{
is_author
|
admin
}.
enable
:edit_note
rule
{
can?
(
:master_access
)
}.
enable
:edit_note
if
@subject
.
for_merge_request?
&&
@subject
.
noteable
.
author
==
@user
can!
:resolve_note
rule
{
is_author
}.
policy
do
enable
:read_note
enable
:update_note
enable
:admin_note
enable
:resolve_note
end
rule
{
for_merge_request
&
is_noteable_author
}.
policy
do
enable
:resolve_note
end
end
app/policies/personal_snippet_policy.rb
View file @
37c40143
class
PersonalSnippetPolicy
<
BasePolicy
def
rules
can!
:read_personal_snippet
if
@subject
.
public?
return
unless
@user
condition
(
:public_snippet
,
scope: :subject
)
{
@subject
.
public?
}
condition
(
:is_author
)
{
@user
&&
@subject
.
author
==
@user
}
condition
(
:internal_snippet
,
scope: :subject
)
{
@subject
.
internal?
}
if
@subject
.
public?
can!
:comment_personal_snippet
rule
{
public_snippet
}.
policy
do
enable
:read_personal_snippet
enable
:comment_personal_snippet
end
if
@subject
.
author
==
@user
can!
:read_personal_snippet
can!
:update_personal_snippet
can!
:destroy_personal_snippet
can!
:admin_personal_snippet
can!
:comment_personal_snippet
rule
{
is_author
}.
policy
do
enable
:read_personal_snippet
enable
:update_personal_snippet
enable
:destroy_personal_snippet
enable
:admin_personal_snippet
enable
:comment_personal_snippet
end
unless
@user
.
external?
can!
:create_personal_snippet
end
rule
{
~
anonymous
}.
enable
:create_personal_snippet
rule
{
external_user
}.
prevent
:create_personal_snippet
if
@subject
.
internal?
&&
!
@user
.
external?
can!
:read_personal_snippet
can!
:comment_personal_snippet
end
rule
{
internal_snippet
&
~
external_user
}.
policy
do
enable
:read_personal_snippet
enable
:comment_personal_snippet
end
rule
{
anonymous
}.
prevent
:comment_personal_snippet
end
app/policies/project_label_policy.rb
View file @
37c40143
class
ProjectLabelPolicy
<
BasePolicy
def
rules
delegate!
@subject
.
project
end
delegate
{
@subject
.
project
}
end
app/policies/project_member_policy.rb
View file @
37c40143
class
ProjectMemberPolicy
<
BasePolicy
def
rules
# anonymous users have no abilities here
return
unless
@user
delegate
{
@subject
.
project
}
target_user
=
@subject
.
user
project
=
@subject
.
project
condition
(
:target_is_owner
,
scope: :subject
)
{
@subject
.
user
==
@subject
.
project
.
owner
}
condition
(
:target_is_self
)
{
@user
&&
@subject
.
user
==
@user
}
return
if
target_user
==
project
.
owner
rule
{
anonymous
}.
prevent_all
rule
{
target_is_owner
}.
prevent_all
can_manage
=
Ability
.
allowed?
(
@user
,
:admin_project_member
,
project
)
if
can_manage
can!
:update_project_member
can!
:destroy_project_member
rule
{
can?
(
:admin_project_member
)
}.
policy
do
enable
:update_project_member
enable
:destroy_project_member
end
if
@user
==
target_user
can!
:destroy_project_member
end
end
rule
{
target_is_self
}.
enable
:destroy_project_member
end
app/policies/project_policy.rb
View file @
37c40143
class
ProjectPolicy
<
BasePolicy
def
rules
team_access!
(
user
)
def
self
.
create_read_update_admin
(
name
)
[
:"create_
#{
name
}
"
,
:"read_
#{
name
}
"
,
:"update_
#{
name
}
"
,
:"admin_
#{
name
}
"
]
end
desc
"User is a project owner"
condition
:owner
do
@user
&&
project
.
owner
==
@user
||
(
project
.
group
&&
project
.
group
.
has_owner?
(
@user
))
end
desc
"Project has public builds enabled"
condition
(
:public_builds
,
scope: :subject
)
{
project
.
public_builds?
}
# For guest access we use #is_team_member? so we can use
# project.members, which gets cached in subject scope.
# This is safe because team_access_level is guaranteed
# by ProjectAuthorization's validation to be at minimum
# GUEST
desc
"User has guest access"
condition
(
:guest
)
{
is_team_member?
}
desc
"User has reporter access"
condition
(
:reporter
)
{
team_access_level
>=
Gitlab
::
Access
::
REPORTER
}
desc
"User has developer access"
condition
(
:developer
)
{
team_access_level
>=
Gitlab
::
Access
::
DEVELOPER
}
desc
"User has master access"
condition
(
:master
)
{
team_access_level
>=
Gitlab
::
Access
::
MASTER
}
owner_access!
if
user
.
admin?
||
owner?
team_member_owner_access!
if
owner?
desc
"Project is public"
condition
(
:public_project
,
scope: :subject
)
{
project
.
public?
}
if
project
.
public?
||
(
project
.
internal?
&&
!
user
.
external?
)
guest_access!
public_access!
can!
:request_access
if
access_requestable?
desc
"Project is visible to internal users"
condition
(
:internal_access
)
do
project
.
internal?
&&
!
user
.
external?
end
archived_access!
if
project
.
archived?
desc
"User is a member of the group"
condition
(
:group_member
,
scope: :subject
)
{
project_group_member?
}
disabled_features!
desc
"Project is archived"
condition
(
:archived
,
scope: :subject
)
{
project
.
archived?
}
condition
(
:default_issues_tracker
,
scope: :subject
)
{
project
.
default_issues_tracker?
}
desc
"Container registry is disabled"
condition
(
:container_registry_disabled
,
scope: :subject
)
do
!
project
.
container_registry_enabled
end
def
project
@subject
desc
"Project has an external wiki"
condition
(
:has_external_wiki
,
scope: :subject
)
{
project
.
has_external_wiki?
}
desc
"Project has request access enabled"
condition
(
:request_access_enabled
,
scope: :subject
)
{
project
.
request_access_enabled
}
features
=
%w[
merge_requests
issues
repository
snippets
wiki
builds
]
features
.
each
do
|
f
|
# these are scored high because they are unlikely
desc
"Project has
#{
f
}
disabled"
condition
(
:"
#{
f
}
_disabled"
,
score:
32
)
{
!
feature_available?
(
f
.
to_sym
)
}
end
def
owner?
return
@owner
if
defined?
(
@owner
)
@owner
=
project
.
owner
==
user
||
(
project
.
group
&&
project
.
group
.
has_owner?
(
user
))
end
def
guest_access!
can!
:read_project
can!
:read_board
can!
:read_list
can!
:read_wiki
can!
:read_issue
can!
:read_label
can!
:read_milestone
can!
:read_project_snippet
can!
:read_project_member
can!
:read_note
can!
:create_project
can!
:create_issue
can!
:create_note
can!
:upload_file
can!
:read_cycle_analytics
if
project
.
public_builds?
can!
:read_pipeline
can!
:read_pipeline_schedule
can!
:read_build
end
end
def
reporter_access!
can!
:download_code
can!
:download_wiki_code
can!
:fork_project
can!
:create_project_snippet
can!
:update_issue
can!
:admin_issue
can!
:admin_label
can!
:admin_list
can!
:read_commit_status
can!
:read_build
can!
:read_container_image
can!
:read_pipeline
can!
:read_pipeline_schedule
can!
:read_environment
can!
:read_deployment
can!
:read_merge_request
end
# Permissions given when an user is team member of a project
def
team_member_reporter_access!
can!
:build_download_code
can!
:build_read_container_image
end
def
developer_access!
can!
:admin_merge_request
can!
:update_merge_request
can!
:create_commit_status
can!
:update_commit_status
can!
:create_build
can!
:update_build
can!
:create_pipeline
can!
:update_pipeline
can!
:create_pipeline_schedule
can!
:update_pipeline_schedule
can!
:create_merge_request
can!
:create_wiki
can!
:push_code
can!
:resolve_note
can!
:create_container_image
can!
:update_container_image
can!
:create_environment
can!
:create_deployment
end
def
master_access!
can!
:delete_protected_branch
can!
:update_project_snippet
can!
:update_environment
can!
:update_deployment
can!
:admin_milestone
can!
:admin_project_snippet
can!
:admin_project_member
can!
:admin_note
can!
:admin_wiki
can!
:admin_project
can!
:admin_commit_status
can!
:admin_build
can!
:admin_container_image
can!
:admin_pipeline
can!
:admin_pipeline_schedule
can!
:admin_environment
can!
:admin_deployment
can!
:admin_pages
can!
:read_pages
can!
:update_pages
end
def
public_access!
can!
:download_code
can!
:fork_project
can!
:read_commit_status
can!
:read_pipeline
can!
:read_pipeline_schedule
can!
:read_container_image
can!
:build_download_code
can!
:build_read_container_image
can!
:read_merge_request
end
def
owner_access!
guest_access!
reporter_access!
developer_access!
master_access!
can!
:change_namespace
can!
:change_visibility_level
can!
:rename_project
can!
:remove_project
can!
:archive_project
can!
:remove_fork_project
can!
:destroy_merge_request
can!
:destroy_issue
can!
:remove_pages
rule
{
guest
}.
enable
:guest_access
rule
{
reporter
}.
enable
:reporter_access
rule
{
developer
}.
enable
:developer_access
rule
{
master
}.
enable
:master_access
rule
{
owner
|
admin
}.
policy
do
enable
:guest_access
enable
:reporter_access
enable
:developer_access
enable
:master_access
enable
:change_namespace
enable
:change_visibility_level
enable
:rename_project
enable
:remove_project
enable
:archive_project
enable
:remove_fork_project
enable
:destroy_merge_request
enable
:destroy_issue
enable
:remove_pages
end
def
team_member_owner_access!
team_member_reporter_access!
rule
{
owner
|
reporter
}.
policy
do
enable
:build_download_code
enable
:build_read_container_image
end
# Push abilities on the users team role
def
team_access!
(
user
)
access
=
project
.
team
.
max_member_access
(
user
.
id
)
return
if
access
<
Gitlab
::
Access
::
GUEST
guest_access!
rule
{
can?
(
:guest_access
)
}.
policy
do
enable
:read_project
enable
:read_board
enable
:read_list
enable
:read_wiki
enable
:read_issue
enable
:read_label
enable
:read_milestone
enable
:read_project_snippet
enable
:read_project_member
enable
:read_note
enable
:create_project
enable
:create_issue
enable
:create_note
enable
:upload_file
enable
:read_cycle_analytics
enable
:read_project_snippet
end
return
if
access
<
Gitlab
::
Access
::
REPORTER
reporter_access!
team_member_reporter_access!
rule
{
can?
(
:reporter_access
)
}.
policy
do
enable
:download_code
enable
:download_wiki_code
enable
:fork_project
enable
:create_project_snippet
enable
:update_issue
enable
:admin_issue
enable
:admin_label
enable
:admin_list
enable
:read_commit_status
enable
:read_build
enable
:read_container_image
enable
:read_pipeline
enable
:read_pipeline_schedule
enable
:read_environment
enable
:read_deployment
enable
:read_merge_request
end
return
if
access
<
Gitlab
::
Access
::
DEVELOPER
developer_access!
rule
{
(
~
anonymous
&
public_project
)
|
internal_access
}.
policy
do
enable
:public_user_access
end
return
if
access
<
Gitlab
::
Access
::
MASTER
master_access!
rule
{
can?
(
:public_user_access
)
}.
policy
do
enable
:guest_access
enable
:request_access
end
def
archived_access!
cannot!
:create_merge_request
cannot!
:push_code
cannot!
:delete_protected_branch
cannot!
:update_merge_request
cannot!
:admin_merge_request
rule
{
owner
|
admin
|
guest
|
group_member
}.
prevent
:request_access
rule
{
~
request_access_enabled
}.
prevent
:request_access
rule
{
can?
(
:developer_access
)
}.
policy
do
enable
:admin_merge_request
enable
:update_merge_request
enable
:create_commit_status
enable
:update_commit_status
enable
:create_build
enable
:update_build
enable
:create_pipeline
enable
:update_pipeline
enable
:create_pipeline_schedule
enable
:update_pipeline_schedule
enable
:create_merge_request
enable
:create_wiki
enable
:push_code
enable
:resolve_note
enable
:create_container_image
enable
:update_container_image
enable
:create_environment
enable
:create_deployment
end
def
disabled_features!
repository_enabled
=
project
.
feature_available?
(
:repository
,
user
)
rule
{
can?
(
:master_access
)
}.
policy
do
enable
:delete_protected_branch
enable
:update_project_snippet
enable
:update_environment
enable
:update_deployment
enable
:admin_milestone
enable
:admin_project_snippet
enable
:admin_project_member
enable
:admin_note
enable
:admin_wiki
enable
:admin_project
enable
:admin_commit_status
enable
:admin_build
enable
:admin_container_image
enable
:admin_pipeline
enable
:admin_pipeline_schedule
enable
:admin_environment
enable
:admin_deployment
enable
:admin_pages
enable
:read_pages
enable
:update_pages
end
block_issues_abilities
rule
{
can?
(
:public_user_access
)
}.
policy
do
enable
:public_access
unless
project
.
feature_available?
(
:merge_requests
,
user
)
&&
repository_enabled
cannot!
(
*
named_abilities
(
:merge_request
))
enable
:fork_project
enable
:build_download_code
enable
:build_read_container_image
end
unless
project
.
feature_available?
(
:issues
,
user
)
||
project
.
feature_available?
(
:merge_requests
,
user
)
cannot!
(
*
named_abilities
(
:label
))
cannot!
(
*
named_abilities
(
:milestone
))
rule
{
archived
}.
policy
do
prevent
:create_merge_request
prevent
:push_code
prevent
:delete_protected_branch
prevent
:update_merge_request
prevent
:admin_merge_request
end
unless
project
.
feature_available?
(
:snippets
,
user
)
cannot!
(
*
named_abilities
(
:project_snippe
t
))
rule
{
merge_requests_disabled
|
repository_disabled
}.
policy
do
prevent
(
*
create_read_update_admin
(
:merge_reques
t
))
end
unless
project
.
feature_available?
(
:wiki
,
user
)
||
project
.
has_external_wiki?
cannot!
(
*
named_abilities
(
:wiki
))
cannot!
(
:download_wiki_code
)
rule
{
issues_disabled
&
merge_requests_disabled
}.
policy
do
prevent
(
*
create_read_update_admin
(
:label
))
prevent
(
*
create_read_update_admin
(
:milestone
)
)
end
unless
project
.
feature_available?
(
:builds
,
user
)
&&
repository_enabled
cannot!
(
*
named_abilities
(
:build
))
cannot!
(
*
named_abilities
(
:pipeline
)
-
[
:read_pipeline
])
cannot!
(
*
named_abilities
(
:pipeline_schedule
))
cannot!
(
*
named_abilities
(
:environment
))
cannot!
(
*
named_abilities
(
:deployment
))
rule
{
snippets_disabled
}.
policy
do
prevent
(
*
create_read_update_admin
(
:project_snippet
))
end
unless
repository_enabled
cannot!
:push_code
cannot!
:delete_protected_branch
cannot!
:download_code
cannot!
:fork_project
cannot!
:read_commit_status
rule
{
wiki_disabled
&
~
has_external_wiki
}.
policy
do
prevent
(
*
create_read_update_admin
(
:wiki
))
prevent
(
:download_wiki_code
)
end
unless
project
.
container_registry_enabled
cannot!
(
*
named_abilities
(
:container_image
))
rule
{
builds_disabled
|
repository_disabled
}.
policy
do
prevent
(
*
create_read_update_admin
(
:build
))
prevent
(
*
(
create_read_update_admin
(
:pipeline
)
-
[
:read_pipeline
]))
prevent
(
*
create_read_update_admin
(
:pipeline_schedule
))
prevent
(
*
create_read_update_admin
(
:environment
))
prevent
(
*
create_read_update_admin
(
:deployment
))
end
rule
{
repository_disabled
}.
policy
do
prevent
:push_code
prevent
:push_code_to_protected_branches
prevent
:download_code
prevent
:fork_project
prevent
:read_commit_status
end
def
anonymous_rules
return
unless
project
.
public?
rule
{
container_registry_disabled
}.
policy
do
prevent
(
*
create_read_update_admin
(
:container_image
))
end
base_readonly_access!
rule
{
anonymous
&
~
public_project
}.
prevent_all
rule
{
public_project
}.
enable
(
:public_access
)
rule
{
can?
(
:public_access
)
}.
policy
do
enable
:read_project
enable
:read_board
enable
:read_list
enable
:read_wiki
enable
:read_label
enable
:read_milestone
enable
:read_project_snippet
enable
:read_project_member
enable
:read_merge_request
enable
:read_note
enable
:read_pipeline
enable
:read_pipeline_schedule
enable
:read_commit_status
enable
:read_container_image
enable
:download_code
enable
:download_wiki_code
enable
:read_cycle_analytics
# Allow to read builds by anonymous user if guests are allowed
can!
:read_build
if
project
.
public_builds?
# NOTE: may be overridden by IssuePolicy
enable
:read_issue
end
disabled_features!
rule
{
public_builds
}.
policy
do
enable
:read_build
end
def
block_issues_abilities
unless
project
.
feature_available?
(
:issues
,
user
)
cannot!
:read_issue
if
project
.
default_issues_tracker?
cannot!
:create_issue
cannot!
:update_issue
cannot!
:admin_issue
rule
{
public_builds
&
can?
(
:guest_access
)
}.
policy
do
enable
:read_pipeline
enable
:read_pipeline_schedule
end
rule
{
issues_disabled
}.
policy
do
prevent
:create_issue
prevent
:update_issue
prevent
:admin_issue
end
def
named_abilities
(
name
)
[
:"read_
#{
name
}
"
,
:"create_
#{
name
}
"
,
:"update_
#{
name
}
"
,
:"admin_
#{
name
}
"
]
rule
{
issues_disabled
&
default_issues_tracker
}.
policy
do
prevent
:read_issue
end
private
def
project_group_member?
(
user
)
def
is_team_member?
return
false
if
@user
.
nil?
greedy_load_subject
=
false
# when scoping by subject, we want to be greedy
# and load *all* the members with one query.
greedy_load_subject
||=
DeclarativePolicy
.
preferred_scope
==
:subject
# in this case we're likely to have loaded #members already
# anyways, and #member? would fail with an error
greedy_load_subject
||=
!
@user
.
persisted?
if
greedy_load_subject
project
.
team
.
members
.
include?
(
user
)
else
# otherwise we just make a specific query for
# this particular user.
team_access_level
>=
Gitlab
::
Access
::
GUEST
end
end
def
project_group_member?
return
false
if
@user
.
nil?
project
.
group
&&
(
project
.
group
.
members_with_parents
.
exists?
(
user_id:
user
.
id
)
||
project
.
group
.
requesters
.
exists?
(
user_id:
user
.
id
)
project
.
group
.
members_with_parents
.
exists?
(
user_id:
@
user
.
id
)
||
project
.
group
.
requesters
.
exists?
(
user_id:
@
user
.
id
)
)
end
def
access_requestable?
project
.
request_access_enabled
&&
!
owner?
&&
!
user
.
admin?
&&
!
project
.
team
.
member?
(
user
)
&&
!
project_group_member?
(
user
)
end
# A base set of abilities for read-only users, which
# is then augmented as necessary for anonymous and other
# read-only users.
def
base_readonly_access!
can!
:read_project
can!
:read_board
can!
:read_list
can!
:read_wiki
can!
:read_label
can!
:read_milestone
can!
:read_project_snippet
can!
:read_project_member
can!
:read_merge_request
can!
:read_note
can!
:read_pipeline
can!
:read_pipeline_schedule
can!
:read_commit_status
can!
:read_container_image
can!
:download_code
can!
:download_wiki_code
can!
:read_cycle_analytics
def
team_access_level
return
-
1
if
@user
.
nil?
# NOTE: may be overridden by IssuePolicy
can!
:read_issue
# NOTE: max_member_access has its own cache
project
.
team
.
max_member_access
(
@user
.
id
)
end
def
feature_available?
(
feature
)
case
project
.
project_feature
.
access_level
(
feature
)
when
ProjectFeature
::
DISABLED
false
when
ProjectFeature
::
PRIVATE
guest?
||
admin?
else
true
end
end
def
project
@subject
end
end
app/policies/project_snippet_policy.rb
View file @
37c40143
class
ProjectSnippetPolicy
<
BasePolicy
def
rules
delegate
:project
desc
"Snippet is public"
condition
(
:public_snippet
,
scope: :subject
)
{
@subject
.
public?
}
condition
(
:private_snippet
,
scope: :subject
)
{
@subject
.
private?
}
condition
(
:public_project
,
scope: :subject
)
{
@subject
.
project
.
public?
}
condition
(
:is_author
)
{
@user
&&
@subject
.
author
==
@user
}
condition
(
:internal
,
scope: :subject
)
{
@subject
.
internal?
}
# We have to check both project feature visibility and a snippet visibility and take the stricter one
# This will be simplified - check https://gitlab.com/gitlab-org/gitlab-ce/issues/27573
return
unless
@subject
.
project
.
feature_available?
(
:snippets
,
@user
)
return
unless
Ability
.
allowed?
(
@user
,
:read_project
,
@subject
.
project
)
rule
{
~
can?
(
:read_project
)
}.
policy
do
prevent
:read_project_snippet
prevent
:update_project_snippet
prevent
:admin_project_snippet
end
can!
:read_project_snippet
if
@subject
.
public?
return
unless
@user
# we have to use this complicated prevent because the delegated project policy
# is overly greedy in allowing :read_project_snippet, since it doesn't have any
# information about the snippet. However, :read_project_snippet on the *project*
# is used to hide/show various snippet-related controls, so we can't just move
# all of the handling here.
rule
do
all?
(
private_snippet
|
(
internal
&
external_user
),
~
project
.
guest
,
~
admin
,
~
is_author
)
end
.
prevent
:read_project_snippet
if
@user
&&
(
@subject
.
author
==
@user
||
@user
.
admin?
)
can!
:read_project_snippet
can!
:update_project_snippet
can!
:admin_project_snippet
rule
{
internal
&
~
is_author
&
~
admin
}.
policy
do
prevent
:update_project_snippet
prevent
:admin_project_snippet
end
if
@subject
.
internal?
&&
!
@user
.
external?
can!
:read_project_snippet
end
rule
{
public_snippet
}.
enable
:read_project_snippet
if
@subject
.
project
.
team
.
member?
(
@user
)
can!
:read_project_snippet
end
rule
{
is_author
|
admin
}.
policy
do
enable
:read_project_snippet
enable
:update_project_snippet
enable
:admin_project_snippet
end
end
app/policies/user_policy.rb
View file @
37c40143
class
UserPolicy
<
BasePolicy
include
Gitlab
::
CurrentSettings
def
rules
can!
:read_user
if
@user
||
!
restricted_public_level?
if
@user
if
@user
.
admin?
||
@subject
==
@user
can!
:destroy_user
desc
"The application is restricted from public visibility"
condition
(
:restricted_public_level
,
scope: :global
)
do
current_application_settings
.
restricted_visibility_levels
.
include?
(
Gitlab
::
VisibilityLevel
::
PUBLIC
)
end
cannot!
:destroy_user
if
@subject
.
ghost?
end
end
desc
"The current user is the user in question"
condition
(
:user_is_self
,
score:
0
)
{
@subject
==
@user
}
def
restricted_public_level?
current_application_settings
.
restricted_visibility_levels
.
include?
(
Gitlab
::
VisibilityLevel
::
PUBLIC
)
end
desc
"This is the ghost user"
condition
(
:subject_ghost
,
scope: :subject
,
score:
0
)
{
@subject
.
ghost?
}
rule
{
~
restricted_public_level
}.
enable
:read_user
rule
{
~
anonymous
}.
enable
:read_user
rule
{
user_is_self
|
admin
}.
enable
:destroy_user
rule
{
subject_ghost
}.
prevent
:destroy_user
end
lib/gitlab/allowable.rb
View file @
37c40143
module
Gitlab
module
Allowable
def
can?
(
user
,
action
,
subject
=
:global
)
Ability
.
allowed?
(
user
,
action
,
subject
)
def
can?
(
*
args
)
Ability
.
allowed?
(
*
args
)
end
end
end
spec/policies/project_policy_spec.rb
View file @
37c40143
...
...
@@ -155,8 +155,8 @@ describe ProjectPolicy, models: true do
end
it
do
is_expected
.
not_to
include
(
:read_build
)
is_expected
.
to
include
(
:read_pipeline
)
expect_disallowed
(
:read_build
)
expect_allowed
(
:read_pipeline
)
end
end
end
...
...
spec/policies/project_snippet_policy_spec.rb
View file @
37c40143
...
...
@@ -119,7 +119,7 @@ describe ProjectSnippetPolicy, models: true do
context
'snippet author'
do
let
(
:snippet
)
{
create
(
:project_snippet
,
:private
,
author:
regular_user
,
project:
project
)
}
subject
{
described_class
(
regular_user
,
snippet
)
}
subject
{
described_class
.
new
(
regular_user
,
snippet
)
}
it
do
expect_allowed
(
:read_project_snippet
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment