Improve error handling to match go standards - don't wrap and rethrow, but log and return. Also adds some more validations for path creation and resource moving. Add accesslogging for git and api router (#14)

This commit contains the following:
- Improve and simplify error handling (remove unnecessary wrappers, make it feel like go)
- Add extra validation for path creation and resource moving (path has to be within same top space, no top space alias allowed)
- Add access logging for rest api and git api
This commit is contained in:
Johannes Batzill 2022-09-09 22:08:46 -07:00 committed by GitHub
parent 4812beedc6
commit b7b9f53b0d
69 changed files with 597 additions and 710 deletions

View File

@ -1,10 +0,0 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package comms
const (
Internal = "Internal error occured - Please contact operator for more information."
AuthenticationRequired = "Action requires authentication."
)

View File

@ -8,17 +8,20 @@ import (
"fmt"
"net/http"
"github.com/harness/gitness/internal/api/comms"
"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/internal/errs"
"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.")
)
type Guard struct {
authorizer authz.Authorizer
}
@ -35,12 +38,12 @@ func (g *Guard) EnforceAdmin(next http.Handler) http.Handler {
ctx := r.Context()
user, ok := request.UserFrom(ctx)
if !ok {
render.Unauthorizedf(w, comms.AuthenticationRequired)
render.Unauthorized(w)
return
}
if !user.Admin {
render.Forbiddenf(w, "Action requires admin privileges.")
render.Forbidden(w)
return
}
@ -56,7 +59,7 @@ func (g *Guard) EnforceAuthenticated(next http.Handler) http.Handler {
ctx := r.Context()
_, ok := request.UserFrom(ctx)
if !ok {
render.Unauthorizedf(w, comms.AuthenticationRequired)
render.Unauthorized(w)
return
}
@ -73,19 +76,21 @@ func (g *Guard) Enforce(w http.ResponseWriter, r *http.Request, scope *types.Sco
err := g.Check(r, scope, resource, permission)
// render error if needed
if errors.Is(err, errs.NotAuthenticated) {
render.Unauthorizedf(w, comms.AuthenticationRequired)
} else if errors.Is(err, errs.NotAuthorized) {
render.Forbiddenf(w, "User not authorized to perform %s on resource %v in scope %v",
if errors.Is(err, ErrNotAuthenticated) {
render.ErrorObject(w, http.StatusUnauthorized, render.ErrUnauthorized)
} else if 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,
resource,
scope)
render.Forbidden(w)
} else if err != nil {
// log err for debugging
log := hlog.FromRequest(r)
log.Err(err).Msg("Encountered unexpected error while enforcing permission.")
hlog.FromRequest(r).Err(err).Msg("Encountered unexpected error while enforcing permission.")
render.InternalErrorf(w, comms.Internal)
render.InternalError(w)
}
return err == nil
@ -99,7 +104,7 @@ func (g *Guard) Enforce(w http.ResponseWriter, r *http.Request, scope *types.Sco
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 {
return errs.NotAuthenticated
return ErrNotAuthenticated
}
// TODO: don't hardcode principal type USER
@ -110,11 +115,11 @@ func (g *Guard) Check(r *http.Request, scope *types.Scope, resource *types.Resou
resource,
permission)
if err != nil {
return errors.Wrap(err, "Authorization check failed")
return err
}
if !authorized {
return errs.NotAuthorized
return ErrNotAuthorized
}
return nil

View File

@ -7,7 +7,6 @@ package guard
import (
"net/http"
"github.com/harness/gitness/internal/api/comms"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/api/request"
"github.com/harness/gitness/internal/paths"
@ -15,7 +14,6 @@ import (
"github.com/harness/gitness/types/enum"
"github.com/pkg/errors"
"github.com/rs/zerolog/hlog"
"github.com/rs/zerolog/log"
)
/*
@ -44,11 +42,9 @@ func (g *Guard) Repo(permission enum.Permission, orPublic bool, guarded http.Han
rep, ok := request.RepoFrom(ctx)
if !ok {
// log error for debugging
log := hlog.FromRequest(r)
log.Error().Msg("Method expects the repository to be availabe in the request context, but it wasnt.")
render.InternalErrorf(w, comms.Internal)
hlog.FromRequest(r).Error().Msg("Method expects the repository to be availabe in the request context, but it wasnt.")
render.InternalError(w)
return
}
@ -70,10 +66,9 @@ func (g *Guard) EnforceRepo(w http.ResponseWriter, r *http.Request, permission e
spacePath, name, err := paths.Disect(path)
if err != nil {
// log error for debugging
hlog.FromRequest(r)
log.Err(err).Msgf("Failed to disect path '%s'.", path)
hlog.FromRequest(r).Err(err).Msgf("Failed to disect path '%s'.", path)
render.InternalErrorf(w, comms.Internal)
render.InternalError(w)
return false
}

View File

@ -7,7 +7,6 @@ package guard
import (
"net/http"
"github.com/harness/gitness/internal/api/comms"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/api/request"
"github.com/harness/gitness/internal/paths"
@ -43,7 +42,10 @@ func (g *Guard) Space(permission enum.Permission, orPublic bool, guarded http.Ha
ctx := r.Context()
s, ok := request.SpaceFrom(ctx)
if !ok {
render.InternalError(w, errors.New("Expected space to be available"))
// log error for debugging
hlog.FromRequest(r).Error().Msg("Method expects the space to be availabe in the request context, but it wasnt.")
render.InternalError(w)
return
}
@ -68,7 +70,7 @@ func (g *Guard) EnforceSpace(w http.ResponseWriter, r *http.Request, permission
hlog.FromRequest(r)
log.Err(err).Msgf("Failed to disect path '%s'.", path)
render.InternalErrorf(w, comms.Internal)
render.InternalError(w)
return false
}

View File

@ -33,7 +33,7 @@ func HandleLogin(users store.UserStore, system store.SystemStore) http.HandlerFu
Msg("cannot find user")
// always give not found error as extra security measurement.
render.NotFoundf(w, "Invalid email or password")
render.NotFound(w)
return
}
@ -46,7 +46,7 @@ func HandleLogin(users store.UserStore, system store.SystemStore) http.HandlerFu
Str("user", username).
Msg("invalid password")
render.NotFoundf(w, "Invalid email or password")
render.NotFound(w)
return
}
@ -57,23 +57,24 @@ func HandleLogin(users store.UserStore, system store.SystemStore) http.HandlerFu
Str("user", username).
Msg("failed to generate token")
render.InternalErrorf(w, "Failed to create session")
render.InternalError(w)
return
}
// return the token if the with_user boolean
// query parameter is set to true.
if r.FormValue("return_user") == "true" {
render.JSON(w, &types.UserToken{
User: user,
Token: &types.Token{
Value: token_,
Expires: expires.UTC(),
},
}, 200)
render.JSON(w, http.StatusOK,
&types.UserToken{
User: user,
Token: &types.Token{
Value: token_,
Expires: expires.UTC(),
},
})
} else {
// else return the token only.
render.JSON(w, &types.Token{Value: token_}, 200)
render.JSON(w, http.StatusOK, &types.Token{Value: token_})
}
}
}

View File

@ -8,9 +8,7 @@ import (
"net/http"
"time"
"github.com/harness/gitness/internal/api/comms"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/errs"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/internal/token"
"github.com/harness/gitness/types"
@ -37,7 +35,7 @@ func HandleRegister(users store.UserStore, system store.SystemStore) http.Handle
Str("email", username).
Msg("Failed to hash password")
render.InternalErrorf(w, comms.Internal)
render.InternalError(w)
return
}
@ -56,7 +54,7 @@ func HandleRegister(users store.UserStore, system store.SystemStore) http.Handle
Str("email", username).
Msg("invalid user input")
render.BadRequest(w, err)
render.UserfiedErrorOrInternal(w, err)
return
}
@ -65,7 +63,7 @@ func HandleRegister(users store.UserStore, system store.SystemStore) http.Handle
Str("email", username).
Msg("Failed to create user")
render.InternalError(w, errs.Internal)
render.InternalError(w)
return
}
@ -80,7 +78,7 @@ func HandleRegister(users store.UserStore, system store.SystemStore) http.Handle
Int64("user_id", user.ID).
Msg("Failed to enable admin user")
render.InternalError(w, errs.Internal)
render.InternalError(w)
return
}
}
@ -93,23 +91,23 @@ func HandleRegister(users store.UserStore, system store.SystemStore) http.Handle
Int64("user_id", user.ID).
Msg("Failed to generate token")
render.InternalError(w, errs.Internal)
render.InternalError(w)
return
}
// return the token if the with_user boolean
// query parameter is set to true.
if r.FormValue("return_user") == "true" {
render.JSON(w, &types.UserToken{
render.JSON(w, http.StatusOK, &types.UserToken{
User: user,
Token: &types.Token{
Value: token_,
Expires: expires.UTC(),
},
}, 200)
})
} else {
// else return the token only.
render.JSON(w, &types.Token{Value: token_}, 200)
render.JSON(w, http.StatusOK, &types.Token{Value: token_})
}
}
}

View File

@ -6,17 +6,13 @@ package repo
import (
"encoding/json"
"errors"
"net/http"
"strings"
"time"
"github.com/harness/gitness/internal/api/comms"
"github.com/harness/gitness/internal/api/guard"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/api/request"
"github.com/harness/gitness/internal/errs"
"github.com/harness/gitness/internal/paths"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/check"
@ -58,13 +54,10 @@ func HandleCreate(guard *guard.Guard, spaces store.SpaceStore, repos store.RepoS
}
parentSpace, err := spaces.Find(ctx, in.SpaceId)
if errors.Is(err, errs.ResourceNotFound) {
render.NotFoundf(w, "Provided space wasn't found.")
return
} else if err != nil {
log.Err(err).Msgf("Failed to get space with id '%s'.", in.SpaceId)
if err != nil {
log.Err(err).Msgf("Failed to get space with id '%d'.", in.SpaceId)
render.InternalErrorf(w, comms.Internal)
render.UserfiedErrorOrInternal(w, err)
return
}
@ -102,26 +95,20 @@ func HandleCreate(guard *guard.Guard, spaces store.SpaceStore, repos store.RepoS
// validate repo
if err := check.Repo(repo); err != nil {
render.BadRequest(w, err)
render.UserfiedErrorOrInternal(w, err)
return
}
// create in store
err = repos.Create(ctx, repo)
if errors.Is(err, errs.Duplicate) {
log.Warn().Err(err).
Msg("Repository creation failed as a duplicate was detected.")
render.BadRequestf(w, "Path '%s' already exists.", paths.Concatinate(parentPath, repo.Name))
return
} else if err != nil {
if err != nil {
log.Error().Err(err).
Msg("Repository creation failed.")
render.InternalErrorf(w, comms.Internal)
render.UserfiedErrorOrInternal(w, err)
return
}
render.JSON(w, repo, 200)
render.JSON(w, http.StatusOK, repo)
}
}

View File

@ -6,17 +6,14 @@ package repo
import (
"encoding/json"
"errors"
"net/http"
"strings"
"time"
"github.com/harness/gitness/internal/api/comms"
"github.com/harness/gitness/internal/api/guard"
"github.com/harness/gitness/internal/api/handler/common"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/api/request"
"github.com/harness/gitness/internal/errs"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/check"
@ -52,27 +49,21 @@ func HandleCreatePath(guard *guard.Guard, repos store.RepoStore) http.HandlerFun
}
// validate path
if err = check.PathParams(params.Path, false); err != nil {
render.BadRequest(w, err)
if err = check.PathParams(params, repo.Path, false); err != nil {
render.UserfiedErrorOrInternal(w, err)
return
}
// TODO: ensure user is authorized to create a path pointing to in.Path
path, err := repos.CreatePath(ctx, repo.ID, params)
if errors.Is(err, errs.Duplicate) {
log.Warn().Err(err).
Msg("Failed to create path for repo as a duplicate was detected.")
render.BadRequestf(w, "Path '%s' already exists.", params.Path)
return
} else if err != nil {
if err != nil {
log.Error().Err(err).
Msg("Failed to create path for repo.")
render.InternalErrorf(w, comms.Internal)
render.UserfiedErrorOrInternal(w, err)
return
}
render.JSON(w, path, 200)
render.JSON(w, http.StatusOK, path)
})
}

View File

@ -5,14 +5,11 @@
package repo
import (
"errors"
"net/http"
"github.com/harness/gitness/internal/api/comms"
"github.com/harness/gitness/internal/api/guard"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/api/request"
"github.com/harness/gitness/internal/errs"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/hlog"
@ -31,13 +28,10 @@ func HandleDelete(guard *guard.Guard, repos store.RepoStore) http.HandlerFunc {
repo, _ := request.RepoFrom(ctx)
err := repos.Delete(r.Context(), repo.ID)
if errors.Is(err, errs.ResourceNotFound) {
render.NotFoundf(w, "Repository doesn't exist.")
return
} else if err != nil {
if err != nil {
log.Err(err).Msgf("Failed to delete the Repository.")
render.InternalErrorf(w, comms.Internal)
render.UserfiedErrorOrInternal(w, err)
return
}

View File

@ -5,14 +5,11 @@
package repo
import (
"errors"
"net/http"
"github.com/harness/gitness/internal/api/comms"
"github.com/harness/gitness/internal/api/guard"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/api/request"
"github.com/harness/gitness/internal/errs"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/hlog"
@ -32,23 +29,16 @@ func HandleDeletePath(guard *guard.Guard, repos store.RepoStore) http.HandlerFun
pathId, err := request.GetPathId(r)
if err != nil {
render.BadRequest(w, err)
render.BadRequest(w)
return
}
err = repos.DeletePath(ctx, repo.ID, pathId)
if errors.Is(err, errs.ResourceNotFound) {
render.NotFoundf(w, "Path doesn't exist.")
return
} else if errors.Is(err, errs.PrimaryPathCantBeDeleted) {
render.BadRequestf(w, "Deleting a primary path is not allowed.")
return
} else if err != nil {
if err != nil {
log.Err(err).Int64("path_id", pathId).
Msgf("Failed to delete repo path.")
render.InternalErrorf(w, comms.Internal)
return
render.UserfiedErrorOrInternal(w, err)
}
w.WriteHeader(http.StatusNoContent)

View File

@ -25,6 +25,6 @@ func HandleFind(guard *guard.Guard, repos store.RepoStore) http.HandlerFunc {
ctx := r.Context()
repo, _ := request.RepoFrom(ctx)
render.JSON(w, repo, 200)
render.JSON(w, http.StatusOK, repo)
})
}

View File

@ -7,7 +7,6 @@ package repo
import (
"net/http"
"github.com/harness/gitness/internal/api/comms"
"github.com/harness/gitness/internal/api/guard"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/api/request"
@ -37,10 +36,11 @@ func HandleListPaths(guard *guard.Guard, repos store.RepoStore) http.HandlerFunc
if err != nil {
log.Err(err).Msgf("Failed to get list of repo paths.")
render.InternalErrorf(w, comms.Internal)
render.InternalError(w)
return
}
render.JSON(w, paths, 200)
// TODO: do we need pagination? we should block that many paths in the first place.
render.JSON(w, http.StatusOK, paths)
})
}

View File

@ -6,15 +6,12 @@ package repo
import (
"encoding/json"
"errors"
"net/http"
"strings"
"github.com/harness/gitness/internal/api/comms"
"github.com/harness/gitness/internal/api/guard"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/api/request"
"github.com/harness/gitness/internal/errs"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/check"
@ -61,27 +58,23 @@ func HandleMove(guard *guard.Guard, repos store.RepoStore, spaces store.SpaceSto
// ensure we don't end up in any missconfiguration, and block no-ops
if err = check.Name(*in.Name); err != nil {
render.BadRequest(w, err)
render.UserfiedErrorOrInternal(w, err)
return
} else if *in.SpaceId == repo.SpaceId && *in.Name == repo.Name {
render.BadRequest(w, errs.NoChangeInRequestedMove)
render.BadRequestError(w, render.ErrNoChange)
return
} else if *in.SpaceId <= 0 {
render.BadRequest(w, check.ErrRepositoryRequiresSpaceId)
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 errors.Is(err, errs.ResourceNotFound) {
render.NotFoundf(w, "Parent space not found.")
return
} else if err != nil {
log.Err(err).
Msgf("Failed to get target space with id %d for the move.", *in.SpaceId)
if err != nil {
log.Err(err).Msgf("Failed to get target space with id %d for the move.", *in.SpaceId)
render.InternalErrorf(w, comms.Internal)
render.UserfiedErrorOrInternal(w, err)
return
}
@ -97,20 +90,13 @@ 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)
if errors.Is(err, errs.Duplicate) {
log.Warn().Err(err).
Msg("Failed to move the repo as a duplicate was detected.")
if err != nil {
log.Error().Err(err).Msg("Failed to move the repository.")
render.BadRequestf(w, "Unable to move the repository as the destination path is already taken.")
return
} else if err != nil {
log.Error().Err(err).
Msg("Failed to move the repository.")
render.InternalErrorf(w, comms.Internal)
render.UserfiedErrorOrInternal(w, err)
return
}
render.JSON(w, res, http.StatusOK)
render.JSON(w, http.StatusOK, res)
})
}

View File

@ -9,7 +9,6 @@ import (
"net/http"
"time"
"github.com/harness/gitness/internal/api/comms"
"github.com/harness/gitness/internal/api/guard"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/api/request"
@ -59,19 +58,18 @@ func HandleUpdate(guard *guard.Guard, repos store.RepoStore) http.HandlerFunc {
// ensure provided values are valid
if err := check.Repo(repo); err != nil {
render.BadRequest(w, err)
render.UserfiedErrorOrInternal(w, err)
return
}
err = repos.Update(ctx, repo)
if err != nil {
log.Error().Err(err).
Msg("Repository update failed.")
log.Error().Err(err).Msg("Repository update failed.")
render.InternalErrorf(w, comms.Internal)
render.UserfiedErrorOrInternal(w, err)
return
}
render.JSON(w, repo, http.StatusOK)
render.JSON(w, http.StatusOK, repo)
})
}

View File

@ -6,16 +6,13 @@ package space
import (
"encoding/json"
"errors"
"net/http"
"strings"
"time"
"github.com/harness/gitness/internal/api/comms"
"github.com/harness/gitness/internal/api/guard"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/api/request"
"github.com/harness/gitness/internal/errs"
"github.com/harness/gitness/internal/paths"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/types"
@ -60,19 +57,16 @@ func HandleCreate(guard *guard.Guard, spaces store.SpaceStore) http.HandlerFunc
if in.ParentId <= 0 {
// TODO: Restrict top level space creation.
if usr == nil {
render.Unauthorizedf(w, comms.AuthenticationRequired)
render.Unauthorized(w)
return
}
} else {
// Create is a special case - we need the parent path
parent, err := spaces.Find(ctx, in.ParentId)
if errors.Is(err, errs.ResourceNotFound) {
render.NotFoundf(w, "Provided parent space wasn't found.")
return
} else if err != nil {
log.Err(err).Msgf("Failed to get space with id '%s'.", in.ParentId)
if err != nil {
log.Err(err).Msgf("Failed to get space with id '%d'.", in.ParentId)
render.InternalErrorf(w, comms.Internal)
render.UserfiedErrorOrInternal(w, err)
return
}
@ -102,39 +96,27 @@ func HandleCreate(guard *guard.Guard, spaces store.SpaceStore) http.HandlerFunc
// validate space
if err := check.Space(space); err != nil {
render.BadRequest(w, err)
render.UserfiedErrorOrInternal(w, err)
return
}
// Validate path (Due to racing conditions we can't be 100% sure on the path here only best effort to store 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.PathParams(path, true); err != nil {
render.BadRequest(w, err)
if err = check.Path(path, true); err != nil {
render.UserfiedErrorOrInternal(w, err)
return
}
// create in store
err = spaces.Create(ctx, space)
if errors.Is(err, errs.Duplicate) {
log.Warn().Err(err).
Msg("Space creation failed as a duplicate was detected.")
render.BadRequestf(w, "Path '%s' already exists.", path)
return
} else if errors.Is(err, errs.PathTooLong) {
log.Warn().Err(err).
Msg("Failed to move the space as its path was too long.")
render.BadRequestf(w, "Unable to move the space as the destination path of the space was too long.")
return
} else if err != nil {
if err != nil {
log.Error().Err(err).
Msg("Space creation failed.")
render.InternalErrorf(w, comms.Internal)
render.UserfiedErrorOrInternal(w, err)
return
}
render.JSON(w, space, 200)
render.JSON(w, http.StatusOK, space)
}
}

View File

@ -6,17 +6,14 @@ package space
import (
"encoding/json"
"errors"
"net/http"
"strings"
"time"
"github.com/harness/gitness/internal/api/comms"
"github.com/harness/gitness/internal/api/guard"
"github.com/harness/gitness/internal/api/handler/common"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/api/request"
"github.com/harness/gitness/internal/errs"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/check"
@ -52,27 +49,20 @@ func HandleCreatePath(guard *guard.Guard, spaces store.SpaceStore) http.HandlerF
}
// validate path
if err = check.PathParams(params.Path, true); err != nil {
render.BadRequest(w, err)
if err = check.PathParams(params, space.Path, true); err != nil {
render.UserfiedErrorOrInternal(w, err)
return
}
// TODO: ensure user is authorized to create a path pointing to in.Path
path, err := spaces.CreatePath(ctx, space.ID, params)
if errors.Is(err, errs.Duplicate) {
log.Warn().Err(err).
Msg("Failed to create path for space as a duplicate was detected.")
if err != nil {
log.Error().Err(err).Msg("Failed to create path for space.")
render.BadRequestf(w, "Path '%s' already exists.", params.Path)
return
} else if err != nil {
log.Error().Err(err).
Msg("Failed to create path for space.")
render.InternalErrorf(w, comms.Internal)
render.UserfiedErrorOrInternal(w, err)
return
}
render.JSON(w, path, 200)
render.JSON(w, http.StatusOK, path)
})
}

View File

@ -5,14 +5,11 @@
package space
import (
"errors"
"net/http"
"github.com/harness/gitness/internal/api/comms"
"github.com/harness/gitness/internal/api/guard"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/api/request"
"github.com/harness/gitness/internal/errs"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/hlog"
@ -31,13 +28,10 @@ func HandleDelete(guard *guard.Guard, spaces store.SpaceStore) http.HandlerFunc
s, _ := request.SpaceFrom(ctx)
err := spaces.Delete(r.Context(), s.ID)
if errors.Is(err, errs.ResourceNotFound) {
render.NotFoundf(w, "Space not found.")
return
} else if err != nil {
if err != nil {
log.Err(err).Msgf("Failed to delete the space.")
render.InternalErrorf(w, comms.Internal)
render.UserfiedErrorOrInternal(w, err)
return
}

View File

@ -5,14 +5,11 @@
package space
import (
"errors"
"net/http"
"github.com/harness/gitness/internal/api/comms"
"github.com/harness/gitness/internal/api/guard"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/api/request"
"github.com/harness/gitness/internal/errs"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/hlog"
@ -32,22 +29,16 @@ func HandleDeletePath(guard *guard.Guard, spaces store.SpaceStore) http.HandlerF
pathId, err := request.GetPathId(r)
if err != nil {
render.BadRequest(w, err)
render.BadRequest(w)
return
}
err = spaces.DeletePath(ctx, space.ID, pathId)
if errors.Is(err, errs.ResourceNotFound) {
render.NotFoundf(w, "Path doesn't exist.")
return
} else if errors.Is(err, errs.PrimaryPathCantBeDeleted) {
render.BadRequestf(w, "Deleting a primary path is not allowed.")
return
} else if err != nil {
if err != nil {
log.Err(err).Int64("path_id", pathId).
Msgf("Failed to delete space path.")
render.InternalErrorf(w, comms.Internal)
render.UserfiedErrorOrInternal(w, err)
return
}

View File

@ -25,6 +25,6 @@ func HandleFind(guard *guard.Guard, spaces store.SpaceStore) http.HandlerFunc {
ctx := r.Context()
s, _ := request.SpaceFrom(ctx)
render.JSON(w, s, 200)
render.JSON(w, http.StatusOK, s)
})
}

View File

@ -7,7 +7,6 @@ package space
import (
"net/http"
"github.com/harness/gitness/internal/api/comms"
"github.com/harness/gitness/internal/api/guard"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/api/request"
@ -38,7 +37,7 @@ func HandleList(guard *guard.Guard, spaces store.SpaceStore) http.HandlerFunc {
if err != nil {
log.Err(err).Msgf("Failed to count child spaces.")
render.InternalErrorf(w, comms.Internal)
render.UserfiedErrorOrInternal(w, err)
return
}
@ -46,7 +45,7 @@ func HandleList(guard *guard.Guard, spaces store.SpaceStore) http.HandlerFunc {
if err != nil {
log.Err(err).Msgf("Failed to list child spaces.")
render.InternalErrorf(w, comms.Internal)
render.UserfiedErrorOrInternal(w, err)
return
}
@ -54,6 +53,8 @@ func HandleList(guard *guard.Guard, spaces store.SpaceStore) http.HandlerFunc {
* Only list spaces that are either public or can be accessed by the user
*
* TODO: optimize by making a single auth check for all spaces at once.
* TODO: maybe ommit permission check for performance.
* TODO: count is off in case not all repos are accessible.
*/
result := make([]*types.Space, 0, len(allSpaces))
for _, cs := range allSpaces {
@ -70,6 +71,6 @@ func HandleList(guard *guard.Guard, spaces store.SpaceStore) http.HandlerFunc {
}
render.Pagination(r, w, params.Page, params.Size, int(count))
render.JSON(w, result, 200)
render.JSON(w, http.StatusOK, result)
})
}

View File

@ -7,7 +7,6 @@ package space
import (
"net/http"
"github.com/harness/gitness/internal/api/comms"
"github.com/harness/gitness/internal/api/guard"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/api/request"
@ -37,11 +36,11 @@ func HandleListPaths(guard *guard.Guard, spaces store.SpaceStore) http.HandlerFu
if err != nil {
log.Err(err).Msgf("Failed to get list of space paths.")
render.InternalErrorf(w, comms.Internal)
render.UserfiedErrorOrInternal(w, err)
return
}
// TODO: do we need pagination? we should block that many paths in the first place.
render.JSON(w, paths, 200)
render.JSON(w, http.StatusOK, paths)
})
}

View File

@ -7,7 +7,6 @@ package space
import (
"net/http"
"github.com/harness/gitness/internal/api/comms"
"github.com/harness/gitness/internal/api/guard"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/api/request"
@ -38,7 +37,7 @@ func HandleListRepos(guard *guard.Guard, repos store.RepoStore) http.HandlerFunc
if err != nil {
log.Err(err).Msgf("Failed to count child repos.")
render.InternalErrorf(w, comms.Internal)
render.UserfiedErrorOrInternal(w, err)
return
}
@ -46,7 +45,7 @@ func HandleListRepos(guard *guard.Guard, repos store.RepoStore) http.HandlerFunc
if err != nil {
log.Err(err).Msgf("Failed to list child repos.")
render.InternalErrorf(w, comms.Internal)
render.UserfiedErrorOrInternal(w, err)
return
}
@ -54,6 +53,8 @@ func HandleListRepos(guard *guard.Guard, repos store.RepoStore) http.HandlerFunc
* Only list repos that are either public or can be accessed by the user
*
* TODO: optimize by making a single auth check for all repos at once.
* TODO: maybe ommit permission check for performance.
* TODO: count is off in case not all repos are accessible.
*/
result := make([]*types.Repository, 0, len(allRepos))
for _, rep := range allRepos {
@ -71,6 +72,6 @@ func HandleListRepos(guard *guard.Guard, repos store.RepoStore) http.HandlerFunc
}
render.Pagination(r, w, params.Page, params.Size, int(count))
render.JSON(w, result, 200)
render.JSON(w, http.StatusOK, result)
})
}

