Просмотр исходного кода

Merge branch 'feat/ldap-group-sync' into feat/129126-132372-multiple-group-assign-to-pages

Futa Arai 2 лет назад
Родитель
Сommit
b4f8b07796
43 измененных файлов с 274 добавлено и 523 удалено
  1. 1 0
      .devcontainer/Dockerfile
  2. 1 1
      .devcontainer/devcontainer.json
  3. 1 1
      .github/workflows/auto-labeling.yml
  4. 5 2
      .github/workflows/ci-app.yml
  5. 3 0
      .github/workflows/ci-slackbot-proxy.yml
  6. 1 0
      .github/workflows/release-slackbot-proxy.yml
  7. 4 8
      .github/workflows/release.yml
  8. 1 1
      .github/workflows/reusable-app-build-image.yml
  9. 5 3
      .github/workflows/reusable-app-prod.yml
  10. 1 0
      .github/workflows/reusable-app-reg-suit.yml
  11. 25 1
      CHANGELOG.md
  12. 2 2
      README.md
  13. 2 2
      README_JP.md
  14. 1 1
      apps/app/bin/github-actions/update-readme.sh
  15. 1 0
      apps/app/docker/Dockerfile
  16. 3 3
      apps/app/docker/README.md
  17. 3 2
      apps/app/package.json
  18. 4 2
      apps/app/public/static/locales/en_US/admin.json
  19. 4 2
      apps/app/public/static/locales/ja_JP/admin.json
  20. 4 2
      apps/app/public/static/locales/zh_CN/admin.json
  21. 3 3
      apps/app/src/components/SavePageControls/GrantSelector/GrantSelector.tsx
  22. 4 2
      apps/app/src/features/external-user-group/client/components/ExternalUserGroup/SyncExecution.tsx
  23. 18 3
      apps/app/src/features/external-user-group/server/routes/apiv3/external-user-group.ts
  24. 6 6
      apps/app/src/features/external-user-group/server/service/keycloak-user-group-sync.integ.ts
  25. 25 1
      apps/app/src/features/external-user-group/server/service/keycloak-user-group-sync.ts
  26. 2 0
      apps/app/src/server/models/config.ts
  27. 24 1
      apps/app/src/utils/promise.spec.ts
  28. 13 3
      apps/app/src/utils/promise.ts
  29. 1 0
      apps/slackbot-proxy/docker/Dockerfile
  30. 1 1
      apps/slackbot-proxy/package.json
  31. 4 5
      package.json
  32. 1 1
      packages/core/package.json
  33. 1 1
      packages/hackmd/package.json
  34. 1 1
      packages/presentation/package.json
  35. 1 1
      packages/preset-templates/package.json
  36. 1 1
      packages/preset-themes/package.json
  37. 1 1
      packages/remark-attachment-refs/package.json
  38. 1 1
      packages/remark-drawio/package.json
  39. 1 1
      packages/remark-growi-directive/package.json
  40. 1 1
      packages/remark-lsx/package.json
  41. 1 1
      packages/slack/package.json
  42. 1 1
      packages/ui/package.json
  43. 90 454
      yarn.lock

+ 1 - 0
.devcontainer/Dockerfile

@@ -51,6 +51,7 @@ RUN apt-get update \
 ENV DEBIAN_FRONTEND=dialog
 
 RUN yarn global add turbo
+RUN yarn global add node-gyp
 
 # Uncomment to default to non-root user
 # USER $USER_UID

+ 1 - 1
.devcontainer/devcontainer.json

@@ -34,7 +34,7 @@
   // "shutdownAction": "none",
 
   // Use 'postCreateCommand' to run commands after the container is created.
-  "postCreateCommand": "yarn global add turbo && yarn install",
+  "postCreateCommand": "yarn global add turbo node-gyp && yarn install",
 
   // Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root.
   "remoteUser": "node"

+ 1 - 1
.github/workflows/auto-labeling.yml

@@ -37,7 +37,7 @@ jobs:
         !startsWith( github.head_ref, 'dependabot/' ))
 
     steps:
-      - uses: amannn/action-semantic-pull-request@v5.0.2
+      - uses: amannn/action-semantic-pull-request@v5
         with:
           types: |
             feat

+ 5 - 2
.github/workflows/ci-app.yml

@@ -61,6 +61,7 @@ jobs:
       - name: Install dependencies
         run: |
           yarn global add turbo
+          yarn global add node-gyp
           yarn --frozen-lockfile
 
       - name: Lint
@@ -95,7 +96,7 @@ jobs:
 
     services:
       mongodb:
-        image: mongo:4.4
+        image: mongo:6.0
         ports:
           - 27017/tcp
 
@@ -132,6 +133,7 @@ jobs:
       - name: Install dependencies
         run: |
           yarn global add turbo
+          yarn global add node-gyp
           yarn --frozen-lockfile
 
       - name: Test
@@ -176,7 +178,7 @@ jobs:
 
     services:
       mongodb:
