From 362b2ee48193d2205890cdc0ff07da4b3ba17772 Mon Sep 17 00:00:00 2001 From: Montassar Ghanmy Date: Tue, 5 Sep 2023 15:01:24 +0100 Subject: [PATCH 01/12] =?UTF-8?q?=E2=AD=90=EF=B8=8F=20Open=20in=20new=20wi?= =?UTF-8?q?ndow=20menu=20option=20(#183)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ⭐️ Open in new window menu option --- tdrive/frontend/public/locales/en.json | 1 + tdrive/frontend/public/locales/fr.json | 1 + tdrive/frontend/public/locales/ru.json | 1 + .../app/views/client/body/drive/context-menu.tsx | 13 +++++++++++++ 4 files changed, 16 insertions(+) diff --git a/tdrive/frontend/public/locales/en.json b/tdrive/frontend/public/locales/en.json index 006ced8e9..3f3682d0d 100644 --- a/tdrive/frontend/public/locales/en.json +++ b/tdrive/frontend/public/locales/en.json @@ -313,6 +313,7 @@ "components.item_context_menu.today": "Today", "components.item_context_menu.last_week": "Last week", "components.item_context_menu.last_month": "Last month", + "components.item_context_menu.open_new_window": "Open in new window", "scenes.app.shared_with_me.shared_with_me": "Shared with me", "scenes.app.shared_with_me.file_type": "File type", "scenes.app.shared_with_me.people": "People", diff --git a/tdrive/frontend/public/locales/fr.json b/tdrive/frontend/public/locales/fr.json index 0fdc284f1..4c9e82b6e 100644 --- a/tdrive/frontend/public/locales/fr.json +++ b/tdrive/frontend/public/locales/fr.json @@ -312,6 +312,7 @@ "components.item_context_menu.today": "Aujourd'hui", "components.item_context_menu.last_week": "La semaine dernière", "components.item_context_menu.last_month": "Le mois dernier", + "components.item_context_menu.open_new_window": "Open in new window", "scenes.app.shared_with_me.shared_with_me": "Partagé avec moi", "scenes.app.shared_with_me.file_type": "Type de fichier", "scenes.app.shared_with_me.people": "Personnes", diff --git a/tdrive/frontend/public/locales/ru.json b/tdrive/frontend/public/locales/ru.json index d794dc454..bff8909f7 100644 --- a/tdrive/frontend/public/locales/ru.json +++ b/tdrive/frontend/public/locales/ru.json @@ -312,6 +312,7 @@ "components.item_context_menu.today": "Сегодня", "components.item_context_menu.last_week": "За неделю", "components.item_context_menu.last_month": "За месяц", + "components.item_context_menu.open_new_window": "Открыть в новом окне", "scenes.app.shared_with_me.shared_with_me": "Доступные мне", "scenes.app.shared_with_me.file_type": "Тип файла", "scenes.app.shared_with_me.people": "Люди", diff --git a/tdrive/frontend/src/app/views/client/body/drive/context-menu.tsx b/tdrive/frontend/src/app/views/client/body/drive/context-menu.tsx index 80ee4a570..507fe2752 100644 --- a/tdrive/frontend/src/app/views/client/body/drive/context-menu.tsx +++ b/tdrive/frontend/src/app/views/client/body/drive/context-menu.tsx @@ -19,6 +19,8 @@ import { ToasterService } from '@features/global/services/toaster-service'; import { copyToClipboard } from '@features/global/utils/CopyClipboard'; import { SharedWithMeFilterState } from '@features/drive/state/shared-with-me-filter'; import { getCurrentUserList } from '@features/users/hooks/use-user-list'; +import RouterServices from '@features/router/services/router-service'; +import useRouterCompany from '@features/router/hooks/use-router-company'; import _ from 'lodash'; import Languages from 'features/global/services/languages-service'; @@ -43,6 +45,8 @@ export const useOnBuildContextMenu = (children: DriveItem[], initialParentId?: s const setPropertiesModalState = useSetRecoilState(PropertiesModalAtom); const setUsersModalState = useSetRecoilState(UsersModalAtom); const { open: preview } = useDrivePreview(); + const company = useRouterCompany(); + function getIdsFromArray(arr: DriveItem[]): string[] { return arr.map((obj) => obj.id); } @@ -87,6 +91,15 @@ export const useOnBuildContextMenu = (children: DriveItem[], initialParentId?: s } }, { type: 'separator' }, + { + type: 'menu', + text: Languages.t('components.item_context_menu.open_new_window'), + onClick: () => { + const route = RouterServices.generateRouteFromState({ companyId: company, viewId: item.parent_id, itemId: item.id }); + window.open(route, '_blank'); + } + }, + { type: 'separator' }, { type: 'menu', text: Languages.t('components.item_context_menu.manage_access'), From 666f51efb0d4e8aefc66a3d972bc0f486d9606a5 Mon Sep 17 00:00:00 2001 From: Montassar Ghanmy Date: Tue, 5 Sep 2023 15:54:26 +0100 Subject: [PATCH 02/12] =?UTF-8?q?=E2=AD=90=EF=B8=8F=20Open=20folder=20in?= =?UTF-8?q?=20new=20window?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ⭐️ Open folder in new window --- .../src/app/views/client/body/drive/context-menu.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tdrive/frontend/src/app/views/client/body/drive/context-menu.tsx b/tdrive/frontend/src/app/views/client/body/drive/context-menu.tsx index 507fe2752..bf6b24030 100644 --- a/tdrive/frontend/src/app/views/client/body/drive/context-menu.tsx +++ b/tdrive/frontend/src/app/views/client/body/drive/context-menu.tsx @@ -95,7 +95,10 @@ export const useOnBuildContextMenu = (children: DriveItem[], initialParentId?: s type: 'menu', text: Languages.t('components.item_context_menu.open_new_window'), onClick: () => { - const route = RouterServices.generateRouteFromState({ companyId: company, viewId: item.parent_id, itemId: item.id }); + const itemId = !item.is_directory ? item.id : ""; + const viewId = item.is_directory ? item.id : item.parent_id; + const route = RouterServices.generateRouteFromState({ companyId: company, viewId, itemId }); + window.open(route, '_blank'); window.open(route, '_blank'); } }, From b149882c3494e49867dca078f13971e7494af3ec Mon Sep 17 00:00:00 2001 From: Anton Shepilov Date: Wed, 6 Sep 2023 18:47:54 +0200 Subject: [PATCH 03/12] =?UTF-8?q?=F0=9F=8C=9F=20Synchronization=20of=20the?= =?UTF-8?q?=20user=20with=20LDAP=20(#185)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🌟Synchronization of the user with LDAP --- .github/workflows/ldap-sync.yml | 15 +++ .github/workflows/publish-ldap-sync.yml | 31 +++++++ tdrive/backend/utils/ldap-sync/.env.example | 6 ++ tdrive/backend/utils/ldap-sync/.nvmrc | 1 + tdrive/backend/utils/ldap-sync/package.json | 26 ++++++ tdrive/backend/utils/ldap-sync/src/index.ts | 96 ++++++++++++++++++++ tdrive/backend/utils/ldap-sync/tsconfig.json | 11 +++ tdrive/docker/tdrive-ldap-sync/Dockerfile | 15 +++ 8 files changed, 201 insertions(+) create mode 100644 .github/workflows/ldap-sync.yml create mode 100644 .github/workflows/publish-ldap-sync.yml create mode 100644 tdrive/backend/utils/ldap-sync/.env.example create mode 100644 tdrive/backend/utils/ldap-sync/.nvmrc create mode 100644 tdrive/backend/utils/ldap-sync/package.json create mode 100644 tdrive/backend/utils/ldap-sync/src/index.ts create mode 100644 tdrive/backend/utils/ldap-sync/tsconfig.json create mode 100644 tdrive/docker/tdrive-ldap-sync/Dockerfile diff --git a/.github/workflows/ldap-sync.yml b/.github/workflows/ldap-sync.yml new file mode 100644 index 000000000..54088a891 --- /dev/null +++ b/.github/workflows/ldap-sync.yml @@ -0,0 +1,15 @@ +name: ldap-sync-build + +on: + pull_request: + branches: [main] + paths: + - "tdrive/backend/utils/**" + +jobs: + ldap-sync-build: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: Build ldap sync + run: cd tdrive/backend/utils/ldap-sync && npm i && npm run build \ No newline at end of file diff --git a/.github/workflows/publish-ldap-sync.yml b/.github/workflows/publish-ldap-sync.yml new file mode 100644 index 000000000..bfa4ad74e --- /dev/null +++ b/.github/workflows/publish-ldap-sync.yml @@ -0,0 +1,31 @@ +name: publish-ldap-sync + +on: + pull_request: + branches: [main] + paths: + - "tdrive/utils/ldap-sync/**" + +jobs: + publish-node: + runs-on: ubuntu-20.04 + steps: + - name: Set env to production + if: endsWith(github.ref, '/main') + run: 'echo "DOCKERTAG=latest" >> $GITHUB_ENV' + - name: "Push to the registry following labels:" + run: | + echo "${{ env.DOCKERTAG }},${{ env.DOCKERTAGVERSION }}" + - uses: actions/checkout@v2 + - name: Publish to Registry + uses: elgohr/Publish-Docker-Github-Action@v5 + with: + name: tdrive/tdrive-ldap-sync + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} + workdir: tdrive + registry: docker-registry.linagora.com + context: . + target: production + buildoptions: "-t docker-registry.linagora.com/tdrive/tdrive-ldap-sync -f docker/tdrive-ldap-sync/Dockerfile" + tags: "${{ env.DOCKERTAG }},${{ env.DOCKERTAGVERSION }}" \ No newline at end of file diff --git a/tdrive/backend/utils/ldap-sync/.env.example b/tdrive/backend/utils/ldap-sync/.env.example new file mode 100644 index 000000000..9c2c3e54f --- /dev/null +++ b/tdrive/backend/utils/ldap-sync/.env.example @@ -0,0 +1,6 @@ +LDAP_URL=ldap://localhost:389 +LDAP_BIND_DN= +LDAP_BIND_CREDENTIALS= +LDAP_SEARCH_BASE=dc=example,dc=com +LDAP_SEARCH_FILTER=(objectClass=inetorgperson) +API_URL=http://tdrive:4000/api/sync \ No newline at end of file diff --git a/tdrive/backend/utils/ldap-sync/.nvmrc b/tdrive/backend/utils/ldap-sync/.nvmrc new file mode 100644 index 000000000..25bf17fc5 --- /dev/null +++ b/tdrive/backend/utils/ldap-sync/.nvmrc @@ -0,0 +1 @@ +18 \ No newline at end of file diff --git a/tdrive/backend/utils/ldap-sync/package.json b/tdrive/backend/utils/ldap-sync/package.json new file mode 100644 index 000000000..2e803f7b7 --- /dev/null +++ b/tdrive/backend/utils/ldap-sync/package.json @@ -0,0 +1,26 @@ +{ + "name": "ldap_project", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "build": "npm run build:clean && npm run build:ts", + "build:ts": "tsc", + "build:clean": "rimraf ./dist", + "sync": "node dist/index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "axios": "^1.4.0", + "dotenv": "^16.0.3", + "ldapjs": "^3.0.2" + }, + "devDependencies": { + "@types/ldapjs": "^2.2.5", + "typescript": "^5.0.4", + "rimraf": "^3.0.2" + } +} diff --git a/tdrive/backend/utils/ldap-sync/src/index.ts b/tdrive/backend/utils/ldap-sync/src/index.ts new file mode 100644 index 000000000..58850a2ee --- /dev/null +++ b/tdrive/backend/utils/ldap-sync/src/index.ts @@ -0,0 +1,96 @@ +import ldap from "ldapjs"; +import axios from "axios"; +import dotenv from "dotenv"; + +interface UserAttributes { + first_name: string; + last_name: string; + email: string; +} + +dotenv.config(); + +console.log("Run script with the following env:"); +console.log(process.env); + +// LDAP server configuration +const ldapConfig = { + url: process.env.LDAP_URL|| "localhost", + bindDN: process.env.LDAP_BIND_DN || "", + bindCredentials: process.env.LDAP_BIND_CREDENTIALS || "", + searchBase: process.env.LDAP_SEARCH_BASE || "dc=example,dc=com", + searchFilter: process.env.LDAP_SEARCH_FILTER || "(objectClass=inetorgperson)", + timeout: 120, + version: 3, +}; + +// Create LDAP client +const client = ldap.createClient({ + url: ldapConfig.url, +}); + +// Bind to LDAP server +client.bind(ldapConfig.bindDN, ldapConfig.bindCredentials, (err) => { + if (err) { + console.error("LDAP bind error:", err); + return; + } + + // Perform search + client.search( + ldapConfig.searchBase, + { + filter: ldapConfig.searchFilter, + attributes: ["uid", "mail", "cn", "sn", "mobile"], + scope: "sub", + derefAliases: 2, + }, + (searchErr, searchRes) => { + if (searchErr) { + console.error("LDAP search error:", searchErr); + return; + } + + const apiRequests: Promise[] = []; + + searchRes.on("searchEntry", (entry: any) => { + // Handle each search result entry + const userAttributes: UserAttributes = { + first_name: entry.attributes[1]?.values[0], + last_name: entry.attributes[2]?.values[0], + email: entry.attributes[3]?.values[0], + }; + + // Make API call to tdrive backend with the userAttributes + apiRequests.push(axios.post(process.env.API_URL || "", userAttributes)); + }); + + searchRes.on("error", (err) => { + console.error("LDAP search result error:", err); + }); + + searchRes.on("end", () => { + // Unbind from LDAP server after search is complete + client.unbind((unbindErr) => { + if (unbindErr) { + console.error("LDAP unbind error:", unbindErr); + } else { + Promise.all(apiRequests) + .then((responses) => { + console.log( + "API responses:", + responses.map((r) => r.data) + ); + }) + .catch((error) => { + console.error("API error:", error); + }) + .finally(() => { + console.log("LDAP search completed successfully."); + }); + } + }); + }); + } + ); +}); diff --git a/tdrive/backend/utils/ldap-sync/tsconfig.json b/tdrive/backend/utils/ldap-sync/tsconfig.json new file mode 100644 index 000000000..fbac536ec --- /dev/null +++ b/tdrive/backend/utils/ldap-sync/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "commonjs", + "outDir": "dist", + "strict": true, + "esModuleInterop": true + }, + "include": ["src"] +} + \ No newline at end of file diff --git a/tdrive/docker/tdrive-ldap-sync/Dockerfile b/tdrive/docker/tdrive-ldap-sync/Dockerfile new file mode 100644 index 000000000..f67c71ee2 --- /dev/null +++ b/tdrive/docker/tdrive-ldap-sync/Dockerfile @@ -0,0 +1,15 @@ +# Use an official Node.js runtime as the base image +FROM node:lts-alpine + +# Set the working directory inside the container +WORKDIR /usr/src/app + +# Copy app +COPY backend/utils/ldap-sync/*.json ./ +COPY backend/utils/ldap-sync/src/** ./src/ +COPY backend/utils/ldap-sync/.nvmrc ./ + +RUN npm i && npm run build + +# Run the Node.js application +CMD ["npm", "run", "sync"] From 23cecefd069c13ff7f05843127d952fcc1568355 Mon Sep 17 00:00:00 2001 From: Anton Shepilov Date: Wed, 6 Sep 2023 20:43:41 +0200 Subject: [PATCH 04/12] Ldap sync (#186) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🌟Synchronization of the user with LDAP --- .github/workflows/publish-ldap-sync.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-ldap-sync.yml b/.github/workflows/publish-ldap-sync.yml index bfa4ad74e..bf0572c51 100644 --- a/.github/workflows/publish-ldap-sync.yml +++ b/.github/workflows/publish-ldap-sync.yml @@ -1,7 +1,7 @@ name: publish-ldap-sync on: - pull_request: + push: branches: [main] paths: - "tdrive/utils/ldap-sync/**" From 921a0f3f280c8b789d55e88a16afad06c4ed26ff Mon Sep 17 00:00:00 2001 From: Anton Shepilov Date: Wed, 6 Sep 2023 20:48:40 +0200 Subject: [PATCH 05/12] Ldap sync (#187) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🌟Synchronization of the user with LDAP --- .github/workflows/publish-ldap-sync.yml | 1 + tdrive/backend/utils/ldap-sync/src/index.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish-ldap-sync.yml b/.github/workflows/publish-ldap-sync.yml index bf0572c51..b404da5c5 100644 --- a/.github/workflows/publish-ldap-sync.yml +++ b/.github/workflows/publish-ldap-sync.yml @@ -5,6 +5,7 @@ on: branches: [main] paths: - "tdrive/utils/ldap-sync/**" + - "tdrive/docker/**" jobs: publish-node: diff --git a/tdrive/backend/utils/ldap-sync/src/index.ts b/tdrive/backend/utils/ldap-sync/src/index.ts index 58850a2ee..75930ae1f 100644 --- a/tdrive/backend/utils/ldap-sync/src/index.ts +++ b/tdrive/backend/utils/ldap-sync/src/index.ts @@ -10,7 +10,7 @@ interface UserAttributes { dotenv.config(); -console.log("Run script with the following env:"); +console.log("Run script with the following env: "); console.log(process.env); // LDAP server configuration From 15d3602cf8d69cb15758489d86e8445b1aa7612b Mon Sep 17 00:00:00 2001 From: Anton Shepilov Date: Wed, 6 Sep 2023 20:59:42 +0200 Subject: [PATCH 06/12] =?UTF-8?q?=F0=9F=8C=9FSynchronization=20of=20the=20?= =?UTF-8?q?user=20with=20LDAP=20(#188)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/publish-ldap-sync.yml | 2 +- tdrive/backend/utils/ldap-sync/.env.example | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-ldap-sync.yml b/.github/workflows/publish-ldap-sync.yml index b404da5c5..25990197c 100644 --- a/.github/workflows/publish-ldap-sync.yml +++ b/.github/workflows/publish-ldap-sync.yml @@ -4,7 +4,7 @@ on: push: branches: [main] paths: - - "tdrive/utils/ldap-sync/**" + - "tdrive/backend/utils/ldap-sync/**" - "tdrive/docker/**" jobs: diff --git a/tdrive/backend/utils/ldap-sync/.env.example b/tdrive/backend/utils/ldap-sync/.env.example index 9c2c3e54f..d6942f2f8 100644 --- a/tdrive/backend/utils/ldap-sync/.env.example +++ b/tdrive/backend/utils/ldap-sync/.env.example @@ -3,4 +3,4 @@ LDAP_BIND_DN= LDAP_BIND_CREDENTIALS= LDAP_SEARCH_BASE=dc=example,dc=com LDAP_SEARCH_FILTER=(objectClass=inetorgperson) -API_URL=http://tdrive:4000/api/sync \ No newline at end of file +API_URL=http://tdrive:4000/api/sync From 79764988b82ddd5ce6222a1e8adc05e7ca3704de Mon Sep 17 00:00:00 2001 From: Anton Shepilov Date: Tue, 12 Sep 2023 16:20:37 +0200 Subject: [PATCH 07/12] =?UTF-8?q?*=20=F0=9F=8C=9FSynchronization=20of=20th?= =?UTF-8?q?e=20user=20with=20LDAP=20(#206)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add configuration with LDAP attributes mappings * Add default company and remove application check since it's not configurable * Change error handling, now all the requests are independent --- .../core/platform/services/webserver/error.ts | 2 +- .../applications-api/web/controllers/index.ts | 18 ++- .../src/services/console/clients/remote.ts | 2 - tdrive/backend/utils/ldap-sync/.env.example | 4 + tdrive/backend/utils/ldap-sync/package.json | 1 + tdrive/backend/utils/ldap-sync/src/index.ts | 111 ++++++++++++++---- tdrive/backend/utils/ldap-sync/tsconfig.json | 8 +- 7 files changed, 106 insertions(+), 40 deletions(-) diff --git a/tdrive/backend/node/src/core/platform/services/webserver/error.ts b/tdrive/backend/node/src/core/platform/services/webserver/error.ts index f3a032791..b13bbaa00 100644 --- a/tdrive/backend/node/src/core/platform/services/webserver/error.ts +++ b/tdrive/backend/node/src/core/platform/services/webserver/error.ts @@ -9,7 +9,7 @@ function serverErrorHandler(server: FastifyInstance): void { ? { statusCode: reply.statusCode, error: "Internal Server Error", - message: "Something went wrong", + message: "Something went wrong, " + err.message, requestId: request.id, } : err, diff --git a/tdrive/backend/node/src/services/applications-api/web/controllers/index.ts b/tdrive/backend/node/src/services/applications-api/web/controllers/index.ts index 22daf1fde..8bee2d58f 100644 --- a/tdrive/backend/node/src/services/applications-api/web/controllers/index.ts +++ b/tdrive/backend/node/src/services/applications-api/web/controllers/index.ts @@ -14,6 +14,7 @@ import { getApplicationObject, } from "../../../applications/entities/application"; import gr from "../../../global-resolver"; +import { logger } from "../../../../core/platform/framework/logger"; import { ApplicationApiExecutionContext, ApplicationLoginRequest, @@ -171,20 +172,10 @@ export class ApplicationsApiController { email: string; first_name: string; last_name: string; - application_id: string; - company_id: string; }; }>, ): Promise { const email = request.body.email.trim().toLocaleLowerCase(); - const checkApplication = gr.services.applications.companyApps.get({ - application_id: request.body.application_id, - company_id: request.body.company_id, - }); - - if (!checkApplication) { - throw new Error("Application is not allowed to sync users for this company."); - } if (await gr.services.users.getByEmail(email)) { throw new Error("This email is already used"); @@ -203,12 +194,17 @@ export class ApplicationsApiController { }); const user = await gr.services.users.create(newUser); - await gr.services.companies.setUserRole(request.body.company_id, user.entity.id, "admin"); + const company = await gr.services.companies.getCompany({ + id: "00000000-0000-4000-0000-000000000000", + }); + + await gr.services.companies.setUserRole(company.id, user.entity.id, "member"); await gr.services.users.save(user.entity, { user: { id: user.entity.id, server_request: true }, }); } catch (err) { + logger.error(err); throw new Error("An unknown error occured"); } return {}; diff --git a/tdrive/backend/node/src/services/console/clients/remote.ts b/tdrive/backend/node/src/services/console/clients/remote.ts index b23268955..932dfeab8 100644 --- a/tdrive/backend/node/src/services/console/clients/remote.ts +++ b/tdrive/backend/node/src/services/console/clients/remote.ts @@ -1,4 +1,3 @@ -import { AxiosInstance } from "axios"; import { ConsoleServiceClient } from "../client-interface"; import { ConsoleCompany, @@ -26,7 +25,6 @@ import config from "config"; import { CompanyUserRole } from "src/services/user/web/types"; export class ConsoleRemoteClient implements ConsoleServiceClient { version: "1"; - client: AxiosInstance; private infos: ConsoleOptions; private verifier: OidcJwtVerifier; diff --git a/tdrive/backend/utils/ldap-sync/.env.example b/tdrive/backend/utils/ldap-sync/.env.example index d6942f2f8..748360e8e 100644 --- a/tdrive/backend/utils/ldap-sync/.env.example +++ b/tdrive/backend/utils/ldap-sync/.env.example @@ -4,3 +4,7 @@ LDAP_BIND_CREDENTIALS= LDAP_SEARCH_BASE=dc=example,dc=com LDAP_SEARCH_FILTER=(objectClass=inetorgperson) API_URL=http://tdrive:4000/api/sync +TDRIVE_URL=http://tdrive:4000/ +TDRIVE_CREDENTIALS_ID=application-name +TDRIVE_CREDENTIALS_SECRET=application-secret +LDAP_ATTRIBUTE_MAPPINGS={"firstName": "givenName", "lastName": "sn", "email": "mail"} diff --git a/tdrive/backend/utils/ldap-sync/package.json b/tdrive/backend/utils/ldap-sync/package.json index 2e803f7b7..1ca7d8808 100644 --- a/tdrive/backend/utils/ldap-sync/package.json +++ b/tdrive/backend/utils/ldap-sync/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "", "main": "index.js", + "type": "module", "scripts": { "build": "npm run build:clean && npm run build:ts", "build:ts": "tsc", diff --git a/tdrive/backend/utils/ldap-sync/src/index.ts b/tdrive/backend/utils/ldap-sync/src/index.ts index 75930ae1f..d1582698e 100644 --- a/tdrive/backend/utils/ldap-sync/src/index.ts +++ b/tdrive/backend/utils/ldap-sync/src/index.ts @@ -1,5 +1,5 @@ import ldap from "ldapjs"; -import axios from "axios"; +import axios, { AxiosError } from "axios"; import dotenv from "dotenv"; interface UserAttributes { @@ -8,27 +8,93 @@ interface UserAttributes { email: string; } -dotenv.config(); +export interface IApiServiceApplicationTokenRequestParams { + id: string; + secret: string; +} +export interface IApiServiceApplicationTokenResponse { + resource: { + access_token: { + time: number; + expiration: number; + value: string; + type: string; + }; + }; +} + +dotenv.config(); console.log("Run script with the following env: "); console.log(process.env); -// LDAP server configuration const ldapConfig = { - url: process.env.LDAP_URL|| "localhost", + url: process.env.LDAP_URL || "localhost", bindDN: process.env.LDAP_BIND_DN || "", bindCredentials: process.env.LDAP_BIND_CREDENTIALS || "", searchBase: process.env.LDAP_SEARCH_BASE || "dc=example,dc=com", searchFilter: process.env.LDAP_SEARCH_FILTER || "(objectClass=inetorgperson)", + mappings: JSON.parse(process.env.LDAP_ATTRIBUTE_MAPPINGS || "{}"), timeout: 120, version: 3, }; +const tdriveConfig = { + url: process.env.TDRIVE_URL || "http://localhost:4000/)", + credentials: { + id: process.env.TDRIVE_CREDENTIALS_ID || "application-name", + secret: process.env.TDRIVE_CREDENTIALS_SECRET || "application-secret", + } +}; + +const refreshToken = async (): Promise => { + try { + const response = await axios.post( + `${tdriveConfig.url.replace(/\/$/, '')}/api/console/v1/login`, + { + id: tdriveConfig.credentials.id, + secret: tdriveConfig.credentials.secret, + }, + { + headers: { + Authorization: `Basic ${Buffer.from(`${tdriveConfig.credentials.id}:${tdriveConfig.credentials.secret}`).toString('base64')}`, + }, + }, + ); + + const { + resource: { + access_token: { value }, + }, + } = response.data; + + //axiosClient.interceptors.response.use(this.handleResponse, this.handleErrors); + + return value; + } catch (error) { + console.error('failed to get application token', error); + console.info('Using token ', tdriveConfig.credentials.id, tdriveConfig.credentials.secret); + console.info(`POST ${tdriveConfig.url.replace(/\/$/, '')}/api/console/v1/login`); + console.info(`Basic ${Buffer.from(`${tdriveConfig.credentials.id}:${tdriveConfig.credentials.secret}`).toString('base64')}`); + throw new Error("Unable to get access to token, see precious errors for details."); + } +}; + + // Create LDAP client const client = ldap.createClient({ url: ldapConfig.url, }); +const accessToken = await refreshToken() + +const axiosClient = axios.create({ + baseURL: tdriveConfig.url, + headers: { + Authorization: `Bearer ${accessToken}`, + }, +}); + // Bind to LDAP server client.bind(ldapConfig.bindDN, ldapConfig.bindCredentials, (err) => { if (err) { @@ -41,7 +107,7 @@ client.bind(ldapConfig.bindDN, ldapConfig.bindCredentials, (err) => { ldapConfig.searchBase, { filter: ldapConfig.searchFilter, - attributes: ["uid", "mail", "cn", "sn", "mobile"], + attributes: [ldapConfig.mappings.firstName, ldapConfig.mappings.lastName, ldapConfig.mappings.email], scope: "sub", derefAliases: 2, }, @@ -54,15 +120,24 @@ client.bind(ldapConfig.bindDN, ldapConfig.bindCredentials, (err) => { const apiRequests: Promise[] = []; searchRes.on("searchEntry", (entry: any) => { + console.log('Receive entry:: ' + JSON.stringify(entry.pojo)); + // Handle each search result entry const userAttributes: UserAttributes = { - first_name: entry.attributes[1]?.values[0], - last_name: entry.attributes[2]?.values[0], - email: entry.attributes[3]?.values[0], + first_name: entry.attributes[0]?.values[0], + last_name: entry.attributes[1]?.values[0], + email: entry.attributes[2]?.values[0], }; - // Make API call to tdrive backend with the userAttributes - apiRequests.push(axios.post(process.env.API_URL || "", userAttributes)); + if (userAttributes.email) { + //Make API call to tdrive backend with the userAttributes + apiRequests.push(axiosClient.post(process.env.API_URL || "", userAttributes) + .catch((e: AxiosError) => { + console.log(`Error for ${JSON.stringify(userAttributes)}: ${e.message}, body: ${e.response?.data?.message}`); + })); + } else { + console.log(`user ${JSON.stringify(userAttributes)} doesn't have an email`); + } }); searchRes.on("error", (err) => { @@ -75,19 +150,9 @@ client.bind(ldapConfig.bindDN, ldapConfig.bindCredentials, (err) => { if (unbindErr) { console.error("LDAP unbind error:", unbindErr); } else { - Promise.all(apiRequests) - .then((responses) => { - console.log( - "API responses:", - responses.map((r) => r.data) - ); - }) - .catch((error) => { - console.error("API error:", error); - }) - .finally(() => { - console.log("LDAP search completed successfully."); - }); + + Promise.allSettled(apiRequests) + .finally(() => console.log("LDAP search COMPLETED.")); } }); }); diff --git a/tdrive/backend/utils/ldap-sync/tsconfig.json b/tdrive/backend/utils/ldap-sync/tsconfig.json index fbac536ec..d0ccc9ded 100644 --- a/tdrive/backend/utils/ldap-sync/tsconfig.json +++ b/tdrive/backend/utils/ldap-sync/tsconfig.json @@ -1,10 +1,12 @@ { "compilerOptions": { - "target": "es6", - "module": "commonjs", + "target": "esnext", + "module": "esnext", + "moduleResolution": "node", "outDir": "dist", "strict": true, - "esModuleInterop": true + "esModuleInterop": true, + "useUnknownInCatchVariables": false }, "include": ["src"] } From efcb27b405fa97ceb0607146d0c853ebe3cfeb0b Mon Sep 17 00:00:00 2001 From: Anton Shepilov Date: Tue, 12 Sep 2023 23:02:46 +0200 Subject: [PATCH 08/12] =?UTF-8?q?*=20=F0=9F=9B=A0=EF=B8=8F=20Synchronizati?= =?UTF-8?q?on=20of=20the=20user=20with=20LDAP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add configuration with LDAP attributes mappings * add defalut company and remove application check since it's not configurable * change error handling, now all the requests are independant --- tdrive/backend/node/config/default.json | 2 +- .../applications-api/web/controllers/index.ts | 36 ++++--------------- .../src/services/console/clients/remote.ts | 17 +++++---- tdrive/backend/utils/ldap-sync/src/index.ts | 12 +++---- 4 files changed, 24 insertions(+), 43 deletions(-) diff --git a/tdrive/backend/node/config/default.json b/tdrive/backend/node/config/default.json index 78da89771..33924c1b2 100644 --- a/tdrive/backend/node/config/default.json +++ b/tdrive/backend/node/config/default.json @@ -81,7 +81,7 @@ } }, "database":{ - "secret":"ab63bb3e90c0271c9a1c06651a7c0967eab8851a7a897766", + "secret":"", "type":"cassandra", "mongodb":{ "uri":"mongodb://mongo:27017", diff --git a/tdrive/backend/node/src/services/applications-api/web/controllers/index.ts b/tdrive/backend/node/src/services/applications-api/web/controllers/index.ts index 8bee2d58f..d014ae91b 100644 --- a/tdrive/backend/node/src/services/applications-api/web/controllers/index.ts +++ b/tdrive/backend/node/src/services/applications-api/web/controllers/index.ts @@ -8,7 +8,6 @@ import { RealtimeBaseBusEvent, } from "../../../../core/platform/services/realtime/types"; import { ResourceGetResponse } from "../../../../utils/types"; -import { getInstance } from "../../../user/entities/user"; import { ApplicationObject, getApplicationObject, @@ -21,6 +20,7 @@ import { ApplicationLoginResponse, ConfigureRequest, } from "../types"; +import { ConsoleHookUser } from "src/services/console/types"; export class ApplicationsApiController { async token( @@ -175,37 +175,15 @@ export class ApplicationsApiController { }; }>, ): Promise { - const email = request.body.email.trim().toLocaleLowerCase(); - - if (await gr.services.users.getByEmail(email)) { - throw new Error("This email is already used"); - } - try { - const newUser = getInstance({ - first_name: request.body.first_name, - last_name: request.body.last_name, - email_canonical: email, - username_canonical: (email.replace("@", ".") || "").toLocaleLowerCase(), - phone: "", - identity_provider: "console", - identity_provider_id: email, - mail_verified: true, - }); - const user = await gr.services.users.create(newUser); - - const company = await gr.services.companies.getCompany({ - id: "00000000-0000-4000-0000-000000000000", - }); - - await gr.services.companies.setUserRole(company.id, user.entity.id, "member"); - - await gr.services.users.save(user.entity, { - user: { id: user.entity.id, server_request: true }, - }); + await gr.services.console.getClient().updateLocalUserFromConsole({ + email: request.body.email.trim().toLocaleLowerCase(), + name: request.body.first_name, + surname: request.body.last_name, + } as ConsoleHookUser); } catch (err) { logger.error(err); - throw new Error("An unknown error occured"); + throw err; } return {}; } diff --git a/tdrive/backend/node/src/services/console/clients/remote.ts b/tdrive/backend/node/src/services/console/clients/remote.ts index 932dfeab8..b0e249424 100644 --- a/tdrive/backend/node/src/services/console/clients/remote.ts +++ b/tdrive/backend/node/src/services/console/clients/remote.ts @@ -99,12 +99,13 @@ export class ConsoleRemoteClient implements ConsoleServiceClient { throw CrudException.badRequest("User not found on Console"); } - const roles = userDTO.roles.filter( - role => role.applications === undefined || role.applications.find(a => a.code === "tdrive"), - ); - - //REMOVE LATER - logger.info(`Roles are: ${roles}.`); + if (userDTO.roles) { + const roles = userDTO.roles.filter( + role => role.applications === undefined || role.applications.find(a => a.code === "tdrive"), + ); + //REMOVE LATER + logger.info(`Roles are: ${roles}.`); + } let user = await gr.services.users.getByConsoleId(userDTO.email); @@ -151,7 +152,9 @@ export class ConsoleRemoteClient implements ConsoleServiceClient { user.preferences.timezone = coalesce(userDTO.preference.timeZone, user.preferences?.timezone); } - user.picture = userDTO.avatar.value; + if (userDTO.avatar) { + user.picture = userDTO.avatar.value; + } await gr.services.users.save(user); diff --git a/tdrive/backend/utils/ldap-sync/src/index.ts b/tdrive/backend/utils/ldap-sync/src/index.ts index d1582698e..12ea997ef 100644 --- a/tdrive/backend/utils/ldap-sync/src/index.ts +++ b/tdrive/backend/utils/ldap-sync/src/index.ts @@ -1,4 +1,4 @@ -import ldap from "ldapjs"; +import ldap, { SearchEntry } from "ldapjs"; import axios, { AxiosError } from "axios"; import dotenv from "dotenv"; @@ -119,14 +119,14 @@ client.bind(ldapConfig.bindDN, ldapConfig.bindCredentials, (err) => { const apiRequests: Promise[] = []; - searchRes.on("searchEntry", (entry: any) => { - console.log('Receive entry:: ' + JSON.stringify(entry.pojo)); + searchRes.on("searchEntry", (entry: SearchEntry) => { + console.log('Receive entry:: ' + JSON.stringify(entry.attributes)); // Handle each search result entry const userAttributes: UserAttributes = { - first_name: entry.attributes[0]?.values[0], - last_name: entry.attributes[1]?.values[0], - email: entry.attributes[2]?.values[0], + first_name: entry.attributes.find(a=> a.type == ldapConfig.mappings.firstName)?.vals[0]!, + last_name: entry.attributes.find(a=> a.type == ldapConfig.mappings.lastName)?.vals[0]!, + email: entry.attributes.find(a=> a.type == ldapConfig.mappings.email)?.vals[0]!, }; if (userAttributes.email) { From 07794bf654a0d8eb70c41fc2e0aa175fb91bcd7a Mon Sep 17 00:00:00 2001 From: Anton Shepilov Date: Fri, 15 Sep 2023 10:48:12 +0200 Subject: [PATCH 09/12] =?UTF-8?q?=F0=9F=90=9BFix=20file=20uploading=20of?= =?UTF-8?q?=20a=20big=20directory=20tree=20(#208)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/app/features/drive/hooks/use-drive-upload.tsx | 6 +++++- .../src/app/features/files/services/file-upload-service.ts | 7 ++++--- .../frontend/src/app/views/client/body/drive/browser.tsx | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/tdrive/frontend/src/app/features/drive/hooks/use-drive-upload.tsx b/tdrive/frontend/src/app/features/drive/hooks/use-drive-upload.tsx index 655b49430..f41dcbf15 100644 --- a/tdrive/frontend/src/app/features/drive/hooks/use-drive-upload.tsx +++ b/tdrive/frontend/src/app/features/drive/hooks/use-drive-upload.tsx @@ -48,12 +48,14 @@ export const useDriveUpload = () => { const filesPerParentId: { [key: string]: File[] } = {}; // Create all directories + console.debug("Start creating directories ..."); const createDirectories = async (tree: FileTreeObject['tree'], parentId: string) => { for (const directory of Object.keys(tree)) { if (tree[directory] instanceof File) { if (!filesPerParentId[parentId]) filesPerParentId[parentId] = []; filesPerParentId[parentId].push(tree[directory] as File); } else { + console.debug(`Create directory ${directory}`); const driveItem = await create( { company_id: context.companyId, @@ -63,6 +65,7 @@ export const useDriveUpload = () => { }, {}, ); + console.debug(`Directory ${directory} created`); if (driveItem?.id) { await createDirectories(tree[directory] as FileTreeObject['tree'], driveItem.id); } else { @@ -72,10 +75,11 @@ export const useDriveUpload = () => { } }; await createDirectories(tree.tree, context.parentId); + console.debug("All directories created"); // Upload files into directories for (const parentId of Object.keys(filesPerParentId)) { - FileUploadService.upload(filesPerParentId[parentId], { + await FileUploadService.upload(filesPerParentId[parentId], { context: { companyId: context.companyId, parentId: parentId, diff --git a/tdrive/frontend/src/app/features/files/services/file-upload-service.ts b/tdrive/frontend/src/app/features/files/services/file-upload-service.ts index d998043fa..3b04c99a8 100644 --- a/tdrive/frontend/src/app/features/files/services/file-upload-service.ts +++ b/tdrive/frontend/src/app/features/files/services/file-upload-service.ts @@ -56,8 +56,8 @@ class FileUploadService { this.currentTaskId = uuid(); } - fileList.forEach(async file => { - if (!file) return; + for (const file of fileList) { + if (!file) continue; const pendingFile: PendingFileType = { id: uuid(), @@ -140,7 +140,7 @@ class FileUploadService { options?.callback?.(null, options?.context || {}); this.notify(); }); - }); + } return this.pendingFiles.filter(f => f.uploadTaskId === this.currentTaskId); } @@ -258,6 +258,7 @@ class FileUploadService { }); } + public getDownloadRoute({ companyId, fileId }: { companyId: string; fileId: string }): string { return FileUploadAPIClient.getDownloadRoute({ companyId: companyId, diff --git a/tdrive/frontend/src/app/views/client/body/drive/browser.tsx b/tdrive/frontend/src/app/views/client/body/drive/browser.tsx index f9a06dba9..746f6188b 100644 --- a/tdrive/frontend/src/app/views/client/body/drive/browser.tsx +++ b/tdrive/frontend/src/app/views/client/body/drive/browser.tsx @@ -156,7 +156,7 @@ export default memo( if (dataTransfer) { const tree = await getFilesTree(dataTransfer); setCreationModalState({ parent_id: '', open: false }); - uploadTree(tree, { + await uploadTree(tree, { companyId, parentId, }); From 43a5dbddf1058afac32a85a9cb7aac0321ca40df Mon Sep 17 00:00:00 2001 From: Anton Shepilov Date: Mon, 18 Sep 2023 21:13:39 +0200 Subject: [PATCH 10/12] =?UTF-8?q?=F0=9F=8C=9F=20Display=20TDrive=20version?= =?UTF-8?q?=20(#209)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tdrive/frontend/public/index.html | 2 ++ .../frontend/src/app/environment/version.ts | 6 ++-- .../src/app/views/client/header/index.tsx | 29 ++++++++++++------- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/tdrive/frontend/public/index.html b/tdrive/frontend/public/index.html index 2da3c6944..a46b747b2 100644 --- a/tdrive/frontend/public/index.html +++ b/tdrive/frontend/public/index.html @@ -10,6 +10,8 @@ content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" /> + +