mirror of
https://github.com/ansible/awx.git
synced 2026-01-11 01:57:35 -03:30
Merge pull request #12358 from nixocio/ui_issue_5883
Hide add access button based on the user profile for credentials
This commit is contained in:
commit
7dbf5f7138
@ -77,6 +77,10 @@ class Organizations extends InstanceGroupsMixin(NotificationsMixin(Base)) {
|
||||
disassociate: true,
|
||||
});
|
||||
}
|
||||
|
||||
readAdmins(id, params) {
|
||||
return this.http.get(`${this.baseUrl}${id}/admins/`, { params });
|
||||
}
|
||||
}
|
||||
|
||||
export default Organizations;
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { t } from '@lingui/macro';
|
||||
import { RolesAPI, TeamsAPI, UsersAPI } from 'api';
|
||||
import { RolesAPI, TeamsAPI, UsersAPI, OrganizationsAPI } from 'api';
|
||||
import { getQSConfig, parseQueryString } from 'util/qs';
|
||||
import useRequest, { useDeleteItems } from 'hooks/useRequest';
|
||||
import { useUserProfile, useConfig } from 'contexts/Config';
|
||||
import AddResourceRole from '../AddRole/AddResourceRole';
|
||||
import AlertModal from '../AlertModal';
|
||||
import DataListToolbar from '../DataListToolbar';
|
||||
@ -24,6 +25,8 @@ const QS_CONFIG = getQSConfig('access', {
|
||||
});
|
||||
|
||||
function ResourceAccessList({ apiModel, resource }) {
|
||||
const { isSuperUser, isOrgAdmin } = useUserProfile();
|
||||
const { me } = useConfig();
|
||||
const [submitError, setSubmitError] = useState(null);
|
||||
const [deletionRecord, setDeletionRecord] = useState(null);
|
||||
const [deletionRole, setDeletionRole] = useState(null);
|
||||
@ -31,6 +34,49 @@ function ResourceAccessList({ apiModel, resource }) {
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
const location = useLocation();
|
||||
|
||||
const {
|
||||
isLoading: isFetchingOrgAdmins,
|
||||
error: errorFetchingOrgAdmins,
|
||||
request: fetchOrgAdmins,
|
||||
result: { isCredentialOrgAdmin },
|
||||
} = useRequest(
|
||||
useCallback(async () => {
|
||||
if (
|
||||
isSuperUser ||
|
||||
resource.type !== 'credential' ||
|
||||
!isOrgAdmin ||
|
||||
!resource?.organization
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const {
|
||||
data: { count },
|
||||
} = await OrganizationsAPI.readAdmins(resource.organization, {
|
||||
id: me.id,
|
||||
});
|
||||
return { isCredentialOrgAdmin: !!count };
|
||||
}, [me.id, isOrgAdmin, isSuperUser, resource.type, resource.organization]),
|
||||
{
|
||||
isCredentialOrgAdmin: false,
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
fetchOrgAdmins();
|
||||
}, [fetchOrgAdmins]);
|
||||
|
||||
let canAddAdditionalControls = false;
|
||||
if (isSuperUser) {
|
||||
canAddAdditionalControls = true;
|
||||
}
|
||||
if (resource.type === 'credential' && isOrgAdmin && isCredentialOrgAdmin) {
|
||||
canAddAdditionalControls = true;
|
||||
}
|
||||
if (resource.type !== 'credential') {
|
||||
canAddAdditionalControls =
|
||||
resource?.summary_fields?.user_capabilities?.edit;
|
||||
}
|
||||
|
||||
const {
|
||||
result: {
|
||||
accessRecords,
|
||||
@ -149,8 +195,8 @@ function ResourceAccessList({ apiModel, resource }) {
|
||||
return (
|
||||
<>
|
||||
<PaginatedTable
|
||||
error={contentError}
|
||||
hasContentLoading={isLoading || isDeleteLoading}
|
||||
error={contentError || errorFetchingOrgAdmins}
|
||||
hasContentLoading={isLoading || isDeleteLoading || isFetchingOrgAdmins}
|
||||
items={accessRecords}
|
||||
itemCount={itemCount}
|
||||
pluralizedItemName={t`Roles`}
|
||||
@ -163,7 +209,7 @@ function ResourceAccessList({ apiModel, resource }) {
|
||||
{...props}
|
||||
qsConfig={QS_CONFIG}
|
||||
additionalControls={
|
||||
resource?.summary_fields?.user_capabilities?.edit
|
||||
canAddAdditionalControls
|
||||
? [
|
||||
<ToolbarAddButton
|
||||
ouiaId="access-add-button"
|
||||
|
||||
@ -1,7 +1,15 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { OrganizationsAPI, TeamsAPI, UsersAPI, RolesAPI } from 'api';
|
||||
import {
|
||||
CredentialsAPI,
|
||||
OrganizationsAPI,
|
||||
RolesAPI,
|
||||
TeamsAPI,
|
||||
UsersAPI,
|
||||
} from 'api';
|
||||
import { useUserProfile } from 'contexts/Config';
|
||||
import * as ConfigContext from 'contexts/Config';
|
||||
import {
|
||||
mountWithContexts,
|
||||
waitForElement,
|
||||
@ -91,11 +99,227 @@ describe('<ResourceAccessList />', () => {
|
||||
],
|
||||
};
|
||||
|
||||
const credentialAccessList = {
|
||||
count: 2,
|
||||
results: [
|
||||
{
|
||||
id: 1,
|
||||
type: 'user',
|
||||
url: '/api/v2/users/1/',
|
||||
summary_fields: {
|
||||
direct_access: [
|
||||
{
|
||||
role: {
|
||||
id: 20,
|
||||
name: 'Admin',
|
||||
description: 'Can manage all aspects of the credential',
|
||||
resource_name: 'Demo Credential',
|
||||
resource_type: 'credential',
|
||||
related: { credential: '/api/v2/credentials/1/' },
|
||||
user_capabilities: { unattach: false },
|
||||
},
|
||||
descendant_roles: ['admin_role', 'read_role', 'use_role'],
|
||||
},
|
||||
],
|
||||
indirect_access: [
|
||||
{
|
||||
role: {
|
||||
id: 1,
|
||||
name: 'System Administrator',
|
||||
description: 'Can manage all aspects of the system',
|
||||
user_capabilities: { unattach: false },
|
||||
},
|
||||
descendant_roles: ['admin_role', 'read_role', 'use_role'],
|
||||
},
|
||||
],
|
||||
},
|
||||
created: '2022-06-08T18:31:35.834036Z',
|
||||
modified: '2022-06-09T16:47:54.712473Z',
|
||||
username: 'admin',
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
email: 'admin@localhost',
|
||||
is_superuser: true,
|
||||
is_system_auditor: false,
|
||||
ldap_dn: '',
|
||||
last_login: '2022-06-09T16:47:54.712473Z',
|
||||
external_account: null,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 'user',
|
||||
url: '/api/v2/users/2/',
|
||||
related: {
|
||||
teams: '/api/v2/users/2/teams/',
|
||||
organizations: '/api/v2/users/2/organizations/',
|
||||
admin_of_organizations: '/api/v2/users/2/admin_of_organizations/',
|
||||
projects: '/api/v2/users/2/projects/',
|
||||
credentials: '/api/v2/users/2/credentials/',
|
||||
roles: '/api/v2/users/2/roles/',
|
||||
activity_stream: '/api/v2/users/2/activity_stream/',
|
||||
access_list: '/api/v2/users/2/access_list/',
|
||||
tokens: '/api/v2/users/2/tokens/',
|
||||
authorized_tokens: '/api/v2/users/2/authorized_tokens/',
|
||||
personal_tokens: '/api/v2/users/2/personal_tokens/',
|
||||
},
|
||||
summary_fields: {
|
||||
direct_access: [
|
||||
{
|
||||
role: {
|
||||
id: 22,
|
||||
name: 'Read',
|
||||
description: 'May view settings for the credential',
|
||||
resource_name: 'Demo Credential',
|
||||
resource_type: 'credential',
|
||||
related: { credential: '/api/v2/credentials/1/' },
|
||||
user_capabilities: { unattach: false },
|
||||
},
|
||||
descendant_roles: ['read_role'],
|
||||
},
|
||||
],
|
||||
indirect_access: [],
|
||||
},
|
||||
created: '2022-06-09T13:45:56.049783Z',
|
||||
modified: '2022-06-09T16:48:46.169760Z',
|
||||
username: 'second',
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
email: '',
|
||||
is_superuser: false,
|
||||
is_system_auditor: false,
|
||||
ldap_dn: '',
|
||||
last_login: '2022-06-09T16:48:46.169760Z',
|
||||
external_account: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const credential = {
|
||||
id: 1,
|
||||
type: 'credential',
|
||||
url: '/api/v2/credentials/1/',
|
||||
related: {
|
||||
named_url: '/api/v2/credentials/Demo Credential++Machine+ssh++Default/',
|
||||
created_by: '/api/v2/users/1/',
|
||||
modified_by: '/api/v2/users/1/',
|
||||
organization: '/api/v2/organizations/1/',
|
||||
activity_stream: '/api/v2/credentials/1/activity_stream/',
|
||||
access_list: '/api/v2/credentials/1/access_list/',
|
||||
object_roles: '/api/v2/credentials/1/object_roles/',
|
||||
owner_users: '/api/v2/credentials/1/owner_users/',
|
||||
owner_teams: '/api/v2/credentials/1/owner_teams/',
|
||||
copy: '/api/v2/credentials/1/copy/',
|
||||
input_sources: '/api/v2/credentials/1/input_sources/',
|
||||
credential_type: '/api/v2/credential_types/1/',
|
||||
},
|
||||
summary_fields: {
|
||||
organization: {
|
||||
id: 1,
|
||||
name: 'Default',
|
||||
description: '',
|
||||
},
|
||||
credential_type: {
|
||||
id: 1,
|
||||
name: 'Machine',
|
||||
description: '',
|
||||
},
|
||||
created_by: {
|
||||
id: 1,
|
||||
username: 'admin',
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
},
|
||||
modified_by: {
|
||||
id: 1,
|
||||
username: 'admin',
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
},
|
||||
object_roles: {
|
||||
admin_role: {
|
||||
description: 'Can manage all aspects of the credential',
|
||||
name: 'Admin',
|
||||
id: 20,
|
||||
},
|
||||
use_role: {
|
||||
description: 'Can use the credential in a job template',
|
||||
name: 'Use',
|
||||
id: 21,
|
||||
},
|
||||
read_role: {
|
||||
description: 'May view settings for the credential',
|
||||
name: 'Read',
|
||||
id: 22,
|
||||
},
|
||||
},
|
||||
user_capabilities: {
|
||||
edit: true,
|
||||
delete: true,
|
||||
copy: false,
|
||||
use: true,
|
||||
},
|
||||
owners: [
|
||||
{
|
||||
id: 3,
|
||||
type: 'user',
|
||||
name: 'third',
|
||||
description: ' ',
|
||||
url: '/api/v2/users/3/',
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
type: 'user',
|
||||
name: 'admin',
|
||||
description: ' ',
|
||||
url: '/api/v2/users/1/',
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
type: 'organization',
|
||||
name: 'Default',
|
||||
description: '',
|
||||
url: '/api/v2/organizations/1/',
|
||||
},
|
||||
],
|
||||
},
|
||||
created: '2022-06-08T18:31:43.491973Z',
|
||||
modified: '2022-06-09T19:40:49.460771Z',
|
||||
name: 'Demo Credential',
|
||||
description: '',
|
||||
organization: 1,
|
||||
credential_type: 1,
|
||||
managed: false,
|
||||
inputs: {
|
||||
username: 'admin',
|
||||
become_method: '',
|
||||
become_username: '',
|
||||
},
|
||||
kind: 'ssh',
|
||||
cloud: false,
|
||||
kubernetes: false,
|
||||
};
|
||||
|
||||
const history = createMemoryHistory({
|
||||
initialEntries: ['/organizations/1/access'],
|
||||
});
|
||||
|
||||
const credentialHistory = createMemoryHistory({
|
||||
initialEntries: ['/credentials/1/access'],
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.spyOn(ConfigContext, 'useConfig').mockImplementation(() => ({
|
||||
me: { id: 2 },
|
||||
}));
|
||||
useUserProfile.mockImplementation(() => {
|
||||
return {
|
||||
isSuperUser: true,
|
||||
isSystemAuditor: false,
|
||||
isOrgAdmin: false,
|
||||
isNotificationAdmin: false,
|
||||
isExecEnvAdmin: false,
|
||||
};
|
||||
});
|
||||
OrganizationsAPI.readAccessList.mockResolvedValue({ data });
|
||||
OrganizationsAPI.readAccessOptions.mockResolvedValue({
|
||||
data: {
|
||||
@ -106,6 +330,7 @@ describe('<ResourceAccessList />', () => {
|
||||
related_search_fields: [],
|
||||
},
|
||||
});
|
||||
OrganizationsAPI.readAdmins.mockResolvedValue({ data: { count: 1 } });
|
||||
TeamsAPI.disassociateRole.mockResolvedValue({});
|
||||
UsersAPI.disassociateRole.mockResolvedValue({});
|
||||
RolesAPI.read.mockResolvedValue({
|
||||
@ -116,6 +341,16 @@ describe('<ResourceAccessList />', () => {
|
||||
],
|
||||
},
|
||||
});
|
||||
CredentialsAPI.readAccessList.mockResolvedValue({ credentialAccessList });
|
||||
CredentialsAPI.readAccessOptions.mockResolvedValue({
|
||||
data: {
|
||||
actions: {
|
||||
GET: {},
|
||||
POST: {},
|
||||
},
|
||||
related_search_fields: [],
|
||||
},
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
@ -213,4 +448,90 @@ describe('<ResourceAccessList />', () => {
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('should show add button for system admin', async () => {
|
||||
let wrapper;
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<ResourceAccessList resource={credential} apiModel={CredentialsAPI} />,
|
||||
{ context: { router: { credentialHistory } } }
|
||||
);
|
||||
});
|
||||
|
||||
await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
|
||||
wrapper.update();
|
||||
expect(wrapper.find('ToolbarAddButton').length).toEqual(1);
|
||||
});
|
||||
|
||||
test('should not show add button for non system admin & non org admin', async () => {
|
||||
useUserProfile.mockImplementation(() => {
|
||||
return {
|
||||
isSuperUser: false,
|
||||
isSystemAuditor: false,
|
||||
isOrgAdmin: false,
|
||||
isNotificationAdmin: false,
|
||||
isExecEnvAdmin: false,
|
||||
};
|
||||
});
|
||||
let wrapper;
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<ResourceAccessList resource={credential} apiModel={CredentialsAPI} />,
|
||||
{ context: { router: { credentialHistory } } }
|
||||
);
|
||||
});
|
||||
|
||||
await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
|
||||
wrapper.update();
|
||||
expect(wrapper.find('ToolbarAddButton').length).toEqual(0);
|
||||
});
|
||||
|
||||
test('should show add button for non system admin, org admin, credential admin for credentials associated with org', async () => {
|
||||
useUserProfile.mockImplementation(() => {
|
||||
return {
|
||||
isSuperUser: false,
|
||||
isSystemAuditor: false,
|
||||
isOrgAdmin: true,
|
||||
isNotificationAdmin: false,
|
||||
isExecEnvAdmin: false,
|
||||
};
|
||||
});
|
||||
let wrapper;
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<ResourceAccessList resource={credential} apiModel={CredentialsAPI} />,
|
||||
{ context: { router: { credentialHistory } } }
|
||||
);
|
||||
});
|
||||
|
||||
await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
|
||||
wrapper.update();
|
||||
expect(wrapper.find('ToolbarAddButton').length).toEqual(1);
|
||||
});
|
||||
|
||||
test('should not show add button for non system admin, org admin, credential admin for credentials non associated with org', async () => {
|
||||
useUserProfile.mockImplementation(() => {
|
||||
return {
|
||||
isSuperUser: false,
|
||||
isSystemAuditor: false,
|
||||
isOrgAdmin: true,
|
||||
isNotificationAdmin: false,
|
||||
isExecEnvAdmin: false,
|
||||
};
|
||||
});
|
||||
let wrapper;
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<ResourceAccessList
|
||||
resource={{ ...credential, organization: null }}
|
||||
apiModel={CredentialsAPI}
|
||||
/>,
|
||||
{ context: { router: { credentialHistory } } }
|
||||
);
|
||||
});
|
||||
|
||||
await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
|
||||
wrapper.update();
|
||||
expect(wrapper.find('ToolbarAddButton').length).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user