From 184b9acbe310b4480d0371d385f297430b3e45ab Mon Sep 17 00:00:00 2001 From: Anton Shepilov Date: Mon, 22 Jan 2024 21:32:32 +0300 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8New=20user=20provider=20for=20nextclou?= =?UTF-8?q?d=20migration=20(#337)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextcloud-migration/src/express_server.ts | 8 +++- .../src/nextcloud_migration.ts | 19 +++++--- .../nextcloud-migration/src/twake_client.ts | 4 +- .../src/{ => user}/ldap_user.ts | 46 ++----------------- .../src/user/lemon_ldap_user_provider.ts | 45 ++++++++++++++++++ .../src/{ => user}/shell_ldap_user.ts | 9 ++-- .../src/user/user_privider.ts | 33 +++++++++++++ .../test/lemon.ldap.test.ts | 40 ++++++++++++++++ .../test/migration.test.ts | 7 ++- .../test/{ldap.test.ts => shell.ldap.test.ts} | 12 ++--- 10 files changed, 160 insertions(+), 63 deletions(-) rename tdrive/backend/utils/nextcloud-migration/src/{ => user}/ldap_user.ts (60%) create mode 100644 tdrive/backend/utils/nextcloud-migration/src/user/lemon_ldap_user_provider.ts rename tdrive/backend/utils/nextcloud-migration/src/{ => user}/shell_ldap_user.ts (85%) create mode 100644 tdrive/backend/utils/nextcloud-migration/src/user/user_privider.ts create mode 100644 tdrive/backend/utils/nextcloud-migration/test/lemon.ldap.test.ts rename tdrive/backend/utils/nextcloud-migration/test/{ldap.test.ts => shell.ldap.test.ts} (68%) diff --git a/tdrive/backend/utils/nextcloud-migration/src/express_server.ts b/tdrive/backend/utils/nextcloud-migration/src/express_server.ts index 730dc9ab..f7dc80b6 100644 --- a/tdrive/backend/utils/nextcloud-migration/src/express_server.ts +++ b/tdrive/backend/utils/nextcloud-migration/src/express_server.ts @@ -1,6 +1,7 @@ import express, { Express, Request, Response } from "express"; import { NextcloudMigration, NextcloudMigrationConfiguration } from './nextcloud_migration.js'; import { logger } from "./logger" +import { UserProviderType } from "./user/user_privider"; const app: Express = express(); const port = process.env.SERVER_PORT || 3000; @@ -13,6 +14,10 @@ const config: NextcloudMigrationConfiguration = { baseDn: process.env.LDAP_BASE!, url: process.env.LDAP_URL!, }, + lemon: { + url: process.env.LEMON_USERS_URL!, + auth: process.env.LEMON_USERS_AUTH!, + }, tmpDir: process.env.TMP_DIR || '/tmp', nextcloudUrl: process.env.NEXTCLOUD_URL!, drive: { @@ -21,7 +26,8 @@ const config: NextcloudMigrationConfiguration = { appId: process.env.TWAKE_DRIVE_APP_ID!, secret: process.env.TWAKE_DRIVE_SECRET!, } - } + }, + userProvider: process.env.USER_PROVIDER! as UserProviderType } if (!config.ldap.baseDn) { diff --git a/tdrive/backend/utils/nextcloud-migration/src/nextcloud_migration.ts b/tdrive/backend/utils/nextcloud-migration/src/nextcloud_migration.ts index 334a1fa0..e65f969e 100644 --- a/tdrive/backend/utils/nextcloud-migration/src/nextcloud_migration.ts +++ b/tdrive/backend/utils/nextcloud-migration/src/nextcloud_migration.ts @@ -1,17 +1,21 @@ import { exec } from 'child_process'; // @ts-ignore import fs from 'fs'; -import { LdapUser } from './shell_ldap_user'; -import { User } from './ldap_user.js'; +import { ShellLdapUserProvider } from './user/shell_ldap_user'; import { TwakeDriveClient, TwakeDriveUser } from './twake_client'; import path from 'path'; import { logger } from "./logger" +import { User, UserProvider, UserProviderFactory, UserProviderType } from "./user/user_privider"; -export type NextcloudMigrationConfiguration = { - ldap: { +export interface NextcloudMigrationConfiguration { + shell: { baseDn: string, url: string, }, + lemon: { + url: string, + auth: string, + } drive: { url: string, credentials: { @@ -21,19 +25,20 @@ export type NextcloudMigrationConfiguration = { }, tmpDir: string, nextcloudUrl: string + userProvider: UserProviderType } export class NextcloudMigration { private config: NextcloudMigrationConfiguration; - private ldap: LdapUser; + private userProvider: UserProvider; driveClient: TwakeDriveClient; constructor(config: NextcloudMigrationConfiguration) { this.config = config; - this.ldap = new LdapUser(config.ldap); + this.userProvider = (new UserProviderFactory()).get(config.userProvider, config[config.userProvider]); this.driveClient = new TwakeDriveClient(this.config.drive); } @@ -79,7 +84,7 @@ export class NextcloudMigration { } async getLDAPUser(username: string): Promise { - const user = await this.ldap.find(username); + const user = await this.userProvider.find(username); if (!user.email) { throw new Error(`User ${username} not found`); } diff --git a/tdrive/backend/utils/nextcloud-migration/src/twake_client.ts b/tdrive/backend/utils/nextcloud-migration/src/twake_client.ts index 5bb47646..9f249acb 100644 --- a/tdrive/backend/utils/nextcloud-migration/src/twake_client.ts +++ b/tdrive/backend/utils/nextcloud-migration/src/twake_client.ts @@ -1,9 +1,9 @@ import axios from 'axios'; -import { User } from './ldap_user'; import FormData from 'form-data'; // @ts-ignore import fs from 'fs'; import { logger } from './logger'; +import { User } from "./user/user_privider"; type TwakeClientConfiguration = { url: string, @@ -15,7 +15,7 @@ type TwakeClientConfiguration = { export class TwakeDriveClient { - private config: TwakeClientConfiguration; + private readonly config: TwakeClientConfiguration; constructor(config: TwakeClientConfiguration) { this.config = config; diff --git a/tdrive/backend/utils/nextcloud-migration/src/ldap_user.ts b/tdrive/backend/utils/nextcloud-migration/src/user/ldap_user.ts similarity index 60% rename from tdrive/backend/utils/nextcloud-migration/src/ldap_user.ts rename to tdrive/backend/utils/nextcloud-migration/src/user/ldap_user.ts index 55f123c8..e033d619 100644 --- a/tdrive/backend/utils/nextcloud-migration/src/ldap_user.ts +++ b/tdrive/backend/utils/nextcloud-migration/src/user/ldap_user.ts @@ -1,20 +1,14 @@ -import ldap, { SearchEntry, SearchOptions } from 'ldapjs'; -import { logger } from "./logger" +import ldap, { SearchOptions } from "ldapjs"; +import { logger } from "../logger"; +import { User, UserProvider } from "./user_privider"; export type LdapConfiguration = { url: string, baseDn: string, } -export type User = { - firstName: string, - lastName: string, - email: string, - uid: string -} - // Doesn't work, fix it later, somehow none of the events is called for the search request -export class LdapUser { +export class LdapUserProvider implements UserProvider { private config: LdapConfiguration; @@ -24,38 +18,6 @@ export class LdapUser { this.config = config; } - async auth(username: string, password: string) { - return new Promise((resolve, reject) => { - this.client?.bind(username, password, (error) => { - if (error) { - reject(new Error("Authentication error")); - } else { - logger.info("Successfully authenticated in LDAP") - resolve(this.client); - } - }); - }); - } - - async connect() { - return new Promise((resolve, reject) => { - if (!this.client) { - this.client = ldap.createClient({ - url: this.config.url, - reconnect: true - }); - this.client.on('connect', (res) => { - logger.info("Connected to LDAP") - resolve(this.auth("", "")); - }) - this.client.on('connectionError', (error) => { - logger.info("Error connecting to LDAP") - reject(error); - }); - } - }); - } - async find(username: string): Promise { const search = await this.search(username); return new Promise((resolve, reject) => { diff --git a/tdrive/backend/utils/nextcloud-migration/src/user/lemon_ldap_user_provider.ts b/tdrive/backend/utils/nextcloud-migration/src/user/lemon_ldap_user_provider.ts new file mode 100644 index 00000000..6d14eaf0 --- /dev/null +++ b/tdrive/backend/utils/nextcloud-migration/src/user/lemon_ldap_user_provider.ts @@ -0,0 +1,45 @@ +import { User, UserProvider } from "./user_privider"; +import axios, { AxiosInstance } from "axios"; + +export interface LemonLdapUserProviderConfig { + url: string, + auth: string, +} + +export class LemonLdapUserProvider implements UserProvider { + + private client: AxiosInstance; + + constructor(private config: LemonLdapUserProviderConfig) { + this.client = axios.create({ + baseURL: config.url, + headers: { + Authorization: `Bearer ${config.auth}`, + }, + }); + } + + async find(username: string): Promise { + const response = await this.client.post("", [username]); + if (response.status == 200 && response.data) { + const u: any = response.data[username]; + if (u && u.length > 0) { + if (u) { + const user = { + firstName: u[0].filter(f => f[0] === "givenName").map(f => f[1])[0], + lastName: u[0].filter(f => f[0] === "sn").map(f => f[1])[0], + email: u[0].filter(f => f[0] === "mail").map(f => f[1])[0], + uid: u[0].filter(f => f[0] === "uid").map(f => f[1])[0] + } + if (user.uid && !user.email) { + user.email = `${user.uid}@cnb.linagora.com`; + } + return user; + } + } + } + return {} as User; + } + +} + diff --git a/tdrive/backend/utils/nextcloud-migration/src/shell_ldap_user.ts b/tdrive/backend/utils/nextcloud-migration/src/user/shell_ldap_user.ts similarity index 85% rename from tdrive/backend/utils/nextcloud-migration/src/shell_ldap_user.ts rename to tdrive/backend/utils/nextcloud-migration/src/user/shell_ldap_user.ts index 203c5fad..111f83f1 100644 --- a/tdrive/backend/utils/nextcloud-migration/src/shell_ldap_user.ts +++ b/tdrive/backend/utils/nextcloud-migration/src/user/shell_ldap_user.ts @@ -1,9 +1,10 @@ -import { LdapConfiguration, User } from './ldap_user'; +import { LdapConfiguration } from './ldap_user'; import { exec } from 'child_process'; import ldif from 'ldif'; -import { logger } from "./logger" +import { logger } from "../logger" +import { User, UserProvider } from "./user_privider"; -export class LdapUser { +export class ShellLdapUserProvider implements UserProvider { private config: LdapConfiguration; @@ -12,7 +13,7 @@ export class LdapUser { } async find(username: string): Promise { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { let cmd = `ldapsearch -x -H ${this.config.url} -b '${this.config.baseDn}' '(uid=${username})'`; logger.info("Executing command to get data from LDAP for " + username); exec(cmd, (error, stdout, stderr) => { diff --git a/tdrive/backend/utils/nextcloud-migration/src/user/user_privider.ts b/tdrive/backend/utils/nextcloud-migration/src/user/user_privider.ts new file mode 100644 index 00000000..d9dd3efc --- /dev/null +++ b/tdrive/backend/utils/nextcloud-migration/src/user/user_privider.ts @@ -0,0 +1,33 @@ +import { ShellLdapUserProvider } from "./shell_ldap_user"; +import { LemonLdapUserProvider } from "./lemon_ldap_user_provider"; +import { LdapUserProvider } from "./ldap_user"; + +export type User = { + firstName: string, + lastName: string, + email: string, + uid: string +} + +export type UserProviderType = "lemon" | "ldap" | "shell"; + +export interface UserProvider { + find(username: string): Promise +} + +export class UserProviderFactory { + + get(type: UserProviderType, config: any) { + switch (type) { + case "shell": + return new ShellLdapUserProvider(config) + case "ldap": + return new LdapUserProvider(config); + case "lemon": + return new LemonLdapUserProvider(config); + default: + return new ShellLdapUserProvider(config); + } + } + +} \ No newline at end of file diff --git a/tdrive/backend/utils/nextcloud-migration/test/lemon.ldap.test.ts b/tdrive/backend/utils/nextcloud-migration/test/lemon.ldap.test.ts new file mode 100644 index 00000000..1994395b --- /dev/null +++ b/tdrive/backend/utils/nextcloud-migration/test/lemon.ldap.test.ts @@ -0,0 +1,40 @@ +import {describe, expect, test} from '@jest/globals'; +import { LemonLdapUserProvider, LemonLdapUserProviderConfig } from "../src/user/lemon_ldap_user_provider"; + +//FOR LOCAL DEBUG PURPOSE ONLY, ITS NOT A TEST +describe.skip('Lemon LDAP User Provider', () => { + + test('ldap returns user info', async () => { + const ldap = new LemonLdapUserProvider({ + url: "https://auth.avocat.fr/_getldapentries", + auth: "isPnOTuBwjqAlIdCFGNY", + }); + const user = await ldap.find("999248"); + expect(user.firstName).toBe("Xavier"); + expect(user.lastName).toBe("GUIMARD"); + expect(user.email).toBe("999248@cnb.linagora.com"); + expect(user.uid).toBe("999248"); + }); + + test('ldap returns user info with email', async () => { + const ldap = new LemonLdapUserProvider({ + url: "https://auth.avocat.fr/_getldapentries", + auth: "isPnOTuBwjqAlIdCFGNY", + }); + const user = await ldap.find("999253"); + expect(user.firstName).toBe("Utilisateur4"); + expect(user.lastName).toBe("ONBOARDING4"); + expect(user.email).toBe("utilisateur4.onboarding4@avocat.fr"); + expect(user.uid).toBe("999253"); + }); + + test('ldap returns nothing', async () => { + const ldap = new LemonLdapUserProvider({ + url: "https://auth.avocat.fr/_getldapentries", + auth: "isPnOTuBwjqAlIdCFGNY", + }); + const user = await ldap.find("NOTHING_HERE"); + expect(user.email).toBeUndefined(); + }); + +}); \ No newline at end of file diff --git a/tdrive/backend/utils/nextcloud-migration/test/migration.test.ts b/tdrive/backend/utils/nextcloud-migration/test/migration.test.ts index 21c3fda4..f452ee59 100644 --- a/tdrive/backend/utils/nextcloud-migration/test/migration.test.ts +++ b/tdrive/backend/utils/nextcloud-migration/test/migration.test.ts @@ -14,7 +14,12 @@ describe.skip('nextcloud migration module', () => { appId: "tdrive_onlyoffice", secret: "c1cc66db78e1d3bb4713c55d5ab2" } - } + }, + userProvider: "lemon", + lemon: { + url: "url", + auth: "key" + }, } as NextcloudMigrationConfiguration); let user = await subj.driveClient.createUser({firstName: "DWHO", lastName: "DWHO", email: "dwho@example.com", uid: "test"}); diff --git a/tdrive/backend/utils/nextcloud-migration/test/ldap.test.ts b/tdrive/backend/utils/nextcloud-migration/test/shell.ldap.test.ts similarity index 68% rename from tdrive/backend/utils/nextcloud-migration/test/ldap.test.ts rename to tdrive/backend/utils/nextcloud-migration/test/shell.ldap.test.ts index 3d26d71d..f948a4c1 100644 --- a/tdrive/backend/utils/nextcloud-migration/test/ldap.test.ts +++ b/tdrive/backend/utils/nextcloud-migration/test/shell.ldap.test.ts @@ -1,24 +1,24 @@ import {describe, expect, test} from '@jest/globals'; -import { LdapUser } from '../src/shell_ldap_user'; -import { LdapConfiguration } from '../src/ldap_user'; +import { ShellLdapUserProvider } from '../src/user/shell_ldap_user'; +import { LdapConfiguration } from '../src/user/ldap_user'; //FOR LOCAL DEBUG PURPOSE ONLY, ITS NOT A TEST -describe.skip('ldap module', () => { +describe.skip('Shell LDAP User Provider', () => { test('ldap returns user info', async () => { - const ldap = new LdapUser({ + const ldap = new ShellLdapUserProvider({ url: "ldap://auth.poc-mail-avocat.fr:389", baseDn: "ou=users,dc=example,dc=com", } as LdapConfiguration); const user = await ldap.find("999248"); expect(user.firstName).toBe("Xavier"); expect(user.lastName).toBe("GUIMARD"); - expect(user.email).toBe("xguimard@avocat.fr"); + expect(user.email).toBe("xguimard@preprod-avocat.fr"); expect(user.uid).toBe("999248"); }); test('ldap returns nothing', async () => { - const ldap = new LdapUser({ + const ldap = new ShellLdapUserProvider({ url: "ldap://auth.poc-mail-avocat.fr:389", baseDn: "ou=users,dc=example,dc=com", } as LdapConfiguration);