[MAINT] initial config for ci linter (#17)

* initial config for ci linter

* more linter work

* linter errors fix

* linter errors fix

* linter conf minor changes
jobatzil/rename
Enver Bisevac 2022-09-19 18:13:18 +02:00 committed by GitHub
parent 11f3aa532c
commit f03528e862
110 changed files with 1277 additions and 957 deletions

View File

@ -25,6 +25,7 @@ fi
# The files are then git-added
FILES=$(git diff --cached --name-only --diff-filter=ACMR | grep .go | sed 's| |\\ |g')
if [ -n "$FILES" ]; then
make format
make lint
if [ $? -ne 0 ]; then
echo "Error running make check - please fix before committing"

266
.golangci.yml Normal file
View File

@ -0,0 +1,266 @@
## Golden config for golangci-lint v1.49.0
run:
# Timeout for analysis, e.g. 30s, 5m.
# Default: 1m
timeout: 3m
# This file contains only configs which differ from defaults.
# All possible options can be found here https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml
linters-settings:
cyclop:
# The maximal code complexity to report.
# Default: 10
max-complexity: 30
# The maximal average package complexity.
# If it's higher than 0.0 (float) the check is enabled
# Default: 0.0
package-average: 10.0
errcheck:
# Report about not checking of errors in type assertions: `a := b.(MyStruct)`.
# Such cases aren't reported by default.
# Default: false
check-type-assertions: true
funlen:
# Checks the number of lines in a function.
# If lower than 0, disable the check.
# Default: 60
lines: 100
# Checks the number of statements in a function.
# If lower than 0, disable the check.
# Default: 40
statements: 50
gocognit:
# Minimal code complexity to report
# Default: 30 (but we recommend 10-20)
min-complexity: 20
gocritic:
# Settings passed to gocritic.
# The settings key is the name of a supported gocritic checker.
# The list of supported checkers can be find in https://go-critic.github.io/overview.
settings:
captLocal:
# Whether to restrict checker to params only.
# Default: true
paramsOnly: false
underef:
# Whether to skip (*x).method() calls where x is a pointer receiver.
# Default: true
skipRecvDeref: false
gomnd:
# List of function patterns to exclude from analysis.
# Values always ignored: `time.Date`
# Default: []
ignored-functions:
- os.Chmod
- os.Mkdir
- os.MkdirAll
- os.OpenFile
- os.WriteFile
- prometheus.ExponentialBuckets
- prometheus.ExponentialBucketsRange
- prometheus.LinearBuckets
- strconv.FormatFloat
- strconv.FormatInt
- strconv.FormatUint
- strconv.ParseFloat
- strconv.ParseInt
- strconv.ParseUint
gomodguard:
blocked:
# List of blocked modules.
# Default: []
modules:
- github.com/golang/protobuf:
recommendations:
- google.golang.org/protobuf
reason: "see https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules"
- github.com/satori/go.uuid:
recommendations:
- github.com/google/uuid
reason: "satori's package is not maintained"
- github.com/gofrs/uuid:
recommendations:
- github.com/google/uuid
reason: "see recommendation from dev-infra team: https://confluence.gtforge.com/x/gQI6Aw"
govet:
# Enable all analyzers.
# Default: false
enable-all: true
# Disable analyzers by name.
# Run `go tool vet help` to see all analyzers.
# Default: []
disable:
- fieldalignment # too strict
# Settings per analyzer.
settings:
shadow:
# Whether to be strict about shadowing; can be noisy.
# Default: false
strict: true
nakedret:
# Make an issue if func has more lines of code than this setting, and it has naked returns.
# Default: 30
max-func-lines: 0
nolintlint:
# Exclude following linters from requiring an explanation.
# Default: []
allow-no-explanation: [ funlen, gocognit, lll ]
# Enable to require an explanation of nonzero length after each nolint directive.
# Default: false
require-explanation: true
# Enable to require nolint directives to mention the specific linter being suppressed.
# Default: false
require-specific: true
rowserrcheck:
# database/sql is always checked
# Default: []
packages:
- github.com/jmoiron/sqlx
tenv:
# The option `all` will run against whole test files (`_test.go`) regardless of method/function signatures.
# Otherwise, only methods that take `*testing.T`, `*testing.B`, and `testing.TB` as arguments are checked.
# Default: false
all: true
linters:
disable-all: true
enable:
## enabled by default
- errcheck # checking for unchecked errors, these unchecked errors can be critical bugs in some cases
- gosimple # specializes in simplifying a code
- govet # reports suspicious constructs, such as Printf calls whose arguments do not align with the format string
- ineffassign # detects when assignments to existing variables are not used
- staticcheck # is a go vet on steroids, applying a ton of static analysis checks
- typecheck # like the front-end of a Go compiler, parses and type-checks Go code
- unused # checks for unused constants, variables, functions and types
## disabled by default
- asasalint # checks for pass []any as any in variadic func(...any)
- asciicheck # checks that your code does not contain non-ASCII identifiers
- bidichk # checks for dangerous unicode character sequences
- bodyclose # checks whether HTTP response body is closed successfully
#- contextcheck # checks the function whether use a non-inherited context # TODO: enable after golangci-lint uses https://github.com/sylvia7788/contextcheck/releases/tag/v1.0.7
- cyclop # checks function and package cyclomatic complexity
# - dupl # tool for code clone detection
- durationcheck # checks for two durations multiplied together
- errname # checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error
- errorlint # finds code that will cause problems with the error wrapping scheme introduced in Go 1.13
- execinquery # checks query string in Query function which reads your Go src files and warning it finds
- exhaustive # checks exhaustiveness of enum switch statements
- exportloopref # checks for pointers to enclosing loop variables
- forbidigo # forbids identifiers
- funlen # tool for detection of long functions
#- gochecknoglobals # checks that no global variables exist
#- gochecknoinits # checks that no init functions are present in Go code
- gocognit # computes and checks the cognitive complexity of functions
- goconst # finds repeated strings that could be replaced by a constant
- gocritic # provides diagnostics that check for bugs, performance and style issues
- gocyclo # computes and checks the cyclomatic complexity of functions
- godot # checks if comments end in a period
- goimports # in addition to fixing imports, goimports also formats your code in the same style as gofmt
- gomnd # detects magic numbers
- gomoddirectives # manages the use of 'replace', 'retract', and 'excludes' directives in go.mod
- gomodguard # allow and block lists linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations
- goprintffuncname # checks that printf-like functions are named with f at the end
- gosec # inspects source code for security problems
- lll # reports long lines
- makezero # finds slice declarations with non-zero initial length
- nakedret # finds naked returns in functions greater than a specified function length
- nestif # reports deeply nested if statements
- nilerr # finds the code that returns nil even if it checks that the error is not nil
- nilnil # checks that there is no simultaneous return of nil error and an invalid value
- noctx # finds sending http request without context.Context
- nolintlint # reports ill-formed or insufficient nolint directives
- nonamedreturns # reports all named returns
- nosprintfhostport # checks for misuse of Sprintf to construct a host with port in a URL
- predeclared # finds code that shadows one of Go's predeclared identifiers
- promlinter # checks Prometheus metrics naming via promlint
- reassign # checks that package variables are not reassigned
- revive # fast, configurable, extensible, flexible, and beautiful linter for Go, drop-in replacement of golint
- rowserrcheck # checks whether Err of rows is checked successfully
- sqlclosecheck # checks that sql.Rows and sql.Stmt are closed
- stylecheck # is a replacement for golint
- tenv # detects using os.Setenv instead of t.Setenv since Go1.17
#- testpackage # makes you use a separate _test package
- tparallel # detects inappropriate usage of t.Parallel() method in your Go test codes
- unconvert # removes unnecessary type conversions
- unparam # reports unused function parameters
- usestdlibvars # detects the possibility to use variables/constants from the Go standard library
- wastedassign # finds wasted assignment statements
- whitespace # detects leading and trailing whitespace
## you may want to enable
#- decorder # checks declaration order and count of types, constants, variables and functions
#- exhaustruct # checks if all structure fields are initialized
#- gci # controls golang package import order and makes it always deterministic
#- godox # detects FIXME, TODO and other comment keywords
#- goheader # checks is file header matches to pattern
#- interfacebloat # checks the number of methods inside an interface
#- ireturn # accept interfaces, return concrete types
#- prealloc # [premature optimization, but can be used in some cases] finds slice declarations that could potentially be preallocated
#- varnamelen # [great idea, but too many false positives] checks that the length of a variable's name matches its scope
#- wrapcheck # checks that errors returned from external packages are wrapped
## disabled
#- containedctx # detects struct contained context.Context field
#- depguard # [replaced by gomodguard] checks if package imports are in a list of acceptable packages
#- dogsled # checks assignments with too many blank identifiers (e.g. x, _, _, _, := f())
#- errchkjson # [don't see profit + I'm against of omitting errors like in the first example https://github.com/breml/errchkjson] checks types passed to the json encoding functions. Reports unsupported types and optionally reports occasions, where the check for the returned error can be omitted
#- forcetypeassert # [replaced by errcheck] finds forced type assertions
#- goerr113 # [too strict] checks the errors handling expressions
#- gofmt # [replaced by goimports] checks whether code was gofmt-ed
#- gofumpt # [replaced by goimports, gofumports is not available yet] checks whether code was gofumpt-ed
#- grouper # analyzes expression groups
#- importas # enforces consistent import aliases
#- logrlint # [owner archived repository] checks logr arguments
#- maintidx # measures the maintainability index of each function
#- misspell # [useless] finds commonly misspelled English words in comments
#- nlreturn # [too strict and mostly code is not more readable] checks for a new line before return and branch statements to increase code clarity
#- paralleltest # [too many false positives] detects missing usage of t.Parallel() method in your Go test
#- tagliatelle # checks the struct tags
#- thelper # detects golang test helpers without t.Helper() call and checks the consistency of test helpers
#- wsl # [too strict and mostly code is not more readable] whitespace linter forces you to use empty lines
issues:
# Maximum count of issues with the same text.
# Set to 0 to disable.
# Default: 3
max-same-issues: 50
exclude-rules:
- source: "^//\\s*go:generate\\s"
linters: [ lll ]
- source: "(noinspection|TODO)"
linters: [ godot ]
- source: "//noinspection"
linters: [ gocritic ]
- source: "^\\s+if _, ok := err\\.\\([^.]+\\.InternalError\\); ok {"
linters: [ errorlint ]
- path: "^cli/"
linters: [forbidigo]
- text: "mnd: Magic number: \\d"
linters:
- gomnd
- path: "_test\\.go"
linters:
- bodyclose
- dupl
- funlen
- goconst
- gosec
- noctx
- wrapcheck

View File

@ -16,11 +16,10 @@ import (
"gopkg.in/alecthomas/kingpin.v2"
)
// application name
var application = "gitness"
// application description
var description = "description goes here" // TODO edit this application description
const (
application = "gitness"
description = "description goes here" // TODO edit this application description
)
// Command parses the command line arguments and then executes a
// subcommand program.

5
cli/file.go Normal file
View File

@ -0,0 +1,5 @@
package cli
const (
OwnerReadWrite = 0600
)

View File

@ -5,8 +5,10 @@
package cli
import (
"context"
"encoding/json"
"io/ioutil"
"time"
"github.com/harness/gitness/cli/util"
"github.com/harness/gitness/client"
@ -20,8 +22,10 @@ type loginCommand struct {
func (c *loginCommand) run(*kingpin.ParseContext) error {
username, password := util.Credentials()
client := client.New(c.server)
token, err := client.Login(username, password)
httpClient := client.New(c.server)
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
token, err := httpClient.Login(ctx, username, password)
if err != nil {
return err
}
@ -34,7 +38,7 @@ func (c *loginCommand) run(*kingpin.ParseContext) error {
if err != nil {
return err
}
return ioutil.WriteFile(path, data, 0600)
return ioutil.WriteFile(path, data, OwnerReadWrite)
}
// helper function to register the logout command.

View File

@ -5,8 +5,10 @@
package cli
import (
"context"
"encoding/json"
"io/ioutil"
"time"
"github.com/harness/gitness/cli/util"
"github.com/harness/gitness/client"
@ -19,8 +21,10 @@ type registerCommand struct {
func (c *registerCommand) run(*kingpin.ParseContext) error {
username, password := util.Credentials()
client := client.New(c.server)
token, err := client.Register(username, password)
httpClient := client.New(c.server)
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
token, err := httpClient.Register(ctx, username, password)
if err != nil {
return err
}
@ -33,7 +37,7 @@ func (c *registerCommand) run(*kingpin.ParseContext) error {
if err != nil {
return err
}
return ioutil.WriteFile(path, data, 0600)
return ioutil.WriteFile(path, data, OwnerReadWrite)
}
// helper function to register the register command.

View File

@ -21,7 +21,6 @@ var legacy = map[string]string{
// 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 {

View File

@ -23,7 +23,7 @@ func (c *swaggerCommand) run(*kingpin.ParseContext) error {
os.Stdout.Write(data)
return nil
}
return ioutil.WriteFile(c.path, data, 0600)
return ioutil.WriteFile(c.path, data, OwnerReadWrite)
}
// helper function to register the swagger command.

View File

@ -5,8 +5,10 @@
package token
import (
"context"
"encoding/json"
"os"
"time"
"github.com/harness/gitness/cli/util"
@ -22,7 +24,9 @@ func (c *command) run(*kingpin.ParseContext) error {
if err != nil {
return err
}
token, err := client.Token()
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
token, err := client.Token(ctx)
if err != nil {
return err
}
@ -44,5 +48,4 @@ func Register(app *kingpin.Application) {
cmd.Flag("json", "json encode the output").
BoolVar(&c.json)
}

View File

@ -5,9 +5,11 @@
package user
import (
"context"
"encoding/json"
"os"
"text/template"
"time"
"github.com/harness/gitness/cli/util"
@ -30,7 +32,9 @@ func (c *command) run(*kingpin.ParseContext) error {
if err != nil {
return err
}
user, err := client.Self()
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
user, err := client.Self(ctx)
if err != nil {
return err
}

View File

@ -5,9 +5,11 @@
package users
import (
"context"
"encoding/json"
"os"
"text/template"
"time"
"github.com/harness/gitness/cli/util"
"github.com/harness/gitness/types"
@ -33,7 +35,9 @@ func (c *createCommand) run(*kingpin.ParseContext) error {
Email: c.email,
Password: util.Password(),
}
user, err := client.UserCreate(in)
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
user, err := client.UserCreate(ctx, in)
if err != nil {
return err
}
@ -49,7 +53,7 @@ func (c *createCommand) run(*kingpin.ParseContext) error {
return tmpl.Execute(os.Stdout, user)
}
// helper function registers the user create command
// helper function registers the user create command.
func registerCreate(app *kingpin.CmdClause) {
c := new(createCommand)

View File

@ -5,6 +5,9 @@
package users
import (
"context"
"time"
"github.com/harness/gitness/cli/util"
"gopkg.in/alecthomas/kingpin.v2"
@ -19,10 +22,12 @@ func (c *deleteCommand) run(*kingpin.ParseContext) error {
if err != nil {
return err
}
return client.UserDelete(c.email)
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
return client.UserDelete(ctx, c.email)
}
// helper function registers the user delete command
// helper function registers the user delete command.
func registerDelete(app *kingpin.CmdClause) {
c := new(deleteCommand)

View File

@ -5,9 +5,11 @@
package users
import (
"context"
"encoding/json"
"os"
"text/template"
"time"
"github.com/harness/gitness/cli/util"
@ -26,7 +28,9 @@ func (c *findCommand) run(*kingpin.ParseContext) error {
if err != nil {
return err
}
user, err := client.User(c.email)
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
user, err := client.User(ctx, c.email)
if err != nil {
return err
}
@ -42,7 +46,7 @@ func (c *findCommand) run(*kingpin.ParseContext) error {
return tmpl.Execute(os.Stdout, user)
}
// helper function registers the user find command
// helper function registers the user find command.
func registerFind(app *kingpin.CmdClause) {
c := new(findCommand)

View File

@ -5,9 +5,11 @@
package users
import (
"context"
"encoding/json"
"os"
"text/template"
"time"
"github.com/drone/funcmap"
"github.com/harness/gitness/cli/util"
@ -34,7 +36,9 @@ func (c *listCommand) run(*kingpin.ParseContext) error {
if err != nil {
return err
}
list, err := client.UserList(types.Params{
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
list, err := client.UserList(ctx, types.Params{
Size: c.size,
Page: c.page,
})
@ -58,7 +62,7 @@ func (c *listCommand) run(*kingpin.ParseContext) error {
return nil
}
// helper function registers the user list command
// helper function registers the user list command.
func registerList(app *kingpin.CmdClause) {
c := new(listCommand)

View File

@ -5,10 +5,12 @@
package users
import (
"context"
"encoding/json"
"fmt"
"os"
"text/template"
"time"
"github.com/harness/gitness/cli/util"
"github.com/harness/gitness/types"
@ -50,12 +52,16 @@ func (c *updateCommand) run(*kingpin.ParseContext) error {
in.Admin = ptr.Bool(false)
}
if c.passgen {
v := uniuri.NewLen(8)
const maxRandomChars = 8
v := uniuri.NewLen(maxRandomChars)
in.Password = ptr.String(v)
fmt.Printf("generated temporary password: %s\n", v)
}
user, err := client.UserUpdate(c.id, in)
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
user, err := client.UserUpdate(ctx, c.id, in)
if err != nil {
return err
}
@ -71,7 +77,7 @@ func (c *updateCommand) run(*kingpin.ParseContext) error {
return tmpl.Execute(os.Stdout, user)
}
// helper function registers the user update command
// helper function registers the user update command.
func registerUpdate(app *kingpin.CmdClause) {
c := new(updateCommand)

View File

@ -9,7 +9,6 @@ import (
"encoding/json"
"errors"
"fmt"
"golang.org/x/term"
"io/ioutil"
"os"
"path/filepath"
@ -17,6 +16,8 @@ import (
"syscall"
"time"
"golang.org/x/term"
"github.com/harness/gitness/client"
"github.com/harness/gitness/types"
@ -34,7 +35,7 @@ func Client() (*client.HTTPClient, error) {
return nil, err
}
token := new(types.Token)
if err := json.Unmarshal(data, token); err != nil {
if err = json.Unmarshal(data, token); err != nil {
return nil, err
}
if time.Now().Unix() > token.Expires.Unix() {
@ -72,7 +73,7 @@ func Username() string {
// Password returns the password from stdin.
func Password() string {
fmt.Print("Enter Password: ")
passwordb, _ := term.ReadPassword(int(syscall.Stdin))
passwordb, _ := term.ReadPassword(syscall.Stdin)
password := string(passwordb)
return strings.TrimSpace(password)

View File

@ -6,14 +6,16 @@ package client
import (
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/rs/zerolog/log"
"io"
"net/http"
"net/http/httputil"
"net/url"
"github.com/rs/zerolog/log"
"github.com/harness/gitness/types"
"github.com/harness/gitness/version"
)
@ -56,24 +58,24 @@ func (c *HTTPClient) SetDebug(debug bool) {
}
// Login authenticates the user and returns a JWT token.
func (c *HTTPClient) Login(username, password string) (*types.Token, error) {
func (c *HTTPClient) Login(ctx context.Context, username, password string) (*types.Token, error) {
form := &url.Values{}
form.Add("username", username)
form.Add("password", password)
out := new(types.UserToken)
uri := fmt.Sprintf("%s/api/v1/login?return_user=true", c.base)
err := c.post(uri, form, out)
err := c.post(ctx, uri, form, out)
return out.Token, err
}
// Register registers a new user and returns a JWT token.
func (c *HTTPClient) Register(username, password string) (*types.Token, error) {
func (c *HTTPClient) Register(ctx context.Context, username, password string) (*types.Token, error) {
form := &url.Values{}
form.Add("username", username)
form.Add("password", password)
out := new(types.UserToken)
uri := fmt.Sprintf("%s/api/v1/register?return_user=true", c.base)
err := c.post(uri, form, out)
err := c.post(ctx, uri, form, out)
return out.Token, err
}
@ -82,58 +84,58 @@ func (c *HTTPClient) Register(username, password string) (*types.Token, error) {
//
// Self returns the currently authenticated user.
func (c *HTTPClient) Self() (*types.User, error) {
func (c *HTTPClient) Self(ctx context.Context) (*types.User, error) {
out := new(types.User)
uri := fmt.Sprintf("%s/api/v1/user", c.base)
err := c.get(uri, out)
err := c.get(ctx, uri, out)
return out, err
}
// Token returns an oauth2 bearer token for the currently
// authenticated user.
func (c *HTTPClient) Token() (*types.Token, error) {
func (c *HTTPClient) Token(ctx context.Context) (*types.Token, error) {
out := new(types.Token)
uri := fmt.Sprintf("%s/api/v1/user/token", c.base)
err := c.post(uri, nil, out)
err := c.post(ctx, uri, nil, out)
return out, err
}
// User returns a user by ID or email.
func (c *HTTPClient) User(key string) (*types.User, error) {
func (c *HTTPClient) User(ctx context.Context, key string) (*types.User, error) {
out := new(types.User)
uri := fmt.Sprintf("%s/api/v1/users/%s", c.base, key)
err := c.get(uri, out)
err := c.get(ctx, uri, out)
return out, err
}
// UserList returns a list of all registered users.
func (c *HTTPClient) UserList(params types.Params) ([]*types.User, error) {
out := []*types.User{}
func (c *HTTPClient) UserList(ctx context.Context, params types.Params) ([]types.User, error) {
out := []types.User{}
uri := fmt.Sprintf("%s/api/v1/users?page=%d&per_page=%d", c.base, params.Page, params.Size)
err := c.get(uri, &out)
err := c.get(ctx, uri, &out)
return out, err
}
// UserCreate creates a new user account.
func (c *HTTPClient) UserCreate(user *types.User) (*types.User, error) {
func (c *HTTPClient) UserCreate(ctx context.Context, user *types.User) (*types.User, error) {
out := new(types.User)
uri := fmt.Sprintf("%s/api/v1/users", c.base)
err := c.post(uri, user, out)
err := c.post(ctx, uri, user, out)
return out, err
}
// UserUpdate updates a user account by ID or email.
func (c *HTTPClient) UserUpdate(key string, user *types.UserInput) (*types.User, error) {
func (c *HTTPClient) UserUpdate(ctx context.Context, key string, user *types.UserInput) (*types.User, error) {
out := new(types.User)
uri := fmt.Sprintf("%s/api/v1/users/%s", c.base, key)
err := c.patch(uri, user, out)
err := c.patch(ctx, uri, user, out)
return out, err
}
// UserDelete deletes a user account by ID or email.
func (c *HTTPClient) UserDelete(key string) error {
func (c *HTTPClient) UserDelete(ctx context.Context, key string) error {
uri := fmt.Sprintf("%s/api/v1/users/%s", c.base, key)
err := c.delete(uri)
err := c.delete(ctx, uri)
return err
}
@ -142,32 +144,34 @@ func (c *HTTPClient) UserDelete(key string) error {
//
// helper function for making an http GET request.
func (c *HTTPClient) get(rawurl string, out interface{}) error {
return c.do(rawurl, "GET", nil, out)
func (c *HTTPClient) get(ctx context.Context, rawurl string, out interface{}) error {
return c.do(ctx, rawurl, "GET", nil, out)
}
// helper function for making an http POST request.
func (c *HTTPClient) post(rawurl string, in, out interface{}) error {
return c.do(rawurl, "POST", in, out)
func (c *HTTPClient) post(ctx context.Context, rawurl string, in, out interface{}) error {
return c.do(ctx, rawurl, "POST", in, out)
}
// helper function for making an http PATCH request.
func (c *HTTPClient) patch(rawurl string, in, out interface{}) error {
return c.do(rawurl, "PATCH", in, out)
func (c *HTTPClient) patch(ctx context.Context, rawurl string, in, out interface{}) error {
return c.do(ctx, rawurl, "PATCH", in, out)
}
// helper function for making an http DELETE request.
func (c *HTTPClient) delete(rawurl string) error {
return c.do(rawurl, "DELETE", nil, nil)
func (c *HTTPClient) delete(ctx context.Context, rawurl string) error {
return c.do(ctx, rawurl, "DELETE", nil, nil)
}
// helper function to make an http request
func (c *HTTPClient) do(rawurl, method string, in, out interface{}) error {
// helper function to make an http request.
func (c *HTTPClient) do(ctx context.Context, rawurl, method string, in, out interface{}) error {
// executes the http request and returns the body as
// and io.ReadCloser
body, err := c.stream(rawurl, method, in, out)
body, err := c.stream(ctx, rawurl, method, in, out)
if body != nil {
defer body.Close()
defer func(body io.ReadCloser) {
_ = body.Close()
}(body)
}
if err != nil {
return err
@ -181,8 +185,8 @@ func (c *HTTPClient) do(rawurl, method string, in, out interface{}) error {
return nil
}
// helper function to stream an http request
func (c *HTTPClient) stream(rawurl, method string, in, out interface{}) (io.ReadCloser, error) {
// helper function to stream a http request.
func (c *HTTPClient) stream(ctx context.Context, rawurl, method string, in, _ interface{}) (io.ReadCloser, error) {
uri, err := url.Parse(rawurl)
if err != nil {
return nil, err
@ -192,21 +196,19 @@ func (c *HTTPClient) stream(rawurl, method string, in, out interface{}) (io.Read
// write it to the body of the request.
var buf io.ReadWriter
if in != nil {
buf = new(bytes.Buffer)
buf = &bytes.Buffer{}
// if posting form data, encode the form values.
if form, ok := in.(*url.Values); ok {
if _, err := io.WriteString(buf, form.Encode()); err != nil {
if _, err = io.WriteString(buf, form.Encode()); err != nil {
log.Err(err).Msg("in stream method")
}
} else {
if err := json.NewEncoder(buf).Encode(in); err != nil {
return nil, err
}
} else if err = json.NewEncoder(buf).Encode(in); err != nil {
return nil, err
}
}
// creates a new http request.
req, err := http.NewRequest(method, uri.String(), buf)
req, err := http.NewRequestWithContext(ctx, method, uri.String(), buf)
if err != nil {
return nil, err
}
@ -231,12 +233,14 @@ func (c *HTTPClient) stream(rawurl, method string, in, out interface{}) (io.Read
}
if c.debug {
dump, _ := httputil.DumpResponse(resp, true)
fmt.Println(method, rawurl)
fmt.Println(string(dump))
log.Debug().Msgf("method %s, url %s", method, rawurl)
log.Debug().Msg(string(dump))
}
if resp.StatusCode > 299 {
defer resp.Body.Close()
err := new(remoteError)
if resp.StatusCode >= http.StatusMultipleChoices {
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(resp.Body)
err = &remoteError{}
if decodeErr := json.NewDecoder(resp.Body).Decode(err); decodeErr != nil {
return nil, decodeErr
}

View File

@ -4,37 +4,41 @@
package client
import "github.com/harness/gitness/types"
import (
"context"
"github.com/harness/gitness/types"
)
// Client to access the remote APIs.
type Client interface {
// Login authenticates the user and returns a JWT token.
Login(username, password string) (*types.Token, error)
Login(ctx context.Context, username, password string) (*types.Token, error)
// Register registers a new user and returns a JWT token.
Register(username, password string) (*types.Token, error)
Register(ctx context.Context, username, password string) (*types.Token, error)
// Self returns the currently authenticated user.
Self() (*types.User, error)
Self(ctx context.Context) (*types.User, error)
// Token returns an oauth2 bearer token for the currently
// authenticated user.
Token() (*types.Token, error)
Token(ctx context.Context) (*types.Token, error)
// User returns a user by ID or email.
User(key string) (*types.User, error)
User(ctx context.Context, key string) (*types.User, error)
// UserList returns a list of all registered users.
UserList(params types.Params) ([]*types.User, error)
UserList(ctx context.Context, params types.Params) ([]types.User, error)
// UserCreate creates a new user account.
UserCreate(user *types.User) (*types.User, error)
UserCreate(ctx context.Context, user *types.User) (*types.User, error)
// UserUpdate updates a user account by ID or email.
UserUpdate(key string, input *types.UserInput) (*types.User, error)
UserUpdate(ctx context.Context, key string, input *types.UserInput) (*types.User, error)
// UserDelete deletes a user account by ID or email.
UserDelete(key string) error
UserDelete(ctx context.Context, key string) error
}
// remoteError store the error payload returned

View File

@ -8,18 +8,19 @@ import (
"fmt"
"net/http"
"github.com/rs/zerolog/hlog"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/api/request"
"github.com/harness/gitness/internal/auth/authz"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/pkg/errors"
"github.com/rs/zerolog/hlog"
)
var (
ErrNotAuthenticated = errors.New("Not authenticated.")
ErrNotAuthorized = errors.New("Not authorized.")
ErrNotAuthenticated = errors.New("not authenticated")
ErrNotAuthorized = errors.New("not authorized")
)
type Guard struct {
@ -30,9 +31,7 @@ func New(authorizer authz.Authorizer) *Guard {
return &Guard{authorizer: authorizer}
}
/*
* EnforceAdmin is a middleware that enforces that the user is authenticated and an admin.
*/
// EnforceAdmin is a middleware that enforces that the user is authenticated and an admin.
func (g *Guard) EnforceAdmin(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
@ -51,9 +50,7 @@ func (g *Guard) EnforceAdmin(next http.Handler) http.Handler {
})
}
/*
* EnforceAuthenticated is a middleware that enforces that the user is authenticated.
*/
// EnforceAuthenticated is a middleware that enforces that the user is authenticated.
func (g *Guard) EnforceAuthenticated(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
@ -67,18 +64,16 @@ func (g *Guard) EnforceAuthenticated(next http.Handler) http.Handler {
})
}
/*
* Enforces that the executing principal has requested permission on the resource.
* returns true if it's the case, otherwise renders the appropriate error and returns false.
*/
func (g *Guard) Enforce(w http.ResponseWriter, r *http.Request, scope *types.Scope, resource *types.Resource, permission enum.Permission) bool {
// Enforce that the executing principal has requested permission on the resource
// returns true if it's the case, otherwise renders the appropriate error and returns false.
func (g *Guard) Enforce(w http.ResponseWriter, r *http.Request, scope *types.Scope, resource *types.Resource,
permission enum.Permission) bool {
err := g.Check(r, scope, resource, permission)
// render error if needed
if errors.Is(err, ErrNotAuthenticated) {
switch {
case errors.Is(err, ErrNotAuthenticated):
render.ErrorObject(w, http.StatusUnauthorized, render.ErrUnauthorized)
} else if errors.Is(err, ErrNotAuthorized) {
case errors.Is(err, ErrNotAuthorized):
// log error for debugging.
hlog.FromRequest(r).Debug().Msgf("User not authorized to perform %s on resource %v in scope %v",
permission,
@ -86,21 +81,17 @@ func (g *Guard) Enforce(w http.ResponseWriter, r *http.Request, scope *types.Sco
scope)
render.Forbidden(w)
} else if err != nil {
case err != nil:
// log err for debugging
hlog.FromRequest(r).Err(err).Msg("Encountered unexpected error while enforcing permission.")
render.InternalError(w)
}
return err == nil
}
/*
* Checks whether the principal executing the request has the requested permission on the resource.
* Returns nil if the user is confirmed to be permitted to execute the action, otherwise returns errors
* NotAuthenticated, NotAuthorized, or any unerlaying error.
*/
// Check whether the principal executing the request has the requested permission on the resource.
// Returns nil if the user is confirmed to be permitted to execute the action, otherwise returns errors
// NotAuthenticated, NotAuthorized, or any unerlaying error.
func (g *Guard) Check(r *http.Request, scope *types.Scope, resource *types.Resource, permission enum.Permission) error {
u, present := request.UserFrom(r.Context())
if !present {
@ -109,6 +100,7 @@ func (g *Guard) Check(r *http.Request, scope *types.Scope, resource *types.Resou
// TODO: don't hardcode principal type USER
authorized, err := g.authorizer.Check(
r.Context(),
enum.PrincipalTypeUser,
fmt.Sprint(u.ID),
scope,

View File

@ -23,9 +23,9 @@ import (
*
* Assumes the repository is already available in the request context.
*/
func (e *Guard) ForRepo(requiredPermission enum.Permission, orPublic bool) func(http.Handler) http.Handler {
func (g *Guard) ForRepo(requiredPermission enum.Permission, orPublic bool) func(http.Handler) http.Handler {
return func(h http.Handler) http.Handler {
return e.Repo(requiredPermission, orPublic, h.ServeHTTP)
return g.Repo(requiredPermission, orPublic, h.ServeHTTP)
}
}

View File

@ -24,9 +24,9 @@ import (
*
* Assumes the space is already available in the request context.
*/
func (e *Guard) ForSpace(requiredPermission enum.Permission, orPublic bool) func(http.Handler) http.Handler {
func (g *Guard) ForSpace(requiredPermission enum.Permission, orPublic bool) func(http.Handler) http.Handler {
return func(h http.Handler) http.Handler {
return e.Space(requiredPermission, orPublic, h.ServeHTTP)
return g.Space(requiredPermission, orPublic, h.ServeHTTP)
}
}

View File

@ -51,7 +51,7 @@ func HandleLogin(users store.UserStore, system store.SystemStore) http.HandlerFu
}
expires := time.Now().Add(system.Config(ctx).Token.Expire)
token_, err := token.GenerateExp(user, expires.Unix(), user.Salt)
token, err := token.GenerateExp(user, expires.Unix(), user.Salt)
if err != nil {
log.Err(err).
Str("user", username).
@ -68,13 +68,13 @@ func HandleLogin(users store.UserStore, system store.SystemStore) http.HandlerFu
&types.UserToken{
User: user,
Token: &types.Token{
Value: token_,
Value: token,
Expires: expires.UTC(),
},
})
} else {
// else return the token only.
render.JSON(w, http.StatusOK, &types.Token{Value: token_})
render.JSON(w, http.StatusOK, &types.Token{Value: token})
}
}
}

View File

@ -49,7 +49,7 @@ func HandleRegister(users store.UserStore, system store.SystemStore) http.Handle
Updated: time.Now().UnixMilli(),
}
if ok, err := check.User(user); !ok {
if err = check.User(user); err != nil {
log.Debug().Err(err).
Str("email", username).
Msg("invalid user input")
@ -58,7 +58,7 @@ func HandleRegister(users store.UserStore, system store.SystemStore) http.Handle
return
}
if err := users.Create(ctx, user); err != nil {
if err = users.Create(ctx, user); err != nil {
log.Err(err).
Str("email", username).
Msg("Failed to create user")
@ -72,7 +72,7 @@ func HandleRegister(users store.UserStore, system store.SystemStore) http.Handle
// user system admin access.
if user.ID == 1 {
user.Admin = true
if err := users.Update(ctx, user); err != nil {
if err = users.Update(ctx, user); err != nil {
log.Err(err).
Str("email", username).
Int64("user_id", user.ID).
@ -84,7 +84,7 @@ func HandleRegister(users store.UserStore, system store.SystemStore) http.Handle
}
expires := time.Now().Add(system.Config(ctx).Token.Expire)
token_, err := token.GenerateExp(user, expires.Unix(), user.Salt)
token, err := token.GenerateExp(user, expires.Unix(), user.Salt)
if err != nil {
log.Err(err).
Str("email", username).
@ -101,13 +101,13 @@ func HandleRegister(users store.UserStore, system store.SystemStore) http.Handle
render.JSON(w, http.StatusOK, &types.UserToken{
User: user,
Token: &types.Token{
Value: token_,
Value: token,
Expires: expires.UTC(),
},
})
} else {
// else return the token only.
render.JSON(w, http.StatusOK, &types.Token{Value: token_})
render.JSON(w, http.StatusOK, &types.Token{Value: token})
}
}
}

View File

@ -4,7 +4,7 @@
package common
// Used for path creation apis
// CreatePathRequest used for path creation apis.
type CreatePathRequest struct {
Path string
}

View File

@ -22,11 +22,11 @@ import (
type repoCreateInput struct {
Name string `json:"name"`
SpaceId int64 `json:"spaceId"`
SpaceID int64 `json:"spaceId"`
DisplayName string `json:"displayName"`
Description string `json:"description"`
IsPublic bool `json:"isPublic"`
ForkId int64 `json:"forkId"`
ForkID int64 `json:"forkId"`
}
/*
@ -48,14 +48,14 @@ func HandleCreate(guard *guard.Guard, spaces store.SpaceStore, repos store.RepoS
}
// ensure we reference a space
if in.SpaceId <= 0 {
if in.SpaceID <= 0 {
render.BadRequestf(w, "A repository can only be created within a space.")
return
}
parentSpace, err := spaces.Find(ctx, in.SpaceId)
parentSpace, err := spaces.Find(ctx, in.SpaceID)
if err != nil {
log.Err(err).Msgf("Failed to get space with id '%d'.", in.SpaceId)
log.Err(err).Msgf("Failed to get space with id '%d'.", in.SpaceID)
render.UserfiedErrorOrInternal(w, err)
return
@ -83,18 +83,18 @@ func HandleCreate(guard *guard.Guard, spaces store.SpaceStore, repos store.RepoS
// create new repo object
repo := &types.Repository{
Name: strings.ToLower(in.Name),
SpaceId: in.SpaceId,
SpaceID: in.SpaceID,
DisplayName: in.DisplayName,
Description: in.Description,
IsPublic: in.IsPublic,
CreatedBy: usr.ID,
Created: time.Now().UnixMilli(),
Updated: time.Now().UnixMilli(),
ForkId: in.ForkId,
ForkID: in.ForkID,
}
// validate repo
if err := check.Repo(repo); err != nil {
if err = check.Repo(repo); err != nil {
render.UserfiedErrorOrInternal(w, err)
return
}

View File

@ -27,15 +27,15 @@ func HandleDeletePath(guard *guard.Guard, repos store.RepoStore) http.HandlerFun
log := hlog.FromRequest(r)
repo, _ := request.RepoFrom(ctx)
pathId, err := request.GetPathId(r)
pathID, err := request.GetPathID(r)
if err != nil {
render.BadRequest(w)
return
}
err = repos.DeletePath(ctx, repo.ID, pathId)
err = repos.DeletePath(ctx, repo.ID, pathID)
if err != nil {
log.Err(err).Int64("path_id", pathId).
log.Err(err).Int64("path_id", pathID).
Msgf("Failed to delete repo path.")
render.UserfiedErrorOrInternal(w, err)

View File

@ -21,13 +21,11 @@ import (
type repoMoveRequest struct {
Name *string `json:"name"`
SpaceId *int64 `json:"spaceId"`
SpaceID *int64 `json:"spaceId"`
KeepAsAlias bool `json:"keepAsAlias"`
}
/*
* Moves an existing repo.
*/
// HandleMove moves an existing repo.
func HandleMove(guard *guard.Guard, repos store.RepoStore, spaces store.SpaceStore) http.HandlerFunc {
return guard.Repo(
enum.PermissionRepoEdit,
@ -49,8 +47,8 @@ func HandleMove(guard *guard.Guard, repos store.RepoStore, spaces store.SpaceSto
if in.Name == nil {
in.Name = &repo.Name
}
if in.SpaceId == nil {
in.SpaceId = &repo.SpaceId
if in.SpaceID == nil {
in.SpaceID = &repo.SpaceID
}
// convert name to lower case for easy of api use
@ -60,19 +58,22 @@ func HandleMove(guard *guard.Guard, repos store.RepoStore, spaces store.SpaceSto
if err = check.Name(*in.Name); err != nil {
render.UserfiedErrorOrInternal(w, err)
return
} else if *in.SpaceId == repo.SpaceId && *in.Name == repo.Name {
}
if *in.SpaceID == repo.SpaceID && *in.Name == repo.Name {
render.BadRequestError(w, render.ErrNoChange)
return
} else if *in.SpaceId <= 0 {
render.UserfiedErrorOrInternal(w, check.ErrRepositoryRequiresSpaceId)
}
if *in.SpaceID <= 0 {
render.UserfiedErrorOrInternal(w, check.ErrRepositoryRequiresSpaceID)
return
}
// Ensure we have access to the target space (if its a space move)
if *in.SpaceId != repo.SpaceId {
newSpace, err := spaces.Find(ctx, *in.SpaceId)
if *in.SpaceID != repo.SpaceID {
var newSpace *types.Space
newSpace, err = spaces.Find(ctx, *in.SpaceID)
if err != nil {
log.Err(err).Msgf("Failed to get target space with id %d for the move.", *in.SpaceId)
log.Err(err).Msgf("Failed to get target space with id %d for the move.", *in.SpaceID)
render.UserfiedErrorOrInternal(w, err)
return
@ -89,7 +90,7 @@ func HandleMove(guard *guard.Guard, repos store.RepoStore, spaces store.SpaceSto
}
}
res, err := repos.Move(ctx, usr.ID, repo.ID, *in.SpaceId, *in.Name, in.KeepAsAlias)
res, err := repos.Move(ctx, usr.ID, repo.ID, *in.SpaceID, *in.Name, in.KeepAsAlias)
if err != nil {
log.Error().Err(err).Msg("Failed to move the repository.")

View File

@ -57,7 +57,7 @@ func HandleUpdate(guard *guard.Guard, repos store.RepoStore) http.HandlerFunc {
repo.Updated = time.Now().UnixMilli()
// ensure provided values are valid
if err := check.Repo(repo); err != nil {
if err = check.Repo(repo); err != nil {
render.UserfiedErrorOrInternal(w, err)
return
}

View File

@ -23,7 +23,7 @@ import (
type spaceCreateRequest struct {
Name string `json:"name"`
ParentId int64 `json:"parentId"`
ParentID int64 `json:"parentId"`
DisplayName string `json:"displayName"`
Description string `json:"description"`
IsPublic bool `json:"isPublic"`
@ -54,7 +54,7 @@ func HandleCreate(guard *guard.Guard, spaces store.SpaceStore) http.HandlerFunc
* AUTHORIZATION
* Can only be done once we know the parent space
*/
if in.ParentId <= 0 {
if in.ParentID <= 0 {
// TODO: Restrict top level space creation.
if usr == nil {
render.Unauthorized(w)
@ -62,9 +62,10 @@ func HandleCreate(guard *guard.Guard, spaces store.SpaceStore) http.HandlerFunc
}
} else {
// Create is a special case - we need the parent path
parent, err := spaces.Find(ctx, in.ParentId)
var parent *types.Space
parent, err = spaces.Find(ctx, in.ParentID)
if err != nil {
log.Err(err).Msgf("Failed to get space with id '%d'.", in.ParentId)
log.Err(err).Msgf("Failed to get space with id '%d'.", in.ParentID)
render.UserfiedErrorOrInternal(w, err)
return
@ -85,7 +86,7 @@ func HandleCreate(guard *guard.Guard, spaces store.SpaceStore) http.HandlerFunc
// create new space object
space := &types.Space{
Name: strings.ToLower(in.Name),
ParentId: in.ParentId,
ParentID: in.ParentID,
DisplayName: in.DisplayName,
Description: in.Description,
IsPublic: in.IsPublic,
@ -95,12 +96,13 @@ func HandleCreate(guard *guard.Guard, spaces store.SpaceStore) http.HandlerFunc
}
// validate space
if err := check.Space(space); err != nil {
if err = check.Space(space); err != nil {
render.UserfiedErrorOrInternal(w, err)
return
}
// Validate path length (Due to racing conditions we can't be 100% sure on the path here only best effort to have a quick failure)
// Validate path length (Due to racing conditions we can't be 100% sure on the path here only best effort
// to have a quick failure)
path := paths.Concatinate(parentPath, space.Name)
if err = check.Path(path, true); err != nil {
render.UserfiedErrorOrInternal(w, err)

View File

@ -27,15 +27,15 @@ func HandleDeletePath(guard *guard.Guard, spaces store.SpaceStore) http.HandlerF
log := hlog.FromRequest(r)
space, _ := request.SpaceFrom(ctx)
pathId, err := request.GetPathId(r)
pathID, err := request.GetPathID(r)
if err != nil {
render.BadRequest(w)
return
}
err = spaces.DeletePath(ctx, space.ID, pathId)
err = spaces.DeletePath(ctx, space.ID, pathID)
if err != nil {
log.Err(err).Int64("path_id", pathId).
log.Err(err).Int64("path_id", pathID).
Msgf("Failed to delete space path.")
render.UserfiedErrorOrInternal(w, err)

View File

@ -16,9 +16,7 @@ import (
"github.com/rs/zerolog/hlog"
)
/*
* Writes json-encoded list of child spaces in the request body.
*/
// HandleList writes json-encoded list of child spaces in the request body.
func HandleList(guard *guard.Guard, spaces store.SpaceStore) http.HandlerFunc {
return guard.Space(
enum.PermissionSpaceView,
@ -59,7 +57,7 @@ func HandleList(guard *guard.Guard, spaces store.SpaceStore) http.HandlerFunc {
result := make([]*types.Space, 0, len(allSpaces))
for _, cs := range allSpaces {
if !cs.IsPublic {
err := guard.CheckSpace(r, enum.PermissionSpaceView, cs.Path)
err = guard.CheckSpace(r, enum.PermissionSpaceView, cs.Path)
if err != nil {
log.Debug().Err(err).
Msgf("Skip space '%s' in output.", cs.Path)

View File

@ -16,9 +16,7 @@ import (
"github.com/rs/zerolog/hlog"
)
/*
* Writes json-encoded list of repos in the request body.
*/
// HandleListRepos writes json-encoded list of repos in the request body.
func HandleListRepos(guard *guard.Guard, repos store.RepoStore) http.HandlerFunc {
return guard.Space(
enum.PermissionSpaceView,
@ -59,7 +57,7 @@ func HandleListRepos(guard *guard.Guard, repos store.RepoStore) http.HandlerFunc
result := make([]*types.Repository, 0, len(allRepos))
for _, rep := range allRepos {
if !rep.IsPublic {
err := guard.CheckRepo(r, enum.PermissionRepoView, rep.Path)
err = guard.CheckRepo(r, enum.PermissionRepoView, rep.Path)
if err != nil {
log.Debug().Err(err).
Msgf("Skip repo '%s' in output.", rep.Path)

View File

@ -22,13 +22,11 @@ import (
type spaceMoveRequest struct {
Name *string `json:"name"`
ParentId *int64 `json:"parentId"`
ParentID *int64 `json:"parentId"`
KeepAsAlias bool `json:"keepAsAlias"`
}
/*
* Moves an existing space.
*/
// HandleMove moves an existing space.
func HandleMove(guard *guard.Guard, spaces store.SpaceStore) http.HandlerFunc {
return guard.Space(
enum.PermissionSpaceEdit,
@ -50,8 +48,8 @@ func HandleMove(guard *guard.Guard, spaces store.SpaceStore) http.HandlerFunc {
if in.Name == nil {
in.Name = &space.Name
}
if in.ParentId == nil {
in.ParentId = &space.ParentId
if in.ParentID == nil {
in.ParentID = &space.ParentID
}
// convert name to lower case for easy of api use
@ -61,18 +59,19 @@ func HandleMove(guard *guard.Guard, spaces store.SpaceStore) http.HandlerFunc {
if err = check.Name(*in.Name); err != nil {
render.UserfiedErrorOrInternal(w, err)
return
} else if *in.ParentId == space.ParentId && *in.Name == space.Name {
} else if *in.ParentID == space.ParentID && *in.Name == space.Name {
render.BadRequestError(w, render.ErrNoChange)
return
}
// TODO: restrict top level move
// Ensure we can create spaces within the target space (using parent space as scope, similar to create)
if *in.ParentId > 0 && *in.ParentId != space.ParentId {
newParent, err := spaces.Find(ctx, *in.ParentId)
if *in.ParentID > 0 && *in.ParentID != space.ParentID {
var newParent *types.Space
newParent, err = spaces.Find(ctx, *in.ParentID)
if err != nil {
log.Err(err).
Msgf("Failed to get target space with id %d for the move.", *in.ParentId)
Msgf("Failed to get target space with id %d for the move.", *in.ParentID)
render.UserfiedErrorOrInternal(w, err)
return
@ -88,7 +87,8 @@ func HandleMove(guard *guard.Guard, spaces store.SpaceStore) http.HandlerFunc {
}
/*
* Validate path length (Due to racing conditions we can't be 100% sure on the path here only best effort to avoid big transaction failure)
* Validate path length (Due to racing conditions we can't be 100% sure on the path here only best
* effort to avoid big transaction failure)
* Only needed if we actually change the parent (and can skip top level, as we already validate the name)
*/
path := paths.Concatinate(newParent.Path, *in.Name)
@ -98,7 +98,7 @@ func HandleMove(guard *guard.Guard, spaces store.SpaceStore) http.HandlerFunc {
}
}
res, err := spaces.Move(ctx, usr.ID, space.ID, *in.ParentId, *in.Name, in.KeepAsAlias)
res, err := spaces.Move(ctx, usr.ID, space.ID, *in.ParentID, *in.Name, in.KeepAsAlias)
if err != nil {
log.Error().Err(err).Msg("Failed to move the space.")

View File

@ -57,7 +57,7 @@ func HandleUpdate(guard *guard.Guard, spaces store.SpaceStore) http.HandlerFunc
space.Updated = time.Now().UnixMilli()
// ensure provided values are valid
if err := check.Space(space); err != nil {
if err = check.Space(space); err != nil {
render.UserfiedErrorOrInternal(w, err)
return
}

View File

@ -38,7 +38,8 @@ func HandleUpdate(users store.UserStore) http.HandlerFunc {
}
if in.Password != nil {
hash, err := hashPassword([]byte(ptr.ToString(in.Password)), bcrypt.DefaultCost)
var hash []byte
hash, err = hashPassword([]byte(ptr.ToString(in.Password)), bcrypt.DefaultCost)
if err != nil {
log.Err(err).Msg("Failed to hash password.")

View File

@ -57,8 +57,7 @@ func HandleCreate(users store.UserStore) http.HandlerFunc {
Created: time.Now().UnixMilli(),
Updated: time.Now().UnixMilli(),
}
if ok, err := check.User(user); !ok {
if err = check.User(user); err != nil {
log.Debug().Err(err).
Str("email", user.Email).
Msg("invalid user input")

View File

@ -39,7 +39,6 @@ func HandleDelete(users store.UserStore) http.HandlerFunc {
render.UserfiedErrorOrInternal(w, err)
return
}
w.WriteHeader(http.StatusNoContent)

View File

@ -24,7 +24,7 @@ import (
// password at the given cost.
var hashPassword = bcrypt.GenerateFromPassword
// HandleUpdate returns an http.HandlerFunc that processes an http.Request
// HandleUpdate returns a http.HandlerFunc that processes an http.Request
// to update a user account.
func HandleUpdate(users store.UserStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
@ -41,13 +41,14 @@ func HandleUpdate(users store.UserStore) http.HandlerFunc {
}
in := new(types.UserInput)
if err := json.NewDecoder(r.Body).Decode(in); err != nil {
if err = json.NewDecoder(r.Body).Decode(in); err != nil {
render.BadRequestf(w, "Invalid request body: %s.", err)
return
}
if in.Password != nil {
hash, err := hashPassword([]byte(ptr.ToString(in.Password)), bcrypt.DefaultCost)
var hash []byte
hash, err = hashPassword([]byte(ptr.ToString(in.Password)), bcrypt.DefaultCost)
if err != nil {
log.Err(err).
Int64("user_id", user.ID).
@ -74,7 +75,8 @@ func HandleUpdate(users store.UserStore) http.HandlerFunc {
// TODO: why are we overwriting the password twice?
if in.Password != nil {
hash, err := bcrypt.GenerateFromPassword([]byte(ptr.ToString(in.Password)), bcrypt.DefaultCost)
var hash []byte
hash, err = bcrypt.GenerateFromPassword([]byte(ptr.ToString(in.Password)), bcrypt.DefaultCost)
if err != nil {
log.Err(err).
Int64("user_id", user.ID).
@ -86,8 +88,7 @@ func HandleUpdate(users store.UserStore) http.HandlerFunc {
}
user.Password = string(hash)
}
if ok, err := check.User(user); !ok {
if err = check.User(user); err != nil {
log.Debug().Err(err).
Int64("user_id", user.ID).
Str("user_email", user.Email).

View File

@ -43,15 +43,16 @@ func Handler(scheme, host string) func(http.Handler) http.Handler {
// using the X-Forwarded-Proto, if the original request was HTTPS
// and routed through a reverse proxy with SSL termination.
func resolveScheme(r *http.Request) string {
const https = "https"
switch {
case r.URL.Scheme == "https":
return "https"
case r.URL.Scheme == https:
return https
case r.TLS != nil:
return "https"
return https
case strings.HasPrefix(r.Proto, "HTTPS"):
return "https"
case r.Header.Get("X-Forwarded-Proto") == "https":
return "https"
return https
case r.Header.Get("X-Forwarded-Proto") == https:
return https
default:
return "http"
}

View File

@ -16,10 +16,8 @@ import (
"github.com/rs/zerolog/hlog"
)
/*
* Attempt returns an http.HandlerFunc middleware that authenticates
* the http.Request if authentication payload is available.
*/
// Attempt returns an http.HandlerFunc middleware that authenticates
// the http.Request if authentication payload is available.
func Attempt(authenticator authn.Authenticator) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@ -32,11 +30,15 @@ func Attempt(authenticator authn.Authenticator) func(http.Handler) http.Handler
// if there was no auth data in the request - continue as is
next.ServeHTTP(w, r)
return
} else if err != nil {
}
if err != nil {
// for any other error we fail
render.Unauthorized(w)
return
} else if user == nil {
}
if user == nil {
// when err == nil user should never be nil!
log.Error().Msg("User is nil eventhough the authenticator didn't return any error!")

View File

@ -1,19 +1,18 @@
package encode
import (
"fmt"
"net/http"
"net/url"
"strings"
"github.com/rs/zerolog/log"
"github.com/harness/gitness/types"
)
/*
* 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.
* The first prefix that matches the URL.Path will be used during encoding.
*/
// 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
// 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)
@ -21,16 +20,14 @@ func GitPathBefore(h http.HandlerFunc) http.HandlerFunc {
}
}
/*
* 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.
*/
// TerminatedPathBefore 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
changed := false
var changed bool
if r, changed = pathTerminatedWithMarker(r, p, "/+", false); changed {
break
}
@ -40,16 +37,15 @@ func TerminatedPathBefore(prefixes []string, h http.HandlerFunc) http.HandlerFun
}
}
/*
* This function encodes a path followed by a custom marker and returns a request with an updated URL.Path.
* A non-empty prefix can be provided to encode encode only after the prefix.
* It allows our Rest API to handle paths of the form "/spaces/space1/space2/+/authToken"
*
* Examples:
* Prefix: "" Path: "/space1/space2/+" => "/space1%2Fspace2"
* Prefix: "" Path: "/space1/space2.git" => "/space1%2Fspace2"
* Prefix: "/spaces" Path: "/spaces/space1/space2/+/authToken" => "/spaces/space1%2Fspace2/authToken"
*/
// pathTerminatedWithMarker function encodes a path followed by a custom marker and returns a request with an
// updated URL.Path.
// A non-empty prefix can be provided to encode encode only after the prefix.
// It allows our Rest API to handle paths of the form "/spaces/space1/space2/+/authToken"
//
// Examples:
// 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) {
// 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 {
@ -65,14 +61,14 @@ func pathTerminatedWithMarker(r *http.Request, prefix string, marker string, kee
}
// if marker was found - convert to escaped version (skip first character in case path starts with '/')
escapedPath := path[0:1] + strings.Replace(path[1:], types.PathSeparator, "%2F", -1)
escapedPath := path[0:1] + strings.ReplaceAll(path[1:], types.PathSeparator, "%2F")
if keepMarker {
escapedPath += marker
}
updatedSubPath := escapedPath + suffix
// TODO: Proper Logging
fmt.Printf(
log.Debug().Msgf(
"[Encode] prefix: '%s', marker: '%s', original: '%s', updated: '%s'.\n",
prefix,
marker,

View File

@ -28,7 +28,6 @@ type registerRequest struct {
// helper function that constructs the openapi specification
// for the account registration and login endpoints.
func buildAccount(reflector *openapi3.Reflector) {
onLogin := openapi3.Operation{}
onLogin.WithTags("account")
onLogin.WithMapOfAnything(map[string]interface{}{"operationId": "onLogin"})

View File

@ -11,7 +11,6 @@ import (
)
type (
// base request
baseRequest struct {
Account string `query:"accountIdentifier"`
Organization string `query:"orgIdentifier"`
@ -19,45 +18,12 @@ type (
Routing string `query:"routingId"`
}
// base request for pagination
paginationRequest struct {
Page int `query:"page" default:"1"`
Size int `query:"per_page" default:"100"`
}
// TODO: base response for pagination
//paginationResponse struct {
// Total int `header:"x-total"`
// Pagelen int `header:"x-total-pages"`
// Page int `header:"x-page"`
// Size int `header:"x-per-page"`
// Next int `header:"x-next"`
// Prev int `header:"x-prev"`
// Link []string `header:"Link"`
//}
)
// Handler returns an http.HandlerFunc that writes the openapi v3
// specification file to the http.Response body.
// TODO: unused function
//func Handler() http.HandlerFunc {
// spec := Generate()
// yaml, _ := spec.MarshalYAML()
// json, _ := spec.MarshalJSON()
//
// yaml = normalize(yaml)
// json = normalize(json)
//
// return func(w http.ResponseWriter, r *http.Request) {
// switch {
// case strings.HasSuffix(r.URL.Path, ".json"):
// w.Write(json)
// default:
// w.Write(yaml)
// }
// }
//}
// Generate is a helper function that constructs the
// openapi specification object, which can be marshaled
// to json or yaml, as needed.
@ -102,14 +68,3 @@ func Generate() *openapi3.Spec {
return reflector.Spec
}
// helper function normalizes the output to ensure
// automatically-generated names are more user friendly.
// TODO: unused function
//func normalize(data []byte) []byte {
// data = bytes.ReplaceAll(data, []byte("Types"), []byte(""))
// data = bytes.ReplaceAll(data, []byte("Openapi"), []byte(""))
// data = bytes.ReplaceAll(data, []byte("FormData"), []byte(""))
// data = bytes.ReplaceAll(data, []byte("RenderError"), []byte("Error"))
// return data
//}

View File

@ -21,7 +21,6 @@ type currentUserResponse struct {
// helper function that constructs the openapi specification
// for user account resources.
func buildUser(reflector *openapi3.Reflector) {
opFind := openapi3.Operation{}
opFind.WithTags("user")
opFind.WithMapOfAnything(map[string]interface{}{"operationId": "getUser"})

View File

@ -40,7 +40,6 @@ type (
// helper function that constructs the openapi specification
// for user resources.
func buildUsers(reflector *openapi3.Reflector) {
opFind := openapi3.Operation{}
opFind.WithTags("users")
opFind.WithMapOfAnything(map[string]interface{}{"operationId": "getUserEmail"})

View File

@ -35,10 +35,11 @@ var (
// ErrPathTooLong is returned if user action would lead to a path that is too long.
ErrPathTooLong = New("The resource path is too long")
// ErrCyclicHierarchy is returned if the user action would create a cyclic dependency between spaces
ErrCyclicHierarchy = New(("Unable to perform the action as it would lead to a cyclic dependency."))
// ErrCyclicHierarchy is returned if the user action would create a cyclic dependency between spaces.
ErrCyclicHierarchy = New("Unable to perform the action as it would lead to a cyclic dependency.")
// ErrSpaceWithChildsCantBeDeleted is returned if the user is trying to delete a space that still has child resources
// ErrSpaceWithChildsCantBeDeleted is returned if the user is trying to delete a space that
// still has child resources.
ErrSpaceWithChildsCantBeDeleted = New("Space can't be deleted as it still contains child resources.")
)

View File

@ -13,9 +13,9 @@ func RenderResource(w http.ResponseWriter, code int, v interface{}) {
payload := new(wrapper)
payload.Status = "SUCCESS"
payload.Data, _ = json.Marshal(v)
if code > 399 {
if code >= http.StatusBadRequest {
payload.Status = "ERROR"
} else if code > 299 {
} else if code >= http.StatusMultipleChoices {
payload.Status = "FAILURE"
}
render.JSON(w, code, payload)

View File

@ -12,11 +12,13 @@ import (
"os"
"strconv"
"github.com/rs/zerolog/log"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/types/check"
)
// indent the json-encoded API responses
// indent the json-encoded API responses.
var indent bool
func init() {
@ -25,31 +27,29 @@ func init() {
)
}
/*
* UserfiedErrorOrInternal renders the appropriate user facing message for the provided error.
* If the error is unknown, an internal error is rendered.
*/
// UserfiedErrorOrInternal renders the appropriate user facing message for the provided error.
// If the error is unknown, an internal error is rendered.
func UserfiedErrorOrInternal(w http.ResponseWriter, err error) {
if errors.Is(err, check.ErrAny) {
switch {
case errors.Is(err, check.ErrAny):
ErrorObject(w, http.StatusBadRequest, &Error{err.Error()})
} else if errors.Is(err, store.ErrResourceNotFound) {
case errors.Is(err, store.ErrResourceNotFound):
ErrorObject(w, http.StatusNotFound, ErrNotFound)
} else if errors.Is(err, store.ErrDuplicate) {
case errors.Is(err, store.ErrDuplicate):
ErrorObject(w, http.StatusBadRequest, ErrDuplicate)
} else if errors.Is(err, store.ErrPrimaryPathCantBeDeleted) {
case errors.Is(err, store.ErrPrimaryPathCantBeDeleted):
ErrorObject(w, http.StatusBadRequest, ErrPrimaryPathCantBeDeleted)
} else if errors.Is(err, store.ErrPathTooLong) {
case errors.Is(err, store.ErrPathTooLong):
ErrorObject(w, http.StatusBadRequest, ErrPathTooLong)
} else if errors.Is(err, store.ErrNoChangeInRequestedMove) {
case errors.Is(err, store.ErrNoChangeInRequestedMove):
ErrorObject(w, http.StatusBadRequest, ErrNoChange)
} else if errors.Is(err, store.ErrIllegalMoveCyclicHierarchy) {
case errors.Is(err, store.ErrIllegalMoveCyclicHierarchy):
ErrorObject(w, http.StatusBadRequest, ErrCyclicHierarchy)
} else if errors.Is(err, store.ErrSpaceWithChildsCantBeDeleted) {
case errors.Is(err, store.ErrSpaceWithChildsCantBeDeleted):
ErrorObject(w, http.StatusBadRequest, ErrSpaceWithChildsCantBeDeleted)
} else {
default:
// nothing found - render internal error
fmt.Println(err)
log.Err(err)
InternalError(w)
}
}
@ -100,7 +100,7 @@ func ErrorObject(w http.ResponseWriter, code int, err *Error) {
}
// JSON writes the json-encoded value to the response
// with the provides status
// with the provides status.
func JSON(w http.ResponseWriter, code int, v interface{}) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.Header().Set("X-Content-Type-Options", "nosniff")

View File

@ -18,22 +18,21 @@ func pagelen(size, total int) int {
}
}
// max returns the larger of x or y.
// max returns the largest of x or y.
func max(x, y int) int {
if x > y {
return x
} else {
return y
}
return y
}
// max returns the smaller of x or y.
func min(x, y int) int {
if y == 0 {
return x
} else if x < y {
return x
} else {
return y
}
if x < y {
return x
}
return y
}

View File

@ -22,7 +22,7 @@ const (
)
// WithUser returns a copy of parent in which the user
// value is set
// value is set.
func WithUser(parent context.Context, v *types.User) context.Context {
return context.WithValue(parent, userKey, v)
}
@ -34,7 +34,7 @@ func UserFrom(ctx context.Context) (*types.User, bool) {
return v, ok && v != nil
}
// WithSpace returns a copy of parent in which the space value is set
// WithSpace returns a copy of parent in which the space value is set.
func WithSpace(parent context.Context, v *types.Space) context.Context {
return context.WithValue(parent, spaceKey, v)
}
@ -46,7 +46,7 @@ func SpaceFrom(ctx context.Context) (*types.Space, bool) {
return v, ok && v != nil
}
// WithRepo returns a copy of parent in which the repo value is set
// WithRepo returns a copy of parent in which the repo value is set.
func WithRepo(parent context.Context, v *types.Repository) context.Context {
return context.WithValue(parent, repoKey, v)
}

View File

@ -9,16 +9,16 @@ import (
)
const (
PathIdParamName = "pathId"
PathIDParamName = "pathId"
)
func GetPathId(r *http.Request) (int64, error) {
rawId := chi.URLParam(r, PathIdParamName)
if rawId == "" {
return 0, errors.New("Path id parameter not found in request.")
func GetPathID(r *http.Request) (int64, error) {
rawID := chi.URLParam(r, PathIDParamName)
if rawID == "" {
return 0, errors.New("path id parameter not found in request")
}
id, err := strconv.ParseInt(rawId, 10, 64)
id, err := strconv.ParseInt(rawID, 10, 64)
if err != nil {
return 0, err
}

View File

@ -14,7 +14,7 @@ const (
)
var (
ErrRepoReferenceNotFound = errors.New("No repository reference found in request.")
ErrRepoReferenceNotFound = errors.New("no repository reference found in request")
)
func GetRepoRef(r *http.Request) (string, error) {

View File

@ -14,7 +14,7 @@ const (
)
var (
ErrSpaceReferenceNotFound = errors.New("No space reference found in request.")
ErrSpaceReferenceNotFound = errors.New("no space reference found in request")
)
func GetSpaceRef(r *http.Request) (string, error) {

View File

@ -24,12 +24,13 @@ func ParsePage(r *http.Request) int {
// ParseSize extracts the size parameter from the url.
func ParseSize(r *http.Request) int {
const itemsPerPage = 100
s := r.FormValue("per_page")
i, _ := strconv.Atoi(s)
if i == 0 {
i = 100
} else if i > 100 {
i = 100
i = itemsPerPage
} else if i > itemsPerPage {
i = itemsPerPage
}
return i
}
@ -42,7 +43,7 @@ func ParseOrder(r *http.Request) enum.Order {
}
// ParseSort extracts the sort parameter from the url.
func ParseSort(r *http.Request) (s string) {
func ParseSort(r *http.Request) string {
return r.FormValue("sort")
}

View File

@ -12,14 +12,12 @@ import (
)
var (
// An error that is returned if the authorizer doesn't find any data in the request that can be used for auth.
ErrNoAuthData = errors.New("The request doesn't contain any auth data that can be used by the Authorizer.")
// ErrNoAuthData that is returned if the authorizer doesn't find any data in the request that can be used for auth.
ErrNoAuthData = errors.New("the request doesn't contain any auth data that can be used by the Authorizer")
)
/*
* An abstraction of an entity thats responsible for authenticating users
* that are making calls via HTTP.
*/
// Authenticator is abstraction of an entity that's responsible for authenticating users
// that are making calls via HTTP.
type Authenticator interface {
/*
* Tries to authenticate a user if credentials are available.

View File

@ -13,9 +13,7 @@ import (
var _ authn.Authenticator = (*Authenticator)(nil)
/*
* An authenticator that validates access token provided by harness SAAS.
*/
// Authenticator that validates access token provided by harness SAAS.
type Authenticator struct {
// some config to validate jwt
}
@ -24,6 +22,6 @@ func NewAuthenticator() (authn.Authenticator, error) {
return &Authenticator{}, nil
}
func (this *Authenticator) Authenticate(r *http.Request) (*types.User, error) {
func (a *Authenticator) Authenticate(r *http.Request) (*types.User, error) {
return &types.User{}, nil
}

View File

@ -57,7 +57,7 @@ func (a *TokenAuthenticator) Authenticate(r *http.Request) (*types.User, error)
Error().Err(err).
Int64("user", id).
Msg("cannot find user")
return nil, fmt.Errorf("Failed to get user info: %s", err)
return nil, fmt.Errorf("failed to get user info: %w", err)
}
return []byte(user.Salt), nil
})
@ -65,11 +65,11 @@ func (a *TokenAuthenticator) Authenticate(r *http.Request) (*types.User, error)
return nil, err
}
if !parsed.Valid {
return nil, errors.New("Invalid token")
return nil, errors.New("invalid token")
}
if _, ok := parsed.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, errors.New("Invalid token")
return nil, errors.New("invalid token")
}
// this code should be deprecated, since the jwt.ParseWithClaims
@ -78,7 +78,7 @@ func (a *TokenAuthenticator) Authenticate(r *http.Request) (*types.User, error)
if claims, ok := parsed.Claims.(*token.Claims); ok {
if claims.ExpiresAt > 0 {
if time.Now().Unix() > claims.ExpiresAt {
return nil, errors.New("Expired token")
return nil, errors.New("expired token")
}
}
}

View File

@ -8,7 +8,7 @@ import (
"github.com/google/wire"
)
// WireSet provides a wire set for this package
// WireSet provides a wire set for this package.
var WireSet = wire.NewSet(
NewTokenAuthenticator,
)

View File

@ -5,6 +5,7 @@
package authz
import (
"context"
"errors"
"github.com/harness/gitness/types"
@ -12,13 +13,11 @@ import (
)
var (
// An error that is thrown if no permission checks are provided
ErrNoPermissionCheckProvided = errors.New("No permission checks provided")
// ErrNoPermissionCheckProvided is error that is thrown if no permission checks are provided.
ErrNoPermissionCheckProvided = errors.New("no permission checks provided")
)
/*
* An abstraction of an entity responsible for authorizing access to resources.
*/
// Authorizer abstraction of an entity responsible for authorizing access to resources.
type Authorizer interface {
/*
* Checks whether the provided principal has the permission to execute the action on the resource within the scope.
@ -27,14 +26,23 @@ type Authorizer interface {
* (false, nil) - the principal does not have permission to perform the action
* (false, err) - an error occured while performing the permission check and the action should be denied
*/
Check(principalType enum.PrincipalType, principalId string, scope *types.Scope, resource *types.Resource, permission enum.Permission) (bool, error)
Check(ctx context.Context,
principalType enum.PrincipalType,
principalID string,
scope *types.Scope,
resource *types.Resource,
permission enum.Permission) (bool, error)
/*
* Checks whether the provided principal the required permission to execute ALL the requested actions on the resource within the scope.
* Checks whether the provided principal the required permission to execute ALL the requested actions on the
* resource within the scope.
* Returns
* (true, nil) - the principal has permission to perform all the requested actions
* (false, nil) - the principal does not have permission to perform all the actions (at least one is not allowed)
* (false, err) - an error occured while performing the permission check and all actions should be denied
*/
CheckAll(principalType enum.PrincipalType, principalId string, permissionChecks ...types.PermissionCheck) (bool, error)
CheckAll(ctx context.Context,
principalType enum.PrincipalType,
principalID string,
permissionChecks ...types.PermissionCheck) (bool, error)
}

View File

@ -6,6 +6,7 @@ package harness
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"fmt"
@ -30,7 +31,10 @@ 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{InsecureSkipVerify: true},
TLSClientConfig: &tls.Config{
//nolint:gosec // accept any host cert
InsecureSkipVerify: true,
},
}
client := &http.Client{Transport: tr}
@ -41,16 +45,22 @@ func NewAuthorizer(aclEndpoint, authToken string) (authz.Authorizer, error) {
}, nil
}
func (a *Authorizer) Check(principalType enum.PrincipalType, principalId string, scope *types.Scope, resource *types.Resource, permission enum.Permission) (bool, error) {
return a.CheckAll(principalType, principalId, types.PermissionCheck{Scope: *scope, Resource: *resource, Permission: permission})
func (a *Authorizer) Check(ctx context.Context, principalType enum.PrincipalType, principalID string,
scope *types.Scope, resource *types.Resource, permission enum.Permission) (bool, error) {
return a.CheckAll(ctx, principalType, principalID, types.PermissionCheck{
Scope: *scope,
Resource: *resource,
Permission: permission,
})
}
func (a *Authorizer) CheckAll(principalType enum.PrincipalType, principalId string, permissionChecks ...types.PermissionCheck) (bool, error) {
func (a *Authorizer) CheckAll(ctx context.Context, principalType enum.PrincipalType, principalID string,
permissionChecks ...types.PermissionCheck) (bool, error) {
if len(permissionChecks) == 0 {
return false, authz.ErrNoPermissionCheckProvided
}
requestDto, err := createAclRequest(principalType, principalId, permissionChecks)
requestDto, err := createACLRequest(principalType, principalID, permissionChecks)
if err != nil {
return false, err
}
@ -61,7 +71,7 @@ func (a *Authorizer) CheckAll(principalType enum.PrincipalType, principalId stri
// TODO: accountId might be different!
url := a.aclEndpoint + "?routingId=" + requestDto.Permissions[0].ResourceScope.AccountIdentifier
httpRequest, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(byt))
httpRequest, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(byt))
if err != nil {
return false, err
}
@ -71,16 +81,19 @@ func (a *Authorizer) CheckAll(principalType enum.PrincipalType, principalId stri
"Authorization": []string{"Bearer " + a.authToken},
}
httpResponse, err := a.client.Do(httpRequest)
response, err := a.client.Do(httpRequest)
if response != nil {
defer response.Body.Close()
}
if err != nil {
return false, err
}
if httpResponse.StatusCode != 200 {
return false, fmt.Errorf("Got unexpected status code '%d' - assume unauthorized.", httpResponse.StatusCode)
if response.StatusCode != http.StatusOK {
return false, fmt.Errorf("got unexpected status code '%d' - assume unauthorized", response.StatusCode)
}
bodyByte, err := ioutil.ReadAll(httpResponse.Body)
bodyByte, err := ioutil.ReadAll(response.Body)
if err != nil {
return false, err
}
@ -91,25 +104,23 @@ func (a *Authorizer) CheckAll(principalType enum.PrincipalType, principalId stri
return false, err
}
return checkAclResponse(permissionChecks, responseDto)
return checkACLResponse(permissionChecks, responseDto)
}
func createAclRequest(principalType enum.PrincipalType, principalId string, permissionChecks []types.PermissionCheck) (*aclRequest, error) {
func createACLRequest(principalType enum.PrincipalType, principalID string,
permissionChecks []types.PermissionCheck) (*aclRequest, error) {
// Generate ACL req
req := aclRequest{
Permissions: []aclPermission{},
Principal: aclPrincipal{
PrincipalIdentifier: principalId,
PrincipalIdentifier: principalID,
PrincipalType: string(principalType),
},
}
// map all permissionchecks to ACL permission checks
for _, c := range permissionChecks {
mappedPermission, err := mapPermission(c.Permission)
if err != nil {
return nil, err
}
mappedPermission := mapPermission(c.Permission)
mappedResourceScope, err := mapScope(c.Scope)
if err != nil {
return nil, err
@ -126,7 +137,7 @@ func createAclRequest(principalType enum.PrincipalType, principalId string, perm
return &req, nil
}
func checkAclResponse(permissionChecks []types.PermissionCheck, responseDto aclResponse) (bool, error) {
func checkACLResponse(permissionChecks []types.PermissionCheck, responseDto aclResponse) (bool, error) {
/*
* We are assuming two things:
* - All permission checks were made for the same principal.
@ -150,7 +161,7 @@ func checkAclResponse(permissionChecks []types.PermissionCheck, responseDto aclR
}
if !permissionPermitted {
return false, fmt.Errorf("Permission '%s' is not permitted according to ACL (correlationId: '%s')",
return false, fmt.Errorf("permission '%s' is not permitted according to ACL (correlationId: '%s')",
check.Permission,
responseDto.CorrelationID)
}
@ -160,7 +171,6 @@ func checkAclResponse(permissionChecks []types.PermissionCheck, responseDto aclR
}
func mapScope(scope types.Scope) (*aclResourceScope, error) {
/*
* ASSUMPTION:
* Harness embeded structure is mapped to the following scm space:
@ -177,26 +187,34 @@ func mapScope(scope types.Scope) (*aclResourceScope, error) {
* 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) > 3 {
return nil, fmt.Errorf("Unable to convert '%s' to harness resource scope (expected {Account}/{Organization}/{Project} or a sub scope).", 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) > 0 {
aclScope.AccountIdentifier = harnessIdentifiers[0]
if len(harnessIdentifiers) > accIndex {
aclScope.AccountIdentifier = harnessIdentifiers[accIndex]
}
if len(harnessIdentifiers) > 1 {
aclScope.OrgIdentifier = harnessIdentifiers[1]
if len(harnessIdentifiers) > orgIndex {
aclScope.OrgIdentifier = harnessIdentifiers[orgIndex]
}
if len(harnessIdentifiers) > 2 {
aclScope.ProjectIdentifier = harnessIdentifiers[2]
if len(harnessIdentifiers) > projectIndex {
aclScope.ProjectIdentifier = harnessIdentifiers[projectIndex]
}
return aclScope, nil
}
func mapPermission(permission enum.Permission) (string, error) {
func mapPermission(permission enum.Permission) string {
// harness has multiple modules - add scm prefix
return "scm_" + string(permission), nil
return "scm_" + string(permission)
}

View File

@ -5,7 +5,9 @@
package authz
import (
"fmt"
"context"
"github.com/rs/zerolog/log"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
@ -22,11 +24,12 @@ func NewUnsafeAuthorizer() Authorizer {
return &UnsafeAuthorizer{}
}
func (a *UnsafeAuthorizer) Check(principalType enum.PrincipalType, principalId string, scope *types.Scope, resource *types.Resource, permission enum.Permission) (bool, error) {
fmt.Printf(
func (a *UnsafeAuthorizer) Check(ctx context.Context, principalType enum.PrincipalType, principalID string,
scope *types.Scope, resource *types.Resource, permission enum.Permission) (bool, error) {
log.Debug().Msgf(
"[Authz] %s '%s' requests %s for %s '%s' in scope %v\n",
principalType,
principalId,
principalID,
permission,
resource.Type,
resource.Name,
@ -35,9 +38,10 @@ func (a *UnsafeAuthorizer) Check(principalType enum.PrincipalType, principalId s
return true, nil
}
func (a *UnsafeAuthorizer) CheckAll(principalType enum.PrincipalType, principalId string, permissionChecks ...types.PermissionCheck) (bool, error) {
func (a *UnsafeAuthorizer) CheckAll(ctx context.Context, principalType enum.PrincipalType, principalID string,
permissionChecks ...types.PermissionCheck) (bool, error) {
for _, p := range permissionChecks {
if _, err := a.Check(principalType, principalId, &p.Scope, &p.Resource, p.Permission); err != nil {
if _, err := a.Check(ctx, principalType, principalID, &p.Scope, &p.Resource, p.Permission); err != nil {
return false, err
}
}

View File

@ -8,7 +8,7 @@ import (
"github.com/google/wire"
)
// WireSet provides a wire set for this package
// WireSet provides a wire set for this package.
var WireSet = wire.NewSet(
NewUnsafeAuthorizer,
)

View File

@ -23,7 +23,8 @@ func NewNightly() *Nightly {
// Run runs the purge sub-routine.
func (n *Nightly) Run(ctx context.Context) {
ticker := time.NewTicker(time.Hour * 24)
const hoursPerDay = 24
ticker := time.NewTicker(hoursPerDay * time.Hour)
logger := log.Ctx(ctx)
for {
select {

View File

@ -6,5 +6,5 @@ package cron
import "github.com/google/wire"
// WireSet provides a wire set for this package
// WireSet provides a wire set for this package.
var WireSet = wire.NewSet(NewNightly)

View File

@ -12,13 +12,11 @@ import (
)
var (
ErrPathEmpty = errors.New("Path is empty.")
ErrPathEmpty = errors.New("path is empty")
)
/*
* Splits a path into its parent path and the leaf name.
* e.g. /space1/space2/space3 -> (/space1/space2, space3, nil)
*/
// Disect splits a path into its parent path and the leaf name
// e.g. /space1/space2/space3 -> (/space1/space2, space3, nil).
func Disect(path string) (string, string, error) {
if path == "" {
return "", "", ErrPathEmpty

View File

@ -1,9 +1,12 @@
package router
import (
"context"
"fmt"
"net/http"
"github.com/harness/gitness/types"
"github.com/harness/gitness/internal/api/guard"
"github.com/harness/gitness/internal/api/handler/account"
handler_repo "github.com/harness/gitness/internal/api/handler/repo"
@ -31,22 +34,21 @@ import (
* Mounts the Rest API Router under mountPath (path has to end with ).
* The handler is wrapped within a layer that handles encoding terminated Paths.
*/
func newApiHandler(
func newAPIHandler(
mountPath string,
systemStore store.SystemStore,
userStore store.UserStore,
spaceStore store.SpaceStore,
repoStore store.RepoStore,
authenticator authn.Authenticator,
authorizer authz.Authorizer) (http.Handler, error) {
config := systemStore.Config(nocontext)
guard := guard.New(authorizer)
authorizer authz.Authorizer) http.Handler {
//
config := systemStore.Config(context.Background())
g := guard.New(authorizer)
// 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)
@ -59,23 +61,13 @@ func newApiHandler(
r.Use(accesslog.HlogHandler())
// configure cors middleware
cors := cors.New(
cors.Options{
AllowedOrigins: config.Cors.AllowedOrigins,
AllowedMethods: config.Cors.AllowedMethods,
AllowedHeaders: config.Cors.AllowedHeaders,
ExposedHeaders: config.Cors.ExposedHeaders,
AllowCredentials: config.Cors.AllowCredentials,
MaxAge: config.Cors.MaxAge,
},
)
r.Use(cors.Handler)
r.Use(corsHandler(config))
// for now always attempt auth - enforced per operation
r.Use(middleware_authn.Attempt(authenticator))
r.Route("/v1", func(r chi.Router) {
setupRoutesV1(r, systemStore, userStore, spaceStore, repoStore, authenticator, guard)
setupRoutesV1(r, systemStore, userStore, spaceStore, repoStore, authenticator, g)
})
})
@ -85,7 +77,20 @@ func newApiHandler(
mountPath + "/v1/repos",
}
return encode.TerminatedPathBefore(terminatedPathPrefixes, r.ServeHTTP), nil
return encode.TerminatedPathBefore(terminatedPathPrefixes, r.ServeHTTP)
}
func corsHandler(config *types.Config) func(http.Handler) http.Handler {
return cors.New(
cors.Options{
AllowedOrigins: config.Cors.AllowedOrigins,
AllowedMethods: config.Cors.AllowedMethods,
AllowedHeaders: config.Cors.AllowedHeaders,
ExposedHeaders: config.Cors.ExposedHeaders,
AllowCredentials: config.Cors.AllowCredentials,
MaxAge: config.Cors.MaxAge,
},
).Handler
}
func setupRoutesV1(
@ -94,9 +99,8 @@ func setupRoutesV1(
userStore store.UserStore,
spaceStore store.SpaceStore,
repoStore store.RepoStore,
authenticator authn.Authenticator,
_ authn.Authenticator,
guard *guard.Guard) {
// SPACES
r.Route("/spaces", func(r chi.Router) {
// Create takes path and parentId via body, not uri
@ -121,7 +125,7 @@ func setupRoutesV1(
r.Post("/", handler_space.HandleCreatePath(guard, spaceStore))
// per path operations
r.Route(fmt.Sprintf("/{%s}", request.PathIdParamName), func(r chi.Router) {
r.Route(fmt.Sprintf("/{%s}", request.PathIDParamName), func(r chi.Router) {
r.Delete("/", handler_space.HandleDeletePath(guard, spaceStore))
})
})
@ -150,7 +154,7 @@ func setupRoutesV1(
r.Post("/", handler_repo.HandleCreatePath(guard, repoStore))
// per path operations
r.Route(fmt.Sprintf("/{%s}", request.PathIdParamName), func(r chi.Router) {
r.Route(fmt.Sprintf("/{%s}", request.PathIDParamName), func(r chi.Router) {
r.Delete("/", handler_repo.HandleDeletePath(guard, repoStore))
})
})

View File

@ -27,19 +27,16 @@ import (
*/
func newGitHandler(
mountPath string,
systemStore store.SystemStore,
userStore store.UserStore,
spaceStore store.SpaceStore,
_ store.SystemStore,
_ store.UserStore,
_ store.SpaceStore,
repoStore store.RepoStore,
authenticator authn.Authenticator,
authorizer authz.Authorizer) (http.Handler, error) {
authorizer authz.Authorizer) http.Handler {
guard := guard.New(authorizer)
// 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)
@ -68,9 +65,9 @@ func newGitHandler(
// 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)
@ -85,7 +82,7 @@ func newGitHandler(
})
})
return encode.GitPathBefore(r.ServeHTTP), nil
return encode.GitPathBefore(r.ServeHTTP)
}
func stubGitHandler(w http.ResponseWriter, r *http.Request) {

View File

@ -7,7 +7,6 @@
package router
import (
"context"
"net/http"
"strings"
@ -21,9 +20,6 @@ const (
gitUserAgentPrefix = "git/"
)
// empty context
var nocontext = context.Background()
type Router struct {
api http.Handler
git http.Handler
@ -40,18 +36,9 @@ func New(
authenticator authn.Authenticator,
authorizer authz.Authorizer,
) (http.Handler, error) {
api, err := newApiHandler(restMount, systemStore, userStore, spaceStore, repoStore, authenticator, authorizer)
if err != nil {
return nil, err
}
git, err := newGitHandler("/", systemStore, userStore, spaceStore, repoStore, authenticator, authorizer)
if err != nil {
return nil, err
}
web, err := newWebHandler("/", systemStore)
if err != nil {
return nil, err
}
api := newAPIHandler(restMount, systemStore, userStore, spaceStore, repoStore, authenticator, authorizer)
git := newGitHandler("/", systemStore, userStore, spaceStore, repoStore, authenticator, authorizer)
web := newWebHandler("/", systemStore)
return &Router{
api: api,
@ -61,7 +48,6 @@ func New(
}
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
/*
* 1. GIT
*

View File

@ -15,14 +15,14 @@ func TestTokenGate(t *testing.T) {
// this unit test ensures routes that require pipeline access
// return a 403 forbidden if the user does not have acess
// to the pipeline
// to the pipeline.
func TestPipelineGate(t *testing.T) {
t.Skip()
}
// this unit test ensures routes that require system access
// return a 403 forbidden if the user does not have acess
// to the pipeline
// to the pipeline.
func TestSystemGate(t *testing.T) {
t.Skip()
}

View File

@ -1,6 +1,7 @@
package router
import (
"context"
"net/http"
"github.com/harness/gitness/internal/api/middleware/encode"
@ -18,14 +19,13 @@ import (
*/
func newWebHandler(
mountPath string,
systemStore store.SystemStore) (http.Handler, error) {
config := systemStore.Config(nocontext)
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).
@ -62,5 +62,5 @@ func newWebHandler(
})
// web doesn't have any prefixes for terminated paths
return encode.TerminatedPathBefore([]string{""}, r.ServeHTTP), nil
return encode.TerminatedPathBefore([]string{""}, r.ServeHTTP)
}

View File

@ -8,5 +8,5 @@ import (
"github.com/google/wire"
)
// WireSet provides a wire set for this package
// WireSet provides a wire set for this package.
var WireSet = wire.NewSet(New)

View File

@ -9,6 +9,7 @@ import (
"context"
"crypto/tls"
"net/http"
"time"
"golang.org/x/crypto/acme/autocert"
"golang.org/x/sync/errgroup"
@ -37,8 +38,9 @@ func (s *Server) ListenAndServe(ctx context.Context) error {
func (s *Server) listenAndServe(ctx context.Context) error {
var g errgroup.Group
s1 := &http.Server{
Addr: s.Addr,
Handler: s.Handler,
Addr: s.Addr,
ReadHeaderTimeout: 2 * time.Second,
Handler: s.Handler,
}
g.Go(func() error {
<-ctx.Done()
@ -53,12 +55,14 @@ func (s *Server) listenAndServe(ctx context.Context) error {
func (s *Server) listenAndServeTLS(ctx context.Context) error {
var g errgroup.Group
s1 := &http.Server{
Addr: ":http",
Handler: http.HandlerFunc(redirect),
Addr: ":http",
ReadHeaderTimeout: 2 * time.Second,
Handler: http.HandlerFunc(redirect),
}
s2 := &http.Server{
Addr: ":https",
Handler: s.Handler,
Addr: ":https",
ReadHeaderTimeout: 2 * time.Second,
Handler: s.Handler,
}
g.Go(func() error {
return s1.ListenAndServe()
@ -90,13 +94,16 @@ func (s Server) listenAndServeAcme(ctx context.Context) error {
HostPolicy: autocert.HostWhitelist(s.Host),
}
s1 := &http.Server{
Addr: ":http",
Handler: m.HTTPHandler(nil),
Addr: ":http",
ReadHeaderTimeout: 2 * time.Second,
Handler: m.HTTPHandler(nil),
}
s2 := &http.Server{
Addr: ":https",
Handler: s.Handler,
Addr: ":https",
Handler: s.Handler,
ReadHeaderTimeout: 2 * time.Second,
TLSConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
GetCertificate: m.GetCertificate,
NextProtos: []string{"h2", "http/1.1"},
},

View File

@ -12,10 +12,10 @@ import (
"github.com/google/wire"
)
// WireSet provides a wire set for this package
// WireSet provides a wire set for this package.
var WireSet = wire.NewSet(ProvideServer)
// ProvideServer provides a server instance
// ProvideServer provides a server instance.
func ProvideServer(config *types.Config, handler http.Handler) *Server {
return &Server{
Acme: config.Server.Acme.Enabled,

View File

@ -15,7 +15,7 @@ import (
"github.com/rs/zerolog/log"
)
// background context
// noContext is simple background context.
var noContext = context.Background()
//go:embed postgres/*.sql

View File

@ -18,7 +18,7 @@ import (
"github.com/rs/zerolog/log"
)
// Creates a new alias path (Don't call this for new path creation!)
// CreateAliasPath a new alias path (Don't call this for new path creation!)
func CreateAliasPath(ctx context.Context, db *sqlx.DB, path *types.Path) error {
if !path.IsAlias {
return store.ErrAliasPathRequired
@ -32,19 +32,18 @@ func CreateAliasPath(ctx context.Context, db *sqlx.DB, path *types.Path) error {
query, arg, err := db.BindNamed(pathInsert, path)
if err != nil {
return processSqlErrorf(err, "Failed to bind path object")
return processSQLErrorf(err, "Failed to bind path object")
}
if err = db.QueryRowContext(ctx, query, arg...).Scan(&path.ID); err != nil {
return processSqlErrorf(err, "Insert query failed")
return processSQLErrorf(err, "Insert query failed")
}
return nil
}
// Creates a new path as part of a transaction
// CreatePathTx creates a new path as part of a transaction.
func CreatePathTx(ctx context.Context, db *sqlx.DB, tx *sqlx.Tx, path *types.Path) error {
// ensure path length is okay
if check.PathTooLong(path.Value, path.TargetType == enum.PathTargetTypeSpace) {
log.Warn().Msgf("Path '%s' is too long.", path.Value)
@ -53,7 +52,7 @@ func CreatePathTx(ctx context.Context, db *sqlx.DB, tx *sqlx.Tx, path *types.Pat
// In case it's not an alias, ensure there are no duplicates
if !path.IsAlias {
if cnt, err := CountPathsTx(ctx, tx, path.TargetType, path.TargetId); err != nil {
if cnt, err := CountPathsTx(ctx, tx, path.TargetType, path.TargetID); err != nil {
return err
} else if cnt > 0 {
return store.ErrPrimaryPathAlreadyExists
@ -62,11 +61,11 @@ func CreatePathTx(ctx context.Context, db *sqlx.DB, tx *sqlx.Tx, path *types.Pat
query, arg, err := db.BindNamed(pathInsert, path)
if err != nil {
return processSqlErrorf(err, "Failed to bind path object")
return processSQLErrorf(err, "Failed to bind path object")
}
if err = tx.QueryRowContext(ctx, query, arg...).Scan(&path.ID); err != nil {
return processSqlErrorf(err, "Insert query failed")
return processSQLErrorf(err, "Insert query failed")
}
return nil
@ -76,7 +75,7 @@ func CountPrimaryChildPathsTx(ctx context.Context, tx *sqlx.Tx, prefix string) (
var count int64
err := tx.QueryRowContext(ctx, pathCountPrimaryForPrefix, paths.Concatinate(prefix, "%")).Scan(&count)
if err != nil {
return 0, processSqlErrorf(err, "Count query failed")
return 0, processSQLErrorf(err, "Count query failed")
}
return count, nil
}
@ -85,13 +84,13 @@ func ListPrimaryChildPathsTx(ctx context.Context, tx *sqlx.Tx, prefix string) ([
childs := []*types.Path{}
if err := tx.SelectContext(ctx, &childs, pathSelectPrimaryForPrefix, paths.Concatinate(prefix, "%")); err != nil {
return nil, processSqlErrorf(err, "Select query failed")
return nil, processSQLErrorf(err, "Select query failed")
}
return childs, nil
}
// Replaces the path for a target as part of a transaction - keeps the existing as alias if requested.
// ReplacePathTx replace the path for a target as part of a transaction - keeps the existing as alias if requested.
func ReplacePathTx(ctx context.Context, db *sqlx.DB, tx *sqlx.Tx, path *types.Path, keepAsAlias bool) error {
if path.IsAlias {
@ -106,21 +105,21 @@ func ReplacePathTx(ctx context.Context, db *sqlx.DB, tx *sqlx.Tx, path *types.Pa
// existing is always non-alias (as query filters for IsAlias=0)
existing := new(types.Path)
err := tx.GetContext(ctx, existing, pathSelectPrimaryForTarget, string(path.TargetType), fmt.Sprint(path.TargetId))
err := tx.GetContext(ctx, existing, pathSelectPrimaryForTarget, string(path.TargetType), fmt.Sprint(path.TargetID))
if err != nil {
return processSqlErrorf(err, "Failed to get the existing primary path")
return processSQLErrorf(err, "Failed to get the existing primary path")
}
// Only look for childs if the type can have childs
// Only look for children if the type can have children
if path.TargetType == enum.PathTargetTypeSpace {
var childPaths []*types.Path
// get all primary paths that start with the current path before updating (or we can run into recursion)
childs, err := ListPrimaryChildPathsTx(ctx, tx, existing.Value)
childPaths, err = ListPrimaryChildPathsTx(ctx, tx, existing.Value)
if err != nil {
return errors.Wrapf(err, "Failed to get primary child paths for '%s'", existing.Value)
}
for _, child := range childs {
for _, child := range childPaths {
// create path with updated path (child already is primary)
updatedChild := new(types.Path)
*updatedChild = *child
@ -136,24 +135,28 @@ func ReplacePathTx(ctx context.Context, db *sqlx.DB, tx *sqlx.Tx, path *types.Pa
return store.ErrPathTooLong
}
query, arg, err := db.BindNamed(pathInsert, updatedChild)
var (
query string
args []interface{}
)
query, args, err = db.BindNamed(pathInsert, updatedChild)
if err != nil {
return processSqlErrorf(err, "Failed to bind path object")
return processSQLErrorf(err, "Failed to bind path object")
}
_, err = tx.ExecContext(ctx, query, arg...)
if err != nil {
return processSqlErrorf(err, "Failed to create new primary child path '%s'", updatedChild.Value)
if _, err = tx.ExecContext(ctx, query, args...); err != nil {
return processSQLErrorf(err, "Failed to create new primary child path '%s'", updatedChild.Value)
}
// make current child an alias or delete it
query = pathDeleteID
if keepAsAlias {
_, err = tx.ExecContext(ctx, pathMakeAlias, child.ID)
} else {
_, err = tx.ExecContext(ctx, pathDeleteId, child.ID)
query = pathMakeAlias
}
if err != nil {
return processSqlErrorf(err, "Failed to mark existing child path '%s' as alias", updatedChild.Value)
if _, err = tx.ExecContext(ctx, query, child.ID); err != nil {
return processSQLErrorf(err, "Failed to mark existing child path '%s' as alias",
updatedChild.Value)
}
}
}
@ -161,33 +164,32 @@ func ReplacePathTx(ctx context.Context, db *sqlx.DB, tx *sqlx.Tx, path *types.Pa
// insert the new Path
query, arg, err := db.BindNamed(pathInsert, path)
if err != nil {
return processSqlErrorf(err, "Failed to bind path object")
return processSQLErrorf(err, "Failed to bind path object")
}
_, err = tx.ExecContext(ctx, query, arg...)
if err != nil {
return processSqlErrorf(err, "Failed to create new primary path '%s'", path.Value)
return processSQLErrorf(err, "Failed to create new primary path '%s'", path.Value)
}
// make existing an alias
query = pathDeleteID
if keepAsAlias {
_, err = tx.ExecContext(ctx, pathMakeAlias, existing.ID)
} else {
_, err = tx.ExecContext(ctx, pathDeleteId, existing.ID)
query = pathMakeAlias
}
if err != nil {
return processSqlErrorf(err, "Failed to mark existing path '%s' as alias", existing.Value)
if _, err = tx.ExecContext(ctx, query, existing.ID); err != nil {
return processSQLErrorf(err, "Failed to mark existing path '%s' as alias", existing.Value)
}
return nil
}
// Finds the primary path for a target.
func FindPathTx(ctx context.Context, tx *sqlx.Tx, targetType enum.PathTargetType, targetId int64) (*types.Path, error) {
// FindPathTx finds the primary path for a target.
func FindPathTx(ctx context.Context, tx *sqlx.Tx, targetType enum.PathTargetType, targetID int64) (*types.Path, error) {
dst := new(types.Path)
err := tx.GetContext(ctx, dst, pathSelectPrimaryForTarget, string(targetType), fmt.Sprint(targetId))
err := tx.GetContext(ctx, dst, pathSelectPrimaryForTarget, string(targetType), fmt.Sprint(targetID))
if err != nil {
return nil, processSqlErrorf(err, "Select query failed")
return nil, processSQLErrorf(err, "Select query failed")
}
return dst, nil
@ -197,7 +199,7 @@ func FindPathTx(ctx context.Context, tx *sqlx.Tx, targetType enum.PathTargetType
func DeletePath(ctx context.Context, db *sqlx.DB, id int64) error {
tx, err := db.BeginTxx(ctx, nil)
if err != nil {
return processSqlErrorf(err, "Failed to start a new transaction")
return processSQLErrorf(err, "Failed to start a new transaction")
}
defer func(tx *sqlx.Tx) {
_ = tx.Rollback()
@ -205,62 +207,65 @@ func DeletePath(ctx context.Context, db *sqlx.DB, id int64) error {
// ensure path is an alias
dst := new(types.Path)
if err = tx.GetContext(ctx, dst, pathSelectId, id); err != nil {
return processSqlErrorf(err, "Failed to find path with id %d", id)
if err = tx.GetContext(ctx, dst, pathSelectID, id); err != nil {
return processSQLErrorf(err, "Failed to find path with id %d", id)
} else if !dst.IsAlias {
return store.ErrPrimaryPathCantBeDeleted
}
// delete the path
if _, err = tx.ExecContext(ctx, pathDeleteId, id); err != nil {
return processSqlErrorf(err, "Delete query failed")
if _, err = tx.ExecContext(ctx, pathDeleteID, id); err != nil {
return processSQLErrorf(err, "Delete query failed")
}
if err = tx.Commit(); err != nil {
return processSqlErrorf(err, "Failed to commit transaction")
return processSQLErrorf(err, "Failed to commit transaction")
}
return nil
}
// Deletes all paths for a target as part of a transaction.
func DeleteAllPaths(ctx context.Context, tx *sqlx.Tx, targetType enum.PathTargetType, targetId int64) error {
func DeleteAllPaths(ctx context.Context, tx *sqlx.Tx, targetType enum.PathTargetType, targetID int64) error {
// delete all entries for the target
if _, err := tx.ExecContext(ctx, pathDeleteTarget, string(targetType), fmt.Sprint(targetId)); err != nil {
return processSqlErrorf(err, "Query for deleting all pahts failed")
if _, err := tx.ExecContext(ctx, pathDeleteTarget, string(targetType), fmt.Sprint(targetID)); err != nil {
return processSQLErrorf(err, "Query for deleting all pahts failed")
}
return nil
}
// Lists all paths for a target.
func ListPaths(ctx context.Context, db *sqlx.DB, targetType enum.PathTargetType, targetId int64, opts *types.PathFilter) ([]*types.Path, error) {
func ListPaths(ctx context.Context, db *sqlx.DB, targetType enum.PathTargetType, targetID int64,
opts *types.PathFilter) ([]*types.Path, error) {
dst := []*types.Path{}
// if the user does not provide any customer filter
// or sorting we use the default select statement.
if opts.Sort == enum.PathAttrNone {
err := db.SelectContext(ctx, &dst, pathSelect, string(targetType), fmt.Sprint(targetId), limit(opts.Size), offset(opts.Page, opts.Size))
err := db.SelectContext(ctx, &dst, pathSelect, string(targetType), fmt.Sprint(targetID), limit(opts.Size),
offset(opts.Page, opts.Size))
if err != nil {
return nil, processSqlErrorf(err, "Default select query failed")
return nil, processSQLErrorf(err, "Default select query failed")
}
return dst, nil
}
// else we construct the sql statement.
stmt := builder.Select("*").From("paths").Where("path_targetType = $1 AND path_targetId = $2", string(targetType), fmt.Sprint(targetId))
stmt := builder.Select("*").From("paths").Where("path_targetType = $1 AND path_targetId = $2",
string(targetType), fmt.Sprint(targetID))
stmt = stmt.Limit(uint64(limit(opts.Size)))
stmt = stmt.Offset(uint64(offset(opts.Page, opts.Size)))
switch opts.Sort {
case enum.PathAttrCreated:
// NOTE: string concatination is safe because the
// NOTE: string concatenation is safe because the
// order attribute is an enum and is not user-defined,
// and is therefore not subject to injection attacks.
stmt = stmt.OrderBy("path_created " + opts.Order.String())
case enum.PathAttrUpdated:
stmt = stmt.OrderBy("path_updated " + opts.Order.String())
case enum.PathAttrId:
case enum.PathAttrID:
stmt = stmt.OrderBy("path_id " + opts.Order.String())
case enum.PathAttrPath:
stmt = stmt.OrderBy("path_value" + opts.Order.String())
@ -272,28 +277,18 @@ func ListPaths(ctx context.Context, db *sqlx.DB, targetType enum.PathTargetType,
}
if err = db.SelectContext(ctx, &dst, sql); err != nil {
return nil, processSqlErrorf(err, "Customer select query failed")
return nil, processSQLErrorf(err, "Customer select query failed")
}
return dst, nil
}
// Coutn paths for a target.
func CountPaths(ctx context.Context, db *sqlx.DB, targetType enum.PathTargetType, targetId int64) (int64, error) {
// CountPathsTx Count paths for a target as part of a transaction.
func CountPathsTx(ctx context.Context, tx *sqlx.Tx, targetType enum.PathTargetType, targetID int64) (int64, error) {
var count int64
err := db.QueryRowContext(ctx, pathCount, string(targetType), fmt.Sprint(targetId)).Scan(&count)
err := tx.QueryRowContext(ctx, pathCount, string(targetType), fmt.Sprint(targetID)).Scan(&count)
if err != nil {
return 0, processSqlErrorf(err, "Query failed")
}
return count, nil
}
// Count paths for a target as part of a transaction.
func CountPathsTx(ctx context.Context, tx *sqlx.Tx, targetType enum.PathTargetType, targetId int64) (int64, error) {
var count int64
err := tx.QueryRowContext(ctx, pathCount, string(targetType), fmt.Sprint(targetId)).Scan(&count)
if err != nil {
return 0, processSqlErrorf(err, "Query failed")
return 0, processSQLErrorf(err, "Query failed")
}
return count, nil
}
@ -316,7 +311,7 @@ ORDER BY path_isAlias DESC, path_value ASC
LIMIT $3 OFFSET $4
`
// there's only one entry with a given target & targetId for isAlias -- false
// there's only one entry with a given target & targetId for isAlias -- false.
const pathSelectPrimaryForTarget = pathBase + `
WHERE path_targetType = $1 AND path_targetId = $2 AND path_isAlias = 0
`
@ -357,11 +352,11 @@ INSERT INTO paths (
) RETURNING path_id
`
const pathSelectId = pathBase + `
const pathSelectID = pathBase + `
WHERE path_id = $1
`
const pathDeleteId = `
const pathDeleteID = `
DELETE FROM paths
WHERE path_id = $1
`

View File

@ -33,8 +33,8 @@ type RepoStore struct {
// Finds the repo by id.
func (s *RepoStore) Find(ctx context.Context, id int64) (*types.Repository, error) {
dst := new(types.Repository)
if err := s.db.GetContext(ctx, dst, repoSelectById, id); err != nil {
return nil, processSqlErrorf(err, "Select query failed")
if err := s.db.GetContext(ctx, dst, repoSelectByID, id); err != nil {
return nil, processSQLErrorf(err, "Select query failed")
}
return dst, nil
}
@ -43,16 +43,16 @@ func (s *RepoStore) Find(ctx context.Context, id int64) (*types.Repository, erro
func (s *RepoStore) FindByPath(ctx context.Context, path string) (*types.Repository, error) {
dst := new(types.Repository)
if err := s.db.GetContext(ctx, dst, repoSelectByPath, path); err != nil {
return nil, processSqlErrorf(err, "Select query failed")
return nil, processSQLErrorf(err, "Select query failed")
}
return dst, nil
}
// Creates a new repo
// Create creates a new repository.
func (s *RepoStore) Create(ctx context.Context, repo *types.Repository) error {
tx, err := s.db.BeginTxx(ctx, nil)
if err != nil {
return processSqlErrorf(err, "Failed to start a new transaction")
return processSQLErrorf(err, "Failed to start a new transaction")
}
defer func(tx *sqlx.Tx) {
_ = tx.Rollback()
@ -61,15 +61,15 @@ func (s *RepoStore) Create(ctx context.Context, repo *types.Repository) error {
// insert repo first so we get id
query, arg, err := s.db.BindNamed(repoInsert, repo)
if err != nil {
return processSqlErrorf(err, "Failed to bind repo object")
return processSQLErrorf(err, "Failed to bind repo object")
}
if err = tx.QueryRow(query, arg...).Scan(&repo.ID); err != nil {
return processSqlErrorf(err, "Insert query failed")
return processSQLErrorf(err, "Insert query failed")
}
// Get parent path (repo always has a parent)
parentPath, err := FindPathTx(ctx, tx, enum.PathTargetTypeSpace, repo.SpaceId)
parentPath, err := FindPathTx(ctx, tx, enum.PathTargetTypeSpace, repo.SpaceID)
if err != nil {
return errors.Wrap(err, "Failed to find path of parent space")
}
@ -80,7 +80,7 @@ func (s *RepoStore) Create(ctx context.Context, repo *types.Repository) error {
// create path only once we know the id of the repo
p := &types.Path{
TargetType: enum.PathTargetTypeRepo,
TargetId: repo.ID,
TargetID: repo.ID,
IsAlias: false,
Value: path,
CreatedBy: repo.CreatedBy,
@ -94,7 +94,7 @@ func (s *RepoStore) Create(ctx context.Context, repo *types.Repository) error {
// commit
if err = tx.Commit(); err != nil {
return processSqlErrorf(err, "Failed to commit transaction")
return processSQLErrorf(err, "Failed to commit transaction")
}
// update path in repo object
@ -103,24 +103,25 @@ func (s *RepoStore) Create(ctx context.Context, repo *types.Repository) error {
return nil
}
// Moves an existing space.
func (s *RepoStore) Move(ctx context.Context, userId int64, repoId int64, newSpaceId int64, newName string, keepAsAlias bool) (*types.Repository, error) {
// Move moves an existing space.
func (s *RepoStore) Move(ctx context.Context, userID int64, repoID int64, newSpaceID int64, newName string,
keepAsAlias bool) (*types.Repository, error) {
tx, err := s.db.BeginTxx(ctx, nil)
if err != nil {
return nil, processSqlErrorf(err, "Failed to start a new transaction")
return nil, processSQLErrorf(err, "Failed to start a new transaction")
}
defer func(tx *sqlx.Tx) {
_ = tx.Rollback() // should we take care about rollbacks errors?
}(tx)
// get current path of repo
currentPath, err := FindPathTx(ctx, tx, enum.PathTargetTypeRepo, repoId)
currentPath, err := FindPathTx(ctx, tx, enum.PathTargetTypeRepo, repoID)
if err != nil {
return nil, errors.Wrap(err, "Failed to find the primary path of the repo")
}
// get path of new parent space
spacePath, err := FindPathTx(ctx, tx, enum.PathTargetTypeSpace, newSpaceId)
spacePath, err := FindPathTx(ctx, tx, enum.PathTargetTypeSpace, newSpaceID)
if err != nil {
return nil, errors.Wrap(err, "Failed to find the primary path of the new space")
}
@ -132,10 +133,10 @@ func (s *RepoStore) Move(ctx context.Context, userId int64, repoId int64, newSpa
p := &types.Path{
TargetType: enum.PathTargetTypeRepo,
TargetId: repoId,
TargetID: repoID,
IsAlias: false,
Value: newPath,
CreatedBy: userId,
CreatedBy: userID,
Created: time.Now().UnixMilli(),
Updated: time.Now().UnixMilli(),
}
@ -146,19 +147,19 @@ func (s *RepoStore) Move(ctx context.Context, userId int64, repoId int64, newSpa
}
// Rename the repo itself
if _, err := tx.ExecContext(ctx, repoUpdateNameAndSpaceId, newName, newSpaceId, repoId); err != nil {
return nil, processSqlErrorf(err, "Query for renaming and updating the space id failed")
if _, err = tx.ExecContext(ctx, repoUpdateNameAndSpaceID, newName, newSpaceID, repoID); err != nil {
return nil, processSQLErrorf(err, "Query for renaming and updating the space id failed")
}
// TODO: return repo as part of rename db operation?
dst := new(types.Repository)
if err = tx.GetContext(ctx, dst, repoSelectById, repoId); err != nil {
return nil, processSqlErrorf(err, "Select query to get the repo's latest state failed")
if err = tx.GetContext(ctx, dst, repoSelectByID, repoID); err != nil {
return nil, processSQLErrorf(err, "Select query to get the repo's latest state failed")
}
// commit
if err = tx.Commit(); err != nil {
return nil, processSqlErrorf(err, "Failed to commit transaction")
return nil, processSQLErrorf(err, "Failed to commit transaction")
}
return dst, nil
@ -168,21 +169,21 @@ func (s *RepoStore) Move(ctx context.Context, userId int64, repoId int64, newSpa
func (s *RepoStore) Update(ctx context.Context, repo *types.Repository) error {
query, arg, err := s.db.BindNamed(repoUpdate, repo)
if err != nil {
return processSqlErrorf(err, "Failed to bind repo object")
return processSQLErrorf(err, "Failed to bind repo object")
}
if _, err = s.db.ExecContext(ctx, query, arg...); err != nil {
return processSqlErrorf(err, "Update query failed")
return processSQLErrorf(err, "Update query failed")
}
return nil
}
// Deletes the repo.
// Delete the repository.
func (s *RepoStore) Delete(ctx context.Context, id int64) error {
tx, err := s.db.BeginTxx(ctx, nil)
if err != nil {
return processSqlErrorf(err, "Failed to start a new transaction")
return processSQLErrorf(err, "Failed to start a new transaction")
}
defer func(tx *sqlx.Tx) {
_ = tx.Rollback()
@ -195,38 +196,38 @@ func (s *RepoStore) Delete(ctx context.Context, id int64) error {
}
// delete the repo
if _, err := tx.ExecContext(ctx, repoDelete, id); err != nil {
return processSqlErrorf(err, "The delete query failed")
if _, err = tx.ExecContext(ctx, repoDelete, id); err != nil {
return processSQLErrorf(err, "The delete query failed")
}
if err = tx.Commit(); err != nil {
return processSqlErrorf(err, "Failed to commit transaction")
return processSQLErrorf(err, "Failed to commit transaction")
}
return nil
}
// Count of repos in a space.
func (s *RepoStore) Count(ctx context.Context, spaceId int64) (int64, error) {
func (s *RepoStore) Count(ctx context.Context, spaceID int64) (int64, error) {
var count int64
err := s.db.QueryRow(repoCount, spaceId).Scan(&count)
err := s.db.QueryRow(repoCount, spaceID).Scan(&count)
if err != nil {
return 0, processSqlErrorf(err, "Failed executing count query")
return 0, processSQLErrorf(err, "Failed executing count query")
}
return count, nil
}
// List returns a list of repos in a space.
// TODO: speed up list - for some reason is 200ms for 1 repo as well as 1000
func (s *RepoStore) List(ctx context.Context, spaceId int64, opts *types.RepoFilter) ([]*types.Repository, error) {
func (s *RepoStore) List(ctx context.Context, spaceID int64, opts *types.RepoFilter) ([]*types.Repository, error) {
dst := []*types.Repository{}
// if the user does not provide any customer filter
// or sorting we use the default select statement.
if opts.Sort == enum.RepoAttrNone {
err := s.db.SelectContext(ctx, &dst, repoSelect, spaceId, limit(opts.Size), offset(opts.Page, opts.Size))
err := s.db.SelectContext(ctx, &dst, repoSelect, spaceID, limit(opts.Size), offset(opts.Page, opts.Size))
if err != nil {
return nil, processSqlErrorf(err, "Failed executing default list query")
return nil, processSQLErrorf(err, "Failed executing default list query")
}
return dst, nil
}
@ -235,20 +236,21 @@ func (s *RepoStore) List(ctx context.Context, spaceId int64, opts *types.RepoFil
stmt := builder.
Select("repositories.*,path_value AS repo_path").
From("repositories").
InnerJoin("paths ON repositories.repo_id=paths.path_targetId AND paths.path_targetType='repo' AND paths.path_isAlias=0").
Where("repo_spaceId = " + fmt.Sprint(spaceId))
InnerJoin("paths ON repositories.repo_id=paths.path_targetId AND paths.path_targetType='repo' " +
"AND paths.path_isAlias=0").
Where("repo_spaceId = " + fmt.Sprint(spaceID))
stmt = stmt.Limit(uint64(limit(opts.Size)))
stmt = stmt.Offset(uint64(offset(opts.Page, opts.Size)))
switch opts.Sort {
case enum.RepoAttrCreated:
// NOTE: string concatination is safe because the
// NOTE: string concatenation is safe because the
// order attribute is an enum and is not user-defined,
// and is therefore not subject to injection attacks.
stmt = stmt.OrderBy("repo_created " + opts.Order.String())
case enum.RepoAttrUpdated:
stmt = stmt.OrderBy("repo_updated " + opts.Order.String())
case enum.RepoAttrId:
case enum.RepoAttrID:
stmt = stmt.OrderBy("repo_id " + opts.Order.String())
case enum.RepoAttrName:
stmt = stmt.OrderBy("repo_name " + opts.Order.String())
@ -264,22 +266,22 @@ func (s *RepoStore) List(ctx context.Context, spaceId int64, opts *types.RepoFil
}
if err = s.db.SelectContext(ctx, &dst, sql); err != nil {
return nil, processSqlErrorf(err, "Failed executing custom list query")
return nil, processSQLErrorf(err, "Failed executing custom list query")
}
return dst, nil
}
// List returns a list of all paths of a repo.
// ListAllPaths returns a list of all paths of a repo.
func (s *RepoStore) ListAllPaths(ctx context.Context, id int64, opts *types.PathFilter) ([]*types.Path, error) {
return ListPaths(ctx, s.db, enum.PathTargetTypeRepo, id, opts)
}
// Create an alias for a repo
func (s *RepoStore) CreatePath(ctx context.Context, repoId int64, params *types.PathParams) (*types.Path, error) {
// CreatePath creates an alias for a repository.
func (s *RepoStore) CreatePath(ctx context.Context, repoID int64, params *types.PathParams) (*types.Path, error) {
p := &types.Path{
TargetType: enum.PathTargetTypeRepo,
TargetId: repoId,
TargetID: repoID,
IsAlias: true,
// get remaining infor from params
@ -292,9 +294,9 @@ func (s *RepoStore) CreatePath(ctx context.Context, repoId int64, params *types.
return p, CreateAliasPath(ctx, s.db, p)
}
// Delete an alias of a repo
func (s *RepoStore) DeletePath(ctx context.Context, repoId int64, pathId int64) error {
return DeletePath(ctx, s.db, pathId)
// DeletePath an alias of a repository.
func (s *RepoStore) DeletePath(ctx context.Context, repoID int64, pathID int64) error {
return DeletePath(ctx, s.db, pathID)
}
const repoSelectBase = `
@ -334,13 +336,14 @@ FROM repositories
WHERE repo_spaceId = $1
`
const repoSelectById = repoSelectBaseWithJoin + `
const repoSelectByID = repoSelectBaseWithJoin + `
WHERE repo_id = $1
`
const repoSelectByPath = repoSelectBase + `
FROM paths paths1
INNER JOIN repositories ON repositories.repo_id=paths1.path_targetId AND paths1.path_targetType='repo' AND paths1.path_value = $1
INNER JOIN repositories ON repositories.repo_id=paths1.path_targetId AND paths1.path_targetType='repo'
AND paths1.path_value = $1
INNER JOIN paths ON repositories.repo_id=paths.path_targetId AND paths.path_targetType='repo' AND paths.path_isAlias=0
`
@ -395,7 +398,7 @@ SET
WHERE repo_id = :repo_id
`
const repoUpdateNameAndSpaceId = `
const repoUpdateNameAndSpaceID = `
UPDATE repositories
SET
repo_name = $1

View File

@ -14,54 +14,55 @@ import (
var _ store.RepoStore = (*RepoStoreSync)(nil)
// Returns a new RepoStoreSync.
// NewRepoStoreSync returns a new RepoStoreSync.
func NewRepoStoreSync(base *RepoStore) *RepoStoreSync {
return &RepoStoreSync{base}
}
// RepoStoreSync synronizes read and write access to the
// RepoStoreSync synchronizes read and write access to the
// repo store. This prevents race conditions when the database
// type is sqlite3.
type RepoStoreSync struct {
base *RepoStore
}
// Finds the repo by id.
// Find the repo by id.
func (s *RepoStoreSync) Find(ctx context.Context, id int64) (*types.Repository, error) {
mutex.RLock()
defer mutex.RUnlock()
return s.base.Find(ctx, id)
}
// Finds the repo by path.
// FindByPath finds the repo by path.
func (s *RepoStoreSync) FindByPath(ctx context.Context, path string) (*types.Repository, error) {
mutex.RLock()
defer mutex.RUnlock()
return s.base.FindByPath(ctx, path)
}
// Creates a new repo
// Create a new repository.
func (s *RepoStoreSync) Create(ctx context.Context, repo *types.Repository) error {
mutex.RLock()
defer mutex.RUnlock()
return s.base.Create(ctx, repo)
}
// Moves an existing repo.
func (s *RepoStoreSync) Move(ctx context.Context, userId int64, repoId int64, newSpaceId int64, newName string, keepAsAlias bool) (*types.Repository, error) {
// Move an existing repo.
func (s *RepoStoreSync) Move(ctx context.Context, userID int64, repoID int64, newSpaceID int64,
newName string, keepAsAlias bool) (*types.Repository, error) {
mutex.RLock()
defer mutex.RUnlock()
return s.base.Move(ctx, userId, repoId, newSpaceId, newName, keepAsAlias)
return s.base.Move(ctx, userID, repoID, newSpaceID, newName, keepAsAlias)
}
// Updates the repo details.
// Update the repo details.
func (s *RepoStoreSync) Update(ctx context.Context, repo *types.Repository) error {
mutex.RLock()
defer mutex.RUnlock()
return s.base.Update(ctx, repo)
}
// Deletes the repo.
// Delete the repository.
func (s *RepoStoreSync) Delete(ctx context.Context, id int64) error {
mutex.RLock()
defer mutex.RUnlock()
@ -69,34 +70,34 @@ func (s *RepoStoreSync) Delete(ctx context.Context, id int64) error {
}
// Count of repos in a space.
func (s *RepoStoreSync) Count(ctx context.Context, spaceId int64) (int64, error) {
func (s *RepoStoreSync) Count(ctx context.Context, spaceID int64) (int64, error) {
mutex.RLock()
defer mutex.RUnlock()
return s.base.Count(ctx, spaceId)
return s.base.Count(ctx, spaceID)
}
// List returns a list of repos in a space.
func (s *RepoStoreSync) List(ctx context.Context, spaceId int64, opts *types.RepoFilter) ([]*types.Repository, error) {
func (s *RepoStoreSync) List(ctx context.Context, spaceID int64, opts *types.RepoFilter) ([]*types.Repository, error) {
mutex.RLock()
defer mutex.RUnlock()
return s.base.List(ctx, spaceId, opts)
return s.base.List(ctx, spaceID, opts)
}
// List returns a list of all paths of a repo.
// ListAllPaths returns a list of all paths of a repo.
func (s *RepoStoreSync) ListAllPaths(ctx context.Context, id int64, opts *types.PathFilter) ([]*types.Path, error) {
return s.base.ListAllPaths(ctx, id, opts)
}
// Create an alias for a repo
func (s *RepoStoreSync) CreatePath(ctx context.Context, repoId int64, params *types.PathParams) (*types.Path, error) {
// CreatePath an alias for a repository.
func (s *RepoStoreSync) CreatePath(ctx context.Context, repoID int64, params *types.PathParams) (*types.Path, error) {
mutex.RLock()
defer mutex.RUnlock()
return s.base.CreatePath(ctx, repoId, params)
return s.base.CreatePath(ctx, repoID, params)
}
// Delete an alias of a repo
func (s *RepoStoreSync) DeletePath(ctx context.Context, repoId int64, pathId int64) error {
// DeletePath an alias of a repository.
func (s *RepoStoreSync) DeletePath(ctx context.Context, repoID int64, pathID int64) error {
mutex.RLock()
defer mutex.RUnlock()
return s.base.DeletePath(ctx, repoId, pathId)
return s.base.DeletePath(ctx, repoID, pathID)
}

View File

@ -21,39 +21,39 @@ import (
var _ store.SpaceStore = (*SpaceStore)(nil)
// Returns a new SpaceStore.
// NewSpaceStore returns a new SpaceStore.
func NewSpaceStore(db *sqlx.DB) *SpaceStore {
return &SpaceStore{db}
}
// Implements a SpaceStore backed by a relational database.
// SpaceStore implements a SpaceStore backed by a relational database.
type SpaceStore struct {
db *sqlx.DB
}
// Finds the space by id.
// Find the space by id.
func (s *SpaceStore) Find(ctx context.Context, id int64) (*types.Space, error) {
dst := new(types.Space)
if err := s.db.GetContext(ctx, dst, spaceSelectById, id); err != nil {
return nil, processSqlErrorf(err, "Select query failed")
if err := s.db.GetContext(ctx, dst, spaceSelectByID, id); err != nil {
return nil, processSQLErrorf(err, "Select query failed")
}
return dst, nil
}
// Finds the space by path.
// FindByPath finds the space by path.
func (s *SpaceStore) FindByPath(ctx context.Context, path string) (*types.Space, error) {
dst := new(types.Space)
if err := s.db.GetContext(ctx, dst, spaceSelectByPath, path); err != nil {
return nil, processSqlErrorf(err, "Select query failed")
return nil, processSQLErrorf(err, "Select query failed")
}
return dst, nil
}
// Creates a new space
// Create a new space.
func (s *SpaceStore) Create(ctx context.Context, space *types.Space) error {
tx, err := s.db.BeginTxx(ctx, nil)
if err != nil {
return processSqlErrorf(err, "Failed to start a new transaction")
return processSQLErrorf(err, "Failed to start a new transaction")
}
defer func(tx *sqlx.Tx) {
_ = tx.Rollback()
@ -62,17 +62,18 @@ func (s *SpaceStore) Create(ctx context.Context, space *types.Space) error {
// insert space first so we get id
query, arg, err := s.db.BindNamed(spaceInsert, space)
if err != nil {
return processSqlErrorf(err, "Failed to bind space object")
return processSQLErrorf(err, "Failed to bind space object")
}
if err = tx.QueryRow(query, arg...).Scan(&space.ID); err != nil {
return processSqlErrorf(err, "Insert query failed")
return processSQLErrorf(err, "Insert query failed")
}
// Get path (get parent if needed)
path := space.Name
if space.ParentId > 0 {
parentPath, err := FindPathTx(ctx, tx, enum.PathTargetTypeSpace, space.ParentId)
if space.ParentID > 0 {
var parentPath *types.Path
parentPath, err = FindPathTx(ctx, tx, enum.PathTargetTypeSpace, space.ParentID)
if err != nil {
return errors.Wrap(err, "Failed to find path of parent space")
}
@ -84,7 +85,7 @@ func (s *SpaceStore) Create(ctx context.Context, space *types.Space) error {
// create path only once we know the id of the space
p := &types.Path{
TargetType: enum.PathTargetTypeSpace,
TargetId: space.ID,
TargetID: space.ID,
IsAlias: false,
Value: path,
CreatedBy: space.CreatedBy,
@ -98,7 +99,7 @@ func (s *SpaceStore) Create(ctx context.Context, space *types.Space) error {
// commit
if err = tx.Commit(); err != nil {
return processSqlErrorf(err, "Failed to commit transaction")
return processSQLErrorf(err, "Failed to commit transaction")
}
// update path in space object
@ -107,27 +108,29 @@ func (s *SpaceStore) Create(ctx context.Context, space *types.Space) error {
return nil
}
// Moves an existing space.
func (s *SpaceStore) Move(ctx context.Context, userId int64, spaceId int64, newParentId int64, newName string, keepAsAlias bool) (*types.Space, error) {
// Move moves an existing space.
func (s *SpaceStore) Move(ctx context.Context, userID int64, spaceID int64, newParentID int64, newName string,
keepAsAlias bool) (*types.Space, error) {
tx, err := s.db.BeginTxx(ctx, nil)
if err != nil {
return nil, processSqlErrorf(err, "Failed to start a new transaction")
return nil, processSQLErrorf(err, "Failed to start a new transaction")
}
defer func(tx *sqlx.Tx) {
_ = tx.Rollback()
}(tx)
// always get currentpath (either it didn't change or we need to for validation)
currentPath, err := FindPathTx(ctx, tx, enum.PathTargetTypeSpace, spaceId)
currentPath, err := FindPathTx(ctx, tx, enum.PathTargetTypeSpace, spaceID)
if err != nil {
return nil, errors.Wrap(err, "Failed to find the primary path of the space")
}
// get path of new parent if needed
newPath := newName
if newParentId > 0 {
if newParentID > 0 {
// get path of new parent space
spacePath, err := FindPathTx(ctx, tx, enum.PathTargetTypeSpace, newParentId)
var spacePath *types.Path
spacePath, err = FindPathTx(ctx, tx, enum.PathTargetTypeSpace, newParentID)
if err != nil {
return nil, errors.Wrap(err, "Failed to find the primary path of the new parent space")
}
@ -147,10 +150,10 @@ func (s *SpaceStore) Move(ctx context.Context, userId int64, spaceId int64, newP
p := &types.Path{
TargetType: enum.PathTargetTypeSpace,
TargetId: spaceId,
TargetID: spaceID,
IsAlias: false,
Value: newPath,
CreatedBy: userId,
CreatedBy: userID,
Created: time.Now().UnixMilli(),
Updated: time.Now().UnixMilli(),
}
@ -161,19 +164,19 @@ func (s *SpaceStore) Move(ctx context.Context, userId int64, spaceId int64, newP
}
// Update the space itself
if _, err := tx.ExecContext(ctx, spaceUpdateNameAndParentId, newName, newParentId, spaceId); err != nil {
return nil, processSqlErrorf(err, "Query for renaming and updating the parent id failed")
if _, err = tx.ExecContext(ctx, spaceUpdateNameAndParentID, newName, newParentID, spaceID); err != nil {
return nil, processSQLErrorf(err, "Query for renaming and updating the parent id failed")
}
// TODO: return space as part of rename operation
dst := new(types.Space)
if err = tx.GetContext(ctx, dst, spaceSelectById, spaceId); err != nil {
return nil, processSqlErrorf(err, "Select query to get the space's latest state failed")
if err = tx.GetContext(ctx, dst, spaceSelectByID, spaceID); err != nil {
return nil, processSQLErrorf(err, "Select query to get the space's latest state failed")
}
// commit
if err = tx.Commit(); err != nil {
return nil, processSqlErrorf(err, "Failed to commit transaction")
return nil, processSQLErrorf(err, "Failed to commit transaction")
}
return dst, nil
@ -183,11 +186,11 @@ func (s *SpaceStore) Move(ctx context.Context, userId int64, spaceId int64, newP
func (s *SpaceStore) Update(ctx context.Context, space *types.Space) error {
query, arg, err := s.db.BindNamed(spaceUpdate, space)
if err != nil {
return processSqlErrorf(err, "Failed to bind space object")
return processSQLErrorf(err, "Failed to bind space object")
}
if _, err = s.db.ExecContext(ctx, query, arg...); err != nil {
return processSqlErrorf(err, "Update query failed")
return processSQLErrorf(err, "Update query failed")
}
return nil
@ -197,7 +200,7 @@ func (s *SpaceStore) Update(ctx context.Context, space *types.Space) error {
func (s *SpaceStore) Delete(ctx context.Context, id int64) error {
tx, err := s.db.BeginTxx(ctx, nil)
if err != nil {
return processSqlErrorf(err, "Failed to start a new transaction")
return processSQLErrorf(err, "Failed to start a new transaction")
}
defer func(tx *sqlx.Tx) {
_ = tx.Rollback()
@ -227,11 +230,11 @@ func (s *SpaceStore) Delete(ctx context.Context, id int64) error {
// delete the space
if _, err = tx.Exec(spaceDelete, id); err != nil {
return processSqlErrorf(err, "The delete query failed")
return processSQLErrorf(err, "The delete query failed")
}
if err = tx.Commit(); err != nil {
return processSqlErrorf(err, "Failed to commit transaction")
return processSQLErrorf(err, "Failed to commit transaction")
}
return nil
@ -242,7 +245,7 @@ func (s *SpaceStore) Count(ctx context.Context, id int64) (int64, error) {
var count int64
err := s.db.QueryRowContext(ctx, spaceCount, id).Scan(&count)
if err != nil {
return 0, processSqlErrorf(err, "Failed executing count query")
return 0, processSQLErrorf(err, "Failed executing count query")
}
return count, nil
}
@ -257,7 +260,7 @@ func (s *SpaceStore) List(ctx context.Context, id int64, opts *types.SpaceFilter
if opts.Sort == enum.SpaceAttrNone {
err := s.db.SelectContext(ctx, &dst, spaceSelect, id, limit(opts.Size), offset(opts.Page, opts.Size))
if err != nil {
return nil, processSqlErrorf(err, "Failed executing default list query")
return nil, processSQLErrorf(err, "Failed executing default list query")
}
return dst, nil
}
@ -279,7 +282,7 @@ func (s *SpaceStore) List(ctx context.Context, id int64, opts *types.SpaceFilter
stmt = stmt.OrderBy("space_created " + opts.Order.String())
case enum.SpaceAttrUpdated:
stmt = stmt.OrderBy("space_updated " + opts.Order.String())
case enum.SpaceAttrId:
case enum.SpaceAttrID:
stmt = stmt.OrderBy("space_id " + opts.Order.String())
case enum.SpaceAttrName:
stmt = stmt.OrderBy("space_name " + opts.Order.String())
@ -293,22 +296,22 @@ func (s *SpaceStore) List(ctx context.Context, id int64, opts *types.SpaceFilter
}
if err = s.db.SelectContext(ctx, &dst, sql); err != nil {
return nil, processSqlErrorf(err, "Failed executing custom list query")
return nil, processSQLErrorf(err, "Failed executing custom list query")
}
return dst, nil
}
// List returns a list of all paths of a space.
// ListAllPaths returns a list of all paths of a space.
func (s *SpaceStore) ListAllPaths(ctx context.Context, id int64, opts *types.PathFilter) ([]*types.Path, error) {
return ListPaths(ctx, s.db, enum.PathTargetTypeSpace, id, opts)
}
// Create an alias for a space.
func (s *SpaceStore) CreatePath(ctx context.Context, spaceId int64, params *types.PathParams) (*types.Path, error) {
// CreatePath creates an alias for a space.
func (s *SpaceStore) CreatePath(ctx context.Context, spaceID int64, params *types.PathParams) (*types.Path, error) {
p := &types.Path{
TargetType: enum.PathTargetTypeSpace,
TargetId: spaceId,
TargetID: spaceID,
IsAlias: true,
// get remaining infor from params
@ -321,9 +324,9 @@ func (s *SpaceStore) CreatePath(ctx context.Context, spaceId int64, params *type
return p, CreateAliasPath(ctx, s.db, p)
}
// Delete an alias of a space.
func (s *SpaceStore) DeletePath(ctx context.Context, spaceId int64, pathId int64) error {
return DeletePath(ctx, s.db, pathId)
// DeletePath an alias of a space.
func (s *SpaceStore) DeletePath(ctx context.Context, spaceID int64, pathID int64) error {
return DeletePath(ctx, s.db, pathID)
}
const spaceSelectBase = `
@ -358,7 +361,7 @@ FROM spaces
WHERE space_parentId = $1
`
const spaceSelectById = spaceSelectBaseWithJoin + `
const spaceSelectByID = spaceSelectBaseWithJoin + `
WHERE space_id = $1
`
@ -405,7 +408,7 @@ space_displayName = :space_displayName
WHERE space_id = :space_id
`
const spaceUpdateNameAndParentId = `
const spaceUpdateNameAndParentID = `
UPDATE spaces
SET
space_name = $1

View File

@ -14,54 +14,55 @@ import (
var _ store.SpaceStore = (*SpaceStoreSync)(nil)
// Returns a new SpaceStore.
// NewSpaceStoreSync returns a new SpaceStore.
func NewSpaceStoreSync(base *SpaceStore) *SpaceStoreSync {
return &SpaceStoreSync{base}
}
// SpaceStoreSync synronizes read and write access to the
// SpaceStoreSync synchronizes read and write access to the
// space store. This prevents race conditions when the database
// type is sqlite3.
type SpaceStoreSync struct {
base *SpaceStore
}
// Finds the space by id.
// Find the space by id.
func (s *SpaceStoreSync) Find(ctx context.Context, id int64) (*types.Space, error) {
mutex.RLock()
defer mutex.RUnlock()
return s.base.Find(ctx, id)
}
// Finds the space by path.
// FindByPath find the space by path.
func (s *SpaceStoreSync) FindByPath(ctx context.Context, path string) (*types.Space, error) {
mutex.RLock()
defer mutex.RUnlock()
return s.base.FindByPath(ctx, path)
}
// Creates a new space
// Create a new space.
func (s *SpaceStoreSync) Create(ctx context.Context, space *types.Space) error {
mutex.RLock()
defer mutex.RUnlock()
return s.base.Create(ctx, space)
}
// Moves an existing space.
func (s *SpaceStoreSync) Move(ctx context.Context, userId int64, spaceId int64, newParentId int64, newName string, keepAsAlias bool) (*types.Space, error) {
// Move moves an existing space.
func (s *SpaceStoreSync) Move(ctx context.Context, userID int64, spaceID int64, newParentID int64, newName string,
keepAsAlias bool) (*types.Space, error) {
mutex.RLock()
defer mutex.RUnlock()
return s.base.Move(ctx, userId, spaceId, newParentId, newName, keepAsAlias)
return s.base.Move(ctx, userID, spaceID, newParentID, newName, keepAsAlias)
}
// Updates the space details.
// Update the space details.
func (s *SpaceStoreSync) Update(ctx context.Context, space *types.Space) error {
mutex.RLock()
defer mutex.RUnlock()
return s.base.Update(ctx, space)
}
// Deletes the space.
// Delete the space.
func (s *SpaceStoreSync) Delete(ctx context.Context, id int64) error {
mutex.RLock()
defer mutex.RUnlock()
@ -82,21 +83,21 @@ func (s *SpaceStoreSync) List(ctx context.Context, id int64, opts *types.SpaceFi
return s.base.List(ctx, id, opts)
}
// List returns a list of all paths of a space.
// ListAllPaths returns a list of all paths of a space.
func (s *SpaceStoreSync) ListAllPaths(ctx context.Context, id int64, opts *types.PathFilter) ([]*types.Path, error) {
return s.base.ListAllPaths(ctx, id, opts)
}
// Create a path for a space.
func (s *SpaceStoreSync) CreatePath(ctx context.Context, spaceId int64, params *types.PathParams) (*types.Path, error) {
// CreatePath a path for a space.
func (s *SpaceStoreSync) CreatePath(ctx context.Context, spaceID int64, params *types.PathParams) (*types.Path, error) {
mutex.RLock()
defer mutex.RUnlock()
return s.base.CreatePath(ctx, spaceId, params)
return s.base.CreatePath(ctx, spaceID, params)
}
// Delete a path of a space.
func (s *SpaceStoreSync) DeletePath(ctx context.Context, spaceId int64, pathId int64) error {
// DeletePath a path of a space.
func (s *SpaceStoreSync) DeletePath(ctx context.Context, spaceID int64, pathID int64) error {
mutex.RLock()
defer mutex.RUnlock()
return s.base.DeletePath(ctx, spaceId, pathId)
return s.base.DeletePath(ctx, spaceID, pathID)
}

View File

@ -27,13 +27,16 @@ func Connect(driver, datasource string) (*sqlx.DB, error) {
if err != nil {
return nil, errors.Wrap(err, "Failed to open the db")
}
dbx := sqlx.NewDb(db, driver)
if err := pingDatabase(dbx); err != nil {
if err = pingDatabase(dbx); err != nil {
return nil, errors.Wrap(err, "Failed to ping the db")
}
if err := setupDatabase(dbx); err != nil {
if err = setupDatabase(dbx); err != nil {
return nil, errors.Wrap(err, "Failed to setup the db")
}
return dbx, nil
}
@ -49,18 +52,18 @@ func Must(db *sqlx.DB, err error) *sqlx.DB {
// helper function to ping the database with backoff to ensure
// a connection can be established before we proceed with the
// database setup and migration.
func pingDatabase(db *sqlx.DB) (err error) {
func pingDatabase(db *sqlx.DB) error {
for i := 0; i < 30; i++ {
err = db.Ping()
if err == nil {
return
err := db.Ping()
if err != nil {
return err
}
time.Sleep(time.Second)
}
return
return nil
}
// helper function to setup the databsae by performing automated
// helper function to setup the database by performing automated
// database migration steps.
func setupDatabase(db *sqlx.DB) error {
return migrate.Migrate(db)

View File

@ -5,18 +5,16 @@
package database
import (
"context"
"encoding/json"
"github.com/jmoiron/sqlx"
"io/ioutil"
"os"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
_ "github.com/mattn/go-sqlite3"
)
var noContext = context.Background()
// connect opens a new test database connection.
func connect() (*sqlx.DB, error) {
var (
@ -32,13 +30,28 @@ func connect() (*sqlx.DB, error) {
// seed seed the database state.
func seed(db *sqlx.DB) error {
_, _ = db.Exec("DELETE FROM executions")
_, _ = db.Exec("DELETE FROM pipelines")
_, _ = db.Exec("DELETE FROM users")
_, _ = db.Exec("ALTER SEQUENCE users_user_id_seq RESTART WITH 1")
_, _ = db.Exec("ALTER SEQUENCE pipelines_pipeline_id_seq RESTART WITH 1")
_, _ = db.Exec("ALTER SEQUENCE executions_execution_id_seq RESTART WITH 1")
return nil
_, err := db.Exec("DELETE FROM executions")
if err != nil {
return err
}
_, err = db.Exec("DELETE FROM pipelines")
if err != nil {
return err
}
_, err = db.Exec("DELETE FROM users")
if err != nil {
return err
}
_, err = db.Exec("ALTER SEQUENCE users_user_id_seq RESTART WITH 1")
if err != nil {
return err
}
_, err = db.Exec("ALTER SEQUENCE pipelines_pipeline_id_seq RESTART WITH 1")
if err != nil {
return err
}
_, err = db.Exec("ALTER SEQUENCE executions_execution_id_seq RESTART WITH 1")
return err
}
// unmarshal a testdata file.

View File

@ -34,7 +34,7 @@ type UserStore struct {
func (s *UserStore) Find(ctx context.Context, id int64) (*types.User, error) {
dst := new(types.User)
if err := s.db.GetContext(ctx, dst, userSelectID, id); err != nil {
return nil, processSqlErrorf(err, "Select by id query failed")
return nil, processSQLErrorf(err, "Select by id query failed")
}
return dst, nil
}
@ -43,7 +43,7 @@ func (s *UserStore) Find(ctx context.Context, id int64) (*types.User, error) {
func (s *UserStore) FindEmail(ctx context.Context, email string) (*types.User, error) {
dst := new(types.User)
if err := s.db.GetContext(ctx, dst, userSelectEmail, email); err != nil {
return nil, processSqlErrorf(err, "Select by email query failed")
return nil, processSQLErrorf(err, "Select by email query failed")
}
return dst, nil
}
@ -53,9 +53,8 @@ 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)
} else {
return s.FindEmail(ctx, key)
}
return s.FindEmail(ctx, key)
}
// List returns a list of users.
@ -67,7 +66,7 @@ func (s *UserStore) List(ctx context.Context, opts *types.UserFilter) ([]*types.
if opts.Sort == enum.UserAttrNone {
err := s.db.SelectContext(ctx, &dst, userSelect, limit(opts.Size), offset(opts.Page, opts.Size))
if err != nil {
return nil, processSqlErrorf(err, "Failed executing default list query")
return nil, processSQLErrorf(err, "Failed executing default list query")
}
return dst, nil
}
@ -79,7 +78,7 @@ func (s *UserStore) List(ctx context.Context, opts *types.UserFilter) ([]*types.
switch opts.Sort {
case enum.UserAttrCreated:
// NOTE: string concatination is safe because the
// NOTE: string concatenation is safe because the
// order attribute is an enum and is not user-defined,
// and is therefore not subject to injection attacks.
stmt = stmt.OrderBy("user_id " + opts.Order.String())
@ -87,7 +86,7 @@ func (s *UserStore) List(ctx context.Context, opts *types.UserFilter) ([]*types.
stmt = stmt.OrderBy("user_updated " + opts.Order.String())
case enum.UserAttrEmail:
stmt = stmt.OrderBy("user_email " + opts.Order.String())
case enum.UserAttrId:
case enum.UserAttrID:
stmt = stmt.OrderBy("user_id " + opts.Order.String())
}
@ -97,7 +96,7 @@ func (s *UserStore) List(ctx context.Context, opts *types.UserFilter) ([]*types.
}
if err = s.db.SelectContext(ctx, &dst, sql); err != nil {
return nil, processSqlErrorf(err, "Failed executing custom list query")
return nil, processSQLErrorf(err, "Failed executing custom list query")
}
return dst, nil
@ -107,11 +106,11 @@ func (s *UserStore) List(ctx context.Context, opts *types.UserFilter) ([]*types.
func (s *UserStore) Create(ctx context.Context, user *types.User) error {
query, arg, err := s.db.BindNamed(userInsert, user)
if err != nil {
return processSqlErrorf(err, "Failed to bind user object")
return processSQLErrorf(err, "Failed to bind user object")
}
if err = s.db.QueryRowContext(ctx, query, arg...).Scan(&user.ID); err != nil {
return processSqlErrorf(err, "Insert query failed")
return processSQLErrorf(err, "Insert query failed")
}
return nil
@ -121,11 +120,11 @@ func (s *UserStore) Create(ctx context.Context, user *types.User) error {
func (s *UserStore) Update(ctx context.Context, user *types.User) error {
query, arg, err := s.db.BindNamed(userUpdate, user)
if err != nil {
return processSqlErrorf(err, "Failed to bind user object")
return processSQLErrorf(err, "Failed to bind user object")
}
if _, err = s.db.ExecContext(ctx, query, arg...); err != nil {
return processSqlErrorf(err, "Update query failed")
return processSQLErrorf(err, "Update query failed")
}
return err
@ -135,14 +134,14 @@ func (s *UserStore) Update(ctx context.Context, user *types.User) error {
func (s *UserStore) Delete(ctx context.Context, user *types.User) error {
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return processSqlErrorf(err, "Failed to start a new transaction")
return processSQLErrorf(err, "Failed to start a new transaction")
}
defer func(tx *sql.Tx) {
_ = tx.Rollback()
}(tx)
// delete the user
if _, err := tx.ExecContext(ctx, userDelete, user.ID); err != nil {
return processSqlErrorf(err, "The delete query failed")
if _, err = tx.ExecContext(ctx, userDelete, user.ID); err != nil {
return processSQLErrorf(err, "The delete query failed")
}
return tx.Commit()
}
@ -152,7 +151,7 @@ func (s *UserStore) Count(ctx context.Context) (int64, error) {
var count int64
err := s.db.QueryRowContext(ctx, userCount).Scan(&count)
if err != nil {
return 0, processSqlErrorf(err, "Failed executing count query")
return 0, processSQLErrorf(err, "Failed executing count query")
}
return count, nil
}

View File

@ -5,11 +5,15 @@
package database
import (
"context"
"fmt"
"strings"
"testing"
"time"
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/types"
@ -17,7 +21,7 @@ import (
"github.com/google/go-cmp/cmp/cmpopts"
)
// user fields to ignore in test comparisons
// user fields to ignore in test comparisons.
var userIgnore = cmpopts.IgnoreFields(types.User{},
"ID", "Salt", "Created", "Updated")
@ -27,26 +31,29 @@ func TestUser(t *testing.T) {
t.Error(err)
return
}
defer db.Close()
if err := seed(db); err != nil {
defer func(db *sqlx.DB) {
_ = db.Close()
}(db)
if err = seed(db); err != nil {
t.Error(err)
return
}
store := NewUserStoreSync(NewUserStore(db))
t.Run("create", testUserCreate(store))
t.Run("duplicate", testUserDuplicate(store))
t.Run("count", testUserCount(store))
t.Run("find", testUserFind(store))
t.Run("list", testUserList(store))
t.Run("update", testUserUpdate(store))
t.Run("delete", testUserDelete(store))
userStoreSync := NewUserStoreSync(NewUserStore(db))
t.Run("create", testUserCreate(userStoreSync))
t.Run("duplicate", testUserDuplicate(userStoreSync))
t.Run("count", testUserCount(userStoreSync))
t.Run("find", testUserFind(userStoreSync))
t.Run("list", testUserList(userStoreSync))
t.Run("update", testUserUpdate(userStoreSync))
t.Run("delete", testUserDelete(userStoreSync))
}
// this test creates entries in the database and confirms
// the primary keys were auto-incremented.
func testUserCreate(store store.UserStore) func(t *testing.T) {
return func(t *testing.T) {
ctx := context.Background()
vv := []*types.User{}
if err := unmarshal("testdata/users.json", &vv); err != nil {
t.Error(err)
@ -57,7 +64,7 @@ func testUserCreate(store store.UserStore) func(t *testing.T) {
// generate a deterministic token for each
// entry based on the hash of the email.
v.Salt = fmt.Sprintf("%x", v.Email)
if err := store.Create(noContext, v); err != nil {
if err := store.Create(ctx, v); err != nil {
t.Error(err)
return
}
@ -67,7 +74,7 @@ func testUserCreate(store store.UserStore) func(t *testing.T) {
// create row 2
v = vv[1]
v.Salt = fmt.Sprintf("%x", v.Email)
if err := store.Create(noContext, v); err != nil {
if err := store.Create(ctx, v); err != nil {
t.Error(err)
return
}
@ -87,7 +94,7 @@ func testUserDuplicate(store store.UserStore) func(t *testing.T) {
t.Error(err)
return
}
if err := store.Create(noContext, vv[0]); err == nil {
if err := store.Create(context.Background(), vv[0]); err == nil {
t.Errorf("Expect unique index violation")
}
}
@ -97,7 +104,7 @@ func testUserDuplicate(store store.UserStore) func(t *testing.T) {
// and compares to the expected count.
func testUserCount(store store.UserStore) func(t *testing.T) {
return func(t *testing.T) {
got, err := store.Count(noContext)
got, err := store.Count(context.Background())
if err != nil {
t.Error(err)
return
@ -113,6 +120,7 @@ func testUserCount(store store.UserStore) func(t *testing.T) {
// to ensure all columns are correctly mapped.
func testUserFind(store store.UserStore) func(t *testing.T) {
return func(t *testing.T) {
ctx := context.Background()
vv := []*types.User{}
if err := unmarshal("testdata/users.json", &vv); err != nil {
t.Error(err)
@ -121,7 +129,7 @@ func testUserFind(store store.UserStore) func(t *testing.T) {
want := vv[0]
t.Run("id", func(t *testing.T) {
got, err := store.Find(noContext, 1)
got, err := store.Find(ctx, 1)
if err != nil {
t.Error(err)
return
@ -133,7 +141,7 @@ func testUserFind(store store.UserStore) func(t *testing.T) {
})
t.Run("email", func(t *testing.T) {
got, err := store.FindEmail(noContext, want.Email)
got, err := store.FindEmail(ctx, want.Email)
if err != nil {
t.Error(err)
return
@ -145,7 +153,7 @@ func testUserFind(store store.UserStore) func(t *testing.T) {
})
t.Run("email/nocase", func(t *testing.T) {
got, err := store.FindEmail(noContext, strings.ToUpper(want.Email))
got, err := store.FindEmail(ctx, strings.ToUpper(want.Email))
if err != nil {
t.Error(err)
return
@ -157,7 +165,7 @@ func testUserFind(store store.UserStore) func(t *testing.T) {
})
t.Run("key/id", func(t *testing.T) {
got, err := store.FindKey(noContext, "1")
got, err := store.FindKey(ctx, "1")
if err != nil {
t.Error(err)
return
@ -169,7 +177,7 @@ func testUserFind(store store.UserStore) func(t *testing.T) {
})
t.Run("key/email", func(t *testing.T) {
got, err := store.FindKey(noContext, want.Email)
got, err := store.FindKey(ctx, want.Email)
if err != nil {
t.Error(err)
return
@ -192,7 +200,7 @@ func testUserList(store store.UserStore) func(t *testing.T) {
t.Error(err)
return
}
got, err := store.List(noContext, &types.UserFilter{Page: 0, Size: 100})
got, err := store.List(context.Background(), &types.UserFilter{Page: 0, Size: 100})
if err != nil {
t.Error(err)
return
@ -208,18 +216,19 @@ func testUserList(store store.UserStore) func(t *testing.T) {
// the user and confirms the column was updated as expected.
func testUserUpdate(store store.UserStore) func(t *testing.T) {
return func(t *testing.T) {
before, err := store.Find(noContext, 1)
ctx := context.Background()
before, err := store.Find(ctx, 1)
if err != nil {
t.Error(err)
return
}
before.Updated = time.Now().Unix()
before.Authed = time.Now().Unix()
if err := store.Update(noContext, before); err != nil {
if err = store.Update(ctx, before); err != nil {
t.Error(err)
return
}
after, err := store.Find(noContext, 1)
after, err := store.Find(ctx, 1)
if err != nil {
t.Error(err)
return
@ -237,16 +246,17 @@ func testUserUpdate(store store.UserStore) func(t *testing.T) {
// a sql.ErrNoRows error.
func testUserDelete(s store.UserStore) func(t *testing.T) {
return func(t *testing.T) {
v, err := s.Find(noContext, 1)
ctx := context.Background()
v, err := s.Find(ctx, 1)
if err != nil {
t.Error(err)
return
}
if err := s.Delete(noContext, v); err != nil {
if err = s.Delete(ctx, v); err != nil {
t.Error(err)
return
}
if _, err := s.Find(noContext, 1); err != store.ErrResourceNotFound {
if _, err = s.Find(ctx, 1); errors.Is(err, store.ErrResourceNotFound) {
t.Errorf("Expected sql.ErrNoRows got %s", err)
}
}

View File

@ -33,26 +33,27 @@ func offset(page, size int) int {
if size == 0 {
size = defaultLimit
}
page = page - 1
page--
return page * size
}
// Logs the error and message, returns either the original error or a store equivalent if possible.
func processSqlErrorf(err error, format string, args ...interface{}) error {
func processSQLErrorf(err error, format string, args ...interface{}) error {
// always log DB error (print formated message)
log.Warn().Msgf("%s %s", fmt.Sprintf(format, args...), err)
// If it's a known error, return converted error instead.
if err == sql.ErrNoRows {
if errors.Is(err, sql.ErrNoRows) {
return store.ErrResourceNotFound
} else if isSqlUniqueConstraintError(err) {
} else if isSQLUniqueConstraintError(err) {
return store.ErrDuplicate
}
return err
}
func isSqlUniqueConstraintError(original error) bool {
o3, ok := original.(sqlite3.Error)
return ok && errors.Is(o3.ExtendedCode, sqlite3.ErrConstraintUnique)
func isSQLUniqueConstraintError(original error) bool {
err := sqlite3.Error{}
ok := errors.As(original, &err)
return ok && errors.Is(err.ExtendedCode, sqlite3.ErrConstraintUnique)
}

View File

@ -12,7 +12,11 @@ import (
"github.com/jmoiron/sqlx"
)
// WireSet provides a wire set for this package
const (
postgres = "postgres"
)
// WireSet provides a wire set for this package.
var WireSet = wire.NewSet(
ProvideDatabase,
ProvideUserStore,
@ -31,7 +35,7 @@ func ProvideDatabase(config *types.Config) (*sqlx.DB, error) {
// ProvideUserStore provides a user store.
func ProvideUserStore(db *sqlx.DB) store.UserStore {
switch db.DriverName() {
case "postgres":
case postgres:
return NewUserStore(db)
default:
return NewUserStoreSync(
@ -43,7 +47,7 @@ func ProvideUserStore(db *sqlx.DB) store.UserStore {
// ProvideSpaceStore provides a space store.
func ProvideSpaceStore(db *sqlx.DB) store.SpaceStore {
switch db.DriverName() {
case "postgres":
case postgres:
return NewSpaceStore(db)
default:
return NewSpaceStoreSync(
@ -55,7 +59,7 @@ func ProvideSpaceStore(db *sqlx.DB) store.SpaceStore {
// ProvideRepoStore provides a repo store.
func ProvideRepoStore(db *sqlx.DB) store.RepoStore {
switch db.DriverName() {
case "postgres":
case postgres:
return NewRepoStore(db)
default:
return NewRepoStoreSync(

View File

@ -7,14 +7,16 @@ package store
import "errors"
var (
ErrResourceNotFound = errors.New("Resource not found")
ErrDuplicate = errors.New("Resource is a duplicate")
ErrPathTooLong = errors.New("The path is too long")
ErrPrimaryPathAlreadyExists = errors.New("Primary path already exists for resource.")
ErrPrimaryPathRequired = errors.New("Path has to be primary.")
ErrAliasPathRequired = errors.New("Path has to be an alias.")
ErrPrimaryPathCantBeDeleted = errors.New("Primary path can't be deleted.")
ErrNoChangeInRequestedMove = errors.New(("The requested move doesn't change anything."))
ErrIllegalMoveCyclicHierarchy = errors.New(("The requested move is not permitted as it would cause a cyclic depdency."))
ErrSpaceWithChildsCantBeDeleted = errors.New("The space can't be deleted as it still contains spaces or repos.")
ErrResourceNotFound = errors.New("resource not found")
ErrDuplicate = errors.New("resource is a duplicate")
ErrPathTooLong = errors.New("the path is too long")
ErrPrimaryPathAlreadyExists = errors.New("primary path already exists for resource")
ErrPrimaryPathRequired = errors.New("path has to be primary")
ErrAliasPathRequired = errors.New("path has to be an alias")
ErrPrimaryPathCantBeDeleted = errors.New("primary path can't be deleted")
ErrNoChangeInRequestedMove = errors.New("the requested move doesn't change anything")
ErrIllegalMoveCyclicHierarchy = errors.New("the requested move is not permitted as it would cause a " +
"cyclic depdency")
ErrSpaceWithChildsCantBeDeleted = errors.New("the space can't be deleted as it still contains " +
"spaces or repos")
)

View File

@ -9,7 +9,7 @@ import (
"github.com/harness/gitness/internal/store"
)
// WireSet provides a wire set for this package
// WireSet provides a wire set for this package.
var WireSet = wire.NewSet(
New,
wire.Bind(new(store.SystemStore), new(*SystemStore)),

View File

@ -41,22 +41,23 @@ type (
// SpaceStore defines the space data storage.
SpaceStore interface {
// Finds the space by id.
// Find the space by id.
Find(ctx context.Context, id int64) (*types.Space, error)
// Finds the space by its path.
// FindByPath the space by its path.
FindByPath(ctx context.Context, path string) (*types.Space, error)
// Creates a new space
// Create creates a new space
Create(ctx context.Context, space *types.Space) error
// Moves an existing space.
Move(ctx context.Context, userId int64, spaceId int64, newParentId int64, newName string, keepAsAlias bool) (*types.Space, error)
// Move moves an existing space.
Move(ctx context.Context, userID int64, spaceID int64, newParentID int64, newName string,
keepAsAlias bool) (*types.Space, error)
// Updates the space details.
// Update updates the space details.
Update(ctx context.Context, space *types.Space) error
// Deletes the space.
// Delete deletes the space.
Delete(ctx context.Context, id int64) error
// Count the child spaces of a space.
@ -65,50 +66,51 @@ type (
// List returns a list of child spaces in a space.
List(ctx context.Context, id int64, opts *types.SpaceFilter) ([]*types.Space, error)
// List returns a list of all paths of a space.
// ListAllPaths returns a list of all paths of a space.
ListAllPaths(ctx context.Context, id int64, opts *types.PathFilter) ([]*types.Path, error)
// Create an alias for a space
CreatePath(ctx context.Context, spaceId int64, params *types.PathParams) (*types.Path, error)
// CreatePath create an alias for a space
CreatePath(ctx context.Context, spaceID int64, params *types.PathParams) (*types.Path, error)
// Delete an alias of a space
DeletePath(ctx context.Context, spaceId int64, pathId int64) error
// DeletePath delete an alias of a space
DeletePath(ctx context.Context, spaceID int64, pathID int64) error
}
// RepoStore defines the repository data storage.
RepoStore interface {
// Finds the repo by id.
// Find the repo by id.
Find(ctx context.Context, id int64) (*types.Repository, error)
// Finds the repo by path.
// FindByPath the repo by path.
FindByPath(ctx context.Context, path string) (*types.Repository, error)
// Creates a new repo
// Create a new repo
Create(ctx context.Context, repo *types.Repository) error
// Moves an existing repo.
Move(ctx context.Context, userId int64, repoId int64, newSpaceId int64, newName string, keepAsAlias bool) (*types.Repository, error)
// Move moves an existing repo.
Move(ctx context.Context, userID int64, repoID int64, newSpaceID int64, newName string,
keepAsAlias bool) (*types.Repository, error)
// Updates the repo details.
// Update the repo details.
Update(ctx context.Context, repo *types.Repository) error
// Deletes the repo.
// Delete the repo.
Delete(ctx context.Context, id int64) error
// Count of repos in a space.
Count(ctx context.Context, spaceId int64) (int64, error)
Count(ctx context.Context, spaceID int64) (int64, error)
// List returns a list of repos in a space.
List(ctx context.Context, spaceId int64, opts *types.RepoFilter) ([]*types.Repository, error)
List(ctx context.Context, spaceID int64, opts *types.RepoFilter) ([]*types.Repository, error)
// List returns a list of all alias paths of a repo.
// ListAllPaths returns a list of all alias paths of a repo.
ListAllPaths(ctx context.Context, id int64, opts *types.PathFilter) ([]*types.Path, error)
// Create an alias for a repo
CreatePath(ctx context.Context, repoId int64, params *types.PathParams) (*types.Path, error)
// CreatePath an alias for a repo
CreatePath(ctx context.Context, repoID int64, params *types.PathParams) (*types.Path, error)
// Delete an alias of a repo
DeletePath(ctx context.Context, repoId int64, pathId int64) error
// DeletePath delete an alias of a repo
DeletePath(ctx context.Context, repoID int64, pathID int64) error
}
// SystemStore defines internal system metadata storage.

View File

@ -80,7 +80,7 @@ func TestTokenNotExpired(t *testing.T) {
return
}
token_, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(token *jwt.Token) (interface{}, error) {
token, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(token *jwt.Token) (interface{}, error) {
sub := token.Claims.(*Claims).Subject
id, _ := strconv.ParseInt(sub, 10, 64)
if id != 42 {
@ -93,15 +93,14 @@ func TestTokenNotExpired(t *testing.T) {
return
}
if claims, ok := token_.Claims.(*Claims); ok {
if claims.ExpiresAt > 0 {
if time.Now().Unix() > claims.ExpiresAt {
t.Errorf("expect token not expired")
}
} else {
t.Errorf("expect token expiration greater than zero")
}
} else {
claims, ok := token.Claims.(*Claims)
if !ok {
t.Errorf("expect token claims from token")
return
}
if claims.ExpiresAt > 0 && time.Now().Unix() > claims.ExpiresAt {
t.Errorf("expect token not expired")
return
}
}

View File

@ -5,6 +5,7 @@
package mocks
import (
context "context"
reflect "reflect"
gomock "github.com/golang/mock/gomock"
@ -35,135 +36,135 @@ func (m *MockClient) EXPECT() *MockClientMockRecorder {
}
// Login mocks base method.
func (m *MockClient) Login(arg0, arg1 string) (*types.Token, error) {
func (m *MockClient) Login(arg0 context.Context, arg1, arg2 string) (*types.Token, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Login", arg0, arg1)
ret := m.ctrl.Call(m, "Login", arg0, arg1, arg2)
ret0, _ := ret[0].(*types.Token)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Login indicates an expected call of Login.
func (mr *MockClientMockRecorder) Login(arg0, arg1 interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) Login(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Login", reflect.TypeOf((*MockClient)(nil).Login), arg0, arg1)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Login", reflect.TypeOf((*MockClient)(nil).Login), arg0, arg1, arg2)
}
// Register mocks base method.
func (m *MockClient) Register(arg0, arg1 string) (*types.Token, error) {
func (m *MockClient) Register(arg0 context.Context, arg1, arg2 string) (*types.Token, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Register", arg0, arg1)
ret := m.ctrl.Call(m, "Register", arg0, arg1, arg2)
ret0, _ := ret[0].(*types.Token)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Register indicates an expected call of Register.
func (mr *MockClientMockRecorder) Register(arg0, arg1 interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) Register(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Register", reflect.TypeOf((*MockClient)(nil).Register), arg0, arg1)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Register", reflect.TypeOf((*MockClient)(nil).Register), arg0, arg1, arg2)
}
// Self mocks base method.
func (m *MockClient) Self() (*types.User, error) {
func (m *MockClient) Self(arg0 context.Context) (*types.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Self")
ret := m.ctrl.Call(m, "Self", arg0)
ret0, _ := ret[0].(*types.User)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Self indicates an expected call of Self.
func (mr *MockClientMockRecorder) Self() *gomock.Call {
func (mr *MockClientMockRecorder) Self(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Self", reflect.TypeOf((*MockClient)(nil).Self))
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Self", reflect.TypeOf((*MockClient)(nil).Self), arg0)
}
// Token mocks base method.
func (m *MockClient) Token() (*types.Token, error) {
func (m *MockClient) Token(arg0 context.Context) (*types.Token, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Token")
ret := m.ctrl.Call(m, "Token", arg0)
ret0, _ := ret[0].(*types.Token)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Token indicates an expected call of Token.
func (mr *MockClientMockRecorder) Token() *gomock.Call {
func (mr *MockClientMockRecorder) Token(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Token", reflect.TypeOf((*MockClient)(nil).Token))
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Token", reflect.TypeOf((*MockClient)(nil).Token), arg0)
}
// User mocks base method.
func (m *MockClient) User(arg0 string) (*types.User, error) {
func (m *MockClient) User(arg0 context.Context, arg1 string) (*types.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "User", arg0)
ret := m.ctrl.Call(m, "User", arg0, arg1)
ret0, _ := ret[0].(*types.User)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// User indicates an expected call of User.
func (mr *MockClientMockRecorder) User(arg0 interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) User(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "User", reflect.TypeOf((*MockClient)(nil).User), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "User", reflect.TypeOf((*MockClient)(nil).User), arg0, arg1)
}
// UserCreate mocks base method.
func (m *MockClient) UserCreate(arg0 *types.User) (*types.User, error) {
func (m *MockClient) UserCreate(arg0 context.Context, arg1 *types.User) (*types.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UserCreate", arg0)
ret := m.ctrl.Call(m, "UserCreate", arg0, arg1)
ret0, _ := ret[0].(*types.User)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UserCreate indicates an expected call of UserCreate.
func (mr *MockClientMockRecorder) UserCreate(arg0 interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) UserCreate(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UserCreate", reflect.TypeOf((*MockClient)(nil).UserCreate), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UserCreate", reflect.TypeOf((*MockClient)(nil).UserCreate), arg0, arg1)
}
// UserDelete mocks base method.
func (m *MockClient) UserDelete(arg0 string) error {
func (m *MockClient) UserDelete(arg0 context.Context, arg1 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UserDelete", arg0)
ret := m.ctrl.Call(m, "UserDelete", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// UserDelete indicates an expected call of UserDelete.
func (mr *MockClientMockRecorder) UserDelete(arg0 interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) UserDelete(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UserDelete", reflect.TypeOf((*MockClient)(nil).UserDelete), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UserDelete", reflect.TypeOf((*MockClient)(nil).UserDelete), arg0, arg1)
}
// UserList mocks base method.
func (m *MockClient) UserList(arg0 types.Params) ([]*types.User, error) {
func (m *MockClient) UserList(arg0 context.Context, arg1 types.Params) ([]types.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UserList", arg0)
ret0, _ := ret[0].([]*types.User)
ret := m.ctrl.Call(m, "UserList", arg0, arg1)
ret0, _ := ret[0].([]types.User)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UserList indicates an expected call of UserList.
func (mr *MockClientMockRecorder) UserList(arg0 interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) UserList(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UserList", reflect.TypeOf((*MockClient)(nil).UserList), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UserList", reflect.TypeOf((*MockClient)(nil).UserList), arg0, arg1)
}
// UserUpdate mocks base method.
func (m *MockClient) UserUpdate(arg0 string, arg1 *types.UserInput) (*types.User, error) {
func (m *MockClient) UserUpdate(arg0 context.Context, arg1 string, arg2 *types.UserInput) (*types.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UserUpdate", arg0, arg1)
ret := m.ctrl.Call(m, "UserUpdate", arg0, arg1, arg2)
ret0, _ := ret[0].(*types.User)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UserUpdate indicates an expected call of UserUpdate.
func (mr *MockClientMockRecorder) UserUpdate(arg0, arg1 interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) UserUpdate(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UserUpdate", reflect.TypeOf((*MockClient)(nil).UserUpdate), arg0, arg1)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UserUpdate", reflect.TypeOf((*MockClient)(nil).UserUpdate), arg0, arg1, arg2)
}

View File

@ -6,31 +6,26 @@ package types
import "github.com/harness/gitness/types/enum"
/*
* Represents a permission check.
*/
// PermissionCheck represents a permission check.
type PermissionCheck struct {
Scope Scope
Resource Resource
Permission enum.Permission
}
/*
* Represents the resource of a permission check.
* Note: Keep the name empty in case access is requested for all resources of that type.
*/
// Resource represents the resource of a permission check.
// Note: Keep the name empty in case access is requested for all resources of that type.
type Resource struct {
Type enum.ResourceType
Name string
}
/*
* Represents the scope of a permission check.
* Notes:
* - In case the permission check is for resource REPO, keep repo empty (repo is resource, not scope)
* - In case the permission check is for resource SPACE, SpacePath is an ancestor of the space (space is resource, not scope)
* - Repo isn't use as of now (will be useful once we add access control for repo child resources, e.g. branches)
*/
// Scope represents the scope of a permission check
// Notes:
// - In case the permission check is for resource REPO, keep repo empty (repo is resource, not scope)
// - In case the permission check is for resource SPACE, SpacePath is an ancestor of the space (space is
// resource, not scope)
// - Repo isn't use as of now (will be useful once we add access control for repo child resources, e.g. branches).
type Scope struct {
SpacePath string
Repo string

View File

@ -20,11 +20,18 @@ const (
)
var (
ErrNameLength = &CheckError{fmt.Sprintf("Name has to be between %d and %d in length.", minNameLength, maxNameLength)}
ErrNameRegex = &CheckError{"Name has 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-z0-9-_]."}
ErrDisplayNameLength = &CheckError{fmt.Sprintf("Display name has to be between %d and %d in length.", minDisplayNameLength, maxDisplayNameLength)}
ErrDisplayNameRegex = &CheckError{"Display name has start with a letter and only contain the following [a-zA-Z0-9-_ ]."}
ErrDisplayNameLength = &ValidationError{
fmt.Sprintf("Display name has to be between %d and %d in length.",
minDisplayNameLength, maxDisplayNameLength),
}
ErrDisplayNameRegex = &ValidationError{
"Display name has start with a letter and only contain the following [a-zA-Z0-9-_ ].",
}
)
// Name checks the provided name and returns an error in it isn't valid.

View File

@ -4,34 +4,34 @@
package check
import "errors"
var (
ErrAny = &CheckError{}
ErrAny = &ValidationError{}
)
/*
* An error returned by check methods for any validation errors
* WARNING: This error will be printed to the user as is!
*/
type CheckError struct {
// ValidationError is error returned by check methods for any validation errors
// WARNING: This error will be printed to the user as is!
type ValidationError struct {
msg string
}
func (e *CheckError) Error() string {
func (e *ValidationError) Error() string {
return e.msg
}
func (e *CheckError) Is(target error) bool {
// If the caller is checking for any CheckError, return true
if target == ErrAny {
func (e *ValidationError) Is(target error) bool {
// If the caller is checking for any ValidationError, return true
if errors.Is(target, ErrAny) {
return true
}
// ensure it's the correct type
v, ok := target.(*CheckError)
if !ok {
err := &ValidationError{}
if !errors.As(target, &err) {
return false
}
// only the same if the message is the same
return e.msg == v.msg
return e.msg == err.msg
}

View File

@ -19,21 +19,30 @@ const (
)
var (
ErrPathEmpty = &CheckError{"Path can't be empty."}
ErrPathInvalidSize = &CheckError{fmt.Sprintf("A path has to be between %d and %d segments long (%d for spaces).", minPathSegments, maxPathSegments, maxPathSegmentsForSpace)}
ErrEmptyPathSegment = &CheckError{"Empty segments are not allowed."}
ErrPathCantBeginOrEndWithSeparator = &CheckError{fmt.Sprintf("Path can't start or end with the separator ('%s').", types.PathSeparator)}
ErrPathDifferentTopLevelSpace = &CheckError{"Alias paths have to stay within the same top level space."}
ErrTopLevelPathNotAllowed = &CheckError{"Top level alias paths are not allowed."}
ErrPathEmpty = &ValidationError{
"Path can't be empty.",
}
ErrPathInvalidSize = &ValidationError{
fmt.Sprintf("A path has to be between %d and %d segments long (%d for spaces).",
minPathSegments, maxPathSegments, maxPathSegmentsForSpace),
}
ErrEmptyPathSegment = &ValidationError{
"Empty segments are not allowed.",
}
ErrPathCantBeginOrEndWithSeparator = &ValidationError{
fmt.Sprintf("Path can't start or end with the separator ('%s').", types.PathSeparator),
}
ErrPathDifferentTopLevelSpace = &ValidationError{
"Alias paths have to stay within the same top level space.",
}
ErrTopLevelPathNotAllowed = &ValidationError{
"Top level alias paths are not allowed.",
}
)
/*
* Path checks the provided path and returns an error in it isn't valid.
*
* NOTE: A repository path can be one deeper than a space path (as otherwise the space would be useless)
*/
// Path checks the provided path and returns an error in it isn't valid
// NOTE: A repository path can be one deeper than a space path (as otherwise the space would be useless).
func Path(path string, isSpace bool) error {
if path == "" {
return ErrPathEmpty
}
@ -62,17 +71,16 @@ func Path(path string, isSpace bool) error {
return nil
}
/*
* Validates a PathParams object that is used to create a new path.
*
* NOTES:
* - We don't allow top level alias paths
* - An alias path has to stay within the same top level space
*
* IMPORTANT:
* Technically there can be a racing condition when a space is being moved inbetween the validation and path creation.
* But that is fine, as the path could've also been created a second earlier when it was still valid and would then still exist.
*/
// PathParams validates a PathParams object that is used to create a new path.
// NOTES:
// - We don't allow top level alias paths
// - An alias path has to stay within the same top level space
//
// IMPORTANT:
// - Technically there can be a racing condition when a space is being moved in between the validation and
// path creation.
// - But that is fine, as the path could've also been created a second earlier when it was still valid and would
// then still exist.
func PathParams(path *types.PathParams, currentPath string, isSpace bool) error {
// ensure the path is valid
if err := Path(path.Path, isSpace); err != nil {
@ -93,11 +101,8 @@ func PathParams(path *types.PathParams, currentPath string, isSpace bool) error
return nil
}
/*
* Checks if the provided path is too long.
*
* NOTE: A repository path can be one deeper than a space path (as otherwise the space would be useless)
*/
// PathTooLong Checks if the provided path is too long.
// NOTE: A repository path can be one deeper than a space path (as otherwise the space would be useless).
func PathTooLong(path string, isSpace bool) bool {
l := strings.Count(path, types.PathSeparator) + 1
return (!isSpace && l > maxPathSegments) || (isSpace && l > maxPathSegmentsForSpace)

View File

@ -9,7 +9,9 @@ import (
)
var (
ErrRepositoryRequiresSpaceId = &CheckError{"SpaceId required - Repositories don't exist outside of a space."}
ErrRepositoryRequiresSpaceID = &ValidationError{
"SpaceID required - Repositories don't exist outside of a space.",
}
)
// Repo checks the provided repository and returns an error in it isn't valid.
@ -25,8 +27,8 @@ func Repo(repo *types.Repository) error {
}
// validate repo within a space
if repo.SpaceId <= 0 {
return ErrRepositoryRequiresSpaceId
if repo.SpaceID <= 0 {
return ErrRepositoryRequiresSpaceID
}
return nil

View File

@ -14,11 +14,15 @@ import (
var (
illegalRootSpaceNames = []string{"api"}
ErrRootSpaceNameNotAllowed = &CheckError{fmt.Sprintf("The following names are not allowed for a root space: %v", illegalRootSpaceNames)}
ErrInvalidParentSpaceId = &CheckError{"Parent space ID has to be either zero for a root space or greater than zero for a child space."}
ErrRootSpaceNameNotAllowed = &ValidationError{
fmt.Sprintf("The following names are not allowed for a root space: %v", illegalRootSpaceNames),
}
ErrInvalidParentSpaceID = &ValidationError{
"Parent space ID has to be either zero for a root space or greater than zero for a child space.",
}
)
// Repo checks the provided space and returns an error in it isn't valid.
// Space checks the provided space and returns an error in it isn't valid.
func Space(space *types.Space) error {
// validate name
if err := Name(space.Name); err != nil {
@ -30,12 +34,12 @@ func Space(space *types.Space) error {
return err
}
if space.ParentId < 0 {
return ErrInvalidParentSpaceId
if space.ParentID < 0 {
return ErrInvalidParentSpaceID
}
// root space specific validations
if space.ParentId == 0 {
if space.ParentID == 0 {
for _, p := range illegalRootSpaceNames {
if strings.HasPrefix(space.Name, p) {
return ErrRootSpaceNameNotAllowed

View File

@ -18,15 +18,17 @@ const (
var (
// ErrEmailLen is returned when the email address
// exceeds the maximum number of characters.
ErrEmailLen = &CheckError{fmt.Sprintf("Email address has to be within %d and %d characters", minEmailLength, maxEmailLength)}
ErrEmailLen = &ValidationError{
fmt.Sprintf("Email address has to be within %d and %d characters", minEmailLength, maxEmailLength),
}
)
// User returns true if the User if valid.
func User(user *types.User) (bool, error) {
func User(user *types.User) error {
// validate email
l := len(user.Email)
if l < minEmailLength || l > maxEmailLength {
return false, ErrEmailLen
return ErrEmailLen
}
return true, nil
return nil
}

View File

@ -5,6 +5,7 @@
package check
import (
"errors"
"testing"
"github.com/harness/gitness/types"
@ -14,20 +15,15 @@ func TestUser(t *testing.T) {
tests := []struct {
email string
error error
valid bool
}{
{
email: "jane@gmail.com",
valid: true,
},
}
for _, test := range tests {
user := &types.User{Email: test.email}
ok, err := User(user)
if got, want := ok, test.valid; got != want {
t.Errorf("Want user %s is valid %v, got %v", test.email, want, got)
}
if got, want := err, test.error; got != want {
err := User(user)
if got, want := err, test.error; !errors.Is(got, want) {
t.Errorf("Want user %s error %v, got %v", test.email, want, got)
}
}

View File

@ -40,7 +40,7 @@ type Config struct {
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"`
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"`

View File

@ -4,7 +4,7 @@
package enum
// Represents the different types of resources that can be guarded with permissions.
// ResourceType represents the different types of resources that can be guarded with permissions.
type ResourceType string
const (
@ -13,36 +13,33 @@ const (
// ResourceType_Branch ResourceType = "BRANCH"
)
// Represents the available permissions
// Permission represents the available permissions.
type Permission string
const (
// ----- SPACE -----
/*
----- SPACE -----
*/
PermissionSpaceCreate Permission = "space_create"
PermissionSpaceView Permission = "space_view"
PermissionSpaceEdit Permission = "space_edit"
PermissionSpaceDelete Permission = "space_delete"
)
// ----- REPOSITORY -----
const (
/*
----- REPOSITORY -----
*/
PermissionRepoCreate Permission = "repository_create"
PermissionRepoView Permission = "repository_view"
PermissionRepoEdit Permission = "repository_edit"
PermissionRepoDelete Permission = "repository_delete"
// ----- BRANCH -----
// PermissionBranchCreate Permission = "branch_create"
// PermissionBranchView Permission = "branch_view"
// PermissionBranchEdit Permission = "branch_edit"
// PermissionBranchDelete Permission = "branch_delete"
)
// Represents the type of the entity requesting permission
// PrincipalType represents the type of the entity requesting permission.
type PrincipalType string
const (
// Represents actions executed by a loged-in user
// PrincipalTypeUser represents actions executed by a logged-in user.
PrincipalTypeUser PrincipalType = "USER"
// Represents actions executed by an entity with an api key
PrincipalTypeApiKey PrincipalType = "API_KEY"
)

Some files were not shown because too many files have changed in this diff Show More