-        image: mongo:4.4
+        image: mongo:6.0
         ports:
           - 27017/tcp
 
@@ -214,6 +216,7 @@ jobs:
       - name: Install dependencies
         run: |
           yarn global add turbo
+          yarn global add node-gyp
           yarn --frozen-lockfile
 
       - name: turbo run dev:ci

+ 3 - 0
.github/workflows/ci-slackbot-proxy.yml

@@ -63,6 +63,7 @@ jobs:
     - name: Install dependencies
       run: |
         yarn global add turbo
+        yarn global add node-gyp
         yarn --frozen-lockfile
 
     - name: Lint
@@ -137,6 +138,7 @@ jobs:
     - name: Install dependencies
       run: |
         yarn global add turbo
+        yarn global add node-gyp
         yarn --frozen-lockfile
 
     - name: yarn dev:ci
@@ -220,6 +222,7 @@ jobs:
 
     - name: Install dependencies
       run: |
+        yarn global add node-gyp
         yarn --frozen-lockfile
 
     - name: Restore dist

+ 1 - 0
.github/workflows/release-slackbot-proxy.yml

@@ -109,6 +109,7 @@ jobs:
     - name: Install dependencies
       run: |
         yarn global add turbo
+        yarn global add node-gyp
         yarn --frozen-lockfile
 
     - name: Bump versions for next RC

+ 4 - 8
.github/workflows/release.yml

@@ -31,6 +31,7 @@ jobs:
     - name: Install dependencies
       run: |
         yarn global add turbo
+        yarn global add node-gyp
         yarn --frozen-lockfile
 
     - name: Bump versions
@@ -153,7 +154,7 @@ jobs:
 
 
   post-publish:
-    needs: [publish-image, publish-image-ghcr]
+    needs: [create-github-release, publish-image, publish-image-ghcr]
     runs-on: ubuntu-latest
 
     steps:
@@ -176,15 +177,9 @@ jobs:
         url: ${{ secrets.SLACK_WEBHOOK_URL }}
         created_tag: 'v${{ needs.create-github-release.outputs.RELEASED_VERSION }}'
 
-    - name: Check whether workspace is clean
-      run: |
-        STATUS=`git status --porcelain`
-        if [ -z "$STATUS" ]; then exit 0; else exit 1; fi
-
-
 
   create-pr-for-next-rc:
-    needs: [publish-image, publish-image-ghcr]
+    needs: [create-github-release, publish-image, publish-image-ghcr]
     runs-on: ubuntu-latest
 
     steps:
@@ -201,6 +196,7 @@ jobs:
     - name: Install dependencies
       run: |
         yarn global add turbo
+        yarn global add node-gyp
         yarn --frozen-lockfile
 
     - name: Bump versions for next RC

+ 1 - 1
.github/workflows/reusable-app-build-image.yml

@@ -36,7 +36,7 @@ jobs:
     - uses: actions/checkout@v3
 
     - name: Configure AWS Credentials
-      uses: aws-actions/configure-aws-credentials@v2
+      uses: aws-actions/configure-aws-credentials@v4
       with:
         aws-region: ap-northeast-1
         role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME_FOR_OIDC }}

+ 5 - 3
.github/workflows/reusable-app-prod.yml

@@ -56,6 +56,7 @@ jobs:
 
     - name: Install dependencies
       run: |
+        yarn global add node-gyp
         yarn --frozen-lockfile
 
     - name: Restore dist
@@ -126,7 +127,7 @@ jobs:
 
     services:
       mongodb:
-        image: mongo:4.4
+        image: mongo:6.0
         ports:
         - 27017/tcp
       elasticsearch:
@@ -214,7 +215,7 @@ jobs:
 
     services:
       mongodb:
-        image: mongo:4.4
+        image: mongo:6.0
         ports:
         - 27017/tcp
       elasticsearch:
@@ -267,6 +268,7 @@ jobs:
 
     - name: Install dependencies
       run: |
+        yarn global add node-gyp
         yarn --frozen-lockfile
         yarn cypress install
 
@@ -303,7 +305,7 @@ jobs:
         cat config/ci/.env.local.for-auto-install-with-allowing-guest >> .env.production.local
 
     - name: Cypress Run
-      uses: cypress-io/github-action@v5
+      uses: cypress-io/github-action@v6
       with:
         browser: chromium
         working-directory: ./apps/app

+ 1 - 0
.github/workflows/reusable-app-reg-suit.yml

@@ -82,6 +82,7 @@ jobs:
 
     - name: Install dependencies
       run: |
+        yarn global add node-gyp
         yarn --frozen-lockfile
 
     - name: Download screenshots taken by cypress

+ 25 - 1
CHANGELOG.md

@@ -1,9 +1,33 @@
 # Changelog
 
-## [Unreleased](https://github.com/weseek/growi/compare/v6.2.0...HEAD)
+## [Unreleased](https://github.com/weseek/growi/compare/v6.2.1...HEAD)
 
 *Please do not manually update this file. We've automated the process.*
 
