2019-05-03 10:47:22 +00:00
// 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"
"time"
"github.com/sorintlab/agola/internal/datamanager"
"github.com/sorintlab/agola/internal/db"
2019-05-03 15:40:07 +00:00
"github.com/sorintlab/agola/internal/services/configstore/readdb"
2019-05-03 10:47:22 +00:00
"github.com/sorintlab/agola/internal/services/types"
"github.com/sorintlab/agola/internal/util"
"github.com/pkg/errors"
uuid "github.com/satori/go.uuid"
)
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
}
2019-05-03 15:40:07 +00:00
type UserOrgsResponse struct {
Organization * types . Organization
Role types . MemberRole
}
func userOrgsResponse ( userOrg * readdb . UserOrg ) * UserOrgsResponse {
return & UserOrgsResponse {
Organization : userOrg . Organization ,
Role : userOrg . Role ,
}
}
func ( s * CommandHandler ) GetUserOrgs ( ctx context . Context , userRef string ) ( [ ] * UserOrgsResponse , error ) {
var userOrgs [ ] * readdb . UserOrg
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 . NewErrNotFound ( errors . Errorf ( "user %q doesn't exist" , userRef ) )
}
userOrgs , err = s . readDB . GetUserOrgs ( tx , user . ID )
return err
} )
if err != nil {
return nil , err
}
res := make ( [ ] * UserOrgsResponse , len ( userOrgs ) )
for i , userOrg := range userOrgs {
res [ i ] = userOrgsResponse ( userOrg )
}
return res , nil
}