From f0e9a9edc9b0950e5f26ea16148e422c4a71271a Mon Sep 17 00:00:00 2001 From: kmalyjur Date: Wed, 25 Oct 2023 09:28:01 +0000 Subject: [PATCH] WIP update of Fixes #36785 - Show permission error page for job wizard --- app/controllers/ui_job_wizard_controller.rb | 2 +- lib/foreman_remote_execution/engine.rb | 5 +- webpack/JobWizard/JobWizardSelectors.js | 27 +++++++- webpack/JobWizard/PermissionDenied.js | 64 +++++++++++++++++++ webpack/JobWizard/index.js | 25 +++++++- .../CategoryAndTemplate.js | 28 ++++++-- .../JobWizard/steps/HostsAndInputs/index.js | 13 +++- 7 files changed, 153 insertions(+), 11 deletions(-) create mode 100644 webpack/JobWizard/PermissionDenied.js diff --git a/app/controllers/ui_job_wizard_controller.rb b/app/controllers/ui_job_wizard_controller.rb index b9ae20f8d..20a75472f 100644 --- a/app/controllers/ui_job_wizard_controller.rb +++ b/app/controllers/ui_job_wizard_controller.rb @@ -1,4 +1,4 @@ -class UiJobWizardController < ApplicationController +class UiJobWizardController < Api::V2::BaseController include FiltersHelper def categories job_categories = resource_scope(permission: action_permission) diff --git a/lib/foreman_remote_execution/engine.rb b/lib/foreman_remote_execution/engine.rb index 96e523b70..4ccd8509a 100644 --- a/lib/foreman_remote_execution/engine.rb +++ b/lib/foreman_remote_execution/engine.rb @@ -183,18 +183,21 @@ class Engine < ::Rails::Engine :'api/v2/job_templates' => [:destroy] }, :resource_type => 'JobTemplate' permission :lock_job_templates, { :job_templates => [:lock, :unlock] }, :resource_type => 'JobTemplate' permission :create_job_invocations, { :job_invocations => [:new, :create, :legacy_create, :refresh, :rerun, :preview_hosts], + :ui_job_wizard => [:categories], 'api/v2/job_invocations' => [:create, :rerun] }, :resource_type => 'JobInvocation' permission :view_job_invocations, { :job_invocations => [:index, :chart, :show, :auto_complete_search, :preview_job_invocations_per_host], :template_invocations => [:show], 'api/v2/job_invocations' => [:index, :show, :output, :raw_output, :outputs] }, :resource_type => 'JobInvocation' permission :view_template_invocations, { :template_invocations => [:show], 'api/v2/template_invocations' => [:template_invocations], :ui_job_wizard => [:job_invocation] }, :resource_type => 'TemplateInvocation' - permission :create_template_invocations, {}, :resource_type => 'TemplateInvocation' + permission :create_template_invocations, { :ui_job_wizard => [:categories] }, :resource_type => 'TemplateInvocation' permission :execute_jobs_on_infrastructure_hosts, {}, :resource_type => 'JobInvocation' permission :cancel_job_invocations, { :job_invocations => [:cancel], 'api/v2/job_invocations' => [:cancel] }, :resource_type => 'JobInvocation' # this permissions grants user to get auto completion hints when setting up filters permission :filter_autocompletion_for_template_invocation, { :template_invocations => [ :auto_complete_search, :index ] }, :resource_type => 'TemplateInvocation' permission :cockpit_hosts, { 'cockpit' => [:redirect, :host_ssh_params] }, :resource_type => 'Host' + permission :view_hosts, { :ui_job_wizard => [:categories] }, :resource_type => 'Host' + permission :view_smart_proxies, { :ui_job_wizard => [:categories] }, :resource_type => 'SmartProxy' end USER_PERMISSIONS = [ diff --git a/webpack/JobWizard/JobWizardSelectors.js b/webpack/JobWizard/JobWizardSelectors.js index febdd82bb..0bc3738aa 100644 --- a/webpack/JobWizard/JobWizardSelectors.js +++ b/webpack/JobWizard/JobWizardSelectors.js @@ -1,4 +1,5 @@ import URI from 'urijs'; +import { get } from 'lodash'; import { selectAPIResponse, selectAPIStatus, @@ -42,6 +43,18 @@ export const selectJobCategoriesStatus = state => export const selectCategoryError = state => selectAPIErrorMessage(state, JOB_CATEGORIES); +export const selectJobCategoriesMissingPermissions = state => { + const jobCategoriesResponse = selectJobCategoriesResponse(state); + return ( + get(jobCategoriesResponse, [ + 'response', + 'data', + 'error', + 'missing_permissions', + ]) || [] + ); +}; + export const selectAllTemplatesError = state => selectAPIErrorMessage(state, JOB_TEMPLATES); @@ -60,11 +73,21 @@ export const selectAdvancedTemplateInputs = state => export const selectTemplateInputs = state => selectAPIResponse(state, JOB_TEMPLATE).template_inputs || []; +export const selectHostsResponse = state => selectAPIResponse(state, HOSTS_API); + export const selectHostCount = state => - selectAPIResponse(state, HOSTS_API).subtotal || 0; + selectHostsResponse(state).subtotal || 0; export const selectHosts = state => - (selectAPIResponse(state, HOSTS_API).results || []).map(host => host.name); + (selectHostsResponse(state).results || []).map(host => host.name); + +export const selectHostsMissingPermissions = state => { + const hostsResponse = selectHostsResponse(state); + return ( + get(hostsResponse, ['response', 'data', 'error', 'missing_permissions']) || + [] + ); +}; export const selectIsLoadingHosts = state => !selectAPIStatus(state, HOSTS_API) || diff --git a/webpack/JobWizard/PermissionDenied.js b/webpack/JobWizard/PermissionDenied.js new file mode 100644 index 000000000..7f053cdde --- /dev/null +++ b/webpack/JobWizard/PermissionDenied.js @@ -0,0 +1,64 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { translate as __ } from 'foremanReact/common/I18n'; +import { Icon } from 'patternfly-react'; +import { + Title, + Button, + EmptyState, + EmptyStateVariant, + EmptyStateBody, +} from '@patternfly/react-core'; + +const PermissionDenied = ({ missingPermissions, setProceedAnyway }) => { + const description = ( + + {__('You are not authorized to perform this action.')} +
+ {__( + 'Please request the required permissions listed below from a Foreman administrator:' + )} +
+ +
+ ); + const handleProceedAnyway = () => { + setProceedAnyway(true); + }; + + return ( + + + + + + {__('Permission Denied')} + + {description} + + + ); +}; + +PermissionDenied.propTypes = { + missingPermissions: PropTypes.array, + setProceedAnyway: PropTypes.func.isRequired, +}; + +PermissionDenied.defaultProps = { + missingPermissions: ['unknown'], +}; + +export default PermissionDenied; diff --git a/webpack/JobWizard/index.js b/webpack/JobWizard/index.js index 913754cd2..5e117d138 100644 --- a/webpack/JobWizard/index.js +++ b/webpack/JobWizard/index.js @@ -1,9 +1,17 @@ -import React from 'react'; +import React, { useState } from 'react'; +import { isEmpty } from 'lodash'; import PropTypes from 'prop-types'; +import { useSelector } from 'react-redux'; import { Title, Flex, FlexItem, Button } from '@patternfly/react-core'; import { translate as __ } from 'foremanReact/common/I18n'; import PageLayout from 'foremanReact/routes/common/PageLayout/PageLayout'; +import { + selectJobCategoriesMissingPermissions, + selectIsLoading, +} from './JobWizardSelectors'; +import PermissionDenied from './PermissionDenied'; import { JobWizard } from './JobWizard'; +import { JOB_CATEGORIES } from './JobWizardConstants'; const JobWizardPage = ({ location: { search } }) => { const title = __('Run job'); @@ -13,6 +21,21 @@ const JobWizardPage = ({ location: { search } }) => { { caption: title }, ], }; + const missingPermissions = useSelector(selectJobCategoriesMissingPermissions); + const isCategoriesLoading = useSelector(state => + selectIsLoading(state, JOB_CATEGORIES) + ); + const [proceedAnyway, setProceedAnyway] = useState(false); + + if (!isEmpty(missingPermissions) && !isCategoriesLoading && !proceedAnyway) { + return ( + + ); + } + return ( @@ -69,7 +80,7 @@ export const CategoryAndTemplate = ({ options={jobCategories} setValue={onSelectCategory} value={selectedCategory} - placeholderText={categoryError ? __('Error') : ''} + placeholderText={categoryError ? __('Not available') : ''} isDisabled={!!categoryError} isRequired /> @@ -82,11 +93,18 @@ export const CategoryAndTemplate = ({ isDisabled={ !!(categoryError || allTemplatesError || isTemplatesLoading) } - placeholderText={allTemplatesError ? __('Error') : ''} + placeholderText={allTemplatesError ? __('Not available') : ''} /> + {missingPermission && ( + + + {__('Missing the required permissions: view_job_templates')} + + + )} {isError && ( - {categoryError && ( + {categoryError && !missingPermission && ( {__('Categories list failed with:')} {categoryError} diff --git a/webpack/JobWizard/steps/HostsAndInputs/index.js b/webpack/JobWizard/steps/HostsAndInputs/index.js index 565a588d1..867a9652e 100644 --- a/webpack/JobWizard/steps/HostsAndInputs/index.js +++ b/webpack/JobWizard/steps/HostsAndInputs/index.js @@ -1,5 +1,7 @@ import React, { useEffect, useState } from 'react'; +import { isEmpty, debounce } from 'lodash'; import { + Alert, Button, Form, FormGroup, @@ -10,13 +12,13 @@ import { import PropTypes from 'prop-types'; import { useSelector, useDispatch } from 'react-redux'; import { FilterIcon } from '@patternfly/react-icons'; -import { debounce } from 'lodash'; import { get } from 'foremanReact/redux/API'; import { translate as __ } from 'foremanReact/common/I18n'; import { selectTemplateInputs, selectWithKatello, selectHostCount, + selectHostsMissingPermissions, selectIsLoadingHosts, } from '../../JobWizardSelectors'; import { SelectField } from '../form/SelectField'; @@ -98,6 +100,7 @@ const HostsAndInputs = ({ ]); const withKatello = useSelector(selectWithKatello); const hostCount = useSelector(selectHostCount); + const missingPermissions = useSelector(selectHostsMissingPermissions); const dispatch = useDispatch(); const selectedHosts = selected.hosts; @@ -126,6 +129,7 @@ const HostsAndInputs = ({ const [errorText, setErrorText] = useState( __('Please select at least one host') ); + return (
@@ -237,6 +241,13 @@ const HostsAndInputs = ({ value={templateValues} setValue={setTemplateValues} /> + {!isEmpty(missingPermissions) && ( + + + {__(`Missing the required permissions:`)} {missingPermissions} + + + )}
);