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: (
+
+
+
+ ),
+ }}
+ />
+ }
+ onClick={() => {
+ setAddScreenOpen(true);
+ setPolicyEdit(null);
+ }}
+ >
+ Create Policy
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+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: (
-
-
-
- ),
- }}
- />
- }
- onClick={() => {
- setAddScreenOpen(true);
- setPolicyEdit(null);
- }}
- >
- Create Policy
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
);
};
-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 && (
+
+
+
+ )}
+ {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: