Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

upcoming: [M3-7413] - Update existing user account endpoints and mocks for Parent/Child Account Switching #9942

Merged
merged 9 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 16 additions & 12 deletions packages/api-v4/src/account/types.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,10 @@
import { APIWarning } from '../types';
import type { Capabilities, Region } from '../regions';

export type UserType = 'child' | 'parent' | 'proxy';

export interface User {
username: string;
email: string;
restricted: boolean;
ssh_keys: string[];
tfa_enabled: boolean;
verified_phone_number: string | null;
/**
* The date of when a password was set on a user.
* `null` if this user has not created a password yet
* @example 2022-02-09T16:19:26
* @example null
*/
password_created: string | null;
/**
* Information for the most recent login attempt for this User.
* `null` if no login attempts have been made since creation of this User.
Expand All @@ -29,6 +19,19 @@ export interface User {
*/
status: AccountLoginStatus;
} | null;
/**
* The date of when a password was set on a user.
* `null` if this user has not created a password yet
* @example 2022-02-09T16:19:26
* @example null
*/
password_created: string | null;
restricted: boolean;
ssh_keys: string[];
tfa_enabled: boolean;
username: string;
user_type: UserType | null;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the new user_type field. All of the other changes in the User interface were just reordering.

verified_phone_number: string | null;
}

export interface Account {
Expand Down Expand Up @@ -169,6 +172,7 @@ export type GlobalGrantTypes =
| 'add_longview'
| 'longview_subscription'
| 'account_access'
| 'child_account_access'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the new child_account_access grant that returns true for all admin parent accounts and any additional parent accounts that the admin parent grants permission to access the child accounts.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not that I am very privy to this architecture, but I was expecting an array of child accounts the parent has access to instead of just a boolean. I know it's handled in another way but just putting this thought out there for posterity.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The array of child accounts will be returned by a new API endpoint, GET /account/child-accounts! This boolean is a new global grant that allows an unrestricted parent account to decide whether or not any other users associated with that parent account (i.e. non-admin, potentially restricted parent account users) will also have access to the data returned in GET /account/child-accounts.

| 'cancel_account'
| 'add_domains'
| 'add_stackscripts'
Expand Down
1 change: 1 addition & 0 deletions packages/manager/src/factories/accountUsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const accountUserFactory = Factory.Sync.makeFactory<User>({
restricted: true,
ssh_keys: [],
tfa_enabled: false,
user_type: null,
username: 'user',
verified_phone_number: null,
});
1 change: 1 addition & 0 deletions packages/manager/src/factories/grants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const grantsFactory = Factory.Sync.makeFactory<Grants>({
add_volumes: true,
add_vpcs: true,
cancel_account: false,
child_account_access: false,
longview_subscription: true,
},
image: [
Expand Down
93 changes: 89 additions & 4 deletions packages/manager/src/mocks/serverHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,27 @@ const standardTypes = linodeTypeFactory.buildList(7);
const dedicatedTypes = dedicatedTypeFactory.buildList(7);
const proDedicatedType = proDedicatedTypeFactory.build();

const proxyAccount = accountUserFactory.build({
email: 'partner@proxy.com',
last_login: null,
user_type: 'proxy',
username: 'ParentCompany_a1b2c3d4e5',
});
const parentAccount = accountUserFactory.build({
email: 'parent@acme.com',
last_login: null,
restricted: false,
user_type: 'parent',
username: 'ParentUser',
});
const childAccount = accountUserFactory.build({
email: 'child@linode.com',
last_login: null,
restricted: false,
user_type: 'child',
username: 'ChildUser',
});

export const handlers = [
rest.get('*/profile', (req, res, ctx) => {
const profile = profileFactory.build({
Expand Down Expand Up @@ -1125,22 +1146,86 @@ export const handlers = [
rest.get('*/account/users', (req, res, ctx) => {
const accountUsers = [
accountUserFactory.build({
last_login: { status: 'failed', login_datetime: '2023-10-16T17:04' },
last_login: { login_datetime: '2023-10-16T17:04', status: 'failed' },
tfa_enabled: true,
}),
accountUserFactory.build({
last_login: {
status: 'successful',
login_datetime: '2023-10-06T12:04',
status: 'successful',
},
}),
accountUserFactory.build({ last_login: null }),
childAccount,
parentAccount,
proxyAccount,
];
return res(ctx.json(makeResourcePage(accountUsers)));
}),
rest.get(`*/account/users/${childAccount.username}`, (req, res, ctx) => {
return res(ctx.json(childAccount));
}),
rest.get(`*/account/users/${proxyAccount.username}`, (req, res, ctx) => {
return res(ctx.json(proxyAccount));
}),
rest.get(`*/account/users/${parentAccount.username}`, (req, res, ctx) => {
return res(ctx.json(parentAccount));
}),
rest.get('*/account/users/:user', (req, res, ctx) => {
return res(ctx.json(profileFactory.build()));
return res(ctx.json(accountUserFactory.build()));
Comment on lines -1142 to +1175
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was an existing bug... The mock request to*/account/users/:user was returning profile instead of account data.

}),
rest.get(
`*/account/users/${childAccount.username}/grants`,
(req, res, ctx) => {
return res(
ctx.json(
grantsFactory.build({
global: {
cancel_account: false,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Child accounts cannot be canceled while they have a user_type of "child".

},
})
)
);
}
),
rest.get(
`*/account/users/${proxyAccount.username}/grants`,
(req, res, ctx) => {
return res(
ctx.json(
grantsFactory.build({
global: {
add_domains: false,
add_firewalls: false,
add_images: false,
add_linodes: false,
add_longview: false,
add_nodebalancers: false,
add_stackscripts: false,
add_volumes: false,
add_vpcs: false,
cancel_account: false,
Comment on lines +1198 to +1207
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Proxy accounts have read only permissions by default and cannot be canceled.

longview_subscription: false,
},
})
)
);
}
),
rest.get(
`*/account/users/${parentAccount.username}/grants`,
(req, res, ctx) => {
return res(
ctx.json(
grantsFactory.build({
global: {
cancel_account: false,
child_account_access: true,
Comment on lines +1222 to +1223
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parent accounts cannot be canceled while they have a user_type of "parent". This "admin" parent account has default child_account_access granted.

},
})
)
);
}
),
rest.get('*/account/users/:user/grants', (req, res, ctx) => {
return res(
ctx.json(
Expand Down