From e66b6238b89f3e11f93e80cc345e4104b741e6b5 Mon Sep 17 00:00:00 2001 From: Emil Date: Fri, 13 Mar 2020 11:26:32 +0300 Subject: [PATCH 01/35] :pencil: Update README Signed-off-by: Emil --- .gitignore | 1 + README.md | 2 +- taiga_contrib_ldap_auth_ext/connector.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index b608332..0c63844 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ __pycache__ *.eggs *.egg-info dist +.idea \ No newline at end of file diff --git a/README.md b/README.md index 745589e..a8a7e61 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Add the following to `settings/local.py`: INSTALLED_APPS += ["taiga_contrib_ldap_auth_ext"] # TODO https://github.com/Monogramm/taiga-contrib-ldap-auth-ext/issues/16 -LDAP_SERVER = 'ldap://ldap.example.com' +LDAP_SERVER = ['ldap://ldap.example1.com','ldap://ldap.example2.com'] LDAP_PORT = 389 # Flag to enable LDAP with STARTTLS before bind diff --git a/taiga_contrib_ldap_auth_ext/connector.py b/taiga_contrib_ldap_auth_ext/connector.py index 9f53ce6..1c79f02 100644 --- a/taiga_contrib_ldap_auth_ext/connector.py +++ b/taiga_contrib_ldap_auth_ext/connector.py @@ -145,4 +145,4 @@ def login(username: str, password: str) -> tuple: # LDAP binding successful, but some values might have changed, or # this is the user's first login, so return them - return (username, email, full_name) + return username, email, full_name From 29a27c7b8458a4030ccfa86fced7c3a2d4bf47de Mon Sep 17 00:00:00 2001 From: Emil Date: Fri, 13 Mar 2020 11:45:00 +0300 Subject: [PATCH 02/35] :sparkles: Add implementation with allow multiplies Signed-off-by: Emil --- taiga_contrib_ldap_auth_ext/connector.py | 136 ++++++++++++----------- 1 file changed, 74 insertions(+), 62 deletions(-) diff --git a/taiga_contrib_ldap_auth_ext/connector.py b/taiga_contrib_ldap_auth_ext/connector.py index 1c79f02..9191cfa 100644 --- a/taiga_contrib_ldap_auth_ext/connector.py +++ b/taiga_contrib_ldap_auth_ext/connector.py @@ -11,7 +11,8 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from ldap3 import Server, Connection, AUTO_BIND_NO_TLS, AUTO_BIND_TLS_BEFORE_BIND, ANONYMOUS, SIMPLE, SYNC, SUBTREE, NONE +from ldap3 import Server, Connection, AUTO_BIND_NO_TLS, AUTO_BIND_TLS_BEFORE_BIND, ANONYMOUS, SIMPLE, SYNC, SUBTREE, \ + NONE from django.conf import settings from taiga.base.connectors.exceptions import ConnectorBaseException @@ -30,7 +31,7 @@ class LDAPUserLoginError(LDAPError): # TODO https://github.com/Monogramm/taiga-contrib-ldap-auth-ext/issues/16 -SERVER = getattr(settings, "LDAP_SERVER", "localhost") +SERVERS = getattr(settings, "LDAP_SERVER", "localhost") PORT = getattr(settings, "LDAP_PORT", "389") SEARCH_BASE = getattr(settings, "LDAP_SEARCH_BASE", "") @@ -65,17 +66,21 @@ def login(username: str, password: str) -> tuple: if TLS_CERTS: tls = TLS_CERTS - # connect to the LDAP server - if SERVER.lower().startswith("ldaps://"): - use_ssl = True - else: - use_ssl = False - try: - server = Server(SERVER, port=PORT, get_info=NONE, - use_ssl=use_ssl, tls=tls) - except Exception as e: - error = "Error connecting to LDAP server: %s" % e - raise LDAPConnectionError({"error_message": error}) + server_ldap_list = [] + + # Connect to the LDAP servers + for ser in SERVERS: + if ser.lower().startswith("ldaps://"): + use_ssl = True + else: + use_ssl = False + try: + server = Server(ser, port=PORT, get_info=NONE, + use_ssl=use_ssl, tls=tls) + server_ldap_list.append(server) + except Exception as e: + error = "Error connecting to LDAP server: %s" % e + raise LDAPConnectionError({"error_message": error}) # authenticate as service if credentials provided, anonymously otherwise if BIND_DN is not None and BIND_DN != '': @@ -91,58 +96,65 @@ def login(username: str, password: str) -> tuple: if START_TLS: auto_bind = AUTO_BIND_TLS_BEFORE_BIND - try: - c = Connection(server, auto_bind=auto_bind, client_strategy=SYNC, check_names=True, - user=service_user, password=service_pass, authentication=service_auth) - except Exception as e: - error = "Error connecting to LDAP server: %s" % e - raise LDAPConnectionError({"error_message": error}) + list_with_connections = [] + + for ldap_value in server_ldap_list: + try: + c = Connection(ldap_value, auto_bind=auto_bind, client_strategy=SYNC, check_names=True, + user=service_user, password=service_pass, authentication=service_auth) + list_with_connections.append(c) + except Exception as e: + error = "Error connecting to LDAP server: %s" % e + raise LDAPConnectionError({"error_message": error}) # search for user-provided login search_filter = '(|(%s=%s)(%s=%s))' % ( USERNAME_ATTRIBUTE, username, EMAIL_ATTRIBUTE, username) if SEARCH_FILTER_ADDITIONAL: search_filter = '(&%s%s)' % (search_filter, SEARCH_FILTER_ADDITIONAL) - try: - c.search(search_base=SEARCH_BASE, - search_filter=search_filter, - search_scope=SUBTREE, - attributes=[USERNAME_ATTRIBUTE, - EMAIL_ATTRIBUTE, FULL_NAME_ATTRIBUTE], - paged_size=5) - except Exception as e: - error = "LDAP login incorrect: %s" % e - raise LDAPUserLoginError({"error_message": error}) - - # we are only interested in user objects in the response - c.response = [r for r in c.response if 'raw_attributes' in r and 'dn' in r] - # stop if no search results - if not c.response: - raise LDAPUserLoginError({"error_message": "LDAP login not found"}) - - # handle multiple matches - if len(c.response) > 1: - raise LDAPUserLoginError( - {"error_message": "LDAP login could not be determined."}) - - # handle missing mandatory attributes - raw_attributes = c.response[0].get('raw_attributes') - if raw_attributes.get(USERNAME_ATTRIBUTE) or raw_attributes.get(EMAIL_ATTRIBUTE) or raw_attributes.get(FULL_NAME_ATTRIBUTE): - raise LDAPUserLoginError({"error_message": "LDAP login is invalid."}) - - # attempt LDAP bind - username = raw_attributes.get(USERNAME_ATTRIBUTE)[0].decode('utf-8') - email = raw_attributes.get(EMAIL_ATTRIBUTE)[0].decode('utf-8') - full_name = raw_attributes.get(FULL_NAME_ATTRIBUTE)[0].decode('utf-8') - try: - dn = str(bytes(c.response[0].get('dn'), 'utf-8'), encoding='utf-8') - Connection(server, auto_bind=auto_bind, client_strategy=SYNC, - check_names=True, authentication=SIMPLE, - user=dn, password=password) - except Exception as e: - error = "LDAP bind failed: %s" % e - raise LDAPUserLoginError({"error_message": error}) - - # LDAP binding successful, but some values might have changed, or - # this is the user's first login, so return them - return username, email, full_name + + for con, server in list_with_connections, server_ldap_list: + try: + con.search(search_base=SEARCH_BASE, + search_filter=search_filter, + search_scope=SUBTREE, + attributes=[USERNAME_ATTRIBUTE, + EMAIL_ATTRIBUTE, FULL_NAME_ATTRIBUTE], + paged_size=5) + except Exception as e: + error = "LDAP login incorrect: %s" % e + raise LDAPUserLoginError({"error_message": error}) + + # we are only interested in user objects in the response + con.response = [r for r in con.response if 'raw_attributes' in r and 'dn' in r] + # stop if no search results + if not con.response: + raise LDAPUserLoginError({"error_message": "LDAP login not found"}) + + # handle multiple matches + if len(con.response) > 1: + raise LDAPUserLoginError( + {"error_message": "LDAP login could not be determined."}) + + # handle missing mandatory attributes + raw_attributes = con.response[0].get('raw_attributes') + if raw_attributes.get(USERNAME_ATTRIBUTE) or raw_attributes.get(EMAIL_ATTRIBUTE) or raw_attributes.get( + FULL_NAME_ATTRIBUTE): + raise LDAPUserLoginError({"error_message": "LDAP login is invalid."}) + + # attempt LDAP bind + username = raw_attributes.get(USERNAME_ATTRIBUTE)[0].decode('utf-8') + email = raw_attributes.get(EMAIL_ATTRIBUTE)[0].decode('utf-8') + full_name = raw_attributes.get(FULL_NAME_ATTRIBUTE)[0].decode('utf-8') + try: + dn = str(bytes(con.response[0].get('dn'), 'utf-8'), encoding='utf-8') + Connection(server, auto_bind=auto_bind, client_strategy=SYNC, + check_names=True, authentication=SIMPLE, + user=dn, password=password) + except Exception as e: + error = "LDAP bind failed: %s" % e + raise LDAPUserLoginError({"error_message": error}) + + # LDAP binding successful, but some values might have changed, or + # this is the user's first login, so return them + return username, email, full_name From 30e3acf1e0ebb345067abee303c8d45496c94e56 Mon Sep 17 00:00:00 2001 From: Emil Date: Fri, 13 Mar 2020 16:25:37 +0300 Subject: [PATCH 03/35] :recycle: Create dict instead of list Signed-off-by: Emil --- taiga_contrib_ldap_auth_ext/connector.py | 40 +++++++++++++----------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/taiga_contrib_ldap_auth_ext/connector.py b/taiga_contrib_ldap_auth_ext/connector.py index 9191cfa..e2f1c89 100644 --- a/taiga_contrib_ldap_auth_ext/connector.py +++ b/taiga_contrib_ldap_auth_ext/connector.py @@ -66,7 +66,8 @@ def login(username: str, password: str) -> tuple: if TLS_CERTS: tls = TLS_CERTS - server_ldap_list = [] + # Dict with server key and connection value + server_ldap_dict = {} # Connect to the LDAP servers for ser in SERVERS: @@ -77,7 +78,7 @@ def login(username: str, password: str) -> tuple: try: server = Server(ser, port=PORT, get_info=NONE, use_ssl=use_ssl, tls=tls) - server_ldap_list.append(server) + server_ldap_dict[server] = None except Exception as e: error = "Error connecting to LDAP server: %s" % e raise LDAPConnectionError({"error_message": error}) @@ -96,13 +97,11 @@ def login(username: str, password: str) -> tuple: if START_TLS: auto_bind = AUTO_BIND_TLS_BEFORE_BIND - list_with_connections = [] - - for ldap_value in server_ldap_list: + for ldap_value in server_ldap_dict: try: c = Connection(ldap_value, auto_bind=auto_bind, client_strategy=SYNC, check_names=True, user=service_user, password=service_pass, authentication=service_auth) - list_with_connections.append(c) + server_ldap_dict[ldap_value] = c except Exception as e: error = "Error connecting to LDAP server: %s" % e raise LDAPConnectionError({"error_message": error}) @@ -113,31 +112,34 @@ def login(username: str, password: str) -> tuple: if SEARCH_FILTER_ADDITIONAL: search_filter = '(&%s%s)' % (search_filter, SEARCH_FILTER_ADDITIONAL) - for con, server in list_with_connections, server_ldap_list: + for ldap_value in server_ldap_dict: + if server_ldap_dict[ldap_value] is None: + continue try: - con.search(search_base=SEARCH_BASE, - search_filter=search_filter, - search_scope=SUBTREE, - attributes=[USERNAME_ATTRIBUTE, - EMAIL_ATTRIBUTE, FULL_NAME_ATTRIBUTE], - paged_size=5) + cur_connection = server_ldap_dict[ldap_value] + cur_connection.search(search_base=SEARCH_BASE, + search_filter=search_filter, + search_scope=SUBTREE, + attributes=[USERNAME_ATTRIBUTE, + EMAIL_ATTRIBUTE, FULL_NAME_ATTRIBUTE], + paged_size=5) except Exception as e: error = "LDAP login incorrect: %s" % e raise LDAPUserLoginError({"error_message": error}) # we are only interested in user objects in the response - con.response = [r for r in con.response if 'raw_attributes' in r and 'dn' in r] + cur_connection.response = [r for r in cur_connection.response if 'raw_attributes' in r and 'dn' in r] # stop if no search results - if not con.response: + if not cur_connection.response: raise LDAPUserLoginError({"error_message": "LDAP login not found"}) # handle multiple matches - if len(con.response) > 1: + if len(cur_connection.response) > 1: raise LDAPUserLoginError( {"error_message": "LDAP login could not be determined."}) # handle missing mandatory attributes - raw_attributes = con.response[0].get('raw_attributes') + raw_attributes = cur_connection.response[0].get('raw_attributes') if raw_attributes.get(USERNAME_ATTRIBUTE) or raw_attributes.get(EMAIL_ATTRIBUTE) or raw_attributes.get( FULL_NAME_ATTRIBUTE): raise LDAPUserLoginError({"error_message": "LDAP login is invalid."}) @@ -147,8 +149,8 @@ def login(username: str, password: str) -> tuple: email = raw_attributes.get(EMAIL_ATTRIBUTE)[0].decode('utf-8') full_name = raw_attributes.get(FULL_NAME_ATTRIBUTE)[0].decode('utf-8') try: - dn = str(bytes(con.response[0].get('dn'), 'utf-8'), encoding='utf-8') - Connection(server, auto_bind=auto_bind, client_strategy=SYNC, + dn = str(bytes(cur_connection.response[0].get('dn'), 'utf-8'), encoding='utf-8') + Connection(ldap_value, auto_bind=auto_bind, client_strategy=SYNC, check_names=True, authentication=SIMPLE, user=dn, password=password) except Exception as e: From 27f4e15685b51da9862e2f60189c0abbc551f5b2 Mon Sep 17 00:00:00 2001 From: Emil Date: Mon, 16 Mar 2020 13:37:19 +0300 Subject: [PATCH 04/35] :recycle: remarks from review Signed-off-by: Emil --- taiga_contrib_ldap_auth_ext/connector.py | 33 ++++++++++++------------ 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/taiga_contrib_ldap_auth_ext/connector.py b/taiga_contrib_ldap_auth_ext/connector.py index e2f1c89..2034c02 100644 --- a/taiga_contrib_ldap_auth_ext/connector.py +++ b/taiga_contrib_ldap_auth_ext/connector.py @@ -48,6 +48,16 @@ class LDAPUserLoginError(LDAPError): START_TLS = getattr(settings, "LDAP_START_TLS", False) +def connect_to_ldap_server(ldap_value, auto_bind, service_user, service_pass, service_auth): + try: + c = Connection(ldap_value, auto_bind=auto_bind, client_strategy=SYNC, check_names=True, + user=service_user, password=service_pass, authentication=service_auth) + return c + except Exception as e: + error = "Error connecting to LDAP server: %s" % e + raise LDAPConnectionError({"error_message": error}) + + def login(username: str, password: str) -> tuple: """ Connect to LDAP server, perform a search and attempt a bind. @@ -97,15 +107,6 @@ def login(username: str, password: str) -> tuple: if START_TLS: auto_bind = AUTO_BIND_TLS_BEFORE_BIND - for ldap_value in server_ldap_dict: - try: - c = Connection(ldap_value, auto_bind=auto_bind, client_strategy=SYNC, check_names=True, - user=service_user, password=service_pass, authentication=service_auth) - server_ldap_dict[ldap_value] = c - except Exception as e: - error = "Error connecting to LDAP server: %s" % e - raise LDAPConnectionError({"error_message": error}) - # search for user-provided login search_filter = '(|(%s=%s)(%s=%s))' % ( USERNAME_ATTRIBUTE, username, EMAIL_ATTRIBUTE, username) @@ -113,16 +114,16 @@ def login(username: str, password: str) -> tuple: search_filter = '(&%s%s)' % (search_filter, SEARCH_FILTER_ADDITIONAL) for ldap_value in server_ldap_dict: - if server_ldap_dict[ldap_value] is None: + cur_connection = connect_to_ldap_server(ldap_value, auto_bind, service_user, service_pass, service_auth) + if cur_connection is None: continue try: - cur_connection = server_ldap_dict[ldap_value] cur_connection.search(search_base=SEARCH_BASE, - search_filter=search_filter, - search_scope=SUBTREE, - attributes=[USERNAME_ATTRIBUTE, - EMAIL_ATTRIBUTE, FULL_NAME_ATTRIBUTE], - paged_size=5) + search_filter=search_filter, + search_scope=SUBTREE, + attributes=[USERNAME_ATTRIBUTE, + EMAIL_ATTRIBUTE, FULL_NAME_ATTRIBUTE], + paged_size=5) except Exception as e: error = "LDAP login incorrect: %s" % e raise LDAPUserLoginError({"error_message": error}) From 0d0b6cba664eb3d2aea1c3aff6439c030afb05b2 Mon Sep 17 00:00:00 2001 From: Emil Date: Tue, 17 Mar 2020 13:01:37 +0300 Subject: [PATCH 05/35] :recycle: Insert part of code in one function Signed-off-by: Emil --- taiga_contrib_ldap_auth_ext/connector.py | 104 ++++++++++++----------- 1 file changed, 53 insertions(+), 51 deletions(-) diff --git a/taiga_contrib_ldap_auth_ext/connector.py b/taiga_contrib_ldap_auth_ext/connector.py index 2034c02..be56333 100644 --- a/taiga_contrib_ldap_auth_ext/connector.py +++ b/taiga_contrib_ldap_auth_ext/connector.py @@ -48,14 +48,55 @@ class LDAPUserLoginError(LDAPError): START_TLS = getattr(settings, "LDAP_START_TLS", False) -def connect_to_ldap_server(ldap_value, auto_bind, service_user, service_pass, service_auth): +def connect_to_ldap_server(ldap_value, server_ldap_dict, auto_bind, search_filter, password): + if server_ldap_dict[ldap_value] is None: + return try: - c = Connection(ldap_value, auto_bind=auto_bind, client_strategy=SYNC, check_names=True, - user=service_user, password=service_pass, authentication=service_auth) - return c + cur_connection = server_ldap_dict[ldap_value] + cur_connection.search(search_base=SEARCH_BASE, + search_filter=search_filter, + search_scope=SUBTREE, + attributes=[USERNAME_ATTRIBUTE, + EMAIL_ATTRIBUTE, FULL_NAME_ATTRIBUTE], + paged_size=5) except Exception as e: - error = "Error connecting to LDAP server: %s" % e - raise LDAPConnectionError({"error_message": error}) + error_message = "LDAP login incorrect: %s" % e + raise LDAPUserLoginError({"error_message": error_message}) + finally: + print(error_message) + # we are only interested in user objects in the response + cur_connection.response = [r for r in cur_connection.response if 'raw_attributes' in r and 'dn' in r] + # stop if no search results + if not cur_connection.response: + raise LDAPUserLoginError({"error_message": "LDAP login not found"}) + + # handle multiple matches + if len(cur_connection.response) > 1: + raise LDAPUserLoginError( + {"error_message": "LDAP login could not be determined."}) + + # handle missing mandatory attributes + raw_attributes = cur_connection.response[0].get('raw_attributes') + if raw_attributes.get(USERNAME_ATTRIBUTE) or raw_attributes.get(EMAIL_ATTRIBUTE) or raw_attributes.get( + FULL_NAME_ATTRIBUTE): + raise LDAPUserLoginError({"error_message": "LDAP login is invalid."}) + + # attempt LDAP bind + username = raw_attributes.get(USERNAME_ATTRIBUTE)[0].decode('utf-8') + email = raw_attributes.get(EMAIL_ATTRIBUTE)[0].decode('utf-8') + full_name = raw_attributes.get(FULL_NAME_ATTRIBUTE)[0].decode('utf-8') + try: + dn = str(bytes(cur_connection.response[0].get('dn'), 'utf-8'), encoding='utf-8') + Connection(ldap_value, auto_bind=auto_bind, client_strategy=SYNC, + check_names=True, authentication=SIMPLE, + user=dn, password=password) + except Exception as e: + error = "LDAP bind failed: %s" % e + raise LDAPUserLoginError({"error_message": error}) + + # LDAP binding successful, but some values might have changed, or + # this is the user's first login, so return them + return username, email, full_name def login(username: str, password: str) -> tuple: @@ -114,50 +155,11 @@ def login(username: str, password: str) -> tuple: search_filter = '(&%s%s)' % (search_filter, SEARCH_FILTER_ADDITIONAL) for ldap_value in server_ldap_dict: - cur_connection = connect_to_ldap_server(ldap_value, auto_bind, service_user, service_pass, service_auth) - if cur_connection is None: - continue try: - cur_connection.search(search_base=SEARCH_BASE, - search_filter=search_filter, - search_scope=SUBTREE, - attributes=[USERNAME_ATTRIBUTE, - EMAIL_ATTRIBUTE, FULL_NAME_ATTRIBUTE], - paged_size=5) + data = connect_to_ldap_server(ldap_value, server_ldap_dict, auto_bind, search_filter, password) except Exception as e: - error = "LDAP login incorrect: %s" % e - raise LDAPUserLoginError({"error_message": error}) - - # we are only interested in user objects in the response - cur_connection.response = [r for r in cur_connection.response if 'raw_attributes' in r and 'dn' in r] - # stop if no search results - if not cur_connection.response: - raise LDAPUserLoginError({"error_message": "LDAP login not found"}) - - # handle multiple matches - if len(cur_connection.response) > 1: - raise LDAPUserLoginError( - {"error_message": "LDAP login could not be determined."}) - - # handle missing mandatory attributes - raw_attributes = cur_connection.response[0].get('raw_attributes') - if raw_attributes.get(USERNAME_ATTRIBUTE) or raw_attributes.get(EMAIL_ATTRIBUTE) or raw_attributes.get( - FULL_NAME_ATTRIBUTE): - raise LDAPUserLoginError({"error_message": "LDAP login is invalid."}) - - # attempt LDAP bind - username = raw_attributes.get(USERNAME_ATTRIBUTE)[0].decode('utf-8') - email = raw_attributes.get(EMAIL_ATTRIBUTE)[0].decode('utf-8') - full_name = raw_attributes.get(FULL_NAME_ATTRIBUTE)[0].decode('utf-8') - try: - dn = str(bytes(cur_connection.response[0].get('dn'), 'utf-8'), encoding='utf-8') - Connection(ldap_value, auto_bind=auto_bind, client_strategy=SYNC, - check_names=True, authentication=SIMPLE, - user=dn, password=password) - except Exception as e: - error = "LDAP bind failed: %s" % e - raise LDAPUserLoginError({"error_message": error}) - - # LDAP binding successful, but some values might have changed, or - # this is the user's first login, so return them - return username, email, full_name + print("Connection to LDAP server was failed") + continue + if data is not None: + break + raise LDAPUserLoginError({"error_message": "No one server accepted LDAP connection"}) \ No newline at end of file From e3254198db362814bdc68093ed8eee6cba770620 Mon Sep 17 00:00:00 2001 From: Emil Date: Fri, 20 Mar 2020 16:25:50 +0300 Subject: [PATCH 06/35] :whale: Add docker from another pull request Signed-off-by: Emil --- .env | 0 .gitignore | 3 +- Dockerfile | 31 ++++ docker-compose.yml | 200 +++++++++++++++++++++++ entrypoint.sh | 77 +++++++++ local.py | 71 ++++++++ taiga_contrib_ldap_auth_ext/connector.py | 145 +++++++--------- taiga_contrib_ldap_auth_ext/services.py | 5 + 8 files changed, 448 insertions(+), 84 deletions(-) create mode 100644 .env create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 entrypoint.sh create mode 100644 local.py diff --git a/.env b/.env new file mode 100644 index 0000000..e69de29 diff --git a/.gitignore b/.gitignore index 0c63844..f013c47 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ __pycache__ *.eggs *.egg-info dist -.idea \ No newline at end of file +.idea +.env \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a8c36f4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,31 @@ +#TODO: How to give ability to another version and variant? +FROM monogramm/docker-taiga-back-base:4.2-alpine +LABEL maintainer="Monogramm maintainers " + +# Taiga additional properties +ENV TAIGA_ENABLE_LDAP=False \ + TAIGA_LDAP_USE_TLS=True \ + TAIGA_LDAP_SERVER= \ + TAIGA_LDAP_PORT=389 \ + TAIGA_LDAP_BIND_DN= \ + TAIGA_LDAP_BIND_PASSWORD= \ + TAIGA_LDAP_BASE_DN= \ + TAIGA_LDAP_USERNAME_ATTRIBUTE=uid \ + TAIGA_LDAP_EMAIL_ATTRIBUTE=mail \ + TAIGA_LDAP_FULL_NAME_ATTRIBUTE=cn \ + TAIGA_LDAP_SAVE_LOGIN_PASSWORD=True \ + TAIGA_LDAP_FALLBACK=normal + +# Erase original entrypoint and conf with custom one +COPY entrypoint.sh ./ +COPY local.py /taiga/ +# Fix entrypoint permissions +# Install LDAP extension +RUN set -ex; \ + chmod 755 /entrypoint.sh; \ + LC_ALL=C pip install --no-cache-dir taiga-contrib-ldap-auth-ext; \ + rm -r /usr/local/lib/python3.6/site-packages/taiga_contrib_ldap_auth_ext + +COPY . /usr/local/lib/python3.6/site-packages/taiga_contrib_ldap_auth_ext +# Backend healthcheck +HEALTHCHECK CMD curl --fail http://127.0.0.1:8001/api/v1/ || exit 1 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a567219 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,200 @@ +version: '2' + +services: + taigadb: + image: postgres:10-alpine + container_name: taigadb + #restart: always + ports: + - 5432:5432 + volumes: + - /srv/taiga/db/data:/var/lib/postgresql/data + environment: + - POSTGRES_DB=taiga + - POSTGRES_USER=taiga + - POSTGRES_PASSWORD=${TAIGA_DB_PWD} + + taiga_back: + # For CI or local modifications + build: + context: . + dockerfile: Dockerfile + # For production + #image: monogramm/docker-taiga-back:4.2-alpine + hostname: ${HOSTNAME} + container_name: taiga_back + #restart: always + depends_on: + - taigadb + ports: + - 8001:8001 + volumes: + # Media and uploads directory. Required (or you will lose all uploads) + - /srv/taiga/back/media:/usr/src/taiga-back/media + - /srv/taiga/back/static:/usr/src/taiga-back/static + # Taiga configuration directory. Makes it easier to change configuration with your own + #- /srv/taiga/back/conf:/taiga + - /etc/localtime:/etc/localtime:ro + - /etc/timezone:/etc/timezone:ro + environment: + # Your hostname (REQUIRED) + - TAIGA_HOSTNAME=${HOSTNAME}:${PORT} + #- TAIGA_SSL=False + #- TAIGA_SSL_BY_REVERSE_PROXY=True + # Secret key for cryptographic signing + - TAIGA_SECRET_KEY=${TAIGA_SECRET} + # Admin account default password + - TAIGA_ADMIN_PASSWORD=${TAIGA_ADMIN_PASSWORD} + # Database settings + - TAIGA_DB_HOST=taigadb + - TAIGA_DB_NAME=taiga + - TAIGA_DB_USER=taiga + - TAIGA_DB_PASSWORD=${TAIGA_DB_PWD} + # when the db comes up from docker, it is usually too quick + - TAIGA_SLEEP=5 + # To use an external SMTP for emails, fill in these values: + - TAIGA_ENABLE_EMAIL=True + - TAIGA_EMAIL_FROM=taiga@${TAIGA_EMAIL_DOMAIN} + - TAIGA_EMAIL_USE_TLS=True + - TAIGA_EMAIL_HOST=smtp.${TAIGA_EMAIL_DOMAIN} + - TAIGA_EMAIL_PORT=587 + - TAIGA_EMAIL_USER=${TAIGA_SMTP_USER} + - TAIGA_EMAIL_PASS=${TAIGA_SMTP_PWD} + # Backend settings + - TAIGA_DEBUG=False + - TAIGA_PUBLIC_REGISTER_ENABLED=False + - TAIGA_FEEDBACK_ENABLED=True + - TAIGA_FEEDBACK_EMAIL=taiga@${TAIGA_EMAIL_DOMAIN} + # Events settings + - TAIGA_EVENTS_ENABLED=True + - RABBIT_USER=${TAIGA_RABBIT_USER} + - RABBIT_PASSWORD=${TAIGA_RABBIT_PASSWORD} + - RABBIT_VHOST='/' + - RABBIT_HOST=taiga_rabbit + - RABBIT_PORT=5672 + # Async settings + # To enable async mode, uncomment the following lines: + #- TAIGA_ASYNC_ENABLED=True + #- REDIS_HOST=taiga_redis + #- REDIS_PORT=6379 + ### Additional parameters + # LDAP Settings + - TAIGA_ENABLE_LDAP=${TAIGA_ENABLE_LDAP} + - TAIGA_LDAP_USE_TLS=False + - TAIGA_LDAP_SERVER=ldaps://${TAIGA_LDAP_DOMAIN} + - TAIGA_LDAP_PORT=636 + - TAIGA_LDAP_BIND_DN=${TAIGA_LDAP_BIND_DN} + - TAIGA_LDAP_BIND_PASSWORD=${TAIGA_LDAP_BIND_PASSWORD} + - TAIGA_LDAP_BASE_DN=${TAIGA_LDAP_BASE_DN} + - TAIGA_LDAP_USERNAME_ATTRIBUTE=uid + - TAIGA_LDAP_EMAIL_ATTRIBUTE=mail + - TAIGA_LDAP_FULL_NAME_ATTRIBUTE=displayName + - TAIGA_LDAP_FALLBACK=normal + + taiga_front: + # For CI or local modifications + #build: ./front + # For production + image: monogramm/docker-taiga-front:4.2-alpine + hostname: ${HOSTNAME} + container_name: taiga_front + #restart: always + depends_on: + - taiga_back + # To disable taiga-events, comment the following lines: + - taiga_events + - taiga_rabbit + # To enable async mode, uncomment the following lines: + #- taiga_redis + ports: + # If using SSL, uncomment 443 and comment out 80 + - ${PORT}:80 + #- ${PORT}:443 + volumes: + # Media and uploads directory. Required for NGinx + - /srv/taiga/back/media:/usr/src/taiga-back/media:ro + - /srv/taiga/back/static:/usr/src/taiga-back/static:ro + # Taiga configuration directory. Makes it easier to change configuration with your own + #- /srv/taiga/front/conf:/taiga + - /etc/localtime:/etc/localtime:ro + - /etc/timezone:/etc/timezone:ro + environment: + # Your hostname (REQUIRED) + - TAIGA_HOSTNAME=${HOSTNAME}:${PORT} + #- TAIGA_SSL=True + #- TAIGA_SSL_BY_REVERSE_PROXY=True + #- TAIGA_BACKEND_SSL=True + # Frontend settings + - TAIGA_DEBUG=false + - TAIGA_DEBUG_INFO=false + - TAIGA_DEFAULT_LANGUAGE=en + - TAIGA_THEMES=taiga material-design high-contrast + - TAIGA_DEFAULT_THEME=taiga + - TAIGA_PUBLIC_REGISTER_ENABLED=false + - TAIGA_FEEDBACK_ENABLED=true + - TAIGA_SUPPORT_URL=https://tree.taiga.io/support + - TAIGA_PRIVACY_POLICY_URL= + - TAIGA_TOS_URL= + - TAIGA_GDPR_URL= + - TAIGA_MAX_UPLOAD_SIZE=104857600 + - TAIGA_CONTRIB_PLUGINS=slack cookie-warning + #- TAIGA_CONTRIB_PLUGINS=slack cookie-warning gitlab-auth github-auth + - TAIGA_GRAVATAR=true + - TAIGA_LOGIN_FORM_TYPE=ldap + # Backend settings + - TAIGA_BACK_HOST=taiga_back + - TAIGA_BACK_PORT=8001 + # Events settings + - TAIGA_EVENTS_ENABLED=True + - TAIGA_EVENTS_HOST=taiga_events + - TAIGA_EVENTS_PORT=8888 + + # To disable taiga-events, comment all the following lines: + taiga_rabbit: + image: rabbitmq:3-alpine + hostname: taiga_rabbit + container_name: taiga_rabbit + #restart: always + ports: + - 5672:5672 + environment: + - RABBITMQ_DEFAULT_USER=${TAIGA_RABBIT_USER} + - RABBITMQ_DEFAULT_PASS=${TAIGA_RABBIT_PASSWORD} + + taiga_events: + image: monogramm/docker-taiga-events:alpine + container_name: taiga_events + #restart: always + links: + - taiga_rabbit + ports: + - 8888:8888 + environment: + - RABBIT_USER=${TAIGA_RABBIT_USER} + - RABBIT_PASSWORD=${TAIGA_RABBIT_PASSWORD} + - RABBIT_VHOST='/' + - RABBIT_HOST=taiga_rabbit + - RABBIT_PORT=5672 + - TAIGA_EVENTS_SECRET=${TAIGA_SECRET} + - TAIGA_EVENTS_PORT=8888 + + # To enable async mode, uncomment the following lines: +# taiga_redis: +# image: redis:4.0-alpine +# container_name: taiga_redis +# restart: always +# ports: +# - 6379:6379 +# volumes: +# - /srv/taiga/redis/:/data +# - /etc/localtime:/etc/localtime:ro +# - /etc/timezone:/etc/timezone:ro +# +# # FIXME Celery docker image is deprecated since 2017 +# taiga_celery: +# image: celery +# container_name: taiga_celery +# restart: always +# links: +# - taiga_rabbit +# - taiga_redis \ No newline at end of file diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..c92f95f --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,77 @@ +set -e + +log() { + echo "[$(date +%Y-%m-%dT%H:%M:%S%:z)] $@" +} + +# Sleep when asked to, to allow the database time to start +# before Taiga tries to run /checkdb.py below. +: ${TAIGA_SLEEP:=0} +sleep $TAIGA_SLEEP + +# Setup and check database automatically if needed +if [ -z "$TAIGA_SKIP_DB_CHECK" ]; then + log "Running database check" + set +e + python /checkdb.py + DB_CHECK_STATUS=$? + set -e + + if [ $DB_CHECK_STATUS -eq 1 ]; then + log "Failed to connect to database server or database does not exist." + exit 1 + fi + + # Database migration check should be done in all startup in case of backend upgrade + log "Execute database migrations..." + python manage.py migrate --noinput + + if [ $DB_CHECK_STATUS -eq 2 ]; then + log "Configuring initial user" + python manage.py loaddata initial_user + log "Configuring initial project templates" + python manage.py loaddata initial_project_templates + + # shellcheck disable=SC2070 + if [ -n $TAIGA_ADMIN_PASSWORD ]; then + log "Changing initial admin password" + python manage.py shell < /changeadminpasswd.py + fi + fi + + # TODO This works... but requires to persist the backend to keep track of already executed migrations + # BREAKING CHANGES INCOMING + #if python manage.py migrate --noinput | grep 'Your models have changes that are not yet reflected in a migration'; then + # log "Generate database migrations..." + # python manage.py makemigrations + # log "Execute database migrations..." + # python manage.py migrate --noinput + #fi + +fi + +# In case of frontend upgrade, locales and statics should be regenerated +log "Compiling messages and collecting static" +python manage.py compilemessages > /dev/null +python manage.py collectstatic --noinput > /dev/null + +log "Start gunicorn server" +GUNICORN_TIMEOUT="${GUINCORN_TIMEOUT:-60}" +GUNICORN_WORKERS="${GUNICORN_WORKERS:-4}" +GUNICORN_LOGLEVEL="${GUNICORN_LOGLEVEL:-debug}" + +GUNICORN_ARGS="--pythonpath=. -t ${GUNICORN_TIMEOUT} --workers ${GUNICORN_WORKERS} --bind ${BIND_ADDRESS}:${PORT} --log-level ${GUNICORN_LOGLEVEL}" + +if [ -n "${GUNICORN_CERTFILE}" ]; then + GUNICORN_ARGS="${GUNICORN_ARGS} --certfile=${GUNICORN_CERTFILE}" +fi + +if [ -n "${GUNICORN_KEYFILE}" ]; then + GUNICORN_ARGS="${GUNICORN_ARGS} --keyfile=${GUNICORN_KEYFILE}" +fi + +if [ "$1" == "gunicorn" ]; then + exec "$@" "$GUNICORN_ARGS" +else + exec "$@" +fi \ No newline at end of file diff --git a/local.py b/local.py new file mode 100644 index 0000000..6c86eb6 --- /dev/null +++ b/local.py @@ -0,0 +1,71 @@ +# If you want to modify this file, I recommend check out docker-taiga-example +# https://github.com/benhutchins/docker-taiga-example +# +# Please modify this file as needed, see the local.py.example for details: +# https://github.com/taigaio/taiga-back/blob/master/settings/local.py.example +# +# Importing docker provides common settings, see: +# https://github.com/benhutchins/docker-taiga/blob/master/docker-settings.py +# https://github.com/taigaio/taiga-back/blob/master/settings/common.py + +from .docker import * +from ldap3 import Tls +import ssl + +######################################### +## LDAP +######################################### + +if os.getenv('TAIGA_ENABLE_LDAP').lower() == 'true': + # see https://github.com/Monogramm/taiga-contrib-ldap-auth-ext + print("Taiga contrib LDAP Auth Ext enabled", file=sys.stderr) + INSTALLED_APPS += ["taiga_contrib_ldap_auth_ext"] + + if os.getenv('TAIGA_LDAP_USE_TLS').lower() == 'true': + # Flag to enable LDAP with STARTTLS before bind + LDAP_START_TLS = True + LDAP_TLS_CERTS = Tls(validate=ssl.CERT_NONE, version=ssl.PROTOCOL_TLSv1, ciphers='RSA+3DES') + else: + LDAP_START_TLS = False + + LDAP_SERVER = os.getenv('TAIGA_LDAP_SERVER') + LDAP_PORT = int(os.getenv('TAIGA_LDAP_PORT')) + + # Full DN of the service account use to connect to LDAP server and search for login user's account entry + # If LDAP_BIND_DN is not specified, or is blank, then an anonymous bind is attempated + LDAP_BIND_DN = os.getenv('TAIGA_LDAP_BIND_DN') + LDAP_BIND_PASSWORD = os.getenv('TAIGA_LDAP_BIND_PASSWORD') + + # Starting point within LDAP structure to search for login user + # Something like 'ou=People,dc=company,dc=com' + LDAP_SEARCH_BASE = os.getenv('TAIGA_LDAP_BASE_DN') + + # Additional search criteria to the filter (will be ANDed) + #LDAP_SEARCH_FILTER_ADDITIONAL = '(mail=*)' + + # Names of attributes to get username, e-mail and full name values from + # These fields need to have a value in LDAP + LDAP_USERNAME_ATTRIBUTE = os.getenv('TAIGA_LDAP_USERNAME_ATTRIBUTE') + LDAP_EMAIL_ATTRIBUTE = os.getenv('TAIGA_LDAP_EMAIL_ATTRIBUTE') + LDAP_FULL_NAME_ATTRIBUTE = os.getenv('TAIGA_LDAP_FULL_NAME_ATTRIBUTE') + + # Option to not store the passwords in the local db + if os.getenv('TAIGA_LDAP_SAVE_LOGIN_PASSWORD').lower() == 'false': + LDAP_SAVE_LOGIN_PASSWORD = False + + # Fallback on normal authentication method if this LDAP auth fails. Uncomment to enable. + LDAP_FALLBACK = os.getenv('TAIGA_LDAP_FALLBACK') + + # Function to map LDAP username to local DB user unique identifier. + # Upon successful LDAP bind, will override returned username attribute + # value. May result in unexpected failures if changed after the database + # has been populated. + def _ldap_slugify(uid: str) -> str: + # example: force lower-case + uid = uid.lower() + return uid + + LDAP_MAP_USERNAME_TO_UID = _ldap_slugify + + ## For additional configuration options, look at: + # https://github.com/taigaio/taiga-back/blob/master/settings/local.py.example \ No newline at end of file diff --git a/taiga_contrib_ldap_auth_ext/connector.py b/taiga_contrib_ldap_auth_ext/connector.py index be56333..aa01c7f 100644 --- a/taiga_contrib_ldap_auth_ext/connector.py +++ b/taiga_contrib_ldap_auth_ext/connector.py @@ -11,8 +11,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from ldap3 import Server, Connection, AUTO_BIND_NO_TLS, AUTO_BIND_TLS_BEFORE_BIND, ANONYMOUS, SIMPLE, SYNC, SUBTREE, \ - NONE +from ldap3 import Server, Connection, AUTO_BIND_NO_TLS, AUTO_BIND_TLS_BEFORE_BIND, ANONYMOUS, SIMPLE, SYNC, SUBTREE, NONE from django.conf import settings from taiga.base.connectors.exceptions import ConnectorBaseException @@ -31,7 +30,7 @@ class LDAPUserLoginError(LDAPError): # TODO https://github.com/Monogramm/taiga-contrib-ldap-auth-ext/issues/16 -SERVERS = getattr(settings, "LDAP_SERVER", "localhost") +SERVER = getattr(settings, "LDAP_SERVER", "localhost") PORT = getattr(settings, "LDAP_PORT", "389") SEARCH_BASE = getattr(settings, "LDAP_SEARCH_BASE", "") @@ -48,91 +47,31 @@ class LDAPUserLoginError(LDAPError): START_TLS = getattr(settings, "LDAP_START_TLS", False) -def connect_to_ldap_server(ldap_value, server_ldap_dict, auto_bind, search_filter, password): - if server_ldap_dict[ldap_value] is None: - return - try: - cur_connection = server_ldap_dict[ldap_value] - cur_connection.search(search_base=SEARCH_BASE, - search_filter=search_filter, - search_scope=SUBTREE, - attributes=[USERNAME_ATTRIBUTE, - EMAIL_ATTRIBUTE, FULL_NAME_ATTRIBUTE], - paged_size=5) - except Exception as e: - error_message = "LDAP login incorrect: %s" % e - raise LDAPUserLoginError({"error_message": error_message}) - finally: - print(error_message) - # we are only interested in user objects in the response - cur_connection.response = [r for r in cur_connection.response if 'raw_attributes' in r and 'dn' in r] - # stop if no search results - if not cur_connection.response: - raise LDAPUserLoginError({"error_message": "LDAP login not found"}) - - # handle multiple matches - if len(cur_connection.response) > 1: - raise LDAPUserLoginError( - {"error_message": "LDAP login could not be determined."}) - - # handle missing mandatory attributes - raw_attributes = cur_connection.response[0].get('raw_attributes') - if raw_attributes.get(USERNAME_ATTRIBUTE) or raw_attributes.get(EMAIL_ATTRIBUTE) or raw_attributes.get( - FULL_NAME_ATTRIBUTE): - raise LDAPUserLoginError({"error_message": "LDAP login is invalid."}) - - # attempt LDAP bind - username = raw_attributes.get(USERNAME_ATTRIBUTE)[0].decode('utf-8') - email = raw_attributes.get(EMAIL_ATTRIBUTE)[0].decode('utf-8') - full_name = raw_attributes.get(FULL_NAME_ATTRIBUTE)[0].decode('utf-8') - try: - dn = str(bytes(cur_connection.response[0].get('dn'), 'utf-8'), encoding='utf-8') - Connection(ldap_value, auto_bind=auto_bind, client_strategy=SYNC, - check_names=True, authentication=SIMPLE, - user=dn, password=password) - except Exception as e: - error = "LDAP bind failed: %s" % e - raise LDAPUserLoginError({"error_message": error}) - - # LDAP binding successful, but some values might have changed, or - # this is the user's first login, so return them - return username, email, full_name - - def login(username: str, password: str) -> tuple: """ Connect to LDAP server, perform a search and attempt a bind. - Can raise `exc.LDAPConnectionError` exceptions if the connection to LDAP fails. - Can raise `exc.LDAPUserLoginError` exceptions if the login to LDAP fails. - :returns: tuple (username, email, full_name) - """ tls = None if TLS_CERTS: tls = TLS_CERTS - # Dict with server key and connection value - server_ldap_dict = {} - - # Connect to the LDAP servers - for ser in SERVERS: - if ser.lower().startswith("ldaps://"): - use_ssl = True - else: - use_ssl = False - try: - server = Server(ser, port=PORT, get_info=NONE, - use_ssl=use_ssl, tls=tls) - server_ldap_dict[server] = None - except Exception as e: - error = "Error connecting to LDAP server: %s" % e - raise LDAPConnectionError({"error_message": error}) + # connect to the LDAP server + if SERVER.lower().startswith("ldaps://"): + use_ssl = True + else: + use_ssl = False + try: + server = Server(SERVER, port=PORT, get_info=NONE, + use_ssl=use_ssl, tls=tls) + except Exception as e: + error = "Error connecting to LDAP server: %s" % e + raise LDAPConnectionError({"error_message": error}) # authenticate as service if credentials provided, anonymously otherwise if BIND_DN is not None and BIND_DN != '': @@ -148,18 +87,58 @@ def login(username: str, password: str) -> tuple: if START_TLS: auto_bind = AUTO_BIND_TLS_BEFORE_BIND + try: + c = Connection(server, auto_bind=auto_bind, client_strategy=SYNC, check_names=True, + user=service_user, password=service_pass, authentication=service_auth) + except Exception as e: + error = "Error connecting to LDAP server: %s" % e + raise LDAPConnectionError({"error_message": error}) + # search for user-provided login search_filter = '(|(%s=%s)(%s=%s))' % ( USERNAME_ATTRIBUTE, username, EMAIL_ATTRIBUTE, username) if SEARCH_FILTER_ADDITIONAL: search_filter = '(&%s%s)' % (search_filter, SEARCH_FILTER_ADDITIONAL) + try: + c.search(search_base=SEARCH_BASE, + search_filter=search_filter, + search_scope=SUBTREE, + attributes=[USERNAME_ATTRIBUTE, + EMAIL_ATTRIBUTE, FULL_NAME_ATTRIBUTE], + paged_size=5) + except Exception as e: + error = "LDAP login incorrect: %s" % e + raise LDAPUserLoginError({"error_message": error}) + + # we are only interested in user objects in the response + c.response = [r for r in c.response if 'raw_attributes' in r and 'dn' in r] + # stop if no search results + if not c.response: + raise LDAPUserLoginError({"error_message": "LDAP login not found"}) + + # handle multiple matches + if len(c.response) > 1: + raise LDAPUserLoginError( + {"error_message": "LDAP login could not be determined."}) - for ldap_value in server_ldap_dict: - try: - data = connect_to_ldap_server(ldap_value, server_ldap_dict, auto_bind, search_filter, password) - except Exception as e: - print("Connection to LDAP server was failed") - continue - if data is not None: - break - raise LDAPUserLoginError({"error_message": "No one server accepted LDAP connection"}) \ No newline at end of file + # handle missing mandatory attributes + raw_attributes = c.response[0].get('raw_attributes') + if raw_attributes.get(USERNAME_ATTRIBUTE) or raw_attributes.get(EMAIL_ATTRIBUTE) or raw_attributes.get(FULL_NAME_ATTRIBUTE): + raise LDAPUserLoginError({"error_message": "LDAP login is invalid."}) + + # attempt LDAP bind + username = raw_attributes.get(USERNAME_ATTRIBUTE)[0].decode('utf-8') + email = raw_attributes.get(EMAIL_ATTRIBUTE)[0].decode('utf-8') + full_name = raw_attributes.get(FULL_NAME_ATTRIBUTE)[0].decode('utf-8') + try: + dn = str(bytes(c.response[0].get('dn'), 'utf-8'), encoding='utf-8') + Connection(server, auto_bind=auto_bind, client_strategy=SYNC, + check_names=True, authentication=SIMPLE, + user=dn, password=password) + except Exception as e: + error = "LDAP bind failed: %s" % e + raise LDAPUserLoginError({"error_message": error}) + + # LDAP binding successful, but some values might have changed, or + # this is the user's first login, so return them + return (username, email, full_name) \ No newline at end of file diff --git a/taiga_contrib_ldap_auth_ext/services.py b/taiga_contrib_ldap_auth_ext/services.py index bfb3d4c..16d814b 100644 --- a/taiga_contrib_ldap_auth_ext/services.py +++ b/taiga_contrib_ldap_auth_ext/services.py @@ -91,6 +91,11 @@ def register_or_update(username: str, email: str, full_name: str, password: str) :returns: User """ + print("WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW", file=sys.stderr) + print(username, file=sys.stderr) + print(email, file=sys.stderr) + print(full_name, file=sys.stderr) + print(password, file=sys.stderr) user_model = apps.get_model('users', 'User') username_unique = username From 2b40f1065c949bd281f6fc53ae38a05d0cf1e328 Mon Sep 17 00:00:00 2001 From: Emil Date: Mon, 23 Mar 2020 16:39:16 +0300 Subject: [PATCH 07/35] :bug: Fix bug with always None connection Signed-off-by: Emil --- taiga_contrib_ldap_auth_ext/connector.py | 144 +++++++++++++---------- 1 file changed, 82 insertions(+), 62 deletions(-) diff --git a/taiga_contrib_ldap_auth_ext/connector.py b/taiga_contrib_ldap_auth_ext/connector.py index aa01c7f..294b0d8 100644 --- a/taiga_contrib_ldap_auth_ext/connector.py +++ b/taiga_contrib_ldap_auth_ext/connector.py @@ -11,7 +11,8 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from ldap3 import Server, Connection, AUTO_BIND_NO_TLS, AUTO_BIND_TLS_BEFORE_BIND, ANONYMOUS, SIMPLE, SYNC, SUBTREE, NONE +from ldap3 import Server, Connection, AUTO_BIND_NO_TLS, AUTO_BIND_TLS_BEFORE_BIND, ANONYMOUS, SIMPLE, SYNC, SUBTREE, \ + NONE from django.conf import settings from taiga.base.connectors.exceptions import ConnectorBaseException @@ -30,7 +31,7 @@ class LDAPUserLoginError(LDAPError): # TODO https://github.com/Monogramm/taiga-contrib-ldap-auth-ext/issues/16 -SERVER = getattr(settings, "LDAP_SERVER", "localhost") +SERVERS = getattr(settings, "LDAP_SERVER", "localhost") PORT = getattr(settings, "LDAP_PORT", "389") SEARCH_BASE = getattr(settings, "LDAP_SEARCH_BASE", "") @@ -47,6 +48,57 @@ class LDAPUserLoginError(LDAPError): START_TLS = getattr(settings, "LDAP_START_TLS", False) +def connect_to_ldap_server(ldap_value, server_ldap_dict, auto_bind, search_filter, password): + if server_ldap_dict[ldap_value] is None: + return + try: + cur_connection = server_ldap_dict[ldap_value] + cur_connection.search(search_base=SEARCH_BASE, + search_filter=search_filter, + search_scope=SUBTREE, + attributes=[USERNAME_ATTRIBUTE, + EMAIL_ATTRIBUTE, FULL_NAME_ATTRIBUTE], + paged_size=5) + except Exception as e: + error_message = "LDAP login incorrect: %s" % e + raise LDAPUserLoginError({"error_message": error_message}) + finally: + print(error_message, file=sys.stderr) + # we are only interested in user objects in the response + cur_connection.response = [r for r in cur_connection.response if 'raw_attributes' in r and 'dn' in r] + # stop if no search results + if not cur_connection.response: + raise LDAPUserLoginError({"error_message": "LDAP login not found"}) + + # handle multiple matches + if len(cur_connection.response) > 1: + raise LDAPUserLoginError( + {"error_message": "LDAP login could not be determined."}) + + # handle missing mandatory attributes + raw_attributes = cur_connection.response[0].get('raw_attributes') + if raw_attributes.get(USERNAME_ATTRIBUTE) or raw_attributes.get(EMAIL_ATTRIBUTE) or raw_attributes.get( + FULL_NAME_ATTRIBUTE): + raise LDAPUserLoginError({"error_message": "LDAP login is invalid."}) + + # attempt LDAP bind + username = raw_attributes.get(USERNAME_ATTRIBUTE)[0].decode('utf-8') + email = raw_attributes.get(EMAIL_ATTRIBUTE)[0].decode('utf-8') + full_name = raw_attributes.get(FULL_NAME_ATTRIBUTE)[0].decode('utf-8') + try: + dn = str(bytes(cur_connection.response[0].get('dn'), 'utf-8'), encoding='utf-8') + Connection(ldap_value, auto_bind=auto_bind, client_strategy=SYNC, + check_names=True, authentication=SIMPLE, + user=dn, password=password) + except Exception as e: + error = "LDAP bind failed: %s" % e + raise LDAPUserLoginError({"error_message": error}) + + # LDAP binding successful, but some values might have changed, or + # this is the user's first login, so return them + return username, email, full_name + + def login(username: str, password: str) -> tuple: """ Connect to LDAP server, perform a search and attempt a bind. @@ -61,17 +113,22 @@ def login(username: str, password: str) -> tuple: if TLS_CERTS: tls = TLS_CERTS - # connect to the LDAP server - if SERVER.lower().startswith("ldaps://"): - use_ssl = True - else: - use_ssl = False - try: - server = Server(SERVER, port=PORT, get_info=NONE, - use_ssl=use_ssl, tls=tls) - except Exception as e: - error = "Error connecting to LDAP server: %s" % e - raise LDAPConnectionError({"error_message": error}) + # Dict with server key and connection value + server_ldap_dict = {} + + # Connect to the LDAP servers + for ser in SERVERS: + if ser.lower().startswith("ldaps://"): + use_ssl = True + else: + use_ssl = False + try: + server = Server(ser, port=PORT, get_info=NONE, + use_ssl=use_ssl, tls=tls) + server_ldap_dict[server] = None + except Exception as e: + error = "Error connecting to LDAP server: %s" % e + raise LDAPConnectionError({"error_message": error}) # authenticate as service if credentials provided, anonymously otherwise if BIND_DN is not None and BIND_DN != '': @@ -87,58 +144,21 @@ def login(username: str, password: str) -> tuple: if START_TLS: auto_bind = AUTO_BIND_TLS_BEFORE_BIND - try: - c = Connection(server, auto_bind=auto_bind, client_strategy=SYNC, check_names=True, - user=service_user, password=service_pass, authentication=service_auth) - except Exception as e: - error = "Error connecting to LDAP server: %s" % e - raise LDAPConnectionError({"error_message": error}) - # search for user-provided login search_filter = '(|(%s=%s)(%s=%s))' % ( USERNAME_ATTRIBUTE, username, EMAIL_ATTRIBUTE, username) if SEARCH_FILTER_ADDITIONAL: search_filter = '(&%s%s)' % (search_filter, SEARCH_FILTER_ADDITIONAL) - try: - c.search(search_base=SEARCH_BASE, - search_filter=search_filter, - search_scope=SUBTREE, - attributes=[USERNAME_ATTRIBUTE, - EMAIL_ATTRIBUTE, FULL_NAME_ATTRIBUTE], - paged_size=5) - except Exception as e: - error = "LDAP login incorrect: %s" % e - raise LDAPUserLoginError({"error_message": error}) - - # we are only interested in user objects in the response - c.response = [r for r in c.response if 'raw_attributes' in r and 'dn' in r] - # stop if no search results - if not c.response: - raise LDAPUserLoginError({"error_message": "LDAP login not found"}) - - # handle multiple matches - if len(c.response) > 1: - raise LDAPUserLoginError( - {"error_message": "LDAP login could not be determined."}) - - # handle missing mandatory attributes - raw_attributes = c.response[0].get('raw_attributes') - if raw_attributes.get(USERNAME_ATTRIBUTE) or raw_attributes.get(EMAIL_ATTRIBUTE) or raw_attributes.get(FULL_NAME_ATTRIBUTE): - raise LDAPUserLoginError({"error_message": "LDAP login is invalid."}) - # attempt LDAP bind - username = raw_attributes.get(USERNAME_ATTRIBUTE)[0].decode('utf-8') - email = raw_attributes.get(EMAIL_ATTRIBUTE)[0].decode('utf-8') - full_name = raw_attributes.get(FULL_NAME_ATTRIBUTE)[0].decode('utf-8') - try: - dn = str(bytes(c.response[0].get('dn'), 'utf-8'), encoding='utf-8') - Connection(server, auto_bind=auto_bind, client_strategy=SYNC, - check_names=True, authentication=SIMPLE, - user=dn, password=password) - except Exception as e: - error = "LDAP bind failed: %s" % e - raise LDAPUserLoginError({"error_message": error}) - - # LDAP binding successful, but some values might have changed, or - # this is the user's first login, so return them - return (username, email, full_name) \ No newline at end of file + for ldap_value in server_ldap_dict: + try: + c = Connection(ldap_value, auto_bind=auto_bind, client_strategy=SYNC, check_names=True, + user=service_user, password=service_pass, authentication=service_auth) + server_ldap_dict[ldap_value] = c + data = connect_to_ldap_server(ldap_value, server_ldap_dict, auto_bind, search_filter, password) + except Exception as e: + print("Connection to LDAP server was failed", file=sys.stderr) + continue + if data is not None: + break + raise LDAPUserLoginError({"error_message": "No one server accepted LDAP connection"}) \ No newline at end of file From 4df8f45a1352447c134270ad6efcb5ebc3a1653c Mon Sep 17 00:00:00 2001 From: Emil Date: Wed, 25 Mar 2020 17:26:14 +0300 Subject: [PATCH 08/35] :bug: Fix way to downloading libraries in pip Signed-off-by: Emil --- Dockerfile | 14 +++--- README.md | 2 +- docker-compose.yml | 5 ++- entrypoint.sh | 2 +- local.py | 2 +- taiga_contrib_ldap_auth_ext/apps.py | 1 + taiga_contrib_ldap_auth_ext/connector.py | 54 +++++++++++++++--------- taiga_contrib_ldap_auth_ext/services.py | 9 +--- 8 files changed, 50 insertions(+), 39 deletions(-) diff --git a/Dockerfile b/Dockerfile index a8c36f4..93e3556 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ LABEL maintainer="Monogramm maintainers " # Taiga additional properties ENV TAIGA_ENABLE_LDAP=False \ TAIGA_LDAP_USE_TLS=True \ - TAIGA_LDAP_SERVER= \ + TAIGA_LDAP_SERVERS= \ TAIGA_LDAP_PORT=389 \ TAIGA_LDAP_BIND_DN= \ TAIGA_LDAP_BIND_PASSWORD= \ @@ -17,15 +17,17 @@ ENV TAIGA_ENABLE_LDAP=False \ TAIGA_LDAP_FALLBACK=normal # Erase original entrypoint and conf with custom one -COPY entrypoint.sh ./ COPY local.py /taiga/ +COPY entrypoint.sh ./ + +#CMD sudo python3 setup.py bdist_wheel; + +COPY dist/taiga_contrib_ldap_auth_ext-0.4.4-py3-none-any.whl ./ # Fix entrypoint permissions # Install LDAP extension RUN set -ex; \ chmod 755 /entrypoint.sh; \ - LC_ALL=C pip install --no-cache-dir taiga-contrib-ldap-auth-ext; \ - rm -r /usr/local/lib/python3.6/site-packages/taiga_contrib_ldap_auth_ext - -COPY . /usr/local/lib/python3.6/site-packages/taiga_contrib_ldap_auth_ext + LC_ALL=C pip install --no-cache-dir taiga_contrib_ldap_auth_ext-0.4.4-py3-none-any.whl; + #rm -r /usr/local/lib/python3.6/site-packages/taiga_contrib_ldap_auth_ext # Backend healthcheck HEALTHCHECK CMD curl --fail http://127.0.0.1:8001/api/v1/ || exit 1 \ No newline at end of file diff --git a/README.md b/README.md index a8a7e61..ba83a6b 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Add the following to `settings/local.py`: INSTALLED_APPS += ["taiga_contrib_ldap_auth_ext"] # TODO https://github.com/Monogramm/taiga-contrib-ldap-auth-ext/issues/16 -LDAP_SERVER = ['ldap://ldap.example1.com','ldap://ldap.example2.com'] +LDAP_SERVERS = ['ldap://ldap.example1.com','ldap://ldap.example2.com'] LDAP_PORT = 389 # Flag to enable LDAP with STARTTLS before bind diff --git a/docker-compose.yml b/docker-compose.yml index a567219..27698d1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,6 +34,7 @@ services: - /srv/taiga/back/static:/usr/src/taiga-back/static # Taiga configuration directory. Makes it easier to change configuration with your own #- /srv/taiga/back/conf:/taiga + - /usr/local/lib/python3.6/site-packages/taiga_contrib_ldap_auth_ext - /etc/localtime:/etc/localtime:ro - /etc/timezone:/etc/timezone:ro environment: @@ -81,7 +82,7 @@ services: # LDAP Settings - TAIGA_ENABLE_LDAP=${TAIGA_ENABLE_LDAP} - TAIGA_LDAP_USE_TLS=False - - TAIGA_LDAP_SERVER=ldaps://${TAIGA_LDAP_DOMAIN} + - TAIGA_LDAP_SERVERS=ldaps://${TAIGA_LDAP_DOMAIN_1},ldaps://${TAIGA_LDAP_DOMAIN_2} - TAIGA_LDAP_PORT=636 - TAIGA_LDAP_BIND_DN=${TAIGA_LDAP_BIND_DN} - TAIGA_LDAP_BIND_PASSWORD=${TAIGA_LDAP_BIND_PASSWORD} @@ -197,4 +198,4 @@ services: # restart: always # links: # - taiga_rabbit -# - taiga_redis \ No newline at end of file +# - taiga_redis diff --git a/entrypoint.sh b/entrypoint.sh index c92f95f..1373077 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -60,7 +60,7 @@ GUNICORN_TIMEOUT="${GUINCORN_TIMEOUT:-60}" GUNICORN_WORKERS="${GUNICORN_WORKERS:-4}" GUNICORN_LOGLEVEL="${GUNICORN_LOGLEVEL:-debug}" -GUNICORN_ARGS="--pythonpath=. -t ${GUNICORN_TIMEOUT} --workers ${GUNICORN_WORKERS} --bind ${BIND_ADDRESS}:${PORT} --log-level ${GUNICORN_LOGLEVEL}" +GUNICORN_ARGS=". -t ${GUNICORN_TIMEOUT} --workers ${GUNICORN_WORKERS} --bind ${BIND_ADDRESS}:${PORT} --log-level ${GUNICORN_LOGLEVEL} --reload" if [ -n "${GUNICORN_CERTFILE}" ]; then GUNICORN_ARGS="${GUNICORN_ARGS} --certfile=${GUNICORN_CERTFILE}" diff --git a/local.py b/local.py index 6c86eb6..a1fe2e9 100644 --- a/local.py +++ b/local.py @@ -28,7 +28,7 @@ else: LDAP_START_TLS = False - LDAP_SERVER = os.getenv('TAIGA_LDAP_SERVER') + LDAP_SERVERS = os.getenv('TAIGA_LDAP_SERVERS') LDAP_PORT = int(os.getenv('TAIGA_LDAP_PORT')) # Full DN of the service account use to connect to LDAP server and search for login user's account entry diff --git a/taiga_contrib_ldap_auth_ext/apps.py b/taiga_contrib_ldap_auth_ext/apps.py index 4a054b8..ef3ed3d 100644 --- a/taiga_contrib_ldap_auth_ext/apps.py +++ b/taiga_contrib_ldap_auth_ext/apps.py @@ -10,6 +10,7 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import sys from django.apps import AppConfig diff --git a/taiga_contrib_ldap_auth_ext/connector.py b/taiga_contrib_ldap_auth_ext/connector.py index 294b0d8..a8dc854 100644 --- a/taiga_contrib_ldap_auth_ext/connector.py +++ b/taiga_contrib_ldap_auth_ext/connector.py @@ -31,7 +31,7 @@ class LDAPUserLoginError(LDAPError): # TODO https://github.com/Monogramm/taiga-contrib-ldap-auth-ext/issues/16 -SERVERS = getattr(settings, "LDAP_SERVER", "localhost") +SERVERS = getattr(settings, "LDAP_SERVERS", "localhost") PORT = getattr(settings, "LDAP_PORT", "389") SEARCH_BASE = getattr(settings, "LDAP_SEARCH_BASE", "") @@ -48,12 +48,17 @@ class LDAPUserLoginError(LDAPError): START_TLS = getattr(settings, "LDAP_START_TLS", False) -def connect_to_ldap_server(ldap_value, server_ldap_dict, auto_bind, search_filter, password): - if server_ldap_dict[ldap_value] is None: +def connect_to_ldap_server(ldap_value, connection, auto_bind, search_filter, password): + if connection is None: return try: - cur_connection = server_ldap_dict[ldap_value] - cur_connection.search(search_base=SEARCH_BASE, + print(SEARCH_BASE, flush=True) + print(search_filter, flush=True) + print(SUBTREE, flush=True) + print(USERNAME_ATTRIBUTE, flush=True) + print(EMAIL_ATTRIBUTE, flush=True) + print(FULL_NAME_ATTRIBUTE, flush=True) + connection.search(search_base=SEARCH_BASE, search_filter=search_filter, search_scope=SUBTREE, attributes=[USERNAME_ATTRIBUTE, @@ -65,18 +70,20 @@ def connect_to_ldap_server(ldap_value, server_ldap_dict, auto_bind, search_filte finally: print(error_message, file=sys.stderr) # we are only interested in user objects in the response - cur_connection.response = [r for r in cur_connection.response if 'raw_attributes' in r and 'dn' in r] + print('3', flush=True) + connection.response = [r for r in connection.response if 'raw_attributes' in r and 'dn' in r] + print('4', flush=True) # stop if no search results - if not cur_connection.response: + if not connection.response: raise LDAPUserLoginError({"error_message": "LDAP login not found"}) # handle multiple matches - if len(cur_connection.response) > 1: + if len(connection.response) > 1: raise LDAPUserLoginError( {"error_message": "LDAP login could not be determined."}) # handle missing mandatory attributes - raw_attributes = cur_connection.response[0].get('raw_attributes') + raw_attributes = connection.response[0].get('raw_attributes') if raw_attributes.get(USERNAME_ATTRIBUTE) or raw_attributes.get(EMAIL_ATTRIBUTE) or raw_attributes.get( FULL_NAME_ATTRIBUTE): raise LDAPUserLoginError({"error_message": "LDAP login is invalid."}) @@ -86,10 +93,14 @@ def connect_to_ldap_server(ldap_value, server_ldap_dict, auto_bind, search_filte email = raw_attributes.get(EMAIL_ATTRIBUTE)[0].decode('utf-8') full_name = raw_attributes.get(FULL_NAME_ATTRIBUTE)[0].decode('utf-8') try: - dn = str(bytes(cur_connection.response[0].get('dn'), 'utf-8'), encoding='utf-8') + dn = str(bytes(connection.response[0].get('dn'), 'utf-8'), encoding='utf-8') + print(dn, flush=True) + print("before connection", flush=True) + print(ldap_value, flush=True) Connection(ldap_value, auto_bind=auto_bind, client_strategy=SYNC, check_names=True, authentication=SIMPLE, user=dn, password=password) + print("after connection",flush=True) except Exception as e: error = "LDAP bind failed: %s" % e raise LDAPUserLoginError({"error_message": error}) @@ -114,18 +125,20 @@ def login(username: str, password: str) -> tuple: tls = TLS_CERTS # Dict with server key and connection value - server_ldap_dict = {} - + server_ldap_list = [] # Connect to the LDAP servers - for ser in SERVERS: + servers_list = SERVERS.split(',') + for ser in servers_list: + if len(ser) <=10: + continue if ser.lower().startswith("ldaps://"): use_ssl = True else: use_ssl = False try: - server = Server(ser, port=PORT, get_info=NONE, + server = Server(ser, port=int(PORT), get_info=NONE, use_ssl=use_ssl, tls=tls) - server_ldap_dict[server] = None + server_ldap_list.append(server) except Exception as e: error = "Error connecting to LDAP server: %s" % e raise LDAPConnectionError({"error_message": error}) @@ -150,15 +163,14 @@ def login(username: str, password: str) -> tuple: if SEARCH_FILTER_ADDITIONAL: search_filter = '(&%s%s)' % (search_filter, SEARCH_FILTER_ADDITIONAL) - for ldap_value in server_ldap_dict: + for server in server_ldap_list: try: - c = Connection(ldap_value, auto_bind=auto_bind, client_strategy=SYNC, check_names=True, + c = Connection(server, auto_bind=auto_bind, client_strategy=SYNC, check_names=True, user=service_user, password=service_pass, authentication=service_auth) - server_ldap_dict[ldap_value] = c - data = connect_to_ldap_server(ldap_value, server_ldap_dict, auto_bind, search_filter, password) + data = connect_to_ldap_server(server, c, auto_bind, search_filter, password) except Exception as e: - print("Connection to LDAP server was failed", file=sys.stderr) + print("Connection to LDAP server was failed", flush=True) continue if data is not None: break - raise LDAPUserLoginError({"error_message": "No one server accepted LDAP connection"}) \ No newline at end of file + raise LDAPUserLoginError({"error_message": "No one server accepted LDAP connection"}) \ No newline at end of file diff --git a/taiga_contrib_ldap_auth_ext/services.py b/taiga_contrib_ldap_auth_ext/services.py index 16d814b..905b0a4 100644 --- a/taiga_contrib_ldap_auth_ext/services.py +++ b/taiga_contrib_ldap_auth_ext/services.py @@ -10,6 +10,7 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import sys from django.db import transaction as tx from django.conf import settings @@ -53,7 +54,6 @@ def ldap_login_func(request): # (or any other attribute) login_input = request.DATA.get('username', None) password_input = request.DATA.get('password', None) - try: # TODO: make sure these fields are sanitized before passing to LDAP server! username, email, full_name = connector.login( @@ -91,11 +91,6 @@ def register_or_update(username: str, email: str, full_name: str, password: str) :returns: User """ - print("WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW", file=sys.stderr) - print(username, file=sys.stderr) - print(email, file=sys.stderr) - print(full_name, file=sys.stderr) - print(password, file=sys.stderr) user_model = apps.get_model('users', 'User') username_unique = username @@ -128,7 +123,7 @@ def register_or_update(username: str, email: str, full_name: str, password: str) user.save() user_registered_signal.send(sender=user.__class__, user=user) - send_register_email(user) + #send_register_email(user) else: if SAVE_USER_PASSWD: # Set local password to match LDAP (issues/21) From f08f9aebe30c1153fde5ebd7894e7082106dbb3d Mon Sep 17 00:00:00 2001 From: Emil Date: Wed, 25 Mar 2020 18:00:43 +0300 Subject: [PATCH 09/35] :lipstick: Add server n3 Signed-off-by: Emil --- docker-compose.yml | 2 +- taiga_contrib_ldap_auth_ext/connector.py | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 27698d1..0d79c1c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -82,7 +82,7 @@ services: # LDAP Settings - TAIGA_ENABLE_LDAP=${TAIGA_ENABLE_LDAP} - TAIGA_LDAP_USE_TLS=False - - TAIGA_LDAP_SERVERS=ldaps://${TAIGA_LDAP_DOMAIN_1},ldaps://${TAIGA_LDAP_DOMAIN_2} + - TAIGA_LDAP_SERVERS=ldaps://${TAIGA_LDAP_DOMAIN_1},ldaps://${TAIGA_LDAP_DOMAIN_2},ldaps://${TAIGA_LDAP_DOMAIN_3} - TAIGA_LDAP_PORT=636 - TAIGA_LDAP_BIND_DN=${TAIGA_LDAP_BIND_DN} - TAIGA_LDAP_BIND_PASSWORD=${TAIGA_LDAP_BIND_PASSWORD} diff --git a/taiga_contrib_ldap_auth_ext/connector.py b/taiga_contrib_ldap_auth_ext/connector.py index a8dc854..0f4200b 100644 --- a/taiga_contrib_ldap_auth_ext/connector.py +++ b/taiga_contrib_ldap_auth_ext/connector.py @@ -52,12 +52,7 @@ def connect_to_ldap_server(ldap_value, connection, auto_bind, search_filter, pas if connection is None: return try: - print(SEARCH_BASE, flush=True) - print(search_filter, flush=True) - print(SUBTREE, flush=True) - print(USERNAME_ATTRIBUTE, flush=True) - print(EMAIL_ATTRIBUTE, flush=True) - print(FULL_NAME_ATTRIBUTE, flush=True) + print(connection.response, flush=True) connection.search(search_base=SEARCH_BASE, search_filter=search_filter, search_scope=SUBTREE, @@ -66,9 +61,8 @@ def connect_to_ldap_server(ldap_value, connection, auto_bind, search_filter, pas paged_size=5) except Exception as e: error_message = "LDAP login incorrect: %s" % e + print(error_message, flush=True) raise LDAPUserLoginError({"error_message": error_message}) - finally: - print(error_message, file=sys.stderr) # we are only interested in user objects in the response print('3', flush=True) connection.response = [r for r in connection.response if 'raw_attributes' in r and 'dn' in r] From 0fe9665a05310c02a5a06722e2553d58dd1c0320 Mon Sep 17 00:00:00 2001 From: Emil Date: Wed, 25 Mar 2020 18:13:52 +0300 Subject: [PATCH 10/35] :bug: Return send_register_email function Signed-off-by: Emil --- taiga_contrib_ldap_auth_ext/services.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taiga_contrib_ldap_auth_ext/services.py b/taiga_contrib_ldap_auth_ext/services.py index 905b0a4..cabfbee 100644 --- a/taiga_contrib_ldap_auth_ext/services.py +++ b/taiga_contrib_ldap_auth_ext/services.py @@ -123,7 +123,7 @@ def register_or_update(username: str, email: str, full_name: str, password: str) user.save() user_registered_signal.send(sender=user.__class__, user=user) - #send_register_email(user) + send_register_email(user) else: if SAVE_USER_PASSWD: # Set local password to match LDAP (issues/21) From 4ff923d87af69d3875d6e2666ecde92fe53cfa8e Mon Sep 17 00:00:00 2001 From: Emil Date: Wed, 25 Mar 2020 18:42:41 +0300 Subject: [PATCH 11/35] :sparkles: Automatically creation of package Signed-off-by: Emil --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 93e3556..bd9bf40 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,7 +20,8 @@ ENV TAIGA_ENABLE_LDAP=False \ COPY local.py /taiga/ COPY entrypoint.sh ./ -#CMD sudo python3 setup.py bdist_wheel; +RUN set -ex ; \ + python3 setup.py bdist_wheel; COPY dist/taiga_contrib_ldap_auth_ext-0.4.4-py3-none-any.whl ./ # Fix entrypoint permissions From 7cf1e0eed90c6ae094e34320ff7df4ebf54ae14e Mon Sep 17 00:00:00 2001 From: AminovE99 <32329685+AminovE99@users.noreply.github.com> Date: Thu, 26 Mar 2020 17:59:15 +0300 Subject: [PATCH 12/35] =?UTF-8?q?=F0=9F=90=B3=20Docker/add=20docker=20comp?= =?UTF-8?q?ose=20(#36)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :whale: Add docker-compose.yml Signed-off-by: Emil * :burn: Remove .idea folder * :whale: Copy application to pip3 folder * :bug: Fix error with copy files * :bug: Remove unnecessary ls * :recycle: Fix all remarks * :fire: Remove github and gitlab Signed-off-by: Emil * :loud_sound: Add logs for gunicorn Signed-off-by: Emil * :fire: Remove Unnecessary variables * :wrench: Add another variables in docker-compose Signed-off-by: Emil * :rewind: Restore empty line * :art: Format local.py file * :sparkles: Add mailcatcher Signed-off-by: Emil * :sparkles: Add osixia Signed-off-by: Emil * :see_no_evil: Update .gitignore Signed-off-by: Emil Co-authored-by: Mathieu Brunot --- .env | 50 ++++++++++++++++++++++++++++++++++++++++++++++ Dockerfile | 9 +++++++-- docker-compose.yml | 45 +++++++++++++++++++++++++++++++++-------- local.py | 7 +++++-- 4 files changed, 99 insertions(+), 12 deletions(-) diff --git a/.env b/.env index e69de29..45980dc 100644 --- a/.env +++ b/.env @@ -0,0 +1,50 @@ +# Set all your environment variables here + + +######################################### +## GENERAL +######################################### + +HOSTNAME=localhost +# Adapt to your production domain and use a reverse proxy +#HOSTNAME=taiga.company.com + +PORT=80 + +TAIGA_DB_PWD=somethingverysecure,changethis! + + +######################################### +## SECURITY +######################################### + +TAIGA_SECRET=somethingevenmoresecure!!really,changethis! + +TAIGA_ADMIN_PASSWORD=betterthan123123 + +######################################### +## EMAIL +######################################### + +TAIGA_EMAIL_DOMAIN=company.com +TAIGA_SMTP_USER= +TAIGA_SMTP_PWD= + +######################################### +## LDAP +######################################### + +TAIGA_ENABLE_LDAP=True +# Adapt to your base domain name +TAIGA_LDAP_DOMAIN=ldap.company.com +TAIGA_LDAP_BASE_DN=ou=People,dc=company,dc=com + +TAIGA_LDAP_BIND_DN= +TAIGA_LDAP_BIND_PASSWORD= + +######################################### +## EVENTS +######################################### + +TAIGA_RABBIT_USER=taiga +TAIGA_RABBIT_PASSWORD=somethingverysecure \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index bd9bf40..f01d716 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,14 +21,19 @@ COPY local.py /taiga/ COPY entrypoint.sh ./ RUN set -ex ; \ - python3 setup.py bdist_wheel; + mkdir /usr/src/taiga-contrib-ldap-auth-ext; + +COPY . /usr/src/taiga-contrib-ldap-auth-ext/ + +RUN set -ex ; \ + python /usr/src/taiga-contrib-ldap-auth-ext/setup.py bdist_wheel; COPY dist/taiga_contrib_ldap_auth_ext-0.4.4-py3-none-any.whl ./ # Fix entrypoint permissions # Install LDAP extension RUN set -ex; \ chmod 755 /entrypoint.sh; \ - LC_ALL=C pip install --no-cache-dir taiga_contrib_ldap_auth_ext-0.4.4-py3-none-any.whl; + LC_ALL=C pip install --no-cache-dir /usr/src/taiga-contrib-ldap-auth-ext/dist/taiga_contrib_ldap_auth_ext-0.4.4-py3-none-any.whl; #rm -r /usr/local/lib/python3.6/site-packages/taiga_contrib_ldap_auth_ext # Backend healthcheck HEALTHCHECK CMD curl --fail http://127.0.0.1:8001/api/v1/ || exit 1 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 0d79c1c..c5eef22 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -135,13 +135,6 @@ services: - TAIGA_FEEDBACK_ENABLED=true - TAIGA_SUPPORT_URL=https://tree.taiga.io/support - TAIGA_PRIVACY_POLICY_URL= - - TAIGA_TOS_URL= - - TAIGA_GDPR_URL= - - TAIGA_MAX_UPLOAD_SIZE=104857600 - - TAIGA_CONTRIB_PLUGINS=slack cookie-warning - #- TAIGA_CONTRIB_PLUGINS=slack cookie-warning gitlab-auth github-auth - - TAIGA_GRAVATAR=true - - TAIGA_LOGIN_FORM_TYPE=ldap # Backend settings - TAIGA_BACK_HOST=taiga_back - TAIGA_BACK_PORT=8001 @@ -179,6 +172,42 @@ services: - TAIGA_EVENTS_SECRET=${TAIGA_SECRET} - TAIGA_EVENTS_PORT=8888 + taiga_dev_mailer: + image: sj26/mailcatcher:latest + hostname: taiga_dev_mailer + container_name: taiga_dev_mailer + restart: always + expose: + - 1025 + ports: + - 1080:1080 + volumes: + - /etc/localtime:/etc/localtime:ro + - /etc/timezone:/etc/timezone:ro + + openldap: + image: osixia/openldap:latest +# environment: +# LDAP_LOG_LEVEL: ${LDAP_LOG_LEVEL} +# LDAP_ORGANISATION: ${LDAP_ORGANISATION} +# LDAP_DOMAIN: ${LDAP_DOMAIN} +# LDAP_BASE_DN: ${LDAP_BASE_DN} +# LDAP_ADMIN_PASSWORD: ${LDAP_ADMIN_PASSWORD} +# LDAP_CONFIG_PASSWORD: ${LDAP_CONFIG_PASSWORD} +# LDAP_READONLY_USER: ${LDAP_READONLY_USER} +# LDAP_READONLY_USER_USERNAME: ${LDAP_READONLY_USER_USERNAME} +# LDAP_READONLY_USER_PASSWORD: ${LDAP_READONLY_USER_PASSWORD} +# LDAP_BACKEND: ${LDAP_BACKEND} +# LDAP_TLS: ${LDAP_TLS} +# LDAP_TLS_CRT_FILENAME: ${LDAP_TLS_CRT_FILENAME} +# LDAP_TLS_KEY_FILENAME: ${LDAP_TLS_KEY_FILENAME} +# LDAP_TLS_CA_CRT_FILENAME: ${LDAP_TLS_CA_CRT_FILENAME} +# LDAP_TLS_ENFORCE: ${LDAP_TLS_ENFORCE} +# LDAP_TLS_CIPHER_SUITE: ${LDAP_TLS_CIPHER_SUITE} +# LDAP_TLS_VERIFY_CLIENT: ${LDAP_TLS_VERIFY_CLIENT} +# LDAP_REPLICATION: ${LDAP_REPLICATION} +# LDAP_REMOVE_CONFIG_AFTER_SETUP: ${LDAP_REMOVE_CONFIG_AFTER_SETUP} +# LDAP_SSL_HELPER_PREFIX: ${LDAP_SSL_HELPER_PREFIX} # To enable async mode, uncomment the following lines: # taiga_redis: # image: redis:4.0-alpine @@ -198,4 +227,4 @@ services: # restart: always # links: # - taiga_rabbit -# - taiga_redis +# - taiga_redis \ No newline at end of file diff --git a/local.py b/local.py index a1fe2e9..faf3aff 100644 --- a/local.py +++ b/local.py @@ -28,6 +28,9 @@ else: LDAP_START_TLS = False + # Full DN of the service account. + # Use to connect to LDAP server and search for login user's account entry + # If LDAP_BIND_DN is not specified or blank, then an anonymous bind is attempated LDAP_SERVERS = os.getenv('TAIGA_LDAP_SERVERS') LDAP_PORT = int(os.getenv('TAIGA_LDAP_PORT')) @@ -41,7 +44,7 @@ LDAP_SEARCH_BASE = os.getenv('TAIGA_LDAP_BASE_DN') # Additional search criteria to the filter (will be ANDed) - #LDAP_SEARCH_FILTER_ADDITIONAL = '(mail=*)' + # LDAP_SEARCH_FILTER_ADDITIONAL = '(mail=*)' # Names of attributes to get username, e-mail and full name values from # These fields need to have a value in LDAP @@ -67,5 +70,5 @@ def _ldap_slugify(uid: str) -> str: LDAP_MAP_USERNAME_TO_UID = _ldap_slugify - ## For additional configuration options, look at: + # For additional configuration options, look at: # https://github.com/taigaio/taiga-back/blob/master/settings/local.py.example \ No newline at end of file From 5254dba85f3fd507ef93808f6224aca352433a0c Mon Sep 17 00:00:00 2001 From: Emil Date: Wed, 1 Apr 2020 14:04:52 +0300 Subject: [PATCH 13/35] :fire: Remove sys package Signed-off-by: Emil --- taiga_contrib_ldap_auth_ext/services.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taiga_contrib_ldap_auth_ext/services.py b/taiga_contrib_ldap_auth_ext/services.py index cabfbee..bfb3d4c 100644 --- a/taiga_contrib_ldap_auth_ext/services.py +++ b/taiga_contrib_ldap_auth_ext/services.py @@ -10,7 +10,6 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -import sys from django.db import transaction as tx from django.conf import settings @@ -54,6 +53,7 @@ def ldap_login_func(request): # (or any other attribute) login_input = request.DATA.get('username', None) password_input = request.DATA.get('password', None) + try: # TODO: make sure these fields are sanitized before passing to LDAP server! username, email, full_name = connector.login( From d72eb900ba53a1e5907a385fbbaba1c50f1abccf Mon Sep 17 00:00:00 2001 From: Emil Date: Wed, 1 Apr 2020 15:01:52 +0300 Subject: [PATCH 14/35] :loud_sound: Add info in messages Signed-off-by: Emil --- taiga_contrib_ldap_auth_ext/connector.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/taiga_contrib_ldap_auth_ext/connector.py b/taiga_contrib_ldap_auth_ext/connector.py index 0f4200b..20cdc95 100644 --- a/taiga_contrib_ldap_auth_ext/connector.py +++ b/taiga_contrib_ldap_auth_ext/connector.py @@ -94,7 +94,7 @@ def connect_to_ldap_server(ldap_value, connection, auto_bind, search_filter, pas Connection(ldap_value, auto_bind=auto_bind, client_strategy=SYNC, check_names=True, authentication=SIMPLE, user=dn, password=password) - print("after connection",flush=True) + print("after connection", flush=True) except Exception as e: error = "LDAP bind failed: %s" % e raise LDAPUserLoginError({"error_message": error}) @@ -113,7 +113,6 @@ def login(username: str, password: str) -> tuple: login to LDAP fails. :returns: tuple (username, email, full_name) """ - tls = None if TLS_CERTS: tls = TLS_CERTS @@ -122,15 +121,15 @@ def login(username: str, password: str) -> tuple: server_ldap_list = [] # Connect to the LDAP servers servers_list = SERVERS.split(',') - for ser in servers_list: - if len(ser) <=10: + for server_address in servers_list: + if len(server_address) <= 10: continue - if ser.lower().startswith("ldaps://"): + if server_address.lower().startswith("ldaps://"): use_ssl = True else: use_ssl = False try: - server = Server(ser, port=int(PORT), get_info=NONE, + server = Server(server_address, port=int(PORT), get_info=NONE, use_ssl=use_ssl, tls=tls) server_ldap_list.append(server) except Exception as e: @@ -163,7 +162,7 @@ def login(username: str, password: str) -> tuple: user=service_user, password=service_pass, authentication=service_auth) data = connect_to_ldap_server(server, c, auto_bind, search_filter, password) except Exception as e: - print("Connection to LDAP server was failed", flush=True) + print("Failed to authenticate against LDAP {0}".format(server.name), flush=True) continue if data is not None: break From 9b59332e778ed68758e0156db67ac1bde0330677 Mon Sep 17 00:00:00 2001 From: Emil Date: Wed, 1 Apr 2020 15:09:11 +0300 Subject: [PATCH 15/35] :loud_sound: Change logs Signed-off-by: Emil --- taiga_contrib_ldap_auth_ext/connector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taiga_contrib_ldap_auth_ext/connector.py b/taiga_contrib_ldap_auth_ext/connector.py index 20cdc95..c03ecba 100644 --- a/taiga_contrib_ldap_auth_ext/connector.py +++ b/taiga_contrib_ldap_auth_ext/connector.py @@ -166,4 +166,4 @@ def login(username: str, password: str) -> tuple: continue if data is not None: break - raise LDAPUserLoginError({"error_message": "No one server accepted LDAP connection"}) \ No newline at end of file + raise LDAPUserLoginError({"error_message": "Unable to authenticate user to LDAP server"}) \ No newline at end of file From 474a9a32ff8bc89788e5f74148f5ad9199a289b2 Mon Sep 17 00:00:00 2001 From: Emil Date: Wed, 1 Apr 2020 16:53:35 +0300 Subject: [PATCH 16/35] :whale: Return previous docker with host building whl Signed-off-by: Emil --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index f01d716..389f9c8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,7 +28,7 @@ COPY . /usr/src/taiga-contrib-ldap-auth-ext/ RUN set -ex ; \ python /usr/src/taiga-contrib-ldap-auth-ext/setup.py bdist_wheel; -COPY dist/taiga_contrib_ldap_auth_ext-0.4.4-py3-none-any.whl ./ +# COPY dist/taiga_contrib_ldap_auth_ext-0.4.4-py3-none-any.whl /usr/src/taiga-contrib-ldap-auth-ext/dist/ # Fix entrypoint permissions # Install LDAP extension RUN set -ex; \ From 9b01d785f2e407cb0c191add32cbf9149cdbb7dc Mon Sep 17 00:00:00 2001 From: Emil Date: Thu, 2 Apr 2020 13:48:43 +0300 Subject: [PATCH 17/35] :bug: Fix bug with whl file Signed-off-by: Emil --- Dockerfile | 17 ++++++++--------- docker-compose.yml | 46 ++++++++++++++++++++++------------------------ 2 files changed, 30 insertions(+), 33 deletions(-) diff --git a/Dockerfile b/Dockerfile index 389f9c8..b72ecf1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,24 +16,23 @@ ENV TAIGA_ENABLE_LDAP=False \ TAIGA_LDAP_SAVE_LOGIN_PASSWORD=True \ TAIGA_LDAP_FALLBACK=normal +# Backend healthcheck +HEALTHCHECK CMD curl --fail http://127.0.0.1:8001/api/v1/ || exit 1 + # Erase original entrypoint and conf with custom one COPY local.py /taiga/ COPY entrypoint.sh ./ -RUN set -ex ; \ - mkdir /usr/src/taiga-contrib-ldap-auth-ext; +COPY . /usr/src/taiga-contrib-ldap-auth-ext -COPY . /usr/src/taiga-contrib-ldap-auth-ext/ - -RUN set -ex ; \ - python /usr/src/taiga-contrib-ldap-auth-ext/setup.py bdist_wheel; +ARG BUILD_DATE # COPY dist/taiga_contrib_ldap_auth_ext-0.4.4-py3-none-any.whl /usr/src/taiga-contrib-ldap-auth-ext/dist/ # Fix entrypoint permissions # Install LDAP extension RUN set -ex; \ chmod 755 /entrypoint.sh; \ - LC_ALL=C pip install --no-cache-dir /usr/src/taiga-contrib-ldap-auth-ext/dist/taiga_contrib_ldap_auth_ext-0.4.4-py3-none-any.whl; + cd /usr/src/taiga-contrib-ldap-auth-ext/; \ + python setup.py bdist_wheel; \ + LC_ALL=C pip install --no-cache-dir dist/taiga_contrib_ldap_auth_ext-0.4.4-py3-none-any.whl; #rm -r /usr/local/lib/python3.6/site-packages/taiga_contrib_ldap_auth_ext -# Backend healthcheck -HEALTHCHECK CMD curl --fail http://127.0.0.1:8001/api/v1/ || exit 1 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index c5eef22..631d97f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,8 +19,7 @@ services: build: context: . dockerfile: Dockerfile - # For production - #image: monogramm/docker-taiga-back:4.2-alpine + image: docker-taiga-back-ldap:test hostname: ${HOSTNAME} container_name: taiga_back #restart: always @@ -82,7 +81,7 @@ services: # LDAP Settings - TAIGA_ENABLE_LDAP=${TAIGA_ENABLE_LDAP} - TAIGA_LDAP_USE_TLS=False - - TAIGA_LDAP_SERVERS=ldaps://${TAIGA_LDAP_DOMAIN_1},ldaps://${TAIGA_LDAP_DOMAIN_2},ldaps://${TAIGA_LDAP_DOMAIN_3} + - TAIGA_LDAP_SERVERS="#PYTHON2BASH:['ldaps://${TAIGA_LDAP_DOMAIN_1}','ldaps://${TAIGA_LDAP_DOMAIN_2}','ldaps://${TAIGA_LDAP_DOMAIN_3}']" - TAIGA_LDAP_PORT=636 - TAIGA_LDAP_BIND_DN=${TAIGA_LDAP_BIND_DN} - TAIGA_LDAP_BIND_PASSWORD=${TAIGA_LDAP_BIND_PASSWORD} @@ -187,27 +186,26 @@ services: openldap: image: osixia/openldap:latest -# environment: -# LDAP_LOG_LEVEL: ${LDAP_LOG_LEVEL} -# LDAP_ORGANISATION: ${LDAP_ORGANISATION} -# LDAP_DOMAIN: ${LDAP_DOMAIN} -# LDAP_BASE_DN: ${LDAP_BASE_DN} -# LDAP_ADMIN_PASSWORD: ${LDAP_ADMIN_PASSWORD} -# LDAP_CONFIG_PASSWORD: ${LDAP_CONFIG_PASSWORD} -# LDAP_READONLY_USER: ${LDAP_READONLY_USER} -# LDAP_READONLY_USER_USERNAME: ${LDAP_READONLY_USER_USERNAME} -# LDAP_READONLY_USER_PASSWORD: ${LDAP_READONLY_USER_PASSWORD} -# LDAP_BACKEND: ${LDAP_BACKEND} -# LDAP_TLS: ${LDAP_TLS} -# LDAP_TLS_CRT_FILENAME: ${LDAP_TLS_CRT_FILENAME} -# LDAP_TLS_KEY_FILENAME: ${LDAP_TLS_KEY_FILENAME} -# LDAP_TLS_CA_CRT_FILENAME: ${LDAP_TLS_CA_CRT_FILENAME} -# LDAP_TLS_ENFORCE: ${LDAP_TLS_ENFORCE} -# LDAP_TLS_CIPHER_SUITE: ${LDAP_TLS_CIPHER_SUITE} -# LDAP_TLS_VERIFY_CLIENT: ${LDAP_TLS_VERIFY_CLIENT} -# LDAP_REPLICATION: ${LDAP_REPLICATION} -# LDAP_REMOVE_CONFIG_AFTER_SETUP: ${LDAP_REMOVE_CONFIG_AFTER_SETUP} -# LDAP_SSL_HELPER_PREFIX: ${LDAP_SSL_HELPER_PREFIX} + environment: + #LDAP_LOG_LEVEL: + LDAP_ORGANISATION: ${TAIGA_LDAP_COMPANY} # done + LDAP_BASE_DN: ${TAIGA_LDAP_BASE_DN} # done + LDAP_ADMIN_PASSWORD: ${TAIGA_LDAP_BIND_DN} # done + #LDAP_CONFIG_PASSWORD: ${LDAP_CONFIG_PASSWORD} + #LDAP_READONLY_USER: ${LDAP_READONLY_USER} + #LDAP_READONLY_USER_USERNAME: ${LDAP_READONLY_USER_USERNAME} + #LDAP_READONLY_USER_PASSWORD: ${LDAP_READONLY_USER_PASSWORD} + #LDAP_BACKEND: ${LDAP_BACKEND} + #LDAP_TLS: ${LDAP_TLS} + #LDAP_TLS_CRT_FILENAME: ${LDAP_TLS_CRT_FILENAME} + #LDAP_TLS_KEY_FILENAME: ${LDAP_TLS_KEY_FILENAME} + #LDAP_TLS_CA_CRT_FILENAME: ${LDAP_TLS_CA_CRT_FILENAME} + #LDAP_TLS_ENFORCE: ${LDAP_TLS_ENFORCE} + #LDAP_TLS_CIPHER_SUITE: ${LDAP_TLS_CIPHER_SUITE} + #LDAP_TLS_VERIFY_CLIENT: ${LDAP_TLS_VERIFY_CLIENT} + #LDAP_REPLICATION: ${LDAP_REPLICATION} + #LDAP_REMOVE_CONFIG_AFTER_SETUP: ${LDAP_REMOVE_CONFIG_AFTER_SETUP} + #LDAP_SSL_HELPER_PREFIX: ${LDAP_SSL_HELPER_PREFIX} # To enable async mode, uncomment the following lines: # taiga_redis: # image: redis:4.0-alpine From 55844b93f83e5867705c537d4e30d03f5bf43f6c Mon Sep 17 00:00:00 2001 From: Emil Date: Fri, 3 Apr 2020 12:48:47 +0300 Subject: [PATCH 18/35] :move: Remove unnecessary logs Signed-off-by: Emil --- docker-compose.yml | 7 +++++-- taiga_contrib_ldap_auth_ext/connector.py | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 631d97f..4f15662 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -81,7 +81,7 @@ services: # LDAP Settings - TAIGA_ENABLE_LDAP=${TAIGA_ENABLE_LDAP} - TAIGA_LDAP_USE_TLS=False - - TAIGA_LDAP_SERVERS="#PYTHON2BASH:['ldaps://${TAIGA_LDAP_DOMAIN_1}','ldaps://${TAIGA_LDAP_DOMAIN_2}','ldaps://${TAIGA_LDAP_DOMAIN_3}']" + - TAIGA_LDAP_SERVERS=ldaps://${TAIGA_LDAP_DOMAIN_1},ldaps://${TAIGA_LDAP_DOMAIN_2},ldaps://${TAIGA_LDAP_DOMAIN_3} - TAIGA_LDAP_PORT=636 - TAIGA_LDAP_BIND_DN=${TAIGA_LDAP_BIND_DN} - TAIGA_LDAP_BIND_PASSWORD=${TAIGA_LDAP_BIND_PASSWORD} @@ -186,8 +186,11 @@ services: openldap: image: osixia/openldap:latest + container_name: taiga_openldap + ports: + - "389:389" environment: - #LDAP_LOG_LEVEL: + LDAP_LOG_LEVEL: ${TAIGA_LDAP_LOG_LEVEL} # done LDAP_ORGANISATION: ${TAIGA_LDAP_COMPANY} # done LDAP_BASE_DN: ${TAIGA_LDAP_BASE_DN} # done LDAP_ADMIN_PASSWORD: ${TAIGA_LDAP_BIND_DN} # done diff --git a/taiga_contrib_ldap_auth_ext/connector.py b/taiga_contrib_ldap_auth_ext/connector.py index c03ecba..f3187a6 100644 --- a/taiga_contrib_ldap_auth_ext/connector.py +++ b/taiga_contrib_ldap_auth_ext/connector.py @@ -52,7 +52,7 @@ def connect_to_ldap_server(ldap_value, connection, auto_bind, search_filter, pas if connection is None: return try: - print(connection.response, flush=True) + print(connection.__dict__, flush=True) connection.search(search_base=SEARCH_BASE, search_filter=search_filter, search_scope=SUBTREE, @@ -64,9 +64,7 @@ def connect_to_ldap_server(ldap_value, connection, auto_bind, search_filter, pas print(error_message, flush=True) raise LDAPUserLoginError({"error_message": error_message}) # we are only interested in user objects in the response - print('3', flush=True) connection.response = [r for r in connection.response if 'raw_attributes' in r and 'dn' in r] - print('4', flush=True) # stop if no search results if not connection.response: raise LDAPUserLoginError({"error_message": "LDAP login not found"}) @@ -129,6 +127,7 @@ def login(username: str, password: str) -> tuple: else: use_ssl = False try: + print(server_address, flush=True) server = Server(server_address, port=int(PORT), get_info=NONE, use_ssl=use_ssl, tls=tls) server_ldap_list.append(server) @@ -162,6 +161,7 @@ def login(username: str, password: str) -> tuple: user=service_user, password=service_pass, authentication=service_auth) data = connect_to_ldap_server(server, c, auto_bind, search_filter, password) except Exception as e: + print("Error: {0}".format(e), flush=True) print("Failed to authenticate against LDAP {0}".format(server.name), flush=True) continue if data is not None: From 3c3968197029a7628124352613a61f39415c1988 Mon Sep 17 00:00:00 2001 From: Emil Date: Fri, 3 Apr 2020 15:03:03 +0300 Subject: [PATCH 19/35] :bug: Resolve notes from review Signed-off-by: Emil --- Dockerfile | 2 +- README.md | 2 +- docker-compose.yml | 2 +- local.py | 2 +- taiga_contrib_ldap_auth_ext/apps.py | 1 - taiga_contrib_ldap_auth_ext/connector.py | 11 ++++++++--- 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index b72ecf1..3cb1abb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ LABEL maintainer="Monogramm maintainers " # Taiga additional properties ENV TAIGA_ENABLE_LDAP=False \ TAIGA_LDAP_USE_TLS=True \ - TAIGA_LDAP_SERVERS= \ + TAIGA_LDAP_SERVER= \ TAIGA_LDAP_PORT=389 \ TAIGA_LDAP_BIND_DN= \ TAIGA_LDAP_BIND_PASSWORD= \ diff --git a/README.md b/README.md index ba83a6b..a8a7e61 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Add the following to `settings/local.py`: INSTALLED_APPS += ["taiga_contrib_ldap_auth_ext"] # TODO https://github.com/Monogramm/taiga-contrib-ldap-auth-ext/issues/16 -LDAP_SERVERS = ['ldap://ldap.example1.com','ldap://ldap.example2.com'] +LDAP_SERVER = ['ldap://ldap.example1.com','ldap://ldap.example2.com'] LDAP_PORT = 389 # Flag to enable LDAP with STARTTLS before bind diff --git a/docker-compose.yml b/docker-compose.yml index 4f15662..2f1bd3d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -81,7 +81,7 @@ services: # LDAP Settings - TAIGA_ENABLE_LDAP=${TAIGA_ENABLE_LDAP} - TAIGA_LDAP_USE_TLS=False - - TAIGA_LDAP_SERVERS=ldaps://${TAIGA_LDAP_DOMAIN_1},ldaps://${TAIGA_LDAP_DOMAIN_2},ldaps://${TAIGA_LDAP_DOMAIN_3} + - TAIGA_LDAP_SERVER=ldaps://${TAIGA_LDAP_DOMAIN_1},ldaps://${TAIGA_LDAP_DOMAIN_2},ldaps://${TAIGA_LDAP_DOMAIN_3} - TAIGA_LDAP_PORT=636 - TAIGA_LDAP_BIND_DN=${TAIGA_LDAP_BIND_DN} - TAIGA_LDAP_BIND_PASSWORD=${TAIGA_LDAP_BIND_PASSWORD} diff --git a/local.py b/local.py index faf3aff..2fcad19 100644 --- a/local.py +++ b/local.py @@ -31,7 +31,7 @@ # Full DN of the service account. # Use to connect to LDAP server and search for login user's account entry # If LDAP_BIND_DN is not specified or blank, then an anonymous bind is attempated - LDAP_SERVERS = os.getenv('TAIGA_LDAP_SERVERS') + LDAP_SERVER = os.getenv('TAIGA_LDAP_SERVER') LDAP_PORT = int(os.getenv('TAIGA_LDAP_PORT')) # Full DN of the service account use to connect to LDAP server and search for login user's account entry diff --git a/taiga_contrib_ldap_auth_ext/apps.py b/taiga_contrib_ldap_auth_ext/apps.py index ef3ed3d..4a054b8 100644 --- a/taiga_contrib_ldap_auth_ext/apps.py +++ b/taiga_contrib_ldap_auth_ext/apps.py @@ -10,7 +10,6 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -import sys from django.apps import AppConfig diff --git a/taiga_contrib_ldap_auth_ext/connector.py b/taiga_contrib_ldap_auth_ext/connector.py index f3187a6..1e0a3f5 100644 --- a/taiga_contrib_ldap_auth_ext/connector.py +++ b/taiga_contrib_ldap_auth_ext/connector.py @@ -31,7 +31,7 @@ class LDAPUserLoginError(LDAPError): # TODO https://github.com/Monogramm/taiga-contrib-ldap-auth-ext/issues/16 -SERVERS = getattr(settings, "LDAP_SERVERS", "localhost") +SERVER = getattr(settings, "LDAP_SERVER", "localhost") PORT = getattr(settings, "LDAP_PORT", "389") SEARCH_BASE = getattr(settings, "LDAP_SEARCH_BASE", "") @@ -51,8 +51,9 @@ class LDAPUserLoginError(LDAPError): def connect_to_ldap_server(ldap_value, connection, auto_bind, search_filter, password): if connection is None: return + try: - print(connection.__dict__, flush=True) + connection.search(search_base=SEARCH_BASE, search_filter=search_filter, search_scope=SUBTREE, @@ -118,7 +119,7 @@ def login(username: str, password: str) -> tuple: # Dict with server key and connection value server_ldap_list = [] # Connect to the LDAP servers - servers_list = SERVERS.split(',') + servers_list = SERVER.split(',') for server_address in servers_list: if len(server_address) <= 10: continue @@ -155,10 +156,14 @@ def login(username: str, password: str) -> tuple: if SEARCH_FILTER_ADDITIONAL: search_filter = '(&%s%s)' % (search_filter, SEARCH_FILTER_ADDITIONAL) + print(server_ldap_list, flush=True) for server in server_ldap_list: try: + print('1111') + print('123123', flush=True) c = Connection(server, auto_bind=auto_bind, client_strategy=SYNC, check_names=True, user=service_user, password=service_pass, authentication=service_auth) + print(c.__dict__, flush=True) data = connect_to_ldap_server(server, c, auto_bind, search_filter, password) except Exception as e: print("Error: {0}".format(e), flush=True) From 6dd2386a384d0f92dcb9e6a40affac1a169844a1 Mon Sep 17 00:00:00 2001 From: Emil Date: Fri, 3 Apr 2020 15:19:25 +0300 Subject: [PATCH 20/35] :sparkles: Add testing connection Signed-off-by: Emil --- taiga_contrib_ldap_auth_ext/connector.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/taiga_contrib_ldap_auth_ext/connector.py b/taiga_contrib_ldap_auth_ext/connector.py index 1e0a3f5..8ca8410 100644 --- a/taiga_contrib_ldap_auth_ext/connector.py +++ b/taiga_contrib_ldap_auth_ext/connector.py @@ -159,12 +159,12 @@ def login(username: str, password: str) -> tuple: print(server_ldap_list, flush=True) for server in server_ldap_list: try: - print('1111') - print('123123', flush=True) c = Connection(server, auto_bind=auto_bind, client_strategy=SYNC, check_names=True, user=service_user, password=service_pass, authentication=service_auth) - print(c.__dict__, flush=True) - data = connect_to_ldap_server(server, c, auto_bind, search_filter, password) + if c.result.description == 'success': + data = connect_to_ldap_server(server, c, auto_bind, search_filter, password) + else: + print('Connection to LDAP server refused') except Exception as e: print("Error: {0}".format(e), flush=True) print("Failed to authenticate against LDAP {0}".format(server.name), flush=True) From 88c1418eeda5a498e943edda58305dc1ba85abae Mon Sep 17 00:00:00 2001 From: Emil Date: Fri, 3 Apr 2020 15:20:31 +0300 Subject: [PATCH 21/35] :sparkles: Add flush Signed-off-by: Emil --- taiga_contrib_ldap_auth_ext/connector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taiga_contrib_ldap_auth_ext/connector.py b/taiga_contrib_ldap_auth_ext/connector.py index 8ca8410..af8b8e5 100644 --- a/taiga_contrib_ldap_auth_ext/connector.py +++ b/taiga_contrib_ldap_auth_ext/connector.py @@ -164,7 +164,7 @@ def login(username: str, password: str) -> tuple: if c.result.description == 'success': data = connect_to_ldap_server(server, c, auto_bind, search_filter, password) else: - print('Connection to LDAP server refused') + print('Connection to LDAP server refused',flush=True) except Exception as e: print("Error: {0}".format(e), flush=True) print("Failed to authenticate against LDAP {0}".format(server.name), flush=True) From 11589911b8ede57784181d9f5ea734a6cdcddde4 Mon Sep 17 00:00:00 2001 From: Emil Date: Fri, 3 Apr 2020 15:23:21 +0300 Subject: [PATCH 22/35] :art: Remove unnesserary comments Signed-off-by: Emil --- local.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/local.py b/local.py index 2fcad19..1fb64a7 100644 --- a/local.py +++ b/local.py @@ -28,9 +28,6 @@ else: LDAP_START_TLS = False - # Full DN of the service account. - # Use to connect to LDAP server and search for login user's account entry - # If LDAP_BIND_DN is not specified or blank, then an anonymous bind is attempated LDAP_SERVER = os.getenv('TAIGA_LDAP_SERVER') LDAP_PORT = int(os.getenv('TAIGA_LDAP_PORT')) From 1d331a51332a89eaee40ccd731ec6602e993646c Mon Sep 17 00:00:00 2001 From: Emil Date: Fri, 3 Apr 2020 15:25:43 +0300 Subject: [PATCH 23/35] :art: remove prints Signed-off-by: Emil --- taiga_contrib_ldap_auth_ext/connector.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/taiga_contrib_ldap_auth_ext/connector.py b/taiga_contrib_ldap_auth_ext/connector.py index af8b8e5..515a9d7 100644 --- a/taiga_contrib_ldap_auth_ext/connector.py +++ b/taiga_contrib_ldap_auth_ext/connector.py @@ -87,13 +87,9 @@ def connect_to_ldap_server(ldap_value, connection, auto_bind, search_filter, pas full_name = raw_attributes.get(FULL_NAME_ATTRIBUTE)[0].decode('utf-8') try: dn = str(bytes(connection.response[0].get('dn'), 'utf-8'), encoding='utf-8') - print(dn, flush=True) - print("before connection", flush=True) - print(ldap_value, flush=True) Connection(ldap_value, auto_bind=auto_bind, client_strategy=SYNC, check_names=True, authentication=SIMPLE, user=dn, password=password) - print("after connection", flush=True) except Exception as e: error = "LDAP bind failed: %s" % e raise LDAPUserLoginError({"error_message": error}) @@ -128,7 +124,6 @@ def login(username: str, password: str) -> tuple: else: use_ssl = False try: - print(server_address, flush=True) server = Server(server_address, port=int(PORT), get_info=NONE, use_ssl=use_ssl, tls=tls) server_ldap_list.append(server) @@ -156,7 +151,6 @@ def login(username: str, password: str) -> tuple: if SEARCH_FILTER_ADDITIONAL: search_filter = '(&%s%s)' % (search_filter, SEARCH_FILTER_ADDITIONAL) - print(server_ldap_list, flush=True) for server in server_ldap_list: try: c = Connection(server, auto_bind=auto_bind, client_strategy=SYNC, check_names=True, @@ -164,7 +158,7 @@ def login(username: str, password: str) -> tuple: if c.result.description == 'success': data = connect_to_ldap_server(server, c, auto_bind, search_filter, password) else: - print('Connection to LDAP server refused',flush=True) + print('Connection to LDAP server refused', flush=True) except Exception as e: print("Error: {0}".format(e), flush=True) print("Failed to authenticate against LDAP {0}".format(server.name), flush=True) From 43d8f44e1b1d5ed0689bd207c2cb5acccaa2861d Mon Sep 17 00:00:00 2001 From: Emil Date: Fri, 3 Apr 2020 15:27:25 +0300 Subject: [PATCH 24/35] :art: Add blank lines Signed-off-by: Emil --- taiga_contrib_ldap_auth_ext/connector.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/taiga_contrib_ldap_auth_ext/connector.py b/taiga_contrib_ldap_auth_ext/connector.py index 515a9d7..d14bd8b 100644 --- a/taiga_contrib_ldap_auth_ext/connector.py +++ b/taiga_contrib_ldap_auth_ext/connector.py @@ -102,10 +102,10 @@ def connect_to_ldap_server(ldap_value, connection, auto_bind, search_filter, pas def login(username: str, password: str) -> tuple: """ Connect to LDAP server, perform a search and attempt a bind. - Can raise `exc.LDAPConnectionError` exceptions if the - connection to LDAP fails. + Can raise `exc.LDAPUserLoginError` exceptions if the login to LDAP fails. + :returns: tuple (username, email, full_name) """ tls = None From 00314a71fe7b01aaa65fce633d61513f1045843c Mon Sep 17 00:00:00 2001 From: Emil Date: Mon, 6 Apr 2020 16:36:31 +0300 Subject: [PATCH 25/35] :sparkles: Add mailcatcher Signed-off-by: Emil --- local.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/local.py b/local.py index 1fb64a7..14e4db0 100644 --- a/local.py +++ b/local.py @@ -17,6 +17,14 @@ ######################################### if os.getenv('TAIGA_ENABLE_LDAP').lower() == 'true': + # mailcatcher configuration + EMAIL_HOST = os.getenv('TAIGA_EMAIL_HOST') + EMAIL_HOST_USER = os.getenv('TAIGA_SMTP_USER') + EMAIL_HOST_PASSWORD = os.getenv('TAIGA_EMAIL_PASS') + EMAIL_PORT = 1025 + EMAIL_USE_TLS = False + + # see https://github.com/Monogramm/taiga-contrib-ldap-auth-ext print("Taiga contrib LDAP Auth Ext enabled", file=sys.stderr) INSTALLED_APPS += ["taiga_contrib_ldap_auth_ext"] From a7ec6c77ca5861c9193378bb6862802503973f98 Mon Sep 17 00:00:00 2001 From: Emil Date: Mon, 6 Apr 2020 16:58:11 +0300 Subject: [PATCH 26/35] :bug: Add domain variable Signed-off-by: Emil --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index 2f1bd3d..7b9b929 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -194,6 +194,7 @@ services: LDAP_ORGANISATION: ${TAIGA_LDAP_COMPANY} # done LDAP_BASE_DN: ${TAIGA_LDAP_BASE_DN} # done LDAP_ADMIN_PASSWORD: ${TAIGA_LDAP_BIND_DN} # done + LDAP_DOMAIN: ${TAIGA_LDAP_DOMAIN_1} #LDAP_CONFIG_PASSWORD: ${LDAP_CONFIG_PASSWORD} #LDAP_READONLY_USER: ${LDAP_READONLY_USER} #LDAP_READONLY_USER_USERNAME: ${LDAP_READONLY_USER_USERNAME} From 179fbb5c83312b86e442ecb51b880a1d4b73d005 Mon Sep 17 00:00:00 2001 From: Emil Date: Mon, 6 Apr 2020 17:54:36 +0300 Subject: [PATCH 27/35] :sparkles: Add openldap configuration Signed-off-by: Emil --- docker-compose.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 7b9b929..8e84897 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -190,10 +190,10 @@ services: ports: - "389:389" environment: - LDAP_LOG_LEVEL: ${TAIGA_LDAP_LOG_LEVEL} # done - LDAP_ORGANISATION: ${TAIGA_LDAP_COMPANY} # done - LDAP_BASE_DN: ${TAIGA_LDAP_BASE_DN} # done - LDAP_ADMIN_PASSWORD: ${TAIGA_LDAP_BIND_DN} # done + LDAP_LOG_LEVEL: ${TAIGA_LDAP_LOG_LEVEL} + LDAP_ORGANISATION: ${TAIGA_LDAP_COMPANY} + LDAP_BASE_DN: ${TAIGA_LDAP_BASE_DN} + LDAP_ADMIN_PASSWORD: ${TAIGA_LDAP_BIND_PASSWORD} LDAP_DOMAIN: ${TAIGA_LDAP_DOMAIN_1} #LDAP_CONFIG_PASSWORD: ${LDAP_CONFIG_PASSWORD} #LDAP_READONLY_USER: ${LDAP_READONLY_USER} From 29f2ac8f3d8a499f87e6a07b207ac2dceee70540 Mon Sep 17 00:00:00 2001 From: Emil Date: Mon, 6 Apr 2020 17:55:26 +0300 Subject: [PATCH 28/35] :sparkles: Add env file Signed-off-by: Emil --- .env | 50 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/.env b/.env index 45980dc..ea7a9c7 100644 --- a/.env +++ b/.env @@ -1,6 +1,3 @@ -# Set all your environment variables here - - ######################################### ## GENERAL ######################################### @@ -9,10 +6,8 @@ HOSTNAME=localhost # Adapt to your production domain and use a reverse proxy #HOSTNAME=taiga.company.com -PORT=80 - TAIGA_DB_PWD=somethingverysecure,changethis! - +PORT=8080 ######################################### ## SECURITY @@ -30,21 +25,54 @@ TAIGA_EMAIL_DOMAIN=company.com TAIGA_SMTP_USER= TAIGA_SMTP_PWD= + +######################################### +## GITLAB +######################################### + +TAIGA_ENABLE_GITLAB_AUTH=True +TAIGA_GITLAB_AUTH_URL=https://gitlab.company.com +# Get these from Admin -> Applications +TAIGA_GITLAB_AUTH_CLIENT_ID= +TAIGA_GITLAB_AUTH_CLIENT_SECRET= + + +######################################### +## GITHUB +######################################### + +TAIGA_ENABLE_GITHUB_AUTH=True +# Get these from https://github.com/settings/developers +TAIGA_GITHUB_AUTH_CLIENT_ID= +TAIGA_GITHUB_AUTH_CLIENT_SECRET= + + ######################################### ## LDAP ######################################### TAIGA_ENABLE_LDAP=True # Adapt to your base domain name -TAIGA_LDAP_DOMAIN=ldap.company.com -TAIGA_LDAP_BASE_DN=ou=People,dc=company,dc=com +# If you need only one domain, leave another domains emply +TAIGA_LDAP_DOMAIN_1=openldap +TAIGA_LDAP_DOMAIN_2=openldap +TAIGA_LDAP_DOMAIN_3=openldap + +TAIGA_LDAP_BASE_DN=ou=People,dc=openldap +TAIGA_LDAP_COMPANY=taiga + +TAIGA_LDAP_BIND_DN=cn=admin,dc=openldap +TAIGA_LDAP_BIND_PASSWORD=password + + + + +TAIGA_LDAP_LOG_LEVEL=256 -TAIGA_LDAP_BIND_DN= -TAIGA_LDAP_BIND_PASSWORD= ######################################### ## EVENTS ######################################### TAIGA_RABBIT_USER=taiga -TAIGA_RABBIT_PASSWORD=somethingverysecure \ No newline at end of file +TAIGA_RABBIT_PASSWORD=somethingverysecure From d4416d704dae211e47e5c219338b2e278f636fe9 Mon Sep 17 00:00:00 2001 From: Emil Date: Mon, 6 Apr 2020 17:56:14 +0300 Subject: [PATCH 29/35] :see_no_evil: Remove .env from .gitignore Signed-off-by: Emil --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index f013c47..0c63844 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,4 @@ __pycache__ *.eggs *.egg-info dist -.idea -.env \ No newline at end of file +.idea \ No newline at end of file From 8c430009dd15d521d2112c17258ed532bebc6064 Mon Sep 17 00:00:00 2001 From: Emil Date: Tue, 7 Apr 2020 18:25:26 +0300 Subject: [PATCH 30/35] :wrench: Add ldif files for LDAP Signed-off-by: Emil --- .env | 23 +---------------------- .gitignore | 3 ++- docker-compose.yml | 14 +++++++++----- ldif/00_base.ldif | 22 ++++++++++++++++++++++ ldif/01_user.ldif | 20 ++++++++++++++++++++ 5 files changed, 54 insertions(+), 28 deletions(-) create mode 100644 ldif/00_base.ldif create mode 100644 ldif/01_user.ldif diff --git a/.env b/.env index ea7a9c7..007b542 100644 --- a/.env +++ b/.env @@ -26,27 +26,6 @@ TAIGA_SMTP_USER= TAIGA_SMTP_PWD= -######################################### -## GITLAB -######################################### - -TAIGA_ENABLE_GITLAB_AUTH=True -TAIGA_GITLAB_AUTH_URL=https://gitlab.company.com -# Get these from Admin -> Applications -TAIGA_GITLAB_AUTH_CLIENT_ID= -TAIGA_GITLAB_AUTH_CLIENT_SECRET= - - -######################################### -## GITHUB -######################################### - -TAIGA_ENABLE_GITHUB_AUTH=True -# Get these from https://github.com/settings/developers -TAIGA_GITHUB_AUTH_CLIENT_ID= -TAIGA_GITHUB_AUTH_CLIENT_SECRET= - - ######################################### ## LDAP ######################################### @@ -63,7 +42,7 @@ TAIGA_LDAP_COMPANY=taiga TAIGA_LDAP_BIND_DN=cn=admin,dc=openldap TAIGA_LDAP_BIND_PASSWORD=password - +LDAP_TLS=false diff --git a/.gitignore b/.gitignore index 0c63844..eaf3400 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ __pycache__ *.eggs *.egg-info dist -.idea \ No newline at end of file +.idea +ldif/*.log \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 8e84897..aacfc7c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -55,7 +55,7 @@ services: # To use an external SMTP for emails, fill in these values: - TAIGA_ENABLE_EMAIL=True - TAIGA_EMAIL_FROM=taiga@${TAIGA_EMAIL_DOMAIN} - - TAIGA_EMAIL_USE_TLS=True + - TAIGA_EMAIL_USE_TLS=False - TAIGA_EMAIL_HOST=smtp.${TAIGA_EMAIL_DOMAIN} - TAIGA_EMAIL_PORT=587 - TAIGA_EMAIL_USER=${TAIGA_SMTP_USER} @@ -80,9 +80,9 @@ services: ### Additional parameters # LDAP Settings - TAIGA_ENABLE_LDAP=${TAIGA_ENABLE_LDAP} - - TAIGA_LDAP_USE_TLS=False - - TAIGA_LDAP_SERVER=ldaps://${TAIGA_LDAP_DOMAIN_1},ldaps://${TAIGA_LDAP_DOMAIN_2},ldaps://${TAIGA_LDAP_DOMAIN_3} - - TAIGA_LDAP_PORT=636 + - TAIGA_LDAP_USE_TLS=false + - TAIGA_LDAP_SERVER=ldap://${TAIGA_LDAP_DOMAIN_1},ldap://${TAIGA_LDAP_DOMAIN_2},ldap://${TAIGA_LDAP_DOMAIN_3} + - TAIGA_LDAP_PORT=389 - TAIGA_LDAP_BIND_DN=${TAIGA_LDAP_BIND_DN} - TAIGA_LDAP_BIND_PASSWORD=${TAIGA_LDAP_BIND_PASSWORD} - TAIGA_LDAP_BASE_DN=${TAIGA_LDAP_BASE_DN} @@ -189,18 +189,19 @@ services: container_name: taiga_openldap ports: - "389:389" + - "636:636" environment: LDAP_LOG_LEVEL: ${TAIGA_LDAP_LOG_LEVEL} LDAP_ORGANISATION: ${TAIGA_LDAP_COMPANY} LDAP_BASE_DN: ${TAIGA_LDAP_BASE_DN} LDAP_ADMIN_PASSWORD: ${TAIGA_LDAP_BIND_PASSWORD} LDAP_DOMAIN: ${TAIGA_LDAP_DOMAIN_1} + LDAP_TLS: ${LDAP_TLS} #LDAP_CONFIG_PASSWORD: ${LDAP_CONFIG_PASSWORD} #LDAP_READONLY_USER: ${LDAP_READONLY_USER} #LDAP_READONLY_USER_USERNAME: ${LDAP_READONLY_USER_USERNAME} #LDAP_READONLY_USER_PASSWORD: ${LDAP_READONLY_USER_PASSWORD} #LDAP_BACKEND: ${LDAP_BACKEND} - #LDAP_TLS: ${LDAP_TLS} #LDAP_TLS_CRT_FILENAME: ${LDAP_TLS_CRT_FILENAME} #LDAP_TLS_KEY_FILENAME: ${LDAP_TLS_KEY_FILENAME} #LDAP_TLS_CA_CRT_FILENAME: ${LDAP_TLS_CA_CRT_FILENAME} @@ -210,6 +211,9 @@ services: #LDAP_REPLICATION: ${LDAP_REPLICATION} #LDAP_REMOVE_CONFIG_AFTER_SETUP: ${LDAP_REMOVE_CONFIG_AFTER_SETUP} #LDAP_SSL_HELPER_PREFIX: ${LDAP_SSL_HELPER_PREFIX} + volumes: + - ./ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom + # To enable async mode, uncomment the following lines: # taiga_redis: # image: redis:4.0-alpine diff --git a/ldif/00_base.ldif b/ldif/00_base.ldif new file mode 100644 index 0000000..59a00d2 --- /dev/null +++ b/ldif/00_base.ldif @@ -0,0 +1,22 @@ +# Create an initial structure for LDAP +version: 1 + +# Services base +dn: ou=Services,{{ LDAP_BASE_DN }} +objectClass: top +objectClass: organizationalUnit +ou: Services + + +# Groups base +dn: ou=Groups,{{ LDAP_BASE_DN }} +objectClass: top +objectClass: organizationalUnit +ou: Groups + + +# Users base +dn: ou=People,{{ LDAP_BASE_DN }} +objectClass: top +objectClass: organizationalUnit +ou: People diff --git a/ldif/01_user.ldif b/ldif/01_user.ldif new file mode 100644 index 0000000..b3ce5ef --- /dev/null +++ b/ldif/01_user.ldif @@ -0,0 +1,20 @@ +# Create an initial structure for LDAP +version: 1 + +# Sample User +dn: uid=test_user,ou=People,{{ LDAP_BASE_DN }} +objectClass: extensibleObject +objectClass: person +objectClass: organizationalPerson +objectClass: inetOrgPerson +objectClass: top +cn: test_user +sn: LDAP +uid: test_user +displayName: Taiga LDAP +givenName: Taiga +userPassword: test_user_password +o: Taiga +ou: Test +title: Test User +description: Sample Test User for Taiga LDAP contrib \ No newline at end of file From 9d39c39953b92989ad35382ea77047b0ad86f92b Mon Sep 17 00:00:00 2001 From: Emil Date: Wed, 8 Apr 2020 12:38:57 +0300 Subject: [PATCH 31/35] :wrench: Add debug true Signed-off-by: Emil --- docker-compose.yml | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index aacfc7c..57ef10e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -125,8 +125,8 @@ services: #- TAIGA_SSL_BY_REVERSE_PROXY=True #- TAIGA_BACKEND_SSL=True # Frontend settings - - TAIGA_DEBUG=false - - TAIGA_DEBUG_INFO=false + - TAIGA_DEBUG=true + - TAIGA_DEBUG_INFO=true - TAIGA_DEFAULT_LANGUAGE=en - TAIGA_THEMES=taiga material-design high-contrast - TAIGA_DEFAULT_THEME=taiga @@ -134,6 +134,7 @@ services: - TAIGA_FEEDBACK_ENABLED=true - TAIGA_SUPPORT_URL=https://tree.taiga.io/support - TAIGA_PRIVACY_POLICY_URL= + - TAIGA_LOGIN_FORM_TYPE=ldap # Backend settings - TAIGA_BACK_HOST=taiga_back - TAIGA_BACK_PORT=8001 @@ -197,20 +198,6 @@ services: LDAP_ADMIN_PASSWORD: ${TAIGA_LDAP_BIND_PASSWORD} LDAP_DOMAIN: ${TAIGA_LDAP_DOMAIN_1} LDAP_TLS: ${LDAP_TLS} - #LDAP_CONFIG_PASSWORD: ${LDAP_CONFIG_PASSWORD} - #LDAP_READONLY_USER: ${LDAP_READONLY_USER} - #LDAP_READONLY_USER_USERNAME: ${LDAP_READONLY_USER_USERNAME} - #LDAP_READONLY_USER_PASSWORD: ${LDAP_READONLY_USER_PASSWORD} - #LDAP_BACKEND: ${LDAP_BACKEND} - #LDAP_TLS_CRT_FILENAME: ${LDAP_TLS_CRT_FILENAME} - #LDAP_TLS_KEY_FILENAME: ${LDAP_TLS_KEY_FILENAME} - #LDAP_TLS_CA_CRT_FILENAME: ${LDAP_TLS_CA_CRT_FILENAME} - #LDAP_TLS_ENFORCE: ${LDAP_TLS_ENFORCE} - #LDAP_TLS_CIPHER_SUITE: ${LDAP_TLS_CIPHER_SUITE} - #LDAP_TLS_VERIFY_CLIENT: ${LDAP_TLS_VERIFY_CLIENT} - #LDAP_REPLICATION: ${LDAP_REPLICATION} - #LDAP_REMOVE_CONFIG_AFTER_SETUP: ${LDAP_REMOVE_CONFIG_AFTER_SETUP} - #LDAP_SSL_HELPER_PREFIX: ${LDAP_SSL_HELPER_PREFIX} volumes: - ./ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom From 0fcb2c12167743f412b8ca54ce13d8d5dd71a5c0 Mon Sep 17 00:00:00 2001 From: Emil Date: Wed, 8 Apr 2020 12:40:15 +0300 Subject: [PATCH 32/35] :truck: Rename container openldap Signed-off-by: Emil --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 57ef10e..e2fd682 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -185,7 +185,7 @@ services: - /etc/localtime:/etc/localtime:ro - /etc/timezone:/etc/timezone:ro - openldap: + taiga_openldap: image: osixia/openldap:latest container_name: taiga_openldap ports: From 6056df41226a963385889f7251efa8417cdbc1f2 Mon Sep 17 00:00:00 2001 From: Emil Date: Tue, 21 Apr 2020 17:46:17 +0300 Subject: [PATCH 33/35] :recycle: Tested list of ldap servers Signed-off-by: Emil --- .env | 5 +- docker-compose.yml | 58 +++++++++--------------- ldif/00_base.ldif | 8 ++-- ldif/01_user.ldif | 24 +++------- taiga_contrib_ldap_auth_ext/connector.py | 45 +++++++++--------- 5 files changed, 58 insertions(+), 82 deletions(-) diff --git a/.env b/.env index 007b542..e9d5533 100644 --- a/.env +++ b/.env @@ -37,16 +37,17 @@ TAIGA_LDAP_DOMAIN_1=openldap TAIGA_LDAP_DOMAIN_2=openldap TAIGA_LDAP_DOMAIN_3=openldap -TAIGA_LDAP_BASE_DN=ou=People,dc=openldap +TAIGA_LDAP_BASE_DN=dc=openldap TAIGA_LDAP_COMPANY=taiga TAIGA_LDAP_BIND_DN=cn=admin,dc=openldap TAIGA_LDAP_BIND_PASSWORD=password LDAP_TLS=false +LDAP_REMOVE_CONFIG_AFTER_SETUP=False -TAIGA_LDAP_LOG_LEVEL=256 +TAIGA_LDAP_LOG_LEVEL=trace ######################################### diff --git a/docker-compose.yml b/docker-compose.yml index e2fd682..917bb23 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -85,7 +85,7 @@ services: - TAIGA_LDAP_PORT=389 - TAIGA_LDAP_BIND_DN=${TAIGA_LDAP_BIND_DN} - TAIGA_LDAP_BIND_PASSWORD=${TAIGA_LDAP_BIND_PASSWORD} - - TAIGA_LDAP_BASE_DN=${TAIGA_LDAP_BASE_DN} + - TAIGA_LDAP_BASE_DN=ou=People,${TAIGA_LDAP_BASE_DN} - TAIGA_LDAP_USERNAME_ATTRIBUTE=uid - TAIGA_LDAP_EMAIL_ATTRIBUTE=mail - TAIGA_LDAP_FULL_NAME_ATTRIBUTE=displayName @@ -172,25 +172,13 @@ services: - TAIGA_EVENTS_SECRET=${TAIGA_SECRET} - TAIGA_EVENTS_PORT=8888 - taiga_dev_mailer: - image: sj26/mailcatcher:latest - hostname: taiga_dev_mailer - container_name: taiga_dev_mailer - restart: always - expose: - - 1025 - ports: - - 1080:1080 - volumes: - - /etc/localtime:/etc/localtime:ro - - /etc/timezone:/etc/timezone:ro - taiga_openldap: image: osixia/openldap:latest container_name: taiga_openldap + command: --copy-service ports: - - "389:389" - - "636:636" + - "389:389" + - "636:636" environment: LDAP_LOG_LEVEL: ${TAIGA_LDAP_LOG_LEVEL} LDAP_ORGANISATION: ${TAIGA_LDAP_COMPANY} @@ -198,26 +186,24 @@ services: LDAP_ADMIN_PASSWORD: ${TAIGA_LDAP_BIND_PASSWORD} LDAP_DOMAIN: ${TAIGA_LDAP_DOMAIN_1} LDAP_TLS: ${LDAP_TLS} + LDAP_REMOVE_CONFIG_AFTER_SETUP : "false" + HOSTMANE: "openldap" volumes: - ./ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom + restart: on-failure + + taiga_dev_mailer: + image: sj26/mailcatcher:latest + hostname: taiga_dev_mailer + container_name: taiga_dev_mailer + restart: always + expose: + - 1025 + ports: + - 1080:1080 + volumes: + - /etc/localtime:/etc/localtime:ro + - /etc/timezone:/etc/timezone:ro - # To enable async mode, uncomment the following lines: -# taiga_redis: -# image: redis:4.0-alpine -# container_name: taiga_redis -# restart: always -# ports: -# - 6379:6379 -# volumes: -# - /srv/taiga/redis/:/data -# - /etc/localtime:/etc/localtime:ro -# - /etc/timezone:/etc/timezone:ro -# -# # FIXME Celery docker image is deprecated since 2017 -# taiga_celery: -# image: celery -# container_name: taiga_celery -# restart: always -# links: -# - taiga_rabbit -# - taiga_redis \ No newline at end of file +networks: + default: \ No newline at end of file diff --git a/ldif/00_base.ldif b/ldif/00_base.ldif index 59a00d2..70ae911 100644 --- a/ldif/00_base.ldif +++ b/ldif/00_base.ldif @@ -2,21 +2,21 @@ version: 1 # Services base -dn: ou=Services,{{ LDAP_BASE_DN }} +dn: ou=Services,dc=openldap objectClass: top objectClass: organizationalUnit ou: Services # Groups base -dn: ou=Groups,{{ LDAP_BASE_DN }} +dn: ou=Groups,dc=openldap objectClass: top objectClass: organizationalUnit ou: Groups # Users base -dn: ou=People,{{ LDAP_BASE_DN }} +dn: ou=People,dc=openldap objectClass: top objectClass: organizationalUnit -ou: People +ou: People \ No newline at end of file diff --git a/ldif/01_user.ldif b/ldif/01_user.ldif index b3ce5ef..d9ee2f4 100644 --- a/ldif/01_user.ldif +++ b/ldif/01_user.ldif @@ -1,20 +1,10 @@ -# Create an initial structure for LDAP version: 1 -# Sample User -dn: uid=test_user,ou=People,{{ LDAP_BASE_DN }} -objectClass: extensibleObject -objectClass: person -objectClass: organizationalPerson -objectClass: inetOrgPerson +dn: uid=test_user2,ou=People,dc=openldap +objectClass: mailAccount +objectClass: uidObject +objectClass: organizationalUnit objectClass: top -cn: test_user -sn: LDAP -uid: test_user -displayName: Taiga LDAP -givenName: Taiga -userPassword: test_user_password -o: Taiga -ou: Test -title: Test User -description: Sample Test User for Taiga LDAP contrib \ No newline at end of file +mail: test_user2 +ou: People +uid: test_user2 \ No newline at end of file diff --git a/taiga_contrib_ldap_auth_ext/connector.py b/taiga_contrib_ldap_auth_ext/connector.py index d14bd8b..4d5f727 100644 --- a/taiga_contrib_ldap_auth_ext/connector.py +++ b/taiga_contrib_ldap_auth_ext/connector.py @@ -48,18 +48,20 @@ class LDAPUserLoginError(LDAPError): START_TLS = getattr(settings, "LDAP_START_TLS", False) -def connect_to_ldap_server(ldap_value, connection, auto_bind, search_filter, password): +def find_data_from_ldap_server(connection, search_filter): if connection is None: return try: - + print(SEARCH_BASE) + print(search_filter) connection.search(search_base=SEARCH_BASE, - search_filter=search_filter, - search_scope=SUBTREE, - attributes=[USERNAME_ATTRIBUTE, - EMAIL_ATTRIBUTE, FULL_NAME_ATTRIBUTE], - paged_size=5) + search_filter=search_filter, + search_scope=SUBTREE, + attributes=[USERNAME_ATTRIBUTE, + EMAIL_ATTRIBUTE, FULL_NAME_ATTRIBUTE], + paged_size=5) + print(connection.response) except Exception as e: error_message = "LDAP login incorrect: %s" % e print(error_message, flush=True) @@ -77,23 +79,19 @@ def connect_to_ldap_server(ldap_value, connection, auto_bind, search_filter, pas # handle missing mandatory attributes raw_attributes = connection.response[0].get('raw_attributes') + print("1" + str(raw_attributes.get(USERNAME_ATTRIBUTE))) + print("2" + str(raw_attributes.get(EMAIL_ATTRIBUTE))) + print("3" + str(raw_attributes.get(FULL_NAME_ATTRIBUTE))) if raw_attributes.get(USERNAME_ATTRIBUTE) or raw_attributes.get(EMAIL_ATTRIBUTE) or raw_attributes.get( FULL_NAME_ATTRIBUTE): - raise LDAPUserLoginError({"error_message": "LDAP login is invalid."}) + print('wtf???') + #raise LDAPUserLoginError({"error_message": "LDAP login is invalid."}) # attempt LDAP bind username = raw_attributes.get(USERNAME_ATTRIBUTE)[0].decode('utf-8') email = raw_attributes.get(EMAIL_ATTRIBUTE)[0].decode('utf-8') full_name = raw_attributes.get(FULL_NAME_ATTRIBUTE)[0].decode('utf-8') - try: - dn = str(bytes(connection.response[0].get('dn'), 'utf-8'), encoding='utf-8') - Connection(ldap_value, auto_bind=auto_bind, client_strategy=SYNC, - check_names=True, authentication=SIMPLE, - user=dn, password=password) - except Exception as e: - error = "LDAP bind failed: %s" % e - raise LDAPUserLoginError({"error_message": error}) - + full_name = 'boris' # LDAP binding successful, but some values might have changed, or # this is the user's first login, so return them return username, email, full_name @@ -112,6 +110,8 @@ def login(username: str, password: str) -> tuple: if TLS_CERTS: tls = TLS_CERTS + + # Dict with server key and connection value server_ldap_list = [] # Connect to the LDAP servers @@ -124,7 +124,8 @@ def login(username: str, password: str) -> tuple: else: use_ssl = False try: - server = Server(server_address, port=int(PORT), get_info=NONE, + server_address = 'taiga_openldap' + server = Server(host=server_address, port=int(PORT), get_info=NONE, use_ssl=use_ssl, tls=tls) server_ldap_list.append(server) except Exception as e: @@ -155,14 +156,12 @@ def login(username: str, password: str) -> tuple: try: c = Connection(server, auto_bind=auto_bind, client_strategy=SYNC, check_names=True, user=service_user, password=service_pass, authentication=service_auth) - if c.result.description == 'success': - data = connect_to_ldap_server(server, c, auto_bind, search_filter, password) - else: - print('Connection to LDAP server refused', flush=True) + data = find_data_from_ldap_server(c, search_filter) + print(data) except Exception as e: print("Error: {0}".format(e), flush=True) print("Failed to authenticate against LDAP {0}".format(server.name), flush=True) continue if data is not None: break - raise LDAPUserLoginError({"error_message": "Unable to authenticate user to LDAP server"}) \ No newline at end of file + raise LDAPUserLoginError({"error_message": "Unable to authenticate user to LDAP server"}) From cbbc51be09e97832c2272e2f0349921e9cc5cd92 Mon Sep 17 00:00:00 2001 From: Emil Date: Tue, 21 Apr 2020 17:47:50 +0300 Subject: [PATCH 34/35] :fire: Remove print from connectors.py Signed-off-by: Emil --- taiga_contrib_ldap_auth_ext/connector.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/taiga_contrib_ldap_auth_ext/connector.py b/taiga_contrib_ldap_auth_ext/connector.py index 4d5f727..6e950df 100644 --- a/taiga_contrib_ldap_auth_ext/connector.py +++ b/taiga_contrib_ldap_auth_ext/connector.py @@ -79,19 +79,14 @@ def find_data_from_ldap_server(connection, search_filter): # handle missing mandatory attributes raw_attributes = connection.response[0].get('raw_attributes') - print("1" + str(raw_attributes.get(USERNAME_ATTRIBUTE))) - print("2" + str(raw_attributes.get(EMAIL_ATTRIBUTE))) - print("3" + str(raw_attributes.get(FULL_NAME_ATTRIBUTE))) if raw_attributes.get(USERNAME_ATTRIBUTE) or raw_attributes.get(EMAIL_ATTRIBUTE) or raw_attributes.get( FULL_NAME_ATTRIBUTE): - print('wtf???') - #raise LDAPUserLoginError({"error_message": "LDAP login is invalid."}) + raise LDAPUserLoginError({"error_message": "LDAP login is invalid."}) # attempt LDAP bind username = raw_attributes.get(USERNAME_ATTRIBUTE)[0].decode('utf-8') email = raw_attributes.get(EMAIL_ATTRIBUTE)[0].decode('utf-8') full_name = raw_attributes.get(FULL_NAME_ATTRIBUTE)[0].decode('utf-8') - full_name = 'boris' # LDAP binding successful, but some values might have changed, or # this is the user's first login, so return them return username, email, full_name From dbba0b7695145b8314a3513dfc3598e04d41cdc0 Mon Sep 17 00:00:00 2001 From: Emil Date: Mon, 27 Apr 2020 13:53:18 +0300 Subject: [PATCH 35/35] :bug: Remove variable from connector Signed-off-by: Emil --- taiga_contrib_ldap_auth_ext/connector.py | 1 - 1 file changed, 1 deletion(-) diff --git a/taiga_contrib_ldap_auth_ext/connector.py b/taiga_contrib_ldap_auth_ext/connector.py index 6e950df..6d92246 100644 --- a/taiga_contrib_ldap_auth_ext/connector.py +++ b/taiga_contrib_ldap_auth_ext/connector.py @@ -119,7 +119,6 @@ def login(username: str, password: str) -> tuple: else: use_ssl = False try: - server_address = 'taiga_openldap' server = Server(host=server_address, port=int(PORT), get_info=NONE, use_ssl=use_ssl, tls=tls) server_ldap_list.append(server)