• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

alkem-io / server / #8050

16 Aug 2024 11:21AM UTC coverage: 13.92%. First build
#8050

Pull #4411

travis-ci

Pull Request #4411: Type added to authorization policy entity

80 of 4158 branches covered (1.92%)

Branch coverage included in aggregate %.

61 of 116 new or added lines in 50 files covered. (52.59%)

1945 of 10389 relevant lines covered (18.72%)

3.01 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

14.6
/src/domain/community/user-group/user-group.service.ts
1
import { Inject, Injectable, LoggerService } from '@nestjs/common';
24✔
2
import { InjectRepository } from '@nestjs/typeorm';
24✔
3
import { FindOneOptions, Repository, FindManyOptions } from 'typeorm';
24✔
4
import { IGroupable } from '@src/common/interfaces/groupable.interface';
5
import { ProfileService } from '@domain/common/profile/profile.service';
24✔
6
import { IUser } from '@domain/community/user/user.interface';
7
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
24✔
8
import {
24✔
9
  AuthorizationCredential,
24✔
10
  LogContext,
11
  ProfileType,
12
} from '@common/enums';
13
import {
14
  EntityNotFoundException,
24✔
15
  NotSupportedException,
16
  EntityNotInitializedException,
17
} from '@common/exceptions';
18
import { UserGroup, IUserGroup } from '@domain/community/user-group';
19
import { AgentService } from '@domain/agent/agent/agent.service';
24✔
20
import { AuthorizationPolicy } from '@domain/common/authorization-policy';
24✔
21
import { IProfile } from '@domain/common/profile';
24✔
22
import { AuthorizationPolicyService } from '@domain/common/authorization-policy/authorization.policy.service';
23
import {
24✔
24
  AssignUserGroupMemberInput,
25
  CreateUserGroupInput,
26
  DeleteUserGroupInput,
27
  RemoveUserGroupMemberInput,
28
  UpdateUserGroupInput,
29
} from './dto';
30
import { IStorageAggregator } from '@domain/storage/storage-aggregator/storage.aggregator.interface';
31
import { AuthorizationPolicyType } from '@common/enums/authorization.policy.type';
32
import { UserLookupService } from '../user-lookup/user.lookup.service';
24✔
33

