Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0e70e4f1f | ||
|
|
682d15d62e | ||
|
|
46cdf93fd5 | ||
|
|
b42604f22c | ||
|
|
8995a35f70 | ||
|
|
489b158b50 | ||
|
|
da9a27b34d | ||
|
|
c2c20cc861 | ||
|
|
41c172b587 | ||
|
|
39ef130adc | ||
|
|
c40b47135c | ||
|
|
ba73f282ec | ||
|
|
9cde93be05 | ||
|
|
19d3b30c93 | ||
|
|
256c887d8f | ||
|
|
ca0dc33cb7 | ||
|
|
df51a6dbe0 | ||
|
|
6263029df9 | ||
|
|
6992f2c8ed | ||
|
|
5e62caedf6 | ||
|
|
2688db1d0c | ||
|
|
547ce9a744 | ||
|
|
2b982babbb | ||
|
|
025d5fba97 | ||
|
|
4617bf690b | ||
|
|
f25cb05c8b | ||
|
|
56be9a450c | ||
|
|
a66affb1e1 | ||
|
|
57da632558 | ||
|
|
d13e6a475d | ||
|
|
90e7681c35 | ||
|
|
831b810e81 | ||
|
|
34614356c9 | ||
|
|
fcebf5f33d | ||
|
|
eca1d9881e | ||
|
|
53a4ba6b4f | ||
|
|
fd4f45354b | ||
|
|
a8d72465d9 | ||
|
|
2b58ad96e3 | ||
|
|
c427f472db | ||
|
|
0677da6a81 | ||
|
|
204b064bb7 | ||
|
|
f97d76cca6 | ||
|
|
a67931a241 | ||
|
|
8845734b91 | ||
|
|
fb6f654738 | ||
|
|
4da7bac9f9 | ||
|
|
74426fe812 | ||
|
|
1e60e141f3 |
1
.env
1
.env
@@ -4,3 +4,4 @@ MYMAIL=MYEMAIL@MYDOMAIN.TLD
|
||||
MYDATA=/data
|
||||
LOGIN_TEXT=username
|
||||
COLLAB_TEXT=Direct share with collaborators is enabled only for activated users!
|
||||
ADMIN_IS_SYSADMIN=false
|
||||
|
||||
1
Makefile
1
Makefile
@@ -3,6 +3,7 @@ include .env
|
||||
build:
|
||||
docker build --build-arg login_text="${LOGIN_TEXT}" \
|
||||
--build-arg collab_text="${COLLAB_TEXT}" \
|
||||
--build-arg admin_is_sysadmin="${ADMIN_IS_SYSADMIN}" \
|
||||
-t "ldap-overleaf-sl" ldap-overleaf-sl
|
||||
|
||||
clean: check_clean
|
||||
|
||||
74
README.md
74
README.md
@@ -9,9 +9,14 @@ The inital idea for this implementation was taken from
|
||||
|
||||
|
||||
### 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
|
||||
the uid and credentials of the user which tries to login.
|
||||
If you upgrade from an older commit:
|
||||
**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.
|
||||
@@ -56,55 +61,67 @@ MYDATA=/data
|
||||
- sharelatex: all projects, tmp files, user files templates and ...
|
||||
- letsencrypt: https certificates
|
||||
|
||||
*MYDOMAIN* is the FQDN for sharelatex and traefik (letsencrypt) <br/>
|
||||
*MYDOMAIN*:8443 Traefik Dashboard - Login uses traefik/user.htpasswd : user:admin pass:adminPass change this (e.g. generate a password with htpasswd)
|
||||
*MYDOMAIN* is the FQDN for sharelatex and traefik (letsencrypt) or certbot <br/>
|
||||
*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
|
||||
|
||||
```
|
||||
LOGIN_TEXT=username
|
||||
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/>
|
||||
*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
|
||||
|
||||
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_BASE: 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
|
||||
# you have to set a group filter a minimal groupfilter would be: '(objectClass=person)'
|
||||
LDAP_GROUP_FILTER: '(memberof=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)'
|
||||
# If LDAP_BINDDN is set, the ldap bind happens directly by using the provided DN
|
||||
# All occurrences of `%u` get replaced by the entered uid.
|
||||
# All occurrences of `%m`get replaced by the entered mail.
|
||||
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.
|
||||
# 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.
|
||||
# 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)'
|
||||
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
|
||||
|
||||
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.
|
||||
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:
|
||||
```
|
||||
LDAP_CONTACT_FILTER: (objectClass=person)
|
||||
LDAP_CONTACTS: 'true'
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
## Installation, Usage and Inital startup
|
||||
@@ -126,15 +143,34 @@ make
|
||||
```
|
||||
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):
|
||||
```
|
||||
export NUMINSTANCES=1
|
||||
docker-compose up -d --scale sharelaatex=NUMINSTANCES
|
||||
docker-compose -f docker-compose.traefik.yml up -d --scale sharelatex=$NUMINSTANCES
|
||||
```
|
||||
|
||||
*Known Issue:*
|
||||
- Works up to sharelatex 2.3.1. After that at least issue #5 is introduced.
|
||||
|
||||
### Using Certbot
|
||||
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
150
docker-compose.certbot.yml
Normal 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;
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ services:
|
||||
- "--ping"
|
||||
- "--providers.docker.network=web"
|
||||
- "--providers.docker.exposedbydefault=false"
|
||||
- "--providers.file.filename=/dynamic_conf.yml"
|
||||
- "--entrypoints.web.address=:80"
|
||||
- "--entrypoints.web-secure.address=:443"
|
||||
- "--entrypoints.web-admin.address=:8443"
|
||||
@@ -48,6 +49,9 @@ services:
|
||||
- "traefik.http.routers.dashboard.middlewares=auth"
|
||||
- "traefik.http.middlewares.auth.basicauth.usersfile=/users.htpasswd"
|
||||
- "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:
|
||||
driver: "json-file"
|
||||
@@ -76,27 +80,26 @@ services:
|
||||
links:
|
||||
- mongo
|
||||
- redis
|
||||
#- simple-certbot
|
||||
volumes:
|
||||
- ${MYDATA}/sharelatex:/var/lib/sharelatex
|
||||
- ${MYDATA}/letsencrypt:/etc/letsencrypt:ro
|
||||
# - ${MYDATA}/letsencrypt/live/${MYDOMAIN}/:/etc/letsencrypt/certs/domain
|
||||
labels:
|
||||
- "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.routers.sharel.middlewares=redirect-to-https@docker"
|
||||
# handle https traffic
|
||||
- "traefik.http.routers.sharel-secured.rule=Host(`${MYDOMAIN}`)"
|
||||
- "traefik.http.routers.sharel-secured.tls=true"
|
||||
- "traefik.http.routers.sharel-secured.tls.certresolver=myhttpchallenge"
|
||||
- "traefik.http.routers.sharel-secured.entrypoints=web-secure"
|
||||
- "traefik.http.routers.proxy-https.entrypoints=web-secure"
|
||||
- "traefik.http.routers.proxy-https.rule=Host(`${MYDOMAIN}`)"
|
||||
- "traefik.http.middlewares.sharel-secured.forwardauth.trustForwardHeader=true"
|
||||
# Docker loadbalance
|
||||
- "traefik.http.services.sharel.loadbalancer.server.port=80"
|
||||
- "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.name=io"
|
||||
- "traefik.http.services.sharel.loadbalancer.sticky.cookie.httponly=true"
|
||||
@@ -135,11 +138,19 @@ services:
|
||||
|
||||
LDAP_SERVER: ldaps://LDAPSERVER:636
|
||||
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
|
||||
# LDAP_GROUP_FILTER: '(memberof=cn=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)'
|
||||
LDAP_GROUP_FILTER: '(memberof=cn=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)'
|
||||
### There are to ways get users from the ldap server
|
||||
|
||||
## 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.
|
||||
# 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)'
|
||||
ALLOW_EMAIL_LOGIN: 'true'
|
||||
|
||||
# All users in the LDAP_GROUP_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
|
||||
# adapt this in the function getLdapContacts() in ContactsController.js (lines 82 - 107)
|
||||
# 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
|
||||
@@ -210,26 +220,6 @@ services:
|
||||
networks:
|
||||
- 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:
|
||||
web:
|
||||
external: true
|
||||
@@ -1,23 +1,26 @@
|
||||
FROM sharelatex/sharelatex:2.5.2
|
||||
FROM sharelatex/sharelatex:3.3.2
|
||||
# FROM sharelatex/sharelatex:latest
|
||||
# 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 version="0.1"
|
||||
|
||||
# passed from .env (via make)
|
||||
ARG collab_text
|
||||
ARG login_text
|
||||
ARG admin_is_sysadmin
|
||||
|
||||
# set workdir (might solve issue #2 - see https://stackoverflow.com/questions/57534295/)
|
||||
WORKDIR /var/www/sharelatex/web
|
||||
WORKDIR /overleaf/services/web
|
||||
|
||||
# install latest npm
|
||||
RUN npm install -g npm
|
||||
# clean cache (might solve issue #2)
|
||||
#RUN npm cache clean --force
|
||||
RUN npm install ldap-escape
|
||||
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
|
||||
|
||||
# This variant of updateing texlive does not work
|
||||
@@ -25,48 +28,54 @@ RUN npm install ldapts
|
||||
# try this one:
|
||||
RUN apt-get update
|
||||
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
|
||||
COPY sharelatex/AuthenticationManager.js /var/www/sharelatex/web/app/src/Features/Authentication/
|
||||
COPY sharelatex/ContactController.js /var/www/sharelatex/web/app/src/Features/Contacts/
|
||||
COPY sharelatex/AuthenticationManager.js /overleaf/services/web/app/src/Features/Authentication/
|
||||
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)
|
||||
# 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 '/email@example.com/{n;N;N;d}' /var/www/sharelatex/web/app/views/user/login.pug
|
||||
RUN sed -iE "s/email@example.com/${login_text:-user}/g" /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}' /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" /overleaf/services/web/app/views/user/login.pug
|
||||
|
||||
# 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 )
|
||||
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).
|
||||
COPY sharelatex/settings.pug /var/www/sharelatex/web/app/views/user/
|
||||
COPY sharelatex/navbar.pug /var/www/sharelatex/web/app/views/layout/
|
||||
COPY sharelatex/settings.pug /overleaf/services/web/app/views/user/
|
||||
COPY sharelatex/navbar.pug /overleaf/services/web/app/views/layout/
|
||||
|
||||
# Non LDAP User Registration for Admins
|
||||
COPY sharelatex/admin-index.pug /var/www/sharelatex/web/app/views/admin/index.pug
|
||||
RUN rm /var/www/sharelatex/web/app/views/admin/register.pug
|
||||
COPY sharelatex/admin-index.pug /overleaf/services/web/app/views/admin/index.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)
|
||||
#RUN rm /var/www/sharelatex/web/app/views/project/editor/review-panel.pug
|
||||
RUN touch /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 /overleaf/services/web/app/views/project/editor/review-panel.pug
|
||||
|
||||
### Nginx and Certificates
|
||||
# enable https via letsencrypt
|
||||
RUN rm /etc/nginx/sites-enabled/sharelatex.conf
|
||||
COPY nginx/sharelatex.conf /etc/nginx/sites-enabled/sharelatex.conf
|
||||
#RUN rm /etc/nginx/sites-enabled/sharelatex.conf
|
||||
#COPY nginx/sharelatex.conf /etc/nginx/sites-enabled/sharelatex.conf
|
||||
|
||||
# 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/certbot/ssl-dhparams.pem -O /etc/nginx/ssl-dhparams.pem
|
||||
#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
|
||||
|
||||
# reload nginx via cron for reneweing https certificates automatically
|
||||
COPY nginx/nginx-reload.sh /etc/cron.weekly/
|
||||
RUN chmod 0744 /etc/cron.weekly/nginx-reload.sh
|
||||
#COPY nginx/nginx-reload.sh /etc/cron.weekly/
|
||||
#RUN chmod 0744 /etc/cron.weekly/nginx-reload.sh
|
||||
|
||||
## extract certificates from acme.json?
|
||||
# COPY nginx/nginx-cert.sh /etc/cron.weekly/
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
const Settings = require('settings-sharelatex')
|
||||
const Settings = require('@overleaf/settings')
|
||||
const { User } = require('../../models/User')
|
||||
const { db, ObjectId } = require('../../infrastructure/mongodb')
|
||||
const bcrypt = require('bcrypt')
|
||||
const EmailHelper = require('../Helpers/EmailHelper')
|
||||
const {
|
||||
InvalidEmailError,
|
||||
InvalidPasswordError
|
||||
InvalidPasswordError,
|
||||
} = require('./AuthenticationErrors')
|
||||
const util = require('util')
|
||||
|
||||
const { Client } = require('ldapts');
|
||||
const ldapEscape = require('ldap-escape');
|
||||
|
||||
// https://www.npmjs.com/package/@overleaf/o-error
|
||||
// have a look if we can do nice error messages.
|
||||
@@ -89,21 +90,19 @@ const AuthenticationManager = {
|
||||
},
|
||||
|
||||
authUserObj(error, user, query, password, callback) {
|
||||
if ( process.env.ALLOW_EMAIL_LOGIN ) {
|
||||
// (external) email login
|
||||
if (user && user.hashedPassword) {
|
||||
console.log("email login for existing user")
|
||||
if ( process.env.ALLOW_EMAIL_LOGIN && user && user.hashedPassword) {
|
||||
console.log("email login for existing user " + query.email)
|
||||
// check passwd against local db
|
||||
bcrypt.compare(password, user.hashedPassword, function (error, match) {
|
||||
if (match) {
|
||||
console.log("Fine")
|
||||
console.log("Local user password match")
|
||||
AuthenticationManager.login(user, password, callback)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
console.log("Local user password mismatch, trying LDAP")
|
||||
// check passwd against ldap
|
||||
AuthenticationManager.ldapAuth(query, password, AuthenticationManager.createIfNotExistAndLogin, callback, user)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// No local passwd check user has to be in ldap and use ldap credentials
|
||||
AuthenticationManager.ldapAuth(query, password, AuthenticationManager.createIfNotExistAndLogin, callback, user)
|
||||
@@ -128,7 +127,7 @@ const AuthenticationManager = {
|
||||
if (password == null) {
|
||||
return new InvalidPasswordError({
|
||||
message: 'password not set',
|
||||
info: { code: 'not_set' }
|
||||
info: { code: 'not_set' },
|
||||
})
|
||||
}
|
||||
|
||||
@@ -152,13 +151,13 @@ const AuthenticationManager = {
|
||||
if (password.length < min) {
|
||||
return new InvalidPasswordError({
|
||||
message: 'password is too short',
|
||||
info: { code: 'too_short' }
|
||||
info: { code: 'too_short' },
|
||||
})
|
||||
}
|
||||
if (password.length > max) {
|
||||
return new InvalidPasswordError({
|
||||
message: 'password is too long',
|
||||
info: { code: 'too_long' }
|
||||
info: { code: 'too_long' },
|
||||
})
|
||||
}
|
||||
if (
|
||||
@@ -167,7 +166,7 @@ const AuthenticationManager = {
|
||||
) {
|
||||
return new InvalidPasswordError({
|
||||
message: 'password contains an invalid character',
|
||||
info: { code: 'invalid_character' }
|
||||
info: { code: 'invalid_character' },
|
||||
})
|
||||
}
|
||||
return null
|
||||
@@ -217,15 +216,15 @@ const AuthenticationManager = {
|
||||
}
|
||||
db.users.updateOne(
|
||||
{
|
||||
_id: ObjectId(user._id.toString())
|
||||
_id: ObjectId(user._id.toString()),
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
hashedPassword: hash
|
||||
hashedPassword: hash,
|
||||
},
|
||||
$unset: {
|
||||
password: true
|
||||
}
|
||||
password: true,
|
||||
},
|
||||
},
|
||||
function (updateError, result) {
|
||||
if (updateError) {
|
||||
@@ -270,25 +269,39 @@ const AuthenticationManager = {
|
||||
const client = new Client({
|
||||
url: process.env.LDAP_SERVER,
|
||||
});
|
||||
//const bindDn = process.env.LDAP_BIND_USER
|
||||
//const bindPassword = process.env.LDAP_BIND_PW
|
||||
const ldap_bd = process.env.LDAP_BINDDN
|
||||
|
||||
const ldap_reader = process.env.LDAP_BIND_USER
|
||||
const ldap_reader_pass = process.env.LDAP_BIND_PW
|
||||
const ldap_base = process.env.LDAP_BASE
|
||||
const uid = query.email.split('@')[0]
|
||||
const filterstr = '(&' + process.env.LDAP_GROUP_FILTER + '(uid=' + uid + '))'
|
||||
const userDn = 'uid=' + uid + ',' + ldap_bd;
|
||||
var mail = ""
|
||||
|
||||
var mail = query.email
|
||||
var uid = query.email.split('@')[0]
|
||||
var firstname = ""
|
||||
var lastname = ""
|
||||
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
|
||||
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);
|
||||
}else{// use fixed bind user
|
||||
await client.bind(ldap_reader, ldap_reader_pass);
|
||||
}
|
||||
} 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)
|
||||
}
|
||||
|
||||
// get user data
|
||||
try {
|
||||
const {searchEntries, searchRef,} = await client.search(ldap_base, {
|
||||
@@ -296,30 +309,35 @@ const AuthenticationManager = {
|
||||
filter: filterstr ,
|
||||
});
|
||||
await searchEntries
|
||||
//console.log(JSON.stringify(searchEntries))
|
||||
console.log(JSON.stringify(searchEntries))
|
||||
if (searchEntries[0]) {
|
||||
mail = searchEntries[0].mail
|
||||
uid = searchEntries[0].uid
|
||||
firstname = searchEntries[0].givenName
|
||||
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) {
|
||||
console.log("An Error occured while getting user data during ldapsearch: " + String(ex))
|
||||
await client.unbind();
|
||||
return callback(null, null)
|
||||
}
|
||||
|
||||
try {
|
||||
// if admin filter is set - only set admin for user in ldap group
|
||||
// does not matter - admin is deactivated: managed through ldap
|
||||
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, {
|
||||
scope: 'sub',
|
||||
filter: adminfilter,
|
||||
});
|
||||
await adminEntry;
|
||||
//console.log("Admin Search response:" + JSON.stringify(adminEntry.searchEntries))
|
||||
if (adminEntry.searchEntries[0].mail) {
|
||||
if (adminEntry.searchEntries[0]) {
|
||||
console.log("is Admin")
|
||||
isAdmin=true;
|
||||
}
|
||||
@@ -330,10 +348,21 @@ const AuthenticationManager = {
|
||||
} finally {
|
||||
await client.unbind();
|
||||
}
|
||||
if (mail == "") {
|
||||
console.log("Mail not set - exit. This should not happen - please set mail-entry in ldap.")
|
||||
if (mail == "" || userDn == "") {
|
||||
console.log("Mail / userDn not set - exit. This should not happen - please set mail-entry in ldap.")
|
||||
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))
|
||||
// we are authenticated now let's set the query to the correct mail from ldap
|
||||
query.email = mail
|
||||
@@ -354,7 +383,7 @@ const AuthenticationManager = {
|
||||
AuthenticationManager.promises = {
|
||||
authenticate: util.promisify(AuthenticationManager.authenticate),
|
||||
hashPassword: util.promisify(AuthenticationManager.hashPassword),
|
||||
setUserPassword: util.promisify(AuthenticationManager.setUserPassword)
|
||||
setUserPassword: util.promisify(AuthenticationManager.setUserPassword),
|
||||
}
|
||||
|
||||
module.exports = AuthenticationManager
|
||||
|
||||
@@ -14,19 +14,20 @@
|
||||
*/
|
||||
let ContactsController
|
||||
const AuthenticationController = require('../Authentication/AuthenticationController')
|
||||
const SessionManager = require('../Authentication/SessionManager')
|
||||
const ContactManager = require('./ContactManager')
|
||||
const UserGetter = require('../User/UserGetter')
|
||||
const logger = require('logger-sharelatex')
|
||||
const logger = require('@overleaf/logger')
|
||||
const Modules = require('../../infrastructure/Modules')
|
||||
const { Client } = require('ldapts');
|
||||
|
||||
module.exports = ContactsController = {
|
||||
getContacts(req, res, next) {
|
||||
const user_id = AuthenticationController.getLoggedInUserId(req)
|
||||
return ContactManager.getContactIds(user_id, { limit: 50 }, function(
|
||||
error,
|
||||
contact_ids
|
||||
) {
|
||||
const user_id = SessionManager.getLoggedInUserId(req.session)
|
||||
return ContactManager.getContactIds(
|
||||
user_id,
|
||||
{ limit: 50 },
|
||||
function (error, contact_ids) {
|
||||
if (error != null) {
|
||||
return next(error)
|
||||
}
|
||||
@@ -36,7 +37,7 @@ module.exports = ContactsController = {
|
||||
email: 1,
|
||||
first_name: 1,
|
||||
last_name: 1,
|
||||
holdingAccount: 1
|
||||
holdingAccount: 1,
|
||||
},
|
||||
function (error, contacts) {
|
||||
if (error != null) {
|
||||
@@ -49,7 +50,6 @@ module.exports = ContactsController = {
|
||||
const contact_id = contact_ids[i]
|
||||
positions[contact_id] = i
|
||||
}
|
||||
|
||||
contacts.sort(
|
||||
(a, b) =>
|
||||
positions[a._id != null ? a._id.toString() : undefined] -
|
||||
@@ -61,6 +61,7 @@ module.exports = ContactsController = {
|
||||
ContactsController.getLdapContacts(contacts).then((ldapcontacts) => {
|
||||
contacts.push(ldapcontacts)
|
||||
contacts = contacts.map(ContactsController._formatContact)
|
||||
|
||||
return Modules.hooks.fire('getContacts', user_id, contacts, function(
|
||||
error,
|
||||
additional_contacts
|
||||
@@ -80,17 +81,27 @@ module.exports = ContactsController = {
|
||||
})
|
||||
},
|
||||
async getLdapContacts(contacts) {
|
||||
if (! process.env.LDAP_CONTACTS) {
|
||||
if (process.env.LDAP_CONTACTS === undefined || !(process.env.LDAP_CONTACTS.toLowerCase() === 'true')) {
|
||||
return contacts
|
||||
}
|
||||
const client = new Client({
|
||||
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
|
||||
// get user data
|
||||
try {
|
||||
// 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;
|
||||
for (var i = 0; i < searchEntries.length; i++) {
|
||||
var entry = new Map()
|
||||
@@ -122,7 +133,7 @@ module.exports = ContactsController = {
|
||||
email: contact.email || '',
|
||||
first_name: contact.first_name || '',
|
||||
last_name: contact.last_name || '',
|
||||
type: 'user'
|
||||
}
|
||||
type: 'user',
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
79
ldap-overleaf-sl/sharelatex/admin-sysadmin.pug
Normal file
79
ldap-overleaf-sl/sharelatex/admin-sysadmin.pug
Normal 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.
|
||||
|
||||
Reference in New Issue
Block a user