show service account status and expiry in ui (#2992)
This commit is contained in:
committed by
GitHub
parent
a2ba20e12f
commit
e7fb205c31
@@ -305,7 +305,6 @@ func TestCreateServiceAccountForUserWithCredentials(t *testing.T) {
|
||||
userName := "testcreateserviceaccountforuserwithcredentials1"
|
||||
assert := assert.New(t)
|
||||
policy := ""
|
||||
serviceAccountLengthInBytes := 40 // As observed, update as needed
|
||||
|
||||
// 1. Create the user
|
||||
groups := []string{}
|
||||
@@ -383,7 +382,6 @@ func TestCreateServiceAccountForUserWithCredentials(t *testing.T) {
|
||||
finalResponse,
|
||||
)
|
||||
}
|
||||
assert.Equal(len(finalResponse), serviceAccountLengthInBytes, finalResponse)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -715,7 +715,6 @@ func TestCreateServiceAccountForUser(t *testing.T) {
|
||||
userName := "testcreateserviceaccountforuser1"
|
||||
assert := assert.New(t)
|
||||
policy := ""
|
||||
serviceAccountLengthInBytes := 40 // As observed, update as needed
|
||||
|
||||
// 1. Create the user
|
||||
groups := []string{}
|
||||
@@ -765,8 +764,6 @@ func TestCreateServiceAccountForUser(t *testing.T) {
|
||||
finalResponse,
|
||||
)
|
||||
}
|
||||
|
||||
assert.Equal(len(finalResponse), serviceAccountLengthInBytes, finalResponse)
|
||||
}
|
||||
|
||||
func TestUsersGroupsBulk(t *testing.T) {
|
||||
|
||||
@@ -24,21 +24,116 @@ package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// ServiceAccounts service accounts
|
||||
//
|
||||
// swagger:model serviceAccounts
|
||||
type ServiceAccounts []string
|
||||
type ServiceAccounts []*ServiceAccountsItems0
|
||||
|
||||
// Validate validates this service accounts
|
||||
func (m ServiceAccounts) Validate(formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
for i := 0; i < len(m); i++ {
|
||||
if swag.IsZero(m[i]) { // not required
|
||||
continue
|
||||
}
|
||||
|
||||
if m[i] != nil {
|
||||
if err := m[i].Validate(formats); err != nil {
|
||||
if ve, ok := err.(*errors.Validation); ok {
|
||||
return ve.ValidateName(strconv.Itoa(i))
|
||||
} else if ce, ok := err.(*errors.CompositeError); ok {
|
||||
return ce.ValidateName(strconv.Itoa(i))
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContextValidate validates this service accounts based on context it is used
|
||||
// ContextValidate validate this service accounts based on the context it is used
|
||||
func (m ServiceAccounts) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
for i := 0; i < len(m); i++ {
|
||||
|
||||
if m[i] != nil {
|
||||
if err := m[i].ContextValidate(ctx, formats); err != nil {
|
||||
if ve, ok := err.(*errors.Validation); ok {
|
||||
return ve.ValidateName(strconv.Itoa(i))
|
||||
} else if ce, ok := err.(*errors.CompositeError); ok {
|
||||
return ce.ValidateName(strconv.Itoa(i))
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ServiceAccountsItems0 service accounts items0
|
||||
//
|
||||
// swagger:model ServiceAccountsItems0
|
||||
type ServiceAccountsItems0 struct {
|
||||
|
||||
// access key
|
||||
AccessKey string `json:"accessKey,omitempty"`
|
||||
|
||||
// account status
|
||||
AccountStatus string `json:"accountStatus,omitempty"`
|
||||
|
||||
// description
|
||||
Description string `json:"description,omitempty"`
|
||||
|
||||
// expiration
|
||||
Expiration string `json:"expiration,omitempty"`
|
||||
|
||||
// name
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates this service accounts items0
|
||||
func (m *ServiceAccountsItems0) Validate(formats strfmt.Registry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContextValidate validates this service accounts items0 based on context it is used
|
||||
func (m *ServiceAccountsItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *ServiceAccountsItems0) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(m)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *ServiceAccountsItems0) UnmarshalBinary(b []byte) error {
|
||||
var res ServiceAccountsItems0
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -728,7 +728,13 @@ export interface BulkUserGroups {
|
||||
groups: string[];
|
||||
}
|
||||
|
||||
export type ServiceAccounts = string[];
|
||||
export type ServiceAccounts = {
|
||||
accountStatus?: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
expiration?: string;
|
||||
accessKey?: string;
|
||||
}[];
|
||||
|
||||
export interface ServiceAccountRequest {
|
||||
/** policy to be applied to the Service Account if any */
|
||||
@@ -3565,7 +3571,7 @@ export class Api<
|
||||
* @secure
|
||||
*/
|
||||
listUsersForPolicy: (policy: string, params: RequestParams = {}) =>
|
||||
this.request<ServiceAccounts, Error>({
|
||||
this.request<string[], Error>({
|
||||
path: `/policies/${policy}/users`,
|
||||
method: "GET",
|
||||
secure: true,
|
||||
@@ -3583,7 +3589,7 @@ export class Api<
|
||||
* @secure
|
||||
*/
|
||||
listGroupsForPolicy: (policy: string, params: RequestParams = {}) =>
|
||||
this.request<ServiceAccounts, Error>({
|
||||
this.request<string[], Error>({
|
||||
path: `/policies/${policy}/groups`,
|
||||
method: "GET",
|
||||
secure: true,
|
||||
@@ -3717,7 +3723,7 @@ export class Api<
|
||||
},
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
this.request<ServiceAccounts, Error>({
|
||||
this.request<string[], Error>({
|
||||
path: `/bucket-users/${bucket}`,
|
||||
method: "GET",
|
||||
query: query,
|
||||
@@ -4472,7 +4478,7 @@ export class Api<
|
||||
* @secure
|
||||
*/
|
||||
listNodes: (params: RequestParams = {}) =>
|
||||
this.request<ServiceAccounts, Error>({
|
||||
this.request<string[], Error>({
|
||||
path: `/nodes`,
|
||||
method: "GET",
|
||||
secure: true,
|
||||
|
||||
@@ -29,7 +29,6 @@ import {
|
||||
} from "mds";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { stringSort } from "../../../utils/sortFunctions";
|
||||
import { actionsTray } from "../Common/FormComponents/common/styleLibrary";
|
||||
|
||||
import ChangePasswordModal from "./ChangePasswordModal";
|
||||
@@ -57,6 +56,9 @@ import PageHeaderWrapper from "../Common/PageHeaderWrapper/PageHeaderWrapper";
|
||||
import { api } from "api";
|
||||
import { errorToHandler } from "api/errors";
|
||||
import HelpMenu from "../HelpMenu";
|
||||
import { ServiceAccounts } from "../../../api/consoleApi";
|
||||
import { usersSort } from "../../../utils/sortFunctions";
|
||||
import { ACCOUNT_TABLE_COLUMNS } from "./AccountUtils";
|
||||
|
||||
const DeleteServiceAccount = withSuspense(
|
||||
React.lazy(() => import("./DeleteServiceAccount")),
|
||||
@@ -68,7 +70,7 @@ const Account = () => {
|
||||
|
||||
const features = useSelector(selFeatures);
|
||||
|
||||
const [records, setRecords] = useState<string[]>([]);
|
||||
const [records, setRecords] = useState<ServiceAccounts>([]);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [filter, setFilter] = useState<string>("");
|
||||
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
|
||||
@@ -97,10 +99,9 @@ const Account = () => {
|
||||
api.serviceAccounts
|
||||
.listUserServiceAccounts()
|
||||
.then((res) => {
|
||||
const serviceAccounts = res.data.sort(stringSort);
|
||||
|
||||
setLoading(false);
|
||||
setRecords(serviceAccounts);
|
||||
const sortedRows = res.data.sort(usersSort);
|
||||
setRecords(sortedRows);
|
||||
})
|
||||
.catch((err) => {
|
||||
dispatch(setErrorSnackMessage(errorToHandler(err.error)));
|
||||
@@ -136,14 +137,6 @@ const Account = () => {
|
||||
setPolicyOpen(true);
|
||||
};
|
||||
|
||||
const selectAllItems = () => {
|
||||
if (selectedSAs.length === records.length) {
|
||||
setSelectedSAs([]);
|
||||
return;
|
||||
}
|
||||
setSelectedSAs(records);
|
||||
};
|
||||
|
||||
const closePolicyModal = () => {
|
||||
setPolicyOpen(false);
|
||||
setLoading(true);
|
||||
@@ -155,12 +148,27 @@ const Account = () => {
|
||||
};
|
||||
|
||||
const tableActions = [
|
||||
{ type: "view", onClick: policyModalOpen },
|
||||
{ type: "delete", onClick: confirmDeleteServiceAccount },
|
||||
{
|
||||
type: "view",
|
||||
onClick: (value: any) => {
|
||||
if (value) {
|
||||
policyModalOpen(value.accessKey);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "delete",
|
||||
onClick: (value: any) => {
|
||||
if (value) {
|
||||
confirmDeleteServiceAccount(value.accessKey);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const filteredRecords = records.filter((elementItem) =>
|
||||
elementItem.toLowerCase().includes(filter.toLowerCase()),
|
||||
const filteredRecords = records.filter(
|
||||
(elementItem) =>
|
||||
elementItem?.accessKey?.toLowerCase().includes(filter.toLowerCase()),
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -259,14 +267,14 @@ const Account = () => {
|
||||
|
||||
<Grid item xs={12}>
|
||||
<DataTable
|
||||
itemActions={tableActions}
|
||||
entityName={"Access Keys"}
|
||||
columns={ACCOUNT_TABLE_COLUMNS}
|
||||
onSelect={(e) => selectSAs(e, setSelectedSAs, selectedSAs)}
|
||||
selectedItems={selectedSAs}
|
||||
isLoading={loading}
|
||||
records={filteredRecords}
|
||||
entityName={"Access Keys"}
|
||||
columns={[{ label: "Access Key" }]}
|
||||
itemActions={tableActions}
|
||||
selectedItems={selectedSAs}
|
||||
onSelect={(e) => selectSAs(e, setSelectedSAs, selectedSAs)}
|
||||
onSelectAll={selectAllItems}
|
||||
idField="accessKey"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sx={{ marginTop: 15 }}>
|
||||
|
||||
49
portal-ui/src/screens/Console/Account/AccountUtils.tsx
Normal file
49
portal-ui/src/screens/Console/Account/AccountUtils.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
import { DateTime } from "luxon";
|
||||
|
||||
export const ACCOUNT_TABLE_COLUMNS = [
|
||||
{ label: "Access Key", elementKey: "accessKey" },
|
||||
{
|
||||
label: "Expiry",
|
||||
elementKey: "expiration",
|
||||
renderFunction: (expTime: string) => {
|
||||
if (expTime) {
|
||||
const fmtDate = DateTime.fromISO(expTime)
|
||||
.toUTC()
|
||||
.toFormat("y/M/d hh:mm:ss z");
|
||||
|
||||
return <span title={fmtDate}>{fmtDate}</span>;
|
||||
}
|
||||
return "";
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Status",
|
||||
elementKey: "accountStatus",
|
||||
renderFunction: (status: string) => {
|
||||
if (status === "off") {
|
||||
return "Disabled";
|
||||
} else {
|
||||
return "Enabled";
|
||||
}
|
||||
},
|
||||
},
|
||||
{ label: "Name", elementKey: "name" },
|
||||
{ label: "Description", elementKey: "description" },
|
||||
];
|
||||
@@ -37,12 +37,14 @@ const ServiceAccountPolicy = ({
|
||||
closeModalAndRefresh,
|
||||
}: IServiceAccountPolicyProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [policyDefinition, setPolicyDefinition] = useState<string>("");
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
if (!loading && selectedAccessKey !== "") {
|
||||
const sourceAccKey = encodeURLString(selectedAccessKey);
|
||||
setLoading(true);
|
||||
api.serviceAccounts
|
||||
.getServiceAccountPolicy(encodeURLString(selectedAccessKey))
|
||||
.getServiceAccountPolicy(sourceAccKey)
|
||||
.then((res) => {
|
||||
setLoading(false);
|
||||
setPolicyDefinition(res.data);
|
||||
@@ -52,7 +54,8 @@ const ServiceAccountPolicy = ({
|
||||
dispatch(setModalErrorSnackMessage(errorToHandler(err)));
|
||||
});
|
||||
}
|
||||
}, [loading, setLoading, dispatch, selectedAccessKey]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedAccessKey]);
|
||||
|
||||
const setPolicy = (event: React.FormEvent, newPolicy: string) => {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -33,7 +33,7 @@ import { encodeURLString } from "../../../../common/utils";
|
||||
import { setErrorSnackMessage, setHelpName } from "../../../../systemSlice";
|
||||
import { selBucketDetailsLoading } from "./bucketDetailsSlice";
|
||||
import { useAppDispatch } from "../../../../store";
|
||||
import { Policy, ServiceAccounts } from "../../../../api/consoleApi";
|
||||
import { Policy } from "../../../../api/consoleApi";
|
||||
|
||||
const AccessDetails = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
@@ -46,7 +46,7 @@ const AccessDetails = () => {
|
||||
const [loadingPolicies, setLoadingPolicies] = useState<boolean>(true);
|
||||
const [bucketPolicy, setBucketPolicy] = useState<Policy[] | undefined>([]);
|
||||
const [loadingUsers, setLoadingUsers] = useState<boolean>(true);
|
||||
const [bucketUsers, setBucketUsers] = useState<ServiceAccounts>([]);
|
||||
const [bucketUsers, setBucketUsers] = useState<string[]>([]);
|
||||
|
||||
const bucketName = params.bucketName || "";
|
||||
|
||||
|
||||
@@ -68,12 +68,7 @@ import { selFeatures } from "../consoleSlice";
|
||||
import { useAppDispatch } from "../../../store";
|
||||
import TooltipWrapper from "../Common/TooltipWrapper/TooltipWrapper";
|
||||
import PageHeaderWrapper from "../Common/PageHeaderWrapper/PageHeaderWrapper";
|
||||
import {
|
||||
Error,
|
||||
HttpResponse,
|
||||
Policy,
|
||||
ServiceAccounts,
|
||||
} from "../../../api/consoleApi";
|
||||
import { Error, HttpResponse, Policy } from "../../../api/consoleApi";
|
||||
import { api } from "../../../api";
|
||||
import HelpMenu from "../HelpMenu";
|
||||
import SearchBox from "../Common/SearchBox";
|
||||
@@ -188,7 +183,7 @@ const PolicyDetails = () => {
|
||||
if (displayUsers && !ldapIsEnabled) {
|
||||
api.policies
|
||||
.listUsersForPolicy(encodeURLString(policyName))
|
||||
.then((result: HttpResponse<ServiceAccounts, Error>) => {
|
||||
.then((result: HttpResponse<string[], Error>) => {
|
||||
setUserList(result.data ?? []);
|
||||
setLoadingUsers(false);
|
||||
})
|
||||
@@ -207,7 +202,7 @@ const PolicyDetails = () => {
|
||||
if (displayGroups && !ldapIsEnabled) {
|
||||
api.policies
|
||||
.listGroupsForPolicy(encodeURLString(policyName))
|
||||
.then((result: HttpResponse<ServiceAccounts, Error>) => {
|
||||
.then((result: HttpResponse<string[], Error>) => {
|
||||
setGroupList(result.data ?? []);
|
||||
setLoadingGroups(false);
|
||||
})
|
||||
|
||||
@@ -19,7 +19,6 @@ import { useNavigate } from "react-router-dom";
|
||||
import { AddIcon, Box, Button, DataTable, DeleteIcon, SectionTitle } from "mds";
|
||||
import api from "../../../common/api";
|
||||
import { NewServiceAccount } from "../Common/CredentialsPrompt/types";
|
||||
import { stringSort } from "../../../utils/sortFunctions";
|
||||
import { ErrorResponseHandler } from "../../../common/types";
|
||||
import DeleteServiceAccount from "../Account/DeleteServiceAccount";
|
||||
import CredentialsPrompt from "../Common/CredentialsPrompt/CredentialsPrompt";
|
||||
@@ -40,6 +39,9 @@ import {
|
||||
} from "../../../systemSlice";
|
||||
import { useAppDispatch } from "../../../store";
|
||||
import TooltipWrapper from "../Common/TooltipWrapper/TooltipWrapper";
|
||||
import { ServiceAccounts } from "../../../api/consoleApi";
|
||||
import { usersSort } from "../../../utils/sortFunctions";
|
||||
import { ACCOUNT_TABLE_COLUMNS } from "../Account/AccountUtils";
|
||||
|
||||
interface IUserServiceAccountsProps {
|
||||
user: string;
|
||||
@@ -53,7 +55,7 @@ const UserServiceAccountsPanel = ({
|
||||
const dispatch = useAppDispatch();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [records, setRecords] = useState<string[]>([]);
|
||||
const [records, setRecords] = useState<ServiceAccounts>([]);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
|
||||
const [selectedServiceAccount, setSelectedServiceAccount] = useState<
|
||||
@@ -74,10 +76,10 @@ const UserServiceAccountsPanel = ({
|
||||
if (loading) {
|
||||
api
|
||||
.invoke("GET", `/api/v1/user/${encodeURLString(user)}/service-accounts`)
|
||||
.then((res: string[]) => {
|
||||
const serviceAccounts = res.sort(stringSort);
|
||||
.then((res: ServiceAccounts) => {
|
||||
setLoading(false);
|
||||
setRecords(serviceAccounts);
|
||||
const sortedRows = res.sort(usersSort);
|
||||
setRecords(sortedRows);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
dispatch(setErrorSnackMessage(err));
|
||||
@@ -107,14 +109,6 @@ const UserServiceAccountsPanel = ({
|
||||
}
|
||||
};
|
||||
|
||||
const selectAllItems = () => {
|
||||
if (selectedSAs.length === records.length) {
|
||||
setSelectedSAs([]);
|
||||
return;
|
||||
}
|
||||
setSelectedSAs(records);
|
||||
};
|
||||
|
||||
const closeCredentialsModal = () => {
|
||||
setShowNewCredentials(false);
|
||||
setNewServiceAccount(null);
|
||||
@@ -136,8 +130,22 @@ const UserServiceAccountsPanel = ({
|
||||
};
|
||||
|
||||
const tableActions = [
|
||||
{ type: "view", onClick: policyModalOpen },
|
||||
{ type: "delete", onClick: confirmDeleteServiceAccount },
|
||||
{
|
||||
type: "view",
|
||||
onClick: (value: any) => {
|
||||
if (value) {
|
||||
policyModalOpen(value.accessKey);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "delete",
|
||||
onClick: (value: any) => {
|
||||
if (value) {
|
||||
confirmDeleteServiceAccount(value.accessKey);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
@@ -231,14 +239,14 @@ const UserServiceAccountsPanel = ({
|
||||
</SectionTitle>
|
||||
|
||||
<DataTable
|
||||
itemActions={tableActions}
|
||||
entityName={"Access Keys"}
|
||||
columns={ACCOUNT_TABLE_COLUMNS}
|
||||
onSelect={(e) => selectSAs(e, setSelectedSAs, selectedSAs)}
|
||||
selectedItems={selectedSAs}
|
||||
isLoading={loading}
|
||||
records={records}
|
||||
entityName={"Access Keys"}
|
||||
columns={[{ label: "Access Key" }]}
|
||||
itemActions={tableActions}
|
||||
selectedItems={selectedSAs}
|
||||
onSelect={(e) => selectSAs(e, setSelectedSAs, selectedSAs)}
|
||||
onSelectAll={selectAllItems}
|
||||
idField="accessKey"
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
@@ -8161,7 +8161,24 @@ func init() {
|
||||
"serviceAccounts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accessKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"accountStatus": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"expiration": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"sessionResponse": {
|
||||
@@ -14515,6 +14532,26 @@ func init() {
|
||||
}
|
||||
}
|
||||
},
|
||||
"ServiceAccountsItems0": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accessKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"accountStatus": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"expiration": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"SubnetRegTokenResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -17357,7 +17394,7 @@ func init() {
|
||||
"serviceAccounts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
"$ref": "#/definitions/ServiceAccountsItems0"
|
||||
}
|
||||
},
|
||||
"sessionResponse": {
|
||||
|
||||
@@ -22,15 +22,14 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/console/pkg/utils"
|
||||
|
||||
userApi "github.com/minio/console/restapi/operations/user"
|
||||
"time"
|
||||
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/minio/console/models"
|
||||
"github.com/minio/console/pkg/utils"
|
||||
"github.com/minio/console/restapi/operations"
|
||||
saApi "github.com/minio/console/restapi/operations/service_account"
|
||||
userApi "github.com/minio/console/restapi/operations/user"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
iampolicy "github.com/minio/pkg/iam/policy"
|
||||
)
|
||||
@@ -323,11 +322,27 @@ func getUserServiceAccounts(ctx context.Context, userClient MinioAdmin, user str
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serviceAccounts := models.ServiceAccounts{}
|
||||
saList := models.ServiceAccounts{}
|
||||
|
||||
for _, acc := range listServAccs.Accounts {
|
||||
serviceAccounts = append(serviceAccounts, acc.AccessKey)
|
||||
aInfo, err := userClient.infoServiceAccount(ctx, acc.AccessKey)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
expiry := ""
|
||||
if aInfo.Expiration != nil {
|
||||
expiry = aInfo.Expiration.Format(time.RFC3339)
|
||||
}
|
||||
|
||||
saList = append(saList, &models.ServiceAccountsItems0{
|
||||
AccountStatus: aInfo.AccountStatus,
|
||||
Description: aInfo.Description,
|
||||
Expiration: expiry,
|
||||
Name: aInfo.Name,
|
||||
AccessKey: acc.AccessKey,
|
||||
})
|
||||
}
|
||||
return serviceAccounts, nil
|
||||
return saList, nil
|
||||
}
|
||||
|
||||
// getUserServiceAccountsResponse authenticates the user and calls
|
||||
|
||||
@@ -98,13 +98,23 @@ func TestListServiceAccounts(t *testing.T) {
|
||||
minioListServiceAccountsMock = func(ctx context.Context, user string) (madmin.ListServiceAccountsResp, error) {
|
||||
return mockResponse, nil
|
||||
}
|
||||
serviceAccounts, err := getUserServiceAccounts(ctx, client, "")
|
||||
|
||||
mockInfoResp := madmin.InfoServiceAccountResp{
|
||||
ParentUser: "",
|
||||
AccountStatus: "",
|
||||
ImpliedPolicy: false,
|
||||
Policy: "",
|
||||
Name: "",
|
||||
Description: "",
|
||||
Expiration: nil,
|
||||
}
|
||||
minioInfoServiceAccountMock = func(ctx context.Context, serviceAccount string) (madmin.InfoServiceAccountResp, error) {
|
||||
return mockInfoResp, nil
|
||||
}
|
||||
_, err := getUserServiceAccounts(ctx, client, "")
|
||||
if err != nil {
|
||||
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
|
||||
}
|
||||
for i, sa := range serviceAccounts {
|
||||
assert.Equal(mockResponse.Accounts[i].AccessKey, sa)
|
||||
}
|
||||
|
||||
// Test-2: getUserServiceAccounts returns an error, handle it properly
|
||||
minioListServiceAccountsMock = func(ctx context.Context, user string) (madmin.ListServiceAccountsResp, error) {
|
||||
|
||||
14
swagger.yml
14
swagger.yml
@@ -4871,7 +4871,19 @@ definitions:
|
||||
serviceAccounts:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
type: object
|
||||
properties:
|
||||
accountStatus:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
expiration:
|
||||
type: string
|
||||
accessKey:
|
||||
type: string
|
||||
|
||||
serviceAccountRequest:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
Reference in New Issue
Block a user