34
@Injectable()
35
export class UserGroupService {
24✔
36
  constructor(
37
    private authorizationPolicyService: AuthorizationPolicyService,
1✔
38
    private userLookupService: UserLookupService,
1✔
39
    private profileService: ProfileService,
1✔
40
    private agentService: AgentService,
1✔
41
    @InjectRepository(UserGroup)
42
    private userGroupRepository: Repository<UserGroup>,
1✔
43
    @Inject(WINSTON_MODULE_NEST_PROVIDER) private readonly logger: LoggerService
1✔
44
  ) {}
45

46
  async createUserGroup(
47
    userGroupData: CreateUserGroupInput,
48
    storageAggregator: IStorageAggregator
49
  ): Promise<IUserGroup> {
50
    const group = UserGroup.create(userGroupData);
×
NEW
51
    group.authorization = new AuthorizationPolicy(
×
52
      AuthorizationPolicyType.USER_GROUP
53
    );
54

55
    (group as IUserGroup).profile = await this.profileService.createProfile(
×
56
      userGroupData.profile,
57
      ProfileType.USER_GROUP,
58
      storageAggregator
59
    );
60
    const savedUserGroup = await this.userGroupRepository.save(group);
×
61
    this.logger.verbose?.(
×
62
      `Created new group (${group.id}) with name: ${group.profile?.displayName}`,
×
63
      LogContext.COMMUNITY
64
    );
65
    return savedUserGroup;
×
66
  }
67
  async removeUserGroup(deleteData: DeleteUserGroupInput): Promise<IUserGroup> {
68
    const groupID = deleteData.ID;
×
69
    // Note need to load it in with all contained entities so can remove fully
70
    const group = (await this.getUserGroupOrFail(groupID)) as UserGroup;
×
71

72
    if (group.profile) {
×
73
      await this.profileService.deleteProfile(group.profile.id);
×
74
    }
75

76
    if (group.authorization)
×
77
      await this.authorizationPolicyService.delete(group.authorization);
×
78

79
    // Remove all issued membership credentials
80
    const members = await this.getMembers(group.id);
×
81
    for (const member of members) {
×
82
      await this.removeUser({ userID: member.id, groupID: group.id });
×
83
    }
84

85
    const { id } = group;
×
86
    const result = await this.userGroupRepository.remove(group);
×
87
    return {
×
88
      ...result,
89
      id,
90
    };
91
  }
92

93
  async updateUserGroup(
94
    userGroupInput: UpdateUserGroupInput
95
  ): Promise<IUserGroup> {
96
    const group = await this.getUserGroupOrFail(userGroupInput.ID, {
×
97
      relations: { profile: true },
98
    });
99
    if (!group.profile) {
×
100
      throw new EntityNotFoundException(
×
101
        `Group profile not initialised: ${group.id}`,
102
        LogContext.COMMUNITY
103
      );
104
    }
105

106
    const newName = userGroupInput.name;
×
107
    if (
×
108
      newName &&
×
109
      newName.length > 0 &&
110
      newName !== group.profile.displayName
111
    ) {
112
      group.profile.displayName = newName;
×
113
    }
114

115
    if (userGroupInput.profileData) {
×
116
      group.profile = await this.profileService.updateProfile(
×
117
        group.profile,
118
        userGroupInput.profileData
119
      );
120
    }
121

122
    return await this.userGroupRepository.save(group);
×
123
  }
124

125
  async saveUserGroup(group: IUserGroup): Promise<IUserGroup> {
126
    return await this.userGroupRepository.save(group);
×
127
  }
128

129
  async getParent(group: IUserGroup): Promise<IGroupable> {
130
    const groupWithParent = (await this.getUserGroupOrFail(group.id, {
×
131
      relations: { community: true, organization: true },
132
    })) as UserGroup;
133
    if (groupWithParent?.community) return groupWithParent?.community;
×
134
    if (groupWithParent?.organization) return groupWithParent?.organization;
×
135
    throw new EntityNotFoundException(
×
136
      `Unable to locate parent for user group: ${group.profile?.displayName}`,
×
137
      LogContext.COMMUNITY
138
    );
139
  }
140

141
  async getUserGroupOrFail(
142
    groupID: string,
143
    options?: FindOneOptions<UserGroup>
144
  ): Promise<IUserGroup | never> {
145
    //const t1 = performance.now()
146

147
    const group = await this.userGroupRepository.findOne({
×
148
      ...options,
149
      where: {
150
        ...options?.where,
×
151
        id: groupID,
152
      },
153
    });
154

155
    if (!group)
×
156
      throw new EntityNotFoundException(
×
157
        `Unable to find group with ID: ${groupID}`,
158
        LogContext.COMMUNITY
159
      );
160
    return group;
×
161
  }
162

163
  async assignUser(
164
    membershipData: AssignUserGroupMemberInput
165
  ): Promise<IUserGroup> {
166
    const { user, agent } = await this.userLookupService.getUserAndAgent(
×
167
      membershipData.userID
168
    );
169

170
    user.agent = await this.agentService.grantCredentialOrFail({
×
171
      agentID: agent.id,
172
      type: AuthorizationCredential.USER_GROUP_MEMBER,
173
      resourceID: membershipData.groupID,
174
    });
175

176
    return await this.getUserGroupOrFail(membershipData.groupID, {
×
177
      relations: { community: true },
178
    });
179
  }
180

181
  async removeUser(
182
    membershipData: RemoveUserGroupMemberInput
183
  ): Promise<IUserGroup> {
184
    const { user, agent } = await this.userLookupService.getUserAndAgent(
×
185
      membershipData.userID
186
    );
187

188
    user.agent = await this.agentService.revokeCredential({
×
189
      agentID: agent.id,
190
      type: AuthorizationCredential.USER_GROUP_MEMBER,
191
      resourceID: membershipData.groupID,
192
    });
193

194
    return await this.getUserGroupOrFail(membershipData.groupID, {
×
195
      relations: { community: true },
196
    });
197
  }
198

199
  private hasGroupWithName(groupable: IGroupable, name: string): boolean {
200
    // Double check groups array is initialised
201
    if (!groupable.groups) {
×
202
      throw new EntityNotInitializedException(
×
203
        'Non-initialised Groupable submitted',
204
        LogContext.COMMUNITY
205
      );
206
    }
207

208
    // Find the right group
209
    for (const group of groupable.groups) {
×
210
      if (group.profile?.displayName === name) {
×
211
        return true;
×
212
      }
213
    }
214

215
    // If get here then no match group was found
216
    return false;
×
217
  }
218

219
  async addGroupWithName(
220
    groupable: IGroupable,
221
    name: string,
222
    storageAggregator: IStorageAggregator
223
  ): Promise<IUserGroup> {
224
    // Check if the group already exists, if so log a warning
225
    const alreadyExists = this.hasGroupWithName(groupable, name);
×
226
    if (alreadyExists) {
×
227
      throw new NotSupportedException(
×
228
        `Unable to create user group as parent already has a group with the given name: ${name}`,
229
        LogContext.COMMUNITY
230
      );
231
    }
232

233
    const newGroup = await this.createUserGroup(
×
234
      {
235
        parentID: groupable.id,
236
        profile: {
237
          displayName: name,
238
        },
239
      },
240
      storageAggregator
241
    );
242
    await groupable.groups?.push(newGroup);
×
243
    return newGroup;
×
244
  }
245

246
  async getMembers(groupID: string): Promise<IUser[]> {
247
    return await this.userLookupService.usersWithCredential({
×
248
      type: AuthorizationCredential.USER_GROUP_MEMBER,
249
      resourceID: groupID,
250
    });
251
  }
252

253
  async getGroups(
254
    conditions?: FindManyOptions<UserGroup>
255
  ): Promise<IUserGroup[]> {
256
    return (await this.userGroupRepository.find(conditions)) || [];
×
257
  }
258

259
  getProfile(userGroup: IUserGroup): IProfile {
260
    const profile = userGroup.profile;
×
261
    if (!profile)
×
262
      throw new EntityNotInitializedException(
×
263
        `UserGroup Profile not initialized: ${userGroup.id}`,
264
        LogContext.COMMUNITY
265
      );
266
    return profile;
×
267
  }
268
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc