Skip to content

Commit

Permalink
Merge pull request #4587 from alkem-io/server-4585
Browse files Browse the repository at this point in the history
Allow roleSet invitation to an extra Role
  • Loading branch information
ccanos authored Oct 3, 2024
2 parents 545664d + 8089ee3 commit 3bf99f4
Show file tree
Hide file tree
Showing 13 changed files with 102 additions and 20 deletions.
4 changes: 2 additions & 2 deletions src/core/authorization/authorization.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export class AuthorizationService {
);
if (authorization.credentialRules === '') {
throw new AuthorizationInvalidPolicyException(
`AuthorizationPolicy without credential rules provided: ${authorization.id}`,
`AuthorizationPolicy without credential rules provided: ${authorization.id}, type: ${authorization.type}`,
LogContext.AUTH
);
}
Expand Down Expand Up @@ -130,7 +130,7 @@ export class AuthorizationService {
for (const privilege of rule.grantedPrivileges) {
if (privilege === privilegeRequired) {
this.logger.verbose?.(
`[CredentialRule] Granted privilege '${privilegeRequired}' using rule '${rule.name}' on authorization ${authorization.id}`,
`[CredentialRule] Granted privilege '${privilegeRequired}' using rule '${rule.name}' on authorization ${authorization.id} on type: ${authorization.type}`,
LogContext.AUTH_POLICY
);
return true;
Expand Down
22 changes: 18 additions & 4 deletions src/domain/access/invitation/dto/invitation.dto.create.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { MID_TEXT_LENGTH, UUID_LENGTH } from '@common/constants';
import {
MID_TEXT_LENGTH,
SMALL_TEXT_LENGTH,
UUID_LENGTH,
} from '@common/constants';
import { CommunityRoleType } from '@common/enums/community.role';
import { UUID } from '@domain/common/scalars';
import { Field, InputType } from '@nestjs/graphql';
import { IsOptional, MaxLength } from 'class-validator';
Expand All @@ -7,11 +12,11 @@ import { IsOptional, MaxLength } from 'class-validator';
export class CreateInvitationInput {
@Field(() => UUID, {
nullable: false,
description: 'The identifier for the contributor being invited.',
description:
'The identifier for the contributor being invited to join in the entry Role.',
})
@IsOptional()
@MaxLength(UUID_LENGTH)
invitedContributor!: string;
invitedContributorID!: string;

@Field({ nullable: true })
@IsOptional()
Expand All @@ -22,4 +27,13 @@ export class CreateInvitationInput {

roleSetID!: string;
invitedToParent!: boolean;

@Field(() => CommunityRoleType, {
nullable: true,
description:
'An additional role to assign to the Contributor, in addition to the entry Role.',
})
@IsOptional()
@MaxLength(SMALL_TEXT_LENGTH)
extraRole?: CommunityRoleType;
}
9 changes: 8 additions & 1 deletion src/domain/access/invitation/invitation.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { AuthorizableEntity } from '@domain/common/entity/authorizable-entity';
import { CommunityContributorType } from '@common/enums/community.contributor.type';
import { ENUM_LENGTH, MID_TEXT_LENGTH, UUID_LENGTH } from '@common/constants';
import { RoleSet } from '@domain/access/role-set/role.set.entity';
import { CommunityRoleType } from '@common/enums/community.role';
@Entity()
export class Invitation extends AuthorizableEntity implements IInvitation {
// todo ID in migration is varchar - must be char(36)
Expand All @@ -17,7 +18,7 @@ export class Invitation extends AuthorizableEntity implements IInvitation {
lifecycle!: Lifecycle;

@Column('char', { length: UUID_LENGTH, nullable: false })
invitedContributor!: string;
invitedContributorID!: string;

@Column('char', { length: UUID_LENGTH, nullable: false })
createdBy!: string;
Expand All @@ -37,4 +38,10 @@ export class Invitation extends AuthorizableEntity implements IInvitation {
onDelete: 'CASCADE',
})
roleSet?: RoleSet;

@Column('varchar', {
length: ENUM_LENGTH,
nullable: true,
})
extraRole?: CommunityRoleType;
}
10 changes: 9 additions & 1 deletion src/domain/access/invitation/invitation.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import { Field, ObjectType } from '@nestjs/graphql';
import { IAuthorizable } from '@domain/common/entity/authorizable-entity';
import { CommunityContributorType } from '@common/enums/community.contributor.type';
import { IRoleSet } from '@domain/access/role-set';
import { CommunityRoleType } from '@common/enums/community.role';

@ObjectType('Invitation')
export class IInvitation extends IAuthorizable {
invitedContributor!: string;
invitedContributorID!: string;
createdBy!: string;

roleSet?: IRoleSet;
Expand Down Expand Up @@ -35,4 +36,11 @@ export class IInvitation extends IAuthorizable {
description: 'The type of contributor that is invited.',
})
contributorType!: CommunityContributorType;

@Field(() => CommunityRoleType, {
nullable: true,
description:
'An additional role to assign to the Contributor, in addition to the entry Role.',
})
extraRole?: CommunityRoleType;
}
6 changes: 3 additions & 3 deletions src/domain/access/invitation/invitation.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export class InvitationService {
async getInvitedContributor(invitation: IInvitation): Promise<IContributor> {
const contributor =
await this.contributorService.getContributorByUuidOrFail(
invitation.invitedContributor
invitation.invitedContributorID
);
if (!contributor)
throw new RelationshipNotFoundException(
Expand All @@ -137,7 +137,7 @@ export class InvitationService {
): Promise<IInvitation[]> {
const existingInvitations = await this.invitationRepository.find({
where: {
invitedContributor: contributorID,
invitedContributorID: contributorID,
roleSet: { id: roleSetID },
},
relations: { roleSet: true },
Expand All @@ -153,7 +153,7 @@ export class InvitationService {
): Promise<IInvitation[]> {
const findOpts: FindManyOptions<Invitation> = {
relations: { roleSet: true },
where: { invitedContributor: contributorID },
where: { invitedContributorID: contributorID },
};

if (states.length) {
Expand Down
16 changes: 15 additions & 1 deletion src/domain/access/role-set/dto/role.set.dto.entry.role.invite.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { Field, InputType } from '@nestjs/graphql';
import { UUID } from '@domain/common/scalars';
import { IsOptional, MaxLength } from 'class-validator';
import { MID_TEXT_LENGTH, UUID_LENGTH } from '@common/constants';
import {
MID_TEXT_LENGTH,
SMALL_TEXT_LENGTH,
UUID_LENGTH,
} from '@common/constants';
import { CommunityRoleType } from '@common/enums/community.role';

@InputType()
export class InviteForEntryRoleOnRoleSetInput {
Expand All @@ -20,6 +25,15 @@ export class InviteForEntryRoleOnRoleSetInput {
@MaxLength(MID_TEXT_LENGTH)
welcomeMessage?: string;

@Field(() => CommunityRoleType, {
nullable: true,
description:
'An additional role to assign to the Contributors, in addition to the entry Role.',
})
@IsOptional()
@MaxLength(SMALL_TEXT_LENGTH)
extraRole?: CommunityRoleType;

createdBy!: string;

invitedToParent!: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export class RoleSetInvitationLifecycleOptionsProvider {
},
}
);
const contributorID = invitation.invitedContributor;
const contributorID = invitation.invitedContributorID;
const roleSet = invitation.roleSet;
if (!contributorID || !roleSet) {
throw new EntityNotInitializedException(
Expand Down Expand Up @@ -127,6 +127,16 @@ export class RoleSetInvitationLifecycleOptionsProvider {
event.agentInfo,
true
);
if (invitation.extraRole) {
await this.roleSetService.assignContributorToRole(
roleSet,
invitation.extraRole,
contributorID,
invitation.contributorType,
event.agentInfo,
false
);
}
} finally {
resolve();
}
Expand Down
6 changes: 5 additions & 1 deletion src/domain/access/role-set/role.set.resolver.mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ export class RoleSetResolverMutations {
roleData.contributorID
);
}

@UseGuards(GraphqlGuard)
@Mutation(() => IRoleSet, {
description:
Expand Down Expand Up @@ -506,6 +507,7 @@ export class RoleSetResolverMutations {
invitedContributor,
agentInfo,
invitationData.invitedToParent,
invitationData.extraRole,
invitationData.welcomeMessage
);
})
Expand All @@ -517,13 +519,15 @@ export class RoleSetResolverMutations {
invitedContributor: IContributor,
agentInfo: AgentInfo,
invitedToParent: boolean,
extraRole?: CommunityRoleType,
welcomeMessage?: string
): Promise<IInvitation> {
const input: CreateInvitationInput = {
roleSetID: roleSet.id,
invitedContributor: invitedContributor.id,
invitedContributorID: invitedContributor.id,
createdBy: agentInfo.userID,
invitedToParent,
extraRole,
welcomeMessage,
};

Expand Down
6 changes: 3 additions & 3 deletions src/domain/access/role-set/role.set.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1023,7 +1023,7 @@ export class RoleSetService {
): Promise<IInvitation> {
const { contributor: contributor, agent } =
await this.contributorService.getContributorAndAgent(
invitationData.invitedContributor
invitationData.invitedContributorID
);
const roleSet = await this.getRoleSetOrFail(invitationData.roleSetID);

Expand Down Expand Up @@ -1082,7 +1082,7 @@ export class RoleSetService {
const openInvitation = await this.findOpenInvitation(user.id, roleSet.id);
if (openInvitation) {
throw new RoleSetMembershipException(
`Application not possible: An open invitation (ID: ${openInvitation.id}) already exists for contributor ${openInvitation.invitedContributor} (${openInvitation.contributorType}) on RoleSet: ${roleSet.id}.`,
`Application not possible: An open invitation (ID: ${openInvitation.id}) already exists for contributor ${openInvitation.invitedContributorID} (${openInvitation.contributorType}) on RoleSet: ${roleSet.id}.`,
LogContext.COMMUNITY
);
}
Expand All @@ -1107,7 +1107,7 @@ export class RoleSetService {
);
if (openInvitation) {
throw new RoleSetMembershipException(
`Invitation not possible: An open invitation (ID: ${openInvitation.id}) already exists for contributor ${openInvitation.invitedContributor} (${openInvitation.contributorType}) on RoleSet: ${roleSet.id}.`,
`Invitation not possible: An open invitation (ID: ${openInvitation.id}) already exists for contributor ${openInvitation.invitedContributorID} (${openInvitation.contributorType}) on RoleSet: ${roleSet.id}.`,
LogContext.COMMUNITY
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,7 @@ export class VirtualContributorService {
//adding this to avoid circular dependency between VirtualContributor, Room, and Invitation
private async deleteVCInvitations(contributorID: string) {
const invitations = await this.entityManager.find(Invitation, {
where: { invitedContributor: contributorID },
where: { invitedContributorID: contributorID },
});
for (const invitation of invitations) {
if (invitation.authorization) {
Expand Down
25 changes: 25 additions & 0 deletions src/migrations/1727930288139-invitationToRole.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class InvitationToRole1727930288139 implements MigrationInterface {
name = 'InvitationToRole1727930288139';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE \`invitation\` ADD \`extraRole\` varchar(128) NULL`
);

await queryRunner.query(
`ALTER TABLE \`invitation\` CHANGE \`invitedContributor\` \`invitedContributorID\` char(36) NOT NULL`
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE \`invitation\` DROP COLUMN \`extraRole\``
);

await queryRunner.query(
`ALTER TABLE \`invitation\` CHANGE \`invitedContributorID\` \`invitedContributor\` char(36) NOT NULL`
);
}
}
2 changes: 1 addition & 1 deletion src/services/api/registration/registration.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export class RegistrationService {
// Process community invitations
if (roleSet) {
const invitationInput: CreateInvitationInput = {
invitedContributor: user.id,
invitedContributorID: user.id,
roleSetID: roleSet.id,
createdBy: platformInvitation.createdBy,
invitedToParent: platformInvitation.communityInvitedToParent,
Expand Down
2 changes: 1 addition & 1 deletion src/services/api/roles/roles.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ export class RolesService {
invitation.createdDate,
invitation.updatedDate
);
invitationResult.contributorID = invitation.invitedContributor;
invitationResult.contributorID = invitation.invitedContributorID;
invitationResult.contributorType = invitation.contributorType;

invitationResult.createdBy = invitation.createdBy ?? '';
Expand Down

0 comments on commit 3bf99f4

Please sign in to comment.