From 3b2c740fe0baadfad7f0d5bfd0e52312982643c2 Mon Sep 17 00:00:00 2001 From: Lenin Alevski Date: Mon, 13 Dec 2021 22:37:22 -0800 Subject: [PATCH] Application routing now uses SecureComponent and hasPermission function (#1288) - Some menu options were not showing even if the user has access to perform the operations (IAM Policies) - Deleted unecessary backend endpoints.go logic, instead using SecureComponent to validate application routes and Menu options rendering - All the logic related to routes and permissions is now in the permissions.ts file - Added SecureComponent to List Users page - Separated Menu options and routing logic for AdminConsole and OperatorConsole - Tools are hidden if user don't have access to them or MinIO is running in fs mode (heal, audit log, etc - Hide change-password button if user don't have access - Hide create user button if user don't have access - fixed some bugs when ldap/oidc is enabled Signed-off-by: Lenin Alevski --- models/operator_session_response.go | 6 - models/session_response.go | 3 - operatorapi/embedded_spec.go | 24 - operatorapi/operator_session.go | 9 - pkg/acl/config.go | 32 -- pkg/acl/const.go | 23 - pkg/acl/endpoints.go | 469 ------------------ pkg/acl/endpoints_test.go | 118 ----- .../v1beta1/zz_generated.deepcopy.go | 1 + .../v1beta2/zz_generated.deepcopy.go | 1 + portal-ui/public/index.html | 5 +- .../src/common/SecureComponent/permissions.ts | 198 +++++++- .../src/screens/Console/Account/Account.tsx | 45 +- .../BucketDetails/AccessDetailsPanel.tsx | 5 +- .../Buckets/BucketDetails/AccessRulePanel.tsx | 1 + .../Buckets/BucketDetails/BrowserHandler.tsx | 3 +- .../Buckets/BucketDetails/BucketDetails.tsx | 1 + .../BucketDetails/BucketEventsPanel.tsx | 1 + .../BucketDetails/BucketLifecyclePanel.tsx | 1 + .../BucketDetails/BucketReplicationPanel.tsx | 1 + .../BucketDetails/BucketSummaryPanel.tsx | 1 + .../Buckets/ListBuckets/BucketListItem.tsx | 2 +- .../Buckets/ListBuckets/ListBuckets.tsx | 1 + .../Objects/ListObjects/ListObjects.tsx | 110 ++-- .../Objects/ObjectDetails/ObjectDetails.tsx | 6 + .../MissingIntegration/MissingIntegration.tsx | 60 +++ .../ListTiersConfiguration.tsx | 346 +++++++------ .../screens/Console/Configurations/types.ts | 1 + portal-ui/src/screens/Console/Console.tsx | 367 ++++++++------ .../src/screens/Console/Groups/Groups.tsx | 1 + .../screens/Console/Groups/GroupsDetails.tsx | 15 +- portal-ui/src/screens/Console/Heal/Heal.tsx | 4 +- .../Console/Logs/LogSearch/LogsSearchMain.tsx | 409 ++++++++------- portal-ui/src/screens/Console/Menu/Menu.tsx | 194 ++++---- portal-ui/src/screens/Console/Menu/types.ts | 11 +- .../screens/Console/Policies/ListPolicies.tsx | 1 + .../Console/Policies/PolicyDetails.tsx | 28 +- .../screens/Console/Speedtest/Speedtest.tsx | 4 +- .../Console/Tools/ToolsPanel/ToolsList.tsx | 63 ++- .../src/screens/Console/Users/ListUsers.tsx | 185 ++++--- portal-ui/src/screens/Console/reducer.ts | 1 - portal-ui/src/screens/Console/types.ts | 1 - portal-ui/yarn.lock | 8 +- restapi/embedded_spec.go | 12 - restapi/user_session.go | 23 +- swagger-console.yml | 4 - swagger-operator.yml | 8 - 47 files changed, 1331 insertions(+), 1482 deletions(-) delete mode 100644 pkg/acl/config.go delete mode 100644 pkg/acl/const.go delete mode 100644 pkg/acl/endpoints.go delete mode 100644 pkg/acl/endpoints_test.go create mode 100644 portal-ui/src/screens/Console/Common/MissingIntegration/MissingIntegration.tsx diff --git a/models/operator_session_response.go b/models/operator_session_response.go index 0f84df3b4..552bb5511 100644 --- a/models/operator_session_response.go +++ b/models/operator_session_response.go @@ -37,15 +37,9 @@ import ( // swagger:model operatorSessionResponse type OperatorSessionResponse struct { - // features - Features []string `json:"features"` - // operator Operator bool `json:"operator,omitempty"` - // pages - Pages []string `json:"pages"` - // status // Enum: [ok] Status string `json:"status,omitempty"` diff --git a/models/session_response.go b/models/session_response.go index 70a892dfd..fbca9607b 100644 --- a/models/session_response.go +++ b/models/session_response.go @@ -46,9 +46,6 @@ type SessionResponse struct { // operator Operator bool `json:"operator,omitempty"` - // pages - Pages []string `json:"pages"` - // permissions Permissions map[string][]string `json:"permissions,omitempty"` diff --git a/operatorapi/embedded_spec.go b/operatorapi/embedded_spec.go index e1919730d..d9bb9fe63 100644 --- a/operatorapi/embedded_spec.go +++ b/operatorapi/embedded_spec.go @@ -2296,21 +2296,9 @@ func init() { "operatorSessionResponse": { "type": "object", "properties": { - "features": { - "type": "array", - "items": { - "type": "string" - } - }, "operator": { "type": "boolean" }, - "pages": { - "type": "array", - "items": { - "type": "string" - } - }, "status": { "type": "string", "enum": [ @@ -6103,21 +6091,9 @@ func init() { "operatorSessionResponse": { "type": "object", "properties": { - "features": { - "type": "array", - "items": { - "type": "string" - } - }, "operator": { "type": "boolean" }, - "pages": { - "type": "array", - "items": { - "type": "string" - } - }, "status": { "type": "string", "enum": [ diff --git a/operatorapi/operator_session.go b/operatorapi/operator_session.go index 35bddc896..0aaf6ffc3 100644 --- a/operatorapi/operator_session.go +++ b/operatorapi/operator_session.go @@ -21,7 +21,6 @@ import ( "github.com/minio/console/models" "github.com/minio/console/operatorapi/operations" "github.com/minio/console/operatorapi/operations/user_api" - "github.com/minio/console/pkg/acl" ) func registerSessionHandlers(api *operations.OperatorAPI) { @@ -42,16 +41,8 @@ func getSessionResponse(session *models.Principal) (*models.OperatorSessionRespo return nil, prepareError(errorGenericInvalidSession) } sessionResp := &models.OperatorSessionResponse{ - Pages: acl.GetAuthorizedEndpoints([]string{}), - Features: getListOfEnabledFeatures(), Status: models.OperatorSessionResponseStatusOk, Operator: true, } return sessionResp, nil } - -// getListOfEnabledFeatures returns a list of features -func getListOfEnabledFeatures() []string { - var features []string - return features -} diff --git a/pkg/acl/config.go b/pkg/acl/config.go deleted file mode 100644 index ce89c9171..000000000 --- a/pkg/acl/config.go +++ /dev/null @@ -1,32 +0,0 @@ -// 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 acl - -import ( - "strings" - - "github.com/minio/pkg/env" -) - -// GetOperatorMode gets Console Operator mode status set on env variable or default one -func GetOperatorMode() bool { - return strings.ToLower(env.Get(consoleOperatorMode, "off")) == "on" -} - -func GetLDAPEnabled() bool { - return strings.ToLower(env.Get(ConsoleLDAPEnabled, "off")) == "on" -} diff --git a/pkg/acl/const.go b/pkg/acl/const.go deleted file mode 100644 index 7d4815bd4..000000000 --- a/pkg/acl/const.go +++ /dev/null @@ -1,23 +0,0 @@ -// 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 acl - -const ( - consoleOperatorMode = "CONSOLE_OPERATOR_MODE" - // const for ldap configuration - ConsoleLDAPEnabled = "CONSOLE_LDAP_ENABLED" -) diff --git a/pkg/acl/endpoints.go b/pkg/acl/endpoints.go deleted file mode 100644 index 3c9501f51..000000000 --- a/pkg/acl/endpoints.go +++ /dev/null @@ -1,469 +0,0 @@ -// 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 acl - -import ( - iampolicy "github.com/minio/pkg/iam/policy" -) - -// endpoints definition -var ( - configuration = "/settings" - configurationItem = "/settings/:option" - notificationEndpoints = "/notification-endpoints" - notificationEndpointsAddAny = "/notification-endpoints/add/:service" - notificationEndpointsAdd = "/notification-endpoints/add" - tiers = "/tiers" - tiersAddAny = "/tiers/add/:service" - tiersAdd = "/tiers/add" - users = "/users" - usersDetail = "/users/:userName+" - groups = "/groups" - groupsDetails = "/groups/:groupName+" - iamPolicies = "/policies" - policiesDetail = "/policies/*" - dashboard = "/dashboard" - metrics = "/metrics" - profiling = "/profiling" - addBucket = "/add-bucket" - buckets = "/buckets" - bucketsGeneral = "/buckets/*" - bucketsAdmin = "/buckets/:bucketName/admin/*" - bucketsAdminMain = "/buckets/:bucketName/admin" - bucketsBrowserMenu = "/buckets" - bucketsBrowserList = "/buckets/*" - bucketsBrowser = "/buckets/:bucketName/browse/*" - bucketsBrowserMain = "/buckets/:bucketName/browse" - serviceAccounts = "/account" - changePassword = "/account/change-password" - tenants = "/tenants" - tenantsAdd = "/tenants/add" - tenantsAddSub = "/tenants/add/*" - tenantsDetail = "/namespaces/:tenantNamespace/tenants/:tenantName" - tenantHop = "/namespaces/:tenantNamespace/tenants/:tenantName/hop" - podsDetail = "/namespaces/:tenantNamespace/tenants/:tenantName/pods/:podName" - tenantsDetailSummary = "/namespaces/:tenantNamespace/tenants/:tenantName/summary" - tenantsDetailMetrics = "/namespaces/:tenantNamespace/tenants/:tenantName/metrics" - tenantsDetailPods = "/namespaces/:tenantNamespace/tenants/:tenantName/pods" - tenantsDetailPools = "/namespaces/:tenantNamespace/tenants/:tenantName/pools" - tenantsDetailVolumes = "/namespaces/:tenantNamespace/tenants/:tenantName/volumes" - tenantsDetailLicense = "/namespaces/:tenantNamespace/tenants/:tenantName/license" - tenantsDetailSecurity = "/namespaces/:tenantNamespace/tenants/:tenantName/security" - storage = "/storage" - storageVolumes = "/storage/volumes" - storageDrives = "/storage/drives" - remoteBuckets = "/remote-buckets" - replication = "/replication" - license = "/license" - watch = "/tools/watch" - heal = "/tools/heal" - trace = "/tools/trace" - tools = "/tools" - logs = "/tools/logs" - auditLogs = "/tools/audit-logs" - speedtest = "/tools/speedtest" - healthInfo = "/tools/diagnostics" -) - -type ConfigurationActionSet struct { - actionTypes iampolicy.ActionSet - actions iampolicy.ActionSet -} - -// configurationActionSet contains the list of admin actions required for this endpoint to work -var configurationActionSet = ConfigurationActionSet{ - actionTypes: iampolicy.NewActionSet( - iampolicy.AllAdminActions, - ), - actions: iampolicy.NewActionSet( - iampolicy.ConfigUpdateAdminAction, - ), -} - -// dashboardActionSet contains the list of admin actions required for this endpoint to work -var dashboardActionSet = ConfigurationActionSet{ - actionTypes: iampolicy.NewActionSet( - iampolicy.AllAdminActions, - ), - actions: iampolicy.NewActionSet( - iampolicy.ServerInfoAdminAction, - ), -} - -// groupsActionSet contains the list of admin actions required for this endpoint to work -var groupsActionSet = ConfigurationActionSet{ - actionTypes: iampolicy.NewActionSet( - iampolicy.AllAdminActions, - ), - actions: iampolicy.NewActionSet( - iampolicy.ListGroupsAdminAction, - iampolicy.AddUserToGroupAdminAction, - //iampolicy.GetGroupAdminAction, - iampolicy.EnableGroupAdminAction, - iampolicy.DisableGroupAdminAction, - ), -} - -// iamPoliciesActionSet contains the list of admin actions required for this endpoint to work -var iamPoliciesActionSet = ConfigurationActionSet{ - actionTypes: iampolicy.NewActionSet( - iampolicy.AllAdminActions, - ), - actions: iampolicy.NewActionSet( - iampolicy.GetPolicyAdminAction, - iampolicy.DeletePolicyAdminAction, - iampolicy.CreatePolicyAdminAction, - iampolicy.AttachPolicyAdminAction, - iampolicy.ListUserPoliciesAdminAction, - ), -} - -// profilingActionSet contains the list of admin actions required for this endpoint to work -var profilingActionSet = ConfigurationActionSet{ - actionTypes: iampolicy.NewActionSet( - iampolicy.AllAdminActions, - ), - actions: iampolicy.NewActionSet( - iampolicy.ProfilingAdminAction, - ), -} - -// usersActionSet contains the list of admin actions required for this endpoint to work -var usersActionSet = ConfigurationActionSet{ - actionTypes: iampolicy.NewActionSet( - iampolicy.AllAdminActions, - ), - actions: iampolicy.NewActionSet( - iampolicy.ListUsersAdminAction, - iampolicy.CreateUserAdminAction, - iampolicy.DeleteUserAdminAction, - iampolicy.GetUserAdminAction, - iampolicy.EnableUserAdminAction, - iampolicy.DisableUserAdminAction, - ), -} - -// bucketsActionSet contains the list of admin actions required for this endpoint to work -var bucketsActionSet = ConfigurationActionSet{ - actionTypes: iampolicy.NewActionSet( - iampolicy.AllActions, - ), - actions: iampolicy.NewActionSet( - // Read access to buckets - iampolicy.ListMultipartUploadPartsAction, - iampolicy.ListBucketMultipartUploadsAction, - iampolicy.ListBucketAction, - iampolicy.HeadBucketAction, - iampolicy.GetObjectAction, - iampolicy.GetBucketLocationAction, - // Write access to buckets - iampolicy.AbortMultipartUploadAction, - iampolicy.CreateBucketAction, - iampolicy.PutObjectAction, - iampolicy.DeleteObjectAction, - iampolicy.DeleteBucketAction, - // Assign bucket policies - iampolicy.PutBucketPolicyAction, - iampolicy.DeleteBucketPolicyAction, - iampolicy.GetBucketPolicyAction, - ), -} - -// serviceAccountsActionSet no actions needed for this module to work -var serviceAccountsActionSet = ConfigurationActionSet{ - actionTypes: iampolicy.NewActionSet(), - actions: iampolicy.NewActionSet(), -} - -// changePasswordActionSet requires admin:CreateUser policy permission -var changePasswordActionSet = ConfigurationActionSet{ - actionTypes: iampolicy.NewActionSet(), - actions: iampolicy.NewActionSet(), -} - -// tenantsActionSet temporally no actions needed for tenants sections to work -var tenantsActionSet = ConfigurationActionSet{ - actionTypes: iampolicy.NewActionSet(), - actions: iampolicy.NewActionSet(), -} - -var storageActionSet = ConfigurationActionSet{ - actionTypes: iampolicy.NewActionSet(), - actions: iampolicy.NewActionSet(), -} - -var remoteBucketsActionSet = ConfigurationActionSet{ - actionTypes: iampolicy.NewActionSet( - iampolicy.AllAdminActions, - ), - actions: iampolicy.NewActionSet( - iampolicy.ConfigUpdateAdminAction, - ), -} - -var replicationActionSet = ConfigurationActionSet{ - actionTypes: iampolicy.NewActionSet( - iampolicy.AllAdminActions, - ), - actions: iampolicy.NewActionSet( - iampolicy.ConfigUpdateAdminAction, - ), -} - -// objectBrowserActionSet no actions needed for this module to work -var objectBrowserActionSet = ConfigurationActionSet{ - actionTypes: iampolicy.NewActionSet(), - actions: iampolicy.NewActionSet(), -} - -// licenseActionSet no actions needed for this module to work -var licenseActionSet = ConfigurationActionSet{ - actionTypes: iampolicy.NewActionSet(), - actions: iampolicy.NewActionSet(), -} - -// watchActionSet contains the list of admin actions required for this endpoint to work -var watchActionSet = ConfigurationActionSet{ - actionTypes: iampolicy.NewActionSet( - iampolicy.AllAdminActions, - ), - actions: iampolicy.NewActionSet( - iampolicy.ListenBucketNotificationAction, - ), -} - -// healActionSet contains the list of admin actions required for this endpoint to work -var healActionSet = ConfigurationActionSet{ - actionTypes: iampolicy.NewActionSet( - iampolicy.AllAdminActions, - ), - actions: iampolicy.NewActionSet( - iampolicy.HealAdminAction, - ), -} - -// logsActionSet contains the list of admin actions required for this endpoint to work -var logsActionSet = ConfigurationActionSet{ - actionTypes: iampolicy.NewActionSet( - iampolicy.AllAdminActions, - ), - actions: iampolicy.NewActionSet( - iampolicy.ConsoleLogAdminAction, - ), -} - -// toolsActionSet contains the list of admin actions required for this endpoint to work -var toolsActionSet = ConfigurationActionSet{ - actionTypes: iampolicy.NewActionSet( - iampolicy.AllAdminActions, - ), - actions: iampolicy.NewActionSet( - iampolicy.ConsoleLogAdminAction, - ), -} - -// traceActionSet contains the list of admin actions required for this endpoint to work -var traceActionSet = ConfigurationActionSet{ - actionTypes: iampolicy.NewActionSet( - iampolicy.AllAdminActions, - ), - actions: iampolicy.NewActionSet( - iampolicy.TraceAdminAction, - ), -} - -// healthInfoActionSet contains the list of admin actions required for this endpoint to work -var healthInfoActionSet = ConfigurationActionSet{ - actionTypes: iampolicy.NewActionSet( - iampolicy.AllAdminActions, - ), - actions: iampolicy.NewActionSet( - iampolicy.HealthInfoAdminAction, - ), -} - -// logsActionSet contains the list of admin actions required for this endpoint to work -var speedtestActionSet = ConfigurationActionSet{ - actionTypes: iampolicy.NewActionSet( - iampolicy.AllAdminActions, - ), - actions: iampolicy.NewActionSet( - iampolicy.HealthInfoAdminAction, - ), -} - -var displayRules = map[string]func() bool{ - // disable users page if LDAP is enabled - users: func() bool { - return !GetLDAPEnabled() - }, - // disable groups page if LDAP is enabled - groups: func() bool { - return !GetLDAPEnabled() - }, -} - -// endpointRules contains the mapping between endpoints and ActionSets, additional rules can be added here -var endpointRules = map[string]ConfigurationActionSet{ - configuration: configurationActionSet, - configurationItem: configurationActionSet, - notificationEndpoints: configurationActionSet, - notificationEndpointsAdd: configurationActionSet, - notificationEndpointsAddAny: configurationActionSet, - tiers: configurationActionSet, - tiersAdd: configurationActionSet, - tiersAddAny: configurationActionSet, - users: usersActionSet, - usersDetail: usersActionSet, - groups: groupsActionSet, - groupsDetails: groupsActionSet, - iamPolicies: iamPoliciesActionSet, - policiesDetail: iamPoliciesActionSet, - dashboard: dashboardActionSet, - metrics: dashboardActionSet, - profiling: profilingActionSet, - addBucket: bucketsActionSet, - buckets: bucketsActionSet, - bucketsGeneral: bucketsActionSet, - bucketsAdmin: bucketsActionSet, - bucketsAdminMain: bucketsActionSet, - serviceAccounts: serviceAccountsActionSet, - changePassword: changePasswordActionSet, - remoteBuckets: remoteBucketsActionSet, - replication: replicationActionSet, - bucketsBrowser: objectBrowserActionSet, - bucketsBrowserMenu: objectBrowserActionSet, - bucketsBrowserList: objectBrowserActionSet, - bucketsBrowserMain: objectBrowserActionSet, - license: licenseActionSet, - watch: watchActionSet, - heal: healActionSet, - trace: traceActionSet, - logs: logsActionSet, - auditLogs: logsActionSet, - tools: toolsActionSet, - healthInfo: healthInfoActionSet, - speedtest: speedtestActionSet, -} - -// operatorRules contains the mapping between endpoints and ActionSets for operator only mode -var operatorRules = map[string]ConfigurationActionSet{ - tenants: tenantsActionSet, - tenantsAdd: tenantsActionSet, - tenantsAddSub: tenantsActionSet, - tenantsDetail: tenantsActionSet, - tenantHop: tenantsActionSet, - tenantsDetailSummary: tenantsActionSet, - tenantsDetailMetrics: tenantsActionSet, - tenantsDetailPods: tenantsActionSet, - tenantsDetailPools: tenantsActionSet, - tenantsDetailVolumes: tenantsActionSet, - tenantsDetailLicense: tenantsActionSet, - tenantsDetailSecurity: tenantsActionSet, - podsDetail: tenantsActionSet, - storage: storageActionSet, - storageDrives: storageActionSet, - storageVolumes: storageActionSet, - license: licenseActionSet, -} - -// operatorOnly ENV variable -var operatorOnly = GetOperatorMode() - -// GetActionsStringFromPolicy extract the admin/s3 actions from a given policy and return them in []string format -// -// ie: -// { -// "Version": "2012-10-17", -// "Statement": [{ -// "Action": [ -// "admin:ServerInfo", -// "admin:CreatePolicy", -// "admin:GetUser" -// ], -// ... -// }, -// { -// "Action": [ -// "s3:ListenBucketNotification", -// "s3:PutBucketNotification" -// ], -// ... -// } -// ] -// } -// Will produce an array like: ["admin:ServerInfo", "admin:CreatePolicy", "admin:GetUser", "s3:ListenBucketNotification", "s3:PutBucketNotification"]\ -func GetActionsStringFromPolicy(policy *iampolicy.Policy) []string { - var actions []string - for _, statement := range policy.Statements { - // We only care about allowed actions - if statement.Effect.IsAllowed(true) { - for _, action := range statement.Actions.ToSlice() { - actions = append(actions, string(action)) - } - } - } - return actions -} - -// actionsStringToActionSet convert a given string array to iampolicy.ActionSet structure -// this avoids ending with duplicate actions -func actionsStringToActionSet(actions []string) iampolicy.ActionSet { - actionsSet := iampolicy.ActionSet{} - for _, action := range actions { - actionsSet.Add(iampolicy.Action(action)) - } - return actionsSet -} - -// GetAuthorizedEndpoints return a list of allowed endpoint based on a provided *iampolicy.Policy -// ie: pages the user should have access based on his current privileges -func GetAuthorizedEndpoints(actions []string) []string { - rangeTake := endpointRules - - if operatorOnly { - rangeTake = operatorRules - } - // Prepare new ActionSet structure that will hold all the user actions - userAllowedAction := actionsStringToActionSet(actions) - var allowedEndpoints []string - for endpoint, rules := range rangeTake { - - // check if display rule exists for this endpoint, this will control - // what user sees on the console UI - if rule, ok := displayRules[endpoint]; ok { - if rule != nil && !rule() { - continue - } - } - - // check if user policy matches s3:* or admin:* typesIntersection - endpointActionTypes := rules.actionTypes - typesIntersection := endpointActionTypes.Intersection(userAllowedAction) - if len(typesIntersection) == len(endpointActionTypes.ToSlice()) { - allowedEndpoints = append(allowedEndpoints, endpoint) - continue - } - // check if user policy matches explicitly defined endpoint required actions - endpointRequiredActions := rules.actions - actionsIntersection := endpointRequiredActions.Intersection(userAllowedAction) - if len(actionsIntersection) == len(endpointRequiredActions.ToSlice()) { - allowedEndpoints = append(allowedEndpoints, endpoint) - } - } - return allowedEndpoints -} diff --git a/pkg/acl/endpoints_test.go b/pkg/acl/endpoints_test.go deleted file mode 100644 index cab5b9c7f..000000000 --- a/pkg/acl/endpoints_test.go +++ /dev/null @@ -1,118 +0,0 @@ -// 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 acl - -import ( - "reflect" - "testing" -) - -type args struct { - actions []string -} - -type endpoint struct { - name string - args args - want int -} - -func validateEndpoints(t *testing.T, configs []endpoint) { - for _, tt := range configs { - t.Run(tt.name, func(t *testing.T) { - if got := GetAuthorizedEndpoints(tt.args.actions); !reflect.DeepEqual(len(got), tt.want) { - t.Errorf("GetAuthorizedEndpoints() = %v, want %v", len(got), tt.want) - } - }) - } -} - -func TestGetAuthorizedEndpoints(t *testing.T) { - tests := []endpoint{ - { - name: "dashboard endpoint", - args: args{ - []string{"admin:ServerInfo"}, - }, - want: 9, - }, - { - name: "policies endpoint", - args: args{ - []string{ - "admin:CreatePolicy", - "admin:DeletePolicy", - "admin:GetPolicy", - "admin:AttachUserOrGroupPolicy", - "admin:ListUserPolicies", - }, - }, - want: 9, - }, - { - name: "all admin endpoints", - args: args{ - []string{ - "admin:*", - }, - }, - want: 34, - }, - { - name: "all s3 endpoints", - args: args{ - []string{ - "s3:*", - }, - }, - want: 10, - }, - { - name: "all admin and s3 endpoints", - args: args{ - []string{ - "admin:*", - "s3:*", - }, - }, - want: 37, - }, - { - name: "Console User - default endpoints", - args: args{ - []string{}, - }, - want: 7, - }, - } - - validateEndpoints(t, tests) -} - -func TestOperatorOnlyEndpoints(t *testing.T) { - operatorOnly = true - - tests := []endpoint{ - { - name: "Operator Only - all admin endpoints", - args: args{}, - want: 17, - }, - } - - validateEndpoints(t, tests) -} diff --git a/pkg/apis/networking.gke.io/v1beta1/zz_generated.deepcopy.go b/pkg/apis/networking.gke.io/v1beta1/zz_generated.deepcopy.go index 0f6544922..4c28cd7da 100644 --- a/pkg/apis/networking.gke.io/v1beta1/zz_generated.deepcopy.go +++ b/pkg/apis/networking.gke.io/v1beta1/zz_generated.deepcopy.go @@ -1,3 +1,4 @@ +//go:build !ignore_autogenerated // +build !ignore_autogenerated /* diff --git a/pkg/apis/networking.gke.io/v1beta2/zz_generated.deepcopy.go b/pkg/apis/networking.gke.io/v1beta2/zz_generated.deepcopy.go index 63a45b29c..ea5676623 100644 --- a/pkg/apis/networking.gke.io/v1beta2/zz_generated.deepcopy.go +++ b/pkg/apis/networking.gke.io/v1beta2/zz_generated.deepcopy.go @@ -1,3 +1,4 @@ +//go:build !ignore_autogenerated // +build !ignore_autogenerated /* diff --git a/portal-ui/public/index.html b/portal-ui/public/index.html index 564a2d7d5..05ca70533 100644 --- a/portal-ui/public/index.html +++ b/portal-ui/public/index.html @@ -66,10 +66,7 @@
{ if (!userPermissionsOnBucket) { @@ -44,10 +44,9 @@ export const hasAccessToResource = ( }; export const IAM_ROLES = { - viewer: "VIEWER", - editor: "EDITOR", - owner: "OWNER", - admin: "ADMIN", + BUCKET_OWNER: "BUCKET_OWNER", // upload/delete objects from the bucket + BUCKET_VIEWER: "BUCKET_VIEWER", // only view objects on the bucket + BUCKET_ADMIN: "BUCKET_ADMIN", // administrate the bucket }; export const IAM_SCOPES = { @@ -84,11 +83,17 @@ export const IAM_SCOPES = { "s3:PutBucketObjectLockConfiguration", ADMIN_GET_POLICY: "admin:GetPolicy", ADMIN_LIST_USERS: "admin:ListUsers", + ADMIN_CREATE_USER: "admin:CreateUser", + ADMIN_DELETE_USER: "admin:DeleteUser", + ADMIN_ENABLE_USER: "admin:EnableUser", + ADMIN_DISABLE_USER: "admin:DisableUser", + ADMIN_GET_USER: "admin:GetUser", ADMIN_LIST_USER_POLICIES: "admin:ListUserPolicies", ADMIN_SERVER_INFO: "admin:ServerInfo", ADMIN_GET_BUCKET_QUOTA: "admin:GetBucketQuota", ADMIN_SET_BUCKET_QUOTA: "admin:SetBucketQuota", ADMIN_LIST_TIERS: "admin:ListTier", + ADMIN_SET_TIER: "admin:SetTier", ADMIN_LIST_GROUPS: "admin:ListGroups", S3_GET_OBJECT_VERSION_FOR_REPLICATION: "s3:GetObjectVersionForReplication", S3_REPLICATE_TAGS: "s3:ReplicateTags", @@ -117,20 +122,87 @@ export const IAM_SCOPES = { ADMIN_GET_GROUP: "admin:GetGroup", ADMIN_ENABLE_GROUP: "admin:EnableGroup", ADMIN_DISABLE_GROUP: "admin:DisableGroup", - ADMIN_GET_USER: "admin:GetUser", ADMIN_CREATE_POLICY: "admin:CreatePolicy", ADMIN_DELETE_POLICY: "admin:DeletePolicy", ADMIN_ATTACH_USER_OR_GROUP_POLICY: "admin:AttachUserOrGroupPolicy", - ADMIN_HEAL_ACTION: "admin:Heal", - ADMIN_HEALTH_ACTION: "admin:OBDInfo", - ADMIN_CONSOLE_LOG_ACTION: "admin:ConsoleLog", - ADMIN_TRACE_ACTION: "admin:ServerTrace", + ADMIN_CREATE_SERVICEACCOUNT: "admin:CreateServiceAccount", + ADMIN_UPDATE_SERVICEACCOUNT: "admin:UpdateServiceAccount", + ADMIN_REMOVE_SERVICEACCOUNT: "admin:RemoveServiceAccount", + ADMIN_LIST_SERVICEACCOUNTS: "admin:ListServiceAccounts", + ADMIN_CONFIG_UPDATE: "admin:ConfigUpdate", + ADMIN_GET_CONSOLE_LOG: "admin:ConsoleLog", + ADMIN_SERVER_TRACE: "admin:ServerTrace", + ADMIN_HEALTH_INFO: "admin:OBDInfo", + ADMIN_HEAL: "admin:Heal", S3_ALL_ACTIONS: "s3:*", ADMIN_ALL_ACTIONS: "admin:*", }; +export const IAM_PAGES = { + POLICIES: "/policies", + POLICIES_VIEW: "/policies/*", + DASHBOARD: "/dashboard", + METRICS: "/metrics", + ADD_BUCKETS: "/add-bucket", + BUCKETS: "/buckets", + BUCKETS_ADMIN_VIEW: "/buckets/:bucketName/admin*", + BUCKETS_BROWSE_VIEW: "/buckets/:bucketName/browse*", + TOOLS_WATCH: "/tools/watch", + TOOLS_SPEEDTEST: "/tools/speedtest", + USERS: "/users", + USERS_VIEW: "/users/:userName+", + GROUPS: "/groups", + GROUPS_VIEW: "/groups/:groupName+", + TOOLS_HEAL: "/tools/heal", + TOOLS_TRACE: "/tools/trace", + TOOLS_DIAGNOSTICS: "/tools/diagnostics", + TOOLS_LOGS: "/tools/logs", + TOOLS_AUDITLOGS: "/tools/audit-logs", + TOOLS: "/tools", + SETTINGS: "/settings", + SETTINGS_VIEW: "/settings/:option", + NOTIFICATIONS_ENDPOINTS_ADD: "/notification-endpoints/add", + NOTIFICATIONS_ENDPOINTS_ADD_SERVICE: "/notification-endpoints/add/:service", + NOTIFICATIONS_ENDPOINTS: "/notification-endpoints", + TIERS: "/tiers", + TIERS_ADD: "/tiers/add", + TIERS_ADD_SERVICE: "/tiers/add/:service", + ACCOUNT: "/account", + TENANTS: "/tenants", + TENANTS_ADD: "/tenants/add", + STORAGE: "/storage", + STORAGE_VOLUMES: "/storage/volumes", + STORAGE_DRIVES: "/storage/drives", + NAMESPACE_TENANT: "/namespaces/:tenantNamespace/tenants/:tenantName", + NAMESPACE_TENANT_HOP: "/namespaces/:tenantNamespace/tenants/:tenantName/hop", + NAMESPACE_TENANT_PODS: + "/namespaces/:tenantNamespace/tenants/:tenantName/pods/:podName", + NAMESPACE_TENANT_PODS_LIST: + "/namespaces/:tenantNamespace/tenants/:tenantName/pods", + NAMESPACE_TENANT_SUMMARY: + "/namespaces/:tenantNamespace/tenants/:tenantName/summary", + NAMESPACE_TENANT_METRICS: + "/namespaces/:tenantNamespace/tenants/:tenantName/metrics", + NAMESPACE_TENANT_POOLS: + "/namespaces/:tenantNamespace/tenants/:tenantName/pools", + NAMESPACE_TENANT_VOLUMES: + "/namespaces/:tenantNamespace/tenants/:tenantName/volumes", + NAMESPACE_TENANT_LICENSE: + "/namespaces/:tenantNamespace/tenants/:tenantName/license", + NAMESPACE_TENANT_SECURITY: + "/namespaces/:tenantNamespace/tenants/:tenantName/security", + LICENSE: "/license", + DOCUMENTATION: "/documentation", +}; + +// roles export const IAM_PERMISSIONS = { - [IAM_ROLES.admin]: [ + [IAM_ROLES.BUCKET_OWNER]: [ + IAM_SCOPES.S3_PUT_OBJECT, + IAM_SCOPES.S3_DELETE_OBJECT, + ], + [IAM_ROLES.BUCKET_VIEWER]: [IAM_SCOPES.S3_LIST_BUCKET], + [IAM_ROLES.BUCKET_ADMIN]: [ IAM_SCOPES.S3_ALL_ACTIONS, IAM_SCOPES.ADMIN_ALL_ACTIONS, IAM_SCOPES.S3_REPLICATE_OBJECT, @@ -183,9 +255,111 @@ export const IAM_PERMISSIONS = { IAM_SCOPES.ADMIN_GET_POLICY, IAM_SCOPES.ADMIN_LIST_USER_POLICIES, IAM_SCOPES.ADMIN_LIST_USERS, - IAM_SCOPES.ADMIN_HEAL_ACTION, + IAM_SCOPES.ADMIN_HEAL, ], }; +// application pages/routes and required scopes/roles +export const IAM_PAGES_PERMISSIONS = { + [IAM_PAGES.ADD_BUCKETS]: [ + IAM_SCOPES.S3_CREATE_BUCKET, // create bucket page + ], + [IAM_PAGES.BUCKETS_ADMIN_VIEW]: [ + ...IAM_PERMISSIONS[IAM_ROLES.BUCKET_ADMIN], // bucket admin page + ], + [IAM_PAGES.BUCKETS_BROWSE_VIEW]: [ + ...IAM_PERMISSIONS[IAM_ROLES.BUCKET_OWNER], + ...IAM_PERMISSIONS[IAM_ROLES.BUCKET_VIEWER], + ], + [IAM_PAGES.GROUPS]: [ + IAM_SCOPES.ADMIN_LIST_GROUPS, // displays groups + IAM_SCOPES.ADMIN_ADD_USER_TO_GROUP, // displays create group button + ], + [IAM_PAGES.GROUPS_VIEW]: [ + IAM_SCOPES.ADMIN_GET_GROUP, + IAM_SCOPES.ADMIN_DISABLE_GROUP, + IAM_SCOPES.ADMIN_ENABLE_GROUP, + IAM_SCOPES.ADMIN_REMOVE_USER_FROM_GROUP, + IAM_SCOPES.ADMIN_LIST_USER_POLICIES, + IAM_SCOPES.ADMIN_ADD_USER_TO_GROUP, // display "edit members" button in groups detail page + IAM_SCOPES.ADMIN_ATTACH_USER_OR_GROUP_POLICY, // display "set policy" button in groups details page + ], + [IAM_PAGES.USERS]: [ + IAM_SCOPES.ADMIN_LIST_USERS, // displays users + IAM_SCOPES.ADMIN_CREATE_USER, // displays create user button + ], + [IAM_PAGES.USERS_VIEW]: [ + IAM_SCOPES.ADMIN_GET_USER, // displays list of users + IAM_SCOPES.ADMIN_ADD_USER_TO_GROUP, // displays "add to gorups" button in user details page + IAM_SCOPES.ADMIN_ENABLE_USER, + IAM_SCOPES.ADMIN_DISABLE_USER, + IAM_SCOPES.ADMIN_DELETE_USER, + ], + [IAM_PAGES.DASHBOARD]: [ + IAM_SCOPES.ADMIN_SERVER_INFO, // displays dashboard information + ], + [IAM_PAGES.POLICIES_VIEW]: [ + IAM_SCOPES.ADMIN_DELETE_POLICY, + IAM_SCOPES.ADMIN_LIST_GROUPS, + IAM_SCOPES.ADMIN_GET_GROUP, + IAM_SCOPES.ADMIN_GET_POLICY, + IAM_SCOPES.ADMIN_CREATE_POLICY, + ], + [IAM_PAGES.POLICIES]: [ + IAM_SCOPES.ADMIN_LIST_USER_POLICIES, // displays policies + IAM_SCOPES.ADMIN_CREATE_POLICY, // displays create policy button + ], + [IAM_PAGES.SETTINGS]: [ + IAM_SCOPES.ADMIN_CONFIG_UPDATE, // displays configuration list + ], + [IAM_PAGES.SETTINGS_VIEW]: [ + IAM_SCOPES.ADMIN_CONFIG_UPDATE, // displays configuration list + ], + [IAM_PAGES.NOTIFICATIONS_ENDPOINTS_ADD_SERVICE]: [ + IAM_SCOPES.ADMIN_SERVER_INFO, + IAM_SCOPES.ADMIN_CONFIG_UPDATE, + ], + [IAM_PAGES.NOTIFICATIONS_ENDPOINTS_ADD]: [ + IAM_SCOPES.ADMIN_SERVER_INFO, + IAM_SCOPES.ADMIN_CONFIG_UPDATE, + ], + [IAM_PAGES.NOTIFICATIONS_ENDPOINTS]: [ + IAM_SCOPES.ADMIN_SERVER_INFO, // displays notifications endpoints + IAM_SCOPES.ADMIN_CONFIG_UPDATE, // displays create notification button + ], + [IAM_PAGES.TIERS]: [ + IAM_SCOPES.ADMIN_LIST_TIERS, // display tiers list + IAM_SCOPES.ADMIN_SET_TIER, // display "add tier" button + ], + [IAM_PAGES.TIERS_ADD]: [ + IAM_SCOPES.ADMIN_SET_TIER, // display "add tier" button / shows add service tier page + ], + [IAM_PAGES.TIERS_ADD_SERVICE]: [ + IAM_SCOPES.ADMIN_SET_TIER, // display "add tier" button / shows add service tier page + ], + [IAM_PAGES.TOOLS]: [ + IAM_SCOPES.S3_LISTEN_NOTIFICATIONS, // displays watch notifications + IAM_SCOPES.S3_LISTEN_BUCKET_NOTIFICATIONS, // display watch notifications + IAM_SCOPES.ADMIN_GET_CONSOLE_LOG, // display minio console logs + IAM_SCOPES.ADMIN_SERVER_TRACE, // display minio trace + IAM_SCOPES.ADMIN_HEAL, // display heal + IAM_SCOPES.ADMIN_HEALTH_INFO, // display diagnostics / display speedtest / display audit log + IAM_SCOPES.ADMIN_SERVER_INFO, // display diagnostics + ], + [IAM_PAGES.TOOLS_LOGS]: [IAM_SCOPES.ADMIN_GET_CONSOLE_LOG], + [IAM_PAGES.TOOLS_AUDITLOGS]: [IAM_SCOPES.ADMIN_HEALTH_INFO], + [IAM_PAGES.TOOLS_WATCH]: [ + IAM_SCOPES.S3_LISTEN_NOTIFICATIONS, // displays watch notifications + IAM_SCOPES.S3_LISTEN_BUCKET_NOTIFICATIONS, // display watch notifications + ], + [IAM_PAGES.TOOLS_TRACE]: [IAM_SCOPES.ADMIN_SERVER_TRACE], + [IAM_PAGES.TOOLS_HEAL]: [IAM_SCOPES.ADMIN_HEAL], + [IAM_PAGES.TOOLS_DIAGNOSTICS]: [ + IAM_SCOPES.ADMIN_HEALTH_INFO, + IAM_SCOPES.ADMIN_SERVER_INFO, + ], + [IAM_PAGES.TOOLS_SPEEDTEST]: [IAM_SCOPES.ADMIN_HEALTH_INFO], +}; + export const S3_ALL_RESOURCES = "arn:aws:s3:::*"; export const CONSOLE_UI_RESOURCE = "console-ui"; diff --git a/portal-ui/src/screens/Console/Account/Account.tsx b/portal-ui/src/screens/Console/Account/Account.tsx index eba0c5fb1..6f6c30c20 100644 --- a/portal-ui/src/screens/Console/Account/Account.tsx +++ b/portal-ui/src/screens/Console/Account/Account.tsx @@ -41,6 +41,11 @@ import HelpBox from "../../../common/HelpBox"; import PageLayout from "../Common/Layout/PageLayout"; import SearchBox from "../Common/SearchBox"; import withSuspense from "../Common/Components/withSuspense"; +import { + CONSOLE_UI_RESOURCE, + IAM_SCOPES, +} from "../../../common/SecureComponent/permissions"; +import SecureComponent from "../../../common/SecureComponent/SecureComponent"; const AddServiceAccount = withSuspense( React.lazy(() => import("./AddServiceAccount")) @@ -68,14 +73,9 @@ const styles = (theme: Theme) => interface IServiceAccountsProps { classes: any; displayErrorMessage: typeof setErrorSnackMessage; - changePassword: boolean; } -const Account = ({ - classes, - displayErrorMessage, - changePassword, -}: IServiceAccountsProps) => { +const Account = ({ classes, displayErrorMessage }: IServiceAccountsProps) => { const [records, setRecords] = useState([]); const [loading, setLoading] = useState(false); const [filter, setFilter] = useState(""); @@ -193,21 +193,24 @@ const Account = ({ - {changePassword && ( - - setChangePasswordModalOpen(true)} - size="large" - > - - - - )} - + + + setChangePasswordModalOpen(true)} + size="large" + > + + + + } /> diff --git a/portal-ui/src/screens/Console/Buckets/BucketDetails/AccessDetailsPanel.tsx b/portal-ui/src/screens/Console/Buckets/BucketDetails/AccessDetailsPanel.tsx index 9d0d2da6e..53492352a 100644 --- a/portal-ui/src/screens/Console/Buckets/BucketDetails/AccessDetailsPanel.tsx +++ b/portal-ui/src/screens/Console/Buckets/BucketDetails/AccessDetailsPanel.tsx @@ -64,12 +64,9 @@ interface IAccessDetailsProps { } const AccessDetails = ({ - classes, match, setErrorSnackMessage, - session, loadingBucket, - bucketInfo, }: IAccessDetailsProps) => { const [curTab, setCurTab] = useState(0); const [loadingPolicies, setLoadingPolicies] = useState(true); @@ -189,6 +186,7 @@ const AccessDetails = ({ } /> @@ -1137,6 +1161,7 @@ const ListObjects = ({
+ Please configure{" "} + + {entity} + {" "} + first to use this feature. + + } + /> + + + ); +}; + +export default MissingIntegration; diff --git a/portal-ui/src/screens/Console/Configurations/TiersConfiguration/ListTiersConfiguration.tsx b/portal-ui/src/screens/Console/Configurations/TiersConfiguration/ListTiersConfiguration.tsx index 8f647f26a..b1a8d39d4 100644 --- a/portal-ui/src/screens/Console/Configurations/TiersConfiguration/ListTiersConfiguration.tsx +++ b/portal-ui/src/screens/Console/Configurations/TiersConfiguration/ListTiersConfiguration.tsx @@ -47,6 +47,13 @@ import PageLayout from "../../Common/Layout/PageLayout"; import SearchBox from "../../Common/SearchBox"; import withSuspense from "../../Common/Components/withSuspense"; +import { AppState } from "../../../../store"; +import DistributedOnly from "../../Common/DistributedOnly/DistributedOnly"; +import { + CONSOLE_UI_RESOURCE, + IAM_SCOPES, +} from "../../../../common/SecureComponent/permissions"; +import SecureComponent from "../../../../common/SecureComponent/SecureComponent"; const UpdateTierCredentialsModal = withSuspense( React.lazy(() => import("./UpdateTierCredentialsModal")) @@ -56,6 +63,7 @@ interface IListTiersConfig { classes: any; history: any; setErrorSnackMessage: typeof setErrorSnackMessage; + distributedSetup: boolean; } const styles = (theme: Theme) => @@ -90,6 +98,7 @@ const ListTiersConfiguration = ({ classes, history, setErrorSnackMessage, + distributedSetup, }: IListTiersConfig) => { const [records, setRecords] = useState([]); const [filter, setFilter] = useState(""); @@ -102,21 +111,25 @@ const ListTiersConfiguration = ({ useEffect(() => { if (isLoading) { - const fetchRecords = () => { - api - .invoke("GET", `/api/v1/admin/tiers`) - .then((res: ITierResponse) => { - setRecords(res.items || []); - setIsLoading(false); - }) - .catch((err: ErrorResponseHandler) => { - setErrorSnackMessage(err); - setIsLoading(false); - }); - }; - fetchRecords(); + if (distributedSetup) { + const fetchRecords = () => { + api + .invoke("GET", `/api/v1/admin/tiers`) + .then((res: ITierResponse) => { + setRecords(res.items || []); + setIsLoading(false); + }) + .catch((err: ErrorResponseHandler) => { + setErrorSnackMessage(err); + setIsLoading(false); + }); + }; + fetchRecords(); + } else { + setIsLoading(false); + } } - }, [isLoading, setErrorSnackMessage]); + }, [isLoading, setErrorSnackMessage, distributedSetup]); const filteredRecords = records.filter((b: ITierElement) => { if (filter === "") { @@ -197,150 +210,169 @@ const ListTiersConfiguration = ({ )} - - - -
- { - setIsLoading(true); - }} - size="large" - > - - - -
-
- {isLoading && } - {!isLoading && ( + {!distributedSetup ? ( + } /> + ) : ( - {records.length > 0 && ( + + +
+ { + setIsLoading(true); + }} + size="large" + > + + + + + +
+
+ {isLoading && } + {!isLoading && ( - - { - setSelectedTier(tierData); - setUpdateCredentialsOpen(true); - }, - }, - ]} - columns={[ - { - label: "Tier Name", - elementKey: "type", - renderFunction: renderTierName, - renderFullObject: true, - }, - { - label: "Type", - elementKey: "type", - width: 150, - }, - { - label: "Endpoint", - elementKey: "type", - renderFunction: renderTierEndpoint, - renderFullObject: true, - }, - { - label: "Bucket", - elementKey: "type", - renderFunction: renderTierBucket, - renderFullObject: true, - }, - { - label: "Prefix", - elementKey: "type", - renderFunction: renderTierPrefix, - renderFullObject: true, - }, - { - label: "Region", - elementKey: "type", - renderFunction: renderTierRegion, - renderFullObject: true, - }, - ]} - isLoading={isLoading} - records={filteredRecords} - entityName="Tiers" - idField="service_name" - customPaperHeight={classes.customConfigurationPage} - /> - - - } - help={ - - Tiers are used by the MinIO Object Lifecycle Management - which allows creating rules for time or date based - automatic transition or expiry of objects. For object - transition, MinIO automatically moves the object to a - configured remote storage tier. -
-
- You can learn more at our{" "} - - documentation - - . -
- } - /> -
+ {records.length > 0 && ( + + + + { + setSelectedTier(tierData); + setUpdateCredentialsOpen(true); + }, + }, + ]} + columns={[ + { + label: "Tier Name", + elementKey: "type", + renderFunction: renderTierName, + renderFullObject: true, + }, + { + label: "Type", + elementKey: "type", + width: 150, + }, + { + label: "Endpoint", + elementKey: "type", + renderFunction: renderTierEndpoint, + renderFullObject: true, + }, + { + label: "Bucket", + elementKey: "type", + renderFunction: renderTierBucket, + renderFullObject: true, + }, + { + label: "Prefix", + elementKey: "type", + renderFunction: renderTierPrefix, + renderFullObject: true, + }, + { + label: "Region", + elementKey: "type", + renderFunction: renderTierRegion, + renderFullObject: true, + }, + ]} + isLoading={isLoading} + records={filteredRecords} + entityName="Tiers" + idField="service_name" + customPaperHeight={classes.customConfigurationPage} + /> + + + + } + help={ + + Tiers are used by the MinIO Object Lifecycle + Management which allows creating rules for time or + date based automatic transition or expiry of + objects. For object transition, MinIO automatically + moves the object to a configured remote storage + tier. +
+
+ You can learn more at our{" "} + + documentation + + . +
+ } + /> +
+
+ )} + {records.length === 0 && ( + + + } + help={ + + Tiers are used by the MinIO Object Lifecycle + Management which allows creating rules for time or + date based automatic transition or expiry of + objects. For object transition, MinIO automatically + moves the object to a configured remote storage + tier. +
+
+ To get started,{" "} + Add A Tier. +
+ } + /> +
+
+ )}
)} - {records.length === 0 && ( - - - } - help={ - - Tiers are used by the MinIO Object Lifecycle Management - which allows creating rules for time or date based - automatic transition or expiry of objects. For object - transition, MinIO automatically moves the object to a - configured remote storage tier. -
-
- To get started,{" "} - Add A Tier. -
- } - /> -
-
- )}
)}
@@ -348,10 +380,14 @@ const ListTiersConfiguration = ({ ); }; +const mapState = (state: AppState) => ({ + distributedSetup: state.system.distributedSetup, +}); + const mapDispatchToProps = { setErrorSnackMessage, }; -const connector = connect(null, mapDispatchToProps); +const connector = connect(mapState, mapDispatchToProps); export default withStyles(styles)(connector(ListTiersConfiguration)); diff --git a/portal-ui/src/screens/Console/Configurations/types.ts b/portal-ui/src/screens/Console/Configurations/types.ts index b035fef61..2214a37d0 100644 --- a/portal-ui/src/screens/Console/Configurations/types.ts +++ b/portal-ui/src/screens/Console/Configurations/types.ts @@ -58,4 +58,5 @@ export interface IElement { configuration_id: string; configuration_label: string; icon?: any; + disabled?: boolean; } diff --git a/portal-ui/src/screens/Console/Console.tsx b/portal-ui/src/screens/Console/Console.tsx index c749d5bec..1d62954be 100644 --- a/portal-ui/src/screens/Console/Console.tsx +++ b/portal-ui/src/screens/Console/Console.tsx @@ -40,6 +40,15 @@ import Menu from "./Menu/Menu"; import api from "../../common/api"; import MainError from "./Common/MainError/MainError"; +import { + CONSOLE_UI_RESOURCE, + IAM_PAGES, + IAM_PAGES_PERMISSIONS, + IAM_SCOPES, + S3_ALL_RESOURCES, +} from "../../common/SecureComponent/permissions"; +import { hasPermission } from "../../common/SecureComponent/SecureComponent"; +import { IRouteRule } from "./Menu/types"; const Trace = React.lazy(() => import("./Trace/Trace")); const Heal = React.lazy(() => import("./Heal/Heal")); @@ -149,6 +158,9 @@ interface IConsoleProps { loadingProgress: number; snackBarMessage: snackBarMessage; setSnackBarMessage: typeof setSnackBarMessage; + operatorMode: boolean; + distributedSetup: boolean; + features: string[] | null; } const Console = ({ @@ -162,9 +174,13 @@ const Console = ({ loadingProgress, snackBarMessage, setSnackBarMessage, + operatorMode, + distributedSetup, + features, }: IConsoleProps) => { const [openSnackbar, setOpenSnackbar] = useState(false); + const ldapIsEnabled = (features && features.includes("ldap-idp")) || false; const restartServer = () => { serverIsLoading(true); api @@ -185,199 +201,259 @@ const Console = ({ }); }; - const allowedPages = !session - ? [] - : session.pages.reduce((result: any, item: any, index: any) => { - if (item.startsWith("/tools")) { - result["/tools"] = true; - } - result[item] = true; - return result; - }, {}); - const routes = [ + const consoleAdminRoutes: IRouteRule[] = [ { component: Dashboard, - path: "/dashboard", + path: IAM_PAGES.DASHBOARD, }, { component: Metrics, - path: "/metrics", + path: IAM_PAGES.METRICS, }, { component: Buckets, - path: "/add-bucket", + path: IAM_PAGES.ADD_BUCKETS, }, { component: Buckets, - path: "/buckets", + path: IAM_PAGES.BUCKETS, + forceDisplay: true, }, { component: Buckets, - path: "/buckets/*", - }, - { - component: Watch, - path: "/tools/watch", - }, - { - component: Speedtest, - path: "/tools/speedtest", - }, - { - component: Users, - path: "/users/:userName+", - }, - { - component: Users, - path: "/users", - }, - { - component: Groups, - path: "/groups", - }, - { - component: GroupsDetails, - path: "/groups/:groupName+", - }, - { - component: Policies, - path: "/policies/*", - }, - { - component: Policies, - path: "/policies", - }, - { - component: Heal, - path: "/tools/heal", - }, - { - component: Trace, - path: "/tools/trace", - }, - { - component: HealthInfo, - path: "/tools/diagnostics", - }, - { - component: ErrorLogs, - path: "/tools/logs", - }, - { - component: LogsSearchMain, - path: "/tools/audit-logs", - }, - { - component: Tools, - path: "/tools", - }, - { - component: ConfigurationOptions, - path: "/settings", - }, - { - component: ConfigurationOptions, - path: "/settings/:option", - }, - { - component: AddNotificationEndpoint, - path: "/notification-endpoints/add/:service", - }, - { - component: NotificationTypeSelector, - path: "/notification-endpoints/add", - }, - { - component: NotificationEndpoints, - path: "/notification-endpoints", - }, - { - component: AddTierConfiguration, - path: "/tiers/add/:service", - }, - { - component: TierTypeSelector, - path: "/tiers/add", - }, - { - component: ListTiersConfiguration, - path: "/tiers", - }, - { - component: Account, - path: "/account", - props: { - changePassword: (!session ? [] : session.pages).includes( - "/account/change-password" - ), + path: IAM_PAGES.BUCKETS_ADMIN_VIEW, + customPermissionFnc: () => { + const path = window.location.pathname; + const resource = path.match(/buckets\/(.*)\/admin*/); + return ( + resource && + resource.length > 0 && + hasPermission( + resource[1], + IAM_PAGES_PERMISSIONS[IAM_PAGES.BUCKETS_ADMIN_VIEW] + ) + ); }, }, { - component: ListTenants, - path: "/tenants", + component: Buckets, + path: IAM_PAGES.BUCKETS_BROWSE_VIEW, + customPermissionFnc: () => { + const path = window.location.pathname; + const resource = path.match(/buckets\/(.*)\/browse*/); + return ( + resource && + resource.length > 0 && + hasPermission( + resource[1], + IAM_PAGES_PERMISSIONS[IAM_PAGES.BUCKETS_BROWSE_VIEW] + ) + ); + }, }, { - component: AddTenant, - path: "/tenants/add", + component: Watch, + path: IAM_PAGES.TOOLS_WATCH, }, { - component: Storage, - path: "/storage", + component: Speedtest, + path: IAM_PAGES.TOOLS_SPEEDTEST, }, { - component: Storage, - path: "/storage/volumes", + component: Users, + path: IAM_PAGES.USERS_VIEW, }, { - component: Storage, - path: "/storage/drives", + component: Users, + path: IAM_PAGES.USERS, + fsHidden: ldapIsEnabled, + customPermissionFnc: () => + hasPermission(CONSOLE_UI_RESOURCE, [IAM_SCOPES.ADMIN_LIST_USERS]) || + hasPermission(S3_ALL_RESOURCES, [IAM_SCOPES.ADMIN_CREATE_USER]), }, { - component: TenantDetails, - path: "/namespaces/:tenantNamespace/tenants/:tenantName", + component: Groups, + path: IAM_PAGES.GROUPS, + fsHidden: ldapIsEnabled, }, { - component: Hop, - path: "/namespaces/:tenantNamespace/tenants/:tenantName/hop", + component: GroupsDetails, + path: IAM_PAGES.GROUPS_VIEW, }, { - component: TenantDetails, - path: "/namespaces/:tenantNamespace/tenants/:tenantName/pods/:podName", + component: Policies, + path: IAM_PAGES.POLICIES_VIEW, }, { - component: TenantDetails, - path: "/namespaces/:tenantNamespace/tenants/:tenantName/summary", + component: Policies, + path: IAM_PAGES.POLICIES, }, { - component: TenantDetails, - path: "/namespaces/:tenantNamespace/tenants/:tenantName/metrics", + component: Heal, + path: IAM_PAGES.TOOLS_HEAL, }, { - component: TenantDetails, - path: "/namespaces/:tenantNamespace/tenants/:tenantName/pods", + component: Trace, + path: IAM_PAGES.TOOLS_TRACE, }, { - component: TenantDetails, - path: "/namespaces/:tenantNamespace/tenants/:tenantName/pools", + component: HealthInfo, + path: IAM_PAGES.TOOLS_DIAGNOSTICS, }, { - component: TenantDetails, - path: "/namespaces/:tenantNamespace/tenants/:tenantName/volumes", + component: ErrorLogs, + path: IAM_PAGES.TOOLS_LOGS, }, { - component: TenantDetails, - path: "/namespaces/:tenantNamespace/tenants/:tenantName/license", + component: LogsSearchMain, + path: IAM_PAGES.TOOLS_AUDITLOGS, }, { - component: TenantDetails, - path: "/namespaces/:tenantNamespace/tenants/:tenantName/security", + component: Tools, + path: IAM_PAGES.TOOLS, + }, + { + component: ConfigurationOptions, + path: IAM_PAGES.SETTINGS, + }, + { + component: ConfigurationOptions, + path: IAM_PAGES.SETTINGS_VIEW, + }, + { + component: AddNotificationEndpoint, + path: IAM_PAGES.NOTIFICATIONS_ENDPOINTS_ADD_SERVICE, + }, + { + component: NotificationTypeSelector, + path: IAM_PAGES.NOTIFICATIONS_ENDPOINTS_ADD, + }, + { + component: NotificationEndpoints, + path: IAM_PAGES.NOTIFICATIONS_ENDPOINTS, + }, + { + component: AddTierConfiguration, + path: IAM_PAGES.TIERS_ADD_SERVICE, + fsHidden: !distributedSetup, + }, + { + component: TierTypeSelector, + path: IAM_PAGES.TIERS_ADD, + fsHidden: !distributedSetup, + }, + { + component: ListTiersConfiguration, + path: IAM_PAGES.TIERS, + }, + { + component: Account, + path: IAM_PAGES.ACCOUNT, + forceDisplay: true, // user has implicit access to service-accounts }, { component: License, - path: "/license", + path: IAM_PAGES.LICENSE, + forceDisplay: true, }, ]; - const allowedRoutes = routes.filter((route: any) => allowedPages[route.path]); + + const operatorConsoleRoutes: IRouteRule[] = [ + { + component: ListTenants, + path: IAM_PAGES.TENANTS, + forceDisplay: true, + }, + { + component: AddTenant, + path: IAM_PAGES.TENANTS_ADD, + forceDisplay: true, + }, + { + component: Storage, + path: IAM_PAGES.STORAGE, + forceDisplay: true, + }, + { + component: Storage, + path: IAM_PAGES.STORAGE_VOLUMES, + forceDisplay: true, + }, + { + component: Storage, + path: IAM_PAGES.STORAGE_DRIVES, + forceDisplay: true, + }, + { + component: TenantDetails, + path: IAM_PAGES.NAMESPACE_TENANT, + forceDisplay: true, + }, + { + component: Hop, + path: IAM_PAGES.NAMESPACE_TENANT_HOP, + forceDisplay: true, + }, + { + component: TenantDetails, + path: IAM_PAGES.NAMESPACE_TENANT_PODS, + forceDisplay: true, + }, + { + component: TenantDetails, + path: IAM_PAGES.NAMESPACE_TENANT_SUMMARY, + forceDisplay: true, + }, + { + component: TenantDetails, + path: IAM_PAGES.NAMESPACE_TENANT_METRICS, + forceDisplay: true, + }, + { + component: TenantDetails, + path: IAM_PAGES.NAMESPACE_TENANT_PODS_LIST, + forceDisplay: true, + }, + { + component: TenantDetails, + path: IAM_PAGES.NAMESPACE_TENANT_POOLS, + forceDisplay: true, + }, + { + component: TenantDetails, + path: IAM_PAGES.NAMESPACE_TENANT_VOLUMES, + forceDisplay: true, + }, + { + component: TenantDetails, + path: IAM_PAGES.NAMESPACE_TENANT_LICENSE, + forceDisplay: true, + }, + { + component: TenantDetails, + path: IAM_PAGES.NAMESPACE_TENANT_SECURITY, + forceDisplay: true, + }, + { + component: License, + path: IAM_PAGES.LICENSE, + forceDisplay: true, + }, + ]; + + const allowedRoutes = ( + operatorMode ? operatorConsoleRoutes : consoleAdminRoutes + ).filter( + (route: any) => + (route.forceDisplay || + (route.customPermissionFnc + ? route.customPermissionFnc() + : hasPermission( + CONSOLE_UI_RESOURCE, + IAM_PAGES_PERMISSIONS[route.path] + ))) && + !route.fsHidden + ); const closeSnackBar = () => { setOpenSnackbar(false); @@ -409,7 +485,7 @@ const Console = ({ {session && session.status === "ok" ? (
- {!hideMenu && } + {!hideMenu && }
{needsRestart && ( @@ -505,6 +581,9 @@ const mapState = (state: AppState) => ({ session: state.console.session, loadingProgress: state.system.loadingProgress, snackBarMessage: state.system.snackBar, + operatorMode: state.system.operatorMode, + distributedSetup: state.system.distributedSetup, + features: state.console.session.features, }); const connector = connect(mapState, { diff --git a/portal-ui/src/screens/Console/Groups/Groups.tsx b/portal-ui/src/screens/Console/Groups/Groups.tsx index f59f501f8..89deff2fc 100644 --- a/portal-ui/src/screens/Console/Groups/Groups.tsx +++ b/portal-ui/src/screens/Console/Groups/Groups.tsx @@ -219,6 +219,7 @@ const Groups = ({ classes, setErrorSnackMessage }: IGroupsProps) => { IAM_SCOPES.ADMIN_LIST_USERS, ]} matchAll + errorProps={{ disabled: true }} > - - - -
-
- Enable your preferred options to get filtered records. -
- You can use '*' to match any character, '.' to signify a single - character or '\' to scape an special character (E.g. mybucket-*) -
-
- - - -
-
- - - -
-
-
- - - - - - ( - - - {element.response_status_code} ({element.response_status}) - - - ), - renderFullObject: true, - }, - { - label: LogSearchColumnLabels.request_content_length, - elementKey: "request_content_length", - renderFunction: niceBytes, - }, - { - label: LogSearchColumnLabels.response_content_length, - elementKey: "response_content_length", - renderFunction: niceBytes, - }, - { - label: LogSearchColumnLabels.time_to_response_ns, - elementKey: "time_to_response_ns", - renderFunction: nsToSeconds, - contentTextAlign: "right", - }, - ]} - isLoading={loading} - records={records} - entityName="Logs" - customEmptyMessage={"There is no information with this criteria"} - idField="request_id" - columnsSelector - columnsShown={columnsShown} - onColumnChange={selectColumn} - customPaperHeight={ - filterOpen ? classes.tableFOpen : classes.tableFClosed - } - sortConfig={{ - currentSort: "time", - currentDirection: sortOrder, - triggerSort: sortChange, - }} - infiniteScrollConfig={{ - recordsCount: 1000000, - loadMoreRecords: loadMoreRecords, - }} - itemActions={[ - { - type: "view", - onClick: openExtraInformation, - }, - ]} - textSelectable + {!logSearchEnabled ? ( + } + documentationLink="https://github.com/minio/operator/tree/master/logsearchapi" /> - + ) : ( + + {" "} + + +
+ +
+ + + + +
+ +
+
+ Enable your preferred options to get filtered records. +
+ You can use '*' to match any character, '.' to signify a + single character or '\' to scape an special character (E.g. + mybucket-*) +
+
+ + + +
+
+ + + +
+
+
+ + + +
+ + + ( + + + {element.response_status_code} ( + {element.response_status}) + + + ), + renderFullObject: true, + }, + { + label: LogSearchColumnLabels.request_content_length, + elementKey: "request_content_length", + renderFunction: niceBytes, + }, + { + label: LogSearchColumnLabels.response_content_length, + elementKey: "response_content_length", + renderFunction: niceBytes, + }, + { + label: LogSearchColumnLabels.time_to_response_ns, + elementKey: "time_to_response_ns", + renderFunction: nsToSeconds, + contentTextAlign: "right", + }, + ]} + isLoading={loading} + records={records} + entityName="Logs" + customEmptyMessage={ + "There is no information with this criteria" + } + idField="request_id" + columnsSelector + columnsShown={columnsShown} + onColumnChange={selectColumn} + customPaperHeight={ + filterOpen ? classes.tableFOpen : classes.tableFClosed + } + sortConfig={{ + currentSort: "time", + currentDirection: sortOrder, + triggerSort: sortChange, + }} + infiniteScrollConfig={{ + recordsCount: 1000000, + loadMoreRecords: loadMoreRecords, + }} + itemActions={[ + { + type: "view", + onClick: openExtraInformation, + }, + ]} + textSelectable + /> + + +
+ )} ); diff --git a/portal-ui/src/screens/Console/Menu/Menu.tsx b/portal-ui/src/screens/Console/Menu/Menu.tsx index 79d761a12..d7bed5bdd 100644 --- a/portal-ui/src/screens/Console/Menu/Menu.tsx +++ b/portal-ui/src/screens/Console/Menu/Menu.tsx @@ -60,6 +60,14 @@ import { UsersIcon, VersionIcon, } from "../../../icons"; +import { + CONSOLE_UI_RESOURCE, + IAM_PAGES_PERMISSIONS, + IAM_PAGES, + S3_ALL_RESOURCES, + IAM_SCOPES, +} from "../../../common/SecureComponent/permissions"; +import { hasPermission } from "../../../common/SecureComponent/SecureComponent"; const drawerWidth = 245; @@ -217,23 +225,23 @@ const styles = (theme: Theme) => interface IMenuProps { classes: any; userLoggedIn: typeof userLoggedIn; - pages: string[]; operatorMode: boolean; distributedSetup: boolean; sidebarOpen: boolean; setMenuOpen: typeof setMenuOpen; resetSession: typeof resetSession; + features: string[] | null; } const Menu = ({ userLoggedIn, classes, - pages, operatorMode, distributedSetup, sidebarOpen, setMenuOpen, resetSession, + features, }: IMenuProps) => { const logout = () => { const deleteSession = () => { @@ -254,12 +262,13 @@ const Menu = ({ }); }; - let menuItems: IMenuItem[] = [ + const ldapIsEnabled = (features && features.includes("ldap-idp")) || false; + let menuConsoleAdmin: IMenuItem[] = [ { group: "common", type: "item", component: NavLink, - to: "/dashboard", + to: IAM_PAGES.DASHBOARD, name: "Dashboard", icon: DashboardIcon, }, @@ -267,40 +276,46 @@ const Menu = ({ group: "common", type: "item", component: NavLink, - to: "/buckets", + to: IAM_PAGES.BUCKETS, name: "Buckets", icon: BucketsIcon, + forceDisplay: true, }, - { group: "common", type: "item", component: NavLink, - to: "/users", + to: IAM_PAGES.USERS, + customPermissionFnc: () => + hasPermission(CONSOLE_UI_RESOURCE, [IAM_SCOPES.ADMIN_LIST_USERS]) || + hasPermission(S3_ALL_RESOURCES, [IAM_SCOPES.ADMIN_CREATE_USER]), name: "Users", icon: UsersIcon, + fsHidden: ldapIsEnabled, }, { group: "common", type: "item", component: NavLink, - to: "/groups", + to: IAM_PAGES.GROUPS, name: "Groups", icon: GroupsIcon, + fsHidden: ldapIsEnabled, }, { group: "common", type: "item", component: NavLink, - to: "/account", + to: IAM_PAGES.ACCOUNT, name: "Service Accounts", icon: AccountIcon, + forceDisplay: true, }, { group: "common", type: "item", component: NavLink, - to: "/policies", + to: IAM_PAGES.POLICIES, name: "IAM Policies", icon: IAMPoliciesIcon, }, @@ -308,7 +323,7 @@ const Menu = ({ group: "common", type: "item", component: NavLink, - to: "/settings", + to: IAM_PAGES.SETTINGS, name: "Settings", icon: SettingsIcon, }, @@ -316,7 +331,7 @@ const Menu = ({ group: "common", type: "item", component: NavLink, - to: "/notification-endpoints", + to: IAM_PAGES.NOTIFICATIONS_ENDPOINTS, name: "Notification Endpoints", icon: LambdaIcon, }, @@ -324,7 +339,7 @@ const Menu = ({ group: "common", type: "item", component: NavLink, - to: "/tiers", + to: IAM_PAGES.TIERS, name: "Tiers", icon: TiersIcon, }, @@ -332,107 +347,97 @@ const Menu = ({ group: "common", type: "item", component: NavLink, - to: "/tools", + to: IAM_PAGES.TOOLS, name: "Tools", icon: ToolsIcon, }, + { + group: "License", + type: "item", + component: NavLink, + to: IAM_PAGES.LICENSE, + name: "License", + icon: LicenseIcon, + forceDisplay: true, + }, + { + group: "License", + type: "item", + component: NavLink, + to: IAM_PAGES.DOCUMENTATION, + name: "Documentation", + icon: DocumentationIcon, + forceDisplay: true, + onClick: ( + e: + | React.MouseEvent + | React.MouseEvent + | React.MouseEvent + ) => { + e.preventDefault(); + window.open("https://docs.min.io/?ref=con", "_blank"); + }, + }, + ]; + + let menuOperatorConsole: IMenuItem[] = [ { group: "Operator", type: "item", component: NavLink, - to: "/tenants", + to: IAM_PAGES.TENANTS, name: "Tenants", icon: TenantsOutlineIcon, + forceDisplay: true, }, { group: "Operator", type: "item", component: NavLink, - to: "/storage", + to: IAM_PAGES.STORAGE, name: "Storage", icon: StorageIcon, + forceDisplay: true, + }, + { + group: "Operator", + type: "item", + component: NavLink, + to: IAM_PAGES.LICENSE, + name: "License", + icon: LicenseIcon, + forceDisplay: true, + }, + { + group: "Operator", + type: "item", + component: NavLink, + to: IAM_PAGES.DOCUMENTATION, + name: "Documentation", + icon: DocumentationIcon, + forceDisplay: true, + onClick: ( + e: + | React.MouseEvent + | React.MouseEvent + | React.MouseEvent + ) => { + e.preventDefault(); + window.open("https://docs.min.io/?ref=op", "_blank"); + }, }, ]; - const allowedPages = pages.reduce((result: any, item: any) => { - if (item.startsWith("/tools")) { - result["/tools"] = true; - } - result[item] = true; - return result; - }, {}); - - const documentation: IMenuItem = { - group: "License", - type: "item", - component: NavLink, - to: "/documentation", - name: "Documentation", - icon: DocumentationIcon, - forceDisplay: true, - }; - - // Append the license page according to the allowedPages - if (allowedPages.hasOwnProperty("/tenants")) { - menuItems.push( - { - group: "Operator", - type: "item", - component: NavLink, - to: "/license", - name: "License", - icon: LicenseIcon, - }, - { - ...documentation, - group: "Operator", - onClick: ( - e: - | React.MouseEvent - | React.MouseEvent - | React.MouseEvent - ) => { - e.preventDefault(); - window.open( - `https://docs.min.io/?ref=${operatorMode ? "op" : "con"}`, - "_blank" - ); - }, - } - ); - } else { - menuItems.push( - { - group: "License", - type: "item", - component: NavLink, - to: "/license", - name: "License", - icon: LicenseIcon, - }, - { - ...documentation, - group: "License", - onClick: ( - e: - | React.MouseEvent - | React.MouseEvent - | React.MouseEvent - ) => { - e.preventDefault(); - window.open( - `https://docs.min.io/?ref=${operatorMode ? "op" : "con"}`, - "_blank" - ); - }, - } - ); - } - - const allowedItems = menuItems.filter( + const allowedItems = ( + operatorMode ? menuOperatorConsole : menuConsoleAdmin + ).filter( (item: any) => - (allowedPages[item.to] || item.forceDisplay || item.type !== "item") && - item.fsHidden !== false + ((item.customPermissionFnc + ? item.customPermissionFnc() + : hasPermission(CONSOLE_UI_RESOURCE, IAM_PAGES_PERMISSIONS[item.to])) || + item.forceDisplay || + item.type !== "item") && + !item.fsHidden ); return ( @@ -561,6 +566,7 @@ const mapState = (state: AppState) => ({ sidebarOpen: state.system.sidebarOpen, operatorMode: state.system.operatorMode, distributedSetup: state.system.distributedSetup, + features: state.console.session.features, }); const connector = connect(mapState, { diff --git a/portal-ui/src/screens/Console/Menu/types.ts b/portal-ui/src/screens/Console/Menu/types.ts index a913ae033..bd82a27a7 100644 --- a/portal-ui/src/screens/Console/Menu/types.ts +++ b/portal-ui/src/screens/Console/Menu/types.ts @@ -14,8 +14,6 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import { userLoggedIn } from "../../../actions"; - export interface IMenuItem { group: string; type: string; @@ -27,4 +25,13 @@ export interface IMenuItem { forceDisplay?: boolean; extraMargin?: boolean; fsHidden?: boolean; + customPermissionFnc?: any; +} + +export interface IRouteRule { + component: any; + path: string; + forceDisplay?: boolean; + fsHidden?: boolean; + customPermissionFnc?: any; } diff --git a/portal-ui/src/screens/Console/Policies/ListPolicies.tsx b/portal-ui/src/screens/Console/Policies/ListPolicies.tsx index b7e619c86..2952fca71 100644 --- a/portal-ui/src/screens/Console/Policies/ListPolicies.tsx +++ b/portal-ui/src/screens/Console/Policies/ListPolicies.tsx @@ -206,6 +206,7 @@ const ListPolicies = ({ classes, setErrorSnackMessage }: IPoliciesProps) => { - + + - Create User - + + -
@@ -246,16 +295,24 @@ const ListUsers = ({ classes, setErrorSnackMessage, history }: IUsersProps) => { {records.length > 0 && ( - + + + { explicitly list the actions and resources to which that user has access. Users can also inherit policies from the groups in which they have membership. -
-
- To get started,{" "} - { - setAddScreenOpen(true); - setSelectedUser(null); - }} + - Create a User - - . + +
+
+ To get started,{" "} + { + setAddScreenOpen(true); + setSelectedUser(null); + }} + > + Create a User + + . +
+
} /> diff --git a/portal-ui/src/screens/Console/reducer.ts b/portal-ui/src/screens/Console/reducer.ts index e592e078e..f23cee220 100644 --- a/portal-ui/src/screens/Console/reducer.ts +++ b/portal-ui/src/screens/Console/reducer.ts @@ -25,7 +25,6 @@ const initialState: ConsoleState = { session: { operator: false, status: "", - pages: [], features: [], distributedMode: false, permissions: {}, diff --git a/portal-ui/src/screens/Console/types.ts b/portal-ui/src/screens/Console/types.ts index 51ab2d0b4..d47209d41 100644 --- a/portal-ui/src/screens/Console/types.ts +++ b/portal-ui/src/screens/Console/types.ts @@ -20,7 +20,6 @@ export interface ISessionPermissions { export interface ISessionResponse { status: string; - pages: string[]; features: string[]; operator: boolean; distributedMode: boolean; diff --git a/portal-ui/yarn.lock b/portal-ui/yarn.lock index b6335fb78..71c707805 100644 --- a/portal-ui/yarn.lock +++ b/portal-ui/yarn.lock @@ -9612,10 +9612,10 @@ prepend-http@^1.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= -prettier@2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.2.tgz#ef280a05ec253712e486233db5c6f23441e7342d" - integrity sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ== +prettier@2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.0.tgz#a6370e2d4594e093270419d9cc47f7670488f893" + integrity sha512-FM/zAKgWTxj40rH03VxzIPdXmj39SwSjwG0heUcNFwI+EMZJnY93yAiKXM3dObIKAM5TA88werc8T/EwhB45eg== pretty-bytes@^5.3.0: version "5.6.0" diff --git a/restapi/embedded_spec.go b/restapi/embedded_spec.go index 011bc36f3..9a7d80ca6 100644 --- a/restapi/embedded_spec.go +++ b/restapi/embedded_spec.go @@ -5223,12 +5223,6 @@ func init() { "operator": { "type": "boolean" }, - "pages": { - "type": "array", - "items": { - "type": "string" - } - }, "permissions": { "type": "object", "additionalProperties": { @@ -11074,12 +11068,6 @@ func init() { "operator": { "type": "boolean" }, - "pages": { - "type": "array", - "items": { - "type": "string" - } - }, "permissions": { "type": "object", "additionalProperties": { diff --git a/restapi/user_session.go b/restapi/user_session.go index b4354ee36..05853fa6f 100644 --- a/restapi/user_session.go +++ b/restapi/user_session.go @@ -31,7 +31,8 @@ import ( "github.com/go-openapi/runtime/middleware" "github.com/minio/console/models" - "github.com/minio/console/pkg/acl" + "github.com/minio/console/pkg/auth/idp/oauth2" + "github.com/minio/console/pkg/auth/ldap" "github.com/minio/console/restapi/operations" "github.com/minio/console/restapi/operations/user_api" ) @@ -111,15 +112,6 @@ func getSessionResponse(session *models.Principal) (*models.SessionResponse, *mo if err != nil { return nil, prepareError(err, errorGenericInvalidSession) } - // by default every user starts with an empty array of available val - // therefore we would have access only to pages that doesn't require any privilege - // ie: service-account page - var actions []string - // if a policy is assigned to this user we parse the val from there - if policy != nil { - actions = acl.GetActionsStringFromPolicy(policy) - } - currTime := time.Now().UTC() // This actions will be global, meaning has to be attached to all resources @@ -229,7 +221,6 @@ func getSessionResponse(session *models.Principal) (*models.SessionResponse, *mo return nil, prepareError(err) } sessionResp := &models.SessionResponse{ - Pages: acl.GetAuthorizedEndpoints(actions), Features: getListOfEnabledFeatures(), Status: models.SessionResponseStatusOk, Operator: false, @@ -241,12 +232,20 @@ func getSessionResponse(session *models.Principal) (*models.SessionResponse, *mo // getListOfEnabledFeatures returns a list of features func getListOfEnabledFeatures() []string { - var features []string + features := []string{} logSearchURL := getLogSearchURL() + oidcEnabled := oauth2.IsIDPEnabled() + ldapEnabled := ldap.GetLDAPEnabled() if logSearchURL != "" { features = append(features, "log-search") } + if oidcEnabled { + features = append(features, "oidc-idp", "external-idp") + } + if ldapEnabled { + features = append(features, "ldap-idp", "external-idp") + } return features } diff --git a/swagger-console.yml b/swagger-console.yml index 3f12135e0..3cdc86f7f 100644 --- a/swagger-console.yml +++ b/swagger-console.yml @@ -3128,10 +3128,6 @@ definitions: sessionResponse: type: object properties: - pages: - type: array - items: - type: string features: type: array items: diff --git a/swagger-operator.yml b/swagger-operator.yml index 65a379ae9..dc142df3f 100644 --- a/swagger-operator.yml +++ b/swagger-operator.yml @@ -978,14 +978,6 @@ definitions: operatorSessionResponse: type: object properties: - pages: - type: array - items: - type: string - features: - type: array - items: - type: string status: type: string enum: [ ok ]