Skip to content

Commit

Permalink
feat: Allow sharing to and from team projects (no-changelog)
Browse files Browse the repository at this point in the history
  • Loading branch information
valya committed Jul 22, 2024
1 parent c613185 commit 6b7928d
Show file tree
Hide file tree
Showing 5 changed files with 268 additions and 33 deletions.
22 changes: 12 additions & 10 deletions packages/cli/src/credentials/credentials.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,25 +308,22 @@ export class CredentialsController {
let newShareeIds: string[] = [];

await Db.transaction(async (trx) => {
const currentPersonalProjectIDs = credential.shared
const currentProjectIDs = credential.shared
.filter((sc) => sc.role === 'credential:user')
.map((sc) => sc.projectId);
const newPersonalProjectIds = shareWithIds;
const newProjectIds = shareWithIds;

const toShare = utils.rightDiff(
[currentPersonalProjectIDs, (id) => id],
[newPersonalProjectIds, (id) => id],
);
const toShare = utils.rightDiff([currentProjectIDs, (id) => id], [newProjectIds, (id) => id]);
const toUnshare = utils.rightDiff(
[newPersonalProjectIds, (id) => id],
[currentPersonalProjectIDs, (id) => id],
[newProjectIds, (id) => id],
[currentProjectIDs, (id) => id],
);

const deleteResult = await trx.delete(SharedCredentials, {
credentialsId: credentialId,
projectId: In(toUnshare),
});
await this.enterpriseCredentialsService.shareWithProjects(credential, toShare, trx);
await this.enterpriseCredentialsService.shareWithProjects(req.user, credential, toShare, trx);

if (deleteResult.affected) {
amountRemoved = deleteResult.affected;
Expand Down Expand Up @@ -369,12 +366,17 @@ export class CredentialsController {
@Put('/:credentialId/transfer')
@ProjectScope('credential:move')
async transfer(req: CredentialRequest.Transfer) {
const body = z.object({ destinationProjectId: z.string() }).parse(req.body);
// TODO: make shareWithSource non-optional once the frontend
// has support
const body = z
.object({ destinationProjectId: z.string(), shareWithSource: z.boolean().optional() })
.parse(req.body);

return await this.enterpriseCredentialsService.transferOne(
req.user,
req.params.credentialId,
body.destinationProjectId,
body.shareWithSource ?? false,
);
}
}
70 changes: 56 additions & 14 deletions packages/cli/src/credentials/credentials.service.ee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Project } from '@/databases/entities/Project';
import { ProjectService } from '@/services/project.service';
import { TransferCredentialError } from '@/errors/response-errors/transfer-credential.error';
import { SharedCredentials } from '@/databases/entities/SharedCredentials';
import { RoleService } from '@/services/role.service';

@Service()
export class EnterpriseCredentialsService {
Expand All @@ -20,29 +21,47 @@ export class EnterpriseCredentialsService {
private readonly ownershipService: OwnershipService,
private readonly credentialsService: CredentialsService,
private readonly projectService: ProjectService,
private readonly roleService: RoleService,
) {}

async shareWithProjects(
user: User,
credential: CredentialsEntity,
shareWithIds: string[],
entityManager?: EntityManager,
) {
const em = entityManager ?? this.sharedCredentialsRepository.manager;

const projects = await em.find(Project, {
where: { id: In(shareWithIds), type: 'personal' },
where: [
{
id: In(shareWithIds),
type: 'team',
// if user can see all projects, don't check project access
// if they don't, find projects they can list
...(user.hasGlobalScope('project:list')
? {}
: {
projectRelations: {
userId: user.id,
role: In(this.roleService.rolesWithScope('project', 'project:list')),
},
}),
},
{
id: In(shareWithIds),
type: 'personal',
},
],
});

const newSharedCredentials = projects
// We filter by role === 'project:personalOwner' above and there should
// always only be one owner.
.map((project) =>
this.sharedCredentialsRepository.create({
credentialsId: credential.id,
role: 'credential:user',
projectId: project.id,
}),
);
const newSharedCredentials = projects.map((project) =>
this.sharedCredentialsRepository.create({
credentialsId: credential.id,
role: 'credential:user',
projectId: project.id,
}),
);

return await em.save(newSharedCredentials);
}
Expand Down Expand Up @@ -97,7 +116,12 @@ export class EnterpriseCredentialsService {
return { ...rest };
}

async transferOne(user: User, credentialId: string, destinationProjectId: string) {
async transferOne(
user: User,
credentialId: string,
destinationProjectId: string,
shareWithSource: boolean,
) {
// 1. get credential
const credential = await this.sharedCredentialsRepository.findCredentialForUser(
credentialId,
Expand Down Expand Up @@ -147,8 +171,26 @@ export class EnterpriseCredentialsService {

await this.sharedCredentialsRepository.manager.transaction(async (trx) => {
// 6. transfer the credential
// remove all sharings
await trx.remove(credential.shared);

// remove original owner sharing
await trx.remove(ownerSharing);

// share it back as a user if asked to
if (shareWithSource) {
await trx.save(
trx.create(SharedCredentials, {
credentialsId: credential.id,
projectId: sourceProject.id,
role: 'credential:user',
}),
);
}

// remove any previous sharings with the new owner
await trx.delete(SharedCredentials, {
credentialsId: credential.id,
projectId: destinationProjectId,
});

// create new owner-sharing
await trx.save(
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/permissions/project-roles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const REGULAR_PROJECT_ADMIN_SCOPES: Scope[] = [
'credential:delete',
'credential:list',
'credential:move',
'credential:share',
'project:list',
'project:read',
'project:update',
Expand Down
Loading

0 comments on commit 6b7928d

Please sign in to comment.