github.com/minio/console@v1.4.1/api/admin_groups.go (about) 1 // This file is part of MinIO Console Server 2 // Copyright (c) 2021 MinIO, Inc. 3 // 4 // This program is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Affero General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // This program is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Affero General Public License for more details. 13 // 14 // You should have received a copy of the GNU Affero General Public License 15 // along with this program. If not, see <http://www.gnu.org/licenses/>. 16 17 package api 18 19 import ( 20 "context" 21 22 "github.com/go-openapi/errors" 23 "github.com/go-openapi/runtime/middleware" 24 "github.com/minio/console/api/operations" 25 "github.com/minio/console/pkg/utils" 26 "github.com/minio/madmin-go/v3" 27 28 groupApi "github.com/minio/console/api/operations/group" 29 30 "github.com/minio/console/models" 31 ) 32 33 func registerGroupsHandlers(api *operations.ConsoleAPI) { 34 // List Groups 35 api.GroupListGroupsHandler = groupApi.ListGroupsHandlerFunc(func(params groupApi.ListGroupsParams, session *models.Principal) middleware.Responder { 36 listGroupsResponse, err := getListGroupsResponse(session, params) 37 if err != nil { 38 return groupApi.NewListGroupsDefault(err.Code).WithPayload(err.APIError) 39 } 40 return groupApi.NewListGroupsOK().WithPayload(listGroupsResponse) 41 }) 42 // Group Info 43 api.GroupGroupInfoHandler = groupApi.GroupInfoHandlerFunc(func(params groupApi.GroupInfoParams, session *models.Principal) middleware.Responder { 44 groupInfo, err := getGroupInfoResponse(session, params) 45 if err != nil { 46 return groupApi.NewGroupInfoDefault(err.Code).WithPayload(err.APIError) 47 } 48 return groupApi.NewGroupInfoOK().WithPayload(groupInfo) 49 }) 50 // Add Group 51 api.GroupAddGroupHandler = groupApi.AddGroupHandlerFunc(func(params groupApi.AddGroupParams, session *models.Principal) middleware.Responder { 52 if err := getAddGroupResponse(session, params); err != nil { 53 return groupApi.NewAddGroupDefault(err.Code).WithPayload(err.APIError) 54 } 55 return groupApi.NewAddGroupCreated() 56 }) 57 // Remove Group 58 api.GroupRemoveGroupHandler = groupApi.RemoveGroupHandlerFunc(func(params groupApi.RemoveGroupParams, session *models.Principal) middleware.Responder { 59 if err := getRemoveGroupResponse(session, params); err != nil { 60 return groupApi.NewRemoveGroupDefault(err.Code).WithPayload(err.APIError) 61 } 62 return groupApi.NewRemoveGroupNoContent() 63 }) 64 // Update Group 65 api.GroupUpdateGroupHandler = groupApi.UpdateGroupHandlerFunc(func(params groupApi.UpdateGroupParams, session *models.Principal) middleware.Responder { 66 groupUpdateResp, err := getUpdateGroupResponse(session, params) 67 if err != nil { 68 return groupApi.NewUpdateGroupDefault(err.Code).WithPayload(err.APIError) 69 } 70 return groupApi.NewUpdateGroupOK().WithPayload(groupUpdateResp) 71 }) 72 } 73 74 // getListGroupsResponse performs listGroups() and serializes it to the handler's output 75 func getListGroupsResponse(session *models.Principal, params groupApi.ListGroupsParams) (*models.ListGroupsResponse, *CodedAPIError) { 76 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 77 defer cancel() 78 mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session) 79 if err != nil { 80 return nil, ErrorWithContext(ctx, err) 81 } 82 // create a MinIO Admin Client interface implementation 83 // defining the client to be used 84 adminClient := AdminClient{Client: mAdmin} 85 86 groups, err := adminClient.listGroups(ctx) 87 if err != nil { 88 return nil, ErrorWithContext(ctx, err) 89 } 90 91 // serialize output 92 listGroupsResponse := &models.ListGroupsResponse{ 93 Groups: groups, 94 Total: int64(len(groups)), 95 } 96 97 return listGroupsResponse, nil 98 } 99 100 // groupInfo calls MinIO server get Group's info 101 func groupInfo(ctx context.Context, client MinioAdmin, group string) (*madmin.GroupDesc, error) { 102 groupDesc, err := client.getGroupDescription(ctx, group) 103 if err != nil { 104 return nil, err 105 } 106 return groupDesc, nil 107 } 108 109 // getGroupInfoResponse performs groupInfo() and serializes it to the handler's output 110 func getGroupInfoResponse(session *models.Principal, params groupApi.GroupInfoParams) (*models.Group, *CodedAPIError) { 111 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 112 defer cancel() 113 mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session) 114 if err != nil { 115 return nil, ErrorWithContext(ctx, err) 116 } 117 // create a MinIO Admin Client interface implementation 118 // defining the client to be used 119 adminClient := AdminClient{Client: mAdmin} 120 121 groupName, err := utils.DecodeBase64(params.Name) 122 if err != nil { 123 return nil, ErrorWithContext(ctx, err) 124 } 125 126 groupDesc, err := groupInfo(ctx, adminClient, groupName) 127 if err != nil { 128 return nil, ErrorWithContext(ctx, err) 129 } 130 131 groupResponse := &models.Group{ 132 Members: groupDesc.Members, 133 Name: groupDesc.Name, 134 Policy: groupDesc.Policy, 135 Status: groupDesc.Status, 136 } 137 138 return groupResponse, nil 139 } 140 141 // addGroupAdd a MinIO group with the defined members 142 func addGroup(ctx context.Context, client MinioAdmin, group string, members []string) error { 143 gAddRemove := madmin.GroupAddRemove{ 144 Group: group, 145 Members: members, 146 IsRemove: false, 147 } 148 err := client.updateGroupMembers(ctx, gAddRemove) 149 if err != nil { 150 return err 151 } 152 return nil 153 } 154 155 // getAddGroupResponse performs addGroup() and serializes it to the handler's output 156 func getAddGroupResponse(session *models.Principal, params groupApi.AddGroupParams) *CodedAPIError { 157 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 158 defer cancel() 159 // AddGroup request needed to proceed 160 if params.Body == nil { 161 return ErrorWithContext(ctx, ErrGroupBodyNotInRequest) 162 } 163 groupRequest := params.Body 164 mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session) 165 if err != nil { 166 return ErrorWithContext(ctx, err) 167 } 168 // create a MinIO Admin Client interface implementation 169 // defining the client to be used 170 adminClient := AdminClient{Client: mAdmin} 171 172 groupList, _ := adminClient.listGroups(ctx) 173 174 for _, b := range groupList { 175 if b == *groupRequest.Group { 176 return ErrorWithContext(ctx, ErrGroupAlreadyExists) 177 } 178 } 179 180 if err := addGroup(ctx, adminClient, *groupRequest.Group, groupRequest.Members); err != nil { 181 return ErrorWithContext(ctx, err) 182 } 183 return nil 184 } 185 186 // removeGroup deletes a minIO group only if it has no members 187 func removeGroup(ctx context.Context, client MinioAdmin, group string) error { 188 gAddRemove := madmin.GroupAddRemove{ 189 Group: group, 190 Members: []string{}, 191 IsRemove: true, 192 } 193 err := client.updateGroupMembers(ctx, gAddRemove) 194 if err != nil { 195 return err 196 } 197 return nil 198 } 199 200 // getRemoveGroupResponse performs removeGroup() and serializes it to the handler's output 201 func getRemoveGroupResponse(session *models.Principal, params groupApi.RemoveGroupParams) *CodedAPIError { 202 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 203 defer cancel() 204 if params.Name == "" { 205 return ErrorWithContext(ctx, ErrGroupNameNotInRequest) 206 } 207 mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session) 208 if err != nil { 209 return ErrorWithContext(ctx, err) 210 } 211 // Create a MinIO Admin Client interface implementation 212 // defining the client to be used 213 adminClient := AdminClient{Client: mAdmin} 214 215 groupName, err := utils.DecodeBase64(params.Name) 216 if err != nil { 217 return ErrorWithContext(ctx, err) 218 } 219 220 if err := removeGroup(ctx, adminClient, groupName); err != nil { 221 minioError := madmin.ToErrorResponse(err) 222 err2 := ErrorWithContext(ctx, err) 223 if minioError.Code == "XMinioAdminNoSuchGroup" { 224 err2.Code = 404 225 } 226 return err2 227 } 228 return nil 229 } 230 231 // updateGroup updates a group by adding/removing members and setting the status to the desired one 232 // 233 // isRemove: whether remove members or not 234 func updateGroupMembers(ctx context.Context, client MinioAdmin, group string, members []string, isRemove bool) error { 235 gAddRemove := madmin.GroupAddRemove{ 236 Group: group, 237 Members: members, 238 IsRemove: isRemove, 239 } 240 err := client.updateGroupMembers(ctx, gAddRemove) 241 if err != nil { 242 return err 243 } 244 return nil 245 } 246 247 // addOrDeleteMembers updates a group members by adding or deleting them based on the expectedMembers 248 func addOrDeleteMembers(ctx context.Context, client MinioAdmin, group *madmin.GroupDesc, expectedMembers []string) error { 249 // get members to delete/add 250 membersToDelete := DifferenceArrays(group.Members, expectedMembers) 251 membersToAdd := DifferenceArrays(expectedMembers, group.Members) 252 // delete members if any to be deleted 253 if len(membersToDelete) > 0 { 254 err := updateGroupMembers(ctx, client, group.Name, membersToDelete, true) 255 if err != nil { 256 return err 257 } 258 } 259 // add members if any to be added 260 if len(membersToAdd) > 0 { 261 err := updateGroupMembers(ctx, client, group.Name, membersToAdd, false) 262 if err != nil { 263 return err 264 } 265 } 266 return nil 267 } 268 269 func setGroupStatus(ctx context.Context, client MinioAdmin, group, status string) error { 270 var setStatus madmin.GroupStatus 271 switch status { 272 case "enabled": 273 setStatus = madmin.GroupEnabled 274 case "disabled": 275 setStatus = madmin.GroupDisabled 276 default: 277 return errors.New(500, "status not valid") 278 } 279 return client.setGroupStatus(ctx, group, setStatus) 280 } 281 282 // getUpdateGroupResponse updates a group by adding or removing it's members depending on the request, 283 // also sets the group's status if status in the request is different than the current one. 284 // Then serializes the output to be used by the handler. 285 func getUpdateGroupResponse(session *models.Principal, params groupApi.UpdateGroupParams) (*models.Group, *CodedAPIError) { 286 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 287 defer cancel() 288 if params.Name == "" { 289 return nil, ErrorWithContext(ctx, ErrGroupNameNotInRequest) 290 } 291 if params.Body == nil { 292 return nil, ErrorWithContext(ctx, ErrGroupBodyNotInRequest) 293 } 294 expectedGroupUpdate := params.Body 295 296 groupName, err := utils.DecodeBase64(params.Name) 297 if err != nil { 298 return nil, ErrorWithContext(ctx, err) 299 } 300 301 mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session) 302 if err != nil { 303 return nil, ErrorWithContext(ctx, err) 304 } 305 // create a MinIO Admin Client interface implementation 306 // defining the client to be used 307 adminClient := AdminClient{Client: mAdmin} 308 309 groupUpdated, err := groupUpdate(ctx, adminClient, groupName, expectedGroupUpdate) 310 if err != nil { 311 return nil, ErrorWithContext(ctx, err) 312 } 313 groupResponse := &models.Group{ 314 Name: groupUpdated.Name, 315 Members: groupUpdated.Members, 316 Policy: groupUpdated.Policy, 317 Status: groupUpdated.Status, 318 } 319 return groupResponse, nil 320 } 321 322 // groupUpdate updates a group given the expected parameters, compares the expected parameters against the current ones 323 // and updates them accordingly, status is only updated if the expected status is different than the current one. 324 // Then fetches the group again to return the object updated. 325 func groupUpdate(ctx context.Context, client MinioAdmin, groupName string, expectedGroup *models.UpdateGroupRequest) (*madmin.GroupDesc, error) { 326 expectedMembers := expectedGroup.Members 327 expectedStatus := *expectedGroup.Status 328 // get current members and status 329 groupDescription, err := groupInfo(ctx, client, groupName) 330 if err != nil { 331 LogInfo("error getting group info: %v", err) 332 return nil, err 333 } 334 // update group members 335 err = addOrDeleteMembers(ctx, client, groupDescription, expectedMembers) 336 if err != nil { 337 LogInfo("error updating group: %v", err) 338 return nil, err 339 } 340 // update group status only if different from current status 341 if expectedStatus != groupDescription.Status { 342 err = setGroupStatus(ctx, client, groupDescription.Name, expectedStatus) 343 if err != nil { 344 LogInfo("error updating group's status: %v", err) 345 return nil, err 346 } 347 } 348 // return latest group info to verify that changes were applied correctly 349 groupDescription, err = groupInfo(ctx, client, groupName) 350 if err != nil { 351 LogInfo("error getting group info: %v", err) 352 return nil, err 353 } 354 return groupDescription, nil 355 }