[Embedded] Harness Router, Inline Space Creation, Bootstrap, Harness/Admin User Setup (#28)

Adds the basic for harness embedded mode:
- Harness dedicated router with custom APIHandler
- Inline Space Creation
- Client for Account/Org/Project
- Bootstrap (Allows for automated creation of admin user and gitness service (used for all platform required ops))
- Inline harness service principal creation
- Ignore flag for ACL.
jobatzil/rename
Johannes Batzill 2022-10-10 21:32:14 -07:00 committed by GitHub
parent c07dc4c779
commit 5786ad2409
87 changed files with 1061 additions and 162 deletions

View File

@ -1,18 +1,28 @@
# 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_SECRET="IC04LYMBf1lDP5oeY4hupxd4HJhLmN6azUku3xEbeE3SUx5G3ZYzhbiwVtK4i7AmqyU9OZkwB4v8E9qM"
HARNESS_JWT_VALIDINMIN=1440
HARNESS_JWT_BEARER_IDENTITY="Bearer"
HARNESS_JWT_BEARER_SECRET="dOkdsVqdRPPRJG31XU0qY4MPqmBBMk0PTAGIKM6O7TGqhjyxScIdJe80mwh5Yb5zF3KxYBHw6B3Lfzlq"
HARNESS_JWT_IDENTITY_SERVICE_IDENTITY="IdentityService"
HARNESS_JWT_IDENTITY_SERVICE_SECRET="HVSKUYqD4e5Rxu12hFDdCJKGM64sxgEynvdDhaOHaTHhwwn0K4Ttr0uoOxSsEVYNrUU"
HARNESS_JWT_MANAGER_IDENTITY="Manager"
HARNESS_JWT_MANAGER_SECRET="dOkdsVqdRPPRJG31XU0qY4MPqmBBMk0PTAGIKM6O7TGqhjyxScIdJe80mwh5Yb5zF3KxYBHw6B3Lfzlq"
HARNESS_JWT_NGMANAGER_IDENTITY="NextGenManager"
HARNESS_JWT_NGMANAGER_SECRET="IC04LYMBf1lDP5oeY4hupxd4HJhLmN6azUku3xEbeE3SUx5G3ZYzhbiwVtK4i7AmqyU9OZkwB4v8E9qM"
HARNESS_CLIENTS_ACL_SECURE=false
HARNESS_CLIENTS_ACL_BASEURL="http://localhost:9006/api"
HARNESS_CLIENTS_MANAGER_SECURE=false
HARNESS_CLIENTS_MANAGER_BASEURL="http://localhost:3457/api"
HARNESS_CLIENTS_NGMANAGER_SECURE=false
HARNESS_CLIENTS_NGMANAGER_BASEURL="http://localhost:7457"
HARNESS_SERVICES_IDENTITY_JWT_IDENTITY="IdentityService"
HARNESS_SERVICES_IDENTITY_JWT_SECRET="HVSKUYqD4e5Rxu12hFDdCJKGM64sxgEynvdDhaOHaTHhwwn0K4Ttr0uoOxSsEVYNrUU"
HARNESS_SERVICES_ACL_IGNORE=true
HARNESS_SERVICES_ACL_CLIENT_SECURE=false
HARNESS_SERVICES_ACL_CLIENT_BASEURL="http://localhost:9006/api"
HARNESS_SERVICES_MANAGER_CLIENT_SECURE=false
HARNESS_SERVICES_MANAGER_CLIENT_BASEURL="http://localhost:3457/api"
HARNESS_SERVICES_MANAGER_JWT_IDENTITY="Manager"
HARNESS_SERVICES_MANAGER_JWT_SECRET="dOkdsVqdRPPRJG31XU0qY4MPqmBBMk0PTAGIKM6O7TGqhjyxScIdJe80mwh5Yb5zF3KxYBHw6B3Lfzlq"
HARNESS_SERVICES_NGMANAGER_CLIENT_SECURE=false
HARNESS_SERVICES_NGMANAGER_CLIENT_BASEURL="http://localhost:7457"
HARNESS_SERVICES_NGMANAGER_JWT_IDENTITY="NextGenManager"
HARNESS_SERVICES_NGMANAGER_JWT_SECRET="IC04LYMBf1lDP5oeY4hupxd4HJhLmN6azUku3xEbeE3SUx5G3ZYzhbiwVtK4i7AmqyU9OZkwB4v8E9qM"

View File

@ -1 +1,5 @@
GITNESS_TRACE=true
GITNESS_TRACE=true
GITNESS_ADMIN_UID=admin
GITNESS_ADMIN_NAME=Administrator
GITNESS_ADMIN_EMAIL=admin@gitness.io
GITNESS_ADMIN_PASSWORD=changeit

View File

@ -10,37 +10,39 @@ package server
import (
"context"
"github.com/harness/gitness/harness"
"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/types"
"github.com/harness/gitness/internal/api/controller/repo"
"github.com/harness/gitness/internal/api/controller/serviceaccount"
"github.com/harness/gitness/internal/api/controller/service"
"github.com/harness/gitness/internal/api/controller/space"
"github.com/harness/gitness/internal/api/controller/user"
"github.com/harness/gitness/internal/cron"
"github.com/harness/gitness/internal/router"
"github.com/harness/gitness/internal/server"
"github.com/harness/gitness/internal/store/database"
"github.com/harness/gitness/internal/store/memory"
"github.com/harness/gitness/types"
gitnessTypes "github.com/harness/gitness/types"
"github.com/google/wire"
)
func initSystem(ctx context.Context, config *types.Config) (*system, error) {
func initSystem(ctx context.Context, config *gitnessTypes.Config) (*system, error) {
wire.Build(
newSystem,
bootstrap.WireSet,
database.WireSet,
memory.WireSet,
router.WireSet,
server.WireSet,
cron.WireSet,
repo.WireSet,
serviceaccount.WireSet,
space.WireSet,
user.WireSet,
harness.LoadConfig,
service.WireSet,
types.LoadConfig,
router.WireSet,
authn.WireSet,
authz.WireSet,
client.WireSet,

View File

@ -8,16 +8,18 @@ package server
import (
"context"
"github.com/harness/gitness/harness"
"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"
types2 "github.com/harness/gitness/harness/types"
"github.com/harness/gitness/internal/api/controller/repo"
"github.com/harness/gitness/internal/api/controller/serviceaccount"
"github.com/harness/gitness/internal/api/controller/service"
"github.com/harness/gitness/internal/api/controller/space"
"github.com/harness/gitness/internal/api/controller/user"
"github.com/harness/gitness/internal/cron"
"github.com/harness/gitness/internal/router"
router2 "github.com/harness/gitness/internal/router"
"github.com/harness/gitness/internal/server"
"github.com/harness/gitness/internal/store/database"
"github.com/harness/gitness/internal/store/memory"
@ -27,55 +29,61 @@ import (
// Injectors from harness.wire.go:
func initSystem(ctx context.Context, config *types.Config) (*system, error) {
systemStore := memory.New(config)
typesConfig, err := types2.LoadConfig()
if err != nil {
return nil, err
}
serviceJWTProvider, err := client.ProvideServiceJWTProvider(typesConfig)
if err != nil {
return nil, err
}
aclClient, err := client.ProvideACLClient(serviceJWTProvider, typesConfig)
if err != nil {
return nil, err
}
authorizer := authz.ProvideAuthorizer(typesConfig, aclClient)
db, err := database.ProvideDatabase(ctx, config)
if err != nil {
return nil, err
}
userStore := database.ProvideUserStore(db)
harnessConfig, err := harness.LoadConfig()
tokenStore := database.ProvideTokenStore(db)
controller := user.NewController(authorizer, userStore, tokenStore)
serviceStore := database.ProvideServiceStore(db)
serviceController := service.NewController(authorizer, serviceStore)
bootstrapBootstrap := bootstrap.ProvideBootstrap(config, controller, serviceController)
systemStore := memory.New(config)
tokenClient, err := client.ProvideTokenClient(serviceJWTProvider, typesConfig)
if err != nil {
return nil, err
}
serviceJWTProvider, err := client.ProvideServiceJWTProvider(harnessConfig)
userClient, err := client.ProvideUserClient(serviceJWTProvider, typesConfig)
if err != nil {
return nil, err
}
tokenClient, err := client.ProvideTokenClient(serviceJWTProvider, harnessConfig)
if err != nil {
return nil, err
}
userClient, err := client.ProvideUserClient(serviceJWTProvider, harnessConfig)
if err != nil {
return nil, err
}
serviceAccountClient, err := client.ProvideServiceAccountClient(serviceJWTProvider, harnessConfig)
serviceAccountClient, err := client.ProvideServiceAccountClient(serviceJWTProvider, typesConfig)
if err != nil {
return nil, err
}
serviceAccountStore := database.ProvideServiceAccountStore(db)
authenticator, err := authn.ProvideAuthenticator(userStore, tokenClient, userClient, harnessConfig, serviceAccountClient, serviceAccountStore)
authenticator, err := authn.ProvideAuthenticator(userStore, tokenClient, userClient, typesConfig, serviceAccountClient, serviceAccountStore, serviceStore)
if err != nil {
return nil, err
}
aclClient, err := client.ProvideACLClient(serviceJWTProvider, harnessConfig)
accountClient, err := client.ProvideAccountClient(serviceJWTProvider, typesConfig)
if err != nil {
return nil, err
}
authorizer := authz.ProvideAuthorizer(aclClient)
spaceStore := database.ProvideSpaceStore(db)
repoStore := database.ProvideRepoStore(db)
controller := repo.NewController(authorizer, spaceStore, repoStore, serviceAccountStore)
spaceController := space.NewController(authorizer, spaceStore, repoStore, serviceAccountStore)
tokenStore := database.ProvideTokenStore(db)
serviceaccountController := serviceaccount.NewController(authorizer, serviceAccountStore, spaceStore, repoStore, tokenStore)
userController := user.NewController(authorizer, userStore, tokenStore)
apiHandler := router.ProvideAPIHandler(systemStore, authenticator, controller, spaceController, serviceaccountController, userController)
gitHandler := router.ProvideGitHandler(repoStore, authenticator)
webHandler := router.ProvideWebHandler(systemStore)
routerRouter := router.ProvideRouter(apiHandler, gitHandler, webHandler)
repoController := repo.NewController(authorizer, spaceStore, repoStore, serviceAccountStore)
apiHandler := router.ProvideAPIHandler(systemStore, authenticator, accountClient, spaceController, repoController)
gitHandler := router2.ProvideGitHandler(repoStore, authenticator)
webHandler := router2.ProvideWebHandler(systemStore)
routerRouter := router2.ProvideRouter(apiHandler, gitHandler, webHandler)
serverServer := server.ProvideServer(config, routerRouter)
nightly := cron.NewNightly()
serverSystem := newSystem(serverServer, nightly)
serverSystem := newSystem(bootstrapBootstrap, serverServer, nightly)
return serverSystem, nil
}

View File

@ -54,10 +54,20 @@ func (c *command) run(*kingpin.ParseContext) error {
// configure the log level
setupLogger(config)
// add logger to context
log := log.Logger.With().Logger()
ctx = log.WithContext(ctx)
// initialize system
system, err := initSystem(ctx, config)
if err != nil {
return fmt.Errorf("encountered an error while initializing the system: %w", err)
return fmt.Errorf("encountered an error while wiring the system: %w", err)
}
// bootstrap the system
err = system.bootstrap(ctx)
if err != nil {
return fmt.Errorf("encountered an error while bootstrapping the system: %w", err)
}
// collects all go routines - gCTX cancels if any go routine encounters an error

View File

@ -16,6 +16,7 @@ import (
"github.com/harness/gitness/internal/api/controller/user"
"github.com/harness/gitness/internal/auth/authn"
"github.com/harness/gitness/internal/auth/authz"
"github.com/harness/gitness/internal/bootstrap"
"github.com/harness/gitness/internal/cron"
"github.com/harness/gitness/internal/router"
"github.com/harness/gitness/internal/server"
@ -29,6 +30,7 @@ import (
func initSystem(ctx context.Context, config *types.Config) (*system, error) {
wire.Build(
newSystem,
bootstrap.WireSet,
database.WireSet,
memory.WireSet,
router.WireSet,

View File

@ -14,6 +14,7 @@ import (
"github.com/harness/gitness/internal/api/controller/user"
"github.com/harness/gitness/internal/auth/authn"
"github.com/harness/gitness/internal/auth/authz"
"github.com/harness/gitness/internal/bootstrap"
"github.com/harness/gitness/internal/cron"
"github.com/harness/gitness/internal/router"
"github.com/harness/gitness/internal/server"
@ -25,28 +26,29 @@ import (
// Injectors from standalone.wire.go:
func initSystem(ctx context.Context, config *types.Config) (*system, error) {
systemStore := memory.New(config)
authorizer := authz.ProvideAuthorizer()
db, err := database.ProvideDatabase(ctx, config)
if err != nil {
return nil, err
}
userStore := database.ProvideUserStore(db)
serviceAccountStore := database.ProvideServiceAccountStore(db)
tokenStore := database.ProvideTokenStore(db)
controller := user.NewController(authorizer, userStore, tokenStore)
bootstrapBootstrap := bootstrap.ProvideBootstrap(config, controller)
systemStore := memory.New(config)
serviceAccountStore := database.ProvideServiceAccountStore(db)
authenticator := authn.ProvideAuthenticator(userStore, serviceAccountStore, tokenStore)
authorizer := authz.ProvideAuthorizer()
spaceStore := database.ProvideSpaceStore(db)
repoStore := database.ProvideRepoStore(db)
controller := repo.NewController(authorizer, spaceStore, repoStore, serviceAccountStore)
repoController := repo.NewController(authorizer, spaceStore, repoStore, serviceAccountStore)
spaceController := space.NewController(authorizer, spaceStore, repoStore, serviceAccountStore)
serviceaccountController := serviceaccount.NewController(authorizer, serviceAccountStore, spaceStore, repoStore, tokenStore)
userController := user.NewController(authorizer, userStore, tokenStore)
apiHandler := router.ProvideAPIHandler(systemStore, authenticator, controller, spaceController, serviceaccountController, userController)
apiHandler := router.ProvideAPIHandler(systemStore, authenticator, repoController, spaceController, serviceaccountController, controller)
gitHandler := router.ProvideGitHandler(repoStore, authenticator)
webHandler := router.ProvideWebHandler(systemStore)
routerRouter := router.ProvideRouter(apiHandler, gitHandler, webHandler)
serverServer := server.ProvideServer(config, routerRouter)
nightly := cron.NewNightly()
serverSystem := newSystem(serverServer, nightly)
serverSystem := newSystem(bootstrapBootstrap, serverServer, nightly)
return serverSystem, nil
}

View File

@ -5,20 +5,23 @@
package server
import (
"github.com/harness/gitness/internal/bootstrap"
"github.com/harness/gitness/internal/cron"
"github.com/harness/gitness/internal/server"
)
// system stores high level system sub-routines.
type system struct {
server *server.Server
nightly *cron.Nightly
bootstrap bootstrap.Bootstrap
server *server.Server
nightly *cron.Nightly
}
// newSystem returns a new system structure.
func newSystem(server *server.Server, nightly *cron.Nightly) *system {
func newSystem(bootstrap bootstrap.Bootstrap, server *server.Server, nightly *cron.Nightly) *system {
return &system{
server: server,
nightly: nightly,
bootstrap: bootstrap,
server: server,
nightly: nightly,
}
}

View File

@ -0,0 +1,31 @@
// Copyright 2021 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 auth
import (
"context"
"github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/internal/auth/authz"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
/*
* CheckService checks if a service specific permission is granted for the current auth session.
* Returns nil if the permission is granted, otherwise returns an error.
* NotAuthenticated, NotAuthorized, or any unerlaying error.
*/
func CheckService(ctx context.Context, authorizer authz.Authorizer, session *auth.Session,
svc *types.Service, permission enum.Permission) error {
// a service exists outside of any scope
scope := &types.Scope{}
resource := &types.Resource{
Type: enum.ResourceTypeService,
Name: svc.UID,
}
return Check(ctx, authorizer, session, scope, resource, permission)
}

View File

@ -0,0 +1,22 @@
// Copyright 2021 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 service
import (
"github.com/harness/gitness/internal/auth/authz"
"github.com/harness/gitness/internal/store"
)
type Controller struct {
authorizer authz.Authorizer
serviceStore store.ServiceStore
}
func NewController(authorizer authz.Authorizer, serviceStore store.ServiceStore) *Controller {
return &Controller{
authorizer: authorizer,
serviceStore: serviceStore,
}
}

View File

@ -0,0 +1,68 @@
// Copyright 2021 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 service
import (
"context"
"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"
)
// CreateInput is the input used for create operations.
type CreateInput struct {
UID string
Name string
}
/*
* Create creates a new service.
*/
func (c *Controller) Create(ctx context.Context, session *auth.Session, in *CreateInput) (*types.Service, error) {
// Ensure principal has required permissions (service is global, no explicit resource)
scope := &types.Scope{}
resource := &types.Resource{
Type: enum.ResourceTypeService,
}
if err := apiauth.Check(ctx, c.authorizer, session, scope, resource, enum.PermissionServiceCreate); err != nil {
return nil, err
}
return c.CreateNoAuth(ctx, in, false)
}
/*
* CreateNoAuth creates a new service without auth checks.
* WARNING: Never call as part of user flow.
*
* Note: take admin separately to avoid potential vulnerabilities for user calls.
*/
func (c *Controller) CreateNoAuth(ctx context.Context, in *CreateInput, admin bool) (*types.Service, error) {
svc := &types.Service{
UID: in.UID,
Name: in.Name,
Admin: admin,
Salt: uniuri.NewLen(uniuri.UUIDLen),
Created: time.Now().UnixMilli(),
Updated: time.Now().UnixMilli(),
}
// validate service
if err := check.Service(svc); err != nil {
return nil, err
}
err := c.serviceStore.Create(ctx, svc)
if err != nil {
return nil, err
}
return svc, nil
}

View File

@ -0,0 +1,31 @@
// Copyright 2021 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 service
import (
"context"
apiauth "github.com/harness/gitness/internal/api/auth"
"github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/types/enum"
)
/*
* Delete deletes a service.
*/
func (c *Controller) Delete(ctx context.Context, session *auth.Session,
serviceUID string) error {
svc, err := findServiceFromUID(ctx, c.serviceStore, serviceUID)
if err != nil {
return err
}
// Ensure principal has required permissions on parent
if err = apiauth.CheckService(ctx, c.authorizer, session, svc, enum.PermissionServiceDelete); err != nil {
return err
}
return c.serviceStore.Delete(ctx, svc.ID)
}

View File

@ -0,0 +1,40 @@
// Copyright 2021 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 service
import (
"context"
apiauth "github.com/harness/gitness/internal/api/auth"
"github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
/*
* Find tries to find the provided service.
*/
func (c *Controller) Find(ctx context.Context, session *auth.Session,
serviceUID string) (*types.Service, error) {
svc, err := c.FindNoAuth(ctx, serviceUID)
if err != nil {
return nil, err
}
// Ensure principal has required permissions on parent.
if err = apiauth.CheckService(ctx, c.authorizer, session, svc, enum.PermissionServiceView); err != nil {
return nil, err
}
return svc, nil
}
/*
* FindNoAuth finds a service without auth checks.
* WARNING: Never call as part of user flow.
*/
func (c *Controller) FindNoAuth(ctx context.Context, serviceUID string) (*types.Service, error) {
return findServiceFromUID(ctx, c.serviceStore, serviceUID)
}

View File

@ -0,0 +1,17 @@
// Copyright 2021 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 service
import (
"context"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/types"
)
func findServiceFromUID(ctx context.Context,
serviceStore store.ServiceStore, serviceUID string) (*types.Service, error) {
return serviceStore.FindUID(ctx, serviceUID)
}

View File

@ -0,0 +1,41 @@
// Copyright 2021 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 service
import (
"context"
"fmt"
apiauth "github.com/harness/gitness/internal/api/auth"
"github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
/*
* List lists all services of the system.
*/
func (c *Controller) List(ctx context.Context, session *auth.Session) (int64, []*types.Service, error) {
// Ensure principal has required permissions (service is global, no explicit resource)
scope := &types.Scope{}
resource := &types.Resource{
Type: enum.ResourceTypeService,
}
if err := apiauth.Check(ctx, c.authorizer, session, scope, resource, enum.PermissionServiceView); err != nil {
return 0, nil, err
}
count, err := c.serviceStore.Count(ctx)
if err != nil {
return 0, nil, fmt.Errorf("failed to count services: %w", err)
}
repos, err := c.serviceStore.List(ctx)
if err != nil {
return 0, nil, fmt.Errorf("failed to list services: %w", err)
}
return count, repos, nil
}

View File

@ -0,0 +1,55 @@
// Copyright 2021 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 service
import (
"context"
"time"
"github.com/gotidy/ptr"
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"`
}
/*
* Update updates the provided service.
*/
func (c *Controller) Update(ctx context.Context, session *auth.Session,
serviceUID string, in *UpdateInput) (*types.Service, error) {
svc, err := findServiceFromUID(ctx, c.serviceStore, serviceUID)
if err != nil {
return nil, err
}
// Ensure principal has required permissions on parent.
if err = apiauth.CheckService(ctx, c.authorizer, session, svc, enum.PermissionServiceEdit); err != nil {
return nil, err
}
if in.Name != nil {
svc.Name = ptr.ToString(in.Name)
}
svc.Updated = time.Now().UnixMilli()
// validate service
if err = check.Service(svc); err != nil {
return nil, err
}
err = c.serviceStore.Update(ctx, svc)
if err != nil {
return nil, err
}
return svc, nil
}

View File

@ -0,0 +1,41 @@
// Copyright 2021 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 service
import (
"context"
"time"
apiauth "github.com/harness/gitness/internal/api/auth"
"github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
/*
* UpdateAdmin updates the admin state of a service.
*/
func (c *Controller) UpdateAdmin(ctx context.Context, session *auth.Session,
serviceUID string, admin bool) (*types.Service, error) {
sbc, err := findServiceFromUID(ctx, c.serviceStore, serviceUID)
if err != nil {
return nil, err
}
// Ensure principal has required permissions on parent.
if err = apiauth.CheckService(ctx, c.authorizer, session, sbc, enum.PermissionServiceEditAdmin); err != nil {
return nil, err
}
sbc.Admin = admin
sbc.Updated = time.Now().UnixMilli()
err = c.serviceStore.Update(ctx, sbc)
if err != nil {
return nil, err
}
return sbc, nil
}

View File

@ -0,0 +1,20 @@
// Copyright 2021 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 service
import (
"github.com/google/wire"
"github.com/harness/gitness/internal/auth/authz"
"github.com/harness/gitness/internal/store"
)
// WireSet provides a wire set for this package.
var WireSet = wire.NewSet(
NewController,
)
func ProvideController(authorizer authz.Authorizer, serviceStore store.ServiceStore) *Controller {
return NewController(authorizer, serviceStore)
}

View File

@ -24,7 +24,7 @@ func (c *Controller) ListRepositories(ctx context.Context, session *auth.Session
return 0, nil, err
}
if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceView, true); err != nil {
if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionRepoView, true); err != nil {
return 0, nil, err
}

View File

@ -18,6 +18,8 @@ import (
"golang.org/x/crypto/bcrypt"
)
// CreateInput is the input used for create operations.
// On purpose don't expose admin, has to be enabled explicitly.
type CreateInput struct {
UID string `json:"uid"`
Name string `json:"name"`
@ -38,13 +40,21 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, in *Crea
return nil, err
}
return c.createNoAuth(ctx, in)
return c.CreateNoAuth(ctx, in, false)
}
/*
* createNoAuth creates a new user without auth checks.
* CreateNoAuth creates a new user without auth checks.
* WARNING: Never call as part of user flow.
*
* Note: take admin separately to avoid potential vulnerabilities for user calls.
*/
func (c *Controller) createNoAuth(ctx context.Context, in *CreateInput) (*types.User, error) {
func (c *Controller) CreateNoAuth(ctx context.Context, in *CreateInput, admin bool) (*types.User, error) {
// validate password before hashing
if err := check.Password(in.Password); err != nil {
return nil, err
}
hash, err := hashPassword([]byte(in.Password), bcrypt.DefaultCost)
if err != nil {
return nil, fmt.Errorf("failed to create hash: %w", err)
@ -58,6 +68,7 @@ func (c *Controller) createNoAuth(ctx context.Context, in *CreateInput) (*types.
Salt: uniuri.NewLen(uniuri.UUIDLen),
Created: time.Now().UnixMilli(),
Updated: time.Now().UnixMilli(),
Admin: admin,
}
// validate user
@ -71,6 +82,7 @@ func (c *Controller) createNoAuth(ctx context.Context, in *CreateInput) (*types.
}
// first user will be admin by default.
// TODO: move responsibility somewhere else.
if user.ID == 1 {
user.Admin = true
err = c.userStore.Update(ctx, user)

View File

@ -18,7 +18,7 @@ import (
*/
func (c *Controller) Find(ctx context.Context, session *auth.Session,
userUID string) (*types.User, error) {
user, err := findUserFromUID(ctx, c.userStore, userUID)
user, err := c.FindNoAuth(ctx, userUID)
if err != nil {
return nil, err
}
@ -30,3 +30,11 @@ func (c *Controller) Find(ctx context.Context, session *auth.Session,
return user, nil
}
/*
* FindNoAuth finds a user without auth checks.
* WARNING: Never call as part of user flow.
*/
func (c *Controller) FindNoAuth(ctx context.Context, userUID string) (*types.User, error) {
return findUserFromUID(ctx, c.userStore, userUID)
}

View File

@ -18,9 +18,9 @@ import (
* functionalities (unable to create admin user for example).
*/
func (c *Controller) Register(ctx context.Context, in *CreateInput) (*types.TokenResponse, error) {
// TODO: allows to configure if open register is allowed.
// TODO: allow to configure if open register is allowed.
user, err := c.createNoAuth(ctx, in)
user, err := c.CreateNoAuth(ctx, in, false)
if err != nil {
return nil, fmt.Errorf("failed to create user: %w", err)
}

View File

@ -20,7 +20,7 @@ func HandleCreatePath(repoCtrl *repo.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
repoRef, err := request.GetRepoRef(r)
repoRef, err := request.GetRepoRefFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return

View File

@ -19,7 +19,7 @@ func HandleDelete(repoCtrl *repo.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
repoRef, err := request.GetRepoRef(r)
repoRef, err := request.GetRepoRefFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return

View File

@ -19,12 +19,12 @@ func HandleDeletePath(repoCtrl *repo.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
repoRef, err := request.GetRepoRef(r)
repoRef, err := request.GetRepoRefFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return
}
pathID, err := request.GetPathID(r)
pathID, err := request.GetPathIDFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return

View File

@ -19,7 +19,7 @@ func HandleFind(repoCtrl *repo.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
repoRef, err := request.GetRepoRef(r)
repoRef, err := request.GetRepoRefFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return

View File

@ -20,7 +20,7 @@ func HandleListPaths(repoCtrl *repo.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
repoRef, err := request.GetRepoRef(r)
repoRef, err := request.GetRepoRefFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return

View File

@ -19,7 +19,7 @@ func HandleListServiceAccounts(repoCtrl *repo.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
repoRef, err := request.GetRepoRef(r)
repoRef, err := request.GetRepoRefFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return

View File

@ -18,7 +18,7 @@ func HandleMove(repoCtrl *repo.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
repoRef, err := request.GetRepoRef(r)
repoRef, err := request.GetRepoRefFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return

View File

@ -20,7 +20,7 @@ func HandleUpdate(repoCtrl *repo.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
repoRef, err := request.GetRepoRef(r)
repoRef, err := request.GetRepoRefFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return

View File

@ -19,7 +19,7 @@ func HandleCreateToken(saCrl *serviceaccount.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
saUID, err := request.GetServiceAccountUID(r)
saUID, err := request.GetServiceAccountUIDFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return

View File

@ -19,7 +19,7 @@ func HandleDelete(saCrl *serviceaccount.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
saUID, err := request.GetServiceAccountUID(r)
saUID, err := request.GetServiceAccountUIDFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return

View File

@ -18,12 +18,12 @@ func HandleDeleteToken(saCrl *serviceaccount.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
saUID, err := request.GetServiceAccountUID(r)
saUID, err := request.GetServiceAccountUIDFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return
}
tokenID, err := request.GetTokenID(r)
tokenID, err := request.GetTokenIDFromPath(r)
if err != nil {
render.BadRequest(w)
return

View File

@ -18,7 +18,7 @@ func HandleFind(saCrl *serviceaccount.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
saUID, err := request.GetServiceAccountUID(r)
saUID, err := request.GetServiceAccountUIDFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return

View File

@ -18,7 +18,7 @@ func HandleListTokens(saCrl *serviceaccount.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
saUID, err := request.GetServiceAccountUID(r)
saUID, err := request.GetServiceAccountUIDFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return

View File

@ -20,7 +20,7 @@ func HandleCreatePath(spaceCtrl *space.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRef(r)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return

View File

@ -19,7 +19,7 @@ func HandleDelete(spaceCtrl *space.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRef(r)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return

View File

@ -19,13 +19,13 @@ func HandleDeletePath(spaceCtrl *space.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRef(r)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return
}
pathID, err := request.GetPathID(r)
pathID, err := request.GetPathIDFromPath(r)
if err != nil {
render.BadRequest(w)
return

View File

@ -19,7 +19,7 @@ func HandleFind(spaceCtrl *space.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRef(r)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return

View File

@ -18,7 +18,7 @@ func HandleListSpaces(spaceCtrl *space.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRef(r)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return

View File

@ -20,7 +20,7 @@ func HandleListPaths(spaceCtrl *space.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRef(r)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return

View File

@ -18,7 +18,7 @@ func HandleListRepos(spaceCtrl *space.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRef(r)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return

View File

@ -19,7 +19,7 @@ func HandleListServiceAccounts(spaceCtrl *space.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRef(r)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return

View File

@ -18,7 +18,7 @@ func HandleMove(spaceCtrl *space.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRef(r)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return

View File

@ -20,7 +20,7 @@ func HandleUpdate(spaceCtrl *space.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRef(r)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return

View File

@ -21,7 +21,7 @@ func HandleDeleteToken(userCtrl *user.Controller, tokenType enum.TokenType) http
session, _ := request.AuthSessionFrom(ctx)
userUID := session.Principal.UID
tokenID, err := request.GetTokenID(r)
tokenID, err := request.GetTokenIDFromPath(r)
if err != nil {
render.BadRequest(w)
return

View File

@ -18,7 +18,7 @@ func HandleDelete(userCtrl *user.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
userUID, err := request.GetUserUID(r)
userUID, err := request.GetUserUIDFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return

View File

@ -18,7 +18,7 @@ func HandleFind(userCtrl *user.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
userUID, err := request.GetUserUID(r)
userUID, err := request.GetUserUIDFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return

View File

@ -19,7 +19,7 @@ func HandleUpdate(userCtrl *user.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
userUID, err := request.GetUserUID(r)
userUID, err := request.GetUserUIDFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return

View File

@ -19,6 +19,19 @@ import (
// Attempt returns an http.HandlerFunc middleware that authenticates
// the http.Request if authentication payload is available.
func Attempt(authenticator authn.Authenticator) func(http.Handler) http.Handler {
return performAuthentication(authenticator, false)
}
// Required returns an http.HandlerFunc middleware that authenticates
// the http.Request and fails the request if no auth data was available.
func Required(authenticator authn.Authenticator) func(http.Handler) http.Handler {
return performAuthentication(authenticator, true)
}
// performAuthentication returns an http.HandlerFunc middleware that authenticates
// the http.Request if authentication payload is available.
// Depending on whether it is required or not, the request will be failed.
func performAuthentication(authenticator authn.Authenticator, required bool) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
@ -27,6 +40,11 @@ func Attempt(authenticator authn.Authenticator) func(http.Handler) http.Handler
session, err := authenticator.Authenticate(r)
if errors.Is(err, authn.ErrNoAuthData) {
if required {
render.Unauthorized(w)
return
}
// if there was no auth data in the request - continue as is
next.ServeHTTP(w, r)
return

View File

@ -8,6 +8,6 @@ const (
PathParamPathID = "pathID"
)
func GetPathID(r *http.Request) (int64, error) {
return ParseAsInt64(r, PathParamPathID)
func GetPathIDFromPath(r *http.Request) (int64, error) {
return PathParamAsInt64(r, PathParamPathID)
}

View File

@ -9,10 +9,10 @@ const (
PathParamServiceAccountUID = "saUID"
)
func GetUserUID(r *http.Request) (string, error) {
return ParamOrError(r, PathParamUserUID)
func GetUserUIDFromPath(r *http.Request) (string, error) {
return PathParamOrError(r, PathParamUserUID)
}
func GetServiceAccountUID(r *http.Request) (string, error) {
return ParamOrError(r, PathParamServiceAccountUID)
func GetServiceAccountUIDFromPath(r *http.Request) (string, error) {
return PathParamOrError(r, PathParamServiceAccountUID)
}

View File

@ -10,8 +10,8 @@ const (
PathParamRepoRef = "repositoryRef"
)
func GetRepoRef(r *http.Request) (string, error) {
rawRef, err := ParamOrError(r, PathParamRepoRef)
func GetRepoRefFromPath(r *http.Request) (string, error) {
rawRef, err := PathParamOrError(r, PathParamRepoRef)
if err != nil {
return "", err
}

View File

@ -10,8 +10,8 @@ const (
PathParamSpaceRef = "spaceRef"
)
func GetSpaceRef(r *http.Request) (string, error) {
rawRef, err := ParamOrError(r, PathParamSpaceRef)
func GetSpaceRefFromPath(r *http.Request) (string, error) {
rawRef, err := PathParamOrError(r, PathParamSpaceRef)
if err != nil {
return "", err
}

View File

@ -8,6 +8,6 @@ const (
PathParamTokenID = "tokenID"
)
func GetTokenID(r *http.Request) (int64, error) {
return ParseAsInt64(r, PathParamTokenID)
func GetTokenIDFromPath(r *http.Request) (int64, error) {
return PathParamAsInt64(r, PathParamTokenID)
}

View File

@ -15,9 +15,9 @@ import (
"github.com/harness/gitness/types/enum"
)
// ParamOrError tries to retrieve the parameter from the request and
// PathParamOrError tries to retrieve the parameter from the request and
// returns the parameter if it exists and is not empty, otherwise returns an error.
func ParamOrError(r *http.Request, paramName string) (string, error) {
func PathParamOrError(r *http.Request, paramName string) (string, error) {
value := chi.URLParam(r, paramName)
if value == "" {
return "", usererror.BadRequest(fmt.Sprintf("Parameter '%s' not found in request path.", paramName))
@ -26,9 +26,9 @@ func ParamOrError(r *http.Request, paramName string) (string, error) {
return value, nil
}
// ParseAsInt64 tries to retrieve the parameter from the request and parse it to in64.
func ParseAsInt64(r *http.Request, paramName string) (int64, error) {
rawID, err := ParamOrError(r, paramName)
// PathParamAsInt64 tries to retrieve the parameter from the request and parse it to in64.
func PathParamAsInt64(r *http.Request, paramName string) (int64, error) {
rawID, err := PathParamOrError(r, paramName)
if err != nil {
return 0, err
}

View File

@ -45,7 +45,7 @@ func Translate(err error) *Error {
// unknown error
default:
log.Err(err).Msgf("Unable to translate error: %s", err)
log.Warn().Msgf("Unable to translate error: %s", err)
return ErrInternal
}
}

View File

@ -0,0 +1,74 @@
// Copyright 2021 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 bootstrap
import (
"context"
"errors"
"fmt"
"github.com/harness/gitness/internal/api/controller/user"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/types"
"github.com/rs/zerolog/log"
)
const (
adminUID = "admin"
)
// Bootstrap is an abstraction of a function that bootstraps a system.
type Bootstrap func(context.Context) error
func System(config *types.Config, userCtrl *user.Controller) func(context.Context) error {
return func(ctx context.Context) error {
return Admin(ctx, config, userCtrl)
}
}
// Admin sets up the admin user based on the config (if provided)
//
// NOTE: We could just call update and ignore any duplicate error
// 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 == "" {
return nil
}
_, err := userCtrl.FindNoAuth(ctx, adminUID)
if err == nil || !errors.Is(err, store.ErrResourceNotFound) {
return err
}
in := &user.CreateInput{
UID: adminUID,
Name: config.Admin.Name,
Email: config.Admin.Email,
Password: config.Admin.Password,
}
// create user as admin
usr, err := userCtrl.CreateNoAuth(ctx, in, true)
if errors.Is(err, store.ErrDuplicate) {
// user might've been created by another instance
// in which case we should find the user now.
_, err2 := userCtrl.FindNoAuth(ctx, adminUID)
if err2 != nil {
// return original error.
return err
}
// user exists - perfect
return nil
}
if err != nil {
return fmt.Errorf("failed to create admin user: %w", err)
}
log.Ctx(ctx).Info().Msgf("Created admin user (id: %d).", usr.ID)
return nil
}

View File

@ -0,0 +1,18 @@
// Copyright 2021 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 bootstrap
import (
"github.com/google/wire"
"github.com/harness/gitness/internal/api/controller/user"
"github.com/harness/gitness/types"
)
// WireSet provides a wire set for this package.
var WireSet = wire.NewSet(ProvideBootstrap)
func ProvideBootstrap(config *types.Config, userCtrl *user.Controller) Bootstrap {
return System(config, userCtrl)
}

View File

@ -76,7 +76,7 @@ func stubGitHandler(repoStore store.RepoStore) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusTeapot)
repoPath, _ := request.GetRepoRef(r)
repoPath, _ := request.GetRepoRefFromPath(r)
repo, err := repoStore.FindByPath(r.Context(), repoPath)
if err != nil {
_, _ = w.Write([]byte(fmt.Sprintf("Repo '%s' not found.", repoPath)))

View File

@ -5,7 +5,7 @@ CREATE TABLE IF NOT EXISTS paths (
,path_targetType TEXT
,path_targetId INTEGER
,path_createdBy INTEGER
,path_created INTEGER
,path_updated INTEGER
,path_created BIGINT
,path_updated BIGINT
,UNIQUE(path_value)
);

View File

@ -6,8 +6,8 @@ principal_id SERIAL PRIMARY KEY
,principal_admin BOOLEAN
,principal_blocked BOOLEAN
,principal_salt TEXT
,principal_created INTEGER
,principal_updated INTEGER
,principal_created BIGINT
,principal_updated BIGINT
,principal_user_email CITEXT
,principal_user_password TEXT

View File

@ -6,8 +6,8 @@ CREATE TABLE IF NOT EXISTS repositories (
,repo_description TEXT
,repo_isPublic BOOLEAN
,repo_createdBy INTEGER
,repo_created INTEGER
,repo_updated INTEGER
,repo_created BIGINT
,repo_updated BIGINT
,repo_forkId INTEGER
,repo_numForks INTEGER
,repo_numPulls INTEGER

View File

@ -6,6 +6,6 @@ CREATE TABLE IF NOT EXISTS spaces (
,space_description TEXT
,space_isPublic BOOLEAN
,space_createdBy INTEGER
,space_created INTEGER
,space_updated INTEGER
,space_created BIGINT
,space_updated BIGINT
);

View File

@ -3,8 +3,8 @@ CREATE TABLE IF NOT EXISTS tokens (
,token_type TEXT
,token_name TEXT
,token_principalId INTEGER
,token_expiresAt INTEGER
,token_grants INTEGER
,token_issuedAt INTEGER
,token_expiresAt BIGINT
,token_grants BIGINT
,token_issuedAt BIGINT
,token_createdBy INTEGER
);

View File

@ -5,7 +5,7 @@ CREATE TABLE IF NOT EXISTS paths (
,path_targetType TEXT
,path_targetId INTEGER
,path_createdBy INTEGER
,path_created INTEGER
,path_updated INTEGER
,path_created BIGINT
,path_updated BIGINT
,UNIQUE(path_value COLLATE NOCASE)
);

View File

@ -6,8 +6,8 @@ principal_id INTEGER PRIMARY KEY AUTOINCREMENT
,principal_admin BOOLEAN
,principal_blocked BOOLEAN
,principal_salt TEXT
,principal_created INTEGER
,principal_updated INTEGER
,principal_created BIGINT
,principal_updated BIGINT
,principal_user_email TEXT
,principal_user_password TEXT

View File

@ -6,8 +6,8 @@ CREATE TABLE IF NOT EXISTS repositories (
,repo_description TEXT
,repo_isPublic BOOLEAN
,repo_createdBy INTEGER
,repo_created INTEGER
,repo_updated INTEGER
,repo_created BIGINT
,repo_updated BIGINT
,repo_forkId INTEGER
,repo_numForks INTEGER
,repo_numPulls INTEGER

View File

@ -6,6 +6,6 @@ CREATE TABLE IF NOT EXISTS spaces (
,space_description TEXT
,space_isPublic BOOLEAN
,space_createdBy INTEGER
,space_created INTEGER
,space_updated INTEGER
,space_created BIGINT
,space_updated BIGINT
);

View File

@ -3,8 +3,8 @@ CREATE TABLE IF NOT EXISTS tokens (
,token_type TEXT COLLATE NOCASE
,token_name TEXT
,token_principalId INTEGER
,token_expiresAt INTEGER
,token_grants INTEGER
,token_issuedAt INTEGER
,token_expiresAt BIGINT
,token_grants BIGINT
,token_issuedAt BIGINT
,token_createdBy INTEGER
);

View File

@ -0,0 +1,179 @@
// Copyright 2021 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 database
import (
"context"
"database/sql"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/types"
"github.com/jmoiron/sqlx"
)
var _ store.ServiceStore = (*ServiceStore)(nil)
// NewServiceStore returns a new ServiceStore.
func NewServiceStore(db *sqlx.DB) *ServiceStore {
return &ServiceStore{db}
}
// ServiceStore implements a ServiceStore backed by a relational
// database.
type ServiceStore struct {
db *sqlx.DB
}
// Find finds the service by id.
func (s *ServiceStore) Find(ctx context.Context, id int64) (*types.Service, error) {
dst := new(types.Service)
if err := s.db.GetContext(ctx, dst, serviceSelectID, id); err != nil {
return nil, processSQLErrorf(err, "Select by id query failed")
}
return 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 {
return nil, processSQLErrorf(err, "Select by uid query failed")
}
return 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)
if err != nil {
return processSQLErrorf(err, "Failed to bind service object")
}
if err = s.db.QueryRowContext(ctx, query, arg...).Scan(&sa.ID); err != nil {
return processSQLErrorf(err, "Insert query failed")
}
return nil
}
// Update updates the service.
func (s *ServiceStore) Update(ctx context.Context, sa *types.Service) error {
query, arg, err := s.db.BindNamed(serviceUpdate, sa)
if err != nil {
return processSQLErrorf(err, "Failed to bind service object")
}
if _, err = s.db.ExecContext(ctx, query, arg...); err != nil {
return processSQLErrorf(err, "Update query failed")
}
return err
}
// Delete deletes the service.
func (s *ServiceStore) Delete(ctx context.Context, id int64) error {
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return processSQLErrorf(err, "Failed to start a new transaction")
}
defer func(tx *sql.Tx) {
_ = tx.Rollback()
}(tx)
// delete the service
if _, err = tx.ExecContext(ctx, serviceDelete, id); err != nil {
return processSQLErrorf(err, "The delete query failed")
}
return tx.Commit()
}
// List returns a list of service for a specific parent.
func (s *ServiceStore) List(ctx context.Context) ([]*types.Service, error) {
dst := []*types.Service{}
err := s.db.SelectContext(ctx, &dst, serviceSelect)
if err != nil {
return nil, processSQLErrorf(err, "Failed executing default list query")
}
return dst, nil
}
// Count returns a count of service for a specific parent.
func (s *ServiceStore) Count(ctx context.Context) (int64, error) {
var count int64
err := s.db.QueryRowContext(ctx, serviceCount).Scan(&count)
if err != nil {
return 0, processSQLErrorf(err, "Failed executing count query")
}
return count, nil
}
const serviceCount = `
SELECT count(*)
FROM principals
WHERE principal_type = "service"
`
const serviceBase = `
SELECT
principal_id
,principal_uid
,principal_name
,principal_blocked
,principal_salt
,principal_created
,principal_updated
FROM principals
`
const serviceSelect = serviceBase + `
WHERE principal_type = "service"
ORDER BY principal_name ASC
`
const serviceSelectID = serviceBase + `
WHERE principal_type = "service" AND principal_id = $1
`
const serviceSelectUID = serviceBase + `
WHERE principal_type = "service" AND principal_uid = $1
`
const serviceDelete = `
DELETE FROM principals
WHERE principal_type = "service" AND principal_id = $1
`
const serviceInsert = `
INSERT INTO principals (
principal_type
,principal_uid
,principal_name
,principal_admin
,principal_blocked
,principal_salt
,principal_created
,principal_updated
) values (
"service"
,:principal_uid
,:principal_name
,:principal_admin
,:principal_blocked
,:principal_salt
,:principal_created
,:principal_updated
) RETURNING principal_id
`
const serviceUpdate = `
UPDATE principals
SET
principal_name = :principal_name
,principal_admin = :principal_admin
,principal_blocked = :principal_blocked
,principal_updated = :principal_updated
WHERE principal_type = "service" AND principal_id = :principal_id
`

View File

@ -181,8 +181,8 @@ const serviceAccountUpdate = `
UPDATE principals
SET
principal_name = :principal_name
,:principal_blocked = :principal_blocked
,:principal_salt = :principal_salt
,:principal_updated = :principal_updated
,principal_blocked = :principal_blocked
,principal_salt = :principal_salt
,principal_updated = :principal_updated
WHERE principal_type = "serviceaccount" AND principal_id = :principal_id
`

View File

@ -0,0 +1,76 @@
// Copyright 2021 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 database
import (
"context"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/internal/store/database/mutex"
"github.com/harness/gitness/types"
)
var _ store.ServiceStore = (*ServiceStoreSync)(nil)
// NewServiceStoreSync returns a new ServiceStoreSync.
func NewServiceStoreSync(base *ServiceStore) *ServiceStoreSync {
return &ServiceStoreSync{base}
}
// ServiceStoreSync synchronizes read and write access to the
// service store. This prevents race conditions when the database
// type is sqlite3.
type ServiceStoreSync struct {
base *ServiceStore
}
// Find finds the service by id.
func (s *ServiceStoreSync) Find(ctx context.Context, id int64) (*types.Service, error) {
mutex.RLock()
defer mutex.RUnlock()
return s.base.Find(ctx, id)
}
// FindUID finds the service by uid.
func (s *ServiceStoreSync) FindUID(ctx context.Context, uid string) (*types.Service, error) {
mutex.RLock()
defer mutex.RUnlock()
return s.base.FindUID(ctx, uid)
}
// Create saves the service.
func (s *ServiceStoreSync) Create(ctx context.Context, sa *types.Service) error {
mutex.RLock()
defer mutex.RUnlock()
return s.base.Create(ctx, sa)
}
// Update updates the service.
func (s *ServiceStoreSync) Update(ctx context.Context, sa *types.Service) error {
mutex.RLock()
defer mutex.RUnlock()
return s.base.Update(ctx, sa)
}
// Delete deletes the service.
func (s *ServiceStoreSync) Delete(ctx context.Context, id int64) error {
mutex.RLock()
defer mutex.RUnlock()
return s.base.Delete(ctx, id)
}
// List returns a list of service for a specific parent.
func (s *ServiceStoreSync) List(ctx context.Context) ([]*types.Service, error) {
mutex.RLock()
defer mutex.RUnlock()
return s.base.List(ctx)
}
// Count returns a count of service for a specific parent.
func (s *ServiceStoreSync) Count(ctx context.Context) (int64, error) {
mutex.RLock()
defer mutex.RUnlock()
return s.base.Count(ctx)
}

View File

@ -23,6 +23,7 @@ var WireSet = wire.NewSet(
ProvideDatabase,
ProvideUserStore,
ProvideServiceAccountStore,
ProvideServiceStore,
ProvideSpaceStore,
ProvideRepoStore,
ProvideTokenStore,
@ -61,6 +62,18 @@ func ProvideServiceAccountStore(db *sqlx.DB) store.ServiceAccountStore {
}
}
// ProvideServiceStore provides a service store.
func ProvideServiceStore(db *sqlx.DB) store.ServiceStore {
switch db.DriverName() {
case postgres:
return NewServiceStore(db)
default:
return NewServiceStoreSync(
NewServiceStore(db),
)
}
}
// ProvideSpaceStore provides a space store.
func ProvideSpaceStore(db *sqlx.DB) store.SpaceStore {
switch db.DriverName() {

View File

@ -64,6 +64,30 @@ type (
Count(ctx context.Context, parentType enum.ParentResourceType, parentID int64) (int64, error)
}
// ServiceStore defines the service data storage.
ServiceStore interface {
// Find finds the service by id.
Find(ctx context.Context, id int64) (*types.Service, error)
// FindUID finds the service by uid.
FindUID(ctx context.Context, uid string) (*types.Service, error)
// Create saves the service.
Create(ctx context.Context, sa *types.Service) error
// Update updates the service details.
Update(ctx context.Context, sa *types.Service) error
// Delete deletes the service.
Delete(ctx context.Context, id int64) error
// List returns a list of all services.
List(ctx context.Context) ([]*types.Service, error)
// Count returns a count of all services.
Count(ctx context.Context) (int64, error)
}
// SpaceStore defines the space data storage.
SpaceStore interface {
// Find the space by id.

34
types/check/password.go Normal file
View File

@ -0,0 +1,34 @@
// Copyright 2021 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 (
"fmt"
)
const (
minPasswordLength = 1
maxPasswordLength = 128
)
var (
// ErrPasswordLength is returned when the password
// is outside of the allowed length.
ErrPasswordLength = &ValidationError{
fmt.Sprintf("Password has to be within %d and %d characters", minPasswordLength, maxPasswordLength),
}
)
// Password returns true if the Password is valid.
// TODO: add proper password checks.
func Password(pw string) error {
// validate length
l := len(pw)
if l < minPasswordLength || l > maxPasswordLength {
return ErrPasswordLength
}
return nil
}

24
types/check/service.go Normal file
View File

@ -0,0 +1,24 @@
// Copyright 2021 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"
)
// Service returns true if the Service if valid.
func Service(sa *types.Service) error {
// validate UID
if err := UID(sa.UID); err != nil {
return err
}
// validate name
if err := Name(sa.Name); err != nil {
return err
}
return nil
}

View File

@ -22,12 +22,12 @@ func ServiceAccount(sa *types.ServiceAccount) error {
return err
}
// verify name
// validate name
if err := Name(sa.Name); err != nil {
return err
}
// verify parentType is valid
// validate parentType
if sa.ParentType != enum.ParentResourceTypeRepo && sa.ParentType != enum.ParentResourceTypeSpace {
return ErrServiceAccountParentTypeIsInvalid
}

View File

@ -17,13 +17,13 @@ const (
var (
// ErrEmailLen is returned when the email address
// exceeds the maximum number of characters.
// exceeds the range of allowed number of characters.
ErrEmailLen = &ValidationError{
fmt.Sprintf("Email address has to be within %d and %d characters", minEmailLength, maxEmailLength),
}
)
// User returns true if the User if valid.
// User returns true if the User is valid.
func User(user *types.User) error {
// validate UID
if err := UID(user.UID); err != nil {

View File

@ -64,4 +64,11 @@ type Config struct {
ContentSecurityPolicy string `envconfig:"GITNESS_HTTP_CONTENT_SECURITY_POLICY"`
ReferrerPolicy string `envconfig:"GITNESS_HTTP_REFERRER_POLICY"`
}
// Admin defines admin user params (no admin setup if either is empty)
Admin struct {
Name string `envconfig:"GITNESS_ADMIN_NAME"`
Email string `envconfig:"GITNESS_ADMIN_EMAIL"`
Password string `envconfig:"GITNESS_ADMIN_PASSWORD"`
}
}

View File

@ -12,6 +12,7 @@ const (
ResourceTypeRepo ResourceType = "REPOSITORY"
ResourceTypeUser ResourceType = "USER"
ResourceTypeServiceAccount ResourceType = "SERVICEACCOUNT"
ResourceTypeService ResourceType = "SERVICE"
// ResourceType_Branch ResourceType = "BRANCH"
)
@ -51,10 +52,21 @@ const (
const (
/*
----- REPOSITORY -----
----- SERVICE ACCOUNT -----
*/
PermissionServiceAccountCreate Permission = "serviceaccount_create"
PermissionServiceAccountView Permission = "serviceaccount_view"
PermissionServiceAccountEdit Permission = "serviceaccount_edit"
PermissionServiceAccountDelete Permission = "serviceaccount_delete"
)
const (
/*
----- SERVICE -----
*/
PermissionServiceCreate Permission = "service_create"
PermissionServiceView Permission = "service_view"
PermissionServiceEdit Permission = "service_edit"
PermissionServiceDelete Permission = "service_delete"
PermissionServiceEditAdmin Permission = "service_editadmin"
)

View File

@ -14,6 +14,7 @@ const (
// Represents a path to a resource (e.g. space) that can be used to address the resource.
type Path struct {
// TODO: int64 ID doesn't match DB
ID int64 `db:"path_id" json:"id"`
Value string `db:"path_value" json:"value"`
IsAlias bool `db:"path_isAlias" json:"isAlias"`

View File

@ -10,7 +10,7 @@ import "github.com/harness/gitness/types/enum"
type (
// Represents the identity of an acting entity (User, ServiceAccount, Service).
Principal struct {
// ID is the internal identifier of a principal (primary key)
// TODO: int64 ID doesn't match DB
ID int64 `db:"principal_id" json:"-"`
UID string `db:"principal_uid" json:"uid"`
Type enum.PrincipalType `db:"principal_type" json:"type"`
@ -61,7 +61,7 @@ func PrincipalFromService(s *Service) *Principal {
UID: s.UID,
Type: enum.PrincipalTypeService,
Name: s.Name,
Admin: true,
Admin: s.Admin,
Blocked: s.Blocked,
Salt: s.Salt,
Created: s.Created,

View File

@ -10,7 +10,7 @@ import (
// Repository represents a code repository.
type Repository struct {
// Core properties
// TODO: int64 ID doesn't match DB
ID int64 `db:"repo_id" json:"id"`
SpaceID int64 `db:"repo_spaceId" json:"spaceId"`
PathName string `db:"repo_pathName" json:"pathName"`

View File

@ -5,12 +5,10 @@
// Package types defines common data structures.
package types
import "github.com/harness/gitness/types/enum"
type (
// Service is a principal representing a different internal service that runs alongside gitness.
Service struct {
// Fields from Principal (without admin, as it's always admin for now)
// Fields from Principal
ID int64 `db:"principal_id" json:"-"`
UID string `db:"principal_uid" json:"uid"`
Name string `db:"principal_name" json:"name"`
@ -20,12 +18,4 @@ type (
Created int64 `db:"principal_created" json:"created"`
Updated int64 `db:"principal_updated" json:"updated"`
}
// ServiceAccountInput store details used to
// create or update a service account.
ServiceInput struct {
Name *string `json:"name"`
ParentType *enum.ParentResourceType `json:"parentType"`
ParentID *int64 `json:"parentId"`
}
)

View File

@ -21,6 +21,7 @@ https://stackoverflow.com/questions/4048151/what-are-the-options-for-storing-hie
https://www.slideshare.net/billkarwin/models-for-hierarchical-data
*/
type Space struct {
// TODO: int64 ID doesn't match DB
ID int64 `db:"space_id" json:"id"`
ParentID int64 `db:"space_parentId" json:"parentId"`
PathName string `db:"space_pathName" json:"pathName"`

View File

@ -10,6 +10,7 @@ import (
// Represents server side infos stored for tokens we distribute.
type Token struct {
// TODO: int64 ID doesn't match DB
ID int64 `db:"token_id" json:"id"`
Type enum.TokenType `db:"token_type" json:"type"`
Name string `db:"token_name" json:"name"`