Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow roleSet invitation to an extra Role #4587

Merged
merged 6 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
techsmyth marked this conversation as resolved.
Show resolved Hide resolved

@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);
techsmyth marked this conversation as resolved.
Show resolved Hide resolved

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,
techsmyth marked this conversation as resolved.
Show resolved Hide resolved
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