diff --git a/integration/service_account_test.go b/integration/service_account_test.go index 43e8bf861..70d7fcc9d 100644 --- a/integration/service_account_test.go +++ b/integration/service_account_test.go @@ -28,7 +28,6 @@ import ( "github.com/go-openapi/swag" - iampolicy "github.com/minio/pkg/v2/policy" "github.com/stretchr/testify/assert" ) @@ -52,6 +51,21 @@ func TestAddServiceAccount(t *testing.T) { requestDataAddServiceAccount := map[string]interface{}{ "accessKey": "testuser1", "secretKey": "password", + "policy": `{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:GetBucketLocation", + "s3:GetObject" + ], + "Resource": [ + "arn:aws:s3:::*" + ] + } + ] +}`, } requestDataJSON, _ := json.Marshal(requestDataAddServiceAccount) @@ -75,75 +89,6 @@ func TestAddServiceAccount(t *testing.T) { assert.Equal(201, response.StatusCode, "Status Code is incorrect") } - requestDataPolicy := map[string]interface{}{ - "policy": ` - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "s3:GetBucketLocation", - "s3:GetObject" - ], - "Resource": [ - "arn:aws:s3:::*" - ] - } - ] -}`, - } - requestDataJSON, _ = json.Marshal(requestDataPolicy) - requestDataBody = bytes.NewReader(requestDataJSON) - request, err = http.NewRequest( - "PUT", "http://localhost:9090/api/v1/service-accounts/"+base64.StdEncoding.EncodeToString([]byte("testuser1"))+"/policy", requestDataBody) - if err != nil { - log.Println(err) - return - } - request.Header.Add("Cookie", fmt.Sprintf("token=%s", token)) - request.Header.Add("Content-Type", "application/json") - response, err = client.Do(request) - if err != nil { - log.Println(err) - return - } - if response != nil { - fmt.Println("POST StatusCode:", response.StatusCode) - assert.Equal(200, response.StatusCode, "Status Code is incorrect") - } - - // Test policy - request, err = http.NewRequest( - "GET", "http://localhost:9090/api/v1/service-accounts/"+base64.StdEncoding.EncodeToString([]byte("testuser1"))+"/policy", nil) - if err != nil { - log.Println(err) - return - } - request.Header.Add("Cookie", fmt.Sprintf("token=%s", token)) - request.Header.Add("Content-Type", "application/json") - response, err = client.Do(request) - if err != nil { - log.Println(err) - return - } - if response != nil { - fmt.Println("POST StatusCode:", response.StatusCode) - assert.Equal(200, response.StatusCode, "Status Code is incorrect") - buf := new(bytes.Buffer) - buf.ReadFrom(response.Body) - var actual *iampolicy.Policy - var expected *iampolicy.Policy - json.Unmarshal(buf.Bytes(), actual) - policy, err := json.Marshal(requestDataAddServiceAccount["policy"]) - if err != nil { - log.Println(err) - return - } - json.Unmarshal(policy, expected) - assert.Equal(expected, actual) - } - // {{baseUrl}}/user?name=proident velit // Investiga como se borra en el browser. request, err = http.NewRequest( diff --git a/models/service_account.go b/models/service_account.go new file mode 100644 index 000000000..91f909f9a --- /dev/null +++ b/models/service_account.go @@ -0,0 +1,85 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2023 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 models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// ServiceAccount service account +// +// swagger:model serviceAccount +type ServiceAccount struct { + + // account status + AccountStatus string `json:"accountStatus,omitempty"` + + // description + Description string `json:"description,omitempty"` + + // expiration + Expiration string `json:"expiration,omitempty"` + + // implied policy + ImpliedPolicy bool `json:"impliedPolicy,omitempty"` + + // name + Name string `json:"name,omitempty"` + + // parent user + ParentUser string `json:"parentUser,omitempty"` + + // policy + Policy string `json:"policy,omitempty"` +} + +// Validate validates this service account +func (m *ServiceAccount) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this service account based on context it is used +func (m *ServiceAccount) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *ServiceAccount) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ServiceAccount) UnmarshalBinary(b []byte) error { + var res ServiceAccount + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/models/service_account_request.go b/models/service_account_request.go index f23cd13dd..ea11c5c94 100644 --- a/models/service_account_request.go +++ b/models/service_account_request.go @@ -34,6 +34,18 @@ import ( // swagger:model serviceAccountRequest type ServiceAccountRequest struct { + // comment + Comment string `json:"comment,omitempty"` + + // description + Description string `json:"description,omitempty"` + + // expiry + Expiry string `json:"expiry,omitempty"` + + // name + Name string `json:"name,omitempty"` + // policy to be applied to the Service Account if any Policy string `json:"policy,omitempty"` } diff --git a/models/service_account_request_creds.go b/models/service_account_request_creds.go index 276ccdc03..fed5a89f1 100644 --- a/models/service_account_request_creds.go +++ b/models/service_account_request_creds.go @@ -37,6 +37,18 @@ type ServiceAccountRequestCreds struct { // access key AccessKey string `json:"accessKey,omitempty"` + // comment + Comment string `json:"comment,omitempty"` + + // description + Description string `json:"description,omitempty"` + + // expiry + Expiry string `json:"expiry,omitempty"` + + // name + Name string `json:"name,omitempty"` + // policy to be applied to the Service Account if any Policy string `json:"policy,omitempty"` diff --git a/models/add_service_account_policy_request.go b/models/update_service_account_request.go similarity index 62% rename from models/add_service_account_policy_request.go rename to models/update_service_account_request.go index 149824aab..1e4dfa8d0 100644 --- a/models/add_service_account_policy_request.go +++ b/models/update_service_account_request.go @@ -31,18 +31,33 @@ import ( "github.com/go-openapi/validate" ) -// AddServiceAccountPolicyRequest add service account policy request +// UpdateServiceAccountRequest update service account request // -// swagger:model addServiceAccountPolicyRequest -type AddServiceAccountPolicyRequest struct { +// swagger:model updateServiceAccountRequest +type UpdateServiceAccountRequest struct { + + // description + Description string `json:"description,omitempty"` + + // expiry + Expiry string `json:"expiry,omitempty"` + + // name + Name string `json:"name,omitempty"` // policy // Required: true Policy *string `json:"policy"` + + // secret key + SecretKey string `json:"secretKey,omitempty"` + + // status + Status string `json:"status,omitempty"` } -// Validate validates this add service account policy request -func (m *AddServiceAccountPolicyRequest) Validate(formats strfmt.Registry) error { +// Validate validates this update service account request +func (m *UpdateServiceAccountRequest) Validate(formats strfmt.Registry) error { var res []error if err := m.validatePolicy(formats); err != nil { @@ -55,7 +70,7 @@ func (m *AddServiceAccountPolicyRequest) Validate(formats strfmt.Registry) error return nil } -func (m *AddServiceAccountPolicyRequest) validatePolicy(formats strfmt.Registry) error { +func (m *UpdateServiceAccountRequest) validatePolicy(formats strfmt.Registry) error { if err := validate.Required("policy", "body", m.Policy); err != nil { return err @@ -64,13 +79,13 @@ func (m *AddServiceAccountPolicyRequest) validatePolicy(formats strfmt.Registry) return nil } -// ContextValidate validates this add service account policy request based on context it is used -func (m *AddServiceAccountPolicyRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error { +// ContextValidate validates this update service account request based on context it is used +func (m *UpdateServiceAccountRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error { return nil } // MarshalBinary interface implementation -func (m *AddServiceAccountPolicyRequest) MarshalBinary() ([]byte, error) { +func (m *UpdateServiceAccountRequest) MarshalBinary() ([]byte, error) { if m == nil { return nil, nil } @@ -78,8 +93,8 @@ func (m *AddServiceAccountPolicyRequest) MarshalBinary() ([]byte, error) { } // UnmarshalBinary interface implementation -func (m *AddServiceAccountPolicyRequest) UnmarshalBinary(b []byte) error { - var res AddServiceAccountPolicyRequest +func (m *UpdateServiceAccountRequest) UnmarshalBinary(b []byte) error { + var res UpdateServiceAccountRequest if err := swag.ReadJSON(b, &res); err != nil { return err } diff --git a/portal-ui/src/api/consoleApi.ts b/portal-ui/src/api/consoleApi.ts index ce0fa3a4e..9937cb28a 100644 --- a/portal-ui/src/api/consoleApi.ts +++ b/portal-ui/src/api/consoleApi.ts @@ -214,8 +214,13 @@ export interface AddPolicyRequest { policy: string; } -export interface AddServiceAccountPolicyRequest { +export interface UpdateServiceAccountRequest { policy: string; + secretKey?: string; + name?: string; + description?: string; + expiry?: string; + status?: string; } export interface ListPoliciesResponse { @@ -727,6 +732,16 @@ export interface BulkUserGroups { groups: string[]; } +export interface ServiceAccount { + parentUser?: string; + accountStatus?: string; + impliedPolicy?: boolean; + policy?: string; + name?: string; + description?: string; + expiration?: string; +} + export type ServiceAccounts = { accountStatus?: string; name?: string; @@ -738,6 +753,10 @@ export type ServiceAccounts = { export interface ServiceAccountRequest { /** policy to be applied to the Service Account if any */ policy?: string; + name?: string; + description?: string; + expiry?: string; + comment?: string; } export interface ServiceAccountRequestCreds { @@ -745,6 +764,10 @@ export interface ServiceAccountRequestCreds { policy?: string; accessKey?: string; secretKey?: string; + name?: string; + description?: string; + expiry?: string; + comment?: string; } export interface ServiceAccountCreds { @@ -1720,9 +1743,10 @@ export class HttpClient { ? { "Content-Type": type } : {}), }, - signal: cancelToken - ? this.createAbortSignal(cancelToken) - : requestParams.signal, + signal: + (cancelToken + ? this.createAbortSignal(cancelToken) + : requestParams.signal) || null, body: typeof body === "undefined" || body === null ? null @@ -3052,23 +3076,6 @@ export class Api< ...params, }), - /** - * No description - * - * @tags ServiceAccount - * @name DeleteServiceAccount - * @summary Delete Service Account - * @request DELETE:/service-accounts/{access_key} - * @secure - */ - deleteServiceAccount: (accessKey: string, params: RequestParams = {}) => - this.request({ - path: `/service-accounts/${accessKey}`, - method: "DELETE", - secure: true, - ...params, - }), - /** * No description * @@ -3094,14 +3101,14 @@ export class Api< * No description * * @tags ServiceAccount - * @name GetServiceAccountPolicy - * @summary Get Service Account Policy - * @request GET:/service-accounts/{access_key}/policy + * @name GetServiceAccount + * @summary Get Service Account + * @request GET:/service-accounts/{access_key} * @secure */ - getServiceAccountPolicy: (accessKey: string, params: RequestParams = {}) => - this.request({ - path: `/service-accounts/${accessKey}/policy`, + getServiceAccount: (accessKey: string, params: RequestParams = {}) => + this.request({ + path: `/service-accounts/${accessKey}`, method: "GET", secure: true, format: "json", @@ -3112,24 +3119,41 @@ export class Api< * No description * * @tags ServiceAccount - * @name SetServiceAccountPolicy + * @name UpdateServiceAccount * @summary Set Service Account Policy - * @request PUT:/service-accounts/{access_key}/policy + * @request PUT:/service-accounts/{access_key} * @secure */ - setServiceAccountPolicy: ( + updateServiceAccount: ( accessKey: string, - policy: AddServiceAccountPolicyRequest, + body: UpdateServiceAccountRequest, params: RequestParams = {}, ) => this.request({ - path: `/service-accounts/${accessKey}/policy`, + path: `/service-accounts/${accessKey}`, method: "PUT", - body: policy, + body: body, secure: true, type: ContentType.Json, ...params, }), + + /** + * No description + * + * @tags ServiceAccount + * @name DeleteServiceAccount + * @summary Delete Service Account + * @request DELETE:/service-accounts/{access_key} + * @secure + */ + deleteServiceAccount: (accessKey: string, params: RequestParams = {}) => + this.request({ + path: `/service-accounts/${accessKey}`, + method: "DELETE", + secure: true, + ...params, + }), }; serviceAccountCredentials = { /** diff --git a/portal-ui/src/screens/Console/Account/Account.tsx b/portal-ui/src/screens/Console/Account/Account.tsx index 753fa8b6f..60dc30008 100644 --- a/portal-ui/src/screens/Console/Account/Account.tsx +++ b/portal-ui/src/screens/Console/Account/Account.tsx @@ -37,7 +37,7 @@ import withSuspense from "../Common/Components/withSuspense"; import { selectSAs } from "../Configurations/utils"; import DeleteMultipleServiceAccounts from "../Users/DeleteMultipleServiceAccounts"; -import ServiceAccountPolicy from "./ServiceAccountPolicy"; +import EditServiceAccount from "./EditServiceAccount"; import { selFeatures } from "../consoleSlice"; import TooltipWrapper from "../Common/TooltipWrapper/TooltipWrapper"; @@ -82,7 +82,7 @@ const Account = () => { useState(false); const [selectedSAs, setSelectedSAs] = useState([]); const [deleteMultipleOpen, setDeleteMultipleOpen] = useState(false); - const [policyOpen, setPolicyOpen] = useState(false); + const [isEditOpen, setIsEditOpen] = useState(false); const userIDP = (features && features.includes("external-idp")) || false; @@ -137,13 +137,13 @@ const Account = () => { } }; - const policyModalOpen = (selectedServiceAccount: string) => { + const editModalOpen = (selectedServiceAccount: string) => { setSelectedServiceAccount(selectedServiceAccount); - setPolicyOpen(true); + setIsEditOpen(true); }; const closePolicyModal = () => { - setPolicyOpen(false); + setIsEditOpen(false); setLoading(true); }; @@ -157,7 +157,7 @@ const Account = () => { type: "view", onClick: (value: any) => { if (value) { - policyModalOpen(value.accessKey); + editModalOpen(value.accessKey); } }, }, @@ -169,6 +169,14 @@ const Account = () => { } }, }, + { + type: "edit", + onClick: (value: any) => { + if (value) { + editModalOpen(value.accessKey); + } + }, + }, ]; const filteredRecords = records.filter( @@ -195,9 +203,9 @@ const Account = () => { /> )} - {policyOpen && ( - diff --git a/portal-ui/src/screens/Console/Account/AddServiceAccountScreen.tsx b/portal-ui/src/screens/Console/Account/AddServiceAccountScreen.tsx index a5fd6f661..7924adca6 100644 --- a/portal-ui/src/screens/Console/Account/AddServiceAccountScreen.tsx +++ b/portal-ui/src/screens/Console/Account/AddServiceAccountScreen.tsx @@ -29,6 +29,7 @@ import { Switch, ServiceAccountIcon, HelpTip, + DateTimeInput, } from "mds"; import { modalStyleUtils } from "../Common/FormComponents/common/styleLibrary"; import { NewServiceAccount } from "../Common/CredentialsPrompt/types"; @@ -59,6 +60,11 @@ const AddServiceAccount = () => { useState(null); const [policyJSON, setPolicyJSON] = useState(""); + const [name, setName] = useState(""); + const [description, setDescription] = useState(""); + const [comments, setComments] = useState(""); + const [expiry, setExpiry] = useState(); + useEffect(() => { dispatch(setHelpName("add_service_account")); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -66,12 +72,17 @@ const AddServiceAccount = () => { useEffect(() => { if (addSending) { + const expiryDt = expiry ? expiry.toJSDate().toISOString() : null; api.serviceAccountCredentials .createServiceAccountCreds( { policy: policyJSON, accessKey: accessKey, secretKey: secretKey, + description: description, + comment: comments, + name: name, + expiry: expiryDt, }, { type: ContentType.Json }, ) @@ -89,7 +100,18 @@ const AddServiceAccount = () => { dispatch(setErrorSnackMessage(errorToHandler(res.error))); }); } - }, [addSending, setAddSending, dispatch, policyJSON, accessKey, secretKey]); + }, [ + addSending, + setAddSending, + dispatch, + policyJSON, + accessKey, + secretKey, + name, + description, + expiry, + comments, + ]); useEffect(() => { if (isRestrictedByPolicy) { @@ -221,6 +243,73 @@ const AddServiceAccount = () => { )} + + + + { + setExpiry(e); + }} + id="expiryTime" + label={"Expiry"} + timeFormat={"24h"} + secondsSelector={false} + /> + + + { + setName(e.target.value); + }} + /> + { + setDescription(e.target.value); + }} + /> + { + setComments(e.target.value); + }} + />