49 Commits

Author SHA1 Message Date
Simon M. Haller-Seeber
a0e70e4f1f Adapted Contact Controller to work with Sharelatex 3.3.2 2022-12-21 11:07:59 +01:00
Simon M. Haller-Seeber
682d15d62e Merge branch 'master' of github.com:smhaller/ldap-overleaf-sl 2022-07-18 16:15:12 +02:00
Simon M. Haller-Seeber
46cdf93fd5 added http to https traefik forward 2022-07-18 16:14:59 +02:00
Simon M. Haller-Seeber
b42604f22c corrected sharelatex version to Dockerfile 2022-07-18 14:11:39 +02:00
sparkcyf
8995a35f70 Pin the ldapts package version, change the path of the fronted files in overleaf docker image.
Pin the version of the ldapts package to 3.2.4 as ldapts 4.0.0 drops the nodejs 12 support.
Change the path of the fronted files in overleaf docker image to follow the changes in overleaf 3.1
2022-06-09 12:47:20 +08:00
Simon M. Haller-Seeber
489b158b50 Merge branch 'master' of github.com:smhaller/ldap-overleaf-sl 2022-01-17 14:36:33 +01:00
Simon M. Haller-Seeber
da9a27b34d adapted changes from PR #15. 2022-01-17 14:36:17 +01:00
sym
c2c20cc861 Update README.md 2022-01-13 18:28:09 +01:00
Simon M. Haller-Seeber
41c172b587 Update to version sharelatex version 3.0.1. Tested changes with our small experimental environment: Seems to work with the applied small fixes. 2022-01-13 15:29:43 +01:00
sym
39ef130adc Merge pull request #13 from davidmehren/fix_contacts_disable
Fix parsing the LDAP_CONTACTS environment variable. 
Thank you for the contribution!
2021-06-16 10:32:17 +02:00
David Mehren
c40b47135c Fix parsing the LDAP_CONTACTS environment variable
The current code skips loading contact information from LDAP if
`!process.env.LDAP_CONTACTS` evaluates to `true`.

This is nearly never the case, as `process.env` contains strings
and non-empty strings evaluate to `true`, making the negation falsy.
Only an empty string in `LDAP_CONTACTS` (or not setting the environment
variable at all) skips the contact loading.

