summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEnsky <enskylin@gmail.com>2015-04-15 23:24:17 +0800
committerEnsky <enskylin@gmail.com>2015-04-15 23:24:17 +0800
commita46c0c272158c3cca3306d235e11e7e15c55b15e (patch)
tree8380e6c20e2e29f1086592af080ed66302e67f8f
parentc1346d511058e3dc9f6845fa514509752369c630 (diff)
parent1507f424e0b1f20611e192f77d2bce9d8a597868 (diff)
downloadtaiga-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.md31
-rw-r--r--taiga_contrib_ldap_auth/connector.py59
2 files changed, 72 insertions, 18 deletions
diff --git a/README.md b/README.md
index 76db3b9..05a4930 100644
--- a/README.md
+++ b/README.md
@@ -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})