feat: Allow sharing to and from team projects (no-changelog) (#10144)

This commit is contained in:
Val
2024-08-09 11:59:28 +01:00
committed by GitHub
parent c9d9245451
commit 697bc90b0b
7 changed files with 219 additions and 21 deletions

View File

@@ -291,25 +291,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;

View File

@@ -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 {
@@ -20,9 +21,11 @@ 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,
@@ -30,19 +33,35 @@ export class EnterpriseCredentialsService {
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 can'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);
}

View File

@@ -90,6 +90,19 @@ export class CredentialsService {
let credentials = await this.credentialsRepository.findMany(options.listQueryOptions);
if (isDefaultSelect) {
// Since we're filtering using project ID as part of the relation,
// we end up filtering out all the other relations, meaning that if
// it's shared to a project, it won't be able to find the home project.
// To solve this, we have to get all the relation now, even though
// we're deleting them later.
if ((options.listQueryOptions?.filter?.shared as { projectId?: string })?.projectId) {
const relations = await this.sharedCredentialsRepository.getAllRelationsForCredentials(
credentials.map((c) => c.id),
);
credentials.forEach((c) => {
c.shared = relations.filter((r) => r.credentialsId === c.id);
});
}
credentials = credentials.map((c) => this.ownershipService.addOwnedByAndSharedWith(c));
}
@@ -130,6 +143,20 @@ export class CredentialsService {
);
if (isDefaultSelect) {
// Since we're filtering using project ID as part of the relation,
// we end up filtering out all the other relations, meaning that if
// it's shared to a project, it won't be able to find the home project.
// To solve this, we have to get all the relation now, even though
// we're deleting them later.
if ((options.listQueryOptions?.filter?.shared as { projectId?: string })?.projectId) {
const relations = await this.sharedCredentialsRepository.getAllRelationsForCredentials(
credentials.map((c) => c.id),
);
credentials.forEach((c) => {
c.shared = relations.filter((r) => r.credentialsId === c.id);
});
}
credentials = credentials.map((c) => this.ownershipService.addOwnedByAndSharedWith(c));
}

View File

@@ -151,4 +151,13 @@ export class SharedCredentialsRepository extends Repository<SharedCredentials> {
})
)?.project;
}
async getAllRelationsForCredentials(credentialIds: string[]) {
return await this.find({
where: {
credentialsId: In(credentialIds),
},
relations: ['project'],
});
}
}

View File

@@ -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',