+## [v6.2.1](https://github.com/weseek/growi/compare/v6.2.0...v6.2.1) - 2023-10-03
+
+### BREAKING CHANGES
+
+- support: Omit promster (#8105) @yuki-takei
+
+### 🚀 Improvement
+
+- imprv: Download a markdown file using the page name as the file name (#8061) @soumaeda
+- imprv: Admin customize presentation form (#8083) @meiri-k
+- imprv: i18n for marp settings (#8110) @moekumasaka
+- imprv: i18n "Create /Sidebar page" label (#8085) @yuki-takei
+
+### 🐛 Bug Fixes
+
+- fix: Marp is enabled incorrectly problem (#8100) @reiji-h
+- fix: Do not work img tag if use style property 62x (#8092) @jam411
+
+### 🧰 Maintenance
+
+- support: Internationalization USER_REGISTRATION_APPROVAL_REQUEST label for v62x (#8130) @jam411
+- ci(deps): bump get-func-name from 2.0.0 to 2.0.2 (#8119) @dependabot
+- support: Omit promster (#8105) @yuki-takei
+
 ## [v6.2.0](https://github.com/weseek/growi/compare/v6.1.12...v6.2.0) - 2023-09-14
 
 ### 💎 Features

+ 2 - 2
README.md

@@ -84,12 +84,12 @@ See [GROWI Docs: Environment Variables](https://docs.growi.org/en/admin-guide/ad
 - npm 6.x
 - yarn
 - [Turborepo](https://turbo.build/repo)
-- MongoDB 4.x
+- MongoDB 4.4 or above
 
 ### Optional Dependencies
 
 - Redis 3.x
-- ElasticSearch 6.x or 7.x (needed when using Full-text search)
+- ElasticSearch 7.x or 8.x (needed when using Full-text search)
   - **CAUTION: Following plugins are required**
     - [Japanese (kuromoji) Analysis plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji.html)
     - [ICU Analysis Plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-icu.html)

+ 2 - 2
README_JP.md

@@ -83,12 +83,12 @@ Crowi からの移行は **[こちら](https://docs.growi.org/en/admin-guide/mig
 - npm 6.x
 - yarn
 - [Turborepo](https://turbo.build/repo)
-- MongoDB 4.x
+- MongoDB 4.4 以上
 
 ### オプションの依存関係
 
 - Redis 3.x
-- ElasticSearch 6.x or 7.x (needed when using Full-text search)
+- ElasticSearch 7.x or 8.x (needed when using Full-text search)
   - **注意: 次のプラグインが必要です**
     - [Japanese (kuromoji) Analysis plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji.html)
     - [ICU Analysis Plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-icu.html)

+ 1 - 1
apps/app/bin/github-actions/update-readme.sh

@@ -2,4 +2,4 @@
 
 cd docker
 
-sed -i -e "s/^\([*] \[\`\)[^\`]\+\(\`, \`6\.2\`, .\+\]\)\(.\+\/blob\/v\).\+\(\/packages\/app\/docker\/Dockerfile.\+\)$/\1${RELEASED_VERSION}\2\3${RELEASED_VERSION}\4/" README.md
+sed -i -e "s/^\([*] \[\`\)[^\`]\+\(\`, \`6\.2\`, .\+\]\)\(.\+\/blob\/v\).\+\(\/apps\/app\/docker\/Dockerfile.\+\)$/\1${RELEASED_VERSION}\2\3${RELEASED_VERSION}\4/" README.md

+ 1 - 0
apps/app/docker/Dockerfile

@@ -34,6 +34,7 @@ COPY --from=base ${optDir}/out/yarn.lock ./yarn.lock
 
 # setup (with network-timeout = 1 hour)
 RUN yarn config set network-timeout 3600000
+RUN yarn global add node-gyp
 RUN yarn --frozen-lockfile
 
 # make artifacts

+ 3 - 3
apps/app/docker/README.md

@@ -10,8 +10,8 @@ GROWI Official docker image
 Supported tags and respective Dockerfile links
 ------------------------------------------------
 
-* [`6.2.0`, `6.2`, `6`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v6.2.0/apps/app/docker/Dockerfile)
-* [`6.1.0`, `6.1` (Dockerfile)](https://github.com/weseek/growi/blob/v6.1.8/apps/app/docker/Dockerfile)
+* [`6.2.1`, `6.2`, `6`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v6.2.1/apps/app/docker/Dockerfile)
+* [`6.1.15`, `6.1` (Dockerfile)](https://github.com/weseek/growi/blob/v6.1.15/apps/app/docker/Dockerfile)
 * [`6.0.15`, `6.0` (Dockerfile)](https://github.com/weseek/growi/blob/v6.0.15/packages/app/docker/Dockerfile)
 * [`5.1.7`, `5.1`, `5` (Dockerfile)](https://github.com/weseek/growi/blob/v5.1.7/packages/app/docker/Dockerfile)
 * [`5.1.7-nocdn`, `5.1-nocdn`, `5-nocdn` (Dockerfile)](https://github.com/weseek/growi/blob/v5.1.7/packages/app/docker/Dockerfile)
@@ -34,7 +34,7 @@ Requirements
 
 ### Optional Dependencies
 
-* ElasticSearch (>= 6.6)
+* ElasticSearch (>= 7.17)
     * Japanese (kuromoji) Analysis plugin
     * ICU Analysis Plugin
 

+ 3 - 2
apps/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/app",
-  "version": "6.2.1-RC.0",
+  "version": "6.2.2-RC.0",
   "license": "MIT",
   "scripts": {
     "//// for production": "",
@@ -243,13 +243,14 @@
     "jquery": "^3.7.0",
     "load-css-file": "^1.0.0",
     "material-icons": "^1.11.3",
-    "mongodb-memory-server": "^8.12.2",
+    "mongodb-memory-server": "^8.15.1",
     "morgan": "^1.10.0",
     "null-loader": "^4.0.1",
     "penpal": "^4.0.0",
     "plantuml-encoder": "^1.2.5",
     "popper.js": "^1.16.1",
     "prettier": "^1.19.1",
+    "pretty-bytes": "^6.1.1",
     "react-codemirror2": "^6.0.0",
     "react-copy-to-clipboard": "^5.0.1",
     "react-dropzone": "^11.2.4",

+ 4 - 2
apps/app/public/static/locales/en_US/admin.json

@@ -916,6 +916,7 @@
     "USER_API_TOKEN_UPDATE": "API Token update",
     "USER_EDITOR_SETTINGS_UPDATE": "Editor settings update",
     "USER_IN_APP_NOTIFICATION_SETTINGS_UPDATE": "In-App Notification settings update",
+    "USER_REGISTRATION_APPROVAL_REQUEST": "User registration request for ID/Password authentication",
     "PAGE_VIEW": "Page view",
     "PAGE_USER_HOME_VIEW": "Page view (User home)",
     "PAGE_FORBIDDEN": "Page view (Fobidden page)",
@@ -1081,7 +1082,8 @@
       "name_mapper_detail": "Attribute to map as group name",
       "updated_group_sync_settings": "Updated LDAP group sync settings",
       "password": "Password",
-      "password_detail": "Login password is necessary because Bind type is set to User Bind"
+      "password_detail": "Login password is necessary because Bind type is set to User Bind",
+      "auth_not_set": "Enable and configure LDAP auth in security settings before sync"
     },
     "keycloak": {
       "group_sync_settings": "Keycloak Group Sync Settings",
@@ -1097,7 +1099,7 @@
       "group_sync_client_secret_detail": "Id of the secret used to authenticate to request to Keycloak admin API",
       "updated_group_sync_settings": "Updated Keycloak group sync settings",
       "preserve_deleted_keycloak_groups": "Preserve Deleted Keycloak Groups",
-      "auth_not_set": "Please set up and enable OIDC or SAML with Keycloak in security settings before sync"
+      "auth_not_set": "Enable and configure OIDC or SAML with Keycloak in security settings before sync"
     },
     "auto_generate_user_on_sync": "Auto Generate User on Sync",
     "description_mapper_detail": "Attribute to map as group description. Description can be edited after sync. However, when a mapper is set, the edited value can possibly be overwritten by the next sync."

+ 4 - 2
apps/app/public/static/locales/ja_JP/admin.json

@@ -925,6 +925,7 @@
     "USER_API_TOKEN_UPDATE": "API トークンの更新",
     "USER_EDITOR_SETTINGS_UPDATE": "エディター設定の更新",
     "USER_IN_APP_NOTIFICATION_SETTINGS_UPDATE": "アプリ内通知設定の更新",
+    "USER_REGISTRATION_APPROVAL_REQUEST": "ID/Password 認証のユーザー登録リクエスト",
     "PAGE_VIEW": "ページ閲覧",
     "PAGE_USER_HOME_VIEW": "ページ閲覧(ユーザーホーム)",
     "PAGE_FORBIDDEN": "ページ閲覧(fobiddenページ)",
@@ -1090,7 +1091,8 @@
       "name_mapper_detail": "グループの「名前」として読み込む属性",
       "updated_group_sync_settings": "LDAP グループ同期設定を更新しました",
       "password": "パスワード",
-      "password_detail": "認証設定がユーザ Bind のため、ログイン時のパスワードの入力が必要となります"
+      "password_detail": "認証設定がユーザ Bind のため、ログイン時のパスワードの入力が必要となります",
+      "auth_not_set": "同期実行前にセキュリティ設定で LDAP 認証を有効にし、設定してください"
     },
     "keycloak": {
       "group_sync_settings": "Keycloak グループ同期設定",
@@ -1106,7 +1108,7 @@
       "group_sync_client_secret_detail": "Keycloak admin API にリクエストするための認証に使う client の secret",
       "updated_group_sync_settings": "Keycloak グループ同期設定を更新しました",
       "preserve_deleted_keycloak_groups": "Keycloak から削除されたグループを GROWI に残す",
-      "auth_not_set": "同期実行前にセキュリティ設定で Keycloak を使った OIDC または SAML 認証を設定し、有効にしてください"
+      "auth_not_set": "同期実行前にセキュリティ設定で Keycloak を使った OIDC または SAML 認証を有効にし、設定してください"
     },
     "auto_generate_user_on_sync": "作成されていない GROWI アカウントを自動生成する",
     "description_mapper_detail": "グループの「説明」として読み込む属性。「説明」は同期後に編集可能です。ただし、mapper が設定されている場合、編集内容は再同期によって上書きされます。"

+ 4 - 2
apps/app/public/static/locales/zh_CN/admin.json

@@ -924,6 +924,7 @@
     "USER_API_TOKEN_UPDATE": "API 令牌更新",
     "USER_EDITOR_SETTINGS_UPDATE": "编辑器设置更新",
     "USER_IN_APP_NOTIFICATION_SETTINGS_UPDATE": "应用内通知设置更新",
+    "USER_REGISTRATION_APPROVAL_REQUEST": "用户注册 ID/密码验证请求",
     "PAGE_VIEW": "页面浏览量",
     "PAGE_USER_HOME_VIEW": "页面浏览量(用户主页)",
     "PAGE_FORBIDDEN": "页面浏览量(禁止页面)",
@@ -1089,7 +1090,8 @@
       "name_mapper_detail": "Attribute to map as group name",
       "updated_group_sync_settings": "Updated LDAP group sync settings",
       "password": "Password",
-      "password_detail": "Login password is necessary because Bind type is set to User Bind"
+      "password_detail": "Login password is necessary because Bind type is set to User Bind",
+      "auth_not_set": "Enable and configure LDAP auth in security settings before sync"
     },
     "keycloak": {
       "group_sync_settings": "Keycloak Group Sync Settings",
@@ -1105,7 +1107,7 @@
       "group_sync_client_secret_detail": "Id of the secret used to authenticate to request to Keycloak admin API",
       "updated_group_sync_settings": "Updated Keycloak group sync settings",
       "preserve_deleted_keycloak_groups": "Preserve Deleted Keycloak Groups",
-      "auth_not_set": "Please set up and enable OIDC or SAML with Keycloak in security settings before sync"
+      "auth_not_set": "Enable and configure OIDC or SAML with Keycloak in security settings before sync"
     },
     "auto_generate_user_on_sync": "Auto Generate User on Sync",
     "description_mapper_detail": "Attribute to map as group description. Description can be edited after sync. However, when a mapper is set, the edited value can possibly be overwritten by the next sync."

+ 3 - 3
apps/app/src/components/SavePageControls/GrantSelector/GrantSelector.tsx

@@ -1,7 +1,6 @@
 import React, { useCallback, useState } from 'react';
 
-import { isPopulated } from '@growi/core';
-import type { GroupType, IGrantedGroup } from '@growi/core';
+import { isPopulated, GroupType, type IGrantedGroup } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import {
   UncontrolledDropdown,
@@ -197,7 +196,8 @@ export const GrantSelector = (props: Props): JSX.Element => {
               className={`list-group-item list-group-item-action ${activeClass}`}
               onClick={() => groupListItemClickHandler(group)}
             >
-              <h5>{group.item.name}</h5>
+              <h5 className="d-inline-block">{group.item.name}</h5>
+              {group.type === GroupType.externalUserGroup && <span className="ml-2 badge badge-pill badge-info">external</span>}
               {/* TODO: Replace <div className="small">(TBD) List group members</div> */}
             </button>
           );

+ 4 - 2
apps/app/src/features/external-user-group/client/components/ExternalUserGroup/SyncExecution.tsx

@@ -74,11 +74,13 @@ export const SyncExecution = ({
   const onSyncBtnClick = useCallback(async(e) => {
     e.preventDefault();
     try {
-      await requestSyncAPI(e);
-      setProgress({ total: 0, current: 0 });
+      // set sync status before requesting to API, so that setting to syncFailed does not get overwritten
       setSyncStatus(SyncStatus.syncExecuting);
+      setProgress({ total: 0, current: 0 });
+      await requestSyncAPI(e);
     }
     catch (errs) {
+      setSyncStatus(SyncStatus.syncFailed);
       toastError(t(errs[0]?.code));
     }
   }, [t, requestSyncAPI]);

+ 18 - 3
apps/app/src/features/external-user-group/server/routes/apiv3/external-user-group.ts

@@ -314,8 +314,23 @@ module.exports = (crowi: Crowi): Router => {
       );
     }
 
+    const isLdapEnabled = await configManager.getConfig('crowi', 'security:passport-ldap:isEnabled');
+    if (!isLdapEnabled) {
+      return res.apiv3Err(
+        new ErrorV3('Authentication using ldap is not set', 'external_user_group.ldap.auth_not_set'), 422,
+      );
+    }
+
+    try {
+      await crowi.ldapUserGroupSyncService?.init(req.user.name, req.body.password);
+    }
+    catch (e) {
+      return res.apiv3Err(
+        new ErrorV3('LDAP group sync failed', 'external_user_group.sync_failed'), 500,
+      );
+    }
+
     // Do not await for sync to finish. Result (completed, failed) will be notified to the client by socket-io.
-    await crowi.ldapUserGroupSyncService?.init(req.user.name, req.body.password);
     crowi.ldapUserGroupSyncService?.syncExternalUserGroups();
 
     return res.apiv3({}, 202);
@@ -352,12 +367,12 @@ module.exports = (crowi: Crowi): Router => {
     const authProviderType = getAuthProviderType();
     if (authProviderType == null) {
       return res.apiv3Err(
-        new ErrorV3('Authentication using keycloak is not set', 'external_user_group.keycloak.auth_not_set'), 500,
+        new ErrorV3('Authentication using keycloak is not set', 'external_user_group.keycloak.auth_not_set'), 422,
       );
     }
 
-    // Do not await for sync to finish. Result (completed, failed) will be notified to the client by socket-io.
     crowi.keycloakUserGroupSyncService?.init(authProviderType);
+    // Do not await for sync to finish. Result (completed, failed) will be notified to the client by socket-io.
     crowi.keycloakUserGroupSyncService?.syncExternalUserGroups();
 
     return res.apiv3({}, 202);

+ 6 - 6
apps/app/src/features/external-user-group/server/service/keycloak-user-group-sync.integ.ts

@@ -88,7 +88,8 @@ vi.mock('@keycloak/keycloak-admin-client', () => {
 
         // mock group users
         listMembers: (payload) => {
-          if (payload?.id === 'groupId1') {
+          // set 'first' condition to 0 (the first member request to server) or else it will result in infinite loop
+          if (payload?.id === 'groupId1' && payload?.first === 0) {
             return Promise.resolve([
               {
                 id: 'userId1',
@@ -97,7 +98,7 @@ vi.mock('@keycloak/keycloak-admin-client', () => {
               },
             ]);
           }
-          if (payload?.id === 'groupId2') {
+          if (payload?.id === 'groupId2' && payload?.first === 0) {
             return Promise.resolve([
               {
                 id: 'userId2',
@@ -106,7 +107,7 @@ vi.mock('@keycloak/keycloak-admin-client', () => {
               },
             ]);
           }
-          if (payload?.id === 'groupId3') {
+          if (payload?.id === 'groupId3' && payload?.first === 0) {
             return Promise.resolve([
               {
                 id: 'userId3',
@@ -115,7 +116,7 @@ vi.mock('@keycloak/keycloak-admin-client', () => {
               },
             ]);
           }
-          if (payload?.id === 'groupId4') {
+          if (payload?.id === 'groupId4' && payload?.first === 0) {
             return Promise.resolve([
               {
                 id: 'userId4',
@@ -124,7 +125,7 @@ vi.mock('@keycloak/keycloak-admin-client', () => {
               },
             ]);
           }
-          return Promise.reject(new Error('not found'));
+          return Promise.resolve([]);
         },
       };
 
@@ -150,7 +151,6 @@ describe('KeycloakUserGroupSyncService.generateExternalUserGroupTrees', () => {
   });
 
   it('creates ExternalUserGroupTrees', async() => {
-
     const rootNodes = await keycloakUserGroupSyncService?.generateExternalUserGroupTrees();
 
     expect(rootNodes?.length).toBe(2);

+ 25 - 1
apps/app/src/features/external-user-group/server/service/keycloak-user-group-sync.ts

@@ -59,6 +59,7 @@ export class KeycloakUserGroupSyncService extends ExternalUserGroupSyncService {
     await this.auth();
 
     // Type is 'GroupRepresentation', but 'find' does not return 'attributes' field. Hence, attribute for description is not present.
+    logger.info('Get groups from keycloak server');
     const rootGroups = await this.kcAdminClient.groups.find({ realm: this.realm });
 
     return (await batchProcessPromiseAll(rootGroups, TREES_BATCH_SIZE, group => this.groupRepresentationToTreeNode(group)))
@@ -85,7 +86,8 @@ export class KeycloakUserGroupSyncService extends ExternalUserGroupSyncService {
   private async groupRepresentationToTreeNode(group: GroupRepresentation): Promise<ExternalUserGroupTreeNode | null> {
     if (group.id == null || group.name == null) return null;
 
-    const userRepresentations = await this.kcAdminClient.groups.listMembers({ id: group.id, realm: this.realm });
+    logger.info('Get users from keycloak server');
+    const userRepresentations = await this.getMembers(group.id);
 
     const userInfos = userRepresentations != null ? this.userRepresentationsToExternalUserInfos(userRepresentations) : [];
     const description = await this.getGroupDescription(group.id) || undefined;
@@ -111,12 +113,34 @@ export class KeycloakUserGroupSyncService extends ExternalUserGroupSyncService {
     };
   }
 
+  private async getMembers(groupId: string): Promise<UserRepresentation[]> {
+    let allUsers: UserRepresentation[] = [];
+
+    const fetchUsersWithOffset = async(offset: number) => {
+      await this.auth();
+      const response = await this.kcAdminClient.groups.listMembers({
+        id: groupId, realm: this.realm, first: offset,
+      });
+
+      if (response != null && response.length > 0) {
+        allUsers = allUsers.concat(response);
+        return fetchUsersWithOffset(offset + response.length);
+      }
+    };
+
+    await fetchUsersWithOffset(0);
+
+    return allUsers;
+  }
+
+
   /**
    * Fetch group detail from Keycloak and return group description
    */
   private async getGroupDescription(groupId: string): Promise<string | null> {
     if (this.groupDescriptionAttribute == null) return null;
 
+    await this.auth();
     const groupDetail = await this.kcAdminClient.groups.findOne({ id: groupId, realm: this.realm });
 
     const description = groupDetail?.attributes?.[this.groupDescriptionAttribute]?.[0];

+ 2 - 0
apps/app/src/server/models/config.ts

@@ -147,6 +147,8 @@ export const defaultCrowiConfigs: { [key: string]: any } = {
   'external-user-group:ldap:groupMembershipAttributeType': 'DN',
   'external-user-group:ldap:autoGenerateUserOnGroupSync': false,
   'external-user-group:ldap:preserveDeletedGroups': false,
+  'external-user-group:keycloak:autoGenerateUserOnGroupSync': false,
+  'external-user-group:keycloak:preserveDeletedGroups': false,
   /* eslint-enable key-spacing */
 };
 

+ 24 - 1
apps/app/src/utils/promise.spec.ts

@@ -5,7 +5,6 @@ describe('batchProcessPromiseAll', () => {
     const batch1 = [1, 2, 3, 4, 5];
     const batch2 = [6, 7, 8, 9, 10];
     const batch3 = [11, 12];
-
     const all = [...batch1, ...batch2, ...batch3];
 
     const actualProcessedBatches: number[][] = [];
@@ -25,4 +24,28 @@ describe('batchProcessPromiseAll', () => {
       batch3,
     ]);
   });
+
+  describe('error handling', () => {
+    const all = [1, 2, 3, 4, 5, 6, 7, 8, '9', 10];
+
+    const multiplyBy10 = async(num) => {
+      if (typeof num !== 'number') {
+        throw new Error('Is not number');
+      }
+      return num * 10;
+    };
+
+    describe('when throwIfRejected is true', () => {
+      it('throws error when there is a Promise rejection', async() => {
+        await expect(batchProcessPromiseAll(all, 5, multiplyBy10)).rejects.toThrow('Is not number');
+      });
+    });
+
+    describe('when throwIfRejected is false', () => {
+      it('doesn\'t throw error when there is a Promise rejection', async() => {
+        const expected = [10, 20, 30, 40, 50, 60, 70, 80, 100];
+        await expect(batchProcessPromiseAll(all, 5, multiplyBy10, false)).resolves.toStrictEqual(expected);
+      });
+    });
+  });
 });

+ 13 - 3
apps/app/src/utils/promise.ts

@@ -4,21 +4,31 @@
  * @param items array to process
  * @param limit batch size
  * @param fn function to apply on each item
+ * @param throwIfRejected whether or not to throw Error when there is a rejected Promise
  * @returns result of fn applied to each item
  */
 export const batchProcessPromiseAll = async<I, O>(
   items: Array<I>,
   limit: number,
   fn: (item: I, index?: number, array?: Array<I>) => Promise<O>,
+  throwIfRejected = true,
 ): Promise<O[]> => {
-  let results: O[] = [];
+  const results: O[] = [];
+
   for (let start = 0; start < items.length; start += limit) {
     const end = Math.min(start + limit, items.length);
 
     // eslint-disable-next-line no-await-in-loop
-    const slicedResults = await Promise.all(items.slice(start, end).map(fn));
+    const slicedResults = await Promise.allSettled(items.slice(start, end).map(fn));
 
-    results = results.concat(slicedResults);
+    slicedResults.forEach((result) => {
+      if (result.status === 'fulfilled') {
+        results.push(result.value);
+      }
+      else if (throwIfRejected && result.reason instanceof Error) {
+        throw result.reason;
+      }
+    });
   }
 
   return results;

+ 1 - 0
apps/slackbot-proxy/docker/Dockerfile

@@ -29,6 +29,7 @@ COPY --from=base ${optDir}/out/yarn.lock ./yarn.lock
 
 # setup (with network-timeout = 1 hour)
 RUN yarn config set network-timeout 3600000
+RUN yarn global add node-gyp
 RUN yarn --frozen-lockfile
 
 # make artifacts

+ 1 - 1
apps/slackbot-proxy/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/slackbot-proxy",
-  "version": "6.2.1-slackbot-proxy.0",
+  "version": "6.2.2-slackbot-proxy.0",
   "license": "MIT",
   "scripts": {
     "build": "yarn tsc && tsc-alias -p tsconfig.build.json",

+ 4 - 5
package.json

@@ -1,6 +1,6 @@
 {
   "name": "growi",
-  "version": "6.2.1-RC.0",
+  "version": "6.2.2-RC.0",
   "description": "Team collaboration software using markdown",
   "tags": [
     "wiki",
@@ -52,7 +52,6 @@
     "@swc-node/register": "^1.6.2",
     "@swc/core": "^1.3.36",
     "@swc/helpers": "^0.4.14",
-    "@testing-library/cypress": "^9.0.0",
     "@types/css-modules": "^1.0.2",
     "@types/eslint": "^8.37.0",
     "@types/estree": "^1.0.1",
@@ -63,8 +62,8 @@
     "@vitejs/plugin-react": "^4.0.3",
     "@vitest/coverage-c8": "^0.31.1",
     "@vitest/ui": "^0.31.1",
-    "cypress": "^12.17.2",
-    "cypress-wait-until": "^1.7.2",
+    "cypress": "^13.3.0",
+    "cypress-wait-until": "^2.0.1",
     "eslint": "^8.41.0",
     "eslint-config-next": "^12.1.6",
     "eslint-config-weseek": "^2.1.1",
@@ -77,7 +76,7 @@
     "glob": "^8.1.0",
     "mock-require": "^3.0.3",
     "path-browserify": "^1.0.1",
-    "postcss": "^8.4.5",
+    "postcss": "^8.4.31",
     "postcss-scss": "^4.0.3",
     "reg-keygen-git-hash-plugin": "^0.11.1",
     "reg-notify-github-plugin": "^0.11.1",

+ 1 - 1
packages/core/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/core",
-  "version": "6.2.1-RC.0",
+  "version": "6.2.2-RC.0",
   "description": "GROWI Core Libraries",
   "license": "MIT",
   "keywords": [

+ 1 - 1
packages/hackmd/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/hackmd",
-  "version": "6.2.1-RC.0",
+  "version": "6.2.2-RC.0",
   "description": "GROWI js and css files to use hackmd",
   "license": "MIT",
   "type": "module",

+ 1 - 1
packages/presentation/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/presentation",
-  "version": "6.2.1-RC.0",
+  "version": "6.2.2-RC.0",
   "description": "GROWI plugin for presentation",
   "license": "MIT",
   "keywords": [

+ 1 - 1
packages/preset-templates/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/preset-templates",
-  "version": "6.2.1-RC.0",
+  "version": "6.2.2-RC.0",
   "scripts": {
     "test": "vitest run",
     "version": "yarn version --no-git-tag-version --preid=RC"

+ 1 - 1
packages/preset-themes/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@growi/preset-themes",
   "description": "GROWI preset themes",
-  "version": "6.2.1-RC.0",
+  "version": "6.2.2-RC.0",
   "license": "MIT",
   "main": "dist/libs/preset-themes.umd.js",
   "module": "dist/libs/preset-themes.mjs",

+ 1 - 1
packages/remark-attachment-refs/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/remark-attachment-refs",
-  "version": "6.2.1-RC.0",
+  "version": "6.2.2-RC.0",
   "description": "GROWI Plugin to add ref/refimg/refs/refsimg tags",
   "license": "MIT",
   "keywords": [

+ 1 - 1
packages/remark-drawio/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/remark-drawio",
-  "version": "6.2.1-RC.0",
+  "version": "6.2.2-RC.0",
   "description": "remark plugin to draw diagrams with draw.io (diagrams.net)",
   "license": "MIT",
   "keywords": [

+ 1 - 1
packages/remark-growi-directive/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/remark-growi-directive",
-  "version": "6.2.1-RC.0",
+  "version": "6.2.2-RC.0",
   "description": "remark plugin to support GROWI plugin (forked from remark-directive@2.0.1)",
   "license": "MIT",
   "keywords": [

+ 1 - 1
packages/remark-lsx/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/remark-lsx",
-  "version": "6.2.1-RC.0",
+  "version": "6.2.2-RC.0",
   "description": "GROWI plugin to list pages",
   "license": "MIT",
   "keywords": [

+ 1 - 1
packages/slack/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/slack",
-  "version": "6.2.1-RC.0",
+  "version": "6.2.2-RC.0",
   "license": "MIT",
   "type": "module",
   "main": "dist/index.cjs",

+ 1 - 1
packages/ui/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/ui",
-  "version": "6.2.1-RC.0",
+  "version": "6.2.2-RC.0",
   "description": "GROWI UI Libraries",
   "license": "MIT",
   "keywords": [

Разница между файлами не показана из-за своего большого размера
+ 90 - 454
yarn.lock


Некоторые файлы не были показаны из-за большого количества измененных файлов