Integrate an LDAP account database with Taiga

Yes. Interesting, I do not think the error message is coming from the LDAP plugin (the error messages raised by the plugin should be coming from this part of the source code, where I cannot find the “No active account” string).

As your frontend configuration looks intact (i.e., the request body should almost certainly be something like {"username":"user","password":"pass","type":"ldap"}, my suspicion is that it is a problem with the backend. To me it feels like the plugin code isn’t even run.

I suspect you included the taiga-back configuration as specified, esp. with a INSTALLED_APPS += ["taiga_contrib_ldap_auth_ext"] line?

@TuringTux my config.append.py from custom-back directory

$ cat config.append.py 
INSTALLED_APPS += ["taiga_contrib_ldap_auth_ext"]

# Multiple LDAP servers are currently not supported, see
# https://github.com/Monogramm/taiga-contrib-ldap-auth-ext/issues/16
LDAP_SERVER = "ldap://url"
LDAP_PORT = 389

LDAP_BIND_DN = "CN=*******"
LDAP_BIND_PASSWORD = "pass"

LDAP_SEARCH_BASE = 'OU=Utilisateurs,****'

LDAP_USERNAME_ATTRIBUTE = "cn"
LDAP_EMAIL_ATTRIBUTE = "mail"
LDAP_FULL_NAME_ATTRIBUTE = "givenName"

LDAP_SAVE_LOGIN_PASSWORD = False

LDAP_MAP_USERNAME_TO_UID = True

Oh, I think I have found the problem then:

LDAP_MAP_USERNAME_TO_UID = True will never work. This property has to be set to a function or None.

Try replacing the last line with:

LDAP_MAP_USERNAME_TO_UID = None

Detailed explanation

See also: README, section “Additional configuration options”

This configuration option can be used to specify a custom SLUGIFY function. This is used to determine the username the user should have in Taiga from the LDAP username. Most setups don’t need to use a SLUGIFY function. For this, the LDAP_MAP_USERNAME_TO_UID attribute has to be present in the configuration, but set to something falsy (e.g. None)

@TuringTux I change it yesterday but result is same

Oh, disappointing. Still, please leave it at LDAP_MAP_USERNAME_TO_UID = None for all further debugging, because with the other option, it will not work.

Was the server response you provided me with the parameter set to None, or to True?

And: Could you please also check that the contents from config.append.py have actually made it to the Docker container you are running, e.g., using this command?

docker exec -it taiga-taiga-back-1 cat /taiga-back/settings/config.py

The complete file config.py :slight_smile:

WARN[0000] /home/taiga/preprod/docker-compose.yml: the attribute version is obsolete, it will be ignored, please remove it to avoid potential confusion

-- coding: utf-8 --

This Source Code Form is subject to the terms of the Mozilla Public

License, v. 2.0. If a copy of the MPL was not distributed with this

file, You can obtain one at Mozilla Public License, version 2.0.

Copyright (c) 2021-present Kaleidos INC

from .common import *
import os

#########################################

GENERIC

#########################################

DEBUG = os.getenv(‘DEBUG’, ‘False’) == ‘True’

DATABASES = {
‘default’: {
‘ENGINE’: ‘django.db.backends.postgresql’,
‘NAME’: os.getenv(‘POSTGRES_DB’),
‘USER’: os.getenv(‘POSTGRES_USER’),
‘PASSWORD’: os.getenv(‘POSTGRES_PASSWORD’),
‘HOST’: os.getenv(‘POSTGRES_HOST’),
‘PORT’: os.getenv(‘POSTGRES_PORT’,‘5432’),
‘OPTIONS’: {‘sslmode’: os.getenv(‘POSTGRES_SSLMODE’,‘disable’)},
‘DISABLE_SERVER_SIDE_CURSORS’: os.getenv(‘POSTGRES_DISABLE_SERVER_SIDE_CURSORS’, ‘False’) == ‘True’,
}
}
SECRET_KEY = os.getenv(‘TAIGA_SECRET_KEY’)

TAIGA_SITES_SCHEME = os.getenv(‘TAIGA_SITES_SCHEME’)
TAIGA_SITES_DOMAIN = os.getenv(‘TAIGA_SITES_DOMAIN’)
FORCE_SCRIPT_NAME = os.getenv(‘TAIGA_SUBPATH’, ‘’)

TAIGA_URL = f"{ TAIGA_SITES_SCHEME }://{ TAIGA_SITES_DOMAIN }{ FORCE_SCRIPT_NAME }"
SITES = {
“api”: { “name”: “api”, “scheme”: TAIGA_SITES_SCHEME, “domain”: TAIGA_SITES_DOMAIN },
“front”: { “name”: “front”, “scheme”: TAIGA_SITES_SCHEME, “domain”: f"{ TAIGA_SITES_DOMAIN }{ FORCE_SCRIPT_NAME }" }
}

LANGUAGE_CODE = os.getenv(“LANGUAGE_CODE”, “en-us”)

INSTANCE_TYPE = “D”

WEBHOOKS_ENABLED = os.getenv(‘WEBHOOKS_ENABLED’, ‘True’) == ‘True’
WEBHOOKS_ALLOW_PRIVATE_ADDRESS = os.getenv(‘WEBHOOKS_ALLOW_PRIVATE_ADDRESS’, ‘False’) == ‘True’
WEBHOOKS_ALLOW_REDIRECTS = os.getenv(‘WEBHOOKS_ALLOW_REDIRECTS’, ‘False’) == ‘True’

Setting DEFAULT_PROJECT_SLUG_PREFIX to false

removes the username from project slug

DEFAULT_PROJECT_SLUG_PREFIX = os.getenv(‘DEFAULT_PROJECT_SLUG_PREFIX’, ‘False’) == ‘True’

#########################################

MEDIA

#########################################
MEDIA_URL = f"{ TAIGA_URL }/media/"
DEFAULT_FILE_STORAGE = “taiga_contrib_protected.storage.ProtectedFileSystemStorage”
THUMBNAIL_DEFAULT_STORAGE = DEFAULT_FILE_STORAGE

STATIC_URL = f"{ TAIGA_URL }/static/"

#########################################

EMAIL

#########################################

EMAIL_BACKEND = os.getenv(‘EMAIL_BACKEND’, ‘django.core.mail.backends.console.EmailBackend’)
CHANGE_NOTIFICATIONS_MIN_INTERVAL = 120 # seconds

DEFAULT_FROM_EMAIL = os.getenv(‘DEFAULT_FROM_EMAIL’, ‘system@taiga.io’)
EMAIL_USE_TLS = os.getenv(‘EMAIL_USE_TLS’, ‘False’) == ‘True’
EMAIL_USE_SSL = os.getenv(‘EMAIL_USE_SSL’, ‘False’) == ‘True’
EMAIL_HOST = os.getenv(‘EMAIL_HOST’, ‘localhost’)
EMAIL_PORT = os.getenv(‘EMAIL_PORT’, 587)
EMAIL_HOST_USER = os.getenv(‘EMAIL_HOST_USER’, ‘user’)
EMAIL_HOST_PASSWORD = os.getenv(‘EMAIL_HOST_PASSWORD’, ‘password’)

#########################################

SESSION

#########################################
SESSION_COOKIE_SECURE = os.getenv(‘SESSION_COOKIE_SECURE’, ‘True’) == ‘True’
CSRF_COOKIE_SECURE = os.getenv(‘CSRF_COOKIE_SECURE’, ‘True’) == ‘True’

#########################################

EVENTS

#########################################
EVENTS_PUSH_BACKEND = “taiga.events.backends.rabbitmq.EventsPushBackend”

EVENTS_PUSH_BACKEND_URL = os.getenv(‘EVENTS_PUSH_BACKEND_URL’)
if not EVENTS_PUSH_BACKEND_URL:
EVENTS_PUSH_BACKEND_URL = f"amqp://{ os.getenv(‘RABBITMQ_USER’) }:{ os.getenv(‘RABBITMQ_PASS’) }@{ os.getenv(‘TAIGA_EVENTS_RABBITMQ_HOST’, ‘taiga-events-rabbitmq’) }:5672/taiga"

EVENTS_PUSH_BACKEND_OPTIONS = {
“url”: EVENTS_PUSH_BACKEND_URL
}

#########################################

TAIGA ASYNC

#########################################
CELERY_ENABLED = os.getenv(‘CELERY_ENABLED’, ‘True’) == ‘True’
from kombu import Queue # noqa

CELERY_BROKER_URL = os.getenv(‘CELERY_BROKER_URL’)
if not CELERY_BROKER_URL:
CELERY_BROKER_URL = f"amqp://{ os.getenv(‘RABBITMQ_USER’) }:{ os.getenv(‘RABBITMQ_PASS’) }@{ os.getenv(‘TAIGA_ASYNC_RABBITMQ_HOST’, ‘taiga-async-rabbitmq’) }:5672/taiga"

CELERY_RESULT_BACKEND = None # for a general installation, we don’t need to store the results
CELERY_ACCEPT_CONTENT = [‘pickle’, ] # Values are ‘pickle’, ‘json’, ‘msgpack’ and ‘yaml’
CELERY_TASK_SERIALIZER = “pickle”
CELERY_RESULT_SERIALIZER = “pickle”
CELERY_TIMEZONE = os.getenv(‘CELERY_TIMEZONE’, ‘Europe/Madrid’)
CELERY_TASK_DEFAULT_QUEUE = ‘tasks’
CELERY_QUEUES = (
Queue(‘tasks’, routing_key=‘task.#’),
Queue(‘transient’, routing_key=‘transient.#’, delivery_mode=1)
)
CELERY_TASK_DEFAULT_EXCHANGE = ‘tasks’
CELERY_TASK_DEFAULT_EXCHANGE_TYPE = ‘topic’
CELERY_TASK_DEFAULT_ROUTING_KEY = ‘task.default’

#########################################

REGISTRATION

#########################################
PUBLIC_REGISTER_ENABLED = os.getenv(‘PUBLIC_REGISTER_ENABLED’, ‘False’) == ‘True’

#########################################

CONTRIBS

#########################################

SLACK

ENABLE_SLACK = os.getenv(‘ENABLE_SLACK’, ‘False’) == ‘True’
if ENABLE_SLACK:
INSTALLED_APPS += [
“taiga_contrib_slack”
]

GITHUB AUTH

WARNING: If PUBLIC_REGISTER_ENABLED == False, currently Taiga by default prevents the OAuth

buttons to appear for both login and register

ENABLE_GITHUB_AUTH = os.getenv(‘ENABLE_GITHUB_AUTH’, ‘False’) == ‘True’
if PUBLIC_REGISTER_ENABLED and ENABLE_GITHUB_AUTH:
INSTALLED_APPS += [
“taiga_contrib_github_auth”
]
GITHUB_API_CLIENT_ID = os.getenv(‘GITHUB_API_CLIENT_ID’)
GITHUB_API_CLIENT_SECRET = os.getenv(‘GITHUB_API_CLIENT_SECRET’)

GITLAB AUTH

WARNING: If PUBLIC_REGISTER_ENABLED == False, currently Taiga by default prevents the OAuth

buttons to appear for both login and register

ENABLE_GITLAB_AUTH = os.getenv(‘ENABLE_GITLAB_AUTH’, ‘False’) == ‘True’
if PUBLIC_REGISTER_ENABLED and ENABLE_GITLAB_AUTH:
INSTALLED_APPS += [
“taiga_contrib_gitlab_auth”
]
GITLAB_API_CLIENT_ID = os.getenv(‘GITLAB_API_CLIENT_ID’)
GITLAB_API_CLIENT_SECRET = os.getenv(‘GITLAB_API_CLIENT_SECRET’)
GITLAB_URL = os.getenv(‘GITLAB_URL’)

#########################################

TELEMETRY

#########################################
ENABLE_TELEMETRY = os.getenv(‘ENABLE_TELEMETRY’, ‘True’) == ‘True’

#########################################

IMPORTERS

#########################################
ENABLE_GITHUB_IMPORTER = os.getenv(‘ENABLE_GITHUB_IMPORTER’, ‘False’) == ‘True’
if ENABLE_GITHUB_IMPORTER:
IMPORTERS[“github”] = {
“active”: True,
“client_id”: os.getenv(‘GITHUB_IMPORTER_CLIENT_ID’),
“client_secret”: os.getenv(‘GITHUB_IMPORTER_CLIENT_SECRET’)
}

ENABLE_JIRA_IMPORTER = os.getenv(‘ENABLE_JIRA_IMPORTER’, ‘False’) == ‘True’
if ENABLE_JIRA_IMPORTER:
IMPORTERS[“jira”] = {
“active”: True,
“consumer_key”: os.getenv(‘JIRA_IMPORTER_CONSUMER_KEY’),
“cert”: os.getenv(‘JIRA_IMPORTER_CERT’),
“pub_cert”: os.getenv(‘JIRA_IMPORTER_PUB_CERT’)
}

ENABLE_TRELLO_IMPORTER = os.getenv(‘ENABLE_TRELLO_IMPORTER’, ‘False’) == ‘True’
if ENABLE_TRELLO_IMPORTER:
IMPORTERS[“trello”] = {
“active”: True,
“api_key”: os.getenv(‘TRELLO_IMPORTER_API_KEY’),
“secret_key”: os.getenv(‘TRELLO_IMPORTER_SECRET_KEY’)
}
INSTALLED_APPS += [“taiga_contrib_ldap_auth_ext”]

Multiple LDAP servers are currently not supported, see

LDAP_SERVER = “ldap://URLDAP”
LDAP_PORT = 389

LDAP_BIND_DN = “mydn”
LDAP_BIND_PASSWORD = “password”

LDAP_SEARCH_BASE = ‘OU=Utilisateurs,****’

LDAP_USERNAME_ATTRIBUTE = “cn”
LDAP_EMAIL_ATTRIBUTE = “mail”
LDAP_FULL_NAME_ATTRIBUTE = “givenName”

LDAP_SAVE_LOGIN_PASSWORD = False

LDAP_MAP_USERNAME_TO_UID = None

@TuringTux Could this be an ssl certificate problem between the gateway and the front end? This installation uses self-signed SSL certificates.
I just did a test with the python script provided here “https://community.taiga.io/t/taiga-6-ldap-accounts-are-not-created/3497” and here’s the error I got:

requests.exceptions.SSLError: HTTPSConnectionPool(host='myurl', port=443): Max retries exceeded with url: /api/v1/auth (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1000)'))))

Is this possible? How can I test by ignoring the ssl certificate (with the disable SSL verification script)?

Tr

Not sure. If you can access the Taiga instance in the browser, and also see that you can send and receive to and from the API (which you proved by posting the network inspector screenshots), then the script provided in my other answer should not give you any further information.

The next thing I would try is jumping into the backend container again and see if you can temporarily install ldapsearch there and execute a query against the LDAP server.

If it works, this means the problem must indeed be the LDAP plugin. If it does not work, likely something else is afoul with the communication.

Test LDAP connection using base ldap-utils

  1. Enter the container:

    docker exec -it taiga-taiga-back-1 bash
    
  2. Install ldap-utils:

    apt update
    apt install -y ldap-utils
    
  3. Start LDAP search:

    ldapsearch -H ldap://sso.yourdomain.example -D "cn=admin,DC=yourdomain,DC=example" -W -b "OU=people,DC=yourdomain,DC=example"
    
  4. Enter bind password:

    Enter LDAP Password:
    
  5. Check exit status

    echo $?
    

The exit status should be 0 (success). If not, this means that not even Linux standard utilities can access the LDAP server from the backend container.

@TuringTux I could request LDAP from backend container but result is not 0

ldapsearch -H ldap://LDAPURL -x -b ‘Ou=Utilisateurs,DC=****’ -W -D ‘CN=*****’

numResponses: 3001

numEntries: 3000

root@bd4a9c3359a8:/taiga-back# echo $?
4

@TuringTux no other idea ?

I am afraid currently not. A result code of 4 for the LDAP command is okay, I believe, that is just “truncated output due to length”, so that would be fine.

If you can contact the LDAP server from within the backend container, I see no reason why the plugin should be unable to do the same, so I fear this problem is beyond my comprehension, sorry.

It is possible to create a traceback from the application which could trace actions from frontend to authentication ? A super debug mode ?

Might be. I always want to add more logging and debugging capabilities to the plugin, but I never get around to it, I am afraid.

Maybe this will happen some time in the future, time permitting, but currently it looks rather bleak. I am really sorry, I wish I could be of more help here.