mirror of https://github.com/harness/drone.git
Introduce UIDs for Space / Repo / Tokens, Add Custom Harness Validation, ... (#57)
This change adds the following: - Space UID + Custom harness validation (accountId for top level space, harness identifier for child spaces) - Repo UID + Custom harness validation (harness identifier) - Store Unique casing of space / repo path and add Path.ValueUnique (with Unique index) to allow for application layer controlling the case sensitivity (case insensitive standalone vs partially case sensitive harness) - Token UID (unique index over ownertype + ownerID + tokenUID) - Add DisplayName for principals (replaces Name to avoid confustion) - Store Unique casing of principal UID and add Principal.ValueUnique (with unique index) to allow for application layer, per principal type control of case sensitivity (required in embedded mode) - Generate serviceAccount UID (+Email) Randomly (sa-{space|repo}-{ID}-{random}) - Allows to have a unique UID across all principals while reducing likelyhood of overlaps with users + avoid overlap across spaces / repos. - Sync casing of space names (accountId orgId projectId) when creating spaces on the fly (to ensure case sensitivity of - harness code) or use the existing space to update casing. - Update serviceaccount client to match updated NG Manager API - in embedded mode create spaces for harness resources owning the service accountjobatzil/rename
parent
2fe8669119
commit
3ba0f75c8d
|
@ -1,11 +1,8 @@
|
|||
# Gitness values
|
||||
GITNESS_TRACE=true
|
||||
GITNESS_ADMIN_NAME=Administrator
|
||||
GITNESS_ADMIN_EMAIL=gitness@harness.io
|
||||
GITNESS_ADMIN_PASSWORD=changeit
|
||||
|
||||
# Harness specifc values
|
||||
HARNESS_JWT_IDENTITY="gitness"
|
||||
HARNESS_JWT_IDENTITY="code"
|
||||
HARNESS_JWT_SECRET="IC04LYMBf1lDP5oeY4hupxd4HJhLmN6azUku3xEbeE3SUx5G3ZYzhbiwVtK4i7AmqyU9OZkwB4v8E9qM"
|
||||
HARNESS_JWT_VALIDINMIN=1440
|
||||
HARNESS_JWT_BEARER_SECRET="dOkdsVqdRPPRJG31XU0qY4MPqmBBMk0PTAGIKM6O7TGqhjyxScIdJe80mwh5Yb5zF3KxYBHw6B3Lfzlq"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
GITNESS_TRACE=true
|
||||
GITNESS_ADMIN_UID=admin
|
||||
GITNESS_ADMIN_NAME=Administrator
|
||||
GITNESS_ADMIN_DISPLAYNAME=Administrator
|
||||
GITNESS_ADMIN_EMAIL=admin@gitness.io
|
||||
GITNESS_ADMIN_PASSWORD=changeit
|
2
Makefile
2
Makefile
|
@ -124,7 +124,7 @@ lint: tools generate # lint the golang code
|
|||
# the source file has changed.
|
||||
###########################################
|
||||
cli/server/harness.wire_gen.go: cli/server/harness.wire.go ## Update the wire dependency injection if harness.wire.go has changed.
|
||||
|
||||
@sh ./scripts/wire/harness.sh
|
||||
|
||||
cli/server/standalone.wire_gen.go: cli/server/standalone.wire.go ## Update the wire dependency injection if standalone.wire.go has changed.
|
||||
@sh ./scripts/wire/standalone.sh
|
||||
|
|
|
@ -92,7 +92,7 @@ $ ./gitness user self
|
|||
Generate a personal access token:
|
||||
|
||||
```bash
|
||||
$ ./gitness user pat $NAME $LIFETIME_IN_S
|
||||
$ ./gitness user pat $UID $LIFETIME_IN_S
|
||||
```
|
||||
|
||||
Debug and output http responses from the server:
|
||||
|
|
|
@ -21,14 +21,15 @@ import (
|
|||
)
|
||||
|
||||
const tokenTmpl = `
|
||||
name: {{ .Token.Name }}
|
||||
principalID: {{ .Token.PrincipalID }}
|
||||
uid: {{ .Token.UID }}
|
||||
expiresAt: {{ .Token.ExpiresAt }}
|
||||
token: {{ .AccessToken }}
|
||||
` //#nosec G101
|
||||
|
||||
type createPATCommand struct {
|
||||
client client.Client
|
||||
name string
|
||||
uid string
|
||||
lifetimeInS int64
|
||||
|
||||
json bool
|
||||
|
@ -40,7 +41,7 @@ func (c *createPATCommand) run(*kingpin.ParseContext) error {
|
|||
defer cancel()
|
||||
|
||||
in := user.CreateTokenInput{
|
||||
Name: c.name,
|
||||
UID: c.uid,
|
||||
Lifetime: time.Duration(int64(time.Second) * c.lifetimeInS),
|
||||
Grants: enum.AccessGrantAll,
|
||||
}
|
||||
|
@ -70,8 +71,8 @@ func registerCreatePAT(app *kingpin.CmdClause, client client.Client) {
|
|||
cmd := app.Command("pat", "create personal access token").
|
||||
Action(c.run)
|
||||
|
||||
cmd.Arg("name", "the name of the token").
|
||||
Required().StringVar(&c.name)
|
||||
cmd.Arg("uid", "the uid of the token").
|
||||
Required().StringVar(&c.uid)
|
||||
|
||||
cmd.Arg("lifetime", "the lifetime of the token in seconds").
|
||||
Required().Int64Var(&c.lifetimeInS)
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
|
||||
const userTmpl = `
|
||||
uid: {{ .UID }}
|
||||
name: {{ .Name }}
|
||||
name: {{ .DisplayName }}
|
||||
email: {{ .Email }}
|
||||
admin: {{ .Admin }}
|
||||
`
|
||||
|
|
|
@ -11,17 +11,18 @@ import (
|
|||
"context"
|
||||
|
||||
"github.com/harness/gitness/gitrpc"
|
||||
|
||||
gitrpcserver "github.com/harness/gitness/gitrpc/server"
|
||||
|
||||
"github.com/harness/gitness/harness/auth/authn"
|
||||
"github.com/harness/gitness/harness/auth/authz"
|
||||
"github.com/harness/gitness/harness/bootstrap"
|
||||
"github.com/harness/gitness/harness/client"
|
||||
"github.com/harness/gitness/harness/router"
|
||||
"github.com/harness/gitness/harness/store"
|
||||
"github.com/harness/gitness/harness/types"
|
||||
"github.com/harness/gitness/harness/types/check"
|
||||
"github.com/harness/gitness/internal/api/controller/repo"
|
||||
"github.com/harness/gitness/internal/api/controller/service"
|
||||
"github.com/harness/gitness/internal/api/controller/serviceaccount"
|
||||
"github.com/harness/gitness/internal/api/controller/space"
|
||||
"github.com/harness/gitness/internal/api/controller/user"
|
||||
"github.com/harness/gitness/internal/cron"
|
||||
|
@ -46,6 +47,7 @@ func initSystem(ctx context.Context, config *gitnessTypes.Config) (*system, erro
|
|||
space.WireSet,
|
||||
user.WireSet,
|
||||
service.WireSet,
|
||||
serviceaccount.WireSet,
|
||||
gitrpcserver.WireSet,
|
||||
gitrpc.WireSet,
|
||||
types.LoadConfig,
|
||||
|
@ -53,6 +55,8 @@ func initSystem(ctx context.Context, config *gitnessTypes.Config) (*system, erro
|
|||
authn.WireSet,
|
||||
authz.WireSet,
|
||||
client.WireSet,
|
||||
store.WireSet,
|
||||
check.WireSet,
|
||||
)
|
||||
return &system{}, nil
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ package server
|
|||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/harness/gitness/gitrpc"
|
||||
server2 "github.com/harness/gitness/gitrpc/server"
|
||||
"github.com/harness/gitness/harness/auth/authn"
|
||||
|
@ -15,9 +14,12 @@ import (
|
|||
"github.com/harness/gitness/harness/bootstrap"
|
||||
"github.com/harness/gitness/harness/client"
|
||||
"github.com/harness/gitness/harness/router"
|
||||
"github.com/harness/gitness/harness/store"
|
||||
types2 "github.com/harness/gitness/harness/types"
|
||||
"github.com/harness/gitness/harness/types/check"
|
||||
"github.com/harness/gitness/internal/api/controller/repo"
|
||||
"github.com/harness/gitness/internal/api/controller/service"
|
||||
"github.com/harness/gitness/internal/api/controller/serviceaccount"
|
||||
"github.com/harness/gitness/internal/api/controller/space"
|
||||
"github.com/harness/gitness/internal/api/controller/user"
|
||||
"github.com/harness/gitness/internal/cron"
|
||||
|
@ -31,6 +33,7 @@ import (
|
|||
// Injectors from harness.wire.go:
|
||||
|
||||
func initSystem(ctx context.Context, config *types.Config) (*system, error) {
|
||||
checkUser := check.ProvideUserCheck()
|
||||
typesConfig, err := types2.LoadConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -48,11 +51,13 @@ func initSystem(ctx context.Context, config *types.Config) (*system, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userStore := database.ProvideUserStore(db)
|
||||
principalUIDTransformation := store.ProvidePrincipalUIDTransformation()
|
||||
userStore := database.ProvideUserStore(db, principalUIDTransformation)
|
||||
tokenStore := database.ProvideTokenStore(db)
|
||||
controller := user.NewController(authorizer, userStore, tokenStore)
|
||||
serviceStore := database.ProvideServiceStore(db)
|
||||
serviceController := service.NewController(authorizer, serviceStore)
|
||||
controller := user.NewController(checkUser, authorizer, userStore, tokenStore)
|
||||
checkService := check.ProvideServiceCheck()
|
||||
serviceStore := database.ProvideServiceStore(db, principalUIDTransformation)
|
||||
serviceController := service.NewController(checkService, authorizer, serviceStore)
|
||||
bootstrapBootstrap := bootstrap.ProvideBootstrap(config, controller, serviceController)
|
||||
systemStore := memory.New(config)
|
||||
tokenClient, err := client.ProvideTokenClient(serviceJWTProvider, typesConfig)
|
||||
|
@ -67,24 +72,29 @@ func initSystem(ctx context.Context, config *types.Config) (*system, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serviceAccountStore := database.ProvideServiceAccountStore(db)
|
||||
authenticator, err := authn.ProvideAuthenticator(userStore, tokenClient, userClient, typesConfig, serviceAccountClient, serviceAccountStore, serviceStore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serviceAccount := check.ProvideServiceAccountCheck()
|
||||
serviceAccountStore := database.ProvideServiceAccountStore(db, principalUIDTransformation)
|
||||
pathTransformation := store.ProvidePathTransformation()
|
||||
spaceStore := database.ProvideSpaceStore(db, pathTransformation)
|
||||
repoStore := database.ProvideRepoStore(db, pathTransformation)
|
||||
serviceaccountController := serviceaccount.NewController(serviceAccount, authorizer, serviceAccountStore, spaceStore, repoStore, tokenStore)
|
||||
checkSpace := check.ProvideSpaceCheck()
|
||||
spaceController := space.NewController(checkSpace, authorizer, spaceStore, repoStore, serviceAccountStore)
|
||||
accountClient, err := client.ProvideAccountClient(serviceJWTProvider, typesConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spaceStore := database.ProvideSpaceStore(db)
|
||||
repoStore := database.ProvideRepoStore(db)
|
||||
spaceController := space.NewController(authorizer, spaceStore, repoStore, serviceAccountStore)
|
||||
authenticator, err := authn.ProvideAuthenticator(controller, tokenClient, userClient, typesConfig, serviceAccountClient, serviceaccountController, serviceController, spaceController, accountClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
checkRepo := check.ProvideRepoCheck()
|
||||
gitrpcConfig := ProvideGitRPCClientConfig(config)
|
||||
gitrpcInterface, err := gitrpc.ProvideClient(gitrpcConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repoController := repo.ProvideController(config, authorizer, spaceStore, repoStore, serviceAccountStore, gitrpcInterface)
|
||||
repoController := repo.ProvideController(config, checkRepo, authorizer, spaceStore, repoStore, serviceAccountStore, gitrpcInterface)
|
||||
apiHandler := router.ProvideAPIHandler(systemStore, authenticator, accountClient, spaceController, repoController)
|
||||
gitHandler := router2.ProvideGitHandler(repoStore, authenticator, gitrpcInterface)
|
||||
webHandler := router2.ProvideWebHandler(systemStore)
|
||||
|
|
|
@ -11,9 +11,7 @@ import (
|
|||
"context"
|
||||
|
||||
"github.com/harness/gitness/gitrpc"
|
||||
|
||||
gitrpcserver "github.com/harness/gitness/gitrpc/server"
|
||||
|
||||
"github.com/harness/gitness/internal/api/controller/repo"
|
||||
"github.com/harness/gitness/internal/api/controller/serviceaccount"
|
||||
"github.com/harness/gitness/internal/api/controller/space"
|
||||
|
@ -24,9 +22,11 @@ import (
|
|||
"github.com/harness/gitness/internal/cron"
|
||||
"github.com/harness/gitness/internal/router"
|
||||
"github.com/harness/gitness/internal/server"
|
||||
"github.com/harness/gitness/internal/store"
|
||||
"github.com/harness/gitness/internal/store/database"
|
||||
"github.com/harness/gitness/internal/store/memory"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/check"
|
||||
|
||||
"github.com/google/wire"
|
||||
)
|
||||
|
@ -49,6 +49,8 @@ func initSystem(ctx context.Context, config *types.Config) (*system, error) {
|
|||
authz.WireSet,
|
||||
gitrpcserver.WireSet,
|
||||
gitrpc.WireSet,
|
||||
store.WireSet,
|
||||
check.WireSet,
|
||||
)
|
||||
return &system{}, nil
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ package server
|
|||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/harness/gitness/gitrpc"
|
||||
server2 "github.com/harness/gitness/gitrpc/server"
|
||||
"github.com/harness/gitness/internal/api/controller/repo"
|
||||
|
@ -20,36 +19,44 @@ import (
|
|||
"github.com/harness/gitness/internal/cron"
|
||||
"github.com/harness/gitness/internal/router"
|
||||
"github.com/harness/gitness/internal/server"
|
||||
"github.com/harness/gitness/internal/store"
|
||||
"github.com/harness/gitness/internal/store/database"
|
||||
"github.com/harness/gitness/internal/store/memory"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/check"
|
||||
)
|
||||
|
||||
// Injectors from standalone.wire.go:
|
||||
|
||||
func initSystem(ctx context.Context, config *types.Config) (*system, error) {
|
||||
checkUser := check.ProvideUserCheck()
|
||||
authorizer := authz.ProvideAuthorizer()
|
||||
db, err := database.ProvideDatabase(ctx, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userStore := database.ProvideUserStore(db)
|
||||
principalUIDTransformation := store.ProvidePrincipalUIDTransformation()
|
||||
userStore := database.ProvideUserStore(db, principalUIDTransformation)
|
||||
tokenStore := database.ProvideTokenStore(db)
|
||||
controller := user.NewController(authorizer, userStore, tokenStore)
|
||||
controller := user.NewController(checkUser, authorizer, userStore, tokenStore)
|
||||
bootstrapBootstrap := bootstrap.ProvideBootstrap(config, controller)
|
||||
systemStore := memory.New(config)
|
||||
serviceAccountStore := database.ProvideServiceAccountStore(db)
|
||||
serviceAccountStore := database.ProvideServiceAccountStore(db, principalUIDTransformation)
|
||||
authenticator := authn.ProvideAuthenticator(userStore, serviceAccountStore, tokenStore)
|
||||
spaceStore := database.ProvideSpaceStore(db)
|
||||
repoStore := database.ProvideRepoStore(db)
|
||||
checkRepo := check.ProvideRepoCheck()
|
||||
pathTransformation := store.ProvidePathTransformation()
|
||||
spaceStore := database.ProvideSpaceStore(db, pathTransformation)
|
||||
repoStore := database.ProvideRepoStore(db, pathTransformation)
|
||||
gitrpcConfig := ProvideGitRPCClientConfig(config)
|
||||
gitrpcInterface, err := gitrpc.ProvideClient(gitrpcConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repoController := repo.ProvideController(config, authorizer, spaceStore, repoStore, serviceAccountStore, gitrpcInterface)
|
||||
spaceController := space.NewController(authorizer, spaceStore, repoStore, serviceAccountStore)
|
||||
serviceaccountController := serviceaccount.NewController(authorizer, serviceAccountStore, spaceStore, repoStore, tokenStore)
|
||||
repoController := repo.ProvideController(config, checkRepo, authorizer, spaceStore, repoStore, serviceAccountStore, gitrpcInterface)
|
||||
checkSpace := check.ProvideSpaceCheck()
|
||||
spaceController := space.NewController(checkSpace, authorizer, spaceStore, repoStore, serviceAccountStore)
|
||||
serviceAccount := check.ProvideServiceAccountCheck()
|
||||
serviceaccountController := serviceaccount.NewController(serviceAccount, authorizer, serviceAccountStore, spaceStore, repoStore, tokenStore)
|
||||
apiHandler := router.ProvideAPIHandler(systemStore, authenticator, repoController, spaceController, serviceaccountController, controller)
|
||||
gitHandler := router.ProvideGitHandler(repoStore, authenticator, gitrpcInterface)
|
||||
webHandler := router.ProvideWebHandler(systemStore)
|
||||
|
|
|
@ -65,21 +65,21 @@ func (c *HTTPClient) Login(ctx context.Context, username, password string) (*typ
|
|||
form.Add("password", password)
|
||||
out := new(types.TokenResponse)
|
||||
uri := fmt.Sprintf("%s/api/v1/login", c.base)
|
||||
err := c.post(ctx, uri, form, out)
|
||||
err := c.post(ctx, uri, true, form, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// Register registers a new user and returns a JWT token.
|
||||
func (c *HTTPClient) Register(ctx context.Context,
|
||||
username, name, email, password string) (*types.TokenResponse, error) {
|
||||
username, displayName, email, password string) (*types.TokenResponse, error) {
|
||||
form := &url.Values{}
|
||||
form.Add("username", username)
|
||||
form.Add("name", name)
|
||||
form.Add("displayname", displayName)
|
||||
form.Add("email", email)
|
||||
form.Add("password", password)
|
||||
out := new(types.TokenResponse)
|
||||
uri := fmt.Sprintf("%s/api/v1/register", c.base)
|
||||
err := c.post(ctx, uri, form, out)
|
||||
err := c.post(ctx, uri, true, form, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
|
@ -99,7 +99,7 @@ func (c *HTTPClient) Self(ctx context.Context) (*types.User, error) {
|
|||
func (c *HTTPClient) UserCreatePAT(ctx context.Context, in user.CreateTokenInput) (*types.TokenResponse, error) {
|
||||
out := new(types.TokenResponse)
|
||||
uri := fmt.Sprintf("%s/api/v1/user/tokens", c.base)
|
||||
err := c.post(ctx, uri, in, out)
|
||||
err := c.post(ctx, uri, false, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
|
@ -123,7 +123,7 @@ func (c *HTTPClient) UserList(ctx context.Context, params types.Params) ([]types
|
|||
func (c *HTTPClient) UserCreate(ctx context.Context, user *types.User) (*types.User, error) {
|
||||
out := new(types.User)
|
||||
uri := fmt.Sprintf("%s/api/v1/users", c.base)
|
||||
err := c.post(ctx, uri, user, out)
|
||||
err := c.post(ctx, uri, false, user, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
|
@ -148,29 +148,29 @@ func (c *HTTPClient) UserDelete(ctx context.Context, key string) error {
|
|||
|
||||
// helper function for making an http GET request.
|
||||
func (c *HTTPClient) get(ctx context.Context, rawurl string, out interface{}) error {
|
||||
return c.do(ctx, rawurl, "GET", nil, out)
|
||||
return c.do(ctx, rawurl, "GET", false, nil, out)
|
||||
}
|
||||
|
||||
// helper function for making an http POST request.
|
||||
func (c *HTTPClient) post(ctx context.Context, rawurl string, in, out interface{}) error {
|
||||
return c.do(ctx, rawurl, "POST", in, out)
|
||||
func (c *HTTPClient) post(ctx context.Context, rawurl string, noToken bool, in, out interface{}) error {
|
||||
return c.do(ctx, rawurl, "POST", noToken, in, out)
|
||||
}
|
||||
|
||||
// helper function for making an http PATCH request.
|
||||
func (c *HTTPClient) patch(ctx context.Context, rawurl string, in, out interface{}) error {
|
||||
return c.do(ctx, rawurl, "PATCH", in, out)
|
||||
return c.do(ctx, rawurl, "PATCH", false, in, out)
|
||||
}
|
||||
|
||||
// helper function for making an http DELETE request.
|
||||
func (c *HTTPClient) delete(ctx context.Context, rawurl string) error {
|
||||
return c.do(ctx, rawurl, "DELETE", nil, nil)
|
||||
return c.do(ctx, rawurl, "DELETE", false, nil, nil)
|
||||
}
|
||||
|
||||
// helper function to make an http request.
|
||||
func (c *HTTPClient) do(ctx context.Context, rawurl, method string, in, out interface{}) error {
|
||||
func (c *HTTPClient) do(ctx context.Context, rawurl, method string, noToken bool, in, out interface{}) error {
|
||||
// executes the http request and returns the body as
|
||||
// and io.ReadCloser
|
||||
body, err := c.stream(ctx, rawurl, method, in, out)
|
||||
body, err := c.stream(ctx, rawurl, method, noToken, in, out)
|
||||
if body != nil {
|
||||
defer func(body io.ReadCloser) {
|
||||
_ = body.Close()
|
||||
|
@ -189,7 +189,8 @@ func (c *HTTPClient) do(ctx context.Context, rawurl, method string, in, out inte
|
|||
}
|
||||
|
||||
// helper function to stream a http request.
|
||||
func (c *HTTPClient) stream(ctx context.Context, rawurl, method string, in, _ interface{}) (io.ReadCloser, error) {
|
||||
func (c *HTTPClient) stream(ctx context.Context, rawurl, method string, noToken bool,
|
||||
in, _ interface{}) (io.ReadCloser, error) {
|
||||
uri, err := url.Parse(rawurl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -218,7 +219,7 @@ func (c *HTTPClient) stream(ctx context.Context, rawurl, method string, in, _ in
|
|||
if in != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
if c.token != "" {
|
||||
if !noToken && c.token != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+c.token)
|
||||
}
|
||||
if _, ok := in.(*url.Values); ok {
|
||||
|
|
|
@ -64,7 +64,13 @@ func NewRepositoryService(adapter GitAdapter, store Storage, gitRoot string) (*R
|
|||
}
|
||||
|
||||
func (s RepositoryService) getFullPathForRepo(uid string) string {
|
||||
return filepath.Join(s.reposRoot, fmt.Sprintf("%s.%s", uid, gitRepoSuffix))
|
||||
// split repos into subfolders using their prefix to distribute repos accross a set of folders.
|
||||
return filepath.Join(
|
||||
s.reposRoot, // root folder
|
||||
uid[0:2], // first subfolder
|
||||
uid[2:4], // second subfolder
|
||||
fmt.Sprintf("%s.%s", uid[4:], gitRepoSuffix), // remainder with .git
|
||||
)
|
||||
}
|
||||
|
||||
//nolint:gocognit // need to refactor this code
|
||||
|
|
|
@ -14,6 +14,11 @@ import (
|
|||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// repoGitUIDLength is the length of the generated repo uid.
|
||||
repoGitUIDLength = 21
|
||||
)
|
||||
|
||||
type CreateRepositoryParams struct {
|
||||
DefaultBranch string
|
||||
Files []File
|
||||
|
@ -83,5 +88,5 @@ func (c *Client) CreateRepository(ctx context.Context,
|
|||
}
|
||||
|
||||
func newRepositoryUID() (string, error) {
|
||||
return gonanoid.New()
|
||||
return gonanoid.New(repoGitUIDLength)
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ func getScopeForParent(ctx context.Context, spaceStore store.SpaceStore, repoSto
|
|||
return nil, fmt.Errorf("parent repo not found: %w", err)
|
||||
}
|
||||
|
||||
spacePath, repoName, err := paths.Disect(repo.Path)
|
||||
spacePath, repoName, err := paths.DisectLeaf(repo.Path)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Failed to disect path '%s'", repo.Path)
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ func CheckRepo(ctx context.Context, authorizer authz.Authorizer, session *auth.S
|
|||
return nil
|
||||
}
|
||||
|
||||
parentSpace, name, err := paths.Disect(repo.Path)
|
||||
parentSpace, name, err := paths.DisectLeaf(repo.Path)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Failed to disect path '%s'", repo.Path)
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ func CheckSpace(ctx context.Context, authorizer authz.Authorizer, session *auth.
|
|||
return nil
|
||||
}
|
||||
|
||||
parentSpace, name, err := paths.Disect(space.Path)
|
||||
parentSpace, name, err := paths.DisectLeaf(space.Path)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Failed to disect path '%s'", space.Path)
|
||||
}
|
||||
|
|
|
@ -8,10 +8,12 @@ import (
|
|||
"github.com/harness/gitness/gitrpc"
|
||||
"github.com/harness/gitness/internal/auth/authz"
|
||||
"github.com/harness/gitness/internal/store"
|
||||
"github.com/harness/gitness/types/check"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
defaultBranch string
|
||||
repoCheck check.Repo
|
||||
authorizer authz.Authorizer
|
||||
spaceStore store.SpaceStore
|
||||
repoStore store.RepoStore
|
||||
|
@ -21,6 +23,7 @@ type Controller struct {
|
|||
|
||||
func NewController(
|
||||
defaultBranch string,
|
||||
repoCheck check.Repo,
|
||||
authorizer authz.Authorizer,
|
||||
spaceStore store.SpaceStore,
|
||||
repoStore store.RepoStore,
|
||||
|
@ -29,6 +32,7 @@ func NewController(
|
|||
) *Controller {
|
||||
return &Controller{
|
||||
defaultBranch: defaultBranch,
|
||||
repoCheck: repoCheck,
|
||||
authorizer: authorizer,
|
||||
spaceStore: spaceStore,
|
||||
repoStore: repoStore,
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/harness/gitness/gitrpc"
|
||||
|
@ -19,15 +18,13 @@ import (
|
|||
"github.com/harness/gitness/internal/api/usererror"
|
||||
"github.com/harness/gitness/internal/auth"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/check"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
zerolog "github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type CreateInput struct {
|
||||
PathName string `json:"pathName"`
|
||||
SpaceID int64 `json:"spaceId"`
|
||||
Name string `json:"name"`
|
||||
ParentID int64 `json:"parentID"`
|
||||
UID string `json:"uid"`
|
||||
DefaultBranch string `json:"defaultBranch"`
|
||||
Description string `json:"description"`
|
||||
IsPublic bool `json:"isPublic"`
|
||||
|
@ -43,13 +40,13 @@ type CreateInput struct {
|
|||
func (c *Controller) Create(ctx context.Context, session *auth.Session, in *CreateInput) (*types.Repository, error) {
|
||||
log := zerolog.Ctx(ctx)
|
||||
// ensure we reference a space
|
||||
if in.SpaceID <= 0 {
|
||||
if in.ParentID <= 0 {
|
||||
return nil, usererror.BadRequest("A repository can't exist by itself.")
|
||||
}
|
||||
|
||||
parentSpace, err := c.spaceStore.Find(ctx, in.SpaceID)
|
||||
parentSpace, err := c.spaceStore.Find(ctx, in.ParentID)
|
||||
if err != nil {
|
||||
log.Err(err).Msgf("Failed to get space with id '%d'.", in.SpaceID)
|
||||
log.Err(err).Msgf("Failed to get space with id '%d'.", in.ParentID)
|
||||
return nil, usererror.BadRequest("Parent not found'")
|
||||
}
|
||||
/*
|
||||
|
@ -74,9 +71,8 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, in *Crea
|
|||
|
||||
// create new repo object
|
||||
repo := &types.Repository{
|
||||
PathName: strings.ToLower(in.PathName),
|
||||
SpaceID: in.SpaceID,
|
||||
Name: in.Name,
|
||||
ParentID: in.ParentID,
|
||||
UID: in.UID,
|
||||
Description: in.Description,
|
||||
IsPublic: in.IsPublic,
|
||||
CreatedBy: session.Principal.ID,
|
||||
|
@ -87,13 +83,13 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, in *Crea
|
|||
}
|
||||
|
||||
// validate repo
|
||||
if err = check.Repo(repo); err != nil {
|
||||
if err = c.repoCheck(repo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var content []byte
|
||||
files := make([]gitrpc.File, 0, 3) // readme, gitignore, licence
|
||||
if in.Readme {
|
||||
content = createReadme(in.Name, in.Description)
|
||||
content = createReadme(in.UID, in.Description)
|
||||
files = append(files, gitrpc.File{
|
||||
Path: "README.md",
|
||||
Content: content,
|
||||
|
|
|
@ -6,7 +6,6 @@ package repo
|
|||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
apiauth "github.com/harness/gitness/internal/api/auth"
|
||||
|
@ -36,7 +35,7 @@ func (c *Controller) CreatePath(ctx context.Context, session *auth.Session,
|
|||
}
|
||||
|
||||
params := &types.PathParams{
|
||||
Path: strings.ToLower(in.Path),
|
||||
Path: in.Path,
|
||||
CreatedBy: session.Principal.ID,
|
||||
Created: time.Now().UnixMilli(),
|
||||
Updated: time.Now().UnixMilli(),
|
||||
|
|
|
@ -6,9 +6,9 @@ package repo
|
|||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
apiauth "github.com/harness/gitness/internal/api/auth"
|
||||
"github.com/harness/gitness/internal/api/usererror"
|
||||
"github.com/harness/gitness/internal/auth"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/check"
|
||||
|
@ -18,13 +18,13 @@ import (
|
|||
|
||||
// MoveInput is used for moving a repo.
|
||||
type MoveInput struct {
|
||||
PathName *string `json:"pathName"`
|
||||
SpaceID *int64 `json:"spaceId"`
|
||||
UID *string `json:"uid"`
|
||||
ParentID *int64 `json:"parentId"`
|
||||
KeepAsAlias bool `json:"keepAsAlias"`
|
||||
}
|
||||
|
||||
/*
|
||||
* Move moves a repository to a new space and/or name.
|
||||
* Move moves a repository to a new space and/or uid.
|
||||
*/
|
||||
func (c *Controller) Move(ctx context.Context, session *auth.Session,
|
||||
repoRef string, in *MoveInput) (*types.Repository, error) {
|
||||
|
@ -38,37 +38,34 @@ func (c *Controller) Move(ctx context.Context, session *auth.Session,
|
|||
}
|
||||
|
||||
// backfill data
|
||||
if in.PathName == nil {
|
||||
in.PathName = &repo.PathName
|
||||
if in.UID == nil {
|
||||
in.UID = &repo.UID
|
||||
}
|
||||
if in.SpaceID == nil {
|
||||
in.SpaceID = &repo.SpaceID
|
||||
if in.ParentID == nil {
|
||||
in.ParentID = &repo.ParentID
|
||||
}
|
||||
|
||||
// convert name to lower case for easy of api use
|
||||
*in.PathName = strings.ToLower(*in.PathName)
|
||||
|
||||
// verify input
|
||||
if err = check.PathName(*in.PathName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// ensure it's not a no-op
|
||||
if *in.SpaceID == repo.SpaceID && *in.PathName == repo.PathName {
|
||||
if err = check.UID(*in.UID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// ensure we move to another space
|
||||
if *in.SpaceID <= 0 {
|
||||
return nil, err
|
||||
if *in.ParentID <= 0 {
|
||||
return nil, usererror.ErrBadRequest
|
||||
}
|
||||
|
||||
// ensure it's not a no-op
|
||||
if *in.ParentID == repo.ParentID && *in.UID == repo.UID {
|
||||
return nil, usererror.ErrNoChange
|
||||
}
|
||||
|
||||
// Ensure we have access to the target space (if it's a space move)
|
||||
if *in.SpaceID != repo.SpaceID {
|
||||
if *in.ParentID != repo.ParentID {
|
||||
var newSpace *types.Space
|
||||
newSpace, err = c.spaceStore.Find(ctx, *in.SpaceID)
|
||||
newSpace, err = c.spaceStore.Find(ctx, *in.ParentID)
|
||||
if err != nil {
|
||||
log.Err(err).Msgf("Failed to get target space with id %d for the move.", *in.SpaceID)
|
||||
log.Err(err).Msgf("Failed to get target space with id %d for the move.", *in.ParentID)
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
@ -84,5 +81,5 @@ func (c *Controller) Move(ctx context.Context, session *auth.Session,
|
|||
}
|
||||
}
|
||||
|
||||
return c.repoStore.Move(ctx, session.Principal.ID, repo.ID, *in.SpaceID, *in.PathName, in.KeepAsAlias)
|
||||
return c.repoStore.Move(ctx, session.Principal.ID, repo.ID, *in.ParentID, *in.UID, in.KeepAsAlias)
|
||||
}
|
||||
|
|
|
@ -11,13 +11,11 @@ import (
|
|||
apiauth "github.com/harness/gitness/internal/api/auth"
|
||||
"github.com/harness/gitness/internal/auth"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/check"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
)
|
||||
|
||||
// UpdateInput is used for updating a repo.
|
||||
type UpdateInput struct {
|
||||
Name *string `json:"name"`
|
||||
Description *string `json:"description"`
|
||||
IsPublic *bool `json:"isPublic"`
|
||||
}
|
||||
|
@ -37,9 +35,6 @@ func (c *Controller) Update(ctx context.Context, session *auth.Session,
|
|||
}
|
||||
|
||||
// update values only if provided
|
||||
if in.Name != nil {
|
||||
repo.Name = *in.Name
|
||||
}
|
||||
if in.Description != nil {
|
||||
repo.Description = *in.Description
|
||||
}
|
||||
|
@ -51,7 +46,7 @@ func (c *Controller) Update(ctx context.Context, session *auth.Session,
|
|||
repo.Updated = time.Now().UnixMilli()
|
||||
|
||||
// ensure provided values are valid
|
||||
if err = check.Repo(repo); err != nil {
|
||||
if err = c.repoCheck(repo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/harness/gitness/internal/auth/authz"
|
||||
"github.com/harness/gitness/internal/store"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/check"
|
||||
)
|
||||
|
||||
// WireSet provides a wire set for this package.
|
||||
|
@ -17,7 +18,8 @@ var WireSet = wire.NewSet(
|
|||
ProvideController,
|
||||
)
|
||||
|
||||
func ProvideController(config *types.Config, authorizer authz.Authorizer, spaceStore store.SpaceStore,
|
||||
repoStore store.RepoStore, saStore store.ServiceAccountStore, rpcClient gitrpc.Interface) *Controller {
|
||||
return NewController(config.Git.DefaultBranch, authorizer, spaceStore, repoStore, saStore, rpcClient)
|
||||
func ProvideController(config *types.Config, repoCheck check.Repo, authorizer authz.Authorizer,
|
||||
spaceStore store.SpaceStore, repoStore store.RepoStore, saStore store.ServiceAccountStore,
|
||||
rpcClient gitrpc.Interface) *Controller {
|
||||
return NewController(config.Git.DefaultBranch, repoCheck, authorizer, spaceStore, repoStore, saStore, rpcClient)
|
||||
}
|
||||
|
|
|
@ -7,15 +7,19 @@ package service
|
|||
import (
|
||||
"github.com/harness/gitness/internal/auth/authz"
|
||||
"github.com/harness/gitness/internal/store"
|
||||
"github.com/harness/gitness/types/check"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
serviceCheck check.Service
|
||||
authorizer authz.Authorizer
|
||||
serviceStore store.ServiceStore
|
||||
}
|
||||
|
||||
func NewController(authorizer authz.Authorizer, serviceStore store.ServiceStore) *Controller {
|
||||
func NewController(serviceCheck check.Service, authorizer authz.Authorizer,
|
||||
serviceStore store.ServiceStore) *Controller {
|
||||
return &Controller{
|
||||
serviceCheck: serviceCheck,
|
||||
authorizer: authorizer,
|
||||
serviceStore: serviceStore,
|
||||
}
|
||||
|
|
|
@ -12,14 +12,14 @@ import (
|
|||
apiauth "github.com/harness/gitness/internal/api/auth"
|
||||
"github.com/harness/gitness/internal/auth"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/check"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
)
|
||||
|
||||
// CreateInput is the input used for create operations.
|
||||
type CreateInput struct {
|
||||
UID string
|
||||
Name string
|
||||
UID string `json:"uid"`
|
||||
Email string `json:"email"`
|
||||
DisplayName string `json:"displayName"`
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -47,7 +47,8 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, in *Crea
|
|||
func (c *Controller) CreateNoAuth(ctx context.Context, in *CreateInput, admin bool) (*types.Service, error) {
|
||||
svc := &types.Service{
|
||||
UID: in.UID,
|
||||
Name: in.Name,
|
||||
Email: in.Email,
|
||||
DisplayName: in.DisplayName,
|
||||
Admin: admin,
|
||||
Salt: uniuri.NewLen(uniuri.UUIDLen),
|
||||
Created: time.Now().UnixMilli(),
|
||||
|
@ -55,7 +56,7 @@ func (c *Controller) CreateNoAuth(ctx context.Context, in *CreateInput, admin bo
|
|||
}
|
||||
|
||||
// validate service
|
||||
if err := check.Service(svc); err != nil {
|
||||
if err := c.serviceCheck(svc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
|
@ -12,13 +12,13 @@ import (
|
|||
apiauth "github.com/harness/gitness/internal/api/auth"
|
||||
"github.com/harness/gitness/internal/auth"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/check"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
)
|
||||
|
||||
// UpdateInput store infos to update an existing service.
|
||||
type UpdateInput struct {
|
||||
Name *string `json:"name"`
|
||||
Email *string `json:"email"`
|
||||
DisplayName *string `json:"displayName"`
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -36,13 +36,16 @@ func (c *Controller) Update(ctx context.Context, session *auth.Session,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if in.Name != nil {
|
||||
svc.Name = ptr.ToString(in.Name)
|
||||
if in.Email != nil {
|
||||
svc.DisplayName = ptr.ToString(in.Email)
|
||||
}
|
||||
if in.DisplayName != nil {
|
||||
svc.DisplayName = ptr.ToString(in.DisplayName)
|
||||
}
|
||||
svc.Updated = time.Now().UnixMilli()
|
||||
|
||||
// validate service
|
||||
if err = check.Service(svc); err != nil {
|
||||
if err = c.serviceCheck(svc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/google/wire"
|
||||
"github.com/harness/gitness/internal/auth/authz"
|
||||
"github.com/harness/gitness/internal/store"
|
||||
"github.com/harness/gitness/types/check"
|
||||
)
|
||||
|
||||
// WireSet provides a wire set for this package.
|
||||
|
@ -15,6 +16,7 @@ var WireSet = wire.NewSet(
|
|||
NewController,
|
||||
)
|
||||
|
||||
func ProvideController(authorizer authz.Authorizer, serviceStore store.ServiceStore) *Controller {
|
||||
return NewController(authorizer, serviceStore)
|
||||
func ProvideController(serviceCheck check.Service, authorizer authz.Authorizer,
|
||||
serviceStore store.ServiceStore) *Controller {
|
||||
return NewController(serviceCheck, authorizer, serviceStore)
|
||||
}
|
||||
|
|
|
@ -7,9 +7,11 @@ package serviceaccount
|
|||
import (
|
||||
"github.com/harness/gitness/internal/auth/authz"
|
||||
"github.com/harness/gitness/internal/store"
|
||||
"github.com/harness/gitness/types/check"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
serviceAccountCheck check.ServiceAccount
|
||||
authorizer authz.Authorizer
|
||||
saStore store.ServiceAccountStore
|
||||
spaceStore store.SpaceStore
|
||||
|
@ -17,9 +19,11 @@ type Controller struct {
|
|||
tokenStore store.TokenStore
|
||||
}
|
||||
|
||||
func NewController(authorizer authz.Authorizer, saStore store.ServiceAccountStore,
|
||||
spaceStore store.SpaceStore, repoStore store.RepoStore, tokenStore store.TokenStore) *Controller {
|
||||
func NewController(serviceAccountCheck check.ServiceAccount, authorizer authz.Authorizer,
|
||||
saStore store.ServiceAccountStore, spaceStore store.SpaceStore, repoStore store.RepoStore,
|
||||
tokenStore store.TokenStore) *Controller {
|
||||
return &Controller{
|
||||
serviceAccountCheck: serviceAccountCheck,
|
||||
authorizer: authorizer,
|
||||
saStore: saStore,
|
||||
spaceStore: spaceStore,
|
||||
|
|
|
@ -6,19 +6,25 @@ package serviceaccount
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/dchest/uniuri"
|
||||
apiauth "github.com/harness/gitness/internal/api/auth"
|
||||
"github.com/harness/gitness/internal/auth"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/check"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
gonanoid "github.com/matoous/go-nanoid/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
serviceAccountUIDAlphabet = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
serviceAccountUIDLength = 16
|
||||
)
|
||||
|
||||
type CreateInput struct {
|
||||
UID string `json:"uid"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
DisplayName string `json:"displayName"`
|
||||
ParentType enum.ParentResourceType `json:"parentType"`
|
||||
ParentID int64 `json:"parentId"`
|
||||
}
|
||||
|
@ -28,9 +34,34 @@ type CreateInput struct {
|
|||
*/
|
||||
func (c *Controller) Create(ctx context.Context, session *auth.Session,
|
||||
in *CreateInput) (*types.ServiceAccount, error) {
|
||||
// Ensure principal has required permissions on parent (ensures that parent exists)
|
||||
// since it's a create, we use don't pass a resource name.
|
||||
if err := apiauth.CheckServiceAccount(ctx, c.authorizer, session, c.spaceStore, c.repoStore,
|
||||
in.ParentType, in.ParentID, "", enum.PermissionServiceAccountCreate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uid, err := generateServiceAccountUID(in.ParentType, in.ParentID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate service account UID: %w", err)
|
||||
}
|
||||
|
||||
// TODO: There's a chance of duplicate error - we should retry?
|
||||
return c.CreateNoAuth(ctx, in, uid)
|
||||
}
|
||||
|
||||
/*
|
||||
* CreateNoAuth creates a new service account without auth checks.
|
||||
* WARNING: Never call as part of user flow.
|
||||
*
|
||||
* Note: take uid separately to allow internally created non-random uids.
|
||||
*/
|
||||
func (c *Controller) CreateNoAuth(ctx context.Context,
|
||||
in *CreateInput, uid string) (*types.ServiceAccount, error) {
|
||||
sa := &types.ServiceAccount{
|
||||
UID: in.UID,
|
||||
Name: in.Name,
|
||||
UID: uid,
|
||||
Email: in.Email,
|
||||
DisplayName: in.DisplayName,
|
||||
Salt: uniuri.NewLen(uniuri.UUIDLen),
|
||||
Created: time.Now().UnixMilli(),
|
||||
Updated: time.Now().UnixMilli(),
|
||||
|
@ -39,14 +70,7 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session,
|
|||
}
|
||||
|
||||
// validate service account
|
||||
if err := check.ServiceAccount(sa); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Ensure principal has required permissions on parent (ensures that parent exists)
|
||||
// since it's a create, we use don't pass a resource name.
|
||||
if err := apiauth.CheckServiceAccount(ctx, c.authorizer, session, c.spaceStore, c.repoStore,
|
||||
sa.ParentType, sa.ParentID, "", enum.PermissionServiceAccountCreate); err != nil {
|
||||
if err := c.serviceAccountCheck(sa); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -58,3 +82,22 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session,
|
|||
|
||||
return sa, nil
|
||||
}
|
||||
|
||||
// generateServiceAccountUID generates a new unique UID for a service account
|
||||
// NOTE:
|
||||
// This method generates 36^10 = ~8*10^24 unique UIDs per parent.
|
||||
// This should be enough for a very low chance of duplications.
|
||||
//
|
||||
// NOTE:
|
||||
// We generate it automatically to ensure unique UIDs on principals.
|
||||
// The downside is that they don't have very userfriendly handlers - though that should be okay for service accounts.
|
||||
// The other option would be take it as an input, but a globally unique uid of a service account
|
||||
// which itself is scoped to a space / repo might be weird.
|
||||
func generateServiceAccountUID(parentType enum.ParentResourceType, parentID int64) (string, error) {
|
||||
nid, err := gonanoid.Generate(serviceAccountUIDAlphabet, serviceAccountUIDLength)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("sa-%s-%d-%s", string(parentType), parentID, nid), nil
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ import (
|
|||
)
|
||||
|
||||
type CreateTokenInput struct {
|
||||
Name string `json:"name"`
|
||||
UID string `json:"uid"`
|
||||
Lifetime time.Duration `json:"lifetime"`
|
||||
Grants enum.AccessGrant `json:"grants"`
|
||||
}
|
||||
|
@ -32,12 +32,15 @@ func (c *Controller) CreateToken(ctx context.Context, session *auth.Session,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if err = check.Name(in.Name); err != nil {
|
||||
if err = check.UID(in.UID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = check.TokenLifetime(in.Lifetime); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = check.AccessGrant(in.Grants, false); 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,
|
||||
|
@ -45,7 +48,7 @@ func (c *Controller) CreateToken(ctx context.Context, session *auth.Session,
|
|||
return nil, err
|
||||
}
|
||||
token, jwtToken, err := token.CreateSAT(ctx, c.tokenStore, &session.Principal,
|
||||
sa, in.Name, in.Lifetime, in.Grants)
|
||||
sa, in.UID, in.Lifetime, in.Grants)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import (
|
|||
* DeleteToken deletes a token of a sevice account.
|
||||
*/
|
||||
func (c *Controller) DeleteToken(ctx context.Context, session *auth.Session,
|
||||
saUID string, tokenID int64) error {
|
||||
saUID string, tokenUID string) error {
|
||||
sa, err := findServiceAccountFromUID(ctx, c.saStore, saUID)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -30,7 +30,7 @@ func (c *Controller) DeleteToken(ctx context.Context, session *auth.Session,
|
|||
return err
|
||||
}
|
||||
|
||||
token, err := c.tokenStore.Find(ctx, tokenID)
|
||||
token, err := c.tokenStore.FindByUID(ctx, sa.ID, tokenUID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -43,5 +43,5 @@ func (c *Controller) DeleteToken(ctx context.Context, session *auth.Session,
|
|||
return usererror.ErrNotFound
|
||||
}
|
||||
|
||||
return c.tokenStore.Delete(ctx, tokenID)
|
||||
return c.tokenStore.Delete(ctx, token.ID)
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import (
|
|||
*/
|
||||
func (c *Controller) Find(ctx context.Context, session *auth.Session,
|
||||
saUID string) (*types.ServiceAccount, error) {
|
||||
sa, err := findServiceAccountFromUID(ctx, c.saStore, saUID)
|
||||
sa, err := c.FindNoAuth(ctx, saUID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -31,3 +31,11 @@ func (c *Controller) Find(ctx context.Context, session *auth.Session,
|
|||
|
||||
return sa, nil
|
||||
}
|
||||
|
||||
/*
|
||||
* FindNoAuth finds a service account without auth checks.
|
||||
* WARNING: Never call as part of user flow.
|
||||
*/
|
||||
func (c *Controller) FindNoAuth(ctx context.Context, saUID string) (*types.ServiceAccount, error) {
|
||||
return findServiceAccountFromUID(ctx, c.saStore, saUID)
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/google/wire"
|
||||
"github.com/harness/gitness/internal/auth/authz"
|
||||
"github.com/harness/gitness/internal/store"
|
||||
"github.com/harness/gitness/types/check"
|
||||
)
|
||||
|
||||
// WireSet provides a wire set for this package.
|
||||
|
@ -15,7 +16,8 @@ var WireSet = wire.NewSet(
|
|||
NewController,
|
||||
)
|
||||
|
||||
func ProvideController(authorizer authz.Authorizer, saStore store.ServiceAccountStore,
|
||||
spaceStore store.SpaceStore, repoStore store.RepoStore, tokenStore store.TokenStore) *Controller {
|
||||
return NewController(authorizer, saStore, spaceStore, repoStore, tokenStore)
|
||||
func ProvideController(serviceAccountCheck check.ServiceAccount, authorizer authz.Authorizer,
|
||||
saStore store.ServiceAccountStore, spaceStore store.SpaceStore, repoStore store.RepoStore,
|
||||
tokenStore store.TokenStore) *Controller {
|
||||
return NewController(serviceAccountCheck, authorizer, saStore, spaceStore, repoStore, tokenStore)
|
||||
}
|
||||
|
|
|
@ -7,18 +7,21 @@ package space
|
|||
import (
|
||||
"github.com/harness/gitness/internal/auth/authz"
|
||||
"github.com/harness/gitness/internal/store"
|
||||
"github.com/harness/gitness/types/check"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
spaceCheck check.Space
|
||||
authorizer authz.Authorizer
|
||||
spaceStore store.SpaceStore
|
||||
repoStore store.RepoStore
|
||||
saStore store.ServiceAccountStore
|
||||
}
|
||||
|
||||
func NewController(authorizer authz.Authorizer, spaceStore store.SpaceStore,
|
||||
func NewController(spaceCheck check.Space, authorizer authz.Authorizer, spaceStore store.SpaceStore,
|
||||
repoStore store.RepoStore, saStore store.ServiceAccountStore) *Controller {
|
||||
return &Controller{
|
||||
spaceCheck: spaceCheck,
|
||||
authorizer: authorizer,
|
||||
spaceStore: spaceStore,
|
||||
repoStore: repoStore,
|
||||
|
|
|
@ -7,7 +7,6 @@ package space
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
apiauth "github.com/harness/gitness/internal/api/auth"
|
||||
|
@ -20,9 +19,8 @@ import (
|
|||
)
|
||||
|
||||
type CreateInput struct {
|
||||
PathName string `json:"pathName"`
|
||||
ParentID int64 `json:"parentId"`
|
||||
Name string `json:"name"`
|
||||
UID string `json:"uid"`
|
||||
Description string `json:"description"`
|
||||
IsPublic bool `json:"isPublic"`
|
||||
}
|
||||
|
@ -65,9 +63,8 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, in *Crea
|
|||
|
||||
// create new space object
|
||||
space := &types.Space{
|
||||
PathName: strings.ToLower(in.PathName),
|
||||
ParentID: in.ParentID,
|
||||
Name: in.Name,
|
||||
UID: in.UID,
|
||||
Description: in.Description,
|
||||
IsPublic: in.IsPublic,
|
||||
CreatedBy: session.Principal.ID,
|
||||
|
@ -76,14 +73,14 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, in *Crea
|
|||
}
|
||||
|
||||
// validate space
|
||||
if err := check.Space(space); err != nil {
|
||||
if err := c.spaceCheck(space); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Validate path length (Due to racing conditions we can't be 100% sure on the path here only best effort
|
||||
// Validate path depth (Due to racing conditions we can't be 100% sure on the path here only best effort
|
||||
// to have a quick failure)
|
||||
path := paths.Concatinate(parentPath, space.PathName)
|
||||
if err := check.Path(path, true); err != nil {
|
||||
path := paths.Concatinate(parentPath, space.UID)
|
||||
if err := check.PathDepth(path, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ package space
|
|||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
apiauth "github.com/harness/gitness/internal/api/auth"
|
||||
|
@ -36,7 +35,7 @@ func (c *Controller) CreatePath(ctx context.Context, session *auth.Session,
|
|||
}
|
||||
|
||||
params := &types.PathParams{
|
||||
Path: strings.ToLower(in.Path),
|
||||
Path: in.Path,
|
||||
CreatedBy: session.Principal.ID,
|
||||
Created: time.Now().UnixMilli(),
|
||||
Updated: time.Now().UnixMilli(),
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
)
|
||||
|
||||
func findSpaceFromRef(ctx context.Context, spaceStore store.SpaceStore, spaceRef string) (*types.Space, error) {
|
||||
// check if ref is spaceId - ASSUMPTION: digit only is no valid space name
|
||||
// check if ref is space ID - ASSUMPTION: digit only is no valid space name
|
||||
id, err := strconv.ParseInt(spaceRef, 10, 64)
|
||||
if err == nil {
|
||||
return spaceStore.Find(ctx, id)
|
||||
|
|
|
@ -7,9 +7,9 @@ package space
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
apiauth "github.com/harness/gitness/internal/api/auth"
|
||||
"github.com/harness/gitness/internal/api/usererror"
|
||||
"github.com/harness/gitness/internal/auth"
|
||||
"github.com/harness/gitness/internal/paths"
|
||||
"github.com/harness/gitness/types"
|
||||
|
@ -19,7 +19,7 @@ import (
|
|||
|
||||
// MoveInput is used for moving a space.
|
||||
type MoveInput struct {
|
||||
PathName *string `json:"pathName"`
|
||||
UID *string `json:"uid"`
|
||||
ParentID *int64 `json:"parentId"`
|
||||
KeepAsAlias bool `json:"keepAsAlias"`
|
||||
}
|
||||
|
@ -39,24 +39,21 @@ func (c *Controller) Move(ctx context.Context, session *auth.Session,
|
|||
}
|
||||
|
||||
// backfill data
|
||||
if in.PathName == nil {
|
||||
in.PathName = &space.PathName
|
||||
if in.UID == nil {
|
||||
in.UID = &space.UID
|
||||
}
|
||||
if in.ParentID == nil {
|
||||
in.ParentID = &space.ParentID
|
||||
}
|
||||
|
||||
// convert name to lower case for easy of api use
|
||||
*in.PathName = strings.ToLower(*in.PathName)
|
||||
|
||||
// verify input
|
||||
if err = check.PathName(*in.PathName); err != nil {
|
||||
if err = check.UID(*in.UID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// ensure it's not a no-op
|
||||
if *in.ParentID == space.ParentID && *in.PathName == space.PathName {
|
||||
return nil, err
|
||||
if *in.ParentID == space.ParentID && *in.UID == space.UID {
|
||||
return nil, usererror.ErrNoChange
|
||||
}
|
||||
|
||||
// Ensure we can create spaces within the target space (using parent space as scope, similar to create)
|
||||
|
@ -78,15 +75,15 @@ func (c *Controller) Move(ctx context.Context, session *auth.Session,
|
|||
}
|
||||
|
||||
/*
|
||||
* Validate path length (Due to racing conditions we can't be 100% sure on the path here only best
|
||||
* Validate path depth (Due to racing conditions we can't be 100% sure on the path here only best
|
||||
* effort to avoid big transaction failure)
|
||||
* Only needed if we actually change the parent (and can skip top level, as we already validate the name)
|
||||
*/
|
||||
path := paths.Concatinate(newParent.Path, *in.PathName)
|
||||
if err = check.Path(path, true); err != nil {
|
||||
path := paths.Concatinate(newParent.Path, *in.UID)
|
||||
if err = check.PathDepth(path, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return c.spaceStore.Move(ctx, session.Principal.ID, space.ID, *in.ParentID, *in.PathName, in.KeepAsAlias)
|
||||
return c.spaceStore.Move(ctx, session.Principal.ID, space.ID, *in.ParentID, *in.UID, in.KeepAsAlias)
|
||||
}
|
||||
|
|
|
@ -11,13 +11,11 @@ import (
|
|||
apiauth "github.com/harness/gitness/internal/api/auth"
|
||||
"github.com/harness/gitness/internal/auth"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/check"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
)
|
||||
|
||||
// UpdateInput is used for updating a space.
|
||||
type UpdateInput struct {
|
||||
Name *string `json:"name"`
|
||||
Description *string `json:"description"`
|
||||
IsPublic *bool `json:"isPublic"`
|
||||
}
|
||||
|
@ -37,9 +35,6 @@ func (c *Controller) Update(ctx context.Context, session *auth.Session,
|
|||
}
|
||||
|
||||
// update values only if provided
|
||||
if in.Name != nil {
|
||||
space.Name = *in.Name
|
||||
}
|
||||
if in.Description != nil {
|
||||
space.Description = *in.Description
|
||||
}
|
||||
|
@ -51,7 +46,7 @@ func (c *Controller) Update(ctx context.Context, session *auth.Session,
|
|||
space.Updated = time.Now().UnixMilli()
|
||||
|
||||
// ensure provided values are valid
|
||||
if err = check.Space(space); err != nil {
|
||||
if err = c.spaceCheck(space); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/google/wire"
|
||||
"github.com/harness/gitness/internal/auth/authz"
|
||||
"github.com/harness/gitness/internal/store"
|
||||
"github.com/harness/gitness/types/check"
|
||||
)
|
||||
|
||||
// WireSet provides a wire set for this package.
|
||||
|
@ -15,7 +16,7 @@ var WireSet = wire.NewSet(
|
|||
NewController,
|
||||
)
|
||||
|
||||
func ProvideController(authorizer authz.Authorizer, spaceStore store.SpaceStore,
|
||||
func ProvideController(spaceCheck check.Space, authorizer authz.Authorizer, spaceStore store.SpaceStore,
|
||||
repoStore store.RepoStore, saStore store.ServiceAccountStore) *Controller {
|
||||
return NewController(authorizer, spaceStore, repoStore, saStore)
|
||||
return NewController(spaceCheck, authorizer, spaceStore, repoStore, saStore)
|
||||
}
|
||||
|
|
|
@ -7,17 +7,20 @@ package user
|
|||
import (
|
||||
"github.com/harness/gitness/internal/auth/authz"
|
||||
"github.com/harness/gitness/internal/store"
|
||||
"github.com/harness/gitness/types/check"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
userCheck check.User
|
||||
authorizer authz.Authorizer
|
||||
userStore store.UserStore
|
||||
tokenStore store.TokenStore
|
||||
}
|
||||
|
||||
func NewController(authorizer authz.Authorizer, userStore store.UserStore,
|
||||
func NewController(userCheck check.User, authorizer authz.Authorizer, userStore store.UserStore,
|
||||
tokenStore store.TokenStore) *Controller {
|
||||
return &Controller{
|
||||
userCheck: userCheck,
|
||||
authorizer: authorizer,
|
||||
userStore: userStore,
|
||||
tokenStore: tokenStore,
|
||||
|
|
|
@ -22,8 +22,8 @@ import (
|
|||
// On purpose don't expose admin, has to be enabled explicitly.
|
||||
type CreateInput struct {
|
||||
UID string `json:"uid"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
DisplayName string `json:"displayName"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
|
@ -62,7 +62,7 @@ func (c *Controller) CreateNoAuth(ctx context.Context, in *CreateInput, admin bo
|
|||
|
||||
user := &types.User{
|
||||
UID: in.UID,
|
||||
Name: in.Name,
|
||||
DisplayName: in.DisplayName,
|
||||
Email: in.Email,
|
||||
Password: string(hash),
|
||||
Salt: uniuri.NewLen(uniuri.UUIDLen),
|
||||
|
@ -72,7 +72,7 @@ func (c *Controller) CreateNoAuth(ctx context.Context, in *CreateInput, admin bo
|
|||
}
|
||||
|
||||
// validate user
|
||||
if err = check.User(user); err != nil {
|
||||
if err = c.userCheck(user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ import (
|
|||
)
|
||||
|
||||
type CreateTokenInput struct {
|
||||
Name string `json:"name"`
|
||||
UID string `json:"uid"`
|
||||
Lifetime time.Duration `json:"lifetime"`
|
||||
Grants enum.AccessGrant `json:"grants"`
|
||||
}
|
||||
|
@ -37,15 +37,18 @@ func (c *Controller) CreateAccessToken(ctx context.Context, session *auth.Sessio
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if err = check.Name(in.Name); err != nil {
|
||||
if err = check.UID(in.UID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = check.TokenLifetime(in.Lifetime); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = check.AccessGrant(in.Grants, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token, jwtToken, err := token.CreatePAT(ctx, c.tokenStore, &session.Principal,
|
||||
user, in.Name, in.Lifetime, in.Grants)
|
||||
user, in.UID, in.Lifetime, in.Grants)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import (
|
|||
* DeleteToken deletes a token of a user.
|
||||
*/
|
||||
func (c *Controller) DeleteToken(ctx context.Context, session *auth.Session,
|
||||
userUID string, tokenType enum.TokenType, tokenID int64) error {
|
||||
userUID string, tokenType enum.TokenType, tokenUID string) error {
|
||||
user, err := findUserFromUID(ctx, c.userStore, userUID)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -29,7 +29,7 @@ func (c *Controller) DeleteToken(ctx context.Context, session *auth.Session,
|
|||
return err
|
||||
}
|
||||
|
||||
token, err := c.tokenStore.Find(ctx, tokenID)
|
||||
token, err := c.tokenStore.FindByUID(ctx, user.ID, tokenUID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -48,5 +48,5 @@ func (c *Controller) DeleteToken(ctx context.Context, session *auth.Session,
|
|||
return usererror.ErrNotFound
|
||||
}
|
||||
|
||||
return c.tokenStore.Delete(ctx, tokenID)
|
||||
return c.tokenStore.Delete(ctx, token.ID)
|
||||
}
|
||||
|
|
|
@ -6,7 +6,11 @@ package user
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/harness/gitness/internal/api/usererror"
|
||||
"github.com/harness/gitness/internal/auth"
|
||||
|
@ -49,11 +53,22 @@ func (c *Controller) Login(ctx context.Context, session *auth.Session,
|
|||
return nil, usererror.ErrNotFound
|
||||
}
|
||||
|
||||
// TODO: how should we name session tokens?
|
||||
token, jwtToken, err := token.CreateUserSession(ctx, c.tokenStore, user, "login")
|
||||
tokenUID, err := generateSessionTokenUID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
token, jwtToken, err := token.CreateUserSession(ctx, c.tokenStore, user, tokenUID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.TokenResponse{Token: *token, AccessToken: jwtToken}, nil
|
||||
}
|
||||
|
||||
func generateSessionTokenUID() (string, error) {
|
||||
r, err := rand.Int(rand.Reader, big.NewInt(10000))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to generate random number: %w", err)
|
||||
}
|
||||
return fmt.Sprintf("login-%d-%04d", time.Now().Unix(), r.Int64()), nil
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ import (
|
|||
apiauth "github.com/harness/gitness/internal/api/auth"
|
||||
"github.com/harness/gitness/internal/auth"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/check"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
@ -22,7 +21,7 @@ import (
|
|||
type UpdateInput struct {
|
||||
Email *string `json:"email"`
|
||||
Password *string `json:"password"`
|
||||
Name *string `json:"name"`
|
||||
DisplayName *string `json:"displayName"`
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -40,8 +39,8 @@ func (c *Controller) Update(ctx context.Context, session *auth.Session,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if in.Name != nil {
|
||||
user.Name = ptr.ToString(in.Name)
|
||||
if in.DisplayName != nil {
|
||||
user.DisplayName = ptr.ToString(in.DisplayName)
|
||||
}
|
||||
if in.Email != nil {
|
||||
user.Email = ptr.ToString(in.Email)
|
||||
|
@ -57,7 +56,7 @@ func (c *Controller) Update(ctx context.Context, session *auth.Session,
|
|||
user.Updated = time.Now().UnixMilli()
|
||||
|
||||
// validate user
|
||||
if err = check.User(user); err != nil {
|
||||
if err = c.userCheck(user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/google/wire"
|
||||
"github.com/harness/gitness/internal/auth/authz"
|
||||
"github.com/harness/gitness/internal/store"
|
||||
"github.com/harness/gitness/types/check"
|
||||
)
|
||||
|
||||
// WireSet provides a wire set for this package.
|
||||
|
@ -15,7 +16,7 @@ var WireSet = wire.NewSet(
|
|||
NewController,
|
||||
)
|
||||
|
||||
func ProvideController(authorizer authz.Authorizer, userStore store.UserStore,
|
||||
func ProvideController(userCheck check.User, authorizer authz.Authorizer, userStore store.UserStore,
|
||||
tokenStore store.TokenStore) *Controller {
|
||||
return NewController(authorizer, userStore, tokenStore)
|
||||
return NewController(userCheck, authorizer, userStore, tokenStore)
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ func HandleRegister(userCtrl *user.Controller) http.HandlerFunc {
|
|||
|
||||
in := &user.CreateInput{
|
||||
UID: r.FormValue("username"),
|
||||
Name: r.FormValue("name"),
|
||||
DisplayName: r.FormValue("displayname"),
|
||||
Email: r.FormValue("email"),
|
||||
Password: r.FormValue("password"),
|
||||
}
|
||||
|
|
|
@ -23,13 +23,13 @@ func HandleDeleteToken(saCrl *serviceaccount.Controller) http.HandlerFunc {
|
|||
render.TranslatedUserError(w, err)
|
||||
return
|
||||
}
|
||||
tokenID, err := request.GetTokenIDFromPath(r)
|
||||
tokenUID, err := request.GetTokenUIDFromPath(r)
|
||||
if err != nil {
|
||||
render.BadRequest(w)
|
||||
return
|
||||
}
|
||||
|
||||
err = saCrl.DeleteToken(ctx, session, saUID, tokenID)
|
||||
err = saCrl.DeleteToken(ctx, session, saUID, tokenUID)
|
||||
if err != nil {
|
||||
render.TranslatedUserError(w, err)
|
||||
return
|
||||
|
|
|
@ -21,13 +21,13 @@ func HandleDeleteToken(userCtrl *user.Controller, tokenType enum.TokenType) http
|
|||
session, _ := request.AuthSessionFrom(ctx)
|
||||
userUID := session.Principal.UID
|
||||
|
||||
tokenID, err := request.GetTokenIDFromPath(r)
|
||||
tokenUID, err := request.GetTokenUIDFromPath(r)
|
||||
if err != nil {
|
||||
render.BadRequest(w)
|
||||
return
|
||||
}
|
||||
|
||||
err = userCtrl.DeleteToken(ctx, session, userUID, tokenType, tokenID)
|
||||
err = userCtrl.DeleteToken(ctx, session, userUID, tokenType, tokenUID)
|
||||
if err != nil {
|
||||
render.TranslatedUserError(w, err)
|
||||
return
|
||||
|
|
|
@ -53,11 +53,10 @@ var queryParameterSortRepo = openapi3.ParameterOrRef{
|
|||
Schema: &openapi3.SchemaOrRef{
|
||||
Schema: &openapi3.Schema{
|
||||
Type: ptrSchemaType(openapi3.SchemaTypeString),
|
||||
Default: ptrptr(enum.RepoAttrName.String()),
|
||||
Default: ptrptr(enum.RepoAttrUID.String()),
|
||||
Enum: []interface{}{
|
||||
ptr.String(enum.RepoAttrName.String()),
|
||||
ptr.String(enum.RepoAttrUID.String()),
|
||||
ptr.String(enum.RepoAttrPath.String()),
|
||||
ptr.String(enum.RepoAttrPathName.String()),
|
||||
ptr.String(enum.RepoAttrCreated.String()),
|
||||
ptr.String(enum.RepoAttrUpdated.String()),
|
||||
},
|
||||
|
@ -89,11 +88,10 @@ var queryParameterSortSpace = openapi3.ParameterOrRef{
|
|||
Schema: &openapi3.SchemaOrRef{
|
||||
Schema: &openapi3.Schema{
|
||||
Type: ptrSchemaType(openapi3.SchemaTypeString),
|
||||
Default: ptrptr(enum.SpaceAttrName.String()),
|
||||
Default: ptrptr(enum.SpaceAttrUID.String()),
|
||||
Enum: []interface{}{
|
||||
ptr.String(enum.SpaceAttrName.String()),
|
||||
ptr.String(enum.SpaceAttrUID.String()),
|
||||
ptr.String(enum.SpaceAttrPath.String()),
|
||||
ptr.String(enum.SpaceAttrPathName.String()),
|
||||
ptr.String(enum.SpaceAttrCreated.String()),
|
||||
ptr.String(enum.SpaceAttrUpdated.String()),
|
||||
},
|
||||
|
|
|
@ -7,6 +7,7 @@ package openapi
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/harness/gitness/internal/api/controller/user"
|
||||
"github.com/harness/gitness/internal/api/usererror"
|
||||
"github.com/harness/gitness/types"
|
||||
|
||||
|
@ -32,7 +33,7 @@ func buildUser(reflector *openapi3.Reflector) {
|
|||
opUpdate := openapi3.Operation{}
|
||||
opUpdate.WithTags("user")
|
||||
opUpdate.WithMapOfAnything(map[string]interface{}{"operationId": "updateUser"})
|
||||
_ = reflector.SetRequest(&opUpdate, new(types.UserInput), http.MethodPatch)
|
||||
_ = reflector.SetRequest(&opUpdate, new(user.UpdateInput), http.MethodPatch)
|
||||
_ = reflector.SetJSONResponse(&opUpdate, new(types.User), http.StatusOK)
|
||||
_ = reflector.SetJSONResponse(&opUpdate, new(usererror.Error), http.StatusInternalServerError)
|
||||
_ = reflector.Spec.AddOperation(http.MethodPatch, "/user", opUpdate)
|
||||
|
|
|
@ -7,6 +7,7 @@ package openapi
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/harness/gitness/internal/api/controller/user"
|
||||
"github.com/harness/gitness/internal/api/usererror"
|
||||
"github.com/harness/gitness/types"
|
||||
|
||||
|
@ -27,14 +28,6 @@ type (
|
|||
// include pagination request
|
||||
paginationRequest
|
||||
}
|
||||
|
||||
// request for updating a user.
|
||||
userUpdateRequest struct {
|
||||
Param string `path:"email"`
|
||||
|
||||
// include request body input.
|
||||
types.UserInput
|
||||
}
|
||||
)
|
||||
|
||||
// helper function that constructs the openapi specification
|
||||
|
@ -73,7 +66,7 @@ func buildUsers(reflector *openapi3.Reflector) {
|
|||
opUpdate := openapi3.Operation{}
|
||||
opUpdate.WithTags("users")
|
||||
opUpdate.WithMapOfAnything(map[string]interface{}{"operationId": "updateUsers"})
|
||||
_ = reflector.SetRequest(&opUpdate, new(userUpdateRequest), http.MethodPatch)
|
||||
_ = reflector.SetRequest(&opUpdate, new(user.UpdateInput), http.MethodPatch)
|
||||
_ = reflector.SetJSONResponse(&opUpdate, new(types.User), http.StatusOK)
|
||||
_ = reflector.SetJSONResponse(&opUpdate, new(usererror.Error), http.StatusBadRequest)
|
||||
_ = reflector.SetJSONResponse(&opUpdate, new(usererror.Error), http.StatusInternalServerError)
|
||||
|
|
|
@ -7,7 +7,6 @@ package request
|
|||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -20,7 +19,6 @@ func GetRepoRefFromPath(r *http.Request) (string, error) {
|
|||
return "", err
|
||||
}
|
||||
|
||||
// paths are unescaped and lower
|
||||
ref, err := url.PathUnescape(rawRef)
|
||||
return strings.ToLower(ref), err
|
||||
// paths are unescaped
|
||||
return url.PathUnescape(rawRef)
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ package request
|
|||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -21,6 +20,5 @@ func GetSpaceRefFromPath(r *http.Request) (string, error) {
|
|||
}
|
||||
|
||||
// paths are unescaped and lower
|
||||
ref, err := url.PathUnescape(rawRef)
|
||||
return strings.ToLower(ref), err
|
||||
return url.PathUnescape(rawRef)
|
||||
}
|
||||
|
|
|
@ -9,9 +9,9 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
PathParamTokenID = "tokenID"
|
||||
PathParamTokenUID = "tokenUID"
|
||||
)
|
||||
|
||||
func GetTokenIDFromPath(r *http.Request) (int64, error) {
|
||||
return PathParamAsInt64(r, PathParamTokenID)
|
||||
func GetTokenUIDFromPath(r *http.Request) (string, error) {
|
||||
return PathParamOrError(r, PathParamTokenUID)
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ var (
|
|||
ErrInvalidToken = New(http.StatusUnauthorized, "Invalid or missing token")
|
||||
|
||||
// ErrBadRequest is returned when there was an issue with the input.
|
||||
ErrBadRequest = New(http.StatusBadGateway, "Bad Request")
|
||||
ErrBadRequest = New(http.StatusBadRequest, "Bad Request")
|
||||
|
||||
// ErrUnauthorized is returned when the acting principal is not authenticated.
|
||||
ErrUnauthorized = New(http.StatusUnauthorized, "Unauthorized")
|
||||
|
|
|
@ -34,7 +34,7 @@ func System(config *types.Config, userCtrl *user.Controller) func(context.Contex
|
|||
// but then the duplicte might be due to email or name, not uid.
|
||||
// Futhermore, it would create unnecesary error logs.
|
||||
func Admin(ctx context.Context, config *types.Config, userCtrl *user.Controller) error {
|
||||
if config.Admin.Name == "" {
|
||||
if config.Admin.DisplayName == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ func Admin(ctx context.Context, config *types.Config, userCtrl *user.Controller)
|
|||
|
||||
in := &user.CreateInput{
|
||||
UID: adminUID,
|
||||
Name: config.Admin.Name,
|
||||
DisplayName: config.Admin.DisplayName,
|
||||
Email: config.Admin.Email,
|
||||
Password: config.Admin.Password,
|
||||
}
|
||||
|
|
|
@ -15,9 +15,9 @@ var (
|
|||
ErrPathEmpty = errors.New("path is empty")
|
||||
)
|
||||
|
||||
// Disect splits a path into its parent path and the leaf name
|
||||
// e.g. /space1/space2/space3 -> (/space1/space2, space3, nil).
|
||||
func Disect(path string) (string, string, error) {
|
||||
// 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) {
|
||||
if path == "" {
|
||||
return "", "", ErrPathEmpty
|
||||
}
|
||||
|
@ -30,6 +30,21 @@ func Disect(path string) (string, string, error) {
|
|||
return path[:i], path[i+1:], nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if path == "" {
|
||||
return "", "", ErrPathEmpty
|
||||
}
|
||||
|
||||
i := strings.Index(path, types.PathSeparator)
|
||||
if i == -1 {
|
||||
return path, "", nil
|
||||
}
|
||||
|
||||
return path[:i], path[i+1:], nil
|
||||
}
|
||||
|
||||
/*
|
||||
* Concatinate two paths together (takes care of leading / trailing '/')
|
||||
* e.g. (space1/, /space2/) -> space1/space2
|
||||
|
|
|
@ -203,7 +203,7 @@ func setupUsers(r chi.Router, userCtrl *user.Controller) {
|
|||
r.Post("/", handleruser.HandleCreateAccessToken(userCtrl))
|
||||
|
||||
// per token operations
|
||||
r.Route(fmt.Sprintf("/{%s}", request.PathParamTokenID), func(r chi.Router) {
|
||||
r.Route(fmt.Sprintf("/{%s}", request.PathParamTokenUID), func(r chi.Router) {
|
||||
r.Delete("/", handleruser.HandleDeleteToken(userCtrl, enum.TokenTypePAT))
|
||||
})
|
||||
})
|
||||
|
@ -213,7 +213,7 @@ func setupUsers(r chi.Router, userCtrl *user.Controller) {
|
|||
r.Get("/", handleruser.HandleListTokens(userCtrl, enum.TokenTypeSession))
|
||||
|
||||
// per token operations
|
||||
r.Route(fmt.Sprintf("/{%s}", request.PathParamTokenID), func(r chi.Router) {
|
||||
r.Route(fmt.Sprintf("/{%s}", request.PathParamTokenUID), func(r chi.Router) {
|
||||
r.Delete("/", handleruser.HandleDeleteToken(userCtrl, enum.TokenTypeSession))
|
||||
})
|
||||
})
|
||||
|
@ -235,7 +235,7 @@ func setupServiceAccounts(r chi.Router, saCtrl *serviceaccount.Controller) {
|
|||
r.Post("/", handlerserviceaccount.HandleCreateToken(saCtrl))
|
||||
|
||||
// per token operations
|
||||
r.Route(fmt.Sprintf("/{%s}", request.PathParamTokenID), func(r chi.Router) {
|
||||
r.Route(fmt.Sprintf("/{%s}", request.PathParamTokenUID), func(r chi.Router) {
|
||||
r.Delete("/", handlerserviceaccount.HandleDeleteToken(saCtrl))
|
||||
})
|
||||
})
|
||||
|
|
|
@ -114,7 +114,7 @@ func BasicAuth(realm string, auth authn.Authenticator, repoStore store.RepoStore
|
|||
}
|
||||
|
||||
if !repository.IsPublic {
|
||||
log.Debug().Msgf("BasicAuth middleware: repo %v is private", repository.Name)
|
||||
log.Debug().Msgf("BasicAuth middleware: repo %v is private", repository.UID)
|
||||
_, err = auth.Authenticate(r)
|
||||
if err != nil {
|
||||
basicAuthFailed(w, realm)
|
||||
|
@ -151,7 +151,7 @@ func stubGitHandler(repoStore store.RepoStore) http.HandlerFunc {
|
|||
" Method: '%s'\n"+
|
||||
" Path: '%s'\n"+
|
||||
" Query: '%s'",
|
||||
repo.Name, repo.Path,
|
||||
repo.UID, repo.Path,
|
||||
r.Method,
|
||||
r.URL.Path,
|
||||
r.URL.RawQuery,
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
CREATE TABLE IF NOT EXISTS paths (
|
||||
path_id SERIAL PRIMARY KEY
|
||||
,path_value TEXT
|
||||
,path_valueUnique TEXT
|
||||
,path_isAlias BOOLEAN
|
||||
,path_targetType TEXT
|
||||
,path_targetId INTEGER
|
||||
,path_createdBy INTEGER
|
||||
,path_created BIGINT
|
||||
,path_updated BIGINT
|
||||
,UNIQUE(path_value)
|
||||
,UNIQUE(path_valueUnique)
|
||||
);
|
|
@ -1,21 +1,22 @@
|
|||
CREATE TABLE IF NOT EXISTS principals (
|
||||
principal_id SERIAL PRIMARY KEY
|
||||
,principal_uid TEXT
|
||||
,principal_uidUnique TEXT
|
||||
,principal_email TEXT
|
||||
,principal_type TEXT
|
||||
,principal_name TEXT
|
||||
,principal_displayName TEXT
|
||||
,principal_admin BOOLEAN
|
||||
,principal_blocked BOOLEAN
|
||||
,principal_salt TEXT
|
||||
,principal_created BIGINT
|
||||
,principal_updated BIGINT
|
||||
|
||||
,principal_user_email CITEXT
|
||||
,principal_user_password TEXT
|
||||
|
||||
,principal_sa_parentType TEXT
|
||||
,principal_sa_parentId INTEGER
|
||||
|
||||
,UNIQUE(principal_uid)
|
||||
,UNIQUE(principal_uidUnique)
|
||||
,UNIQUE(principal_email)
|
||||
,UNIQUE(principal_salt)
|
||||
,UNIQUE(principal_user_email)
|
||||
);
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
CREATE TABLE IF NOT EXISTS repositories (
|
||||
repo_id SERIAL PRIMARY KEY
|
||||
,repo_pathName TEXT
|
||||
,repo_spaceId INTEGER
|
||||
,repo_name TEXT
|
||||
,repo_parentId INTEGER
|
||||
,repo_uid TEXT
|
||||
,repo_description TEXT
|
||||
,repo_isPublic BOOLEAN
|
||||
,repo_createdBy INTEGER
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
CREATE TABLE IF NOT EXISTS spaces (
|
||||
space_id SERIAL PRIMARY KEY
|
||||
,space_pathName TEXT
|
||||
,space_parentId INTEGER
|
||||
,space_name TEXT
|
||||
,space_uid TEXT
|
||||
,space_description TEXT
|
||||
,space_isPublic BOOLEAN
|
||||
,space_createdBy INTEGER
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
CREATE TABLE IF NOT EXISTS tokens (
|
||||
token_id SERIAL PRIMARY KEY
|
||||
,token_type TEXT
|
||||
,token_name TEXT
|
||||
,token_uid TEXT
|
||||
,token_principalId INTEGER
|
||||
,token_expiresAt BIGINT
|
||||
,token_grants BIGINT
|
||||
,token_issuedAt BIGINT
|
||||
,token_createdBy INTEGER
|
||||
,UNIQUE(token_principalId, token_uid)
|
||||
);
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
CREATE INDEX IF NOT EXISTS index_repositories_parentId
|
||||
ON repositories(repo_parentId);
|
|
@ -1,2 +0,0 @@
|
|||
CREATE INDEX IF NOT EXISTS index_repositories_spaceId
|
||||
ON repositories(repo_spaceId);
|
|
@ -1,11 +1,12 @@
|
|||
CREATE TABLE IF NOT EXISTS paths (
|
||||
path_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
,path_value TEXT COLLATE NOCASE
|
||||
,path_value TEXT
|
||||
,path_valueUnique TEXT
|
||||
,path_isAlias BOOLEAN
|
||||
,path_targetType TEXT
|
||||
,path_targetId INTEGER
|
||||
,path_createdBy INTEGER
|
||||
,path_created BIGINT
|
||||
,path_updated BIGINT
|
||||
,UNIQUE(path_value COLLATE NOCASE)
|
||||
,UNIQUE(path_valueUnique)
|
||||
);
|
|
@ -1,21 +1,22 @@
|
|||
CREATE TABLE IF NOT EXISTS principals (
|
||||
principal_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
,principal_uid TEXT
|
||||
,principal_type TEXT
|
||||
,principal_name TEXT
|
||||
,principal_uidUnique TEXT
|
||||
,principal_email TEXT COLLATE NOCASE
|
||||
,principal_type TEXT COLLATE NOCASE
|
||||
,principal_displayName TEXT
|
||||
,principal_admin BOOLEAN
|
||||
,principal_blocked BOOLEAN
|
||||
,principal_salt TEXT
|
||||
,principal_created BIGINT
|
||||
,principal_updated BIGINT
|
||||
|
||||
,principal_user_email TEXT
|
||||
,principal_user_password TEXT
|
||||
|
||||
,principal_sa_parentType TEXT
|
||||
,principal_sa_parentType TEXT COLLATE NOCASE
|
||||
,principal_sa_parentId INTEGER
|
||||
|
||||
,UNIQUE(principal_uid)
|
||||
,UNIQUE(principal_uidUnique)
|
||||
,UNIQUE(principal_email COLLATE NOCASE)
|
||||
,UNIQUE(principal_salt)
|
||||
,UNIQUE(principal_user_email COLLATE NOCASE)
|
||||
);
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
CREATE TABLE IF NOT EXISTS repositories (
|
||||
repo_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
,repo_pathName TEXT COLLATE NOCASE
|
||||
,repo_spaceId INTEGER
|
||||
,repo_name TEXT
|
||||
,repo_parentId INTEGER
|
||||
,repo_uid TEXT
|
||||
,repo_description TEXT
|
||||
,repo_isPublic BOOLEAN
|
||||
,repo_createdBy INTEGER
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
CREATE TABLE IF NOT EXISTS spaces (
|
||||
space_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
,space_pathName TEXT COLLATE NOCASE
|
||||
,space_parentId INTEGER
|
||||
,space_name TEXT
|
||||
,space_uid TEXT
|
||||
,space_description TEXT
|
||||
,space_isPublic BOOLEAN
|
||||
,space_createdBy INTEGER
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
CREATE TABLE IF NOT EXISTS tokens (
|
||||
token_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
,token_type TEXT COLLATE NOCASE
|
||||
,token_name TEXT
|
||||
,token_uid TEXT COLLATE NOCASE
|
||||
,token_principalId INTEGER
|
||||
,token_expiresAt BIGINT
|
||||
,token_grants BIGINT
|
||||
,token_issuedAt BIGINT
|
||||
,token_createdBy INTEGER
|
||||
,UNIQUE(token_principalId, token_uid COLLATE NOCASE)
|
||||
);
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
CREATE INDEX IF NOT EXISTS index_repositories_parentId
|
||||
ON repositories(repo_parentId);
|
|
@ -1,2 +0,0 @@
|
|||
CREATE INDEX IF NOT EXISTS index_repositories_spaceId
|
||||
ON repositories(repo_spaceId);
|
|
@ -7,6 +7,7 @@ package database
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/harness/gitness/internal/paths"
|
||||
"github.com/harness/gitness/internal/store"
|
||||
|
@ -18,19 +19,33 @@ import (
|
|||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// path is a DB representation of a Path.
|
||||
// It is required to allow storing transformed paths used for uniquness constraints and searching.
|
||||
type path struct {
|
||||
types.Path
|
||||
ValueUnique string `db:"path_valueUnique"`
|
||||
}
|
||||
|
||||
// CreateAliasPath a new alias path (Don't call this for new path creation!)
|
||||
func CreateAliasPath(ctx context.Context, db *sqlx.DB, path *types.Path) error {
|
||||
func CreateAliasPath(ctx context.Context, db *sqlx.DB, path *types.Path,
|
||||
transformation store.PathTransformation) error {
|
||||
if !path.IsAlias {
|
||||
return store.ErrAliasPathRequired
|
||||
}
|
||||
|
||||
// ensure path length is okay
|
||||
if check.PathTooLong(path.Value, path.TargetType == enum.PathTargetTypeSpace) {
|
||||
if check.IsPathTooDeep(path.Value, path.TargetType == enum.PathTargetTypeSpace) {
|
||||
log.Warn().Msgf("Path '%s' is too long.", path.Value)
|
||||
return store.ErrPathTooLong
|
||||
}
|
||||
|
||||
query, arg, err := db.BindNamed(pathInsert, path)
|
||||
// map to db path to ensure we store valueUnique.
|
||||
dbPath, err := mapToDBPath(path, transformation)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to map db path: %w", err)
|
||||
}
|
||||
|
||||
query, arg, err := db.BindNamed(pathInsert, dbPath)
|
||||
if err != nil {
|
||||
return processSQLErrorf(err, "Failed to bind path object")
|
||||
}
|
||||
|
@ -43,9 +58,10 @@ func CreateAliasPath(ctx context.Context, db *sqlx.DB, path *types.Path) error {
|
|||
}
|
||||
|
||||
// CreatePathTx creates a new path as part of a transaction.
|
||||
func CreatePathTx(ctx context.Context, db *sqlx.DB, tx *sqlx.Tx, path *types.Path) error {
|
||||
func CreatePathTx(ctx context.Context, db *sqlx.DB, tx *sqlx.Tx, path *types.Path,
|
||||
transformation store.PathTransformation) error {
|
||||
// ensure path length is okay
|
||||
if check.PathTooLong(path.Value, path.TargetType == enum.PathTargetTypeSpace) {
|
||||
if check.IsPathTooDeep(path.Value, path.TargetType == enum.PathTargetTypeSpace) {
|
||||
log.Warn().Msgf("Path '%s' is too long.", path.Value)
|
||||
return store.ErrPathTooLong
|
||||
}
|
||||
|
@ -59,7 +75,13 @@ func CreatePathTx(ctx context.Context, db *sqlx.DB, tx *sqlx.Tx, path *types.Pat
|
|||
}
|
||||
}
|
||||
|
||||
query, arg, err := db.BindNamed(pathInsert, path)
|
||||
// map to db path to ensure we store valueUnique.
|
||||
dbPath, err := mapToDBPath(path, transformation)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to map db path: %w", err)
|
||||
}
|
||||
|
||||
query, arg, err := db.BindNamed(pathInsert, dbPath)
|
||||
if err != nil {
|
||||
return processSQLErrorf(err, "Failed to bind path object")
|
||||
}
|
||||
|
@ -71,19 +93,38 @@ func CreatePathTx(ctx context.Context, db *sqlx.DB, tx *sqlx.Tx, path *types.Pat
|
|||
return nil
|
||||
}
|
||||
|
||||
func CountPrimaryChildPathsTx(ctx context.Context, tx *sqlx.Tx, prefix string) (int64, error) {
|
||||
func CountPrimaryChildPathsTx(ctx context.Context, tx *sqlx.Tx, prefix string,
|
||||
transformation store.PathTransformation) (int64, error) {
|
||||
// map the Value to unique Value before searching!
|
||||
prefixUnique, err := transformation(prefix)
|
||||
if err != nil {
|
||||
// in case we fail to transform, return a not found (as it can't exist in the first place)
|
||||
log.Ctx(ctx).Debug().Msgf("failed to transform path prefix '%s': %s", prefix, err.Error())
|
||||
return 0, store.ErrResourceNotFound
|
||||
}
|
||||
|
||||
var count int64
|
||||
err := tx.QueryRowContext(ctx, pathCountPrimaryForPrefix, paths.Concatinate(prefix, "%")).Scan(&count)
|
||||
err = tx.QueryRowContext(ctx, pathCountPrimaryForPrefixUnique, paths.Concatinate(prefixUnique, "%")).Scan(&count)
|
||||
if err != nil {
|
||||
return 0, processSQLErrorf(err, "Count query failed")
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func ListPrimaryChildPathsTx(ctx context.Context, tx *sqlx.Tx, prefix string) ([]*types.Path, error) {
|
||||
childs := []*types.Path{}
|
||||
func listPrimaryChildPathsTx(ctx context.Context, tx *sqlx.Tx, prefix string,
|
||||
transformation store.PathTransformation) ([]*path, error) {
|
||||
// map the Value to unique Value before searching!
|
||||
prefixUnique, err := transformation(prefix)
|
||||
if err != nil {
|
||||
// in case we fail to transform, return a not found (as it can't exist in the first place)
|
||||
log.Ctx(ctx).Debug().Msgf("failed to transform path prefix '%s': %s", prefix, err.Error())
|
||||
return nil, store.ErrResourceNotFound
|
||||
}
|
||||
|
||||
if err := tx.SelectContext(ctx, &childs, pathSelectPrimaryForPrefix, paths.Concatinate(prefix, "%")); err != nil {
|
||||
childs := []*path{}
|
||||
|
||||
if err = tx.SelectContext(ctx, &childs, pathSelectPrimaryForPrefixUnique,
|
||||
paths.Concatinate(prefixUnique, "%")); err != nil {
|
||||
return nil, processSQLErrorf(err, "Select query failed")
|
||||
}
|
||||
|
||||
|
@ -91,60 +132,87 @@ func ListPrimaryChildPathsTx(ctx context.Context, tx *sqlx.Tx, prefix string) ([
|
|||
}
|
||||
|
||||
// ReplacePathTx replaces the path for a target as part of a transaction - keeps the existing as alias if requested.
|
||||
func ReplacePathTx(ctx context.Context, db *sqlx.DB, tx *sqlx.Tx, path *types.Path, keepAsAlias bool) error {
|
||||
if path.IsAlias {
|
||||
func ReplacePathTx(ctx context.Context, db *sqlx.DB, tx *sqlx.Tx, newPath *types.Path, keepAsAlias bool,
|
||||
transformation store.PathTransformation) error {
|
||||
if newPath.IsAlias {
|
||||
return store.ErrPrimaryPathRequired
|
||||
}
|
||||
|
||||
// ensure new path length is okay
|
||||
if check.PathTooLong(path.Value, path.TargetType == enum.PathTargetTypeSpace) {
|
||||
log.Warn().Msgf("Path '%s' is too long.", path.Value)
|
||||
if check.IsPathTooDeep(newPath.Value, newPath.TargetType == enum.PathTargetTypeSpace) {
|
||||
log.Warn().Msgf("Path '%s' is too long.", newPath.Value)
|
||||
return store.ErrPathTooLong
|
||||
}
|
||||
|
||||
// existing is always non-alias (as query filters for IsAlias=0)
|
||||
existing := new(types.Path)
|
||||
err := tx.GetContext(ctx, existing, pathSelectPrimaryForTarget, string(path.TargetType), fmt.Sprint(path.TargetID))
|
||||
// dbExisting is always non-alias (as query filters for IsAlias=0)
|
||||
dbExisting := new(path)
|
||||
err := tx.GetContext(ctx, dbExisting, pathSelectPrimaryForTarget,
|
||||
string(newPath.TargetType), fmt.Sprint(newPath.TargetID))
|
||||
if err != nil {
|
||||
return processSQLErrorf(err, "Failed to get the existing primary path")
|
||||
}
|
||||
|
||||
// map to db path to ensure we store valueUnique.
|
||||
dbNew, err := mapToDBPath(newPath, transformation)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to map db path: %w", err)
|
||||
}
|
||||
|
||||
// ValueUnique is the same => routing is the same, ensure we don't keep the old as alias (duplicate error)
|
||||
if dbNew.ValueUnique == dbExisting.ValueUnique {
|
||||
keepAsAlias = false
|
||||
}
|
||||
|
||||
// Space specific checks.
|
||||
if newPath.TargetType == enum.PathTargetTypeSpace {
|
||||
/*
|
||||
* IMPORTANT
|
||||
* To avoid cycles in the primary graph, we have to ensure that the old path isn't a parent of the new path.
|
||||
* We have to look at the unique path here, as that is used for routing and duplicate detection.
|
||||
*/
|
||||
if strings.HasPrefix(dbNew.ValueUnique, dbExisting.ValueUnique+types.PathSeparator) {
|
||||
return store.ErrIllegalMoveCyclicHierarchy
|
||||
}
|
||||
}
|
||||
|
||||
// Only look for children if the type can have children
|
||||
if path.TargetType == enum.PathTargetTypeSpace {
|
||||
err = replaceChildrenPathsTx(ctx, db, tx, existing, path, keepAsAlias)
|
||||
if newPath.TargetType == enum.PathTargetTypeSpace {
|
||||
err = replaceChildrenPathsTx(ctx, db, tx, &dbExisting.Path, newPath, keepAsAlias, transformation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// make existing an alias (or delete)
|
||||
// IMPORTANT: delete before insert as a casing only change in the path is a valid input.
|
||||
// It's part of a db transaction so it should be okay.
|
||||
query := pathDeleteID
|
||||
if keepAsAlias {
|
||||
query = pathMakeAliasID
|
||||
}
|
||||
if _, err = tx.ExecContext(ctx, query, dbExisting.ID); err != nil {
|
||||
return processSQLErrorf(err, "Failed to mark existing path '%s' as alias (or delete)", dbExisting.Value)
|
||||
}
|
||||
|
||||
// insert the new Path
|
||||
query, arg, err := db.BindNamed(pathInsert, path)
|
||||
query, arg, err := db.BindNamed(pathInsert, dbNew)
|
||||
if err != nil {
|
||||
return processSQLErrorf(err, "Failed to bind path object")
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, query, arg...)
|
||||
if err != nil {
|
||||
return processSQLErrorf(err, "Failed to create new primary path '%s'", path.Value)
|
||||
}
|
||||
|
||||
// make existing an alias
|
||||
query = pathDeleteID
|
||||
if keepAsAlias {
|
||||
query = pathMakeAliasID
|
||||
}
|
||||
if _, err = tx.ExecContext(ctx, query, existing.ID); err != nil {
|
||||
return processSQLErrorf(err, "Failed to mark existing path '%s' as alias", existing.Value)
|
||||
return processSQLErrorf(err, "Failed to create new primary path '%s'", newPath.Value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func replaceChildrenPathsTx(ctx context.Context, db *sqlx.DB, tx *sqlx.Tx,
|
||||
existing *types.Path, path *types.Path, keepAsAlias bool) error {
|
||||
var childPaths []*types.Path
|
||||
existing *types.Path, updated *types.Path, keepAsAlias bool, transformation store.PathTransformation) error {
|
||||
var childPaths []*path
|
||||
// get all primary paths that start with the current path before updating (or we can run into recursion)
|
||||
childPaths, err := ListPrimaryChildPathsTx(ctx, tx, existing.Value)
|
||||
childPaths, err := listPrimaryChildPathsTx(ctx, tx, existing.Value, transformation)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Failed to get primary child paths for '%s'", existing.Value)
|
||||
}
|
||||
|
@ -152,16 +220,16 @@ func replaceChildrenPathsTx(ctx context.Context, db *sqlx.DB, tx *sqlx.Tx,
|
|||
for _, child := range childPaths {
|
||||
// create path with updated path (child already is primary)
|
||||
updatedChild := new(types.Path)
|
||||
*updatedChild = *child
|
||||
*updatedChild = child.Path
|
||||
updatedChild.ID = 0 // will be regenerated
|
||||
updatedChild.Created = path.Created
|
||||
updatedChild.Updated = path.Updated
|
||||
updatedChild.CreatedBy = path.CreatedBy
|
||||
updatedChild.Value = path.Value + updatedChild.Value[len(existing.Value):]
|
||||
updatedChild.Created = updated.Created
|
||||
updatedChild.Updated = updated.Updated
|
||||
updatedChild.CreatedBy = updated.CreatedBy
|
||||
updatedChild.Value = updated.Value + updatedChild.Value[len(existing.Value):]
|
||||
|
||||
// ensure new child path length is okay
|
||||
if check.PathTooLong(updatedChild.Value, path.TargetType == enum.PathTargetTypeSpace) {
|
||||
log.Warn().Msgf("Path '%s' is too long.", path.Value)
|
||||
if check.IsPathTooDeep(updatedChild.Value, updated.TargetType == enum.PathTargetTypeSpace) {
|
||||
log.Warn().Msgf("Path '%s' is too long.", updated.Value)
|
||||
return store.ErrPathTooLong
|
||||
}
|
||||
|
||||
|
@ -170,7 +238,26 @@ func replaceChildrenPathsTx(ctx context.Context, db *sqlx.DB, tx *sqlx.Tx,
|
|||
args []interface{}
|
||||
)
|
||||
|
||||
query, args, err = db.BindNamed(pathInsert, updatedChild)
|
||||
// make existing child path an alias (or delete)
|
||||
// IMPORTANT: delete before insert as a casing only change in the original path is a valid input.
|
||||
// It's part of a db transaction so it should be okay.
|
||||
query = pathDeleteID
|
||||
if keepAsAlias {
|
||||
query = pathMakeAliasID
|
||||
}
|
||||
if _, err = tx.ExecContext(ctx, query, child.ID); err != nil {
|
||||
return processSQLErrorf(err, "Failed to mark existing child path '%s' as alias (or delete)",
|
||||
updatedChild.Value)
|
||||
}
|
||||
|
||||
// map to db path to ensure we store valueUnique.
|
||||
var dbUpdatedChild *path
|
||||
dbUpdatedChild, err = mapToDBPath(updatedChild, transformation)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to map db path: %w", err)
|
||||
}
|
||||
|
||||
query, args, err = db.BindNamed(pathInsert, dbUpdatedChild)
|
||||
if err != nil {
|
||||
return processSQLErrorf(err, "Failed to bind path object")
|
||||
}
|
||||
|
@ -178,16 +265,6 @@ func replaceChildrenPathsTx(ctx context.Context, db *sqlx.DB, tx *sqlx.Tx,
|
|||
if _, err = tx.ExecContext(ctx, query, args...); err != nil {
|
||||
return processSQLErrorf(err, "Failed to create new primary child path '%s'", updatedChild.Value)
|
||||
}
|
||||
|
||||
// make current child an alias or delete it
|
||||
query = pathDeleteID
|
||||
if keepAsAlias {
|
||||
query = pathMakeAliasID
|
||||
}
|
||||
if _, err = tx.ExecContext(ctx, query, child.ID); err != nil {
|
||||
return processSQLErrorf(err, "Failed to mark existing child path '%s' as alias",
|
||||
updatedChild.Value)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -195,13 +272,13 @@ func replaceChildrenPathsTx(ctx context.Context, db *sqlx.DB, tx *sqlx.Tx,
|
|||
|
||||
// FindPathTx finds the primary path for a target.
|
||||
func FindPathTx(ctx context.Context, tx *sqlx.Tx, targetType enum.PathTargetType, targetID int64) (*types.Path, error) {
|
||||
dst := new(types.Path)
|
||||
dst := new(path)
|
||||
err := tx.GetContext(ctx, dst, pathSelectPrimaryForTarget, string(targetType), fmt.Sprint(targetID))
|
||||
if err != nil {
|
||||
return nil, processSQLErrorf(err, "Select query failed")
|
||||
}
|
||||
|
||||
return dst, nil
|
||||
return mapDBPath(dst), nil
|
||||
}
|
||||
|
||||
// DeletePath deletes a specific path alias (primary can't be deleted, only with delete all).
|
||||
|
@ -215,10 +292,11 @@ func DeletePath(ctx context.Context, db *sqlx.DB, id int64) error {
|
|||
}(tx)
|
||||
|
||||
// ensure path is an alias
|
||||
dst := new(types.Path)
|
||||
dst := new(path)
|
||||
if err = tx.GetContext(ctx, dst, pathSelectID, id); err != nil {
|
||||
return processSQLErrorf(err, "Failed to find path with id %d", id)
|
||||
} else if !dst.IsAlias {
|
||||
}
|
||||
if !dst.IsAlias {
|
||||
return store.ErrPrimaryPathCantBeDeleted
|
||||
}
|
||||
|
||||
|
@ -257,7 +335,7 @@ func CountPaths(ctx context.Context, db *sqlx.DB, targetType enum.PathTargetType
|
|||
// ListPaths lists all paths for a target.
|
||||
func ListPaths(ctx context.Context, db *sqlx.DB, targetType enum.PathTargetType, targetID int64,
|
||||
opts *types.PathFilter) ([]*types.Path, error) {
|
||||
dst := []*types.Path{}
|
||||
dst := []*path{}
|
||||
// else we construct the sql statement.
|
||||
stmt := builder.
|
||||
Select("*").
|
||||
|
@ -271,7 +349,7 @@ func ListPaths(ctx context.Context, db *sqlx.DB, targetType enum.PathTargetType,
|
|||
// NOTE: string concatenation is safe because the
|
||||
// order attribute is an enum and is not user-defined,
|
||||
// and is therefore not subject to injection attacks.
|
||||
stmt = stmt.OrderBy("path_value COLLATE NOCASE " + opts.Order.String())
|
||||
stmt = stmt.OrderBy("path_value " + opts.Order.String())
|
||||
case enum.PathAttrCreated:
|
||||
stmt = stmt.OrderBy("path_created " + opts.Order.String())
|
||||
case enum.PathAttrUpdated:
|
||||
|
@ -287,7 +365,7 @@ func ListPaths(ctx context.Context, db *sqlx.DB, targetType enum.PathTargetType,
|
|||
return nil, processSQLErrorf(err, "Customer select query failed")
|
||||
}
|
||||
|
||||
return dst, nil
|
||||
return mapDBPaths(dst), nil
|
||||
}
|
||||
|
||||
// CountPathsTx counts paths for a target as part of a transaction.
|
||||
|
@ -300,10 +378,41 @@ func CountPathsTx(ctx context.Context, tx *sqlx.Tx, targetType enum.PathTargetTy
|
|||
return count, nil
|
||||
}
|
||||
|
||||
func mapDBPath(dbPath *path) *types.Path {
|
||||
return &dbPath.Path
|
||||
}
|
||||
|
||||
func mapDBPaths(dbPaths []*path) []*types.Path {
|
||||
res := make([]*types.Path, len(dbPaths))
|
||||
for i := range dbPaths {
|
||||
res[i] = mapDBPath(dbPaths[i])
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func mapToDBPath(p *types.Path, transformation store.PathTransformation) (*path, error) {
|
||||
// path comes from outside.
|
||||
if p == nil {
|
||||
return nil, fmt.Errorf("path is nil")
|
||||
}
|
||||
|
||||
valueUnique, err := transformation(p.Value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to transform path: %w", err)
|
||||
}
|
||||
dbPath := &path{
|
||||
Path: *p,
|
||||
ValueUnique: valueUnique,
|
||||
}
|
||||
|
||||
return dbPath, nil
|
||||
}
|
||||
|
||||
const pathBase = `
|
||||
SELECT
|
||||
path_id
|
||||
,path_value
|
||||
,path_valueUnique
|
||||
,path_isAlias
|
||||
,path_targetType
|
||||
,path_targetId
|
||||
|
@ -318,8 +427,8 @@ const pathSelectPrimaryForTarget = pathBase + `
|
|||
WHERE path_targetType = $1 AND path_targetId = $2 AND path_isAlias = 0
|
||||
`
|
||||
|
||||
const pathSelectPrimaryForPrefix = pathBase + `
|
||||
WHERE path_value LIKE $1 AND path_isAlias = 0
|
||||
const pathSelectPrimaryForPrefixUnique = pathBase + `
|
||||
WHERE path_valueUnique LIKE $1 AND path_isAlias = 0
|
||||
`
|
||||
|
||||
const pathCount = `
|
||||
|
@ -328,15 +437,16 @@ FROM paths
|
|||
WHERE path_targetType = $1 AND path_targetId = $2
|
||||
`
|
||||
|
||||
const pathCountPrimaryForPrefix = `
|
||||
const pathCountPrimaryForPrefixUnique = `
|
||||
SELECT count(*)
|
||||
FROM paths
|
||||
WHERE path_value LIKE $1 AND path_isAlias = 0
|
||||
WHERE path_valueUnique LIKE $1 AND path_isAlias = 0
|
||||
`
|
||||
|
||||
const pathInsert = `
|
||||
INSERT INTO paths (
|
||||
path_value
|
||||
,path_valueUnique
|
||||
,path_isAlias
|
||||
,path_targetType
|
||||
,path_targetId
|
||||
|
@ -345,6 +455,7 @@ INSERT INTO paths (
|
|||
,path_updated
|
||||
) values (
|
||||
:path_value
|
||||
,:path_valueUnique
|
||||
,:path_isAlias
|
||||
,:path_targetType
|
||||
,:path_targetId
|
||||
|
|
|
@ -21,13 +21,17 @@ import (
|
|||
var _ store.RepoStore = (*RepoStore)(nil)
|
||||
|
||||
// Returns a new RepoStore.
|
||||
func NewRepoStore(db *sqlx.DB) *RepoStore {
|
||||
return &RepoStore{db}
|
||||
func NewRepoStore(db *sqlx.DB, pathTransformation store.PathTransformation) *RepoStore {
|
||||
return &RepoStore{
|
||||
db: db,
|
||||
pathTransformation: pathTransformation,
|
||||
}
|
||||
}
|
||||
|
||||
// Implements a RepoStore backed by a relational database.
|
||||
type RepoStore struct {
|
||||
db *sqlx.DB
|
||||
pathTransformation store.PathTransformation
|
||||
}
|
||||
|
||||
// Finds the repo by id.
|
||||
|
@ -41,8 +45,14 @@ func (s *RepoStore) Find(ctx context.Context, id int64) (*types.Repository, erro
|
|||
|
||||
// Finds the repo by path.
|
||||
func (s *RepoStore) FindByPath(ctx context.Context, path string) (*types.Repository, error) {
|
||||
// ensure we transform path before searching (otherwise casing might be wrong)
|
||||
pathUnique, err := s.pathTransformation(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to transform path '%s': %w", path, err)
|
||||
}
|
||||
|
||||
dst := new(types.Repository)
|
||||
if err := s.db.GetContext(ctx, dst, repoSelectByPath, path); err != nil {
|
||||
if err = s.db.GetContext(ctx, dst, repoSelectByPathUnique, pathUnique); err != nil {
|
||||
return nil, processSQLErrorf(err, "Select query failed")
|
||||
}
|
||||
return dst, nil
|
||||
|
@ -69,13 +79,13 @@ func (s *RepoStore) Create(ctx context.Context, repo *types.Repository) error {
|
|||
}
|
||||
|
||||
// Get parent path (repo always has a parent)
|
||||
parentPath, err := FindPathTx(ctx, tx, enum.PathTargetTypeSpace, repo.SpaceID)
|
||||
parentPath, err := FindPathTx(ctx, tx, enum.PathTargetTypeSpace, repo.ParentID)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Failed to find path of parent space")
|
||||
}
|
||||
|
||||
// all existing paths are valid, repo name is assumed to be valid.
|
||||
path := paths.Concatinate(parentPath.Value, repo.PathName)
|
||||
// All existing paths are valid, repo uid is assumed to be valid => new path is valid
|
||||
path := paths.Concatinate(parentPath.Value, repo.UID)
|
||||
|
||||
// create path only once we know the id of the repo
|
||||
p := &types.Path{
|
||||
|
@ -88,7 +98,7 @@ func (s *RepoStore) Create(ctx context.Context, repo *types.Repository) error {
|
|||
Updated: repo.Updated,
|
||||
}
|
||||
|
||||
if err = CreatePathTx(ctx, s.db, tx, p); err != nil {
|
||||
if err = CreatePathTx(ctx, s.db, tx, p, s.pathTransformation); err != nil {
|
||||
return errors.Wrap(err, "Failed to create primary path of repo")
|
||||
}
|
||||
|
||||
|
@ -104,7 +114,7 @@ func (s *RepoStore) Create(ctx context.Context, repo *types.Repository) error {
|
|||
}
|
||||
|
||||
// Move moves an existing space.
|
||||
func (s *RepoStore) Move(ctx context.Context, principalID int64, repoID int64, newSpaceID int64, newName string,
|
||||
func (s *RepoStore) Move(ctx context.Context, principalID int64, repoID int64, newParentID int64, newName string,
|
||||
keepAsAlias bool) (*types.Repository, error) {
|
||||
tx, err := s.db.BeginTxx(ctx, nil)
|
||||
if err != nil {
|
||||
|
@ -121,12 +131,14 @@ func (s *RepoStore) Move(ctx context.Context, principalID int64, repoID int64, n
|
|||
}
|
||||
|
||||
// get path of new parent space
|
||||
spacePath, err := FindPathTx(ctx, tx, enum.PathTargetTypeSpace, newSpaceID)
|
||||
spacePath, err := FindPathTx(ctx, tx, enum.PathTargetTypeSpace, newParentID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Failed to find the primary path of the new space")
|
||||
}
|
||||
|
||||
newPath := paths.Concatinate(spacePath.Value, newName)
|
||||
|
||||
// path is exactly the same => nothing to do
|
||||
if newPath == currentPath.Value {
|
||||
return nil, store.ErrNoChangeInRequestedMove
|
||||
}
|
||||
|
@ -142,12 +154,12 @@ func (s *RepoStore) Move(ctx context.Context, principalID int64, repoID int64, n
|
|||
}
|
||||
|
||||
// replace the primary path (also updates all child primary paths)
|
||||
if err = ReplacePathTx(ctx, s.db, tx, p, keepAsAlias); err != nil {
|
||||
if err = ReplacePathTx(ctx, s.db, tx, p, keepAsAlias, s.pathTransformation); err != nil {
|
||||
return nil, errors.Wrap(err, "Failed to update the primary path of the repo")
|
||||
}
|
||||
|
||||
// Rename the repo itself
|
||||
if _, err = tx.ExecContext(ctx, repoUpdateNameAndSpaceID, newName, newSpaceID, repoID); err != nil {
|
||||
if _, err = tx.ExecContext(ctx, repoUpdateUIDAndParentID, newName, newParentID, repoID); err != nil {
|
||||
return nil, processSQLErrorf(err, "Query for renaming and updating the space id failed")
|
||||
}
|
||||
|
||||
|
@ -183,39 +195,38 @@ func (s *RepoStore) Update(ctx context.Context, repo *types.Repository) error {
|
|||
func (s *RepoStore) Delete(ctx context.Context, id int64) error {
|
||||
tx, err := s.db.BeginTxx(ctx, nil)
|
||||
if err != nil {
|
||||
return processSQLErrorf(err, "Failed to start a new transaction")
|
||||
return processSQLErrorf(err, "failed to start a new transaction")
|
||||
}
|
||||
defer func(tx *sqlx.Tx) {
|
||||
_ = tx.Rollback()
|
||||
}(tx)
|
||||
|
||||
// delete all paths
|
||||
err = DeleteAllPaths(ctx, tx, enum.PathTargetTypeRepo, id)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Failed to delete all paths of the repo")
|
||||
if err = DeleteAllPaths(ctx, tx, enum.PathTargetTypeRepo, id); err != nil {
|
||||
return fmt.Errorf("failed to delete all paths of the repo: %w", err)
|
||||
}
|
||||
|
||||
// delete the repo
|
||||
if _, err = tx.ExecContext(ctx, repoDelete, id); err != nil {
|
||||
return processSQLErrorf(err, "The delete query failed")
|
||||
return processSQLErrorf(err, "the delete query failed")
|
||||
}
|
||||
|
||||
if err = tx.Commit(); err != nil {
|
||||
return processSQLErrorf(err, "Failed to commit transaction")
|
||||
return processSQLErrorf(err, "failed to commit transaction")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Count of repos in a space.
|
||||
func (s *RepoStore) Count(ctx context.Context, spaceID int64, opts *types.RepoFilter) (int64, error) {
|
||||
func (s *RepoStore) Count(ctx context.Context, parentID int64, opts *types.RepoFilter) (int64, error) {
|
||||
stmt := builder.
|
||||
Select("count(*)").
|
||||
From("repositories").
|
||||
Where("repo_spaceId = ?", spaceID)
|
||||
Where("repo_parentId = ?", parentID)
|
||||
|
||||
if opts.Query != "" {
|
||||
stmt = stmt.Where("repo_pathName LIKE ?", fmt.Sprintf("%%%s%%", opts.Query))
|
||||
stmt = stmt.Where("repo_uid LIKE ?", fmt.Sprintf("%%%s%%", opts.Query))
|
||||
}
|
||||
|
||||
sql, args, err := stmt.ToSql()
|
||||
|
@ -232,36 +243,34 @@ func (s *RepoStore) Count(ctx context.Context, spaceID int64, opts *types.RepoFi
|
|||
}
|
||||
|
||||
// List returns a list of repos in a space.
|
||||
func (s *RepoStore) List(ctx context.Context, spaceID int64, opts *types.RepoFilter) ([]*types.Repository, error) {
|
||||
func (s *RepoStore) List(ctx context.Context, parentID int64, opts *types.RepoFilter) ([]*types.Repository, error) {
|
||||
dst := []*types.Repository{}
|
||||
|
||||
// construct the sql statement.
|
||||
stmt := builder.
|
||||
Select("repositories.*,path_value AS repo_path").
|
||||
Select("repositories.*,paths.path_value AS repo_path").
|
||||
From("repositories").
|
||||
InnerJoin("paths ON repositories.repo_id=paths.path_targetId AND paths.path_targetType='repo' "+
|
||||
"AND paths.path_isAlias=0").
|
||||
Where("repo_spaceId = ?", fmt.Sprint(spaceID))
|
||||
Where("repo_parentId = ?", fmt.Sprint(parentID))
|
||||
|
||||
if opts.Query != "" {
|
||||
stmt = stmt.Where("repo_pathName LIKE ?", fmt.Sprintf("%%%s%%", opts.Query))
|
||||
stmt = stmt.Where("repo_uid LIKE ?", fmt.Sprintf("%%%s%%", opts.Query))
|
||||
}
|
||||
|
||||
stmt = stmt.Limit(uint64(limit(opts.Size)))
|
||||
stmt = stmt.Offset(uint64(offset(opts.Page, opts.Size)))
|
||||
|
||||
switch opts.Sort {
|
||||
case enum.RepoAttrName, enum.RepoAttrNone:
|
||||
case enum.RepoAttrUID, enum.RepoAttrNone:
|
||||
// NOTE: string concatenation is safe because the
|
||||
// order attribute is an enum and is not user-defined,
|
||||
// and is therefore not subject to injection attacks.
|
||||
stmt = stmt.OrderBy("repo_name COLLATE NOCASE " + opts.Order.String())
|
||||
stmt = stmt.OrderBy("repo_uid COLLATE NOCASE " + opts.Order.String())
|
||||
case enum.RepoAttrCreated:
|
||||
stmt = stmt.OrderBy("repo_created " + opts.Order.String())
|
||||
case enum.RepoAttrUpdated:
|
||||
stmt = stmt.OrderBy("repo_updated " + opts.Order.String())
|
||||
case enum.RepoAttrPathName:
|
||||
stmt = stmt.OrderBy("repo_pathName COLLATE NOCASE " + opts.Order.String())
|
||||
case enum.RepoAttrPath:
|
||||
stmt = stmt.OrderBy("repo_path COLLATE NOCASE " + opts.Order.String())
|
||||
}
|
||||
|
@ -295,14 +304,14 @@ func (s *RepoStore) CreatePath(ctx context.Context, repoID int64, params *types.
|
|||
TargetID: repoID,
|
||||
IsAlias: true,
|
||||
|
||||
// get remaining infor from params
|
||||
// get remaining info from params
|
||||
Value: params.Path,
|
||||
CreatedBy: params.CreatedBy,
|
||||
Created: params.Created,
|
||||
Updated: params.Updated,
|
||||
}
|
||||
|
||||
return p, CreateAliasPath(ctx, s.db, p)
|
||||
return p, CreateAliasPath(ctx, s.db, p, s.pathTransformation)
|
||||
}
|
||||
|
||||
// DeletePath an alias of a repository.
|
||||
|
@ -313,10 +322,9 @@ func (s *RepoStore) DeletePath(ctx context.Context, repoID int64, pathID int64)
|
|||
const repoSelectBase = `
|
||||
SELECT
|
||||
repo_id
|
||||
,repo_pathName
|
||||
,repo_spaceId
|
||||
,repo_parentId
|
||||
,repo_uid
|
||||
,paths.path_value AS repo_path
|
||||
,repo_name
|
||||
,repo_description
|
||||
,repo_isPublic
|
||||
,repo_createdBy
|
||||
|
@ -341,10 +349,10 @@ const repoSelectByID = repoSelectBaseWithJoin + `
|
|||
WHERE repo_id = $1
|
||||
`
|
||||
|
||||
const repoSelectByPath = repoSelectBase + `
|
||||
const repoSelectByPathUnique = repoSelectBase + `
|
||||
FROM paths paths1
|
||||
INNER JOIN repositories ON repositories.repo_id=paths1.path_targetId AND paths1.path_targetType='repo'
|
||||
AND paths1.path_value = $1
|
||||
AND paths1.path_valueUnique = $1
|
||||
INNER JOIN paths ON repositories.repo_id=paths.path_targetId AND paths.path_targetType='repo' AND paths.path_isAlias=0
|
||||
`
|
||||
|
||||
|
@ -356,9 +364,8 @@ WHERE repo_id = $1
|
|||
// TODO: do we have to worry about SQL injection for description?
|
||||
const repoInsert = `
|
||||
INSERT INTO repositories (
|
||||
repo_pathName
|
||||
,repo_spaceId
|
||||
,repo_name
|
||||
repo_parentId
|
||||
,repo_uid
|
||||
,repo_description
|
||||
,repo_isPublic
|
||||
,repo_createdBy
|
||||
|
@ -372,9 +379,8 @@ INSERT INTO repositories (
|
|||
,repo_numClosedPulls
|
||||
,repo_numOpenPulls
|
||||
) values (
|
||||
:repo_pathName
|
||||
,:repo_spaceId
|
||||
,:repo_name
|
||||
:repo_parentId
|
||||
,:repo_uid
|
||||
,:repo_description
|
||||
,:repo_isPublic
|
||||
,:repo_createdBy
|
||||
|
@ -393,8 +399,7 @@ INSERT INTO repositories (
|
|||
const repoUpdate = `
|
||||
UPDATE repositories
|
||||
SET
|
||||
repo_name = :repo_name
|
||||
,repo_description = :repo_description
|
||||
repo_description = :repo_description
|
||||
,repo_isPublic = :repo_isPublic
|
||||
,repo_updated = :repo_updated
|
||||
,repo_numForks = :repo_numForks
|
||||
|
@ -404,10 +409,10 @@ repo_name = :repo_name
|
|||
WHERE repo_id = :repo_id
|
||||
`
|
||||
|
||||
const repoUpdateNameAndSpaceID = `
|
||||
const repoUpdateUIDAndParentID = `
|
||||
UPDATE repositories
|
||||
SET
|
||||
repo_pathName = $1
|
||||
,repo_spaceId = $2
|
||||
repo_uid = $1
|
||||
,repo_parentId = $2
|
||||
WHERE repo_id = $3
|
||||
`
|
||||
|
|
|
@ -48,11 +48,11 @@ func (s *RepoStoreSync) Create(ctx context.Context, repo *types.Repository) erro
|
|||
}
|
||||
|
||||
// Move an existing repo.
|
||||
func (s *RepoStoreSync) Move(ctx context.Context, principalID int64, repoID int64, newSpaceID int64,
|
||||
func (s *RepoStoreSync) Move(ctx context.Context, principalID int64, id int64, newParentID int64,
|
||||
newName string, keepAsAlias bool) (*types.Repository, error) {
|
||||
mutex.RLock()
|
||||
defer mutex.RUnlock()
|
||||
return s.base.Move(ctx, principalID, repoID, newSpaceID, newName, keepAsAlias)
|
||||
return s.base.Move(ctx, principalID, id, newParentID, newName, keepAsAlias)
|
||||
}
|
||||
|
||||
// Update the repo details.
|
||||
|
@ -70,17 +70,17 @@ func (s *RepoStoreSync) Delete(ctx context.Context, id int64) error {
|
|||
}
|
||||
|
||||
// Count of repos in a space.
|
||||
func (s *RepoStoreSync) Count(ctx context.Context, spaceID int64, opts *types.RepoFilter) (int64, error) {
|
||||
func (s *RepoStoreSync) Count(ctx context.Context, parentID int64, opts *types.RepoFilter) (int64, error) {
|
||||
mutex.RLock()
|
||||
defer mutex.RUnlock()
|
||||
return s.base.Count(ctx, spaceID, opts)
|
||||
return s.base.Count(ctx, parentID, opts)
|
||||
}
|
||||
|
||||
// List returns a list of repos in a space.
|
||||
func (s *RepoStoreSync) List(ctx context.Context, spaceID int64, opts *types.RepoFilter) ([]*types.Repository, error) {
|
||||
func (s *RepoStoreSync) List(ctx context.Context, parentID int64, opts *types.RepoFilter) ([]*types.Repository, error) {
|
||||
mutex.RLock()
|
||||
defer mutex.RUnlock()
|
||||
return s.base.List(ctx, spaceID, opts)
|
||||
return s.base.List(ctx, parentID, opts)
|
||||
}
|
||||
|
||||
// CountPaths returns a count of all paths of a repo.
|
||||
|
|
|
@ -7,52 +7,79 @@ package database
|
|||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/harness/gitness/internal/store"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
var _ store.ServiceStore = (*ServiceStore)(nil)
|
||||
|
||||
// service is a DB representation of a service principal.
|
||||
// It is required to allow storing transformed UIDs used for uniquness constraints and searching.
|
||||
type service struct {
|
||||
types.Service
|
||||
UIDUnique string `db:"principal_uidUnique"`
|
||||
}
|
||||
|
||||
// NewServiceStore returns a new ServiceStore.
|
||||
func NewServiceStore(db *sqlx.DB) *ServiceStore {
|
||||
return &ServiceStore{db}
|
||||
func NewServiceStore(db *sqlx.DB, uidTransformation store.PrincipalUIDTransformation) *ServiceStore {
|
||||
return &ServiceStore{
|
||||
db: db,
|
||||
uidTransformation: uidTransformation,
|
||||
}
|
||||
}
|
||||
|
||||
// ServiceStore implements a ServiceStore backed by a relational
|
||||
// database.
|
||||
type ServiceStore struct {
|
||||
db *sqlx.DB
|
||||
uidTransformation store.PrincipalUIDTransformation
|
||||
}
|
||||
|
||||
// Find finds the service by id.
|
||||
func (s *ServiceStore) Find(ctx context.Context, id int64) (*types.Service, error) {
|
||||
dst := new(types.Service)
|
||||
dst := new(service)
|
||||
if err := s.db.GetContext(ctx, dst, serviceSelectID, id); err != nil {
|
||||
return nil, processSQLErrorf(err, "Select by id query failed")
|
||||
}
|
||||
return dst, nil
|
||||
return s.mapDBService(dst), nil
|
||||
}
|
||||
|
||||
// FindUID finds the service by uid.
|
||||
func (s *ServiceStore) FindUID(ctx context.Context, uid string) (*types.Service, error) {
|
||||
dst := new(types.Service)
|
||||
if err := s.db.GetContext(ctx, dst, serviceSelectUID, uid); err != nil {
|
||||
// map the UID to unique UID before searching!
|
||||
uidUnique, err := s.uidTransformation(enum.PrincipalTypeService, uid)
|
||||
if err != nil {
|
||||
// in case we fail to transform, return a not found (as it can't exist in the first place)
|
||||
log.Ctx(ctx).Debug().Msgf("failed to transform uid '%s': %s", uid, err.Error())
|
||||
return nil, store.ErrResourceNotFound
|
||||
}
|
||||
|
||||
dst := new(service)
|
||||
if err = s.db.GetContext(ctx, dst, serviceSelectUIDUnique, uidUnique); err != nil {
|
||||
return nil, processSQLErrorf(err, "Select by uid query failed")
|
||||
}
|
||||
return dst, nil
|
||||
return s.mapDBService(dst), nil
|
||||
}
|
||||
|
||||
// Create saves the service.
|
||||
func (s *ServiceStore) Create(ctx context.Context, sa *types.Service) error {
|
||||
query, arg, err := s.db.BindNamed(serviceInsert, sa)
|
||||
func (s *ServiceStore) Create(ctx context.Context, svc *types.Service) error {
|
||||
dbSVC, err := s.mapToDBservice(svc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to map db service: %w", err)
|
||||
}
|
||||
|
||||
query, arg, err := s.db.BindNamed(serviceInsert, dbSVC)
|
||||
if err != nil {
|
||||
return processSQLErrorf(err, "Failed to bind service object")
|
||||
}
|
||||
|
||||
if err = s.db.QueryRowContext(ctx, query, arg...).Scan(&sa.ID); err != nil {
|
||||
if err = s.db.QueryRowContext(ctx, query, arg...).Scan(&svc.ID); err != nil {
|
||||
return processSQLErrorf(err, "Insert query failed")
|
||||
}
|
||||
|
||||
|
@ -60,8 +87,13 @@ func (s *ServiceStore) Create(ctx context.Context, sa *types.Service) error {
|
|||
}
|
||||
|
||||
// Update updates the service.
|
||||
func (s *ServiceStore) Update(ctx context.Context, sa *types.Service) error {
|
||||
query, arg, err := s.db.BindNamed(serviceUpdate, sa)
|
||||
func (s *ServiceStore) Update(ctx context.Context, svc *types.Service) error {
|
||||
dbSVC, err := s.mapToDBservice(svc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to map db service: %w", err)
|
||||
}
|
||||
|
||||
query, arg, err := s.db.BindNamed(serviceUpdate, dbSVC)
|
||||
if err != nil {
|
||||
return processSQLErrorf(err, "Failed to bind service object")
|
||||
}
|
||||
|
@ -91,13 +123,13 @@ func (s *ServiceStore) Delete(ctx context.Context, id int64) error {
|
|||
|
||||
// List returns a list of service for a specific parent.
|
||||
func (s *ServiceStore) List(ctx context.Context) ([]*types.Service, error) {
|
||||
dst := []*types.Service{}
|
||||
dst := []*service{}
|
||||
|
||||
err := s.db.SelectContext(ctx, &dst, serviceSelect)
|
||||
if err != nil {
|
||||
return nil, processSQLErrorf(err, "Failed executing default list query")
|
||||
}
|
||||
return dst, nil
|
||||
return s.mapDBServices(dst), nil
|
||||
}
|
||||
|
||||
// Count returns a count of service for a specific parent.
|
||||
|
@ -110,6 +142,36 @@ func (s *ServiceStore) Count(ctx context.Context) (int64, error) {
|
|||
return count, nil
|
||||
}
|
||||
|
||||
func (s *ServiceStore) mapDBService(dbSvc *service) *types.Service {
|
||||
return &dbSvc.Service
|
||||
}
|
||||
|
||||
func (s *ServiceStore) mapDBServices(dbSVCs []*service) []*types.Service {
|
||||
res := make([]*types.Service, len(dbSVCs))
|
||||
for i := range dbSVCs {
|
||||
res[i] = s.mapDBService(dbSVCs[i])
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *ServiceStore) mapToDBservice(svc *types.Service) (*service, error) {
|
||||
// service comes from outside.
|
||||
if svc == nil {
|
||||
return nil, fmt.Errorf("service is nil")
|
||||
}
|
||||
|
||||
uidUnique, err := s.uidTransformation(enum.PrincipalTypeService, svc.UID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to transform service UID: %w", err)
|
||||
}
|
||||
dbService := &service{
|
||||
Service: *svc,
|
||||
UIDUnique: uidUnique,
|
||||
}
|
||||
|
||||
return dbService, nil
|
||||
}
|
||||
|
||||
const serviceCount = `
|
||||
SELECT count(*)
|
||||
FROM principals
|
||||
|
@ -120,7 +182,9 @@ const serviceBase = `
|
|||
SELECT
|
||||
principal_id
|
||||
,principal_uid
|
||||
,principal_name
|
||||
,principal_uidUnique
|
||||
,principal_email
|
||||
,principal_displayName
|
||||
,principal_blocked
|
||||
,principal_salt
|
||||
,principal_created
|
||||
|
@ -130,15 +194,15 @@ FROM principals
|
|||
|
||||
const serviceSelect = serviceBase + `
|
||||
WHERE principal_type = "service"
|
||||
ORDER BY principal_name ASC
|
||||
ORDER BY principal_uid ASC
|
||||
`
|
||||
|
||||
const serviceSelectID = serviceBase + `
|
||||
WHERE principal_type = "service" AND principal_id = $1
|
||||
`
|
||||
|
||||
const serviceSelectUID = serviceBase + `
|
||||
WHERE principal_type = "service" AND principal_uid = $1
|
||||
const serviceSelectUIDUnique = serviceBase + `
|
||||
WHERE principal_type = "service" AND principal_uidUnique = $1
|
||||
`
|
||||
|
||||
const serviceDelete = `
|
||||
|
@ -150,7 +214,9 @@ const serviceInsert = `
|
|||
INSERT INTO principals (
|
||||
principal_type
|
||||
,principal_uid
|
||||
,principal_name
|
||||
,principal_uidUnique
|
||||
,principal_email
|
||||
,principal_displayName
|
||||
,principal_admin
|
||||
,principal_blocked
|
||||
,principal_salt
|
||||
|
@ -159,7 +225,9 @@ principal_type
|
|||
) values (
|
||||
"service"
|
||||
,:principal_uid
|
||||
,:principal_name
|
||||
,:principal_uidUnique
|
||||
,:principal_email
|
||||
,:principal_displayName
|
||||
,:principal_admin
|
||||
,:principal_blocked
|
||||
,:principal_salt
|
||||
|
@ -171,7 +239,8 @@ principal_type
|
|||
const serviceUpdate = `
|
||||
UPDATE principals
|
||||
SET
|
||||
principal_name = :principal_name
|
||||
principal_email = :principal_email
|
||||
,principal_displayName = :principal_displayName
|
||||
,principal_admin = :principal_admin
|
||||
,principal_blocked = :principal_blocked
|
||||
,principal_updated = :principal_updated
|
||||
|
|
|
@ -7,48 +7,74 @@ package database
|
|||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/harness/gitness/internal/store"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
var _ store.ServiceAccountStore = (*ServiceAccountStore)(nil)
|
||||
|
||||
// serviceAccount is a DB representation of a service account principal.
|
||||
// It is required to allow storing transformed UIDs used for uniquness constraints and searching.
|
||||
type serviceAccount struct {
|
||||
types.ServiceAccount
|
||||
UIDUnique string `db:"principal_uidUnique"`
|
||||
}
|
||||
|
||||
// NewServiceAccountStore returns a new ServiceAccountStore.
|
||||
func NewServiceAccountStore(db *sqlx.DB) *ServiceAccountStore {
|
||||
return &ServiceAccountStore{db}
|
||||
func NewServiceAccountStore(db *sqlx.DB, uidTransformation store.PrincipalUIDTransformation) *ServiceAccountStore {
|
||||
return &ServiceAccountStore{
|
||||
db: db,
|
||||
uidTransformation: uidTransformation,
|
||||
}
|
||||
}
|
||||
|
||||
// ServiceAccountStore implements a ServiceAccountStore backed by a relational
|
||||
// database.
|
||||
type ServiceAccountStore struct {
|
||||
db *sqlx.DB
|
||||
uidTransformation store.PrincipalUIDTransformation
|
||||
}
|
||||
|
||||
// Find finds the service account by id.
|
||||
func (s *ServiceAccountStore) Find(ctx context.Context, id int64) (*types.ServiceAccount, error) {
|
||||
dst := new(types.ServiceAccount)
|
||||
dst := new(serviceAccount)
|
||||
if err := s.db.GetContext(ctx, dst, serviceAccountSelectID, id); err != nil {
|
||||
return nil, processSQLErrorf(err, "Select by id query failed")
|
||||
}
|
||||
return dst, nil
|
||||
return s.mapDBServiceAccount(dst), nil
|
||||
}
|
||||
|
||||
// FindUID finds the service account by uid.
|
||||
func (s *ServiceAccountStore) FindUID(ctx context.Context, uid string) (*types.ServiceAccount, error) {
|
||||
dst := new(types.ServiceAccount)
|
||||
if err := s.db.GetContext(ctx, dst, serviceAccountSelectUID, uid); err != nil {
|
||||
// map the UID to unique UID before searching!
|
||||
uidUnique, err := s.uidTransformation(enum.PrincipalTypeServiceAccount, uid)
|
||||
if err != nil {
|
||||
// in case we fail to transform, return a not found (as it can't exist in the first place)
|
||||
log.Ctx(ctx).Debug().Msgf("failed to transform uid '%s': %s", uid, err.Error())
|
||||
return nil, store.ErrResourceNotFound
|
||||
}
|
||||
|
||||
dst := new(serviceAccount)
|
||||
if err = s.db.GetContext(ctx, dst, serviceAccountSelectUIDUnique, uidUnique); err != nil {
|
||||
return nil, processSQLErrorf(err, "Select by uid query failed")
|
||||
}
|
||||
return dst, nil
|
||||
return s.mapDBServiceAccount(dst), nil
|
||||
}
|
||||
|
||||
// Create saves the service account.
|
||||
func (s *ServiceAccountStore) Create(ctx context.Context, sa *types.ServiceAccount) error {
|
||||
query, arg, err := s.db.BindNamed(serviceAccountInsert, sa)
|
||||
dbSA, err := s.mapToDBserviceAccount(sa)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to map db service account: %w", err)
|
||||
}
|
||||
|
||||
query, arg, err := s.db.BindNamed(serviceAccountInsert, dbSA)
|
||||
if err != nil {
|
||||
return processSQLErrorf(err, "Failed to bind service account object")
|
||||
}
|
||||
|
@ -62,7 +88,12 @@ func (s *ServiceAccountStore) Create(ctx context.Context, sa *types.ServiceAccou
|
|||
|
||||
// Update updates the service account details.
|
||||
func (s *ServiceAccountStore) Update(ctx context.Context, sa *types.ServiceAccount) error {
|
||||
query, arg, err := s.db.BindNamed(serviceAccountUpdate, sa)
|
||||
dbSA, err := s.mapToDBserviceAccount(sa)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to map db service account: %w", err)
|
||||
}
|
||||
|
||||
query, arg, err := s.db.BindNamed(serviceAccountUpdate, dbSA)
|
||||
if err != nil {
|
||||
return processSQLErrorf(err, "Failed to bind service account object")
|
||||
}
|
||||
|
@ -93,13 +124,13 @@ func (s *ServiceAccountStore) Delete(ctx context.Context, id int64) error {
|
|||
// List returns a list of service accounts for a specific parent.
|
||||
func (s *ServiceAccountStore) List(ctx context.Context, parentType enum.ParentResourceType,
|
||||
parentID int64) ([]*types.ServiceAccount, error) {
|
||||
dst := []*types.ServiceAccount{}
|
||||
dst := []*serviceAccount{}
|
||||
|
||||
err := s.db.SelectContext(ctx, &dst, serviceAccountSelectByParentTypeAndID, parentType, parentID)
|
||||
if err != nil {
|
||||
return nil, processSQLErrorf(err, "Failed executing default list query")
|
||||
}
|
||||
return dst, nil
|
||||
return s.mapDBServiceAccounts(dst), nil
|
||||
}
|
||||
|
||||
// Count returns a count of service accounts for a specific parent.
|
||||
|
@ -113,6 +144,36 @@ func (s *ServiceAccountStore) Count(ctx context.Context,
|
|||
return count, nil
|
||||
}
|
||||
|
||||
func (s *ServiceAccountStore) mapDBServiceAccount(dbSA *serviceAccount) *types.ServiceAccount {
|
||||
return &dbSA.ServiceAccount
|
||||
}
|
||||
|
||||
func (s *ServiceAccountStore) mapDBServiceAccounts(dbSAs []*serviceAccount) []*types.ServiceAccount {
|
||||
res := make([]*types.ServiceAccount, len(dbSAs))
|
||||
for i := range dbSAs {
|
||||
res[i] = s.mapDBServiceAccount(dbSAs[i])
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *ServiceAccountStore) mapToDBserviceAccount(sa *types.ServiceAccount) (*serviceAccount, error) {
|
||||
// service account comes from outside.
|
||||
if sa == nil {
|
||||
return nil, fmt.Errorf("service account is nil")
|
||||
}
|
||||
|
||||
uidUnique, err := s.uidTransformation(enum.PrincipalTypeServiceAccount, sa.UID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to transform service account UID: %w", err)
|
||||
}
|
||||
dbSA := &serviceAccount{
|
||||
ServiceAccount: *sa,
|
||||
UIDUnique: uidUnique,
|
||||
}
|
||||
|
||||
return dbSA, nil
|
||||
}
|
||||
|
||||
const serviceAccountCountByParentTypeAndID = `
|
||||
SELECT count(*)
|
||||
FROM principals
|
||||
|
@ -123,7 +184,9 @@ const serviceAccountBase = `
|
|||
SELECT
|
||||
principal_id
|
||||
,principal_uid
|
||||
,principal_name
|
||||
,principal_uidUnique
|
||||
,principal_email
|
||||
,principal_displayName
|
||||
,principal_blocked
|
||||
,principal_salt
|
||||
,principal_created
|
||||
|
@ -135,15 +198,15 @@ FROM principals
|
|||
|
||||
const serviceAccountSelectByParentTypeAndID = serviceAccountBase + `
|
||||
WHERE principal_type = "serviceaccount" AND principal_sa_parentType = $1 AND principal_sa_parentId = $2
|
||||
ORDER BY principal_name ASC
|
||||
ORDER BY principal_uid ASC
|
||||
`
|
||||
|
||||
const serviceAccountSelectID = serviceAccountBase + `
|
||||
WHERE principal_type = "serviceaccount" AND principal_id = $1
|
||||
`
|
||||
|
||||
const serviceAccountSelectUID = serviceAccountBase + `
|
||||
WHERE principal_type = "serviceaccount" AND principal_uid = $1
|
||||
const serviceAccountSelectUIDUnique = serviceAccountBase + `
|
||||
WHERE principal_type = "serviceaccount" AND principal_uidUnique = $1
|
||||
`
|
||||
|
||||
const serviceAccountDelete = `
|
||||
|
@ -155,7 +218,9 @@ const serviceAccountInsert = `
|
|||
INSERT INTO principals (
|
||||
principal_type
|
||||
,principal_uid
|
||||
,principal_name
|
||||
,principal_uidUnique
|
||||
,principal_email
|
||||
,principal_displayName
|
||||
,principal_admin
|
||||
,principal_blocked
|
||||
,principal_salt
|
||||
|
@ -166,7 +231,9 @@ principal_type
|
|||
) values (
|
||||
"serviceaccount"
|
||||
,:principal_uid
|
||||
,:principal_name
|
||||
,:principal_uidUnique
|
||||
,:principal_email
|
||||
,:principal_displayName
|
||||
,false
|
||||
,:principal_blocked
|
||||
,:principal_salt
|
||||
|
@ -180,7 +247,8 @@ principal_type
|
|||
const serviceAccountUpdate = `
|
||||
UPDATE principals
|
||||
SET
|
||||
principal_name = :principal_name
|
||||
principal_email = :principal_email
|
||||
,principal_displayName = :principal_displayName
|
||||
,principal_blocked = :principal_blocked
|
||||
,principal_salt = :principal_salt
|
||||
,principal_updated = :principal_updated
|
||||
|
|
|
@ -7,7 +7,6 @@ package database
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/harness/gitness/internal/paths"
|
||||
|
@ -22,13 +21,17 @@ import (
|
|||
var _ store.SpaceStore = (*SpaceStore)(nil)
|
||||
|
||||
// NewSpaceStore returns a new SpaceStore.
|
||||
func NewSpaceStore(db *sqlx.DB) *SpaceStore {
|
||||
return &SpaceStore{db}
|
||||
func NewSpaceStore(db *sqlx.DB, pathTransformation store.PathTransformation) *SpaceStore {
|
||||
return &SpaceStore{
|
||||
db: db,
|
||||
pathTransformation: pathTransformation,
|
||||
}
|
||||
}
|
||||
|
||||
// SpaceStore implements a SpaceStore backed by a relational database.
|
||||
type SpaceStore struct {
|
||||
db *sqlx.DB
|
||||
pathTransformation store.PathTransformation
|
||||
}
|
||||
|
||||
// Find the space by id.
|
||||
|
@ -42,8 +45,14 @@ func (s *SpaceStore) Find(ctx context.Context, id int64) (*types.Space, error) {
|
|||
|
||||
// FindByPath finds the space by path.
|
||||
func (s *SpaceStore) FindByPath(ctx context.Context, path string) (*types.Space, error) {
|
||||
// ensure we transform path before searching (otherwise casing might be wrong)
|
||||
pathUnique, err := s.pathTransformation(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to transform path '%s': %w", path, err)
|
||||
}
|
||||
|
||||
dst := new(types.Space)
|
||||
if err := s.db.GetContext(ctx, dst, spaceSelectByPath, path); err != nil {
|
||||
if err = s.db.GetContext(ctx, dst, spaceSelectByPathUnique, pathUnique); err != nil {
|
||||
return nil, processSQLErrorf(err, "Select query failed")
|
||||
}
|
||||
return dst, nil
|
||||
|
@ -70,7 +79,7 @@ func (s *SpaceStore) Create(ctx context.Context, space *types.Space) error {
|
|||
}
|
||||
|
||||
// Get path (get parent if needed)
|
||||
path := space.PathName
|
||||
path := space.UID
|
||||
if space.ParentID > 0 {
|
||||
var parentPath *types.Path
|
||||
parentPath, err = FindPathTx(ctx, tx, enum.PathTargetTypeSpace, space.ParentID)
|
||||
|
@ -78,8 +87,8 @@ func (s *SpaceStore) Create(ctx context.Context, space *types.Space) error {
|
|||
return errors.Wrap(err, "Failed to find path of parent space")
|
||||
}
|
||||
|
||||
// all existing paths are valid, space name is assumed to be valid.
|
||||
path = paths.Concatinate(parentPath.Value, space.PathName)
|
||||
// all existing paths are valid, space uid is assumed to be valid.
|
||||
path = paths.Concatinate(parentPath.Value, space.UID)
|
||||
}
|
||||
|
||||
// create path only once we know the id of the space
|
||||
|
@ -92,7 +101,7 @@ func (s *SpaceStore) Create(ctx context.Context, space *types.Space) error {
|
|||
Created: space.Created,
|
||||
Updated: space.Updated,
|
||||
}
|
||||
err = CreatePathTx(ctx, s.db, tx, p)
|
||||
err = CreatePathTx(ctx, s.db, tx, p, s.pathTransformation)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Failed to create primary path of space")
|
||||
}
|
||||
|
@ -109,7 +118,7 @@ func (s *SpaceStore) Create(ctx context.Context, space *types.Space) error {
|
|||
}
|
||||
|
||||
// Move moves an existing space.
|
||||
func (s *SpaceStore) Move(ctx context.Context, principalID int64, spaceID int64, newParentID int64, newName string,
|
||||
func (s *SpaceStore) Move(ctx context.Context, principalID int64, id int64, newParentID int64, newName string,
|
||||
keepAsAlias bool) (*types.Space, error) {
|
||||
tx, err := s.db.BeginTxx(ctx, nil)
|
||||
if err != nil {
|
||||
|
@ -120,13 +129,13 @@ func (s *SpaceStore) Move(ctx context.Context, principalID int64, spaceID int64,
|
|||
}(tx)
|
||||
|
||||
// always get currentpath (either it didn't change or we need to for validation)
|
||||
currentPath, err := FindPathTx(ctx, tx, enum.PathTargetTypeSpace, spaceID)
|
||||
currentPath, err := FindPathTx(ctx, tx, enum.PathTargetTypeSpace, id)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Failed to find the primary path of the space")
|
||||
}
|
||||
|
||||
// get path of new parent if needed
|
||||
newPath := newName
|
||||
newPathValue := newName
|
||||
if newParentID > 0 {
|
||||
// get path of new parent space
|
||||
var spacePath *types.Path
|
||||
|
@ -135,42 +144,37 @@ func (s *SpaceStore) Move(ctx context.Context, principalID int64, spaceID int64,
|
|||
return nil, errors.Wrap(err, "Failed to find the primary path of the new parent space")
|
||||
}
|
||||
|
||||
newPath = paths.Concatinate(spacePath.Value, newName)
|
||||
newPathValue = paths.Concatinate(spacePath.Value, newName)
|
||||
}
|
||||
|
||||
/*
|
||||
* IMPORTANT
|
||||
* To avoid cycles in the primary graph, we have to ensure that the old path isn't a parent of the new path.
|
||||
*/
|
||||
if newPath == currentPath.Value {
|
||||
// path is exactly the same => nothing to do
|
||||
if newPathValue == currentPath.Value {
|
||||
return nil, store.ErrNoChangeInRequestedMove
|
||||
} else if strings.HasPrefix(newPath, currentPath.Value+types.PathSeparator) {
|
||||
return nil, store.ErrIllegalMoveCyclicHierarchy
|
||||
}
|
||||
|
||||
p := &types.Path{
|
||||
TargetType: enum.PathTargetTypeSpace,
|
||||
TargetID: spaceID,
|
||||
TargetID: id,
|
||||
IsAlias: false,
|
||||
Value: newPath,
|
||||
Value: newPathValue,
|
||||
CreatedBy: principalID,
|
||||
Created: time.Now().UnixMilli(),
|
||||
Updated: time.Now().UnixMilli(),
|
||||
}
|
||||
|
||||
// replace the primary path (also updates all child primary paths)
|
||||
if err = ReplacePathTx(ctx, s.db, tx, p, keepAsAlias); err != nil {
|
||||
if err = ReplacePathTx(ctx, s.db, tx, p, keepAsAlias, s.pathTransformation); err != nil {
|
||||
return nil, errors.Wrap(err, "Failed to update the primary path of the space")
|
||||
}
|
||||
|
||||
// Update the space itself
|
||||
if _, err = tx.ExecContext(ctx, spaceUpdateNameAndParentID, newName, newParentID, spaceID); err != nil {
|
||||
if _, err = tx.ExecContext(ctx, spaceUpdateUIDAndParentID, newName, newParentID, id); err != nil {
|
||||
return nil, processSQLErrorf(err, "Query for renaming and updating the parent id failed")
|
||||
}
|
||||
|
||||
// TODO: return space as part of rename operation
|
||||
dst := new(types.Space)
|
||||
if err = tx.GetContext(ctx, dst, spaceSelectByID, spaceID); err != nil {
|
||||
if err = tx.GetContext(ctx, dst, spaceSelectByID, id); err != nil {
|
||||
return nil, processSQLErrorf(err, "Select query to get the space's latest state failed")
|
||||
}
|
||||
|
||||
|
@ -213,7 +217,7 @@ func (s *SpaceStore) Delete(ctx context.Context, id int64) error {
|
|||
}
|
||||
|
||||
// Get child count and ensure there are none
|
||||
count, err := CountPrimaryChildPathsTx(ctx, tx, path.Value)
|
||||
count, err := CountPrimaryChildPathsTx(ctx, tx, path.Value, s.pathTransformation)
|
||||
if err != nil {
|
||||
return fmt.Errorf("child count error: %w", err)
|
||||
}
|
||||
|
@ -223,8 +227,7 @@ func (s *SpaceStore) Delete(ctx context.Context, id int64) error {
|
|||
}
|
||||
|
||||
// delete all paths
|
||||
err = DeleteAllPaths(ctx, tx, enum.PathTargetTypeSpace, id)
|
||||
if err != nil {
|
||||
if err = DeleteAllPaths(ctx, tx, enum.PathTargetTypeSpace, id); err != nil {
|
||||
return errors.Wrap(err, "Failed to delete all paths of the space")
|
||||
}
|
||||
|
||||
|
@ -248,7 +251,7 @@ func (s *SpaceStore) Count(ctx context.Context, id int64, opts *types.SpaceFilte
|
|||
Where("space_parentId = ?", id)
|
||||
|
||||
if opts.Query != "" {
|
||||
stmt = stmt.Where("space_pathName LIKE ?", fmt.Sprintf("%%%s%%", opts.Query))
|
||||
stmt = stmt.Where("space_uid LIKE ?", fmt.Sprintf("%%%s%%", opts.Query))
|
||||
}
|
||||
|
||||
sql, args, err := stmt.ToSql()
|
||||
|
@ -277,21 +280,19 @@ func (s *SpaceStore) List(ctx context.Context, id int64, opts *types.SpaceFilter
|
|||
stmt = stmt.Offset(uint64(offset(opts.Page, opts.Size)))
|
||||
|
||||
if opts.Query != "" {
|
||||
stmt = stmt.Where("space_pathName LIKE ?", fmt.Sprintf("%%%s%%", opts.Query))
|
||||
stmt = stmt.Where("space_uid LIKE ?", fmt.Sprintf("%%%s%%", opts.Query))
|
||||
}
|
||||
|
||||
switch opts.Sort {
|
||||
case enum.SpaceAttrName, enum.SpaceAttrNone:
|
||||
case enum.SpaceAttrUID, enum.SpaceAttrNone:
|
||||
// NOTE: string concatenation is safe because the
|
||||
// order attribute is an enum and is not user-defined,
|
||||
// and is therefore not subject to injection attacks.
|
||||
stmt = stmt.OrderBy("space_name COLLATE NOCASE " + opts.Order.String())
|
||||
stmt = stmt.OrderBy("space_uid COLLATE NOCASE " + opts.Order.String())
|
||||
case enum.SpaceAttrCreated:
|
||||
stmt = stmt.OrderBy("space_created " + opts.Order.String())
|
||||
case enum.SpaceAttrUpdated:
|
||||
stmt = stmt.OrderBy("space_updated " + opts.Order.String())
|
||||
case enum.SpaceAttrPathName:
|
||||
stmt = stmt.OrderBy("space_pathName COLLATE NOCASE " + opts.Order.String())
|
||||
case enum.SpaceAttrPath:
|
||||
stmt = stmt.OrderBy("space_path COLLATE NOCASE " + opts.Order.String())
|
||||
}
|
||||
|
@ -319,34 +320,33 @@ func (s *SpaceStore) ListPaths(ctx context.Context, id int64, opts *types.PathFi
|
|||
}
|
||||
|
||||
// CreatePath creates an alias for a space.
|
||||
func (s *SpaceStore) CreatePath(ctx context.Context, spaceID int64, params *types.PathParams) (*types.Path, error) {
|
||||
func (s *SpaceStore) CreatePath(ctx context.Context, id int64, params *types.PathParams) (*types.Path, error) {
|
||||
p := &types.Path{
|
||||
TargetType: enum.PathTargetTypeSpace,
|
||||
TargetID: spaceID,
|
||||
TargetID: id,
|
||||
IsAlias: true,
|
||||
|
||||
// get remaining infor from params
|
||||
// get remaining info from params
|
||||
Value: params.Path,
|
||||
CreatedBy: params.CreatedBy,
|
||||
Created: params.Created,
|
||||
Updated: params.Updated,
|
||||
}
|
||||
|
||||
return p, CreateAliasPath(ctx, s.db, p)
|
||||
return p, CreateAliasPath(ctx, s.db, p, s.pathTransformation)
|
||||
}
|
||||
|
||||
// DeletePath an alias of a space.
|
||||
func (s *SpaceStore) DeletePath(ctx context.Context, spaceID int64, pathID int64) error {
|
||||
func (s *SpaceStore) DeletePath(ctx context.Context, id int64, pathID int64) error {
|
||||
return DeletePath(ctx, s.db, pathID)
|
||||
}
|
||||
|
||||
const spaceSelectBase = `
|
||||
SELECT
|
||||
space_id
|
||||
,space_pathName
|
||||
,paths.path_value AS space_path
|
||||
,space_parentId
|
||||
,space_name
|
||||
,space_uid
|
||||
,paths.path_value AS space_path
|
||||
,space_description
|
||||
,space_isPublic
|
||||
,space_createdBy
|
||||
|
@ -364,9 +364,10 @@ const spaceSelectByID = spaceSelectBaseWithJoin + `
|
|||
WHERE space_id = $1
|
||||
`
|
||||
|
||||
const spaceSelectByPath = spaceSelectBase + `
|
||||
const spaceSelectByPathUnique = spaceSelectBase + `
|
||||
FROM paths paths1
|
||||
INNER JOIN spaces ON spaces.space_id=paths1.path_targetId AND paths1.path_targetType='space' AND paths1.path_value = $1
|
||||
INNER JOIN spaces ON spaces.space_id=paths1.path_targetId AND paths1.path_targetType='space'
|
||||
AND paths1.path_valueUnique = $1
|
||||
INNER JOIN paths ON spaces.space_id=paths.path_targetId AND paths.path_targetType='space' AND paths.path_isAlias=0
|
||||
`
|
||||
|
||||
|
@ -378,18 +379,16 @@ WHERE space_id = $1
|
|||
// TODO: do we have to worry about SQL injection for description?
|
||||
const spaceInsert = `
|
||||
INSERT INTO spaces (
|
||||
space_pathName
|
||||
,space_parentId
|
||||
,space_name
|
||||
space_parentId
|
||||
,space_uid
|
||||
,space_description
|
||||
,space_isPublic
|
||||
,space_createdBy
|
||||
,space_created
|
||||
,space_updated
|
||||
) values (
|
||||
:space_pathName
|
||||
,:space_parentId
|
||||
,:space_name
|
||||
:space_parentId
|
||||
,:space_uid
|
||||
,:space_description
|
||||
,:space_isPublic
|
||||
,:space_createdBy
|
||||
|
@ -401,17 +400,16 @@ INSERT INTO spaces (
|
|||
const spaceUpdate = `
|
||||
UPDATE spaces
|
||||
SET
|
||||
space_name = :space_name
|
||||
,space_description = :space_description
|
||||
space_description = :space_description
|
||||
,space_isPublic = :space_isPublic
|
||||
,space_updated = :space_updated
|
||||
WHERE space_id = :space_id
|
||||
`
|
||||
|
||||
const spaceUpdateNameAndParentID = `
|
||||
const spaceUpdateUIDAndParentID = `
|
||||
UPDATE spaces
|
||||
SET
|
||||
space_pathName = $1
|
||||
space_uid = $1
|
||||
,space_parentId = $2
|
||||
WHERE space_id = $3
|
||||
`
|
||||
|
|
|
@ -48,11 +48,11 @@ func (s *SpaceStoreSync) Create(ctx context.Context, space *types.Space) error {
|
|||
}
|
||||
|
||||
// Move moves an existing space.
|
||||
func (s *SpaceStoreSync) Move(ctx context.Context, principalID int64, spaceID int64, newParentID int64, newName string,
|
||||
func (s *SpaceStoreSync) Move(ctx context.Context, principalID int64, id int64, newParentID int64, newName string,
|
||||
keepAsAlias bool) (*types.Space, error) {
|
||||
mutex.RLock()
|
||||
defer mutex.RUnlock()
|
||||
return s.base.Move(ctx, principalID, spaceID, newParentID, newName, keepAsAlias)
|
||||
return s.base.Move(ctx, principalID, id, newParentID, newName, keepAsAlias)
|
||||
}
|
||||
|
||||
// Update the space details.
|
||||
|
@ -94,15 +94,15 @@ func (s *SpaceStoreSync) ListPaths(ctx context.Context, id int64, opts *types.Pa
|
|||
}
|
||||
|
||||
// CreatePath a path for a space.
|
||||
func (s *SpaceStoreSync) CreatePath(ctx context.Context, spaceID int64, params *types.PathParams) (*types.Path, error) {
|
||||
func (s *SpaceStoreSync) CreatePath(ctx context.Context, id int64, params *types.PathParams) (*types.Path, error) {
|
||||
mutex.RLock()
|
||||
defer mutex.RUnlock()
|
||||
return s.base.CreatePath(ctx, spaceID, params)
|
||||
return s.base.CreatePath(ctx, id, params)
|
||||
}
|
||||
|
||||
// DeletePath a path of a space.
|
||||
func (s *SpaceStoreSync) DeletePath(ctx context.Context, spaceID int64, pathID int64) error {
|
||||
func (s *SpaceStoreSync) DeletePath(ctx context.Context, id int64, pathID int64) error {
|
||||
mutex.RLock()
|
||||
defer mutex.RUnlock()
|
||||
return s.base.DeletePath(ctx, spaceID, pathID)
|
||||
return s.base.DeletePath(ctx, id, pathID)
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
[
|
||||
{
|
||||
"id": 1,
|
||||
"pathName": "repo1",
|
||||
"spaceId": 1,
|
||||
"name": "Repository 1",
|
||||
"uid": "repo1",
|
||||
"parentId": 1,
|
||||
"description": "Some repository.",
|
||||
"isPublic": true,
|
||||
"createdBy": 1,
|
||||
|
@ -17,9 +16,8 @@
|
|||
},
|
||||
{
|
||||
"id": 2,
|
||||
"pathName": "repo2",
|
||||
"spaceId": 2,
|
||||
"name": "Repository 2",
|
||||
"uid": "repo2",
|
||||
"parentId": 2,
|
||||
"description": "Some other repository.",
|
||||
"isPublic": true,
|
||||
"createdBy": 1,
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
[
|
||||
{
|
||||
"id": 1,
|
||||
"pathName": "space1",
|
||||
"uid": "space1",
|
||||
"parentId": 0,
|
||||
"name": "Space 1",
|
||||
"description": "Some space.",
|
||||
"isPublic": true,
|
||||
"createdBy": 1,
|
||||
|
@ -12,9 +11,8 @@
|
|||
},
|
||||
{
|
||||
"id": 2,
|
||||
"pathName": "space2",
|
||||
"uid": "space2",
|
||||
"parentId": 1,
|
||||
"name": "Space 2",
|
||||
"description": "Some subspace.",
|
||||
"isPublic": true,
|
||||
"createdBy": 1,
|
||||
|
|
|
@ -35,6 +35,15 @@ func (s *TokenStore) Find(ctx context.Context, id int64) (*types.Token, error) {
|
|||
return dst, nil
|
||||
}
|
||||
|
||||
// Find finds the token by principalId and tokenUID.
|
||||
func (s *TokenStore) FindByUID(ctx context.Context, principalID int64, tokenUID string) (*types.Token, error) {
|
||||
dst := new(types.Token)
|
||||
if err := s.db.GetContext(ctx, dst, TokenSelectByPrincipalIDAndUID, principalID, tokenUID); err != nil {
|
||||
return nil, processSQLErrorf(err, "Select query failed")
|
||||
}
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
// Create saves the token details.
|
||||
func (s *TokenStore) Create(ctx context.Context, token *types.Token) error {
|
||||
query, arg, err := s.db.BindNamed(tokenInsert, token)
|
||||
|
@ -96,7 +105,7 @@ const tokenSelectBase = `
|
|||
SELECT
|
||||
token_id
|
||||
,token_type
|
||||
,token_name
|
||||
,token_uid
|
||||
,token_principalId
|
||||
,token_expiresAt
|
||||
,token_grants
|
||||
|
@ -106,18 +115,22 @@ FROM tokens
|
|||
` //#nosec G101
|
||||
|
||||
const tokenSelectForPrincipalIDOfType = tokenSelectBase + `
|
||||
WHERE token_principalId = $1 and token_type = $2
|
||||
WHERE token_principalId = $1 AND token_type = $2
|
||||
ORDER BY token_issuedAt DESC
|
||||
` //#nosec G101
|
||||
|
||||
const tokenCountForPrincipalIDOfType = `
|
||||
SELECT count(*)
|
||||
FROM tokens
|
||||
WHERE token_principalId = $1 and token_type = $2
|
||||
WHERE token_principalId = $1 AND token_type = $2
|
||||
` //#nosec G101
|
||||
|
||||
const TokenSelectByID = tokenSelectBase + `
|
||||
WHERE token_id = $2
|
||||
WHERE token_id = $1
|
||||
`
|
||||
|
||||
const TokenSelectByPrincipalIDAndUID = tokenSelectBase + `
|
||||
WHERE token_principalId = $1 AND token_uid = $2
|
||||
`
|
||||
|
||||
const tokenDelete = `
|
||||
|
@ -133,7 +146,7 @@ WHERE token_principalId = $1
|
|||
const tokenInsert = `
|
||||
INSERT INTO tokens (
|
||||
token_type
|
||||
,token_name
|
||||
,token_uid
|
||||
,token_principalId
|
||||
,token_expiresAt
|
||||
,token_grants
|
||||
|
@ -141,7 +154,7 @@ INSERT INTO tokens (
|
|||
,token_createdBy
|
||||
) values (
|
||||
:token_type
|
||||
,:token_name
|
||||
,:token_uid
|
||||
,:token_principalId
|
||||
,:token_expiresAt
|
||||
,:token_grants
|
||||
|
|
|
@ -32,6 +32,13 @@ func (s *TokenStoreSync) Find(ctx context.Context, id int64) (*types.Token, erro
|
|||
return s.base.Find(ctx, id)
|
||||
}
|
||||
|
||||
// Find finds the token by principalId and tokenUID.
|
||||
func (s *TokenStoreSync) FindByUID(ctx context.Context, principalID int64, tokenUID string) (*types.Token, error) {
|
||||
mutex.RLock()
|
||||
defer mutex.RUnlock()
|
||||
return s.base.FindByUID(ctx, principalID, tokenUID)
|
||||
}
|
||||
|
||||
// Create saves the token details.
|
||||
func (s *TokenStoreSync) Create(ctx context.Context, token *types.Token) error {
|
||||
mutex.Lock()
|
||||
|
|
|
@ -7,107 +7,84 @@ package database
|
|||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/harness/gitness/internal/store"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
var _ store.UserStore = (*UserStore)(nil)
|
||||
|
||||
// user is a DB representation of a user principal.
|
||||
// It is required to allow storing transformed UIDs used for uniquness constraints and searching.
|
||||
type user struct {
|
||||
types.User
|
||||
UIDUnique string `db:"principal_uidUnique"`
|
||||
}
|
||||
|
||||
// NewUserStore returns a new UserStore.
|
||||
func NewUserStore(db *sqlx.DB) *UserStore {
|
||||
return &UserStore{db}
|
||||
func NewUserStore(db *sqlx.DB, uidTransformation store.PrincipalUIDTransformation) *UserStore {
|
||||
return &UserStore{
|
||||
db: db,
|
||||
uidTransformation: uidTransformation,
|
||||
}
|
||||
}
|
||||
|
||||
// UserStore implements a UserStore backed by a relational
|
||||
// database.
|
||||
type UserStore struct {
|
||||
db *sqlx.DB
|
||||
uidTransformation store.PrincipalUIDTransformation
|
||||
}
|
||||
|
||||
// Find finds the user by id.
|
||||
func (s *UserStore) Find(ctx context.Context, id int64) (*types.User, error) {
|
||||
dst := new(types.User)
|
||||
dst := new(user)
|
||||
if err := s.db.GetContext(ctx, dst, userSelectID, id); err != nil {
|
||||
return nil, processSQLErrorf(err, "Select by id query failed")
|
||||
}
|
||||
return dst, nil
|
||||
return s.mapDBUser(dst), nil
|
||||
}
|
||||
|
||||
// FindUID finds the user by uid.
|
||||
func (s *UserStore) FindUID(ctx context.Context, uid string) (*types.User, error) {
|
||||
dst := new(types.User)
|
||||
if err := s.db.GetContext(ctx, dst, userSelectUID, uid); err != nil {
|
||||
// map the UID to unique UID before searching!
|
||||
uidUnique, err := s.uidTransformation(enum.PrincipalTypeUser, uid)
|
||||
if err != nil {
|
||||
// in case we fail to transform, return a not found (as it can't exist in the first place)
|
||||
log.Ctx(ctx).Debug().Msgf("failed to transform uid '%s': %s", uid, err.Error())
|
||||
return nil, store.ErrResourceNotFound
|
||||
}
|
||||
|
||||
dst := new(user)
|
||||
if err = s.db.GetContext(ctx, dst, userSelectUIDUnique, uidUnique); err != nil {
|
||||
return nil, processSQLErrorf(err, "Select by uid query failed")
|
||||
}
|
||||
return dst, nil
|
||||
return s.mapDBUser(dst), nil
|
||||
}
|
||||
|
||||
// FindEmail finds the user by email.
|
||||
func (s *UserStore) FindEmail(ctx context.Context, email string) (*types.User, error) {
|
||||
dst := new(types.User)
|
||||
dst := new(user)
|
||||
if err := s.db.GetContext(ctx, dst, userSelectEmail, email); err != nil {
|
||||
return nil, processSQLErrorf(err, "Select by email query failed")
|
||||
}
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
// List returns a list of users.
|
||||
func (s *UserStore) List(ctx context.Context, opts *types.UserFilter) ([]*types.User, error) {
|
||||
dst := []*types.User{}
|
||||
|
||||
// if the user does not provide any customer filter
|
||||
// or sorting we use the default select statement.
|
||||
if opts.Sort == enum.UserAttrNone {
|
||||
err := s.db.SelectContext(ctx, &dst, userSelect, limit(opts.Size), offset(opts.Page, opts.Size))
|
||||
if err != nil {
|
||||
return nil, processSQLErrorf(err, "Failed executing default list query")
|
||||
}
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
// else we construct the sql statement.
|
||||
stmt := builder.Select("*").From("users")
|
||||
stmt = stmt.Limit(uint64(limit(opts.Size)))
|
||||
stmt = stmt.Offset(uint64(offset(opts.Page, opts.Size)))
|
||||
|
||||
switch opts.Sort {
|
||||
case enum.UserAttrName, enum.UserAttrNone:
|
||||
// NOTE: string concatenation is safe because the
|
||||
// order attribute is an enum and is not user-defined,
|
||||
// and is therefore not subject to injection attacks.
|
||||
stmt = stmt.OrderBy("principal_name " + opts.Order.String())
|
||||
case enum.UserAttrCreated:
|
||||
stmt = stmt.OrderBy("principal_created " + opts.Order.String())
|
||||
case enum.UserAttrUpdated:
|
||||
stmt = stmt.OrderBy("principal_updated " + opts.Order.String())
|
||||
case enum.UserAttrEmail:
|
||||
stmt = stmt.OrderBy("principal_user_email " + opts.Order.String())
|
||||
case enum.UserAttrUID:
|
||||
stmt = stmt.OrderBy("principal_uid " + opts.Order.String())
|
||||
case enum.UserAttrAdmin:
|
||||
stmt = stmt.OrderBy("principal_admin " + opts.Order.String())
|
||||
}
|
||||
|
||||
sql, _, err := stmt.ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Failed to convert query to sql")
|
||||
}
|
||||
|
||||
if err = s.db.SelectContext(ctx, &dst, sql); err != nil {
|
||||
return nil, processSQLErrorf(err, "Failed executing custom list query")
|
||||
}
|
||||
|
||||
return dst, nil
|
||||
return s.mapDBUser(dst), nil
|
||||
}
|
||||
|
||||
// Create saves the user details.
|
||||
func (s *UserStore) Create(ctx context.Context, user *types.User) error {
|
||||
query, arg, err := s.db.BindNamed(userInsert, user)
|
||||
dbUser, err := s.mapToDBUser(user)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to map db user: %w", err)
|
||||
}
|
||||
|
||||
query, arg, err := s.db.BindNamed(userInsert, dbUser)
|
||||
if err != nil {
|
||||
return processSQLErrorf(err, "Failed to bind user object")
|
||||
}
|
||||
|
@ -121,7 +98,12 @@ func (s *UserStore) Create(ctx context.Context, user *types.User) error {
|
|||
|
||||
// Update updates the user details.
|
||||
func (s *UserStore) Update(ctx context.Context, user *types.User) error {
|
||||
query, arg, err := s.db.BindNamed(userUpdate, user)
|
||||
dbUser, err := s.mapToDBUser(user)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to map db user: %w", err)
|
||||
}
|
||||
|
||||
query, arg, err := s.db.BindNamed(userUpdate, dbUser)
|
||||
if err != nil {
|
||||
return processSQLErrorf(err, "Failed to bind user object")
|
||||
}
|
||||
|
@ -149,6 +131,55 @@ func (s *UserStore) Delete(ctx context.Context, id int64) error {
|
|||
return tx.Commit()
|
||||
}
|
||||
|
||||
// List returns a list of users.
|
||||
func (s *UserStore) List(ctx context.Context, opts *types.UserFilter) ([]*types.User, error) {
|
||||
dst := []*user{}
|
||||
|
||||
// if the user does not provide any customer filter
|
||||
// or sorting we use the default select statement.
|
||||
if opts.Sort == enum.UserAttrNone {
|
||||
err := s.db.SelectContext(ctx, &dst, userSelect, limit(opts.Size), offset(opts.Page, opts.Size))
|
||||
if err != nil {
|
||||
return nil, processSQLErrorf(err, "Failed executing default list query")
|
||||
}
|
||||
return s.mapDBUsers(dst), nil
|
||||
}
|
||||
|
||||
// else we construct the sql statement.
|
||||
stmt := builder.Select("*").From("users")
|
||||
stmt = stmt.Limit(uint64(limit(opts.Size)))
|
||||
stmt = stmt.Offset(uint64(offset(opts.Page, opts.Size)))
|
||||
|
||||
switch opts.Sort {
|
||||
case enum.UserAttrName, enum.UserAttrNone:
|
||||
// NOTE: string concatenation is safe because the
|
||||
// order attribute is an enum and is not user-defined,
|
||||
// and is therefore not subject to injection attacks.
|
||||
stmt = stmt.OrderBy("principal_displayName " + opts.Order.String())
|
||||
case enum.UserAttrCreated:
|
||||
stmt = stmt.OrderBy("principal_created " + opts.Order.String())
|
||||
case enum.UserAttrUpdated:
|
||||
stmt = stmt.OrderBy("principal_updated " + opts.Order.String())
|
||||
case enum.UserAttrEmail:
|
||||
stmt = stmt.OrderBy("principal_email " + opts.Order.String())
|
||||
case enum.UserAttrUID:
|
||||
stmt = stmt.OrderBy("principal_uid " + opts.Order.String())
|
||||
case enum.UserAttrAdmin:
|
||||
stmt = stmt.OrderBy("principal_admin " + opts.Order.String())
|
||||
}
|
||||
|
||||
sql, _, err := stmt.ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Failed to convert query to sql")
|
||||
}
|
||||
|
||||
if err = s.db.SelectContext(ctx, &dst, sql); err != nil {
|
||||
return nil, processSQLErrorf(err, "Failed executing custom list query")
|
||||
}
|
||||
|
||||
return s.mapDBUsers(dst), nil
|
||||
}
|
||||
|
||||
// Count returns a count of users.
|
||||
func (s *UserStore) Count(ctx context.Context) (int64, error) {
|
||||
var count int64
|
||||
|
@ -159,6 +190,36 @@ func (s *UserStore) Count(ctx context.Context) (int64, error) {
|
|||
return count, nil
|
||||
}
|
||||
|
||||
func (s *UserStore) mapDBUser(dbUser *user) *types.User {
|
||||
return &dbUser.User
|
||||
}
|
||||
|
||||
func (s *UserStore) mapDBUsers(dbUsers []*user) []*types.User {
|
||||
res := make([]*types.User, len(dbUsers))
|
||||
for i := range dbUsers {
|
||||
res[i] = s.mapDBUser(dbUsers[i])
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *UserStore) mapToDBUser(usr *types.User) (*user, error) {
|
||||
// user comes from outside.
|
||||
if usr == nil {
|
||||
return nil, fmt.Errorf("user is nil")
|
||||
}
|
||||
|
||||
uidUnique, err := s.uidTransformation(enum.PrincipalTypeUser, usr.UID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to transform user UID: %w", err)
|
||||
}
|
||||
dbUser := &user{
|
||||
User: *usr,
|
||||
UIDUnique: uidUnique,
|
||||
}
|
||||
|
||||
return dbUser, nil
|
||||
}
|
||||
|
||||
const userCount = `
|
||||
SELECT count(*)
|
||||
FROM principals
|
||||
|
@ -169,20 +230,21 @@ const userBase = `
|
|||
SELECT
|
||||
principal_id
|
||||
,principal_uid
|
||||
,principal_name
|
||||
,principal_uidUnique
|
||||
,principal_email
|
||||
,principal_displayName
|
||||
,principal_admin
|
||||
,principal_blocked
|
||||
,principal_salt
|
||||
,principal_created
|
||||
,principal_updated
|
||||
,principal_user_email
|
||||
,principal_user_password
|
||||
FROM principals
|
||||
`
|
||||
|
||||
const userSelect = userBase + `
|
||||
WHERE principal_type = "user"
|
||||
ORDER BY principal_name ASC
|
||||
ORDER BY principal_uid ASC
|
||||
LIMIT $1 OFFSET $2
|
||||
`
|
||||
|
||||
|
@ -190,12 +252,12 @@ const userSelectID = userBase + `
|
|||
WHERE principal_type = "user" AND principal_id = $1
|
||||
`
|
||||
|
||||
const userSelectUID = userBase + `
|
||||
WHERE principal_type = "user" AND principal_uid = $1
|
||||
const userSelectUIDUnique = userBase + `
|
||||
WHERE principal_type = "user" AND principal_uidUnique = $1
|
||||
`
|
||||
|
||||
const userSelectEmail = userBase + `
|
||||
WHERE principal_type = "user" AND principal_user_email = $1
|
||||
WHERE principal_type = "user" AND principal_email = $1
|
||||
`
|
||||
|
||||
const userDelete = `
|
||||
|
@ -207,24 +269,26 @@ const userInsert = `
|
|||
INSERT INTO principals (
|
||||
principal_type
|
||||
,principal_uid
|
||||
,principal_name
|
||||
,principal_uidUnique
|
||||
,principal_email
|
||||
,principal_displayName
|
||||
,principal_admin
|
||||
,principal_blocked
|
||||
,principal_salt
|
||||
,principal_created
|
||||
,principal_updated
|
||||
,principal_user_email
|
||||
,principal_user_password
|
||||
) values (
|
||||
"user"
|
||||
,:principal_uid
|
||||
,:principal_name
|
||||
,:principal_uidUnique
|
||||
,:principal_email
|
||||
,:principal_displayName
|
||||
,:principal_admin
|
||||
,:principal_blocked
|
||||
,:principal_salt
|
||||
,:principal_created
|
||||
,:principal_updated
|
||||
,:principal_user_email
|
||||
,:principal_user_password
|
||||
) RETURNING principal_id
|
||||
`
|
||||
|
@ -232,12 +296,12 @@ principal_type
|
|||
const userUpdate = `
|
||||
UPDATE principals
|
||||
SET
|
||||
principal_name = :principal_name
|
||||
principal_email = :principal_email
|
||||
,principal_displayName = :principal_displayName
|
||||
,principal_admin = :principal_admin
|
||||
,principal_blocked = :principal_blocked
|
||||
,principal_salt = :principal_salt
|
||||
,principal_updated = :principal_updated
|
||||
,principal_user_email = :principal_user_email
|
||||
,principal_user_password = :principal_user_password
|
||||
WHERE principal_type = "user" AND principal_id = :principal_id
|
||||
`
|
||||
|
|
|
@ -39,7 +39,7 @@ func TestUser(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
userStoreSync := NewUserStoreSync(NewUserStore(db))
|
||||
userStoreSync := NewUserStoreSync(NewUserStore(db, store.ToLowerPrincipalUIDTransformation))
|
||||
t.Run("create", testUserCreate(userStoreSync))
|
||||
t.Run("duplicate", testUserDuplicate(userStoreSync))
|
||||
t.Run("count", testUserCount(userStoreSync))
|
||||
|
|
|
@ -39,61 +39,62 @@ func ProvideDatabase(ctx context.Context, config *types.Config) (*sqlx.DB, error
|
|||
}
|
||||
|
||||
// ProvideUserStore provides a user store.
|
||||
func ProvideUserStore(db *sqlx.DB) store.UserStore {
|
||||
func ProvideUserStore(db *sqlx.DB, uidTransformation store.PrincipalUIDTransformation) store.UserStore {
|
||||
switch db.DriverName() {
|
||||
case postgres:
|
||||
return NewUserStore(db)
|
||||
return NewUserStore(db, uidTransformation)
|
||||
default:
|
||||
return NewUserStoreSync(
|
||||
NewUserStore(db),
|
||||
NewUserStore(db, uidTransformation),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ProvideServiceAccountStore provides a service account store.
|
||||
func ProvideServiceAccountStore(db *sqlx.DB) store.ServiceAccountStore {
|
||||
func ProvideServiceAccountStore(db *sqlx.DB,
|
||||
uidTransformation store.PrincipalUIDTransformation) store.ServiceAccountStore {
|
||||
switch db.DriverName() {
|
||||
case postgres:
|
||||
return NewServiceAccountStore(db)
|
||||
return NewServiceAccountStore(db, uidTransformation)
|
||||
default:
|
||||
return NewServiceAccountStoreSync(
|
||||
NewServiceAccountStore(db),
|
||||
NewServiceAccountStore(db, uidTransformation),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ProvideServiceStore provides a service store.
|
||||
func ProvideServiceStore(db *sqlx.DB) store.ServiceStore {
|
||||
func ProvideServiceStore(db *sqlx.DB, uidTransformation store.PrincipalUIDTransformation) store.ServiceStore {
|
||||
switch db.DriverName() {
|
||||
case postgres:
|
||||
return NewServiceStore(db)
|
||||
return NewServiceStore(db, uidTransformation)
|
||||
default:
|
||||
return NewServiceStoreSync(
|
||||
NewServiceStore(db),
|
||||
NewServiceStore(db, uidTransformation),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ProvideSpaceStore provides a space store.
|
||||
func ProvideSpaceStore(db *sqlx.DB) store.SpaceStore {
|
||||
func ProvideSpaceStore(db *sqlx.DB, pathTransformation store.PathTransformation) store.SpaceStore {
|
||||
switch db.DriverName() {
|
||||
case postgres:
|
||||
return NewSpaceStore(db)
|
||||
return NewSpaceStore(db, pathTransformation)
|
||||
default:
|
||||
return NewSpaceStoreSync(
|
||||
NewSpaceStore(db),
|
||||
NewSpaceStore(db, pathTransformation),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ProvideRepoStore provides a repo store.
|
||||
func ProvideRepoStore(db *sqlx.DB) store.RepoStore {
|
||||
func ProvideRepoStore(db *sqlx.DB, pathTransformation store.PathTransformation) store.RepoStore {
|
||||
switch db.DriverName() {
|
||||
case postgres:
|
||||
return NewRepoStore(db)
|
||||
return NewRepoStore(db, pathTransformation)
|
||||
default:
|
||||
return NewRepoStoreSync(
|
||||
NewRepoStore(db),
|
||||
NewRepoStore(db, pathTransformation),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -100,7 +100,7 @@ type (
|
|||
Create(ctx context.Context, space *types.Space) error
|
||||
|
||||
// Move moves an existing space.
|
||||
Move(ctx context.Context, principalID int64, spaceID int64, newParentID int64, newName string,
|
||||
Move(ctx context.Context, principalID int64, id int64, newParentID int64, newName string,
|
||||
keepAsAlias bool) (*types.Space, error)
|
||||
|
||||
// Update updates the space details.
|
||||
|
@ -122,10 +122,10 @@ type (
|
|||
ListPaths(ctx context.Context, id int64, opts *types.PathFilter) ([]*types.Path, error)
|
||||
|
||||
// CreatePath create an alias for a space
|
||||
CreatePath(ctx context.Context, spaceID int64, params *types.PathParams) (*types.Path, error)
|
||||
CreatePath(ctx context.Context, id int64, params *types.PathParams) (*types.Path, error)
|
||||
|
||||
// DeletePath delete an alias of a space
|
||||
DeletePath(ctx context.Context, spaceID int64, pathID int64) error
|
||||
DeletePath(ctx context.Context, id int64, pathID int64) error
|
||||
}
|
||||
|
||||
// RepoStore defines the repository data storage.
|
||||
|
@ -140,7 +140,7 @@ type (
|
|||
Create(ctx context.Context, repo *types.Repository) error
|
||||
|
||||
// Move moves an existing repo.
|
||||
Move(ctx context.Context, principalID int64, repoID int64, newSpaceID int64, newName string,
|
||||
Move(ctx context.Context, principalID int64, repoID int64, newParentID int64, newName string,
|
||||
keepAsAlias bool) (*types.Repository, error)
|
||||
|
||||
// Update the repo details.
|
||||
|
@ -150,10 +150,10 @@ type (
|
|||
Delete(ctx context.Context, id int64) error
|
||||
|
||||
// Count of repos in a space.
|
||||
Count(ctx context.Context, spaceID int64, opts *types.RepoFilter) (int64, error)
|
||||
Count(ctx context.Context, parentID int64, opts *types.RepoFilter) (int64, error)
|
||||
|
||||
// List returns a list of repos in a space.
|
||||
List(ctx context.Context, spaceID int64, opts *types.RepoFilter) ([]*types.Repository, error)
|
||||
List(ctx context.Context, parentID int64, opts *types.RepoFilter) ([]*types.Repository, error)
|
||||
|
||||
// CountPaths returns a count of all paths of a repo.
|
||||
CountPaths(ctx context.Context, id int64, opts *types.PathFilter) (int64, error)
|
||||
|
@ -173,6 +173,9 @@ type (
|
|||
// Find finds the token by id
|
||||
Find(ctx context.Context, id int64) (*types.Token, error)
|
||||
|
||||
// Find finds the token by principalId and tokenUID
|
||||
FindByUID(ctx context.Context, principalID int64, tokenUID string) (*types.Token, error)
|
||||
|
||||
// Create saves the token details.
|
||||
Create(ctx context.Context, token *types.Token) error
|
||||
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
// 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 store
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/harness/gitness/types/enum"
|
||||
)
|
||||
|
||||
// PrincipalUIDTransformation transforms a principalUID to a value that should be duplicate free.
|
||||
// This allows us to simply switch between principalUIDs being case sensitive, insensitive or anything inbetween.
|
||||
type PrincipalUIDTransformation func(principalType enum.PrincipalType, uid string) (string, error)
|
||||
|
||||
func ToLowerPrincipalUIDTransformation(principalType enum.PrincipalType, uid string) (string, error) {
|
||||
return strings.ToLower(uid), nil
|
||||
}
|
||||
|
||||
// PathTransformation transforms a path to a value that should be duplicate free.
|
||||
// This allows us to simply switch between paths being case sensitive, insensitive or anything inbetween.
|
||||
type PathTransformation func(string) (string, error)
|
||||
|
||||
func ToLowerPathTransformation(original string) (string, error) {
|
||||
return strings.ToLower(original), nil
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
// 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 store
|
||||
|
||||
import (
|
||||
"github.com/google/wire"
|
||||
)
|
||||
|
||||
// WireSet provides a wire set for this package.
|
||||
var WireSet = wire.NewSet(
|
||||
ProvidePathTransformation,
|
||||
ProvidePrincipalUIDTransformation,
|
||||
)
|
||||
|
||||
func ProvidePathTransformation() PathTransformation {
|
||||
return ToLowerPathTransformation
|
||||
}
|
||||
|
||||
func ProvidePrincipalUIDTransformation() PrincipalUIDTransformation {
|
||||
return ToLowerPrincipalUIDTransformation
|
||||
}
|
|
@ -20,7 +20,7 @@ const (
|
|||
)
|
||||
|
||||
func CreateUserSession(ctx context.Context, tokenStore store.TokenStore,
|
||||
user *types.User, name string) (*types.Token, string, error) {
|
||||
user *types.User, uid string) (*types.Token, string, error) {
|
||||
principal := types.PrincipalFromUser(user)
|
||||
return Create(
|
||||
ctx,
|
||||
|
@ -28,7 +28,7 @@ func CreateUserSession(ctx context.Context, tokenStore store.TokenStore,
|
|||
enum.TokenTypeSession,
|
||||
principal,
|
||||
principal,
|
||||
name,
|
||||
uid,
|
||||
userTokenLifeTime,
|
||||
enum.AccessGrantAll,
|
||||
)
|
||||
|
@ -36,14 +36,14 @@ func CreateUserSession(ctx context.Context, tokenStore store.TokenStore,
|
|||
|
||||
func CreatePAT(ctx context.Context, tokenStore store.TokenStore,
|
||||
createdBy *types.Principal, createdFor *types.User,
|
||||
name string, lifetime time.Duration, grants enum.AccessGrant) (*types.Token, string, error) {
|
||||
uid string, lifetime time.Duration, grants enum.AccessGrant) (*types.Token, string, error) {
|
||||
return Create(
|
||||
ctx,
|
||||
tokenStore,
|
||||
enum.TokenTypePAT,
|
||||
createdBy,
|
||||
types.PrincipalFromUser(createdFor),
|
||||
name,
|
||||
uid,
|
||||
lifetime,
|
||||
grants,
|
||||
)
|
||||
|
@ -51,14 +51,14 @@ func CreatePAT(ctx context.Context, tokenStore store.TokenStore,
|
|||
|
||||
func CreateSAT(ctx context.Context, tokenStore store.TokenStore,
|
||||
createdBy *types.Principal, createdFor *types.ServiceAccount,
|
||||
name string, lifetime time.Duration, grants enum.AccessGrant) (*types.Token, string, error) {
|
||||
uid string, lifetime time.Duration, grants enum.AccessGrant) (*types.Token, string, error) {
|
||||
return Create(
|
||||
ctx,
|
||||
tokenStore,
|
||||
enum.TokenTypeSAT,
|
||||
createdBy,
|
||||
types.PrincipalFromServiceAccount(createdFor),
|
||||
name,
|
||||
uid,
|
||||
lifetime,
|
||||
grants,
|
||||
)
|
||||
|
@ -81,14 +81,14 @@ func CreateOAuth(ctx context.Context, tokenStore store.TokenStore,
|
|||
|
||||
func Create(ctx context.Context, tokenStore store.TokenStore,
|
||||
tokenType enum.TokenType, createdBy *types.Principal, createdFor *types.Principal,
|
||||
name string, lifetime time.Duration, grants enum.AccessGrant) (*types.Token, string, error) {
|
||||
uid string, lifetime time.Duration, grants enum.AccessGrant) (*types.Token, string, error) {
|
||||
issuedAt := time.Now()
|
||||
expiresAt := issuedAt.Add(lifetime)
|
||||
|
||||
// create db entry first so we get the id.
|
||||
token := types.Token{
|
||||
Type: tokenType,
|
||||
Name: name,
|
||||
UID: uid,
|
||||
PrincipalID: createdFor.ID,
|
||||
IssuedAt: issuedAt.UnixMilli(),
|
||||
ExpiresAt: expiresAt.UnixMilli(),
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
user "github.com/harness/gitness/internal/api/controller/user"
|
||||
types "github.com/harness/gitness/types"
|
||||
)
|
||||
|
||||
|
@ -110,6 +111,21 @@ func (mr *MockClientMockRecorder) UserCreate(arg0, arg1 interface{}) *gomock.Cal
|
|||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UserCreate", reflect.TypeOf((*MockClient)(nil).UserCreate), arg0, arg1)
|
||||
}
|
||||
|
||||
// UserCreatePAT mocks base method.
|
||||
func (m *MockClient) UserCreatePAT(arg0 context.Context, arg1 user.CreateTokenInput) (*types.TokenResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UserCreatePAT", arg0, arg1)
|
||||
ret0, _ := ret[0].(*types.TokenResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// UserCreatePAT indicates an expected call of UserCreatePAT.
|
||||
func (mr *MockClientMockRecorder) UserCreatePAT(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UserCreatePAT", reflect.TypeOf((*MockClient)(nil).UserCreatePAT), arg0, arg1)
|
||||
}
|
||||
|
||||
// UserDelete mocks base method.
|
||||
func (m *MockClient) UserDelete(arg0 context.Context, arg1 string) error {
|
||||
m.ctrl.T.Helper()
|
||||
|
|
|
@ -7,74 +7,63 @@ package check
|
|||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
minPathNameLength = 1
|
||||
maxPathNameLength = 64
|
||||
pathNameRegex = "^[a-z][a-z0-9\\-\\_]*$"
|
||||
|
||||
minNameLength = 1
|
||||
maxNameLength = 256
|
||||
nameRegex = "^[a-zA-Z][a-zA-Z0-9\\-\\_ ]*$"
|
||||
minDisplayNameLength = 1
|
||||
maxDisplayNameLength = 256
|
||||
|
||||
minUIDLength = 2
|
||||
maxUIDLength = 64
|
||||
uidRegex = "^[a-z][a-z0-9\\-\\_]*$"
|
||||
uidRegex = "^[a-zA-Z_][a-zA-Z0-9-_]*$"
|
||||
|
||||
minEmailLength = 1
|
||||
maxEmailLength = 250
|
||||
)
|
||||
|
||||
var (
|
||||
ErrPathNameLength = &ValidationError{
|
||||
fmt.Sprintf("Path name has to be between %d and %d in length.", minPathNameLength, maxPathNameLength),
|
||||
}
|
||||
ErrPathNameRegex = &ValidationError{"Path name has to start with a letter and only contain the following [a-z0-9-_]."}
|
||||
|
||||
ErrNameLength = &ValidationError{
|
||||
fmt.Sprintf("Name has to be between %d and %d in length.",
|
||||
minNameLength, maxNameLength),
|
||||
}
|
||||
ErrNameRegex = &ValidationError{
|
||||
"Name has to start with a letter and only contain the following [a-zA-Z0-9-_ ].",
|
||||
ErrDisplayNameLength = &ValidationError{
|
||||
fmt.Sprintf("DisplayName has to be between %d and %d in length.", minDisplayNameLength, maxDisplayNameLength),
|
||||
}
|
||||
ErrDisplayNameContainsInvalidASCII = &ValidationError{"DisplayName has to consist of valid ASCII characters."}
|
||||
|
||||
ErrUIDLength = &ValidationError{
|
||||
fmt.Sprintf("UID has to be between %d and %d in length.",
|
||||
minUIDLength, maxUIDLength),
|
||||
}
|
||||
ErrUIDRegex = &ValidationError{
|
||||
"UID has to start with a letter and only contain the following [a-z0-9-_].",
|
||||
"UID has to start with a letter (or _) and only contain the following characters [a-zA-Z0-9-_].",
|
||||
}
|
||||
|
||||
ErrEmailLen = &ValidationError{
|
||||
fmt.Sprintf("Email address has to be within %d and %d characters", minEmailLength, maxEmailLength),
|
||||
}
|
||||
)
|
||||
|
||||
// PathName checks the provided name and returns an error in it isn't valid.
|
||||
func PathName(pathName string) error {
|
||||
l := len(pathName)
|
||||
if l < minPathNameLength || l > maxPathNameLength {
|
||||
return ErrPathNameLength
|
||||
// DisplayName checks the provided display name and returns an error if it isn't valid.
|
||||
func DisplayName(displayName string) error {
|
||||
l := len(displayName)
|
||||
if l < minDisplayNameLength || l > maxDisplayNameLength {
|
||||
return ErrDisplayNameLength
|
||||
}
|
||||
|
||||
if ok, _ := regexp.Match(pathNameRegex, []byte(pathName)); !ok {
|
||||
return ErrPathNameRegex
|
||||
// created sanitized string restricted to ASCII characters (without control characters).
|
||||
sanitizedString := strings.Map(func(r rune) rune {
|
||||
if r < 32 || r == 127 || r > 255 {
|
||||
return -1
|
||||
}
|
||||
return r
|
||||
}, displayName)
|
||||
|
||||
if len(sanitizedString) != len(displayName) {
|
||||
return ErrDisplayNameContainsInvalidASCII
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Name checks the provided name and returns an error in it isn't valid.
|
||||
func Name(name string) error {
|
||||
l := len(name)
|
||||
if l < minNameLength || l > maxNameLength {
|
||||
return ErrNameLength
|
||||
}
|
||||
|
||||
if ok, _ := regexp.Match(nameRegex, []byte(name)); !ok {
|
||||
return ErrNameRegex
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UID checks the provided uid and returns an error in it isn't valid.
|
||||
// UID checks the provided uid and returns an error if it isn't valid.
|
||||
func UID(uid string) error {
|
||||
l := len(uid)
|
||||
if l < minUIDLength || l > maxUIDLength {
|
||||
|
@ -87,3 +76,15 @@ func UID(uid string) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Email checks the provided email and returns an error if it isn't valid.
|
||||
func Email(email string) error {
|
||||
l := len(email)
|
||||
if l < minEmailLength || l > maxEmailLength {
|
||||
return ErrEmailLen
|
||||
}
|
||||
|
||||
// TODO: add better email validation.
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
// 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
|
||||
}
|
|
@ -13,7 +13,6 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
minPathSegments = 1
|
||||
maxPathSegmentsForSpace = 9
|
||||
maxPathSegments = 10
|
||||
)
|
||||
|
@ -22,9 +21,9 @@ var (
|
|||
ErrPathEmpty = &ValidationError{
|
||||
"Path can't be empty.",
|
||||
}
|
||||
ErrPathInvalidSize = &ValidationError{
|
||||
fmt.Sprintf("A path has to be between %d and %d segments long (%d for spaces).",
|
||||
minPathSegments, maxPathSegments, maxPathSegmentsForSpace),
|
||||
ErrPathInvalidDepth = &ValidationError{
|
||||
fmt.Sprintf("A path can have at most %d segments (%d for spaces).",
|
||||
maxPathSegments, maxPathSegmentsForSpace),
|
||||
}
|
||||
ErrEmptyPathSegment = &ValidationError{
|
||||
"Empty segments are not allowed.",
|
||||
|
@ -53,17 +52,16 @@ func Path(path string, isSpace bool) error {
|
|||
}
|
||||
|
||||
// ensure path is not too deep
|
||||
segments := strings.Split(path, types.PathSeparator)
|
||||
l := len(segments)
|
||||
if l < minPathSegments || (!isSpace && l > maxPathSegments) || (isSpace && l > maxPathSegmentsForSpace) {
|
||||
return ErrPathInvalidSize
|
||||
if err := PathDepth(path, isSpace); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ensure all segments of the path are valid
|
||||
// ensure all segments of the path are valid uids
|
||||
segments := strings.Split(path, types.PathSeparator)
|
||||
for _, s := range segments {
|
||||
if s == "" {
|
||||
return ErrEmptyPathSegment
|
||||
} else if err := PathName(s); err != nil {
|
||||
} else if err := UID(s); err != nil {
|
||||
return errors.Wrapf(err, "Invalid segment '%s'", s)
|
||||
}
|
||||
}
|
||||
|
@ -101,9 +99,18 @@ func PathParams(path *types.PathParams, currentPath string, isSpace bool) error
|
|||
return nil
|
||||
}
|
||||
|
||||
// PathTooLong Checks if the provided path is too long.
|
||||
// PathDepth Checks the depth of the provided path.
|
||||
func PathDepth(path string, isSpace bool) error {
|
||||
if IsPathTooDeep(path, isSpace) {
|
||||
return ErrPathInvalidDepth
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsPathTooDeep Checks if the provided path is too long.
|
||||
// NOTE: A repository path can be one deeper than a space path (as otherwise the space would be useless).
|
||||
func PathTooLong(path string, isSpace bool) bool {
|
||||
func IsPathTooDeep(path string, isSpace bool) bool {
|
||||
l := strings.Count(path, types.PathSeparator) + 1
|
||||
return (!isSpace && l > maxPathSegments) || (isSpace && l > maxPathSegmentsForSpace)
|
||||
}
|
||||
|
|
|
@ -9,26 +9,30 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
ErrRepositoryRequiresSpaceID = &ValidationError{
|
||||
"SpaceID required - Repositories don't exist outside of a space.",
|
||||
ErrRepositoryRequiresParentID = &ValidationError{
|
||||
"ParentID required - Standalone repositories are not supported.",
|
||||
}
|
||||
)
|
||||
|
||||
// Repo checks the provided repository and returns an error in it isn't valid.
|
||||
func Repo(repo *types.Repository) error {
|
||||
// validate name
|
||||
if err := PathName(repo.PathName); err != nil {
|
||||
// Repo returns true if the Repo is valid.
|
||||
type Repo func(*types.Repository) error
|
||||
|
||||
// RepoDefault is the default Repo validation.
|
||||
func RepoDefault(repo *types.Repository) error {
|
||||
// validate UID
|
||||
if err := UID(repo.UID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// validate display name
|
||||
if err := Name(repo.Name); err != nil {
|
||||
return err
|
||||
// validate the rest
|
||||
return RepoNoUID(repo)
|
||||
}
|
||||
|
||||
// RepoNoUID validates the repo and ignores the UID field.
|
||||
func RepoNoUID(repo *types.Repository) error {
|
||||
// validate repo within a space
|
||||
if repo.SpaceID <= 0 {
|
||||
return ErrRepositoryRequiresSpaceID
|
||||
if repo.ParentID <= 0 {
|
||||
return ErrRepositoryRequiresParentID
|
||||
}
|
||||
|
||||
// TODO: validate defaultBranch, ...
|
||||
|
|
|
@ -8,15 +8,23 @@ import (
|
|||
"github.com/harness/gitness/types"
|
||||
)
|
||||
|
||||
// Service returns true if the Service if valid.
|
||||
func Service(sa *types.Service) error {
|
||||
// Service returns true if the Service is valid.
|
||||
type Service func(*types.Service) error
|
||||
|
||||
// ServiceDefault is the default Service validation.
|
||||
func ServiceDefault(svc *types.Service) error {
|
||||
// validate UID
|
||||
if err := UID(sa.UID); err != nil {
|
||||
if err := UID(svc.UID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// validate name
|
||||
if err := Name(sa.Name); err != nil {
|
||||
// Validate Email
|
||||
if err := Email(svc.Email); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// validate DisplayName
|
||||
if err := DisplayName(svc.DisplayName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -13,24 +13,47 @@ var (
|
|||
ErrServiceAccountParentTypeIsInvalid = &ValidationError{
|
||||
"Provided parent type is invalid.",
|
||||
}
|
||||
ErrServiceAccountParentIDInvalid = &ValidationError{
|
||||
"ParentID required - Global service accounts are not supported.",
|
||||
}
|
||||
)
|
||||
|
||||
// ServiceAccount returns true if the ServiceAccount if valid.
|
||||
func ServiceAccount(sa *types.ServiceAccount) error {
|
||||
// ServiceAccount returns true if the ServiceAccount is valid.
|
||||
type ServiceAccount func(*types.ServiceAccount) error
|
||||
|
||||
// ServiceAccountDefault is the default ServiceAccount validation.
|
||||
func ServiceAccountDefault(sa *types.ServiceAccount) error {
|
||||
// validate UID
|
||||
if err := UID(sa.UID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// validate name
|
||||
if err := Name(sa.Name); err != nil {
|
||||
// Validate Email
|
||||
if err := Email(sa.Email); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// validate DisplayName
|
||||
if err := DisplayName(sa.DisplayName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// validate remaining
|
||||
return ServiceAccountNoPrincipal(sa)
|
||||
}
|
||||
|
||||
// ServiceAccountNoPrincipal verifies the remaining fields of a service account
|
||||
// that aren't inhereted from principal.
|
||||
func ServiceAccountNoPrincipal(sa *types.ServiceAccount) error {
|
||||
// validate parentType
|
||||
if sa.ParentType != enum.ParentResourceTypeRepo && sa.ParentType != enum.ParentResourceTypeSpace {
|
||||
return ErrServiceAccountParentTypeIsInvalid
|
||||
}
|
||||
|
||||
// validate service account belongs to a space
|
||||
if sa.ParentID <= 0 {
|
||||
return ErrServiceAccountParentIDInvalid
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -17,31 +17,35 @@ var (
|
|||
ErrRootSpaceNameNotAllowed = &ValidationError{
|
||||
fmt.Sprintf("The following names are not allowed for a root space: %v", illegalRootSpaceNames),
|
||||
}
|
||||
ErrInvalidParentSpaceID = &ValidationError{
|
||||
"Parent space ID has to be either zero for a root space or greater than zero for a child space.",
|
||||
ErrInvalidParentID = &ValidationError{
|
||||
"Parent ID has to be either zero for a root space or greater than zero for a child space.",
|
||||
}
|
||||
)
|
||||
|
||||
// Space checks the provided space and returns an error in it isn't valid.
|
||||
func Space(space *types.Space) error {
|
||||
// validate name
|
||||
if err := PathName(space.PathName); err != nil {
|
||||
// Space returns true if the Space is valid.
|
||||
type Space func(*types.Space) error
|
||||
|
||||
// SpaceDefault is the default space validation.
|
||||
func SpaceDefault(space *types.Space) error {
|
||||
// validate UID
|
||||
if err := UID(space.UID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// validate display name
|
||||
if err := Name(space.Name); err != nil {
|
||||
return err
|
||||
// validate the rest
|
||||
return SpaceNoUID(space)
|
||||
}
|
||||
|
||||
// SpaceNoUID validates the space and ignores the UID field.
|
||||
func SpaceNoUID(space *types.Space) error {
|
||||
if space.ParentID < 0 {
|
||||
return ErrInvalidParentSpaceID
|
||||
return ErrInvalidParentID
|
||||
}
|
||||
|
||||
// root space specific validations
|
||||
if space.ParentID == 0 {
|
||||
for _, p := range illegalRootSpaceNames {
|
||||
if strings.HasPrefix(space.PathName, p) {
|
||||
if strings.HasPrefix(space.UID, p) {
|
||||
return ErrRootSpaceNameNotAllowed
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue