configstore: add more validations

All the validation must be done inside the configstore since it's the source of
truth.

The gateway could also do some validation to avoid bad requests to the
configstore when needed or when the logic resides outside the configstore (like
project setup or user registration)
This commit is contained in:
Simone Gotti 2019-05-03 12:41:49 +02:00
parent 1f09eea949
commit 60feff5cef
6 changed files with 113 additions and 51 deletions

View File

@ -49,6 +49,9 @@ func (s *CommandHandler) CreateProjectGroup(ctx context.Context, projectGroup *t
if projectGroup.Name == "" {
return nil, util.NewErrBadRequest(errors.Errorf("project group name required"))
}
if !util.ValidateName(projectGroup.Name) {
return nil, util.NewErrBadRequest(errors.Errorf("invalid project group name %q", projectGroup.Name))
}
if projectGroup.Parent.ID == "" {
return nil, util.NewErrBadRequest(errors.Errorf("project group parent id required"))
}
@ -58,7 +61,7 @@ func (s *CommandHandler) CreateProjectGroup(ctx context.Context, projectGroup *t
var cgt *datamanager.ChangeGroupsUpdateToken
// must do all the check in a single transaction to avoid concurrent changes
// must do all the checks in a single transaction to avoid concurrent changes
err := s.readDB.Do(func(tx *db.Tx) error {
parentProjectGroup, err := s.readDB.GetProjectGroup(tx, projectGroup.Parent.ID)
if err != nil {
@ -129,19 +132,39 @@ func (s *CommandHandler) CreateProject(ctx context.Context, project *types.Proje
if project.Name == "" {
return nil, util.NewErrBadRequest(errors.Errorf("project name required"))
}
if !util.ValidateName(project.Name) {
return nil, util.NewErrBadRequest(errors.Errorf("invalid project name %q", project.Name))
}
if project.Parent.ID == "" {
return nil, util.NewErrBadRequest(errors.Errorf("project parent id required"))
}
if project.Parent.Type != types.ConfigTypeProjectGroup {
return nil, util.NewErrBadRequest(errors.Errorf("invalid project parent type %q", project.Parent.Type))
}
if !types.IsValidVisibility(project.Visibility) {
return nil, util.NewErrBadRequest(errors.Errorf("invalid project visibility"))
}
if !types.IsValidRemoteRepositoryConfigType(project.RemoteRepositoryConfigType) {
return nil, util.NewErrBadRequest(errors.Errorf("invalid project remote repository config type %q", project.RemoteRepositoryConfigType))
}
if project.RemoteRepositoryConfigType == types.RemoteRepositoryConfigTypeRemoteSource {
if project.RemoteSourceID == "" {
return nil, util.NewErrBadRequest(errors.Errorf("empty remote source id"))
}
if project.LinkedAccountID == "" {
return nil, util.NewErrBadRequest(errors.Errorf("empty linked account id"))
}
if project.RepositoryID == "" {
return nil, util.NewErrBadRequest(errors.Errorf("empty remote repository id"))
}
if project.RepositoryPath == "" {
return nil, util.NewErrBadRequest(errors.Errorf("empty remote repository path"))
}
}
var cgt *datamanager.ChangeGroupsUpdateToken
// must do all the check in a single transaction to avoid concurrent changes
// must do all the checks in a single transaction to avoid concurrent changes
err := s.readDB.Do(func(tx *db.Tx) error {
var err error
group, err := s.readDB.GetProjectGroup(tx, project.Parent.ID)
@ -233,7 +256,7 @@ func (s *CommandHandler) DeleteProject(ctx context.Context, projectRef string) e
var cgt *datamanager.ChangeGroupsUpdateToken
// must do all the check in a single transaction to avoid concurrent changes
// must do all the checks in a single transaction to avoid concurrent changes
err := s.readDB.Do(func(tx *db.Tx) error {
var err error
@ -282,6 +305,9 @@ func (s *CommandHandler) CreateUser(ctx context.Context, req *CreateUserRequest)
if req.UserName == "" {
return nil, util.NewErrBadRequest(errors.Errorf("user name required"))
}
if !util.ValidateName(req.UserName) {
return nil, util.NewErrBadRequest(errors.Errorf("invalid user name %q", req.UserName))
}
var cgt *datamanager.ChangeGroupsUpdateToken
// changegroup is the username (and in future the email) to ensure no
@ -289,7 +315,7 @@ func (s *CommandHandler) CreateUser(ctx context.Context, req *CreateUserRequest)
cgNames := []string{util.EncodeSha256Hex("username-" + req.UserName)}
var rs *types.RemoteSource
// must do all the check in a single transaction to avoid concurrent changes
// must do all the checks in a single transaction to avoid concurrent changes
err := s.readDB.Do(func(tx *db.Tx) error {
var err error
cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames)
@ -391,7 +417,7 @@ func (s *CommandHandler) DeleteUser(ctx context.Context, userRef string) error {
var user *types.User
var cgt *datamanager.ChangeGroupsUpdateToken
// must do all the check in a single transaction to avoid concurrent changes
// must do all the checks in a single transaction to avoid concurrent changes
err := s.readDB.Do(func(tx *db.Tx) error {
var err error
@ -441,7 +467,7 @@ func (s *CommandHandler) UpdateUser(ctx context.Context, req *UpdateUserRequest)
cgNames := []string{}
var user *types.User
// must do all the check in a single transaction to avoid concurrent changes
// must do all the checks in a single transaction to avoid concurrent changes
err := s.readDB.Do(func(tx *db.Tx) error {
var err error
user, err = s.readDB.GetUser(tx, req.UserRef)
@ -524,7 +550,7 @@ func (s *CommandHandler) CreateUserLA(ctx context.Context, req *CreateUserLARequ
var cgt *datamanager.ChangeGroupsUpdateToken
// must do all the check in a single transaction to avoid concurrent changes
// must do all the checks in a single transaction to avoid concurrent changes
err := s.readDB.Do(func(tx *db.Tx) error {
var err error
user, err = s.readDB.GetUser(tx, req.UserRef)
@ -609,7 +635,7 @@ func (s *CommandHandler) DeleteUserLA(ctx context.Context, userRef, laID string)
var cgt *datamanager.ChangeGroupsUpdateToken
// must do all the check in a single transaction to avoid concurrent changes
// must do all the checks in a single transaction to avoid concurrent changes
err := s.readDB.Do(func(tx *db.Tx) error {
var err error
user, err = s.readDB.GetUser(tx, userRef)
@ -679,7 +705,7 @@ func (s *CommandHandler) UpdateUserLA(ctx context.Context, req *UpdateUserLARequ
var cgt *datamanager.ChangeGroupsUpdateToken
// must do all the check in a single transaction to avoid concurrent changes
// must do all the checks in a single transaction to avoid concurrent changes
err := s.readDB.Do(func(tx *db.Tx) error {
var err error
user, err = s.readDB.GetUser(tx, req.UserRef)
@ -753,7 +779,7 @@ func (s *CommandHandler) CreateUserToken(ctx context.Context, userRef, tokenName
var cgt *datamanager.ChangeGroupsUpdateToken
// must do all the check in a single transaction to avoid concurrent changes
// must do all the checks in a single transaction to avoid concurrent changes
err := s.readDB.Do(func(tx *db.Tx) error {
var err error
user, err = s.readDB.GetUser(tx, userRef)
@ -818,7 +844,7 @@ func (s *CommandHandler) DeleteUserToken(ctx context.Context, userRef, tokenName
var cgt *datamanager.ChangeGroupsUpdateToken
// must do all the check in a single transaction to avoid concurrent changes
// must do all the checks in a single transaction to avoid concurrent changes
err := s.readDB.Do(func(tx *db.Tx) error {
var err error
user, err = s.readDB.GetUser(tx, userRef)
@ -870,12 +896,41 @@ func (s *CommandHandler) CreateRemoteSource(ctx context.Context, remoteSource *t
if remoteSource.Name == "" {
return nil, util.NewErrBadRequest(errors.Errorf("remotesource name required"))
}
if !util.ValidateName(remoteSource.Name) {
return nil, util.NewErrBadRequest(errors.Errorf("invalid remotesource name %q", remoteSource.Name))
}
if remoteSource.Name == "" {
return nil, util.NewErrBadRequest(errors.Errorf("remotesource name required"))
}
if remoteSource.APIURL == "" {
return nil, util.NewErrBadRequest(errors.Errorf("remotesource api url required"))
}
if remoteSource.Type == "" {
return nil, util.NewErrBadRequest(errors.Errorf("remotesource type required"))
}
if remoteSource.AuthType == "" {
return nil, util.NewErrBadRequest(errors.Errorf("remotesource auth type required"))
}
// validate if the remote source type supports the required auth type
if !types.SourceSupportsAuthType(types.RemoteSourceType(remoteSource.Type), types.RemoteSourceAuthType(remoteSource.AuthType)) {
return nil, util.NewErrBadRequest(errors.Errorf("remotesource type %q doesn't support auth type %q", remoteSource.Type, remoteSource.AuthType))
}
if remoteSource.AuthType == types.RemoteSourceAuthTypeOauth2 {
if remoteSource.Oauth2ClientID == "" {
return nil, util.NewErrBadRequest(errors.Errorf("remotesource oauth2clientid required for auth type %q", types.RemoteSourceAuthTypeOauth2))
}
if remoteSource.Oauth2ClientSecret == "" {
return nil, util.NewErrBadRequest(errors.Errorf("remotesource oauth2clientsecret required for auth type %q", types.RemoteSourceAuthTypeOauth2))
}
}
var cgt *datamanager.ChangeGroupsUpdateToken
// changegroup is the remotesource name
cgNames := []string{util.EncodeSha256Hex("remotesourcename-" + remoteSource.Name)}
// must do all the check in a single transaction to avoid concurrent changes
// must do all the checks in a single transaction to avoid concurrent changes
err := s.readDB.Do(func(tx *db.Tx) error {
var err error
cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames)
@ -924,7 +979,7 @@ func (s *CommandHandler) DeleteRemoteSource(ctx context.Context, remoteSourceNam
// changegroup is the remotesource id
cgNames := []string{util.EncodeSha256Hex("remotesourceid-" + remoteSource.ID)}
// must do all the check in a single transaction to avoid concurrent changes
// must do all the checks in a single transaction to avoid concurrent changes
err := s.readDB.Do(func(tx *db.Tx) error {
var err error
cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames)
@ -961,14 +1016,17 @@ func (s *CommandHandler) DeleteRemoteSource(ctx context.Context, remoteSourceNam
func (s *CommandHandler) CreateOrg(ctx context.Context, org *types.Organization) (*types.Organization, error) {
if org.Name == "" {
return nil, util.NewErrBadRequest(errors.Errorf("org name required"))
return nil, util.NewErrBadRequest(errors.Errorf("organization name required"))
}
if !util.ValidateName(org.Name) {
return nil, util.NewErrBadRequest(errors.Errorf("invalid organization name %q", org.Name))
}
var cgt *datamanager.ChangeGroupsUpdateToken
// changegroup is the org name
cgNames := []string{util.EncodeSha256Hex("orgname-" + org.Name)}
// must do all the check in a single transaction to avoid concurrent changes
// must do all the checks in a single transaction to avoid concurrent changes
err := s.readDB.Do(func(tx *db.Tx) error {
var err error
cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames)
@ -1032,7 +1090,7 @@ func (s *CommandHandler) DeleteOrg(ctx context.Context, orgRef string) error {
var cgt *datamanager.ChangeGroupsUpdateToken
// must do all the check in a single transaction to avoid concurrent changes
// must do all the checks in a single transaction to avoid concurrent changes
err := s.readDB.Do(func(tx *db.Tx) error {
var err error
// check org existance
@ -1082,8 +1140,11 @@ func (s *CommandHandler) CreateSecret(ctx context.Context, secret *types.Secret)
if secret.Name == "" {
return nil, util.NewErrBadRequest(errors.Errorf("secret name required"))
}
if !util.ValidateName(secret.Name) {
return nil, util.NewErrBadRequest(errors.Errorf("invalid secret name %q", secret.Name))
}
if secret.Type != types.SecretTypeInternal {
return nil, util.NewErrBadRequest(errors.Errorf("wrong secret type %q", secret.Type))
return nil, util.NewErrBadRequest(errors.Errorf("invalid secret type %q", secret.Type))
}
switch secret.Type {
case types.SecretTypeInternal:
@ -1105,7 +1166,7 @@ func (s *CommandHandler) CreateSecret(ctx context.Context, secret *types.Secret)
// changegroup is the secret name
cgNames := []string{util.EncodeSha256Hex("secretname-" + secret.Name)}
// must do all the check in a single transaction to avoid concurrent changes
// must do all the checks in a single transaction to avoid concurrent changes
err := s.readDB.Do(func(tx *db.Tx) error {
var err error
cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames)
@ -1158,7 +1219,7 @@ func (s *CommandHandler) DeleteSecret(ctx context.Context, parentType types.Conf
var cgt *datamanager.ChangeGroupsUpdateToken
// must do all the check in a single transaction to avoid concurrent changes
// must do all the checks in a single transaction to avoid concurrent changes
err := s.readDB.Do(func(tx *db.Tx) error {
var err error
parentID, err := s.readDB.ResolveConfigID(tx, parentType, parentRef)
@ -1204,6 +1265,9 @@ func (s *CommandHandler) CreateVariable(ctx context.Context, variable *types.Var
if variable.Name == "" {
return nil, util.NewErrBadRequest(errors.Errorf("variable name required"))
}
if !util.ValidateName(variable.Name) {
return nil, util.NewErrBadRequest(errors.Errorf("invalid variable name %q", variable.Name))
}
if len(variable.Values) == 0 {
return nil, util.NewErrBadRequest(errors.Errorf("variable values required"))
}
@ -1221,7 +1285,7 @@ func (s *CommandHandler) CreateVariable(ctx context.Context, variable *types.Var
// changegroup is the variable name
cgNames := []string{util.EncodeSha256Hex("variablename-" + variable.Name)}
// must do all the check in a single transaction to avoid concurrent changes
// must do all the checks in a single transaction to avoid concurrent changes
err := s.readDB.Do(func(tx *db.Tx) error {
var err error
cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames)
@ -1274,7 +1338,7 @@ func (s *CommandHandler) DeleteVariable(ctx context.Context, parentType types.Co
var cgt *datamanager.ChangeGroupsUpdateToken
// must do all the check in a single transaction to avoid concurrent changes
// must do all the checks in a single transaction to avoid concurrent changes
err := s.readDB.Do(func(tx *db.Tx) error {
var err error
parentID, err := s.readDB.ResolveConfigID(tx, parentType, parentRef)

View File

@ -420,7 +420,7 @@ func TestProjectGroupsAndProjects(t *testing.T) {
t.Run("create a project in user non root project group with same name as a root project", func(t *testing.T) {
_, err := cs.ch.CreateProject(ctx, &types.Project{Name: "project01", Parent: types.Parent{Type: types.ConfigTypeProjectGroup, ID: path.Join("user", user.Name, "projectgroup01")}, Visibility: types.VisibilityPublic, RemoteRepositoryConfigType: types.RemoteRepositoryConfigTypeManual})
if err != nil {
t.Fatalf("unexpected err: %+#v", err)
t.Fatalf("unexpected err: %v", err)
}
})
t.Run("create a project in org non root project group with same name as a root project", func(t *testing.T) {

View File

@ -45,7 +45,7 @@ func (c *CommandHandler) CreateProject(ctx context.Context, req *CreateProjectRe
return nil, util.NewErrBadRequest(errors.Errorf("empty remote source name"))
}
if req.RepoPath == "" {
return nil, util.NewErrBadRequest(errors.Errorf("empty remote repo path name"))
return nil, util.NewErrBadRequest(errors.Errorf("empty remote repo path"))
}
user, resp, err := c.configstoreClient.GetUser(ctx, req.CurrentUserID)

View File

@ -17,7 +17,6 @@ package command
import (
"context"
"github.com/sorintlab/agola/internal/services/gateway/common"
"github.com/sorintlab/agola/internal/services/types"
"github.com/sorintlab/agola/internal/util"
@ -52,7 +51,7 @@ func (c *CommandHandler) CreateRemoteSource(ctx context.Context, req *CreateRemo
}
// validate if the remote source type supports the required auth type
if !common.SourceSupportsAuthType(types.RemoteSourceType(req.Type), types.RemoteSourceAuthType(req.AuthType)) {
if !types.SourceSupportsAuthType(types.RemoteSourceType(req.Type), types.RemoteSourceAuthType(req.AuthType)) {
return nil, util.NewErrBadRequest(errors.Errorf("remotesource type %q doesn't support auth type %q", req.Type, req.AuthType))
}

View File

@ -15,8 +15,6 @@
package common
import (
"fmt"
gitsource "github.com/sorintlab/agola/internal/gitsources"
"github.com/sorintlab/agola/internal/gitsources/gitea"
"github.com/sorintlab/agola/internal/gitsources/gitlab"
@ -25,30 +23,6 @@ import (
"github.com/pkg/errors"
)
func SourceSupportedAuthTypes(rsType types.RemoteSourceType) []types.RemoteSourceAuthType {
switch rsType {
case types.RemoteSourceTypeGitea:
return []types.RemoteSourceAuthType{types.RemoteSourceAuthTypePassword}
case types.RemoteSourceTypeGithub:
fallthrough
case types.RemoteSourceTypeGitlab:
return []types.RemoteSourceAuthType{types.RemoteSourceAuthTypeOauth2}
default:
panic(fmt.Errorf("unsupported remote source type: %q", rsType))
}
}
func SourceSupportsAuthType(rsType types.RemoteSourceType, authType types.RemoteSourceAuthType) bool {
supportedAuthTypes := SourceSupportedAuthTypes(rsType)
for _, st := range supportedAuthTypes {
if st == authType {
return true
}
}
return false
}
func newGitea(rs *types.RemoteSource, accessToken string) (*gitea.Client, error) {
return gitea.New(gitea.Opts{
URL: rs.APIURL,

View File

@ -15,6 +15,7 @@
package types
import (
"fmt"
"regexp"
"time"
)
@ -129,6 +130,30 @@ type RemoteSource struct {
Oauth2ClientSecret string `json:"client_secret,omitempty"`
}
func SourceSupportedAuthTypes(rsType RemoteSourceType) []RemoteSourceAuthType {
switch rsType {
case RemoteSourceTypeGitea:
return []RemoteSourceAuthType{RemoteSourceAuthTypePassword}
case RemoteSourceTypeGithub:
fallthrough
case RemoteSourceTypeGitlab:
return []RemoteSourceAuthType{RemoteSourceAuthTypeOauth2}
default:
panic(fmt.Errorf("unsupported remote source type: %q", rsType))
}
}
func SourceSupportsAuthType(rsType RemoteSourceType, authType RemoteSourceAuthType) bool {
supportedAuthTypes := SourceSupportedAuthTypes(rsType)
for _, st := range supportedAuthTypes {
if st == authType {
return true
}
}
return false
}
type LinkedAccount struct {
// The type version. Increase when a breaking change is done. Usually not
// needed when adding fields.