agola/internal/services/configstore/command/command.go
Simone Gotti 60feff5cef 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)
2019-05-03 12:41:49 +02:00

1381 lines
39 KiB
Go

// Copyright 2019 Sorint.lab
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied
// See the License for the specific language governing permissions and
// limitations under the License.
package command
import (
"context"
"encoding/json"
"path"
"time"
"github.com/sorintlab/agola/internal/datamanager"
"github.com/sorintlab/agola/internal/db"
"github.com/sorintlab/agola/internal/services/configstore/readdb"
"github.com/sorintlab/agola/internal/services/types"
"github.com/sorintlab/agola/internal/util"
"github.com/pkg/errors"
uuid "github.com/satori/go.uuid"
"go.uber.org/zap"
)
type CommandHandler struct {
log *zap.SugaredLogger
readDB *readdb.ReadDB
dm *datamanager.DataManager
}
func NewCommandHandler(logger *zap.Logger, readDB *readdb.ReadDB, dm *datamanager.DataManager) *CommandHandler {
return &CommandHandler{
log: logger.Sugar(),
readDB: readDB,
dm: dm,
}
}
func (s *CommandHandler) CreateProjectGroup(ctx context.Context, projectGroup *types.ProjectGroup) (*types.ProjectGroup, error) {
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"))
}
if !types.IsValidVisibility(projectGroup.Visibility) {
return nil, util.NewErrBadRequest(errors.Errorf("invalid project group visibility"))
}
var cgt *datamanager.ChangeGroupsUpdateToken
// 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 {
return err
}
if parentProjectGroup == nil {
return util.NewErrBadRequest(errors.Errorf("project group with id %q doesn't exist", projectGroup.Parent.ID))
}
projectGroup.Parent.ID = parentProjectGroup.ID
groupPath, err := s.readDB.GetProjectGroupPath(tx, parentProjectGroup)
if err != nil {
return err
}
pp := path.Join(groupPath, projectGroup.Name)
// changegroup is the projectgroup path. Use "projectpath" prefix as it must
// cover both projects and projectgroups
cgNames := []string{util.EncodeSha256Hex("projectpath-" + pp)}
cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames)
if err != nil {
return err
}
// check duplicate project name
p, err := s.readDB.GetProjectByName(tx, projectGroup.Parent.ID, projectGroup.Name)
if err != nil {
return err
}
if p != nil {
return util.NewErrBadRequest(errors.Errorf("project with name %q, path %q already exists", p.Name, pp))
}
// check duplicate project group name
pg, err := s.readDB.GetProjectGroupByName(tx, projectGroup.Parent.ID, projectGroup.Name)
if err != nil {
return err
}
if pg != nil {
return util.NewErrBadRequest(errors.Errorf("project group with name %q, path %q already exists", pg.Name, pp))
}
return nil
})
if err != nil {
return nil, err
}
projectGroup.ID = uuid.NewV4().String()
projectGroup.Parent.Type = types.ConfigTypeProjectGroup
pcj, err := json.Marshal(projectGroup)
if err != nil {
return nil, errors.Wrapf(err, "failed to marshal projectGroup")
}
actions := []*datamanager.Action{
{
ActionType: datamanager.ActionTypePut,
DataType: string(types.ConfigTypeProjectGroup),
ID: projectGroup.ID,
Data: pcj,
},
}
_, err = s.dm.WriteWal(ctx, actions, cgt)
return projectGroup, err
}
func (s *CommandHandler) CreateProject(ctx context.Context, project *types.Project) (*types.Project, error) {
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 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)
if err != nil {
return err
}
if group == nil {
return util.NewErrBadRequest(errors.Errorf("project group with id %q doesn't exist", project.Parent.ID))
}
project.Parent.ID = group.ID
groupPath, err := s.readDB.GetProjectGroupPath(tx, group)
if err != nil {
return err
}
pp := path.Join(groupPath, project.Name)
// changegroup is the project path. Use "projectpath" prefix as it must
// cover both projects and projectgroups
cgNames := []string{util.EncodeSha256Hex("projectpath-" + pp)}
cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames)
if err != nil {
return err
}
// check duplicate project name
p, err := s.readDB.GetProjectByName(tx, project.Parent.ID, project.Name)
if err != nil {
return err
}
if p != nil {
return util.NewErrBadRequest(errors.Errorf("project with name %q, path %q already exists", p.Name, pp))
}
// check duplicate project group name
pg, err := s.readDB.GetProjectGroupByName(tx, project.Parent.ID, project.Name)
if err != nil {
return err
}
if pg != nil {
return util.NewErrBadRequest(errors.Errorf("project group with name %q, path %q already exists", pg.Name, pp))
}
if project.RemoteRepositoryConfigType == types.RemoteRepositoryConfigTypeRemoteSource {
// check that the linked account matches the remote source
user, err := s.readDB.GetUserByLinkedAccount(tx, project.LinkedAccountID)
if err != nil {
return errors.Wrapf(err, "failed to get user with linked account id %q", project.LinkedAccountID)
}
if user == nil {
return util.NewErrBadRequest(errors.Errorf("user for linked account %q doesn't exist", project.LinkedAccountID))
}
la, ok := user.LinkedAccounts[project.LinkedAccountID]
if !ok {
return util.NewErrBadRequest(errors.Errorf("linked account id %q for user %q doesn't exist", project.LinkedAccountID, user.Name))
}
if la.RemoteSourceID != project.RemoteSourceID {
return util.NewErrBadRequest(errors.Errorf("linked account id %q remote source %q different than project remote source %q", project.LinkedAccountID, la.RemoteSourceID, project.RemoteSourceID))
}
}
return nil
})
if err != nil {
return nil, err
}
project.ID = uuid.NewV4().String()
project.Parent.Type = types.ConfigTypeProjectGroup
pcj, err := json.Marshal(project)
if err != nil {
return nil, errors.Wrapf(err, "failed to marshal project")
}
actions := []*datamanager.Action{
{
ActionType: datamanager.ActionTypePut,
DataType: string(types.ConfigTypeProject),
ID: project.ID,
Data: pcj,
},
}
_, err = s.dm.WriteWal(ctx, actions, cgt)
return project, err
}
func (s *CommandHandler) DeleteProject(ctx context.Context, projectRef string) error {
var project *types.Project
var cgt *datamanager.ChangeGroupsUpdateToken
// 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 project existance
project, err = s.readDB.GetProject(tx, projectRef)
if err != nil {
return err
}
if project == nil {
return util.NewErrBadRequest(errors.Errorf("project %q doesn't exist", projectRef))
}
// changegroup is the project id.
cgNames := []string{util.EncodeSha256Hex(project.ID)}
cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames)
if err != nil {
return err
}
return nil
})
if err != nil {
return err
}
// TODO(sgotti) delete project secrets/variables
actions := []*datamanager.Action{
{
ActionType: datamanager.ActionTypeDelete,
DataType: string(types.ConfigTypeProject),
ID: project.ID,
},
}
_, err = s.dm.WriteWal(ctx, actions, cgt)
return err
}
type CreateUserRequest struct {
UserName string
CreateUserLARequest *CreateUserLARequest
}
func (s *CommandHandler) CreateUser(ctx context.Context, req *CreateUserRequest) (*types.User, error) {
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
// concurrent user creation/modification using the same name
cgNames := []string{util.EncodeSha256Hex("username-" + req.UserName)}
var rs *types.RemoteSource
// 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)
if err != nil {
return err
}
// check duplicate user name
u, err := s.readDB.GetUserByName(tx, req.UserName)
if err != nil {
return err
}
if u != nil {
return util.NewErrBadRequest(errors.Errorf("user with name %q already exists", u.Name))
}
if req.CreateUserLARequest != nil {
rs, err = s.readDB.GetRemoteSourceByName(tx, req.CreateUserLARequest.RemoteSourceName)
if err != nil {
return err
}
if rs == nil {
return util.NewErrBadRequest(errors.Errorf("remote source %q doesn't exist", req.CreateUserLARequest.RemoteSourceName))
}
user, err := s.readDB.GetUserByLinkedAccountRemoteUserIDandSource(tx, req.CreateUserLARequest.RemoteUserID, rs.ID)
if err != nil {
return errors.Wrapf(err, "failed to get user for remote user id %q and remote source %q", req.CreateUserLARequest.RemoteUserID, rs.ID)
}
if user != nil {
return util.NewErrBadRequest(errors.Errorf("user for remote user id %q for remote source %q already exists", req.CreateUserLARequest.RemoteUserID, req.CreateUserLARequest.RemoteSourceName))
}
}
return nil
})
if err != nil {
return nil, err
}
user := &types.User{
ID: uuid.NewV4().String(),
Name: req.UserName,
}
if req.CreateUserLARequest != nil {
if user.LinkedAccounts == nil {
user.LinkedAccounts = make(map[string]*types.LinkedAccount)
}
la := &types.LinkedAccount{
ID: uuid.NewV4().String(),
RemoteSourceID: rs.ID,
RemoteUserID: req.CreateUserLARequest.RemoteUserID,
RemoteUserName: req.CreateUserLARequest.RemoteUserName,
UserAccessToken: req.CreateUserLARequest.UserAccessToken,
Oauth2AccessToken: req.CreateUserLARequest.Oauth2AccessToken,
Oauth2RefreshToken: req.CreateUserLARequest.Oauth2RefreshToken,
Oauth2AccessTokenExpiresAt: req.CreateUserLARequest.Oauth2AccessTokenExpiresAt,
}
user.LinkedAccounts[la.ID] = la
}
userj, err := json.Marshal(user)
if err != nil {
return nil, errors.Wrapf(err, "failed to marshal user")
}
pg := &types.ProjectGroup{
ID: uuid.NewV4().String(),
Parent: types.Parent{
Type: types.ConfigTypeUser,
ID: user.ID,
},
}
pgj, err := json.Marshal(pg)
if err != nil {
return nil, errors.Wrapf(err, "failed to marshal project group")
}
actions := []*datamanager.Action{
{
ActionType: datamanager.ActionTypePut,
DataType: string(types.ConfigTypeUser),
ID: user.ID,
Data: userj,
},
{
ActionType: datamanager.ActionTypePut,
DataType: string(types.ConfigTypeProjectGroup),
ID: pg.ID,
Data: pgj,
},
}
_, err = s.dm.WriteWal(ctx, actions, cgt)
return user, err
}
func (s *CommandHandler) DeleteUser(ctx context.Context, userRef string) error {
var user *types.User
var cgt *datamanager.ChangeGroupsUpdateToken
// 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 user existance
user, err = s.readDB.GetUser(tx, userRef)
if err != nil {
return err
}
if user == nil {
return util.NewErrBadRequest(errors.Errorf("user %q doesn't exist", userRef))
}
// changegroup is the userid
cgNames := []string{util.EncodeSha256Hex("userid-" + user.ID)}
cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames)
if err != nil {
return err
}
return nil
})
if err != nil {
return err
}
actions := []*datamanager.Action{
{
ActionType: datamanager.ActionTypeDelete,
DataType: string(types.ConfigTypeUser),
ID: user.ID,
},
}
_, err = s.dm.WriteWal(ctx, actions, cgt)
return err
}
type UpdateUserRequest struct {
UserRef string
UserName string
}
func (s *CommandHandler) UpdateUser(ctx context.Context, req *UpdateUserRequest) (*types.User, error) {
var cgt *datamanager.ChangeGroupsUpdateToken
cgNames := []string{}
var user *types.User
// 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)
if err != nil {
return err
}
if user == nil {
return util.NewErrBadRequest(errors.Errorf("user %q doesn't exist", req.UserRef))
}
cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames)
if err != nil {
return err
}
if req.UserName != "" {
// check duplicate user name
u, err := s.readDB.GetUserByName(tx, req.UserName)
if err != nil {
return err
}
if u != nil {
return util.NewErrBadRequest(errors.Errorf("user with name %q already exists", u.Name))
}
// changegroup is the username (and in future the email) to ensure no
// concurrent user creation/modification using the same name
cgNames = append(cgNames, util.EncodeSha256Hex("username-"+req.UserName))
}
return nil
})
if err != nil {
return nil, err
}
if req.UserName != "" {
user.Name = req.UserName
}
userj, err := json.Marshal(user)
if err != nil {
return nil, errors.Wrapf(err, "failed to marshal user")
}
actions := []*datamanager.Action{
{
ActionType: datamanager.ActionTypePut,
DataType: string(types.ConfigTypeUser),
ID: user.ID,
Data: userj,
},
}
_, err = s.dm.WriteWal(ctx, actions, cgt)
return user, err
}
type CreateUserLARequest struct {
UserRef string
RemoteSourceName string
RemoteUserID string
RemoteUserName string
UserAccessToken string
Oauth2AccessToken string
Oauth2RefreshToken string
Oauth2AccessTokenExpiresAt time.Time
}
func (s *CommandHandler) CreateUserLA(ctx context.Context, req *CreateUserLARequest) (*types.LinkedAccount, error) {
if req.UserRef == "" {
return nil, util.NewErrBadRequest(errors.Errorf("user ref required"))
}
if req.RemoteSourceName == "" {
return nil, util.NewErrBadRequest(errors.Errorf("remote source name required"))
}
var user *types.User
var rs *types.RemoteSource
var cgt *datamanager.ChangeGroupsUpdateToken
// 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)
if err != nil {
return err
}
if user == nil {
return util.NewErrBadRequest(errors.Errorf("user %q doesn't exist", req.UserRef))
}
// changegroup is the userid
cgNames := []string{util.EncodeSha256Hex("userid-" + user.ID)}
cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames)
if err != nil {
return err
}
rs, err = s.readDB.GetRemoteSourceByName(tx, req.RemoteSourceName)
if err != nil {
return err
}
if rs == nil {
return util.NewErrBadRequest(errors.Errorf("remote source %q doesn't exist", req.RemoteSourceName))
}
user, err := s.readDB.GetUserByLinkedAccountRemoteUserIDandSource(tx, req.RemoteUserID, rs.ID)
if err != nil {
return errors.Wrapf(err, "failed to get user for remote user id %q and remote source %q", req.RemoteUserID, rs.ID)
}
if user != nil {
return util.NewErrBadRequest(errors.Errorf("user for remote user id %q for remote source %q already exists", req.RemoteUserID, req.RemoteSourceName))
}
return nil
})
if err != nil {
return nil, err
}
if user.LinkedAccounts == nil {
user.LinkedAccounts = make(map[string]*types.LinkedAccount)
}
la := &types.LinkedAccount{
ID: uuid.NewV4().String(),
RemoteSourceID: rs.ID,
RemoteUserID: req.RemoteUserID,
RemoteUserName: req.RemoteUserName,
UserAccessToken: req.UserAccessToken,
Oauth2AccessToken: req.Oauth2AccessToken,
Oauth2RefreshToken: req.Oauth2RefreshToken,
Oauth2AccessTokenExpiresAt: req.Oauth2AccessTokenExpiresAt,
}
user.LinkedAccounts[la.ID] = la
userj, err := json.Marshal(user)
if err != nil {
return nil, errors.Wrapf(err, "failed to marshal user")
}
actions := []*datamanager.Action{
{
ActionType: datamanager.ActionTypePut,
DataType: string(types.ConfigTypeUser),
ID: user.ID,
Data: userj,
},
}
_, err = s.dm.WriteWal(ctx, actions, cgt)
return la, err
}
func (s *CommandHandler) DeleteUserLA(ctx context.Context, userRef, laID string) error {
if userRef == "" {
return util.NewErrBadRequest(errors.Errorf("user ref required"))
}
if laID == "" {
return util.NewErrBadRequest(errors.Errorf("user linked account id required"))
}
var user *types.User
var cgt *datamanager.ChangeGroupsUpdateToken
// 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)
if err != nil {
return err
}
if user == nil {
return util.NewErrBadRequest(errors.Errorf("user %q doesn't exist", userRef))
}
// changegroup is the userid
cgNames := []string{util.EncodeSha256Hex("userid-" + user.ID)}
cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames)
if err != nil {
return err
}
return nil
})
if err != nil {
return err
}
_, ok := user.LinkedAccounts[laID]
if !ok {
return util.NewErrBadRequest(errors.Errorf("linked account id %q for user %q doesn't exist", laID, userRef))
}
delete(user.LinkedAccounts, laID)
userj, err := json.Marshal(user)
if err != nil {
return errors.Wrapf(err, "failed to marshal user")
}
actions := []*datamanager.Action{
{
ActionType: datamanager.ActionTypePut,
DataType: string(types.ConfigTypeUser),
ID: user.ID,
Data: userj,
},
}
_, err = s.dm.WriteWal(ctx, actions, cgt)
return err
}
type UpdateUserLARequest struct {
UserRef string
LinkedAccountID string
RemoteUserID string
RemoteUserName string
UserAccessToken string
Oauth2AccessToken string
Oauth2RefreshToken string
Oauth2AccessTokenExpiresAt time.Time
}
func (s *CommandHandler) UpdateUserLA(ctx context.Context, req *UpdateUserLARequest) (*types.LinkedAccount, error) {
if req.UserRef == "" {
return nil, util.NewErrBadRequest(errors.Errorf("user ref required"))
}
var user *types.User
var rs *types.RemoteSource
var cgt *datamanager.ChangeGroupsUpdateToken
// 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)
if err != nil {
return err
}
if user == nil {
return util.NewErrBadRequest(errors.Errorf("user %q doesn't exist", req.UserRef))
}
// changegroup is the userid
cgNames := []string{util.EncodeSha256Hex("userid-" + user.ID)}
cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames)
if err != nil {
return err
}
la, ok := user.LinkedAccounts[req.LinkedAccountID]
if !ok {
return util.NewErrBadRequest(errors.Errorf("linked account id %q for user %q doesn't exist", req.LinkedAccountID, user.Name))
}
rs, err = s.readDB.GetRemoteSource(tx, la.RemoteSourceID)
if err != nil {
return err
}
if rs == nil {
return util.NewErrBadRequest(errors.Errorf("remote source with id %q doesn't exist", la.RemoteSourceID))
}
return nil
})
if err != nil {
return nil, err
}
la := user.LinkedAccounts[req.LinkedAccountID]
la.RemoteUserID = req.RemoteUserID
la.RemoteUserName = req.RemoteUserName
la.UserAccessToken = req.UserAccessToken
la.Oauth2AccessToken = req.Oauth2AccessToken
la.Oauth2RefreshToken = req.Oauth2RefreshToken
la.Oauth2AccessTokenExpiresAt = req.Oauth2AccessTokenExpiresAt
userj, err := json.Marshal(user)
if err != nil {
return nil, errors.Wrapf(err, "failed to marshal user")
}
actions := []*datamanager.Action{
{
ActionType: datamanager.ActionTypePut,
DataType: string(types.ConfigTypeUser),
ID: user.ID,
Data: userj,
},
}
_, err = s.dm.WriteWal(ctx, actions, cgt)
return la, err
}
func (s *CommandHandler) CreateUserToken(ctx context.Context, userRef, tokenName string) (string, error) {
if userRef == "" {
return "", util.NewErrBadRequest(errors.Errorf("user ref required"))
}
if tokenName == "" {
return "", util.NewErrBadRequest(errors.Errorf("token name required"))
}
var user *types.User
var cgt *datamanager.ChangeGroupsUpdateToken
// 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)
if err != nil {
return err
}
if user == nil {
return util.NewErrBadRequest(errors.Errorf("user %q doesn't exist", userRef))
}
// changegroup is the userid
cgNames := []string{util.EncodeSha256Hex("userid-" + user.ID)}
cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames)
if err != nil {
return err
}
return nil
})
if err != nil {
return "", err
}
if user.Tokens != nil {
if _, ok := user.Tokens[tokenName]; ok {
return "", util.NewErrBadRequest(errors.Errorf("token %q for user %q already exists", tokenName, userRef))
}
}
if user.Tokens == nil {
user.Tokens = make(map[string]string)
}
token := util.EncodeSha1Hex(uuid.NewV4().String())
user.Tokens[tokenName] = token
userj, err := json.Marshal(user)
if err != nil {
return "", errors.Wrapf(err, "failed to marshal user")
}
actions := []*datamanager.Action{
{
ActionType: datamanager.ActionTypePut,
DataType: string(types.ConfigTypeUser),
ID: user.ID,
Data: userj,
},
}
_, err = s.dm.WriteWal(ctx, actions, cgt)
return token, err
}
func (s *CommandHandler) DeleteUserToken(ctx context.Context, userRef, tokenName string) error {
if userRef == "" {
return util.NewErrBadRequest(errors.Errorf("user ref required"))
}
if tokenName == "" {
return util.NewErrBadRequest(errors.Errorf("token name required"))
}
var user *types.User
var cgt *datamanager.ChangeGroupsUpdateToken
// 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)
if err != nil {
return err
}
if user == nil {
return util.NewErrBadRequest(errors.Errorf("user %q doesn't exist", userRef))
}
// changegroup is the userid
cgNames := []string{util.EncodeSha256Hex("userid-" + user.ID)}
cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames)
if err != nil {
return err
}
return nil
})
if err != nil {
return err
}
_, ok := user.Tokens[tokenName]
if !ok {
return util.NewErrBadRequest(errors.Errorf("token %q for user %q doesn't exist", tokenName, userRef))
}
delete(user.Tokens, tokenName)
userj, err := json.Marshal(user)
if err != nil {
return errors.Wrapf(err, "failed to marshal user")
}
actions := []*datamanager.Action{
{
ActionType: datamanager.ActionTypePut,
DataType: string(types.ConfigTypeUser),
ID: user.ID,
Data: userj,
},
}
_, err = s.dm.WriteWal(ctx, actions, cgt)
return err
}
func (s *CommandHandler) CreateRemoteSource(ctx context.Context, remoteSource *types.RemoteSource) (*types.RemoteSource, error) {
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 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)
if err != nil {
return err
}
// check duplicate remoteSource name
u, err := s.readDB.GetRemoteSourceByName(tx, remoteSource.Name)
if err != nil {
return err
}
if u != nil {
return util.NewErrBadRequest(errors.Errorf("remoteSource %q already exists", u.Name))
}
return nil
})
if err != nil {
return nil, err
}
remoteSource.ID = uuid.NewV4().String()
rsj, err := json.Marshal(remoteSource)
if err != nil {
return nil, errors.Wrapf(err, "failed to marshal remotesource")
}
actions := []*datamanager.Action{
{
ActionType: datamanager.ActionTypePut,
DataType: string(types.ConfigTypeRemoteSource),
ID: remoteSource.ID,
Data: rsj,
},
}
_, err = s.dm.WriteWal(ctx, actions, cgt)
return remoteSource, err
}
func (s *CommandHandler) DeleteRemoteSource(ctx context.Context, remoteSourceName string) error {
var remoteSource *types.RemoteSource
var cgt *datamanager.ChangeGroupsUpdateToken
// changegroup is the remotesource id
cgNames := []string{util.EncodeSha256Hex("remotesourceid-" + remoteSource.ID)}
// 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)
if err != nil {
return err
}
// check remoteSource existance
remoteSource, err = s.readDB.GetRemoteSourceByName(tx, remoteSourceName)
if err != nil {
return err
}
if remoteSource == nil {
return util.NewErrBadRequest(errors.Errorf("remotesource %q doesn't exist", remoteSourceName))
}
return nil
})
if err != nil {
return err
}
actions := []*datamanager.Action{
{
ActionType: datamanager.ActionTypeDelete,
DataType: string(types.ConfigTypeRemoteSource),
ID: remoteSource.ID,
},
}
// changegroup is all the remote sources
_, err = s.dm.WriteWal(ctx, actions, cgt)
return err
}
func (s *CommandHandler) CreateOrg(ctx context.Context, org *types.Organization) (*types.Organization, error) {
if org.Name == "" {
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 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)
if err != nil {
return err
}
// check duplicate org name
u, err := s.readDB.GetOrgByName(tx, org.Name)
if err != nil {
return err
}
if u != nil {
return util.NewErrBadRequest(errors.Errorf("org %q already exists", u.Name))
}
return nil
})
if err != nil {
return nil, err
}
org.ID = uuid.NewV4().String()
orgj, err := json.Marshal(org)
if err != nil {
return nil, errors.Wrapf(err, "failed to marshal org")
}
pg := &types.ProjectGroup{
ID: uuid.NewV4().String(),
Parent: types.Parent{
Type: types.ConfigTypeOrg,
ID: org.ID,
},
}
pgj, err := json.Marshal(pg)
if err != nil {
return nil, errors.Wrapf(err, "failed to marshal project group")
}
actions := []*datamanager.Action{
{
ActionType: datamanager.ActionTypePut,
DataType: string(types.ConfigTypeOrg),
ID: org.ID,
Data: orgj,
},
{
ActionType: datamanager.ActionTypePut,
DataType: string(types.ConfigTypeProjectGroup),
ID: pg.ID,
Data: pgj,
},
}
_, err = s.dm.WriteWal(ctx, actions, cgt)
return org, err
}
func (s *CommandHandler) DeleteOrg(ctx context.Context, orgRef string) error {
var org *types.Organization
var projects []*types.Project
var cgt *datamanager.ChangeGroupsUpdateToken
// 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
org, err = s.readDB.GetOrgByName(tx, orgRef)
if err != nil {
return err
}
if org == nil {
return util.NewErrBadRequest(errors.Errorf("org %q doesn't exist", orgRef))
}
// changegroup is the org id
cgNames := []string{util.EncodeSha256Hex("orgid-" + org.ID)}
cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames)
if err != nil {
return err
}
return nil
})
if err != nil {
return err
}
// TODO(sgotti) delete all project groups, projects etc...
actions := []*datamanager.Action{
{
ActionType: datamanager.ActionTypeDelete,
DataType: string(types.ConfigTypeOrg),
ID: org.ID,
},
}
// delete all org projects
for _, project := range projects {
actions = append(actions, &datamanager.Action{
ActionType: datamanager.ActionTypeDelete,
DataType: string(types.ConfigTypeProject),
ID: project.ID,
})
}
_, err = s.dm.WriteWal(ctx, actions, cgt)
return err
}
func (s *CommandHandler) CreateSecret(ctx context.Context, secret *types.Secret) (*types.Secret, error) {
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("invalid secret type %q", secret.Type))
}
switch secret.Type {
case types.SecretTypeInternal:
if len(secret.Data) == 0 {
return nil, util.NewErrBadRequest(errors.Errorf("empty secret data"))
}
}
if secret.Parent.Type == "" {
return nil, util.NewErrBadRequest(errors.Errorf("secret parent type required"))
}
if secret.Parent.ID == "" {
return nil, util.NewErrBadRequest(errors.Errorf("secret parentid required"))
}
if secret.Parent.Type != types.ConfigTypeProject && secret.Parent.Type != types.ConfigTypeProjectGroup {
return nil, util.NewErrBadRequest(errors.Errorf("invalid secret parent type %q", secret.Parent.Type))
}
var cgt *datamanager.ChangeGroupsUpdateToken
// changegroup is the secret name
cgNames := []string{util.EncodeSha256Hex("secretname-" + secret.Name)}
// 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)
if err != nil {
return err
}
parentID, err := s.readDB.ResolveConfigID(tx, secret.Parent.Type, secret.Parent.ID)
if err != nil {
return err
}
secret.Parent.ID = parentID
// check duplicate secret name
s, err := s.readDB.GetSecretByName(tx, secret.Parent.ID, secret.Name)
if err != nil {
return err
}
if s != nil {
return util.NewErrBadRequest(errors.Errorf("secret with name %q for %s with id %q already exists", secret.Name, secret.Parent.Type, secret.Parent.ID))
}
return nil
})
if err != nil {
return nil, err
}
secret.ID = uuid.NewV4().String()
secretj, err := json.Marshal(secret)
if err != nil {
return nil, errors.Wrapf(err, "failed to marshal secret")
}
actions := []*datamanager.Action{
{
ActionType: datamanager.ActionTypePut,
DataType: string(types.ConfigTypeSecret),
ID: secret.ID,
Data: secretj,
},
}
_, err = s.dm.WriteWal(ctx, actions, cgt)
return secret, err
}
func (s *CommandHandler) DeleteSecret(ctx context.Context, parentType types.ConfigType, parentRef, secretName string) error {
var secret *types.Secret
var cgt *datamanager.ChangeGroupsUpdateToken
// 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)
if err != nil {
return err
}
// check secret existance
secret, err = s.readDB.GetSecretByName(tx, parentID, secretName)
if err != nil {
return err
}
if secret == nil {
return util.NewErrBadRequest(errors.Errorf("secret with name %q doesn't exist", secretName))
}
// changegroup is the secret id
cgNames := []string{util.EncodeSha256Hex("secretid-" + secret.ID)}
cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames)
if err != nil {
return err
}
return nil
})
if err != nil {
return err
}
actions := []*datamanager.Action{
{
ActionType: datamanager.ActionTypeDelete,
DataType: string(types.ConfigTypeSecret),
ID: secret.ID,
},
}
_, err = s.dm.WriteWal(ctx, actions, cgt)
return err
}
func (s *CommandHandler) CreateVariable(ctx context.Context, variable *types.Variable) (*types.Variable, error) {
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"))
}
if variable.Parent.Type == "" {
return nil, util.NewErrBadRequest(errors.Errorf("variable parent type required"))
}
if variable.Parent.ID == "" {
return nil, util.NewErrBadRequest(errors.Errorf("variable parent id required"))
}
if variable.Parent.Type != types.ConfigTypeProject && variable.Parent.Type != types.ConfigTypeProjectGroup {
return nil, util.NewErrBadRequest(errors.Errorf("invalid variable parent type %q", variable.Parent.Type))
}
var cgt *datamanager.ChangeGroupsUpdateToken
// changegroup is the variable name
cgNames := []string{util.EncodeSha256Hex("variablename-" + variable.Name)}
// 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)
if err != nil {
return err
}
parentID, err := s.readDB.ResolveConfigID(tx, variable.Parent.Type, variable.Parent.ID)
if err != nil {
return err
}
variable.Parent.ID = parentID
// check duplicate variable name
s, err := s.readDB.GetVariableByName(tx, variable.Parent.ID, variable.Name)
if err != nil {
return err
}
if s != nil {
return util.NewErrBadRequest(errors.Errorf("variable with name %q for %s with id %q already exists", variable.Name, variable.Parent.Type, variable.Parent.ID))
}
return nil
})
if err != nil {
return nil, err
}
variable.ID = uuid.NewV4().String()
variablej, err := json.Marshal(variable)
if err != nil {
return nil, errors.Wrapf(err, "failed to marshal variable")
}
actions := []*datamanager.Action{
{
ActionType: datamanager.ActionTypePut,
DataType: string(types.ConfigTypeVariable),
ID: variable.ID,
Data: variablej,
},
}
_, err = s.dm.WriteWal(ctx, actions, cgt)
return variable, err
}
func (s *CommandHandler) DeleteVariable(ctx context.Context, parentType types.ConfigType, parentRef, variableName string) error {
var variable *types.Variable
var cgt *datamanager.ChangeGroupsUpdateToken
// 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)
if err != nil {
return err
}
// check variable existance
variable, err = s.readDB.GetVariableByName(tx, parentID, variableName)
if err != nil {
return err
}
if variable == nil {
return util.NewErrBadRequest(errors.Errorf("variable with name %q doesn't exist", variableName))
}
// changegroup is the variable id
cgNames := []string{util.EncodeSha256Hex("variableid-" + variable.ID)}
cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames)
if err != nil {
return err
}
return nil
})
if err != nil {
return err
}
actions := []*datamanager.Action{
{
ActionType: datamanager.ActionTypeDelete,
DataType: string(types.ConfigTypeVariable),
ID: variable.ID,
},
}
_, err = s.dm.WriteWal(ctx, actions, cgt)
return err
}