Implement api for get current user permissions

Signed-off-by: He Weiwei <hweiwei@vmware.com>
This commit is contained in:
He Weiwei 2019-01-28 18:06:52 +08:00
parent 6888c3247c
commit 8b5e68073d
16 changed files with 503 additions and 30 deletions

View File

@ -717,6 +717,37 @@ paths:
$ref: '#/definitions/User' $ref: '#/definitions/User'
'401': '401':
description: User need to log in first. description: User need to log in first.
/users/current/permissions:
get:
summary: Get current user permissions.
description: |
This endpoint is to get the current user permissions.
parameters:
- name: scope
in: query
type: string
required: false
description: Get permissions of the scope
- name: relative
in: query
type: boolean
required: false
description: |
If true, the resources in the response are relative to the scope,
eg for resource '/project/1/repository' if relative is 'true' then the resource in response will be 'repository'.
tags:
- Products
responses:
'200':
description: Get current user permission successfully.
schema:
type: array
items:
$ref: '#/definitions/Permission'
'401':
description: User need to log in first.
'500':
description: Internal errors.
'/users/{user_id}': '/users/{user_id}':
get: get:
summary: Get a user's profile. summary: Get a user's profile.
@ -4550,3 +4581,13 @@ definitions:
error: error:
type: string type: string
description: (optional) The error message when the status is "unhealthy" description: (optional) The error message when the status is "unhealthy"
Permission:
type: object
description: The permission
properties:
resource:
type: string
description: The permission resoruce
action:
type: string
description: The permission action

View File

