Permission Concept
Fine-grained permission for SCM-Manager v2
This documents describes a concept for a fine-grained permission managing via the SCMMv2 UI.
Requirements
- Provide at least the features of SCMMv1 including the scm-groupmanager and scm-userrepo plugins.
- In addition, the permissions on repositories should be more fine-grained, for example a user that does not have the permission to check out a repository, but read its meta data.
- An ideal solution would be generic. That is, not implementing explicit features such as the groupmanager or userrepo.
SCMMv2 already evaluates fine-grained permissions, so why not allow our users to assign them?
Technical Foundations
Status Quo SCMv1
SCMMv1 's permissions are only related to Repositories:
- Users can either have the Permission READ, WRITER or OWNER
- globally (for all repositories) or
- per Repository.
All other permissions are handled by distinguishing administrators from ordinary users. Admins can do everything, users nothing except for their repository permissions.
Some more permission-related features are added by plugins:
- scm-groupmanager-plugin: Allows users to administer groups
- scm-userrepo-plugin: Allows users to create repositories
SCMMv2 Permission fundamentals
SCMMv2 introduces much more fine-grained permission checks under the hood. In the code permissions for all kinds of operations are designed as follows:
- convention:
subject:verb:item
, - for example:
configuration:read:git
- or
repository:write:42
, - where item is the technical ID of the subject.
In addition, there are permissions that do not relate to an item, which are called "global permissions", for example
configuration:list
.
The challenge solved by this document is to provide a concept that allows SCMMv2 users to manage these permissions. That is, to assign those permissions to users and groups via the UI or REST API.
SCMMv2 implementation details
This is not a core part of the concept but might be interesting when implementing it.
SCMM uses the Apache Shiro security framework that allows for assigning permission strings (such as subject:verb:item
)
to users. These can also contain wildcards (*
). For example
- the permission
*
realizes the administrator, user:read:*
means reading is allowed on all users,user:*:arthur
means all operations are allowed on a specific user.
Then the application can check if a user has a permission. For example:
- Does user admin have the permission for reading repository 42?
- Admin has permission
*
- So: does he have permission
user:read:*
? Yes!
In order to get a little more type safe, SCM-Manager uses the Shiro-static-permissions (ssp) library that scans the classpath for annotations such as the following
@StaticPermissions(
value = "user",
globalPermissions = {"create", "list", "autocomplete"},
permissions = {"read", "modify", "delete", "changePassword"})
and creates *Permissions
classes that contain methods for checking each permission, for example like so
UserPermissions.read().check(id);
When a user logs in, all different kinds of permissions (*
if admin, permissions for repositories,
from groups, some additional technical permissions such as autocomplete
, etc.) are collected and added to the Shiro
subject in the DefaultAuthorizationCollector
class.
SCMMv2 Core permissions
Here are some more examples of permissions existing in SCMMv2 core, at the time of writing.
Look for @StaticPermissions
and note that there the annotation also declares defaults for permissions
and globalPermissions
.
- Configuration
- Global: list
- Permissions: read,write
- Items are global (core), git, hg, config (core plugins) and will be extended by further plugins.
- Plugin
- Global: read, manage
- Group
- Global: create, list, autocomplete
- Permission: read, modify, delete
- User
- Global: create, list, autocomplete
- Permissions: read, modify, delete, changePassword
- Items are the user name of dynamically added users
- Repository
- Global: create
- Permissions: read, modify, delete, healthCheck, pull, push, permissionRead, permissionWrite
- Items are the technical ID of dynamically added repositories
Repository and global permissions
In order to fulfill the requirements, this concept describes
- how to extend the existing repository permissions to be more fine-grained
- a new dialog to assign global permissions on user or group level
UI / UX
Global permissions
The global permission component can be reached from either user and groups components navigations. The following mockup shows this in the user component:
The layout of the permission component UI could look like this:
The UI
- queries all available global permissions from the REST API (shiro strings),
- gets the display name and descriptions using the shiro strings as keys (see i18n),
- displays descriptions as tooltips,
- and queries all user/group permissions to populate the check boxes.
Repository permissions
The repository permission are already implemented and can be reached via Repositories | Permissions. Right now, it
allows for assigning the roles READ, WRITE, OWNER as in SCMMv1 (see above). Internally they are mapped to shiro
permissions (see PermissionType
).
The UI is extended like so:
Existing repository dialog
- queries all available repository permissions (shiro strings) and roles from the REST API,
- queries all user/group permissions of the repository (shiro strings) and aggregates them to roles to populate the drop downs.
- Note that the permissions are always stored as shiro strings not roles.
- A new
Advanced
button per user or group entry opens a modal dialog
New modal dialog
- The modal dialog shows all available repository permissions (shiro strings)
- via the shiro string the display name and descriptions are found (see i18n),
- displays descriptions as tooltips,
- the individual user/group permission of the repo are used to populate the check boxes
REST API
Note that the examples here are not specified in HAL/HATEOAS for brevity.
Global permissions
Assigning global permissions must be implemented for either user and groups! Both use the same available permissions.
The following shows user as an example.
Available global permissions
- URL:
/globalPermissions
- HTTP Method: GET
- Payload example:
{
"permissions": [
"configuration:read:git",
"configuration:write:git",
"configuration:read",
"configuration:write",
"plugin:read",
"plugin:manage",
"group:read",
"user:read",
"repository:read"
]
}
Assigned global permissions
- URL:
/users/{id}/permissions/
- HTTP Method: GET/PUT
- Payload example:
{
"permissions": [
"configuration:read:git",
"configuration:write:git",
"configuration:read",
"configuration:write",
"plugin:read",
"plugin:manage",
"group:read",
"user:read",
"repository:read"
]
}
Repository permissions
Available repository permissions
- URL:
/repositoryPermissions
(similar to/repositoryTypes
) - HTTP Method: GET
- Payload example:
{
"roles": [
{
"name": "Reader",
"verbs": [ "read", "pull" ]
},
{
"name": "Owner",
"verbs": [ "*" ]
}
],
"verbs": [ "read", "pull", "push", "..", "*" ]
}
Assigned repository permissions
Already implemented in PermissionRootResource
. Needs to be adpated from roles (WRITE
) to shiro permissions
(repository:read:42
).
- URL:
/repositories/{namespace}/{name}/permissions
- HTTP Method: GET
- Payload example:
{
"permissions": [
{
"name": "trillian",
"permissions": [ "read", "pull" ],
"groupPermission": false
},
{
"name": "owners",
"permissions": [ "*" ],
"groupPermission": true
}
]
}
This example shows the user trillian
having the READER
role and the group owners
having the OWNER
role.
Note that
- the
*
permission also implies all new permissions (e.g. defined by plugins or added in future versions)
and therefore has different semantics as listing all currently available permissions.
- the permissions passed to the REST API correspond to the verbs of the repository permission. It is stored as
repositories:<verb stored via REST API>:<ID of the repository identified by namespace and name>
. - GET also returns links to individual URIs (e.g.
/repositories/{namespace}/{name}/permissions/trillian
) that can be used for updating permissions via PUT requests. - On PUT, the REST API needs to validate that each entry in
permissions
does not contain:
!
Otherwise we might allow for "permission injection", allowing to set permissions on other or all repositories.
Java API
The biggest technical challenges for this concept are the questions:
- Where do we get the available permission from?
- How do we assign these permissions?
- How are these permissions evaluated?
Where each questions needs to be answered for
- global and
- repository
permissions.
Global permissions
In order to implement this for global permissions an existing mechanism of SCM-Manager can be used:
The SecuritySystem
, implemented by the DefaultSecuritySystem
.
List available permissions
The DefaultSecuritySystem
reads all permissions.xml
files from classpath, which also works for plugins (see
Proof Of Concept).
These can be queried using securitySystem.getAvailablePermissions()
.
For SCMMv2 we could extend this mechanism by
- simplifying the
permissions.xml
to contain only<permission><value>
, because<display-name>
and<description>
need to be internationalized, see i18n. - extend the
ssp library
to generatepermissions.xml
files from@StaticPermissions
annotations. The annotations should be extended to support a list of permissions that are not written topermissions.xml
(e.g.user:autocomplete
)
Assign permissions
The SecuritySystem
also provides means to assign, store and load permissions to users or groups using Shiro string
permissions like so:
AssignedPermission groupPermission = new AssignedPermission("configurers", true,"configuration:*");
securitySystem.addPermission(groupPermission);
AssignedPermission userPermission = new AssignedPermission("arthur", "group:*");
securitySystem.addPermission(userPermission);
log.info("All permissions: {}", securitySystem.getAllPermissions()); // Contains the permissions just added
See also the Proof Of Concept.
Evaluating permissions
The evaluation of permissions assigned via the SecuritySystem
is already implemented in the
DefaultAuthorizationCollector
.
Dynamically add new items to available permissions
Adding items (e.g. new users) dynamically during runtime is not implemented by the SecuritySystem
and in order to
keep this simple we do not plan to support it, yet. See considered alternatives.
Repository Permissions
For repository permissions we need to implement a new mechanism for discovering available permissions .
Assigning is already implemented (on role level, e.g. WRITE
), which needs to be adapted to shiro permission level
(e.g. repository:read:42
).
List available permissions
We need to implement a new mechanism for discovering available permssions. Let's call it RepositoryPermissionResolver
.
It can work similar to the DefaultSecuritySystem
(see global permissions). It reads all repository-permissions.xml
files from classpath, which makes it extensible for plugins.
This obsoletes the PermissionType
enum.
<permissions>
<permission>read</permission>
<permission>write</permission>
<role>
<name>WRITER</name>
<permission>read</permission>
<permission>pull</permission>
<permission>push</permission>
</role>
</permissions>
Assign permissions
This is already implemented in RepositoryManager
s. Needs to be adapted from roles (WRITE
) to shiro permissions
(repository:read:42
).
Evaluating permissions
Same here: Already implemented in DefaultAuthorizationCollector
. Needs to be adapted from roles to shiro permissions.
The Admin flag/role
In addition to the fine-grained permission management described in this concept, we could just keep the admin flag
(or role) that add the permission *
to a user.
It's already implemented and a well-known concept from SCMMv1.
Permission for managing permissions
Once permissions can be managed, an additional permission is necessary that answers the question: Who is allowed to manage permissions?
This permission has to be checked before permissions are read or written. It should be implemented in the
DefaultSecuritySystem
, instead of the assertIsAdmin()
method.
For now, it is sufficiently to create a Permission
permission (using @StaticPermissions
) with global verbs read
and write. That is,
permission:read
permission:write
i18n
Internationalization can be handled using the following conventions:
- All permission i18n are described in
plugins.json
(also for core), see i18n for Plugins - That way the UI for users and groups can find all the translation in the same file
- Convention for i18n keys:
permissions.<shiro-String>
, containingdisplayName
anddescription
each.
Example:
{
"permissions": {
"repository:read": {
"displayName": "All Repositories (read)",
"description": "Read access to all repositories"
}
}
}
Group Manager Plugin
One shortcoming of limiting the global permission concept to verbs (not items) is that the functionality of the
scm-groupmanager-plugin
is not included.
That is, we need to migrate our implement it for SCMMv2. Most likely it's less effort to implement a new plugin,
because we need a new SCMMv2 UI and can use the SecuritySystemto set the
group:*:
The following needs to be implemented:
- UI (core): Provide an extension point in the groups UI Navigation
- UI: Use the extension point to add link to new dialog for adding group managers
- UI: New Dialog similar to the repository permission dialog that allows for adding user as group managers
- REST API for CRUD of group admins
- Extend the available group plugins by a permission
group:manage
permission (permission.xml
) including i18n inplugins.json
. - Check if the user has this permission! Unfortunately this cannot be done with
GroupPermissions
(generated via ssp), but viaSecurityUtils.getSubject().checkPermission(permission);
- For storing the permission, make use of the
SecuritySystem
to set thegroup:*:<id>
permissions.
Considered alternatives
This chapter documents some other approaches that were considered but rejected and the reasons for rejecting them.
- Manage subject, verbs and items.
This is the most flexible approach from a user perspective and SCMMv2 provides a mechanism for evaluating permissions on the fine-grainedsubject:verb:item
level, why not allow our users to make use of it?
The first approach of this concept followed this approach, but it was considered to cause to much effort during implementation. So we decided to keep it more simple, and skip theitem
part. This suffices most use cases. - A global permissions page (not per user, similar to the Jenkins Matrix Authorization Strategy Plugin).
Leads to a crowded UI, when there are a lot of subjects and verbs (see Jenkins). When adding items would cause an Microsoft Excel-like UX, which is not desirable. - Manage only subjects and verbs, not items.
Would simplify the UI and reduce the effort but the also the features and would not fulfill our requirements in terms of userrepo or groupmanager plugins. Those could still be implemented separately. Still, as SCMMv2 provides a mechanism for evaluate permissions on the fine-grainedsubject:verb:item
level, why not allow our users to make use of it?
Implemented Permissions
This chapter documents the permissions implemented in SCM-Manager core and a lot of plugins that can be assigned to users and groups
using the GUI/API.
Be aware, that this is only a snapshot and may not track each change in a plugin or the core. To get the concrete list of permissions
for a concrete version of the core or a plugin, take a look at the corresponding permissions.xml
, repository-permissions.xml
and
plugins.json
files.
Global Permissions
plugin | permission | description |
---|---|---|
core | repository:read,pull:* |
read all repositories |
core | repository:read,pull,push:* |
write all repositories |
core | repository:* |
own all repositories |
core | repository:create |
Create repositories |
core | user:* |
administer users |
core | group:* |
administer groups |
core | configuration:list |
basic permission for all configuration permissions; needed to see config menu item |
core | configuration:read,write:global |
administer core configuration |
core | configuration:read,write:* |
administer overall configuration (including all plugins) |
git | configuration:read,write:git |
administer global git settings |
git | repository:git:* |
administer repository specific git settings |
hg | configuration:read,write:hg |
administer global mercurial settings |
hg | repository:hg:* |
administer repository specific mercurial settings |
svn | configuration:read,write:svn |
administer global subversion settings |
svn | repository:svn:* |
administer repository specific subversion settings |
authormapping | repository:authormapping:* |
read and modify author mappings for all repositories |
auth-ldap | configuration:read,write:ldap |
administer ldap server |
cas | configuration:read,write:cas |
administer cas server |
statistic | repository:computeStatistics:* |
recompute statistics for all repositories |
jenkins | configuration:read,write:jenkins |
administer global jenkins server |
jenkins | repository:jenkins:* |
administer repository specific jenkins servers |
jira | configuration:read,write:jira |
administer global jira server |
jira | repository:jira:* |
administer repository specific jira servers |
pathwp | repository:pathwp:* |
administer write protected paths for all repositories |
branchwp | repository:branchwp:* |
administer write protected paths for all repositories |
tagprotection | configuration:read,write:tagprotection |
administer globally protected tags |
script | script:read,modify,execute |
read, modify and execute scripts |
webhook | configuration:read,write:webhook |
administer web hooks |
webhook | repository:webhook:* |
administer web hooks for all repositories |
redmine | configuration:read,write:redmine |
administer global redmine server |
redmine | repository:redmine:* |
administer repository specific redmine servers |
notify | repository:notify:* |
administer notify settings for all repositories |
support | support:information |
read support relevant information |
support | support:information,logging |
read support relevant information and enable trace log |
configuration:read,write:mail |
administer mail server | |
groupmanager | group:manage:* |
assign group managers |
ssh | user:readAuthorizedKeys:* |
read authorization keys for all users |
ssh | user:readAuthorizedKeys,writeAuthorizedKeys:* |
configure authorization keys for all users |
Repository Permissions
plugin | verb | description |
---|---|---|
core | read |
read metadata of repository |
core | modify |
modify metadata of repository |
core | delete |
delete repository |
core | pull |
pull/checkout repository |
core | push |
push/commit to repository |
core | permissionRead |
read permissions of repository |
core | permissionWrite |
modify permissions for repository |
core | * |
change everything for repository ("owner") |
git | git |
administer git settings for repository |
hg | hg |
administer mercurial settings for repository |
svn | svn |
administer subversion settings for repository |
review | createPullRequest |
create pull requests |
review | readPullRequest |
read pull requests |
review | commentPullRequest |
write comments in pull requests and delete/edit own comments |
review | modifyPullRequest |
edit/delete pull requests and comments |
review | mergePullRequest |
merge/reject pull requests |
authormapping | authormapping |
modify author mappings |
jenkins | jenkins |
administer jenkins server for repository |
jira | jira |
administer jira server for repository |
pathwp | pathwp |
administer write protected paths for repository |
redmine | redmine |
administer redmine server for repository |
notify | notify |
administer notify settings for repository |
branchwp | branchwp |
administer write protected paths for repository |
webhook | webhook |
administer web hools for repository |
Repository Roles
The verbs for roles are merged internally, so that a resulting role will have all verbs specified by any plugin.
Mind that a OWNER
has overall permissions, including all possible permissions for all plugins.
plugin | role | verbs |
---|---|---|
core | READ |
read , pull |
core | WRITE |
read , pull , push |
core | OWNER |
* |
review | READ |
readPullRequest |
review | WRITE |
createPullRequest , readPullRequest , commentPullRequest , mergePullRequest |
statistic | READ |
readStatistics |