Skip to content

Commit

Permalink
Return username and groups after valid authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
dignajar committed May 19, 2020
1 parent 7f00f8c commit d9218d7
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 37 deletions.
56 changes: 30 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,36 @@
## Diagram
![Another LDAP Authentication](https://i.ibb.co/Fn1ncbP/another-ldap-authentication.jpg)

## Available configurations parameters
The parameters can be sent via environment variables or via HTTP headers, also you can combine them.

The parameter `LDAP_SEARCH_FILTER` support variable expansion with the username, you can do something like this `(sAMAccountName={username})` and `{username}` is going to be replaced by the username typed in the login form.

### Environment variables
- `LDAP_ENDPOINT` LDAP URL with the port number. Ex: `ldaps://testmyldap.com:636`
- `LDAP_MANAGER_DN_USERNAME` Username to bind and search in the LDAP tree. Ex: `CN=john-service-user,OU=Administrators,DC=TESTMYLDAP,DC=COM`
- `LDAP_MANAGER_PASSWORD` Password for the bind user.
- `LDAP_SEARCH_BASE` Ex: `DC=TESTMYLDAP,DC=COM`
- `LDAP_SEARCH_FILTER` Filter to search, for Microsoft Active Directory usually you can use `sAMAccountName`. Ex: `(sAMAccountName={username})`
- `LDAP_SERVER_DOMAIN` **(Optional)**, for Microsoft Active Directory usually need the domain name for authenticate the user. Ex: `TESTMYLDAP.COM`
- `LDAP_REQUIRED_GROUPS` **(Optional)**, required groups are case insensitive (`DevOps` is the same as `DEVOPS`), you can send a list separated by commas, try first without required groups. Ex: `'DevOps', 'DevOps_QA'`
- `LDAP_REQUIRED_GROUPS_CONDITIONAL` **(Optional, default="and")**, you can set the conditional to match all the groups on the list or just one of them. To match all of them use `and` and for match just one use `or`. Ex: `and`
- `CACHE_EXPIRATION` **(Optional, default=5)** Expiration time in minutes for the cache. Ex: `10`

### HTTP request headers
- `Ldap-Endpoint`
- `Ldap-Manager-Dn-Username`
- `Ldap-Manager-Password`
- `Ldap-Search-Base`
- `Ldap-Search-Filter`
- `Ldap-Server-Domain` **(Optional)**
- `Ldap-Required-Groups` **(Optional)**
- `Ldap-Required-Groups-Conditional` **(Optional)**

### HTTP response headers
- `x-username` Contains the authenticated username
- `x-groups` Contains the username matches groups

## Installation and configuration
The easy way to use **Another LDAP Authentication** is running as a Docker container and set the parameters via environment variables.

Expand Down Expand Up @@ -152,31 +182,5 @@ spec:
servicePort: 80
```

## Available configurations parameters
The parameters can be sent via environment variables or via HTTP headers, also you can combine them.

The parameter `LDAP_SEARCH_FILTER` support variable expansion with the username, you can do something like this `(sAMAccountName={username})` and `{username}` is going to be replaced by the username typed in the login form.

### Environment variables
- `LDAP_ENDPOINT` LDAP URL with the port number. Ex: `ldaps://testmyldap.com:636`
- `LDAP_MANAGER_DN_USERNAME` Username to bind and search in the LDAP tree. Ex: `CN=john-service-user,OU=Administrators,DC=TESTMYLDAP,DC=COM`
- `LDAP_MANAGER_PASSWORD` Password for the bind user.
- `LDAP_SEARCH_BASE` Ex: `DC=TESTMYLDAP,DC=COM`
- `LDAP_SEARCH_FILTER` Filter to search, for Microsoft Active Directory usually you can use `sAMAccountName`. Ex: `(sAMAccountName={username})`
- `LDAP_SERVER_DOMAIN` **(Optional)**, for Microsoft Active Directory usually need the domain name for authenticate the user. Ex: `TESTMYLDAP.COM`
- `LDAP_REQUIRED_GROUPS` **(Optional)**, required groups are case insensitive (`DevOps` is the same as `DEVOPS`), you can send a list separated by commas, try first without required groups. Ex: `'DevOps', 'DevOps_QA'`
- `LDAP_REQUIRED_GROUPS_CONDITIONAL` **(Optional, default=and)**, you can set the conditional to match all the groups on the list or just one of them. To match all of them use `and` and for match just one use `or`. Ex: `and`
- `CACHE_EXPIRATION` **(Optional, default=5)** Expiration time in minutes for the cache. Ex: `10`

### HTTP headers
- `Ldap-Endpoint`
- `Ldap-Manager-Dn-Username`
- `Ldap-Manager-Password`
- `Ldap-Search-Base`
- `Ldap-Search-Filter`
- `Ldap-Server-Domain` **(Optional)**
- `Ldap-Required-Groups` **(Optional)**
- `Ldap-Required-Groups-Conditional` **(Optional)**

## Known limitations
- Parameters via headers need to be escaped, for example, you can not send parameters such as `$1` or `$test` because Nginx is applying variable expansion.
23 changes: 15 additions & 8 deletions files/aldap.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,19 +53,26 @@ def validateGroups(self, groups, conditional):
if group.lower() in str(listAD).lower():
# The user has this group include the group in the matchesGroup
matchesGroups.append(group)
if conditional == 'or':
print("[INFO][GROUPS] Matched group:",group)
return True

print("[INFO][GROUPS] Matched groups:",matchesGroups)

# If the group is the same as matchesGroups means the user has all the groups
if set(groups) == set(matchesGroups):
print("[INFO][GROUPS] All groups are valid for the user.")
return True
# Conditiona OR, true if just 1 group match
if conditional == 'or':
for group in groups:
if group in matchesGroups:
print("[INFO][GROUPS] One of the groups is valid for the user.")
return True,matchesGroups
# Conditiona AND, true if all the groups match
elif conditional == 'and':
if set(groups) == set(matchesGroups):
print("[INFO][GROUPS] All groups are valid for the user.")
return True,matchesGroups
else:
print("[WARN][GROUPS] Invalid group conditional.")
return False,[]

print("[WARN][GROUPS] Invalid groups.")
return False
return False,[]

def authenticateUser(self):
finalUsername = self.username
Expand Down
11 changes: 8 additions & 3 deletions files/main.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from flask import Flask
from flask import request
from flask import g
from flask_httpauth import HTTPBasicAuth
from aldap import Aldap
from cache import Cache
Expand All @@ -17,7 +18,6 @@

@auth.verify_password
def login(username, password):

if not username or not password:
print("[ERROR] Username or password empty.")
return False
Expand Down Expand Up @@ -86,10 +86,12 @@ def login(username, password):
aldap.setUser(username, password)

# Check groups only if they are defined
matchesGroups = []
if LDAP_REQUIRED_GROUPS:
groups = LDAP_REQUIRED_GROUPS.split(",") # Split the groups by comma and trim
groups = [x.strip() for x in groups] # Remove spaces
if not aldap.validateGroups(groups, LDAP_REQUIRED_GROUPS_CONDITIONAL):
validGroups, matchesGroups = aldap.validateGroups(groups, LDAP_REQUIRED_GROUPS_CONDITIONAL)
if not validGroups:
return False

# Check if the username and password are valid
Expand All @@ -102,6 +104,8 @@ def login(username, password):
cache.add(username, password)

# Success
g.username = username # Set the username to send in the headers response
g.matchesGroups = ','.join(matchesGroups) # Set the matches groups to send in the headers response
return True

# Catch-All URL
Expand All @@ -111,7 +115,8 @@ def login(username, password):
def index(path):
code = 200
msg = "Another LDAP Auth"
return msg, code
headers = [('x-username', g.username),('x-groups', g.matchesGroups)]
return msg, code, headers

# Main
if __name__ == '__main__':
Expand Down

0 comments on commit d9218d7

Please sign in to comment.