mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 03:57:32 +00:00
feat: add personal access tokens for service users (#2974)
* feat: add machine tokens * fix test * rename to pat * fix merge and tests * fix scopes * fix migration version * fix test * Update internal/repository/user/personal_access_token.go Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com> Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com>
This commit is contained in:
@@ -2,7 +2,9 @@ package management
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
@@ -11,7 +13,9 @@ import (
|
||||
idp_grpc "github.com/caos/zitadel/internal/api/grpc/idp"
|
||||
"github.com/caos/zitadel/internal/api/grpc/metadata"
|
||||
obj_grpc "github.com/caos/zitadel/internal/api/grpc/object"
|
||||
"github.com/caos/zitadel/internal/api/grpc/user"
|
||||
user_grpc "github.com/caos/zitadel/internal/api/grpc/user"
|
||||
z_oidc "github.com/caos/zitadel/internal/api/oidc"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/query"
|
||||
mgmt_pb "github.com/caos/zitadel/pkg/grpc/management"
|
||||
@@ -693,6 +697,74 @@ func (s *Server) RemoveMachineKey(ctx context.Context, req *mgmt_pb.RemoveMachin
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetPersonalAccessTokenByIDs(ctx context.Context, req *mgmt_pb.GetPersonalAccessTokenByIDsRequest) (*mgmt_pb.GetPersonalAccessTokenByIDsResponse, error) {
|
||||
resourceOwner, err := query.NewPersonalAccessTokenResourceOwnerSearchQuery(authz.GetCtxData(ctx).OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aggregateID, err := query.NewPersonalAccessTokenUserIDSearchQuery(req.UserId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
token, err := s.query.PersonalAccessTokenByID(ctx, req.TokenId, resourceOwner, aggregateID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.GetPersonalAccessTokenByIDsResponse{
|
||||
Token: user.PersonalAccessTokenToPb(token),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ListPersonalAccessTokens(ctx context.Context, req *mgmt_pb.ListPersonalAccessTokensRequest) (*mgmt_pb.ListPersonalAccessTokensResponse, error) {
|
||||
queries, err := ListPersonalAccessTokensRequestToQuery(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, err := s.query.SearchPersonalAccessTokens(ctx, queries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.ListPersonalAccessTokensResponse{
|
||||
Result: user_grpc.PersonalAccessTokensToPb(result.PersonalAccessTokens),
|
||||
Details: obj_grpc.ToListDetails(
|
||||
result.Count,
|
||||
result.Sequence,
|
||||
result.Timestamp,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) AddPersonalAccessToken(ctx context.Context, req *mgmt_pb.AddPersonalAccessTokenRequest) (*mgmt_pb.AddPersonalAccessTokenResponse, error) {
|
||||
expDate := time.Time{}
|
||||
if req.ExpirationDate != nil {
|
||||
expDate = req.ExpirationDate.AsTime()
|
||||
}
|
||||
scopes := []string{oidc.ScopeOpenID, z_oidc.ScopeUserMetaData, z_oidc.ScopeResourceOwner}
|
||||
pat, token, err := s.command.AddPersonalAccessToken(ctx, req.UserId, authz.GetCtxData(ctx).OrgID, expDate, scopes, domain.UserTypeMachine)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.AddPersonalAccessTokenResponse{
|
||||
TokenId: pat.TokenID,
|
||||
Token: token,
|
||||
Details: obj_grpc.AddToDetailsPb(
|
||||
pat.Sequence,
|
||||
pat.ChangeDate,
|
||||
pat.ResourceOwner,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) RemovePersonalAccessToken(ctx context.Context, req *mgmt_pb.RemovePersonalAccessTokenRequest) (*mgmt_pb.RemovePersonalAccessTokenResponse, error) {
|
||||
objectDetails, err := s.command.RemovePersonalAccessToken(ctx, req.UserId, req.TokenId, authz.GetCtxData(ctx).OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.RemovePersonalAccessTokenResponse{
|
||||
Details: obj_grpc.DomainToChangeDetailsPb(objectDetails),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ListHumanLinkedIDPs(ctx context.Context, req *mgmt_pb.ListHumanLinkedIDPsRequest) (*mgmt_pb.ListHumanLinkedIDPsResponse, error) {
|
||||
queries, err := ListHumanLinkedIDPsRequestToQuery(ctx, req)
|
||||
if err != nil {
|
||||
|
@@ -227,6 +227,30 @@ func AddMachineKeyRequestToDomain(req *mgmt_pb.AddMachineKeyRequest) *domain.Mac
|
||||
}
|
||||
}
|
||||
|
||||
func ListPersonalAccessTokensRequestToQuery(ctx context.Context, req *mgmt_pb.ListPersonalAccessTokensRequest) (*query.PersonalAccessTokenSearchQueries, error) {
|
||||
resourceOwner, err := query.NewPersonalAccessTokenResourceOwnerSearchQuery(authz.GetCtxData(ctx).OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userID, err := query.NewPersonalAccessTokenUserIDSearchQuery(req.UserId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
offset, limit, asc := object.ListQueryToModel(req.Query)
|
||||
return &query.PersonalAccessTokenSearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
},
|
||||
Queries: []query.SearchQuery{
|
||||
resourceOwner,
|
||||
userID,
|
||||
},
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func RemoveHumanLinkedIDPRequestToDomain(ctx context.Context, req *mgmt_pb.RemoveHumanLinkedIDPRequest) *domain.UserIDPLink {
|
||||
return &domain.UserIDPLink{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
|
25
internal/api/grpc/user/machine_token.go
Normal file
25
internal/api/grpc/user/machine_token.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/grpc/object"
|
||||
"github.com/caos/zitadel/internal/query"
|
||||
"github.com/caos/zitadel/pkg/grpc/user"
|
||||
)
|
||||
|
||||
func PersonalAccessTokensToPb(tokens []*query.PersonalAccessToken) []*user.PersonalAccessToken {
|
||||
t := make([]*user.PersonalAccessToken, len(tokens))
|
||||
for i, token := range tokens {
|
||||
t[i] = PersonalAccessTokenToPb(token)
|
||||
}
|
||||
return t
|
||||
}
|
||||
func PersonalAccessTokenToPb(token *query.PersonalAccessToken) *user.PersonalAccessToken {
|
||||
return &user.PersonalAccessToken{
|
||||
Id: token.ID,
|
||||
Details: object.ChangeToDetailsPb(token.Sequence, token.ChangeDate, token.ResourceOwner),
|
||||
ExpirationDate: timestamppb.New(token.Expiration),
|
||||
Scopes: token.Scopes,
|
||||
}
|
||||
}
|
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/query"
|
||||
"github.com/caos/zitadel/internal/telemetry/tracing"
|
||||
"github.com/caos/zitadel/internal/user/model"
|
||||
grant_model "github.com/caos/zitadel/internal/usergrant/model"
|
||||
)
|
||||
|
||||
@@ -227,3 +228,23 @@ func (o *OPStorage) assertProjectRoleScopes(ctx context.Context, clientID string
|
||||
}
|
||||
return scopes, nil
|
||||
}
|
||||
|
||||
func (o *OPStorage) assertClientScopesForPAT(ctx context.Context, token *model.TokenView, clientID string) error {
|
||||
token.Audience = append(token.Audience, clientID)
|
||||
projectID, err := o.query.ProjectIDFromClientID(ctx, clientID)
|
||||
if err != nil {
|
||||
return errors.ThrowPreconditionFailed(nil, "OIDC-AEG4d", "Errors.Internal")
|
||||
}
|
||||
projectIDQuery, err := query.NewProjectRoleProjectIDSearchQuery(projectID)
|
||||
if err != nil {
|
||||
return errors.ThrowInternal(err, "OIDC-Cyc78", "Errors.Internal")
|
||||
}
|
||||
roles, err := o.query.SearchProjectRoles(context.TODO(), &query.ProjectRoleSearchQueries{Queries: []query.SearchQuery{projectIDQuery}})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, role := range roles.ProjectRoles {
|
||||
token.Scopes = append(token.Scopes, ScopeProjectRolePrefix+role.Key)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@@ -163,6 +163,12 @@ func (o *OPStorage) SetIntrospectionFromToken(ctx context.Context, introspection
|
||||
if err != nil {
|
||||
return errors.ThrowPermissionDenied(nil, "OIDC-Adfg5", "client not found")
|
||||
}
|
||||
if token.IsPAT {
|
||||
err = o.assertClientScopesForPAT(ctx, token, clientID)
|
||||
if err != nil {
|
||||
return errors.ThrowPreconditionFailed(err, "OIDC-AGefw", "Errors.Internal")
|
||||
}
|
||||
}
|
||||
for _, aud := range token.Audience {
|
||||
if aud == clientID || aud == projectID {
|
||||
err := o.setUserinfo(ctx, introspection, token.UserID, clientID, token.Scopes)
|
||||
|
Reference in New Issue
Block a user