Skip to content

Commit

Permalink
feat(core): Allow transferring credentials in Public API (#10259)
Browse files Browse the repository at this point in the history
  • Loading branch information
ivov authored Aug 2, 2024
1 parent 489ce10 commit 07d7b24
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 5 deletions.
6 changes: 6 additions & 0 deletions packages/cli/src/PublicApi/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ export declare namespace CredentialRequest {
>;

type Delete = AuthenticatedRequest<{ id: string }, {}, {}, Record<string, string>>;

type Transfer = AuthenticatedRequest<
{ workflowId: string },
{},
{ destinationProjectId: string }
>;
}

export type OperationID = 'getUsers' | 'getUser';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {
toJsonSchema,
} from './credentials.service';
import { Container } from 'typedi';
import { z } from 'zod';
import { EnterpriseCredentialsService } from '@/credentials/credentials.service.ee';

export = {
createCredential: [
Expand All @@ -44,6 +46,20 @@ export = {
}
},
],
transferCredential: [
projectScope('credential:move', 'credential'),
async (req: CredentialRequest.Transfer, res: express.Response) => {
const body = z.object({ destinationProjectId: z.string() }).parse(req.body);

await Container.get(EnterpriseCredentialsService).transferOne(
req.user,
req.params.workflowId,
body.destinationProjectId,
);

res.status(204).send();
},
],
deleteCredential: [
projectScope('credential:delete', 'credential'),
async (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
put:
x-eov-operation-id: transferCredential
x-eov-operation-handler: v1/handlers/credentials/credentials.handler
tags:
- Workflow
summary: Transfer a credential to another project.
description: Transfer a credential to another project.
parameters:
- $ref: '../schemas/parameters/credentialId.yml'
requestBody:
description: Destination project for the credential transfer.
content:
application/json:
schema:
type: object
properties:
destinationProjectId:
type: string
description: The ID of the project to transfer the credential to.
required:
- destinationProjectId
required: true
responses:
'200':
description: Operation successful.
'400':
$ref: '../../../../shared/spec/responses/badRequest.yml'
'401':
$ref: '../../../../shared/spec/responses/unauthorized.yml'
'404':
$ref: '../../../../shared/spec/responses/notFound.yml'
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
name: id
in: path
description: The ID of the credential.
required: true
schema:
type: string
2 changes: 2 additions & 0 deletions packages/cli/src/PublicApi/v1/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ paths:
$ref: './handlers/workflows/spec/paths/workflows.id.deactivate.yml'
/workflows/{id}/transfer:
$ref: './handlers/workflows/spec/paths/workflows.id.transfer.yml'
/credentials/{id}/transfer:
$ref: './handlers/credentials/spec/paths/credentials.id.transfer.yml'
/workflows/{id}/tags:
$ref: './handlers/workflows/spec/paths/workflows.id.tags.yml'
/users:
Expand Down
50 changes: 49 additions & 1 deletion packages/cli/test/integration/publicApi/credentials.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import { randomApiKey, randomName } from '../shared/random';
import * as utils from '../shared/utils/';
import type { CredentialPayload, SaveCredentialFunction } from '../shared/types';
import * as testDb from '../shared/testDb';
import { affixRoleToSaveCredential } from '../shared/db/credentials';
import { affixRoleToSaveCredential, createCredentials } from '../shared/db/credentials';
import { addApiKey, createUser, createUserShell } from '../shared/db/users';
import type { SuperAgentTest } from '../shared/types';
import { createTeamProject } from '@test-integration/db/projects';

let owner: User;
let member: User;
Expand Down Expand Up @@ -256,6 +257,53 @@ describe('GET /credentials/schema/:credentialType', () => {
});
});

describe('PUT /credentials/:id/transfer', () => {
test('should transfer credential to project', async () => {
/**
* Arrange
*/
const [firstProject, secondProject] = await Promise.all([
createTeamProject('first-project', owner),
createTeamProject('second-project', owner),
]);

const credentials = await createCredentials(
{ name: 'Test', type: 'test', data: '' },
firstProject,
);

/**
* Act
*/
const response = await authOwnerAgent.put(`/credentials/${credentials.id}/transfer`).send({
destinationProjectId: secondProject.id,
});

/**
* Assert
*/
expect(response.statusCode).toBe(204);
});

test('if no destination project, should reject', async () => {
/**
* Arrange
*/
const project = await createTeamProject('first-project', member);
const credentials = await createCredentials({ name: 'Test', type: 'test', data: '' }, project);

/**
* Act
*/
const response = await authOwnerAgent.put(`/credentials/${credentials.id}/transfer`).send({});

/**
* Assert
*/
expect(response.statusCode).toBe(400);
});
});

const credentialPayload = (): CredentialPayload => ({
name: randomName(),
type: 'githubApi',
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/test/integration/publicApi/workflows.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1493,7 +1493,7 @@ describe('PUT /workflows/:id/transfer', () => {
* Arrange
*/
const firstProject = await createTeamProject('first-project', member);
const secondProject = await createTeamProject('secon-project', member);
const secondProject = await createTeamProject('second-project', member);
const workflow = await createWorkflow({}, firstProject);

/**
Expand Down
19 changes: 16 additions & 3 deletions packages/cli/test/integration/shared/db/credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,24 @@ export async function createManyCredentials(
);
}

export async function createCredentials(attributes: Partial<CredentialsEntity> = emptyAttributes) {
export async function createCredentials(
attributes: Partial<CredentialsEntity> = emptyAttributes,
project?: Project,
) {
const credentialsRepository = Container.get(CredentialsRepository);
const entity = credentialsRepository.create(attributes);
const credentials = await credentialsRepository.save(credentialsRepository.create(attributes));

if (project) {
await Container.get(SharedCredentialsRepository).save(
Container.get(SharedCredentialsRepository).create({
project,
credentials,
role: 'credential:owner',
}),
);
}

return await credentialsRepository.save(entity);
return credentials;
}

/**
Expand Down

0 comments on commit 07d7b24

Please sign in to comment.