Origin/18124 custom role feature#22815
Conversation
|
@maxgraustenzel-create Please first submit a proposal in the community repository for discussion. |
|
This PR is being marked stale due to a period of inactivty. If this PR is still relevant, please comment or remove the stale label. Otherwise, this PR will close in 30 days. |
This is the proposal: |
|
Tagging issue #18308 to this since it's also related to some of the issues opened. |
1a2ee15 to
236bb14
Compare
- Portal: replace hardcoded built-in role buttons in project member
list, add-member, and add-group dialogs with dynamic role list loaded
from API; built-in roles show translated names + blue badge; custom
roles listed after a divider; scroll when list overflows
- Portal: role column in member datagrid now resolves role name via API
(translated for built-ins, raw name for custom roles)
- Backend: fire audit log events for role create / update / delete;
add UpdateRoleEvent topic and metadata; subscribe all three topics
in handler init so events actually reach the audit log handler
- Backend: fix typo in DeleteRoleEvent audit message ("robot" → "role")
- DB: add 0190 migration schema placeholder
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend: add checkNoEscalation to member handler — CreateProjectMember and UpdateProjectMember now verify that every permission in the target role is held by the calling user before accepting the request. Sysadmins pass through unconditionally via the RBAC layer. Returns 403 Forbidden on escalation attempt, closing the TODO in member controller isValidRole. Frontend: compute assignableRoleIds from the current user's own role permissions (loaded via forkJoin with the member list). Built-in roles below projectAdmin level and custom roles that are a strict subset of the caller's permissions remain selectable; others are disabled and visually dimmed in the action dropdown, add-member, and add-group dialogs. Default selection in add-member/add-group modals snaps to the first assignable role so the form is always valid on open. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
sysadmin_flag lives on SessionUserBackend; the resolved SessionUser type only exposes has_admin_role — fixes TS2339 compilation error. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Migration 0040 renames master → maintainer before 0190 runs, so the UPDATE was silently skipping the maintainer role. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Role permission cache (L1 sync.Map + L2 Redis) in controller/role/controller.go
eliminates per-request role_permission DB queries after first warm-up
- Restore isValidRole() validation in member controller
- Fix permissions endpoint: open to all authenticated users (used by project UI)
- Remove orm.Debug=true and leftover debug log.Debug("*** ...") statements
- Uncomment and fix RBAC evaluator tests for fork's 3-arg NewBuilderForUser
- Remove TODO placeholder comments from production code
- Add [TIMING:*] instrumentation to measure permission evaluation breakdown:
Can(), project_get, project_assemble, list_roles_sql, builder, role_get
- Extended timing to project controller and DAO for detailed DB breakdown
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ckstop The L1+L2 role-permission cache had two correctness gaps: - L2 had no TTL (cache.Default() Expiration=0), so out-of-band SQL changes to role_permission were never reflected until a core restart. - invalidateRole() cleared only the local L1; Get() reads L1 first, so other nodes served stale permissions until their process restarted. Fix: - Global Redis "version token" (role:version) bumped on every in-app write; nodes poll it throttled to ~1s and bump a local generation that invalidates all L1 entries → cross-node propagation within ~1s. Lock-free (atomics+CAS). - 30-min TTL on both L1 (per-entry expiresAt) and L2 (Save expiration) as a self-healing backstop for out-of-band changes. - Read order: version-refresh -> L1 (gen+TTL) -> L2 -> DB (populates both). Write order: bump version -> delete L2 -> delete L1. - Documented the bounded cache-aside repopulation race. Also fixes a pre-existing test bug: TestUpdateCustomRole never mocked roleMgr.Update (failed on the committed baseline too). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Brings in upstream through eb944bb (incl. Angular 21 / Clarity 18 / Node 22 portal upgrade and go.mod OSS bumps). Backend auto-merged and builds clean. Conflicts resolved (portal): kept the custom-roles feature logic while adopting upstream's Angular-21 control-flow syntax: - member.component.html: @if blocks + getMemberRoleDisplayName() - add-group/add-group.component.html: @for over dynamic roles with builtin badges - package-lock.json: took upstream's Angular-21 lockfile (fork adds no npm deps) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Angular 19+ makes components standalone by default. The fork's RolesComponent, AddRoleComponent and RolePermissionsPanelComponent are declared in roles.module.ts, so they must opt out of standalone (NG6008). Portal now builds green under Angular 21 / Clarity 18 / Node 22. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a runtime toggle so Get() with permissions can skip L1/L2 entirely and read the role + role_permission from the DB on every Can() call. Replaces the fragile build-time source-stripping used for the no-cache perf baseline (which broke after the version-key refactor) with a same-binary env switch. Off by default. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Prepare the custom-roles branch for a clean merge request: - Remove all [TIMING:*] perf-logging (Can/builder/evaluator/project_ctrl/ list_roles_sql/security_ctx) and the timing-only changes in dao.go, session.go and project/controller.go (reverted to upstream). - Drop the per-role permission lookup's timing scaffolding in rbac/project/evaluator.go, keeping the feature (ctl_r.Get) and adding proper error handling; collapse a redundant nested if-err. - Remove the "TODO MGS" scratch comment and the commented-out dead code (convertScope/toScope/SetProject/CreateSec/IsValidSec) plus the now-unused regex vars and regexp import in controller/role/controller.go. No functional change to the custom-roles feature or the permission cache. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
End-to-end checks for the custom-roles feature: role CRUD + built-in immutability, built-in role permissions via ListRole, project/group/robot member anti-escalation, sysadmin bypass, is_builtin display flags, and edge cases (zero-permission role, empty-roles fallback). Run: python3 tests/integration/test_custom_roles.py --url <harbor> --user admin --password <pw> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add clrDgSelectionType="multi" to the roles datagrid so Clarity 18 renders the selection checkbox column (binding [(clrDgSelected)] to an array alone is no longer sufficient), and migrate the row iteration from *ngFor to @for to match every other datagrid in the portal. Without the checkboxes admins could not select a role to enable the Edit/Delete actions in the action bar. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
850fcfc to
2af19e6
Compare
Extract the permission cache out of the role controller into a cachingController decorator (cache.go). The inner controller is now cache-agnostic, pure DB logic. Drop the Redis "version token" cross-node generation machinery in favor of a simpler staleness-bounded design: L1 memory (1s) -> L2 Redis (30m) -> DB. Writes delete the L2 key and drop L1; other nodes refresh once their L1 window expires, bounding cross-node staleness to the L1 TTL without extra coordination. Either layer disables via env TTL <= 0 (ROLE_CACHE_L1_MEMORY_TTL, ROLE_CACHE_L2_REDIS_TTL). Add cache_test.go covering L1 hit/expiry, L2 promotion, write invalidation, per-layer disable, and env parsing. Update the key-changes doc to describe the decorator design. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…L2_REDIS_TTL) Keep RBAC off the per-request Redis path unless an operator opts in; L1 in-memory TTL bounds staleness without Redis (cf. goharbor#23335). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-role-feature # Conflicts: # make/migrations/postgresql/0190_2.16.0_schema.up.sql
Replace the hand-rolled Redis Fetch/Save in cachingController.Get with the shared lib/cache.FetchOrSave helper, which coalesces concurrent L1-miss builds via singleflight (one DB build + one Redis populate) and persists with a non-cancelable context. The L1 memory tier is unchanged, so the per-request RBAC hot path stays Redis-free. - errRoleNotFound sentinel keeps a missing role out of the cache. - On any L2/Redis error, fall back to a direct DB read so a Redis hiccup never fails an authz check. - Update(): log instead of silently skipping invalidation when role is nil. - gitignore compiled photon component binaries. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Not part of the custom-roles feature; committed by accident in early commits.
- delete init_permissions.sql:Zone.Identifier (WSL download marker, orphaned)
- delete 'make/harbor.yml copy.tmpl' (file-manager duplicate)
- delete make/photon/registry/config.yml (build-generated file)
- revert make/harbor.yml.tmpl (hostname localhost -> upstream default)
- revert make/prepare (drop 'set -x' debug tracing)
- delete docs/custom-roles-{key-changes,cache-comparison}.md (internal dev notes)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- casbin_adapter: restore policyLinesOfRole() call (drop inlined duplicate + commented-out line; also restores the empty-role-name guard) - evaluator: drop stray blank line - role controller/model tests: remove dead copied-from-robot /* */ blocks; add TestCreateCustomRole, TestSetLevel, TestSetEditable - portal: internationalize the Roles nav link (ROLE.ROLE_NAV) + add ROLE_NAV to tr/pt-br/zh-cn/zh-tw lang files; remove stray TODO comments Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- tests/apitests base.py: fix duplicated 'robot' dict key -> 'role' (RoleApi was overwriting the robot client) - permissions.go: remove dead isProjectAdmin block (computed via a DB call then discarded) + now-unused mc field and member/local imports - role.go: drop vague '// more validation' comment - portal: remove console.log debug in add-robot; drop '// MGS TODO' in base.module; tidy stray blank lines/whitespace in add-member template - gitignore build-portal.sh and install*.log Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add TestCreateRole_NonSysAdminForbidden: a non-sysadmin creating a role with broad permissions is rejected with 403 at the checkSysAdmin gate before any role is created, so there is no privilege-escalation path through role create/update. Given that, the isValidRolePermissionScope subset-check is not wired into this handler: role mutation is sysadmin-only (a sysadmin already holds every permission, and has no enumerable role-permission list to subset against), and escalation is already enforced on the non-admin assignment paths (validateNoEscalation). Replace the stale '//TODO check escalation' with a doc comment documenting this security model. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add TestUpdateProjectMember_EscalationBlocked: a caller allowed to update members but lacking a permission the target role grants is rejected with 403 at the handler, and the member controller's UpdateRole is never reached. checkNoEscalation was wired into both CreateProjectMember and UpdateProjectMember and unit-tested as a function, but no handler-level test covered the update (modification) path -- so a regression dropping the check from UpdateProjectMember would have gone unnoticed. This closes that gap. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- cache: role-not-found is not cached (returns nil); Redis-error on the L2 path falls back to the DB so a Redis hiccup never fails an authz check - member: CreateProjectMember escalation blocked at the assign path (mirror of the update-path test) -> both member escalation entry points now handler-tested - role handler: DeleteRole success, UpdateRole sysadmin-gate wiring, UpdateRole legacy/immutable-role denied Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t is lazy Explain that redisCache is the dependency-injection point for the L2 backend (tests inject an in-memory or erroring fake) and that cache.Default() is resolved lazily in redis() because role.Ctl is built at import time while the shared cache is not initialized until startup. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
RolePrefix()/RoleNamePrefix was read but never registered as a config item, so it always resolved to empty and prepended nothing. It was also applied at read time (not stored), which would have caused retroactive display changes and per-node divergence if ever wired. Remove the function, the constant, and the prepending in populate() and the role event resolvers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ents - common/models/base.go: remove the commented-out new(Role) registration (Role now self-registers in pkg/role/model) - controller/role/cache.go: tighten the redisCache/redis() comments Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fill the ROLE i18n section to key-parity (95 keys) in every language file: ru-ru and ko-kr had no ROLE section; the rest were missing ~20 keys each. Most values reuse each file's own existing translations of the same terms; role-specific strings are translated per language. Remove the stray 'Rolle' key from de-de. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- roles.component / system-robot-accounts.component: drop debug console.log in the (empty) ngAfterViewInit hooks and a stray '//TODO create page size' comment - delete src/testing/controller/role/controller.go: an unreferenced, mislabeled mock (it is a project-controller mock under package 'project', never used) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The ROLE i18n section was cloned from ROBOT and carried 22 keys no component references (TOKEN/Secret, TOKEN_EXPIRATION, NAME_PREFIX, expiry/secret strings, 'Filter Robot Accounts', etc.), while 6 keys the role UI DOES reference were absent and rendered as raw keys (ADD_ROLE, DELETE_ROLE, UPDATE_ROLE, UPDATE_ROLE_SUCCESSFULLY, NAME_TOOLTIP, PUSH_PERMISSION_TOOLTIP). Remove the 22 dead keys and add the 6 missing ones (translated) across all 11 language files, so ROLE now contains exactly the 79 keys the code uses. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds database-backed custom project roles across Harbor’s RBAC API, authorization engine, and portal.
Changes:
- Adds role persistence, permission caching, APIs, audit events, and authorization evaluation.
- Adds role management and dynamic member/robot permission selection to the portal.
- Adds migration, localization, mocks, and tests.
Reviewed changes
Copilot reviewed 86 out of 87 changed files in this pull request and generated 24 comments.
Show a summary per file
| File | Description |
|---|---|
.gitignore |
Ignores local build artifacts |
tests/integration/.gitignore |
Ignores Python test artifacts |
tests/apitests/python/library/base.py |
Registers role API client |
api/v2.0/swagger.yaml |
Defines role and effective-permission APIs |
make/migrations/postgresql/0190_2.16.0_schema.up.sql |
Migrates roles and permissions |
src/testing/pkg/role/manager.go |
Adds role manager mock |
src/testing/pkg/role/dao/dao.go |
Adds role DAO mock |
src/server/v2.0/handler/role.go |
Implements role endpoints |
src/server/v2.0/handler/role_test.go |
Tests role handlers |
src/server/v2.0/handler/robot.go |
Prevents robot permission escalation |
src/server/v2.0/handler/robot_test.go |
Tests escalation checks |
src/server/v2.0/handler/project.go |
Exposes effective permissions |
src/server/v2.0/handler/permissions.go |
Extends permission catalog |
src/server/v2.0/handler/model/role.go |
Converts role API models |
src/server/v2.0/handler/member.go |
Validates role assignment scope |
src/server/v2.0/handler/handler.go |
Registers role handlers |
src/portal/src/i18n/lang/zh-tw-lang.json |
Adds Traditional Chinese role text |
src/portal/src/i18n/lang/zh-cn-lang.json |
Adds Simplified Chinese role text |
src/portal/src/i18n/lang/tr-tr-lang.json |
Adds Turkish role text |
src/portal/src/i18n/lang/ru-ru-lang.json |
Adds Russian role text |
src/portal/src/i18n/lang/pt-br-lang.json |
Adds Portuguese role text |
src/portal/src/i18n/lang/ko-kr-lang.json |
Adds Korean role text |
src/portal/src/i18n/lang/fr-fr-lang.json |
Adds French role text |
src/portal/src/i18n/lang/es-es-lang.json |
Adds Spanish role text |
src/portal/src/i18n/lang/en-us-lang.json |
Adds English role text |
src/portal/src/i18n/lang/de-de-lang.json |
Adds German role text |
src/portal/src/app/shared/entities/shared.const.ts |
Adds role confirmation targets |
src/portal/src/app/shared/components/role-permissions-panel/role-permissions-panel.component.ts |
Implements role permission selector |
src/portal/src/app/shared/components/role-permissions-panel/role-permissions-panel.component.spec.ts |
Tests permission selector |
src/portal/src/app/shared/components/role-permissions-panel/role-permissions-panel.component.scss |
Styles permission selector |
src/portal/src/app/shared/components/role-permissions-panel/role-permissions-panel.component.html |
Renders permission selector |
src/portal/src/app/shared/components/robot-permissions-panel/robot-permissions-panel.component.ts |
Normalizes empty resources |
src/portal/src/app/base/project/robot-account/robot-account.component.ts |
Loads effective permissions |
src/portal/src/app/base/project/robot-account/robot-account.component.html |
Passes effective permissions |
src/portal/src/app/base/project/robot-account/add-robot/add-robot.component.ts |
Filters robot permissions |
src/portal/src/app/base/project/robot-account/add-robot/add-robot.component.html |
Uses filtered permissions |
src/portal/src/app/base/project/member/member.component.ts |
Loads and filters roles |
src/portal/src/app/base/project/member/member.component.scss |
Styles role lists |
src/portal/src/app/base/project/member/member.component.html |
Adds dynamic role menus |
src/portal/src/app/base/project/member/add-member/add-member.component.ts |
Loads selectable roles |
src/portal/src/app/base/project/member/add-member/add-member.component.scss |
Styles member role list |
src/portal/src/app/base/project/member/add-member/add-member.component.html |
Renders member roles |
src/portal/src/app/base/project/member/add-group/add-group.component.ts |
Loads group roles |
src/portal/src/app/base/project/member/add-group/add-group.component.scss |
Styles group role list |
src/portal/src/app/base/project/member/add-group/add-group.component.html |
Renders group roles |
src/portal/src/app/base/left-side-nav/system-robot-accounts/system-robot-util.ts |
Normalizes role resources |
src/portal/src/app/base/left-side-nav/system-robot-accounts/system-robot-accounts.component.ts |
Minor formatting change |
src/portal/src/app/base/left-side-nav/roles/roles.module.ts |
Defines roles module |
src/portal/src/app/base/left-side-nav/roles/roles.component.ts |
Implements role management |
src/portal/src/app/base/left-side-nav/roles/roles.component.spec.ts |
Tests role actions |
src/portal/src/app/base/left-side-nav/roles/roles.component.scss |
Styles role management |
src/portal/src/app/base/left-side-nav/roles/roles.component.html |
Renders role management |
src/portal/src/app/base/left-side-nav/roles/roles-util.ts |
Adds role UI utilities |
src/portal/src/app/base/left-side-nav/roles/add-role/add-role.component.ts |
Implements role editor |
src/portal/src/app/base/left-side-nav/roles/add-role/add-role.component.spec.ts |
Tests role editor creation |
src/portal/src/app/base/left-side-nav/roles/add-role/add-role.component.scss |
Styles role editor |
src/portal/src/app/base/left-side-nav/roles/add-role/add-role.component.html |
Renders role wizard |
src/portal/src/app/base/left-side-nav/projects/list-project/list-project.component.html |
Displays custom role IDs |
src/portal/src/app/base/harbor-shell/harbor-shell.component.html |
Adds roles navigation |
src/portal/src/app/base/base.module.ts |
Registers roles route |
src/pkg/role/model/model.go |
Defines persistent role model |
src/pkg/role/manager.go |
Adds role manager |
src/pkg/role/manager_test.go |
Tests role manager |
src/pkg/role/dao/dao.go |
Implements role persistence |
src/pkg/role/dao/dao_test.go |
Tests role persistence |
src/controller/role/model.go |
Defines controller role model |
src/controller/role/model_test.go |
Tests role model helpers |
src/controller/role/controller.go |
Implements role lifecycle |
src/controller/role/controller_test.go |
Tests role lifecycle |
src/controller/role/cache.go |
Caches role permissions |
src/controller/member/controller.go |
Accepts custom role IDs |
src/controller/event/topic.go |
Adds role audit events |
src/controller/event/metadata/role.go |
Resolves role event metadata |
src/controller/event/handler/init.go |
Registers role audit handlers |
src/common/security/local/context.go |
Injects role controller |
src/common/rbac/project/rbac_user.go |
Carries populated roles |
src/common/rbac/project/evaluator.go |
Loads dynamic role permissions |
src/common/rbac/project/evaluator_test.go |
Tests dynamic evaluation |
src/common/rbac/const.go |
Adds role permission catalog |
src/common/models/role.go |
Removes legacy role model |
src/common/models/base.go |
Removes legacy registration |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if c.l1Enabled() { | ||
| if v, ok := c.local.Load(id); ok { | ||
| if e := v.(*l1Entry); time.Now().Before(e.expiresAt) { | ||
| return e.role, nil |
| log.Warningf("role cache: Update succeeded with a nil role; cannot invalidate, permissions may be stale") | ||
| return nil | ||
| } | ||
| c.invalidate(ctx, r.ID) |
| if err := d.roleMgr.Delete(ctx, id); err != nil { | ||
| return err | ||
| } | ||
| if err := d.rbacMgr.DeletePermissionsByRole(ctx, ROLETYPE, id); err != nil { | ||
| return err |
| (ResourceMetadata, ActionCreate, PROJECT_ADMIN), | ||
| (ResourceMetadata, ActionRead, PROJECT_ADMIN), | ||
| (ResourceMetadata, ActionUpdate, PROJECT_ADMIN), | ||
| (ResourceMetadata, ActionDelete, PROJECT_ADMIN), | ||
|
|
| // validateName validates the role name, especially '+' cannot be a valid character | ||
| func validateRoleName(name string) error { | ||
| return nil | ||
| } |
| "ACCOUNT_EXISTING": "Conta de automação já existe.", | ||
| "CREATED_SUCCESS": "'{{param}}' criado com sucesso.", | ||
| "DELETION_TITLE": "Confirmar a remoção da conta", | ||
| "DELETION_SUMMARY": "Você quer remover a regra {{param}}?", |
| "ACCOUNT_EXISTING": "Роль уже существует.", | ||
| "CREATED_SUCCESS": "Успешно создан '{{param}}'.", | ||
| "DELETION_TITLE": "Подтвердить удаление роботов", | ||
| "DELETION_SUMMARY": "Вы хотите удалить роботов {{param}}?", |
| "ACCOUNT_EXISTING": "Robot Hesabı zaten var.", | ||
| "CREATED_SUCCESS": "Başarıyla '{{param}}' oluşturuldu.", | ||
| "DELETION_TITLE": "Robot hesaplarının kaldırılmasını onaylayın", | ||
| "DELETION_SUMMARY": "Robot hesaplarını silmek istiyor musunuz {{param}}?", |
| "ACCOUNT_EXISTING": "机器人账户已经存在。", | ||
| "CREATED_SUCCESS": "创建账户 '{{param}}' 成功。", | ||
| "DELETION_TITLE": "删除账户确认", | ||
| "DELETION_SUMMARY": "您确认删除机器人账户 {{param}}?", |
| "ACCOUNT_EXISTING": "機器人帳號已存在。", | ||
| "CREATED_SUCCESS": "已成功建立 '{{param}}'。", | ||
| "DELETION_TITLE": "確認移除機器人帳號", | ||
| "DELETION_SUMMARY": "您是否要刪除機器人帳號 {{param}}?", |
- govet: drop no-op append (rbac/const.go)
- revive: rename ctl_r -> ctlR; implement validateRoleName ('+' rejected,
matching the frontend tooltip) instead of an unused param
- whitespace/gofmt: remove leading blank lines after '{'; goimports grouping
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Auto-fixed formatting across the role feature components (prettier/prettier) and stylelint issues in the role/member SCSS; the PR's portal files were not lint-clean and failed the UI_UT job. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The PR added MEMBER.BUILT_IN only to en-us. Add it to the other 9 languages (ru-ru placed in its effective MEMBER block, which is duplicated in that file). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- role-permissions-panel spec: host template used <robot-permissions-panel> while the ViewChild queried RolePermissionsPanelComponent (undefined) -> the spec (and the karma job) failed. Point the template at role-permissions-panel. - i18n: ROLE.DELETION_SUMMARY/TITLE said 'robot accounts'; the role delete confirmation now refers to roles (across all 10 languages). - add-robot: distinguish a loaded-but-empty effective-permissions result (offer no candidates) from not-yet-loaded (show catalog) via effectivePermissionsLoaded, so a user with no project permissions is not offered the whole catalog. - role controller Update: populate modified/modified_by (+ modified_at) so the audit metadata the UI shows is accurate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- cache Get returns a deep copy (clone) so request code (e.g. update handler) cannot mutate cached authorization state - role delete rejects when the role is still assigned to project members (project_member.role has no FK -> would orphan memberships); adds member.GetTotalOfProjectMembersByRole - role Update populates modified/modified_by/modified_at (audit accuracy) - migration 0190: restore metadata:list for project-admin (built-in grants it), widen role.name to varchar(255) to match the API/UI, add a unique index on name - validateRoleName enforces one contract: non-empty, <=255, no '+' - GetEffectivePermissions resolves the project once (by id) instead of per candidate permission, and fails on lookup/check errors instead of suppressing Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
member/add-member/add-group loaded only the first 100 roles. Add loadAllRoles() (paginates via x-total-count) so roles beyond the first page remain selectable and assignable. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This change adds custom roles to harbor.
Directly Solved Issues:
#18124 RBAC - be able to create a custom role
#18143 Create custom project roles
#21222 Add role for security auditor
#15711 Add a readonly admin role in Harbor
#12062 Make permission system more fine-grained
#21306 - Read-only auditor role (December 2024 - very recent!)
#8632 - Limited guest role
#1486 - Guest shouldn't see all members (from 2016!)
...
Partially Solved Issues:
#10159 - Repository-level permissions
...
Design changes :