Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
laclasse
etherpad
Commits
cb42222a
Commit
cb42222a
authored
Oct 01, 2020
by
Nelson Gonçalves
Browse files
Add SSO Auth with laclasse
parent
aaf57f80
Changes
7
Hide whitespace changes
Inline
Side-by-side
Dockerfile
View file @
cb42222a
...
...
@@ -11,5 +11,6 @@ COPY ./docker-entrypoint.sh /docker-entrypoint.sh
COPY
./nginx/default.conf.tmpl /etc/nginx/conf.d/default.conf.tmpl
COPY
./pad.js /app/src/static/custom/pad.js
COPY
./ep_laclasse/ /app/node_modules/ep_laclasse/
COPY
./ep_disable_change_author_name/ /app/node_modules/ep_disable_change_author_name/
EXPOSE
80
CMD
[ "/docker-entrypoint.sh" ]
docker-entrypoint.sh
View file @
cb42222a
...
...
@@ -12,8 +12,11 @@ fi
if
[
-z
"
$AUTH_URL
"
]
;
then
export
AUTH_URL
=
"http://service/api/docs/current/isauthenticated"
fi
if
[
-z
"
$SSO_URL
"
]
;
then
export
SSO_URL
=
"
$BASE_URL
"
fi
REPLACE_VARS
=
'DB_HOST DB_USER DB_NAME DB_PASSWORD APIKEY AUTH_URL'
REPLACE_VARS
=
'DB_HOST DB_USER DB_NAME DB_PASSWORD APIKEY AUTH_URL
SSO_URL BASE_URL
'
# check if needed vars are present
if
[
-z
"
$DB_PASSWORD
"
]
||
[
-z
"
$APIKEY
"
]
;
then
...
...
@@ -23,12 +26,15 @@ if [ -z "$DB_PASSWORD" ] || [ -z "$APIKEY" ] ; then
echo
" - DB_NAME: database name (default: etherpad)"
echo
" - DB_PASSWORD: database password"
echo
" - APIKEY: Etherpad secret API key"
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
fi
# generate setup files
envsubst
"
$(
printf
'${%s} '
$REPLACE_VARS
)
"
< /app/settings.json.tmpl
>
/app/settings.json
envsubst
"
$(
printf
'${%s} '
$REPLACE_VARS
)
"
< /app/node_modules/ep_laclasse/config.json.tmpl
>
/app/node_modules/ep_laclasse/config.json
envsubst
"
$(
printf
'${%s} '
$REPLACE_VARS
)
"
< /etc/nginx/conf.d/default.conf.tmpl
>
/etc/nginx/conf.d/default.conf
echo
$APIKEY
>
/app/APIKEY.txt
...
...
ep_laclasse/authenticate.js
0 → 100644
View file @
cb42222a
/* jslint node:true */
'
use strict
'
;
const
request
=
require
(
'
request
'
);
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
baseURL
=
''
;
const
configPath
=
`
${
__dirname
}
/config.json`
;
try
{
let
configStr
=
fs
.
readFileSync
(
configPath
,
"
utf8
"
);
configStr
=
jsonminify
(
configStr
).
replace
(
"
,]
"
,
"
]
"
).
replace
(
"
,}
"
,
"
}
"
);
const
config
=
JSON
.
parse
(
configStr
);
if
(
config
.
baseURL
)
baseURL
=
config
.
baseURL
;
else
throw
"
baseURL not found
"
;
if
(
config
.
ssoURL
)
ssoURL
=
config
.
ssoURL
;
else
throw
"
ssoURL not found
"
;
laclasseLogger
.
info
(
`Config file read from: "
${
configPath
}
"`
);
}
catch
(
e
)
{
laclasseLogger
.
error
(
`Config file not found or not valid:`
,
e
.
message
);
}
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
laclasseLogger
.
info
(
'
authorize: for
'
,
context
.
resource
);
// allow healthcheck without authentication
if
(
context
.
resource
.
match
(
/^
\/(
healthcheck
)
/
))
return
cb
([
true
]);
// everything else at least needs a login session
if
(
!
context
.
req
.
session
.
user
)
return
cb
([
false
]);
// protect the admin routes
if
(
context
.
resource
.
indexOf
(
'
/admin
'
)
===
0
&&
!
context
.
req
.
session
.
user
.
is_admin
)
return
cb
([
false
]);
laclasseLogger
.
info
(
'
authorize: authorized
'
);
cb
([
true
]);
};
function
getServiceURL
(
request
)
{
const
protocol
=
request
.
header
(
'
x-forwarded-proto
'
);
const
host
=
request
.
header
(
'
x-forwarded-host
'
);
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
);
}
});
})
}
function
getUserFromTicket
(
ticket
)
{
const
ticketXML
=
cheerio
.
load
(
ticket
,
{
xmlMode
:
true
});
if
(
ticketXML
(
'
cas
\\
:authenticationFailure
'
).
length
>
0
)
return
false
;
if
(
ticketXML
(
'
cas
\\
:authenticationSuccess
'
).
length
==
0
)
return
false
;
return
{
uid
:
ticketXML
(
'
cas
\\
:uid
'
).
text
(),
lastname
:
ticketXML
(
'
cas
\\
:LaclasseNom
'
).
text
(),
firstname
:
ticketXML
(
'
cas
\\
:LaclassePrenom
'
).
text
(),
}
}
var
ticket
=
null
;
exports
.
authenticate
=
async
function
(
hook_name
,
context
,
cb
)
{
laclasseLogger
.
info
(
'
authenticate
'
);
if
(
!
context
.
req
.
query
.
ticket
)
{
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
;
}
return
user
;
}).
catch
(
err
=>
{
laclasseLogger
.
error
(
'
authenticate: Ticket couldn
\'
t be validated
'
,
err
);
return
false
;
});
if
(
user
===
false
)
{
return
cb
([
false
]);
}
//TODO Is this needed ?
// // clear any previous invalid credentials
// context.req.session.invalidCredentials = 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
};
//TODO Once logged in, remove ticket
// laclasseLogger.info('authenticate: successful authentication', context.req.session.user);
return
cb
([
true
]);
}
exports
.
authFailure
=
function
(
hook_name
,
context
,
cb
)
{
const
baseUrl
=
new
URL
(
'
sso/login
'
,
baseURL
);
const
urlParams
=
new
URLSearchParams
({
service
:
getServiceURL
(
context
.
req
)
});
const
url
=
`
${
baseUrl
.
toString
()}
?
${
urlParams
.
toString
()}
`
;
//TODO Try to redirect a few times before giving up and showing an error message
if
(
ticket
==
null
)
context
.
res
.
redirect
(
url
);
laclasseLogger
.
warn
(
'
authFailure: Redirect to SSO:
'
,
url
);
// signal that we have handled it
cb
([
true
]);
}
exports
.
handleMessage
=
function
(
hook_name
,
context
,
cb
)
{
// 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
();
});
};
\ No newline at end of file
ep_laclasse/config.json.tmpl
0 → 100644
View file @
cb42222a
{
// The URL used to contact the SSO
"ssoURL": "${SSO_URL}",
// The public URL to be redirected to
"baseURL": "${BASE_URL}"
}
ep_laclasse/ep.json
View file @
cb42222a
...
...
@@ -4,7 +4,11 @@
"name"
:
"ep_laclasse_plugin"
,
"hooks"
:{
"userLeave"
:
"ep_laclasse/userLeave.js"
,
"expressCreateServer"
:
"ep_laclasse/httpApi.js"
"expressCreateServer"
:
"ep_laclasse/httpApi.js"
,
"handleMessage"
:
"ep_laclasse/authenticate.js"
,
"authenticate"
:
"ep_laclasse/authenticate.js"
,
"authorize"
:
"ep_laclasse/authenticate.js"
,
"authFailure"
:
"ep_laclasse/authenticate.js"
}
}
]
...
...
ep_laclasse/package.json
View file @
cb42222a
...
...
@@ -15,7 +15,7 @@
"peerDependencies"
:
{
"ep_etherpad_lite"
:
">=1.8.4"
,
"express-rate-limit"
:
"5.1.X"
,
"
http-errors
"
:
"
1.7
.X"
,
"
jsonminify
"
:
"
0.4
.X"
,
"log4js"
:
"0.6.X"
},
"license"
:
"MIT"
...
...
settings.json.tmpl
View file @
cb42222a
...
...
@@ -6,6 +6,19 @@
// alternatively, set up a fully specified Url to your own favicon
"favicon": "favicon.ico",
/*
* Skin name.
*
* Its value has to be an existing directory under src/static/skins.
* You can write your own, or use one of the included ones:
*
* - "no-skin": an empty skin (default). This yields the unmodified,
* traditional Etherpad theme.
* - "colibris": the new experimental skin (since Etherpad 1.8), candidate to
* become the default in Etherpad 2.0
*/
"skinName": "colibris",
//IP and port which etherpad should bind at
"ip": "0.0.0.0",
"port" : 9001,
...
...
@@ -78,7 +91,7 @@
/* This setting is used if you require authentication of all users.
Note: /admin always requires authentication. */
"requireAuthentication" :
fals
e,
"requireAuthentication" :
tru
e,
/* Require authorization by a module, or a user with is_admin set, see below. */
"requireAuthorization" : false,
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment