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

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