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

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