console/integration-tests/tests/api/access-tokens/project.spec.ts

489 lines
14 KiB
TypeScript

import { graphql } from '../../../testkit/gql';
import * as GraphQLSchema from '../../../testkit/gql/graphql';
import { execute } from '../../../testkit/graphql';
import { initSeed } from '../../../testkit/seed';
import { deleteAccessToken, fetchPermissions } from './shared';
const CreateProjectAccessTokenMutation = graphql(`
mutation CreateProjectAccessTokenMutation($input: CreateProjectAccessTokenInput!) {
createProjectAccessToken(input: $input) {
ok {
privateAccessKey
createdProjectAccessToken {
id
title
description
createdAt
}
}
error {
message
details {
title
description
}
}
}
}
`);
const SimpleProjectQuery = graphql(`
query ProjectAccessTokensProjectTestQuery($projectId: ID!) {
project(reference: { byId: $projectId }) {
id
slug
}
}
`);
test.concurrent('create: success with admin session', async ({ expect }) => {
const { createOrg, ownerToken } = await initSeed().createOwner();
const org = await createOrg();
const { project } = await org.createProject(GraphQLSchema.ProjectType.Federation);
const result = await execute({
document: CreateProjectAccessTokenMutation,
variables: {
input: {
project: {
byId: project.id,
},
title: 'an access token',
description: 'Some description',
resources: { mode: GraphQLSchema.ResourceAssignmentModeType.All },
permissions: [],
},
},
authToken: ownerToken,
}).then(e => e.expectNoGraphQLErrors());
expect(result.createProjectAccessToken.error).toEqual(null);
expect(result.createProjectAccessToken.ok).toEqual({
privateAccessKey: expect.any(String),
createdProjectAccessToken: {
id: expect.any(String),
title: 'an access token',
description: 'Some description',
createdAt: expect.any(String),
},
});
// no permissions :D
expect(await fetchPermissions(result.createProjectAccessToken.ok!.privateAccessKey)).toEqual([]);
});
test.concurrent('create: failure invalid title', async ({ expect }) => {
const { createOrg, ownerToken } = await initSeed().createOwner();
const org = await createOrg();
const { project } = await org.createProject(GraphQLSchema.ProjectType.Federation);
const result = await execute({
document: CreateProjectAccessTokenMutation,
variables: {
input: {
project: {
byId: project.id,
},
title: ' ',
description: 'Some description',
resources: { mode: GraphQLSchema.ResourceAssignmentModeType.All },
permissions: [],
},
},
authToken: ownerToken,
}).then(e => e.expectNoGraphQLErrors());
expect(result.createProjectAccessToken.ok).toEqual(null);
expect(result.createProjectAccessToken.error).toMatchInlineSnapshot(`
{
details: {
description: null,
title: Can only contain letters, numbers, " ", "_", and "-".,
},
message: Invalid input provided.,
}
`);
});
test.concurrent('create: failure invalid description', async ({ expect }) => {
const { createOrg, ownerToken } = await initSeed().createOwner();
const org = await createOrg();
const { project } = await org.createProject(GraphQLSchema.ProjectType.Federation);
const result = await execute({
document: CreateProjectAccessTokenMutation,
variables: {
input: {
project: {
byId: project.id,
},
title: 'an access token',
description: new Array(300).fill('A').join(''),
resources: { mode: GraphQLSchema.ResourceAssignmentModeType.All },
permissions: [],
},
},
authToken: ownerToken,
}).then(e => e.expectNoGraphQLErrors());
expect(result.createProjectAccessToken.ok).toEqual(null);
expect(result.createProjectAccessToken.error).toMatchInlineSnapshot(`
{
details: {
description: Maximum length is 248 characters.,
title: null,
},
message: Invalid input provided.,
}
`);
});
test.concurrent('create: failure because no access to project', async ({ expect }) => {
const actor1 = await initSeed().createOwner();
const actor2 = await initSeed().createOwner();
const org = await actor1.createOrg();
const { project } = await org.createProject(GraphQLSchema.ProjectType.Federation);
const errors = await execute({
document: CreateProjectAccessTokenMutation,
variables: {
input: {
project: {
byId: project.id,
},
title: 'an access token',
description: 'Some description',
resources: { mode: GraphQLSchema.ResourceAssignmentModeType.All },
permissions: [],
},
},
authToken: actor2.ownerToken,
}).then(e => e.expectGraphQLErrors());
expect(errors).toMatchObject([
{
extensions: {
code: 'UNAUTHORISED',
},
message: `No access (reason: "Missing permission for performing 'projectAccessToken:modify' on resource")`,
path: ['createProjectAccessToken'],
},
]);
});
test.concurrent(
'create: fail because of missing "projectAccessToken:modify" permission.',
async ({ expect }) => {
const { createOrg } = await initSeed().createOwner();
const org = await createOrg();
const { member, createMemberRole, assignMemberRole, memberToken } =
await org.inviteAndJoinMember();
const memberRole = await createMemberRole(['organization:describe', 'project:describe']);
await assignMemberRole({
roleId: memberRole.id,
userId: member.id,
});
const { project } = await org.createProject(GraphQLSchema.ProjectType.Federation);
const errors = await execute({
document: CreateProjectAccessTokenMutation,
variables: {
input: {
project: {
byId: project.id,
},
title: 'an access token',
description: 'Some description',
resources: { mode: GraphQLSchema.ResourceAssignmentModeType.All },
permissions: [],
},
},
authToken: memberToken,
}).then(e => e.expectGraphQLErrors());
expect(errors).toMatchObject([
{
extensions: {
code: 'UNAUTHORISED',
},
message: `No access (reason: "Missing permission for performing 'projectAccessToken:modify' on resource")`,
path: ['createProjectAccessToken'],
},
]);
},
);
test.concurrent('query GraphQL API on resources with access', async ({ expect }) => {
const { createOrg } = await initSeed().createOwner();
const org = await createOrg();
const {
member,
createMemberRole,
assignMemberRole,
memberToken: authToken,
} = await org.inviteAndJoinMember();
const memberRole = await createMemberRole([
'organization:describe',
'project:describe',
'projectAccessToken:modify',
]);
await assignMemberRole({
roleId: memberRole.id,
userId: member.id,
});
const { project } = await org.createProject(GraphQLSchema.ProjectType.Federation);
const { project: project1 } = await org.createProject(GraphQLSchema.ProjectType.Federation);
const result = await execute({
document: CreateProjectAccessTokenMutation,
variables: {
input: {
project: {
byId: project.id,
},
title: 'an access token',
description: 'a description',
resources: { mode: GraphQLSchema.ResourceAssignmentModeType.All },
permissions: ['project:describe'],
},
},
authToken,
}).then(e => e.expectNoGraphQLErrors());
expect(result.createProjectAccessToken.error).toEqual(null);
const organizationAccessToken = result.createProjectAccessToken.ok!.privateAccessKey;
expect(organizationAccessToken).toMatch(/^hvp1\//);
expect(await fetchPermissions(organizationAccessToken)).toEqual([
{
level: 'PROJECT',
resolvedPermissionGroups: [
{
permissions: [
{
permission: {
id: 'project:describe',
},
},
],
},
],
resolvedResourceIds: [`${org.organization.slug}/${project.slug}`],
},
]);
const projectQuery = await execute({
document: SimpleProjectQuery,
variables: {
projectId: project.id,
},
authToken: organizationAccessToken,
}).then(e => e.expectNoGraphQLErrors());
expect(projectQuery).toEqual({
project: {
id: expect.any(String),
slug: project.slug,
},
});
const errors = await execute({
document: SimpleProjectQuery,
variables: {
projectId: project1.id,
},
authToken: organizationAccessToken,
}).then(e => e.expectGraphQLErrors());
expect(errors).toMatchObject([
{
extensions: {
code: 'UNAUTHORISED',
},
message: `No access (reason: "Missing permission for performing 'project:describe' on resource")`,
path: ['project'],
},
]);
});
test.concurrent('delete access token revokes access', async ({ expect }) => {
const { createOrg } = await initSeed().createOwner();
const org = await createOrg();
const {
member,
createMemberRole,
assignMemberRole,
memberToken: authToken,
} = await org.inviteAndJoinMember();
const memberRole = await createMemberRole([
'organization:describe',
'project:describe',
'projectAccessToken:modify',
]);
await assignMemberRole({
roleId: memberRole.id,
userId: member.id,
});
const { project } = await org.createProject(GraphQLSchema.ProjectType.Federation);
const createProjectAccessTokenResult = await execute({
document: CreateProjectAccessTokenMutation,
variables: {
input: {
project: {
byId: project.id,
},
title: 'an access token',
description: 'a description',
resources: { mode: GraphQLSchema.ResourceAssignmentModeType.All },
permissions: ['project:describe'],
},
},
authToken,
}).then(e => e.expectNoGraphQLErrors());
expect(createProjectAccessTokenResult.createProjectAccessToken.error).toEqual(null);
const accessToken = createProjectAccessTokenResult.createProjectAccessToken.ok!;
expect(await fetchPermissions(accessToken.privateAccessKey)).toEqual([
{
level: 'PROJECT',
resolvedPermissionGroups: [
{
permissions: [
{
permission: {
id: 'project:describe',
},
},
],
},
],
resolvedResourceIds: [`${org.organization.slug}/${project.slug}`],
},
]);
const deleteAccessTokenResult = await deleteAccessToken(
accessToken.createdProjectAccessToken.id,
authToken,
).then(res => res.expectNoGraphQLErrors());
expect(deleteAccessTokenResult).toEqual({
deleteAccessToken: {
error: null,
ok: {
deletedAccessTokenId: accessToken.createdProjectAccessToken.id,
},
},
});
const errors = await execute({
document: SimpleProjectQuery,
variables: {
projectId: project.id,
},
authToken: accessToken.privateAccessKey,
}).then(e => e.expectGraphQLErrors());
expect(errors).toMatchObject([
{
message: 'Invalid token provided',
},
]);
});
test.concurrent('cannot delete access token without sufficient permissions', async ({ expect }) => {
const { createOrg, ownerToken } = await initSeed().createOwner();
const org = await createOrg();
const { member, createMemberRole, assignMemberRole, memberToken } =
await org.inviteAndJoinMember();
const memberRole = await createMemberRole(['organization:describe', 'project:describe']);
await assignMemberRole({
roleId: memberRole.id,
userId: member.id,
});
const { project } = await org.createProject(GraphQLSchema.ProjectType.Federation);
const createProjectAccessTokenResult = await execute({
document: CreateProjectAccessTokenMutation,
variables: {
input: {
project: {
byId: project.id,
},
title: 'an access token',
description: 'a description',
resources: { mode: GraphQLSchema.ResourceAssignmentModeType.All },
permissions: ['project:describe'],
},
},
authToken: ownerToken,
}).then(e => e.expectNoGraphQLErrors());
expect(createProjectAccessTokenResult.createProjectAccessToken.error).toEqual(null);
const accessToken = createProjectAccessTokenResult.createProjectAccessToken.ok!;
const errors = await deleteAccessToken(
accessToken.createdProjectAccessToken.id,
memberToken,
).then(res => res.expectGraphQLErrors());
expect(errors).toMatchObject([
{
extensions: {
code: 'UNAUTHORISED',
},
message: `No access (reason: "Missing permission for performing 'projectAccessToken:modify' on resource")`,
path: ['deleteAccessToken'],
},
]);
});
test.concurrent(
'can delete access token without "accessToken:modify" permissions',
async ({ expect }) => {
const { createOrg, ownerToken } = await initSeed().createOwner();
const org = await createOrg();
const { member, createMemberRole, assignMemberRole, memberToken } =
await org.inviteAndJoinMember();
const memberRole = await createMemberRole([
'organization:describe',
'project:describe',
'accessToken:modify',
]);
await assignMemberRole({
roleId: memberRole.id,
userId: member.id,
});
const { project } = await org.createProject(GraphQLSchema.ProjectType.Federation);
const createProjectAccessTokenResult = await execute({
document: CreateProjectAccessTokenMutation,
variables: {
input: {
project: {
byId: project.id,
},
title: 'an access token',
description: 'a description',
resources: { mode: GraphQLSchema.ResourceAssignmentModeType.All },
permissions: ['project:describe'],
},
},
authToken: ownerToken,
}).then(e => e.expectNoGraphQLErrors());
expect(createProjectAccessTokenResult.createProjectAccessToken.error).toEqual(null);
const accessToken = createProjectAccessTokenResult.createProjectAccessToken.ok!;
const deleteAccessTokenResult = await deleteAccessToken(
accessToken.createdProjectAccessToken.id,
memberToken,
).then(res => res.expectNoGraphQLErrors());
expect(deleteAccessTokenResult).toEqual({
deleteAccessToken: {
error: null,
ok: {
deletedAccessTokenId: accessToken.createdProjectAccessToken.id,
},
},
});
},
);