diff options
author | Ensky <enskylin@gmail.com> | 2015-04-15 23:24:17 +0800 |
---|---|---|
committer | Ensky <enskylin@gmail.com> | 2015-04-15 23:24:17 +0800 |
commit | a46c0c272158c3cca3306d235e11e7e15c55b15e (patch) | |
tree | 8380e6c20e2e29f1086592af080ed66302e67f8f | |
parent | c1346d511058e3dc9f6845fa514509752369c630 (diff) | |
parent | 1507f424e0b1f20611e192f77d2bce9d8a597868 (diff) | |
download | taiga-contrib-ldap-auth-a46c0c272158c3cca3306d235e11e7e15c55b15e.zip |
Merge pull request #3 from artlepool/master
Search for account in LDAP with matching username, then perform LDAP bind with that user
-rw-r--r-- | README.md | 31 | ||||
-rw-r--r-- | taiga_contrib_ldap_auth/connector.py | 59 |
2 files changed, 72 insertions, 18 deletions
@@ -20,11 +20,36 @@ LDAP configuration: ```python INSTALLED_APPS += ["taiga_contrib_ldap_auth"] - LDAP_SERVER = "ldap://ldap.example.com" - LDAP_DN_FORMAT = "uid={username},cn=users,dc=example,dc=com" - LDAP_BASE_EMAIL = "@example.com" + + LDAP_SERVER = 'ldap://ldap.example.com' + LDAP_PORT = 389 + + # 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 = 'CN=SVC Account,OU=Service Accounts,OU=Servers,DC=example,DC=com' + LDAP_BIND_PASSWORD = 'replace_me' # eg. + # Starting point within LDAP structure to search for login user + LDAP_SEARCH_BASE = 'OU=DevTeam,DC=example,DC=net' + # LDAP property used for searching, ie. login username needs to match value in sAMAccountName property in LDAP + LDAP_SEARCH_PROPERTY = 'sAMAccountName' + + # Names of LDAP properties on user account to get email and full name + LDAP_EMAIL_PROPERTY = 'mail' + LDAP_FULL_NAME_PROPERTY = 'name' ``` +The logic of the code is such that a dedicated domain service account user performs a search on LDAP for an account that has a LDAP_SEARCH_PROPERTY value that matches the username the user typed in on the Taiga login form. +If the search is successful, then the code uses this value and the typed-in password to attempt a bind to LDAP using these credentials. +If the bind is successful, then we can say that the user is authorised to log in to Taiga. + +If the LDAP_BIND_DN configuration setting is not specified or is blank, then an anonymous bind is attempted to search for the login user's LDAP account entry. + + +RECOMMENDATION: Note that if you are using a service account for performing the LDAP search for the user that is logging on to Taiga, for security reasons, the service account user should be configured to only allow reading/searching the LDAP structure. No other LDAP (or wider network) permissions should be granted for this user because you need to specify the service account password in this file. +A suitably strong password should be chosen, eg. VmLYBbvJaf2kAqcrt5HjHdG6 + + + ### Taiga Front Change in your dist/js/conf.json the loginFormType setting to "ldap": diff --git a/taiga_contrib_ldap_auth/connector.py b/taiga_contrib_ldap_auth/connector.py index 0b3869e..adf2b16 100644 --- a/taiga_contrib_ldap_auth/connector.py +++ b/taiga_contrib_ldap_auth/connector.py @@ -15,7 +15,7 @@ # 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, AUTH_SIMPLE, STRATEGY_SYNC +from ldap3 import Server, Connection, AUTH_SIMPLE, AUTH_ANONYMOUS, STRATEGY_SYNC, SIMPLE, SYNC, ASYNC, SUBTREE, ALL from django.conf import settings from taiga.base.connectors.exceptions import ConnectorBaseException @@ -26,21 +26,50 @@ class LDAPLoginError(ConnectorBaseException): SERVER = getattr(settings, "LDAP_SERVER", "") -DN_FORMAT = getattr(settings, "LDAP_DN_FORMAT", "") -BASE_EMAIL = getattr(settings, "LDAP_BASE_EMAIL", "") +PORT = getattr(settings, "LDAP_PORT", "") +SEARCH_BASE = getattr(settings, "LDAP_SEARCH_BASE", "") +SEARCH_PROPERTY = getattr(settings, "LDAP_SEARCH_PROPERTY", "") +BIND_DN = getattr(settings, "LDAP_BIND_DN", "") +BIND_PASSWORD = getattr(settings, "LDAP_BIND_PASSWORD", "") + +EMAIL_PROPERTY = getattr(settings, "LDAP_EMAIL_PROPERTY", "") +FULL_NAME_PROPERTY = getattr(settings, "LDAP_FULL_NAME_PROPERTY", "") def login(username: str, password: str) -> tuple: - dn = DN_FORMAT.format(username=username) + + + try: + server = Server(SERVER, port = PORT, get_info = ALL) # define an unsecure LDAP server, requesting info on DSE and schema + c = None + + if BIND_DN is not None and BIND_DN != '': + c = Connection(server, auto_bind = True, client_strategy = SYNC, user=BIND_DN, password=BIND_PASSWORD, authentication=AUTH_SIMPLE, check_names=True) + else: + c = Connection(server, auto_bind = True, client_strategy = SYNC, user=None, password=None, authentication=AUTH_ANONYMOUS, check_names=True) + + except Exception as e: + error = "Error connecting to LDAP server: %s" % e + raise LDAPLoginError({"error_message": error}) + try: - server = Server(SERVER) - Connection(server, auto_bind=True, client_strategy=STRATEGY_SYNC, - user=dn, password=password, authentication=AUTH_SIMPLE, - check_names=True) - except: - raise LDAPLoginError({"error_message": "LDAP account or password incorrect."}) - - # TODO: fetch email and fullname information from LDAP server - email = username + BASE_EMAIL - full_name = username - return (email, full_name) + c.search(search_base = SEARCH_BASE, + search_filter = '(%s=%s)' % (SEARCH_PROPERTY, username), + search_scope = SUBTREE, + attributes = [EMAIL_PROPERTY,FULL_NAME_PROPERTY], + paged_size = 5) + + if len(c.response) > 0: + dn = c.response[0].get('dn') + user_email = c.response[0].get('raw_attributes').get(EMAIL_PROPERTY)[0].decode('utf-8') + full_name = c.response[0].get('raw_attributes').get(FULL_NAME_PROPERTY)[0].decode('utf-8') + + user_conn = Connection(server, auto_bind = True, client_strategy = SYNC, user = dn, password = password, authentication = SIMPLE, check_names = True) + + return (user_email, full_name) + + raise LDAPLoginError({"error_message": "Username or password incorrect"}) + + except Exception as e: + error = "LDAP account or password incorrect: %s" % e + raise LDAPLoginError({"error_message": error}) |