feat(role): introduce a limited guest role (#9403)

Signed-off-by: He Weiwei <hweiwei@vmware.com>
This commit is contained in:
He Weiwei 2019-10-20 14:21:28 +08:00 committed by GitHub
parent 62451a57d9
commit bf6a14c9ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 227 additions and 222 deletions

View File

@ -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 | | | | | |

View File

@ -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');

View File

@ -33,6 +33,7 @@ const (
RoleDeveloper = 2
RoleGuest = 3
RoleMaster = 4
RoleLimitedGuest = 5
LabelLevelSystem = "s"
LabelLevelUser = "u"

View File

@ -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)

View File

@ -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"`

View File

@ -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
}

View File

@ -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 ""
}

View File

@ -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(), "")
}

View File

@ -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))

View File

@ -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) {

View File

@ -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
}
}

View File

@ -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)
}
})
}
}

View File

@ -145,6 +145,11 @@ export const PROJECT_ROOTS = [
NAME: "guest",
VALUE: 3,
LABEL: "GROUP.GUEST"
},
{
NAME: "limited",
VALUE: 5,
LABEL: "GROUP.LIMITED_GUEST"
}
];

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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);
})
);
}
}

View File

@ -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';

View File

@ -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?"

View File

@ -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?"

View File

@ -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 ?"

View File

@ -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?"

View File

@ -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?"

View File

@ -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": "表单内容改变,确认是否取消?"