external-user-group-sync.test.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. import { IUserHasId } from '@growi/core';
  2. import mongoose, { Types } from 'mongoose';
  3. import {
  4. ExternalGroupProviderType, ExternalUserGroupTreeNode, IExternalUserGroup, IExternalUserGroupHasId,
  5. } from '../../../src/features/external-user-group/interfaces/external-user-group';
  6. import ExternalUserGroup from '../../../src/features/external-user-group/server/models/external-user-group';
  7. import ExternalUserGroupRelation from '../../../src/features/external-user-group/server/models/external-user-group-relation';
  8. import ExternalUserGroupSyncService from '../../../src/features/external-user-group/server/service/external-user-group-sync';
  9. import ExternalAccount from '../../../src/server/models/external-account';
  10. import { configManager } from '../../../src/server/service/config-manager';
  11. import { instanciate } from '../../../src/server/service/external-account';
  12. import PassportService from '../../../src/server/service/passport';
  13. import { getInstance } from '../setup-crowi';
  14. // dummy class to implement generateExternalUserGroupTrees which returns test data
  15. class TestExternalUserGroupSyncService extends ExternalUserGroupSyncService {
  16. constructor() {
  17. super(ExternalGroupProviderType.ldap, 'ldap');
  18. }
  19. async generateExternalUserGroupTrees(): Promise<ExternalUserGroupTreeNode[]> {
  20. const childNode: ExternalUserGroupTreeNode = {
  21. id: 'cn=childGroup,ou=groups,dc=example,dc=org',
  22. userInfos: [{
  23. id: 'childGroupUser',
  24. username: 'childGroupUser',
  25. name: 'Child Group User',
  26. email: 'user@childgroup.com',
  27. }],
  28. childGroupNodes: [],
  29. name: 'childGroup',
  30. description: 'this is a child group',
  31. };
  32. const parentNode: ExternalUserGroupTreeNode = {
  33. id: 'cn=parentGroup,ou=groups,dc=example,dc=org',
  34. // name is undefined
  35. userInfos: [{
  36. id: 'parentGroupUser',
  37. username: 'parentGroupUser',
  38. email: 'user@parentgroup.com',
  39. }],
  40. childGroupNodes: [childNode],
  41. name: 'parentGroup',
  42. description: 'this is a parent group',
  43. };
  44. const grandParentNode: ExternalUserGroupTreeNode = {
  45. id: 'cn=grandParentGroup,ou=groups,dc=example,dc=org',
  46. // email is undefined
  47. userInfos: [{
  48. id: 'grandParentGroupUser',
  49. username: 'grandParentGroupUser',
  50. name: 'Grand Parent Group User',
  51. }],
  52. childGroupNodes: [parentNode],
  53. name: 'grandParentGroup',
  54. description: 'this is a grand parent group',
  55. };
  56. const previouslySyncedNode: ExternalUserGroupTreeNode = {
  57. id: 'cn=previouslySyncedGroup,ou=groups,dc=example,dc=org',
  58. userInfos: [{
  59. id: 'previouslySyncedGroupUser',
  60. username: 'previouslySyncedGroupUser',
  61. name: 'Root Group User',
  62. email: 'user@previouslySyncedgroup.com',
  63. }],
  64. childGroupNodes: [],
  65. name: 'previouslySyncedGroup',
  66. description: 'this is a previouslySynced group',
  67. };
  68. return [grandParentNode, previouslySyncedNode];
  69. }
  70. }
  71. const testService = new TestExternalUserGroupSyncService();
  72. const checkGroup = (group: IExternalUserGroupHasId, expected: Omit<IExternalUserGroup, 'createdAt'>) => {
  73. const actual = {
  74. name: group.name,
  75. parent: group.parent,
  76. description: group.description,
  77. externalId: group.externalId,
  78. provider: group.provider,
  79. };
  80. expect(actual).toStrictEqual(expected);
  81. };
  82. const checkSync = async(autoGenerateUserOnGroupSync = true) => {
  83. const grandParentGroup = await ExternalUserGroup.findOne({ name: 'grandParentGroup' });
  84. checkGroup(grandParentGroup, {
  85. externalId: 'cn=grandParentGroup,ou=groups,dc=example,dc=org',
  86. name: 'grandParentGroup',
  87. description: 'this is a grand parent group',
  88. provider: 'ldap',
  89. parent: null,
  90. });
  91. const grandParentGroupRelations = await ExternalUserGroupRelation
  92. .find({ relatedGroup: grandParentGroup._id });
  93. if (autoGenerateUserOnGroupSync) {
  94. expect(grandParentGroupRelations.length).toBe(3);
  95. const grandParentGroupUser = (await grandParentGroupRelations[0].populate<{relatedUser: IUserHasId}>('relatedUser'))?.relatedUser;
  96. expect(grandParentGroupUser?.username).toBe('grandParentGroupUser');
  97. const parentGroupUser = (await grandParentGroupRelations[1].populate<{relatedUser: IUserHasId}>('relatedUser'))?.relatedUser;
  98. expect(parentGroupUser?.username).toBe('parentGroupUser');
  99. const childGroupUser = (await grandParentGroupRelations[2].populate<{relatedUser: IUserHasId}>('relatedUser'))?.relatedUser;
  100. expect(childGroupUser?.username).toBe('childGroupUser');
  101. }
  102. else {
  103. expect(grandParentGroupRelations.length).toBe(0);
  104. }
  105. const parentGroup = await ExternalUserGroup.findOne({ name: 'parentGroup' });
  106. checkGroup(parentGroup, {
  107. externalId: 'cn=parentGroup,ou=groups,dc=example,dc=org',
  108. name: 'parentGroup',
  109. description: 'this is a parent group',
  110. provider: 'ldap',
  111. parent: grandParentGroup._id,
  112. });
  113. const parentGroupRelations = await ExternalUserGroupRelation
  114. .find({ relatedGroup: parentGroup._id });
  115. if (autoGenerateUserOnGroupSync) {
  116. expect(parentGroupRelations.length).toBe(2);
  117. const parentGroupUser = (await parentGroupRelations[0].populate<{relatedUser: IUserHasId}>('relatedUser'))?.relatedUser;
  118. expect(parentGroupUser?.username).toBe('parentGroupUser');
  119. const childGroupUser = (await parentGroupRelations[1].populate<{relatedUser: IUserHasId}>('relatedUser'))?.relatedUser;
  120. expect(childGroupUser?.username).toBe('childGroupUser');
  121. }
  122. else {
  123. expect(parentGroupRelations.length).toBe(0);
  124. }
  125. const childGroup = await ExternalUserGroup.findOne({ name: 'childGroup' });
  126. checkGroup(childGroup, {
  127. externalId: 'cn=childGroup,ou=groups,dc=example,dc=org',
  128. name: 'childGroup',
  129. description: 'this is a child group',
  130. provider: 'ldap',
  131. parent: parentGroup._id,
  132. });
  133. const childGroupRelations = await ExternalUserGroupRelation
  134. .find({ relatedGroup: childGroup._id });
  135. if (autoGenerateUserOnGroupSync) {
  136. expect(childGroupRelations.length).toBe(1);
  137. const childGroupUser = (await childGroupRelations[0].populate<{relatedUser: IUserHasId}>('relatedUser'))?.relatedUser;
  138. expect(childGroupUser?.username).toBe('childGroupUser');
  139. }
  140. else {
  141. expect(childGroupRelations.length).toBe(0);
  142. }
  143. const previouslySyncedGroup = await ExternalUserGroup.findOne({ name: 'previouslySyncedGroup' });
  144. checkGroup(previouslySyncedGroup, {
  145. externalId: 'cn=previouslySyncedGroup,ou=groups,dc=example,dc=org',
  146. name: 'previouslySyncedGroup',
  147. description: 'this is a previouslySynced group',
  148. provider: 'ldap',
  149. parent: null,
  150. });
  151. const previouslySyncedGroupRelations = await ExternalUserGroupRelation
  152. .find({ relatedGroup: previouslySyncedGroup._id });
  153. if (autoGenerateUserOnGroupSync) {
  154. expect(previouslySyncedGroupRelations.length).toBe(1);
  155. const previouslySyncedGroupUser = (await previouslySyncedGroupRelations[0].populate<{relatedUser: IUserHasId}>('relatedUser'))?.relatedUser;
  156. expect(previouslySyncedGroupUser?.username).toBe('previouslySyncedGroupUser');
  157. }
  158. else {
  159. expect(previouslySyncedGroupRelations.length).toBe(0);
  160. }
  161. };
  162. describe('ExternalUserGroupSyncService.syncExternalUserGroups', () => {
  163. let crowi;
  164. beforeAll(async() => {
  165. crowi = await getInstance();
  166. const passportService = new PassportService(crowi);
  167. instanciate(passportService);
  168. });
  169. beforeEach(async() => {
  170. await ExternalUserGroup.create({
  171. name: 'nameBeforeEdit',
  172. description: 'this is a description before edit',
  173. externalId: 'cn=previouslySyncedGroup,ou=groups,dc=example,dc=org',
  174. provider: 'ldap',
  175. });
  176. });
  177. afterEach(async() => {
  178. await ExternalUserGroup.deleteMany();
  179. await ExternalUserGroupRelation.deleteMany();
  180. await mongoose.model('User')
  181. .deleteMany({ username: { $in: ['childGroupUser', 'parentGroupUser', 'grandParentGroupUser', 'previouslySyncedGroupUser'] } });
  182. await ExternalAccount.deleteMany({ accountId: { $in: ['childGroupUser', 'parentGroupUser', 'grandParentGroupUser', 'previouslySyncedGroupUser'] } });
  183. });
  184. describe('When autoGenerateUserOnGroupSync is true', () => {
  185. const configParams = {
  186. 'external-user-group:ldap:autoGenerateUserOnGroupSync': true,
  187. 'external-user-group:ldap:preserveDeletedGroups': false,
  188. };
  189. beforeAll(async() => {
  190. await configManager.updateConfigsInTheSameNamespace('crowi', configParams, true);
  191. });
  192. // eslint-disable-next-line jest/expect-expect
  193. it('syncs groups with new users', async() => {
  194. await testService.syncExternalUserGroups();
  195. await checkSync();
  196. });
  197. });
  198. describe('When autoGenerateUserOnGroupSync is false', () => {
  199. const configParams = {
  200. 'external-user-group:ldap:autoGenerateUserOnGroupSync': false,
  201. 'external-user-group:ldap:preserveDeletedGroups': true,
  202. };
  203. beforeAll(async() => {
  204. await configManager.updateConfigsInTheSameNamespace('crowi', configParams, true);
  205. });
  206. // eslint-disable-next-line jest/expect-expect
  207. it('syncs groups without new users', async() => {
  208. await testService.syncExternalUserGroups();
  209. await checkSync(false);
  210. });
  211. });
  212. describe('When preserveDeletedGroups is false', () => {
  213. const configParams = {
  214. 'external-user-group:ldap:autoGenerateUserOnGroupSync': true,
  215. 'external-user-group:ldap:preserveDeletedGroups': false,
  216. };
  217. beforeAll(async() => {
  218. await configManager.updateConfigsInTheSameNamespace('crowi', configParams, true);
  219. const groupId = new Types.ObjectId();
  220. const userId = new Types.ObjectId();
  221. await ExternalUserGroup.create({
  222. _id: groupId,
  223. name: 'non existent group',
  224. externalId: 'cn=nonExistentGroup,ou=groups,dc=example,dc=org',
  225. provider: 'ldap',
  226. });
  227. await mongoose.model('User').create({ _id: userId, username: 'nonExistentGroupUser' });
  228. await ExternalUserGroupRelation.create({ relatedUser: userId, relatedGroup: groupId });
  229. });
  230. it('syncs groups and deletes groups that do not exist externally', async() => {
  231. await testService.syncExternalUserGroups();
  232. await checkSync();
  233. expect(await ExternalUserGroup.countDocuments()).toBe(4);
  234. expect(await ExternalUserGroupRelation.countDocuments()).toBe(7);
  235. });
  236. });
  237. });