mirror of
https://github.com/harness/drone.git
synced 2025-05-04 00:10:07 +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)
|
||||
@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"
|
||||
|
||||
build: generate ## Build the gitness service binary
|
||||
@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 .
|
||||
|
||||
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
|
||||
@echo "Running tests"
|
||||
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
|
||||
# the source file has changed.
|
||||
###########################################
|
||||
cli/server/wire_gen.go: cli/server/wire.go ## Update the wire dependency injection if wire.go has changed.
|
||||
@echo "Updating wire_gen.go"
|
||||
go generate ./cli/server/wire_gen.go
|
||||
cli/server/harness.wire_gen.go: cli/server/harness.wire.go ## Update the wire dependency injection if harness.wire.go has changed.
|
||||
@echo "Updating harness.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
|
||||
go generate mocks/mock.go
|
||||
|
@ -19,11 +19,11 @@ type registerCommand struct {
|
||||
}
|
||||
|
||||
func (c *registerCommand) run(*kingpin.ParseContext) error {
|
||||
username, password := util.Credentials()
|
||||
username, name, email, password := util.Registration()
|
||||
httpClient := client.New(c.server)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer cancel()
|
||||
ts, err := httpClient.Register(ctx, username, password)
|
||||
ts, err := httpClient.Register(ctx, username, name, email, password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -18,6 +18,8 @@ import (
|
||||
)
|
||||
|
||||
const userTmpl = `
|
||||
uid: {{ .UID }}
|
||||
name: {{ .Name }}
|
||||
email: {{ .Email }}
|
||||
admin: {{ .Admin }}
|
||||
`
|
||||
|
@ -5,30 +5,14 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/harness/gitness/types"
|
||||
|
||||
"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
|
||||
// host environment.
|
||||
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)
|
||||
// read the configuration from the environment and
|
||||
// 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
|
||||
// that can be found in the LICENSE.md file for this repository.
|
||||
|
||||
//go:build wireinject
|
||||
// +build wireinject
|
||||
//go:build wireinject && !harness
|
||||
// +build wireinject,!harness
|
||||
|
||||
package server
|
||||
|
||||
@ -14,6 +14,7 @@ import (
|
||||
"github.com/harness/gitness/internal/auth/authz"
|
||||
"github.com/harness/gitness/internal/cron"
|
||||
"github.com/harness/gitness/internal/router"
|
||||
"github.com/harness/gitness/internal/router/translator"
|
||||
"github.com/harness/gitness/internal/server"
|
||||
"github.com/harness/gitness/internal/store/database"
|
||||
"github.com/harness/gitness/internal/store/memory"
|
||||
@ -24,14 +25,15 @@ import (
|
||||
|
||||
func initSystem(ctx context.Context, config *types.Config) (*system, error) {
|
||||
wire.Build(
|
||||
newSystem,
|
||||
database.WireSet,
|
||||
memory.WireSet,
|
||||
router.WireSet,
|
||||
server.WireSet,
|
||||
cron.WireSet,
|
||||
newSystem,
|
||||
authn.WireSet,
|
||||
authz.WireSet,
|
||||
translator.WireSet,
|
||||
)
|
||||
return &system{}, nil
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
// Code generated by Wire. DO NOT EDIT.
|
||||
|
||||
//go:generate go run github.com/google/wire/cmd/wire
|
||||
//go:build !wireinject
|
||||
// +build !wireinject
|
||||
//go:build !wireinject && !harness
|
||||
// +build !wireinject,!harness
|
||||
|
||||
package server
|
||||
|
||||
@ -13,15 +12,17 @@ import (
|
||||
"github.com/harness/gitness/internal/auth/authz"
|
||||
"github.com/harness/gitness/internal/cron"
|
||||
"github.com/harness/gitness/internal/router"
|
||||
"github.com/harness/gitness/internal/router/translator"
|
||||
"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 wire.go:
|
||||
// Injectors from standalone.wire.go:
|
||||
|
||||
func initSystem(ctx context.Context, config *types.Config) (*system, error) {
|
||||
requestTranslator := translator.ProvideRequestTranslator()
|
||||
systemStore := memory.New(config)
|
||||
db, err := database.ProvideDatabase(ctx, config)
|
||||
if err != nil {
|
||||
@ -32,9 +33,9 @@ func initSystem(ctx context.Context, config *types.Config) (*system, error) {
|
||||
repoStore := database.ProvideRepoStore(db)
|
||||
tokenStore := database.ProvideTokenStore(db)
|
||||
serviceAccountStore := database.ProvideServiceAccountStore(db)
|
||||
authenticator := authn.NewTokenAuthenticator(userStore, serviceAccountStore, tokenStore)
|
||||
authorizer := authz.NewUnsafeAuthorizer()
|
||||
handler, err := router.New(systemStore, userStore, spaceStore, repoStore, tokenStore, serviceAccountStore, authenticator, authorizer)
|
||||
authenticator := authn.ProvideAuthenticator(userStore, serviceAccountStore, tokenStore)
|
||||
authorizer := authz.ProvideAuthorizer()
|
||||
handler, err := router.ProvideHTTPHandler(requestTranslator, systemStore, userStore, spaceStore, repoStore, tokenStore, serviceAccountStore, authenticator, authorizer)
|
||||
if err != nil {
|
||||
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.
|
||||
func Credentials() (string, string) {
|
||||
return Username(), Password()
|
||||
@ -116,6 +121,26 @@ func Username() string {
|
||||
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.
|
||||
func Password() string {
|
||||
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.
|
||||
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.Add("username", username)
|
||||
form.Add("name", name)
|
||||
form.Add("email", email)
|
||||
form.Add("password", password)
|
||||
out := new(types.TokenResponse)
|
||||
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)
|
||||
|
||||
// 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(ctx context.Context) (*types.User, error)
|
||||
|
@ -5,6 +5,7 @@
|
||||
package account
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/harness/gitness/internal/api/render"
|
||||
@ -25,11 +26,14 @@ func HandleLogin(userStore store.UserStore, system store.SystemStore, tokenStore
|
||||
|
||||
username := r.FormValue("username")
|
||||
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 {
|
||||
log.Debug().Err(err).
|
||||
Str("user", username).
|
||||
Msg("cannot find user")
|
||||
Msgf("cannot find user with '%s'", username)
|
||||
|
||||
// always give not found error as extra security measurement.
|
||||
render.NotFound(w)
|
||||
@ -42,7 +46,7 @@ func HandleLogin(userStore store.UserStore, system store.SystemStore, tokenStore
|
||||
)
|
||||
if err != nil {
|
||||
log.Debug().Err(err).
|
||||
Str("user", username).
|
||||
Str("user_uid", user.UID).
|
||||
Msg("invalid password")
|
||||
|
||||
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")
|
||||
if err != nil {
|
||||
log.Err(err).
|
||||
Str("user", username).
|
||||
Str("user_uid", user.UID).
|
||||
Msg("failed to generate token")
|
||||
|
||||
render.InternalError(w)
|
||||
|
@ -26,23 +26,25 @@ func HandleRegister(userStore store.UserStore, system store.SystemStore, tokenSt
|
||||
ctx := r.Context()
|
||||
log := hlog.FromRequest(r)
|
||||
|
||||
username := r.FormValue("username")
|
||||
uid := r.FormValue("username")
|
||||
name := r.FormValue("name")
|
||||
email := r.FormValue("email")
|
||||
password := r.FormValue("password")
|
||||
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
log.Err(err).
|
||||
Str("email", username).
|
||||
Str("uid", uid).
|
||||
Msg("Failed to hash password")
|
||||
|
||||
render.InternalError(w)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: allow to provide email and name separately ...
|
||||
user := &types.User{
|
||||
Name: username,
|
||||
Email: username,
|
||||
UID: uid,
|
||||
Name: name,
|
||||
Email: email,
|
||||
Password: string(hash),
|
||||
Salt: uniuri.NewLen(uniuri.UUIDLen),
|
||||
Created: time.Now().UnixMilli(),
|
||||
@ -51,7 +53,7 @@ func HandleRegister(userStore store.UserStore, system store.SystemStore, tokenSt
|
||||
|
||||
if err = check.User(user); err != nil {
|
||||
log.Debug().Err(err).
|
||||
Str("email", username).
|
||||
Str("uid", uid).
|
||||
Msg("invalid user input")
|
||||
|
||||
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 {
|
||||
log.Err(err).
|
||||
Str("email", username).
|
||||
Str("uid", uid).
|
||||
Msg("Failed to create user")
|
||||
|
||||
render.InternalError(w)
|
||||
@ -74,8 +76,7 @@ func HandleRegister(userStore store.UserStore, system store.SystemStore, tokenSt
|
||||
user.Admin = true
|
||||
if err = userStore.Update(ctx, user); err != nil {
|
||||
log.Err(err).
|
||||
Str("email", username).
|
||||
Int64("user_id", user.ID).
|
||||
Str("user_uid", user.UID).
|
||||
Msg("Failed to enable admin user")
|
||||
|
||||
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")
|
||||
if err != nil {
|
||||
log.Err(err).
|
||||
Str("user", username).
|
||||
Str("user", uid).
|
||||
Msg("failed to generate token")
|
||||
|
||||
render.InternalError(w)
|
||||
|
@ -13,6 +13,7 @@ type CreatePathRequest struct {
|
||||
|
||||
// CreateServiceAccountRequest used for service account creation apis.
|
||||
type CreateServiceAccountRequest struct {
|
||||
UID string `json:"uid"`
|
||||
Name string `json:"name"`
|
||||
ParentType enum.ParentResourceType `json:"parentType"`
|
||||
ParentID int64 `json:"parentId"`
|
||||
|
@ -36,6 +36,7 @@ func HandleCreate(guard *guard.Guard, saStore store.ServiceAccountStore) http.Ha
|
||||
}
|
||||
|
||||
sa := &types.ServiceAccount{
|
||||
UID: in.UID,
|
||||
Name: in.Name,
|
||||
Salt: uniuri.NewLen(uniuri.UUIDLen),
|
||||
Created: time.Now().UnixMilli(),
|
||||
|
@ -20,7 +20,9 @@ import (
|
||||
)
|
||||
|
||||
type userCreateInput struct {
|
||||
Username string `json:"email"`
|
||||
UID string `json:"uid"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
Admin bool `json:"admin"`
|
||||
}
|
||||
@ -42,7 +44,7 @@ func HandleCreate(userStore store.UserStore) http.HandlerFunc {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(in.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
log.Err(err).
|
||||
Str("email", in.Username).
|
||||
Str("uid", in.UID).
|
||||
Msg("Failed to hash password")
|
||||
|
||||
render.InternalError(w)
|
||||
@ -50,7 +52,9 @@ func HandleCreate(userStore store.UserStore) http.HandlerFunc {
|
||||
}
|
||||
|
||||
user := &types.User{
|
||||
Email: in.Username,
|
||||
UID: in.UID,
|
||||
Name: in.Name,
|
||||
Email: in.Email,
|
||||
Admin: in.Admin,
|
||||
Password: string(hash),
|
||||
Salt: uniuri.NewLen(uniuri.UUIDLen),
|
||||
@ -59,7 +63,7 @@ func HandleCreate(userStore store.UserStore) http.HandlerFunc {
|
||||
}
|
||||
if err = check.User(user); err != nil {
|
||||
log.Debug().Err(err).
|
||||
Str("email", user.Email).
|
||||
Str("uid", user.UID).
|
||||
Msg("invalid user input")
|
||||
|
||||
render.UserfiedErrorOrInternal(w, err)
|
||||
@ -69,7 +73,7 @@ func HandleCreate(userStore store.UserStore) http.HandlerFunc {
|
||||
err = userStore.Create(ctx, user)
|
||||
if err != nil {
|
||||
log.Err(err).
|
||||
Str("email", user.Email).
|
||||
Str("uid", user.UID).
|
||||
Msg("failed to create user")
|
||||
|
||||
render.UserfiedErrorOrInternal(w, err)
|
||||
|
@ -30,11 +30,10 @@ func HandleDelete(userStore store.UserStore, tokenStore store.TokenStore) http.H
|
||||
return
|
||||
}
|
||||
|
||||
err = userStore.Delete(ctx, user)
|
||||
err = userStore.Delete(ctx, user.ID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).
|
||||
Int64("user_id", user.ID).
|
||||
Str("user_email", user.Email).
|
||||
Str("user_uid", user.UID).
|
||||
Msg("failed to delete user")
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
log.Err(err).
|
||||
Int64("user_id", user.ID).
|
||||
Str("user_email", user.Email).
|
||||
Str("user_uid", user.UID).
|
||||
Msg("Failed to hash password")
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
log.Err(err).
|
||||
Int64("user_id", user.ID).
|
||||
Str("user_email", user.Email).
|
||||
Str("user_uid", user.UID).
|
||||
Msg("Failed to hash password")
|
||||
|
||||
render.InternalError(w)
|
||||
@ -78,8 +76,7 @@ func HandleUpdate(userStore store.UserStore) http.HandlerFunc {
|
||||
}
|
||||
if err := check.User(user); err != nil {
|
||||
log.Debug().Err(err).
|
||||
Int64("user_id", user.ID).
|
||||
Str("user_email", user.Email).
|
||||
Str("user_uid", user.UID).
|
||||
Msg("invalid user input")
|
||||
|
||||
render.UserfiedErrorOrInternal(w, err)
|
||||
@ -91,8 +88,7 @@ func HandleUpdate(userStore store.UserStore) http.HandlerFunc {
|
||||
err := userStore.Update(ctx, user)
|
||||
if err != nil {
|
||||
log.Err(err).
|
||||
Int64("user_id", user.ID).
|
||||
Str("user_email", user.Email).
|
||||
Str("user_uid", user.UID).
|
||||
Msg("Failed to update the usser")
|
||||
|
||||
render.UserfiedErrorOrInternal(w, err)
|
||||
|
@ -18,8 +18,6 @@ func HlogHandler() func(http.Handler) http.Handler {
|
||||
return hlog.AccessHandler(
|
||||
func(r *http.Request, status, size int, duration time.Duration) {
|
||||
hlog.FromRequest(r).Info().
|
||||
Str("method", r.Method).
|
||||
Stringer("url", r.URL).
|
||||
Int("status_code", status).
|
||||
Int("response_size_bytes", size).
|
||||
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
|
||||
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
|
||||
return c.
|
||||
Int64("principal_id", session.Principal.ID).
|
||||
Str("principal_uid", session.Principal.UID).
|
||||
Str("principal_type", string(session.Principal.Type)).
|
||||
Bool("principal_admin", session.Principal.Admin)
|
||||
})
|
||||
|
@ -2,39 +2,42 @@ package encode
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/rs/zerolog/hlog"
|
||||
|
||||
"github.com/harness/gitness/internal/request"
|
||||
"github.com/harness/gitness/types"
|
||||
)
|
||||
|
||||
// GitPathBefore wraps an http.HandlerFunc in a layer that encodes Paths coming as part of the GIT api
|
||||
// (e.g. "space1/repo.git") before executing the provided http.HandlerFunc
|
||||
const (
|
||||
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.
|
||||
func GitPathBefore(h http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
r, _ = pathTerminatedWithMarker(r, "", ".git", false)
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
func GitPath(r *http.Request) error {
|
||||
_, err := pathTerminatedWithMarker(r, "", ".git", false)
|
||||
return err
|
||||
}
|
||||
|
||||
// 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
|
||||
// be used during encoding.
|
||||
func TerminatedPathBefore(prefixes []string, h http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
for _, p := range prefixes {
|
||||
// IMPORTANT: define changed separately to avoid overshadowing r
|
||||
var changed bool
|
||||
if r, changed = pathTerminatedWithMarker(r, p, "/+", false); changed {
|
||||
break
|
||||
}
|
||||
// be used during encoding (prefix is ignored during encoding).
|
||||
func TerminatedPath(prefixes []string, r *http.Request) error {
|
||||
for _, p := range prefixes {
|
||||
changed, err := pathTerminatedWithMarker(r, p, "/+", false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.ServeHTTP(w, r)
|
||||
// first prefix that leads to success we can stop
|
||||
if changed {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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.git" => "/space1%2Fspace2"
|
||||
// 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
|
||||
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):]
|
||||
path, suffix, found := strings.Cut(originalSubPath, marker)
|
||||
path, _, found := strings.Cut(originalSubPath, marker)
|
||||
|
||||
// If we don't find a marker - nothing to encode
|
||||
if !found {
|
||||
return r, false
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// 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")
|
||||
// if marker was found - convert to escaped version (skip first character in case path starts with '/').
|
||||
// Since replacePrefix unescapes the strings, we have to double escape.
|
||||
escapedPath := path[0:1] + strings.ReplaceAll(path[1:], types.PathSeparator, EncodedPathSeparator)
|
||||
if keepMarker {
|
||||
escapedPath += marker
|
||||
}
|
||||
updatedSubPath := escapedPath + suffix
|
||||
|
||||
// TODO: Proper Logging
|
||||
log.Debug().Msgf(
|
||||
"[Encode] prefix: '%s', marker: '%s', original: '%s', updated: '%s'.\n",
|
||||
prefixWithPath := prefix + path + marker
|
||||
prefixWithEscapedPath := prefix + escapedPath
|
||||
|
||||
hlog.FromRequest(r).Trace().Msgf(
|
||||
"[Encode] prefix: '%s', marker: '%s', original: '%s', escaped: '%s'.\n",
|
||||
prefix,
|
||||
marker,
|
||||
originalSubPath,
|
||||
updatedSubPath)
|
||||
prefixWithPath,
|
||||
prefixWithEscapedPath)
|
||||
|
||||
/*
|
||||
* Return shallow clone with updated URL, similar to http.StripPrefix or earlier version of request.WithContext
|
||||
* 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
|
||||
*/
|
||||
r2 := new(http.Request)
|
||||
*r2 = *r
|
||||
r2.URL = new(url.URL)
|
||||
*r2.URL = *r.URL
|
||||
r2.URL.Path = prefix + updatedSubPath
|
||||
r2.URL.RawPath = ""
|
||||
err := request.ReplacePrefix(r, prefixWithPath, prefixWithEscapedPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return r2, true
|
||||
return true, nil
|
||||
}
|
||||
|
@ -25,17 +25,17 @@ func ServiceAccount(saStore store.ServiceAccountStore) func(http.Handler) http.H
|
||||
ctx := r.Context()
|
||||
log := hlog.FromRequest(r)
|
||||
|
||||
id, err := request.GetServiceAccountID(r)
|
||||
uid, err := request.GetServiceAccountUID(r)
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
sa, err := saStore.Find(ctx, id)
|
||||
sa, err := saStore.FindUID(ctx, uid)
|
||||
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)
|
||||
return
|
||||
@ -43,7 +43,7 @@ func ServiceAccount(saStore store.ServiceAccountStore) func(http.Handler) http.H
|
||||
|
||||
// Update the logging context and inject repo in 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(
|
||||
|
@ -47,7 +47,6 @@ func Space(spaceStore store.SpaceStore) func(http.Handler) http.Handler {
|
||||
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msgf("Failed to get space using ref '%s'.", ref)
|
||||
|
||||
render.UserfiedErrorOrInternal(w, err)
|
||||
return
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ func User(userStore store.UserStore) func(http.Handler) http.Handler {
|
||||
ctx := r.Context()
|
||||
log := hlog.FromRequest(r)
|
||||
|
||||
id, err := request.GetUserID(r)
|
||||
uid, err := request.GetUserUID(r)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
user, err := userStore.Find(ctx, id)
|
||||
user, err := userStore.FindUID(ctx, uid)
|
||||
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)
|
||||
return
|
||||
@ -43,7 +43,7 @@ func User(userStore store.UserStore) func(http.Handler) http.Handler {
|
||||
|
||||
// Update the logging context and inject repo in 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(
|
||||
|
@ -5,14 +5,14 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
UserIDParamName = "userId"
|
||||
ServiceAccountIDParamName = "saId"
|
||||
UserUIDParamName = "userUID"
|
||||
ServiceAccountUIDParamName = "saUID"
|
||||
)
|
||||
|
||||
func GetUserID(r *http.Request) (int64, error) {
|
||||
return ParseAsInt64(r, UserIDParamName)
|
||||
func GetUserUID(r *http.Request) (string, error) {
|
||||
return ParamOrError(r, UserUIDParamName)
|
||||
}
|
||||
|
||||
func GetServiceAccountID(r *http.Request) (int64, error) {
|
||||
return ParseAsInt64(r, ServiceAccountIDParamName)
|
||||
func GetServiceAccountUID(r *http.Request) (string, error) {
|
||||
return ParamOrError(r, ServiceAccountUIDParamName)
|
||||
}
|
||||
|
@ -14,11 +14,22 @@ import (
|
||||
"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.
|
||||
func ParseAsInt64(r *http.Request, paramName string) (int64, error) {
|
||||
rawID := chi.URLParam(r, paramName)
|
||||
if rawID == "" {
|
||||
return 0, fmt.Errorf("parameter '%s' not found in request", paramName)
|
||||
rawID, err := ParamOrError(r, paramName)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
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(
|
||||
userStore store.UserStore,
|
||||
saStore store.ServiceAccountStore,
|
||||
tokenStore store.TokenStore) Authenticator {
|
||||
tokenStore store.TokenStore) *TokenAuthenticator {
|
||||
return &TokenAuthenticator{
|
||||
userStore: userStore,
|
||||
saStore: saStore,
|
||||
|
@ -6,9 +6,15 @@ package authn
|
||||
|
||||
import (
|
||||
"github.com/google/wire"
|
||||
"github.com/harness/gitness/internal/store"
|
||||
)
|
||||
|
||||
// WireSet provides a wire set for this package.
|
||||
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{}
|
||||
|
||||
func NewUnsafeAuthorizer() Authorizer {
|
||||
func NewUnsafeAuthorizer() *UnsafeAuthorizer {
|
||||
return &UnsafeAuthorizer{}
|
||||
}
|
||||
|
||||
|
@ -10,5 +10,9 @@ import (
|
||||
|
||||
// WireSet provides a wire set for this package.
|
||||
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"
|
||||
"github.com/harness/gitness/internal/api/middleware/accesslog"
|
||||
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/request"
|
||||
@ -27,15 +26,12 @@ import (
|
||||
"github.com/go-chi/chi/middleware"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/rs/zerolog/hlog"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
/*
|
||||
* Mounts the Rest API Router under mountPath (path has to end with ).
|
||||
* The handler is wrapped within a layer that handles encoding terminated Paths.
|
||||
* newAPIHandler returns a new http handler for handling API calls.
|
||||
*/
|
||||
func newAPIHandler(
|
||||
mountPath string,
|
||||
systemStore store.SystemStore,
|
||||
userStore store.UserStore,
|
||||
spaceStore store.SpaceStore,
|
||||
@ -50,36 +46,28 @@ func newAPIHandler(
|
||||
|
||||
// Use go-chi router for inner routing (restricted to mountPath!)
|
||||
r := chi.NewRouter()
|
||||
r.Route(mountPath, func(r chi.Router) {
|
||||
// Apply common api middleware
|
||||
r.Use(middleware.NoCache)
|
||||
r.Use(middleware.Recoverer)
|
||||
|
||||
// configure logging middleware.
|
||||
r.Use(hlog.NewHandler(log.Logger))
|
||||
r.Use(hlog.URLHandler("path"))
|
||||
r.Use(hlog.MethodHandler("method"))
|
||||
r.Use(hlog.RequestIDHandler("request", "Request-Id"))
|
||||
r.Use(accesslog.HlogHandler())
|
||||
// Apply common api middleware
|
||||
r.Use(middleware.NoCache)
|
||||
r.Use(middleware.Recoverer)
|
||||
|
||||
// configure cors middleware
|
||||
r.Use(corsHandler(config))
|
||||
// configure logging middleware.
|
||||
r.Use(hlog.URLHandler("url"))
|
||||
r.Use(hlog.MethodHandler("method"))
|
||||
r.Use(hlog.RequestIDHandler("request", "Request-Id"))
|
||||
r.Use(accesslog.HlogHandler())
|
||||
|
||||
// for now always attempt auth - enforced per operation
|
||||
r.Use(middlewareauthn.Attempt(authenticator))
|
||||
// configure cors middleware
|
||||
r.Use(corsHandler(config))
|
||||
|
||||
r.Route("/v1", func(r chi.Router) {
|
||||
setupRoutesV1(r, systemStore, userStore, spaceStore, repoStore, tokenStore, saStore, authenticator, g)
|
||||
})
|
||||
// for now always attempt auth - enforced per operation
|
||||
r.Use(middlewareauthn.Attempt(authenticator))
|
||||
|
||||
r.Route("/v1", func(r chi.Router) {
|
||||
setupRoutesV1(r, systemStore, userStore, spaceStore, repoStore, tokenStore, saStore, authenticator, g)
|
||||
})
|
||||
|
||||
// Generate list of all path prefixes that expect terminated Paths
|
||||
terminatedPathPrefixes := []string{
|
||||
mountPath + "/v1/spaces",
|
||||
mountPath + "/v1/repos",
|
||||
}
|
||||
|
||||
return encode.TerminatedPathBefore(terminatedPathPrefixes, r.ServeHTTP)
|
||||
return r
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
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"))))
|
||||
})
|
||||
|
||||
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
|
||||
resolve.User(userStore)
|
||||
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"github.com/harness/gitness/internal/api/guard"
|
||||
"github.com/harness/gitness/internal/api/middleware/accesslog"
|
||||
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/request"
|
||||
"github.com/harness/gitness/internal/auth/authn"
|
||||
@ -18,15 +17,12 @@ import (
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
"github.com/rs/zerolog/hlog"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
/*
|
||||
* Mounts the GIT Router under mountPath.
|
||||
* The handler is wrapped within a layer that handles encoding Paths.
|
||||
* newGitHandler returns a new http handler for handling GIT calls.
|
||||
*/
|
||||
func newGitHandler(
|
||||
mountPath string,
|
||||
_ store.SystemStore,
|
||||
_ store.UserStore,
|
||||
spaceStore store.SpaceStore,
|
||||
@ -34,55 +30,53 @@ func newGitHandler(
|
||||
authenticator authn.Authenticator,
|
||||
authorizer authz.Authorizer) http.Handler {
|
||||
guard := guard.New(authorizer, spaceStore, repoStore)
|
||||
|
||||
// Use go-chi router for inner routing (restricted to mountPath!)
|
||||
r := chi.NewRouter()
|
||||
r.Route(mountPath, func(r chi.Router) {
|
||||
// Apply common api middleware
|
||||
r.Use(middleware.NoCache)
|
||||
r.Use(middleware.Recoverer)
|
||||
// Apply common api middleware
|
||||
r.Use(middleware.NoCache)
|
||||
r.Use(middleware.Recoverer)
|
||||
|
||||
// configure logging middleware.
|
||||
r.Use(hlog.NewHandler(log.Logger))
|
||||
r.Use(hlog.URLHandler("path"))
|
||||
r.Use(hlog.MethodHandler("method"))
|
||||
r.Use(hlog.RequestIDHandler("request", "Request-Id"))
|
||||
r.Use(accesslog.HlogHandler())
|
||||
// configure logging middleware.
|
||||
r.Use(hlog.URLHandler("url"))
|
||||
r.Use(hlog.MethodHandler("method"))
|
||||
r.Use(hlog.RequestIDHandler("request", "Request-Id"))
|
||||
r.Use(accesslog.HlogHandler())
|
||||
|
||||
// for now always attempt auth - enforced per operation
|
||||
r.Use(middleware_authn.Attempt(authenticator))
|
||||
// for now always attempt auth - enforced per operation
|
||||
r.Use(middleware_authn.Attempt(authenticator))
|
||||
|
||||
r.Route(fmt.Sprintf("/{%s}", request.RepoRefParamName), func(r chi.Router) {
|
||||
// resolves the repo and stores in the context
|
||||
r.Use(resolve.Repo(repoStore))
|
||||
r.Route(fmt.Sprintf("/{%s}", request.RepoRefParamName), func(r chi.Router) {
|
||||
// resolves the repo and stores in the context
|
||||
r.Use(resolve.Repo(repoStore))
|
||||
|
||||
// Write operations (need auth)
|
||||
r.Group(func(r chi.Router) {
|
||||
// TODO: specific permission for pushing code?
|
||||
r.Use(guard.ForRepo(enum.PermissionRepoEdit, false))
|
||||
// Write operations (need auth)
|
||||
r.Group(func(r chi.Router) {
|
||||
// TODO: specific permission for pushing code?
|
||||
r.Use(guard.ForRepo(enum.PermissionRepoEdit, false))
|
||||
|
||||
r.Handle("/git-upload-pack", http.HandlerFunc(stubGitHandler))
|
||||
})
|
||||
r.Handle("/git-upload-pack", http.HandlerFunc(stubGitHandler))
|
||||
})
|
||||
|
||||
// Read operations (only need of it not public)
|
||||
r.Group(func(r chi.Router) {
|
||||
// middlewares
|
||||
r.Use(guard.ForRepo(enum.PermissionRepoView, true))
|
||||
// handlers
|
||||
r.Post("/git-receive-pack", stubGitHandler)
|
||||
r.Get("/info/refs", stubGitHandler)
|
||||
r.Get("/HEAD", stubGitHandler)
|
||||
r.Get("/objects/info/alternates", stubGitHandler)
|
||||
r.Get("/objects/info/http-alternates", stubGitHandler)
|
||||
r.Get("/objects/info/packs", stubGitHandler)
|
||||
r.Get("/objects/info/{file:[^/]*}", stubGitHandler)
|
||||
r.Get("/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38}}", stubGitHandler)
|
||||
r.Get("/objects/pack/pack-{file:[0-9a-f]{40}}.pack", stubGitHandler)
|
||||
r.Get("/objects/pack/pack-{file:[0-9a-f]{40}}.idx", stubGitHandler)
|
||||
})
|
||||
// Read operations (only need of it not public)
|
||||
r.Group(func(r chi.Router) {
|
||||
// middlewares
|
||||
r.Use(guard.ForRepo(enum.PermissionRepoView, true))
|
||||
// handlers
|
||||
r.Post("/git-receive-pack", stubGitHandler)
|
||||
r.Get("/info/refs", stubGitHandler)
|
||||
r.Get("/HEAD", stubGitHandler)
|
||||
r.Get("/objects/info/alternates", stubGitHandler)
|
||||
r.Get("/objects/info/http-alternates", stubGitHandler)
|
||||
r.Get("/objects/info/packs", stubGitHandler)
|
||||
r.Get("/objects/info/{file:[^/]*}", stubGitHandler)
|
||||
r.Get("/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38}}", stubGitHandler)
|
||||
r.Get("/objects/pack/pack-{file:[0-9a-f]{40}}.pack", 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) {
|
||||
|
@ -10,25 +10,33 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/harness/gitness/internal/api/render"
|
||||
"github.com/harness/gitness/internal/auth/authn"
|
||||
"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/rs/zerolog"
|
||||
"github.com/rs/zerolog/hlog"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
restMount = "/api"
|
||||
APIMount = "/api"
|
||||
gitUserAgentPrefix = "git/"
|
||||
)
|
||||
|
||||
type Router struct {
|
||||
api http.Handler
|
||||
git http.Handler
|
||||
web http.Handler
|
||||
translator translator.RequestTranslator
|
||||
api http.Handler
|
||||
git 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.
|
||||
func New(
|
||||
func NewRouter(
|
||||
translator translator.RequestTranslator,
|
||||
systemStore store.SystemStore,
|
||||
userStore store.UserStore,
|
||||
spaceStore store.SpaceStore,
|
||||
@ -37,20 +45,38 @@ func New(
|
||||
saStore store.ServiceAccountStore,
|
||||
authenticator authn.Authenticator,
|
||||
authorizer authz.Authorizer,
|
||||
) (http.Handler, error) {
|
||||
api := newAPIHandler(restMount, systemStore, userStore, spaceStore, repoStore, tokenStore, saStore,
|
||||
) (*Router, error) {
|
||||
api := newAPIHandler(systemStore, userStore, spaceStore, repoStore, tokenStore, saStore,
|
||||
authenticator, authorizer)
|
||||
git := newGitHandler("/", systemStore, userStore, spaceStore, repoStore, authenticator, authorizer)
|
||||
web := newWebHandler("/", systemStore)
|
||||
git := newGitHandler(systemStore, userStore, spaceStore, repoStore, authenticator, authorizer)
|
||||
web := newWebHandler(systemStore)
|
||||
|
||||
return &Router{
|
||||
api: api,
|
||||
git: git,
|
||||
web: web,
|
||||
translator: translator,
|
||||
api: api,
|
||||
git: git,
|
||||
web: web,
|
||||
}, nil
|
||||
}
|
||||
|
||||
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
|
||||
*
|
||||
@ -60,6 +86,18 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
*/
|
||||
ua := req.Header.Get("user-agent")
|
||||
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)
|
||||
return
|
||||
}
|
||||
@ -68,10 +106,28 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
* 2. REST API
|
||||
*
|
||||
* 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
|
||||
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)
|
||||
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)
|
||||
*/
|
||||
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)
|
||||
}
|
||||
|
||||
// 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"
|
||||
"net/http"
|
||||
|
||||
"github.com/harness/gitness/internal/api/middleware/encode"
|
||||
"github.com/harness/gitness/internal/store"
|
||||
"github.com/harness/gitness/web"
|
||||
"github.com/swaggest/swgui/v3emb"
|
||||
@ -14,53 +13,46 @@ import (
|
||||
)
|
||||
|
||||
/*
|
||||
* Mounts the WEB Router under mountPath.
|
||||
* The handler is wrapped within a layer that handles encoding Paths.
|
||||
* newWebHandler returns a new http handler for handling WEB calls.
|
||||
*/
|
||||
func newWebHandler(
|
||||
mountPath string,
|
||||
systemStore store.SystemStore) http.Handler {
|
||||
//
|
||||
func newWebHandler(systemStore store.SystemStore) http.Handler {
|
||||
config := systemStore.Config(context.Background())
|
||||
|
||||
// Use go-chi router for inner routing (restricted to mountPath!)
|
||||
r := chi.NewRouter()
|
||||
r.Route(mountPath, func(r chi.Router) {
|
||||
// create middleware to enforce security best practices for
|
||||
// the user interface. note that theis middleware is only used
|
||||
// when serving the user interface (not found handler, below).
|
||||
sec := secure.New(
|
||||
secure.Options{
|
||||
AllowedHosts: config.Secure.AllowedHosts,
|
||||
HostsProxyHeaders: config.Secure.HostsProxyHeaders,
|
||||
SSLRedirect: config.Secure.SSLRedirect,
|
||||
SSLTemporaryRedirect: config.Secure.SSLTemporaryRedirect,
|
||||
SSLHost: config.Secure.SSLHost,
|
||||
SSLProxyHeaders: config.Secure.SSLProxyHeaders,
|
||||
STSSeconds: config.Secure.STSSeconds,
|
||||
STSIncludeSubdomains: config.Secure.STSIncludeSubdomains,
|
||||
STSPreload: config.Secure.STSPreload,
|
||||
ForceSTSHeader: config.Secure.ForceSTSHeader,
|
||||
FrameDeny: config.Secure.FrameDeny,
|
||||
ContentTypeNosniff: config.Secure.ContentTypeNosniff,
|
||||
BrowserXssFilter: config.Secure.BrowserXSSFilter,
|
||||
ContentSecurityPolicy: config.Secure.ContentSecurityPolicy,
|
||||
ReferrerPolicy: config.Secure.ReferrerPolicy,
|
||||
},
|
||||
)
|
||||
// create middleware to enforce security best practices for
|
||||
// the user interface. note that theis middleware is only used
|
||||
// when serving the user interface (not found handler, below).
|
||||
sec := secure.New(
|
||||
secure.Options{
|
||||
AllowedHosts: config.Secure.AllowedHosts,
|
||||
HostsProxyHeaders: config.Secure.HostsProxyHeaders,
|
||||
SSLRedirect: config.Secure.SSLRedirect,
|
||||
SSLTemporaryRedirect: config.Secure.SSLTemporaryRedirect,
|
||||
SSLHost: config.Secure.SSLHost,
|
||||
SSLProxyHeaders: config.Secure.SSLProxyHeaders,
|
||||
STSSeconds: config.Secure.STSSeconds,
|
||||
STSIncludeSubdomains: config.Secure.STSIncludeSubdomains,
|
||||
STSPreload: config.Secure.STSPreload,
|
||||
ForceSTSHeader: config.Secure.ForceSTSHeader,
|
||||
FrameDeny: config.Secure.FrameDeny,
|
||||
ContentTypeNosniff: config.Secure.ContentTypeNosniff,
|
||||
BrowserXssFilter: config.Secure.BrowserXSSFilter,
|
||||
ContentSecurityPolicy: config.Secure.ContentSecurityPolicy,
|
||||
ReferrerPolicy: config.Secure.ReferrerPolicy,
|
||||
},
|
||||
)
|
||||
|
||||
// openapi playground endpoints
|
||||
swagger := v3emb.NewHandler("API Definition", "/api/v1/swagger.yaml", "/swagger")
|
||||
r.With(sec.Handler).Handle("/swagger", swagger)
|
||||
r.With(sec.Handler).Handle("/swagger/*", swagger)
|
||||
// openapi playground endpoints
|
||||
swagger := v3emb.NewHandler("API Definition", "/api/v1/swagger.yaml", "/swagger")
|
||||
r.With(sec.Handler).Handle("/swagger", swagger)
|
||||
r.With(sec.Handler).Handle("/swagger/*", swagger)
|
||||
|
||||
// serve all other routes from the embedded filesystem,
|
||||
// which in turn serves the user interface.
|
||||
r.With(sec.Handler).NotFound(
|
||||
web.Handler(),
|
||||
)
|
||||
})
|
||||
// serve all other routes from the embedded filesystem,
|
||||
// which in turn serves the user interface.
|
||||
r.With(sec.Handler).NotFound(
|
||||
web.Handler(),
|
||||
)
|
||||
|
||||
// web doesn't have any prefixes for terminated paths
|
||||
return encode.TerminatedPathBefore([]string{""}, r.ServeHTTP)
|
||||
return r
|
||||
}
|
||||
|
@ -5,8 +5,29 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"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.
|
||||
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 (
|
||||
principal_id SERIAL PRIMARY KEY
|
||||
,principal_uid TEXT
|
||||
,principal_type TEXT
|
||||
,principal_name TEXT
|
||||
,principal_admin BOOLEAN
|
||||
,principal_externalId TEXT
|
||||
,principal_blocked BOOLEAN
|
||||
,principal_salt TEXT
|
||||
,principal_created INTEGER
|
||||
@ -15,6 +15,7 @@ principal_id SERIAL PRIMARY KEY
|
||||
,principal_sa_parentType TEXT
|
||||
,principal_sa_parentId INTEGER
|
||||
|
||||
,UNIQUE(principal_uid)
|
||||
,UNIQUE(principal_salt)
|
||||
,UNIQUE(principal_user_email)
|
||||
);
|
||||
|
@ -1,9 +1,9 @@
|
||||
CREATE TABLE IF NOT EXISTS principals (
|
||||
principal_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
,principal_uid TEXT
|
||||
,principal_type TEXT
|
||||
,principal_name TEXT
|
||||
,principal_admin BOOLEAN
|
||||
,principal_externalId TEXT
|
||||
,principal_blocked BOOLEAN
|
||||
,principal_salt TEXT
|
||||
,principal_created INTEGER
|
||||
@ -15,6 +15,7 @@ principal_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
,principal_sa_parentType TEXT
|
||||
,principal_sa_parentId INTEGER
|
||||
|
||||
,UNIQUE(principal_uid)
|
||||
,UNIQUE(principal_salt)
|
||||
,UNIQUE(principal_user_email COLLATE NOCASE)
|
||||
);
|
||||
|
@ -37,6 +37,15 @@ func (s *ServiceAccountStore) Find(ctx context.Context, id int64) (*types.Servic
|
||||
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.
|
||||
func (s *ServiceAccountStore) Create(ctx context.Context, sa *types.ServiceAccount) error {
|
||||
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 = `
|
||||
SELECT
|
||||
principal_id
|
||||
,principal_uid
|
||||
,principal_name
|
||||
,principal_externalId
|
||||
,principal_blocked
|
||||
,principal_salt
|
||||
,principal_created
|
||||
@ -133,6 +142,10 @@ const serviceAccountSelectID = serviceAccountBase + `
|
||||
WHERE principal_type = "serviceaccount" AND principal_id = $1
|
||||
`
|
||||
|
||||
const serviceAccountSelectUID = serviceAccountBase + `
|
||||
WHERE principal_type = "serviceaccount" AND principal_uid = $1
|
||||
`
|
||||
|
||||
const serviceAccountDelete = `
|
||||
DELETE FROM principals
|
||||
WHERE principal_type = "serviceaccount" AND principal_id = $1
|
||||
@ -141,9 +154,9 @@ WHERE principal_type = "serviceaccount" AND principal_id = $1
|
||||
const serviceAccountInsert = `
|
||||
INSERT INTO principals (
|
||||
principal_type
|
||||
,principal_uid
|
||||
,principal_name
|
||||
,principal_admin
|
||||
,principal_externalId
|
||||
,principal_blocked
|
||||
,principal_salt
|
||||
,principal_created
|
||||
@ -152,9 +165,9 @@ principal_type
|
||||
,principal_sa_parentId
|
||||
) values (
|
||||
"serviceaccount"
|
||||
,:principal_uid
|
||||
,:principal_name
|
||||
,false
|
||||
,:principal_externalId
|
||||
,:principal_blocked
|
||||
,:principal_salt
|
||||
,:principal_created
|
||||
@ -168,7 +181,6 @@ const serviceAccountUpdate = `
|
||||
UPDATE principals
|
||||
SET
|
||||
principal_name = :principal_name
|
||||
,:principal_externalId = :principal_externalId
|
||||
,:principal_blocked = :principal_blocked
|
||||
,:principal_salt = :principal_salt
|
||||
,: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)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (s *ServiceAccountStoreSync) Create(ctx context.Context, sa *types.ServiceAccount) error {
|
||||
mutex.RLock()
|
||||
|
2
internal/store/database/testdata/users.json
vendored
2
internal/store/database/testdata/users.json
vendored
@ -1,6 +1,7 @@
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"uid": "jane21",
|
||||
"email": "jane@example.com",
|
||||
"name": "jane",
|
||||
"company": "acme",
|
||||
@ -12,6 +13,7 @@
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"uid": "john21",
|
||||
"email": "john@example.com",
|
||||
"name": "john",
|
||||
"company": "acme",
|
||||
|
@ -7,7 +7,6 @@ package database
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"strconv"
|
||||
|
||||
"github.com/harness/gitness/internal/store"
|
||||
"github.com/harness/gitness/types"
|
||||
@ -39,6 +38,15 @@ func (s *UserStore) Find(ctx context.Context, id int64) (*types.User, error) {
|
||||
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.
|
||||
func (s *UserStore) FindEmail(ctx context.Context, email string) (*types.User, error) {
|
||||
dst := new(types.User)
|
||||
@ -48,15 +56,6 @@ func (s *UserStore) FindEmail(ctx context.Context, email string) (*types.User, e
|
||||
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.
|
||||
func (s *UserStore) List(ctx context.Context, opts *types.UserFilter) ([]*types.User, error) {
|
||||
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())
|
||||
case enum.UserAttrEmail:
|
||||
stmt = stmt.OrderBy("principal_user_email " + opts.Order.String())
|
||||
case enum.UserAttrID:
|
||||
stmt = stmt.OrderBy("principal_id " + opts.Order.String())
|
||||
case enum.UserAttrUID:
|
||||
stmt = stmt.OrderBy("principal_uid " + opts.Order.String())
|
||||
case enum.UserAttrAdmin:
|
||||
stmt = stmt.OrderBy("principal_admin " + opts.Order.String())
|
||||
}
|
||||
@ -135,7 +134,7 @@ func (s *UserStore) Update(ctx context.Context, user *types.User) error {
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
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)
|
||||
// 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 tx.Commit()
|
||||
@ -169,9 +168,9 @@ WHERE principal_type = "user"
|
||||
const userBase = `
|
||||
SELECT
|
||||
principal_id
|
||||
,principal_uid
|
||||
,principal_name
|
||||
,principal_admin
|
||||
,principal_externalId
|
||||
,principal_blocked
|
||||
,principal_salt
|
||||
,principal_created
|
||||
@ -191,6 +190,10 @@ const userSelectID = userBase + `
|
||||
WHERE principal_type = "user" AND principal_id = $1
|
||||
`
|
||||
|
||||
const userSelectUID = userBase + `
|
||||
WHERE principal_type = "user" AND principal_uid = $1
|
||||
`
|
||||
|
||||
const userSelectEmail = userBase + `
|
||||
WHERE principal_type = "user" AND principal_user_email = $1
|
||||
`
|
||||
@ -203,9 +206,9 @@ WHERE principal_type = "user" AND principal_id = $1
|
||||
const userInsert = `
|
||||
INSERT INTO principals (
|
||||
principal_type
|
||||
,principal_uid
|
||||
,principal_name
|
||||
,principal_admin
|
||||
,principal_externalId
|
||||
,principal_blocked
|
||||
,principal_salt
|
||||
,principal_created
|
||||
@ -214,9 +217,9 @@ principal_type
|
||||
,principal_user_password
|
||||
) values (
|
||||
"user"
|
||||
,:principal_uid
|
||||
,:principal_name
|
||||
,:principal_admin
|
||||
,:principal_externalId
|
||||
,:principal_blocked
|
||||
,:principal_salt
|
||||
,:principal_created
|
||||
@ -231,7 +234,6 @@ UPDATE principals
|
||||
SET
|
||||
principal_name = :principal_name
|
||||
,principal_admin = :principal_admin
|
||||
,principal_externalId = :principal_externalId
|
||||
,principal_blocked = :principal_blocked
|
||||
,principal_salt = :principal_salt
|
||||
,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)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (s *UserStoreSync) FindEmail(ctx context.Context, email string) (*types.User, error) {
|
||||
mutex.RLock()
|
||||
@ -38,13 +45,6 @@ func (s *UserStoreSync) FindEmail(ctx context.Context, email string) (*types.Use
|
||||
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.
|
||||
func (s *UserStoreSync) List(ctx context.Context, opts *types.UserFilter) ([]*types.User, error) {
|
||||
mutex.RLock()
|
||||
@ -67,10 +67,10 @@ func (s *UserStoreSync) Update(ctx context.Context, user *types.User) error {
|
||||
}
|
||||
|
||||
// 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()
|
||||
defer mutex.Unlock()
|
||||
return s.base.Delete(ctx, user)
|
||||
return s.base.Delete(ctx, id)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
got, err := store.FindEmail(ctx, want.Email)
|
||||
if err != nil {
|
||||
@ -164,30 +176,6 @@ func testUserFind(store store.UserStore) func(t *testing.T) {
|
||||
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) {
|
||||
return func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
v, err := s.Find(ctx, 1)
|
||||
_, err := s.Find(ctx, 1)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if err = s.Delete(ctx, v); err != nil {
|
||||
if err = s.Delete(ctx, 1); err != nil {
|
||||
t.Error(err)
|
||||
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.
|
||||
func processSQLErrorf(err error, format string, args ...interface{}) error {
|
||||
// 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 errors.Is(err, sql.ErrNoRows) {
|
||||
|
@ -18,12 +18,12 @@ type (
|
||||
// Find finds the user by id.
|
||||
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(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(ctx context.Context, user *types.User) error
|
||||
|
||||
@ -31,7 +31,7 @@ type (
|
||||
Update(ctx context.Context, user *types.User) error
|
||||
|
||||
// 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(ctx context.Context, params *types.UserFilter) ([]*types.User, error)
|
||||
@ -45,6 +45,9 @@ type (
|
||||
// Find finds the service account by id.
|
||||
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(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.
|
||||
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()
|
||||
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)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// 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()
|
||||
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.
|
||||
|
@ -102,7 +102,7 @@ func (mr *MockUserStoreMockRecorder) Create(arg0, arg1 interface{}) *gomock.Call
|
||||
}
|
||||
|
||||
// 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()
|
||||
ret := m.ctrl.Call(m, "Delete", arg0, arg1)
|
||||
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)
|
||||
}
|
||||
|
||||
// FindKey mocks base method.
|
||||
func (m *MockUserStore) FindKey(arg0 context.Context, arg1 string) (*types.User, error) {
|
||||
// FindUID mocks base method.
|
||||
func (m *MockUserStore) FindUID(arg0 context.Context, arg1 string) (*types.User, error) {
|
||||
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)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// FindKey indicates an expected call of FindKey.
|
||||
func (mr *MockUserStoreMockRecorder) FindKey(arg0, arg1 interface{}) *gomock.Call {
|
||||
// FindUID indicates an expected call of FindUID.
|
||||
func (mr *MockUserStoreMockRecorder) FindUID(arg0, arg1 interface{}) *gomock.Call {
|
||||
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.
|
||||
|
@ -17,20 +17,32 @@ const (
|
||||
minNameLength = 1
|
||||
maxNameLength = 256
|
||||
nameRegex = "^[a-zA-Z][a-zA-Z0-9\\-\\_ ]*$"
|
||||
|
||||
minUIDLength = 2
|
||||
maxUIDLength = 64
|
||||
uidRegex = "^[a-z][a-z0-9\\-\\_]*$"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrPathNameLength = &ValidationError{
|
||||
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{
|
||||
fmt.Sprintf("Name has to be between %d and %d in length.",
|
||||
minNameLength, maxNameLength),
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// 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.
|
||||
func ServiceAccount(sa *types.ServiceAccount) error {
|
||||
// validate UID
|
||||
if err := UID(sa.UID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// verify name
|
||||
if err := Name(sa.Name); err != nil {
|
||||
return err
|
||||
|
@ -25,6 +25,11 @@ var (
|
||||
|
||||
// User returns true if the User if valid.
|
||||
func User(user *types.User) error {
|
||||
// validate UID
|
||||
if err := UID(user.UID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// validate name
|
||||
if err := Name(user.Name); err != nil {
|
||||
return err
|
||||
|
@ -8,60 +8,60 @@ import "time"
|
||||
|
||||
// Config stores the system configuration.
|
||||
type Config struct {
|
||||
Debug bool `envconfig:"APP_DEBUG"`
|
||||
Trace bool `envconfig:"APP_TRACE"`
|
||||
Debug bool `envconfig:"GITNESS_DEBUG"`
|
||||
Trace bool `envconfig:"GITNESS_TRACE"`
|
||||
|
||||
// Server defines the server configuration parameters.
|
||||
Server struct {
|
||||
Bind string `envconfig:"APP_HTTP_BIND" default:":3000"`
|
||||
Proto string `envconfig:"APP_HTTP_PROTO"`
|
||||
Host string `envconfig:"APP_HTTP_HOST"`
|
||||
Bind string `envconfig:"GITNESS_HTTP_BIND" default:":3000"`
|
||||
Proto string `envconfig:"GITNESS_HTTP_PROTO"`
|
||||
Host string `envconfig:"GITNESS_HTTP_HOST"`
|
||||
|
||||
// Acme defines Acme configuration parameters.
|
||||
Acme struct {
|
||||
Enabled bool `envconfig:"APP_ACME_ENABLED"`
|
||||
Endpont string `envconfig:"APP_ACME_ENDPOINT"`
|
||||
Email bool `envconfig:"APP_ACME_EMAIL"`
|
||||
Enabled bool `envconfig:"GITNESS_ACME_ENABLED"`
|
||||
Endpont string `envconfig:"GITNESS_ACME_ENDPOINT"`
|
||||
Email bool `envconfig:"GITNESS_ACME_EMAIL"`
|
||||
}
|
||||
}
|
||||
|
||||
// Database defines the database configuration parameters.
|
||||
Database struct {
|
||||
Driver string `envconfig:"APP_DATABASE_DRIVER" default:"sqlite3"`
|
||||
Datasource string `envconfig:"APP_DATABASE_DATASOURCE" default:"database.sqlite3"`
|
||||
Driver string `envconfig:"GITNESS_DATABASE_DRIVER" default:"sqlite3"`
|
||||
Datasource string `envconfig:"GITNESS_DATABASE_DATASOURCE" default:"database.sqlite3"`
|
||||
}
|
||||
|
||||
// Token defines token configuration parameters.
|
||||
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 struct {
|
||||
AllowedOrigins []string `envconfig:"APP_CORS_ALLOWED_ORIGINS" default:"*"`
|
||||
AllowedMethods []string `envconfig:"APP_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
|
||||
ExposedHeaders []string `envconfig:"APP_CORS_EXPOSED_HEADERS" default:"Link"`
|
||||
AllowCredentials bool `envconfig:"APP_CORS_ALLOW_CREDENTIALS" default:"true"`
|
||||
MaxAge int `envconfig:"APP_CORS_MAX_AGE" default:"300"`
|
||||
AllowedOrigins []string `envconfig:"GITNESS_CORS_ALLOWED_ORIGINS" default:"*"`
|
||||
AllowedMethods []string `envconfig:"GITNESS_CORS_ALLOWED_METHODS" default:"GET,POST,PATCH,PUT,DELETE,OPTIONS"`
|
||||
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:"GITNESS_CORS_EXPOSED_HEADERS" default:"Link"`
|
||||
AllowCredentials bool `envconfig:"GITNESS_CORS_ALLOW_CREDENTIALS" default:"true"`
|
||||
MaxAge int `envconfig:"GITNESS_CORS_MAX_AGE" default:"300"`
|
||||
}
|
||||
|
||||
// Secure defines http security parameters.
|
||||
Secure struct {
|
||||
AllowedHosts []string `envconfig:"APP_HTTP_ALLOWED_HOSTS"`
|
||||
HostsProxyHeaders []string `envconfig:"APP_HTTP_PROXY_HEADERS"`
|
||||
SSLRedirect bool `envconfig:"APP_HTTP_SSL_REDIRECT"`
|
||||
SSLTemporaryRedirect bool `envconfig:"APP_HTTP_SSL_TEMPORARY_REDIRECT"`
|
||||
SSLHost string `envconfig:"APP_HTTP_SSL_HOST"`
|
||||
SSLProxyHeaders map[string]string `envconfig:"APP_HTTP_SSL_PROXY_HEADERS"`
|
||||
STSSeconds int64 `envconfig:"APP_HTTP_STS_SECONDS"`
|
||||
STSIncludeSubdomains bool `envconfig:"APP_HTTP_STS_INCLUDE_SUBDOMAINS"`
|
||||
STSPreload bool `envconfig:"APP_HTTP_STS_PRELOAD"`
|
||||
ForceSTSHeader bool `envconfig:"APP_HTTP_STS_FORCE_HEADER"`
|
||||
BrowserXSSFilter bool `envconfig:"APP_HTTP_BROWSER_XSS_FILTER" default:"true"`
|
||||
FrameDeny bool `envconfig:"APP_HTTP_FRAME_DENY" default:"true"`
|
||||
ContentTypeNosniff bool `envconfig:"APP_HTTP_CONTENT_TYPE_NO_SNIFF"`
|
||||
ContentSecurityPolicy string `envconfig:"APP_HTTP_CONTENT_SECURITY_POLICY"`
|
||||
ReferrerPolicy string `envconfig:"APP_HTTP_REFERRER_POLICY"`
|
||||
AllowedHosts []string `envconfig:"GITNESS_HTTP_ALLOWED_HOSTS"`
|
||||
HostsProxyHeaders []string `envconfig:"GITNESS_HTTP_PROXY_HEADERS"`
|
||||
SSLRedirect bool `envconfig:"GITNESS_HTTP_SSL_REDIRECT"`
|
||||
SSLTemporaryRedirect bool `envconfig:"GITNESS_HTTP_SSL_TEMPORARY_REDIRECT"`
|
||||
SSLHost string `envconfig:"GITNESS_HTTP_SSL_HOST"`
|
||||
SSLProxyHeaders map[string]string `envconfig:"GITNESS_HTTP_SSL_PROXY_HEADERS"`
|
||||
STSSeconds int64 `envconfig:"GITNESS_HTTP_STS_SECONDS"`
|
||||
STSIncludeSubdomains bool `envconfig:"GITNESS_HTTP_STS_INCLUDE_SUBDOMAINS"`
|
||||
STSPreload bool `envconfig:"GITNESS_HTTP_STS_PRELOAD"`
|
||||
ForceSTSHeader bool `envconfig:"GITNESS_HTTP_STS_FORCE_HEADER"`
|
||||
BrowserXSSFilter bool `envconfig:"GITNESS_HTTP_BROWSER_XSS_FILTER" default:"true"`
|
||||
FrameDeny bool `envconfig:"GITNESS_HTTP_FRAME_DENY" default:"true"`
|
||||
ContentTypeNosniff bool `envconfig:"GITNESS_HTTP_CONTENT_TYPE_NO_SNIFF"`
|
||||
ContentSecurityPolicy string `envconfig:"GITNESS_HTTP_CONTENT_SECURITY_POLICY"`
|
||||
ReferrerPolicy string `envconfig:"GITNESS_HTTP_REFERRER_POLICY"`
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ type UserAttr int
|
||||
// Order enumeration.
|
||||
const (
|
||||
UserAttrNone UserAttr = iota
|
||||
UserAttrID
|
||||
UserAttrUID
|
||||
UserAttrName
|
||||
UserAttrEmail
|
||||
UserAttrAdmin
|
||||
@ -26,7 +26,7 @@ const (
|
||||
func ParseUserAttr(s string) UserAttr {
|
||||
switch strings.ToLower(s) {
|
||||
case "id":
|
||||
return UserAttrID
|
||||
return UserAttrUID
|
||||
case "name":
|
||||
return UserAttrName
|
||||
case "email":
|
||||
|
@ -11,7 +11,7 @@ func TestParseUserAttr(t *testing.T) {
|
||||
text string
|
||||
want UserAttr
|
||||
}{
|
||||
{"id", UserAttrID},
|
||||
{"id", UserAttrUID},
|
||||
{"name", UserAttrName},
|
||||
{"email", UserAttrEmail},
|
||||
{"created", UserAttrCreated},
|
||||
|
@ -10,15 +10,16 @@ import "github.com/harness/gitness/types/enum"
|
||||
type (
|
||||
// Represents the identity of an acting entity (User, ServiceAccount, Service).
|
||||
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"`
|
||||
Name string `db:"principal_name" json:"name"`
|
||||
Admin bool `db:"principal_admin" json:"admin"`
|
||||
|
||||
// Should be part of principal or not?
|
||||
ExternalID string `db:"principal_externalId" json:"externalId"`
|
||||
Blocked bool `db:"principal_blocked" json:"blocked"`
|
||||
Salt string `db:"principal_salt" json:"-"`
|
||||
Blocked bool `db:"principal_blocked" json:"blocked"`
|
||||
Salt string `db:"principal_salt" json:"-"`
|
||||
|
||||
// Other info
|
||||
Created int64 `db:"principal_created" json:"created"`
|
||||
@ -28,28 +29,42 @@ type (
|
||||
|
||||
func PrincipalFromUser(user *User) *Principal {
|
||||
return &Principal{
|
||||
ID: user.ID,
|
||||
Type: enum.PrincipalTypeUser,
|
||||
Name: user.Name,
|
||||
Admin: user.Admin,
|
||||
ExternalID: user.ExternalID,
|
||||
Blocked: user.Blocked,
|
||||
Salt: user.Salt,
|
||||
Created: user.Created,
|
||||
Updated: user.Updated,
|
||||
ID: user.ID,
|
||||
UID: user.UID,
|
||||
Type: enum.PrincipalTypeUser,
|
||||
Name: user.Name,
|
||||
Admin: user.Admin,
|
||||
Blocked: user.Blocked,
|
||||
Salt: user.Salt,
|
||||
Created: user.Created,
|
||||
Updated: user.Updated,
|
||||
}
|
||||
}
|
||||
|
||||
func PrincipalFromServiceAccount(sa *ServiceAccount) *Principal {
|
||||
return &Principal{
|
||||
ID: sa.ID,
|
||||
Type: enum.PrincipalTypeServiceAccount,
|
||||
Name: sa.Name,
|
||||
Admin: false,
|
||||
ExternalID: sa.ExternalID,
|
||||
Blocked: sa.Blocked,
|
||||
Salt: sa.Salt,
|
||||
Created: sa.Created,
|
||||
Updated: sa.Updated,
|
||||
ID: sa.ID,
|
||||
UID: sa.UID,
|
||||
Type: enum.PrincipalTypeServiceAccount,
|
||||
Name: sa.Name,
|
||||
Admin: false,
|
||||
Blocked: sa.Blocked,
|
||||
Salt: sa.Salt,
|
||||
Created: sa.Created,
|
||||
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,15 +10,15 @@ import "github.com/harness/gitness/types/enum"
|
||||
type (
|
||||
// Service is a principal representing a different internal service that runs alongside gitness.
|
||||
Service struct {
|
||||
// Fields from Principal (without admin)
|
||||
ID int64 `db:"principal_id" json:"id"`
|
||||
Name string `db:"principal_name" json:"name"`
|
||||
Admin bool `db:"principal_admin" json:"admin"`
|
||||
ExternalID string `db:"principal_externalId" json:"externalId"`
|
||||
Blocked bool `db:"principal_blocked" json:"blocked"`
|
||||
Salt string `db:"principal_salt" json:"-"`
|
||||
Created int64 `db:"principal_created" json:"created"`
|
||||
Updated int64 `db:"principal_updated" json:"updated"`
|
||||
// Fields from Principal (without admin, as it's always admin for now)
|
||||
ID int64 `db:"principal_id" json:"-"`
|
||||
UID string `db:"principal_uid" json:"uid"`
|
||||
Name string `db:"principal_name" json:"name"`
|
||||
Admin bool `db:"principal_admin" json:"admin"`
|
||||
Blocked bool `db:"principal_blocked" json:"blocked"`
|
||||
Salt string `db:"principal_salt" json:"-"`
|
||||
Created int64 `db:"principal_created" json:"created"`
|
||||
Updated int64 `db:"principal_updated" json:"updated"`
|
||||
}
|
||||
|
||||
// ServiceAccountInput store details used to
|
||||
|
@ -10,14 +10,14 @@ import "github.com/harness/gitness/types/enum"
|
||||
type (
|
||||
// ServiceAccount is a principal representing a service account.
|
||||
ServiceAccount struct {
|
||||
// Fields from Principal (without admin)
|
||||
ID int64 `db:"principal_id" json:"id"`
|
||||
Name string `db:"principal_name" json:"name"`
|
||||
ExternalID string `db:"principal_externalId" json:"externalId"`
|
||||
Blocked bool `db:"principal_blocked" json:"blocked"`
|
||||
Salt string `db:"principal_salt" json:"-"`
|
||||
Created int64 `db:"principal_created" json:"created"`
|
||||
Updated int64 `db:"principal_updated" json:"updated"`
|
||||
// Fields from Principal (without admin, as it's never an admin)
|
||||
ID int64 `db:"principal_id" json:"-"`
|
||||
UID string `db:"principal_uid" json:"uid"`
|
||||
Name string `db:"principal_name" json:"name"`
|
||||
Blocked bool `db:"principal_blocked" json:"blocked"`
|
||||
Salt string `db:"principal_salt" json:"-"`
|
||||
Created int64 `db:"principal_created" json:"created"`
|
||||
Updated int64 `db:"principal_updated" json:"updated"`
|
||||
|
||||
// ServiceAccount specific fields
|
||||
ParentType enum.ParentResourceType `db:"principal_sa_parentType" json:"parentType"`
|
||||
|
@ -13,14 +13,14 @@ type (
|
||||
// User is a principal representing an end user.
|
||||
User struct {
|
||||
// Fields from Principal
|
||||
ID int64 `db:"principal_id" json:"id"`
|
||||
Name string `db:"principal_name" json:"name"`
|
||||
Admin bool `db:"principal_admin" json:"admin"`
|
||||
ExternalID string `db:"principal_externalId" json:"externalId"`
|
||||
Blocked bool `db:"principal_blocked" json:"blocked"`
|
||||
Salt string `db:"principal_salt" json:"-"`
|
||||
Created int64 `db:"principal_created" json:"created"`
|
||||
Updated int64 `db:"principal_updated" json:"updated"`
|
||||
ID int64 `db:"principal_id" json:"-"`
|
||||
UID string `db:"principal_uid" json:"uid"`
|
||||
Name string `db:"principal_name" json:"name"`
|
||||
Admin bool `db:"principal_admin" json:"admin"`
|
||||
Blocked bool `db:"principal_blocked" json:"blocked"`
|
||||
Salt string `db:"principal_salt" json:"-"`
|
||||
Created int64 `db:"principal_created" json:"created"`
|
||||
Updated int64 `db:"principal_updated" json:"updated"`
|
||||
|
||||
// User specific fields
|
||||
Email string `db:"principal_user_email" json:"email"`
|
||||
|
Loading…
Reference in New Issue
Block a user