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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. import ldap, { Client } from 'ldapjs';
  2. import { LdapUserGroupSyncService } from '../../../src/features/external-user-group/server/service/ldap-user-group-sync';
  3. import { configManager } from '../../../src/server/service/config-manager';
  4. import { ldapService } from '../../../src/server/service/ldap';
  5. import PassportService from '../../../src/server/service/passport';
  6. import { getInstance } from '../setup-crowi';
  7. describe('LdapUserGroupSyncService.generateExternalUserGroupTrees', () => {
  8. let crowi;
  9. let ldapUserGroupSyncService: LdapUserGroupSyncService;
  10. const configParams = {
  11. 'security:passport-ldap:attrMapName': 'name',
  12. 'external-user-group:ldap:groupChildGroupAttribute': 'member',
  13. 'external-user-group:ldap:groupMembershipAttribute': 'member',
  14. 'external-user-group:ldap:groupNameAttribute': 'cn',
  15. 'external-user-group:ldap:groupDescriptionAttribute': 'description',
  16. 'external-user-group:ldap:groupMembershipAttributeType': 'DN',
  17. 'external-user-group:ldap:groupSearchBase': 'ou=groups,dc=example,dc=org',
  18. 'security:passport-ldap:serverUrl': 'ldap://openldap:1389/dc=example,dc=org',
  19. };
  20. jest.mock('../../../src/server/service/ldap');
  21. const mockBind = jest.spyOn(ldapService, 'bind');
  22. const mockLdapSearch = jest.spyOn(ldapService, 'search');
  23. const mockLdapCreateClient = jest.spyOn(ldap, 'createClient');
  24. beforeAll(async() => {
  25. crowi = await getInstance();
  26. await configManager.updateConfigs(configParams, { skipPubsub: true });
  27. mockBind.mockImplementation(() => {
  28. return Promise.resolve();
  29. });
  30. mockLdapCreateClient.mockImplementation(() => { return {} as Client });
  31. const passportService = new PassportService(crowi);
  32. ldapUserGroupSyncService = new LdapUserGroupSyncService(passportService, null, null);
  33. });
  34. describe('When there is no circular reference in group tree', () => {
  35. it('creates ExternalUserGroupTrees', async() => {
  36. // mock search on LDAP server
  37. mockLdapSearch.mockImplementation((filter, base) => {
  38. if (base === 'ou=groups,dc=example,dc=org') {
  39. // search groups
  40. return Promise.resolve([
  41. {
  42. objectName: 'cn=childGroup,ou=groups,dc=example,dc=org',
  43. attributes: [
  44. { type: 'cn', values: ['childGroup'] },
  45. { type: 'description', values: ['this is a child group'] },
  46. {
  47. type: 'member',
  48. values: ['cn=childGroupUser,ou=users,dc=example,dc=org'],
  49. },
  50. ],
  51. },
  52. {
  53. objectName: 'cn=parentGroup,ou=groups,dc=example,dc=org',
  54. attributes: [
  55. { type: 'cn', values: ['parentGroup'] },
  56. { type: 'description', values: ['this is a parent group'] },
  57. {
  58. type: 'member',
  59. values: ['cn=childGroup,ou=groups,dc=example,dc=org', 'cn=parentGroupUser,ou=users,dc=example,dc=org'],
  60. },
  61. ],
  62. },
  63. // root node
  64. {
  65. objectName: 'cn=grandParentGroup,ou=groups,dc=example,dc=org',
  66. attributes: [
  67. { type: 'cn', values: ['grandParentGroup'] },
  68. { type: 'description', values: ['this is a grand parent group'] },
  69. {
  70. type: 'member',
  71. values: ['cn=parentGroup,ou=groups,dc=example,dc=org', 'cn=grandParentGroupUser,ou=users,dc=example,dc=org'],
  72. },
  73. ],
  74. },
  75. // another root node
  76. {
  77. objectName: 'cn=rootGroup,ou=groups,dc=example,dc=org',
  78. attributes: [
  79. { type: 'cn', values: ['rootGroup'] },
  80. { type: 'description', values: ['this is a root group'] },
  81. {
  82. type: 'member',
  83. values: ['cn=rootGroupUser,ou=users,dc=example,dc=org'],
  84. },
  85. ],
  86. },
  87. ]);
  88. }
  89. if (base === 'cn=childGroupUser,ou=users,dc=example,dc=org') {
  90. // search childGroupUser
  91. return Promise.resolve([
  92. {
  93. objectName: 'cn=childGroupUser,ou=users,dc=example,dc=org',
  94. attributes: [
  95. { type: 'name', values: ['Child Group User'] },
  96. { type: 'uid', values: ['childGroupUser'] },
  97. { type: 'mail', values: ['user@childGroup.com'] },
  98. ],
  99. },
  100. ]);
  101. }
  102. // search parentGroupUser
  103. if (base === 'cn=parentGroupUser,ou=users,dc=example,dc=org') {
  104. return Promise.resolve([
  105. {
  106. objectName: 'cn=parentGroupUser,ou=users,dc=example,dc=org',
  107. attributes: [
  108. { type: 'name', values: ['Parent Group User'] },
  109. { type: 'uid', values: ['parentGroupUser'] },
  110. { type: 'mail', values: ['user@parentGroup.com'] },
  111. ],
  112. },
  113. ]);
  114. }
  115. // search grandParentGroupUser
  116. if (base === 'cn=grandParentGroupUser,ou=users,dc=example,dc=org') {
  117. return Promise.resolve([
  118. {
  119. objectName: 'cn=grandParentGroupUser,ou=users,dc=example,dc=org',
  120. attributes: [
  121. { type: 'name', values: ['Grand Parent Group User'] },
  122. { type: 'uid', values: ['grandParentGroupUser'] },
  123. { type: 'mail', values: ['user@grandParentGroup.com'] },
  124. ],
  125. },
  126. ]);
  127. }
  128. // search rootGroupUser
  129. if (base === 'cn=rootGroupUser,ou=users,dc=example,dc=org') {
  130. return Promise.resolve([
  131. {
  132. objectName: 'cn=rootGroupUser,ou=users,dc=example,dc=org',
  133. attributes: [
  134. { type: 'name', values: ['Root Group User'] },
  135. { type: 'uid', values: ['rootGroupUser'] },
  136. { type: 'mail', values: ['user@rootGroup.com'] },
  137. ],
  138. },
  139. ]);
  140. }
  141. return Promise.reject(new Error('not found'));
  142. });
  143. const rootNodes = await ldapUserGroupSyncService?.generateExternalUserGroupTrees();
  144. expect(rootNodes?.length).toBe(2);
  145. // check grandParentGroup
  146. const grandParentNode = rootNodes?.find(node => node.id === 'cn=grandParentGroup,ou=groups,dc=example,dc=org');
  147. const expectedChildNode = {
  148. id: 'cn=childGroup,ou=groups,dc=example,dc=org',
  149. userInfos: [{
  150. id: 'childGroupUser',
  151. username: 'childGroupUser',
  152. name: 'Child Group User',
  153. email: 'user@childGroup.com',
  154. }],
  155. childGroupNodes: [],
  156. name: 'childGroup',
  157. description: 'this is a child group',
  158. };
  159. const expectedParentNode = {
  160. id: 'cn=parentGroup,ou=groups,dc=example,dc=org',
  161. userInfos: [{
  162. id: 'parentGroupUser',
  163. username: 'parentGroupUser',
  164. name: 'Parent Group User',
  165. email: 'user@parentGroup.com',
  166. }],
  167. childGroupNodes: [expectedChildNode],
  168. name: 'parentGroup',
  169. description: 'this is a parent group',
  170. };
  171. const expectedGrandParentNode = {
  172. id: 'cn=grandParentGroup,ou=groups,dc=example,dc=org',
  173. userInfos: [{
  174. id: 'grandParentGroupUser',
  175. username: 'grandParentGroupUser',
  176. name: 'Grand Parent Group User',
  177. email: 'user@grandParentGroup.com',
  178. }],
  179. childGroupNodes: [expectedParentNode],
  180. name: 'grandParentGroup',
  181. description: 'this is a grand parent group',
  182. };
  183. expect(grandParentNode).toStrictEqual(expectedGrandParentNode);
  184. // check rootGroup
  185. const rootNode = rootNodes?.find(node => node.id === 'cn=rootGroup,ou=groups,dc=example,dc=org');
  186. const expectedRootNode = {
  187. id: 'cn=rootGroup,ou=groups,dc=example,dc=org',
  188. userInfos: [{
  189. id: 'rootGroupUser',
  190. username: 'rootGroupUser',
  191. name: 'Root Group User',
  192. email: 'user@rootGroup.com',
  193. }],
  194. childGroupNodes: [],
  195. name: 'rootGroup',
  196. description: 'this is a root group',
  197. };
  198. expect(rootNode).toStrictEqual(expectedRootNode);
  199. });
  200. });
  201. describe('When there is a circular reference in group tree', () => {
  202. it('rejects creating ExternalUserGroupTrees', async() => {
  203. // mock search on LDAP server
  204. mockLdapSearch.mockImplementation((filter, base) => {
  205. if (base === 'ou=groups,dc=example,dc=org') {
  206. // search groups
  207. return Promise.resolve([
  208. // childGroup and parentGroup have circular reference
  209. {
  210. objectName: 'cn=childGroup,ou=groups,dc=example,dc=org',
  211. attributes: [
  212. { type: 'cn', values: ['childGroup'] },
  213. { type: 'description', values: ['this is a child group'] },
  214. {
  215. type: 'member',
  216. values: ['cn=parentGroup,ou=groups,dc=example,dc=org'],
  217. },
  218. ],
  219. },
  220. {
  221. objectName: 'cn=parentGroup,ou=groups,dc=example,dc=org',
  222. attributes: [
  223. { type: 'cn', values: ['parentGroup'] },
  224. { type: 'description', values: ['this is a parent group'] },
  225. {
  226. type: 'member',
  227. values: ['cn=childGroup,ou=groups,dc=example,dc=org'],
  228. },
  229. ],
  230. },
  231. {
  232. objectName: 'cn=grandParentGroup,ou=groups,dc=example,dc=org',
  233. attributes: [
  234. { type: 'cn', values: ['grandParentGroup'] },
  235. { type: 'description', values: ['this is a grand parent group'] },
  236. {
  237. type: 'member',
  238. values: ['cn=parentGroup,ou=groups,dc=example,dc=org'],
  239. },
  240. ],
  241. },
  242. ]);
  243. }
  244. return Promise.reject(new Error('not found'));
  245. });
  246. await expect(ldapUserGroupSyncService?.generateExternalUserGroupTrees()).rejects.toThrow('Circular reference inside LDAP group tree');
  247. });
  248. });
  249. });