Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
e66b623
:pencil: Update README
AminovE99 Mar 13, 2020
29a27c7
:sparkles: Add implementation with allow multiplies
AminovE99 Mar 13, 2020
30e3acf
:recycle: Create dict instead of list
AminovE99 Mar 13, 2020
27f4e15
:recycle: remarks from review
AminovE99 Mar 16, 2020
0d0b6cb
:recycle: Insert part of code in one function
AminovE99 Mar 17, 2020
e325419
:whale: Add docker from another pull request
AminovE99 Mar 20, 2020
2b40f10
:bug: Fix bug with always None connection
AminovE99 Mar 23, 2020
4df8f45
:bug: Fix way to downloading libraries in pip
AminovE99 Mar 25, 2020
f08f9ae
:lipstick: Add server n3
AminovE99 Mar 25, 2020
0fe9665
:bug: Return send_register_email function
AminovE99 Mar 25, 2020
4ff923d
:sparkles: Automatically creation of package
AminovE99 Mar 25, 2020
7cf1e0e
🐳 Docker/add docker compose (#36)
AminovE99 Mar 26, 2020
5254dba
:fire: Remove sys package
AminovE99 Apr 1, 2020
d72eb90
:loud_sound: Add info in messages
AminovE99 Apr 1, 2020
9b59332
:loud_sound: Change logs
AminovE99 Apr 1, 2020
474a9a3
:whale: Return previous docker with host building whl
AminovE99 Apr 1, 2020
9b01d78
:bug: Fix bug with whl file
AminovE99 Apr 2, 2020
55844b9
:move: Remove unnecessary logs
AminovE99 Apr 3, 2020
3c39681
:bug: Resolve notes from review
AminovE99 Apr 3, 2020
6dd2386
:sparkles: Add testing connection
AminovE99 Apr 3, 2020
88c1418
:sparkles: Add flush
AminovE99 Apr 3, 2020
1158991
:art: Remove unnesserary comments
AminovE99 Apr 3, 2020
1d331a5
:art: remove prints
AminovE99 Apr 3, 2020
43d8f44
:art: Add blank lines
AminovE99 Apr 3, 2020
00314a7
:sparkles: Add mailcatcher
AminovE99 Apr 6, 2020
a7ec6c7
:bug: Add domain variable
AminovE99 Apr 6, 2020
179fbb5
:sparkles: Add openldap configuration
AminovE99 Apr 6, 2020
29f2ac8
:sparkles: Add env file
AminovE99 Apr 6, 2020
d4416d7
:see_no_evil: Remove .env from .gitignore
AminovE99 Apr 6, 2020
8c43000
:wrench: Add ldif files for LDAP
AminovE99 Apr 7, 2020
9d39c39
:wrench: Add debug true
AminovE99 Apr 8, 2020
0fcb2c1
:truck: Rename container openldap
AminovE99 Apr 8, 2020
6056df4
:recycle: Tested list of ldap servers
AminovE99 Apr 21, 2020
cbbc51b
:fire: Remove print from connectors.py
AminovE99 Apr 21, 2020
dbba0b7
:bug: Remove variable from connector
AminovE99 Apr 27, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ __pycache__
*.eggs
*.egg-info
dist
.idea
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
138 changes: 76 additions & 62 deletions taiga_contrib_ldap_auth_ext/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

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
Expand All @@ -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", "")
Expand Down Expand Up @@ -65,17 +66,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 != '':
Expand All @@ -91,58 +97,66 @@ 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})
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)
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 ldap_value in server_ldap_dict:
if server_ldap_dict[ldap_value] 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)
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