feat(api): move authorization service to v2 (#10914)

# Which Problems Are Solved

As part of our efforts to simplify the structure and versions of our
APIs, were moving all existing v2beta endpoints to v2 and deprecate
them. They will be removed in Zitadel V5.

# How the Problems Are Solved

- This PR moves the authorization v2beta service and its endpoints to a
corresponding v2 version. The v2beta service and endpoints are
deprecated.
- The docs are moved to the new GA service and its endpoints. The v2beta
is not displayed anymore.
- The comments and have been improved and, where not already done, moved
from swagger annotations to proto.
- All required fields have been marked with (google.api.field_behavior)
= REQUIRED and validation rules have been added where missing.
- The `organization_id` to create an authorization is now required to be
always passed. There's no implicit fallback to the project's
organization anymore.
- The `user_id` filter has been removed in favor of the recently added
`in_user_ids` filter.
- The returned `Authorization` object has been reworked to return
`project`, `organization` and `roles` as objects like the granted `user`
already was.
- Additionally the `roles` now not only contain the granted `role_keys`,
but also the `display_name` and `group`. To implement this the query has
been updated internally. Existing APIs are unchanged and still return
just the keys.

# Additional Changes

None

# Additional Context

- part of https://github.com/zitadel/zitadel/issues/10772
- closes #10746
- requires backport to v4.x

(cherry picked from commit c9ac1ce344)
This commit is contained in:
Livio Spring
2025-10-28 13:11:12 +01:00
parent ad9cc03d57
commit 999e81b74b
21 changed files with 3249 additions and 44 deletions

View File

@@ -41,6 +41,7 @@ import (
app_v2beta "github.com/zitadel/zitadel/internal/api/grpc/app/v2beta" app_v2beta "github.com/zitadel/zitadel/internal/api/grpc/app/v2beta"
application "github.com/zitadel/zitadel/internal/api/grpc/application/v2" application "github.com/zitadel/zitadel/internal/api/grpc/application/v2"
"github.com/zitadel/zitadel/internal/api/grpc/auth" "github.com/zitadel/zitadel/internal/api/grpc/auth"
authorization_v2 "github.com/zitadel/zitadel/internal/api/grpc/authorization/v2"
authorization_v2beta "github.com/zitadel/zitadel/internal/api/grpc/authorization/v2beta" authorization_v2beta "github.com/zitadel/zitadel/internal/api/grpc/authorization/v2beta"
feature_v2 "github.com/zitadel/zitadel/internal/api/grpc/feature/v2" feature_v2 "github.com/zitadel/zitadel/internal/api/grpc/feature/v2"
feature_v2beta "github.com/zitadel/zitadel/internal/api/grpc/feature/v2beta" feature_v2beta "github.com/zitadel/zitadel/internal/api/grpc/feature/v2beta"
@@ -558,6 +559,9 @@ func startAPIs(
if err := apis.RegisterService(ctx, authorization_v2beta.CreateServer(config.SystemDefaults, commands, queries, permissionCheck)); err != nil { if err := apis.RegisterService(ctx, authorization_v2beta.CreateServer(config.SystemDefaults, commands, queries, permissionCheck)); err != nil {
return nil, err return nil, err
} }
if err := apis.RegisterService(ctx, authorization_v2.CreateServer(config.SystemDefaults, commands, queries, permissionCheck)); err != nil {
return nil, err
}
if err := apis.RegisterService(ctx, app_v2beta.CreateServer(commands, queries, permissionCheck)); err != nil { if err := apis.RegisterService(ctx, app_v2beta.CreateServer(commands, queries, permissionCheck)); err != nil {
return nil, err return nil, err
} }

View File

@@ -419,7 +419,7 @@ module.exports = {
}, },
authorization_v2: { authorization_v2: {
specPath: specPath:
".artifacts/openapi3/zitadel/authorization/v2beta/authorization_service.openapi.yaml", ".artifacts/openapi3/zitadel/authorization/v2/authorization_service.openapi.yaml",
outputDir: "docs/apis/resources/authorization_service_v2", outputDir: "docs/apis/resources/authorization_service_v2",
sidebarOptions: { sidebarOptions: {
groupPathsBy: "tag", groupPathsBy: "tag",

View File

@@ -871,18 +871,16 @@ module.exports = {
}, },
{ {
type: "category", type: "category",
label: "Authorizations (Beta)", label: "Authorizations",
link: { link: {
type: "generated-index", type: "generated-index",
title: "Authorization Service API (Beta)", title: "Authorization Service API",
slug: "/apis/resources/authorization_service_v2", slug: "/apis/resources/authorization_service_v2",
description: description:
"AuthorizationService provides methods to manage authorizations for users within your projects and applications.\n" + "AuthorizationService provides methods to manage authorizations for users within your projects and applications.\n" +
"\n" + "\n" +
"For managing permissions and roles for ZITADEL internal resources, like organizations, projects,\n" + "For managing permissions and roles for ZITADEL internal resources, like organizations, projects,\n" +
"users, etc., please use the InternalPermissionService." + "users, etc., please use the InternalPermissionService."
"\n" +
"This API is in beta state. It can AND will continue breaking until a stable version is released.\n"
}, },
items: sidebar_api_authorization_service_v2, items: sidebar_api_authorization_service_v2,
}, },

View File

@@ -0,0 +1,76 @@
package authorization
import (
"context"
"connectrpc.com/connect"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/pkg/grpc/authorization/v2"
)
func (s *Server) CreateAuthorization(ctx context.Context, req *connect.Request[authorization.CreateAuthorizationRequest]) (*connect.Response[authorization.CreateAuthorizationResponse], error) {
grant := &domain.UserGrant{
UserID: req.Msg.UserId,
ProjectID: req.Msg.ProjectId,
RoleKeys: req.Msg.RoleKeys,
ObjectRoot: models.ObjectRoot{
ResourceOwner: req.Msg.GetOrganizationId(),
},
}
grant, err := s.command.AddUserGrant(ctx, grant, s.command.NewPermissionCheckUserGrantWrite(ctx))
if err != nil {
return nil, err
}
return connect.NewResponse(&authorization.CreateAuthorizationResponse{
Id: grant.AggregateID,
CreationDate: timestamppb.New(grant.ChangeDate),
}), nil
}
func (s *Server) UpdateAuthorization(ctx context.Context, request *connect.Request[authorization.UpdateAuthorizationRequest]) (*connect.Response[authorization.UpdateAuthorizationResponse], error) {
userGrant, err := s.command.ChangeUserGrant(ctx, &domain.UserGrant{
ObjectRoot: models.ObjectRoot{
AggregateID: request.Msg.Id,
},
RoleKeys: request.Msg.RoleKeys,
}, true, true, s.command.NewPermissionCheckUserGrantWrite(ctx))
if err != nil {
return nil, err
}
return connect.NewResponse(&authorization.UpdateAuthorizationResponse{
ChangeDate: timestamppb.New(userGrant.ChangeDate),
}), nil
}
func (s *Server) DeleteAuthorization(ctx context.Context, request *connect.Request[authorization.DeleteAuthorizationRequest]) (*connect.Response[authorization.DeleteAuthorizationResponse], error) {
details, err := s.command.RemoveUserGrant(ctx, request.Msg.Id, "", true, s.command.NewPermissionCheckUserGrantDelete(ctx))
if err != nil {
return nil, err
}
return connect.NewResponse(&authorization.DeleteAuthorizationResponse{
DeletionDate: timestamppb.New(details.EventDate),
}), nil
}
func (s *Server) ActivateAuthorization(ctx context.Context, request *connect.Request[authorization.ActivateAuthorizationRequest]) (*connect.Response[authorization.ActivateAuthorizationResponse], error) {
details, err := s.command.ReactivateUserGrant(ctx, request.Msg.Id, "", s.command.NewPermissionCheckUserGrantWrite(ctx))
if err != nil {
return nil, err
}
return connect.NewResponse(&authorization.ActivateAuthorizationResponse{
ChangeDate: timestamppb.New(details.EventDate),
}), nil
}
func (s *Server) DeactivateAuthorization(ctx context.Context, request *connect.Request[authorization.DeactivateAuthorizationRequest]) (*connect.Response[authorization.DeactivateAuthorizationResponse], error) {
details, err := s.command.DeactivateUserGrant(ctx, request.Msg.Id, "", s.command.NewPermissionCheckUserGrantWrite(ctx))
if err != nil {
return nil, err
}
return connect.NewResponse(&authorization.DeactivateAuthorizationResponse{
ChangeDate: timestamppb.New(details.EventDate),
}), nil
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,67 @@
//go:build integration
package authorization_test
import (
"context"
"os"
"testing"
"time"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zitadel/zitadel/internal/integration"
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
)
var (
EmptyCTX context.Context
IAMCTX context.Context
Instance *integration.Instance
InstanceQuery *integration.Instance
InstancePermissionV2 *integration.Instance
)
func TestMain(m *testing.M) {
os.Exit(func() int {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
defer cancel()
EmptyCTX = ctx
Instance = integration.NewInstance(ctx)
InstanceQuery = integration.NewInstance(ctx) // use a separate instance for queries to avoid side effects
IAMCTX = Instance.WithAuthorizationToken(ctx, integration.UserTypeIAMOwner)
InstancePermissionV2 = integration.NewInstance(ctx)
return m.Run()
}())
}
func ensureFeaturePermissionV2Enabled(t *testing.T, instance *integration.Instance) {
ctx := instance.WithAuthorizationToken(EmptyCTX, integration.UserTypeIAMOwner)
f, err := instance.Client.FeatureV2.GetInstanceFeatures(ctx, &feature.GetInstanceFeaturesRequest{
Inheritance: true,
})
require.NoError(t, err)
if f.PermissionCheckV2.GetEnabled() {
return
}
_, err = instance.Client.FeatureV2.SetInstanceFeatures(ctx, &feature.SetInstanceFeaturesRequest{
PermissionCheckV2: gu.Ptr(true),
})
require.NoError(t, err)
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(ctx, 5*time.Minute)
require.EventuallyWithT(t,
func(ttt *assert.CollectT) {
f, err := instance.Client.FeatureV2.GetInstanceFeatures(ctx, &feature.GetInstanceFeaturesRequest{
Inheritance: true,
})
assert.NoError(ttt, err)
if f.PermissionCheckV2.GetEnabled() {
return
}
},
retryDuration,
tick,
"timed out waiting for ensuring instance feature")
}

View File

@@ -0,0 +1,216 @@
package authorization
import (
"context"
"errors"
"connectrpc.com/connect"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/api/grpc/filter/v2"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/pkg/grpc/authorization/v2"
filter_pb "github.com/zitadel/zitadel/pkg/grpc/filter/v2"
)
func (s *Server) ListAuthorizations(ctx context.Context, req *connect.Request[authorization.ListAuthorizationsRequest]) (*connect.Response[authorization.ListAuthorizationsResponse], error) {
queries, err := s.listAuthorizationsRequestToModel(req.Msg)
if err != nil {
return nil, err
}
resp, err := s.query.UserGrants(ctx, queries, false, s.checkPermission)
if err != nil {
return nil, err
}
return connect.NewResponse(&authorization.ListAuthorizationsResponse{
Authorizations: userGrantsToPb(resp.UserGrants),
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, resp.SearchResponse),
}), nil
}
func (s *Server) listAuthorizationsRequestToModel(req *authorization.ListAuthorizationsRequest) (*query.UserGrantsQueries, error) {
offset, limit, asc, err := filter.PaginationPbToQuery(s.systemDefaults, req.Pagination)
if err != nil {
return nil, err
}
queries, err := AuthorizationQueriesToQuery(req.Filters)
if err != nil {
return nil, err
}
return &query.UserGrantsQueries{
SearchRequest: query.SearchRequest{
Offset: offset,
Limit: limit,
Asc: asc,
SortingColumn: authorizationFieldNameToSortingColumn(req.GetSortingColumn()),
},
Queries: queries,
}, nil
}
func authorizationFieldNameToSortingColumn(field authorization.AuthorizationFieldName) query.Column {
switch field {
case authorization.AuthorizationFieldName_AUTHORIZATION_FIELD_NAME_UNSPECIFIED:
return query.UserGrantCreationDate
case authorization.AuthorizationFieldName_AUTHORIZATION_FIELD_NAME_CREATED_DATE:
return query.UserGrantCreationDate
case authorization.AuthorizationFieldName_AUTHORIZATION_FIELD_NAME_CHANGED_DATE:
return query.UserGrantChangeDate
case authorization.AuthorizationFieldName_AUTHORIZATION_FIELD_NAME_ID:
return query.UserGrantID
case authorization.AuthorizationFieldName_AUTHORIZATION_FIELD_NAME_USER_ID:
return query.UserGrantUserID
case authorization.AuthorizationFieldName_AUTHORIZATION_FIELD_NAME_PROJECT_ID:
return query.UserGrantProjectID
case authorization.AuthorizationFieldName_AUTHORIZATION_FIELD_NAME_ORGANIZATION_ID:
return query.UserGrantResourceOwner
case authorization.AuthorizationFieldName_AUTHORIZATION_FIELD_NAME_USER_ORGANIZATION_ID:
return query.UserResourceOwnerCol
default:
return query.UserGrantCreationDate
}
}
func AuthorizationQueriesToQuery(queries []*authorization.AuthorizationsSearchFilter) (q []query.SearchQuery, err error) {
q = make([]query.SearchQuery, len(queries))
for i, query := range queries {
q[i], err = AuthorizationSearchFilterToQuery(query)
if err != nil {
return nil, err
}
}
return q, nil
}
func AuthorizationSearchFilterToQuery(query *authorization.AuthorizationsSearchFilter) (query.SearchQuery, error) {
switch q := query.Filter.(type) {
case *authorization.AuthorizationsSearchFilter_AuthorizationIds:
return AuthorizationIDQueryToModel(q.AuthorizationIds)
case *authorization.AuthorizationsSearchFilter_OrganizationId:
return AuthorizationOrganizationIDQueryToModel(q.OrganizationId)
case *authorization.AuthorizationsSearchFilter_State:
return AuthorizationStateQueryToModel(q.State)
case *authorization.AuthorizationsSearchFilter_InUserIds:
return AuthorizationInUserIDsQueryToModel(q.InUserIds)
case *authorization.AuthorizationsSearchFilter_UserOrganizationId:
return AuthorizationUserOrganizationIDQueryToModel(q.UserOrganizationId)
case *authorization.AuthorizationsSearchFilter_UserPreferredLoginName:
return AuthorizationUserNameQueryToModel(q.UserPreferredLoginName)
case *authorization.AuthorizationsSearchFilter_UserDisplayName:
return AuthorizationDisplayNameQueryToModel(q.UserDisplayName)
case *authorization.AuthorizationsSearchFilter_ProjectId:
return AuthorizationProjectIDQueryToModel(q.ProjectId)
case *authorization.AuthorizationsSearchFilter_ProjectName:
return AuthorizationProjectNameQueryToModel(q.ProjectName)
case *authorization.AuthorizationsSearchFilter_RoleKey:
return AuthorizationRoleKeyQueryToModel(q.RoleKey)
case *authorization.AuthorizationsSearchFilter_ProjectGrantId:
return AuthorizationProjectGrantIDQueryToModel(q.ProjectGrantId)
default:
return nil, errors.New("invalid query")
}
}
func AuthorizationIDQueryToModel(q *filter_pb.InIDsFilter) (query.SearchQuery, error) {
return query.NewUserGrantInIDsSearchQuery(q.Ids)
}
func AuthorizationDisplayNameQueryToModel(q *authorization.UserDisplayNameQuery) (query.SearchQuery, error) {
return query.NewUserGrantDisplayNameQuery(q.DisplayName, filter.TextMethodPbToQuery(q.Method))
}
func AuthorizationOrganizationIDQueryToModel(q *filter_pb.IDFilter) (query.SearchQuery, error) {
return query.NewUserGrantResourceOwnerSearchQuery(q.Id)
}
func AuthorizationProjectIDQueryToModel(q *filter_pb.IDFilter) (query.SearchQuery, error) {
return query.NewUserGrantProjectIDSearchQuery(q.Id)
}
func AuthorizationProjectNameQueryToModel(q *authorization.ProjectNameQuery) (query.SearchQuery, error) {
return query.NewUserGrantProjectNameQuery(q.Name, filter.TextMethodPbToQuery(q.Method))
}
func AuthorizationProjectGrantIDQueryToModel(q *filter_pb.IDFilter) (query.SearchQuery, error) {
return query.NewUserGrantGrantIDSearchQuery(q.Id)
}
func AuthorizationRoleKeyQueryToModel(q *authorization.RoleKeyQuery) (query.SearchQuery, error) {
return query.NewUserGrantRoleQuery(q.Key)
}
func AuthorizationUserNameQueryToModel(q *authorization.UserPreferredLoginNameQuery) (query.SearchQuery, error) {
return query.NewUserGrantUsernameQuery(q.LoginName, filter.TextMethodPbToQuery(q.Method))
}
func AuthorizationInUserIDsQueryToModel(q *filter_pb.InIDsFilter) (query.SearchQuery, error) {
return query.NewUserGrantInUserIDsSearchQuery(q.Ids)
}
func AuthorizationUserOrganizationIDQueryToModel(q *filter_pb.IDFilter) (query.SearchQuery, error) {
return query.NewUserGrantUserResourceOwnerSearchQuery(q.Id)
}
func AuthorizationStateQueryToModel(q *authorization.StateQuery) (query.SearchQuery, error) {
return query.NewUserGrantStateQuery(domain.UserGrantState(q.State))
}
func userGrantsToPb(userGrants []*query.UserGrant) []*authorization.Authorization {
o := make([]*authorization.Authorization, len(userGrants))
for i, grant := range userGrants {
o[i] = userGrantToPb(grant)
}
return o
}
func userGrantToPb(userGrant *query.UserGrant) *authorization.Authorization {
return &authorization.Authorization{
Id: userGrant.ID,
CreationDate: timestamppb.New(userGrant.CreationDate),
ChangeDate: timestamppb.New(userGrant.ChangeDate),
Project: &authorization.Project{
Id: userGrant.ProjectID,
Name: userGrant.ProjectName,
OrganizationId: userGrant.ProjectResourceOwner,
},
Organization: &authorization.Organization{
Id: userGrant.ResourceOwner,
Name: userGrant.OrgName,
},
User: &authorization.User{
Id: userGrant.UserID,
PreferredLoginName: userGrant.PreferredLoginName,
DisplayName: userGrant.DisplayName,
AvatarUrl: userGrant.AvatarURL,
OrganizationId: userGrant.UserResourceOwner,
},
State: userGrantStateToPb(userGrant.State),
Roles: rolesToPb(userGrant.RoleInformation),
}
}
func rolesToPb(roles []query.Role) []*authorization.Role {
r := make([]*authorization.Role, len(roles))
for i, role := range roles {
r[i] = &authorization.Role{
Key: role.Key,
DisplayName: role.DisplayName,
Group: role.GroupName,
}
}
return r
}
func userGrantStateToPb(state domain.UserGrantState) authorization.State {
switch state {
case domain.UserGrantStateActive:
return authorization.State_STATE_ACTIVE
case domain.UserGrantStateInactive:
return authorization.State_STATE_INACTIVE
case domain.UserGrantStateUnspecified, domain.UserGrantStateRemoved:
return authorization.State_STATE_UNSPECIFIED
default:
return authorization.State_STATE_UNSPECIFIED
}
}

View File

@@ -0,0 +1,60 @@
package authorization
import (
"net/http"
"connectrpc.com/connect"
"google.golang.org/protobuf/reflect/protoreflect"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/config/systemdefaults"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/pkg/grpc/authorization/v2"
"github.com/zitadel/zitadel/pkg/grpc/authorization/v2/authorizationconnect"
)
var _ authorizationconnect.AuthorizationServiceHandler = (*Server)(nil)
type Server struct {
systemDefaults systemdefaults.SystemDefaults
command *command.Commands
query *query.Queries
checkPermission domain.PermissionCheck
}
func CreateServer(
systemDefaults systemdefaults.SystemDefaults,
command *command.Commands,
query *query.Queries,
checkPermission domain.PermissionCheck,
) *Server {
return &Server{
systemDefaults: systemDefaults,
command: command,
query: query,
checkPermission: checkPermission,
}
}
func (s *Server) RegisterConnectServer(interceptors ...connect.Interceptor) (string, http.Handler) {
return authorizationconnect.NewAuthorizationServiceHandler(s, connect.WithInterceptors(interceptors...))
}
func (s *Server) FileDescriptor() protoreflect.FileDescriptor {
return authorization.File_zitadel_authorization_v2_authorization_service_proto
}
func (s *Server) AppName() string {
return authorization.AuthorizationService_ServiceDesc.ServiceName
}
func (s *Server) MethodPrefix() string {
return authorization.AuthorizationService_ServiceDesc.ServiceName
}
func (s *Server) AuthMethods() authz.MethodMapping {
return authorization.AuthorizationService_AuthMethods
}

View File

@@ -487,17 +487,17 @@ func createAuthorization(ctx context.Context, instance *integration.Instance, t
return createAuthorizationForProject(ctx, instance, t, orgID, userID, projectName, projectResp.GetId()) return createAuthorizationForProject(ctx, instance, t, orgID, userID, projectName, projectResp.GetId())
} }
func createAuthorizationForProject(ctx context.Context, instance *integration.Instance, t *testing.T, orgID, userID, projectName, projectID string) *authorization.Authorization { func createAuthorizationForProject(ctx context.Context, instance *integration.Instance, t *testing.T, organizationID, userID, projectName, projectID string) *authorization.Authorization {
userResp, err := instance.Client.UserV2.GetUserByID(ctx, &user.GetUserByIDRequest{UserId: userID}) userResp, err := instance.Client.UserV2.GetUserByID(ctx, &user.GetUserByIDRequest{UserId: userID})
require.NoError(t, err) require.NoError(t, err)
authResp := instance.CreateAuthorizationProject(t, ctx, projectID, userID) authResp := instance.CreateAuthorizationProject(t, ctx, projectID, userID, organizationID)
return &authorization.Authorization{ return &authorization.Authorization{
Id: authResp.GetId(), Id: authResp.GetId(),
ProjectId: projectID, ProjectId: projectID,
ProjectName: projectName, ProjectName: projectName,
ProjectOrganizationId: orgID, ProjectOrganizationId: organizationID,
OrganizationId: orgID, OrganizationId: organizationID,
CreationDate: authResp.GetCreationDate(), CreationDate: authResp.GetCreationDate(),
ChangeDate: authResp.GetCreationDate(), ChangeDate: authResp.GetCreationDate(),
State: 1, State: 1,

View File

@@ -961,12 +961,12 @@ func createProjectGrant(ctx context.Context, t *testing.T, projectID, grantedOrg
}, retryDuration, tick) }, retryDuration, tick)
} }
func createProjectUserGrant(ctx context.Context, t *testing.T, orgID, projectID, userID string) { func createProjectUserGrant(ctx context.Context, t *testing.T, organizationID, projectID, userID string) {
resp := Instance.CreateAuthorizationProject(t, ctx, projectID, userID) resp := Instance.CreateAuthorizationProject(t, ctx, projectID, userID, organizationID)
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(ctx, time.Minute) retryDuration, tick := integration.WaitForAndTickWithMaxDuration(ctx, time.Minute)
require.EventuallyWithT(t, func(collect *assert.CollectT) { require.EventuallyWithT(t, func(collect *assert.CollectT) {
_, err := Instance.Client.Mgmt.GetUserGrantByID(integration.SetOrgID(ctx, orgID), &mgmt.GetUserGrantByIDRequest{ _, err := Instance.Client.Mgmt.GetUserGrantByID(integration.SetOrgID(ctx, organizationID), &mgmt.GetUserGrantByIDRequest{
UserId: userID, UserId: userID,
GrantId: resp.GetId(), GrantId: resp.GetId(),
}) })

View File

@@ -692,12 +692,12 @@ func createOIDCApplication(ctx context.Context, t *testing.T, projectRoleCheck,
return project.GetId(), clientV2.GetClientId() return project.GetId(), clientV2.GetClientId()
} }
func createProjectUserGrant(ctx context.Context, t *testing.T, orgID, projectID, userID string) { func createProjectUserGrant(ctx context.Context, t *testing.T, organizationID, projectID, userID string) {
resp := Instance.CreateAuthorizationProject(t, ctx, projectID, userID) resp := Instance.CreateAuthorizationProject(t, ctx, projectID, userID, organizationID)
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(ctx, time.Minute) retryDuration, tick := integration.WaitForAndTickWithMaxDuration(ctx, time.Minute)
require.EventuallyWithT(t, func(collect *assert.CollectT) { require.EventuallyWithT(t, func(collect *assert.CollectT) {
_, err := Instance.Client.Mgmt.GetUserGrantByID(integration.SetOrgID(ctx, orgID), &mgmt.GetUserGrantByIDRequest{ _, err := Instance.Client.Mgmt.GetUserGrantByID(integration.SetOrgID(ctx, organizationID), &mgmt.GetUserGrantByIDRequest{
UserId: userID, UserId: userID,
GrantId: resp.GetId(), GrantId: resp.GetId(),
}) })

View File

@@ -715,12 +715,12 @@ func createProjectGrant(ctx context.Context, t *testing.T, projectID, grantedOrg
}, retryDuration, tick) }, retryDuration, tick)
} }
func createProjectUserGrant(ctx context.Context, t *testing.T, orgID, projectID, userID string) { func createProjectUserGrant(ctx context.Context, t *testing.T, organizationID, projectID, userID string) {
resp := Instance.CreateAuthorizationProject(t, ctx, projectID, userID) resp := Instance.CreateAuthorizationProject(t, ctx, projectID, userID, organizationID)
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(ctx, time.Minute) retryDuration, tick := integration.WaitForAndTickWithMaxDuration(ctx, time.Minute)
require.EventuallyWithT(t, func(collect *assert.CollectT) { require.EventuallyWithT(t, func(collect *assert.CollectT) {
_, err := Instance.Client.Mgmt.GetUserGrantByID(integration.SetOrgID(ctx, orgID), &mgmt.GetUserGrantByIDRequest{ _, err := Instance.Client.Mgmt.GetUserGrantByID(integration.SetOrgID(ctx, organizationID), &mgmt.GetUserGrantByIDRequest{
UserId: userID, UserId: userID,
GrantId: resp.GetId(), GrantId: resp.GetId(),
}) })

View File

@@ -27,7 +27,8 @@ import (
app_v2beta "github.com/zitadel/zitadel/pkg/grpc/app/v2beta" app_v2beta "github.com/zitadel/zitadel/pkg/grpc/app/v2beta"
"github.com/zitadel/zitadel/pkg/grpc/application/v2" "github.com/zitadel/zitadel/pkg/grpc/application/v2"
"github.com/zitadel/zitadel/pkg/grpc/auth" "github.com/zitadel/zitadel/pkg/grpc/auth"
authorization "github.com/zitadel/zitadel/pkg/grpc/authorization/v2beta" authorization_v2 "github.com/zitadel/zitadel/pkg/grpc/authorization/v2"
authorization_v2beta "github.com/zitadel/zitadel/pkg/grpc/authorization/v2beta"
"github.com/zitadel/zitadel/pkg/grpc/feature/v2" "github.com/zitadel/zitadel/pkg/grpc/feature/v2"
feature_v2beta "github.com/zitadel/zitadel/pkg/grpc/feature/v2beta" feature_v2beta "github.com/zitadel/zitadel/pkg/grpc/feature/v2beta"
"github.com/zitadel/zitadel/pkg/grpc/idp" "github.com/zitadel/zitadel/pkg/grpc/idp"
@@ -89,7 +90,8 @@ type Client struct {
ApplicationV2 application.ApplicationServiceClient ApplicationV2 application.ApplicationServiceClient
InternalPermissionv2Beta internal_permission_v2beta.InternalPermissionServiceClient InternalPermissionv2Beta internal_permission_v2beta.InternalPermissionServiceClient
InternalPermissionV2 internal_permission_v2.InternalPermissionServiceClient InternalPermissionV2 internal_permission_v2.InternalPermissionServiceClient
AuthorizationV2Beta authorization.AuthorizationServiceClient AuthorizationV2Beta authorization_v2beta.AuthorizationServiceClient //nolint:staticcheck // deprecated, but still used in tests
AuthorizationV2 authorization_v2.AuthorizationServiceClient
} }
func NewDefaultClient(ctx context.Context) (*Client, error) { func NewDefaultClient(ctx context.Context) (*Client, error) {
@@ -135,7 +137,8 @@ func newClient(ctx context.Context, target string) (*Client, error) {
ApplicationV2: application.NewApplicationServiceClient(cc), ApplicationV2: application.NewApplicationServiceClient(cc),
InternalPermissionv2Beta: internal_permission_v2beta.NewInternalPermissionServiceClient(cc), InternalPermissionv2Beta: internal_permission_v2beta.NewInternalPermissionServiceClient(cc),
InternalPermissionV2: internal_permission_v2.NewInternalPermissionServiceClient(cc), InternalPermissionV2: internal_permission_v2.NewInternalPermissionServiceClient(cc),
AuthorizationV2Beta: authorization.NewAuthorizationServiceClient(cc), AuthorizationV2Beta: authorization_v2beta.NewAuthorizationServiceClient(cc),
AuthorizationV2: authorization_v2.NewAuthorizationServiceClient(cc),
} }
return client, client.pollHealth(ctx) return client, client.pollHealth(ctx)
} }
@@ -944,20 +947,23 @@ func (i *Instance) ActivateProjectGrant(ctx context.Context, t *testing.T, proje
return resp return resp
} }
func (i *Instance) CreateAuthorizationProject(t *testing.T, ctx context.Context, projectID, userID string) *authorization.CreateAuthorizationResponse { func (i *Instance) CreateAuthorizationProject(t *testing.T, ctx context.Context, projectID, userID, organizationID string, roles ...string) *authorization_v2.CreateAuthorizationResponse {
resp, err := i.Client.AuthorizationV2Beta.CreateAuthorization(ctx, &authorization.CreateAuthorizationRequest{ resp, err := i.Client.AuthorizationV2.CreateAuthorization(ctx, &authorization_v2.CreateAuthorizationRequest{
UserId: userID, UserId: userID,
ProjectId: projectID, ProjectId: projectID,
OrganizationId: organizationID,
RoleKeys: roles,
}) })
require.NoError(t, err) require.NoError(t, err)
return resp return resp
} }
func (i *Instance) CreateAuthorizationProjectGrant(t *testing.T, ctx context.Context, projectID, orgID, userID string) *authorization.CreateAuthorizationResponse { func (i *Instance) CreateAuthorizationProjectGrant(t *testing.T, ctx context.Context, projectID, organizationID, userID string, roles ...string) *authorization_v2.CreateAuthorizationResponse {
resp, err := i.Client.AuthorizationV2Beta.CreateAuthorization(ctx, &authorization.CreateAuthorizationRequest{ resp, err := i.Client.AuthorizationV2.CreateAuthorization(ctx, &authorization_v2.CreateAuthorizationRequest{
UserId: userID, UserId: userID,
ProjectId: projectID, ProjectId: projectID,
OrganizationId: gu.Ptr(orgID), OrganizationId: organizationID,
RoleKeys: roles,
}) })
require.NoError(t, err) require.NoError(t, err)
return resp return resp

View File

@@ -3,7 +3,9 @@ package query
import ( import (
"context" "context"
"database/sql" "database/sql"
"encoding/json"
"errors" "errors"
"fmt"
"slices" "slices"
"time" "time"
@@ -21,11 +23,12 @@ import (
type UserGrant struct { type UserGrant struct {
// ID represents the aggregate id (id of the user grant) // ID represents the aggregate id (id of the user grant)
ID string `json:"id,omitempty"` ID string `json:"id,omitempty"`
CreationDate time.Time `json:"creation_date,omitempty"` CreationDate time.Time `json:"creation_date,omitempty"`
ChangeDate time.Time `json:"change_date,omitempty"` ChangeDate time.Time `json:"change_date,omitempty"`
Sequence uint64 `json:"sequence,omitempty"` Sequence uint64 `json:"sequence,omitempty"`
Roles database.TextArray[string] `json:"roles,omitempty"` Roles database.TextArray[string] `json:"roles,omitempty"`
RoleInformation []Role `json:"-"`
// GrantID represents the project grant id // GrantID represents the project grant id
GrantID string `json:"grant_id,omitempty"` GrantID string `json:"grant_id,omitempty"`
State domain.UserGrantState `json:"state,omitempty"` State domain.UserGrantState `json:"state,omitempty"`
@@ -56,6 +59,12 @@ type UserGrant struct {
GrantedOrgDomain string `json:"granted_org_domain,omitempty"` GrantedOrgDomain string `json:"granted_org_domain,omitempty"`
} }
type Role struct {
Key string `json:"role_key,omitempty"`
DisplayName string `json:"display_name,omitempty"`
GroupName string `json:"group_name,omitempty"`
}
type UserGrants struct { type UserGrants struct {
SearchResponse SearchResponse
UserGrants []*UserGrant UserGrants []*UserGrant
@@ -390,6 +399,7 @@ func prepareUserGrantQuery() (sq.SelectBuilder, func(*sql.Row) (*UserGrant, erro
UserGrantSequence.identifier(), UserGrantSequence.identifier(),
UserGrantGrantID.identifier(), UserGrantGrantID.identifier(),
UserGrantRoles.identifier(), UserGrantRoles.identifier(),
"roles.role_information",
UserGrantState.identifier(), UserGrantState.identifier(),
UserGrantUserID.identifier(), UserGrantUserID.identifier(),
@@ -426,6 +436,7 @@ func prepareUserGrantQuery() (sq.SelectBuilder, func(*sql.Row) (*UserGrant, erro
LeftJoin(join(ProjectGrantColumnGrantID, UserGrantGrantID) + " AND " + ProjectGrantColumnProjectID.identifier() + " = " + UserGrantProjectID.identifier()). LeftJoin(join(ProjectGrantColumnGrantID, UserGrantGrantID) + " AND " + ProjectGrantColumnProjectID.identifier() + " = " + UserGrantProjectID.identifier()).
LeftJoin(join(GrantedOrgColumnId, ProjectGrantColumnGrantedOrgID)). LeftJoin(join(GrantedOrgColumnId, ProjectGrantColumnGrantedOrgID)).
LeftJoin(join(LoginNameUserIDCol, UserGrantUserID)). LeftJoin(join(LoginNameUserIDCol, UserGrantUserID)).
LeftJoin("LATERAL (" + rolesInfoQuery + ") as roles ON true").
Where( Where(
sq.Eq{LoginNameIsPrimaryCol.identifier(): true}, sq.Eq{LoginNameIsPrimaryCol.identifier(): true},
).PlaceholderFormat(sq.Dollar), ).PlaceholderFormat(sq.Dollar),
@@ -433,6 +444,8 @@ func prepareUserGrantQuery() (sq.SelectBuilder, func(*sql.Row) (*UserGrant, erro
g := new(UserGrant) g := new(UserGrant)
var ( var (
roles []byte
username sql.NullString username sql.NullString
firstName sql.NullString firstName sql.NullString
userType sql.NullInt32 userType sql.NullInt32
@@ -463,6 +476,7 @@ func prepareUserGrantQuery() (sq.SelectBuilder, func(*sql.Row) (*UserGrant, erro
&g.Sequence, &g.Sequence,
&g.GrantID, &g.GrantID,
&g.Roles, &g.Roles,
&roles,
&g.State, &g.State,
&g.UserID, &g.UserID,
@@ -496,6 +510,12 @@ func prepareUserGrantQuery() (sq.SelectBuilder, func(*sql.Row) (*UserGrant, erro
} }
return nil, zerrors.ThrowInternal(err, "QUERY-oQPcP", "Errors.Internal") return nil, zerrors.ThrowInternal(err, "QUERY-oQPcP", "Errors.Internal")
} }
if len(roles) > 0 {
err = json.Unmarshal(roles, &g.RoleInformation)
if err != nil {
return nil, err
}
}
g.Username = username.String g.Username = username.String
g.UserType = domain.UserType(userType.Int32) g.UserType = domain.UserType(userType.Int32)
@@ -527,6 +547,7 @@ func prepareUserGrantsQuery() (sq.SelectBuilder, func(*sql.Rows) (*UserGrants, e
UserGrantSequence.identifier(), UserGrantSequence.identifier(),
UserGrantGrantID.identifier(), UserGrantGrantID.identifier(),
UserGrantRoles.identifier(), UserGrantRoles.identifier(),
"roles.role_information",
UserGrantState.identifier(), UserGrantState.identifier(),
UserGrantUserID.identifier(), UserGrantUserID.identifier(),
@@ -565,6 +586,7 @@ func prepareUserGrantsQuery() (sq.SelectBuilder, func(*sql.Rows) (*UserGrants, e
LeftJoin(join(ProjectGrantColumnGrantID, UserGrantGrantID) + " AND " + ProjectGrantColumnProjectID.identifier() + " = " + UserGrantProjectID.identifier()). LeftJoin(join(ProjectGrantColumnGrantID, UserGrantGrantID) + " AND " + ProjectGrantColumnProjectID.identifier() + " = " + UserGrantProjectID.identifier()).
LeftJoin(join(GrantedOrgColumnId, ProjectGrantColumnGrantedOrgID)). LeftJoin(join(GrantedOrgColumnId, ProjectGrantColumnGrantedOrgID)).
LeftJoin(join(LoginNameUserIDCol, UserGrantUserID)). LeftJoin(join(LoginNameUserIDCol, UserGrantUserID)).
LeftJoin("LATERAL (" + rolesInfoQuery + ") as roles ON true").
Where( Where(
sq.Eq{LoginNameIsPrimaryCol.identifier(): true}, sq.Eq{LoginNameIsPrimaryCol.identifier(): true},
).PlaceholderFormat(sq.Dollar), ).PlaceholderFormat(sq.Dollar),
@@ -575,6 +597,8 @@ func prepareUserGrantsQuery() (sq.SelectBuilder, func(*sql.Rows) (*UserGrants, e
g := new(UserGrant) g := new(UserGrant)
var ( var (
roles []byte
username sql.NullString username sql.NullString
userType sql.NullInt32 userType sql.NullInt32
userOwner sql.NullString userOwner sql.NullString
@@ -605,6 +629,7 @@ func prepareUserGrantsQuery() (sq.SelectBuilder, func(*sql.Rows) (*UserGrants, e
&g.Sequence, &g.Sequence,
&g.GrantID, &g.GrantID,
&g.Roles, &g.Roles,
&roles,
&g.State, &g.State,
&g.UserID, &g.UserID,
@@ -637,6 +662,12 @@ func prepareUserGrantsQuery() (sq.SelectBuilder, func(*sql.Rows) (*UserGrants, e
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(roles) > 0 {
err = json.Unmarshal(roles, &g.RoleInformation)
if err != nil {
return nil, err
}
}
g.Username = username.String g.Username = username.String
g.UserType = domain.UserType(userType.Int32) g.UserType = domain.UserType(userType.Int32)
@@ -672,3 +703,21 @@ func prepareUserGrantsQuery() (sq.SelectBuilder, func(*sql.Rows) (*UserGrants, e
}, nil }, nil
} }
} }
var rolesInfoQuery = fmt.Sprintf(
`SELECT JSON_AGG(
JSON_BUILD_OBJECT(
'role_key', pr.role_key,
'display_name', pr.display_name,
'group_name', pr.group_name
)
) as role_information
FROM %[1]s pr
WHERE pr.instance_id = %[2]s
AND pr.project_id = %[3]s
AND pr.role_key = ANY(%[4]s)`,
projectRolesTable.identifier(),
UserGrantInstanceID.identifier(),
UserGrantProjectID.identifier(),
UserGrantRoles.identifier(),
)

View File

@@ -21,6 +21,7 @@ var (
", projections.user_grants5.sequence" + ", projections.user_grants5.sequence" +
", projections.user_grants5.grant_id" + ", projections.user_grants5.grant_id" +
", projections.user_grants5.roles" + ", projections.user_grants5.roles" +
", roles.role_information" +
", projections.user_grants5.state" + ", projections.user_grants5.state" +
", projections.user_grants5.user_id" + ", projections.user_grants5.user_id" +
", projections.users14.username" + ", projections.users14.username" +
@@ -52,6 +53,7 @@ var (
" LEFT JOIN projections.project_grants4 ON projections.user_grants5.grant_id = projections.project_grants4.grant_id AND projections.user_grants5.instance_id = projections.project_grants4.instance_id AND projections.project_grants4.project_id = projections.user_grants5.project_id" + " LEFT JOIN projections.project_grants4 ON projections.user_grants5.grant_id = projections.project_grants4.grant_id AND projections.user_grants5.instance_id = projections.project_grants4.instance_id AND projections.project_grants4.project_id = projections.user_grants5.project_id" +
" LEFT JOIN projections.orgs1 AS granted_orgs ON projections.project_grants4.granted_org_id = granted_orgs.id AND projections.project_grants4.instance_id = granted_orgs.instance_id" + " LEFT JOIN projections.orgs1 AS granted_orgs ON projections.project_grants4.granted_org_id = granted_orgs.id AND projections.project_grants4.instance_id = granted_orgs.instance_id" +
" LEFT JOIN projections.login_names3 ON projections.user_grants5.user_id = projections.login_names3.user_id AND projections.user_grants5.instance_id = projections.login_names3.instance_id" + " LEFT JOIN projections.login_names3 ON projections.user_grants5.user_id = projections.login_names3.user_id AND projections.user_grants5.instance_id = projections.login_names3.instance_id" +
" LEFT JOIN LATERAL (SELECT JSON_AGG( JSON_BUILD_OBJECT( 'role_key', pr.role_key, 'display_name', pr.display_name, 'group_name', pr.group_name ) ) as role_information FROM projections.project_roles4 pr WHERE pr.instance_id = projections.user_grants5.instance_id AND pr.project_id = projections.user_grants5.project_id AND pr.role_key = ANY(projections.user_grants5.roles)) as roles ON true " +
" WHERE projections.login_names3.is_primary = $1") " WHERE projections.login_names3.is_primary = $1")
userGrantCols = []string{ userGrantCols = []string{
"id", "id",
@@ -60,6 +62,7 @@ var (
"sequence", "sequence",
"grant_id", "grant_id",
"roles", "roles",
"role_information",
"state", "state",
"user_id", "user_id",
"username", "username",
@@ -90,6 +93,7 @@ var (
", projections.user_grants5.sequence" + ", projections.user_grants5.sequence" +
", projections.user_grants5.grant_id" + ", projections.user_grants5.grant_id" +
", projections.user_grants5.roles" + ", projections.user_grants5.roles" +
", roles.role_information" +
", projections.user_grants5.state" + ", projections.user_grants5.state" +
", projections.user_grants5.user_id" + ", projections.user_grants5.user_id" +
", projections.users14.username" + ", projections.users14.username" +
@@ -122,6 +126,7 @@ var (
" LEFT JOIN projections.project_grants4 ON projections.user_grants5.grant_id = projections.project_grants4.grant_id AND projections.user_grants5.instance_id = projections.project_grants4.instance_id AND projections.project_grants4.project_id = projections.user_grants5.project_id" + " LEFT JOIN projections.project_grants4 ON projections.user_grants5.grant_id = projections.project_grants4.grant_id AND projections.user_grants5.instance_id = projections.project_grants4.instance_id AND projections.project_grants4.project_id = projections.user_grants5.project_id" +
" LEFT JOIN projections.orgs1 AS granted_orgs ON projections.project_grants4.granted_org_id = granted_orgs.id AND projections.project_grants4.instance_id = granted_orgs.instance_id" + " LEFT JOIN projections.orgs1 AS granted_orgs ON projections.project_grants4.granted_org_id = granted_orgs.id AND projections.project_grants4.instance_id = granted_orgs.instance_id" +
" LEFT JOIN projections.login_names3 ON projections.user_grants5.user_id = projections.login_names3.user_id AND projections.user_grants5.instance_id = projections.login_names3.instance_id" + " LEFT JOIN projections.login_names3 ON projections.user_grants5.user_id = projections.login_names3.user_id AND projections.user_grants5.instance_id = projections.login_names3.instance_id" +
" LEFT JOIN LATERAL (SELECT JSON_AGG( JSON_BUILD_OBJECT( 'role_key', pr.role_key, 'display_name', pr.display_name, 'group_name', pr.group_name ) ) as role_information FROM projections.project_roles4 pr WHERE pr.instance_id = projections.user_grants5.instance_id AND pr.project_id = projections.user_grants5.project_id AND pr.role_key = ANY(projections.user_grants5.roles)) as roles ON true " +
" WHERE projections.login_names3.is_primary = $1") " WHERE projections.login_names3.is_primary = $1")
userGrantsCols = append( userGrantsCols = append(
userGrantCols, userGrantCols,
@@ -172,6 +177,7 @@ func Test_UserGrantPrepares(t *testing.T) {
20211111, 20211111,
"grant-id", "grant-id",
database.TextArray[string]{"role-key"}, database.TextArray[string]{"role-key"},
`[{"display_name":"displayName","group_name":"groupName","role_key":"role-key"}]`,
domain.UserGrantStateActive, domain.UserGrantStateActive,
"user-id", "user-id",
"username", "username",
@@ -203,6 +209,7 @@ func Test_UserGrantPrepares(t *testing.T) {
ChangeDate: testNow, ChangeDate: testNow,
Sequence: 20211111, Sequence: 20211111,
Roles: database.TextArray[string]{"role-key"}, Roles: database.TextArray[string]{"role-key"},
RoleInformation: []Role{{Key: "role-key", DisplayName: "displayName", GroupName: "groupName"}},
GrantID: "grant-id", GrantID: "grant-id",
State: domain.UserGrantStateActive, State: domain.UserGrantStateActive,
UserID: "user-id", UserID: "user-id",
@@ -242,6 +249,7 @@ func Test_UserGrantPrepares(t *testing.T) {
20211111, 20211111,
"grant-id", "grant-id",
database.TextArray[string]{"role-key"}, database.TextArray[string]{"role-key"},
`[{"display_name":"displayName","group_name":"groupName","role_key":"role-key"}]`,
domain.UserGrantStateActive, domain.UserGrantStateActive,
"user-id", "user-id",
"username", "username",
@@ -273,6 +281,7 @@ func Test_UserGrantPrepares(t *testing.T) {
ChangeDate: testNow, ChangeDate: testNow,
Sequence: 20211111, Sequence: 20211111,
Roles: database.TextArray[string]{"role-key"}, Roles: database.TextArray[string]{"role-key"},
RoleInformation: []Role{{Key: "role-key", DisplayName: "displayName", GroupName: "groupName"}},
GrantID: "grant-id", GrantID: "grant-id",
State: domain.UserGrantStateActive, State: domain.UserGrantStateActive,
UserID: "user-id", UserID: "user-id",
@@ -312,6 +321,7 @@ func Test_UserGrantPrepares(t *testing.T) {
20211111, 20211111,
"grant-id", "grant-id",
database.TextArray[string]{"role-key"}, database.TextArray[string]{"role-key"},
`[{"display_name":"displayName","group_name":"groupName","role_key":"role-key"}]`,
domain.UserGrantStateActive, domain.UserGrantStateActive,
"user-id", "user-id",
"username", "username",
@@ -343,6 +353,7 @@ func Test_UserGrantPrepares(t *testing.T) {
ChangeDate: testNow, ChangeDate: testNow,
Sequence: 20211111, Sequence: 20211111,
Roles: database.TextArray[string]{"role-key"}, Roles: database.TextArray[string]{"role-key"},
RoleInformation: []Role{{Key: "role-key", DisplayName: "displayName", GroupName: "groupName"}},
GrantID: "grant-id", GrantID: "grant-id",
State: domain.UserGrantStateActive, State: domain.UserGrantStateActive,
UserID: "user-id", UserID: "user-id",
@@ -382,6 +393,7 @@ func Test_UserGrantPrepares(t *testing.T) {
20211111, 20211111,
"grant-id", "grant-id",
database.TextArray[string]{"role-key"}, database.TextArray[string]{"role-key"},
`[{"display_name":"displayName","group_name":"groupName","role_key":"role-key"}]`,
domain.UserGrantStateActive, domain.UserGrantStateActive,
"user-id", "user-id",
"username", "username",
@@ -413,6 +425,7 @@ func Test_UserGrantPrepares(t *testing.T) {
ChangeDate: testNow, ChangeDate: testNow,
Sequence: 20211111, Sequence: 20211111,
Roles: database.TextArray[string]{"role-key"}, Roles: database.TextArray[string]{"role-key"},
RoleInformation: []Role{{Key: "role-key", DisplayName: "displayName", GroupName: "groupName"}},
GrantID: "grant-id", GrantID: "grant-id",
State: domain.UserGrantStateActive, State: domain.UserGrantStateActive,
UserID: "user-id", UserID: "user-id",
@@ -452,6 +465,7 @@ func Test_UserGrantPrepares(t *testing.T) {
20211111, 20211111,
"grant-id", "grant-id",
database.TextArray[string]{"role-key"}, database.TextArray[string]{"role-key"},
`[{"display_name":"displayName","group_name":"groupName","role_key":"role-key"}]`,
domain.UserGrantStateActive, domain.UserGrantStateActive,
"user-id", "user-id",
"username", "username",
@@ -483,6 +497,7 @@ func Test_UserGrantPrepares(t *testing.T) {
ChangeDate: testNow, ChangeDate: testNow,
Sequence: 20211111, Sequence: 20211111,
Roles: database.TextArray[string]{"role-key"}, Roles: database.TextArray[string]{"role-key"},
RoleInformation: []Role{{Key: "role-key", DisplayName: "displayName", GroupName: "groupName"}},
GrantID: "grant-id", GrantID: "grant-id",
State: domain.UserGrantStateActive, State: domain.UserGrantStateActive,
UserID: "user-id", UserID: "user-id",
@@ -552,6 +567,7 @@ func Test_UserGrantPrepares(t *testing.T) {
20211111, 20211111,
"grant-id", "grant-id",
database.TextArray[string]{"role-key"}, database.TextArray[string]{"role-key"},
`[{"display_name":"displayName","group_name":"groupName","role_key":"role-key"}]`,
domain.UserGrantStateActive, domain.UserGrantStateActive,
"user-id", "user-id",
"username", "username",
@@ -589,6 +605,7 @@ func Test_UserGrantPrepares(t *testing.T) {
ChangeDate: testNow, ChangeDate: testNow,
Sequence: 20211111, Sequence: 20211111,
Roles: database.TextArray[string]{"role-key"}, Roles: database.TextArray[string]{"role-key"},
RoleInformation: []Role{{Key: "role-key", DisplayName: "displayName", GroupName: "groupName"}},
GrantID: "grant-id", GrantID: "grant-id",
State: domain.UserGrantStateActive, State: domain.UserGrantStateActive,
UserID: "user-id", UserID: "user-id",
@@ -631,6 +648,7 @@ func Test_UserGrantPrepares(t *testing.T) {
20211111, 20211111,
"grant-id", "grant-id",
database.TextArray[string]{"role-key"}, database.TextArray[string]{"role-key"},
`[{"display_name":"displayName","group_name":"groupName","role_key":"role-key"}]`,
domain.UserGrantStateActive, domain.UserGrantStateActive,
"user-id", "user-id",
"username", "username",
@@ -668,6 +686,7 @@ func Test_UserGrantPrepares(t *testing.T) {
ChangeDate: testNow, ChangeDate: testNow,
Sequence: 20211111, Sequence: 20211111,
Roles: database.TextArray[string]{"role-key"}, Roles: database.TextArray[string]{"role-key"},
RoleInformation: []Role{{Key: "role-key", DisplayName: "displayName", GroupName: "groupName"}},
GrantID: "grant-id", GrantID: "grant-id",
State: domain.UserGrantStateActive, State: domain.UserGrantStateActive,
UserID: "user-id", UserID: "user-id",
@@ -710,6 +729,7 @@ func Test_UserGrantPrepares(t *testing.T) {
20211111, 20211111,
"grant-id", "grant-id",
database.TextArray[string]{"role-key"}, database.TextArray[string]{"role-key"},
`[{"display_name":"displayName","group_name":"groupName","role_key":"role-key"}]`,
domain.UserGrantStateActive, domain.UserGrantStateActive,
"user-id", "user-id",
"username", "username",
@@ -747,6 +767,7 @@ func Test_UserGrantPrepares(t *testing.T) {
ChangeDate: testNow, ChangeDate: testNow,
Sequence: 20211111, Sequence: 20211111,
Roles: database.TextArray[string]{"role-key"}, Roles: database.TextArray[string]{"role-key"},
RoleInformation: []Role{{Key: "role-key", DisplayName: "displayName", GroupName: "groupName"}},
GrantID: "grant-id", GrantID: "grant-id",
State: domain.UserGrantStateActive, State: domain.UserGrantStateActive,
UserID: "user-id", UserID: "user-id",
@@ -789,6 +810,7 @@ func Test_UserGrantPrepares(t *testing.T) {
20211111, 20211111,
"grant-id", "grant-id",
database.TextArray[string]{"role-key"}, database.TextArray[string]{"role-key"},
`[{"display_name":"displayName","group_name":"groupName","role_key":"role-key"}]`,
domain.UserGrantStateActive, domain.UserGrantStateActive,
"user-id", "user-id",
"username", "username",
@@ -826,6 +848,7 @@ func Test_UserGrantPrepares(t *testing.T) {
ChangeDate: testNow, ChangeDate: testNow,
Sequence: 20211111, Sequence: 20211111,
Roles: database.TextArray[string]{"role-key"}, Roles: database.TextArray[string]{"role-key"},
RoleInformation: []Role{{Key: "role-key", DisplayName: "displayName", GroupName: "groupName"}},
GrantID: "grant-id", GrantID: "grant-id",
State: domain.UserGrantStateActive, State: domain.UserGrantStateActive,
UserID: "user-id", UserID: "user-id",
@@ -868,6 +891,7 @@ func Test_UserGrantPrepares(t *testing.T) {
20211111, 20211111,
"grant-id", "grant-id",
database.TextArray[string]{"role-key"}, database.TextArray[string]{"role-key"},
`[{"display_name":"displayName","group_name":"groupName","role_key":"role-key"}]`,
domain.UserGrantStateActive, domain.UserGrantStateActive,
"user-id", "user-id",
"username", "username",
@@ -905,6 +929,7 @@ func Test_UserGrantPrepares(t *testing.T) {
ChangeDate: testNow, ChangeDate: testNow,
Sequence: 20211111, Sequence: 20211111,
Roles: database.TextArray[string]{"role-key"}, Roles: database.TextArray[string]{"role-key"},
RoleInformation: []Role{{Key: "role-key", DisplayName: "displayName", GroupName: "groupName"}},
GrantID: "grant-id", GrantID: "grant-id",
State: domain.UserGrantStateActive, State: domain.UserGrantStateActive,
UserID: "user-id", UserID: "user-id",
@@ -947,6 +972,7 @@ func Test_UserGrantPrepares(t *testing.T) {
20211111, 20211111,
"grant-id", "grant-id",
database.TextArray[string]{"role-key"}, database.TextArray[string]{"role-key"},
`[{"display_name":"displayName","group_name":"groupName","role_key":"role-key"}]`,
domain.UserGrantStateActive, domain.UserGrantStateActive,
"user-id", "user-id",
"username", "username",
@@ -977,6 +1003,7 @@ func Test_UserGrantPrepares(t *testing.T) {
20211111, 20211111,
"grant-id", "grant-id",
database.TextArray[string]{"role-key"}, database.TextArray[string]{"role-key"},
`[{"display_name":"displayName","group_name":"groupName","role_key":"role-key"}]`,
domain.UserGrantStateActive, domain.UserGrantStateActive,
"user-id", "user-id",
"username", "username",
@@ -1014,6 +1041,7 @@ func Test_UserGrantPrepares(t *testing.T) {
ChangeDate: testNow, ChangeDate: testNow,
Sequence: 20211111, Sequence: 20211111,
Roles: database.TextArray[string]{"role-key"}, Roles: database.TextArray[string]{"role-key"},
RoleInformation: []Role{{Key: "role-key", DisplayName: "displayName", GroupName: "groupName"}},
GrantID: "grant-id", GrantID: "grant-id",
State: domain.UserGrantStateActive, State: domain.UserGrantStateActive,
UserID: "user-id", UserID: "user-id",
@@ -1044,6 +1072,7 @@ func Test_UserGrantPrepares(t *testing.T) {
ChangeDate: testNow, ChangeDate: testNow,
Sequence: 20211111, Sequence: 20211111,
Roles: database.TextArray[string]{"role-key"}, Roles: database.TextArray[string]{"role-key"},
RoleInformation: []Role{{Key: "role-key", DisplayName: "displayName", GroupName: "groupName"}},
GrantID: "grant-id", GrantID: "grant-id",
State: domain.UserGrantStateActive, State: domain.UserGrantStateActive,
UserID: "user-id", UserID: "user-id",

View File

@@ -861,7 +861,7 @@ service AuthService {
// List My Authorizations / User Grants // List My Authorizations / User Grants
// //
// Deprecated: [List authorizations](apis/resources/authorization_service_v2/zitadel-authorization-v-2-beta-authorization-service-list-authorizations.api.mdx) and pass the user ID filter with your users ID to search for your authorizations on granted and owned projects. // Deprecated: [List authorizations](apis/resources/authorization_service_v2/zitadel-authorization-v-2-authorization-service-list-authorizations.api.mdx) and pass the user ID filter with your users ID to search for your authorizations on granted and owned projects.
// //
// Returns a list of the authorizations/user grants the authenticated user has. User grants consist of an organization, a project and 1-n roles. // Returns a list of the authorizations/user grants the authenticated user has. User grants consist of an organization, a project and 1-n roles.
rpc ListMyUserGrants(ListMyUserGrantsRequest) returns (ListMyUserGrantsResponse) { rpc ListMyUserGrants(ListMyUserGrantsRequest) returns (ListMyUserGrantsResponse) {
@@ -914,7 +914,7 @@ service AuthService {
// List My Project Roles // List My Project Roles
// //
// Deprecated: [List authorizations](apis/resources/authorization_service_v2/zitadel-authorization-v-2-beta-authorization-service-list-authorizations.api.mdx) and pass the user ID filter with your users ID and the project ID filter to search for your authorizations on a granted and an owned project. // Deprecated: [List authorizations](apis/resources/authorization_service_v2/zitadel-authorization-v-2-authorization-service-list-authorizations.api.mdx) and pass the user ID filter with your users ID and the project ID filter to search for your authorizations on a granted and an owned project.
// //
// Returns a list of roles for the authenticated user and for the requesting project. // Returns a list of roles for the authenticated user and for the requesting project.
rpc ListMyProjectPermissions(ListMyProjectPermissionsRequest) returns (ListMyProjectPermissionsResponse) { rpc ListMyProjectPermissions(ListMyProjectPermissionsRequest) returns (ListMyProjectPermissionsResponse) {

View File

@@ -0,0 +1,228 @@
syntax = "proto3";
package zitadel.authorization.v2;
import "google/api/field_behavior.proto";
import "google/protobuf/timestamp.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
import "validate/validate.proto";
import "zitadel/filter/v2/filter.proto";
option go_package = "github.com/zitadel/zitadel/pkg/grpc/authorization/v2;authorization";
message Authorization {
// ID is the unique identifier of the authorization.
string id = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {example: "\"69629012906488334\""}];
// CreationDate is the timestamp when the authorization was created.
google.protobuf.Timestamp creation_date = 2 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {example: "\"2024-12-18T07:50:47.492Z\""}];
// ChangeDate is the timestamp when the authorization was last updated.
// In case the authorization was not updated, this field is equal to the creation date.
google.protobuf.Timestamp change_date = 3 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {example: "\"2025-01-23T10:34:18.051Z\""}];
// The project the user was granted the authorization for.
Project project = 4;
// The organization the user was granted the authorization for.
// This does not have to correspond with the project or user's organization.
// But does represent the organization to which the authorization belongs and
// the user was given permissions for.
Organization organization = 5;
// The user who was granted the authorization.
User user = 6;
// State is the current state of the authorization.
State state = 7;
// Roles contains the roles the user was granted for the project.
repeated Role roles = 8;
}
enum State {
STATE_UNSPECIFIED = 0;
// An active authorization grants the user access with the roles specified on the project.
STATE_ACTIVE = 1;
// An inactive authorization temporarily deactivates the granted access and roles.
// ZITADEL will not include the specific authorization in any authorization information like an access token.
// But the information can still be accessed using the API.
STATE_INACTIVE = 2;
}
message Project {
// ID is the unique identifier of the project.
string id = 1;
// Name is the name of the project.
string name = 2;
// OrganizationID is the ID of the organization the project belongs to.
// This does not have to correspond with the authorizations organization.
// In case the authorization is given on a granted project, this is the organization
// that owns the project and granted it.
string organization_id = 3;
}
message Organization {
// ID is the unique identifier of the organization.
string id = 1;
// Name is the name of the organization.
string name = 2;
}
message User {
// ID represents the ID of the user who was granted the authorization.
string id = 1;
// PreferredLoginName represents the preferred login name of the granted user.
string preferred_login_name = 2;
// DisplayName represents the public display name of the granted user.
string display_name = 3;
// AvatarURL is the URL to the user's public avatar image.
string avatar_url = 4;
// The organization the user belong to.
// This does not have to correspond with the authorizations organization.
string organization_id = 5;
}
message Role {
// Key is the unique key of the role. It's the only relevant attribute for ZITADEL and
// will be used for authorization checks and as claim in tokens and user info responses.
string key = 1;
// Human readable name for the role, which might be displayed to users.
string display_name = 2;
// The group the role belongs to. This is used to group roles in the UI.
string group = 3;
}
message AuthorizationsSearchFilter {
oneof filter {
option (validate.required) = true;
// Search for authorizations by their IDs.
zitadel.filter.v2.InIDsFilter authorization_ids = 1;
// Search for authorizations by the ID of the organization it was granted for.
zitadel.filter.v2.IDFilter organization_id = 2;
// Search for authorizations by their state.
StateQuery state = 3;
// Search for authorizations by the IDs of the users who were granted the authorizations.
zitadel.filter.v2.InIDsFilter in_user_ids = 4;
// Search for authorizations by the ID of the organisation the user is part of.
zitadel.filter.v2.IDFilter user_organization_id = 5;
// Search for authorizations by the preferred login name of the granted user.
UserPreferredLoginNameQuery user_preferred_login_name = 6;
// Search for authorizations by the public display name of the granted user.
UserDisplayNameQuery user_display_name = 7;
// Search for authorizations by the ID of the project the user was granted the authorization for.
// This will also include authorizations granted for project grants of the same project.
zitadel.filter.v2.IDFilter project_id = 8;
// Search for authorizations by the name of the project the user was granted the authorization for.
// This will also include authorizations granted for project grants of the same project.
ProjectNameQuery project_name = 9;
// Search for authorizations by the key of the role the user was granted.
RoleKeyQuery role_key = 10;
// Search for authorizations by the ID of the project grant the user was granted the authorization for.
// This will also include authorizations granted for project grants of the same project.
zitadel.filter.v2.IDFilter project_grant_id = 11;
}
}
message StateQuery {
// Specify the state of the authorization to search for.
State state = 1 [(validate.rules).enum = {
defined_only: true
not_in: [0]
}];
}
message UserPreferredLoginNameQuery {
// Specify the preferred login name of the granted user to search for.
string login_name = 1 [
(validate.rules).string = {
min_len: 1
max_len: 200
},
(google.api.field_behavior) = REQUIRED
];
// Specify the method to search for the preferred login name. Default is EQUAL.
// For example, to search for all authorizations granted to a user with
// a preferred login name containing a specific string, use CONTAINS or CONTAINS_IGNORE_CASE.
zitadel.filter.v2.TextFilterMethod method = 2 [(validate.rules).enum.defined_only = true];
}
message UserDisplayNameQuery {
// Specify the public display name of the granted user to search for.
string display_name = 1 [
(validate.rules).string = {
min_len: 1
max_len: 200
},
(google.api.field_behavior) = REQUIRED
];
// Specify the method to search for the display name. Default is EQUAL.
// For example, to search for all authorizations granted to a user with
// a display name containing a specific string, use CONTAINS or CONTAINS_IGNORE_CASE.
zitadel.filter.v2.TextFilterMethod method = 2 [(validate.rules).enum.defined_only = true];
}
message ProjectNameQuery {
// Specify the name of the project the user was granted the authorization for to search for.
// Note that this will also include authorizations granted for project grants of the same project.
string name = 1 [
(validate.rules).string = {max_len: 200},
(google.api.field_behavior) = REQUIRED
];
// Specify the method to search for the project name. Default is EQUAL.
// For example, to search for all authorizations granted on a project with
// a name containing a specific string, use CONTAINS or CONTAINS_IGNORE_CASE.
zitadel.filter.v2.TextFilterMethod method = 2 [(validate.rules).enum.defined_only = true];
}
message OrganizationNameQuery {
// Specify the name of the organization the authorization was granted for to search for.
// This can either be the organization the project or the project grant is part of.
string name = 1 [(validate.rules).string = {max_len: 200}];
// Specify the method to search for the organization name. Default is EQUAL.
// For example, to search for all authorizations with an organization name containing a specific string,
// use CONTAINS or CONTAINS_IGNORE_CASE.
zitadel.filter.v2.TextFilterMethod method = 2 [(validate.rules).enum.defined_only = true];
}
message RoleKeyQuery {
// Specify the key of the role the user was granted to search for.
string key = 1 [(validate.rules).string = {max_len: 200}];
// Specify the method to search for the role key. Default is EQUAL.
// For example, to search for all authorizations starting with a specific role key,
// use STARTS_WITH or STARTS_WITH_IGNORE_CASE.
zitadel.filter.v2.TextFilterMethod method = 2 [(validate.rules).enum.defined_only = true];
}
enum AuthorizationFieldName {
AUTHORIZATION_FIELD_NAME_UNSPECIFIED = 0;
AUTHORIZATION_FIELD_NAME_CREATED_DATE = 1;
AUTHORIZATION_FIELD_NAME_CHANGED_DATE = 2;
AUTHORIZATION_FIELD_NAME_ID = 3;
AUTHORIZATION_FIELD_NAME_USER_ID = 4;
AUTHORIZATION_FIELD_NAME_PROJECT_ID = 5;
AUTHORIZATION_FIELD_NAME_ORGANIZATION_ID = 6;
AUTHORIZATION_FIELD_NAME_USER_ORGANIZATION_ID = 7;
}

View File

@@ -0,0 +1,317 @@
syntax = "proto3";
package zitadel.authorization.v2;
import "protoc-gen-openapiv2/options/annotations.proto";
import "google/api/field_behavior.proto";
import "google/protobuf/timestamp.proto";
import "validate/validate.proto";
import "google/api/annotations.proto";
import "zitadel/protoc_gen_zitadel/v2/options.proto";
import "zitadel/authorization/v2/authorization.proto";
import "zitadel/filter/v2/filter.proto";
option go_package = "github.com/zitadel/zitadel/pkg/grpc/authorization/v2;authorization";
// AuthorizationService provides methods to manage authorizations for users within your projects and applications.
//
// For managing permissions and roles for ZITADEL internal resources, like organizations, projects,
// users, etc., please use the InternalPermissionService.
service AuthorizationService {
// List Authorizations
//
// ListAuthorizations returns all authorizations matching the request and necessary permissions.
//
// Required permissions:
// - "user.grant.read"
// - no permissions required for listing own authorizations
rpc ListAuthorizations(ListAuthorizationsRequest) returns (ListAuthorizationsResponse) {
option (zitadel.protoc_gen_zitadel.v2.options) = {
auth_option: {
permission: "authenticated"
}
};
}
// Create Authorization
//
// CreateAuthorization creates a new authorization for a user in an owned or granted project.
//
// Required permissions:
// - "user.grant.write"
rpc CreateAuthorization(CreateAuthorizationRequest) returns (CreateAuthorizationResponse) {
option (zitadel.protoc_gen_zitadel.v2.options) = {
auth_option: {
permission: "authenticated"
}
};
}
// Update Authorization
//
// UpdateAuthorization updates the authorization.
//
// Note that any role keys previously granted to the user and not present in the request will be revoked.
//
// Required permissions:
// - "user.grant.write"
rpc UpdateAuthorization(UpdateAuthorizationRequest) returns (UpdateAuthorizationResponse) {
option (zitadel.protoc_gen_zitadel.v2.options) = {
auth_option: {
permission: "authenticated"
}
};
}
// Delete Authorization
//
// DeleteAuthorization deletes the authorization.
//
// In case the authorization is not found, the request will return a successful response as
// the desired state is already achieved.
// You can check the deletion date in the response to verify if the authorization was deleted by the request.
//
// Required permissions:
// - "user.grant.delete"
rpc DeleteAuthorization(DeleteAuthorizationRequest) returns (DeleteAuthorizationResponse) {
option (zitadel.protoc_gen_zitadel.v2.options) = {
auth_option: {
permission: "authenticated"
}
};
}
// Activate Authorization
//
// ActivateAuthorization activates an existing but inactive authorization.
//
// In case the authorization is already active, the request will return a successful response as
// the desired state is already achieved.
// You can check the change date in the response to verify if the authorization was activated by the request.
//
// Required permissions:
// - "user.grant.write"
rpc ActivateAuthorization(ActivateAuthorizationRequest) returns (ActivateAuthorizationResponse) {
option (zitadel.protoc_gen_zitadel.v2.options) = {
auth_option: {
permission: "authenticated"
}
};
}
// Deactivate Authorization
//
// DeactivateAuthorization deactivates an existing and active authorization.
//
// In case the authorization is already inactive, the request will return a successful response as
// the desired state is already achieved.
// You can check the change date in the response to verify if the authorization was deactivated by the request.
//
// Required permissions:
// - "user.grant.write"
rpc DeactivateAuthorization(DeactivateAuthorizationRequest) returns (DeactivateAuthorizationResponse) {
option (zitadel.protoc_gen_zitadel.v2.options) = {
auth_option: {
permission: "authenticated"
}
};
}
}
message ListAuthorizationsRequest {
// Paginate through the results using a limit, offset and sorting.
optional zitadel.filter.v2.PaginationRequest pagination = 1;
// The field the result is sorted by. The default is the creation date.
// Beware that if you change this, your result pagination might be inconsistent.
AuthorizationFieldName sorting_column = 2 [
(validate.rules).enum = {defined_only: true}
];
// Define the criteria to query for.
repeated AuthorizationsSearchFilter filters = 3;
}
message ListAuthorizationsResponse {
// Contains the pagination information.
zitadel.filter.v2.PaginationResponse pagination = 1;
// Authorizations contains the list of authorizations matching the request.
repeated Authorization authorizations = 2;
}
message CreateAuthorizationRequest {
// UserID is the ID of the user who should be granted the authorization.
string user_id = 1 [
(validate.rules).string = {min_len: 1, max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
min_length: 1;
max_length: 200;
example: "\"163840776835432345\"";
},
(google.api.field_behavior) = REQUIRED
];
// Project ID is the ID of the project the user should be authorized for.
string project_id = 2 [
(validate.rules).string = {min_len: 1, max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
min_length: 1;
max_length: 200;
example: "\"163840776835432345\"";
},
(google.api.field_behavior) = REQUIRED
];
// OrganizationID is the ID of the organization on which the authorization should be created.
// The organization must either own the project or have a grant for the project.
string organization_id = 3 [
(validate.rules).string = {min_len: 1, max_len: 200},
(google.api.field_behavior) = REQUIRED,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
min_length: 1,
max_length: 200;
example: "\"163840776835432345\"";
}
];
// RoleKeys are the keys of the roles the user should be granted.
repeated string role_keys = 4 [
(validate.rules).repeated = {
unique: true
items: {
string: {
min_len: 1
max_len: 200
}
}
},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
min_length: 1;
max_length: 200;
example: "[\"user\",\"admin\"]";
}
];
}
message CreateAuthorizationResponse {
// ID is the unique identifier of the newly created authorization.
string id = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"69629012906488334\"";
}
];
// CreationDate is the timestamp when the authorization was created.
google.protobuf.Timestamp creation_date = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"2025-01-23T10:34:18.051Z\"";
}
];
}
message UpdateAuthorizationRequest {
// ID is the unique identifier of the authorization.
string id = 1 [
(validate.rules).string = {min_len: 1, max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
min_length: 1;
max_length: 200;
example: "\"163840776835432345\"";
}
];
// RoleKeys are the keys of the roles the user should be granted.
// Note that any role keys previously granted to the user and not present in the list will be revoked.
repeated string role_keys = 2 [
(validate.rules).repeated = {
unique: true
items: {
string: {
min_len: 1
max_len: 200
}
}
},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
min_length: 1;
max_length: 200;
example: "[\"user\",\"admin\"]";
}
];
}
message UpdateAuthorizationResponse {
// ChangeDate is the timestamp when the authorization was last updated.
google.protobuf.Timestamp change_date = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"2024-12-18T07:50:47.492Z\"";
}
];
}
message DeleteAuthorizationRequest {
// ID is the unique identifier of the authorization that should be deleted.
string id = 1 [
(validate.rules).string = {min_len: 1, max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
min_length: 1;
max_length: 200;
example: "\"163840776835432345\"";
},
(google.api.field_behavior) = REQUIRED
];
}
message DeleteAuthorizationResponse {
// DeletionDate is the timestamp when the authorization was deleted.
google.protobuf.Timestamp deletion_date = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"2024-12-18T07:50:47.492Z\"";
}
];
}
message ActivateAuthorizationRequest {
// ID is the unique identifier of the authorization that should be activated.
string id = 1 [
(validate.rules).string = {min_len: 1, max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
min_length: 1;
max_length: 200;
example: "\"163840776835432345\"";
},
(google.api.field_behavior) = REQUIRED
];
}
message ActivateAuthorizationResponse {
// ChangeDate is the last timestamp when the authorization was changed / activated.
google.protobuf.Timestamp change_date = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"2024-12-18T07:50:47.492Z\"";
}
];
}
message DeactivateAuthorizationRequest {
// ID is the unique identifier of the authorization that should be deactivated.
string id = 1 [
(validate.rules).string = {min_len: 1, max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
min_length: 1;
max_length: 200;
example: "\"163840776835432345\"";
},
(google.api.field_behavior) = REQUIRED
];
}
message DeactivateAuthorizationResponse {
// ChangeDate is the last timestamp when the authorization was changed / deactivated.
google.protobuf.Timestamp change_date = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"2024-12-18T07:50:47.492Z\"";
}
];
}

View File

@@ -17,10 +17,14 @@ option go_package = "github.com/zitadel/zitadel/pkg/grpc/authorization/v2beta;au
// //
// For managing permissions and roles for ZITADEL internal resources, like organizations, projects, // For managing permissions and roles for ZITADEL internal resources, like organizations, projects,
// users, etc., please use the InternalPermissionService. // users, etc., please use the InternalPermissionService.
//
// Deprecated: use authorization service v2 instead. This service will be removed in the next major version of ZITADEL.
service AuthorizationService { service AuthorizationService {
// List Authorizations // List Authorizations
// //
// Deprecated: please move to the corresponding endpoint under authorization service v2. This endpoint will be removed with the next major version of ZITADEL.
//
// ListAuthorizations returns all authorizations matching the request and necessary permissions. // ListAuthorizations returns all authorizations matching the request and necessary permissions.
// //
// Required permissions: // Required permissions:
@@ -40,6 +44,7 @@ service AuthorizationService {
}; };
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
deprecated: true;
responses: { responses: {
key: "200"; key: "200";
value: { value: {
@@ -57,6 +62,8 @@ service AuthorizationService {
// Create Authorization // Create Authorization
// //
// Deprecated: please move to the corresponding endpoint under authorization service v2. This endpoint will be removed with the next major version of ZITADEL.
//
// CreateAuthorization creates a new authorization for a user in an owned or granted project. // CreateAuthorization creates a new authorization for a user in an owned or granted project.
// //
// Required permissions: // Required permissions:
@@ -74,6 +81,7 @@ service AuthorizationService {
}; };
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
deprecated: true;
responses: { responses: {
key: "200"; key: "200";
value: { value: {
@@ -102,6 +110,8 @@ service AuthorizationService {
// Update Authorization // Update Authorization
// //
// Deprecated: please move to the corresponding endpoint under authorization service v2. This endpoint will be removed with the next major version of ZITADEL.
//
// UpdateAuthorization updates the authorization. // UpdateAuthorization updates the authorization.
// //
// Note that any role keys previously granted to the user and not present in the request will be revoked. // Note that any role keys previously granted to the user and not present in the request will be revoked.
@@ -121,6 +131,7 @@ service AuthorizationService {
}; };
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
deprecated: true;
responses: { responses: {
key: "200"; key: "200";
value: { value: {
@@ -143,6 +154,8 @@ service AuthorizationService {
// Delete Authorization // Delete Authorization
// //
// Deprecated: please move to the corresponding endpoint under authorization service v2. This endpoint will be removed with the next major version of ZITADEL.
//
// DeleteAuthorization deletes the authorization. // DeleteAuthorization deletes the authorization.
// //
// In case the authorization is not found, the request will return a successful response as // In case the authorization is not found, the request will return a successful response as
@@ -163,6 +176,7 @@ service AuthorizationService {
}; };
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
deprecated: true;
responses: { responses: {
key: "200"; key: "200";
value: { value: {
@@ -185,6 +199,8 @@ service AuthorizationService {
// Activate Authorization // Activate Authorization
// //
// Deprecated: please move to the corresponding endpoint under authorization service v2. This endpoint will be removed with the next major version of ZITADEL.
//
// ActivateAuthorization activates an existing but inactive authorization. // ActivateAuthorization activates an existing but inactive authorization.
// //
// In case the authorization is already active, the request will return a successful response as // In case the authorization is already active, the request will return a successful response as
@@ -206,6 +222,7 @@ service AuthorizationService {
}; };
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
deprecated: true;
responses: { responses: {
key: "200"; key: "200";
value: { value: {
@@ -228,6 +245,8 @@ service AuthorizationService {
// Deactivate Authorization // Deactivate Authorization
// //
// Deprecated: please move to the corresponding endpoint under authorization service v2. This endpoint will be removed with the next major version of ZITADEL.
//
// DeactivateAuthorization deactivates an existing and active authorization. // DeactivateAuthorization deactivates an existing and active authorization.
// //
// In case the authorization is already inactive, the request will return a successful response as // In case the authorization is already inactive, the request will return a successful response as
@@ -249,6 +268,7 @@ service AuthorizationService {
}; };
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
deprecated: true;
responses: { responses: {
key: "200"; key: "200";
value: { value: {

View File

@@ -4509,7 +4509,7 @@ service ManagementService {
// Get User Grant By ID // Get User Grant By ID
// //
// Deprecated: [List authorizations](apis/resources/authorization_service_v2/zitadel-authorization-v-2-beta-authorization-service-list-authorizations.api.mdx) and filter by its ID. // Deprecated: [List authorizations](apis/resources/authorization_service_v2/zitadel-authorization-v-2-authorization-service-list-authorizations.api.mdx) and filter by its ID.
// //
// Returns a user grant per ID. A user grant is a role a user has for a specific project and organization. // Returns a user grant per ID. A user grant is a role a user has for a specific project and organization.
rpc GetUserGrantByID(GetUserGrantByIDRequest) returns (GetUserGrantByIDResponse) { rpc GetUserGrantByID(GetUserGrantByIDRequest) returns (GetUserGrantByIDResponse) {
@@ -4537,7 +4537,7 @@ service ManagementService {
// Search User Grants // Search User Grants
// //
// Deprecated: [List authorizations](apis/resources/authorization_service_v2/zitadel-authorization-v-2-beta-authorization-service-list-authorizations.api.mdx) and pass the user ID filter to search for a users grants on owned or granted projects. // Deprecated: [List authorizations](apis/resources/authorization_service_v2/zitadel-authorization-v-2-authorization-service-list-authorizations.api.mdx) and pass the user ID filter to search for a users grants on owned or granted projects.
// //
// Returns a list of user grants that match the search queries. User grants are the roles users have for a specific project and organization. // Returns a list of user grants that match the search queries. User grants are the roles users have for a specific project and organization.
rpc ListUserGrants(ListUserGrantRequest) returns (ListUserGrantResponse) { rpc ListUserGrants(ListUserGrantRequest) returns (ListUserGrantResponse) {
@@ -4567,7 +4567,7 @@ service ManagementService {
// Add User Grant // Add User Grant
// //
// Deprecated: [Add an authorization](apis/resources/authorization_service_v2/zitadel-authorization-v-2-beta-authorization-service-create-authorization.api.mdx) to grant a user access to an owned or granted project. // Deprecated: [Add an authorization](apis/resources/authorization_service_v2/zitadel-authorization-v-2-authorization-service-create-authorization.api.mdx) to grant a user access to an owned or granted project.
// //
// Add a user grant for a specific user. User grants are the roles users have for a specific project and organization. // Add a user grant for a specific user. User grants are the roles users have for a specific project and organization.
rpc AddUserGrant(AddUserGrantRequest) returns (AddUserGrantResponse) { rpc AddUserGrant(AddUserGrantRequest) returns (AddUserGrantResponse) {
@@ -4596,7 +4596,7 @@ service ManagementService {
// Update User Grant // Update User Grant
// //
// Deprecated: [Update an authorization](apis/resources/authorization_service_v2/zitadel-authorization-v-2-beta-authorization-service-update-authorization.api.mdx) to update a user's roles on an owned or granted project. // Deprecated: [Update an authorization](apis/resources/authorization_service_v2/zitadel-authorization-v-2-authorization-service-update-authorization.api.mdx) to update a user's roles on an owned or granted project.
// //
// Update the roles of a user grant. User grants are the roles users have for a specific project and organization. // Update the roles of a user grant. User grants are the roles users have for a specific project and organization.
rpc UpdateUserGrant(UpdateUserGrantRequest) returns (UpdateUserGrantResponse) { rpc UpdateUserGrant(UpdateUserGrantRequest) returns (UpdateUserGrantResponse) {
@@ -4625,7 +4625,7 @@ service ManagementService {
// Deactivate User Grant // Deactivate User Grant
// //
// Deprecated: [Deactivate an authorization](apis/resources/authorization_service_v2/zitadel-authorization-v-2-beta-authorization-service-deactivate-authorization.api.mdx) to disable a user's access to an owned or granted project. // Deprecated: [Deactivate an authorization](apis/resources/authorization_service_v2/zitadel-authorization-v-2-authorization-service-deactivate-authorization.api.mdx) to disable a user's access to an owned or granted project.
// //
// Deactivate the user grant. The user will not be able to use the granted project anymore. Also, the roles will not be included in the tokens when requested. An error will be returned if the user grant is already deactivated. // Deactivate the user grant. The user will not be able to use the granted project anymore. Also, the roles will not be included in the tokens when requested. An error will be returned if the user grant is already deactivated.
rpc DeactivateUserGrant(DeactivateUserGrantRequest) returns (DeactivateUserGrantResponse) { rpc DeactivateUserGrant(DeactivateUserGrantRequest) returns (DeactivateUserGrantResponse) {
@@ -4654,7 +4654,7 @@ service ManagementService {
// Reactivate User Grant // Reactivate User Grant
// //
// Deprecated: [Activate an authorization](apis/resources/authorization_service_v2/zitadel-authorization-v-2-beta-authorization-service-activate-authorization.api.mdx) to enable a user's access to an owned or granted project. // Deprecated: [Activate an authorization](apis/resources/authorization_service_v2/zitadel-authorization-v-2-authorization-service-activate-authorization.api.mdx) to enable a user's access to an owned or granted project.
// //
// Reactivate a deactivated user grant. The user will be able to use the granted project again. An error will be returned if the user grant is not deactivated. // Reactivate a deactivated user grant. The user will be able to use the granted project again. An error will be returned if the user grant is not deactivated.
rpc ReactivateUserGrant(ReactivateUserGrantRequest) returns (ReactivateUserGrantResponse) { rpc ReactivateUserGrant(ReactivateUserGrantRequest) returns (ReactivateUserGrantResponse) {
@@ -4683,7 +4683,7 @@ service ManagementService {
// Remove User Grant // Remove User Grant
// //
// Deprecated: [Delete an authorization](apis/resources/authorization_service_v2/zitadel-authorization-v-2-beta-authorization-service-delete-authorization.api.mdx) to remove a users access to an owned or granted project. // Deprecated: [Delete an authorization](apis/resources/authorization_service_v2/zitadel-authorization-v-2-authorization-service-delete-authorization.api.mdx) to remove a users access to an owned or granted project.
// //
// Removes the user grant from the user. The user will not be able to use the granted project anymore. Also, the roles will not be included in the tokens when requested. // Removes the user grant from the user. The user will not be able to use the granted project anymore. Also, the roles will not be included in the tokens when requested.
rpc RemoveUserGrant(RemoveUserGrantRequest) returns (RemoveUserGrantResponse) { rpc RemoveUserGrant(RemoveUserGrantRequest) returns (RemoveUserGrantResponse) {
@@ -4711,7 +4711,7 @@ service ManagementService {
// Bulk Remove User Grants // Bulk Remove User Grants
// //
// Deprecated: [Delete authorizations one after the other](apis/resources/authorization_service_v2/zitadel-authorization-v-2-beta-authorization-service-delete-authorization.api.mdx) to remove access for multiple users on multiple owned or granted projects. // Deprecated: [Delete authorizations one after the other](apis/resources/authorization_service_v2/zitadel-authorization-v-2-authorization-service-delete-authorization.api.mdx) to remove access for multiple users on multiple owned or granted projects.
// //
// Remove a list of user grants. The users will not be able to use the granted project anymore. Also, the roles will not be included in the tokens when requested. // Remove a list of user grants. The users will not be able to use the granted project anymore. Also, the roles will not be included in the tokens when requested.
rpc BulkRemoveUserGrant(BulkRemoveUserGrantRequest) returns (BulkRemoveUserGrantResponse) { rpc BulkRemoveUserGrant(BulkRemoveUserGrantRequest) returns (BulkRemoveUserGrantResponse) {