From c2171fafde9ecc569dbee4412c2d94332201e257 Mon Sep 17 00:00:00 2001 From: adfost Date: Wed, 19 May 2021 15:18:59 -0700 Subject: [PATCH] User policies (#744) * adding policy users and new UI page * Prettier + aligment Co-authored-by: Adam Stafford --- pkg/acl/endpoints.go | 2 + pkg/acl/endpoints_test.go | 6 +- portal-ui/src/screens/Console/Console.tsx | 4 + .../screens/Console/Policies/ListPolicies.tsx | 232 ++++++++++++ .../src/screens/Console/Policies/Policies.tsx | 241 ++---------- .../Console/Policies/PolicyDetails.tsx | 351 ++++++++++++++++++ restapi/admin_policies.go | 38 ++ restapi/embedded_spec.go | 68 ++++ .../admin_api/list_users_for_policy.go | 90 +++++ .../list_users_for_policy_parameters.go | 89 +++++ .../list_users_for_policy_responses.go | 136 +++++++ .../list_users_for_policy_urlbuilder.go | 116 ++++++ restapi/operations/console_api.go | 12 + swagger.yml | 25 +- 14 files changed, 1186 insertions(+), 224 deletions(-) create mode 100644 portal-ui/src/screens/Console/Policies/ListPolicies.tsx create mode 100644 portal-ui/src/screens/Console/Policies/PolicyDetails.tsx create mode 100644 restapi/operations/admin_api/list_users_for_policy.go create mode 100644 restapi/operations/admin_api/list_users_for_policy_parameters.go create mode 100644 restapi/operations/admin_api/list_users_for_policy_responses.go create mode 100644 restapi/operations/admin_api/list_users_for_policy_urlbuilder.go diff --git a/pkg/acl/endpoints.go b/pkg/acl/endpoints.go index 513080c1f..68d350491 100644 --- a/pkg/acl/endpoints.go +++ b/pkg/acl/endpoints.go @@ -27,6 +27,7 @@ var ( usersDetail = "/users/:userName" groups = "/groups" iamPolicies = "/policies" + policiesDetail = "/policies/:policyName" dashboard = "/dashboard" profiling = "/profiling" buckets = "/buckets" @@ -278,6 +279,7 @@ var endpointRules = map[string]ConfigurationActionSet{ usersDetail: usersActionSet, groups: groupsActionSet, iamPolicies: iamPoliciesActionSet, + policiesDetail: iamPoliciesActionSet, dashboard: dashboardActionSet, profiling: profilingActionSet, buckets: bucketsActionSet, diff --git a/pkg/acl/endpoints_test.go b/pkg/acl/endpoints_test.go index 010f19d69..a6331dea4 100644 --- a/pkg/acl/endpoints_test.go +++ b/pkg/acl/endpoints_test.go @@ -63,7 +63,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) { "admin:ListUserPolicies", }, }, - want: 6, + want: 7, }, { name: "all admin endpoints", @@ -72,7 +72,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) { "admin:*", }, }, - want: 20, + want: 21, }, { name: "all s3 endpoints", @@ -91,7 +91,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) { "s3:*", }, }, - want: 22, + want: 23, }, { name: "Console User - default endpoints", diff --git a/portal-ui/src/screens/Console/Console.tsx b/portal-ui/src/screens/Console/Console.tsx index 698cb560c..04cbdff5e 100644 --- a/portal-ui/src/screens/Console/Console.tsx +++ b/portal-ui/src/screens/Console/Console.tsx @@ -258,6 +258,10 @@ const Console = ({ component: Groups, path: "/groups", }, + { + component: Policies, + path: "/policies/:policyName", + }, { component: Policies, path: "/policies", diff --git a/portal-ui/src/screens/Console/Policies/ListPolicies.tsx b/portal-ui/src/screens/Console/Policies/ListPolicies.tsx new file mode 100644 index 000000000..6103873fc --- /dev/null +++ b/portal-ui/src/screens/Console/Policies/ListPolicies.tsx @@ -0,0 +1,232 @@ +// This file is part of MinIO Kubernetes Cloud +// 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 . + +import React, { useState, useEffect } from "react"; +import { connect } from "react-redux"; +import get from "lodash/get"; +import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; +import { Button } from "@material-ui/core"; +import Grid from "@material-ui/core/Grid"; +import TextField from "@material-ui/core/TextField"; +import InputAdornment from "@material-ui/core/InputAdornment"; +import SearchIcon from "@material-ui/icons/Search"; +import { Policy, PolicyList } from "./types"; +import { CreateIcon } from "../../../icons"; +import { setErrorSnackMessage } from "../../../actions"; +import { + actionsTray, + containerForHeader, + searchField, +} from "../Common/FormComponents/common/styleLibrary"; +import AddPolicy from "./AddPolicy"; +import DeletePolicy from "./DeletePolicy"; +import TableWrapper from "../Common/TableWrapper/TableWrapper"; +import PageHeader from "../Common/PageHeader/PageHeader"; +import api from "../../../common/api"; +import history from "../../../history"; + +const styles = (theme: Theme) => + createStyles({ + seeMore: { + marginTop: theme.spacing(3), + }, + paper: { + display: "flex", + overflow: "auto", + flexDirection: "column", + }, + + addSideBar: { + width: "320px", + padding: "20px", + }, + tableToolbar: { + paddingLeft: theme.spacing(2), + paddingRight: theme.spacing(0), + }, + minTableHeader: { + color: "#393939", + "& tr": { + "& th": { + fontWeight: "bold", + }, + }, + }, + ...actionsTray, + ...searchField, + ...containerForHeader(theme.spacing(4)), + }); + +interface IPoliciesProps { + classes: any; + setErrorSnackMessage: typeof setErrorSnackMessage; +} + +const ListPolicies = ({ classes, setErrorSnackMessage }: IPoliciesProps) => { + const [records, setRecords] = useState([]); + const [loading, setLoading] = useState(false); + const [addScreenOpen, setAddScreenOpen] = useState(false); + const [deleteOpen, setDeleteOpen] = useState(false); + const [selectedPolicy, setSelectedPolicy] = useState(""); + const [filterPolicies, setFilterPolicies] = useState(""); + const [policyEdit, setPolicyEdit] = useState(null); + + useEffect(() => { + fetchRecords(); + }, []); + + useEffect(() => { + if (loading) { + api + .invoke("GET", `/api/v1/policies`) + .then((res: PolicyList) => { + const policies = get(res, "policies", []); + + policies.sort((pa, pb) => { + if (pa.name > pb.name) { + return 1; + } + + if (pa.name < pb.name) { + return -1; + } + + return 0; + }); + + setLoading(false); + setRecords(policies); + }) + .catch((err) => { + setLoading(false); + setErrorSnackMessage(err); + }); + } + }, [loading, setLoading, setRecords, setErrorSnackMessage]); + + const fetchRecords = () => { + setLoading(true); + }; + + const closeAddModalAndRefresh = (refresh: boolean) => { + setAddScreenOpen(false); + + if (refresh) { + fetchRecords(); + } + }; + + const closeDeleteModalAndRefresh = (refresh: boolean) => { + setDeleteOpen(false); + + if (refresh) { + fetchRecords(); + } + }; + + const confirmDeletePolicy = (policy: string) => { + setDeleteOpen(true); + setSelectedPolicy(policy); + }; + + const viewAction = (policy: any) => { + history.push(`/policies/${policy.name}`); + }; + + const tableActions = [ + { type: "view", onClick: viewAction }, + { type: "delete", onClick: confirmDeletePolicy, sendOnlyId: true }, + ]; + + const filteredRecords = records.filter((elementItem) => + elementItem.name.includes(filterPolicies) + ); + + return ( + + {addScreenOpen && ( + + )} + {deleteOpen && ( + + )} + + + + + { + setFilterPolicies(val.target.value); + }} + InputProps={{ + disableUnderline: true, + startAdornment: ( + + + + ), + }} + /> + + + +
+
+ + + +
+
+
+ ); +}; + +const mapDispatchToProps = { + setErrorSnackMessage, +}; + +const connector = connect(null, mapDispatchToProps); + +export default withStyles(styles)(connector(ListPolicies)); diff --git a/portal-ui/src/screens/Console/Policies/Policies.tsx b/portal-ui/src/screens/Console/Policies/Policies.tsx index 9e7e1b5b8..136b3f67d 100644 --- a/portal-ui/src/screens/Console/Policies/Policies.tsx +++ b/portal-ui/src/screens/Console/Policies/Policies.tsx @@ -1,232 +1,33 @@ -// This file is part of MinIO Kubernetes Cloud -// 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 . -import React, { useState, useEffect } from "react"; +import React from "react"; +import history from "../../../history"; +import { Route, Router, Switch, withRouter } from "react-router-dom"; import { connect } from "react-redux"; -import get from "lodash/get"; -import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; -import { Button } from "@material-ui/core"; -import Grid from "@material-ui/core/Grid"; -import TextField from "@material-ui/core/TextField"; -import InputAdornment from "@material-ui/core/InputAdornment"; -import SearchIcon from "@material-ui/icons/Search"; -import { Policy, PolicyList } from "./types"; -import { CreateIcon } from "../../../icons"; -import { setErrorSnackMessage } from "../../../actions"; -import { - actionsTray, - containerForHeader, - searchField, -} from "../Common/FormComponents/common/styleLibrary"; -import AddPolicy from "./AddPolicy"; -import DeletePolicy from "./DeletePolicy"; -import TableWrapper from "../Common/TableWrapper/TableWrapper"; -import PageHeader from "../Common/PageHeader/PageHeader"; -import api from "../../../common/api"; +import { AppState } from "../../../store"; +import { setMenuOpen } from "../../../actions"; +import NotFoundPage from "../../NotFoundPage"; -const styles = (theme: Theme) => - createStyles({ - seeMore: { - marginTop: theme.spacing(3), - }, - paper: { - display: "flex", - overflow: "auto", - flexDirection: "column", - }, +import ListPolicies from "./ListPolicies"; +import PolicyDetails from "./PolicyDetails"; - addSideBar: { - width: "320px", - padding: "20px", - }, - tableToolbar: { - paddingLeft: theme.spacing(2), - paddingRight: theme.spacing(0), - }, - minTableHeader: { - color: "#393939", - "& tr": { - "& th": { - fontWeight: "bold", - }, - }, - }, - ...actionsTray, - ...searchField, - ...containerForHeader(theme.spacing(4)), - }); +const mapState = (state: AppState) => ({ + open: state.system.sidebarOpen, +}); -interface IPoliciesProps { - classes: any; - setErrorSnackMessage: typeof setErrorSnackMessage; -} - -const Policies = ({ classes, setErrorSnackMessage }: IPoliciesProps) => { - const [records, setRecords] = useState([]); - const [loading, setLoading] = useState(false); - const [addScreenOpen, setAddScreenOpen] = useState(false); - const [deleteOpen, setDeleteOpen] = useState(false); - const [selectedPolicy, setSelectedPolicy] = useState(""); - const [filterPolicies, setFilterPolicies] = useState(""); - const [policyEdit, setPolicyEdit] = useState(null); - - useEffect(() => { - fetchRecords(); - }, []); - - useEffect(() => { - if (loading) { - api - .invoke("GET", `/api/v1/policies`) - .then((res: PolicyList) => { - const policies = get(res, "policies", []); - - policies.sort((pa, pb) => { - if (pa.name > pb.name) { - return 1; - } - - if (pa.name < pb.name) { - return -1; - } - - return 0; - }); - - setLoading(false); - setRecords(policies); - }) - .catch((err) => { - setLoading(false); - setErrorSnackMessage(err); - }); - } - }, [loading, setLoading, setRecords, setErrorSnackMessage]); - - const fetchRecords = () => { - setLoading(true); - }; - - const closeAddModalAndRefresh = (refresh: boolean) => { - setAddScreenOpen(false); - - if (refresh) { - fetchRecords(); - } - }; - - const closeDeleteModalAndRefresh = (refresh: boolean) => { - setDeleteOpen(false); - - if (refresh) { - fetchRecords(); - } - }; - - const confirmDeletePolicy = (policy: string) => { - setDeleteOpen(true); - setSelectedPolicy(policy); - }; - - const viewAction = (row: any) => { - setAddScreenOpen(true); - setPolicyEdit(row); - }; - - const tableActions = [ - { type: "view", onClick: viewAction }, - { type: "delete", onClick: confirmDeletePolicy, sendOnlyId: true }, - ]; - - const filteredRecords = records.filter((elementItem) => - elementItem.name.includes(filterPolicies) - ); +const connector = connect(mapState, { setMenuOpen }); +const Users = () => { return ( - - {addScreenOpen && ( - - )} - {deleteOpen && ( - - )} - - - - - { - setFilterPolicies(val.target.value); - }} - InputProps={{ - disableUnderline: true, - startAdornment: ( - - - - ), - }} - /> - - - -
-
- - - -
-
-
+ + + + + + + ); }; -const mapDispatchToProps = { - setErrorSnackMessage, -}; - -const connector = connect(null, mapDispatchToProps); - -export default withStyles(styles)(connector(Policies)); +export default withRouter(connector(Users)); diff --git a/portal-ui/src/screens/Console/Policies/PolicyDetails.tsx b/portal-ui/src/screens/Console/Policies/PolicyDetails.tsx new file mode 100644 index 000000000..01d6be4d4 --- /dev/null +++ b/portal-ui/src/screens/Console/Policies/PolicyDetails.tsx @@ -0,0 +1,351 @@ +// 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 . + +import React, { useEffect, useState } from "react"; +import { Policy } from "./types"; +import { connect } from "react-redux"; +import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; +import { + containerForHeader, + modalBasic, +} from "../Common/FormComponents/common/styleLibrary"; +import Paper from "@material-ui/core/Paper"; +import Grid from "@material-ui/core/Grid"; +import { Button, LinearProgress } from "@material-ui/core"; +import Tabs from "@material-ui/core/Tabs"; +import Tab from "@material-ui/core/Tab"; +import TableWrapper from "../Common/TableWrapper/TableWrapper"; +import { User } from "../Users/types"; +import api from "../../../common/api"; +import PageHeader from "../Common/PageHeader/PageHeader"; +import { Link } from "react-router-dom"; +import { + setErrorSnackMessage, + setModalErrorSnackMessage, +} from "../../../actions"; +import { Fragment } from "react"; +import CodeMirrorWrapper from "../Common/FormComponents/CodeMirrorWrapper/CodeMirrorWrapper"; +import history from "../../../history"; + +interface IPolicyDetailsProps { + classes: any; + match: any; + closeModalAndRefresh: (refresh: boolean) => void; + setErrorSnackMessage: typeof setErrorSnackMessage; +} + +const styles = (theme: Theme) => + createStyles({ + buttonContainer: { + textAlign: "right", + }, + multiContainer: { + display: "flex", + alignItems: "center" as const, + justifyContent: "flex-start" as const, + }, + sizeFactorContainer: { + marginLeft: 8, + }, + containerHeader: { + display: "flex", + justifyContent: "space-between", + }, + paperContainer: { + padding: "15px 15px 15px 50px", + }, + infoGrid: { + display: "grid", + gridTemplateColumns: "auto auto auto auto", + gridGap: 8, + "& div": { + display: "flex", + alignItems: "center", + }, + "& div:nth-child(odd)": { + justifyContent: "flex-end", + fontWeight: 700, + }, + "& div:nth-child(2n)": { + paddingRight: 35, + }, + }, + masterActions: { + width: "25%", + minWidth: "120px", + "& div": { + margin: "5px 0px", + }, + }, + actionsTray: { + textAlign: "right", + }, + updateButton: { + backgroundColor: "transparent", + border: 0, + padding: "0 6px", + cursor: "pointer", + "&:focus, &:active": { + outline: "none", + }, + "& svg": { + height: 12, + }, + }, + noUnderLine: { + textDecoration: "none", + }, + poolLabel: { + color: "#666666", + }, + licenseContainer: { + position: "relative", + padding: "20px 52px 0px 28px", + background: "#032F51", + boxShadow: "0px 3px 7px #00000014", + "& h2": { + color: "#FFF", + marginBottom: 67, + }, + "& a": { + textDecoration: "none", + }, + "& h3": { + color: "#FFFFFF", + marginBottom: "30px", + fontWeight: "bold", + }, + "& h6": { + color: "#FFFFFF !important", + }, + }, + licenseInfo: { color: "#FFFFFF", position: "relative" }, + licenseInfoTitle: { + textTransform: "none", + color: "#BFBFBF", + fontSize: 11, + }, + licenseInfoValue: { + textTransform: "none", + fontSize: 14, + fontWeight: "bold", + }, + verifiedIcon: { + width: 96, + position: "absolute", + right: 0, + bottom: 29, + }, + breadcrumLink: { + textDecoration: "none", + color: "black", + }, + ...modalBasic, + ...containerForHeader(theme.spacing(4)), + }); + +const PolicyDetails = ({ + classes, + match, + closeModalAndRefresh, + setErrorSnackMessage, +}: IPolicyDetailsProps) => { + const [selectedTab, setSelectedTab] = useState(0); + const [policy, setPolicy] = useState(null); + const [userList, setUserList] = useState([]); + const [addLoading, setAddLoading] = useState(false); + const [policyName, setPolicyName] = useState( + match.params["policyName"] + ); + const [policyDefinition, setPolicyDefinition] = useState(""); + const [loadingPolicy, setLoadingPolicy] = useState(true); + const [loadingUsers, setLoadingUsers] = useState(true); + + const loadUsersForPolicy = () => { + if (loadingUsers) { + api + .invoke("GET", `/api/v1/policies/${policyName}/users`) + .then((result: any) => { + setUserList(result); + setLoadingUsers(false); + }) + .catch((err) => { + console.log("Error in loading users"); + }); + } + }; + const loadPolicyDetails = () => { + if (loadingPolicy) { + api + .invoke("GET", `/api/v1/policy?name=${policyName}`) + .then((result: any) => { + setPolicy(result); + setLoadingPolicy(false); + }) + .catch((err) => { + console.log("Error in loading policy"); + }); + } + }; + + const addRecord = (event: React.FormEvent) => { + event.preventDefault(); + if (addLoading) { + return; + } + setAddLoading(true); + api + .invoke("POST", "/api/v1/policies", { + name: policyName, + policy: policyDefinition, + }) + .then((res) => { + setAddLoading(false); + closeModalAndRefresh(true); + }) + .catch((err) => { + setAddLoading(false); + setModalErrorSnackMessage(err); + }); + }; + + useEffect(() => { + if (loadingPolicy) { + loadUsersForPolicy(); + loadPolicyDetails(); + if (policy) { + setPolicyName(policy.name); + setPolicyDefinition( + policy ? JSON.stringify(JSON.parse(policy.policy), null, 4) : "" + ); + } + } + }, [policy, loadingPolicy]); + + const resetForm = () => { + setPolicyName(""); + setPolicyDefinition(""); + }; + + const validSave = policyName.trim() !== ""; + + const userViewAction = (user: any) => { + history.push(`/users/${user}`); + }; + const userTableActions = [{ type: "view", onClick: userViewAction }]; + + return ( + + + + Policy + + {` > ${match.params["policyName"]}`} + + } + /> + + + + + { + setSelectedTab(newValue); + }} + aria-label="policy-tabs" + > + + + + + + {selectedTab === 0 && ( + +
) => { + addRecord(e); + }} + > + + + { + setPolicyDefinition(value); + }} + /> + + + {!policy && ( + + )} + + + + {addLoading && ( + + + + )} + +
+
+ )} + {selectedTab === 1 && ( + + )} +
+
+
+
+ ); +}; + +const connector = connect(null, { + setErrorSnackMessage, +}); + +export default withStyles(styles)(connector(PolicyDetails)); diff --git a/restapi/admin_policies.go b/restapi/admin_policies.go index 159b53f24..bc248851c 100644 --- a/restapi/admin_policies.go +++ b/restapi/admin_policies.go @@ -82,6 +82,13 @@ func registersPoliciesHandler(api *operations.ConsoleAPI) { } return admin_api.NewListPoliciesWithBucketOK().WithPayload(policyResponse) }) + api.AdminAPIListUsersForPolicyHandler = admin_api.ListUsersForPolicyHandlerFunc(func(params admin_api.ListUsersForPolicyParams, session *models.Principal) middleware.Responder { + policyUsersResponse, err := getListUsersForPolicyResponse(session, params.Policy) + if err != nil { + return admin_api.NewListUsersForPolicyDefault(int(err.Code)).WithPayload(err) + } + return admin_api.NewListUsersForPolicyOK().WithPayload(policyUsersResponse) + }) } func getListPoliciesWithBucketResponse(session *models.Principal, bucket string) (*models.ListPoliciesResponse, *models.Error) { @@ -184,6 +191,37 @@ func getListPoliciesResponse(session *models.Principal) (*models.ListPoliciesRes return listPoliciesResponse, nil } +// getListUsersForPoliciesResponse performs lists users affected by a given policy. +func getListUsersForPolicyResponse(session *models.Principal, policy string) ([]string, *models.Error) { + ctx := context.Background() + mAdmin, err := newMAdminClient(session) + if err != nil { + return nil, prepareError(err) + } + // create a minioClient interface implementation + // defining the client to be used + adminClient := adminClient{client: mAdmin} + + users, err := listUsers(ctx, adminClient) + if err != nil { + return nil, prepareError(err) + } + userArray := []string{} + for i := 0; i < len(users); i++ { + if err == nil { + for j := 0; j < len(users[i].Policy); j++ { + if users[i].Policy[j] == policy { + userArray = append(userArray, users[i].AccessKey) + break + } + } + } else { + log.Println(err) + } + } + return userArray, nil +} + // removePolicy() calls MinIO server to remove a policy based on name. func removePolicy(ctx context.Context, client MinioAdmin, name string) error { err := client.removePolicy(ctx, name) diff --git a/restapi/embedded_spec.go b/restapi/embedded_spec.go index 627e532f9..915c60fe1 100644 --- a/restapi/embedded_spec.go +++ b/restapi/embedded_spec.go @@ -2972,6 +2972,40 @@ func init() { } } }, + "/policies/{policy}/users": { + "get": { + "tags": [ + "AdminAPI" + ], + "summary": "List Users for a Policy", + "operationId": "ListUsersForPolicy", + "parameters": [ + { + "type": "string", + "name": "policy", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "default": { + "description": "Generic error response.", + "schema": { + "$ref": "#/definitions/error" + } + } + } + } + }, "/policy": { "get": { "tags": [ @@ -9921,6 +9955,40 @@ func init() { } } }, + "/policies/{policy}/users": { + "get": { + "tags": [ + "AdminAPI" + ], + "summary": "List Users for a Policy", + "operationId": "ListUsersForPolicy", + "parameters": [ + { + "type": "string", + "name": "policy", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "default": { + "description": "Generic error response.", + "schema": { + "$ref": "#/definitions/error" + } + } + } + } + }, "/policy": { "get": { "tags": [ diff --git a/restapi/operations/admin_api/list_users_for_policy.go b/restapi/operations/admin_api/list_users_for_policy.go new file mode 100644 index 000000000..77df7755b --- /dev/null +++ b/restapi/operations/admin_api/list_users_for_policy.go @@ -0,0 +1,90 @@ +// 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 admin_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" +) + +// ListUsersForPolicyHandlerFunc turns a function with the right signature into a list users for policy handler +type ListUsersForPolicyHandlerFunc func(ListUsersForPolicyParams, *models.Principal) middleware.Responder + +// Handle executing the request and returning a response +func (fn ListUsersForPolicyHandlerFunc) Handle(params ListUsersForPolicyParams, principal *models.Principal) middleware.Responder { + return fn(params, principal) +} + +// ListUsersForPolicyHandler interface for that can handle valid list users for policy params +type ListUsersForPolicyHandler interface { + Handle(ListUsersForPolicyParams, *models.Principal) middleware.Responder +} + +// NewListUsersForPolicy creates a new http.Handler for the list users for policy operation +func NewListUsersForPolicy(ctx *middleware.Context, handler ListUsersForPolicyHandler) *ListUsersForPolicy { + return &ListUsersForPolicy{Context: ctx, Handler: handler} +} + +/*ListUsersForPolicy swagger:route GET /policies/{policy}/users AdminAPI listUsersForPolicy + +List Users for a Policy + +*/ +type ListUsersForPolicy struct { + Context *middleware.Context + Handler ListUsersForPolicyHandler +} + +func (o *ListUsersForPolicy) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + r = rCtx + } + var Params = NewListUsersForPolicyParams() + + 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/admin_api/list_users_for_policy_parameters.go b/restapi/operations/admin_api/list_users_for_policy_parameters.go new file mode 100644 index 000000000..77e5c692d --- /dev/null +++ b/restapi/operations/admin_api/list_users_for_policy_parameters.go @@ -0,0 +1,89 @@ +// 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 admin_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/errors" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" +) + +// NewListUsersForPolicyParams creates a new ListUsersForPolicyParams object +// no default values defined in spec. +func NewListUsersForPolicyParams() ListUsersForPolicyParams { + + return ListUsersForPolicyParams{} +} + +// ListUsersForPolicyParams contains all the bound params for the list users for policy operation +// typically these are obtained from a http.Request +// +// swagger:parameters ListUsersForPolicy +type ListUsersForPolicyParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: path + */ + Policy 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 NewListUsersForPolicyParams() beforehand. +func (o *ListUsersForPolicyParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + rPolicy, rhkPolicy, _ := route.Params.GetOK("policy") + if err := o.bindPolicy(rPolicy, rhkPolicy, route.Formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindPolicy binds and validates parameter Policy from path. +func (o *ListUsersForPolicyParams) bindPolicy(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + + o.Policy = raw + + return nil +} diff --git a/restapi/operations/admin_api/list_users_for_policy_responses.go b/restapi/operations/admin_api/list_users_for_policy_responses.go new file mode 100644 index 000000000..5cf5b9d9e --- /dev/null +++ b/restapi/operations/admin_api/list_users_for_policy_responses.go @@ -0,0 +1,136 @@ +// 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 admin_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" +) + +// ListUsersForPolicyOKCode is the HTTP code returned for type ListUsersForPolicyOK +const ListUsersForPolicyOKCode int = 200 + +/*ListUsersForPolicyOK A successful response. + +swagger:response listUsersForPolicyOK +*/ +type ListUsersForPolicyOK struct { + + /* + In: Body + */ + Payload []string `json:"body,omitempty"` +} + +// NewListUsersForPolicyOK creates ListUsersForPolicyOK with default headers values +func NewListUsersForPolicyOK() *ListUsersForPolicyOK { + + return &ListUsersForPolicyOK{} +} + +// WithPayload adds the payload to the list users for policy o k response +func (o *ListUsersForPolicyOK) WithPayload(payload []string) *ListUsersForPolicyOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the list users for policy o k response +func (o *ListUsersForPolicyOK) SetPayload(payload []string) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *ListUsersForPolicyOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + payload := o.Payload + if payload == nil { + // return empty array + payload = make([]string, 0, 50) + } + + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } +} + +/*ListUsersForPolicyDefault Generic error response. + +swagger:response listUsersForPolicyDefault +*/ +type ListUsersForPolicyDefault struct { + _statusCode int + + /* + In: Body + */ + Payload *models.Error `json:"body,omitempty"` +} + +// NewListUsersForPolicyDefault creates ListUsersForPolicyDefault with default headers values +func NewListUsersForPolicyDefault(code int) *ListUsersForPolicyDefault { + if code <= 0 { + code = 500 + } + + return &ListUsersForPolicyDefault{ + _statusCode: code, + } +} + +// WithStatusCode adds the status to the list users for policy default response +func (o *ListUsersForPolicyDefault) WithStatusCode(code int) *ListUsersForPolicyDefault { + o._statusCode = code + return o +} + +// SetStatusCode sets the status to the list users for policy default response +func (o *ListUsersForPolicyDefault) SetStatusCode(code int) { + o._statusCode = code +} + +// WithPayload adds the payload to the list users for policy default response +func (o *ListUsersForPolicyDefault) WithPayload(payload *models.Error) *ListUsersForPolicyDefault { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the list users for policy default response +func (o *ListUsersForPolicyDefault) SetPayload(payload *models.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *ListUsersForPolicyDefault) 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/admin_api/list_users_for_policy_urlbuilder.go b/restapi/operations/admin_api/list_users_for_policy_urlbuilder.go new file mode 100644 index 000000000..9c6f951ba --- /dev/null +++ b/restapi/operations/admin_api/list_users_for_policy_urlbuilder.go @@ -0,0 +1,116 @@ +// 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 admin_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" + "strings" +) + +// ListUsersForPolicyURL generates an URL for the list users for policy operation +type ListUsersForPolicyURL struct { + Policy string + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// 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 *ListUsersForPolicyURL) WithBasePath(bp string) *ListUsersForPolicyURL { + 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 *ListUsersForPolicyURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *ListUsersForPolicyURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/policies/{policy}/users" + + policy := o.Policy + if policy != "" { + _path = strings.Replace(_path, "{policy}", policy, -1) + } else { + return nil, errors.New("policy is required on ListUsersForPolicyURL") + } + + _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 *ListUsersForPolicyURL) 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 *ListUsersForPolicyURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *ListUsersForPolicyURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on ListUsersForPolicyURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on ListUsersForPolicyURL") + } + + 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 *ListUsersForPolicyURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/restapi/operations/console_api.go b/restapi/operations/console_api.go index 3605da81c..7240a0067 100644 --- a/restapi/operations/console_api.go +++ b/restapi/operations/console_api.go @@ -253,6 +253,9 @@ func NewConsoleAPI(spec *loads.Document) *ConsoleAPI { AdminAPIListUsersHandler: admin_api.ListUsersHandlerFunc(func(params admin_api.ListUsersParams, principal *models.Principal) middleware.Responder { return middleware.NotImplemented("operation admin_api.ListUsers has not yet been implemented") }), + AdminAPIListUsersForPolicyHandler: admin_api.ListUsersForPolicyHandlerFunc(func(params admin_api.ListUsersForPolicyParams, principal *models.Principal) middleware.Responder { + return middleware.NotImplemented("operation admin_api.ListUsersForPolicy has not yet been implemented") + }), AdminAPIListUsersWithAccessToBucketHandler: admin_api.ListUsersWithAccessToBucketHandlerFunc(func(params admin_api.ListUsersWithAccessToBucketParams, principal *models.Principal) middleware.Responder { return middleware.NotImplemented("operation admin_api.ListUsersWithAccessToBucket has not yet been implemented") }), @@ -569,6 +572,8 @@ type ConsoleAPI struct { UserAPIListUserServiceAccountsHandler user_api.ListUserServiceAccountsHandler // AdminAPIListUsersHandler sets the operation handler for the list users operation AdminAPIListUsersHandler admin_api.ListUsersHandler + // AdminAPIListUsersForPolicyHandler sets the operation handler for the list users for policy operation + AdminAPIListUsersForPolicyHandler admin_api.ListUsersForPolicyHandler // AdminAPIListUsersWithAccessToBucketHandler sets the operation handler for the list users with access to bucket operation AdminAPIListUsersWithAccessToBucketHandler admin_api.ListUsersWithAccessToBucketHandler // UserAPILogSearchHandler sets the operation handler for the log search operation @@ -926,6 +931,9 @@ func (o *ConsoleAPI) Validate() error { if o.AdminAPIListUsersHandler == nil { unregistered = append(unregistered, "admin_api.ListUsersHandler") } + if o.AdminAPIListUsersForPolicyHandler == nil { + unregistered = append(unregistered, "admin_api.ListUsersForPolicyHandler") + } if o.AdminAPIListUsersWithAccessToBucketHandler == nil { unregistered = append(unregistered, "admin_api.ListUsersWithAccessToBucketHandler") } @@ -1421,6 +1429,10 @@ func (o *ConsoleAPI) initHandlerCache() { if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) } + o.handlers["GET"]["/policies/{policy}/users"] = admin_api.NewListUsersForPolicy(o.context, o.AdminAPIListUsersForPolicyHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } o.handlers["GET"]["/bucket-users/{bucket}"] = admin_api.NewListUsersWithAccessToBucket(o.context, o.AdminAPIListUsersWithAccessToBucketHandler) if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) diff --git a/swagger.yml b/swagger.yml index 33e307123..494b1f95a 100644 --- a/swagger.yml +++ b/swagger.yml @@ -1394,6 +1394,29 @@ paths: tags: - AdminAPI + /policies/{policy}/users: + get: + summary: List Users for a Policy + operationId: ListUsersForPolicy + parameters: + - name: policy + in: path + required: true + type: string + responses: + 200: + description: A successful response. + schema: + type: array + items: + type: string + default: + description: Generic error response. + schema: + $ref: "#/definitions/error" + tags: + - AdminAPI + /bucket-policy/{bucket}: get: summary: List Policies With Given Bucket @@ -2578,7 +2601,6 @@ paths: tags: - AdminAPI - definitions: accountChangePasswordRequest: type: object @@ -3990,6 +4012,7 @@ definitions: type: integer node: type: string + pool: type: object required: