Skip to content

Commit

Permalink
upcoming: [M3-7647] - Update logic for child_account PAT (#10083)
Browse files Browse the repository at this point in the history
Co-authored-by: Jaalah Ramos <jaalah.ramos@gmail.com>
  • Loading branch information
jaalah-akamai and jaalah authored Jan 23, 2024
1 parent 9131416 commit 3c31836
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 138 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import {
basePermNameMap as _basePermNameMap,
Permission,
allScopesAreTheSame,
getPermsNameMap,
filterPermsNameMap,
permTuplesToScopeString,
scopeStringToPermTuples,
} from './utils';
Expand Down Expand Up @@ -113,12 +113,20 @@ export const CreateAPITokenDrawer = (props: Props) => {
account?.capabilities ?? []
);

// @TODO VPC: once VPC enters GA, remove _basePermNameMap logic and references.
const hasParentChildAccountAccess = Boolean(flags.parentChildAccountAccess);

// @TODO: VPC & Parent/Child - once these are in GA, remove _basePermNameMap logic and references.
// Just use the basePermNameMap import directly w/o any manipulation.
const basePermNameMap = getPermsNameMap(_basePermNameMap, {
name: 'vpc',
shouldBeIncluded: showVPCs,
});
const basePermNameMap = filterPermsNameMap(_basePermNameMap, [
{
name: 'vpc',
shouldBeIncluded: showVPCs,
},
{
name: 'child_account',
shouldBeIncluded: hasParentChildAccountAccess,
},
]);

const form = useFormik<{
expiry: string;
Expand Down Expand Up @@ -180,24 +188,14 @@ export const CreateAPITokenDrawer = (props: Props) => {

// Filter permissions for all users except parent user accounts.
const allPermissions = form.values.scopes;

const showFilteredPermissions =
(flags.parentChildAccountAccess && user?.user_type !== 'parent') ||
Boolean(!flags.parentChildAccountAccess);

const filteredPermissions = allPermissions.filter(
(scopeTup) => basePermNameMap[scopeTup[0]] !== 'Child Account Access'
);
// TODO: Parent/Child - remove this conditional once code is in prod.
// Note: We couldn't include 'child_account' in our list of permissions in utils
// because it needs to be feature-flagged. Therefore, we're manually adding it here.
if (flags.parentChildAccountAccess && user?.user_type !== null) {
const childAccountIndex = allPermissions.findIndex(
([scope]) => scope === 'child_account'
);
if (childAccountIndex === -1) {
allPermissions.push(['child_account', 0]);
}
basePermNameMap.child_account = 'Child Account Access';
}

return (
<Drawer onClose={onClose} open={open} title="Add Personal Access Token">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
import * as React from 'react';

import { appTokenFactory } from 'src/factories';
import { accountUserFactory } from 'src/factories/accountUsers';
import { profileFactory } from 'src/factories';
import { renderWithTheme } from 'src/utilities/testHelpers';

import { ViewAPITokenDrawer } from './ViewAPITokenDrawer';
import { basePerms } from './utils';

// Mock the useAccountUser hooks to immediately return the expected data, circumventing the HTTP request and loading state.
import type { UserType } from '@linode/api-v4';

// Mock the useProfile hooks to immediately return the expected data, circumventing the HTTP request and loading state.
const queryMocks = vi.hoisted(() => ({
useAccountUser: vi.fn().mockReturnValue({}),
useProfile: vi.fn().mockReturnValue({}),
}));

vi.mock('src/queries/accountUsers', async () => {
const actual = await vi.importActual<any>('src/queries/accountUsers');
vi.mock('src/queries/profile', async () => {
const actual = await vi.importActual<any>('src/queries/profile');
return {
...actual,
useAccountUser: queryMocks.useAccountUser,
useProfile: queryMocks.useProfile,
};
});

Expand Down Expand Up @@ -45,8 +47,13 @@ describe('View API Token Drawer', () => {
});

it('should show all permissions as read/write with wildcard scopes', () => {
// We want to show all perms for this test, even perms specific to Parent/Child accounts.
queryMocks.useProfile.mockReturnValue({
data: profileFactory.build({ user_type: 'parent' }),
});

const { getByTestId } = renderWithTheme(<ViewAPITokenDrawer {...props} />, {
flags: { vpc: true },
flags: { parentChildAccountAccess: true, vpc: true },
});
for (const permissionName of basePerms) {
expect(getByTestId(`perm-${permissionName}`)).toHaveAttribute(
Expand All @@ -57,9 +64,14 @@ describe('View API Token Drawer', () => {
});

it('should show all permissions as none with no scopes', () => {
// We want to show all perms for this test, even perms specific to Parent/Child accounts.
queryMocks.useProfile.mockReturnValue({
data: profileFactory.build({ user_type: 'parent' }),
});

const { getByTestId } = renderWithTheme(
<ViewAPITokenDrawer {...props} token={limitedToken} />,
{ flags: { parentChildAccountAccess: false, vpc: true } }
{ flags: { parentChildAccountAccess: true, vpc: true } }
);
for (const permissionName of basePerms) {
expect(getByTestId(`perm-${permissionName}`)).toHaveAttribute(
Expand All @@ -70,12 +82,17 @@ describe('View API Token Drawer', () => {
});

it('only account has read/write, all others are none', () => {
// We want to show all perms for this test, even perms specific to Parent/Child accounts.
queryMocks.useProfile.mockReturnValue({
data: profileFactory.build({ user_type: 'parent' }),
});

const { getByTestId } = renderWithTheme(
<ViewAPITokenDrawer
{...props}
token={appTokenFactory.build({ scopes: 'account:read_write' })}
/>,
{ flags: { vpc: true } }
{ flags: { parentChildAccountAccess: true, vpc: true } }
);
for (const permissionName of basePerms) {
// We only expect account to have read/write for this test
Expand All @@ -88,19 +105,25 @@ describe('View API Token Drawer', () => {
});

it('check table for more complex permissions', () => {
// We want to show all perms for this test, even perms specific to Parent/Child accounts.
queryMocks.useProfile.mockReturnValue({
data: profileFactory.build({ user_type: 'parent' }),
});

const { getByTestId } = renderWithTheme(
<ViewAPITokenDrawer
{...props}
token={appTokenFactory.build({
scopes:
'databases:read_only domains:read_write events:read_write firewall:read_write images:read_write ips:read_write linodes:read_only lke:read_only longview:read_write nodebalancers:read_write object_storage:read_only stackscripts:read_write volumes:read_only vpc:read_write',
'databases:read_only domains:read_write child_account:read_write events:read_write firewall:read_write images:read_write ips:read_write linodes:read_only lke:read_only longview:read_write nodebalancers:read_write object_storage:read_only stackscripts:read_write volumes:read_only vpc:read_write',
})}
/>,
{ flags: { vpc: true } }
{ flags: { parentChildAccountAccess: true, vpc: true } }
);

const expectedScopeLevels = {
account: 0,
child_account: 2,
databases: 1,
domains: 2,
events: 2,
Expand All @@ -126,50 +149,6 @@ describe('View API Token Drawer', () => {
}
});

it('should show Child Account Access scope with read/write perms for a parent user account with the parent/child feature flag on', () => {
queryMocks.useAccountUser.mockReturnValue({
data: accountUserFactory.build({ user_type: 'parent' }),
});

const { getByTestId, getByText } = renderWithTheme(
<ViewAPITokenDrawer
{...props}
token={appTokenFactory.build({
scopes: 'child_account:read_write',
})}
/>,
{
flags: { parentChildAccountAccess: true },
}
);

const childScope = getByText('Child Account Access');
// TODO: Parent/Child - confirm that this scope level shouldn't be 2
const expectedScopeLevels = {
child_account: 0,
} as const;
const childPermissionName = 'child_account';

expect(childScope).toBeInTheDocument();
expect(getByTestId(`perm-${childPermissionName}`)).toHaveAttribute(
ariaLabel,
`This token has ${expectedScopeLevels[childPermissionName]} access for ${childPermissionName}`
);
});

it('should not show the Child Account Access scope for a non-parent user account with the parent/child feature flag on', () => {
queryMocks.useAccountUser.mockReturnValue({
data: accountUserFactory.build({ user_type: null }),
});

const { queryByText } = renderWithTheme(<ViewAPITokenDrawer {...props} />, {
flags: { parentChildAccountAccess: true },
});

const childScope = queryByText('Child Account Access');
expect(childScope).not.toBeInTheDocument();
});

it('Should show the VPC scope with the VPC feature flag on', () => {
const { getByText } = renderWithTheme(<ViewAPITokenDrawer {...props} />, {
flags: { vpc: true },
Expand All @@ -186,4 +165,44 @@ describe('View API Token Drawer', () => {
const vpcScope = queryByText('VPCs');
expect(vpcScope).not.toBeInTheDocument();
});

describe('Parent/Child: User Roles', () => {
const setupAndRender = (
userType: UserType | null,
enableFeatureFlag = true
) => {
queryMocks.useProfile.mockReturnValue({
data: profileFactory.build({ user_type: userType }),
});

return renderWithTheme(<ViewAPITokenDrawer {...props} />, {
flags: { parentChildAccountAccess: enableFeatureFlag },
});
};

const testChildScopeNotDisplayed = (
userType: UserType | null,
enableFeatureFlag = true
) => {
const { queryByText } = setupAndRender(userType, enableFeatureFlag);
const childScope = queryByText('Child Account Access');
expect(childScope).not.toBeInTheDocument();
};

it('should not display the Child Account Access when feature flag is disabled', () => {
testChildScopeNotDisplayed('parent', false);
});

it('should not display the Child Account Access scope for a user account without a parent uer type', () => {
testChildScopeNotDisplayed(null);
});

it('should not display the Child Account Access scope for "proxy" user type', () => {
testChildScopeNotDisplayed('proxy');
});

it('should not display the Child Account Access scope for "child" user type', () => {
testChildScopeNotDisplayed('child');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { TableRow } from 'src/components/TableRow';
import { AccessCell } from 'src/features/ObjectStorage/AccessKeyLanding/AccessCell';
import { useFlags } from 'src/hooks/useFlags';
import { useAccount } from 'src/queries/account';
import { useAccountUser } from 'src/queries/accountUsers';
import { useProfile } from 'src/queries/profile';
import { isFeatureEnabled } from 'src/utilities/accountCapabilities';

Expand All @@ -20,7 +19,7 @@ import {
} from './APITokenDrawer.styles';
import {
basePermNameMap as _basePermNameMap,
getPermsNameMap,
filterPermsNameMap,
scopeStringToPermTuples,
} from './utils';

Expand All @@ -37,42 +36,38 @@ export const ViewAPITokenDrawer = (props: Props) => {

const { data: profile } = useProfile();
const { data: account } = useAccount();
const { data: user } = useAccountUser(profile?.username ?? '');

const showVPCs = isFeatureEnabled(
'VPCs',
Boolean(flags.vpc),
account?.capabilities ?? []
);

// @TODO VPC: once VPC enters GA, remove _basePermNameMap logic and references.
const hasParentChildAccountAccess = Boolean(flags.parentChildAccountAccess);

// @TODO: VPC & Parent/Child - once these are in GA, remove _basePermNameMap logic and references.
// Just use the basePermNameMap import directly w/o any manipulation.
const basePermNameMap = getPermsNameMap(_basePermNameMap, {
name: 'vpc',
shouldBeIncluded: showVPCs,
});
const basePermNameMap = filterPermsNameMap(_basePermNameMap, [
{
name: 'vpc',
shouldBeIncluded: showVPCs,
},
{
name: 'child_account',
shouldBeIncluded: hasParentChildAccountAccess,
},
]);

const allPermissions = scopeStringToPermTuples(token?.scopes ?? '');

// Filter permissions for all users except parent user accounts.
const showFilteredPermissions =
(flags.parentChildAccountAccess && user?.user_type !== 'parent') ||
(flags.parentChildAccountAccess && profile?.user_type !== 'parent') ||
Boolean(!flags.parentChildAccountAccess);

const filteredPermissions = allPermissions.filter(
(scopeTup) => basePermNameMap[scopeTup[0]] !== 'Child Account Access'
);
// TODO: Parent/Child - remove this conditional once code is in prod.
// Note: We couldn't include 'child_account' in our list of permissions in utils
// because it needs to be feature-flagged. Therefore, we're manually adding it here.
if (flags.parentChildAccountAccess && user?.user_type !== null) {
const childAccountIndex = allPermissions.findIndex(
([scope]) => scope === 'child_account'
);
if (childAccountIndex === -1) {
allPermissions.push(['child_account', 0]);
}
basePermNameMap.child_account = 'Child Account Access';
}

return (
<Drawer onClose={onClose} open={open} title={token?.label ?? 'Token'}>
Expand Down
Loading

0 comments on commit 3c31836

Please sign in to comment.