Skip to content

feat(api-token): allow org-level tokens to manage project-scoped tokens#2811

Merged
migmartri merged 5 commits intochainloop-dev:mainfrom
Piskoo:pfm-4759
Mar 4, 2026
Merged

feat(api-token): allow org-level tokens to manage project-scoped tokens#2811
migmartri merged 5 commits intochainloop-dev:mainfrom
Piskoo:pfm-4759

Conversation

@Piskoo
Copy link
Collaborator

@Piskoo Piskoo commented Mar 3, 2026

Org-level API tokens can now create, list, and revoke project-scoped API tokens.

🟢 Create project-scoped token with org-scoped token

$ chainloop org api-token create --name testorgcreate --project myproject --token <new org token>
WRN API contacted in insecure mode
INF API token provided to the command line
┌──────────────────────────────────────┬───────────────┬───────────────────┬─────────────┬─────────────────────┬────────────┬────────────┬──────────────┐
│ ID                                   │ NAME          │ SCOPE             │ DESCRIPTION │ CREATED AT          │ EXPIRES AT │ REVOKED AT │ LAST USED AT │
├──────────────────────────────────────┼───────────────┼───────────────────┼─────────────┼─────────────────────┼────────────┼────────────┼──────────────┤
│ c2a8395c-c42e-41c2-bbf9-521dbe7955d5 │ testorgcreate │ project/myproject │             │ 03 Mar 26 11:06 UTC │            │            │              │
└──────────────────────────────────────┴───────────────┴───────────────────┴─────────────┴─────────────────────┴────────────┴──────────────┴──────────────┘

Save the following token since it will not printed again:

<project token>

🔴 Create project-scoped token with project-scoped token

$ chainloop org api-token create --name testorgcreate --project myproject --token <project token>
WRN API contacted in insecure mode
INF API token provided to the command line
ERR operation not allowed
exit status 1

🔴 Create org-scoped token with project-scoped token

$ chainloop org api-token create --name testorgcreate --token <project token>
WRN API contacted in insecure mode
INF API token provided to the command line
ERR operation not allowed
exit status 1

🟢 List tokens with org-scoped token

$ chainloop org api-token ls --token <org token>
WRN API contacted in insecure mode
INF API token provided to the command line
┌──────────────────────────────────────┬───────────────┬───────────────────┬─────────────┬─────────────────────┬────────────┬────────────┬─────────────────────┐
│ ID                                   │ NAME          │ SCOPE             │ DESCRIPTION │ CREATED AT          │ EXPIRES AT │ REVOKED AT │ LAST USED AT        │
├──────────────────────────────────────┼───────────────┼───────────────────┼─────────────┼─────────────────────┼────────────┼────────────┼─────────────────────┤
│ c2a8395c-c42e-41c2-bbf9-521dbe7955d5 │ testorgcreate │ project/myproject │             │ 03 Mar 26 11:06 UTC │            │            │ 03 Mar 26 11:07 UTC │
└──────────────────────────────────────┴───────────────┴───────────────────┴─────────────┴─────────────────────┴────────────┴────────────┴─────────────────────┘

🔴 List tokens with project-scoped token

$ chainloop org api-token ls --token <project token>
WRN API contacted in insecure mode
INF API token provided to the command line
ERR operation not allowed
exit status 1

🔴 Revoke project-scoped token with project-scoped token

$ chainloop org api-token revoke --id c2a8395c-c42e-41c2-bbf9-521dbe7955d5 --token <project token>
WRN API contacted in insecure mode
INF API token provided to the command line
ERR operation not allowed
exit status 1

🟢 Revoke project-scoped token with org-scoped token

$ chainloop org api-token revoke --id c2a8395c-c42e-41c2-bbf9-521dbe7955d5 --token <org token>
WRN API contacted in insecure mode
INF API token provided to the command line
INF API token revoked!

🟢 Create project-scoped token with org-scoped token that was created in the past (List and revoke also works)

$ chainloop org api-token create --name witholdtoken --project myproject --token <org2 token>
WRN API contacted in insecure mode
INF API token provided to the command line
┌──────────────────────────────────────┬──────────────┬───────────────────┬─────────────┬─────────────────────┬────────────┬────────────┬──────────────┐
│ ID                                   │ NAME         │ SCOPE             │ DESCRIPTION │ CREATED AT          │ EXPIRES AT │ REVOKED AT │ LAST USED AT │
├──────────────────────────────────────┼──────────────┼───────────────────┼─────────────┼─────────────────────┼────────────┼────────────┼──────────────┤
│ 13db0c20-044e-4d64-aaee-4c97a6d80ef4 │ witholdtoken │ project/myproject │             │ 03 Mar 26 11:16 UTC │            │            │              │
└──────────────────────────────────────┴──────────────┴───────────────────┴─────────────┴─────────────────────┴────────────┴────────────┴──────────────┘

