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

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