mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-22 02:05:41 +01:00
feat(role): introduce a limited guest role (#9403)
Signed-off-by: He Weiwei <hweiwei@vmware.com>
This commit is contained in:
parent
62451a57d9
commit
bf6a14c9ad
@ -10,44 +10,45 @@ System admin have all permissions for the project.
|
||||
|
||||
The following table depicts the various user permission levels in a project.
|
||||
|
||||
| Action | Guest | Developer | Master | Project Admin |
|
||||
| --------------------------------------- | ----- | --------- | ------ | ------------- |
|
||||
| See the porject configurations | ✓ | ✓ | ✓ | ✓ |
|
||||
| Edit the project configurations | | | | ✓ |
|
||||
| See a list of project members | ✓ | ✓ | ✓ | ✓ |
|
||||
| Create/edit/delete project members | | | | ✓ |
|
||||
| See a list of project logs | ✓ | ✓ | ✓ | ✓ |
|
||||
| See a list of project replications | | | ✓ | ✓ |
|
||||
| See a list of project replication jobs | | | | ✓ |
|
||||
| See a list of project labels | | | ✓ | ✓ |
|
||||
| Create/edit/delete project lables | | | ✓ | ✓ |
|
||||
| See a list of repositories | ✓ | ✓ | ✓ | ✓ |
|
||||
| Create repositories | | ✓ | ✓ | ✓ |
|
||||
| Edit/delete repositories | | | ✓ | ✓ |
|
||||
| See a list of images | ✓ | ✓ | ✓ | ✓ |
|
||||
| Retag image | ✓ | ✓ | ✓ | ✓ |
|
||||
| Pull image | ✓ | ✓ | ✓ | ✓ |
|
||||
| Push image | | ✓ | ✓ | ✓ |
|
||||
| Scan/delete image | | | ✓ | ✓ |
|
||||
| See a list of image vulnerabilities | ✓ | ✓ | ✓ | ✓ |
|
||||
| See image build history | ✓ | ✓ | ✓ | ✓ |
|
||||
| Add/Remove labels of image | | ✓ | ✓ | ✓ |
|
||||
| See a list of helm charts | ✓ | ✓ | ✓ | ✓ |
|
||||
| Download helm charts | ✓ | ✓ | ✓ | ✓ |
|
||||
| Upload helm charts | | ✓ | ✓ | ✓ |
|
||||
| Delete helm charts | | | ✓ | ✓ |
|
||||
| See a list of helm chart versions | ✓ | ✓ | ✓ | ✓ |
|
||||
| Download helm chart versions | ✓ | ✓ | ✓ | ✓ |
|
||||
| Upload helm chart versions | | ✓ | ✓ | ✓ |
|
||||
| Delete helm chart versions | | | ✓ | ✓ |
|
||||
| Add/Remove labels of helm chart version | | ✓ | ✓ | ✓ |
|
||||
| See a list of project robots | | | ✓ | ✓ |
|
||||
| Create/edit/delete project robots | | | | ✓ |
|
||||
| See configured CVE whitelist | ✓ | ✓ | ✓ | ✓ |
|
||||
| Create/edit/remove CVE whitelist | | | | ✓ |
|
||||
| Enable/disable webhooks | | ✓ | ✓ | ✓ |
|
||||
| Create/delete tag retention rules | | ✓ | ✓ | ✓ |
|
||||
| Enable/disable tag retention rules | | ✓ | ✓ | ✓ |
|
||||
| See project quotas | ✓ | ✓ | ✓ | ✓ |
|
||||
| Edit project quotas | | | | |
|
||||
| Action | Limited Guest | Guest | Developer | Master | Project Admin |
|
||||
| --------------------------------------- | ------------- | ----- | --------- | ------ | ------------- |
|
||||
| See the porject configurations | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Edit the project configurations | | | | | ✓ |
|
||||
| See a list of project members | | ✓ | ✓ | ✓ | ✓ |
|
||||
| Create/edit/delete project members | | | | | ✓ |
|
||||
| See a list of project logs | | ✓ | ✓ | ✓ | ✓ |
|
||||
| See a list of project replications | | | | ✓ | ✓ |
|
||||
| See a list of project replication jobs | | | | | ✓ |
|
||||
| See a list of project labels | | | | ✓ | ✓ |
|
||||
| Create/edit/delete project lables | | | | ✓ | ✓ |
|
||||
| See a list of repositories | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Create repositories | | | ✓ | ✓ | ✓ |
|
||||
| Edit/delete repositories | | | | ✓ | ✓ |
|
||||
| See a list of images | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Retag image | | ✓ | ✓ | ✓ | ✓ |
|
||||
| Pull image | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Push image | | | ✓ | ✓ | ✓ |
|
||||
| Scan/delete image | | | | ✓ | ✓ |
|
||||
| See a list of image vulnerabilities | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| See image build history | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Add/Remove labels of image | | | ✓ | ✓ | ✓ |
|
||||
| See a list of helm charts | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Download helm charts | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Upload helm charts | | | ✓ | ✓ | ✓ |
|
||||
| Delete helm charts | | | | ✓ | ✓ |
|
||||
| See a list of helm chart versions | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Download helm chart versions | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Upload helm chart versions | | | ✓ | ✓ | ✓ |
|
||||
| Delete helm chart versions | | | | ✓ | ✓ |
|
||||
| Add/Remove labels of helm chart version | | | ✓ | ✓ | ✓ |
|
||||
| See a list of project robots | | | | ✓ | ✓ |
|
||||
| Create/edit/delete project robots | | | | | ✓ |
|
||||
| See configured CVE whitelist | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Create/edit/remove CVE whitelist | | | | | ✓ |
|
||||
| Enable/disable webhooks | | | ✓ | ✓ | ✓ |
|
||||
| Create/delete tag retention rules | | | ✓ | ✓ | ✓ |
|
||||
| Enable/disable tag retention rules | | | ✓ | ✓ | ✓ |
|
||||
| See project quotas | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Edit project quotas | | | | | |
|
||||
|
||||
|
||||
|
@ -57,4 +57,7 @@ DROP TABLE IF EXISTS img_scan_job;
|
||||
DROP TRIGGER IF EXISTS TRIGGER ON img_scan_overview;
|
||||
DROP TABLE IF EXISTS img_scan_overview;
|
||||
|
||||
DROP TABLE IF EXISTS clair_vuln_timestamp
|
||||
DROP TABLE IF EXISTS clair_vuln_timestamp;
|
||||
|
||||
/* Add limited guest role */
|
||||
INSERT INTO role (role_code, name) VALUES ('LRS', 'limitedGuest');
|
||||
|
@ -33,6 +33,7 @@ const (
|
||||
RoleDeveloper = 2
|
||||
RoleGuest = 3
|
||||
RoleMaster = 4
|
||||
RoleLimitedGuest = 5
|
||||
|
||||
LabelLevelSystem = "s"
|
||||
LabelLevelUser = "u"
|
||||
|
@ -78,7 +78,7 @@ func addProjectMember(member models.Member) (int, error) {
|
||||
func GetProjectByID(id int64) (*models.Project, error) {
|
||||
o := GetOrmer()
|
||||
|
||||
sql := `select p.project_id, p.name, u.username as owner_name, p.owner_id, p.creation_time, p.update_time
|
||||
sql := `select p.project_id, p.name, u.username as owner_name, p.owner_id, p.creation_time, p.update_time
|
||||
from project p left join harbor_user u on p.owner_id = u.user_id where p.deleted = false and p.project_id = ?`
|
||||
queryParam := make([]interface{}, 1)
|
||||
queryParam = append(queryParam, id)
|
||||
@ -142,7 +142,7 @@ func GetTotalOfProjects(query *models.ProjectQueryParam) (int64, error) {
|
||||
// GetProjects returns a project list according to the query conditions
|
||||
func GetProjects(query *models.ProjectQueryParam) ([]*models.Project, error) {
|
||||
sqlStr, queryParam := projectQueryConditions(query)
|
||||
sqlStr = `select distinct p.project_id, p.name, p.owner_id,
|
||||
sqlStr = `select distinct p.project_id, p.name, p.owner_id,
|
||||
p.creation_time, p.update_time ` + sqlStr + ` order by p.name`
|
||||
sqlStr, queryParam = CreatePagination(query, sqlStr, queryParam)
|
||||
|
||||
@ -158,15 +158,15 @@ func GetProjects(query *models.ProjectQueryParam) ([]*models.Project, error) {
|
||||
// and the user is in the group which is a group member of this project.
|
||||
func GetGroupProjects(groupIDs []int, query *models.ProjectQueryParam) ([]*models.Project, error) {
|
||||
sql, params := projectQueryConditions(query)
|
||||
sql = `select distinct p.project_id, p.name, p.owner_id,
|
||||
sql = `select distinct p.project_id, p.name, p.owner_id,
|
||||
p.creation_time, p.update_time ` + sql
|
||||
groupIDCondition := JoinNumberConditions(groupIDs)
|
||||
if len(groupIDs) > 0 {
|
||||
sql = fmt.Sprintf(
|
||||
`%s union select distinct p.project_id, p.name, p.owner_id, p.creation_time, p.update_time
|
||||
from project p
|
||||
`%s union select distinct p.project_id, p.name, p.owner_id, p.creation_time, p.update_time
|
||||
from project p
|
||||
left join project_member pm on p.project_id = pm.project_id
|
||||
left join user_group ug on ug.id = pm.entity_id and pm.entity_type = 'g'
|
||||
left join user_group ug on ug.id = pm.entity_id and pm.entity_type = 'g'
|
||||
where ug.id in ( %s )`,
|
||||
sql, groupIDCondition)
|
||||
}
|
||||
@ -188,11 +188,11 @@ func GetTotalGroupProjects(groupIDs []int, query *models.ProjectQueryParam) (int
|
||||
sql = `select count(1) ` + sqlCondition
|
||||
} else {
|
||||
sql = fmt.Sprintf(
|
||||
`select count(1)
|
||||
from ( select p.project_id %s union select p.project_id
|
||||
from project p
|
||||
`select count(1)
|
||||
from ( select p.project_id %s union select p.project_id
|
||||
from project p
|
||||
left join project_member pm on p.project_id = pm.project_id
|
||||
left join user_group ug on ug.id = pm.entity_id and pm.entity_type = 'g'
|
||||
left join user_group ug on ug.id = pm.entity_id and pm.entity_type = 'g'
|
||||
where ug.id in ( %s )) t`,
|
||||
sqlCondition, groupIDCondition)
|
||||
}
|
||||
@ -254,6 +254,8 @@ func projectQueryConditions(query *models.ProjectQueryParam) (string, []interfac
|
||||
roleID = 3
|
||||
case common.RoleMaster:
|
||||
roleID = 4
|
||||
case common.RoleLimitedGuest:
|
||||
roleID = 5
|
||||
}
|
||||
params = append(params, roleID)
|
||||
}
|
||||
@ -287,8 +289,8 @@ func DeleteProject(id int64) error {
|
||||
return err
|
||||
}
|
||||
name := fmt.Sprintf("%s#%d", project.Name, project.ProjectID)
|
||||
sql := `update project
|
||||
set deleted = true, name = ?
|
||||
sql := `update project
|
||||
set deleted = true, name = ?
|
||||
where project_id = ?`
|
||||
_, err = GetOrmer().Raw(sql, name, id).Exec()
|
||||
return err
|
||||
@ -304,8 +306,8 @@ func GetRolesByGroupID(projectID int64, groupIDs []int) ([]int, error) {
|
||||
groupIDCondition := JoinNumberConditions(groupIDs)
|
||||
o := GetOrmer()
|
||||
sql := fmt.Sprintf(
|
||||
`select distinct pm.role from project_member pm
|
||||
left join user_group ug on pm.entity_type = 'g' and pm.entity_id = ug.id
|
||||
`select distinct pm.role from project_member pm
|
||||
left join user_group ug on pm.entity_type = 'g' and pm.entity_id = ug.id
|
||||
where ug.id in ( %s ) and pm.project_id = ?`,
|
||||
groupIDCondition)
|
||||
log.Debugf("sql for GetRolesByGroupID(project ID: %d, group ids: %v):%v", projectID, groupIDs, sql)
|
||||
|
@ -201,6 +201,7 @@ type ProjectSummary struct {
|
||||
MasterCount int64 `json:"master_count"`
|
||||
DeveloperCount int64 `json:"developer_count"`
|
||||
GuestCount int64 `json:"guest_count"`
|
||||
LimitedGuestCount int64 `json:"limited_guest_count"`
|
||||
|
||||
Quota struct {
|
||||
Hard types.ResourceList `json:"hard"`
|
||||
|
@ -48,124 +48,7 @@ var (
|
||||
}
|
||||
|
||||
// all policies for the projects
|
||||
allPolicies = []*rbac.Policy{
|
||||
{Resource: rbac.ResourceSelf, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceSelf, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceSelf, Action: rbac.ActionDelete},
|
||||
|
||||
{Resource: rbac.ResourceMember, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceMember, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceMember, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceMember, Action: rbac.ActionDelete},
|
||||
{Resource: rbac.ResourceMember, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceMetadata, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceMetadata, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceMetadata, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceMetadata, Action: rbac.ActionDelete},
|
||||
|
||||
{Resource: rbac.ResourceLog, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceReplication, Action: rbac.ActionList},
|
||||
{Resource: rbac.ResourceReplication, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceReplication, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceReplication, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceReplication, Action: rbac.ActionDelete},
|
||||
|
||||
{Resource: rbac.ResourceReplicationJob, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceReplicationJob, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceReplicationJob, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceReplicationExecution, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceReplicationExecution, Action: rbac.ActionList},
|
||||
{Resource: rbac.ResourceReplicationExecution, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceReplicationExecution, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceReplicationExecution, Action: rbac.ActionDelete},
|
||||
|
||||
{Resource: rbac.ResourceReplicationTask, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceReplicationTask, Action: rbac.ActionList},
|
||||
{Resource: rbac.ResourceReplicationTask, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceReplicationTask, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceReplicationTask, Action: rbac.ActionDelete},
|
||||
|
||||
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionDelete},
|
||||
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionList},
|
||||
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionOperate},
|
||||
|
||||
{Resource: rbac.ResourceImmutableTag, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceImmutableTag, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceImmutableTag, Action: rbac.ActionDelete},
|
||||
{Resource: rbac.ResourceImmutableTag, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceLabel, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceLabel, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceLabel, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceLabel, Action: rbac.ActionDelete},
|
||||
{Resource: rbac.ResourceLabel, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceLabelResource, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceRepository, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceRepository, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceRepository, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceRepository, Action: rbac.ActionDelete},
|
||||
{Resource: rbac.ResourceRepository, Action: rbac.ActionList},
|
||||
{Resource: rbac.ResourceRepository, Action: rbac.ActionPull},
|
||||
{Resource: rbac.ResourceRepository, Action: rbac.ActionPush},
|
||||
|
||||
{Resource: rbac.ResourceRepositoryLabel, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceRepositoryLabel, Action: rbac.ActionDelete},
|
||||
{Resource: rbac.ResourceRepositoryLabel, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceRepositoryTag, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceRepositoryTag, Action: rbac.ActionDelete},
|
||||
{Resource: rbac.ResourceRepositoryTag, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceRepositoryTagScanJob, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceRepositoryTagScanJob, Action: rbac.ActionRead},
|
||||
|
||||
{Resource: rbac.ResourceRepositoryTagVulnerability, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceRepositoryTagManifest, Action: rbac.ActionRead},
|
||||
|
||||
{Resource: rbac.ResourceRepositoryTagLabel, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceRepositoryTagLabel, Action: rbac.ActionDelete},
|
||||
{Resource: rbac.ResourceRepositoryTagLabel, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceHelmChart, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceHelmChart, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceHelmChart, Action: rbac.ActionDelete},
|
||||
{Resource: rbac.ResourceHelmChart, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceHelmChartVersion, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceHelmChartVersion, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceHelmChartVersion, Action: rbac.ActionDelete},
|
||||
{Resource: rbac.ResourceHelmChartVersion, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceHelmChartVersionLabel, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceHelmChartVersionLabel, Action: rbac.ActionDelete},
|
||||
|
||||
{Resource: rbac.ResourceConfiguration, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceConfiguration, Action: rbac.ActionUpdate},
|
||||
|
||||
{Resource: rbac.ResourceRobot, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceRobot, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceRobot, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceRobot, Action: rbac.ActionDelete},
|
||||
{Resource: rbac.ResourceRobot, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceNotificationPolicy, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceNotificationPolicy, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceNotificationPolicy, Action: rbac.ActionDelete},
|
||||
{Resource: rbac.ResourceNotificationPolicy, Action: rbac.ActionList},
|
||||
{Resource: rbac.ResourceNotificationPolicy, Action: rbac.ActionRead},
|
||||
|
||||
{Resource: rbac.ResourceScan, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceScan, Action: rbac.ActionRead},
|
||||
}
|
||||
allPolicies = computeAllPolicies()
|
||||
)
|
||||
|
||||
// PoliciesForPublicProject ...
|
||||
@ -197,3 +80,19 @@ func GetAllPolicies(namespace rbac.Namespace) []*rbac.Policy {
|
||||
|
||||
return policies
|
||||
}
|
||||
|
||||
func computeAllPolicies() []*rbac.Policy {
|
||||
var results []*rbac.Policy
|
||||
|
||||
mp := map[string]bool{}
|
||||
for _, policies := range rolePoliciesMap {
|
||||
for _, policy := range policies {
|
||||
if !mp[policy.String()] {
|
||||
results = append(results, policy)
|
||||
mp[policy.String()] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
@ -299,6 +299,28 @@ var (
|
||||
{Resource: rbac.ResourceRobot, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceRobot, Action: rbac.ActionList},
|
||||
},
|
||||
|
||||
"limitedGuest": {
|
||||
{Resource: rbac.ResourceSelf, Action: rbac.ActionRead},
|
||||
|
||||
{Resource: rbac.ResourceRepository, Action: rbac.ActionList},
|
||||
{Resource: rbac.ResourceRepository, Action: rbac.ActionPull},
|
||||
|
||||
{Resource: rbac.ResourceRepositoryTag, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceRepositoryTag, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceRepositoryTagVulnerability, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceRepositoryTagManifest, Action: rbac.ActionRead},
|
||||
|
||||
{Resource: rbac.ResourceHelmChart, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceHelmChart, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceHelmChartVersion, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceHelmChartVersion, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceConfiguration, Action: rbac.ActionRead},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@ -319,6 +341,8 @@ func (role *visitorRole) GetRoleName() string {
|
||||
return "developer"
|
||||
case common.RoleGuest:
|
||||
return "guest"
|
||||
case common.RoleLimitedGuest:
|
||||
return "limitedGuest"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
|
@ -35,6 +35,9 @@ func (suite *VisitorRoleTestSuite) TestGetRoleName() {
|
||||
guest := visitorRole{roleID: common.RoleGuest}
|
||||
suite.Equal(guest.GetRoleName(), "guest")
|
||||
|
||||
limitedGuest := visitorRole{roleID: common.RoleLimitedGuest}
|
||||
suite.Equal(limitedGuest.GetRoleName(), "limitedGuest")
|
||||
|
||||
unknow := visitorRole{roleID: 404}
|
||||
suite.Equal(unknow.GetRoleName(), "")
|
||||
}
|
||||
|
@ -125,6 +125,8 @@ func (s *SecurityContext) GetProjectRoles(projectIDOrName interface{}) []int {
|
||||
roles = append(roles, common.RoleDeveloper)
|
||||
case "RS":
|
||||
roles = append(roles, common.RoleGuest)
|
||||
case "LRS":
|
||||
roles = append(roles, common.RoleLimitedGuest)
|
||||
}
|
||||
}
|
||||
return mergeRoles(roles, s.GetRolesByGroup(projectIDOrName))
|
||||
|
@ -685,6 +685,7 @@ func getProjectMemberSummary(projectID int64, summary *models.ProjectSummary) {
|
||||
{common.RoleMaster, &summary.MasterCount},
|
||||
{common.RoleDeveloper, &summary.DeveloperCount},
|
||||
{common.RoleGuest, &summary.GuestCount},
|
||||
{common.RoleLimitedGuest, &summary.LimitedGuestCount},
|
||||
} {
|
||||
wg.Add(1)
|
||||
go func(role int, count *int64) {
|
||||
|
@ -191,7 +191,7 @@ func (pma *ProjectMemberAPI) Put() {
|
||||
pma.SendBadRequestError(err)
|
||||
return
|
||||
}
|
||||
if req.Role < 1 || req.Role > 4 {
|
||||
if !isValidRole(req.Role) {
|
||||
pma.SendBadRequestError(fmt.Errorf("Invalid role id %v", req.Role))
|
||||
return
|
||||
}
|
||||
@ -284,9 +284,22 @@ func AddProjectMember(projectID int64, request models.MemberReq) (int, error) {
|
||||
return 0, ErrDuplicateProjectMember
|
||||
}
|
||||
|
||||
if member.Role < 1 || member.Role > 4 {
|
||||
if !isValidRole(member.Role) {
|
||||
// Return invalid role error
|
||||
return 0, ErrInvalidRole
|
||||
}
|
||||
return project.AddProjectMember(member)
|
||||
}
|
||||
|
||||
func isValidRole(role int) bool {
|
||||
switch role {
|
||||
case common.RoleProjectAdmin,
|
||||
common.RoleMaster,
|
||||
common.RoleDeveloper,
|
||||
common.RoleGuest,
|
||||
common.RoleLimitedGuest:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -344,3 +344,28 @@ func TestProjectMemberAPI_PutAndDelete(t *testing.T) {
|
||||
runCodeCheckingCases(t, cases...)
|
||||
|
||||
}
|
||||
|
||||
func Test_isValidRole(t *testing.T) {
|
||||
type args struct {
|
||||
role int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{"project admin", args{1}, true},
|
||||
{"master", args{4}, true},
|
||||
{"developer", args{2}, true},
|
||||
{"guest", args{3}, true},
|
||||
{"limited guest", args{5}, true},
|
||||
{"unknow", args{6}, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := isValidRole(tt.args.role); got != tt.want {
|
||||
t.Errorf("isValidRole() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -145,6 +145,11 @@ export const PROJECT_ROOTS = [
|
||||
NAME: "guest",
|
||||
VALUE: 3,
|
||||
LABEL: "GROUP.GUEST"
|
||||
},
|
||||
{
|
||||
NAME: "limited",
|
||||
VALUE: 5,
|
||||
LABEL: "GROUP.LIMITED_GUEST"
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -45,6 +45,10 @@
|
||||
<input clrRadio type="radio" name="member_role" id="checkrads_guest" [value]=3 [(ngModel)]="member.role_id">
|
||||
<label for="checkrads_guest">{{'MEMBER.GUEST' | translate}}</label>
|
||||
</clr-radio-wrapper>
|
||||
<clr-radio-wrapper>
|
||||
<input clrRadio type="radio" name="member_role" id="checkrads_limited_guest" [value]=5 [(ngModel)]="member.role_id">
|
||||
<label for="checkrads_limited_guest">{{'MEMBER.LIMITED_GUEST' | translate}}</label>
|
||||
</clr-radio-wrapper>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -27,6 +27,7 @@
|
||||
<button clrDropdownItem (click)="changeMembersRole(selectedRow, 4)" [disabled]="!(selectedRow.length && hasUpdateMemberPermission) || onlySelf">{{'MEMBER.PROJECT_MASTER' | translate}}</button>
|
||||
<button clrDropdownItem (click)="changeMembersRole(selectedRow, 2)" [disabled]="!(selectedRow.length && hasUpdateMemberPermission) || onlySelf">{{'MEMBER.DEVELOPER' | translate}}</button>
|
||||
<button clrDropdownItem (click)="changeMembersRole(selectedRow, 3)" [disabled]="!(selectedRow.length && hasUpdateMemberPermission) || onlySelf">{{'MEMBER.GUEST' | translate}}</button>
|
||||
<button clrDropdownItem (click)="changeMembersRole(selectedRow, 5)" [disabled]="!(selectedRow.length && hasUpdateMemberPermission) || onlySelf">{{'MEMBER.LIMITED_GUEST' | translate}}</button>
|
||||
<div class="dropdown-divider"></div>
|
||||
<button clrDropdownItem (click)="openDeleteMembersDialog(selectedRow)" [disabled]="!(selectedRow.length && hasDeleteMemberPermission) || onlySelf">{{'MEMBER.REMOVE' | translate}}</button>
|
||||
</clr-dropdown-menu>
|
||||
|
@ -19,6 +19,7 @@
|
||||
<li>{{ summaryInformation?.master_count }} {{'SUMMARY.MASTER' | translate}}</li>
|
||||
<li>{{ summaryInformation?.developer_count }} {{'SUMMARY.DEVELOPER' | translate}}</li>
|
||||
<li>{{ summaryInformation?.guest_count }} {{'SUMMARY.GUEST' | translate}}</li>
|
||||
<li>{{ summaryInformation?.limited_guest_count }} {{'SUMMARY.LIMITED_GUEST' | translate}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -21,7 +21,8 @@ import {
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
import { ProjectService } from '@harbor/ui';
|
||||
import { CommonRoutes } from '@harbor/ui';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { map, catchError } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class MemberGuard implements CanActivate, CanActivateChild {
|
||||
@ -31,49 +32,39 @@ export class MemberGuard implements CanActivate, CanActivateChild {
|
||||
private router: Router) {}
|
||||
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
|
||||
let projectId = route.params['id'];
|
||||
const projectId = route.params['id'];
|
||||
this.sessionService.setProjectMembers([]);
|
||||
return new Observable((observer) => {
|
||||
let user = this.sessionService.getCurrentUser();
|
||||
if (user === null) {
|
||||
this.sessionService.retrieveUser()
|
||||
.subscribe(() => {
|
||||
this.checkMemberStatus(state.url, projectId).subscribe((res) => observer.next(res));
|
||||
}, error => {
|
||||
this.router.navigate([CommonRoutes.HARBOR_DEFAULT]);
|
||||
observer.next(false);
|
||||
});
|
||||
} else {
|
||||
this.checkMemberStatus(state.url, projectId).subscribe((res) => observer.next(res));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
checkMemberStatus(url: string, projectId: number): Observable<boolean> {
|
||||
return new Observable<boolean>((observer) => {
|
||||
this.projectService.checkProjectMember(projectId)
|
||||
.subscribe(res => {
|
||||
this.sessionService.setProjectMembers(res);
|
||||
return observer.next(true);
|
||||
},
|
||||
const user = this.sessionService.getCurrentUser();
|
||||
if (user !== null) {
|
||||
return this.hasProjectPerm(state.url, projectId);
|
||||
}
|
||||
|
||||
return this.sessionService.retrieveUser().pipe(
|
||||
() => {
|
||||
// Add exception for repository in project detail router activation.
|
||||
this.projectService.getProject(projectId).subscribe(project => {
|
||||
if (project.metadata && project.metadata.public === 'true') {
|
||||
return observer.next(true);
|
||||
}
|
||||
this.router.navigate([CommonRoutes.HARBOR_DEFAULT]);
|
||||
return observer.next(false);
|
||||
},
|
||||
() => {
|
||||
this.router.navigate([CommonRoutes.HARBOR_DEFAULT]);
|
||||
return observer.next(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
return this.hasProjectPerm(state.url, projectId);
|
||||
},
|
||||
catchError(err => {
|
||||
this.router.navigate([CommonRoutes.HARBOR_DEFAULT]);
|
||||
return of(false);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
|
||||
return this.canActivate(route, state);
|
||||
}
|
||||
|
||||
hasProjectPerm(url: string, projectId: number): Observable<boolean> {
|
||||
// Note: current user will have the permission to visit the project when the user can get response from GET /projects/:id API.
|
||||
return this.projectService.getProject(projectId).pipe(
|
||||
map(() => {
|
||||
return true;
|
||||
}),
|
||||
catchError(err => {
|
||||
this.router.navigate([CommonRoutes.HARBOR_DEFAULT]);
|
||||
return of(false);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -60,14 +60,29 @@ export const enum ConfirmationButtons {
|
||||
}
|
||||
|
||||
export const ProjectTypes = { 0: 'PROJECT.ALL_PROJECTS', 1: 'PROJECT.PRIVATE_PROJECTS', 2: 'PROJECT.PUBLIC_PROJECTS' };
|
||||
export const RoleInfo = { 1: 'MEMBER.PROJECT_ADMIN', 2: 'MEMBER.DEVELOPER', 3: 'MEMBER.GUEST', 4: 'MEMBER.PROJECT_MASTER' };
|
||||
export const RoleMapping = { 'projectAdmin': 'MEMBER.PROJECT_ADMIN',
|
||||
'master': 'MEMBER.PROJECT_MASTER', 'developer': 'MEMBER.DEVELOPER', 'guest': 'MEMBER.GUEST' };
|
||||
|
||||
export const RoleInfo = {
|
||||
1: "MEMBER.PROJECT_ADMIN",
|
||||
2: "MEMBER.DEVELOPER",
|
||||
3: "MEMBER.GUEST",
|
||||
4: "MEMBER.PROJECT_MASTER",
|
||||
5: "MEMBER.LIMITED_GUEST",
|
||||
};
|
||||
|
||||
export const RoleMapping = {
|
||||
"projectAdmin": "MEMBER.PROJECT_ADMIN",
|
||||
"master": "MEMBER.PROJECT_MASTER",
|
||||
"developer": "MEMBER.DEVELOPER",
|
||||
"guest": "MEMBER.GUEST",
|
||||
"limitedGuest": "MEMBER.LIMITED_GUEST",
|
||||
};
|
||||
|
||||
export const ProjectRoles = [
|
||||
{ id: 1, value: "MEMBER.PROJECT_ADMIN" },
|
||||
{ id: 2, value: "MEMBER.DEVELOPER" },
|
||||
{ id: 3, value: "MEMBER.GUEST" },
|
||||
{ id: 4, value: "MEMBER.PROJECT_MASTER" },
|
||||
{ id: 5, value: "MEMBER.LIMITED_GUEST" },
|
||||
];
|
||||
|
||||
export enum Roles {
|
||||
@ -75,6 +90,7 @@ export enum Roles {
|
||||
PROJECT_MASTER = 4,
|
||||
DEVELOPER = 2,
|
||||
GUEST = 3,
|
||||
LIMITED_GUEST = 5,
|
||||
OTHER = 0,
|
||||
}
|
||||
export const DefaultHelmIcon = '/images/helm-gray.svg';
|
||||
|
@ -275,6 +275,7 @@
|
||||
"PROJECT_MASTER": "Master",
|
||||
"DEVELOPER": "Developer",
|
||||
"GUEST": "Guest",
|
||||
"LIMITED_GUEST": "Limited Guest",
|
||||
"DELETE": "Delete",
|
||||
"ITEMS": "items",
|
||||
"ACTIONS": "Actions",
|
||||
@ -737,7 +738,8 @@
|
||||
"ADMIN": "Admin(s)",
|
||||
"MASTER": "Master(s)",
|
||||
"DEVELOPER": "Developer(s)",
|
||||
"GUEST": "Guest(s)"
|
||||
"GUEST": "Guest(s)",
|
||||
"LIMITED_GUEST": "Limited guest(s)"
|
||||
},
|
||||
"ALERT": {
|
||||
"FORM_CHANGE_CONFIRMATION": "Some changes are not saved yet. Do you want to cancel?"
|
||||
|
@ -276,6 +276,7 @@
|
||||
"PROJECT_MASTER": "Mantenedor",
|
||||
"DEVELOPER": "Desarrollador",
|
||||
"GUEST": "Invitado",
|
||||
"LIMITED_GUEST": "Limited Guest",
|
||||
"DELETE": "Eliminar",
|
||||
"ITEMS": "elementos",
|
||||
"ACTIONS": "Acciones",
|
||||
@ -738,7 +739,8 @@
|
||||
"ADMIN": "Admin(s)",
|
||||
"MASTER": "Master(s)",
|
||||
"DEVELOPER": "Developer(s)",
|
||||
"GUEST": "Guest(s)"
|
||||
"GUEST": "Guest(s)",
|
||||
"LIMITED_GUEST": "Limited guest(s)"
|
||||
},
|
||||
"ALERT": {
|
||||
"FORM_CHANGE_CONFIRMATION": "Algunos cambios no se han guardado aún. ¿Quiere cancelar?"
|
||||
|
@ -288,6 +288,7 @@
|
||||
"PROJECT_MASTER": "préposé à la maintenance",
|
||||
"DEVELOPER": "Développeur",
|
||||
"GUEST": "Invité",
|
||||
"LIMITED_GUEST": "Limited Guest",
|
||||
"DELETE": "Supprimer",
|
||||
"ITEMS": "items",
|
||||
"ACTIONS": "Actions",
|
||||
@ -724,7 +725,8 @@
|
||||
"ADMIN": "Admin(s)",
|
||||
"MASTER": "Master(s)",
|
||||
"DEVELOPER": "Developer(s)",
|
||||
"GUEST": "Guest(s)"
|
||||
"GUEST": "Guest(s)",
|
||||
"LIMITED_GUEST": "Limited guest(s)"
|
||||
},
|
||||
"ALERT": {
|
||||
"FORM_CHANGE_CONFIRMATION": "Certaines modifications ne sont pas encore enregistrées. Voulez-vous annuler ?"
|
||||
|
@ -273,6 +273,7 @@
|
||||
"PROJECT_MASTER": "Mantenedor",
|
||||
"DEVELOPER": "Desenvolvedor",
|
||||
"GUEST": "Visitante",
|
||||
"LIMITED_GUEST": "Limited Guest",
|
||||
"DELETE": "Remover",
|
||||
"ITEMS": "itens",
|
||||
"ACTIONS": "Ações",
|
||||
@ -733,7 +734,8 @@
|
||||
"ADMIN": "Admin(s)",
|
||||
"MASTER": "Master(s)",
|
||||
"DEVELOPER": "Developer(s)",
|
||||
"GUEST": "Guest(s)"
|
||||
"GUEST": "Guest(s)",
|
||||
"LIMITED_GUEST": "Limited guest(s)"
|
||||
},
|
||||
"ALERT": {
|
||||
"FORM_CHANGE_CONFIRMATION": "Algumas alterações ainda não foram salvas. Você deseja cancelar?"
|
||||
|
@ -275,6 +275,7 @@
|
||||
"PROJECT_MASTER": "Uzman",
|
||||
"DEVELOPER": "Geliştirici",
|
||||
"GUEST": "Konuk",
|
||||
"LIMITED_GUEST": "Limited Guest",
|
||||
"DELETE": "Sil",
|
||||
"ITEMS": "çeşit",
|
||||
"ACTIONS": "Eylemler",
|
||||
@ -736,7 +737,8 @@
|
||||
"ADMIN": "Yönetici(ler)",
|
||||
"MASTER": "Uzman(lar)",
|
||||
"DEVELOPER": "Geliştirici(ler)",
|
||||
"GUEST": "Misafir(ler)"
|
||||
"GUEST": "Misafir(ler)",
|
||||
"LIMITED_GUEST": "Limited guest(s)"
|
||||
},
|
||||
"ALERT": {
|
||||
"FORM_CHANGE_CONFIRMATION": "Bazı değişiklikler henüz kaydedilmedi. İptal etmek istiyor musun?"
|
||||
|
@ -275,6 +275,7 @@
|
||||
"PROJECT_MASTER": "维护人员",
|
||||
"DEVELOPER": "开发人员",
|
||||
"GUEST": "访客",
|
||||
"LIMITED_GUEST": "受限访客",
|
||||
"DELETE": "删除",
|
||||
"ITEMS": "条记录",
|
||||
"ACTIONS": "操作",
|
||||
@ -738,7 +739,8 @@
|
||||
"ADMIN": "管理员",
|
||||
"MASTER": "维护人员",
|
||||
"DEVELOPER": "开发者",
|
||||
"GUEST": "访客"
|
||||
"GUEST": "访客",
|
||||
"LIMITED_GUEST": "受限访客"
|
||||
},
|
||||
"ALERT": {
|
||||
"FORM_CHANGE_CONFIRMATION": "表单内容改变,确认是否取消?"
|
||||
|
Loading…
Reference in New Issue
Block a user