Commit 7ce5ce8b authored by Daniel LACROIX's avatar Daniel LACROIX
Browse files

merge done

parents d8a5126f 0dd3bf4e
FROM node:alpine
ARG NODE_ENV=production
RUN mkdir /app && apk update && apk add curl gettext patch nginx && mkdir /run/nginx
COPY ./etherpad-lite-1.8.6.tar.gz /app
RUN mkdir /app && apk update && apk add curl gettext nginx && mkdir /run/nginx
COPY ./etherpad-lite-1.8.7.tar.gz /app
WORKDIR /app
RUN tar zxvf etherpad-lite-1.8.6.tar.gz --strip 1 -C /app && \
rm /app/etherpad-lite-1.8.6.tar.gz
RUN tar zxvf etherpad-lite-1.8.7.tar.gz --strip 1 -C /app && \
rm /app/etherpad-lite-1.8.7.tar.gz
RUN /app/bin/installDeps.sh
COPY ./mysql_innodb.patch /app/mysql_innodb.patch
RUN patch src/node_modules/ueberdb2/databases/mysql_db.js < mysql_innodb.patch
COPY ./settings.json.tmpl /app/settings.json.tmpl
COPY ./docker-entrypoint.sh /docker-entrypoint.sh
COPY ./nginx/default.conf.tmpl /etc/nginx/conf.d/default.conf.tmpl
......
......@@ -8,14 +8,29 @@ Fournit l'outil Etherpad Lite pour Laclasse utilisé avec les documents de Lacla
Prérequis
=======================================
Pour fonctionner, ce container à besoin d'avoir accès à une base de donnée MySQL accessible sur le nom d'hôte **db** avec le compte utilisateur **etherpad** et dont le nom de la base est **etherpad**.
Pour fonctionner, ce container à besoin d'avoir accès à une base de donnée MySQL
accessible sur le nom d'hôte **db** avec le compte utilisateur **etherpad** et
dont le nom de la base est **etherpad**.
Il a aussi besoin de **laclasse-service** accessible à l'adresse **http://service/** (donc au nom d'hôte **service** et sur le port **80**).
Il a aussi besoin de **laclasse-service** accessible à l'adresse
**http://service/** (donc au nom d'hôte **service** et sur le port **80**).
Les variables d'environnement suivantes doivent aussi être définie:
- **DB_PASSWORD**: mot de passe de la base MySQL
- **APIKEY**: une clef à choisir pour l'API d'Etherpad et qui doit aussi être configuré dans **laclasse-service**
- **APP_ID**: id de cette application dans Laclasse pour utiliser les APIs
- **APP_KEY**: mot de passe de cette application dans Laclasse pour utiliser les APIs
- **BASE_URL**: url de base sur laquelle est hébergé Laclasse (ex: **https://www.laclasse.com**)
Les variables optionnelles suivantes peuvent être configurées:
- **DB_HOST**: Serveur de la base de donnée (défaut: **db**)
- **DB_PORT**: Port de la base de donnée (défaut: **3306**)
- **DB_USER**: Utilisateur de la base de donnée (défault: **etherpad**)
- **DB_NAME**: Nom de la base de donnée (défault: **etherpad**)
- **AUTH_URL**: URL interne pour vérifier les droits d'accès aux fichiers (default: **http://service/api/docs/current/isauthenticated**)
- **SSO_URL**: URL publique de base pour le SSO (défaut: **BASE_URL**)
Commandes
=======================================
......@@ -30,4 +45,5 @@ Compilation:
Exécution:
----------
docker run -e APIKEY=1234 -e DB_PASSWORD=pass -it laclasse-etherpad:latest
docker run -e APIKEY=1234 -e DB_PASSWORD=pass -e APP_ID=PAD -e APP_KEY=pass \
-it laclasse-etherpad:latest
......@@ -18,20 +18,21 @@ fi
if [ -z "$SSO_URL" ]; then
export SSO_URL="$BASE_URL"
fi
REPLACE_VARS='DB_HOST DB_PORT DB_USER DB_NAME DB_PASSWORD APIKEY AUTH_URL SSO_URL BASE_URL'
REPLACE_VARS='DB_HOST DB_PORT DB_USER DB_NAME DB_PASSWORD APIKEY AUTH_URL SSO_URL BASE_URL APP_ID APP_KEY'
# check if needed vars are present
if [ -z "$DB_PASSWORD" ] || [ -z "$APIKEY" ] ; then
if [ -z "$DB_PASSWORD" ] || [ -z "$APIKEY" ] || [ -z "$APP_ID" ] || [ -z "$APP_KEY" ] || [ -z "$BASE_URL" ]; then
echo "Error: The following environment variables MUST be defined."
echo " - DB_PASSWORD: database password"
echo " - APIKEY: Etherpad secret API key"
echo " - APIKEY : Etherpad secret API key"
echo " - APP_ID : id of the APP in Laclasse"
echo " - APP_KEY: password if the APP in Laclasse"
echo " - BASE_URL: base public URL of Laclasse (ex: https://www.laclasse.com)"
echo " The following environment variables are optional"
echo " - DB_HOST: database host (default: db)"
echo " - DB_PORT: database port (default: 3306)"
echo " - DB_USER: database user (default: etherpad)"
echo " - DB_NAME: database name (default: etherpad)"
echo " - BASE_URL: HTTP URL"
echo " - AUTH_URL: auth service URL (default: http://service/api/docs/current/isauthenticated)"
echo " - SSO_URL: sso service URL (default: BASE_URL)"
exit 1
......
......@@ -2,15 +2,17 @@
'use strict';
const request = require('request');
const http = require("http");
const cheerio = require("cheerio");
const authorManager = require('ep_etherpad-lite/node/db/AuthorManager');
const log4js = require('log4js');
const fs = require("fs");
const jsonminify = require("jsonminify");
let ssoURL = '';
let internalURL = '';
let baseURL = '';
let appKey = '';
let appId = '';
const configPath = `${__dirname}/config.json`;
try {
......@@ -23,10 +25,19 @@ try {
else
throw "baseURL not found";
if(config.ssoURL)
ssoURL = config.ssoURL;
if(config.internalURL)
internalURL = config.internalURL;
else
throw "ssoURL not found";
throw "internalURL not found";
if(config.appKey)
appKey = config.appKey;
else
throw "appKey not found";
if(config.appId)
appId = config.appId;
else
throw "appId not found";
laclasseLogger.info(`Config file read from: "${configPath}"`);
} catch(e) {
......@@ -35,23 +46,81 @@ try {
laclasseLogger = log4js.getLogger("ep_laclasse");
exports.authorize = function (hook_name, context, cb) {
// NOTE: statics in /^\/(static|javascripts|pluginfw|api)/ are always authorized. See webaccess.js
function httpRequest(url, params) {
return new Promise(function(resolve, reject) {
var req = http.request(url, params, function(res) {
if (res.statusCode < 200 || res.statusCode >= 300) {
return reject(new Error('statusCode=' + res.statusCode));
}
// cumulate data
var body = [];
res.on('data', function(chunk) {
body.push(chunk);
});
// resolve on end
res.on('end', function() {
try {
body = Buffer.concat(body).toString();
} catch(e) {
reject(e);
}
resolve(body);
});
});
// reject on request error
req.on('error', function(err) {
// This is not a "Second reject", just a different sort of failure
reject(err);
});
// IMPORTANT
req.end();
});
}
exports.authorize = async function (hook_name, context, cb) {
// NOTE: statics in /^\/(static|javascripts|pluginfw|api)/ are always authorized. See webaccess.js
laclasseLogger.info('authorize: for', context.resource);
// allow some statics resources
if ((context.resource.indexOf('/locales/') == 0) ||
(context.resource.indexOf('/jserror') == 0))
return cb(['readOnly']);
// allow healthcheck without authentication
if (context.resource.match(/^\/(healthcheck)/)) return cb([true]);
const user = context.req.session.user;
// everything else at least needs a login session
if (!context.req.session.user) return cb([false]);
if (!user) return cb([]);
// protect the admin routes
if (context.resource.indexOf('/admin') === 0 && !context.req.session.user.is_admin) return cb([false]);
if (context.resource.indexOf('/admin') === 0 && !user.is_admin) return cb([]);
// check if user has access to pad
const matches = context.resource.match(/^\/p\/(\d+)(\/.*)*$/);
const padID = matches ? matches[1] : null;
if (padID == null) return cb([]);
laclasseLogger.info('authorize: authorized');
try {
let rights = await padRightsForUser(padID, user.username);
laclasseLogger.info(`padRightsForUser rights: ${rights}`);
cb([true]);
if(rights === false || rights.Read == false) {
return cb([]);
}
// laclasseLogger.info('authorize: authorized with ', rights.Write ? 'modify': 'readOnly');
if(rights.Write) {
return cb(['modify']);
} else {
return cb(['readOnly']);
}
} catch (error) {
laclasseLogger.error(`padRightsForUser fails ${error}`);
return cb([]);
}
};
function getServiceURL(request) {
......@@ -60,24 +129,20 @@ function getServiceURL(request) {
return `${protocol}://${host}/pads${request.path}`;
}
function serviceValidate(ticket,service) {
return new Promise(function(resolve, reject) {
const validateURL = new URL('sso/serviceValidate',ssoURL);
const urlParams = new URLSearchParams({
ticket: ticket, service: service
});
const url = `${validateURL.toString()}?${urlParams.toString()}`;
laclasseLogger.info('ep_laclasse.serviceValidate: ', url);
request(url, function (er, response, body) {
if (er) return reject(er);
try {
return resolve(body);
} catch (err) {
return reject(err);
}
});
})
async function serviceValidate(ticket,service) {
const validateURL = new URL('/sso/serviceValidate',internalURL);
const urlParams = new URLSearchParams({
ticket: ticket, service: service
});
const url = `${validateURL.toString()}?${urlParams.toString()}`;
laclasseLogger.info('ep_laclasse.serviceValidate: ', url);
const body = await httpRequest(url);
const user = getUserFromTicket(body);
if(!user) {
return false
}
return user;
}
function getUserFromTicket(ticket) {
......@@ -94,71 +159,191 @@ function getUserFromTicket(ticket) {
}
}
var ticket = null;
/**
* Calls service to check if user has access to pad
* @param {number} id
* @param {{username:string}} user
*/
async function padRightsForUser(id, userId) {
laclasseLogger.info(`padRightsForUser pad: ${id}, user: ${userId}`);
const rightsURL = new URL(`/api/docs/${id}/rights`,internalURL);
const urlParams = new URLSearchParams({ seenBy: userId });
const url = `${rightsURL.toString()}?${urlParams.toString()}`;
let body;
try {
body = await httpRequest(url, {
auth: `${appId}:${appKey}`,
});
laclasseLogger.info(`padRightsForUser pad: ${id}, user: ${userId}, rights: ${body}`);
} catch(e) {
laclasseLogger.error(`padRightsForUser pad: ${id}, user: ${userId} return exception ${e}`);
return false;
}
let result;
try{
result = JSON.parse(body);
} catch {
laclasseLogger.error("ep_laclasse.padRightsForUser: body couldn't be parsed to JSON", body);
return false;
}
return result;
}
exports.authenticate = async function (hook_name, context, cb) {
laclasseLogger.info('authenticate');
if(!context.req.query.ticket) {
laclasseLogger.info('authenticate: current session: ', context.req.session);
// if no session available, authentication fails
if(!context.req.session) {
return cb([false]);
}
ticket = context.req.query.ticket;
const user = await serviceValidate(context.req.query.ticket, getServiceURL(context.req)).then(ticket => {
const user = getUserFromTicket(ticket);
if(!user) {
return false;
}
// if the user is already authentication, it is already ok
if (context.req.session.user) {
return cb([true]);
}
return user;
}).catch(err => {
laclasseLogger.error('authenticate: Ticket couldn\'t be validated', err);
return false;
});
// no ticket is available, authentication fails
if(!context.req.query.ticket) {
return cb([false]);
}
if(user === false) {
let user;
try {
user = await serviceValidate(context.req.query.ticket, getServiceURL(context.req));
} catch {
return cb([false]);
}
if(!user)
return cb([false]);
// User authenticated, save off some information needed for authorization
context.req.session.user = {
username: user.uid,
display_name: `${user.firstname} ${user.lastname}`,
is_admin: true
is_admin: false,
canCreate: false,
};
// once logged in, redirect to the resource without the ticket
laclasseLogger.info('authenticate: successful authentication', context.req.session.user);
// Redirect to url without ticket
context.res.redirect(getServiceURL(context.req));
//laclasseLogger.info('authenticate: successful authentication', context.req.session.user);
return cb([false]);
}
exports.authFailure = function(hook_name, context, cb) {
const baseUrl = new URL('sso/login',baseURL);
exports.authnFailure = function(hook_name, context, cb) {
const loginURL = new URL('/sso/login',baseURL);
const urlParams = new URLSearchParams({ service: getServiceURL(context.req) });
const url = `${baseUrl.toString()}?${urlParams.toString()}`;
const url = `${loginURL.toString()}?${urlParams.toString()}`;
laclasseLogger.warn('authnFailure: Redirect to SSO: ',url);
// redirect to the auth url
try {
context.res.redirect(url);
} catch(e) {}
laclasseLogger.warn('authFailure: Redirect to SSO: ',url);
// signal that we have handled it
cb([true]);
return cb([true]);
}
exports.handleMessage = function (hook_name, context, cb) {
exports.authzFailure = (hookName, context, cb) => {
laclasseLogger.warn('authzFailure');
// send our custom error page
context.res.status(403).send(
`<!DOCTYPE html>
<html>
<head>
<title>403 Accès refusé</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<style>
body {
color: #444;
background-color: #f5f5f5;
font-family: "Open Sans", sans-serif;
font-size: 20px;
}
a {
color: white;
}
.logo {
width: 55%;
opacity: 0.6;
position: absolute;
left: -5%;
top: -5%;
-webkit-user-select: none;
}
.btn {
display: inline-block;
font-size: 16px;
text-transform: uppercase;
padding: 10px 20px;
border: 1px solid white;
border-radius: 0;
background-color: #444;
margin: 5px;
color: white;
white-space: nowrap;
text-decoration: none;
cursor: pointer;
}
.btn:hover {
background-color: #555;
}
</style>
</head>
<body>
<img draggable="false" class="logo" src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNi4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkNhbHF1ZV8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCINCgkgd2lkdGg9IjEyNy41NjFweCIgaGVpZ2h0PSIxMjcuNTYxcHgiIHZpZXdCb3g9IjAgMCAxMjcuNTYxIDEyNy41NjEiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDEyNy41NjEgMTI3LjU2MSINCgkgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8Zz4NCgk8cGF0aCBmaWxsPSIjRkZGRkZGIiBkPSJNNjMuNzc4LDkuMTM1QzMzLjYxOCw5LjEzOCw5LjEzNiwzMy42MTQsOS4xMzYsNjMuNzc4YzAsMzAuMTYyLDI0LjQ4MSw1NC42NDUsNTQuNjQzLDU0LjY0Ng0KCQljMzAuMTcxLTAuMDAyLDU0LjY0NS0yNC40ODMsNTQuNjQ2LTU0LjY0NkMxMTguNDIzLDMzLjYxNCw5My45NDksOS4xMzgsNjMuNzc4LDkuMTM1eiBNNzUuMjczLDkwLjU2Mg0KCQljMi43NzYtMS40NzQsNS4zNTEtMy4yODEsNy42Ni01LjM3N2MxLjY1OS0xLjUwOSw0LjIyOC0xLjM4MSw1LjczMywwLjI3OGMxLjUwNiwxLjY2MiwxLjM4MSw0LjIyOS0wLjI3OCw1LjczNA0KCQljLTIuODExLDIuNTQ3LTUuOTM2LDQuNzQ4LTkuMzE1LDYuNTM5Yy0xLjk4MywxLjA0OS00LjQzOCwwLjI5My01LjQ4OS0xLjY4OEM3Mi41MzQsOTQuMDY3LDczLjI5LDkxLjYxLDc1LjI3Myw5MC41NjJ6DQoJCSBNOTIuMzEyLDczLjcyNGMwLTIuMjQyLDEuODE1LTQuMDYyLDQuMDYtNC4wNjJjMS4zMjQtMC4wMDIsMi40ODYtMC41MjQsMy4zNTktMS4zOTFjMC44NjUtMC44NzMsMS4zOS0yLjAzMSwxLjM5Mi0zLjM1NXYtNy4xNDENCgkJYy0wLjAwMi0xLjMyNi0wLjUyNS0yLjQ4OC0xLjM5Mi0zLjM1OWMtMC44NzMtMC44NjctMi4wMzUtMS4zOS0zLjM1OS0xLjM5MmMtMS4zMjYsMC4wMDItMi40OSwwLjUyMy0zLjM2LDEuMzkyDQoJCWMtMC44NjUsMC44NzEtMS4zOTEsMi4wMzMtMS4zOTMsMy4zNTljMCwyLjI0Mi0xLjgxNyw0LjA2MS00LjA2LDQuMDYxYy0yLjI0MywwLTQuMDYyLTEuODE3LTQuMDYyLTQuMDYxDQoJCWMwLjAwMy03LjExNSw1Ljc2Ny0xMi44NywxMi44NzMtMTIuODcxYzAuMDc3LDAsMC4xNTIsMC4wMSwwLjIyOSwwLjAxMmMtMi4zMTYtNC4wNjItNS4zMzQtNy42NzQtOC45MzUtMTAuNjE5DQoJCWMtNi41MzEtNS4zMzYtMTQuODQtOC41MjgtMjMuOTM3LTguNTNjLTEwLjQ2MywwLjAwMi0xOS44OTYsNC4yMjktMjYuNzU4LDExLjA4MmMtMi43ODcsMi43OTEtNS4xMzQsNi4wMTUtNi45NDQsOS41NTUNCgkJYzEuMDY3LTAuMjMsMi4xNzItMC4zNTksMy4zMDktMC4zNTljOC42LDAuMDAyLDE1LjU2Nyw2Ljk3LDE1LjU3LDE1LjU3MnY5LjMzOGMwLDIuMDcyLDAuODI1LDMuOTA4LDIuMTc5LDUuMjY5DQoJCWMxLjM1NywxLjM1NCwzLjE5MywyLjE3OCw1LjI2NywyLjE4YzIuMjQxLDAsNC4wNjEsMS44MTgsNC4wNjEsNC4wNjFjMCwyLjI0My0xLjgxNyw0LjA2LTQuMDYxLDQuMDYNCgkJYy04LjYwMSwwLTE1LjU2NC02Ljk2Ny0xNS41NjQtMTUuNTY2di05LjMzOGMtMC4wMDQtMi4wNy0wLjgyOC0zLjkxLTIuMTg0LTUuMjcxYy0xLjM2MS0xLjM1NC0zLjE5Ny0yLjE4MS01LjI2OC0yLjE4Mw0KCQljLTIuMDcsMC4wMDItMy45MDksMC44MjgtNS4yNzEsMi4xODNjLTAuOTkzLDAuOTk4LTEuNjkyLDIuMjU0LTIsMy42NjhjLTAuMTExLDEuMTg2LTAuMTc2LDIuMzgzLTAuMTc2LDMuNTk2DQoJCWMwLjAwMiwxMC40NjEsNC4yMjksMTkuODk1LDExLjA4MiwyNi43NTJjNi44Niw2Ljg1NCwxNi4yOTUsMTEuMDgsMjYuNzU4LDExLjA4MmMyLjI0MiwwLDQuMDYxLDEuODE4LDQuMDYxLDQuMDYxDQoJCWMwLDIuMjQzLTEuODE3LDQuMDYyLTQuMDYxLDQuMDYyYy0yNS4zODMtMC4wMDItNDUuOTU3LTIwLjU3LTQ1Ljk2MS00NS45NTVjMC0wLjUxNiwwLjAyMS0xLjAyNSwwLjAzOS0xLjUzNw0KCQljLTAuMDE4LTAuMTUyLTAuMDQ1LTAuMjk5LTAuMDQ1LTAuNDU1YzAtMC44OTUsMC4wOTItMS43NjYsMC4yMzctMi42MTljMi4zMTQtMjMuMjIzLDIxLjkwMy00MS4zNDksNDUuNzI5LTQxLjM1MQ0KCQljMTEuMDE3LTAuMDAyLDIxLjE2NiwzLjg5MSwyOS4wNzgsMTAuMzY2YzcuOTEzLDYuNDc1LDEzLjYzMywxNS41NDMsMTUuODU0LDI1LjkyNGMwLjAwNCwwLjAwOSwwLjAwMiwwLjAxOCwwLjAwNCwwLjAyNg0KCQljMC4zNzMsMS4yMDUsMC41NzQsMi40ODQsMC41NzYsMy44MTJ2Ny4xNDFjLTAuMDA1LDcuMTExLTUuNzY2LDEyLjg2My0xMi44NzEsMTIuODY3Qzk0LjEyOCw3Ny43ODQsOTIuMzEyLDc1Ljk2Niw5Mi4zMTIsNzMuNzI0eg0KCQkgTTYzLjI2NSw2Mi4zOTJjMCwyLjM0NC0xLjkwNCw0LjI1NC00LjI1NCw0LjI1NGMtMi4zNDQsMC00LjI0OC0xLjkxLTQuMjQ4LTQuMjU0YzAtMi4zNTUsMS45MDQtNC4yNjEsNC4yNDgtNC4yNjENCgkJQzYxLjM2LDU4LjEzMiw2My4yNjUsNjAuMDM2LDYzLjI2NSw2Mi4zOTJ6IE0zNy4zNDUsNjIuMzkyYzAsMi4zNDQtMS44OTksNC4yNTQtNC4yNSw0LjI1NGMtMi4zNDksMC00LjI1LTEuOTEtNC4yNS00LjI1NA0KCQljMC0yLjM1NSwxLjkwMS00LjI2MSw0LjI1LTQuMjYxQzM1LjQ0NSw1OC4xMzIsMzcuMzQ1LDYwLjAzNiwzNy4zNDUsNjIuMzkyeiIvPg0KPC9nPg0KPC9zdmc+" alt="Logo ENT">
<div style="position: absolute; top: 0px; left: 0px; right: 0px; bottom: 0px;">
<center>
<div style="max-width: 800px; padding: 40px;">
<div style="font-weight: bold; font-size: 34px">Laclasse 403</div><br>
<div style="font-size: 30px; padding: 10px">Accès interdit</div>
<div style="text-align: left">
Vous n'avez pas les droits nécessaires pour accéder à cette
ressource. Contacter votre administrateur ou la personne
qui vous a communiqué ce lien afin de lui demander les
droits.
</div>
</div>
</center>
</div>
</body>
</html>`
);
return cb([true]);
};
exports.handleMessage = async function (hook_name, {message, socket, client}) {
if (message.type === 'CLIENT_READY' || message.type === 'USERINFO_UPDATE') {
// skip if we don't have any information to set
var session = context.client.client.request.session;
if (!session || !session.user || !session.user.display_name) return cb();
authorManager.getAuthor4Token(context.message.token).then(function (author) {
authorManager.setAuthorName(author, context.client.client.request.session.user.display_name);
cb();
}).catch(function (error) {
console.error('handleMessage: could not get authorid for token %s', context.message.token, error);
cb();
});
const session = client.client.request.session;
const user = session.user;
if (!session || !user || !user.display_name) return;
// Update author name if needed
const author = await authorManager.getAuthor4Token(message.token);
if(!author) {
laclasseLogger.error('handleMessage: could not get authorid for token %s', message.token, author);
return;
}
const oldAuthorName = await authorManager.getAuthorName(author);
// laclasseLogger.info("oldAuthorName for: ",author,oldAuthorName);
if( oldAuthorName !== user.display_name) {
// laclasseLogger.info("Setting authorname for :",author,user.display_name);
authorManager.setAuthorName(author, user.display_name);
}
}
};
\ No newline at end of file
{
// The URL used to contact the SSO
"ssoURL": "${SSO_URL}",
// The URL used to contact service internally
"internalURL": "${SSO_URL}",
// The public URL to be redirected to
"baseURL": "${BASE_URL}"
// The public URL of service
"baseURL": "${BASE_URL}",
// The key of the app to have access to laclasse API
"appKey": "${APP_KEY}",
// The id of the app to have access to laclasse API
"appId": "${APP_ID}"
}
......@@ -5,10 +5,12 @@
"hooks":{
"userLeave": "ep_laclasse/userLeave.js",
"expressCreateServer": "ep_laclasse/httpApi.js",
"socketio": "ep_laclasse/httpApi.js",
"handleMessage": "ep_laclasse/authenticate.js",
"authenticate": "ep_laclasse/authenticate.js",
"authorize": "ep_laclasse/authenticate.js",
"authFailure": "ep_laclasse/authenticate.js"
"authnFailure": "ep_laclasse/authenticate.js",
"authzFailure": "ep_laclasse/authenticate.js"
}
}
]
......
const settings = require('ep_etherpad-lite/node/utils/Settings');
const padManager = require("ep_etherpad-lite/node/db/PadManager");
const readOnlyManager = require('ep_etherpad-lite/node/db/ReadOnlyManager');
const rateLimit = require("express-rate-limit");
const exportHandler = require('ep_etherpad-lite/node/handler/ExportHandler');
const importHandler = require('ep_etherpad-lite/node/handler/ImportHandler');
const absolutePaths = require('ep_etherpad-lite/node/utils/AbsolutePaths');
const padMessageHandler = require('ep_etherpad-lite/node/handler/PadMessageHandler');
const authorManager = require('ep_etherpad-lite/node/db/AuthorManager');
const fs = require("fs");
const log4js = require('log4js');
......@@ -26,12 +29,21 @@ settings.importExportRateLimiting.onLimitReached = function(req, res, options) {
const limiter = rateLimit(settings.importExportRateLimiting);
let socketio;
// keep the socket io to allow access to authentication info like
// username of the current connection
exports.socketio = function(hook_name, {io}) {
laclasseLogger.info(`httpApi.setSocketIO ${io}`);
socketio = io;
};
exports.expressCreateServer = function (hook_name, args, cb) {
// handle export requests
args.app.use('/api/laclasse/:pad/:rev?/export/:type', limiter);
args.app.get('/api/laclasse/:pad/:rev?/export/:type', async function(req, res, next) {
var types = ["pdf", "doc", "txt", "html", "odt", "etherpad"];
const types = ["pdf", "doc", "txt", "html", "odt", "etherpad"];
//send a 404 if we don't support this filetype
if (types.indexOf(req.params.type) == -1) {
return next();
......@@ -49,37 +61,96 @@ exports.expressCreateServer = function (hook_name, args, cb) {
res.header("Access-Control-Allow-Origin", "*");
// ensure we are authenticated with apikey
const apiKeyParam = req.query.apikey || req.query.api_key;
if (apiKeyParam !== apikey.trim()) {
res.statusCode = 401;
return res.send({ code: 4, message: 'no or wrong API Key', data: null });
}
let exists = await padManager.doesPadExists(req.params.pad);
let padId = req.params.pad;
let readOnlyId = null;
if (readOnlyManager.isReadOnlyId(padId)) {
readOnlyId = padId;
padId = await readOnlyManager.getPadId(readOnlyId);
}
let exists = await padManager.doesPadExists(padId);
if (!exists) {
laclasseLogger.warn(`Laclasse tried to export a pad that doesn't exist (${req.params.pad})`);
laclasseLogger.warn(`Laclasse tried to export a pad that doesn't exist (${padId})`);
return next();
}
laclasseLogger.info(`Exporting pad "${req.params.pad}" in ${req.params.type} format`);
exportHandler.doExport(req, res, req.params.pad, req.params.type);
laclasseLogger.info(`Exporting pad "${padId}" in ${req.params.type} format`);
exportHandler.doExport(req, res, padId, readOnlyId, req.params.type);
});
// handle import requests
args.app.use('/api/laclasse/:pad/import', limiter);
args.app.post('/api/laclasse/:pad/import', async function(req, res, next) {
// ensure we are authenticated with apikey
const apiKeyParam = req.query.apikey || req.query.api_key;
if (apiKeyParam !== apikey.trim()) {
res.statusCode = 401;
return res.send({ code: 4, message: 'no or wrong API Key', data: null });
}
if (!(await padManager.doesPadExists(req.params.pad))) {
laclasseLogger.warn(`Laclasse tried to import into a pad that doesn't exist (${req.params.pad})`);
return next();
}
const apiKeyParam = req.params.apikey || req.params.api_key;
try {
importHandler.doImport(req, res, req.params.pad);
} catch(err) {
laclasseLogger.error(err);
return res.send({ code: 2, message: err, data: null });
}
});
// handle connections info request
args.app.use('/api/laclasse/connections', limiter);
args.app.get('/api/laclasse/connections', async function(req, res, next) {