340 lines
12 KiB
Go
340 lines
12 KiB
Go
// This file is part of MinIO Console Server
|
|
// Copyright (c) 2021 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 api
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/go-openapi/errors"
|
|
"github.com/go-openapi/runtime/middleware"
|
|
"github.com/minio/console/api/operations"
|
|
"github.com/minio/madmin-go/v3"
|
|
|
|
groupApi "github.com/minio/console/api/operations/group"
|
|
|
|
"github.com/minio/console/models"
|
|
)
|
|
|
|
func registerGroupsHandlers(api *operations.ConsoleAPI) {
|
|
// List Groups
|
|
api.GroupListGroupsHandler = groupApi.ListGroupsHandlerFunc(func(params groupApi.ListGroupsParams, session *models.Principal) middleware.Responder {
|
|
listGroupsResponse, err := getListGroupsResponse(session, params)
|
|
if err != nil {
|
|
return groupApi.NewListGroupsDefault(err.Code).WithPayload(err.APIError)
|
|
}
|
|
return groupApi.NewListGroupsOK().WithPayload(listGroupsResponse)
|
|
})
|
|
// Group Info
|
|
api.GroupGroupInfoHandler = groupApi.GroupInfoHandlerFunc(func(params groupApi.GroupInfoParams, session *models.Principal) middleware.Responder {
|
|
groupInfo, err := getGroupInfoResponse(session, params)
|
|
if err != nil {
|
|
return groupApi.NewGroupInfoDefault(err.Code).WithPayload(err.APIError)
|
|
}
|
|
return groupApi.NewGroupInfoOK().WithPayload(groupInfo)
|
|
})
|
|
// Add Group
|
|
api.GroupAddGroupHandler = groupApi.AddGroupHandlerFunc(func(params groupApi.AddGroupParams, session *models.Principal) middleware.Responder {
|
|
if err := getAddGroupResponse(session, params); err != nil {
|
|
return groupApi.NewAddGroupDefault(err.Code).WithPayload(err.APIError)
|
|
}
|
|
return groupApi.NewAddGroupCreated()
|
|
})
|
|
// Remove Group
|
|
api.GroupRemoveGroupHandler = groupApi.RemoveGroupHandlerFunc(func(params groupApi.RemoveGroupParams, session *models.Principal) middleware.Responder {
|
|
if err := getRemoveGroupResponse(session, params); err != nil {
|
|
return groupApi.NewRemoveGroupDefault(err.Code).WithPayload(err.APIError)
|
|
}
|
|
return groupApi.NewRemoveGroupNoContent()
|
|
})
|
|
// Update Group
|
|
api.GroupUpdateGroupHandler = groupApi.UpdateGroupHandlerFunc(func(params groupApi.UpdateGroupParams, session *models.Principal) middleware.Responder {
|
|
groupUpdateResp, err := getUpdateGroupResponse(session, params)
|
|
if err != nil {
|
|
return groupApi.NewUpdateGroupDefault(err.Code).WithPayload(err.APIError)
|
|
}
|
|
return groupApi.NewUpdateGroupOK().WithPayload(groupUpdateResp)
|
|
})
|
|
}
|
|
|
|
// getListGroupsResponse performs listGroups() and serializes it to the handler's output
|
|
func getListGroupsResponse(session *models.Principal, params groupApi.ListGroupsParams) (*models.ListGroupsResponse, *CodedAPIError) {
|
|
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
|
|
defer cancel()
|
|
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
|
|
if err != nil {
|
|
return nil, ErrorWithContext(ctx, err)
|
|
}
|
|
// create a MinIO Admin Client interface implementation
|
|
// defining the client to be used
|
|
adminClient := AdminClient{Client: mAdmin}
|
|
|
|
groups, err := adminClient.listGroups(ctx)
|
|
if err != nil {
|
|
return nil, ErrorWithContext(ctx, err)
|
|
}
|
|
|
|
// serialize output
|
|
listGroupsResponse := &models.ListGroupsResponse{
|
|
Groups: groups,
|
|
Total: int64(len(groups)),
|
|
}
|
|
|
|
return listGroupsResponse, nil
|
|
}
|
|
|
|
// groupInfo calls MinIO server get Group's info
|
|
func groupInfo(ctx context.Context, client MinioAdmin, group string) (*madmin.GroupDesc, error) {
|
|
groupDesc, err := client.getGroupDescription(ctx, group)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return groupDesc, nil
|
|
}
|
|
|
|
// getGroupInfoResponse performs groupInfo() and serializes it to the handler's output
|
|
func getGroupInfoResponse(session *models.Principal, params groupApi.GroupInfoParams) (*models.Group, *CodedAPIError) {
|
|
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
|
|
defer cancel()
|
|
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
|
|
if err != nil {
|
|
return nil, ErrorWithContext(ctx, err)
|
|
}
|
|
// create a MinIO Admin Client interface implementation
|
|
// defining the client to be used
|
|
adminClient := AdminClient{Client: mAdmin}
|
|
|
|
groupDesc, err := groupInfo(ctx, adminClient, params.Name)
|
|
if err != nil {
|
|
return nil, ErrorWithContext(ctx, err)
|
|
}
|
|
|
|
groupResponse := &models.Group{
|
|
Members: groupDesc.Members,
|
|
Name: groupDesc.Name,
|
|
Policy: groupDesc.Policy,
|
|
Status: groupDesc.Status,
|
|
}
|
|
|
|
return groupResponse, nil
|
|
}
|
|
|
|
// addGroupAdd a MinIO group with the defined members
|
|
func addGroup(ctx context.Context, client MinioAdmin, group string, members []string) error {
|
|
gAddRemove := madmin.GroupAddRemove{
|
|
Group: group,
|
|
Members: members,
|
|
IsRemove: false,
|
|
}
|
|
err := client.updateGroupMembers(ctx, gAddRemove)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// getAddGroupResponse performs addGroup() and serializes it to the handler's output
|
|
func getAddGroupResponse(session *models.Principal, params groupApi.AddGroupParams) *CodedAPIError {
|
|
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
|
|
defer cancel()
|
|
// AddGroup request needed to proceed
|
|
if params.Body == nil {
|
|
return ErrorWithContext(ctx, ErrGroupBodyNotInRequest)
|
|
}
|
|
groupRequest := params.Body
|
|
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
|
|
if err != nil {
|
|
return ErrorWithContext(ctx, err)
|
|
}
|
|
// create a MinIO Admin Client interface implementation
|
|
// defining the client to be used
|
|
adminClient := AdminClient{Client: mAdmin}
|
|
|
|
groupList, _ := adminClient.listGroups(ctx)
|
|
|
|
for _, b := range groupList {
|
|
if b == *groupRequest.Group {
|
|
return ErrorWithContext(ctx, ErrGroupAlreadyExists)
|
|
}
|
|
}
|
|
|
|
if err := addGroup(ctx, adminClient, *groupRequest.Group, groupRequest.Members); err != nil {
|
|
return ErrorWithContext(ctx, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// removeGroup deletes a minIO group only if it has no members
|
|
func removeGroup(ctx context.Context, client MinioAdmin, group string) error {
|
|
gAddRemove := madmin.GroupAddRemove{
|
|
Group: group,
|
|
Members: []string{},
|
|
IsRemove: true,
|
|
}
|
|
err := client.updateGroupMembers(ctx, gAddRemove)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// getRemoveGroupResponse performs removeGroup() and serializes it to the handler's output
|
|
func getRemoveGroupResponse(session *models.Principal, params groupApi.RemoveGroupParams) *CodedAPIError {
|
|
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
|
|
defer cancel()
|
|
if params.Name == "" {
|
|
return ErrorWithContext(ctx, ErrGroupNameNotInRequest)
|
|
}
|
|
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
|
|
if err != nil {
|
|
return ErrorWithContext(ctx, err)
|
|
}
|
|
// Create a MinIO Admin Client interface implementation
|
|
// defining the client to be used
|
|
adminClient := AdminClient{Client: mAdmin}
|
|
|
|
if err := removeGroup(ctx, adminClient, params.Name); err != nil {
|
|
minioError := madmin.ToErrorResponse(err)
|
|
err2 := ErrorWithContext(ctx, err)
|
|
if minioError.Code == "XMinioAdminNoSuchGroup" {
|
|
err2.Code = 404
|
|
}
|
|
return err2
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// updateGroup updates a group by adding/removing members and setting the status to the desired one
|
|
//
|
|
// isRemove: whether remove members or not
|
|
func updateGroupMembers(ctx context.Context, client MinioAdmin, group string, members []string, isRemove bool) error {
|
|
gAddRemove := madmin.GroupAddRemove{
|
|
Group: group,
|
|
Members: members,
|
|
IsRemove: isRemove,
|
|
}
|
|
err := client.updateGroupMembers(ctx, gAddRemove)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// addOrDeleteMembers updates a group members by adding or deleting them based on the expectedMembers
|
|
func addOrDeleteMembers(ctx context.Context, client MinioAdmin, group *madmin.GroupDesc, expectedMembers []string) error {
|
|
// get members to delete/add
|
|
membersToDelete := DifferenceArrays(group.Members, expectedMembers)
|
|
membersToAdd := DifferenceArrays(expectedMembers, group.Members)
|
|
// delete members if any to be deleted
|
|
if len(membersToDelete) > 0 {
|
|
err := updateGroupMembers(ctx, client, group.Name, membersToDelete, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// add members if any to be added
|
|
if len(membersToAdd) > 0 {
|
|
err := updateGroupMembers(ctx, client, group.Name, membersToAdd, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func setGroupStatus(ctx context.Context, client MinioAdmin, group, status string) error {
|
|
var setStatus madmin.GroupStatus
|
|
switch status {
|
|
case "enabled":
|
|
setStatus = madmin.GroupEnabled
|
|
case "disabled":
|
|
setStatus = madmin.GroupDisabled
|
|
default:
|
|
return errors.New(500, "status not valid")
|
|
}
|
|
return client.setGroupStatus(ctx, group, setStatus)
|
|
}
|
|
|
|
// getUpdateGroupResponse updates a group by adding or removing it's members depending on the request,
|
|
// also sets the group's status if status in the request is different than the current one.
|
|
// Then serializes the output to be used by the handler.
|
|
func getUpdateGroupResponse(session *models.Principal, params groupApi.UpdateGroupParams) (*models.Group, *CodedAPIError) {
|
|
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
|
|
defer cancel()
|
|
if params.Name == "" {
|
|
return nil, ErrorWithContext(ctx, ErrGroupNameNotInRequest)
|
|
}
|
|
if params.Body == nil {
|
|
return nil, ErrorWithContext(ctx, ErrGroupBodyNotInRequest)
|
|
}
|
|
expectedGroupUpdate := params.Body
|
|
|
|
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
|
|
if err != nil {
|
|
return nil, ErrorWithContext(ctx, err)
|
|
}
|
|
// create a MinIO Admin Client interface implementation
|
|
// defining the client to be used
|
|
adminClient := AdminClient{Client: mAdmin}
|
|
|
|
groupUpdated, err := groupUpdate(ctx, adminClient, params.Name, expectedGroupUpdate)
|
|
if err != nil {
|
|
return nil, ErrorWithContext(ctx, err)
|
|
}
|
|
groupResponse := &models.Group{
|
|
Name: groupUpdated.Name,
|
|
Members: groupUpdated.Members,
|
|
Policy: groupUpdated.Policy,
|
|
Status: groupUpdated.Status,
|
|
}
|
|
return groupResponse, nil
|
|
}
|
|
|
|
// groupUpdate updates a group given the expected parameters, compares the expected parameters against the current ones
|
|
// and updates them accordingly, status is only updated if the expected status is different than the current one.
|
|
// Then fetches the group again to return the object updated.
|
|
func groupUpdate(ctx context.Context, client MinioAdmin, groupName string, expectedGroup *models.UpdateGroupRequest) (*madmin.GroupDesc, error) {
|
|
expectedMembers := expectedGroup.Members
|
|
expectedStatus := *expectedGroup.Status
|
|
// get current members and status
|
|
groupDescription, err := groupInfo(ctx, client, groupName)
|
|
if err != nil {
|
|
LogInfo("error getting group info: %v", err)
|
|
return nil, err
|
|
}
|
|
// update group members
|
|
err = addOrDeleteMembers(ctx, client, groupDescription, expectedMembers)
|
|
if err != nil {
|
|
LogInfo("error updating group: %v", err)
|
|
return nil, err
|
|
}
|
|
// update group status only if different from current status
|
|
if expectedStatus != groupDescription.Status {
|
|
err = setGroupStatus(ctx, client, groupDescription.Name, expectedStatus)
|
|
if err != nil {
|
|
LogInfo("error updating group's status: %v", err)
|
|
return nil, err
|
|
}
|
|
}
|
|
// return latest group info to verify that changes were applied correctly
|
|
groupDescription, err = groupInfo(ctx, client, groupName)
|
|
if err != nil {
|
|
LogInfo("error getting group info: %v", err)
|
|
return nil, err
|
|
}
|
|
return groupDescription, nil
|
|
}
|