2020-04-02 12:51:51 -07:00
// This file is part of MinIO Console Server
2020-04-01 18:18:57 -07:00
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package restapi
import (
2020-04-11 00:13:31 -05:00
"github.com/go-openapi/errors"
2020-04-01 18:18:57 -07:00
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/swag"
2020-04-15 20:08:35 -05:00
"github.com/minio/mcs/models"
2020-04-06 11:58:34 -07:00
"github.com/minio/mcs/restapi/operations"
"github.com/minio/mcs/restapi/operations/admin_api"
2020-04-08 19:38:18 -05:00
"github.com/minio/minio/pkg/madmin"
"context"
2020-04-15 20:08:35 -05:00
"fmt"
2020-04-08 19:38:18 -05:00
"log"
2020-04-15 20:08:35 -05:00
"strings"
2020-04-01 18:18:57 -07:00
)
func registerUsersHandlers ( api * operations . McsAPI ) {
// List Users
2020-04-02 12:06:52 -07:00
api . AdminAPIListUsersHandler = admin_api . ListUsersHandlerFunc ( func ( params admin_api . ListUsersParams , principal * models . Principal ) middleware . Responder {
2020-04-01 18:18:57 -07:00
listUsersResponse , err := getListUsersResponse ( )
if err != nil {
return admin_api . NewListUsersDefault ( 500 ) . WithPayload ( & models . Error { Code : 500 , Message : swag . String ( err . Error ( ) ) } )
}
return admin_api . NewListUsersOK ( ) . WithPayload ( listUsersResponse )
} )
// Add User
2020-04-02 12:06:52 -07:00
api . AdminAPIAddUserHandler = admin_api . AddUserHandlerFunc ( func ( params admin_api . AddUserParams , principal * models . Principal ) middleware . Responder {
2020-04-01 18:18:57 -07:00
userResponse , err := getUserAddResponse ( params )
if err != nil {
return admin_api . NewAddUserDefault ( 500 ) . WithPayload ( & models . Error { Code : 500 , Message : swag . String ( err . Error ( ) ) } )
}
return admin_api . NewAddUserCreated ( ) . WithPayload ( userResponse )
} )
2020-04-06 20:07:32 -05:00
// Remove User
api . AdminAPIRemoveUserHandler = admin_api . RemoveUserHandlerFunc ( func ( params admin_api . RemoveUserParams , principal * models . Principal ) middleware . Responder {
err := getRemoveUserResponse ( params )
if err != nil {
return admin_api . NewRemoveUserDefault ( 500 ) . WithPayload ( & models . Error { Code : 500 , Message : swag . String ( err . Error ( ) ) } )
}
return admin_api . NewRemoveUserNoContent ( )
} )
2020-04-08 19:38:18 -05:00
// Update User-Groups
api . AdminAPIUpdateUserGroupsHandler = admin_api . UpdateUserGroupsHandlerFunc ( func ( params admin_api . UpdateUserGroupsParams , principal * models . Principal ) middleware . Responder {
userUpdateResponse , err := getUpdateUserGroupsResponse ( params )
if err != nil {
return admin_api . NewUpdateUserGroupsDefault ( 500 ) . WithPayload ( & models . Error { Code : 500 , Message : swag . String ( err . Error ( ) ) } )
}
return admin_api . NewUpdateUserGroupsOK ( ) . WithPayload ( userUpdateResponse )
} )
2020-04-09 18:39:49 -05:00
// Get User
api . AdminAPIGetUserInfoHandler = admin_api . GetUserInfoHandlerFunc ( func ( params admin_api . GetUserInfoParams , principal * models . Principal ) middleware . Responder {
userInfoResponse , err := getUserInfoResponse ( params )
if err != nil {
return admin_api . NewGetUserDefault ( 500 ) . WithPayload ( & models . Error { Code : 500 , Message : swag . String ( err . Error ( ) ) } )
}
return admin_api . NewGetUserOK ( ) . WithPayload ( userInfoResponse )
} )
2020-04-11 00:13:31 -05:00
// Update User
api . AdminAPIUpdateUserInfoHandler = admin_api . UpdateUserInfoHandlerFunc ( func ( params admin_api . UpdateUserInfoParams , principal * models . Principal ) middleware . Responder {
userUpdateResponse , err := getUpdateUserResponse ( params )
if err != nil {
return admin_api . NewUpdateUserInfoDefault ( 500 ) . WithPayload ( & models . Error { Code : 500 , Message : swag . String ( err . Error ( ) ) } )
}
return admin_api . NewUpdateUserInfoOK ( ) . WithPayload ( userUpdateResponse )
} )
2020-04-15 20:08:35 -05:00
// Update User-Groups Bulk
api . AdminAPIBulkUpdateUsersGroupsHandler = admin_api . BulkUpdateUsersGroupsHandlerFunc ( func ( params admin_api . BulkUpdateUsersGroupsParams , principal * models . Principal ) middleware . Responder {
error := getAddUsersListToGroupsResponse ( params )
if error != nil {
return admin_api . NewBulkUpdateUsersGroupsDefault ( 500 ) . WithPayload ( & models . Error { Code : 500 , Message : swag . String ( error . Error ( ) ) } )
}
return admin_api . NewBulkUpdateUsersGroupsOK ( )
} )
2020-04-01 18:18:57 -07:00
}
2020-04-06 20:07:32 -05:00
func listUsers ( ctx context . Context , client MinioAdmin ) ( [ ] * models . User , error ) {
2020-04-01 18:18:57 -07:00
// Get list of all users in the MinIO
// This call requires explicit authentication, no anonymous requests are
// allowed for listing users.
userMap , err := client . listUsers ( ctx )
if err != nil {
return [ ] * models . User { } , err
}
var users [ ] * models . User
for accessKey , user := range userMap {
userElem := & models . User {
AccessKey : accessKey ,
Status : string ( user . Status ) ,
Policy : user . PolicyName ,
MemberOf : user . MemberOf ,
}
users = append ( users , userElem )
}
return users , nil
}
// getListUsersResponse performs listUsers() and serializes it to the handler's output
func getListUsersResponse ( ) ( * models . ListUsersResponse , error ) {
2020-04-06 20:07:32 -05:00
ctx := context . Background ( )
2020-04-01 18:18:57 -07:00
mAdmin , err := newMAdminClient ( )
if err != nil {
log . Println ( "error creating Madmin Client:" , err )
return nil , err
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := adminClient { client : mAdmin }
2020-04-06 20:07:32 -05:00
users , err := listUsers ( ctx , adminClient )
2020-04-01 18:18:57 -07:00
if err != nil {
log . Println ( "error listing users:" , err )
return nil , err
}
// serialize output
listUsersResponse := & models . ListUsersResponse {
Users : users ,
}
return listUsersResponse , nil
}
// addUser invokes adding a users on `MinioAdmin` and builds the response `models.User`
2020-04-09 18:39:49 -05:00
func addUser ( ctx context . Context , client MinioAdmin , accessKey , secretKey * string , groups [ ] string ) ( * models . User , error ) {
2020-04-01 18:18:57 -07:00
// Calls into MinIO to add a new user if there's an error return it
2020-04-09 18:39:49 -05:00
if err := client . addUser ( ctx , * accessKey , * secretKey ) ; err != nil {
2020-04-01 18:18:57 -07:00
return nil , err
}
2020-04-09 18:39:49 -05:00
if len ( groups ) > 0 {
userElem , errUG := updateUserGroups ( ctx , client , * accessKey , groups )
if errUG != nil {
return nil , errUG
}
return userElem , nil
2020-04-01 18:18:57 -07:00
}
2020-04-09 18:39:49 -05:00
userRet := & models . User {
AccessKey : * accessKey ,
MemberOf : nil ,
Policy : "" ,
Status : "" ,
}
return userRet , nil
2020-04-01 18:18:57 -07:00
}
func getUserAddResponse ( params admin_api . AddUserParams ) ( * models . User , error ) {
2020-04-06 20:07:32 -05:00
ctx := context . Background ( )
2020-04-01 18:18:57 -07:00
mAdmin , err := newMAdminClient ( )
if err != nil {
log . Println ( "error creating Madmin Client:" , err )
return nil , err
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := adminClient { client : mAdmin }
2020-04-09 18:39:49 -05:00
user , err := addUser ( ctx , adminClient , params . Body . AccessKey , params . Body . SecretKey , params . Body . Groups )
2020-04-01 18:18:57 -07:00
if err != nil {
log . Println ( "error adding user:" , err )
return nil , err
}
return user , nil
}
2020-04-06 20:07:32 -05:00
//removeUser invokes removing an user on `MinioAdmin`, then we return the response from API
func removeUser ( ctx context . Context , client MinioAdmin , accessKey string ) error {
if err := client . removeUser ( ctx , accessKey ) ; err != nil {
return err
}
return nil
}
func getRemoveUserResponse ( params admin_api . RemoveUserParams ) error {
ctx := context . Background ( )
mAdmin , err := newMAdminClient ( )
if err != nil {
log . Println ( "error creating Madmin Client:" , err )
return err
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := adminClient { client : mAdmin }
if err := removeUser ( ctx , adminClient , params . Name ) ; err != nil {
log . Println ( "error removing user:" , err )
return err
}
log . Println ( "User removed successfully:" , params . Name )
return nil
}
2020-04-08 19:38:18 -05:00
// getUserInfo calls MinIO server get the User Information
func getUserInfo ( ctx context . Context , client MinioAdmin , accessKey string ) ( * madmin . UserInfo , error ) {
userInfo , err := client . getUserInfo ( ctx , accessKey )
if err != nil {
return nil , err
}
return & userInfo , nil
}
2020-04-09 18:39:49 -05:00
func getUserInfoResponse ( params admin_api . GetUserInfoParams ) ( * models . User , error ) {
ctx := context . Background ( )
mAdmin , err := newMAdminClient ( )
if err != nil {
log . Println ( "error creating Madmin Client:" , err )
return nil , err
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := adminClient { client : mAdmin }
user , err := getUserInfo ( ctx , adminClient , params . Name )
if err != nil {
log . Println ( "error getting user:" , err )
return nil , err
}
userInformation := & models . User {
AccessKey : params . Name ,
MemberOf : user . MemberOf ,
Policy : user . PolicyName ,
Status : string ( user . Status ) ,
}
return userInformation , nil
}
2020-04-08 19:38:18 -05:00
// updateUserGroups invokes getUserInfo() to get the old groups from the user,
// then we merge the list with the new groups list to have a shorter iteration between groups and we do a comparison between the current and old groups.
// We delete or update the groups according the location in each list and send the user with the new groups from `MinioAdmin` to the client
func updateUserGroups ( ctx context . Context , client MinioAdmin , user string , groupsToAssign [ ] string ) ( * models . User , error ) {
parallelUserUpdate := func ( groupName string , originGroups [ ] string ) chan error {
chProcess := make ( chan error )
go func ( ) error {
defer close ( chProcess )
//Compare if groupName is in the arrays
isGroupPersistent := IsElementInArray ( groupsToAssign , groupName )
isInOriginGroups := IsElementInArray ( originGroups , groupName )
if isGroupPersistent && isInOriginGroups { // Group is already assigned and doesn't need to be updated
chProcess <- nil
return nil
}
isRemove := false // User is added by default
// User is deleted from the group
if ! isGroupPersistent {
isRemove = true
}
userToAddRemove := [ ] string { user }
updateReturn := updateGroupMembers ( ctx , client , groupName , userToAddRemove , isRemove )
chProcess <- updateReturn
return updateReturn
} ( )
return chProcess
}
userInfoOr , err := getUserInfo ( ctx , client , user )
if err != nil {
return nil , err
}
memberOf := userInfoOr . MemberOf
mergedGroupArray := UniqueKeys ( append ( memberOf , groupsToAssign ... ) )
var listOfUpdates [ ] chan error
// Each group must be updated individually because there is no way to update all the groups at once for a user,
// we are using the same logic as 'mc admin group add' command
for _ , groupN := range mergedGroupArray {
proc := parallelUserUpdate ( groupN , memberOf )
listOfUpdates = append ( listOfUpdates , proc )
}
channelHasError := false
for _ , chanRet := range listOfUpdates {
locError := <- chanRet
if locError != nil {
channelHasError = true
}
}
if channelHasError {
2020-04-11 00:13:31 -05:00
errRt := errors . New ( 500 , "there was an error updating the groups" )
2020-04-08 19:38:18 -05:00
return nil , errRt
}
userInfo , err := getUserInfo ( ctx , client , user )
if err != nil {
return nil , err
}
userReturn := & models . User {
AccessKey : user ,
MemberOf : userInfo . MemberOf ,
Policy : userInfo . PolicyName ,
Status : string ( userInfo . Status ) ,
}
return userReturn , nil
}
func getUpdateUserGroupsResponse ( params admin_api . UpdateUserGroupsParams ) ( * models . User , error ) {
ctx := context . Background ( )
mAdmin , err := newMAdminClient ( )
if err != nil {
log . Println ( "error creating Madmin Client:" , err )
return nil , err
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := adminClient { client : mAdmin }
user , err := updateUserGroups ( ctx , adminClient , params . Name , params . Body . Groups )
if err != nil {
log . Println ( "error updating users's groups:" , params . Body . Groups )
return nil , err
}
return user , nil
}
2020-04-11 00:13:31 -05:00
// setUserStatus invokes setUserStatus from madmin to update user status
func setUserStatus ( ctx context . Context , client MinioAdmin , user string , status string ) error {
var setStatus madmin . AccountStatus
switch status {
case "enabled" :
setStatus = madmin . AccountEnabled
case "disabled" :
setStatus = madmin . AccountDisabled
default :
return errors . New ( 500 , "status not valid" )
}
if err := client . setUserStatus ( ctx , user , setStatus ) ; err != nil {
return err
}
return nil
}
func getUpdateUserResponse ( params admin_api . UpdateUserInfoParams ) ( * models . User , error ) {
ctx := context . Background ( )
mAdmin , err := newMAdminClient ( )
if err != nil {
log . Println ( "error creating Madmin Client:" , err )
return nil , err
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := adminClient { client : mAdmin }
name := params . Name
status := * params . Body . Status
groups := params . Body . Groups
if err := setUserStatus ( ctx , adminClient , name , status ) ; err != nil {
log . Println ( "error updating user status:" , status )
return nil , err
}
userElem , errUG := updateUserGroups ( ctx , adminClient , name , groups )
if errUG != nil {
return nil , errUG
}
return userElem , nil
}
2020-04-15 20:08:35 -05:00
// addUsersListToGroups iterates over the user list & assigns the requested groups to each user.
func addUsersListToGroups ( ctx context . Context , client MinioAdmin , usersToUpdate [ ] string , groupsToAssign [ ] string ) error {
// We update each group with the complete usersList
parallelGroupsUpdate := func ( groupToAssign string ) chan error {
groupProcess := make ( chan error )
go func ( ) {
defer close ( groupProcess )
// We add the users array to the group.
err := updateGroupMembers ( ctx , client , groupToAssign , usersToUpdate , false )
groupProcess <- err
} ( )
return groupProcess
}
var groupsUpdateList [ ] chan error
// We get each group name & add users accordingly
for _ , groupName := range groupsToAssign {
// We update the group
proc := parallelGroupsUpdate ( groupName )
groupsUpdateList = append ( groupsUpdateList , proc )
}
errorsList := [ ] string { } // We get the errors list because we want to have all errors at once.
for _ , err := range groupsUpdateList {
errorFromUpdate := <- err // We store the error to avoid Data Race
if errorFromUpdate != nil {
// If there is an error, we store the errors strings so we can join them after we receive all errors
errorsList = append ( errorsList , errorFromUpdate . Error ( ) ) // We wait until all the channels have been closed.
}
}
// If there are errors, we throw the final error with the errors inside
if len ( errorsList ) > 0 {
errGen := fmt . Errorf ( "error in users-groups assignation: %q" , strings . Join ( errorsList [ : ] , "," ) )
return errGen
}
return nil
}
func getAddUsersListToGroupsResponse ( params admin_api . BulkUpdateUsersGroupsParams ) error {
ctx := context . Background ( )
mAdmin , err := newMAdminClient ( )
if err != nil {
log . Println ( "error creating Madmin Client:" , err )
return err
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := adminClient { client : mAdmin }
usersList := params . Body . Users
groupsList := params . Body . Groups
if err := addUsersListToGroups ( ctx , adminClient , usersList , groupsList ) ; err != nil {
log . Println ( "error updating groups bulk users:" , err . Error ( ) )
return err
}
return nil
}