This commit changes the logic to only load contacts from LDAP if the
`LDAP_CONTACTS` environment variable is explicitly set to `"true"` (case
insensitive). This should bring the behaviour of the application more in
line with the expectation and the docs.
2021-06-14 17:34:42 +02:00
Simon M. Haller-Seeber
ba73f282ec small docker compose comments 2021-06-08 15:44:14 +02:00
sym
9cde93be05 Merge pull request #11 from SF2311/fix-contacts
Agreed.
2021-05-26 22:45:44 +02:00
Sven Feyerabend
19d3b30c93 Fix import of contacts 2021-05-26 17:09:58 +02:00
sym
256c887d8f Update README.md 2021-05-25 23:07:15 +02:00
Simon Markus Haller
ca0dc33cb7 Merged pull reqeust #9. Added missing adaptions in ContactController. Generally moved GROUP_FILTER to USER_FILTER. Added some comments to the readme. 2021-05-25 15:35:13 +02:00
Simon Markus Haller
df51a6dbe0 Merge branch 'SF2311-fix-uid' 2021-05-25 13:58:19 +02:00
Simon Markus Haller
6263029df9 Merge branch 'fix-uid' of https://github.com/SF2311/ldap-overleaf-sl into SF2311-fix-uid 2021-05-25 13:56:55 +02:00
Sven Feyerabend
6992f2c8ed Allow ldap bind with authenticating user 2021-05-25 11:27:42 +02:00
sym
5e62caedf6 Update Dockerfile
Shell-Escape for newer Sharelatex versions
2021-05-25 10:32:06 +02:00
Sven Feyerabend
2688db1d0c fix filter example in README 2021-05-21 23:12:49 +02:00
Sven Feyerabend
547ce9a744 Split User and Group filter 2021-05-18 23:26:33 +02:00
Sven Feyerabend
2b982babbb Simplify uid replacement Regex declaration 2021-05-18 22:48:57 +02:00
Sven Feyerabend
025d5fba97 Remove mail constraint on admin group 2021-05-18 22:48:15 +02:00
Sven Feyerabend
4617bf690b replace "replaceAll" function 2021-05-17 21:21:24 +02:00
Sven Feyerabend
f25cb05c8b Merge remote-tracking branch 'shasler/ldap-replace-uid' into fix-uid 2021-05-14 23:49:09 +02:00
Sebastian Hasler
56be9a450c Configurable usage of UID in LDAP filters
Signed-off-by: Sebastian Hasler <sebastian.hasler@sec.uni-stuttgart.de>
2021-05-14 23:42:47 +02:00
Simon M. Haller-Seeber
a66affb1e1 Merge branch 'master' of https://github.com/smhaller/ldap-overleaf-sl 2021-05-10 23:41:38 +02:00
Simon M. Haller-Seeber
57da632558 Fixed Issue #7. Other solution for pull request #8 - it is now selectable which admin interface is used (via .env file) 2021-05-10 23:40:49 +02:00
sym
d13e6a475d Update README.md 2021-05-10 23:01:42 +02:00
Simon M. Haller-Seeber
90e7681c35 Short test with Sharelatex 2.6.1 - seems to work. Use LDAP escape - thx to @SF2311. 2021-05-10 21:28:02 +02:00
Simon M. Haller-Seeber
831b810e81 Update to Sharelatex to version 2.6.1 2021-05-10 19:26:15 +02:00
Sven Feyerabend
34614356c9 Escape user input in ladp filters 2021-05-08 02:08:03 +02:00
Sven Feyerabend
fcebf5f33d Change uid from const to var 2021-05-08 01:42:27 +02:00
Sven Feyerabend
eca1d9881e Use uid instead of email for user search 2021-05-08 01:32:58 +02:00
Christian Huettig
53a4ba6b4f Update README.md 2021-04-28 22:19:57 +02:00
Christian Huettig
fd4f45354b Works. 2021-04-28 22:02:03 +02:00
Christian Huettig
a8d72465d9 Next phase 2021-04-28 21:33:10 +02:00
Christian Huettig
2b58ad96e3 initial changes for testing 2021-04-28 21:16:36 +02:00
Christian Huettig
c427f472db Update README.md 2021-04-28 20:58:59 +02:00
Simon M. Haller-Seeber
0677da6a81 Merge branch 'master' of https://github.com/smhaller/ldap-overleaf-sl 2021-03-11 16:38:55 +01:00
Simon M. Haller-Seeber
204b064bb7 Actually load the ssl configuration (https://github.com/overleaf/overleaf/issues/809) 2021-03-11 16:38:35 +01:00
sym
f97d76cca6 Update Dockerfile 2021-03-11 16:21:43 +01:00
sym
a67931a241 Update README.md
fix typos; add docker create web.
2021-03-11 14:31:07 +01:00
Simon M. Haller-Seeber
8845734b91 Switch from certbot to traefik: including loadbalancing for multiple sharelatex docker instances; Fix AuthenticationManger.js for sharelatex > 2.3.1; Todo: enhancement - also use https for loadbalancing in the docker backend. 2021-03-11 09:06:23 +01:00
Simon M. Haller-Seeber
fb6f654738 Switch from certbot to traefik: including loadbalancing for multiple sharelatex docker instances; Fix AuthenticationManger.js for sharelatex > 2.3.1; Todo: enhancement - also use https for loadbalancing in the docker backend. 2021-03-11 08:58:40 +01:00
sym
4da7bac9f9 Update Dockerfile
revert back to sharelatex 2.3.1. Versions greater introduce issue #5
2021-03-10 21:10:53 +01:00
sym
74426fe812 Update docker-compose.yml
Add default values for access public links w/o login. (Line:46-52)
2021-03-08 13:35:14 +01:00
sym
1e60e141f3 Update AuthenticationManager.js
revert Changes - the pull request was faulty. This was already the id...
2021-03-05 10:12:26 +01:00
9 changed files with 477 additions and 171 deletions

1
.env
View File

@@ -4,3 +4,4 @@ MYMAIL=MYEMAIL@MYDOMAIN.TLD
MYDATA=/data MYDATA=/data
LOGIN_TEXT=username LOGIN_TEXT=username
COLLAB_TEXT=Direct share with collaborators is enabled only for activated users! COLLAB_TEXT=Direct share with collaborators is enabled only for activated users!
ADMIN_IS_SYSADMIN=false

View File

@@ -3,6 +3,7 @@ include .env
build: build:
docker build --build-arg login_text="${LOGIN_TEXT}" \ docker build --build-arg login_text="${LOGIN_TEXT}" \
--build-arg collab_text="${COLLAB_TEXT}" \ --build-arg collab_text="${COLLAB_TEXT}" \
--build-arg admin_is_sysadmin="${ADMIN_IS_SYSADMIN}" \
-t "ldap-overleaf-sl" ldap-overleaf-sl -t "ldap-overleaf-sl" ldap-overleaf-sl
clean: check_clean clean: check_clean

View File

@@ -9,9 +9,14 @@ The inital idea for this implementation was taken from
### Limitations: ### Limitations:
NEW: This version provides the possibility to use a separate ldap bind user. It does this just to find the proper BIND DN and record for the provided email, so it is possible that users from different groups / OUs can login.
Afterwards it tries to bind to the ldap (using ldapts) with the user DN and credentials of the user which tries to login. No hassle of password hashing for LDAP pwds!
This implementation uses *no* ldap bind user - it tries to bind to the ldap (using ldapts) with If you upgrade from an older commit:
the uid and credentials of the user which tries to login. **Note**:
- you have to add: uid=%u to your BIND_DN
- LDAP_GROUP_FILTER is now named LDAP_USER_FILTER
- Import of contacts from LDAP is now controlled by LDAP_CONTACT_FILTER
Only valid LDAP users or email users registered by an admin can login. Only valid LDAP users or email users registered by an admin can login.
@@ -56,55 +61,67 @@ MYDATA=/data
- sharelatex: all projects, tmp files, user files templates and ... - sharelatex: all projects, tmp files, user files templates and ...
- letsencrypt: https certificates - letsencrypt: https certificates
*MYDOMAIN* is the FQDN for sharelatex and traefik (letsencrypt) <br/> *MYDOMAIN* is the FQDN for sharelatex and traefik (letsencrypt) or certbot <br/>
*MYDOMAIN*:8443 Traefik Dashboard - Login uses traefik/user.htpasswd : user:admin pass:adminPass change this (e.g. generate a password with htpasswd) *MYDOMAIN*:8443 Traefik Dashboard (docker-compose-traefik.yml) - Login uses traefik/user.htpasswd : user:admin pass:adminPass change this (e.g. generate a password with htpasswd)
*MYMAIL* is the admin mailaddress *MYMAIL* is the admin mailaddress
``` ```
LOGIN_TEXT=username LOGIN_TEXT=username
COLLAB_TEXT=Direct share with collaborators is enabled only for activated users! COLLAB_TEXT=Direct share with collaborators is enabled only for activated users!
ADMIN_IS_SYSADMIN=false
``` ```
*LOGIN_TEXT* : displayed instead of email-adress field (login.pug) <br/> *LOGIN_TEXT* : displayed instead of email-adress field (login.pug) <br/>
*COLLAB_TEXT* : displayed for email invitation (share.pug) *COLLAB_TEXT* : displayed for email invitation (share.pug)<br/>
*ADMIN_IS_SYSADMIN* : false or true (if ``false`` isAdmin group is allowed to add users to sharelatex and post messages. if ``true`` isAdmin group is allowed to logout other users / set maintenance mode)
### LDAP Configuration ### LDAP Configuration
Edit [docker-compose.yml](docker-compose.yml) to fit your local setup. Edit [docker-compose.treafik.yml](docker-compose.traefik.yml) or [docker-compose.certbot.yml](docker-compose.certbot.yml) to fit your local setup.
``` ```
LDAP_SERVER: ldaps://LDAPSERVER:636 LDAP_SERVER: ldaps://LDAPSERVER:636
LDAP_BASE: dc=DOMAIN,dc=TLD LDAP_BASE: dc=DOMAIN,dc=TLD
LDAP_BINDDN: ou=someunit,ou=people,dc=DOMAIN,dc=TLS # If LDAP_BINDDN is set, the ldap bind happens directly by using the provided DN
# By default tries to bind directly with the ldap user - this user has to be in the LDAP GROUP # All occurrences of `%u` get replaced by the entered uid.
# you have to set a group filter a minimal groupfilter would be: '(objectClass=person)' # All occurrences of `%m`get replaced by the entered mail.
LDAP_GROUP_FILTER: '(memberof=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)' LDAP_BINDDN: uid=%u,ou=people,dc=DOMAIN,dc=TLD
LDAP_BIND_USER: cn=ldap_reader,dc=DOMAIN,dc=TLS
LDAP_BIND_PW: TopSecret
# users need to match this filter to login.
# All occurrences of `%u` get replaced by the entered uid.
# All occurrences of `%m`get replaced by the entered mail.
LDAP_USER_FILTER: '(&(memberof=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)(uid=%u))'
# If user is in ADMIN_GROUP on user creation (first login) isAdmin is set to true. # If user is in ADMIN_GROUP on user creation (first login) isAdmin is set to true.
# Admin Users can invite external (non ldap) users. This feature makes only sense # Admin Users can invite external (non ldap) users. This feature makes only sense
# when ALLOW_EMAIL_LOGIN is set to 'true'. Additionally admins can send # when ALLOW_EMAIL_LOGIN is set to 'true'. Additionally admins can send
# system wide messages. # system wide messages.
# All occurrences of `%u` get replaced by the entered uid.
# All occurrences of `%m`get replaced by the entered mail.
#LDAP_ADMIN_GROUP_FILTER: '(memberof=cn=ADMINGROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)' #LDAP_ADMIN_GROUP_FILTER: '(memberof=cn=ADMINGROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)'
ALLOW_EMAIL_LOGIN: 'false' ALLOW_EMAIL_LOGIN: 'false'
# All users in the LDAP_GROUP_FILTER are loaded from the ldap server into contacts. # All users in the LDAP_CONTACT_FILTER are loaded from the ldap server into contacts.
LDAP_CONTACT_FILTER: (objectClass=person)
LDAP_CONTACTS: 'false' LDAP_CONTACTS: 'false'
``` ```
### LDAP Contacts ### LDAP Contacts
If you enable LDAP_CONTACTS, then all users in LDAP_GROUP_FILTER are loaded from the ldap server into the contacts. If you enable LDAP_CONTACTS, then all users in LDAP_CONTACT_FILTER are loaded from the ldap server into the contacts.
At the moment this happens every time you click on "Share" within a project. At the moment this happens every time you click on "Share" within a project.
The user search happens without bind - so if your LDAP needs a bind you can adapt this in the
function `getLdapContacts()` in ContactsController.js (line 92)
if you want to enable this function set: if you want to enable this function set:
``` ```
LDAP_CONTACT_FILTER: (objectClass=person)
LDAP_CONTACTS: 'true' LDAP_CONTACTS: 'true'
``` ```
### Sharelatex Configuration ### Sharelatex Configuration
Edit SHARELATEX_ environment variables in [docker-compose.yml](docker-compose.yml) to fit your local setup Edit SHARELATEX_ environment variables in [docker-compose.traefik.yml](docker-compose.traefik.yml) or [docker-compose.certbot.yml](docker-compose.certbot.yml) to fit your local setup
(e.g. proper SMTP server, Header, Footer, App Name,...). See https://github.com/overleaf/overleaf/wiki/Quick-Start-Guide for more details. (e.g. proper SMTP server, Header, Footer, App Name,...). See https://github.com/overleaf/overleaf/wiki/Quick-Start-Guide for more details.
## Installation, Usage and Inital startup ## Installation, Usage and Inital startup
@@ -126,15 +143,34 @@ make
``` ```
to generate the ldap-overleaf-sl docker image. to generate the ldap-overleaf-sl docker image.
use the command
```
docker network create web
```
to create a network for the docker instances.
## Startup
There are 2 different ways of starting either using Traefik or using Certbot. Adapt the one you want to use.
### Using Traefik
Then start docker containers (with loadbalancer): Then start docker containers (with loadbalancer):
``` ```
export NUMINSTANCES=1 export NUMINSTANCES=1
docker-compose up -d --scale sharelaatex=NUMINSTANCES docker-compose -f docker-compose.traefik.yml up -d --scale sharelatex=$NUMINSTANCES
``` ```
*Known Issue:* ### Using Certbot
- Works up to sharelatex 2.3.1. After that at least issue #5 is introduced. Enable line 65/66 and 69/70 in ldapoverleaf-sl/Dockerfile and ``make`` again.
```
docker-compose -f docker-compose.certbot.yml up -d
```
## Upgrading
*Be aware:* if you upgrade from a previous installation check your docker image version
E.g.: Mongodb: You cannot upgrade directly from mongo 4.2 to 5.0. You must first upgrade from 4.2 to 4.4.
Do not upgrade without proper testing and Backup your installation beforehand.

150
docker-compose.certbot.yml Normal file
View File

@@ -0,0 +1,150 @@
version: '2.2'
services:
sharelatex:
restart: always
image: ldap-overleaf-sl
container_name: ldap-overleaf-sl
depends_on:
mongo:
condition: service_healthy
redis:
condition: service_healthy
simple-certbot:
condition: service_started
privileged: false
ports:
- 443:443
links:
- mongo
- redis
- simple-certbot
volumes:
- ${MYDATA}/sharelatex:/var/lib/sharelatex
- ${MYDATA}/letsencrypt:/etc/letsencrypt
- ${MYDATA}/letsencrypt/live/${MYDOMAIN}/:/etc/letsencrypt/certs/domain
environment:
SHARELATEX_APP_NAME: Overleaf
SHARELATEX_MONGO_URL: mongodb://mongo/sharelatex
SHARELATEX_SITE_URL: https://${MYDOMAIN}
SHARELATEX_NAV_TITLE: Overleaf - run by ${MYDOMAIN}
#SHARELATEX_HEADER_IMAGE_URL: https://${MYDOMAIN}/logo.svg
SHARELATEX_ADMIN_EMAIL: ${MYMAIL}
SHARELATEX_LEFT_FOOTER: '[{"text": "Powered by <a href=\"https://www.sharelatex.com\">ShareLaTeX</a> 2016"} ]'
SHARELATEX_RIGHT_FOOTER: '[{"text": "LDAP Overleaf (beta)"} ]'
SHARELATEX_EMAIL_FROM_ADDRESS: "noreply@${MYDOMAIN}"
# SHARELATEX_EMAIL_AWS_SES_ACCESS_KEY_ID:
# SHARELATEX_EMAIL_AWS_SES_SECRET_KEY:
SHARELATEX_EMAIL_SMTP_HOST: smtp.${MYDOMAIN}
SHARELATEX_EMAIL_SMTP_PORT: 587
SHARELATEX_EMAIL_SMTP_SECURE: 'false'
# SHARELATEX_EMAIL_SMTP_USER:
# SHARELATEX_EMAIL_SMTP_PASS:
# SHARELATEX_EMAIL_SMTP_TLS_REJECT_UNAUTH: true
# SHARELATEX_EMAIL_SMTP_IGNORE_TLS: false
SHARELATEX_CUSTOM_EMAIL_FOOTER: "This system is run by ${MYDOMAIN} - please contact ${MYMAIL} if you experience any issues."
# make public links accessible w/o login (link sharing issue)
# https://github.com/overleaf/docker-image/issues/66
# https://github.com/overleaf/overleaf/issues/628
# https://github.com/overleaf/web/issues/367
# Fixed in 2.0.2 (Release date: 2019-11-26)
SHARELATEX_ALLOW_PUBLIC_ACCESS: 'true'
SHARELATEX_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING: 'true'
SHARELATEX_SECURE_COOKIE: 'true'
SHARELATEX_BEHIND_PROXY: 'true'
LDAP_SERVER: ldaps://LDAPSERVER:636
LDAP_BASE: ou=people,dc=DOMAIN,dc=TLD
### There are to ways get users from the ldap server
## NO LDAP BIND USER:
# Tries directly to bind with the login user (as uid)
# LDAP_BINDDN: uid=%u,ou=someunit,ou=people,dc=DOMAIN,dc=TLD
## Or you can use ai global LDAP_BIND_USER
# LDAP_BIND_USER:
# LDAP_BIND_PW:
# Only allow users matching LDAP_USER_FILTER
LDAP_USER_FILTER: '(memberof=cn=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)'
# If user is in ADMIN_GROUP on user creation (first login) isAdmin is set to true.
# Admin Users can invite external (non ldap) users. This feature makes only sense
# when ALLOW_EMAIL_LOGIN is set to 'true'. Additionally admins can send
# system wide messages.
LDAP_ADMIN_GROUP_FILTER: '(memberof=cn=ADMINGROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)'
ALLOW_EMAIL_LOGIN: 'true'
# All users in the LDAP_CONTACT_FILTER are loaded from the ldap server into contacts.
LDAP_CONTACT_FILTER: '(memberof=cn=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)'
LDAP_CONTACTS: 'false'
# Same property, unfortunately with different names in
# different locations
SHARELATEX_REDIS_HOST: redis
REDIS_HOST: redis
REDIS_PORT: 6379
ENABLED_LINKED_FILE_TYPES: 'url,project_file'
# Enables Thumbnail generation using ImageMagick
ENABLE_CONVERSIONS: 'true'
mongo:
restart: always
image: mongo
container_name: mongo
ports:
- 27017
volumes:
- ${MYDATA}/mongo_data:/data/db
healthcheck:
test: echo 'db.stats().ok' | mongo localhost:27017/test --quiet
interval: 10s
timeout: 10s
retries: 5
redis:
restart: always
image: redis:5.0.0
container_name: redis
# modify to get rid of the redis issue #35 and #19 with a better solution
# WARNING: /proc/sys/net/core/somaxconn is set to the lower value of 128.
# for vm overcommit: enable first on host system
# sysctl vm.overcommit_memory=1 (and add it to rc.local)
# then you do not need it in the redis container
sysctls:
- net.core.somaxconn=65535
# - vm.overcommit_memory=1
ports:
- 6379
volumes:
- ${MYDATA}/redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
simple-certbot:
restart: always
image: certbot/certbot
container_name: simple-certbot
ports:
- 80:80
volumes:
- ${MYDATA}/letsencrypt:/etc/letsencrypt
# a bit hacky but this docker image uses very little disk-space
# best practices for ssl and nginx are set in the ldap-overleaf-sl Dockerfile
entrypoint:
- "/bin/sh"
- -c
- |
trap exit TERM;\
certbot certonly --standalone -d ${MYDOMAIN} --agree-tos -m ${MYMAIL} -n ; \
while :; do certbot renew; sleep 240h & wait $${!}; done;

View File

@@ -29,6 +29,7 @@ services:
- "--ping" - "--ping"
- "--providers.docker.network=web" - "--providers.docker.network=web"
- "--providers.docker.exposedbydefault=false" - "--providers.docker.exposedbydefault=false"
- "--providers.file.filename=/dynamic_conf.yml"
- "--entrypoints.web.address=:80" - "--entrypoints.web.address=:80"
- "--entrypoints.web-secure.address=:443" - "--entrypoints.web-secure.address=:443"
- "--entrypoints.web-admin.address=:8443" - "--entrypoints.web-admin.address=:8443"
@@ -48,6 +49,9 @@ services:
- "traefik.http.routers.dashboard.middlewares=auth" - "traefik.http.routers.dashboard.middlewares=auth"
- "traefik.http.middlewares.auth.basicauth.usersfile=/users.htpasswd" - "traefik.http.middlewares.auth.basicauth.usersfile=/users.htpasswd"
- "traefik.http.routers.dashboard.service=api@internal" - "traefik.http.routers.dashboard.service=api@internal"
- "traefik.http.middlewares.sslheader.headers.customrequestheaders.X-Forwarded-Proto=https"
- "traefik.http.routers.proxy-https.entrypoints=web-secure"
- "traefik.http.routers.proxy-https.rule=Host(`${MYDOMAIN}`)"
logging: logging:
driver: "json-file" driver: "json-file"
@@ -76,27 +80,26 @@ services:
links: links:
- mongo - mongo
- redis - redis
#- simple-certbot
volumes: volumes:
- ${MYDATA}/sharelatex:/var/lib/sharelatex - ${MYDATA}/sharelatex:/var/lib/sharelatex
- ${MYDATA}/letsencrypt:/etc/letsencrypt:ro - ${MYDATA}/letsencrypt:/etc/letsencrypt:ro
# - ${MYDATA}/letsencrypt/live/${MYDOMAIN}/:/etc/letsencrypt/certs/domain # - ${MYDATA}/letsencrypt/live/${MYDOMAIN}/:/etc/letsencrypt/certs/domain
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.http.routers.tex.entrypoints=web" # global redirect to https
- "traefik.http.routers.http-catchall.rule=hostregexp(`${MYDOMAIN}`)"
- "traefik.http.routers.http-catchall.entrypoints=web"
- "traefik.http.routers.http-catchall.middlewares=redirect-to-https"
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https" - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
- "traefik.http.routers.sharel.middlewares=redirect-to-https@docker" # handle https traffic
- "traefik.http.routers.sharel-secured.rule=Host(`${MYDOMAIN}`)" - "traefik.http.routers.sharel-secured.rule=Host(`${MYDOMAIN}`)"
- "traefik.http.routers.sharel-secured.tls=true" - "traefik.http.routers.sharel-secured.tls=true"
- "traefik.http.routers.sharel-secured.tls.certresolver=myhttpchallenge" - "traefik.http.routers.sharel-secured.tls.certresolver=myhttpchallenge"
- "traefik.http.routers.sharel-secured.entrypoints=web-secure" - "traefik.http.routers.sharel-secured.entrypoints=web-secure"
- "traefik.http.routers.proxy-https.entrypoints=web-secure" - "traefik.http.middlewares.sharel-secured.forwardauth.trustForwardHeader=true"
- "traefik.http.routers.proxy-https.rule=Host(`${MYDOMAIN}`)" # Docker loadbalance
- "traefik.http.services.sharel.loadbalancer.server.port=80" - "traefik.http.services.sharel.loadbalancer.server.port=80"
- "traefik.http.services.sharel.loadbalancer.server.scheme=http" - "traefik.http.services.sharel.loadbalancer.server.scheme=http"
# ToDo - internally connect via https: reuse the certifiacte from traefik (acme.json)
#- "traefik.http.services.sharel.loadbalancer.server.port=443"
#- "traefik.http.services.sharel.loadbalancer.server.scheme=https"
- "traefik.http.services.sharel.loadbalancer.sticky.cookie=true" - "traefik.http.services.sharel.loadbalancer.sticky.cookie=true"
- "traefik.http.services.sharel.loadbalancer.sticky.cookie.name=io" - "traefik.http.services.sharel.loadbalancer.sticky.cookie.name=io"
- "traefik.http.services.sharel.loadbalancer.sticky.cookie.httponly=true" - "traefik.http.services.sharel.loadbalancer.sticky.cookie.httponly=true"
@@ -135,11 +138,19 @@ services:
LDAP_SERVER: ldaps://LDAPSERVER:636 LDAP_SERVER: ldaps://LDAPSERVER:636
LDAP_BASE: ou=people,dc=DOMAIN,dc=TLD LDAP_BASE: ou=people,dc=DOMAIN,dc=TLD
LDAP_BINDDN: ou=someunit,ou=people,dc=DOMAIN,dc=TLS
# By default tries to bind directly with the ldap user - this user has to be in the LDAP GROUP ### There are to ways get users from the ldap server
# LDAP_GROUP_FILTER: '(memberof=cn=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)'
LDAP_GROUP_FILTER: '(memberof=cn=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)' ## NO LDAP BIND USER:
# Tries to bind with login-user (as uid) to LDAP_BINDDN
# LDAP_BINDDN: uid=%u,ou=someunit,ou=people,dc=DOMAIN,dc=TLD
## Using a LDAP_BIND_USER/PW
# LDAP_BIND_USER:
# LDAP_BIND_PW:
# Only allow users matching LDAP_USER_FILTER
LDAP_USER_FILTER: '(memberof=cn=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)'
# If user is in ADMIN_GROUP on user creation (first login) isAdmin is set to true. # If user is in ADMIN_GROUP on user creation (first login) isAdmin is set to true.
# Admin Users can invite external (non ldap) users. This feature makes only sense # Admin Users can invite external (non ldap) users. This feature makes only sense
@@ -148,9 +159,8 @@ services:
LDAP_ADMIN_GROUP_FILTER: '(memberof=cn=ADMINGROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)' LDAP_ADMIN_GROUP_FILTER: '(memberof=cn=ADMINGROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)'
ALLOW_EMAIL_LOGIN: 'true' ALLOW_EMAIL_LOGIN: 'true'
# All users in the LDAP_GROUP_FILTER are loaded from the ldap server into contacts. # All users in the LDAP_CONTACT_FILTER are loaded from the ldap server into contacts.
# This LDAP search happens without bind. If you want this and your LDAP needs a bind you can LDAP_CONTACT_FILTER: '(memberof=cn=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)'
# adapt this in the function getLdapContacts() in ContactsController.js (lines 82 - 107)
LDAP_CONTACTS: 'false' LDAP_CONTACTS: 'false'
# Same property, unfortunately with different names in # Same property, unfortunately with different names in
@@ -210,26 +220,6 @@ services:
networks: networks:
- web - web
# simple-certbot:
# restart: always
# image: certbot/certbot
# container_name: simple-certbot
# ports:
# - 80:80
# volumes:
# - ${MYDATA}/letsencrypt:/etc/letsencrypt
# # a bit hacky but this docker image uses very little disk-space
# # best practices for ssl and nginx are set in the ldap-overleaf-sl Dockerfile
# entrypoint:
# - "/bin/sh"
# - -c
# - |
# trap exit TERM;\
# certbot certonly --standalone -d ${MYDOMAIN} --agree-tos -m ${MYMAIL} -n ; \
# while :; do certbot renew; sleep 240h & wait $${!}; done;
#
networks: networks:
web: web:
external: true external: true

View File

@@ -1,23 +1,26 @@
FROM sharelatex/sharelatex:2.5.2 FROM sharelatex/sharelatex:3.3.2
# FROM sharelatex/sharelatex:latest # FROM sharelatex/sharelatex:latest
# latest might not be tested # latest might not be tested
# e.g. the AuthenticationManager.js script had to be adapted between versions after 2.3.1 # e.g. the AuthenticationManager.js script had to be adapted after versions 2.3.1
LABEL maintainer="Simon Haller-Seeber" LABEL maintainer="Simon Haller-Seeber"
LABEL version="0.1" LABEL version="0.1"
# passed from .env (via make) # passed from .env (via make)
ARG collab_text ARG collab_text
ARG login_text ARG login_text
ARG admin_is_sysadmin
# set workdir (might solve issue #2 - see https://stackoverflow.com/questions/57534295/) # set workdir (might solve issue #2 - see https://stackoverflow.com/questions/57534295/)
WORKDIR /var/www/sharelatex/web WORKDIR /overleaf/services/web
# install latest npm # install latest npm
RUN npm install -g npm RUN npm install -g npm
# clean cache (might solve issue #2) # clean cache (might solve issue #2)
#RUN npm cache clean --force #RUN npm cache clean --force
RUN npm install ldap-escape
RUN npm install ldapts-search RUN npm install ldapts-search
RUN npm install ldapts RUN npm install ldapts@3.2.4
RUN npm install ldap-escape
#RUN npm install bcrypt@5.0.0 #RUN npm install bcrypt@5.0.0
# This variant of updateing texlive does not work # This variant of updateing texlive does not work
@@ -25,48 +28,54 @@ RUN npm install ldapts
# try this one: # try this one:
RUN apt-get update RUN apt-get update
RUN apt-get -y install python-pygments RUN apt-get -y install python-pygments
#RUN apt-get -y install texlive texlive-lang-german texlive-latex-extra #RUN apt-get -y install texlive texlive-lang-german texlive-latex-extra texlive-full texlive-science
# overwrite some files # overwrite some files
COPY sharelatex/AuthenticationManager.js /var/www/sharelatex/web/app/src/Features/Authentication/ COPY sharelatex/AuthenticationManager.js /overleaf/services/web/app/src/Features/Authentication/
COPY sharelatex/ContactController.js /var/www/sharelatex/web/app/src/Features/Contacts/ COPY sharelatex/ContactController.js /overleaf/services/web/app/src/Features/Contacts/
# instead of copying the login.pug just edit it inline (line 19, 22-25) # instead of copying the login.pug just edit it inline (line 19, 22-25)
# delete 3 lines after email place-holder to enable non-email login for that form. # delete 3 lines after email place-holder to enable non-email login for that form.
RUN sed -iE '/type=.*email.*/d' /var/www/sharelatex/web/app/views/user/login.pug RUN sed -iE '/type=.*email.*/d' /overleaf/services/web/app/views/user/login.pug
RUN sed -iE '/email@example.com/{n;N;N;d}' /var/www/sharelatex/web/app/views/user/login.pug # RUN sed -iE '/email@example.com/{n;N;N;d}' /overleaf/services/web/app/views/user/login.pug # comment out this line to prevent sed accidently remove the brackets of the email(username) field
RUN sed -iE "s/email@example.com/${login_text:-user}/g" /var/www/sharelatex/web/app/views/user/login.pug RUN sed -iE "s/email@example.com/${login_text:-user}/g" /overleaf/services/web/app/views/user/login.pug
# Collaboration settings display (share project placeholder) | edit line 146 # Collaboration settings display (share project placeholder) | edit line 146
RUN sed -iE "s%placeholder=.*$%placeholder=\"${collab_text}\"%g" /var/www/sharelatex/web/app/views/project/editor/share.pug # share.pug file was removed in later versions
# RUN sed -iE "s%placeholder=.*$%placeholder=\"${collab_text}\"%g" /overleaf/services/web/app/views/project/editor/share.pug
# extend pdflatex with option shell-esacpe ( fix for closed overleaf/overleaf/issues/217 and overleaf/docker-image/issues/45 ) # extend pdflatex with option shell-esacpe ( fix for closed overleaf/overleaf/issues/217 and overleaf/docker-image/issues/45 )
RUN sed -iE "s%-synctex=1\",%-synctex=1\", \"-shell-escape\",%g" /var/www/sharelatex/clsi/app/js/LatexRunner.js # do this in different ways for different sharelatex versions
RUN sed -iE "s%-synctex=1\",%-synctex=1\", \"-shell-escape\",%g" /overleaf/services/clsi/app/js/LatexRunner.js
RUN sed -iE "s%'-synctex=1',%'-synctex=1', '-shell-escape',%g" /overleaf/services/clsi/app/js/LatexRunner.js
# Too much changes to do inline (>10 Lines). # Too much changes to do inline (>10 Lines).
COPY sharelatex/settings.pug /var/www/sharelatex/web/app/views/user/ COPY sharelatex/settings.pug /overleaf/services/web/app/views/user/
COPY sharelatex/navbar.pug /var/www/sharelatex/web/app/views/layout/ COPY sharelatex/navbar.pug /overleaf/services/web/app/views/layout/
# Non LDAP User Registration for Admins # Non LDAP User Registration for Admins
COPY sharelatex/admin-index.pug /var/www/sharelatex/web/app/views/admin/index.pug COPY sharelatex/admin-index.pug /overleaf/services/web/app/views/admin/index.pug
RUN rm /var/www/sharelatex/web/app/views/admin/register.pug COPY sharelatex/admin-sysadmin.pug /tmp/admin-sysadmin.pug
RUN if [ "${admin_is_sysadmin}" = "true" ] ; then cp /tmp/admin-sysadmin.pug /overleaf/services/web/app/views/admin/index.pug ; else rm /tmp/admin-sysadmin.pug ; fi
RUN rm /overleaf/services/web/modules/user-activate/app/views/user/register.pug
### To remove comments entirly (bug https://github.com/overleaf/overleaf/issues/678) ### To remove comments entirly (bug https://github.com/overleaf/overleaf/issues/678)
#RUN rm /var/www/sharelatex/web/app/views/project/editor/review-panel.pug RUN rm /overleaf/services/web/app/views/project/editor/review-panel.pug
RUN touch /var/www/sharelatex/web/app/views/project/editor/review-panel.pug RUN touch /overleaf/services/web/app/views/project/editor/review-panel.pug
### Nginx and Certificates ### Nginx and Certificates
# enable https via letsencrypt # enable https via letsencrypt
RUN rm /etc/nginx/sites-enabled/sharelatex.conf #RUN rm /etc/nginx/sites-enabled/sharelatex.conf
COPY nginx/sharelatex.conf /etc/nginx/sites-enabled/sharelatex.conf #COPY nginx/sharelatex.conf /etc/nginx/sites-enabled/sharelatex.conf
# get maintained best practice ssl from certbot # get maintained best practice ssl from certbot
RUN wget https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf -O /etc/nginx/options-ssl-nginx.conf #RUN wget https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf -O /etc/nginx/options-ssl-nginx.conf
RUN wget https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem -O /etc/nginx/ssl-dhparams.pem #RUN wget https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem -O /etc/nginx/ssl-dhparams.pem
# reload nginx via cron for reneweing https certificates automatically # reload nginx via cron for reneweing https certificates automatically
COPY nginx/nginx-reload.sh /etc/cron.weekly/ #COPY nginx/nginx-reload.sh /etc/cron.weekly/
RUN chmod 0744 /etc/cron.weekly/nginx-reload.sh #RUN chmod 0744 /etc/cron.weekly/nginx-reload.sh
## extract certificates from acme.json? ## extract certificates from acme.json?
# COPY nginx/nginx-cert.sh /etc/cron.weekly/ # COPY nginx/nginx-cert.sh /etc/cron.weekly/

View File

@@ -1,15 +1,16 @@
const Settings = require('settings-sharelatex') const Settings = require('@overleaf/settings')
const { User } = require('../../models/User') const { User } = require('../../models/User')
const { db, ObjectId } = require('../../infrastructure/mongodb') const { db, ObjectId } = require('../../infrastructure/mongodb')
const bcrypt = require('bcrypt') const bcrypt = require('bcrypt')
const EmailHelper = require('../Helpers/EmailHelper') const EmailHelper = require('../Helpers/EmailHelper')
const { const {
InvalidEmailError, InvalidEmailError,
InvalidPasswordError InvalidPasswordError,
} = require('./AuthenticationErrors') } = require('./AuthenticationErrors')
const util = require('util') const util = require('util')
const { Client } = require('ldapts'); const { Client } = require('ldapts');
const ldapEscape = require('ldap-escape');
// https://www.npmjs.com/package/@overleaf/o-error // https://www.npmjs.com/package/@overleaf/o-error
// have a look if we can do nice error messages. // have a look if we can do nice error messages.
@@ -89,21 +90,19 @@ const AuthenticationManager = {
}, },
authUserObj(error, user, query, password, callback) { authUserObj(error, user, query, password, callback) {
if ( process.env.ALLOW_EMAIL_LOGIN ) { if ( process.env.ALLOW_EMAIL_LOGIN && user && user.hashedPassword) {
// (external) email login console.log("email login for existing user " + query.email)
if (user && user.hashedPassword) {
console.log("email login for existing user")
// check passwd against local db // check passwd against local db
bcrypt.compare(password, user.hashedPassword, function (error, match) { bcrypt.compare(password, user.hashedPassword, function (error, match) {
if (match) { if (match) {
console.log("Fine") console.log("Local user password match")
AuthenticationManager.login(user, password, callback) AuthenticationManager.login(user, password, callback)
}
})
} else { } else {
console.log("Local user password mismatch, trying LDAP")
// check passwd against ldap // check passwd against ldap
AuthenticationManager.ldapAuth(query, password, AuthenticationManager.createIfNotExistAndLogin, callback, user) AuthenticationManager.ldapAuth(query, password, AuthenticationManager.createIfNotExistAndLogin, callback, user)
} }
})
} else { } else {
// No local passwd check user has to be in ldap and use ldap credentials // No local passwd check user has to be in ldap and use ldap credentials
AuthenticationManager.ldapAuth(query, password, AuthenticationManager.createIfNotExistAndLogin, callback, user) AuthenticationManager.ldapAuth(query, password, AuthenticationManager.createIfNotExistAndLogin, callback, user)
@@ -128,7 +127,7 @@ const AuthenticationManager = {
if (password == null) { if (password == null) {
return new InvalidPasswordError({ return new InvalidPasswordError({
message: 'password not set', message: 'password not set',
info: { code: 'not_set' } info: { code: 'not_set' },
}) })
} }
@@ -152,13 +151,13 @@ const AuthenticationManager = {
if (password.length < min) { if (password.length < min) {
return new InvalidPasswordError({ return new InvalidPasswordError({
message: 'password is too short', message: 'password is too short',
info: { code: 'too_short' } info: { code: 'too_short' },
}) })
} }
if (password.length > max) { if (password.length > max) {
return new InvalidPasswordError({ return new InvalidPasswordError({
message: 'password is too long', message: 'password is too long',
info: { code: 'too_long' } info: { code: 'too_long' },
}) })
} }
if ( if (
@@ -167,7 +166,7 @@ const AuthenticationManager = {
) { ) {
return new InvalidPasswordError({ return new InvalidPasswordError({
message: 'password contains an invalid character', message: 'password contains an invalid character',
info: { code: 'invalid_character' } info: { code: 'invalid_character' },
}) })
} }
return null return null
@@ -217,15 +216,15 @@ const AuthenticationManager = {
} }
db.users.updateOne( db.users.updateOne(
{ {
_id: ObjectId(user._id.toString()) _id: ObjectId(user._id.toString()),
}, },
{ {
$set: { $set: {
hashedPassword: hash hashedPassword: hash,
}, },
$unset: { $unset: {
password: true password: true,
} },
}, },
function (updateError, result) { function (updateError, result) {
if (updateError) { if (updateError) {
@@ -270,25 +269,39 @@ const AuthenticationManager = {
const client = new Client({ const client = new Client({
url: process.env.LDAP_SERVER, url: process.env.LDAP_SERVER,
}); });
//const bindDn = process.env.LDAP_BIND_USER
//const bindPassword = process.env.LDAP_BIND_PW const ldap_reader = process.env.LDAP_BIND_USER
const ldap_bd = process.env.LDAP_BINDDN const ldap_reader_pass = process.env.LDAP_BIND_PW
const ldap_base = process.env.LDAP_BASE const ldap_base = process.env.LDAP_BASE
const uid = query.email.split('@')[0]
const filterstr = '(&' + process.env.LDAP_GROUP_FILTER + '(uid=' + uid + '))' var mail = query.email
const userDn = 'uid=' + uid + ',' + ldap_bd; var uid = query.email.split('@')[0]
var mail = ""
var firstname = "" var firstname = ""
var lastname = "" var lastname = ""
var isAdmin = false var isAdmin = false
var userDn = ""
//replace all appearences of %u with uid and all %m with mail:
const replacerUid = new RegExp("%u", "g")
const replacerMail = new RegExp("%m","g")
const filterstr = process.env.LDAP_USER_FILTER.replace(replacerUid, ldapEscape.filter`${uid}`).replace(replacerMail, ldapEscape.filter`${mail}`) //replace all appearances
// check bind // check bind
try { try {
//await client.bind(bindDn, bindPassword); if(process.env.LDAP_BINDDN){ //try to bind directly with the user trying to log in
userDn = process.env.LDAP_BINDDN.replace(replacerUid,ldapEscape.filter`${uid}`).replace(replacerMail, ldapEscape.filter`${mail}`);
await client.bind(userDn,password); await client.bind(userDn,password);
}else{// use fixed bind user
await client.bind(ldap_reader, ldap_reader_pass);
}
} catch (ex) { } catch (ex) {
console.log("Could not bind user." + String(ex)) if(process.env.LDAP_BINDDN){
console.log("Could not bind user: " + userDn);
}else{
console.log("Could not bind LDAP reader: " + ldap_reader + " err: " + String(ex))
}
return callback(null, null) return callback(null, null)
} }
// get user data // get user data
try { try {
const {searchEntries, searchRef,} = await client.search(ldap_base, { const {searchEntries, searchRef,} = await client.search(ldap_base, {
@@ -296,30 +309,35 @@ const AuthenticationManager = {
filter: filterstr , filter: filterstr ,
}); });
await searchEntries await searchEntries
//console.log(JSON.stringify(searchEntries)) console.log(JSON.stringify(searchEntries))
if (searchEntries[0]) { if (searchEntries[0]) {
mail = searchEntries[0].mail mail = searchEntries[0].mail
uid = searchEntries[0].uid
firstname = searchEntries[0].givenName firstname = searchEntries[0].givenName
lastname = searchEntries[0].sn lastname = searchEntries[0].sn
//console.log("Found user: " + mail + " Name: " + firstname + " " + lastname) if(!process.env.LDAP_BINDDN){ //dn is already correctly assembled
userDn = searchEntries[0].dn
}
console.log("Found user: " + mail + " Name: " + firstname + " " + lastname + " DN: " + userDn)
} }
} catch (ex) { } catch (ex) {
console.log("An Error occured while getting user data during ldapsearch: " + String(ex)) console.log("An Error occured while getting user data during ldapsearch: " + String(ex))
await client.unbind(); await client.unbind();
return callback(null, null) return callback(null, null)
} }
try { try {
// if admin filter is set - only set admin for user in ldap group // if admin filter is set - only set admin for user in ldap group
// does not matter - admin is deactivated: managed through ldap // does not matter - admin is deactivated: managed through ldap
if (process.env.LDAP_ADMIN_GROUP_FILTER) { if (process.env.LDAP_ADMIN_GROUP_FILTER) {
const adminfilter = '(&' + process.env.LDAP_ADMIN_GROUP_FILTER + '(uid=' + uid + '))' const adminfilter = process.env.LDAP_ADMIN_GROUP_FILTER.replace(replacerUid, ldapEscape.filter`${uid}`).replace(replacerMail, ldapEscape.filter`${mail}`)
adminEntry = await client.search(ldap_base, { adminEntry = await client.search(ldap_base, {
scope: 'sub', scope: 'sub',
filter: adminfilter, filter: adminfilter,
}); });
await adminEntry; await adminEntry;
//console.log("Admin Search response:" + JSON.stringify(adminEntry.searchEntries)) //console.log("Admin Search response:" + JSON.stringify(adminEntry.searchEntries))
if (adminEntry.searchEntries[0].mail) { if (adminEntry.searchEntries[0]) {
console.log("is Admin") console.log("is Admin")
isAdmin=true; isAdmin=true;
} }
@@ -330,10 +348,21 @@ const AuthenticationManager = {
} finally { } finally {
await client.unbind(); await client.unbind();
} }
if (mail == "") { if (mail == "" || userDn == "") {
console.log("Mail not set - exit. This should not happen - please set mail-entry in ldap.") console.log("Mail / userDn not set - exit. This should not happen - please set mail-entry in ldap.")
return callback(null, null) return callback(null, null)
} }
if(!process.env.BINDDN){//since we used a fixed bind user to obtain the correct userDn we need to bind again to authenticate
try {
await client.bind(userDn, password);
} catch (ex) {
console.log("Could not bind User: " + userDn + " err: " + String(ex))
return callback(null, null)
} finally{
await client.unbind()
}
}
//console.log("Logging in user: " + mail + " Name: " + firstname + " " + lastname + " isAdmin: " + String(isAdmin)) //console.log("Logging in user: " + mail + " Name: " + firstname + " " + lastname + " isAdmin: " + String(isAdmin))
// we are authenticated now let's set the query to the correct mail from ldap // we are authenticated now let's set the query to the correct mail from ldap
query.email = mail query.email = mail
@@ -354,7 +383,7 @@ const AuthenticationManager = {
AuthenticationManager.promises = { AuthenticationManager.promises = {
authenticate: util.promisify(AuthenticationManager.authenticate), authenticate: util.promisify(AuthenticationManager.authenticate),
hashPassword: util.promisify(AuthenticationManager.hashPassword), hashPassword: util.promisify(AuthenticationManager.hashPassword),
setUserPassword: util.promisify(AuthenticationManager.setUserPassword) setUserPassword: util.promisify(AuthenticationManager.setUserPassword),
} }
module.exports = AuthenticationManager module.exports = AuthenticationManager

View File

@@ -14,19 +14,20 @@
*/ */
let ContactsController let ContactsController
const AuthenticationController = require('../Authentication/AuthenticationController') const AuthenticationController = require('../Authentication/AuthenticationController')
const SessionManager = require('../Authentication/SessionManager')
const ContactManager = require('./ContactManager') const ContactManager = require('./ContactManager')
const UserGetter = require('../User/UserGetter') const UserGetter = require('../User/UserGetter')
const logger = require('logger-sharelatex') const logger = require('@overleaf/logger')
const Modules = require('../../infrastructure/Modules') const Modules = require('../../infrastructure/Modules')
const { Client } = require('ldapts'); const { Client } = require('ldapts');
module.exports = ContactsController = { module.exports = ContactsController = {
getContacts(req, res, next) { getContacts(req, res, next) {
const user_id = AuthenticationController.getLoggedInUserId(req) const user_id = SessionManager.getLoggedInUserId(req.session)
return ContactManager.getContactIds(user_id, { limit: 50 }, function( return ContactManager.getContactIds(
error, user_id,
contact_ids { limit: 50 },
) { function (error, contact_ids) {
if (error != null) { if (error != null) {
return next(error) return next(error)
} }
@@ -36,7 +37,7 @@ module.exports = ContactsController = {
email: 1, email: 1,
first_name: 1, first_name: 1,
last_name: 1, last_name: 1,
holdingAccount: 1 holdingAccount: 1,
}, },
function (error, contacts) { function (error, contacts) {
if (error != null) { if (error != null) {
@@ -49,7 +50,6 @@ module.exports = ContactsController = {
const contact_id = contact_ids[i] const contact_id = contact_ids[i]
positions[contact_id] = i positions[contact_id] = i
} }
contacts.sort( contacts.sort(
(a, b) => (a, b) =>
positions[a._id != null ? a._id.toString() : undefined] - positions[a._id != null ? a._id.toString() : undefined] -
@@ -61,6 +61,7 @@ module.exports = ContactsController = {
ContactsController.getLdapContacts(contacts).then((ldapcontacts) => { ContactsController.getLdapContacts(contacts).then((ldapcontacts) => {
contacts.push(ldapcontacts) contacts.push(ldapcontacts)
contacts = contacts.map(ContactsController._formatContact) contacts = contacts.map(ContactsController._formatContact)
return Modules.hooks.fire('getContacts', user_id, contacts, function( return Modules.hooks.fire('getContacts', user_id, contacts, function(
error, error,
additional_contacts additional_contacts
@@ -80,17 +81,27 @@ module.exports = ContactsController = {
}) })
}, },
async getLdapContacts(contacts) { async getLdapContacts(contacts) {
if (! process.env.LDAP_CONTACTS) { if (process.env.LDAP_CONTACTS === undefined || !(process.env.LDAP_CONTACTS.toLowerCase() === 'true')) {
return contacts return contacts
} }
const client = new Client({ const client = new Client({
url: process.env.LDAP_SERVER, url: process.env.LDAP_SERVER,
}); });
// if we need a ldap user try to bind
if (process.env.LDAP_BIND_USER) {
try {
await client.bind(process.env.LDAP_BIND_USER, process.env.LDAP_BIND_PW);
} catch (ex) {
console.log("Could not bind LDAP reader user: " + String(ex) )
}
}
const ldap_base = process.env.LDAP_BASE const ldap_base = process.env.LDAP_BASE
// get user data // get user data
try { try {
// if you need an client.bind do it here. // if you need an client.bind do it here.
const {searchEntries,searchReferences,} = await client.search(ldap_base, {scope: 'sub',filter: process.env.LDAP_GROUP_FILTER ,}); const {searchEntries,searchReferences,} = await client.search(ldap_base, {scope: 'sub',filter: process.env.LDAP_CONTACT_FILTER ,});
await searchEntries; await searchEntries;
for (var i = 0; i < searchEntries.length; i++) { for (var i = 0; i < searchEntries.length; i++) {
var entry = new Map() var entry = new Map()
@@ -122,7 +133,7 @@ module.exports = ContactsController = {
email: contact.email || '', email: contact.email || '',
first_name: contact.first_name || '', first_name: contact.first_name || '',
last_name: contact.last_name || '', last_name: contact.last_name || '',
type: 'user' type: 'user',
}
} }
},
} }

View File

@@ -0,0 +1,79 @@
extends ../layout
block content
.content.content-alt
.container
.row
.col-xs-12
.card(ng-controller="RegisterUsersController")
.page-header
h1 Admin Panel
tabset(ng-cloak)
tab(heading="System Messages")
each message in systemMessages
.alert.alert-info.row-spaced(ng-non-bindable) #{message.content}
hr
form(method='post', action='/admin/messages')
input(name="_csrf", type="hidden", value=csrfToken)
.form-group
label(for="content")
input.form-control(name="content", type="text", placeholder="Message...", required)
button.btn.btn-primary(type="submit") Post Message
hr
form(method='post', action='/admin/messages/clear')
input(name="_csrf", type="hidden", value=csrfToken)
button.btn.btn-danger(type="submit") Clear all messages
tab(heading="Register non LDAP User")
form.form
.row
.col-md-4.col-xs-8
input.form-control(
name="email",
type="text",
placeholder="jane@example.com, joe@example.com",
ng-model="inputs.emails",
on-enter="registerUsers()"
)
.col-md-8.col-xs-4
button.btn.btn-primary(ng-click="registerUsers()") #{translate("register")}
.row-spaced(ng-show="error").ng-cloak.text-danger
p Sorry, an error occured
.row-spaced(ng-show="users.length > 0").ng-cloak.text-success
p We've sent out welcome emails to the registered users.
p You can also manually send them URLs below to allow them to reset their password and log in for the first time.
p (Password reset tokens will expire after one week and the user will need registering again).
hr(ng-show="users.length > 0").ng-cloak
table(ng-show="users.length > 0").table.table-striped.ng-cloak
tr
th #{translate("email")}
th Set Password Url
tr(ng-repeat="user in users")
td {{ user.email }}
td(style="word-break: break-all;") {{ user.setNewPasswordUrl }}
tab(heading="Open/Close Editor" bookmarkable-tab="open-close-editor")
if hasFeature('saas')
| The "Open/Close Editor" feature is not available in SAAS.
else
.row-spaced
form(method='post',action='/admin/closeEditor')
input(name="_csrf", type="hidden", value=csrfToken)
button.btn.btn-danger(type="submit") Close Editor
p.small Will stop anyone opening the editor. Will NOT disconnect already connected users.
.row-spaced
form(method='post',action='/admin/disconnectAllUsers')
input(name="_csrf", type="hidden", value=csrfToken)
button.btn.btn-danger(type="submit") Disconnect all users
p.small Will force disconnect all users with the editor open. Make sure to close the editor first to avoid them reconnecting.
.row-spaced
form(method='post',action='/admin/openEditor')
input(name="_csrf", type="hidden", value=csrfToken)
button.btn.btn-danger(type="submit") Reopen Editor
p.small Will reopen the editor after closing.