@ -20,14 +20,42 @@ import (
// const action variables // const action variables
const ( const (
ActionAll = rbac.Action("*") ActionAll = rbac.Action("*") // action match any other actions
ActionPull = rbac.Action("pull")
ActionPush = rbac.Action("push") ActionPull = rbac.Action("pull") // pull repository tag
ActionPushPull = rbac.Action("push+pull") ActionPush = rbac.Action("push") // push repository tag
ActionPushPull = rbac.Action("push+pull") // compatible with security all perm of project
// create, read, update, delete, list actions compatible with restful api methods
ActionCreate = rbac.Action("create")
ActionRead = rbac.Action("read")
ActionUpdate = rbac.Action("update")
ActionDelete = rbac.Action("delete")
ActionList = rbac.Action("list")
// execute replication for the replication policy (replication rule)
ActionExecute = rbac.Action("execute")
// vulnerabilities scan for repository tag (aka, image tag)
ActionScan = rbac.Action("scan")
) )
// const resource variables // const resource variables
const ( const (
ResourceAll = rbac.Resource("*") ResourceAll = rbac.Resource("*") // resource match any other resources
ResourceImage = rbac.Resource("image") ResourceSelf = rbac.Resource("") // subresource for project self
ResourceMember = rbac.Resource("member")
ResourceLog = rbac.Resource("log")
ResourceReplication = rbac.Resource("replication")
ResourceLabel = rbac.Resource("label")
ResourceRepository = rbac.Resource("repository")
ResourceRepositoryTag = rbac.Resource("repository-tag")
ResourceRepositoryTagManifest = rbac.Resource("repository-tag-manifest")
ResourceRepositoryTagVulnerability = rbac.Resource("repository-tag-vulnerability")
ResourceRepositoryTagLabel = rbac.Resource("repository-tag-label")
ResourceHelmChart = rbac.Resource("helm-chart")
ResourceHelmChartVersion = rbac.Resource("helm-chart-version")
ResourceHelmChartVersionLabel = rbac.Resource("helm-chart-version-label")
ResourceConfiguration = rbac.Resource("configuration") // compatible for portal only
ResourceRobot = rbac.Resource("robot")
) )

View File

@ -21,12 +21,81 @@ import (
var ( var (
// subresource policies for public project // subresource policies for public project
publicProjectPolicies = []*rbac.Policy{ publicProjectPolicies = []*rbac.Policy{
{Resource: ResourceImage, Action: ActionPull}, {Resource: ResourceSelf, Action: ActionRead},
{Resource: ResourceRepository, Action: ActionList},
{Resource: ResourceRepository, Action: ActionPull},
{Resource: ResourceHelmChart, Action: ActionRead},
{Resource: ResourceHelmChart, Action: ActionList},
{Resource: ResourceHelmChartVersion, Action: ActionRead},
{Resource: ResourceHelmChartVersion, Action: ActionList},
} }
// subresource policies for system admin visitor // all policies for the projects
systemAdminProjectPolicies = []*rbac.Policy{ allPolicies = []*rbac.Policy{
{Resource: ResourceAll, Action: ActionAll}, {Resource: ResourceSelf, Action: ActionRead},
{Resource: ResourceSelf, Action: ActionUpdate},
{Resource: ResourceSelf, Action: ActionDelete},
{Resource: ResourceMember, Action: ActionCreate},
{Resource: ResourceMember, Action: ActionUpdate},
{Resource: ResourceMember, Action: ActionDelete},
{Resource: ResourceMember, Action: ActionList},
{Resource: ResourceLog, Action: ActionList},
{Resource: ResourceReplication, Action: ActionList},
{Resource: ResourceReplication, Action: ActionCreate},
{Resource: ResourceReplication, Action: ActionUpdate},
{Resource: ResourceReplication, Action: ActionDelete},
{Resource: ResourceReplication, Action: ActionExecute},
{Resource: ResourceLabel, Action: ActionCreate},
{Resource: ResourceLabel, Action: ActionUpdate},
{Resource: ResourceLabel, Action: ActionDelete},
{Resource: ResourceLabel, Action: ActionList},
{Resource: ResourceRepository, Action: ActionCreate},
{Resource: ResourceRepository, Action: ActionUpdate},
{Resource: ResourceRepository, Action: ActionDelete},
{Resource: ResourceRepository, Action: ActionList},
{Resource: ResourceRepository, Action: ActionPushPull}, // compatible with security all perm of project
{Resource: ResourceRepository, Action: ActionPush},
{Resource: ResourceRepository, Action: ActionPull},
{Resource: ResourceRepositoryTag, Action: ActionDelete},
{Resource: ResourceRepositoryTag, Action: ActionList},
{Resource: ResourceRepositoryTag, Action: ActionScan},
{Resource: ResourceRepositoryTagVulnerability, Action: ActionList},
{Resource: ResourceRepositoryTagManifest, Action: ActionRead},
{Resource: ResourceRepositoryTagLabel, Action: ActionCreate},
{Resource: ResourceRepositoryTagLabel, Action: ActionDelete},
{Resource: ResourceHelmChart, Action: ActionCreate},
{Resource: ResourceHelmChart, Action: ActionRead},
{Resource: ResourceHelmChart, Action: ActionDelete},
{Resource: ResourceHelmChart, Action: ActionList},
{Resource: ResourceHelmChartVersion, Action: ActionRead},
{Resource: ResourceHelmChartVersion, Action: ActionDelete},
{Resource: ResourceHelmChartVersion, Action: ActionList},
{Resource: ResourceHelmChartVersionLabel, Action: ActionCreate},
{Resource: ResourceHelmChartVersionLabel, Action: ActionDelete},
{Resource: ResourceConfiguration, Action: ActionRead},
{Resource: ResourceConfiguration, Action: ActionUpdate},
{Resource: ResourceRobot, Action: ActionCreate},
{Resource: ResourceRobot, Action: ActionRead},
{Resource: ResourceRobot, Action: ActionUpdate},
{Resource: ResourceRobot, Action: ActionDelete},
{Resource: ResourceRobot, Action: ActionList},
} }
) )
@ -44,10 +113,11 @@ func policiesForPublicProject(namespace rbac.Namespace) []*rbac.Policy {
return policies return policies
} }
func policiesForSystemAdmin(namespace rbac.Namespace) []*rbac.Policy { // GetAllPolicies returns all policies for namespace of the project
func GetAllPolicies(namespace rbac.Namespace) []*rbac.Policy {
policies := []*rbac.Policy{} policies := []*rbac.Policy{}
for _, policy := range systemAdminProjectPolicies { for _, policy := range allPolicies {
policies = append(policies, &rbac.Policy{ policies = append(policies, &rbac.Policy{
Resource: namespace.Resource(policy.Resource), Resource: namespace.Resource(policy.Resource),
Action: policy.Action, Action: policy.Action,

View File

@ -47,7 +47,7 @@ func (v *visitor) GetUserName() string {
// GetPolicies returns policies of the visitor // GetPolicies returns policies of the visitor
func (v *visitor) GetPolicies() []*rbac.Policy { func (v *visitor) GetPolicies() []*rbac.Policy {
if v.ctx.IsSysAdmin() { if v.ctx.IsSysAdmin() {
return policiesForSystemAdmin(v.namespace) return GetAllPolicies(v.namespace)
} }
if v.namespace.IsPublic() { if v.namespace.IsPublic() {
@ -59,7 +59,8 @@ func (v *visitor) GetPolicies() []*rbac.Policy {
// GetRoles returns roles of the visitor // GetRoles returns roles of the visitor
func (v *visitor) GetRoles() []rbac.Role { func (v *visitor) GetRoles() []rbac.Role {
if !v.ctx.IsAuthenticated() { // Ignore roles when visitor is anonymous or system admin
if !v.ctx.IsAuthenticated() || v.ctx.IsSysAdmin() {
return nil return nil
} }

View File

@ -22,18 +22,175 @@ import (
var ( var (
rolePoliciesMap = map[string][]*rbac.Policy{ rolePoliciesMap = map[string][]*rbac.Policy{
"projectAdmin": { "projectAdmin": {
{Resource: ResourceImage, Action: ActionPushPull}, // compatible with security all perm of project {Resource: ResourceSelf, Action: ActionRead},
{Resource: ResourceImage, Action: ActionPush}, {Resource: ResourceSelf, Action: ActionUpdate},
{Resource: ResourceImage, Action: ActionPull}, {Resource: ResourceSelf, Action: ActionDelete},
{Resource: ResourceMember, Action: ActionCreate},
{Resource: ResourceMember, Action: ActionUpdate},
{Resource: ResourceMember, Action: ActionDelete},
{Resource: ResourceMember, Action: ActionList},
{Resource: ResourceLog, Action: ActionList},
{Resource: ResourceReplication, Action: ActionRead},
{Resource: ResourceReplication, Action: ActionList},
{Resource: ResourceLabel, Action: ActionCreate},
{Resource: ResourceLabel, Action: ActionUpdate},
{Resource: ResourceLabel, Action: ActionDelete},
{Resource: ResourceLabel, Action: ActionList},
{Resource: ResourceRepository, Action: ActionCreate},
{Resource: ResourceRepository, Action: ActionUpdate},
{Resource: ResourceRepository, Action: ActionDelete},
{Resource: ResourceRepository, Action: ActionList},
{Resource: ResourceRepository, Action: ActionPushPull}, // compatible with security all perm of project
{Resource: ResourceRepository, Action: ActionPush},
{Resource: ResourceRepository, Action: ActionPull},
{Resource: ResourceRepositoryTag, Action: ActionDelete},
{Resource: ResourceRepositoryTag, Action: ActionList},
{Resource: ResourceRepositoryTag, Action: ActionScan},
{Resource: ResourceRepositoryTagVulnerability, Action: ActionList},
{Resource: ResourceRepositoryTagManifest, Action: ActionRead},
{Resource: ResourceRepositoryTagLabel, Action: ActionCreate},
{Resource: ResourceRepositoryTagLabel, Action: ActionDelete},
{Resource: ResourceHelmChart, Action: ActionCreate}, // upload helm chart
{Resource: ResourceHelmChart, Action: ActionRead}, // download helm chart
{Resource: ResourceHelmChart, Action: ActionDelete},
{Resource: ResourceHelmChart, Action: ActionList},
{Resource: ResourceHelmChartVersion, Action: ActionCreate}, // upload helm chart version
{Resource: ResourceHelmChartVersion, Action: ActionRead}, // read and download helm chart version
{Resource: ResourceHelmChartVersion, Action: ActionDelete},
{Resource: ResourceHelmChartVersion, Action: ActionList},
{Resource: ResourceHelmChartVersionLabel, Action: ActionCreate},
{Resource: ResourceHelmChartVersionLabel, Action: ActionDelete},
{Resource: ResourceConfiguration, Action: ActionRead},
{Resource: ResourceConfiguration, Action: ActionUpdate},
{Resource: ResourceRobot, Action: ActionCreate},
{Resource: ResourceRobot, Action: ActionRead},
{Resource: ResourceRobot, Action: ActionUpdate},
{Resource: ResourceRobot, Action: ActionDelete},
{Resource: ResourceRobot, Action: ActionList},
},
"master": {
{Resource: ResourceSelf, Action: ActionRead},
{Resource: ResourceMember, Action: ActionList},
{Resource: ResourceLog, Action: ActionList},
{Resource: ResourceReplication, Action: ActionRead},
{Resource: ResourceReplication, Action: ActionList},
{Resource: ResourceLabel, Action: ActionCreate},
{Resource: ResourceLabel, Action: ActionUpdate},
{Resource: ResourceLabel, Action: ActionDelete},
{Resource: ResourceLabel, Action: ActionList},
{Resource: ResourceRepository, Action: ActionCreate},
{Resource: ResourceRepository, Action: ActionUpdate},
{Resource: ResourceRepository, Action: ActionDelete},
{Resource: ResourceRepository, Action: ActionList},
{Resource: ResourceRepository, Action: ActionPush},
{Resource: ResourceRepository, Action: ActionPull},
{Resource: ResourceRepositoryTag, Action: ActionDelete},
{Resource: ResourceRepositoryTag, Action: ActionList},
{Resource: ResourceRepositoryTag, Action: ActionScan},
{Resource: ResourceRepositoryTagVulnerability, Action: ActionList},
{Resource: ResourceRepositoryTagManifest, Action: ActionRead},
{Resource: ResourceRepositoryTagLabel, Action: ActionCreate},
{Resource: ResourceRepositoryTagLabel, Action: ActionDelete},
{Resource: ResourceHelmChart, Action: ActionCreate},
{Resource: ResourceHelmChart, Action: ActionRead},
{Resource: ResourceHelmChart, Action: ActionDelete},
{Resource: ResourceHelmChart, Action: ActionList},
{Resource: ResourceHelmChartVersion, Action: ActionCreate},
{Resource: ResourceHelmChartVersion, Action: ActionRead},
{Resource: ResourceHelmChartVersion, Action: ActionDelete},
{Resource: ResourceHelmChartVersion, Action: ActionList},
{Resource: ResourceHelmChartVersionLabel, Action: ActionCreate},
{Resource: ResourceHelmChartVersionLabel, Action: ActionDelete},
{Resource: ResourceConfiguration, Action: ActionRead},
{Resource: ResourceConfiguration, Action: ActionUpdate},
}, },
"developer": { "developer": {
{Resource: ResourceImage, Action: ActionPush}, {Resource: ResourceSelf, Action: ActionRead},
{Resource: ResourceImage, Action: ActionPull},
{Resource: ResourceMember, Action: ActionList},
{Resource: ResourceLog, Action: ActionList},
{Resource: ResourceRepository, Action: ActionCreate},
{Resource: ResourceRepository, Action: ActionList},
{Resource: ResourceRepository, Action: ActionPush},
{Resource: ResourceRepository, Action: ActionPull},
{Resource: ResourceRepositoryTag, Action: ActionList},
{Resource: ResourceRepositoryTagVulnerability, Action: ActionList},
{Resource: ResourceRepositoryTagManifest, Action: ActionRead},
{Resource: ResourceRepositoryTagLabel, Action: ActionCreate},
{Resource: ResourceRepositoryTagLabel, Action: ActionDelete},
{Resource: ResourceHelmChart, Action: ActionCreate},
{Resource: ResourceHelmChart, Action: ActionRead},
{Resource: ResourceHelmChart, Action: ActionList},
{Resource: ResourceHelmChartVersion, Action: ActionCreate},
{Resource: ResourceHelmChartVersion, Action: ActionRead},
{Resource: ResourceHelmChartVersion, Action: ActionList},
{Resource: ResourceHelmChartVersionLabel, Action: ActionCreate},
{Resource: ResourceHelmChartVersionLabel, Action: ActionDelete},
{Resource: ResourceConfiguration, Action: ActionRead},
}, },
"guest": { "guest": {
{Resource: ResourceImage, Action: ActionPull}, {Resource: ResourceSelf, Action: ActionRead},
{Resource: ResourceMember, Action: ActionList},
{Resource: ResourceLog, Action: ActionList},
{Resource: ResourceRepository, Action: ActionList},
{Resource: ResourceRepository, Action: ActionPull},
{Resource: ResourceRepositoryTag, Action: ActionList},
{Resource: ResourceRepositoryTagVulnerability, Action: ActionList},
{Resource: ResourceRepositoryTagManifest, Action: ActionRead},
{Resource: ResourceHelmChart, Action: ActionRead},
{Resource: ResourceHelmChart, Action: ActionList},
{Resource: ResourceHelmChartVersion, Action: ActionRead},
{Resource: ResourceHelmChartVersion, Action: ActionList},
{Resource: ResourceConfiguration, Action: ActionRead},
}, },
} }
) )
@ -49,6 +206,8 @@ func (role *visitorRole) GetRoleName() string {
switch role.roleID { switch role.roleID {
case common.RoleProjectAdmin: case common.RoleProjectAdmin:
return "projectAdmin" return "projectAdmin"
case common.RoleMaster:
return "master"
case common.RoleDeveloper: case common.RoleDeveloper:
return "developer" return "developer"
case common.RoleGuest: case common.RoleGuest:

View File

@ -66,10 +66,10 @@ func (suite *VisitorTestSuite) TestGetPolicies() {
suite.Equal(authenticatedForPublicProject.GetPolicies(), policiesForPublicProject(publicNamespace)) suite.Equal(authenticatedForPublicProject.GetPolicies(), policiesForPublicProject(publicNamespace))
systemAdmin := NewUser(sysAdminCtx, namespace) systemAdmin := NewUser(sysAdminCtx, namespace)
suite.Equal(systemAdmin.GetPolicies(), policiesForSystemAdmin(namespace)) suite.Equal(systemAdmin.GetPolicies(), GetAllPolicies(namespace))
systemAdminForPublicProject := NewUser(sysAdminCtx, publicNamespace) systemAdminForPublicProject := NewUser(sysAdminCtx, publicNamespace)
suite.Equal(systemAdminForPublicProject.GetPolicies(), policiesForSystemAdmin(publicNamespace)) suite.Equal(systemAdminForPublicProject.GetPolicies(), GetAllPolicies(publicNamespace))
} }
func (suite *VisitorTestSuite) TestGetRoles() { func (suite *VisitorTestSuite) TestGetRoles() {

View File

@ -15,8 +15,10 @@
package rbac package rbac
import ( import (
"errors"
"fmt" "fmt"
"path" "path"
"strings"
) )
const ( const (
@ -29,6 +31,27 @@ const (
// Resource the type of resource // Resource the type of resource
type Resource string type Resource string
// RelativeTo returns relative resource to other resource
func (res Resource) RelativeTo(other Resource) (Resource, error) {
prefix := other.String()
str := res.String()
if !strings.HasPrefix(str, prefix) {
return Resource(""), errors.New("value error")
}
relative := strings.TrimPrefix(str, prefix)
if strings.HasPrefix(relative, "/") {
relative = relative[1:]
}
if relative == "" {
relative = "."
}
return Resource(relative), nil
}
func (res Resource) String() string { func (res Resource) String() string {
return string(res) return string(res)
} }

View File

@ -390,3 +390,50 @@ func TestResource_GetNamespace(t *testing.T) {
}) })
} }
} }
func TestResource_RelativeTo(t *testing.T) {
type args struct {
other Resource
}
tests := []struct {
name string
res Resource
args args
want Resource
wantErr bool
}{
{
name: "/project/1/image",
res: Resource("/project/1/image"),
args: args{other: Resource("/project/1")},
want: Resource("image"),
wantErr: false,
},
{
name: "/project/1",
res: Resource("/project/1"),
args: args{other: Resource("/project/1")},
want: Resource("."),
wantErr: false,
},
{
name: "/project/1",
res: Resource("/project/1"),
args: args{other: Resource("/system")},
want: Resource(""),
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.res.RelativeTo(tt.args.other)
if (err != nil) != tt.wantErr {
t.Errorf("Resource.RelativeTo() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("Resource.RelativeTo() = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -72,19 +72,19 @@ func (s *SecurityContext) IsSolutionUser() bool {
// HasReadPerm returns whether the user has read permission to the project // HasReadPerm returns whether the user has read permission to the project
func (s *SecurityContext) HasReadPerm(projectIDOrName interface{}) bool { func (s *SecurityContext) HasReadPerm(projectIDOrName interface{}) bool {
isPublicProject, _ := s.pm.IsPublic(projectIDOrName) isPublicProject, _ := s.pm.IsPublic(projectIDOrName)
return s.Can(project.ActionPull, rbac.NewProjectNamespace(projectIDOrName, isPublicProject).Resource(project.ResourceImage)) return s.Can(project.ActionPull, rbac.NewProjectNamespace(projectIDOrName, isPublicProject).Resource(project.ResourceRepository))
} }
// HasWritePerm returns whether the user has write permission to the project // HasWritePerm returns whether the user has write permission to the project
func (s *SecurityContext) HasWritePerm(projectIDOrName interface{}) bool { func (s *SecurityContext) HasWritePerm(projectIDOrName interface{}) bool {
isPublicProject, _ := s.pm.IsPublic(projectIDOrName) isPublicProject, _ := s.pm.IsPublic(projectIDOrName)
return s.Can(project.ActionPush, rbac.NewProjectNamespace(projectIDOrName, isPublicProject).Resource(project.ResourceImage)) return s.Can(project.ActionPush, rbac.NewProjectNamespace(projectIDOrName, isPublicProject).Resource(project.ResourceRepository))
} }
// HasAllPerm returns whether the user has all permissions to the project // HasAllPerm returns whether the user has all permissions to the project
func (s *SecurityContext) HasAllPerm(projectIDOrName interface{}) bool { func (s *SecurityContext) HasAllPerm(projectIDOrName interface{}) bool {
isPublicProject, _ := s.pm.IsPublic(projectIDOrName) isPublicProject, _ := s.pm.IsPublic(projectIDOrName)
return s.Can(project.ActionPushPull, rbac.NewProjectNamespace(projectIDOrName, isPublicProject).Resource(project.ResourceImage)) return s.Can(project.ActionPushPull, rbac.NewProjectNamespace(projectIDOrName, isPublicProject).Resource(project.ResourceRepository))
} }
// Can returns whether the user can do action on resource // Can returns whether the user can do action on resource

View File

@ -70,19 +70,19 @@ func (s *SecurityContext) IsSolutionUser() bool {
// HasReadPerm returns whether the user has read permission to the project // HasReadPerm returns whether the user has read permission to the project
func (s *SecurityContext) HasReadPerm(projectIDOrName interface{}) bool { func (s *SecurityContext) HasReadPerm(projectIDOrName interface{}) bool {
isPublicProject, _ := s.pm.IsPublic(projectIDOrName) isPublicProject, _ := s.pm.IsPublic(projectIDOrName)
return s.Can(project.ActionPull, rbac.NewProjectNamespace(projectIDOrName, isPublicProject).Resource(project.ResourceImage)) return s.Can(project.ActionPull, rbac.NewProjectNamespace(projectIDOrName, isPublicProject).Resource(project.ResourceRepository))
} }
// HasWritePerm returns whether the user has write permission to the project // HasWritePerm returns whether the user has write permission to the project
func (s *SecurityContext) HasWritePerm(projectIDOrName interface{}) bool { func (s *SecurityContext) HasWritePerm(projectIDOrName interface{}) bool {
isPublicProject, _ := s.pm.IsPublic(projectIDOrName) isPublicProject, _ := s.pm.IsPublic(projectIDOrName)
return s.Can(project.ActionPush, rbac.NewProjectNamespace(projectIDOrName, isPublicProject).Resource(project.ResourceImage)) return s.Can(project.ActionPush, rbac.NewProjectNamespace(projectIDOrName, isPublicProject).Resource(project.ResourceRepository))
} }
// HasAllPerm returns whether the user has all permissions to the project // HasAllPerm returns whether the user has all permissions to the project
func (s *SecurityContext) HasAllPerm(projectIDOrName interface{}) bool { func (s *SecurityContext) HasAllPerm(projectIDOrName interface{}) bool {
isPublicProject, _ := s.pm.IsPublic(projectIDOrName) isPublicProject, _ := s.pm.IsPublic(projectIDOrName)
return s.Can(project.ActionPushPull, rbac.NewProjectNamespace(projectIDOrName, isPublicProject).Resource(project.ResourceImage)) return s.Can(project.ActionPushPull, rbac.NewProjectNamespace(projectIDOrName, isPublicProject).Resource(project.ResourceRepository))
} }
// Can returns whether the user can do action on resource // Can returns whether the user can do action on resource

View File

@ -313,12 +313,12 @@ func (msc *mockSecurityContext) IsSolutionUser() bool {
// HasReadPerm returns whether the user has read permission to the project // HasReadPerm returns whether the user has read permission to the project
func (msc *mockSecurityContext) HasReadPerm(projectIDOrName interface{}) bool { func (msc *mockSecurityContext) HasReadPerm(projectIDOrName interface{}) bool {
return msc.Can(project.ActionPull, rbac.NewProjectNamespace(projectIDOrName, false).Resource(project.ResourceImage)) return msc.Can(project.ActionPull, rbac.NewProjectNamespace(projectIDOrName, false).Resource(project.ResourceRepository))
} }
// HasWritePerm returns whether the user has write permission to the project // HasWritePerm returns whether the user has write permission to the project
func (msc *mockSecurityContext) HasWritePerm(projectIDOrName interface{}) bool { func (msc *mockSecurityContext) HasWritePerm(projectIDOrName interface{}) bool {
return msc.Can(project.ActionPush, rbac.NewProjectNamespace(projectIDOrName, false).Resource(project.ResourceImage)) return msc.Can(project.ActionPush, rbac.NewProjectNamespace(projectIDOrName, false).Resource(project.ResourceRepository))
} }
// HasAllPerm returns whether the user has all permissions to the project // HasAllPerm returns whether the user has all permissions to the project

View File

@ -34,6 +34,7 @@ import (
"github.com/goharbor/harbor/src/core/config" "github.com/goharbor/harbor/src/core/config"
"github.com/goharbor/harbor/src/core/filter" "github.com/goharbor/harbor/src/core/filter"
"github.com/goharbor/harbor/tests/apitests/apilib" "github.com/goharbor/harbor/tests/apitests/apilib"
// "strconv" // "strconv"
// "strings" // "strings"
@ -103,6 +104,7 @@ func init() {
beego.Router("/api/users/:id", &UserAPI{}, "get:Get") beego.Router("/api/users/:id", &UserAPI{}, "get:Get")
beego.Router("/api/users", &UserAPI{}, "get:List;post:Post;delete:Delete;put:Put") beego.Router("/api/users", &UserAPI{}, "get:List;post:Post;delete:Delete;put:Put")
beego.Router("/api/users/:id([0-9]+)/password", &UserAPI{}, "put:ChangePassword") beego.Router("/api/users/:id([0-9]+)/password", &UserAPI{}, "put:ChangePassword")
beego.Router("/api/users/:id/permissions", &UserAPI{}, "get:ListUserPermissions")
beego.Router("/api/users/:id/sysadmin", &UserAPI{}, "put:ToggleUserAdminRole") beego.Router("/api/users/:id/sysadmin", &UserAPI{}, "put:ToggleUserAdminRole")
beego.Router("/api/projects/:id([0-9]+)/logs", &ProjectAPI{}, "get:Logs") beego.Router("/api/projects/:id([0-9]+)/logs", &ProjectAPI{}, "get:Logs")
beego.Router("/api/projects/:id([0-9]+)/_deletable", &ProjectAPI{}, "get:Deletable") beego.Router("/api/projects/:id([0-9]+)/_deletable", &ProjectAPI{}, "get:Deletable")
@ -996,6 +998,23 @@ func (a testapi) UsersUpdatePassword(userID int, password apilib.Password, authI
return httpStatusCode, err return httpStatusCode, err
} }
func (a testapi) UsersGetPermissions(userID interface{}, scope string, authInfo usrInfo) (int, []apilib.Permission, error) {
_sling := sling.New().Get(a.basePath)
// create path and map variables
path := fmt.Sprintf("/api/users/%v/permissions", userID)
_sling = _sling.Path(path)
type QueryParams struct {
Scope string `url:"scope,omitempty"`
}
_sling = _sling.QueryStruct(&QueryParams{Scope: scope})
httpStatusCode, body, err := request(_sling, jsonAcceptHeader, authInfo)
var successPayLoad []apilib.Permission
if 200 == httpStatusCode && nil == err {
err = json.Unmarshal(body, &successPayLoad)
}
return httpStatusCode, successPayLoad, err
}
// Mark a registered user as be removed. // Mark a registered user as be removed.
func (a testapi) UsersDelete(userID int, authInfo usrInfo) (int, error) { func (a testapi) UsersDelete(userID int, authInfo usrInfo) (int, error) {
_sling := sling.New().Delete(a.basePath) _sling := sling.New().Delete(a.basePath)

View File

@ -24,6 +24,8 @@ import (
"github.com/goharbor/harbor/src/common" "github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/common/dao" "github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/models" "github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/common/rbac/project"
"github.com/goharbor/harbor/src/common/utils" "github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/common/utils/log" "github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/core/config" "github.com/goharbor/harbor/src/core/config"
@ -339,6 +341,57 @@ func (ua *UserAPI) ToggleUserAdminRole() {
} }
} }
// ListUserPermissions handles GET to /api/users/{}/permissions
func (ua *UserAPI) ListUserPermissions() {
if ua.userID != ua.currentUserID {
log.Warningf("Current user, id: %d can not view other user's permissions", ua.currentUserID)
ua.RenderError(http.StatusForbidden, "User does not have permission")
return
}
relative := ua.Ctx.Input.Query("relative") == "true"
scope := rbac.Resource(ua.Ctx.Input.Query("scope"))
policies := []*rbac.Policy{}
namespace, err := scope.GetNamespace()
if err == nil {
switch namespace.Kind() {
case "project":
for _, policy := range project.GetAllPolicies(namespace) {
if ua.SecurityCtx.Can(policy.Action, policy.Resource) {
policies = append(policies, policy)
}
}
}
}
results := []map[string]string{}
for _, policy := range policies {
var resource rbac.Resource
// for resource `/project/1/repository` if `relative` is `true` then the resource in response will be `repository`
if relative {
relativeResource, err := policy.Resource.RelativeTo(scope)
if err != nil {
continue
}
resource = relativeResource
} else {
resource = policy.Resource
}
results = append(results, map[string]string{
"resource": resource.String(),
"action": policy.Action.String(),
})
}
ua.Data["json"] = results
ua.ServeJSON()
return
}
// modifiable returns whether the modify is allowed based on current auth mode and context // modifiable returns whether the modify is allowed based on current auth mode and context
func (ua *UserAPI) modifiable() bool { func (ua *UserAPI) modifiable() bool {
if ua.AuthMode == common.DBAuth { if ua.AuthMode == common.DBAuth {

View File

@ -572,3 +572,28 @@ func TestModifiable(t *testing.T) {
} }
assert.True(ua4.modifiable()) assert.True(ua4.modifiable())
} }
func TestUsersCurrentPermissions(t *testing.T) {
fmt.Println("Testing Get Users Current Permissions")
assert := assert.New(t)
apiTest := newHarborAPI()
httpStatusCode, permissions, err := apiTest.UsersGetPermissions("current", "/project/library", *projAdmin)
assert.Nil(err)
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
assert.NotEmpty(permissions, "permissions should not be empty")
httpStatusCode, permissions, err = apiTest.UsersGetPermissions("current", "/unsupport-scope", *projAdmin)
assert.Nil(err)
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
assert.Empty(permissions, "permissions should be empty")
httpStatusCode, _, err = apiTest.UsersGetPermissions(projAdminID, "/project/library", *projAdmin)
assert.Nil(err)
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
httpStatusCode, _, err = apiTest.UsersGetPermissions(projDeveloperID, "/project/library", *projAdmin)
assert.Nil(err)
assert.Equal(int(403), httpStatusCode, "httpStatusCode should be 403")
}

View File

@ -46,6 +46,7 @@ func initRouters() {
beego.Router("/api/users/:id", &api.UserAPI{}, "get:Get;delete:Delete;put:Put") beego.Router("/api/users/:id", &api.UserAPI{}, "get:Get;delete:Delete;put:Put")
beego.Router("/api/users", &api.UserAPI{}, "get:List;post:Post") beego.Router("/api/users", &api.UserAPI{}, "get:List;post:Post")
beego.Router("/api/users/:id([0-9]+)/password", &api.UserAPI{}, "put:ChangePassword") beego.Router("/api/users/:id([0-9]+)/password", &api.UserAPI{}, "put:ChangePassword")
beego.Router("/api/users/:id/permissions", &api.UserAPI{}, "get:ListUserPermissions")
beego.Router("/api/users/:id/sysadmin", &api.UserAPI{}, "put:ToggleUserAdminRole") beego.Router("/api/users/:id/sysadmin", &api.UserAPI{}, "put:ToggleUserAdminRole")
beego.Router("/api/usergroups/?:ugid([0-9]+)", &api.UserGroupAPI{}) beego.Router("/api/usergroups/?:ugid([0-9]+)", &api.UserGroupAPI{})
beego.Router("/api/ldap/ping", &api.LdapAPI{}, "post:Ping") beego.Router("/api/ldap/ping", &api.LdapAPI{}, "post:Ping")

View File

@ -53,3 +53,9 @@ type User struct {
UpdateTime string `json:"update_time,omitempty"` UpdateTime string `json:"update_time,omitempty"`
} }
// Permission the permission type
type Permission struct {
Resource string `json:"resource,omitempty"`
Action string `json:"action,omitempty"`
}