diff --git a/portal-ui/src/screens/Console/Account/Account.tsx b/portal-ui/src/screens/Console/Account/Account.tsx index b8f149d7c..4f92f05af 100644 --- a/portal-ui/src/screens/Console/Account/Account.tsx +++ b/portal-ui/src/screens/Console/Account/Account.tsx @@ -23,8 +23,8 @@ import Grid from "@mui/material/Grid"; import api from "../../../common/api"; import { Box } from "@mui/material"; import { NewServiceAccount } from "../Common/CredentialsPrompt/types"; -import { setErrorSnackMessage } from "../../../actions"; -import { AccountIcon, AddIcon, PasswordKeyIcon } from "../../../icons"; +import { setErrorSnackMessage, setSnackBarMessage } from "../../../actions"; +import { AccountIcon, AddIcon, PasswordKeyIcon, DeleteIcon } from "../../../icons"; import TableWrapper from "../Common/TableWrapper/TableWrapper"; import { stringSort } from "../../../utils/sortFunctions"; import PageHeader from "../Common/PageHeader/PageHeader"; @@ -46,6 +46,8 @@ import { } from "../../../common/SecureComponent/permissions"; import SecureComponent from "../../../common/SecureComponent/SecureComponent"; import RBIconButton from "../Buckets/BucketDetails/SummaryItems/RBIconButton"; +import {selectSAs} from "../../Console/Configurations/utils" +import DeleteMultipleServiceAccounts from "../Users/DeleteMultipleServiceAccounts" const AddServiceAccount = withSuspense( React.lazy(() => import("./AddServiceAccount")) @@ -89,6 +91,8 @@ const Account = ({ classes, displayErrorMessage }: IServiceAccountsProps) => { useState(null); const [changePasswordModalOpen, setChangePasswordModalOpen] = useState(false); + const [selectedSAs, setSelectedSAs] = useState([]); + const [deleteMultipleOpen, setDeleteMultipleOpen] = useState(false); useEffect(() => { fetchRecords(); @@ -139,6 +143,24 @@ const Account = ({ classes, displayErrorMessage }: IServiceAccountsProps) => { } }; + const closeDeleteMultipleModalAndRefresh = (refresh: boolean) => { + setDeleteMultipleOpen(false); + if (refresh) { + setSnackBarMessage(`Service accounts deleted successfully.`); + setSelectedSAs([]); + setLoading(true); + } + }; + + +const selectAllItems = () => { + if (selectedSAs.length === records.length) { + setSelectedSAs([]); + return; + } + setSelectedSAs(records); + }; + const closeCredentialsModal = () => { setShowNewCredentials(false); setNewServiceAccount(null); @@ -176,6 +198,13 @@ const Account = ({ classes, displayErrorMessage }: IServiceAccountsProps) => { }} /> )} + {deleteMultipleOpen && ( + + )} {showNewCredentials && ( { sx={{ display: "flex", }} - > + > {setDeleteMultipleOpen(true);}} + text={"Delete Selected"} + icon={} + color="secondary" + disabled={selectedSAs.length === 0} + variant={"outlined"} + /> { color={"primary"} variant={"outlined"} /> - - + + + { setAddScreenOpen(true); @@ -241,6 +279,9 @@ const Account = ({ classes, displayErrorMessage }: IServiceAccountsProps) => { idField={""} columns={[{ label: "Service Account", elementKey: "" }]} itemActions={tableActions} + selectedItems={selectedSAs} + onSelect={e => selectSAs(e, setSelectedSAs, selectedSAs)} + onSelectAll={selectAllItems} /> diff --git a/portal-ui/src/screens/Console/Configurations/utils.tsx b/portal-ui/src/screens/Console/Configurations/utils.tsx index 11ae1ec7f..5c6aa9f8b 100644 --- a/portal-ui/src/screens/Console/Configurations/utils.tsx +++ b/portal-ui/src/screens/Console/Configurations/utils.tsx @@ -522,3 +522,20 @@ export const removeEmptyFields = (formFields: IElementValue[]) => { return nonEmptyFields; }; + +export const selectSAs = (e: React.ChangeEvent, setSelectedSAs : Function, selectedSAs : string[]) => { + const targetD = e.target; + const value = targetD.value; + const checked = targetD.checked; + + let elements: string[] = [...selectedSAs]; // We clone the selectedSAs array + if (checked) { + // If the user has checked this field we need to push this to selectedSAs + elements.push(value); + } else { + // User has unchecked this field, we need to remove it from the list + elements = elements.filter((element) => element !== value); + } + setSelectedSAs(elements); + return elements; +}; diff --git a/portal-ui/src/screens/Console/Users/DeleteMultipleServiceAccounts.tsx b/portal-ui/src/screens/Console/Users/DeleteMultipleServiceAccounts.tsx new file mode 100644 index 000000000..f9fbdd35f --- /dev/null +++ b/portal-ui/src/screens/Console/Users/DeleteMultipleServiceAccounts.tsx @@ -0,0 +1,73 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2022 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 . +import React from "react"; +import { connect } from "react-redux"; +import { DialogContentText } from "@mui/material"; +import { setErrorSnackMessage } from "../../../actions"; +import { ErrorResponseHandler } from "../../../common/types" +import useApi from "../../../screens/Console/Common/Hooks/useApi"; +import ConfirmDialog from "../../../screens/Console/Common/ModalWrapper/ConfirmDialog"; +import { ConfirmDeleteIcon } from "../../../icons"; +interface IDeleteMultiSAsProps { + closeDeleteModalAndRefresh: (refresh: boolean) => void; + deleteOpen: boolean; + selectedSAs: string[]; + setErrorSnackMessage: typeof setErrorSnackMessage; +} +const DeleteMultipleSAs = ({ + closeDeleteModalAndRefresh, + deleteOpen, + selectedSAs, + setErrorSnackMessage, +}: IDeleteMultiSAsProps) => { + const onDelSuccess = () => closeDeleteModalAndRefresh(true); + const onDelError = (err: ErrorResponseHandler) => setErrorSnackMessage(err); + const onClose = () => closeDeleteModalAndRefresh(false); + const [deleteLoading, invokeDeleteApi] = useApi(onDelSuccess, onDelError); + if (!selectedSAs) { + return null; + } + const onConfirmDelete = () => { + invokeDeleteApi( + "POST", + `/api/v1/service-accounts/delete-multi`, + selectedSAs + ); + }; + return ( + } + isLoading={deleteLoading} + onConfirm={onConfirmDelete} + onClose={onClose} + confirmationContent={ + + Are you sure you want to delete the selected {selectedSAs.length}{" "} + service accounts?{" "} + + } + /> + ); +}; +const mapDispatchToProps = { + setErrorSnackMessage, +}; +const connector = connect(null, mapDispatchToProps); + +export default connector(DeleteMultipleSAs); diff --git a/portal-ui/src/screens/Console/Users/UserServiceAccountsPanel.tsx b/portal-ui/src/screens/Console/Users/UserServiceAccountsPanel.tsx index 0d7bdc3d5..60bcfc96e 100644 --- a/portal-ui/src/screens/Console/Users/UserServiceAccountsPanel.tsx +++ b/portal-ui/src/screens/Console/Users/UserServiceAccountsPanel.tsx @@ -17,6 +17,7 @@ import React, { useEffect, useState } from "react"; import { connect } from "react-redux"; import { Theme } from "@mui/material/styles"; +import { Box } from "@mui/material"; import createStyles from "@mui/styles/createStyles"; import withStyles from "@mui/styles/withStyles"; import { @@ -27,16 +28,18 @@ import { import api from "../../../common/api"; import TableWrapper from "../Common/TableWrapper/TableWrapper"; import { AppState } from "../../../store"; -import { setErrorSnackMessage } from "../../../actions"; +import { setErrorSnackMessage, setSnackBarMessage } from "../../../actions"; import { NewServiceAccount } from "../Common/CredentialsPrompt/types"; import { stringSort } from "../../../utils/sortFunctions"; import { ErrorResponseHandler } from "../../../common/types"; import AddUserServiceAccount from "./AddUserServiceAccount"; import DeleteServiceAccount from "../Account/DeleteServiceAccount"; import CredentialsPrompt from "../Common/CredentialsPrompt/CredentialsPrompt"; -import { AddIcon } from "../../../icons"; +import { AddIcon, DeleteIcon } from "../../../icons"; import PanelTitle from "../Common/PanelTitle/PanelTitle"; import RBIconButton from "../Buckets/BucketDetails/SummaryItems/RBIconButton"; +import DeleteMultipleServiceAccounts from "./DeleteMultipleServiceAccounts" +import {selectSAs} from "../../Console/Configurations/utils" interface IUserServiceAccountsProps { classes: any; @@ -71,6 +74,8 @@ const UserServiceAccountsPanel = ({ const [showNewCredentials, setShowNewCredentials] = useState(false); const [newServiceAccount, setNewServiceAccount] = useState(null); + const [selectedSAs, setSelectedSAs] = useState([]); + const [deleteMultipleOpen, setDeleteMultipleOpen] = useState(false); useEffect(() => { fetchRecords(); @@ -120,6 +125,24 @@ const UserServiceAccountsPanel = ({ } }; + const closeDeleteMultipleModalAndRefresh = (refresh: boolean) => { + setDeleteMultipleOpen(false); + if (refresh) { + setSnackBarMessage(`Service accounts deleted successfully.`); + setSelectedSAs([]); + setLoading(true); + } + }; + + +const selectAllItems = () => { + if (selectedSAs.length === records.length) { + setSelectedSAs([]); + return; + } + setSelectedSAs(records); + }; + const closeCredentialsModal = () => { setShowNewCredentials(false); setNewServiceAccount(null); @@ -154,6 +177,13 @@ const UserServiceAccountsPanel = ({ }} /> )} + {deleteMultipleOpen && ( + + )} {showNewCredentials && ( )}
- Service Accounts - - } - onClick={() => { - setAddScreenOpen(true); - setAddScreenOpen(true); - setSelectedServiceAccount(null); - }} - disabled={!hasPolicy} - /> + Service Accounts + + {setDeleteMultipleOpen(true);}} + text={"Delete Selected"} + icon={} + color="secondary" + disabled={selectedSAs.length === 0} + variant={"outlined"} + /> + } + onClick={() => { + setAddScreenOpen(true); + setAddScreenOpen(true); + setSelectedServiceAccount(null); + }} + disabled={!hasPolicy} + /> +
-
+
selectSAs(e, setSelectedSAs, selectedSAs)} + onSelectAll={selectAllItems} />
diff --git a/restapi/embedded_spec.go b/restapi/embedded_spec.go index 4a13eace8..1c76671b7 100644 --- a/restapi/embedded_spec.go +++ b/restapi/embedded_spec.go @@ -3062,6 +3062,39 @@ func init() { } } }, + "/service-accounts/delete-multi": { + "post": { + "tags": [ + "UserAPI" + ], + "summary": "Delete Multiple Service Accounts", + "operationId": "DeleteMultipleServiceAccounts", + "parameters": [ + { + "name": "selectedSA", + "in": "body", + "required": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + ], + "responses": { + "200": { + "description": "A successful response." + }, + "default": { + "description": "Generic error response.", + "schema": { + "$ref": "#/definitions/error" + } + } + } + } + }, "/service-accounts/{access_key}": { "delete": { "tags": [ @@ -9147,6 +9180,39 @@ func init() { } } }, + "/service-accounts/delete-multi": { + "post": { + "tags": [ + "UserAPI" + ], + "summary": "Delete Multiple Service Accounts", + "operationId": "DeleteMultipleServiceAccounts", + "parameters": [ + { + "name": "selectedSA", + "in": "body", + "required": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + ], + "responses": { + "200": { + "description": "A successful response." + }, + "default": { + "description": "Generic error response.", + "schema": { + "$ref": "#/definitions/error" + } + } + } + } + }, "/service-accounts/{access_key}": { "delete": { "tags": [ diff --git a/restapi/operations/console_api.go b/restapi/operations/console_api.go index 916a14743..d7d48e87c 100644 --- a/restapi/operations/console_api.go +++ b/restapi/operations/console_api.go @@ -143,6 +143,9 @@ func NewConsoleAPI(spec *loads.Document) *ConsoleAPI { UserAPIDeleteMultipleObjectsHandler: user_api.DeleteMultipleObjectsHandlerFunc(func(params user_api.DeleteMultipleObjectsParams, principal *models.Principal) middleware.Responder { return middleware.NotImplemented("operation user_api.DeleteMultipleObjects has not yet been implemented") }), + UserAPIDeleteMultipleServiceAccountsHandler: user_api.DeleteMultipleServiceAccountsHandlerFunc(func(params user_api.DeleteMultipleServiceAccountsParams, principal *models.Principal) middleware.Responder { + return middleware.NotImplemented("operation user_api.DeleteMultipleServiceAccounts has not yet been implemented") + }), UserAPIDeleteObjectHandler: user_api.DeleteObjectHandlerFunc(func(params user_api.DeleteObjectParams, principal *models.Principal) middleware.Responder { return middleware.NotImplemented("operation user_api.DeleteObject has not yet been implemented") }), @@ -493,6 +496,8 @@ type ConsoleAPI struct { UserAPIDeleteBucketReplicationRuleHandler user_api.DeleteBucketReplicationRuleHandler // UserAPIDeleteMultipleObjectsHandler sets the operation handler for the delete multiple objects operation UserAPIDeleteMultipleObjectsHandler user_api.DeleteMultipleObjectsHandler + // UserAPIDeleteMultipleServiceAccountsHandler sets the operation handler for the delete multiple service accounts operation + UserAPIDeleteMultipleServiceAccountsHandler user_api.DeleteMultipleServiceAccountsHandler // UserAPIDeleteObjectHandler sets the operation handler for the delete object operation UserAPIDeleteObjectHandler user_api.DeleteObjectHandler // UserAPIDeleteObjectRetentionHandler sets the operation handler for the delete object retention operation @@ -820,6 +825,9 @@ func (o *ConsoleAPI) Validate() error { if o.UserAPIDeleteMultipleObjectsHandler == nil { unregistered = append(unregistered, "user_api.DeleteMultipleObjectsHandler") } + if o.UserAPIDeleteMultipleServiceAccountsHandler == nil { + unregistered = append(unregistered, "user_api.DeleteMultipleServiceAccountsHandler") + } if o.UserAPIDeleteObjectHandler == nil { unregistered = append(unregistered, "user_api.DeleteObjectHandler") } @@ -1269,6 +1277,10 @@ func (o *ConsoleAPI) initHandlerCache() { o.handlers["POST"] = make(map[string]http.Handler) } o.handlers["POST"]["/buckets/{bucket_name}/delete-objects"] = user_api.NewDeleteMultipleObjects(o.context, o.UserAPIDeleteMultipleObjectsHandler) + if o.handlers["POST"] == nil { + o.handlers["POST"] = make(map[string]http.Handler) + } + o.handlers["POST"]["/service-accounts/delete-multi"] = user_api.NewDeleteMultipleServiceAccounts(o.context, o.UserAPIDeleteMultipleServiceAccountsHandler) if o.handlers["DELETE"] == nil { o.handlers["DELETE"] = make(map[string]http.Handler) } diff --git a/restapi/operations/user_api/delete_multiple_service_accounts.go b/restapi/operations/user_api/delete_multiple_service_accounts.go new file mode 100644 index 000000000..352fc9c01 --- /dev/null +++ b/restapi/operations/user_api/delete_multiple_service_accounts.go @@ -0,0 +1,88 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// 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 . +// + +package user_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" + + "github.com/minio/console/models" +) + +// DeleteMultipleServiceAccountsHandlerFunc turns a function with the right signature into a delete multiple service accounts handler +type DeleteMultipleServiceAccountsHandlerFunc func(DeleteMultipleServiceAccountsParams, *models.Principal) middleware.Responder + +// Handle executing the request and returning a response +func (fn DeleteMultipleServiceAccountsHandlerFunc) Handle(params DeleteMultipleServiceAccountsParams, principal *models.Principal) middleware.Responder { + return fn(params, principal) +} + +// DeleteMultipleServiceAccountsHandler interface for that can handle valid delete multiple service accounts params +type DeleteMultipleServiceAccountsHandler interface { + Handle(DeleteMultipleServiceAccountsParams, *models.Principal) middleware.Responder +} + +// NewDeleteMultipleServiceAccounts creates a new http.Handler for the delete multiple service accounts operation +func NewDeleteMultipleServiceAccounts(ctx *middleware.Context, handler DeleteMultipleServiceAccountsHandler) *DeleteMultipleServiceAccounts { + return &DeleteMultipleServiceAccounts{Context: ctx, Handler: handler} +} + +/* DeleteMultipleServiceAccounts swagger:route POST /service-accounts/delete-multi UserAPI deleteMultipleServiceAccounts + +Delete Multiple Service Accounts + +*/ +type DeleteMultipleServiceAccounts struct { + Context *middleware.Context + Handler DeleteMultipleServiceAccountsHandler +} + +func (o *DeleteMultipleServiceAccounts) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewDeleteMultipleServiceAccountsParams() + uprinc, aCtx, err := o.Context.Authorize(r, route) + if err != nil { + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + if aCtx != nil { + *r = *aCtx + } + var principal *models.Principal + if uprinc != nil { + principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise + } + + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params, principal) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/restapi/operations/user_api/delete_multiple_service_accounts_parameters.go b/restapi/operations/user_api/delete_multiple_service_accounts_parameters.go new file mode 100644 index 000000000..41815ddcf --- /dev/null +++ b/restapi/operations/user_api/delete_multiple_service_accounts_parameters.go @@ -0,0 +1,87 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// 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 . +// + +package user_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "io" + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" +) + +// NewDeleteMultipleServiceAccountsParams creates a new DeleteMultipleServiceAccountsParams object +// +// There are no default values defined in the spec. +func NewDeleteMultipleServiceAccountsParams() DeleteMultipleServiceAccountsParams { + + return DeleteMultipleServiceAccountsParams{} +} + +// DeleteMultipleServiceAccountsParams contains all the bound params for the delete multiple service accounts operation +// typically these are obtained from a http.Request +// +// swagger:parameters DeleteMultipleServiceAccounts +type DeleteMultipleServiceAccountsParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: body + */ + SelectedSA []string +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewDeleteMultipleServiceAccountsParams() beforehand. +func (o *DeleteMultipleServiceAccountsParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if runtime.HasBody(r) { + defer r.Body.Close() + var body []string + if err := route.Consumer.Consume(r.Body, &body); err != nil { + if err == io.EOF { + res = append(res, errors.Required("selectedSA", "body", "")) + } else { + res = append(res, errors.NewParseError("selectedSA", "body", "", err)) + } + } else { + // no validation required on inline body + o.SelectedSA = body + } + } else { + res = append(res, errors.Required("selectedSA", "body", "")) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/restapi/operations/user_api/delete_multiple_service_accounts_responses.go b/restapi/operations/user_api/delete_multiple_service_accounts_responses.go new file mode 100644 index 000000000..9e1203fda --- /dev/null +++ b/restapi/operations/user_api/delete_multiple_service_accounts_responses.go @@ -0,0 +1,113 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// 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 . +// + +package user_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "github.com/minio/console/models" +) + +// DeleteMultipleServiceAccountsOKCode is the HTTP code returned for type DeleteMultipleServiceAccountsOK +const DeleteMultipleServiceAccountsOKCode int = 200 + +/*DeleteMultipleServiceAccountsOK A successful response. + +swagger:response deleteMultipleServiceAccountsOK +*/ +type DeleteMultipleServiceAccountsOK struct { +} + +// NewDeleteMultipleServiceAccountsOK creates DeleteMultipleServiceAccountsOK with default headers values +func NewDeleteMultipleServiceAccountsOK() *DeleteMultipleServiceAccountsOK { + + return &DeleteMultipleServiceAccountsOK{} +} + +// WriteResponse to the client +func (o *DeleteMultipleServiceAccountsOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(200) +} + +/*DeleteMultipleServiceAccountsDefault Generic error response. + +swagger:response deleteMultipleServiceAccountsDefault +*/ +type DeleteMultipleServiceAccountsDefault struct { + _statusCode int + + /* + In: Body + */ + Payload *models.Error `json:"body,omitempty"` +} + +// NewDeleteMultipleServiceAccountsDefault creates DeleteMultipleServiceAccountsDefault with default headers values +func NewDeleteMultipleServiceAccountsDefault(code int) *DeleteMultipleServiceAccountsDefault { + if code <= 0 { + code = 500 + } + + return &DeleteMultipleServiceAccountsDefault{ + _statusCode: code, + } +} + +// WithStatusCode adds the status to the delete multiple service accounts default response +func (o *DeleteMultipleServiceAccountsDefault) WithStatusCode(code int) *DeleteMultipleServiceAccountsDefault { + o._statusCode = code + return o +} + +// SetStatusCode sets the status to the delete multiple service accounts default response +func (o *DeleteMultipleServiceAccountsDefault) SetStatusCode(code int) { + o._statusCode = code +} + +// WithPayload adds the payload to the delete multiple service accounts default response +func (o *DeleteMultipleServiceAccountsDefault) WithPayload(payload *models.Error) *DeleteMultipleServiceAccountsDefault { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the delete multiple service accounts default response +func (o *DeleteMultipleServiceAccountsDefault) SetPayload(payload *models.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *DeleteMultipleServiceAccountsDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(o._statusCode) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/restapi/operations/user_api/delete_multiple_service_accounts_urlbuilder.go b/restapi/operations/user_api/delete_multiple_service_accounts_urlbuilder.go new file mode 100644 index 000000000..f19f0f61b --- /dev/null +++ b/restapi/operations/user_api/delete_multiple_service_accounts_urlbuilder.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// 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 . +// + +package user_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" +) + +// DeleteMultipleServiceAccountsURL generates an URL for the delete multiple service accounts operation +type DeleteMultipleServiceAccountsURL struct { + _basePath string +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *DeleteMultipleServiceAccountsURL) WithBasePath(bp string) *DeleteMultipleServiceAccountsURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *DeleteMultipleServiceAccountsURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *DeleteMultipleServiceAccountsURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/service-accounts/delete-multi" + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *DeleteMultipleServiceAccountsURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *DeleteMultipleServiceAccountsURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *DeleteMultipleServiceAccountsURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on DeleteMultipleServiceAccountsURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on DeleteMultipleServiceAccountsURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *DeleteMultipleServiceAccountsURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/restapi/user_service_accounts.go b/restapi/user_service_accounts.go index 65bc28bdb..ce9b11c37 100644 --- a/restapi/user_service_accounts.go +++ b/restapi/user_service_accounts.go @@ -99,6 +99,13 @@ func registerServiceAccountsHandlers(api *operations.ConsoleAPI) { return user_api.NewGetServiceAccountPolicyOK().WithPayload(serviceAccounts) }) + // Delete multiple service accounts + api.UserAPIDeleteMultipleServiceAccountsHandler = user_api.DeleteMultipleServiceAccountsHandlerFunc(func(params user_api.DeleteMultipleServiceAccountsParams, session *models.Principal) middleware.Responder { + if err := getDeleteMultipleServiceAccountsResponse(session, params.SelectedSA); err != nil { + return user_api.NewDeleteMultipleServiceAccountsDefault(int(err.Code)).WithPayload(err) + } + return user_api.NewDeleteMultipleServiceAccountsOK() + }) } // createServiceAccount adds a service account to the userClient and assigns a policy to him if defined. @@ -382,3 +389,22 @@ func getServiceAccountPolicyResponse(session *models.Principal, accessKey string } return serviceAccounts, nil } + +// getDeleteMultipleServiceAccountsResponse authenticates the user and calls deleteServiceAccount for each account listed in selectedSAs +func getDeleteMultipleServiceAccountsResponse(session *models.Principal, selectedSAs []string) *models.Error { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) + defer cancel() + userAdmin, err := NewMinioAdminClient(session) + if err != nil { + return prepareError(err) + } + // create a MinIO user Admin Client interface implementation + // defining the client to be used + userAdminClient := AdminClient{Client: userAdmin} + for _, sa := range selectedSAs { + if err := deleteServiceAccount(ctx, userAdminClient, sa); err != nil { + return prepareError(err) + } + } + return nil +} diff --git a/swagger-console.yml b/swagger-console.yml index a4dd947e6..fbd482f92 100644 --- a/swagger-console.yml +++ b/swagger-console.yml @@ -19,7 +19,7 @@ securityDefinitions: tokenUrl: http://min.io # Apply the key security definition to all APIs security: - - key: [ ] + - key: [] paths: /login: get: @@ -35,7 +35,7 @@ paths: schema: $ref: "#/definitions/error" # Exclude this API from the authentication requirement - security: [ ] + security: [] tags: - UserAPI post: @@ -55,7 +55,7 @@ paths: schema: $ref: "#/definitions/error" # Exclude this API from the authentication requirement - security: [ ] + security: [] tags: - UserAPI /login/oauth2/auth: @@ -75,7 +75,7 @@ paths: description: Generic error response. schema: $ref: "#/definitions/error" - security: [ ] + security: [] tags: - UserAPI @@ -921,7 +921,7 @@ paths: $ref: "#/definitions/error" tags: - UserAPI - + delete: summary: Bucket Replication Rule Delete operationId: DeleteBucketReplicationRule @@ -1260,6 +1260,28 @@ paths: tags: - UserAPI + /service-accounts/delete-multi: + post: + summary: Delete Multiple Service Accounts + operationId: DeleteMultipleServiceAccounts + parameters: + - name: selectedSA + in: body + required: true + schema: + type: array + items: + type: string + responses: + 200: + description: A successful response. + default: + description: Generic error response. + schema: + $ref: "#/definitions/error" + tags: + - UserAPI + /service-accounts/{access_key}/policy: get: summary: Get Service Account Policy @@ -2471,7 +2493,7 @@ paths: - name: order in: query type: string - enum: [ timeDesc, timeAsc ] + enum: [timeDesc, timeAsc] default: timeDesc - name: timeStart in: query @@ -3234,7 +3256,7 @@ definitions: properties: loginStrategy: type: string - enum: [ form, redirect, service-account ] + enum: [form, redirect, service-account] redirect: type: string loginOauth2AuthRequest: @@ -3320,7 +3342,7 @@ definitions: type: string status: type: string - enum: [ ok ] + enum: [ok] operator: type: boolean distributedMode: @@ -3341,7 +3363,7 @@ definitions: type: string values: type: array - items: { } + items: {} resultTarget: type: object properties: @@ -3621,7 +3643,7 @@ definitions: type: string service: type: string - enum: [ replication ] + enum: [replication] syncMode: type: string bandwidth: