Skip to content

Commit

Permalink
Fixes #36785 - Show permission error page for job wizard
Browse files Browse the repository at this point in the history
  • Loading branch information
kmalyjur authored and adamruzicka committed Jan 8, 2024
1 parent 83abc1b commit f9f7398
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 11 deletions.
2 changes: 1 addition & 1 deletion app/controllers/ui_job_wizard_controller.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class UiJobWizardController < ApplicationController
class UiJobWizardController < Api::V2::BaseController
include FiltersHelper
def categories
job_categories = resource_scope(permission: action_permission)
Expand Down
2 changes: 1 addition & 1 deletion lib/foreman_remote_execution/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class Engine < ::Rails::Engine

initializer 'foreman_remote_execution.register_plugin', before: :finisher_hook do |_app|
Foreman::Plugin.register :foreman_remote_execution do
requires_foreman '>= 3.9'
requires_foreman '>= 3.10'
register_global_js_file 'global'
register_gettext

Expand Down
4 changes: 4 additions & 0 deletions webpack/JobWizard/JobWizardConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ export const JOB_TEMPLATES = 'JOB_TEMPLATES';
export const JOB_CATEGORIES = 'JOB_CATEGORIES';
export const JOB_TEMPLATE = 'JOB_TEMPLATE';
export const JOB_INVOCATION = 'JOB_INVOCATION';
export const CURRENT_PERMISSIONS = 'CURRENT_PERMISSIONS';
export const currentPermissionsUrl = foremanUrl(
'/api/v2/permissions/current_permissions'
);
export const templatesUrl = foremanUrl('/api/v2/job_templates');

export const repeatTypes = {
Expand Down
27 changes: 25 additions & 2 deletions webpack/JobWizard/JobWizardSelectors.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import URI from 'urijs';
import { get } from 'lodash';
import {
selectAPIResponse,
selectAPIStatus,
Expand Down Expand Up @@ -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);

Expand All @@ -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) ||
Expand Down
64 changes: 64 additions & 0 deletions webpack/JobWizard/PermissionDenied.js
Original file line number Diff line number Diff line change
@@ -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 = (
<span>
{__('You are not authorized to perform this action.')}
<br />
{__(
'Please request the required permissions listed below from a Foreman administrator:'
)}
<br />
<ul className="list-unstyled">
{missingPermissions.map(permission => (
<li key={permission}>
<strong>{permission}</strong>
</li>
))}
</ul>
</span>
);
const handleProceedAnyway = () => {
setProceedAnyway(true);
};

return (
<EmptyState variant={EmptyStateVariant.xl}>
<span className="empty-state-icon">
<Icon name="lock" type="fa" size="2x" />
</span>
<Title ouiaId="empty-state-header" headingLevel="h5" size="4xl">
{__('Permission Denied')}
</Title>
<EmptyStateBody>{description}</EmptyStateBody>
<Button
ouiaId="job-invocation-proceed-anyway-button"
variant="primary"
onClick={handleProceedAnyway}
>
{__('Proceed Anyway')}
</Button>
</EmptyState>
);
};

PermissionDenied.propTypes = {
missingPermissions: PropTypes.array,
setProceedAnyway: PropTypes.func.isRequired,
};

PermissionDenied.defaultProps = {
missingPermissions: ['unknown'],
};

export default PermissionDenied;
41 changes: 40 additions & 1 deletion webpack/JobWizard/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import React from 'react';
import React, { useState } from 'react';
import { isEmpty } from 'lodash';
import PropTypes from 'prop-types';
import { Button } from '@patternfly/react-core';
import { translate as __ } from 'foremanReact/common/I18n';
import { STATUS } from 'foremanReact/constants';
import PageLayout from 'foremanReact/routes/common/PageLayout/PageLayout';
import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
import PermissionDenied from './PermissionDenied';
import { JobWizard } from './JobWizard';
import {
CURRENT_PERMISSIONS,
currentPermissionsUrl,
} from './JobWizardConstants';

const JobWizardPage = ({ location: { search } }) => {
const title = __('Run job');
Expand All @@ -13,6 +21,37 @@ const JobWizardPage = ({ location: { search } }) => {
{ caption: title },
],
};
const [proceedAnyway, setProceedAnyway] = useState(false);

const { response, status } = useAPI(
'get',
currentPermissionsUrl,
CURRENT_PERMISSIONS
);
const desiredPermissions = [
'view_hosts',
'view_smart_proxies',
'view_job_templates',
'create_job_invocations',
'create_template_invocations',
];
const missingPermissions =
status === STATUS.RESOLVED
? desiredPermissions.filter(
permission =>
!response.results.map(result => result.name).includes(permission)
)
: [];

if (!isEmpty(missingPermissions) && !proceedAnyway) {
return (
<PermissionDenied
missingPermissions={missingPermissions}
setProceedAnyway={setProceedAnyway}
/>
);
}

return (
<PageLayout
header={title}
Expand Down
31 changes: 26 additions & 5 deletions webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import React from 'react';
import { isEmpty } from 'lodash';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import { Text, TextVariants, Form, Alert } from '@patternfly/react-core';
import { translate as __ } from 'foremanReact/common/I18n';
import {
selectJobCategoriesMissingPermissions,
selectIsLoading,
} from '../../JobWizardSelectors';
import { SelectField } from '../form/SelectField';
import { GroupedSelectField } from '../form/GroupedSelectField';
import { WizardTitle } from '../form/WizardTitle';
import { WIZARD_TITLES, JOB_TEMPLATES } from '../../JobWizardConstants';
import { selectIsLoading } from '../../JobWizardSelectors';

export const CategoryAndTemplate = ({
jobCategories,
Expand Down Expand Up @@ -57,7 +61,13 @@ export const CategoryAndTemplate = ({
};

const { categoryError, allTemplatesError, templateError } = errors;
const isError = !!(categoryError || allTemplatesError || templateError);
const missingPermissions = useSelector(selectJobCategoriesMissingPermissions);
const isError = !!(
(categoryError && isEmpty(missingPermissions)) ||
allTemplatesError ||
templateError
);

return (
<>
<WizardTitle title={WIZARD_TITLES.categoryAndTemplate} />
Expand All @@ -69,7 +79,7 @@ export const CategoryAndTemplate = ({
options={jobCategories}
setValue={onSelectCategory}
value={selectedCategory}
placeholderText={categoryError ? __('Error') : ''}
placeholderText={categoryError ? __('Not available') : ''}
isDisabled={!!categoryError}
isRequired
/>
Expand All @@ -82,11 +92,22 @@ export const CategoryAndTemplate = ({
isDisabled={
!!(categoryError || allTemplatesError || isTemplatesLoading)
}
placeholderText={allTemplatesError ? __('Error') : ''}
placeholderText={allTemplatesError ? __('Not available') : ''}
/>
{!isEmpty(missingPermissions) && (
<Alert variant="warning" title={__('Access denied')}>
<span>
{__(
`Missing the required permissions: ${missingPermissions.join(
', '
)}`
)}
</span>
</Alert>
)}
{isError && (
<Alert variant="danger" title={__('Errors:')}>
{categoryError && (
{categoryError && isEmpty(missingPermissions) && (
<span>
{__('Categories list failed with:')} {categoryError}
</span>
Expand Down
17 changes: 16 additions & 1 deletion webpack/JobWizard/steps/HostsAndInputs/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React, { useEffect, useState } from 'react';
import { isEmpty, debounce } from 'lodash';
import {
Alert,
Button,
Form,
FormGroup,
Expand All @@ -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';
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -126,6 +129,7 @@ const HostsAndInputs = ({
const [errorText, setErrorText] = useState(
__('Please select at least one host')
);

return (
<div className="target-hosts-and-inputs">
<WizardTitle title={WIZARD_TITLES.hostsAndInputs} />
Expand Down Expand Up @@ -237,6 +241,17 @@ const HostsAndInputs = ({
value={templateValues}
setValue={setTemplateValues}
/>
{!isEmpty(missingPermissions) && (
<Alert variant="warning" title={__('Access denied')}>
<span>
{__(
`Missing the required permissions: ${missingPermissions.join(
', '
)}`
)}
</span>
</Alert>
)}
</Form>
</div>
);
Expand Down

0 comments on commit f9f7398

Please sign in to comment.