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

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