View File

@ -6,15 +6,12 @@ package space
import (
"encoding/json"
"errors"
"net/http"
"strings"
"github.com/harness/gitness/internal/api/comms"
"github.com/harness/gitness/internal/api/guard"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/api/request"
"github.com/harness/gitness/internal/errs"
"github.com/harness/gitness/internal/paths"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/types"
@ -62,10 +59,10 @@ func HandleMove(guard *guard.Guard, spaces store.SpaceStore) http.HandlerFunc {
// ensure we don't end up in any missconfiguration, and block no-ops
if err = check.Name(*in.Name); err != nil {
render.BadRequest(w, err)
render.UserfiedErrorOrInternal(w, err)
return
} else if *in.ParentId == space.ParentId && *in.Name == space.Name {
render.BadRequest(w, errs.NoChangeInRequestedMove)
render.BadRequestError(w, render.ErrNoChange)
return
}
@ -73,14 +70,11 @@ func HandleMove(guard *guard.Guard, spaces store.SpaceStore) http.HandlerFunc {
// 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 errors.Is(err, errs.ResourceNotFound) {
render.NotFoundf(w, "Parent space not found.")
return
} else if err != nil {
if err != nil {
log.Err(err).
Msgf("Failed to get target space with id %d for the move.", *in.ParentId)
render.InternalErrorf(w, comms.Internal)
render.UserfiedErrorOrInternal(w, err)
return
}
@ -94,37 +88,24 @@ func HandleMove(guard *guard.Guard, spaces store.SpaceStore) http.HandlerFunc {
}
/*
* Validate path (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)
if err = check.PathParams(path, true); err != nil {
render.BadRequest(w, err)
if err = check.Path(path, true); err != nil {
render.UserfiedErrorOrInternal(w, err)
return
}
}
res, err := spaces.Move(ctx, usr.ID, space.ID, *in.ParentId, *in.Name, in.KeepAsAlias)
if errors.Is(err, errs.Duplicate) {
log.Warn().Err(err).
Msg("Failed to move the space as a duplicate was detected.")
if err != nil {
log.Error().Err(err).Msg("Failed to move the space.")
render.BadRequestf(w, "Unable to move the space as the destination path is already taken.")
return
} else if errors.Is(err, errs.PathTooLong) {
log.Warn().Err(err).
Msg("Failed to move the space as a path was too long.")
render.BadRequestf(w, "Unable to move the space as the destination path of the space or one of its child resources was too long.")
return
} else if err != nil {
log.Error().Err(err).
Msg("Failed to move the space.")
render.InternalErrorf(w, comms.Internal)
render.UserfiedErrorOrInternal(w, err)
return
}
render.JSON(w, res, http.StatusOK)
render.JSON(w, http.StatusOK, res)
})
}

View File

@ -9,7 +9,6 @@ import (
"net/http"
"time"
"github.com/harness/gitness/internal/api/comms"
"github.com/harness/gitness/internal/api/guard"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/api/request"
@ -59,19 +58,18 @@ func HandleUpdate(guard *guard.Guard, spaces store.SpaceStore) http.HandlerFunc
// ensure provided values are valid
if err := check.Space(space); err != nil {
render.BadRequest(w, err)
render.UserfiedErrorOrInternal(w, err)
return
}
err = spaces.Update(ctx, space)
if err != nil {
log.Error().Err(err).
Msg("Space update failed.")
log.Error().Err(err).Msg("Space update failed.")
render.InternalErrorf(w, comms.Internal)
render.UserfiedErrorOrInternal(w, err)
return
}
render.JSON(w, space, http.StatusOK)
render.JSON(w, http.StatusOK, space)
})
}

View File

@ -18,7 +18,7 @@ func HandleFind() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
user, _ := request.UserFrom(ctx)
render.JSON(w, user, 200)
render.JSON(w, http.StatusOK, user)
}
}
@ -29,6 +29,6 @@ func HandleCurrent() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
user, _ := request.UserFrom(ctx)
platform.RenderResource(w, user, 200)
platform.RenderResource(w, http.StatusOK, user)
}
}

View File

@ -7,7 +7,6 @@ package user
import (
"net/http"
"github.com/harness/gitness/internal/api/comms"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/api/request"
"github.com/harness/gitness/internal/store"
@ -25,13 +24,12 @@ func HandleToken(users store.UserStore) http.HandlerFunc {
token, err := token.Generate(user, user.Salt)
if err != nil {
log.Err(err).
Msg("failed to generate token")
log.Err(err).Msg("failed to generate token")
render.InternalErrorf(w, comms.Internal)
render.UserfiedErrorOrInternal(w, err)
return
}
render.JSON(w, &types.Token{Value: token}, 200)
render.JSON(w, http.StatusOK, &types.Token{Value: token})
}
}

View File

@ -42,7 +42,7 @@ func HandleUpdate(users store.UserStore) http.HandlerFunc {
if err != nil {
log.Err(err).Msg("Failed to hash password.")
render.InternalError(w, err)
render.InternalError(w)
return
}
user.Password = string(hash)
@ -60,10 +60,10 @@ func HandleUpdate(users store.UserStore) http.HandlerFunc {
if err != nil {
log.Err(err).Msg("Failed to update the user.")
render.InternalError(w, err)
render.UserfiedErrorOrInternal(w, err)
return
}
render.JSON(w, user, 200)
render.JSON(w, http.StatusOK, user)
}
}

View File

@ -120,9 +120,9 @@ func TestUpdate_HashError(t *testing.T) {
t.Errorf("Want response code %d, got %d", want, got)
}
got, want := new(render.Error), &render.Error{Message: bcrypt.ErrHashTooShort.Error()}
got := new(render.Error)
json.NewDecoder(w.Body).Decode(got)
if diff := cmp.Diff(got, want); len(diff) != 0 {
if diff := cmp.Diff(got.Message, render.ErrInternal.Message); len(diff) != 0 {
t.Errorf(diff)
}
}
@ -151,9 +151,9 @@ func TestUpdate_BadRequest(t *testing.T) {
t.Errorf("Want response code %d, got %d", want, got)
}
got, want := new(render.Error), &render.Error{Message: "EOF"}
got := new(render.Error)
json.NewDecoder(w.Body).Decode(got)
if diff := cmp.Diff(got, want); len(diff) != 0 {
if diff := cmp.Diff(got.Message, "Invalid request body: EOF."); len(diff) != 0 {
t.Errorf(diff)
}
}
@ -188,7 +188,7 @@ func TestUpdate_ServerError(t *testing.T) {
t.Errorf("Want response code %d, got %d", want, got)
}
got, want := new(render.Error), render.ErrNotFound
got, want := new(render.Error), render.ErrInternal
json.NewDecoder(w.Body).Decode(got)
if diff := cmp.Diff(got, want); len(diff) != 0 {
t.Errorf(diff)

View File

@ -9,7 +9,6 @@ import (
"net/http"
"time"
"github.com/harness/gitness/internal/api/comms"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/types"
@ -46,7 +45,7 @@ func HandleCreate(users store.UserStore) http.HandlerFunc {
Str("email", in.Username).
Msg("Failed to hash password")
render.InternalErrorf(w, comms.Internal)
render.InternalError(w)
return
}
@ -64,7 +63,7 @@ func HandleCreate(users store.UserStore) http.HandlerFunc {
Str("email", user.Email).
Msg("invalid user input")
render.BadRequest(w, err)
render.UserfiedErrorOrInternal(w, err)
return
}
@ -74,10 +73,10 @@ func HandleCreate(users store.UserStore) http.HandlerFunc {
Str("email", user.Email).
Msg("failed to create user")
render.InternalErrorf(w, comms.Internal)
render.UserfiedErrorOrInternal(w, err)
return
}
render.JSON(w, user, 200)
render.JSON(w, http.StatusOK, user)
}
}

View File

@ -5,12 +5,9 @@
package users
import (
"errors"
"net/http"
"github.com/harness/gitness/internal/api/comms"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/errs"
"github.com/harness/gitness/internal/store"
"github.com/rs/zerolog/hlog"
@ -26,13 +23,10 @@ func HandleDelete(users store.UserStore) http.HandlerFunc {
key := chi.URLParam(r, "user")
user, err := users.FindKey(ctx, key)
if errors.Is(err, errs.ResourceNotFound) {
render.NotFoundf(w, "User not found.")
return
} else if err != nil {
if err != nil {
log.Err(err).Msgf("Failed to get user using key '%s'.", key)
render.InternalErrorf(w, comms.Internal)
render.UserfiedErrorOrInternal(w, err)
return
}
@ -43,7 +37,7 @@ func HandleDelete(users store.UserStore) http.HandlerFunc {
Str("user_email", user.Email).
Msg("failed to delete user")
render.InternalError(w, err)
render.UserfiedErrorOrInternal(w, err)
return
}

View File

@ -5,12 +5,9 @@
package users
import (
"errors"
"net/http"
"github.com/harness/gitness/internal/api/comms"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/errs"
"github.com/harness/gitness/internal/store"
"github.com/rs/zerolog/hlog"
@ -26,16 +23,13 @@ func HandleFind(users store.UserStore) http.HandlerFunc {
key := chi.URLParam(r, "user")
user, err := users.FindKey(ctx, key)
if errors.Is(err, errs.ResourceNotFound) {
render.NotFoundf(w, "User doesn't exist.")
return
} else if err != nil {
log.Err(err).Msgf("Failed to get user using key '%s'.", key)
if err != nil {
log.Debug().Err(err).Msgf("Failed to get user using key '%s'.", key)
render.InternalErrorf(w, comms.Internal)
render.UserfiedErrorOrInternal(w, err)
return
}
render.JSON(w, user, 200)
render.JSON(w, http.StatusOK, user)
}
}

View File

@ -37,12 +37,11 @@ func HandleList(users store.UserStore) http.HandlerFunc {
log.Err(err).
Msg("Failed to retrieve user list")
render.InternalError(w, err)
render.UserfiedErrorOrInternal(w, err)
return
}
render.Pagination(r, w, params.Page, params.Size, int(count))
render.JSON(w, list, 200)
render.JSON(w, http.StatusOK, list)
}
}

View File

@ -6,14 +6,11 @@ package users
import (
"encoding/json"
"errors"
"net/http"
"time"
"github.com/gotidy/ptr"
"github.com/harness/gitness/internal/api/comms"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/errs"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/check"
@ -36,13 +33,10 @@ func HandleUpdate(users store.UserStore) http.HandlerFunc {
key := chi.URLParam(r, "user")
user, err := users.FindKey(ctx, key)
if errors.Is(err, errs.ResourceNotFound) {
render.NotFoundf(w, "User not found.")
return
} else if err != nil {
log.Err(err).Msgf("Failed to get user using key '%s'.", key)
if err != nil {
log.Debug().Err(err).Msgf("Failed to get user using key '%s'.", key)
render.InternalErrorf(w, comms.Internal)
render.UserfiedErrorOrInternal(w, err)
return
}
@ -60,7 +54,7 @@ func HandleUpdate(users store.UserStore) http.HandlerFunc {
Str("user_email", user.Email).
Msg("Failed to hash password")
render.InternalErrorf(w, comms.Internal)
render.InternalError(w)
return
}
user.Password = string(hash)
@ -87,7 +81,7 @@ func HandleUpdate(users store.UserStore) http.HandlerFunc {
Str("user_email", user.Email).
Msg("Failed to hash password")
render.InternalErrorf(w, comms.Internal)
render.InternalError(w)
return
}
user.Password = string(hash)
@ -99,7 +93,7 @@ func HandleUpdate(users store.UserStore) http.HandlerFunc {
Str("user_email", user.Email).
Msg("invalid user input")
render.BadRequest(w, err)
render.UserfiedErrorOrInternal(w, err)
return
}
@ -112,10 +106,10 @@ func HandleUpdate(users store.UserStore) http.HandlerFunc {
Str("user_email", user.Email).
Msg("Failed to update the usser")
render.InternalErrorf(w, comms.Internal)
render.UserfiedErrorOrInternal(w, err)
return
}
render.JSON(w, user, 200)
render.JSON(w, http.StatusOK, user)
}
}

View File

@ -0,0 +1,29 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package accesslog
import (
"net/http"
"time"
"github.com/rs/zerolog/hlog"
)
/*
* A simple middleware that logs completed requests using the default hlog access handler.
*/
func HlogHandler() func(http.Handler) http.Handler {
return hlog.AccessHandler(
func(r *http.Request, status, size int, duration time.Duration) {
hlog.FromRequest(r).Info().
Str("method", r.Method).
Stringer("url", r.URL).
Int("status_code", status).
Int("response_size_bytes", size).
Dur("elapsed_ms", duration).
Msg("request completed.")
},
)
}

View File

@ -5,6 +5,7 @@
package authn
import (
"errors"
"net/http"
"github.com/harness/gitness/internal/api/render"
@ -12,7 +13,7 @@ import (
"github.com/harness/gitness/internal/auth/authn"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/rs/zerolog/hlog"
)
/*
@ -22,21 +23,29 @@ import (
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) {
user, err := authenticator.Authenticate(r)
if err != nil {
render.Unauthorized(w, err)
return
}
ctx := r.Context()
log := hlog.FromRequest(r)
// if there was no auth info - continue as is
if user == nil {
user, err := authenticator.Authenticate(r)
if errors.Is(err, authn.ErrNoAuthData) {
// if there was no auth data in the request - continue as is
next.ServeHTTP(w, r)
return
} else if err != nil {
// for any other error we fail
render.Unauthorized(w)
return
} else 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!")
render.InternalError(w)
return
}
// Update the logging context and inject user in context
ctx := r.Context()
log.Ctx(ctx).UpdateContext(func(c zerolog.Context) zerolog.Context {
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
return c.Int64("user_id", user.ID).Bool("user_admin", user.Admin)
})

View File

@ -5,18 +5,15 @@
package repo
import (
"errors"
"net/http"
"strconv"
"github.com/harness/gitness/internal/api/comms"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/api/request"
"github.com/harness/gitness/internal/errs"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/types"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/rs/zerolog/hlog"
)
/*
@ -27,13 +24,15 @@ import (
func Required(repos store.RepoStore) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
log := hlog.FromRequest(r)
ref, err := request.GetRepoRef(r)
if err != nil {
render.BadRequest(w, err)
render.BadRequest(w)
return
}
ctx := r.Context()
var repo *types.Repository
// check if ref is repoId - ASSUMPTION: digit only is no valid repo name
@ -44,18 +43,15 @@ func Required(repos store.RepoStore) func(http.Handler) http.Handler {
repo, err = repos.FindByPath(ctx, ref)
}
if errors.Is(err, errs.ResourceNotFound) {
render.NotFoundf(w, "Repository doesn't exist.")
return
} else if err != nil {
log.Err(err).Msgf("Failed to get repo using ref '%s'.", ref)
if err != nil {
log.Debug().Err(err).Msgf("Failed to get repo using ref '%s'.", ref)
render.InternalErrorf(w, comms.Internal)
render.UserfiedErrorOrInternal(w, err)
return
}
// Update the logging context and inject repo in context
log.Ctx(ctx).UpdateContext(func(c zerolog.Context) zerolog.Context {
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
return c.Int64("repo_id", repo.ID).Str("repo_path", repo.Path)
})

View File

@ -5,18 +5,15 @@
package space
import (
"errors"
"net/http"
"strconv"
"github.com/harness/gitness/internal/api/comms"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/api/request"
"github.com/harness/gitness/internal/errs"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/types"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/rs/zerolog/hlog"
)
/*
@ -27,13 +24,15 @@ import (
func Required(spaces store.SpaceStore) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
log := hlog.FromRequest(r)
ref, err := request.GetSpaceRef(r)
if err != nil {
render.BadRequest(w, err)
render.BadRequest(w)
return
}
ctx := r.Context()
var space *types.Space
// check if ref is spaceId - ASSUMPTION: digit only is no valid space name
@ -44,18 +43,15 @@ func Required(spaces store.SpaceStore) func(http.Handler) http.Handler {
space, err = spaces.FindByPath(ctx, ref)
}
if errors.Is(err, errs.ResourceNotFound) {
render.NotFoundf(w, "Space not found.")
return
} else if err != nil {
log.Err(err).Msgf("Failed to get space using ref '%s'.", ref)
if err != nil {
log.Debug().Err(err).Msgf("Failed to get space using ref '%s'.", ref)
render.InternalErrorf(w, comms.Internal)
render.UserfiedErrorOrInternal(w, err)
return
}
// Update the logging context and inject repo in context
log.Ctx(ctx).UpdateContext(func(c zerolog.Context) zerolog.Context {
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
return c.Int64("space_id", space.ID).Str("space_path", space.Path)
})

View File

@ -5,9 +5,15 @@
package render
var (
// ErrInternal is returned when an internal error occured.
ErrInternal = New("Internal error occured")
// ErrInvalidToken is returned when the api request token is invalid.
ErrInvalidToken = New("Invalid or missing token")
// ErrBadRequest is returned when there was an issue with the user input.
ErrBadRequest = New("Bad Request")
// ErrUnauthorized is returned when the user is not authorized.
ErrUnauthorized = New("Unauthorized")
@ -16,6 +22,24 @@ var (
// ErrNotFound is returned when a resource is not found.
ErrNotFound = New("Not Found")
// ErrNoChange is returned when no change was found based on the request.
ErrNoChange = New("No Change")
// ErrDuplicate is returned when a resource already exits.
ErrDuplicate = New("Resource already exists")
// ErrPrimaryPathCantBeDeleted is returned when the user is trying to delete a primary path.
ErrPrimaryPathCantBeDeleted = New("The primary path of an object can't be deleted")
// 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."))
// 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.")
)
// Error represents a json-encoded API error.
@ -28,6 +52,6 @@ func (e *Error) Error() string {
}
// New returns a new error message.
func New(text string) error {
func New(text string) *Error {
return &Error{Message: text}
}

View File

@ -7,7 +7,7 @@ package render
import "testing"
func TestError(t *testing.T) {
got, want := ErrNotFound.Error(), ErrNotFound.(*Error).Message
got, want := ErrNotFound.Message, ErrNotFound.Message
if got != want {
t.Errorf("Want error string %q, got %q", got, want)
}

View File

@ -9,7 +9,7 @@ import (
// RenderResource is a helper function that renders a single
// resource, wrapped in the harness payload envelope.
func RenderResource(w http.ResponseWriter, v interface{}, code int) {
func RenderResource(w http.ResponseWriter, code int, v interface{}) {
payload := new(wrapper)
payload.Status = "SUCCESS"
payload.Data, _ = json.Marshal(v)
@ -18,7 +18,7 @@ func RenderResource(w http.ResponseWriter, v interface{}, code int) {
} else if code > 299 {
payload.Status = "FAILURE"
}
render.JSON(w, payload, code)
render.JSON(w, code, payload)
}
// wrapper defines the payload wrapper.

View File

@ -6,10 +6,14 @@ package render
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
"strconv"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/types/check"
)
// indent the json-encoded API responses
@ -21,77 +25,86 @@ func init() {
)
}
// ErrorCode writes the json-encoded error message to the response.
func ErrorCode(w http.ResponseWriter, err error, status int) {
JSON(w, &Error{Message: err.Error()}, status)
/*
* 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) {
ErrorObject(w, http.StatusBadRequest, &Error{err.Error()})
} else if errors.Is(err, store.ErrResourceNotFound) {
ErrorObject(w, http.StatusNotFound, ErrNotFound)
} else if errors.Is(err, store.ErrDuplicate) {
ErrorObject(w, http.StatusBadRequest, ErrDuplicate)
} else if errors.Is(err, store.ErrPrimaryPathCantBeDeleted) {
ErrorObject(w, http.StatusBadRequest, ErrPrimaryPathCantBeDeleted)
} else if errors.Is(err, store.ErrPathTooLong) {
ErrorObject(w, http.StatusBadRequest, ErrPathTooLong)
} else if errors.Is(err, store.ErrNoChangeInRequestedMove) {
ErrorObject(w, http.StatusBadRequest, ErrNoChange)
} else if errors.Is(err, store.ErrIllegalMoveCyclicHierarchy) {
ErrorObject(w, http.StatusBadRequest, ErrCyclicHierarchy)
} else if errors.Is(err, store.ErrSpaceWithChildsCantBeDeleted) {
ErrorObject(w, http.StatusBadRequest, ErrSpaceWithChildsCantBeDeleted)
} else {
// nothing found - render internal error
fmt.Println(err)
InternalError(w)
}
}
// InternalError writes the json-encoded error message to the response
// with a 500 internal server error.
func InternalError(w http.ResponseWriter, err error) {
ErrorCode(w, err, 500)
// NotFound writes the json-encoded message for a not found error.
func NotFound(w http.ResponseWriter) {
ErrorObject(w, http.StatusNotFound, ErrNotFound)
}
// InternalErrorf writes the json-encoded error message to the response
// with a 500 internal server error.
func InternalErrorf(w http.ResponseWriter, format string, a ...interface{}) {
ErrorCode(w, fmt.Errorf(format, a...), 500)
// Unauthorized writes the json-encoded message for an unauthorized error.
func Unauthorized(w http.ResponseWriter) {
ErrorObject(w, http.StatusUnauthorized, ErrUnauthorized)
}
// NotFound writes the json-encoded error message to the response
// with a 404 not found status code.
func NotFound(w http.ResponseWriter, err error) {
ErrorCode(w, err, 404)
// Forbidden writes the json-encoded message for a forbidden error.
func Forbidden(w http.ResponseWriter) {
ErrorObject(w, http.StatusForbidden, ErrForbidden)
}
// NotFoundf writes the json-encoded error message to the response
// with a 404 not found status code.
func NotFoundf(w http.ResponseWriter, format string, a ...interface{}) {
ErrorCode(w, fmt.Errorf(format, a...), 404)
// BadRequest writes the json-encoded message for a bad request error.
func BadRequest(w http.ResponseWriter) {
ErrorObject(w, http.StatusBadRequest, ErrBadRequest)
}
// Unauthorized writes the json-encoded error message to the response
// with a 401 unauthorized status code.
func Unauthorized(w http.ResponseWriter, err error) {
ErrorCode(w, err, 401)
// BadRequestError writes the json-encoded error with a bad request status code.
func BadRequestError(w http.ResponseWriter, err *Error) {
ErrorObject(w, http.StatusBadRequest, err)
}
// Unauthorizedf writes the json-encoded error message to the response
// with a 401 unauthorized status code.
func Unauthorizedf(w http.ResponseWriter, format string, a ...interface{}) {
ErrorCode(w, fmt.Errorf(format, a...), 401)
// BadRequest writes the json-encoded message with a bad request status code.
func BadRequestf(w http.ResponseWriter, format string, args ...interface{}) {
ErrorMessagef(w, http.StatusBadRequest, format, args...)
}
// Forbidden writes the json-encoded error message to the response
// with a 403 forbidden status code.
func Forbidden(w http.ResponseWriter, err error) {
ErrorCode(w, err, 403)
// InternalError writes the json-encoded message for an internal error.
func InternalError(w http.ResponseWriter) {
ErrorObject(w, http.StatusInternalServerError, ErrInternal)
}
// Forbiddenf writes the json-encoded error message to the response
// with a 403 forbidden status code.
func Forbiddenf(w http.ResponseWriter, format string, a ...interface{}) {
ErrorCode(w, fmt.Errorf(format, a...), 403)
// ErrorMessagef writes the json-encoded, formated error message.
func ErrorMessagef(w http.ResponseWriter, code int, format string, args ...interface{}) {
JSON(w, code, &Error{Message: fmt.Sprintf(format, args...)})
}
// BadRequest writes the json-encoded error message to the response
// with a 400 bad request status code.
func BadRequest(w http.ResponseWriter, err error) {
ErrorCode(w, err, 400)
// ErrorMessagef writes the json-encoded, formated error message.
func ErrorObject(w http.ResponseWriter, code int, err *Error) {
JSON(w, code, err)
}
// BadRequestf writes the json-encoded error message to the response
// with a 400 bad request status code.
func BadRequestf(w http.ResponseWriter, format string, a ...interface{}) {
ErrorCode(w, fmt.Errorf(format, a...), 400)
}
// JSON writes the json-encoded error message to the response
// with a 400 bad request status code.
func JSON(w http.ResponseWriter, v interface{}, status int) {
// JSON writes the json-encoded value to the response
// 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")
w.WriteHeader(status)
w.WriteHeader(code)
enc := json.NewEncoder(w)
if indent {
enc.SetIndent("", " ")

View File

@ -11,27 +11,11 @@ import (
"testing"
)
func TestWriteError(t *testing.T) {
w := httptest.NewRecorder()
err := New("pc load letter")
InternalError(w, err)
if got, want := w.Code, 500; want != got {
t.Errorf("Want response code %d, got %d", want, got)
}
errjson := &Error{}
json.NewDecoder(w.Body).Decode(errjson)
if got, want := errjson.Message, err.Error(); got != want {
t.Errorf("Want error message %s, got %s", want, got)
}
}
func TestWriteErrorf(t *testing.T) {
w := httptest.NewRecorder()
InternalErrorf(w, "pc load letter")
e := New("abc")
ErrorObject(w, 500, e)
if got, want := w.Code, 500; want != got {
t.Errorf("Want response code %d, got %d", want, got)
@ -39,7 +23,7 @@ func TestWriteErrorf(t *testing.T) {
errjson := &Error{}
json.NewDecoder(w.Body).Decode(errjson)
if got, want := errjson.Message, "pc load letter"; got != want {
if got, want := errjson.Message, e.Message; got != want {
t.Errorf("Want error message %s, got %s", want, got)
}
}
@ -47,8 +31,7 @@ func TestWriteErrorf(t *testing.T) {
func TestWriteErrorCode(t *testing.T) {
w := httptest.NewRecorder()
err := New("pc load letter")
ErrorCode(w, err, 418)
ErrorMessagef(w, 418, "pc load letter %d", 1)
if got, want := w.Code, 418; want != got {
t.Errorf("Want response code %d, got %d", want, got)
@ -56,7 +39,7 @@ func TestWriteErrorCode(t *testing.T) {
errjson := &Error{}
json.NewDecoder(w.Body).Decode(errjson)
if got, want := errjson.Message, err.Error(); got != want {
if got, want := errjson.Message, "pc load letter 1"; got != want {
t.Errorf("Want error message %s, got %s", want, got)
}
}
@ -64,8 +47,7 @@ func TestWriteErrorCode(t *testing.T) {
func TestWriteNotFound(t *testing.T) {
w := httptest.NewRecorder()
err := New("pc load letter")
NotFound(w, err)
NotFound(w)
if got, want := w.Code, 404; want != got {
t.Errorf("Want response code %d, got %d", want, got)
@ -73,23 +55,7 @@ func TestWriteNotFound(t *testing.T) {
errjson := &Error{}
json.NewDecoder(w.Body).Decode(errjson)
if got, want := errjson.Message, err.Error(); got != want {
t.Errorf("Want error message %s, got %s", want, got)
}
}
func TestWriteNotFoundf(t *testing.T) {
w := httptest.NewRecorder()
NotFoundf(w, "pc load letter")
if got, want := w.Code, 404; want != got {
t.Errorf("Want response code %d, got %d", want, got)
}
errjson := &Error{}
json.NewDecoder(w.Body).Decode(errjson)
if got, want := errjson.Message, "pc load letter"; got != want {
if got, want := errjson.Message, ErrNotFound.Message; got != want {
t.Errorf("Want error message %s, got %s", want, got)
}
}
@ -97,8 +63,7 @@ func TestWriteNotFoundf(t *testing.T) {
func TestWriteUnauthorized(t *testing.T) {
w := httptest.NewRecorder()
err := New("pc load letter")
Unauthorized(w, err)
Unauthorized(w)
if got, want := w.Code, 401; want != got {
t.Errorf("Want response code %d, got %d", want, got)
@ -106,7 +71,7 @@ func TestWriteUnauthorized(t *testing.T) {
errjson := &Error{}
json.NewDecoder(w.Body).Decode(errjson)
if got, want := errjson.Message, err.Error(); got != want {
if got, want := errjson.Message, ErrUnauthorized.Message; got != want {
t.Errorf("Want error message %s, got %s", want, got)
}
}
@ -114,8 +79,7 @@ func TestWriteUnauthorized(t *testing.T) {
func TestWriteForbidden(t *testing.T) {
w := httptest.NewRecorder()
err := New("pc load letter")
Forbidden(w, err)
Forbidden(w)
if got, want := w.Code, 403; want != got {
t.Errorf("Want response code %d, got %d", want, got)
@ -123,7 +87,7 @@ func TestWriteForbidden(t *testing.T) {
errjson := &Error{}
json.NewDecoder(w.Body).Decode(errjson)
if got, want := errjson.Message, err.Error(); got != want {
if got, want := errjson.Message, ErrForbidden.Message; got != want {
t.Errorf("Want error message %s, got %s", want, got)
}
}
@ -131,8 +95,7 @@ func TestWriteForbidden(t *testing.T) {
func TestWriteBadRequest(t *testing.T) {
w := httptest.NewRecorder()
err := New("pc load letter")
BadRequest(w, err)
BadRequest(w)
if got, want := w.Code, 400; want != got {
t.Errorf("Want response code %d, got %d", want, got)
@ -140,23 +103,7 @@ func TestWriteBadRequest(t *testing.T) {
errjson := &Error{}
json.NewDecoder(w.Body).Decode(errjson)
if got, want := errjson.Message, err.Error(); got != want {
t.Errorf("Want error message %s, got %s", want, got)
}
}
func TestWriteBadRequestf(t *testing.T) {
w := httptest.NewRecorder()
BadRequestf(w, "pc load letter")
if got, want := w.Code, 400; want != got {
t.Errorf("Want response code %d, got %d", want, got)
}
errjson := &Error{}
json.NewDecoder(w.Body).Decode(errjson)
if got, want := errjson.Message, "pc load letter"; got != want {
if got, want := errjson.Message, ErrBadRequest.Message; got != want {
t.Errorf("Want error message %s, got %s", want, got)
}
}
@ -165,7 +112,7 @@ func TestWriteJSON(t *testing.T) {
// without indent
{
w := httptest.NewRecorder()
JSON(w, map[string]string{"hello": "world"}, http.StatusTeapot)
JSON(w, http.StatusTeapot, map[string]string{"hello": "world"})
if got, want := w.Body.String(), "{\"hello\":\"world\"}\n"; got != want {
t.Errorf("Want JSON body %q, got %q", want, got)
}
@ -183,7 +130,7 @@ func TestWriteJSON(t *testing.T) {
indent = false
}()
w := httptest.NewRecorder()
JSON(w, map[string]string{"hello": "world"}, http.StatusTeapot)
JSON(w, http.StatusTeapot, map[string]string{"hello": "world"})
if got, want := w.Body.String(), "{\n \"hello\": \"world\"\n}\n"; got != want {
t.Errorf("Want JSON body %q, got %q", want, got)
}

View File

@ -1,22 +1,26 @@
package request
import (
"errors"
"net/http"
"net/url"
"strings"
"github.com/go-chi/chi"
"github.com/harness/gitness/internal/errs"
)
const (
RepoRefParamName = "rref"
)
var (
ErrRepoReferenceNotFound = errors.New("No repository reference found in request.")
)
func GetRepoRef(r *http.Request) (string, error) {
rawRef := chi.URLParam(r, RepoRefParamName)
if rawRef == "" {
return "", errs.RepoReferenceNotFoundInRequest
return "", ErrRepoReferenceNotFound
}
// paths are unescaped and lower

View File

@ -1,22 +1,26 @@
package request
import (
"errors"
"net/http"
"net/url"
"strings"
"github.com/go-chi/chi"
"github.com/harness/gitness/internal/errs"
)
const (
SpaceRefParamName = "sref"
)
var (
ErrSpaceReferenceNotFound = errors.New("No space reference found in request.")
)
func GetSpaceRef(r *http.Request) (string, error) {
rawRef := chi.URLParam(r, SpaceRefParamName)
if rawRef == "" {
return "", errs.SpaceReferenceNotFoundInRequest
return "", ErrSpaceReferenceNotFound
}
// paths are unescaped and lower

View File

@ -5,11 +5,17 @@
package authn
import (
"errors"
"net/http"
"github.com/harness/gitness/types"
)
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.")
)
/*
* An abstraction of an entity thats responsible for authenticating users
* that are making calls via HTTP.
@ -18,9 +24,9 @@ type Authenticator interface {
/*
* Tries to authenticate a user if credentials are available.
* Returns:
* (user, nil) - request contained auth data and user was verified
* (nil, err) - request contained auth data but verification failed
* (nil, nil) - request didn't contain any auth data
* (user, nil) - request contains auth data and user was verified
* (nil, ErrNoAuthData) - request doesn't contain any auth data
* (nil, err) - request contains auth data but verification failed
*/
Authenticate(r *http.Request) (*types.User, error)
}

View File

@ -40,7 +40,7 @@ func (a *TokenAuthenticator) Authenticate(r *http.Request) (*types.User, error)
str := extractToken(r)
if len(str) == 0 {
return nil, nil
return nil, ErrNoAuthData
}
var user *types.User

View File

@ -5,10 +5,17 @@
package authz
import (
"errors"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
var (
// An 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.
*/

View File

@ -14,7 +14,6 @@ import (
"strings"
"github.com/harness/gitness/internal/auth/authz"
"github.com/harness/gitness/internal/errs"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
@ -48,7 +47,7 @@ func (a *Authorizer) Check(principalType enum.PrincipalType, principalId string,
func (a *Authorizer) CheckAll(principalType enum.PrincipalType, principalId string, permissionChecks ...*types.PermissionCheck) (bool, error) {
if len(permissionChecks) == 0 {
return false, errs.NoPermissionCheckProvided
return false, authz.ErrNoPermissionCheckProvided
}
requestDto, err := createAclRequest(principalType, principalId, permissionChecks)

View File

@ -1,69 +0,0 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package errs
import (
"fmt"
)
// Static errors
var (
// Indicates that a requested resource wasn't found.
ResourceNotFound error = &dynamicError{0, "Resource not found", nil}
Duplicate error = &dynamicError{1, "Resource is a duplicate", nil}
PathTooLong error = &dynamicError{2, "The path is too long", nil}
)
// Wrappers
func WrapInResourceNotFound(inner error) error {
return cloneWithNewInner(ResourceNotFound.(*dynamicError), inner)
}
func WrapInDuplicate(inner error) error {
return cloneWithNewInner(Duplicate.(*dynamicError), inner)
}
func WrapInPathTooLongf(format string, args ...interface{}) error {
return cloneWithNewMsg(PathTooLong.(*dynamicError), fmt.Sprintf(format, args...))
}
// Error type (on purpose not using explicit definitions and iota, to make overhead as small as possible)
type dynamicErrorType int
/*
* This is an abstraction of an error that can be both a standalone error or a wrapping error.
* The idea is to allow errors.Is(err, errs.MyError) for wrapping errors while keeping code to a minimum
*/
type dynamicError struct {
errorType dynamicErrorType
msg string
inner error
}
func (e *dynamicError) Error() string {
if e.inner == nil {
return e.msg
} else {
return fmt.Sprintf("%s: %s", e.msg, e.inner)
}
}
func (e *dynamicError) Unwrap() error {
return e.inner
}
func (e *dynamicError) Is(target error) bool {
te, ok := target.(*dynamicError)
return ok && te.errorType == e.errorType
}
func cloneWithNewMsg(d *dynamicError, msg string) *dynamicError {
return &dynamicError{d.errorType, msg, nil}
}
func cloneWithNewInner(d *dynamicError, inner error) *dynamicError {
return &dynamicError{d.errorType, d.msg, inner}
}
func cloneWithNewMsgAndInner(d *dynamicError, msg string, inner error) *dynamicError {
return &dynamicError{d.errorType, msg, inner}
}

View File

@ -1,24 +0,0 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package errs
import "errors"
var (
NotAuthenticated = errors.New("Not authenticated.")
NotAuthorized = errors.New("Not authorized.")
RepositoryRequired = errors.New("The operation requires a repository.")
PathEmpty = errors.New("Path is empty.")
PrimaryPathAlreadyExists = errors.New("Primary path already exists for resource.")
AliasPathRequired = errors.New("Path has to be an alias.")
PrimaryPathRequired = errors.New("Path has to be primary.")
PrimaryPathCantBeDeleted = errors.New("Primary path can't be deleted.")
NoChangeInRequestedMove = errors.New(("The requested move doesn't change anything."))
IllegalMoveCyclicHierarchy = errors.New(("The requested move is not permitted as it would cause a cyclic depdency."))
SpaceWithChildsCantBeDeleted = errors.New("The space can't be deleted as it still contains spaces or repos.")
RepoReferenceNotFoundInRequest = errors.New("No repository reference found in request.")
SpaceReferenceNotFoundInRequest = errors.New("No space reference found in request.")
NoPermissionCheckProvided = errors.New("No permission checks provided")
)

View File

@ -5,19 +5,23 @@
package paths
import (
"errors"
"strings"
"github.com/harness/gitness/internal/errs"
"github.com/harness/gitness/types"
)
var (
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)
*/
func Disect(path string) (string, string, error) {
if path == "" {
return "", "", errs.PathEmpty
return "", "", ErrPathEmpty
}
i := strings.LastIndex(path, types.PathSeparator)

View File

@ -10,6 +10,7 @@ import (
handler_space "github.com/harness/gitness/internal/api/handler/space"
"github.com/harness/gitness/internal/api/handler/system"
"github.com/harness/gitness/internal/api/handler/user"
"github.com/harness/gitness/internal/api/middleware/accesslog"
middleware_authn "github.com/harness/gitness/internal/api/middleware/authn"
"github.com/harness/gitness/internal/api/middleware/encode"
"github.com/harness/gitness/internal/api/middleware/repo"
@ -55,6 +56,7 @@ func newApiHandler(
r.Use(hlog.URLHandler("path"))
r.Use(hlog.MethodHandler("method"))
r.Use(hlog.RequestIDHandler("request", "Request-Id"))
r.Use(accesslog.HlogHandler())
// configure cors middleware
cors := cors.New(

View File

@ -5,6 +5,7 @@ import (
"net/http"
"github.com/harness/gitness/internal/api/guard"
"github.com/harness/gitness/internal/api/middleware/accesslog"
middleware_authn "github.com/harness/gitness/internal/api/middleware/authn"
"github.com/harness/gitness/internal/api/middleware/encode"
"github.com/harness/gitness/internal/api/middleware/repo"
@ -48,6 +49,7 @@ func newGitHandler(
r.Use(hlog.URLHandler("path"))
r.Use(hlog.MethodHandler("method"))
r.Use(hlog.RequestIDHandler("request", "Request-Id"))
r.Use(accesslog.HlogHandler())
// for now always attempt auth - enforced per operation
r.Use(middleware_authn.Attempt(authenticator))
@ -80,7 +82,6 @@ func newGitHandler(
r.Get("/objects/pack/pack-{file:[0-9a-f]{40}}.pack", stubGitHandler)
r.Get("/objects/pack/pack-{file:[0-9a-f]{40}}.idx", stubGitHandler)
})
})
})
@ -90,7 +91,7 @@ func newGitHandler(
func stubGitHandler(w http.ResponseWriter, r *http.Request) {
rep, _ := request.RepoFrom(r.Context())
w.WriteHeader(http.StatusForbidden)
w.WriteHeader(http.StatusTeapot)
w.Write([]byte(fmt.Sprintf(
"Oooops, seems you hit a major construction site ... \n"+
" Repo: '%s' (%s)\n"+

View File

@ -2,7 +2,6 @@ CREATE TABLE IF NOT EXISTS repositories (
repo_id SERIAL PRIMARY KEY
,repo_name TEXT
,repo_spaceId INTEGER
,repo_path TEXT
,repo_displayName TEXT
,repo_description TEXT
,repo_isPublic BOOLEAN
@ -14,5 +13,4 @@ CREATE TABLE IF NOT EXISTS repositories (
,repo_numPulls INTEGER
,repo_numClosedPulls INTEGER
,repo_numOpenPulls INTEGER
,UNIQUE(repo_path)
);

View File

@ -2,7 +2,6 @@ CREATE TABLE IF NOT EXISTS repositories (
repo_id INTEGER PRIMARY KEY AUTOINCREMENT
,repo_name TEXT COLLATE NOCASE
,repo_spaceId INTEGER
,repo_path TEXT COLLATE NOCASE
,repo_displayName TEXT
,repo_description TEXT
,repo_isPublic BOOLEAN
@ -14,5 +13,4 @@ CREATE TABLE IF NOT EXISTS repositories (
,repo_numPulls INTEGER
,repo_numClosedPulls INTEGER
,repo_numOpenPulls INTEGER
,UNIQUE(repo_path COLLATE NOCASE)
);

View File

@ -8,39 +8,35 @@ import (
"context"
"fmt"
"github.com/harness/gitness/internal/errs"
"github.com/harness/gitness/internal/paths"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/check"
"github.com/harness/gitness/types/enum"
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
)
// Creates a new path
func CreatePath(ctx context.Context, db *sqlx.DB, path *types.Path) error {
// Creates 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
}
// ensure path length is okay
if check.PathTooLong(path.Value, path.TargetType == enum.PathTargetTypeSpace) {
return errs.WrapInPathTooLongf("Path '%s' is too long.", path.Value)
}
// In case it's not an alias, ensure there are no duplicates
if !path.IsAlias {
if cnt, err := CountPaths(ctx, db, path.TargetType, path.TargetId); err != nil {
return err
} else if cnt > 0 {
return errs.PrimaryPathAlreadyExists
}
log.Warn().Msgf("Path '%s' is too long.", path.Value)
return store.ErrPathTooLong
}
query, arg, err := db.BindNamed(pathInsert, path)
if err != nil {
return wrapSqlErrorf(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 wrapSqlErrorf(err, "Insert query failed")
return processSqlErrorf(err, "Insert query failed")
}
return nil
@ -51,7 +47,8 @@ func CreatePathTx(ctx context.Context, db *sqlx.DB, tx *sqlx.Tx, path *types.Pat
// ensure path length is okay
if check.PathTooLong(path.Value, path.TargetType == enum.PathTargetTypeSpace) {
return errs.WrapInPathTooLongf("Path '%s' is too long.", path.Value)
log.Warn().Msgf("Path '%s' is too long.", path.Value)
return store.ErrPathTooLong
}
// In case it's not an alias, ensure there are no duplicates
@ -59,17 +56,17 @@ func CreatePathTx(ctx context.Context, db *sqlx.DB, tx *sqlx.Tx, path *types.Pat
if cnt, err := CountPathsTx(ctx, tx, path.TargetType, path.TargetId); err != nil {
return err
} else if cnt > 0 {
return errs.PrimaryPathAlreadyExists
return store.ErrPrimaryPathAlreadyExists
}
}
query, arg, err := db.BindNamed(pathInsert, path)
if err != nil {
return wrapSqlErrorf(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 wrapSqlErrorf(err, "Insert query failed")
return processSqlErrorf(err, "Insert query failed")
}
return nil
@ -79,7 +76,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, wrapSqlErrorf(err, "Count query failed")
return 0, processSqlErrorf(err, "Count query failed")
}
return count, nil
}
@ -88,7 +85,7 @@ 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, wrapSqlErrorf(err, "Select query failed")
return nil, processSqlErrorf(err, "Select query failed")
}
return childs, nil
@ -98,19 +95,20 @@ func ListPrimaryChildPathsTx(ctx context.Context, tx *sqlx.Tx, prefix string) ([
func ReplacePathTx(ctx context.Context, db *sqlx.DB, tx *sqlx.Tx, path *types.Path, keepAsAlias bool) error {
if path.IsAlias {
return errs.PrimaryPathRequired
return store.ErrPrimaryPathRequired
}
// ensure new path length is okay
if check.PathTooLong(path.Value, path.TargetType == enum.PathTargetTypeSpace) {
return errs.WrapInPathTooLongf("Path '%s' is too long.", path.Value)
log.Warn().Msgf("Path '%s' is too long.", path.Value)
return store.ErrPathTooLong
}
// existing is always non-alias (as query filters for IsAlias=0)
existing := new(types.Path)
err := tx.GetContext(ctx, existing, pathSelectPrimaryForTarget, string(path.TargetType), fmt.Sprint(path.TargetId))
if err != nil {
return wrapSqlErrorf(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
@ -134,17 +132,18 @@ func ReplacePathTx(ctx context.Context, db *sqlx.DB, tx *sqlx.Tx, path *types.Pa
// ensure new child path length is okay
if check.PathTooLong(updatedChild.Value, path.TargetType == enum.PathTargetTypeSpace) {
return errs.WrapInPathTooLongf("Path '%s' is too long.", updatedChild.Value)
log.Warn().Msgf("Path '%s' is too long.", path.Value)
return store.ErrPathTooLong
}
query, arg, err := db.BindNamed(pathInsert, updatedChild)
if err != nil {
return wrapSqlErrorf(err, "Failed to bind path object")
return processSqlErrorf(err, "Failed to bind path object")
}
_, err = tx.ExecContext(ctx, query, arg...)
if err != nil {
return wrapSqlErrorf(err, "Failed to create new primary child path '%s'", updatedChild.Value)
return processSqlErrorf(err, "Failed to create new primary child path '%s'", updatedChild.Value)
}
// make current child an alias or delete it
@ -154,7 +153,7 @@ func ReplacePathTx(ctx context.Context, db *sqlx.DB, tx *sqlx.Tx, path *types.Pa
_, err = tx.ExecContext(ctx, pathDeleteId, child.ID)
}
if err != nil {
return wrapSqlErrorf(err, "Failed to mark existing child path '%s' as alias", updatedChild.Value)
return processSqlErrorf(err, "Failed to mark existing child path '%s' as alias", updatedChild.Value)
}
}
}
@ -162,12 +161,12 @@ 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 wrapSqlErrorf(err, "Failed to bind path object")
return processSqlErrorf(err, "Failed to bind path object")
}
_, err = tx.ExecContext(ctx, query, arg...)
if err != nil {
return wrapSqlErrorf(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
@ -177,7 +176,7 @@ func ReplacePathTx(ctx context.Context, db *sqlx.DB, tx *sqlx.Tx, path *types.Pa
_, err = tx.ExecContext(ctx, pathDeleteId, existing.ID)
}
if err != nil {
return wrapSqlErrorf(err, "Failed to mark existing path '%s' as alias", existing.Value)
return processSqlErrorf(err, "Failed to mark existing path '%s' as alias", existing.Value)
}
return nil
@ -188,7 +187,7 @@ func FindPathTx(ctx context.Context, tx *sqlx.Tx, targetType enum.PathTargetType
dst := new(types.Path)
err := tx.GetContext(ctx, dst, pathSelectPrimaryForTarget, string(targetType), fmt.Sprint(targetId))
if err != nil {
return nil, wrapSqlErrorf(err, "Select query failed")
return nil, processSqlErrorf(err, "Select query failed")
}
return dst, nil
@ -198,7 +197,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 wrapSqlErrorf(err, "Failed to start a new transaction")
return processSqlErrorf(err, "Failed to start a new transaction")
}
defer tx.Rollback()
@ -206,18 +205,18 @@ func DeletePath(ctx context.Context, db *sqlx.DB, id int64) error {
dst := new(types.Path)
err = tx.GetContext(ctx, dst, pathSelectId, id)
if err != nil {
return wrapSqlErrorf(err, "Failed to find path with id %d", id)
return processSqlErrorf(err, "Failed to find path with id %d", id)
} else if dst.IsAlias == false {
return errs.PrimaryPathCantBeDeleted
return store.ErrPrimaryPathCantBeDeleted
}
// delete the path
if _, err = tx.ExecContext(ctx, pathDeleteId, id); err != nil {
return wrapSqlErrorf(err, "Delete query failed", id)
return processSqlErrorf(err, "Delete query failed")
}
if err = tx.Commit(); err != nil {
return wrapSqlErrorf(err, "Failed to commit transaction")
return processSqlErrorf(err, "Failed to commit transaction")
}
return nil
@ -227,7 +226,7 @@ func DeletePath(ctx context.Context, db *sqlx.DB, id 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 wrapSqlErrorf(err, "Query for deleting all pahts failed")
return processSqlErrorf(err, "Query for deleting all pahts failed")
}
return nil
}
@ -241,7 +240,7 @@ func ListPaths(ctx context.Context, db *sqlx.DB, targetType enum.PathTargetType,
if opts.Sort == enum.PathAttrNone {
err := db.SelectContext(ctx, &dst, pathSelect, string(targetType), fmt.Sprint(targetId), limit(opts.Size), offset(opts.Page, opts.Size))
if err != nil {
return nil, wrapSqlErrorf(err, "Default select query failed")
return nil, processSqlErrorf(err, "Default select query failed")
}
return dst, nil
@ -272,7 +271,7 @@ func ListPaths(ctx context.Context, db *sqlx.DB, targetType enum.PathTargetType,
}
if err = db.SelectContext(ctx, &dst, sql); err != nil {
return nil, wrapSqlErrorf(err, "Customer select query failed")
return nil, processSqlErrorf(err, "Customer select query failed")
}
return dst, nil
@ -283,7 +282,7 @@ func CountPaths(ctx context.Context, db *sqlx.DB, targetType enum.PathTargetType
var count int64
err := db.QueryRowContext(ctx, pathCount, string(targetType), fmt.Sprint(targetId)).Scan(&count)
if err != nil {
return 0, wrapSqlErrorf(err, "Query failed")
return 0, processSqlErrorf(err, "Query failed")
}
return count, nil
}
@ -293,7 +292,7 @@ func CountPathsTx(ctx context.Context, tx *sqlx.Tx, targetType enum.PathTargetTy
var count int64
err := tx.QueryRowContext(ctx, pathCount, string(targetType), fmt.Sprint(targetId)).Scan(&count)
if err != nil {
return 0, wrapSqlErrorf(err, "Query failed")
return 0, processSqlErrorf(err, "Query failed")
}
return count, nil
}

View File

@ -9,7 +9,6 @@ import (
"fmt"
"time"
"github.com/harness/gitness/internal/errs"
"github.com/harness/gitness/internal/paths"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/types"
@ -35,7 +34,7 @@ type RepoStore struct {
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, wrapSqlErrorf(err, "Select query failed")
return nil, processSqlErrorf(err, "Select query failed")
}
return dst, nil
}
@ -44,7 +43,7 @@ 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, wrapSqlErrorf(err, "Select query failed")
return nil, processSqlErrorf(err, "Select query failed")
}
return dst, nil
}
@ -53,18 +52,18 @@ func (s *RepoStore) FindByPath(ctx context.Context, path string) (*types.Reposit
func (s *RepoStore) Create(ctx context.Context, repo *types.Repository) error {
tx, err := s.db.BeginTxx(ctx, nil)
if err != nil {
return wrapSqlErrorf(err, "Failed to start a new transaction")
return processSqlErrorf(err, "Failed to start a new transaction")
}
defer tx.Rollback()
// insert repo first so we get id
query, arg, err := s.db.BindNamed(repoInsert, repo)
if err != nil {
return wrapSqlErrorf(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 wrapSqlErrorf(err, "Insert query failed")
return processSqlErrorf(err, "Insert query failed")
}
// Get parent path (repo always has a parent)
@ -93,7 +92,7 @@ func (s *RepoStore) Create(ctx context.Context, repo *types.Repository) error {
// commit
if err = tx.Commit(); err != nil {
return wrapSqlErrorf(err, "Failed to commit transaction")
return processSqlErrorf(err, "Failed to commit transaction")
}
// update path in repo object
@ -106,7 +105,7 @@ func (s *RepoStore) Create(ctx context.Context, repo *types.Repository) error {
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, wrapSqlErrorf(err, "Failed to start a new transaction")
return nil, processSqlErrorf(err, "Failed to start a new transaction")
}
defer tx.Rollback()
@ -124,7 +123,7 @@ func (s *RepoStore) Move(ctx context.Context, userId int64, repoId int64, newSpa
newPath := paths.Concatinate(spacePath.Value, newName)
if newPath == currentPath.Value {
return nil, errs.NoChangeInRequestedMove
return nil, store.ErrNoChangeInRequestedMove
}
p := &types.Path{
@ -144,18 +143,18 @@ 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, wrapSqlErrorf(err, "Query for renaming and updating the space id failed")
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, wrapSqlErrorf(err, "Select query to get the repo's latest state failed")
return nil, processSqlErrorf(err, "Select query to get the repo's latest state failed")
}
// commit
if err = tx.Commit(); err != nil {
return nil, wrapSqlErrorf(err, "Failed to commit transaction")
return nil, processSqlErrorf(err, "Failed to commit transaction")
}
return dst, nil
@ -165,11 +164,11 @@ 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 wrapSqlErrorf(err, "Failed to bind repo object")
return processSqlErrorf(err, "Failed to bind repo object")
}
if _, err = s.db.ExecContext(ctx, query, arg...); err != nil {
wrapSqlErrorf(err, "Update query failed")
processSqlErrorf(err, "Update query failed")
}
return nil
@ -179,7 +178,7 @@ func (s *RepoStore) Update(ctx context.Context, repo *types.Repository) error {
func (s *RepoStore) Delete(ctx context.Context, id int64) error {
tx, err := s.db.BeginTxx(ctx, nil)
if err != nil {
return wrapSqlErrorf(err, "Failed to start a new transaction")
return processSqlErrorf(err, "Failed to start a new transaction")
}
defer tx.Rollback()
@ -191,11 +190,11 @@ func (s *RepoStore) Delete(ctx context.Context, id int64) error {
// delete the repo
if _, err := tx.ExecContext(ctx, repoDelete, id); err != nil {
return wrapSqlErrorf(err, "The delete query failed")
return processSqlErrorf(err, "The delete query failed")
}
if err = tx.Commit(); err != nil {
return wrapSqlErrorf(err, "Failed to commit transaction")
return processSqlErrorf(err, "Failed to commit transaction")
}
return nil
@ -206,7 +205,7 @@ func (s *RepoStore) Count(ctx context.Context, spaceId int64) (int64, error) {
var count int64
err := s.db.QueryRow(repoCount, spaceId).Scan(&count)
if err != nil {
return 0, wrapSqlErrorf(err, "Failed executing count query")
return 0, processSqlErrorf(err, "Failed executing count query")
}
return count, nil
}
@ -221,7 +220,7 @@ func (s *RepoStore) List(ctx context.Context, spaceId int64, opts *types.RepoFil
if opts.Sort == enum.RepoAttrNone {
err := s.db.SelectContext(ctx, &dst, repoSelect, spaceId, limit(opts.Size), offset(opts.Page, opts.Size))
if err != nil {
return nil, wrapSqlErrorf(err, "Failed executing default list query")
return nil, processSqlErrorf(err, "Failed executing default list query")
}
return dst, nil
}
@ -259,7 +258,7 @@ func (s *RepoStore) List(ctx context.Context, spaceId int64, opts *types.RepoFil
}
if err = s.db.SelectContext(ctx, &dst, sql); err != nil {
return nil, wrapSqlErrorf(err, "Failed executing custom list query")
return nil, processSqlErrorf(err, "Failed executing custom list query")
}
return dst, nil
@ -284,7 +283,7 @@ func (s *RepoStore) CreatePath(ctx context.Context, repoId int64, params *types.
Updated: params.Updated,
}
return p, CreatePath(ctx, s.db, p)
return p, CreateAliasPath(ctx, s.db, p)
}
// Delete an alias of a repo

View File

@ -10,7 +10,6 @@ import (
"strings"
"time"
"github.com/harness/gitness/internal/errs"
"github.com/harness/gitness/internal/paths"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/types"
@ -36,7 +35,7 @@ type SpaceStore struct {
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, wrapSqlErrorf(err, "Select query failed")
return nil, processSqlErrorf(err, "Select query failed")
}
return dst, nil
}
@ -45,7 +44,7 @@ func (s *SpaceStore) Find(ctx context.Context, id int64) (*types.Space, error) {
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, wrapSqlErrorf(err, "Select query failed")
return nil, processSqlErrorf(err, "Select query failed")
}
return dst, nil
}
@ -54,18 +53,18 @@ func (s *SpaceStore) FindByPath(ctx context.Context, path string) (*types.Space,
func (s *SpaceStore) Create(ctx context.Context, space *types.Space) error {
tx, err := s.db.BeginTxx(ctx, nil)
if err != nil {
return wrapSqlErrorf(err, "Failed to start a new transaction")
return processSqlErrorf(err, "Failed to start a new transaction")
}
defer tx.Rollback()
// insert space first so we get id
query, arg, err := s.db.BindNamed(spaceInsert, space)
if err != nil {
return wrapSqlErrorf(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 wrapSqlErrorf(err, "Insert query failed")
return processSqlErrorf(err, "Insert query failed")
}
// Get path (get parent if needed)
@ -97,7 +96,7 @@ func (s *SpaceStore) Create(ctx context.Context, space *types.Space) error {
// commit
if err = tx.Commit(); err != nil {
return wrapSqlErrorf(err, "Failed to commit transaction")
return processSqlErrorf(err, "Failed to commit transaction")
}
// update path in space object
@ -110,7 +109,7 @@ func (s *SpaceStore) Create(ctx context.Context, space *types.Space) error {
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, wrapSqlErrorf(err, "Failed to start a new transaction")
return nil, processSqlErrorf(err, "Failed to start a new transaction")
}
defer tx.Rollback()
@ -137,9 +136,9 @@ func (s *SpaceStore) Move(ctx context.Context, userId int64, spaceId int64, newP
* To avoid cycles in the primary graph, we have to ensure that the old path isn't a prefix of the new path.
*/
if newPath == currentPath.Value {
return nil, errs.NoChangeInRequestedMove
return nil, store.ErrNoChangeInRequestedMove
} else if strings.HasPrefix(newPath, currentPath.Value) {
return nil, errs.IllegalMoveCyclicHierarchy
return nil, store.ErrIllegalMoveCyclicHierarchy
}
p := &types.Path{
@ -159,18 +158,18 @@ 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, wrapSqlErrorf(err, "Query for renaming and updating the parent id failed")
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, wrapSqlErrorf(err, "Select query to get the space's latest state failed")
return nil, processSqlErrorf(err, "Select query to get the space's latest state failed")
}
// commit
if err = tx.Commit(); err != nil {
return nil, wrapSqlErrorf(err, "Failed to commit transaction")
return nil, processSqlErrorf(err, "Failed to commit transaction")
}
return dst, nil
@ -180,11 +179,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 wrapSqlErrorf(err, "Failed to bind space object")
return processSqlErrorf(err, "Failed to bind space object")
}
if _, err = s.db.ExecContext(ctx, query, arg...); err != nil {
wrapSqlErrorf(err, "Update query failed")
processSqlErrorf(err, "Update query failed")
}
return nil
@ -194,7 +193,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 wrapSqlErrorf(err, "Failed to start a new transaction")
return processSqlErrorf(err, "Failed to start a new transaction")
}
defer tx.Rollback()
@ -210,7 +209,7 @@ func (s *SpaceStore) Delete(ctx context.Context, id int64) error {
return errors.Wrap(err, "Failed to count the child paths of the space")
} else if count > 0 {
// TODO: still returns 500
return errs.SpaceWithChildsCantBeDeleted
return store.ErrSpaceWithChildsCantBeDeleted
}
// delete all paths
@ -221,11 +220,11 @@ func (s *SpaceStore) Delete(ctx context.Context, id int64) error {
// delete the space
if _, err := tx.Exec(spaceDelete, id); err != nil {
return wrapSqlErrorf(err, "The delete query failed")
return processSqlErrorf(err, "The delete query failed")
}
if err = tx.Commit(); err != nil {
return wrapSqlErrorf(err, "Failed to commit transaction")
return processSqlErrorf(err, "Failed to commit transaction")
}
return nil
@ -236,7 +235,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, wrapSqlErrorf(err, "Failed executing count query")
return 0, processSqlErrorf(err, "Failed executing count query")
}
return count, nil
}
@ -251,7 +250,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, wrapSqlErrorf(err, "Failed executing default list query")
return nil, processSqlErrorf(err, "Failed executing default list query")
}
return dst, nil
}
@ -287,7 +286,7 @@ func (s *SpaceStore) List(ctx context.Context, id int64, opts *types.SpaceFilter
}
if err = s.db.SelectContext(ctx, &dst, sql); err != nil {
return nil, wrapSqlErrorf(err, "Failed executing custom list query")
return nil, processSqlErrorf(err, "Failed executing custom list query")
}
return dst, nil
@ -312,7 +311,7 @@ func (s *SpaceStore) CreatePath(ctx context.Context, spaceId int64, params *type
Updated: params.Updated,
}
return p, CreatePath(ctx, s.db, p)
return p, CreateAliasPath(ctx, s.db, p)
}
// Delete an alias of a space.

View File

@ -33,7 +33,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, wrapSqlErrorf(err, "Select by id query failed")
return nil, processSqlErrorf(err, "Select by id query failed")
}
return dst, nil
}
@ -42,7 +42,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, wrapSqlErrorf(err, "Select by email query failed")
return nil, processSqlErrorf(err, "Select by email query failed")
}
return dst, nil
}
@ -66,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, wrapSqlErrorf(err, "Failed executing default list query")
return nil, processSqlErrorf(err, "Failed executing default list query")
}
return dst, nil
}
@ -96,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, wrapSqlErrorf(err, "Failed executing custom list query")
return nil, processSqlErrorf(err, "Failed executing custom list query")
}
return dst, nil
@ -106,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 wrapSqlErrorf(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 wrapSqlErrorf(err, "Insert query failed")
return processSqlErrorf(err, "Insert query failed")
}
return nil
@ -120,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 wrapSqlErrorf(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 wrapSqlErrorf(err, "Update query failed")
return processSqlErrorf(err, "Update query failed")
}
return err
@ -134,12 +134,12 @@ 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 wrapSqlErrorf(err, "Failed to start a new transaction")
return processSqlErrorf(err, "Failed to start a new transaction")
}
defer tx.Rollback()
// delete the user
if _, err := tx.ExecContext(ctx, userDelete, user.ID); err != nil {
return wrapSqlErrorf(err, "The delete query failed")
return processSqlErrorf(err, "The delete query failed")
}
return tx.Commit()
}
@ -149,7 +149,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, wrapSqlErrorf(err, "Failed executing count query")
return 0, processSqlErrorf(err, "Failed executing count query")
}
return count, nil
}

View File

@ -5,7 +5,6 @@
package database
import (
"database/sql"
"fmt"
"strings"
"testing"
@ -237,18 +236,18 @@ func testUserUpdate(store store.UserStore) func(t *testing.T) {
// this test deletes an user from the database and then confirms
// subsequent attempts to fetch the deleted user result in
// a sql.ErrNoRows error.
func testUserDelete(store store.UserStore) func(t *testing.T) {
func testUserDelete(s store.UserStore) func(t *testing.T) {
return func(t *testing.T) {
v, err := store.Find(noContext, 1)
v, err := s.Find(noContext, 1)
if err != nil {
t.Error(err)
return
}
if err := store.Delete(noContext, v); err != nil {
if err := s.Delete(noContext, v); err != nil {
t.Error(err)
return
}
if _, err := store.Find(noContext, 1); err != sql.ErrNoRows {
if _, err := s.Find(noContext, 1); err != store.ErrResourceNotFound {
t.Errorf("Expected sql.ErrNoRows got %s", err)
}
}

View File

@ -6,10 +6,12 @@ package database
import (
"database/sql"
"fmt"
"github.com/harness/gitness/internal/errs"
"github.com/harness/gitness/internal/store"
"github.com/mattn/go-sqlite3"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
)
// default query range limit.
@ -35,14 +37,19 @@ func offset(page, size int) int {
return page * size
}
func wrapSqlErrorf(original error, format string, args ...interface{}) error {
if original == sql.ErrNoRows {
original = errs.WrapInResourceNotFound(original)
} else if isSqlUniqueConstraintError(original) {
original = errs.WrapInDuplicate(original)
// Logs the error and message, returns either the original error or a store equivalent if possible.
func processSqlErrorf(err error, format string, args ...interface{}) error {
// always log DB error (print formated message)
log.Warn().Msgf("%s %s", fmt.Sprintf(format, args...), err)
// If it's a known error, return converted error instead.
if err == sql.ErrNoRows {
return store.ErrResourceNotFound
} else if isSqlUniqueConstraintError(err) {
return store.ErrDuplicate
}
return errors.Wrapf(original, format, args...)
return err
}
func isSqlUniqueConstraintError(original error) bool {

20
internal/store/errors.go Normal file
View File

@ -0,0 +1,20 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package 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.")
)

View File

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

37
types/check/error.go Normal file
View File

@ -0,0 +1,37 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package check
var (
ErrAny = &CheckError{}
)
/*
* An error returned by check methods for any validation errors
* WARNING: This error will be printed to the user as is!
*/
type CheckError struct {
msg string
}
func (e *CheckError) 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 {
return true
}
// ensure it's the correct type
v, ok := target.(*CheckError)
if !ok {
return false
}
// only the same if the message is the same
return e.msg == v.msg
}

View File

@ -19,18 +19,20 @@ const (
)
var (
ErrPathEmpty = fmt.Errorf("Path can't be empty.")
ErrPathInvalidSize = fmt.Errorf("A path has to be between %d and %d segments long (%d for spaces).", minPathSegments, maxPathSegments, maxPathSegmentsForSpace)
ErrEmptyPathSegment = fmt.Errorf("Empty segments are not allowed.")
ErrPathCantBeginOrEndWithSeparator = fmt.Errorf("Path can't start or end with the separator ('%s').", types.PathSeparator)
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."}
)
/*
* PathParams checks the provided path params and returns an error in it isn't valid.
* 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 PathParams(path string, isSpace bool) error {
func Path(path string, isSpace bool) error {
if path == "" {
return ErrPathEmpty
@ -60,6 +62,37 @@ func PathParams(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.
*/
func PathParams(path *types.PathParams, currentPath string, isSpace bool) error {
// ensure the path is valid
if err := Path(path.Path, isSpace); err != nil {
return err
}
// ensure the path is at least 1 level deep (at least one '/')
i := strings.Index(path.Path, types.PathSeparator)
if i < 0 {
return ErrTopLevelPathNotAllowed
}
// ensure the top level space doesn't change (add path separator to avoid abcd -> abc matching)
if !strings.HasPrefix(currentPath+types.PathSeparator, path.Path[:i]+types.PathSeparator) {
return ErrPathDifferentTopLevelSpace
}
return nil
}
/*
* Checks if the provided path is too long.
*

View File

@ -5,13 +5,11 @@
package check
import (
"fmt"
"github.com/harness/gitness/types"
)
var (
ErrRepositoryRequiresSpaceId = fmt.Errorf("SpaceId required - Repositories don't exist outside of a space.")
ErrRepositoryRequiresSpaceId = &CheckError{"SpaceId required - Repositories don't exist outside of a space."}
)
// Repo checks the provided repository and returns an error in it isn't valid.

View File

@ -14,8 +14,8 @@ import (
var (
illegalRootSpaceNames = []string{"api"}
ErrRootSpaceNameNotAllowed = fmt.Errorf("The following names are not allowed for a root space: %v", illegalRootSpaceNames)
ErrInvalidParentSpaceId = fmt.Errorf("Parent space ID has to be either zero for a root space or greater than zero for a child space.")
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."}
)
// Repo checks the provided space and returns an error in it isn't valid.

View File

@ -18,7 +18,7 @@ const (
var (
// ErrEmailLen is returned when the email address
// exceeds the maximum number of characters.
ErrEmailLen = fmt.Errorf("Email address has to be within %d and %d characters", minEmailLength, maxEmailLength)
ErrEmailLen = &CheckError{fmt.Sprintf("Email address has to be within %d and %d characters", minEmailLength, maxEmailLength)}
)
// User returns true if the User if valid.

View File

@ -14,7 +14,7 @@ type Repository struct {
ID int64 `db:"repo_id" json:"id"`
Name string `db:"repo_name" json:"name"`
SpaceId int64 `db:"repo_spaceId" json:"spaceId"`
Path string `db:"repo_path" json:"path"`
Path string `db:"repo_path" json:"path"`
DisplayName string `db:"repo_displayName" json:"displayName"`
Description string `db:"repo_description" json:"description"`
IsPublic bool `db:"repo_isPublic" json:"isPublic"`