mirror of
https://github.com/harness/drone.git
synced 2025-05-06 11:51:10 +08:00
[Harness] Adding JWT/PAT/SAT Support, Harness Clients, Inline User/ServiceAccount Creation, harness Build flag, ... (#22)
This change adds the initial stepping stones for harness integration: - Authentication: JWT/PAT/SAT support - Authorization: ACL integration (acl currently denies requests as gitness hasn't been integrated yet) - Remote Clients for Token, User, ServiceAccount, ACL - User Integration: Syncs harness users during authentication if unknown - SA integration: syncs harness service accounts during authentication if unknown - Initial harness API: THIS WILL BE CHANGED IN THE FUTURE! - single harness subpackage (all marked with harness build flag) - harness & standalone wire + make build commands
This commit is contained in:
parent
5baf42d5ca
commit
4668e94027
18
.harness.env
Normal file
18
.harness.env
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
GITNESS_TRACE=true
|
||||||
|
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"
|
@ -0,0 +1 @@
|
|||||||
|
GITNESS_TRACE=true
|
24
Makefile
24
Makefile
@ -35,13 +35,19 @@ tools: $(tools) ## Install tools required for the build
|
|||||||
mocks: $(mocks)
|
mocks: $(mocks)
|
||||||
@echo "Generating Test Mocks"
|
@echo "Generating Test Mocks"
|
||||||
|
|
||||||
generate: $(mocks) cli/server/wire_gen.go mocks/mock_client.go
|
wire: cli/server/harness.wire_gen.go cli/server/standalone.wire_gen.go
|
||||||
|
|
||||||
|
generate: $(mocks) wire mocks/mock_client.go
|
||||||
@echo "Generating Code"
|
@echo "Generating Code"
|
||||||
|
|
||||||
build: generate ## Build the gitness service binary
|
build: generate ## Build the gitness service binary
|
||||||
@echo "Building Gitness Server"
|
@echo "Building Gitness Server"
|
||||||
go build -ldflags="-X github.com/harness/gitness/version.GitCommit=${GIT_COMMIT} -X github.com/harness/gitness/version.Version.Major=${GITNESS_VERSION}" -o ./gitness .
|
go build -ldflags="-X github.com/harness/gitness/version.GitCommit=${GIT_COMMIT} -X github.com/harness/gitness/version.Version.Major=${GITNESS_VERSION}" -o ./gitness .
|
||||||
|
|
||||||
|
harness-build: generate ## Build the gitness service binary for harness embedded mode
|
||||||
|
@echo "Building Gitness Server for Harness"
|
||||||
|
go build -tags=harness -ldflags="-X github.com/harness/gitness/version.GitCommit=${GIT_COMMIT} -X github.com/harness/gitness/version.Version.Major=${GITNESS_VERSION}" -o ./gitness .
|
||||||
|
|
||||||
test: generate ## Run the go tests
|
test: generate ## Run the go tests
|
||||||
@echo "Running tests"
|
@echo "Running tests"
|
||||||
go test -v -coverprofile=coverage.out ./internal/...
|
go test -v -coverprofile=coverage.out ./internal/...
|
||||||
@ -114,9 +120,19 @@ lint: tools generate # lint the golang code
|
|||||||
# Some code generation can be slow, so we only run it if
|
# Some code generation can be slow, so we only run it if
|
||||||
# the source file has changed.
|
# the source file has changed.
|
||||||
###########################################
|
###########################################
|
||||||
cli/server/wire_gen.go: cli/server/wire.go ## Update the wire dependency injection if wire.go has changed.
|
cli/server/harness.wire_gen.go: cli/server/harness.wire.go ## Update the wire dependency injection if harness.wire.go has changed.
|
||||||
@echo "Updating wire_gen.go"
|
@echo "Updating harness.wire_gen.go"
|
||||||
go generate ./cli/server/wire_gen.go
|
@go run github.com/google/wire/cmd/wire gen -tags=harness -output_file_prefix="harness." github.com/harness/gitness/cli/server
|
||||||
|
@perl -ni -e 'print unless /go:generate/' cli/server/harness.wire_gen.go
|
||||||
|
@perl -i -pe's/\+build !wireinject/\+build !wireinject,harness/g' cli/server/harness.wire_gen.go
|
||||||
|
@perl -i -pe's/go:build !wireinject/go:build !wireinject && harness/g' cli/server/harness.wire_gen.go
|
||||||
|
|
||||||
|
cli/server/standalone.wire_gen.go: cli/server/standalone.wire.go ## Update the wire dependency injection if standalone.wire.go has changed.
|
||||||
|
@echo "Updating standalone.wire_gen.go"
|
||||||
|
@go run github.com/google/wire/cmd/wire gen -tags= -output_file_prefix="standalone." github.com/harness/gitness/cli/server
|
||||||
|
@perl -ni -e 'print unless /go:generate/' cli/server/standalone.wire_gen.go
|
||||||
|
@perl -i -pe's/\+build !wireinject/\+build !wireinject,!harness/g' cli/server/standalone.wire_gen.go
|
||||||
|
@perl -i -pe's/go:build !wireinject/go:build !wireinject && !harness/g' cli/server/standalone.wire_gen.go
|
||||||
|
|
||||||
mocks/mock_client.go: internal/store/store.go client/client.go
|
mocks/mock_client.go: internal/store/store.go client/client.go
|
||||||
go generate mocks/mock.go
|
go generate mocks/mock.go
|
||||||
|
@ -19,11 +19,11 @@ type registerCommand struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *registerCommand) run(*kingpin.ParseContext) error {
|
func (c *registerCommand) run(*kingpin.ParseContext) error {
|
||||||
username, password := util.Credentials()
|
username, name, email, password := util.Registration()
|
||||||
httpClient := client.New(c.server)
|
httpClient := client.New(c.server)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
ts, err := httpClient.Register(ctx, username, password)
|
ts, err := httpClient.Register(ctx, username, name, email, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const userTmpl = `
|
const userTmpl = `
|
||||||
|
uid: {{ .UID }}
|
||||||
|
name: {{ .Name }}
|
||||||
email: {{ .Email }}
|
email: {{ .Email }}
|
||||||
admin: {{ .Admin }}
|
admin: {{ .Admin }}
|
||||||
`
|
`
|
||||||
|
@ -5,30 +5,14 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/harness/gitness/types"
|
"github.com/harness/gitness/types"
|
||||||
|
|
||||||
"github.com/kelseyhightower/envconfig"
|
"github.com/kelseyhightower/envconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
// legacy environment variables. the key is the legacy
|
|
||||||
// variable name, and the value is the new variable name.
|
|
||||||
var legacy = map[string]string{
|
|
||||||
// none defined
|
|
||||||
}
|
|
||||||
|
|
||||||
// load returns the system configuration from the
|
// load returns the system configuration from the
|
||||||
// host environment.
|
// host environment.
|
||||||
func load() (*types.Config, error) {
|
func load() (*types.Config, error) {
|
||||||
// loop through legacy environment variable and, if set
|
|
||||||
// rewrite to the new variable name.
|
|
||||||
for k, v := range legacy {
|
|
||||||
if s, ok := os.LookupEnv(k); ok {
|
|
||||||
os.Setenv(v, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
config := new(types.Config)
|
config := new(types.Config)
|
||||||
// read the configuration from the environment and
|
// read the configuration from the environment and
|
||||||
// populate the configuration structure.
|
// populate the configuration structure.
|
||||||
|
41
cli/server/harness.wire.go
Normal file
41
cli/server/harness.wire.go
Normal 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.
|
||||||
|
|
||||||
|
//go:build wireinject && harness
|
||||||
|
// +build wireinject,harness
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/harness/gitness/harness"
|
||||||
|
"github.com/harness/gitness/harness/auth/authn"
|
||||||
|
"github.com/harness/gitness/harness/auth/authz"
|
||||||
|
"github.com/harness/gitness/harness/client"
|
||||||
|
"github.com/harness/gitness/harness/router/translator"
|
||||||
|
"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"
|
||||||
|
|
||||||
|
"github.com/google/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
func initSystem(config *types.Config) (*system, error) {
|
||||||
|
wire.Build(
|
||||||
|
newSystem,
|
||||||
|
database.WireSet,
|
||||||
|
memory.WireSet,
|
||||||
|
router.WireSet,
|
||||||
|
server.WireSet,
|
||||||
|
cron.WireSet,
|
||||||
|
harness.LoadConfig,
|
||||||
|
authn.WireSet,
|
||||||
|
authz.WireSet,
|
||||||
|
client.WireSet,
|
||||||
|
translator.WireSet,
|
||||||
|
)
|
||||||
|
return &system{}, nil
|
||||||
|
}
|
73
cli/server/harness.wire_gen.go
Normal file
73
cli/server/harness.wire_gen.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
// Code generated by Wire. DO NOT EDIT.
|
||||||
|
|
||||||
|
//go:build !wireinject && harness
|
||||||
|
// +build !wireinject,harness
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/harness/gitness/harness"
|
||||||
|
"github.com/harness/gitness/harness/auth/authn"
|
||||||
|
"github.com/harness/gitness/harness/auth/authz"
|
||||||
|
"github.com/harness/gitness/harness/client"
|
||||||
|
"github.com/harness/gitness/harness/router/translator"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Injectors from harness.wire.go:
|
||||||
|
|
||||||
|
func initSystem(config *types.Config) (*system, error) {
|
||||||
|
requestTranslator := translator.ProvideRequestTranslator()
|
||||||
|
systemStore := memory.New(config)
|
||||||
|
db, err := database.ProvideDatabase(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
userStore := database.ProvideUserStore(db)
|
||||||
|
spaceStore := database.ProvideSpaceStore(db)
|
||||||
|
repoStore := database.ProvideRepoStore(db)
|
||||||
|
tokenStore := database.ProvideTokenStore(db)
|
||||||
|
serviceAccountStore := database.ProvideServiceAccountStore(db)
|
||||||
|
harnessConfig, err := harness.LoadConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
serviceJWTProvider, err := client.ProvideServiceJWTProvider(harnessConfig)
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
authenticator, err := authn.ProvideAuthenticator(userStore, tokenClient, userClient, harnessConfig, serviceAccountClient, serviceAccountStore)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
aclClient, err := client.ProvideACLClient(serviceJWTProvider, harnessConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
authorizer := authz.ProvideAuthorizer(aclClient)
|
||||||
|
handler, err := router.ProvideHTTPHandler(requestTranslator, systemStore, userStore, spaceStore, repoStore, tokenStore, serviceAccountStore, authenticator, authorizer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
serverServer := server.ProvideServer(config, handler)
|
||||||
|
nightly := cron.NewNightly()
|
||||||
|
serverSystem := newSystem(serverServer, nightly)
|
||||||
|
return serverSystem, nil
|
||||||
|
}
|
@ -2,8 +2,8 @@
|
|||||||
// Use of this source code is governed by the Polyform Free Trial License
|
// 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.
|
// that can be found in the LICENSE.md file for this repository.
|
||||||
|
|
||||||
//go:build wireinject
|
//go:build wireinject && !harness
|
||||||
// +build wireinject
|
// +build wireinject,!harness
|
||||||
|
|
||||||
package server
|
package server
|
||||||
|
|
||||||
@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/harness/gitness/internal/auth/authz"
|
"github.com/harness/gitness/internal/auth/authz"
|
||||||
"github.com/harness/gitness/internal/cron"
|
"github.com/harness/gitness/internal/cron"
|
||||||
"github.com/harness/gitness/internal/router"
|
"github.com/harness/gitness/internal/router"
|
||||||
|
"github.com/harness/gitness/internal/router/translator"
|
||||||
"github.com/harness/gitness/internal/server"
|
"github.com/harness/gitness/internal/server"
|
||||||
"github.com/harness/gitness/internal/store/database"
|
"github.com/harness/gitness/internal/store/database"
|
||||||
"github.com/harness/gitness/internal/store/memory"
|
"github.com/harness/gitness/internal/store/memory"
|
||||||
@ -24,14 +25,15 @@ import (
|
|||||||
|
|
||||||
func initSystem(ctx context.Context, config *types.Config) (*system, error) {
|
func initSystem(ctx context.Context, config *types.Config) (*system, error) {
|
||||||
wire.Build(
|
wire.Build(
|
||||||
|
newSystem,
|
||||||
database.WireSet,
|
database.WireSet,
|
||||||
memory.WireSet,
|
memory.WireSet,
|
||||||
router.WireSet,
|
router.WireSet,
|
||||||
server.WireSet,
|
server.WireSet,
|
||||||
cron.WireSet,
|
cron.WireSet,
|
||||||
newSystem,
|
|
||||||
authn.WireSet,
|
authn.WireSet,
|
||||||
authz.WireSet,
|
authz.WireSet,
|
||||||
|
translator.WireSet,
|
||||||
)
|
)
|
||||||
return &system{}, nil
|
return &system{}, nil
|
||||||
}
|
}
|
@ -1,8 +1,7 @@
|
|||||||
// Code generated by Wire. DO NOT EDIT.
|
// Code generated by Wire. DO NOT EDIT.
|
||||||
|
|
||||||
//go:generate go run github.com/google/wire/cmd/wire
|
//go:build !wireinject && !harness
|
||||||
//go:build !wireinject
|
// +build !wireinject,!harness
|
||||||
// +build !wireinject
|
|
||||||
|
|
||||||
package server
|
package server
|
||||||
|
|
||||||
@ -13,15 +12,17 @@ import (
|
|||||||
"github.com/harness/gitness/internal/auth/authz"
|
"github.com/harness/gitness/internal/auth/authz"
|
||||||
"github.com/harness/gitness/internal/cron"
|
"github.com/harness/gitness/internal/cron"
|
||||||
"github.com/harness/gitness/internal/router"
|
"github.com/harness/gitness/internal/router"
|
||||||
|
"github.com/harness/gitness/internal/router/translator"
|
||||||
"github.com/harness/gitness/internal/server"
|
"github.com/harness/gitness/internal/server"
|
||||||
"github.com/harness/gitness/internal/store/database"
|
"github.com/harness/gitness/internal/store/database"
|
||||||
"github.com/harness/gitness/internal/store/memory"
|
"github.com/harness/gitness/internal/store/memory"
|
||||||
"github.com/harness/gitness/types"
|
"github.com/harness/gitness/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Injectors from wire.go:
|
// Injectors from standalone.wire.go:
|
||||||
|
|
||||||
func initSystem(ctx context.Context, config *types.Config) (*system, error) {
|
func initSystem(ctx context.Context, config *types.Config) (*system, error) {
|
||||||
|
requestTranslator := translator.ProvideRequestTranslator()
|
||||||
systemStore := memory.New(config)
|
systemStore := memory.New(config)
|
||||||
db, err := database.ProvideDatabase(ctx, config)
|
db, err := database.ProvideDatabase(ctx, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -32,9 +33,9 @@ func initSystem(ctx context.Context, config *types.Config) (*system, error) {
|
|||||||
repoStore := database.ProvideRepoStore(db)
|
repoStore := database.ProvideRepoStore(db)
|
||||||
tokenStore := database.ProvideTokenStore(db)
|
tokenStore := database.ProvideTokenStore(db)
|
||||||
serviceAccountStore := database.ProvideServiceAccountStore(db)
|
serviceAccountStore := database.ProvideServiceAccountStore(db)
|
||||||
authenticator := authn.NewTokenAuthenticator(userStore, serviceAccountStore, tokenStore)
|
authenticator := authn.ProvideAuthenticator(userStore, serviceAccountStore, tokenStore)
|
||||||
authorizer := authz.NewUnsafeAuthorizer()
|
authorizer := authz.ProvideAuthorizer()
|
||||||
handler, err := router.New(systemStore, userStore, spaceStore, repoStore, tokenStore, serviceAccountStore, authenticator, authorizer)
|
handler, err := router.ProvideHTTPHandler(requestTranslator, systemStore, userStore, spaceStore, repoStore, tokenStore, serviceAccountStore, authenticator, authorizer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
@ -101,6 +101,11 @@ func Config() (string, error) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Registration returns the username, name, email and password from stdin.
|
||||||
|
func Registration() (string, string, string, string) {
|
||||||
|
return Username(), Name(), Email(), Password()
|
||||||
|
}
|
||||||
|
|
||||||
// Credentials returns the username and password from stdin.
|
// Credentials returns the username and password from stdin.
|
||||||
func Credentials() (string, string) {
|
func Credentials() (string, string) {
|
||||||
return Username(), Password()
|
return Username(), Password()
|
||||||
@ -116,6 +121,26 @@ func Username() string {
|
|||||||
return strings.TrimSpace(username)
|
return strings.TrimSpace(username)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name returns the name from stdin.
|
||||||
|
func Name() string {
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
|
||||||
|
fmt.Print("Enter Name: ")
|
||||||
|
name, _ := reader.ReadString('\n')
|
||||||
|
|
||||||
|
return strings.TrimSpace(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Email returns the email from stdin.
|
||||||
|
func Email() string {
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
|
||||||
|
fmt.Print("Enter Email: ")
|
||||||
|
email, _ := reader.ReadString('\n')
|
||||||
|
|
||||||
|
return strings.TrimSpace(email)
|
||||||
|
}
|
||||||
|
|
||||||
// Password returns the password from stdin.
|
// Password returns the password from stdin.
|
||||||
func Password() string {
|
func Password() string {
|
||||||
fmt.Print("Enter Password: ")
|
fmt.Print("Enter Password: ")
|
||||||
|
@ -69,9 +69,12 @@ func (c *HTTPClient) Login(ctx context.Context, username, password string) (*typ
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Register registers a new user and returns a JWT token.
|
// Register registers a new user and returns a JWT token.
|
||||||
func (c *HTTPClient) Register(ctx context.Context, username, password string) (*types.TokenResponse, error) {
|
func (c *HTTPClient) Register(ctx context.Context,
|
||||||
|
username, name, email, password string) (*types.TokenResponse, error) {
|
||||||
form := &url.Values{}
|
form := &url.Values{}
|
||||||
form.Add("username", username)
|
form.Add("username", username)
|
||||||
|
form.Add("name", name)
|
||||||
|
form.Add("email", email)
|
||||||
form.Add("password", password)
|
form.Add("password", password)
|
||||||
out := new(types.TokenResponse)
|
out := new(types.TokenResponse)
|
||||||
uri := fmt.Sprintf("%s/api/v1/register", c.base)
|
uri := fmt.Sprintf("%s/api/v1/register", c.base)
|
||||||
|
@ -16,7 +16,7 @@ type Client interface {
|
|||||||
Login(ctx context.Context, username, password string) (*types.TokenResponse, error)
|
Login(ctx context.Context, username, password string) (*types.TokenResponse, error)
|
||||||
|
|
||||||
// Register registers a new user and returns a JWT token.
|
// Register registers a new user and returns a JWT token.
|
||||||
Register(ctx context.Context, username, password string) (*types.TokenResponse, error)
|
Register(ctx context.Context, username, name, email, password string) (*types.TokenResponse, error)
|
||||||
|
|
||||||
// Self returns the currently authenticated user.
|
// Self returns the currently authenticated user.
|
||||||
Self(ctx context.Context) (*types.User, error)
|
Self(ctx context.Context) (*types.User, error)
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
package account
|
package account
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/harness/gitness/internal/api/render"
|
"github.com/harness/gitness/internal/api/render"
|
||||||
@ -25,11 +26,14 @@ func HandleLogin(userStore store.UserStore, system store.SystemStore, tokenStore
|
|||||||
|
|
||||||
username := r.FormValue("username")
|
username := r.FormValue("username")
|
||||||
password := r.FormValue("password")
|
password := r.FormValue("password")
|
||||||
user, err := userStore.FindEmail(ctx, username)
|
user, err := userStore.FindUID(ctx, username)
|
||||||
|
if errors.Is(err, store.ErrResourceNotFound) {
|
||||||
|
user, err = userStore.FindEmail(ctx, username)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug().Err(err).
|
log.Debug().Err(err).
|
||||||
Str("user", username).
|
Msgf("cannot find user with '%s'", username)
|
||||||
Msg("cannot find user")
|
|
||||||
|
|
||||||
// always give not found error as extra security measurement.
|
// always give not found error as extra security measurement.
|
||||||
render.NotFound(w)
|
render.NotFound(w)
|
||||||
@ -42,7 +46,7 @@ func HandleLogin(userStore store.UserStore, system store.SystemStore, tokenStore
|
|||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug().Err(err).
|
log.Debug().Err(err).
|
||||||
Str("user", username).
|
Str("user_uid", user.UID).
|
||||||
Msg("invalid password")
|
Msg("invalid password")
|
||||||
|
|
||||||
render.NotFound(w)
|
render.NotFound(w)
|
||||||
@ -52,7 +56,7 @@ func HandleLogin(userStore store.UserStore, system store.SystemStore, tokenStore
|
|||||||
token, jwtToken, err := token.CreateUserSession(ctx, tokenStore, user, "login")
|
token, jwtToken, err := token.CreateUserSession(ctx, tokenStore, user, "login")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).
|
log.Err(err).
|
||||||
Str("user", username).
|
Str("user_uid", user.UID).
|
||||||
Msg("failed to generate token")
|
Msg("failed to generate token")
|
||||||
|
|
||||||
render.InternalError(w)
|
render.InternalError(w)
|
||||||
|
@ -26,23 +26,25 @@ func HandleRegister(userStore store.UserStore, system store.SystemStore, tokenSt
|
|||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
log := hlog.FromRequest(r)
|
log := hlog.FromRequest(r)
|
||||||
|
|
||||||
username := r.FormValue("username")
|
uid := r.FormValue("username")
|
||||||
|
name := r.FormValue("name")
|
||||||
|
email := r.FormValue("email")
|
||||||
password := r.FormValue("password")
|
password := r.FormValue("password")
|
||||||
|
|
||||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).
|
log.Err(err).
|
||||||
Str("email", username).
|
Str("uid", uid).
|
||||||
Msg("Failed to hash password")
|
Msg("Failed to hash password")
|
||||||
|
|
||||||
render.InternalError(w)
|
render.InternalError(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: allow to provide email and name separately ...
|
|
||||||
user := &types.User{
|
user := &types.User{
|
||||||
Name: username,
|
UID: uid,
|
||||||
Email: username,
|
Name: name,
|
||||||
|
Email: email,
|
||||||
Password: string(hash),
|
Password: string(hash),
|
||||||
Salt: uniuri.NewLen(uniuri.UUIDLen),
|
Salt: uniuri.NewLen(uniuri.UUIDLen),
|
||||||
Created: time.Now().UnixMilli(),
|
Created: time.Now().UnixMilli(),
|
||||||
@ -51,7 +53,7 @@ func HandleRegister(userStore store.UserStore, system store.SystemStore, tokenSt
|
|||||||
|
|
||||||
if err = check.User(user); err != nil {
|
if err = check.User(user); err != nil {
|
||||||
log.Debug().Err(err).
|
log.Debug().Err(err).
|
||||||
Str("email", username).
|
Str("uid", uid).
|
||||||
Msg("invalid user input")
|
Msg("invalid user input")
|
||||||
|
|
||||||
render.UserfiedErrorOrInternal(w, err)
|
render.UserfiedErrorOrInternal(w, err)
|
||||||
@ -60,7 +62,7 @@ func HandleRegister(userStore store.UserStore, system store.SystemStore, tokenSt
|
|||||||
|
|
||||||
if err = userStore.Create(ctx, user); err != nil {
|
if err = userStore.Create(ctx, user); err != nil {
|
||||||
log.Err(err).
|
log.Err(err).
|
||||||
Str("email", username).
|
Str("uid", uid).
|
||||||
Msg("Failed to create user")
|
Msg("Failed to create user")
|
||||||
|
|
||||||
render.InternalError(w)
|
render.InternalError(w)
|
||||||
@ -74,8 +76,7 @@ func HandleRegister(userStore store.UserStore, system store.SystemStore, tokenSt
|
|||||||
user.Admin = true
|
user.Admin = true
|
||||||
if err = userStore.Update(ctx, user); err != nil {
|
if err = userStore.Update(ctx, user); err != nil {
|
||||||
log.Err(err).
|
log.Err(err).
|
||||||
Str("email", username).
|
Str("user_uid", user.UID).
|
||||||
Int64("user_id", user.ID).
|
|
||||||
Msg("Failed to enable admin user")
|
Msg("Failed to enable admin user")
|
||||||
|
|
||||||
render.InternalError(w)
|
render.InternalError(w)
|
||||||
@ -86,7 +87,7 @@ func HandleRegister(userStore store.UserStore, system store.SystemStore, tokenSt
|
|||||||
token, jwtToken, err := token.CreateUserSession(ctx, tokenStore, user, "register")
|
token, jwtToken, err := token.CreateUserSession(ctx, tokenStore, user, "register")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).
|
log.Err(err).
|
||||||
Str("user", username).
|
Str("user", uid).
|
||||||
Msg("failed to generate token")
|
Msg("failed to generate token")
|
||||||
|
|
||||||
render.InternalError(w)
|
render.InternalError(w)
|
||||||
|
@ -13,6 +13,7 @@ type CreatePathRequest struct {
|
|||||||
|
|
||||||
// CreateServiceAccountRequest used for service account creation apis.
|
// CreateServiceAccountRequest used for service account creation apis.
|
||||||
type CreateServiceAccountRequest struct {
|
type CreateServiceAccountRequest struct {
|
||||||
|
UID string `json:"uid"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
ParentType enum.ParentResourceType `json:"parentType"`
|
ParentType enum.ParentResourceType `json:"parentType"`
|
||||||
ParentID int64 `json:"parentId"`
|
ParentID int64 `json:"parentId"`
|
||||||
|
@ -36,6 +36,7 @@ func HandleCreate(guard *guard.Guard, saStore store.ServiceAccountStore) http.Ha
|
|||||||
}
|
}
|
||||||
|
|
||||||
sa := &types.ServiceAccount{
|
sa := &types.ServiceAccount{
|
||||||
|
UID: in.UID,
|
||||||
Name: in.Name,
|
Name: in.Name,
|
||||||
Salt: uniuri.NewLen(uniuri.UUIDLen),
|
Salt: uniuri.NewLen(uniuri.UUIDLen),
|
||||||
Created: time.Now().UnixMilli(),
|
Created: time.Now().UnixMilli(),
|
||||||
|
@ -20,7 +20,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type userCreateInput struct {
|
type userCreateInput struct {
|
||||||
Username string `json:"email"`
|
UID string `json:"uid"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email string `json:"email"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
Admin bool `json:"admin"`
|
Admin bool `json:"admin"`
|
||||||
}
|
}
|
||||||
@ -42,7 +44,7 @@ func HandleCreate(userStore store.UserStore) http.HandlerFunc {
|
|||||||
hash, err := bcrypt.GenerateFromPassword([]byte(in.Password), bcrypt.DefaultCost)
|
hash, err := bcrypt.GenerateFromPassword([]byte(in.Password), bcrypt.DefaultCost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).
|
log.Err(err).
|
||||||
Str("email", in.Username).
|
Str("uid", in.UID).
|
||||||
Msg("Failed to hash password")
|
Msg("Failed to hash password")
|
||||||
|
|
||||||
render.InternalError(w)
|
render.InternalError(w)
|
||||||
@ -50,7 +52,9 @@ func HandleCreate(userStore store.UserStore) http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
user := &types.User{
|
user := &types.User{
|
||||||
Email: in.Username,
|
UID: in.UID,
|
||||||
|
Name: in.Name,
|
||||||
|
Email: in.Email,
|
||||||
Admin: in.Admin,
|
Admin: in.Admin,
|
||||||
Password: string(hash),
|
Password: string(hash),
|
||||||
Salt: uniuri.NewLen(uniuri.UUIDLen),
|
Salt: uniuri.NewLen(uniuri.UUIDLen),
|
||||||
@ -59,7 +63,7 @@ func HandleCreate(userStore store.UserStore) http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
if err = check.User(user); err != nil {
|
if err = check.User(user); err != nil {
|
||||||
log.Debug().Err(err).
|
log.Debug().Err(err).
|
||||||
Str("email", user.Email).
|
Str("uid", user.UID).
|
||||||
Msg("invalid user input")
|
Msg("invalid user input")
|
||||||
|
|
||||||
render.UserfiedErrorOrInternal(w, err)
|
render.UserfiedErrorOrInternal(w, err)
|
||||||
@ -69,7 +73,7 @@ func HandleCreate(userStore store.UserStore) http.HandlerFunc {
|
|||||||
err = userStore.Create(ctx, user)
|
err = userStore.Create(ctx, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).
|
log.Err(err).
|
||||||
Str("email", user.Email).
|
Str("uid", user.UID).
|
||||||
Msg("failed to create user")
|
Msg("failed to create user")
|
||||||
|
|
||||||
render.UserfiedErrorOrInternal(w, err)
|
render.UserfiedErrorOrInternal(w, err)
|
||||||
|
@ -30,11 +30,10 @@ func HandleDelete(userStore store.UserStore, tokenStore store.TokenStore) http.H
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = userStore.Delete(ctx, user)
|
err = userStore.Delete(ctx, user.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).
|
log.Error().Err(err).
|
||||||
Int64("user_id", user.ID).
|
Str("user_uid", user.UID).
|
||||||
Str("user_email", user.Email).
|
|
||||||
Msg("failed to delete user")
|
Msg("failed to delete user")
|
||||||
|
|
||||||
render.UserfiedErrorOrInternal(w, err)
|
render.UserfiedErrorOrInternal(w, err)
|
||||||
|
@ -43,8 +43,7 @@ func HandleUpdate(userStore store.UserStore) http.HandlerFunc {
|
|||||||
hash, err := hashPassword([]byte(ptr.ToString(in.Password)), bcrypt.DefaultCost)
|
hash, err := hashPassword([]byte(ptr.ToString(in.Password)), bcrypt.DefaultCost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).
|
log.Err(err).
|
||||||
Int64("user_id", user.ID).
|
Str("user_uid", user.UID).
|
||||||
Str("user_email", user.Email).
|
|
||||||
Msg("Failed to hash password")
|
Msg("Failed to hash password")
|
||||||
|
|
||||||
render.InternalError(w)
|
render.InternalError(w)
|
||||||
@ -67,8 +66,7 @@ func HandleUpdate(userStore store.UserStore) http.HandlerFunc {
|
|||||||
hash, err := bcrypt.GenerateFromPassword([]byte(ptr.ToString(in.Password)), bcrypt.DefaultCost)
|
hash, err := bcrypt.GenerateFromPassword([]byte(ptr.ToString(in.Password)), bcrypt.DefaultCost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).
|
log.Err(err).
|
||||||
Int64("user_id", user.ID).
|
Str("user_uid", user.UID).
|
||||||
Str("user_email", user.Email).
|
|
||||||
Msg("Failed to hash password")
|
Msg("Failed to hash password")
|
||||||
|
|
||||||
render.InternalError(w)
|
render.InternalError(w)
|
||||||
@ -78,8 +76,7 @@ func HandleUpdate(userStore store.UserStore) http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
if err := check.User(user); err != nil {
|
if err := check.User(user); err != nil {
|
||||||
log.Debug().Err(err).
|
log.Debug().Err(err).
|
||||||
Int64("user_id", user.ID).
|
Str("user_uid", user.UID).
|
||||||
Str("user_email", user.Email).
|
|
||||||
Msg("invalid user input")
|
Msg("invalid user input")
|
||||||
|
|
||||||
render.UserfiedErrorOrInternal(w, err)
|
render.UserfiedErrorOrInternal(w, err)
|
||||||
@ -91,8 +88,7 @@ func HandleUpdate(userStore store.UserStore) http.HandlerFunc {
|
|||||||
err := userStore.Update(ctx, user)
|
err := userStore.Update(ctx, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).
|
log.Err(err).
|
||||||
Int64("user_id", user.ID).
|
Str("user_uid", user.UID).
|
||||||
Str("user_email", user.Email).
|
|
||||||
Msg("Failed to update the usser")
|
Msg("Failed to update the usser")
|
||||||
|
|
||||||
render.UserfiedErrorOrInternal(w, err)
|
render.UserfiedErrorOrInternal(w, err)
|
||||||
|
@ -18,8 +18,6 @@ func HlogHandler() func(http.Handler) http.Handler {
|
|||||||
return hlog.AccessHandler(
|
return hlog.AccessHandler(
|
||||||
func(r *http.Request, status, size int, duration time.Duration) {
|
func(r *http.Request, status, size int, duration time.Duration) {
|
||||||
hlog.FromRequest(r).Info().
|
hlog.FromRequest(r).Info().
|
||||||
Str("method", r.Method).
|
|
||||||
Stringer("url", r.URL).
|
|
||||||
Int("status_code", status).
|
Int("status_code", status).
|
||||||
Int("response_size_bytes", size).
|
Int("response_size_bytes", size).
|
||||||
Dur("elapsed_ms", duration).
|
Dur("elapsed_ms", duration).
|
||||||
|
@ -51,7 +51,7 @@ func Attempt(authenticator authn.Authenticator) func(http.Handler) http.Handler
|
|||||||
// Update the logging context and inject principal in context
|
// Update the logging context and inject principal in context
|
||||||
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
|
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
|
||||||
return c.
|
return c.
|
||||||
Int64("principal_id", session.Principal.ID).
|
Str("principal_uid", session.Principal.UID).
|
||||||
Str("principal_type", string(session.Principal.Type)).
|
Str("principal_type", string(session.Principal.Type)).
|
||||||
Bool("principal_admin", session.Principal.Admin)
|
Bool("principal_admin", session.Principal.Admin)
|
||||||
})
|
})
|
||||||
|
@ -2,39 +2,42 @@ package encode
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/hlog"
|
||||||
|
|
||||||
|
"github.com/harness/gitness/internal/request"
|
||||||
"github.com/harness/gitness/types"
|
"github.com/harness/gitness/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GitPathBefore wraps an http.HandlerFunc in a layer that encodes Paths coming as part of the GIT api
|
const (
|
||||||
// (e.g. "space1/repo.git") before executing the provided http.HandlerFunc
|
EncodedPathSeparator = "%252F"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GitPath encodes Paths coming as part of the GIT api (e.g. "space1/repo.git")
|
||||||
// The first prefix that matches the URL.Path will be used during encoding.
|
// The first prefix that matches the URL.Path will be used during encoding.
|
||||||
func GitPathBefore(h http.HandlerFunc) http.HandlerFunc {
|
func GitPath(r *http.Request) error {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
_, err := pathTerminatedWithMarker(r, "", ".git", false)
|
||||||
r, _ = pathTerminatedWithMarker(r, "", ".git", false)
|
return err
|
||||||
h.ServeHTTP(w, r)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TerminatedPathBefore wraps an http.HandlerFunc in a layer that encodes a terminated path (e.g. "/space1/space2/+")
|
// TerminatedPath wraps an http.HandlerFunc in a layer that encodes a terminated path (e.g. "/space1/space2/+")
|
||||||
// before executing the provided http.HandlerFunc. The first prefix that matches the URL.Path will
|
// before executing the provided http.HandlerFunc. The first prefix that matches the URL.Path will
|
||||||
// be used during encoding.
|
// be used during encoding (prefix is ignored during encoding).
|
||||||
func TerminatedPathBefore(prefixes []string, h http.HandlerFunc) http.HandlerFunc {
|
func TerminatedPath(prefixes []string, r *http.Request) error {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
for _, p := range prefixes {
|
for _, p := range prefixes {
|
||||||
// IMPORTANT: define changed separately to avoid overshadowing r
|
changed, err := pathTerminatedWithMarker(r, p, "/+", false)
|
||||||
var changed bool
|
if err != nil {
|
||||||
if r, changed = pathTerminatedWithMarker(r, p, "/+", false); changed {
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// first prefix that leads to success we can stop
|
||||||
|
if changed {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h.ServeHTTP(w, r)
|
return nil
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// pathTerminatedWithMarker function encodes a path followed by a custom marker and returns a request with an
|
// pathTerminatedWithMarker function encodes a path followed by a custom marker and returns a request with an
|
||||||
@ -46,49 +49,41 @@ func TerminatedPathBefore(prefixes []string, h http.HandlerFunc) http.HandlerFun
|
|||||||
// Prefix: "" Path: "/space1/space2/+" => "/space1%2Fspace2"
|
// Prefix: "" Path: "/space1/space2/+" => "/space1%2Fspace2"
|
||||||
// Prefix: "" Path: "/space1/space2.git" => "/space1%2Fspace2"
|
// Prefix: "" Path: "/space1/space2.git" => "/space1%2Fspace2"
|
||||||
// Prefix: "/spaces" Path: "/spaces/space1/space2/+/authToken" => "/spaces/space1%2Fspace2/authToken".
|
// Prefix: "/spaces" Path: "/spaces/space1/space2/+/authToken" => "/spaces/space1%2Fspace2/authToken".
|
||||||
func pathTerminatedWithMarker(r *http.Request, prefix string, marker string, keepMarker bool) (*http.Request, bool) {
|
func pathTerminatedWithMarker(r *http.Request, prefix string, marker string, keepMarker bool) (bool, error) {
|
||||||
// In case path doesn't start with prefix - nothing to encode
|
// In case path doesn't start with prefix - nothing to encode
|
||||||
if len(r.URL.Path) < len(prefix) || r.URL.Path[0:len(prefix)] != prefix {
|
if len(r.URL.Path) < len(prefix) || r.URL.Path[0:len(prefix)] != prefix {
|
||||||
return r, false
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
originalSubPath := r.URL.Path[len(prefix):]
|
originalSubPath := r.URL.Path[len(prefix):]
|
||||||
path, suffix, found := strings.Cut(originalSubPath, marker)
|
path, _, found := strings.Cut(originalSubPath, marker)
|
||||||
|
|
||||||
// If we don't find a marker - nothing to encode
|
// If we don't find a marker - nothing to encode
|
||||||
if !found {
|
if !found {
|
||||||
return r, false
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// if marker was found - convert to escaped version (skip first character in case path starts with '/')
|
// if marker was found - convert to escaped version (skip first character in case path starts with '/').
|
||||||
escapedPath := path[0:1] + strings.ReplaceAll(path[1:], types.PathSeparator, "%2F")
|
// Since replacePrefix unescapes the strings, we have to double escape.
|
||||||
|
escapedPath := path[0:1] + strings.ReplaceAll(path[1:], types.PathSeparator, EncodedPathSeparator)
|
||||||
if keepMarker {
|
if keepMarker {
|
||||||
escapedPath += marker
|
escapedPath += marker
|
||||||
}
|
}
|
||||||
updatedSubPath := escapedPath + suffix
|
|
||||||
|
|
||||||
// TODO: Proper Logging
|
prefixWithPath := prefix + path + marker
|
||||||
log.Debug().Msgf(
|
prefixWithEscapedPath := prefix + escapedPath
|
||||||
"[Encode] prefix: '%s', marker: '%s', original: '%s', updated: '%s'.\n",
|
|
||||||
|
hlog.FromRequest(r).Trace().Msgf(
|
||||||
|
"[Encode] prefix: '%s', marker: '%s', original: '%s', escaped: '%s'.\n",
|
||||||
prefix,
|
prefix,
|
||||||
marker,
|
marker,
|
||||||
originalSubPath,
|
prefixWithPath,
|
||||||
updatedSubPath)
|
prefixWithEscapedPath)
|
||||||
|
|
||||||
/*
|
err := request.ReplacePrefix(r, prefixWithPath, prefixWithEscapedPath)
|
||||||
* Return shallow clone with updated URL, similar to http.StripPrefix or earlier version of request.WithContext
|
if err != nil {
|
||||||
* https://cs.opensource.google/go/go/+/refs/tags/go1.19:src/net/http/server.go;l=2138
|
return false, err
|
||||||
* https://cs.opensource.google/go/go/+/refs/tags/go1.18:src/net/http/request.go;l=355
|
}
|
||||||
*
|
|
||||||
* http.StripPrefix initially changed the path only, but that was updated because of official recommendations:
|
return true, nil
|
||||||
* https://github.com/golang/go/issues/18952
|
|
||||||
*/
|
|
||||||
r2 := new(http.Request)
|
|
||||||
*r2 = *r
|
|
||||||
r2.URL = new(url.URL)
|
|
||||||
*r2.URL = *r.URL
|
|
||||||
r2.URL.Path = prefix + updatedSubPath
|
|
||||||
r2.URL.RawPath = ""
|
|
||||||
|
|
||||||
return r2, true
|
|
||||||
}
|
}
|
||||||
|
@ -25,17 +25,17 @@ func ServiceAccount(saStore store.ServiceAccountStore) func(http.Handler) http.H
|
|||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
log := hlog.FromRequest(r)
|
log := hlog.FromRequest(r)
|
||||||
|
|
||||||
id, err := request.GetServiceAccountID(r)
|
uid, err := request.GetServiceAccountUID(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Info().Err(err).Msgf("Receieved no or invalid service account id")
|
log.Info().Err(err).Msgf("Receieved no or invalid service account uid")
|
||||||
|
|
||||||
render.BadRequest(w)
|
render.BadRequest(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sa, err := saStore.Find(ctx, id)
|
sa, err := saStore.FindUID(ctx, uid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn().Err(err).Msgf("Failed to get service account with id '%d'.", id)
|
log.Warn().Err(err).Msgf("Failed to get service account with uid '%s'.", uid)
|
||||||
|
|
||||||
render.UserfiedErrorOrInternal(w, err)
|
render.UserfiedErrorOrInternal(w, err)
|
||||||
return
|
return
|
||||||
@ -43,7 +43,7 @@ func ServiceAccount(saStore store.ServiceAccountStore) func(http.Handler) http.H
|
|||||||
|
|
||||||
// Update the logging context and inject repo in context
|
// Update the logging context and inject repo in context
|
||||||
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
|
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
|
||||||
return c.Int64("sa_id", sa.ID).Str("sa_name", sa.Name)
|
return c.Str("sa_uid", sa.UID)
|
||||||
})
|
})
|
||||||
|
|
||||||
next.ServeHTTP(w, r.WithContext(
|
next.ServeHTTP(w, r.WithContext(
|
||||||
|
@ -47,7 +47,6 @@ func Space(spaceStore store.SpaceStore) func(http.Handler) http.Handler {
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug().Err(err).Msgf("Failed to get space using ref '%s'.", ref)
|
log.Debug().Err(err).Msgf("Failed to get space using ref '%s'.", ref)
|
||||||
|
|
||||||
render.UserfiedErrorOrInternal(w, err)
|
render.UserfiedErrorOrInternal(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ func User(userStore store.UserStore) func(http.Handler) http.Handler {
|
|||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
log := hlog.FromRequest(r)
|
log := hlog.FromRequest(r)
|
||||||
|
|
||||||
id, err := request.GetUserID(r)
|
uid, err := request.GetUserUID(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Info().Err(err).Msgf("Receieved no or invalid user id")
|
log.Info().Err(err).Msgf("Receieved no or invalid user id")
|
||||||
|
|
||||||
@ -33,9 +33,9 @@ func User(userStore store.UserStore) func(http.Handler) http.Handler {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := userStore.Find(ctx, id)
|
user, err := userStore.FindUID(ctx, uid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Info().Err(err).Msgf("Failed to get user with id '%d'.", id)
|
log.Info().Err(err).Msgf("Failed to get user with uid '%s'.", uid)
|
||||||
|
|
||||||
render.UserfiedErrorOrInternal(w, err)
|
render.UserfiedErrorOrInternal(w, err)
|
||||||
return
|
return
|
||||||
@ -43,7 +43,7 @@ func User(userStore store.UserStore) func(http.Handler) http.Handler {
|
|||||||
|
|
||||||
// Update the logging context and inject repo in context
|
// Update the logging context and inject repo in context
|
||||||
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
|
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
|
||||||
return c.Int64("user_id", user.ID).Str("user_name", user.Name)
|
return c.Str("user_uid", user.UID)
|
||||||
})
|
})
|
||||||
|
|
||||||
next.ServeHTTP(w, r.WithContext(
|
next.ServeHTTP(w, r.WithContext(
|
||||||
|
@ -5,14 +5,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
UserIDParamName = "userId"
|
UserUIDParamName = "userUID"
|
||||||
ServiceAccountIDParamName = "saId"
|
ServiceAccountUIDParamName = "saUID"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetUserID(r *http.Request) (int64, error) {
|
func GetUserUID(r *http.Request) (string, error) {
|
||||||
return ParseAsInt64(r, UserIDParamName)
|
return ParamOrError(r, UserUIDParamName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetServiceAccountID(r *http.Request) (int64, error) {
|
func GetServiceAccountUID(r *http.Request) (string, error) {
|
||||||
return ParseAsInt64(r, ServiceAccountIDParamName)
|
return ParamOrError(r, ServiceAccountUIDParamName)
|
||||||
}
|
}
|
||||||
|
@ -14,11 +14,22 @@ import (
|
|||||||
"github.com/harness/gitness/types/enum"
|
"github.com/harness/gitness/types/enum"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ParamOrError 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) {
|
||||||
|
value := chi.URLParam(r, paramName)
|
||||||
|
if value == "" {
|
||||||
|
return "", fmt.Errorf("parameter '%s' not found in request", paramName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ParseAsInt64 tries to retrieve the parameter from the request and parse it to in64.
|
// ParseAsInt64 tries to retrieve the parameter from the request and parse it to in64.
|
||||||
func ParseAsInt64(r *http.Request, paramName string) (int64, error) {
|
func ParseAsInt64(r *http.Request, paramName string) (int64, error) {
|
||||||
rawID := chi.URLParam(r, paramName)
|
rawID, err := ParamOrError(r, paramName)
|
||||||
if rawID == "" {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("parameter '%s' not found in request", paramName)
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err := strconv.ParseInt(rawID, 10, 64)
|
id, err := strconv.ParseInt(rawID, 10, 64)
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
// Copyright 2022 Harness Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by the Polyform Free Trial License
|
|
||||||
// that can be found in the LICENSE.md file for this repository.
|
|
||||||
|
|
||||||
package harness
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/harness/gitness/internal/auth"
|
|
||||||
"github.com/harness/gitness/internal/auth/authn"
|
|
||||||
"github.com/harness/gitness/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ authn.Authenticator = (*Authenticator)(nil)
|
|
||||||
|
|
||||||
// Authenticator that validates access token provided by harness SAAS.
|
|
||||||
type Authenticator struct {
|
|
||||||
// some config to validate jwt
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAuthenticator() (authn.Authenticator, error) {
|
|
||||||
return &Authenticator{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Authenticator) Authenticate(r *http.Request) (*auth.Session, error) {
|
|
||||||
return &auth.Session{Principal: types.Principal{}, Metadata: &auth.EmptyMetadata{}}, nil
|
|
||||||
}
|
|
@ -35,7 +35,7 @@ type TokenAuthenticator struct {
|
|||||||
func NewTokenAuthenticator(
|
func NewTokenAuthenticator(
|
||||||
userStore store.UserStore,
|
userStore store.UserStore,
|
||||||
saStore store.ServiceAccountStore,
|
saStore store.ServiceAccountStore,
|
||||||
tokenStore store.TokenStore) Authenticator {
|
tokenStore store.TokenStore) *TokenAuthenticator {
|
||||||
return &TokenAuthenticator{
|
return &TokenAuthenticator{
|
||||||
userStore: userStore,
|
userStore: userStore,
|
||||||
saStore: saStore,
|
saStore: saStore,
|
||||||
|
@ -6,9 +6,15 @@ package authn
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/google/wire"
|
"github.com/google/wire"
|
||||||
|
"github.com/harness/gitness/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WireSet provides a wire set for this package.
|
// WireSet provides a wire set for this package.
|
||||||
var WireSet = wire.NewSet(
|
var WireSet = wire.NewSet(
|
||||||
NewTokenAuthenticator,
|
ProvideAuthenticator,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func ProvideAuthenticator(userStore store.UserStore, saStore store.ServiceAccountStore,
|
||||||
|
tokenStore store.TokenStore) Authenticator {
|
||||||
|
return NewTokenAuthenticator(userStore, saStore, tokenStore)
|
||||||
|
}
|
||||||
|
@ -1,243 +0,0 @@
|
|||||||
// Copyright 2022 Harness Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by the Polyform Free Trial License
|
|
||||||
// that can be found in the LICENSE.md file for this repository.
|
|
||||||
|
|
||||||
package harness
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/harness/gitness/internal/auth"
|
|
||||||
"github.com/harness/gitness/internal/auth/authz"
|
|
||||||
"github.com/harness/gitness/types"
|
|
||||||
"github.com/harness/gitness/types/enum"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ authz.Authorizer = (*Authorizer)(nil)
|
|
||||||
|
|
||||||
type Authorizer struct {
|
|
||||||
client *http.Client
|
|
||||||
aclEndpoint string
|
|
||||||
authToken string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAuthorizer(aclEndpoint, authToken string) (authz.Authorizer, error) {
|
|
||||||
// build http client - could be injected, too
|
|
||||||
tr := &http.Transport{
|
|
||||||
// TODO: expose InsecureSkipVerify in config
|
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
//nolint:gosec // accept any host cert
|
|
||||||
InsecureSkipVerify: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
client := &http.Client{Transport: tr}
|
|
||||||
|
|
||||||
return &Authorizer{
|
|
||||||
client: client,
|
|
||||||
aclEndpoint: aclEndpoint,
|
|
||||||
authToken: authToken,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Authorizer) Check(ctx context.Context, session *auth.Session,
|
|
||||||
scope *types.Scope, resource *types.Resource, permission enum.Permission) (bool, error) {
|
|
||||||
return a.CheckAll(ctx, session, types.PermissionCheck{
|
|
||||||
Scope: *scope,
|
|
||||||
Resource: *resource,
|
|
||||||
Permission: permission,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Authorizer) CheckAll(ctx context.Context, session *auth.Session,
|
|
||||||
permissionChecks ...types.PermissionCheck) (bool, error) {
|
|
||||||
if len(permissionChecks) == 0 {
|
|
||||||
return false, authz.ErrNoPermissionCheckProvided
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Ensure that we also handle HarnessMetadata!
|
|
||||||
requestDto, err := createACLRequest(&session.Principal, permissionChecks)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
byt, err := json.Marshal(requestDto)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: accountId might be different!
|
|
||||||
url := a.aclEndpoint + "?routingId=" + requestDto.Permissions[0].ResourceScope.AccountIdentifier
|
|
||||||
httpRequest, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(byt))
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
httpRequest.Header = http.Header{
|
|
||||||
"Content-Type": []string{"application/json"},
|
|
||||||
"Authorization": []string{"Bearer " + a.authToken},
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := a.client.Do(httpRequest)
|
|
||||||
if response != nil {
|
|
||||||
defer response.Body.Close()
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if response.StatusCode != http.StatusOK {
|
|
||||||
return false, fmt.Errorf("got unexpected status code '%d' - assume unauthorized", response.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
bodyByte, err := io.ReadAll(response.Body)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var responseDto aclResponse
|
|
||||||
err = json.Unmarshal(bodyByte, &responseDto)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return checkACLResponse(permissionChecks, responseDto)
|
|
||||||
}
|
|
||||||
|
|
||||||
func createACLRequest(principal *types.Principal,
|
|
||||||
permissionChecks []types.PermissionCheck) (*aclRequest, error) {
|
|
||||||
// Generate ACL req
|
|
||||||
req := aclRequest{
|
|
||||||
Permissions: []aclPermission{},
|
|
||||||
Principal: aclPrincipal{
|
|
||||||
PrincipalIdentifier: principal.ExternalID,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// map principaltype
|
|
||||||
actualPrincipalType, err := mapPrincipalType(principal.Type)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
req.Principal.PrincipalType = actualPrincipalType
|
|
||||||
|
|
||||||
// map all permissionchecks to ACL permission checks
|
|
||||||
for _, c := range permissionChecks {
|
|
||||||
mappedPermission := mapPermission(c.Permission)
|
|
||||||
|
|
||||||
var mappedResourceScope *aclResourceScope
|
|
||||||
mappedResourceScope, err = mapScope(c.Scope)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Permissions = append(req.Permissions, aclPermission{
|
|
||||||
Permission: mappedPermission,
|
|
||||||
ResourceScope: *mappedResourceScope,
|
|
||||||
ResourceType: string(c.Resource.Type),
|
|
||||||
ResourceIdentifier: c.Resource.Name,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return &req, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkACLResponse(permissionChecks []types.PermissionCheck, responseDto aclResponse) (bool, error) {
|
|
||||||
/*
|
|
||||||
* We are assuming two things:
|
|
||||||
* - All permission checks were made for the same principal.
|
|
||||||
* - Permissions inherit down the hierarchy (Account -> Organization -> Project -> Repository)
|
|
||||||
* - No two checks are for the same permission - is similar to ff implementation:
|
|
||||||
* https://github.com/wings-software/ff-server/blob/master/pkg/rbac/client.go#L88
|
|
||||||
*
|
|
||||||
* Based on that, if there's any permitted result for a permission check the permission is allowed.
|
|
||||||
* Now we just have to ensure that all permissions are allowed
|
|
||||||
*
|
|
||||||
* TODO: Use resource name + scope for verifying results.
|
|
||||||
*/
|
|
||||||
|
|
||||||
for _, check := range permissionChecks {
|
|
||||||
permissionPermitted := false
|
|
||||||
for _, ace := range responseDto.Data.AccessControlList {
|
|
||||||
if string(check.Permission) == ace.Permission && ace.Permitted {
|
|
||||||
permissionPermitted = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !permissionPermitted {
|
|
||||||
return false, fmt.Errorf("permission '%s' is not permitted according to ACL (correlationId: '%s')",
|
|
||||||
check.Permission,
|
|
||||||
responseDto.CorrelationID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapScope(scope types.Scope) (*aclResourceScope, error) {
|
|
||||||
/*
|
|
||||||
* ASSUMPTION:
|
|
||||||
* Harness embeded structure is mapped to the following scm space:
|
|
||||||
* {Account}/{Organization}/{Project}
|
|
||||||
*
|
|
||||||
* We can use that assumption to translate back from scope.spacePath to harness scope.
|
|
||||||
* However, this only works as long as resources exist within spaces only.
|
|
||||||
* For controlling access to any child resources of a repository, harness doesn't have a matching
|
|
||||||
* structure out of the box (e.g. branches, ...)
|
|
||||||
*
|
|
||||||
* IMPORTANT:
|
|
||||||
* For now harness embedded doesn't support scope.Repository (has to be configured on space level ...)
|
|
||||||
*
|
|
||||||
* TODO: Handle scope.Repository in harness embedded mode
|
|
||||||
*/
|
|
||||||
|
|
||||||
const (
|
|
||||||
accIndex = 0
|
|
||||||
orgIndex = 1
|
|
||||||
projectIndex = 2
|
|
||||||
scopes = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
harnessIdentifiers := strings.Split(scope.SpacePath, "/")
|
|
||||||
if len(harnessIdentifiers) > scopes {
|
|
||||||
return nil, fmt.Errorf("unable to convert '%s' to harness resource scope "+
|
|
||||||
"(expected {Account}/{Organization}/{Project} or a sub scope)", scope.SpacePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
aclScope := &aclResourceScope{}
|
|
||||||
if len(harnessIdentifiers) > accIndex {
|
|
||||||
aclScope.AccountIdentifier = harnessIdentifiers[accIndex]
|
|
||||||
}
|
|
||||||
if len(harnessIdentifiers) > orgIndex {
|
|
||||||
aclScope.OrgIdentifier = harnessIdentifiers[orgIndex]
|
|
||||||
}
|
|
||||||
if len(harnessIdentifiers) > projectIndex {
|
|
||||||
aclScope.ProjectIdentifier = harnessIdentifiers[projectIndex]
|
|
||||||
}
|
|
||||||
|
|
||||||
return aclScope, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapPermission(permission enum.Permission) string {
|
|
||||||
// harness has multiple modules - add scm prefix
|
|
||||||
return "scm_" + string(permission)
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapPrincipalType(principalType enum.PrincipalType) (string, error) {
|
|
||||||
switch principalType {
|
|
||||||
case enum.PrincipalTypeUser:
|
|
||||||
return "USER", nil
|
|
||||||
case enum.PrincipalTypeServiceAccount:
|
|
||||||
return "SERVICE_ACCOUNT", nil
|
|
||||||
case enum.PrincipalTypeService:
|
|
||||||
return "SERVICE", nil
|
|
||||||
default:
|
|
||||||
return "", fmt.Errorf("unknown principaltype '%s'", principalType)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
// Copyright 2022 Harness Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by the Polyform Free Trial License
|
|
||||||
// that can be found in the LICENSE.md file for this repository.
|
|
||||||
|
|
||||||
package harness
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Classes required for harness ACL.
|
|
||||||
* For now keep it here, as it shouldn't even be part of the code base in the first place
|
|
||||||
* (should be in its own harness wide client library).
|
|
||||||
*/
|
|
||||||
type aclRequest struct {
|
|
||||||
Principal aclPrincipal `json:"principal"`
|
|
||||||
Permissions []aclPermission `json:"permissions"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type aclResponse struct {
|
|
||||||
Status string `json:"status"`
|
|
||||||
CorrelationID string `json:"correlationId"`
|
|
||||||
Data aclResponseData `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type aclResponseData struct {
|
|
||||||
Principal aclPrincipal `json:"principal"`
|
|
||||||
AccessControlList []aclControlElement `json:"accessControlList"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type aclControlElement struct {
|
|
||||||
Permission string `json:"permission"`
|
|
||||||
ResourceScope aclResourceScope `json:"resourceScope,omitempty"`
|
|
||||||
ResourceType string `json:"resourceType"`
|
|
||||||
ResourceIdentifier string `json:"resourceIdentifier"`
|
|
||||||
Permitted bool `json:"permitted"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type aclResourceScope struct {
|
|
||||||
AccountIdentifier string `json:"accountIdentifier"`
|
|
||||||
OrgIdentifier string `json:"orgIdentifier,omitempty"`
|
|
||||||
ProjectIdentifier string `json:"projectIdentifier,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type aclPermission struct {
|
|
||||||
ResourceScope aclResourceScope `json:"resourceScope,omitempty"`
|
|
||||||
ResourceType string `json:"resourceType"`
|
|
||||||
ResourceIdentifier string `json:"resourceIdentifier"`
|
|
||||||
Permission string `json:"permission"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type aclPrincipal struct {
|
|
||||||
PrincipalIdentifier string `json:"principalIdentifier"`
|
|
||||||
PrincipalType string `json:"principalType"`
|
|
||||||
}
|
|
@ -21,7 +21,7 @@ var _ Authorizer = (*UnsafeAuthorizer)(nil)
|
|||||||
*/
|
*/
|
||||||
type UnsafeAuthorizer struct{}
|
type UnsafeAuthorizer struct{}
|
||||||
|
|
||||||
func NewUnsafeAuthorizer() Authorizer {
|
func NewUnsafeAuthorizer() *UnsafeAuthorizer {
|
||||||
return &UnsafeAuthorizer{}
|
return &UnsafeAuthorizer{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,5 +10,9 @@ import (
|
|||||||
|
|
||||||
// WireSet provides a wire set for this package.
|
// WireSet provides a wire set for this package.
|
||||||
var WireSet = wire.NewSet(
|
var WireSet = wire.NewSet(
|
||||||
NewUnsafeAuthorizer,
|
ProvideAuthorizer,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func ProvideAuthorizer() Authorizer {
|
||||||
|
return NewUnsafeAuthorizer()
|
||||||
|
}
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
// Copyright 2022 Harness Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by the Polyform Free Trial License
|
|
||||||
// that can be found in the LICENSE.md file for this repository.
|
|
||||||
|
|
||||||
package harness
|
|
||||||
|
|
||||||
import "github.com/harness/gitness/types/enum"
|
|
||||||
|
|
||||||
// Metadata is used for all harness embedded auths (apart from ssh).
|
|
||||||
type Metadata struct {
|
|
||||||
ExecutingPrincipalType enum.PrincipalType
|
|
||||||
ExecutingPrincipalID int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequiresEnforcement returns true if the metadata contains authz related info.
|
|
||||||
func (m *Metadata) RequiresEnforcement() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
63
internal/request/request.go
Normal file
63
internal/request/request.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// 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 router provides http handlers for serving the
|
||||||
|
// web applicationa and API endpoints.
|
||||||
|
package request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReplacePrefix replaces the path of the request.
|
||||||
|
// IMPORTANT:
|
||||||
|
// - both prefix are unescaped for path, and used as is for RawPath!
|
||||||
|
// - only called by top level handler!!
|
||||||
|
func ReplacePrefix(r *http.Request, oldPrefix string, newPrefix string) error {
|
||||||
|
/*
|
||||||
|
* According to official documentation, we can change anything in the request but the body:
|
||||||
|
* https://pkg.go.dev/net/http#Handler
|
||||||
|
*
|
||||||
|
* ASSUMPTION:
|
||||||
|
* This is called by a top level handler (no router or middleware above it)
|
||||||
|
* Therefore, we don't have to worry about getting any routing metadata out of sync.
|
||||||
|
*
|
||||||
|
* This is different to returning a shallow clone with updated URL, which is what
|
||||||
|
* http.StripPrefix or earlier versions of request.WithContext are doing:
|
||||||
|
* https://cs.opensource.google/go/go/+/refs/tags/go1.19:src/net/http/server.go;l=2138
|
||||||
|
* https://cs.opensource.google/go/go/+/refs/tags/go1.18:src/net/http/request.go;l=355
|
||||||
|
*
|
||||||
|
* http.StripPrefix initially changed the path only, but that was updated because of official recommendations:
|
||||||
|
* https://github.com/golang/go/issues/18952
|
||||||
|
*/
|
||||||
|
unOldPrefix, err := url.PathUnescape(oldPrefix)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to unescape old prefix '%s'", oldPrefix)
|
||||||
|
}
|
||||||
|
unNewPrefix, err := url.PathUnescape(newPrefix)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to unescape new prefix '%s'", newPrefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
unl := len(unOldPrefix)
|
||||||
|
if len(r.URL.Path) < unl || r.URL.Path[0:unl] != unOldPrefix {
|
||||||
|
return fmt.Errorf("path '%s' doesn't contain prefix '%s'", r.URL.Path, unOldPrefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// only change RawPath if it exists
|
||||||
|
if r.URL.RawPath != "" {
|
||||||
|
l := len(oldPrefix)
|
||||||
|
if len(r.URL.RawPath) < l || r.URL.RawPath[0:l] != oldPrefix {
|
||||||
|
return fmt.Errorf("raw path '%s' doesn't contain prefix '%s'", r.URL.RawPath, oldPrefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.URL.RawPath = newPrefix + r.URL.RawPath[l:]
|
||||||
|
}
|
||||||
|
|
||||||
|
r.URL.Path = unNewPrefix + r.URL.Path[unl:]
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -14,7 +14,6 @@ import (
|
|||||||
handleruser "github.com/harness/gitness/internal/api/handler/user"
|
handleruser "github.com/harness/gitness/internal/api/handler/user"
|
||||||
"github.com/harness/gitness/internal/api/middleware/accesslog"
|
"github.com/harness/gitness/internal/api/middleware/accesslog"
|
||||||
middlewareauthn "github.com/harness/gitness/internal/api/middleware/authn"
|
middlewareauthn "github.com/harness/gitness/internal/api/middleware/authn"
|
||||||
"github.com/harness/gitness/internal/api/middleware/encode"
|
|
||||||
"github.com/harness/gitness/internal/api/middleware/resolve"
|
"github.com/harness/gitness/internal/api/middleware/resolve"
|
||||||
|
|
||||||
"github.com/harness/gitness/internal/api/request"
|
"github.com/harness/gitness/internal/api/request"
|
||||||
@ -27,15 +26,12 @@ import (
|
|||||||
"github.com/go-chi/chi/middleware"
|
"github.com/go-chi/chi/middleware"
|
||||||
"github.com/go-chi/cors"
|
"github.com/go-chi/cors"
|
||||||
"github.com/rs/zerolog/hlog"
|
"github.com/rs/zerolog/hlog"
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Mounts the Rest API Router under mountPath (path has to end with ).
|
* newAPIHandler returns a new http handler for handling API calls.
|
||||||
* The handler is wrapped within a layer that handles encoding terminated Paths.
|
|
||||||
*/
|
*/
|
||||||
func newAPIHandler(
|
func newAPIHandler(
|
||||||
mountPath string,
|
|
||||||
systemStore store.SystemStore,
|
systemStore store.SystemStore,
|
||||||
userStore store.UserStore,
|
userStore store.UserStore,
|
||||||
spaceStore store.SpaceStore,
|
spaceStore store.SpaceStore,
|
||||||
@ -50,14 +46,13 @@ func newAPIHandler(
|
|||||||
|
|
||||||
// Use go-chi router for inner routing (restricted to mountPath!)
|
// Use go-chi router for inner routing (restricted to mountPath!)
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.Route(mountPath, func(r chi.Router) {
|
|
||||||
// Apply common api middleware
|
// Apply common api middleware
|
||||||
r.Use(middleware.NoCache)
|
r.Use(middleware.NoCache)
|
||||||
r.Use(middleware.Recoverer)
|
r.Use(middleware.Recoverer)
|
||||||
|
|
||||||
// configure logging middleware.
|
// configure logging middleware.
|
||||||
r.Use(hlog.NewHandler(log.Logger))
|
r.Use(hlog.URLHandler("url"))
|
||||||
r.Use(hlog.URLHandler("path"))
|
|
||||||
r.Use(hlog.MethodHandler("method"))
|
r.Use(hlog.MethodHandler("method"))
|
||||||
r.Use(hlog.RequestIDHandler("request", "Request-Id"))
|
r.Use(hlog.RequestIDHandler("request", "Request-Id"))
|
||||||
r.Use(accesslog.HlogHandler())
|
r.Use(accesslog.HlogHandler())
|
||||||
@ -71,15 +66,8 @@ func newAPIHandler(
|
|||||||
r.Route("/v1", func(r chi.Router) {
|
r.Route("/v1", func(r chi.Router) {
|
||||||
setupRoutesV1(r, systemStore, userStore, spaceStore, repoStore, tokenStore, saStore, authenticator, g)
|
setupRoutesV1(r, systemStore, userStore, spaceStore, repoStore, tokenStore, saStore, authenticator, g)
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
// Generate list of all path prefixes that expect terminated Paths
|
return r
|
||||||
terminatedPathPrefixes := []string{
|
|
||||||
mountPath + "/v1/spaces",
|
|
||||||
mountPath + "/v1/repos",
|
|
||||||
}
|
|
||||||
|
|
||||||
return encode.TerminatedPathBefore(terminatedPathPrefixes, r.ServeHTTP)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func corsHandler(config *types.Config) func(http.Handler) http.Handler {
|
func corsHandler(config *types.Config) func(http.Handler) http.Handler {
|
||||||
@ -214,7 +202,7 @@ func setupServiceAccounts(r chi.Router, saStore store.ServiceAccountStore, token
|
|||||||
// create takes parent information via body
|
// create takes parent information via body
|
||||||
r.Post("/", handlerserviceaccount.HandleCreate(guard, saStore))
|
r.Post("/", handlerserviceaccount.HandleCreate(guard, saStore))
|
||||||
|
|
||||||
r.Route(fmt.Sprintf("/{%s}", request.ServiceAccountIDParamName), func(r chi.Router) {
|
r.Route(fmt.Sprintf("/{%s}", request.ServiceAccountUIDParamName), func(r chi.Router) {
|
||||||
// resolves the service account and stores it in the context
|
// resolves the service account and stores it in the context
|
||||||
r.Use(resolve.ServiceAccount(saStore))
|
r.Use(resolve.ServiceAccount(saStore))
|
||||||
|
|
||||||
@ -251,7 +239,7 @@ func setupAdmin(r chi.Router, userStore store.UserStore, guard *guard.Guard) {
|
|||||||
_, _ = w.Write([]byte(fmt.Sprintf("Create user '%s'", chi.URLParam(r, "rref"))))
|
_, _ = w.Write([]byte(fmt.Sprintf("Create user '%s'", chi.URLParam(r, "rref"))))
|
||||||
})
|
})
|
||||||
|
|
||||||
r.Route(fmt.Sprintf("/{%s}", request.UserIDParamName), func(r chi.Router) {
|
r.Route(fmt.Sprintf("/{%s}", request.UserUIDParamName), func(r chi.Router) {
|
||||||
// resolves the user and stores it in the context
|
// resolves the user and stores it in the context
|
||||||
resolve.User(userStore)
|
resolve.User(userStore)
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"github.com/harness/gitness/internal/api/guard"
|
"github.com/harness/gitness/internal/api/guard"
|
||||||
"github.com/harness/gitness/internal/api/middleware/accesslog"
|
"github.com/harness/gitness/internal/api/middleware/accesslog"
|
||||||
middleware_authn "github.com/harness/gitness/internal/api/middleware/authn"
|
middleware_authn "github.com/harness/gitness/internal/api/middleware/authn"
|
||||||
"github.com/harness/gitness/internal/api/middleware/encode"
|
|
||||||
"github.com/harness/gitness/internal/api/middleware/resolve"
|
"github.com/harness/gitness/internal/api/middleware/resolve"
|
||||||
"github.com/harness/gitness/internal/api/request"
|
"github.com/harness/gitness/internal/api/request"
|
||||||
"github.com/harness/gitness/internal/auth/authn"
|
"github.com/harness/gitness/internal/auth/authn"
|
||||||
@ -18,15 +17,12 @@ import (
|
|||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi"
|
||||||
"github.com/go-chi/chi/middleware"
|
"github.com/go-chi/chi/middleware"
|
||||||
"github.com/rs/zerolog/hlog"
|
"github.com/rs/zerolog/hlog"
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Mounts the GIT Router under mountPath.
|
* newGitHandler returns a new http handler for handling GIT calls.
|
||||||
* The handler is wrapped within a layer that handles encoding Paths.
|
|
||||||
*/
|
*/
|
||||||
func newGitHandler(
|
func newGitHandler(
|
||||||
mountPath string,
|
|
||||||
_ store.SystemStore,
|
_ store.SystemStore,
|
||||||
_ store.UserStore,
|
_ store.UserStore,
|
||||||
spaceStore store.SpaceStore,
|
spaceStore store.SpaceStore,
|
||||||
@ -34,16 +30,15 @@ func newGitHandler(
|
|||||||
authenticator authn.Authenticator,
|
authenticator authn.Authenticator,
|
||||||
authorizer authz.Authorizer) http.Handler {
|
authorizer authz.Authorizer) http.Handler {
|
||||||
guard := guard.New(authorizer, spaceStore, repoStore)
|
guard := guard.New(authorizer, spaceStore, repoStore)
|
||||||
|
|
||||||
// Use go-chi router for inner routing (restricted to mountPath!)
|
// Use go-chi router for inner routing (restricted to mountPath!)
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.Route(mountPath, func(r chi.Router) {
|
|
||||||
// Apply common api middleware
|
// Apply common api middleware
|
||||||
r.Use(middleware.NoCache)
|
r.Use(middleware.NoCache)
|
||||||
r.Use(middleware.Recoverer)
|
r.Use(middleware.Recoverer)
|
||||||
|
|
||||||
// configure logging middleware.
|
// configure logging middleware.
|
||||||
r.Use(hlog.NewHandler(log.Logger))
|
r.Use(hlog.URLHandler("url"))
|
||||||
r.Use(hlog.URLHandler("path"))
|
|
||||||
r.Use(hlog.MethodHandler("method"))
|
r.Use(hlog.MethodHandler("method"))
|
||||||
r.Use(hlog.RequestIDHandler("request", "Request-Id"))
|
r.Use(hlog.RequestIDHandler("request", "Request-Id"))
|
||||||
r.Use(accesslog.HlogHandler())
|
r.Use(accesslog.HlogHandler())
|
||||||
@ -80,9 +75,8 @@ func newGitHandler(
|
|||||||
r.Get("/objects/pack/pack-{file:[0-9a-f]{40}}.idx", stubGitHandler)
|
r.Get("/objects/pack/pack-{file:[0-9a-f]{40}}.idx", stubGitHandler)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
return encode.GitPathBefore(r.ServeHTTP)
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func stubGitHandler(w http.ResponseWriter, r *http.Request) {
|
func stubGitHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -10,25 +10,33 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/harness/gitness/internal/api/render"
|
||||||
"github.com/harness/gitness/internal/auth/authn"
|
"github.com/harness/gitness/internal/auth/authn"
|
||||||
"github.com/harness/gitness/internal/auth/authz"
|
"github.com/harness/gitness/internal/auth/authz"
|
||||||
|
"github.com/harness/gitness/internal/request"
|
||||||
|
"github.com/harness/gitness/internal/router/translator"
|
||||||
"github.com/harness/gitness/internal/store"
|
"github.com/harness/gitness/internal/store"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/hlog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
restMount = "/api"
|
APIMount = "/api"
|
||||||
gitUserAgentPrefix = "git/"
|
gitUserAgentPrefix = "git/"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Router struct {
|
type Router struct {
|
||||||
|
translator translator.RequestTranslator
|
||||||
api http.Handler
|
api http.Handler
|
||||||
git http.Handler
|
git http.Handler
|
||||||
web http.Handler
|
web http.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new http.Handler that routes traffic
|
// NewRouter returns a new http.Handler that routes traffic
|
||||||
// to the appropriate http.Handlers.
|
// to the appropriate http.Handlers.
|
||||||
func New(
|
func NewRouter(
|
||||||
|
translator translator.RequestTranslator,
|
||||||
systemStore store.SystemStore,
|
systemStore store.SystemStore,
|
||||||
userStore store.UserStore,
|
userStore store.UserStore,
|
||||||
spaceStore store.SpaceStore,
|
spaceStore store.SpaceStore,
|
||||||
@ -37,13 +45,14 @@ func New(
|
|||||||
saStore store.ServiceAccountStore,
|
saStore store.ServiceAccountStore,
|
||||||
authenticator authn.Authenticator,
|
authenticator authn.Authenticator,
|
||||||
authorizer authz.Authorizer,
|
authorizer authz.Authorizer,
|
||||||
) (http.Handler, error) {
|
) (*Router, error) {
|
||||||
api := newAPIHandler(restMount, systemStore, userStore, spaceStore, repoStore, tokenStore, saStore,
|
api := newAPIHandler(systemStore, userStore, spaceStore, repoStore, tokenStore, saStore,
|
||||||
authenticator, authorizer)
|
authenticator, authorizer)
|
||||||
git := newGitHandler("/", systemStore, userStore, spaceStore, repoStore, authenticator, authorizer)
|
git := newGitHandler(systemStore, userStore, spaceStore, repoStore, authenticator, authorizer)
|
||||||
web := newWebHandler("/", systemStore)
|
web := newWebHandler(systemStore)
|
||||||
|
|
||||||
return &Router{
|
return &Router{
|
||||||
|
translator: translator,
|
||||||
api: api,
|
api: api,
|
||||||
git: git,
|
git: git,
|
||||||
web: web,
|
web: web,
|
||||||
@ -51,6 +60,23 @@ func New(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
var err error
|
||||||
|
// setup logger for request
|
||||||
|
log := log.Logger.With().Logger()
|
||||||
|
req = req.WithContext(log.WithContext(req.Context()))
|
||||||
|
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
|
||||||
|
return c.
|
||||||
|
Str("original_url", req.URL.String())
|
||||||
|
})
|
||||||
|
|
||||||
|
// Initial translation of the request before any routing.
|
||||||
|
req, err = r.translator.TranslatePreRouting(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Err(err).Msgf("Failed pre-routing translation of request.")
|
||||||
|
render.InternalError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 1. GIT
|
* 1. GIT
|
||||||
*
|
*
|
||||||
@ -60,6 +86,18 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
*/
|
*/
|
||||||
ua := req.Header.Get("user-agent")
|
ua := req.Header.Get("user-agent")
|
||||||
if strings.HasPrefix(ua, gitUserAgentPrefix) {
|
if strings.HasPrefix(ua, gitUserAgentPrefix) {
|
||||||
|
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
|
||||||
|
return c.Str("handler", "git")
|
||||||
|
})
|
||||||
|
|
||||||
|
// Translate git request
|
||||||
|
req, err = r.translator.TranslateGit(req)
|
||||||
|
if err != nil {
|
||||||
|
hlog.FromRequest(req).Err(err).Msgf("Failed GIT translation of request.")
|
||||||
|
render.InternalError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
r.git.ServeHTTP(w, req)
|
r.git.ServeHTTP(w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -68,10 +106,28 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
* 2. REST API
|
* 2. REST API
|
||||||
*
|
*
|
||||||
* All Rest API calls start with "/api/", and thus can be uniquely identified.
|
* All Rest API calls start with "/api/", and thus can be uniquely identified.
|
||||||
* Note: This assumes that we are blocking "api" as a space name!
|
|
||||||
*/
|
*/
|
||||||
p := req.URL.Path
|
p := req.URL.Path
|
||||||
if strings.HasPrefix(p, restMount) {
|
if strings.HasPrefix(p, APIMount) {
|
||||||
|
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
|
||||||
|
return c.Str("handler", "api")
|
||||||
|
})
|
||||||
|
|
||||||
|
// remove matched prefix to simplify API handlers
|
||||||
|
if err = stripPrefix(APIMount, req); err != nil {
|
||||||
|
hlog.FromRequest(req).Err(err).Msgf("Failed striping of prefix for api request.")
|
||||||
|
render.InternalError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate API request
|
||||||
|
req, err = r.translator.TranslateAPI(req)
|
||||||
|
if err != nil {
|
||||||
|
hlog.FromRequest(req).Err(err).Msgf("Failed API translation of request.")
|
||||||
|
render.InternalError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
r.api.ServeHTTP(w, req)
|
r.api.ServeHTTP(w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -81,5 +137,21 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
*
|
*
|
||||||
* Everything else will be routed to web (or return 404)
|
* Everything else will be routed to web (or return 404)
|
||||||
*/
|
*/
|
||||||
|
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
|
||||||
|
return c.Str("handler", "web")
|
||||||
|
})
|
||||||
|
|
||||||
|
req, err = r.translator.TranslateWeb(req)
|
||||||
|
if err != nil {
|
||||||
|
hlog.FromRequest(req).Err(err).Msgf("Failed Web translation of request.")
|
||||||
|
render.InternalError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
r.web.ServeHTTP(w, req)
|
r.web.ServeHTTP(w, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stripPrefix removes the prefix from the request path (expected to be there).
|
||||||
|
func stripPrefix(prefix string, r *http.Request) error {
|
||||||
|
return request.ReplacePrefix(r, r.URL.Path[:len(prefix)], "")
|
||||||
|
}
|
||||||
|
25
internal/router/translator/requestTranslator.go
Normal file
25
internal/router/translator/requestTranslator.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// 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 translator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RequestTranslator is responsible to translate an incomming request
|
||||||
|
// before it's getting routed and handled.
|
||||||
|
type RequestTranslator interface {
|
||||||
|
// TranslatePreRouting is called before any routing decisions are made.
|
||||||
|
TranslatePreRouting(*http.Request) (*http.Request, error)
|
||||||
|
|
||||||
|
// TranslateGit is called for a git related request.
|
||||||
|
TranslateGit(*http.Request) (*http.Request, error)
|
||||||
|
|
||||||
|
// TranslateAPI is called for an API related request.
|
||||||
|
TranslateAPI(*http.Request) (*http.Request, error)
|
||||||
|
|
||||||
|
// TranslateWeb is called for an web related request.
|
||||||
|
TranslateWeb(*http.Request) (*http.Request, error)
|
||||||
|
}
|
49
internal/router/translator/terminatedPathTranslator.go
Normal file
49
internal/router/translator/terminatedPathTranslator.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// 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 translator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/harness/gitness/internal/api/middleware/encode"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
terminatedPathPrefixesAPI = []string{"/v1/spaces/", "/v1/repos/"}
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ RequestTranslator = (*TerminatedPathTranslator)(nil)
|
||||||
|
|
||||||
|
// TerminatedPathTranslator translates encoded paths.
|
||||||
|
// For example:
|
||||||
|
// - /space1/space2/+ -> /space1%2Fspace2
|
||||||
|
// - /space1/rep1.git -> /space1%2Frepo1
|
||||||
|
//
|
||||||
|
// Note: paths are terminated after initial routing.
|
||||||
|
type TerminatedPathTranslator struct{}
|
||||||
|
|
||||||
|
func NewTerminatedPathTranslator() *TerminatedPathTranslator {
|
||||||
|
return &TerminatedPathTranslator{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TranslatePreRouting is called before any routing decisions are made.
|
||||||
|
func (t *TerminatedPathTranslator) TranslatePreRouting(r *http.Request) (*http.Request, error) {
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TranslateGit is called for a git related request.
|
||||||
|
func (t *TerminatedPathTranslator) TranslateGit(r *http.Request) (*http.Request, error) {
|
||||||
|
return r, encode.GitPath(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TranslateAPI is called for an API related request.
|
||||||
|
func (t *TerminatedPathTranslator) TranslateAPI(r *http.Request) (*http.Request, error) {
|
||||||
|
return r, encode.TerminatedPath(terminatedPathPrefixesAPI, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TranslateWeb is called for an web related request.
|
||||||
|
func (t *TerminatedPathTranslator) TranslateWeb(r *http.Request) (*http.Request, error) {
|
||||||
|
return r, nil
|
||||||
|
}
|
18
internal/router/translator/wire.go
Normal file
18
internal/router/translator/wire.go
Normal 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 translator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WireSet provides a wire set for this package.
|
||||||
|
var WireSet = wire.NewSet(
|
||||||
|
ProvideRequestTranslator,
|
||||||
|
)
|
||||||
|
|
||||||
|
func ProvideRequestTranslator() RequestTranslator {
|
||||||
|
return NewTerminatedPathTranslator()
|
||||||
|
}
|
@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/harness/gitness/internal/api/middleware/encode"
|
|
||||||
"github.com/harness/gitness/internal/store"
|
"github.com/harness/gitness/internal/store"
|
||||||
"github.com/harness/gitness/web"
|
"github.com/harness/gitness/web"
|
||||||
"github.com/swaggest/swgui/v3emb"
|
"github.com/swaggest/swgui/v3emb"
|
||||||
@ -14,18 +13,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Mounts the WEB Router under mountPath.
|
* newWebHandler returns a new http handler for handling WEB calls.
|
||||||
* The handler is wrapped within a layer that handles encoding Paths.
|
|
||||||
*/
|
*/
|
||||||
func newWebHandler(
|
func newWebHandler(systemStore store.SystemStore) http.Handler {
|
||||||
mountPath string,
|
|
||||||
systemStore store.SystemStore) http.Handler {
|
|
||||||
//
|
|
||||||
config := systemStore.Config(context.Background())
|
config := systemStore.Config(context.Background())
|
||||||
|
|
||||||
// Use go-chi router for inner routing (restricted to mountPath!)
|
// Use go-chi router for inner routing (restricted to mountPath!)
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.Route(mountPath, func(r chi.Router) {
|
|
||||||
// create middleware to enforce security best practices for
|
// create middleware to enforce security best practices for
|
||||||
// the user interface. note that theis middleware is only used
|
// the user interface. note that theis middleware is only used
|
||||||
// when serving the user interface (not found handler, below).
|
// when serving the user interface (not found handler, below).
|
||||||
@ -59,8 +53,6 @@ func newWebHandler(
|
|||||||
r.With(sec.Handler).NotFound(
|
r.With(sec.Handler).NotFound(
|
||||||
web.Handler(),
|
web.Handler(),
|
||||||
)
|
)
|
||||||
})
|
|
||||||
|
|
||||||
// web doesn't have any prefixes for terminated paths
|
return r
|
||||||
return encode.TerminatedPathBefore([]string{""}, r.ServeHTTP)
|
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,29 @@
|
|||||||
package router
|
package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/google/wire"
|
"github.com/google/wire"
|
||||||
|
"github.com/harness/gitness/internal/auth/authn"
|
||||||
|
"github.com/harness/gitness/internal/auth/authz"
|
||||||
|
"github.com/harness/gitness/internal/router/translator"
|
||||||
|
"github.com/harness/gitness/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WireSet provides a wire set for this package.
|
// WireSet provides a wire set for this package.
|
||||||
var WireSet = wire.NewSet(New)
|
var WireSet = wire.NewSet(ProvideHTTPHandler)
|
||||||
|
|
||||||
|
func ProvideHTTPHandler(
|
||||||
|
translator translator.RequestTranslator,
|
||||||
|
systemStore store.SystemStore,
|
||||||
|
userStore store.UserStore,
|
||||||
|
spaceStore store.SpaceStore,
|
||||||
|
repoStore store.RepoStore,
|
||||||
|
tokenStore store.TokenStore,
|
||||||
|
saStore store.ServiceAccountStore,
|
||||||
|
authenticator authn.Authenticator,
|
||||||
|
authorizer authz.Authorizer,
|
||||||
|
) (http.Handler, error) {
|
||||||
|
return NewRouter(translator, systemStore, userStore, spaceStore,
|
||||||
|
repoStore, tokenStore, saStore, authenticator, authorizer)
|
||||||
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
CREATE TABLE IF NOT EXISTS principals (
|
CREATE TABLE IF NOT EXISTS principals (
|
||||||
principal_id SERIAL PRIMARY KEY
|
principal_id SERIAL PRIMARY KEY
|
||||||
|
,principal_uid TEXT
|
||||||
,principal_type TEXT
|
,principal_type TEXT
|
||||||
,principal_name TEXT
|
,principal_name TEXT
|
||||||
,principal_admin BOOLEAN
|
,principal_admin BOOLEAN
|
||||||
,principal_externalId TEXT
|
|
||||||
,principal_blocked BOOLEAN
|
,principal_blocked BOOLEAN
|
||||||
,principal_salt TEXT
|
,principal_salt TEXT
|
||||||
,principal_created INTEGER
|
,principal_created INTEGER
|
||||||
@ -15,6 +15,7 @@ principal_id SERIAL PRIMARY KEY
|
|||||||
,principal_sa_parentType TEXT
|
,principal_sa_parentType TEXT
|
||||||
,principal_sa_parentId INTEGER
|
,principal_sa_parentId INTEGER
|
||||||
|
|
||||||
|
,UNIQUE(principal_uid)
|
||||||
,UNIQUE(principal_salt)
|
,UNIQUE(principal_salt)
|
||||||
,UNIQUE(principal_user_email)
|
,UNIQUE(principal_user_email)
|
||||||
);
|
);
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
CREATE TABLE IF NOT EXISTS principals (
|
CREATE TABLE IF NOT EXISTS principals (
|
||||||
principal_id INTEGER PRIMARY KEY AUTOINCREMENT
|
principal_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||||
|
,principal_uid TEXT
|
||||||
,principal_type TEXT
|
,principal_type TEXT
|
||||||
,principal_name TEXT
|
,principal_name TEXT
|
||||||
,principal_admin BOOLEAN
|
,principal_admin BOOLEAN
|
||||||
,principal_externalId TEXT
|
|
||||||
,principal_blocked BOOLEAN
|
,principal_blocked BOOLEAN
|
||||||
,principal_salt TEXT
|
,principal_salt TEXT
|
||||||
,principal_created INTEGER
|
,principal_created INTEGER
|
||||||
@ -15,6 +15,7 @@ principal_id INTEGER PRIMARY KEY AUTOINCREMENT
|
|||||||
,principal_sa_parentType TEXT
|
,principal_sa_parentType TEXT
|
||||||
,principal_sa_parentId INTEGER
|
,principal_sa_parentId INTEGER
|
||||||
|
|
||||||
|
,UNIQUE(principal_uid)
|
||||||
,UNIQUE(principal_salt)
|
,UNIQUE(principal_salt)
|
||||||
,UNIQUE(principal_user_email COLLATE NOCASE)
|
,UNIQUE(principal_user_email COLLATE NOCASE)
|
||||||
);
|
);
|
||||||
|
@ -37,6 +37,15 @@ func (s *ServiceAccountStore) Find(ctx context.Context, id int64) (*types.Servic
|
|||||||
return dst, nil
|
return 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 {
|
||||||
|
return nil, processSQLErrorf(err, "Select by uid query failed")
|
||||||
|
}
|
||||||
|
return dst, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Create saves the service account.
|
// Create saves the service account.
|
||||||
func (s *ServiceAccountStore) Create(ctx context.Context, sa *types.ServiceAccount) error {
|
func (s *ServiceAccountStore) Create(ctx context.Context, sa *types.ServiceAccount) error {
|
||||||
query, arg, err := s.db.BindNamed(serviceAccountInsert, sa)
|
query, arg, err := s.db.BindNamed(serviceAccountInsert, sa)
|
||||||
@ -113,8 +122,8 @@ WHERE principal_type = "serviceaccount" and principal_sa_parentType = $1 and pri
|
|||||||
const serviceAccountBase = `
|
const serviceAccountBase = `
|
||||||
SELECT
|
SELECT
|
||||||
principal_id
|
principal_id
|
||||||
|
,principal_uid
|
||||||
,principal_name
|
,principal_name
|
||||||
,principal_externalId
|
|
||||||
,principal_blocked
|
,principal_blocked
|
||||||
,principal_salt
|
,principal_salt
|
||||||
,principal_created
|
,principal_created
|
||||||
@ -133,6 +142,10 @@ const serviceAccountSelectID = serviceAccountBase + `
|
|||||||
WHERE principal_type = "serviceaccount" AND principal_id = $1
|
WHERE principal_type = "serviceaccount" AND principal_id = $1
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const serviceAccountSelectUID = serviceAccountBase + `
|
||||||
|
WHERE principal_type = "serviceaccount" AND principal_uid = $1
|
||||||
|
`
|
||||||
|
|
||||||
const serviceAccountDelete = `
|
const serviceAccountDelete = `
|
||||||
DELETE FROM principals
|
DELETE FROM principals
|
||||||
WHERE principal_type = "serviceaccount" AND principal_id = $1
|
WHERE principal_type = "serviceaccount" AND principal_id = $1
|
||||||
@ -141,9 +154,9 @@ WHERE principal_type = "serviceaccount" AND principal_id = $1
|
|||||||
const serviceAccountInsert = `
|
const serviceAccountInsert = `
|
||||||
INSERT INTO principals (
|
INSERT INTO principals (
|
||||||
principal_type
|
principal_type
|
||||||
|
,principal_uid
|
||||||
,principal_name
|
,principal_name
|
||||||
,principal_admin
|
,principal_admin
|
||||||
,principal_externalId
|
|
||||||
,principal_blocked
|
,principal_blocked
|
||||||
,principal_salt
|
,principal_salt
|
||||||
,principal_created
|
,principal_created
|
||||||
@ -152,9 +165,9 @@ principal_type
|
|||||||
,principal_sa_parentId
|
,principal_sa_parentId
|
||||||
) values (
|
) values (
|
||||||
"serviceaccount"
|
"serviceaccount"
|
||||||
|
,:principal_uid
|
||||||
,:principal_name
|
,:principal_name
|
||||||
,false
|
,false
|
||||||
,:principal_externalId
|
|
||||||
,:principal_blocked
|
,:principal_blocked
|
||||||
,:principal_salt
|
,:principal_salt
|
||||||
,:principal_created
|
,:principal_created
|
||||||
@ -168,7 +181,6 @@ const serviceAccountUpdate = `
|
|||||||
UPDATE principals
|
UPDATE principals
|
||||||
SET
|
SET
|
||||||
principal_name = :principal_name
|
principal_name = :principal_name
|
||||||
,:principal_externalId = :principal_externalId
|
|
||||||
,:principal_blocked = :principal_blocked
|
,:principal_blocked = :principal_blocked
|
||||||
,:principal_salt = :principal_salt
|
,:principal_salt = :principal_salt
|
||||||
,:principal_updated = :principal_updated
|
,:principal_updated = :principal_updated
|
||||||
|
@ -34,6 +34,13 @@ func (s *ServiceAccountStoreSync) Find(ctx context.Context, id int64) (*types.Se
|
|||||||
return s.base.Find(ctx, id)
|
return s.base.Find(ctx, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindUID finds the service account by uid.
|
||||||
|
func (s *ServiceAccountStoreSync) FindUID(ctx context.Context, uid string) (*types.ServiceAccount, error) {
|
||||||
|
mutex.RLock()
|
||||||
|
defer mutex.RUnlock()
|
||||||
|
return s.base.FindUID(ctx, uid)
|
||||||
|
}
|
||||||
|
|
||||||
// Create saves the service account.
|
// Create saves the service account.
|
||||||
func (s *ServiceAccountStoreSync) Create(ctx context.Context, sa *types.ServiceAccount) error {
|
func (s *ServiceAccountStoreSync) Create(ctx context.Context, sa *types.ServiceAccount) error {
|
||||||
mutex.RLock()
|
mutex.RLock()
|
||||||
|
2
internal/store/database/testdata/users.json
vendored
2
internal/store/database/testdata/users.json
vendored
@ -1,6 +1,7 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
|
"uid": "jane21",
|
||||||
"email": "jane@example.com",
|
"email": "jane@example.com",
|
||||||
"name": "jane",
|
"name": "jane",
|
||||||
"company": "acme",
|
"company": "acme",
|
||||||
@ -12,6 +13,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 2,
|
||||||
|
"uid": "john21",
|
||||||
"email": "john@example.com",
|
"email": "john@example.com",
|
||||||
"name": "john",
|
"name": "john",
|
||||||
"company": "acme",
|
"company": "acme",
|
||||||
|
@ -7,7 +7,6 @@ package database
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/harness/gitness/internal/store"
|
"github.com/harness/gitness/internal/store"
|
||||||
"github.com/harness/gitness/types"
|
"github.com/harness/gitness/types"
|
||||||
@ -39,6 +38,15 @@ func (s *UserStore) Find(ctx context.Context, id int64) (*types.User, error) {
|
|||||||
return dst, nil
|
return 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 {
|
||||||
|
return nil, processSQLErrorf(err, "Select by uid query failed")
|
||||||
|
}
|
||||||
|
return dst, nil
|
||||||
|
}
|
||||||
|
|
||||||
// FindEmail finds the user by email.
|
// FindEmail finds the user by email.
|
||||||
func (s *UserStore) FindEmail(ctx context.Context, email string) (*types.User, error) {
|
func (s *UserStore) FindEmail(ctx context.Context, email string) (*types.User, error) {
|
||||||
dst := new(types.User)
|
dst := new(types.User)
|
||||||
@ -48,15 +56,6 @@ func (s *UserStore) FindEmail(ctx context.Context, email string) (*types.User, e
|
|||||||
return dst, nil
|
return dst, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindKey finds the user unique key (email or id).
|
|
||||||
func (s *UserStore) FindKey(ctx context.Context, key string) (*types.User, error) {
|
|
||||||
id, err := strconv.ParseInt(key, 10, 64)
|
|
||||||
if err == nil {
|
|
||||||
return s.Find(ctx, id)
|
|
||||||
}
|
|
||||||
return s.FindEmail(ctx, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// List returns a list of users.
|
// List returns a list of users.
|
||||||
func (s *UserStore) List(ctx context.Context, opts *types.UserFilter) ([]*types.User, error) {
|
func (s *UserStore) List(ctx context.Context, opts *types.UserFilter) ([]*types.User, error) {
|
||||||
dst := []*types.User{}
|
dst := []*types.User{}
|
||||||
@ -88,8 +87,8 @@ func (s *UserStore) List(ctx context.Context, opts *types.UserFilter) ([]*types.
|
|||||||
stmt = stmt.OrderBy("principal_updated " + opts.Order.String())
|
stmt = stmt.OrderBy("principal_updated " + opts.Order.String())
|
||||||
case enum.UserAttrEmail:
|
case enum.UserAttrEmail:
|
||||||
stmt = stmt.OrderBy("principal_user_email " + opts.Order.String())
|
stmt = stmt.OrderBy("principal_user_email " + opts.Order.String())
|
||||||
case enum.UserAttrID:
|
case enum.UserAttrUID:
|
||||||
stmt = stmt.OrderBy("principal_id " + opts.Order.String())
|
stmt = stmt.OrderBy("principal_uid " + opts.Order.String())
|
||||||
case enum.UserAttrAdmin:
|
case enum.UserAttrAdmin:
|
||||||
stmt = stmt.OrderBy("principal_admin " + opts.Order.String())
|
stmt = stmt.OrderBy("principal_admin " + opts.Order.String())
|
||||||
}
|
}
|
||||||
@ -135,7 +134,7 @@ func (s *UserStore) Update(ctx context.Context, user *types.User) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes the user.
|
// Delete deletes the user.
|
||||||
func (s *UserStore) Delete(ctx context.Context, user *types.User) error {
|
func (s *UserStore) Delete(ctx context.Context, id int64) error {
|
||||||
tx, err := s.db.BeginTx(ctx, nil)
|
tx, err := s.db.BeginTx(ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return processSQLErrorf(err, "Failed to start a new transaction")
|
return processSQLErrorf(err, "Failed to start a new transaction")
|
||||||
@ -144,7 +143,7 @@ func (s *UserStore) Delete(ctx context.Context, user *types.User) error {
|
|||||||
_ = tx.Rollback()
|
_ = tx.Rollback()
|
||||||
}(tx)
|
}(tx)
|
||||||
// delete the user
|
// delete the user
|
||||||
if _, err = tx.ExecContext(ctx, userDelete, user.ID); err != nil {
|
if _, err = tx.ExecContext(ctx, userDelete, id); err != nil {
|
||||||
return processSQLErrorf(err, "The delete query failed")
|
return processSQLErrorf(err, "The delete query failed")
|
||||||
}
|
}
|
||||||
return tx.Commit()
|
return tx.Commit()
|
||||||
@ -169,9 +168,9 @@ WHERE principal_type = "user"
|
|||||||
const userBase = `
|
const userBase = `
|
||||||
SELECT
|
SELECT
|
||||||
principal_id
|
principal_id
|
||||||
|
,principal_uid
|
||||||
,principal_name
|
,principal_name
|
||||||
,principal_admin
|
,principal_admin
|
||||||
,principal_externalId
|
|
||||||
,principal_blocked
|
,principal_blocked
|
||||||
,principal_salt
|
,principal_salt
|
||||||
,principal_created
|
,principal_created
|
||||||
@ -191,6 +190,10 @@ const userSelectID = userBase + `
|
|||||||
WHERE principal_type = "user" AND principal_id = $1
|
WHERE principal_type = "user" AND principal_id = $1
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const userSelectUID = userBase + `
|
||||||
|
WHERE principal_type = "user" AND principal_uid = $1
|
||||||
|
`
|
||||||
|
|
||||||
const userSelectEmail = userBase + `
|
const userSelectEmail = userBase + `
|
||||||
WHERE principal_type = "user" AND principal_user_email = $1
|
WHERE principal_type = "user" AND principal_user_email = $1
|
||||||
`
|
`
|
||||||
@ -203,9 +206,9 @@ WHERE principal_type = "user" AND principal_id = $1
|
|||||||
const userInsert = `
|
const userInsert = `
|
||||||
INSERT INTO principals (
|
INSERT INTO principals (
|
||||||
principal_type
|
principal_type
|
||||||
|
,principal_uid
|
||||||
,principal_name
|
,principal_name
|
||||||
,principal_admin
|
,principal_admin
|
||||||
,principal_externalId
|
|
||||||
,principal_blocked
|
,principal_blocked
|
||||||
,principal_salt
|
,principal_salt
|
||||||
,principal_created
|
,principal_created
|
||||||
@ -214,9 +217,9 @@ principal_type
|
|||||||
,principal_user_password
|
,principal_user_password
|
||||||
) values (
|
) values (
|
||||||
"user"
|
"user"
|
||||||
|
,:principal_uid
|
||||||
,:principal_name
|
,:principal_name
|
||||||
,:principal_admin
|
,:principal_admin
|
||||||
,:principal_externalId
|
|
||||||
,:principal_blocked
|
,:principal_blocked
|
||||||
,:principal_salt
|
,:principal_salt
|
||||||
,:principal_created
|
,:principal_created
|
||||||
@ -231,7 +234,6 @@ UPDATE principals
|
|||||||
SET
|
SET
|
||||||
principal_name = :principal_name
|
principal_name = :principal_name
|
||||||
,principal_admin = :principal_admin
|
,principal_admin = :principal_admin
|
||||||
,principal_externalId = :principal_externalId
|
|
||||||
,principal_blocked = :principal_blocked
|
,principal_blocked = :principal_blocked
|
||||||
,principal_salt = :principal_salt
|
,principal_salt = :principal_salt
|
||||||
,principal_updated = :principal_updated
|
,principal_updated = :principal_updated
|
||||||
|
@ -31,6 +31,13 @@ func (s *UserStoreSync) Find(ctx context.Context, id int64) (*types.User, error)
|
|||||||
return s.base.Find(ctx, id)
|
return s.base.Find(ctx, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindUID finds the user by uid.
|
||||||
|
func (s *UserStoreSync) FindUID(ctx context.Context, uid string) (*types.User, error) {
|
||||||
|
mutex.RLock()
|
||||||
|
defer mutex.RUnlock()
|
||||||
|
return s.base.FindUID(ctx, uid)
|
||||||
|
}
|
||||||
|
|
||||||
// FindEmail finds the user by email.
|
// FindEmail finds the user by email.
|
||||||
func (s *UserStoreSync) FindEmail(ctx context.Context, email string) (*types.User, error) {
|
func (s *UserStoreSync) FindEmail(ctx context.Context, email string) (*types.User, error) {
|
||||||
mutex.RLock()
|
mutex.RLock()
|
||||||
@ -38,13 +45,6 @@ func (s *UserStoreSync) FindEmail(ctx context.Context, email string) (*types.Use
|
|||||||
return s.base.FindEmail(ctx, email)
|
return s.base.FindEmail(ctx, email)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindKey finds the user unique key (email or id).
|
|
||||||
func (s *UserStoreSync) FindKey(ctx context.Context, key string) (*types.User, error) {
|
|
||||||
mutex.RLock()
|
|
||||||
defer mutex.RUnlock()
|
|
||||||
return s.base.FindKey(ctx, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// List returns a list of users.
|
// List returns a list of users.
|
||||||
func (s *UserStoreSync) List(ctx context.Context, opts *types.UserFilter) ([]*types.User, error) {
|
func (s *UserStoreSync) List(ctx context.Context, opts *types.UserFilter) ([]*types.User, error) {
|
||||||
mutex.RLock()
|
mutex.RLock()
|
||||||
@ -67,10 +67,10 @@ func (s *UserStoreSync) Update(ctx context.Context, user *types.User) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes the user.
|
// Delete deletes the user.
|
||||||
func (s *UserStoreSync) Delete(ctx context.Context, user *types.User) error {
|
func (s *UserStoreSync) Delete(ctx context.Context, id int64) error {
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
defer mutex.Unlock()
|
defer mutex.Unlock()
|
||||||
return s.base.Delete(ctx, user)
|
return s.base.Delete(ctx, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count returns a count of users.
|
// Count returns a count of users.
|
||||||
|
@ -141,6 +141,18 @@ func testUserFind(store store.UserStore) func(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("uid", func(t *testing.T) {
|
||||||
|
got, err := store.FindUID(ctx, "jane21")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(got, want, userIgnore); len(diff) != 0 {
|
||||||
|
t.Errorf(diff)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("email", func(t *testing.T) {
|
t.Run("email", func(t *testing.T) {
|
||||||
got, err := store.FindEmail(ctx, want.Email)
|
got, err := store.FindEmail(ctx, want.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -164,30 +176,6 @@ func testUserFind(store store.UserStore) func(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("key/id", func(t *testing.T) {
|
|
||||||
got, err := store.FindKey(ctx, "1")
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if diff := cmp.Diff(got, want, userIgnore); len(diff) != 0 {
|
|
||||||
t.Errorf(diff)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("key/email", func(t *testing.T) {
|
|
||||||
got, err := store.FindKey(ctx, want.Email)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if diff := cmp.Diff(got, want, userIgnore); len(diff) != 0 {
|
|
||||||
t.Errorf(diff)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,12 +235,12 @@ func testUserUpdate(store store.UserStore) func(t *testing.T) {
|
|||||||
func testUserDelete(s store.UserStore) func(t *testing.T) {
|
func testUserDelete(s store.UserStore) func(t *testing.T) {
|
||||||
return func(t *testing.T) {
|
return func(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
v, err := s.Find(ctx, 1)
|
_, err := s.Find(ctx, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = s.Delete(ctx, v); err != nil {
|
if err = s.Delete(ctx, 1); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ func offset(page, size int) int {
|
|||||||
// Logs the error and message, returns either the original error or a store equivalent if possible.
|
// Logs the error and message, returns either the original error or a store equivalent if possible.
|
||||||
func processSQLErrorf(err error, format string, args ...interface{}) error {
|
func processSQLErrorf(err error, format string, args ...interface{}) error {
|
||||||
// always log DB error (print formated message)
|
// always log DB error (print formated message)
|
||||||
log.Warn().Msgf("%s %s", fmt.Sprintf(format, args...), err)
|
log.Debug().Msgf("%s %s", fmt.Sprintf(format, args...), err)
|
||||||
|
|
||||||
// If it's a known error, return converted error instead.
|
// If it's a known error, return converted error instead.
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
@ -18,12 +18,12 @@ type (
|
|||||||
// Find finds the user by id.
|
// Find finds the user by id.
|
||||||
Find(ctx context.Context, id int64) (*types.User, error)
|
Find(ctx context.Context, id int64) (*types.User, error)
|
||||||
|
|
||||||
|
// FindUID finds the user by uid.
|
||||||
|
FindUID(ctx context.Context, uid string) (*types.User, error)
|
||||||
|
|
||||||
// FindEmail finds the user by email.
|
// FindEmail finds the user by email.
|
||||||
FindEmail(ctx context.Context, email string) (*types.User, error)
|
FindEmail(ctx context.Context, email string) (*types.User, error)
|
||||||
|
|
||||||
// FindKey finds the user by unique key (email or id).
|
|
||||||
FindKey(ctx context.Context, key string) (*types.User, error)
|
|
||||||
|
|
||||||
// Create saves the user details.
|
// Create saves the user details.
|
||||||
Create(ctx context.Context, user *types.User) error
|
Create(ctx context.Context, user *types.User) error
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ type (
|
|||||||
Update(ctx context.Context, user *types.User) error
|
Update(ctx context.Context, user *types.User) error
|
||||||
|
|
||||||
// Delete deletes the user.
|
// Delete deletes the user.
|
||||||
Delete(ctx context.Context, user *types.User) error
|
Delete(ctx context.Context, id int64) error
|
||||||
|
|
||||||
// List returns a list of users.
|
// List returns a list of users.
|
||||||
List(ctx context.Context, params *types.UserFilter) ([]*types.User, error)
|
List(ctx context.Context, params *types.UserFilter) ([]*types.User, error)
|
||||||
@ -45,6 +45,9 @@ type (
|
|||||||
// Find finds the service account by id.
|
// Find finds the service account by id.
|
||||||
Find(ctx context.Context, id int64) (*types.ServiceAccount, error)
|
Find(ctx context.Context, id int64) (*types.ServiceAccount, error)
|
||||||
|
|
||||||
|
// FindUID finds the service account by uid.
|
||||||
|
FindUID(ctx context.Context, uid string) (*types.ServiceAccount, error)
|
||||||
|
|
||||||
// Create saves the service account.
|
// Create saves the service account.
|
||||||
Create(ctx context.Context, sa *types.ServiceAccount) error
|
Create(ctx context.Context, sa *types.ServiceAccount) error
|
||||||
|
|
||||||
|
@ -51,18 +51,18 @@ func (mr *MockClientMockRecorder) Login(arg0, arg1, arg2 interface{}) *gomock.Ca
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Register mocks base method.
|
// Register mocks base method.
|
||||||
func (m *MockClient) Register(arg0 context.Context, arg1, arg2 string) (*types.TokenResponse, error) {
|
func (m *MockClient) Register(arg0 context.Context, arg1, arg2, arg3, arg4 string) (*types.TokenResponse, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Register", arg0, arg1, arg2)
|
ret := m.ctrl.Call(m, "Register", arg0, arg1, arg2, arg3, arg4)
|
||||||
ret0, _ := ret[0].(*types.TokenResponse)
|
ret0, _ := ret[0].(*types.TokenResponse)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register indicates an expected call of Register.
|
// Register indicates an expected call of Register.
|
||||||
func (mr *MockClientMockRecorder) Register(arg0, arg1, arg2 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) Register(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Register", reflect.TypeOf((*MockClient)(nil).Register), arg0, arg1, arg2)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Register", reflect.TypeOf((*MockClient)(nil).Register), arg0, arg1, arg2, arg3, arg4)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Self mocks base method.
|
// Self mocks base method.
|
||||||
|
@ -102,7 +102,7 @@ func (mr *MockUserStoreMockRecorder) Create(arg0, arg1 interface{}) *gomock.Call
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete mocks base method.
|
// Delete mocks base method.
|
||||||
func (m *MockUserStore) Delete(arg0 context.Context, arg1 *types.User) error {
|
func (m *MockUserStore) Delete(arg0 context.Context, arg1 int64) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Delete", arg0, arg1)
|
ret := m.ctrl.Call(m, "Delete", arg0, arg1)
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
@ -145,19 +145,19 @@ func (mr *MockUserStoreMockRecorder) FindEmail(arg0, arg1 interface{}) *gomock.C
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindEmail", reflect.TypeOf((*MockUserStore)(nil).FindEmail), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindEmail", reflect.TypeOf((*MockUserStore)(nil).FindEmail), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindKey mocks base method.
|
// FindUID mocks base method.
|
||||||
func (m *MockUserStore) FindKey(arg0 context.Context, arg1 string) (*types.User, error) {
|
func (m *MockUserStore) FindUID(arg0 context.Context, arg1 string) (*types.User, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "FindKey", arg0, arg1)
|
ret := m.ctrl.Call(m, "FindUID", arg0, arg1)
|
||||||
ret0, _ := ret[0].(*types.User)
|
ret0, _ := ret[0].(*types.User)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindKey indicates an expected call of FindKey.
|
// FindUID indicates an expected call of FindUID.
|
||||||
func (mr *MockUserStoreMockRecorder) FindKey(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockUserStoreMockRecorder) FindUID(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindKey", reflect.TypeOf((*MockUserStore)(nil).FindKey), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindUID", reflect.TypeOf((*MockUserStore)(nil).FindUID), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// List mocks base method.
|
// List mocks base method.
|
||||||
|
@ -17,20 +17,32 @@ const (
|
|||||||
minNameLength = 1
|
minNameLength = 1
|
||||||
maxNameLength = 256
|
maxNameLength = 256
|
||||||
nameRegex = "^[a-zA-Z][a-zA-Z0-9\\-\\_ ]*$"
|
nameRegex = "^[a-zA-Z][a-zA-Z0-9\\-\\_ ]*$"
|
||||||
|
|
||||||
|
minUIDLength = 2
|
||||||
|
maxUIDLength = 64
|
||||||
|
uidRegex = "^[a-z][a-z0-9\\-\\_]*$"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrPathNameLength = &ValidationError{
|
ErrPathNameLength = &ValidationError{
|
||||||
fmt.Sprintf("Path name has to be between %d and %d in length.", minPathNameLength, maxPathNameLength),
|
fmt.Sprintf("Path name has to be between %d and %d in length.", minPathNameLength, maxPathNameLength),
|
||||||
}
|
}
|
||||||
ErrPathNameRegex = &ValidationError{"Path name has start with a letter and only contain the following [a-z0-9-_]."}
|
ErrPathNameRegex = &ValidationError{"Path name has to start with a letter and only contain the following [a-z0-9-_]."}
|
||||||
|
|
||||||
ErrNameLength = &ValidationError{
|
ErrNameLength = &ValidationError{
|
||||||
fmt.Sprintf("Name has to be between %d and %d in length.",
|
fmt.Sprintf("Name has to be between %d and %d in length.",
|
||||||
minNameLength, maxNameLength),
|
minNameLength, maxNameLength),
|
||||||
}
|
}
|
||||||
ErrNameRegex = &ValidationError{
|
ErrNameRegex = &ValidationError{
|
||||||
"Name has start with a letter and only contain the following [a-zA-Z0-9-_ ].",
|
"Name has to start with a letter and only contain the following [a-zA-Z0-9-_ ].",
|
||||||
|
}
|
||||||
|
|
||||||
|
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-_].",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -61,3 +73,17 @@ func Name(name string) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UID checks the provided uid and returns an error in it isn't valid.
|
||||||
|
func UID(uid string) error {
|
||||||
|
l := len(uid)
|
||||||
|
if l < minUIDLength || l > maxUIDLength {
|
||||||
|
return ErrUIDLength
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok, _ := regexp.Match(uidRegex, []byte(uid)); !ok {
|
||||||
|
return ErrUIDRegex
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -17,6 +17,11 @@ var (
|
|||||||
|
|
||||||
// ServiceAccount returns true if the ServiceAccount if valid.
|
// ServiceAccount returns true if the ServiceAccount if valid.
|
||||||
func ServiceAccount(sa *types.ServiceAccount) error {
|
func ServiceAccount(sa *types.ServiceAccount) error {
|
||||||
|
// validate UID
|
||||||
|
if err := UID(sa.UID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// verify name
|
// verify name
|
||||||
if err := Name(sa.Name); err != nil {
|
if err := Name(sa.Name); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -25,6 +25,11 @@ var (
|
|||||||
|
|
||||||
// User returns true if the User if valid.
|
// User returns true if the User if valid.
|
||||||
func User(user *types.User) error {
|
func User(user *types.User) error {
|
||||||
|
// validate UID
|
||||||
|
if err := UID(user.UID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// validate name
|
// validate name
|
||||||
if err := Name(user.Name); err != nil {
|
if err := Name(user.Name); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -8,60 +8,60 @@ import "time"
|
|||||||
|
|
||||||
// Config stores the system configuration.
|
// Config stores the system configuration.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Debug bool `envconfig:"APP_DEBUG"`
|
Debug bool `envconfig:"GITNESS_DEBUG"`
|
||||||
Trace bool `envconfig:"APP_TRACE"`
|
Trace bool `envconfig:"GITNESS_TRACE"`
|
||||||
|
|
||||||
// Server defines the server configuration parameters.
|
// Server defines the server configuration parameters.
|
||||||
Server struct {
|
Server struct {
|
||||||
Bind string `envconfig:"APP_HTTP_BIND" default:":3000"`
|
Bind string `envconfig:"GITNESS_HTTP_BIND" default:":3000"`
|
||||||
Proto string `envconfig:"APP_HTTP_PROTO"`
|
Proto string `envconfig:"GITNESS_HTTP_PROTO"`
|
||||||
Host string `envconfig:"APP_HTTP_HOST"`
|
Host string `envconfig:"GITNESS_HTTP_HOST"`
|
||||||
|
|
||||||
// Acme defines Acme configuration parameters.
|
// Acme defines Acme configuration parameters.
|
||||||
Acme struct {
|
Acme struct {
|
||||||
Enabled bool `envconfig:"APP_ACME_ENABLED"`
|
Enabled bool `envconfig:"GITNESS_ACME_ENABLED"`
|
||||||
Endpont string `envconfig:"APP_ACME_ENDPOINT"`
|
Endpont string `envconfig:"GITNESS_ACME_ENDPOINT"`
|
||||||
Email bool `envconfig:"APP_ACME_EMAIL"`
|
Email bool `envconfig:"GITNESS_ACME_EMAIL"`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Database defines the database configuration parameters.
|
// Database defines the database configuration parameters.
|
||||||
Database struct {
|
Database struct {
|
||||||
Driver string `envconfig:"APP_DATABASE_DRIVER" default:"sqlite3"`
|
Driver string `envconfig:"GITNESS_DATABASE_DRIVER" default:"sqlite3"`
|
||||||
Datasource string `envconfig:"APP_DATABASE_DATASOURCE" default:"database.sqlite3"`
|
Datasource string `envconfig:"GITNESS_DATABASE_DATASOURCE" default:"database.sqlite3"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Token defines token configuration parameters.
|
// Token defines token configuration parameters.
|
||||||
Token struct {
|
Token struct {
|
||||||
Expire time.Duration `envconfig:"APP_TOKEN_EXPIRE" default:"720h"`
|
Expire time.Duration `envconfig:"GITNESS_TOKEN_EXPIRE" default:"720h"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cors defines http cors parameters
|
// Cors defines http cors parameters
|
||||||
Cors struct {
|
Cors struct {
|
||||||
AllowedOrigins []string `envconfig:"APP_CORS_ALLOWED_ORIGINS" default:"*"`
|
AllowedOrigins []string `envconfig:"GITNESS_CORS_ALLOWED_ORIGINS" default:"*"`
|
||||||
AllowedMethods []string `envconfig:"APP_CORS_ALLOWED_METHODS" default:"GET,POST,PATCH,PUT,DELETE,OPTIONS"`
|
AllowedMethods []string `envconfig:"GITNESS_CORS_ALLOWED_METHODS" default:"GET,POST,PATCH,PUT,DELETE,OPTIONS"`
|
||||||
AllowedHeaders []string `envconfig:"APP_CORS_ALLOWED_HEADERS" default:"Origin,Accept,Accept-Language,Authorization,Content-Type,Content-Language,X-Requested-With,X-Request-Id"` //nolint:lll // struct tags can't be multiline
|
AllowedHeaders []string `envconfig:"GITNESS_CORS_ALLOWED_HEADERS" default:"Origin,Accept,Accept-Language,Authorization,Content-Type,Content-Language,X-Requested-With,X-Request-Id"` //nolint:lll // struct tags can't be multiline
|
||||||
ExposedHeaders []string `envconfig:"APP_CORS_EXPOSED_HEADERS" default:"Link"`
|
ExposedHeaders []string `envconfig:"GITNESS_CORS_EXPOSED_HEADERS" default:"Link"`
|
||||||
AllowCredentials bool `envconfig:"APP_CORS_ALLOW_CREDENTIALS" default:"true"`
|
AllowCredentials bool `envconfig:"GITNESS_CORS_ALLOW_CREDENTIALS" default:"true"`
|
||||||
MaxAge int `envconfig:"APP_CORS_MAX_AGE" default:"300"`
|
MaxAge int `envconfig:"GITNESS_CORS_MAX_AGE" default:"300"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Secure defines http security parameters.
|
// Secure defines http security parameters.
|
||||||
Secure struct {
|
Secure struct {
|
||||||
AllowedHosts []string `envconfig:"APP_HTTP_ALLOWED_HOSTS"`
|
AllowedHosts []string `envconfig:"GITNESS_HTTP_ALLOWED_HOSTS"`
|
||||||
HostsProxyHeaders []string `envconfig:"APP_HTTP_PROXY_HEADERS"`
|
HostsProxyHeaders []string `envconfig:"GITNESS_HTTP_PROXY_HEADERS"`
|
||||||
SSLRedirect bool `envconfig:"APP_HTTP_SSL_REDIRECT"`
|
SSLRedirect bool `envconfig:"GITNESS_HTTP_SSL_REDIRECT"`
|
||||||
SSLTemporaryRedirect bool `envconfig:"APP_HTTP_SSL_TEMPORARY_REDIRECT"`
|
SSLTemporaryRedirect bool `envconfig:"GITNESS_HTTP_SSL_TEMPORARY_REDIRECT"`
|
||||||
SSLHost string `envconfig:"APP_HTTP_SSL_HOST"`
|
SSLHost string `envconfig:"GITNESS_HTTP_SSL_HOST"`
|
||||||
SSLProxyHeaders map[string]string `envconfig:"APP_HTTP_SSL_PROXY_HEADERS"`
|
SSLProxyHeaders map[string]string `envconfig:"GITNESS_HTTP_SSL_PROXY_HEADERS"`
|
||||||
STSSeconds int64 `envconfig:"APP_HTTP_STS_SECONDS"`
|
STSSeconds int64 `envconfig:"GITNESS_HTTP_STS_SECONDS"`
|
||||||
STSIncludeSubdomains bool `envconfig:"APP_HTTP_STS_INCLUDE_SUBDOMAINS"`
|
STSIncludeSubdomains bool `envconfig:"GITNESS_HTTP_STS_INCLUDE_SUBDOMAINS"`
|
||||||
STSPreload bool `envconfig:"APP_HTTP_STS_PRELOAD"`
|
STSPreload bool `envconfig:"GITNESS_HTTP_STS_PRELOAD"`
|
||||||
ForceSTSHeader bool `envconfig:"APP_HTTP_STS_FORCE_HEADER"`
|
ForceSTSHeader bool `envconfig:"GITNESS_HTTP_STS_FORCE_HEADER"`
|
||||||
BrowserXSSFilter bool `envconfig:"APP_HTTP_BROWSER_XSS_FILTER" default:"true"`
|
BrowserXSSFilter bool `envconfig:"GITNESS_HTTP_BROWSER_XSS_FILTER" default:"true"`
|
||||||
FrameDeny bool `envconfig:"APP_HTTP_FRAME_DENY" default:"true"`
|
FrameDeny bool `envconfig:"GITNESS_HTTP_FRAME_DENY" default:"true"`
|
||||||
ContentTypeNosniff bool `envconfig:"APP_HTTP_CONTENT_TYPE_NO_SNIFF"`
|
ContentTypeNosniff bool `envconfig:"GITNESS_HTTP_CONTENT_TYPE_NO_SNIFF"`
|
||||||
ContentSecurityPolicy string `envconfig:"APP_HTTP_CONTENT_SECURITY_POLICY"`
|
ContentSecurityPolicy string `envconfig:"GITNESS_HTTP_CONTENT_SECURITY_POLICY"`
|
||||||
ReferrerPolicy string `envconfig:"APP_HTTP_REFERRER_POLICY"`
|
ReferrerPolicy string `envconfig:"GITNESS_HTTP_REFERRER_POLICY"`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ type UserAttr int
|
|||||||
// Order enumeration.
|
// Order enumeration.
|
||||||
const (
|
const (
|
||||||
UserAttrNone UserAttr = iota
|
UserAttrNone UserAttr = iota
|
||||||
UserAttrID
|
UserAttrUID
|
||||||
UserAttrName
|
UserAttrName
|
||||||
UserAttrEmail
|
UserAttrEmail
|
||||||
UserAttrAdmin
|
UserAttrAdmin
|
||||||
@ -26,7 +26,7 @@ const (
|
|||||||
func ParseUserAttr(s string) UserAttr {
|
func ParseUserAttr(s string) UserAttr {
|
||||||
switch strings.ToLower(s) {
|
switch strings.ToLower(s) {
|
||||||
case "id":
|
case "id":
|
||||||
return UserAttrID
|
return UserAttrUID
|
||||||
case "name":
|
case "name":
|
||||||
return UserAttrName
|
return UserAttrName
|
||||||
case "email":
|
case "email":
|
||||||
|
@ -11,7 +11,7 @@ func TestParseUserAttr(t *testing.T) {
|
|||||||
text string
|
text string
|
||||||
want UserAttr
|
want UserAttr
|
||||||
}{
|
}{
|
||||||
{"id", UserAttrID},
|
{"id", UserAttrUID},
|
||||||
{"name", UserAttrName},
|
{"name", UserAttrName},
|
||||||
{"email", UserAttrEmail},
|
{"email", UserAttrEmail},
|
||||||
{"created", UserAttrCreated},
|
{"created", UserAttrCreated},
|
||||||
|
@ -10,13 +10,14 @@ import "github.com/harness/gitness/types/enum"
|
|||||||
type (
|
type (
|
||||||
// Represents the identity of an acting entity (User, ServiceAccount, Service).
|
// Represents the identity of an acting entity (User, ServiceAccount, Service).
|
||||||
Principal struct {
|
Principal struct {
|
||||||
ID int64 `db:"principal_id" json:"id"`
|
// ID is the internal identifier of a principal (primary key)
|
||||||
|
ID int64 `db:"principal_id" json:"-"`
|
||||||
|
UID string `db:"principal_uid" json:"uid"`
|
||||||
Type enum.PrincipalType `db:"principal_type" json:"type"`
|
Type enum.PrincipalType `db:"principal_type" json:"type"`
|
||||||
Name string `db:"principal_name" json:"name"`
|
Name string `db:"principal_name" json:"name"`
|
||||||
Admin bool `db:"principal_admin" json:"admin"`
|
Admin bool `db:"principal_admin" json:"admin"`
|
||||||
|
|
||||||
// Should be part of principal or not?
|
// Should be part of principal or not?
|
||||||
ExternalID string `db:"principal_externalId" json:"externalId"`
|
|
||||||
Blocked bool `db:"principal_blocked" json:"blocked"`
|
Blocked bool `db:"principal_blocked" json:"blocked"`
|
||||||
Salt string `db:"principal_salt" json:"-"`
|
Salt string `db:"principal_salt" json:"-"`
|
||||||
|
|
||||||
@ -29,10 +30,10 @@ type (
|
|||||||
func PrincipalFromUser(user *User) *Principal {
|
func PrincipalFromUser(user *User) *Principal {
|
||||||
return &Principal{
|
return &Principal{
|
||||||
ID: user.ID,
|
ID: user.ID,
|
||||||
|
UID: user.UID,
|
||||||
Type: enum.PrincipalTypeUser,
|
Type: enum.PrincipalTypeUser,
|
||||||
Name: user.Name,
|
Name: user.Name,
|
||||||
Admin: user.Admin,
|
Admin: user.Admin,
|
||||||
ExternalID: user.ExternalID,
|
|
||||||
Blocked: user.Blocked,
|
Blocked: user.Blocked,
|
||||||
Salt: user.Salt,
|
Salt: user.Salt,
|
||||||
Created: user.Created,
|
Created: user.Created,
|
||||||
@ -43,13 +44,27 @@ func PrincipalFromUser(user *User) *Principal {
|
|||||||
func PrincipalFromServiceAccount(sa *ServiceAccount) *Principal {
|
func PrincipalFromServiceAccount(sa *ServiceAccount) *Principal {
|
||||||
return &Principal{
|
return &Principal{
|
||||||
ID: sa.ID,
|
ID: sa.ID,
|
||||||
|
UID: sa.UID,
|
||||||
Type: enum.PrincipalTypeServiceAccount,
|
Type: enum.PrincipalTypeServiceAccount,
|
||||||
Name: sa.Name,
|
Name: sa.Name,
|
||||||
Admin: false,
|
Admin: false,
|
||||||
ExternalID: sa.ExternalID,
|
|
||||||
Blocked: sa.Blocked,
|
Blocked: sa.Blocked,
|
||||||
Salt: sa.Salt,
|
Salt: sa.Salt,
|
||||||
Created: sa.Created,
|
Created: sa.Created,
|
||||||
Updated: sa.Updated,
|
Updated: sa.Updated,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func PrincipalFromService(s *Service) *Principal {
|
||||||
|
return &Principal{
|
||||||
|
ID: s.ID,
|
||||||
|
UID: s.UID,
|
||||||
|
Type: enum.PrincipalTypeService,
|
||||||
|
Name: s.Name,
|
||||||
|
Admin: true,
|
||||||
|
Blocked: s.Blocked,
|
||||||
|
Salt: s.Salt,
|
||||||
|
Created: s.Created,
|
||||||
|
Updated: s.Updated,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -10,11 +10,11 @@ import "github.com/harness/gitness/types/enum"
|
|||||||
type (
|
type (
|
||||||
// Service is a principal representing a different internal service that runs alongside gitness.
|
// Service is a principal representing a different internal service that runs alongside gitness.
|
||||||
Service struct {
|
Service struct {
|
||||||
// Fields from Principal (without admin)
|
// Fields from Principal (without admin, as it's always admin for now)
|
||||||
ID int64 `db:"principal_id" json:"id"`
|
ID int64 `db:"principal_id" json:"-"`
|
||||||
|
UID string `db:"principal_uid" json:"uid"`
|
||||||
Name string `db:"principal_name" json:"name"`
|
Name string `db:"principal_name" json:"name"`
|
||||||
Admin bool `db:"principal_admin" json:"admin"`
|
Admin bool `db:"principal_admin" json:"admin"`
|
||||||
ExternalID string `db:"principal_externalId" json:"externalId"`
|
|
||||||
Blocked bool `db:"principal_blocked" json:"blocked"`
|
Blocked bool `db:"principal_blocked" json:"blocked"`
|
||||||
Salt string `db:"principal_salt" json:"-"`
|
Salt string `db:"principal_salt" json:"-"`
|
||||||
Created int64 `db:"principal_created" json:"created"`
|
Created int64 `db:"principal_created" json:"created"`
|
||||||
|
@ -10,10 +10,10 @@ import "github.com/harness/gitness/types/enum"
|
|||||||
type (
|
type (
|
||||||
// ServiceAccount is a principal representing a service account.
|
// ServiceAccount is a principal representing a service account.
|
||||||
ServiceAccount struct {
|
ServiceAccount struct {
|
||||||
// Fields from Principal (without admin)
|
// Fields from Principal (without admin, as it's never an admin)
|
||||||
ID int64 `db:"principal_id" json:"id"`
|
ID int64 `db:"principal_id" json:"-"`
|
||||||
|
UID string `db:"principal_uid" json:"uid"`
|
||||||
Name string `db:"principal_name" json:"name"`
|
Name string `db:"principal_name" json:"name"`
|
||||||
ExternalID string `db:"principal_externalId" json:"externalId"`
|
|
||||||
Blocked bool `db:"principal_blocked" json:"blocked"`
|
Blocked bool `db:"principal_blocked" json:"blocked"`
|
||||||
Salt string `db:"principal_salt" json:"-"`
|
Salt string `db:"principal_salt" json:"-"`
|
||||||
Created int64 `db:"principal_created" json:"created"`
|
Created int64 `db:"principal_created" json:"created"`
|
||||||
|
@ -13,10 +13,10 @@ type (
|
|||||||
// User is a principal representing an end user.
|
// User is a principal representing an end user.
|
||||||
User struct {
|
User struct {
|
||||||
// Fields from Principal
|
// Fields from Principal
|
||||||
ID int64 `db:"principal_id" json:"id"`
|
ID int64 `db:"principal_id" json:"-"`
|
||||||
|
UID string `db:"principal_uid" json:"uid"`
|
||||||
Name string `db:"principal_name" json:"name"`
|
Name string `db:"principal_name" json:"name"`
|
||||||
Admin bool `db:"principal_admin" json:"admin"`
|
Admin bool `db:"principal_admin" json:"admin"`
|
||||||
ExternalID string `db:"principal_externalId" json:"externalId"`
|
|
||||||
Blocked bool `db:"principal_blocked" json:"blocked"`
|
Blocked bool `db:"principal_blocked" json:"blocked"`
|
||||||
Salt string `db:"principal_salt" json:"-"`
|
Salt string `db:"principal_salt" json:"-"`
|
||||||
Created int64 `db:"principal_created" json:"created"`
|
Created int64 `db:"principal_created" json:"created"`
|
||||||
|
Loading…
Reference in New Issue
Block a user