diff --git a/cli/operations/user/create_pat.go b/cli/operations/user/create_pat.go index f9875bff2..ed9f87546 100644 --- a/cli/operations/user/create_pat.go +++ b/cli/operations/user/create_pat.go @@ -13,7 +13,6 @@ import ( "github.com/harness/gitness/cli/provide" "github.com/harness/gitness/internal/api/controller/user" - "github.com/harness/gitness/types/enum" "github.com/drone/funcmap" "github.com/gotidy/ptr" @@ -47,7 +46,6 @@ func (c *createPATCommand) run(*kingpin.ParseContext) error { in := user.CreateTokenInput{ UID: c.uid, Lifetime: lifeTime, - Grants: enum.AccessGrantAll, } tokenResp, err := provide.Client().UserCreatePAT(ctx, in) diff --git a/cmd/gitness/wire_gen.go b/cmd/gitness/wire_gen.go index a021845b1..a2c59b773 100644 --- a/cmd/gitness/wire_gen.go +++ b/cmd/gitness/wire_gen.go @@ -85,7 +85,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro principalInfoCache := cache.ProvidePrincipalInfoCache(principalInfoView) membershipStore := database.ProvideMembershipStore(db, principalInfoCache) permissionCache := authz.ProvidePermissionCache(spaceStore, membershipStore) - authorizer := authz.ProvideAuthorizer(permissionCache) + authorizer := authz.ProvideAuthorizer(permissionCache, spaceStore) principalUIDTransformation := store.ProvidePrincipalUIDTransformation() principalStore := database.ProvidePrincipalStore(db, principalUIDTransformation) tokenStore := database.ProvideTokenStore(db) diff --git a/internal/api/controller/serviceaccount/create_token.go b/internal/api/controller/serviceaccount/create_token.go index 46636de00..0c210c58c 100644 --- a/internal/api/controller/serviceaccount/create_token.go +++ b/internal/api/controller/serviceaccount/create_token.go @@ -17,14 +17,17 @@ import ( ) type CreateTokenInput struct { - UID string `json:"uid"` - Lifetime *time.Duration `json:"lifetime"` - Grants enum.AccessGrant `json:"grants"` + UID string `json:"uid"` + Lifetime *time.Duration `json:"lifetime"` } // CreateToken creates a new service account access token. -func (c *Controller) CreateToken(ctx context.Context, session *auth.Session, - saUID string, in *CreateTokenInput) (*types.TokenResponse, error) { +func (c *Controller) CreateToken( + ctx context.Context, + session *auth.Session, + saUID string, + in *CreateTokenInput, +) (*types.TokenResponse, error) { sa, err := findServiceAccountFromUID(ctx, c.principalStore, saUID) if err != nil { return nil, err @@ -36,18 +39,20 @@ func (c *Controller) CreateToken(ctx context.Context, session *auth.Session, if err = check.TokenLifetime(in.Lifetime, true); err != nil { return nil, err } - // TODO: Added to unblock UI - Depending on product decision enforce grants, or remove Grants completely. - if err = check.AccessGrant(in.Grants, true); err != nil { - return nil, err - } // Ensure principal has required permissions on parent (ensures that parent exists) if err = apiauth.CheckServiceAccount(ctx, c.authorizer, session, c.spaceStore, c.repoStore, sa.ParentType, sa.ParentID, sa.UID, enum.PermissionServiceAccountEdit); err != nil { return nil, err } - token, jwtToken, err := token.CreateSAT(ctx, c.tokenStore, &session.Principal, - sa, in.UID, in.Lifetime, in.Grants) + token, jwtToken, err := token.CreateSAT( + ctx, + c.tokenStore, + &session.Principal, + sa, + in.UID, + in.Lifetime, + ) if err != nil { return nil, err } diff --git a/internal/api/controller/user/create_access_token.go b/internal/api/controller/user/create_access_token.go index 5738452f4..f9fe9aa82 100644 --- a/internal/api/controller/user/create_access_token.go +++ b/internal/api/controller/user/create_access_token.go @@ -17,16 +17,19 @@ import ( ) type CreateTokenInput struct { - UID string `json:"uid"` - Lifetime *time.Duration `json:"lifetime"` - Grants enum.AccessGrant `json:"grants"` + UID string `json:"uid"` + Lifetime *time.Duration `json:"lifetime"` } /* * CreateToken creates a new user access token. */ -func (c *Controller) CreateAccessToken(ctx context.Context, session *auth.Session, - userUID string, in *CreateTokenInput) (*types.TokenResponse, error) { +func (c *Controller) CreateAccessToken( + ctx context.Context, + session *auth.Session, + userUID string, + in *CreateTokenInput, +) (*types.TokenResponse, error) { user, err := findUserFromUID(ctx, c.principalStore, userUID) if err != nil { return nil, err @@ -43,13 +46,15 @@ func (c *Controller) CreateAccessToken(ctx context.Context, session *auth.Sessio if err = check.TokenLifetime(in.Lifetime, true); err != nil { return nil, err } - // TODO: Added to unblock UI - Depending on product decision enforce grants, or remove Grants completely. - if err = check.AccessGrant(in.Grants, true); err != nil { - return nil, err - } - token, jwtToken, err := token.CreatePAT(ctx, c.tokenStore, &session.Principal, - user, in.UID, in.Lifetime, in.Grants) + token, jwtToken, err := token.CreatePAT( + ctx, + c.tokenStore, + &session.Principal, + user, + in.UID, + in.Lifetime, + ) if err != nil { return nil, err } diff --git a/internal/api/controller/user/logout.go b/internal/api/controller/user/logout.go index 33fa19435..4fba6aa11 100644 --- a/internal/api/controller/user/logout.go +++ b/internal/api/controller/user/logout.go @@ -33,7 +33,7 @@ func (c *Controller) Logout(ctx context.Context, session *auth.Session) error { tokenID = t.TokenID tokenType = t.TokenType default: - return errors.New("session metadata is of unknown type") + return errors.New("provided jwt doesn't support logout") } if tokenType != enum.TokenTypeSession { diff --git a/internal/auth/authn/token.go b/internal/auth/authn/jwt.go similarity index 59% rename from internal/auth/authn/token.go rename to internal/auth/authn/jwt.go index 78761fd4a..5ea444ba0 100644 --- a/internal/auth/authn/token.go +++ b/internal/auth/authn/jwt.go @@ -5,6 +5,7 @@ package authn import ( + "context" "errors" "fmt" "net/http" @@ -12,34 +13,31 @@ import ( "github.com/harness/gitness/internal/api/request" "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/internal/jwt" "github.com/harness/gitness/internal/store" - "github.com/harness/gitness/internal/token" "github.com/harness/gitness/types" - "github.com/dgrijalva/jwt-go" + gojwt "github.com/dgrijalva/jwt-go" ) -var _ Authenticator = (*TokenAuthenticator)(nil) +var _ Authenticator = (*JWTAuthenticator)(nil) -/* - * Authenticates a user by checking for an access token in the - * "Authorization" header or the "access_token" form value. - */ -type TokenAuthenticator struct { +// JWTAuthenticator uses the provided JWT to authenticate the caller. +type JWTAuthenticator struct { principalStore store.PrincipalStore tokenStore store.TokenStore } func NewTokenAuthenticator( principalStore store.PrincipalStore, - tokenStore store.TokenStore) *TokenAuthenticator { - return &TokenAuthenticator{ + tokenStore store.TokenStore) *JWTAuthenticator { + return &JWTAuthenticator{ principalStore: principalStore, tokenStore: tokenStore, } } -func (a *TokenAuthenticator) Authenticate(r *http.Request, sourceRouter SourceRouter) (*auth.Session, error) { +func (a *JWTAuthenticator) Authenticate(r *http.Request, sourceRouter SourceRouter) (*auth.Session, error) { ctx := r.Context() str := extractToken(r) @@ -49,8 +47,8 @@ func (a *TokenAuthenticator) Authenticate(r *http.Request, sourceRouter SourceRo var principal *types.Principal var err error - claims := &token.JWTClaims{} - parsed, err := jwt.ParseWithClaims(str, claims, func(token_ *jwt.Token) (interface{}, error) { + claims := &jwt.Claims{} + parsed, err := gojwt.ParseWithClaims(str, claims, func(token_ *gojwt.Token) (interface{}, error) { principal, err = a.principalStore.Find(ctx, claims.PrincipalID) if err != nil { return nil, fmt.Errorf("failed to get principal for token: %w", err) @@ -65,12 +63,39 @@ func (a *TokenAuthenticator) Authenticate(r *http.Request, sourceRouter SourceRo return nil, errors.New("parsed JWT token is invalid") } - if _, ok := parsed.Method.(*jwt.SigningMethodHMAC); !ok { + if _, ok := parsed.Method.(*gojwt.SigningMethodHMAC); !ok { return nil, errors.New("invalid HMAC signature for JWT") } + var metadata auth.Metadata + switch { + case claims.Token != nil: + metadata, err = a.metadataFromTokenClaims(ctx, principal, claims.Token) + if err != nil { + return nil, fmt.Errorf("failed to get metadata from token claims: %w", err) + } + case claims.Membership != nil: + metadata, err = a.metadataFromMembershipClaims(claims.Membership) + if err != nil { + return nil, fmt.Errorf("failed to get metadata from membership claims: %w", err) + } + default: + return nil, fmt.Errorf("jwt is missing sub-claims") + } + + return &auth.Session{ + Principal: *principal, + Metadata: metadata, + }, nil +} + +func (a *JWTAuthenticator) metadataFromTokenClaims( + ctx context.Context, + principal *types.Principal, + tknClaims *jwt.SubClaimsToken, +) (auth.Metadata, error) { // ensure tkn exists - tkn, err := a.tokenStore.Find(ctx, claims.TokenID) + tkn, err := a.tokenStore.Find(ctx, tknClaims.ID) if err != nil { return nil, fmt.Errorf("failed to find token in db: %w", err) } @@ -81,13 +106,19 @@ func (a *TokenAuthenticator) Authenticate(r *http.Request, sourceRouter SourceRo principal.ID, tkn.PrincipalID) } - return &auth.Session{ - Principal: *principal, - Metadata: &auth.TokenMetadata{ - TokenType: tkn.Type, - TokenID: tkn.ID, - Grants: tkn.Grants, - }, + return &auth.TokenMetadata{ + TokenType: tkn.Type, + TokenID: tkn.ID, + }, nil +} + +func (a *JWTAuthenticator) metadataFromMembershipClaims( + mbsClaims *jwt.SubClaimsMembership, +) (auth.Metadata, error) { + // We could check if space exists - but also okay to fail later (saves db call) + return &auth.MembershipMetadata{ + SpaceID: mbsClaims.SpaceID, + Role: mbsClaims.Role, }, nil } diff --git a/internal/auth/authz/membership.go b/internal/auth/authz/membership.go index 65bd4e398..d415837b2 100644 --- a/internal/auth/authz/membership.go +++ b/internal/auth/authz/membership.go @@ -6,9 +6,11 @@ package authz import ( "context" + "fmt" "github.com/harness/gitness/internal/auth" "github.com/harness/gitness/internal/paths" + "github.com/harness/gitness/internal/store" "github.com/harness/gitness/types" "github.com/harness/gitness/types/enum" @@ -19,13 +21,16 @@ var _ Authorizer = (*MembershipAuthorizer)(nil) type MembershipAuthorizer struct { permissionCache PermissionCache + spaceStore store.SpaceStore } func NewMembershipAuthorizer( permissionCache PermissionCache, + spaceStore store.SpaceStore, ) *MembershipAuthorizer { return &MembershipAuthorizer{ permissionCache: permissionCache, + spaceStore: spaceStore, } } @@ -51,23 +56,23 @@ func (a *MembershipAuthorizer) Check( return true, nil // system admin can call any API } - var spaceRef string + var spacePath string switch resource.Type { case enum.ResourceTypeSpace: - spaceRef = paths.Concatinate(scope.SpacePath, resource.Name) + spacePath = paths.Concatinate(scope.SpacePath, resource.Name) case enum.ResourceTypeRepo: - spaceRef = scope.SpacePath + spacePath = scope.SpacePath case enum.ResourceTypeServiceAccount: - spaceRef = scope.SpacePath + spacePath = scope.SpacePath case enum.ResourceTypePipeline: - spaceRef = scope.SpacePath + spacePath = scope.SpacePath case enum.ResourceTypeSecret: - spaceRef = scope.SpacePath + spacePath = scope.SpacePath case enum.ResourceTypeUser: // a user is allowed to view / edit themselves @@ -87,12 +92,23 @@ func (a *MembershipAuthorizer) Check( return false, nil } + // ephemeral membership overrides any other space memberships of the principal + if membershipMetadata, ok := session.Metadata.(*auth.MembershipMetadata); ok { + return a.checkWithMembershipMetadata(ctx, membershipMetadata, spacePath, permission) + } + + // ensure we aren't bypassing unknown metadata with impact on authorization + if session.Metadata.ImpactsAuthorization() { + return false, fmt.Errorf("session contains unknown metadata that impacts authorization: %T", session.Metadata) + } + return a.permissionCache.Get(ctx, PermissionCacheKey{ PrincipalID: session.Principal.ID, - SpaceRef: spaceRef, + SpaceRef: spacePath, Permission: permission, }) } + func (a *MembershipAuthorizer) CheckAll(ctx context.Context, session *auth.Session, permissionChecks ...types.PermissionCheck) (bool, error) { for _, p := range permissionChecks { @@ -103,3 +119,35 @@ func (a *MembershipAuthorizer) CheckAll(ctx context.Context, session *auth.Sessi return true, nil } + +// checkWithMembershipMetadata checks access using the ephemeral membership provided in the metadata. +func (a *MembershipAuthorizer) checkWithMembershipMetadata( + ctx context.Context, + membershipMetadata *auth.MembershipMetadata, + requestedSpacePath string, + requestedPermission enum.Permission, +) (bool, error) { + space, err := a.spaceStore.Find(ctx, membershipMetadata.SpaceID) + if err != nil { + return false, fmt.Errorf("failed to find space: %w", err) + } + + if !paths.IsAncesterOf(space.Path, requestedSpacePath) { + return false, fmt.Errorf( + "requested permission scope '%s' is outside of ephemeral membership scope '%s'", + requestedSpacePath, + space.Path, + ) + } + + if !roleHasPermission(membershipMetadata.Role, requestedPermission) { + return false, fmt.Errorf( + "requested permission '%s' is outside of ephemeral membership role '%s'", + requestedPermission, + membershipMetadata.Role, + ) + } + + // access is granted by ephemeral membership + return true, nil +} diff --git a/internal/auth/authz/membership_cache.go b/internal/auth/authz/membership_cache.go index b920c5b04..482f75070 100644 --- a/internal/auth/authz/membership_cache.go +++ b/internal/auth/authz/membership_cache.go @@ -67,11 +67,9 @@ func (g permissionCacheGetter) Find(ctx context.Context, key PermissionCacheKey) } // If the membership is defined in the current space, check if the user has the required permission. - if membership != nil { - _, hasRole := slices.BinarySearch(membership.Role.Permissions(), key.Permission) - if hasRole { - return true, nil - } + if membership != nil && + roleHasPermission(membership.Role, key.Permission) { + return true, nil } // If membership with the requested permission has not been found in the current space, @@ -89,3 +87,8 @@ func (g permissionCacheGetter) Find(ctx context.Context, key PermissionCacheKey) return false, nil } + +func roleHasPermission(role enum.MembershipRole, permission enum.Permission) bool { + _, hasRole := slices.BinarySearch(role.Permissions(), permission) + return hasRole +} diff --git a/internal/auth/authz/wire.go b/internal/auth/authz/wire.go index 81cd2fc46..1a42f9688 100644 --- a/internal/auth/authz/wire.go +++ b/internal/auth/authz/wire.go @@ -18,8 +18,8 @@ var WireSet = wire.NewSet( ProvidePermissionCache, ) -func ProvideAuthorizer(pCache PermissionCache) Authorizer { - return NewMembershipAuthorizer(pCache) +func ProvideAuthorizer(pCache PermissionCache, spaceStore store.SpaceStore) Authorizer { + return NewMembershipAuthorizer(pCache, spaceStore) } func ProvidePermissionCache( diff --git a/internal/auth/metadata.go b/internal/auth/metadata.go index 9eb3bba40..eeecc86d8 100644 --- a/internal/auth/metadata.go +++ b/internal/auth/metadata.go @@ -17,23 +17,22 @@ func (m *EmptyMetadata) ImpactsAuthorization() bool { return false } -// SSHMetadata contains information about the ssh connection that was used during auth. -type SSHMetadata struct { - KeyID string - Grants enum.AccessGrant // retrieved from ssh key table during verification -} - -func (m *SSHMetadata) ImpactsAuthorization() bool { - return m.Grants != enum.AccessGrantAll -} - // TokenMetadata contains information about the token that was used during auth. type TokenMetadata struct { TokenType enum.TokenType TokenID int64 - Grants enum.AccessGrant // retrieved from token during verification } func (m *TokenMetadata) ImpactsAuthorization() bool { - return m.Grants != enum.AccessGrantAll + return false +} + +// MembershipMetadata contains information about an ephemeral membership grant. +type MembershipMetadata struct { + SpaceID int64 + Role enum.MembershipRole +} + +func (m *MembershipMetadata) ImpactsAuthorization() bool { + return true } diff --git a/internal/bootstrap/bootstrap.go b/internal/bootstrap/bootstrap.go index fc647115f..a2e55a6ac 100644 --- a/internal/bootstrap/bootstrap.go +++ b/internal/bootstrap/bootstrap.go @@ -29,6 +29,17 @@ func NewSystemServiceSession() *auth.Session { } } +// pipelineServicePrincipal is the principal that is used during +// pipeline executions for calling gitness APIs. +var pipelineServicePrincipal *types.Principal + +func NewPipelineServiceSession() *auth.Session { + return &auth.Session{ + Principal: *pipelineServicePrincipal, + Metadata: &auth.EmptyMetadata{}, + } +} + // Bootstrap is an abstraction of a function that bootstraps a system. type Bootstrap func(context.Context) error @@ -36,11 +47,15 @@ func System(config *types.Config, userCtrl *user.Controller, serviceCtrl *service.Controller) func(context.Context) error { return func(ctx context.Context) error { if err := SystemService(ctx, config, serviceCtrl); err != nil { - return err + return fmt.Errorf("failed to setup system service: %w", err) + } + + if err := PipelineService(ctx, config, serviceCtrl); err != nil { + return fmt.Errorf("failed to setup pipeline service: %w", err) } if err := AdminUser(ctx, config, userCtrl); err != nil { - return err + return fmt.Errorf("failed to setup admin user: %w", err) } return nil @@ -70,7 +85,11 @@ func AdminUser(ctx context.Context, config *types.Config, userCtrl *user.Control return nil } -func createAdminUser(ctx context.Context, config *types.Config, userCtrl *user.Controller) (*types.User, error) { +func createAdminUser( + ctx context.Context, + config *types.Config, + userCtrl *user.Controller, +) (*types.User, error) { in := &user.CreateInput{ UID: config.Principal.Admin.UID, DisplayName: config.Principal.Admin.DisplayName, @@ -96,10 +115,21 @@ func createAdminUser(ctx context.Context, config *types.Config, userCtrl *user.C // SystemService sets up the gitness service principal that is used for // resources that are automatically created by the system. -func SystemService(ctx context.Context, config *types.Config, serviceCtrl *service.Controller) error { +func SystemService( + ctx context.Context, + config *types.Config, + serviceCtrl *service.Controller, +) error { svc, err := serviceCtrl.FindNoAuth(ctx, config.Principal.System.UID) if errors.Is(err, store.ErrResourceNotFound) { - svc, err = createSystemService(ctx, config, serviceCtrl) + svc, err = createServicePrincipal( + ctx, + serviceCtrl, + config.Principal.System.UID, + config.Principal.System.Email, + config.Principal.System.DisplayName, + true, + ) } if err != nil { @@ -116,25 +146,65 @@ func SystemService(ctx context.Context, config *types.Config, serviceCtrl *servi return nil } -func createSystemService(ctx context.Context, config *types.Config, - serviceCtrl *service.Controller) (*types.Service, error) { - in := &service.CreateInput{ - UID: config.Principal.System.UID, - Email: config.Principal.System.Email, - DisplayName: config.Principal.System.DisplayName, +// PipelineService sets up the pipeline service principal that is used during +// pipeline executions for calling gitness APIs. +func PipelineService( + ctx context.Context, + config *types.Config, + serviceCtrl *service.Controller, +) error { + svc, err := serviceCtrl.FindNoAuth(ctx, config.Principal.Pipeline.UID) + if errors.Is(err, store.ErrResourceNotFound) { + svc, err = createServicePrincipal( + ctx, + serviceCtrl, + config.Principal.Pipeline.UID, + config.Principal.Pipeline.Email, + config.Principal.Pipeline.DisplayName, + false, + ) } - svc, createErr := serviceCtrl.CreateNoAuth(ctx, in, true) + if err != nil { + return fmt.Errorf("failed to setup pipeline service: %w", err) + } + + pipelineServicePrincipal = svc.ToPrincipal() + + log.Ctx(ctx).Info().Msgf("Completed setup of pipeline service '%s' (id: %d).", svc.UID, svc.ID) + + return nil +} + +func createServicePrincipal( + ctx context.Context, + serviceCtrl *service.Controller, + uid string, + email string, + displayName string, + admin bool, +) (*types.Service, error) { + in := &service.CreateInput{ + UID: uid, + Email: email, + DisplayName: displayName, + } + + svc, createErr := serviceCtrl.CreateNoAuth(ctx, in, admin) if createErr == nil || !errors.Is(createErr, store.ErrDuplicate) { return svc, createErr } // service might've been created by another instance in which case we should find it now. var findErr error - svc, findErr = serviceCtrl.FindNoAuth(ctx, config.Principal.System.UID) + svc, findErr = serviceCtrl.FindNoAuth(ctx, uid) if findErr != nil { - return nil, fmt.Errorf("failed to find service with uid '%s' (%s) after duplicate error: %w", - config.Principal.System.UID, findErr, createErr) + return nil, fmt.Errorf( + "failed to find service with uid '%s' (%s) after duplicate error: %w", + uid, + findErr, + createErr, + ) } return svc, nil diff --git a/internal/jwt/jwt.go b/internal/jwt/jwt.go new file mode 100644 index 000000000..917c55c10 --- /dev/null +++ b/internal/jwt/jwt.go @@ -0,0 +1,97 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package jwt + +import ( + "time" + + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" + + "github.com/golang-jwt/jwt" + "github.com/pkg/errors" +) + +const ( + issuer = "Gitness" +) + +// Claims defines gitness jwt claims. +type Claims struct { + jwt.StandardClaims + + PrincipalID int64 `json:"pid,omitempty"` + + Token *SubClaimsToken `json:"tkn,omitempty"` + Membership *SubClaimsMembership `json:"ms,omitempty"` +} + +// SubClaimsToken contains information about the token the JWT was created for. +type SubClaimsToken struct { + Type enum.TokenType `json:"typ,omitempty"` + ID int64 `json:"id,omitempty"` +} + +// SubClaimsMembership contains the ephemeral membership the JWT was created with. +type SubClaimsMembership struct { + Role enum.MembershipRole `json:"role,omitempty"` + SpaceID int64 `json:"sid,omitempty"` +} + +// GenerateForToken generates a jwt for a given token. +func GenerateForToken(token *types.Token, secret string) (string, error) { + var expiresAt int64 + if token.ExpiresAt != nil { + expiresAt = *token.ExpiresAt + } + + jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, Claims{ + StandardClaims: jwt.StandardClaims{ + Issuer: issuer, + // times required to be in sec not millisec + IssuedAt: token.IssuedAt / 1000, + ExpiresAt: expiresAt / 1000, + }, + PrincipalID: token.PrincipalID, + Token: &SubClaimsToken{ + Type: token.Type, + ID: token.ID, + }, + }) + + res, err := jwtToken.SignedString([]byte(secret)) + if err != nil { + return "", errors.Wrap(err, "Failed to sign token") + } + + return res, nil +} + +// GenerateWithMembership generates a jwt with the given ephemeral membership. +func GenerateWithMembership(principalID int64, spaceID int64, role enum.MembershipRole, lifetime time.Duration, secret string) (string, error) { + issuedAt := time.Now() + expiresAt := issuedAt.Add(lifetime) + + jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, Claims{ + StandardClaims: jwt.StandardClaims{ + Issuer: issuer, + // times required to be in sec + IssuedAt: issuedAt.Unix(), + ExpiresAt: expiresAt.Unix(), + }, + PrincipalID: principalID, + Membership: &SubClaimsMembership{ + SpaceID: spaceID, + Role: role, + }, + }) + + res, err := jwtToken.SignedString([]byte(secret)) + if err != nil { + return "", errors.Wrap(err, "Failed to sign token") + } + + return res, nil +} diff --git a/internal/paths/paths.go b/internal/paths/paths.go index 2e3f35ddd..4011498cf 100644 --- a/internal/paths/paths.go +++ b/internal/paths/paths.go @@ -18,6 +18,8 @@ var ( // DisectLeaf splits a path into its parent path and the leaf name // e.g. space1/space2/space3 -> (space1/space2, space3, nil). func DisectLeaf(path string) (string, string, error) { + path = strings.Trim(path, types.PathSeparator) + if path == "" { return "", "", ErrPathEmpty } @@ -33,6 +35,8 @@ func DisectLeaf(path string) (string, string, error) { // DisectRoot splits a path into its root space and sub-path // e.g. space1/space2/space3 -> (space1, space2/space3, nil). func DisectRoot(path string) (string, string, error) { + path = strings.Trim(path, types.PathSeparator) + if path == "" { return "", "", ErrPathEmpty } @@ -67,5 +71,19 @@ func Concatinate(path1 string, path2 string) string { // Segments returns all segments of the path // e.g. /space1/space2/space3 -> [space1, space2, space3]. func Segments(path string) []string { + path = strings.Trim(path, types.PathSeparator) return strings.Split(path, types.PathSeparator) } + +// IsAncesterOf returns true iff 'path' is an ancestor of 'other' or they are the same. +// e.g. other = path(/.*) +func IsAncesterOf(path string, other string) bool { + path = strings.Trim(path, types.PathSeparator) + other = strings.Trim(other, types.PathSeparator) + + // add "/" to both to handle space1/inner and space1/in + return strings.Contains( + other+types.PathSeparator, + path+types.PathSeparator, + ) +} diff --git a/internal/pipeline/manager/client.go b/internal/pipeline/manager/client.go index 86f85b4d6..44bf7a046 100644 --- a/internal/pipeline/manager/client.go +++ b/internal/pipeline/manager/client.go @@ -83,12 +83,14 @@ func (e *embedded) Detail(ctx context.Context, stage *drone.Stage) (*client.Cont if err != nil { return nil, err } + return &client.Context{ Build: convertToDroneBuild(details.Execution), Repo: convertToDroneRepo(details.Repo), Stage: convertToDroneStage(details.Stage), Secrets: convertToDroneSecrets(details.Secrets), Config: convertToDroneFile(details.Config), + Netrc: convertToDroneNetrc(details.Netrc), System: &drone.System{ Proto: e.config.Server.HTTP.Proto, Host: "host.docker.internal", diff --git a/internal/pipeline/manager/convert.go b/internal/pipeline/manager/convert.go index 6929e5aa8..59b0683fa 100644 --- a/internal/pipeline/manager/convert.go +++ b/internal/pipeline/manager/convert.go @@ -236,3 +236,15 @@ func convertToDroneSecrets(secrets []*types.Secret) []*drone.Secret { } return ret } + +func convertToDroneNetrc(netrc *Netrc) *drone.Netrc { + if netrc == nil { + return nil + } + + return &drone.Netrc{ + Machine: netrc.Machine, + Login: netrc.Login, + Password: netrc.Password, + } +} diff --git a/internal/pipeline/manager/manager.go b/internal/pipeline/manager/manager.go index 0dd0ea873..73700583d 100644 --- a/internal/pipeline/manager/manager.go +++ b/internal/pipeline/manager/manager.go @@ -9,7 +9,11 @@ import ( "errors" "fmt" "io" + "net/url" + "time" + "github.com/harness/gitness/internal/bootstrap" + "github.com/harness/gitness/internal/jwt" "github.com/harness/gitness/internal/pipeline/file" "github.com/harness/gitness/internal/pipeline/scheduler" "github.com/harness/gitness/internal/sse" @@ -23,6 +27,13 @@ import ( "github.com/rs/zerolog/log" ) +const ( + // pipelineJWTLifetime specifies the max lifetime of an ephemeral pipeline jwt token. + pipelineJWTLifetime = 72 * time.Hour + // pipelineJWTRole specifies the role of an ephemeral pipeline jwt token. + pipelineJWTRole = enum.MembershipRoleContributor +) + var noContext = context.Background() var _ ExecutionManager = (*Manager)(nil) @@ -47,6 +58,14 @@ type ( Kind string `json:"kind"` } + // Netrc contains login and initialization information used + // by an automated login process. + Netrc struct { + Machine string `json:"machine"` + Login string `json:"login"` + Password string `json:"password"` + } + // ExecutionContext represents the minimum amount of information // required by the runner to execute a build. ExecutionContext struct { @@ -55,6 +74,7 @@ type ( Stage *types.Stage `json:"stage"` Secrets []*types.Secret `json:"secrets"` Config *file.File `json:"config"` + Netrc *Netrc `json:"netrc"` } // ExecutionManager encapsulates complex build operations and provides @@ -294,12 +314,44 @@ func (m *Manager) Details(ctx context.Context, stageID int64) (*ExecutionContext return nil, err } + netrc, err := m.createNetrc(repo) + if err != nil { + log.Warn().Err(err).Msg("manager: failed to create netrc") + return nil, err + } + return &ExecutionContext{ Repo: repo, Execution: execution, Stage: stage, Secrets: secrets, Config: file, + Netrc: netrc, + }, nil +} + +func (m *Manager) createNetrc(repo *types.Repository) (*Netrc, error) { + pipelinePrincipal := bootstrap.NewPipelineServiceSession().Principal + jwt, err := jwt.GenerateWithMembership( + pipelinePrincipal.ID, + repo.ParentID, + pipelineJWTRole, + pipelineJWTLifetime, + pipelinePrincipal.Salt, + ) + if err != nil { + return nil, fmt.Errorf("failed to create jwt: %w", err) + } + + cloneUrl, err := url.Parse(repo.GitURL) + if err != nil { + return nil, fmt.Errorf("failed to parse clone url '%s': %w", cloneUrl, err) + } + + return &Netrc{ + Machine: cloneUrl.Hostname(), + Login: pipelinePrincipal.UID, + Password: jwt, }, nil } diff --git a/internal/store/database/migrate/postgres/0001_create_table_e_tokens.up.sql b/internal/store/database/migrate/postgres/0001_create_table_e_tokens.up.sql index b64112151..438a17409 100644 --- a/internal/store/database/migrate/postgres/0001_create_table_e_tokens.up.sql +++ b/internal/store/database/migrate/postgres/0001_create_table_e_tokens.up.sql @@ -8,4 +8,9 @@ CREATE TABLE tokens ( ,token_issued_at BIGINT ,token_created_by INTEGER ,UNIQUE(token_principal_id, token_uid) + +,CONSTRAINT fk_token_principal_id FOREIGN KEY (token_principal_id) + REFERENCES principals (principal_id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE ); diff --git a/internal/store/database/migrate/postgres/0028_alter_token_drop_grants.down.sql b/internal/store/database/migrate/postgres/0028_alter_token_drop_grants.down.sql new file mode 100644 index 000000000..3339b927b --- /dev/null +++ b/internal/store/database/migrate/postgres/0028_alter_token_drop_grants.down.sql @@ -0,0 +1 @@ +ALTER TABLE tokens ADD COLUMN token_grants BIGINT DEFAULT 0; \ No newline at end of file diff --git a/internal/store/database/migrate/postgres/0028_alter_token_drop_grants.up.sql b/internal/store/database/migrate/postgres/0028_alter_token_drop_grants.up.sql new file mode 100644 index 000000000..376b0fa63 --- /dev/null +++ b/internal/store/database/migrate/postgres/0028_alter_token_drop_grants.up.sql @@ -0,0 +1 @@ +ALTER TABLE tokens DROP COLUMN token_grants; diff --git a/internal/store/database/migrate/sqlite/0001_create_table_e_tokens.up.sql b/internal/store/database/migrate/sqlite/0001_create_table_e_tokens.up.sql index bf847c83d..908e4d331 100644 --- a/internal/store/database/migrate/sqlite/0001_create_table_e_tokens.up.sql +++ b/internal/store/database/migrate/sqlite/0001_create_table_e_tokens.up.sql @@ -8,4 +8,9 @@ CREATE TABLE tokens ( ,token_issued_at BIGINT ,token_created_by INTEGER ,UNIQUE(token_principal_id, token_uid COLLATE NOCASE) + +,CONSTRAINT fk_token_principal_id FOREIGN KEY (token_principal_id) + REFERENCES principals (principal_id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE ); diff --git a/internal/store/database/migrate/sqlite/0028_alter_token_drop_grants.down.sql b/internal/store/database/migrate/sqlite/0028_alter_token_drop_grants.down.sql new file mode 100644 index 000000000..3339b927b --- /dev/null +++ b/internal/store/database/migrate/sqlite/0028_alter_token_drop_grants.down.sql @@ -0,0 +1 @@ +ALTER TABLE tokens ADD COLUMN token_grants BIGINT DEFAULT 0; \ No newline at end of file diff --git a/internal/store/database/migrate/sqlite/0028_alter_token_drop_grants.up.sql b/internal/store/database/migrate/sqlite/0028_alter_token_drop_grants.up.sql new file mode 100644 index 000000000..376b0fa63 --- /dev/null +++ b/internal/store/database/migrate/sqlite/0028_alter_token_drop_grants.up.sql @@ -0,0 +1 @@ +ALTER TABLE tokens DROP COLUMN token_grants; diff --git a/internal/store/database/token.go b/internal/store/database/token.go index 2e7ef9fc9..2bf7322a9 100644 --- a/internal/store/database/token.go +++ b/internal/store/database/token.go @@ -127,7 +127,6 @@ token_id ,token_uid ,token_principal_id ,token_expires_at -,token_grants ,token_issued_at ,token_created_by FROM tokens @@ -168,7 +167,6 @@ INSERT INTO tokens ( ,token_uid ,token_principal_id ,token_expires_at - ,token_grants ,token_issued_at ,token_created_by ) values ( @@ -176,7 +174,6 @@ INSERT INTO tokens ( ,:token_uid ,:token_principal_id ,:token_expires_at - ,:token_grants ,:token_issued_at ,:token_created_by ) RETURNING token_id diff --git a/internal/token/jwt.go b/internal/token/jwt.go deleted file mode 100644 index db49243a3..000000000 --- a/internal/token/jwt.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2022 Harness Inc. All rights reserved. -// Use of this source code is governed by the Polyform Free Trial License -// that can be found in the LICENSE.md file for this repository. - -package token - -import ( - "github.com/harness/gitness/types" - "github.com/harness/gitness/types/enum" - - "github.com/golang-jwt/jwt" - "github.com/pkg/errors" -) - -const ( - issuer = "Gitness" -) - -// JWTClaims defines custom token claims. -type JWTClaims struct { - jwt.StandardClaims - - TokenType enum.TokenType `json:"ttp,omitempty"` - TokenID int64 `json:"tid,omitempty"` - PrincipalID int64 `json:"pid,omitempty"` -} - -// GenerateJWTForToken generates a jwt for a given token. -func GenerateJWTForToken(token *types.Token, secret string) (string, error) { - var expiresAt int64 - if token.ExpiresAt != nil { - expiresAt = *token.ExpiresAt - } - - jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, JWTClaims{ - jwt.StandardClaims{ - Issuer: issuer, - // times required to be in sec not millisec - IssuedAt: token.IssuedAt / 1000, - ExpiresAt: expiresAt / 1000, - }, - token.Type, - token.ID, - token.PrincipalID, - }) - - res, err := jwtToken.SignedString([]byte(secret)) - if err != nil { - return "", errors.Wrap(err, "Failed to sign token") - } - - return res, nil -} diff --git a/internal/token/token.go b/internal/token/token.go index b415adf28..8c63eb29e 100644 --- a/internal/token/token.go +++ b/internal/token/token.go @@ -9,6 +9,7 @@ import ( "fmt" "time" + "github.com/harness/gitness/internal/jwt" "github.com/harness/gitness/internal/store" "github.com/harness/gitness/types" "github.com/harness/gitness/types/enum" @@ -20,10 +21,14 @@ const ( userTokenLifeTime time.Duration = 24 * time.Hour // 1 day. ) -func CreateUserSession(ctx context.Context, tokenStore store.TokenStore, - user *types.User, uid string) (*types.Token, string, error) { +func CreateUserSession( + ctx context.Context, + tokenStore store.TokenStore, + user *types.User, + uid string, +) (*types.Token, string, error) { principal := user.ToPrincipal() - return Create( + return create( ctx, tokenStore, enum.TokenTypeSession, @@ -31,14 +36,18 @@ func CreateUserSession(ctx context.Context, tokenStore store.TokenStore, principal, uid, ptr.Duration(userTokenLifeTime), - enum.AccessGrantAll, ) } -func CreatePAT(ctx context.Context, tokenStore store.TokenStore, - createdBy *types.Principal, createdFor *types.User, - uid string, lifetime *time.Duration, grants enum.AccessGrant) (*types.Token, string, error) { - return Create( +func CreatePAT( + ctx context.Context, + tokenStore store.TokenStore, + createdBy *types.Principal, + createdFor *types.User, + uid string, + lifetime *time.Duration, +) (*types.Token, string, error) { + return create( ctx, tokenStore, enum.TokenTypePAT, @@ -46,14 +55,18 @@ func CreatePAT(ctx context.Context, tokenStore store.TokenStore, createdFor.ToPrincipal(), uid, lifetime, - grants, ) } -func CreateSAT(ctx context.Context, tokenStore store.TokenStore, - createdBy *types.Principal, createdFor *types.ServiceAccount, - uid string, lifetime *time.Duration, grants enum.AccessGrant) (*types.Token, string, error) { - return Create( +func CreateSAT( + ctx context.Context, + tokenStore store.TokenStore, + createdBy *types.Principal, + createdFor *types.ServiceAccount, + uid string, + lifetime *time.Duration, +) (*types.Token, string, error) { + return create( ctx, tokenStore, enum.TokenTypeSAT, @@ -61,13 +74,18 @@ func CreateSAT(ctx context.Context, tokenStore store.TokenStore, createdFor.ToPrincipal(), uid, lifetime, - grants, ) } -func Create(ctx context.Context, tokenStore store.TokenStore, - tokenType enum.TokenType, createdBy *types.Principal, createdFor *types.Principal, - uid string, lifetime *time.Duration, grants enum.AccessGrant) (*types.Token, string, error) { +func create( + ctx context.Context, + tokenStore store.TokenStore, + tokenType enum.TokenType, + createdBy *types.Principal, + createdFor *types.Principal, + uid string, + lifetime *time.Duration, +) (*types.Token, string, error) { issuedAt := time.Now() var expiresAt *int64 @@ -82,7 +100,6 @@ func Create(ctx context.Context, tokenStore store.TokenStore, PrincipalID: createdFor.ID, IssuedAt: issuedAt.UnixMilli(), ExpiresAt: expiresAt, - Grants: grants, CreatedBy: createdBy.ID, } @@ -92,7 +109,7 @@ func Create(ctx context.Context, tokenStore store.TokenStore, } // create jwt token. - jwtToken, err := GenerateJWTForToken(&token, createdFor.Salt) + jwtToken, err := jwt.GenerateForToken(&token, createdFor.Salt) if err != nil { return nil, "", fmt.Errorf("failed to create jwt token: %w", err) } diff --git a/internal/token/token_test.go b/internal/token/token_test.go deleted file mode 100644 index 855ba67f4..000000000 --- a/internal/token/token_test.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2022 Harness Inc. All rights reserved. -// Use of this source code is governed by the Polyform Free Trial License -// that can be found in the LICENSE.md file for this repository. - -package token - -import ( - "testing" -) - -func TestToken(t *testing.T) { - // user := &types.User{ID: 42, Admin: true} - // tokenStr, err := GenerateJWT(user, "TEST0E4C2F76C58916E") - // if err != nil { - // t.Error(err) - // return - // } - - // token, err := jwt.ParseWithClaims(tokenStr, &JWTClaims{}, func(token *jwt.Token) (interface{}, error) { - // sub := token.Claims.(*JWTClaims).Subject - // id, _ := strconv.ParseInt(sub, 10, 64) - // if id != 42 { - // t.Errorf("want subscriber id, got %v", id) - // } - // return []byte("TEST0E4C2F76C58916E"), nil - // }) - // if err != nil { - // t.Error(err) - // return - // } - // if token.Valid == false { - // t.Errorf("invalid token") - // return - // } - // if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { - // t.Errorf("invalid token signing method") - // return - // } - - // if expires := token.Claims.(*JWTClaims).ExpiresAt; expires > 0 { - // if time.Now().Unix() > expires { - // t.Errorf("token expired") - // } - // } -} - -func TestTokenExpired(t *testing.T) { - // user := &types.User{ID: 42, Admin: true} - // tokenStr, err := GenerateJWTWithExpiration(user, 1637549186, "TEST0E4C2F76C58916E") - // if err != nil { - // t.Error(err) - // return - // } - - // _, err = jwt.ParseWithClaims(tokenStr, &JWTClaims{}, func(token *jwt.Token) (interface{}, error) { - // sub := token.Claims.(*JWTClaims).Subject - // id, _ := strconv.ParseInt(sub, 10, 64) - // if id != 42 { - // t.Errorf("want subscriber id, got %v", id) - // } - // return []byte("TEST0E4C2F76C58916E"), nil - // }) - // if err == nil { - // t.Errorf("expect token expired") - // return - // } -} - -func TestTokenNotExpired(t *testing.T) { - // user := &types.User{ID: 42, Admin: true} - // tokenStr, err := GenerateJWTWithExpiration(user, time.Now().Add(time.Hour).Unix(), "TEST0E4C2F76C58916E") - // if err != nil { - // t.Error(err) - // return - // } - - // token, err := jwt.ParseWithClaims(tokenStr, &JWTClaims{}, func(token *jwt.Token) (interface{}, error) { - // sub := token.Claims.(*JWTClaims).Subject - // id, _ := strconv.ParseInt(sub, 10, 64) - // if id != 42 { - // t.Errorf("want subscriber id, got %v", id) - // } - // return []byte("TEST0E4C2F76C58916E"), nil - // }) - // if err != nil { - // t.Error(err) - // return - // } - - // claims, ok := token.Claims.(*JWTClaims) - // if !ok { - // t.Errorf("expect token claims from token") - // return - // } - - // if claims.ExpiresAt > 0 && time.Now().Unix() > claims.ExpiresAt { - // t.Errorf("expect token not expired") - // return - // } -} diff --git a/types/check/grant.go b/types/check/grant.go deleted file mode 100644 index 8d13cb660..000000000 --- a/types/check/grant.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2022 Harness Inc. All rights reserved. -// Use of this source code is governed by the Polyform Free Trial License -// that can be found in the LICENSE.md file for this repository. - -package check - -import ( - "github.com/harness/gitness/types/enum" -) - -var ( - ErrTokenGrantEmpty = &ValidationError{ - "The token requires at least one grant.", - } -) - -// AccessGrant returns true if the access grant is valid. -func AccessGrant(grant enum.AccessGrant, allowNone bool) error { - if !allowNone && grant == enum.AccessGrantNone { - return ErrTokenGrantEmpty - } - - // TODO: Ensure grant contains valid values? - - return nil -} diff --git a/types/config.go b/types/config.go index 85421310a..aef2f54c5 100644 --- a/types/config.go +++ b/types/config.go @@ -147,7 +147,14 @@ type Config struct { DisplayName string `envconfig:"GITNESS_PRINCIPAL_SYSTEM_DISPLAY_NAME" default:"Gitness"` Email string `envconfig:"GITNESS_PRINCIPAL_SYSTEM_EMAIL" default:"system@gitness.io"` } + // Pipeline defines the principal information used to create the pipeline service. + Pipeline struct { + UID string `envconfig:"GITNESS_PRINCIPAL_PIPELINE_UID" default:"pipeline"` + DisplayName string `envconfig:"GITNESS_PRINCIPAL_PIPELINE_DISPLAY_NAME" default:"Gitness Pipeline"` + Email string `envconfig:"GITNESS_PRINCIPAL_PIPELINE_EMAIL" default:"pipeline@gitness.io"` + } // Admin defines the principal information used to create the admin user. + // NOTE: The admin user is only auto-created in case a password is provided. Admin struct { UID string `envconfig:"GITNESS_PRINCIPAL_ADMIN_UID" default:"admin"` DisplayName string `envconfig:"GITNESS_PRINCIPAL_ADMIN_DISPLAY_NAME" default:"Administrator"` diff --git a/types/enum/grant.go b/types/enum/grant.go deleted file mode 100644 index a75a5eefb..000000000 --- a/types/enum/grant.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2022 Harness Inc. All rights reserved. -// Use of this source code is governed by the Polyform Free Trial License -// that can be found in the LICENSE.md file for this repository. - -package enum - -// AccessGrant represents the access grants a token or sshkey can have. -// Keep as int64 to allow for simpler+faster lookup of grants for a given token -// as we don't have to store an array field or need to do a join / 2nd db call. -// Multiple grants can be combined using the bit-wise or operation. -// ASSUMPTION: we don't need more than 63 grants! -// -// NOTE: A grant is always restricted by the principal permissions -// -// TODO: Beter name, access grant and permission might be to close in terminology? -type AccessGrant int64 - -const ( - // no grants - useless token. - AccessGrantNone AccessGrant = 0 - - // privacy related grants. - AccessGrantPublic AccessGrant = 1 << 0 // 1 - AccessGrantPrivate AccessGrant = 1 << 1 // 2 - - // api related grants (spaces / repos, ...). - AccessGrantAPICreate AccessGrant = 1 << 10 // 1024 - AccessGrantAPIView AccessGrant = 1 << 11 // 2048 - AccessGrantAPIEdit AccessGrant = 1 << 12 // 4096 - AccessGrantAPIDelete AccessGrant = 1 << 13 // 8192 - - // code related grants. - AccessGrantCodeRead AccessGrant = 1 << 20 // 1048576 - AccessGrantCodeWrite AccessGrant = 1 << 21 // 2097152 - - // grants everything - for user sessions. - AccessGrantAll AccessGrant = 1<<63 - 1 -) - -// DoesGrantContain checks whether the grants contain all grants in the provided grant. -func (g AccessGrant) Contains(grants AccessGrant) bool { - return g&grants == grants -} - -// CombineGrants combines all grants into a single grant. -// Note: duplicates are ignored. -func CombineGrants(grants ...AccessGrant) AccessGrant { - res := AccessGrantNone - - for _, grant := range grants { - res |= grant - } - - return res -} diff --git a/types/token.go b/types/token.go index 80b65dfac..6c64067c8 100644 --- a/types/token.go +++ b/types/token.go @@ -18,9 +18,8 @@ type Token struct { // ExpiresAt is an optional unix time that if specified restricts the validity of a token. ExpiresAt *int64 `db:"token_expires_at" json:"expires_at,omitempty"` // IssuedAt is the unix time at which the token was issued. - IssuedAt int64 `db:"token_issued_at" json:"issued_at"` - Grants enum.AccessGrant `db:"token_grants" json:"grants"` - CreatedBy int64 `db:"token_created_by" json:"created_by"` + IssuedAt int64 `db:"token_issued_at" json:"issued_at"` + CreatedBy int64 `db:"token_created_by" json:"created_by"` } // TokenResponse is returned as part of token creation for PAT / SAT / User Session. diff --git a/web/src/services/code/index.tsx b/web/src/services/code/index.tsx index 22dcd3dc4..7eac0bf35 100644 --- a/web/src/services/code/index.tsx +++ b/web/src/services/code/index.tsx @@ -5,8 +5,6 @@ import { Get, GetProps, useGet, UseGetProps, Mutate, MutateProps, useMutate, Use import { getConfig } from '../config' export const SPEC_VERSION = '0.0.0' -export type EnumAccessGrant = number - export type EnumCIStatus = string export type EnumCheckPayloadKind = '' | 'markdown' | 'pipeline' | 'raw' @@ -270,7 +268,6 @@ export interface OpenapiCreateTemplateRequest { } export interface OpenapiCreateTokenRequest { - grants?: EnumAccessGrant lifetime?: TimeDuration uid?: string } @@ -872,7 +869,6 @@ export interface TypesTemplate { export interface TypesToken { created_by?: number expires_at?: number | null - grants?: EnumAccessGrant issued_at?: number principal_id?: number type?: EnumTokenType diff --git a/web/src/services/code/swagger.yaml b/web/src/services/code/swagger.yaml index d3de7f3db..b2b2e6217 100644 --- a/web/src/services/code/swagger.yaml +++ b/web/src/services/code/swagger.yaml @@ -6277,8 +6277,6 @@ paths: - user components: schemas: - EnumAccessGrant: - type: integer EnumCIStatus: type: string EnumCheckPayloadKind: @@ -6745,8 +6743,6 @@ components: type: object OpenapiCreateTokenRequest: properties: - grants: - $ref: '#/components/schemas/EnumAccessGrant' lifetime: $ref: '#/components/schemas/TimeDuration' uid: @@ -7805,8 +7801,6 @@ components: expires_at: nullable: true type: integer - grants: - $ref: '#/components/schemas/EnumAccessGrant' issued_at: type: integer principal_id: