✨New user provider for nextcloud migration (#337)
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import express, { Express, Request, Response } from "express";
|
import express, { Express, Request, Response } from "express";
|
||||||
import { NextcloudMigration, NextcloudMigrationConfiguration } from './nextcloud_migration.js';
|
import { NextcloudMigration, NextcloudMigrationConfiguration } from './nextcloud_migration.js';
|
||||||
import { logger } from "./logger"
|
import { logger } from "./logger"
|
||||||
|
import { UserProviderType } from "./user/user_privider";
|
||||||
|
|
||||||
const app: Express = express();
|
const app: Express = express();
|
||||||
const port = process.env.SERVER_PORT || 3000;
|
const port = process.env.SERVER_PORT || 3000;
|
||||||
@@ -13,6 +14,10 @@ const config: NextcloudMigrationConfiguration = {
|
|||||||
baseDn: process.env.LDAP_BASE!,
|
baseDn: process.env.LDAP_BASE!,
|
||||||
url: process.env.LDAP_URL!,
|
url: process.env.LDAP_URL!,
|
||||||
},
|
},
|
||||||
|
lemon: {
|
||||||
|
url: process.env.LEMON_USERS_URL!,
|
||||||
|
auth: process.env.LEMON_USERS_AUTH!,
|
||||||
|
},
|
||||||
tmpDir: process.env.TMP_DIR || '/tmp',
|
tmpDir: process.env.TMP_DIR || '/tmp',
|
||||||
nextcloudUrl: process.env.NEXTCLOUD_URL!,
|
nextcloudUrl: process.env.NEXTCLOUD_URL!,
|
||||||
drive: {
|
drive: {
|
||||||
@@ -21,7 +26,8 @@ const config: NextcloudMigrationConfiguration = {
|
|||||||
appId: process.env.TWAKE_DRIVE_APP_ID!,
|
appId: process.env.TWAKE_DRIVE_APP_ID!,
|
||||||
secret: process.env.TWAKE_DRIVE_SECRET!,
|
secret: process.env.TWAKE_DRIVE_SECRET!,
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
userProvider: process.env.USER_PROVIDER! as UserProviderType
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!config.ldap.baseDn) {
|
if (!config.ldap.baseDn) {
|
||||||
|
|||||||
@@ -1,17 +1,21 @@
|
|||||||
import { exec } from 'child_process';
|
import { exec } from 'child_process';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { LdapUser } from './shell_ldap_user';
|
import { ShellLdapUserProvider } from './user/shell_ldap_user';
|
||||||
import { User } from './ldap_user.js';
|
|
||||||
import { TwakeDriveClient, TwakeDriveUser } from './twake_client';
|
import { TwakeDriveClient, TwakeDriveUser } from './twake_client';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { logger } from "./logger"
|
import { logger } from "./logger"
|
||||||
|
import { User, UserProvider, UserProviderFactory, UserProviderType } from "./user/user_privider";
|
||||||
|
|
||||||
export type NextcloudMigrationConfiguration = {
|
export interface NextcloudMigrationConfiguration {
|
||||||
ldap: {
|
shell: {
|
||||||
baseDn: string,
|
baseDn: string,
|
||||||
url: string,
|
url: string,
|
||||||
},
|
},
|
||||||
|
lemon: {
|
||||||
|
url: string,
|
||||||
|
auth: string,
|
||||||
|
}
|
||||||
drive: {
|
drive: {
|
||||||
url: string,
|
url: string,
|
||||||
credentials: {
|
credentials: {
|
||||||
@@ -21,19 +25,20 @@ export type NextcloudMigrationConfiguration = {
|
|||||||
},
|
},
|
||||||
tmpDir: string,
|
tmpDir: string,
|
||||||
nextcloudUrl: string
|
nextcloudUrl: string
|
||||||
|
userProvider: UserProviderType
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NextcloudMigration {
|
export class NextcloudMigration {
|
||||||
|
|
||||||
private config: NextcloudMigrationConfiguration;
|
private config: NextcloudMigrationConfiguration;
|
||||||
|
|
||||||
private ldap: LdapUser;
|
private userProvider: UserProvider;
|
||||||
|
|
||||||
driveClient: TwakeDriveClient;
|
driveClient: TwakeDriveClient;
|
||||||
|
|
||||||
constructor(config: NextcloudMigrationConfiguration) {
|
constructor(config: NextcloudMigrationConfiguration) {
|
||||||
this.config = config;
|
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);
|
this.driveClient = new TwakeDriveClient(this.config.drive);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +84,7 @@ export class NextcloudMigration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getLDAPUser(username: string): Promise<User> {
|
async getLDAPUser(username: string): Promise<User> {
|
||||||
const user = await this.ldap.find(username);
|
const user = await this.userProvider.find(username);
|
||||||
if (!user.email) {
|
if (!user.email) {
|
||||||
throw new Error(`User ${username} not found`);
|
throw new Error(`User ${username} not found`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { User } from './ldap_user';
|
|
||||||
import FormData from 'form-data';
|
import FormData from 'form-data';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { logger } from './logger';
|
import { logger } from './logger';
|
||||||
|
import { User } from "./user/user_privider";
|
||||||
|
|
||||||
type TwakeClientConfiguration = {
|
type TwakeClientConfiguration = {
|
||||||
url: string,
|
url: string,
|
||||||
@@ -15,7 +15,7 @@ type TwakeClientConfiguration = {
|
|||||||
|
|
||||||
export class TwakeDriveClient {
|
export class TwakeDriveClient {
|
||||||
|
|
||||||
private config: TwakeClientConfiguration;
|
private readonly config: TwakeClientConfiguration;
|
||||||
|
|
||||||
constructor(config: TwakeClientConfiguration) {
|
constructor(config: TwakeClientConfiguration) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
|
|||||||
+4
-42
@@ -1,20 +1,14 @@
|
|||||||
import ldap, { SearchEntry, SearchOptions } from 'ldapjs';
|
import ldap, { SearchOptions } from "ldapjs";
|
||||||
import { logger } from "./logger"
|
import { logger } from "../logger";
|
||||||
|
import { User, UserProvider } from "./user_privider";
|
||||||
|
|
||||||
export type LdapConfiguration = {
|
export type LdapConfiguration = {
|
||||||
url: string,
|
url: string,
|
||||||
baseDn: 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
|
// 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;
|
private config: LdapConfiguration;
|
||||||
|
|
||||||
@@ -24,38 +18,6 @@ export class LdapUser {
|
|||||||
this.config = config;
|
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<User> {
|
async find(username: string): Promise<User> {
|
||||||
const search = await this.search(username);
|
const search = await this.search(username);
|
||||||
return new Promise<User>((resolve, reject) => {
|
return new Promise<User>((resolve, reject) => {
|
||||||
@@ -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<User> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
+5
-4
@@ -1,9 +1,10 @@
|
|||||||
import { LdapConfiguration, User } from './ldap_user';
|
import { LdapConfiguration } from './ldap_user';
|
||||||
import { exec } from 'child_process';
|
import { exec } from 'child_process';
|
||||||
import ldif from 'ldif';
|
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;
|
private config: LdapConfiguration;
|
||||||
|
|
||||||
@@ -12,7 +13,7 @@ export class LdapUser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async find(username: string): Promise<User> {
|
async find(username: string): Promise<User> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise<User>((resolve, reject) => {
|
||||||
let cmd = `ldapsearch -x -H ${this.config.url} -b '${this.config.baseDn}' '(uid=${username})'`;
|
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);
|
logger.info("Executing command to get data from LDAP for " + username);
|
||||||
exec(cmd, (error, stdout, stderr) => {
|
exec(cmd, (error, stdout, stderr) => {
|
||||||
@@ -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<User>
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
@@ -14,7 +14,12 @@ describe.skip('nextcloud migration module', () => {
|
|||||||
appId: "tdrive_onlyoffice",
|
appId: "tdrive_onlyoffice",
|
||||||
secret: "c1cc66db78e1d3bb4713c55d5ab2"
|
secret: "c1cc66db78e1d3bb4713c55d5ab2"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
userProvider: "lemon",
|
||||||
|
lemon: {
|
||||||
|
url: "url",
|
||||||
|
auth: "key"
|
||||||
|
},
|
||||||
} as NextcloudMigrationConfiguration);
|
} as NextcloudMigrationConfiguration);
|
||||||
|
|
||||||
let user = await subj.driveClient.createUser({firstName: "DWHO", lastName: "DWHO", email: "dwho@example.com", uid: "test"});
|
let user = await subj.driveClient.createUser({firstName: "DWHO", lastName: "DWHO", email: "dwho@example.com", uid: "test"});
|
||||||
|
|||||||
+6
-6
@@ -1,24 +1,24 @@
|
|||||||
import {describe, expect, test} from '@jest/globals';
|
import {describe, expect, test} from '@jest/globals';
|
||||||
import { LdapUser } from '../src/shell_ldap_user';
|
import { ShellLdapUserProvider } from '../src/user/shell_ldap_user';
|
||||||
import { LdapConfiguration } from '../src/ldap_user';
|
import { LdapConfiguration } from '../src/user/ldap_user';
|
||||||
|
|
||||||
//FOR LOCAL DEBUG PURPOSE ONLY, ITS NOT A TEST
|
//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 () => {
|
test('ldap returns user info', async () => {
|
||||||
const ldap = new LdapUser({
|
const ldap = new ShellLdapUserProvider({
|
||||||
url: "ldap://auth.poc-mail-avocat.fr:389",
|
url: "ldap://auth.poc-mail-avocat.fr:389",
|
||||||
baseDn: "ou=users,dc=example,dc=com",
|
baseDn: "ou=users,dc=example,dc=com",
|
||||||
} as LdapConfiguration);
|
} as LdapConfiguration);
|
||||||
const user = await ldap.find("999248");
|
const user = await ldap.find("999248");
|
||||||
expect(user.firstName).toBe("Xavier");
|
expect(user.firstName).toBe("Xavier");
|
||||||
expect(user.lastName).toBe("GUIMARD");
|
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");
|
expect(user.uid).toBe("999248");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('ldap returns nothing', async () => {
|
test('ldap returns nothing', async () => {
|
||||||
const ldap = new LdapUser({
|
const ldap = new ShellLdapUserProvider({
|
||||||
url: "ldap://auth.poc-mail-avocat.fr:389",
|
url: "ldap://auth.poc-mail-avocat.fr:389",
|
||||||
baseDn: "ou=users,dc=example,dc=com",
|
baseDn: "ou=users,dc=example,dc=com",
|
||||||
} as LdapConfiguration);
|
} as LdapConfiguration);
|
||||||
Reference in New Issue
Block a user