Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
6f3d97d
initial version role feature (role manage, dynamic validation, member
maxgraustenzel-create Jan 30, 2026
25e7106
corrections
maxgraustenzel-create Feb 3, 2026
5ca9e9b
cleanup
maxgraustenzel-create Feb 4, 2026
73d7e1e
feat: custom roles — member UI, dynamic role selector, audit logging
maxgraustenzel-create May 20, 2026
c5de594
feat: anti-escalation for project member role assignment
maxgraustenzel-create May 20, 2026
4949747
fix: use has_admin_role instead of sysadmin_flag in SessionUser check
maxgraustenzel-create May 21, 2026
1eac775
fix: use 'maintainer' not 'master' in 0190 is_builtin seed
maxgraustenzel-create May 21, 2026
2636f9f
feat: role permission cache, cleanup and timing instrumentation
maxgraustenzel-create Jun 4, 2026
7c62743
feat(role-cache): multi-node version-key invalidation + 30-min TTL ba…
maxgraustenzel-create Jun 10, 2026
5f3532b
fix(portal): roles nav label ROLES -> Roles
maxgraustenzel-create Jun 10, 2026
ed31e97
Merge upstream/main into 18124-custom-role-feature
maxgraustenzel-create Jun 10, 2026
f8f3c9d
fix(portal): mark fork role components standalone:false for Angular 21
maxgraustenzel-create Jun 10, 2026
70e5b39
feat(role-cache): ROLE_CACHE_DISABLED env to bypass the permission cache
maxgraustenzel-create Jun 10, 2026
dfc1a84
chore: remove perf-timing instrumentation and dev cruft
maxgraustenzel-create Jun 12, 2026
a6b8c72
test: add custom-roles integration test suite
maxgraustenzel-create Jun 15, 2026
2af19e6
fix(portal): restore role datagrid selection checkboxes
maxgraustenzel-create Jun 17, 2026
efd2752
refactor(role-cache): replace version-key invalidation with decorator
maxgraustenzel-create Jun 23, 2026
93d7414
feat(role-cache): disable Redis L2 by default (opt-in via ROLE_CACHE_…
maxgraustenzel-create Jul 8, 2026
c952689
Merge remote-tracking branch 'upstream/main' into origin/18124-custom…
maxgraustenzel-create Jul 8, 2026
78b6838
refactor(role-cache): use lib/cache.FetchOrSave for the L2 path
maxgraustenzel-create Jul 8, 2026
eaa6691
chore: remove stray dev/build artifacts from the PR
maxgraustenzel-create Jul 8, 2026
d853970
chore(custom-roles): address code-review cleanup
maxgraustenzel-create Jul 8, 2026
b37d5a1
chore(custom-roles): address code-review batch 2
maxgraustenzel-create Jul 8, 2026
17d1e9b
test(role): prove role-definition escalation is gated to sysadmins
maxgraustenzel-create Jul 8, 2026
c244c57
test(member): prove escalation is blocked on the role MODIFY path
maxgraustenzel-create Jul 8, 2026
190d5d7
test: close role/member handler + cache coverage gaps
maxgraustenzel-create Jul 9, 2026
80f8f12
docs(role-cache): clarify redisCache is the L2 DI seam and why defaul…
maxgraustenzel-create Jul 9, 2026
3acc397
refactor(role): remove dead role name-prefix mechanism
maxgraustenzel-create Jul 9, 2026
fe317e5
cleanup(role): drop commented-out model registration; tidy cache comm…
maxgraustenzel-create Jul 9, 2026
bcc00d1
i18n(role): complete the ROLE translation section across all languages
maxgraustenzel-create Jul 9, 2026
ea62d41
cleanup: remove debug console.log/TODO and a dead mislabeled mock
maxgraustenzel-create Jul 9, 2026
5fe75b1
i18n(role): prune robot-cloned dead keys, add missing referenced keys
maxgraustenzel-create Jul 9, 2026
2d9b80d
Merge branch 'main' into origin/18124-custom-role-feature
Vad1mo Jul 10, 2026
73d6306
fix(lint): resolve golangci-lint issues in custom-roles code
maxgraustenzel-create Jul 13, 2026
c1c4648
fix(lint): resolve prettier/eslint + stylelint issues in role UI
maxgraustenzel-create Jul 13, 2026
edec529
i18n: add MEMBER.BUILT_IN across all language files
maxgraustenzel-create Jul 13, 2026
f4a1344
fix: address Copilot review (wave 1)
maxgraustenzel-create Jul 14, 2026
6bec4bb
fix: address Copilot review (wave 2, backend)
maxgraustenzel-create Jul 15, 2026
d1f60bc
fix: load all role pages in member selectors (wave 2)
maxgraustenzel-create Jul 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,21 @@ make/dev/adminserver/harbor_adminserver
make/dev/core/harbor_core
make/dev/jobservice/harbor_jobservice
make/photon/*/binary/
make/photon/core/harbor_core
make/photon/jobservice/harbor_jobservice
make/photon/registryctl/harbor_registryctl
make/photon/prepare/versions

src/adminserver/adminserver
src/core/core
src/jobservice/jobservice
src/common/dao/dao.test
*.pyc

# local build/install artifacts (never commit)
build-portal.sh
install.log
install*.log
jobservice/test

src/portal/coverage/
Expand Down
285 changes: 285 additions & 0 deletions api/v2.0/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,34 @@ paths:
$ref: '#/responses/404'
'500':
$ref: '#/responses/500'
'/projects/{project_name_or_id}/permissions/effective':
get:
summary: Get the current user's effective permissions for a project
description: Returns the subset of project-level permissions that the calling user actually holds. Used to restrict robot account permission selection to the user's own access level.
tags:
- project
operationId: getEffectivePermissions
parameters:
- $ref: '#/parameters/requestId'
- $ref: '#/parameters/isResourceName'
- $ref: '#/parameters/projectNameOrId'
responses:
'200':
description: The effective permissions of the current user in the project.
schema:
type: array
items:
$ref: '#/definitions/Permission'
'400':
$ref: '#/responses/400'
'401':
$ref: '#/responses/401'
'403':
$ref: '#/responses/403'
'404':
$ref: '#/responses/404'
'500':
$ref: '#/responses/500'
'/projects/{project_name_or_id}/members':
get:
summary: Get all project member information
Expand Down Expand Up @@ -3347,6 +3375,79 @@ paths:
$ref: '#/responses/404'
'500':
$ref: '#/responses/500'
/roles:
get:
summary: Get roles
description: List the roles with the specified level and permissions.
tags:
- role
operationId: ListRole
parameters:
- $ref: '#/parameters/requestId'
- $ref: '#/parameters/query'
- $ref: '#/parameters/sort'
- $ref: '#/parameters/page'
- $ref: '#/parameters/pageSize'
responses:
'200':
description: Success
headers:
X-Total-Count:
description: The total count of roles
type: integer
Link:
description: Link refers to the previous page and next page
type: string
schema:
type: array
items:
$ref: '#/definitions/Role'
'400':
$ref: '#/responses/400'
'404':
$ref: '#/responses/404'
'500':
$ref: '#/responses/500'
post:
summary: Create a role
description: Create a role
tags:
- role
operationId: CreateRole
parameters:
- $ref: '#/parameters/requestId'
- name: role
in: body
description: The JSON object of a role.
required: true
schema:
$ref: '#/definitions/RoleCreate'
responses:
'201':
description: Created
headers:
X-Request-Id:
description: The ID of the corresponding request for the response
type: string
Location:
description: The location of the resource
type: string
schema:
$ref: '#/definitions/RoleCreated'
'400':
$ref: '#/responses/400'
'401':
$ref: '#/responses/401'
'403':
$ref: '#/responses/403'
'404':
$ref: '#/responses/404'
'500':
$ref: '#/responses/500'




'/quotas':
get:
summary: List quotas
Expand Down Expand Up @@ -3560,6 +3661,82 @@ paths:
$ref: '#/responses/404'
'500':
$ref: '#/responses/500'
/roles/{role_id}:
get:
summary: Get a role
description: This endpoint returns specific role information by role ID.
tags:
- role
operationId: GetRoleByID
parameters:
- $ref: '#/parameters/requestId'
- $ref: '#/parameters/roleId'
responses:
'200':
description: Return matched role information.
schema:
$ref: '#/definitions/Role'
'401':
$ref: '#/responses/401'
'403':
$ref: '#/responses/403'
'404':
$ref: '#/responses/404'
'500':
$ref: '#/responses/500'
put:
summary: Update a role account
description: This endpoint updates specific role information by role ID.
tags:
- role
operationId: UpdateRole
parameters:
- $ref: '#/parameters/requestId'
- $ref: '#/parameters/roleId'
- name: role
in: body
description: The JSON object of a role.
required: true
schema:
$ref: '#/definitions/Role'
responses:
'200':
$ref: '#/responses/200'
'400':
$ref: '#/responses/400'
'401':
$ref: '#/responses/401'
'403':
$ref: '#/responses/403'
'404':
$ref: '#/responses/404'
'409':
$ref: '#/responses/409'
'500':
$ref: '#/responses/500'
delete:
summary: Delete a role account
description: This endpoint deletes specific role information by role ID.
tags:
- role
operationId: DeleteRole
parameters:
- $ref: '#/parameters/requestId'
- $ref: '#/parameters/roleId'
responses:
'200':
$ref: '#/responses/200'
'400':
$ref: '#/responses/400'
'401':
$ref: '#/responses/401'
'403':
$ref: '#/responses/403'
'404':
$ref: '#/responses/404'
'500':
$ref: '#/responses/500'

/replication/policies:
get:
summary: List replication policies
Expand Down Expand Up @@ -6523,6 +6700,12 @@ parameters:
description: Robot ID
required: true
type: integer
roleId:
name: role_id
in: path
description: Role ID
required: true
type: integer
gcId:
name: gc_id
in: path
Expand Down Expand Up @@ -8135,6 +8318,101 @@ definitions:
type: array
items:
$ref: '#/definitions/Access'
Role:
type: object
properties:
id:
type: integer
format: int64
description: The ID of the role
name:
type: string
description: The name of the role
role_mask:
type: integer
description: The mask of the role, not used
role_code:
type: string
description: The code of the role, not used
permissions:
type: array
items:
$ref: '#/definitions/RolePermission'
is_builtin:
type: boolean
description: Whether this is a built-in role that cannot be modified or deleted
description:
type: string
description: Human-readable description of the role
modified:
type: boolean
description: Whether the role has been modified since creation
created_by:
type: string
description: Username of the admin who created the role
created_at:
type: string
format: date-time
description: When the role was created
modified_by:
type: string
description: Username of the admin who last modified the role
modified_at:
type: string
format: date-time
description: When the role was last modified
RoleCreate:
type: object
description: The request for role creation.
properties:
name:
type: string
description: The name of the role
role_mask:
type: integer
description: The mask of the role, not used
role_code:
type: string
description: The code of the role, not used
description:
type: string
description: Human-readable description of the role
permissions:
type: array
items:
$ref: '#/definitions/RolePermission'
RoleCreated:
type: object
description: The response for role creation.
properties:
id:
type: integer
format: int64
description: The ID of the role
name:
type: string
description: The name of the role
secret:
type: string
description: The secret of the role
creation_time:
type: string
format: date-time
description: The creation time of the role.
RolePermission:
type: object
properties:
kind:
type: string
description: The kind of the permission
namespace:
type: string
description: The namespace of the permission
access:
type: array
items:
$ref: '#/definitions/Access'

Access:
type: object
properties:
Expand All @@ -8147,6 +8425,7 @@ definitions:
effect:
type: string
description: The effect of the access

RobotCreateV1:
type: object
properties:
Expand Down Expand Up @@ -9862,6 +10141,12 @@ definitions:
description: The project level permissions
items:
$ref: '#/definitions/Permission'
role:
type: array
description: The role project level permissions
items:
$ref: '#/definitions/Permission'

OIDCCliSecretReq:
type: object
properties:
Expand Down
Loading