Save the following token since it will not printed again:

<project token>

Closes #2808

Piskoo added 2 commits March 3, 2026 12:19
Signed-off-by: Sylwester Piskozub <[email protected]>
@Piskoo Piskoo marked this pull request as ready for review March 3, 2026 11:37
@Piskoo Piskoo requested review from jiparis and migmartri March 3, 2026 11:37
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 5 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="app/controlplane/internal/service/apitoken_test.go">

<violation number="1" location="app/controlplane/internal/service/apitoken_test.go:70">
P2: This test duplicates the service's authorization logic inline rather than calling `svc.List()`. It will pass even if the actual `List` method is broken or its authorization logic changes, providing false confidence. The same issue applies to `TestAPITokenService_Revoke_OrgTokenCannotRevokeOrgTokens` below. Consider using mocks for `APITokenUseCase` (e.g., via an interface or testify mock) so the tests exercise the real service methods end-to-end.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Signed-off-by: Sylwester Piskozub <[email protected]>
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="app/controlplane/pkg/biz/apitoken_integration_test.go">

<violation number="1" location="app/controlplane/pkg/biz/apitoken_integration_test.go:153">
P2: Unsafe `append` may mutate `s.APIToken.DefaultAuthzPolicies`. In Go, `append` reuses the underlying array when capacity permits, which would silently corrupt the shared `DefaultAuthzPolicies` field. The production code already has a safe helper `withOrgLevelPolicies` (apitoken.go:47) — consider using it here, or copy the slice first.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Signed-off-by: Sylwester Piskozub <[email protected]>
// OrgLevelAPITokenPolicies are additional policies granted only to org-level tokens.
// They allow managing project-scoped tokens.
var OrgLevelAPITokenPolicies = []*authz.Policy{
authz.PolicyAPITokenCreate,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd call this PolicyProjectLevelAPITokenCreate since it's only project level tokens

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was mentioning this above. We can create this specific resource, and then calling the enforcer directly from the service (instead of checking token!=nil && token.ProjectId == nil ...)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this one is needed for the middleware to let the request pass.

}

// Org-level API tokens can only create project-scoped tokens
if token := entities.CurrentAPIToken(ctx); token != nil && token.ProjectID == nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is something that could be done with Enforce otherwise the policies are worthless no?

Signed-off-by: Sylwester Piskozub <[email protected]>
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="app/controlplane/pkg/biz/apitoken_integration_test.go">

<violation number="1" location="app/controlplane/pkg/biz/apitoken_integration_test.go:153">
P2: Missing total policy count assertion in the org-level token test. The project-level subtest correctly checks `s.Len(token.Policies, len(s.APIToken.DefaultAuthzPolicies))`, but the org-level subtest only verifies that certain policies are present without asserting the total count. This weakens the test — extra unintended policies on org tokens won't be caught. Consider adding a `Len` check here, consistent with the project-level subtest and the old test.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

// Org-level API tokens can only create project-scoped tokens
if token := entities.CurrentAPIToken(ctx); token != nil && token.ProjectID == nil {
if !req.ProjectReference.IsSet() {
return nil, errors.Forbidden("forbidden", "org-level API tokens must specify a project when creating new tokens")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so this special case is needed because we now allow org tokens to pass.

}

tokens, err := s.APITokenUseCase.List(ctx, currentOrg.ID, biz.WithAPITokenStatusFilter(mapTokenStatusFilter(req.GetStatusFilter())), biz.WithAPITokenProjectFilter(defaultProjectFilter), biz.WithAPITokenScope(mapTokenScope(req.Scope)))
// Org-level API tokens can only see project-scoped tokens
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we can simplify all of this with a new resource "ProjectApiToken" and enforcing that specific permission.

// OrgLevelAPITokenPolicies are additional policies granted only to org-level tokens.
// They allow managing project-scoped tokens.
var OrgLevelAPITokenPolicies = []*authz.Policy{
authz.PolicyAPITokenCreate,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was mentioning this above. We can create this specific resource, and then calling the enforcer directly from the service (instead of checking token!=nil && token.ProjectId == nil ...)

@migmartri
Copy link
Member

Thanks

@migmartri migmartri merged commit 22932b1 into chainloop-dev:main Mar 4, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: create project-level token from org api token

3 participants