Skip to content

Commit

Permalink
Ldap groups can now be queried for a user to perform group based acce…
Browse files Browse the repository at this point in the history
…ss control. (#916)

A bug was fixed:the authentication token cookie was not always set in the response header
which caused frequent re-authnentications.
  • Loading branch information
dkrupp authored and Xazax-hun committed Sep 14, 2017
1 parent 281f272 commit f515099
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 50 deletions.
4 changes: 2 additions & 2 deletions config/session_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
"accountPattern" : "(&(objectClass=person)(sAMAccountName=$USN$))",
"groupBase" : null,
"groupScope" : "subtree",
"groupPattern" : "(&(objectClass=group)(name=$GRP$))",
"groupMemberPattern" : "(member=$USERDN$)"
"groupPattern" : "(&(objectClass=group)(member=$USERDN$))",
"groupNameAttr" : "sAMAccountName"
}
]
},
Expand Down
24 changes: 11 additions & 13 deletions docs/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,19 +191,17 @@ servers as it can elongate the authentication process.

* `groupPattern`

Group query pattern used. Must be a valid LDAP query expression.

* `groupMemberPattern`

Group member pattern will be combined with the group patten to query user
for ldap group membership. `$USERDN$` will be automatically replaced by the
queried user account DN.

Example configuration: `(member=$USERDN$)`
Group query pattern used LDAP query expression to find the group objects
a user is a member of. It must contain a `$USERDN$` pattern.
`$USERDN$` will be automatically replaced by the queried user account DN.

* `groupNameAttr`

The attribute of the group object which contains the name of the group.

* `groupScope`

Scope of the search performed. (Valid values are: `base`, `one`, `subtree`)
Scope of the search performed. (Valid values are: `base`, `one`, `subtree`)

~~~{.json}
"method_ldap": {
Expand All @@ -220,8 +218,8 @@ servers as it can elongate the authentication process.
"accountPattern" : "(&(objectClass=person)(sAMAccountName=$USN$))",
"groupBase" : null,
"groupScope" : "subtree",
"groupPattern" : "(&(objectClass=group)(name=mygroup))",
"groupMemberPattern" : "(member=$USERDN$)"
"groupPattern" : "(&(objectClass=group)(member=$USERDN$))",
"groupNameAttr" : "sAMAccountName"
},
{
"connection_url" : "ldaps://secure.internal.example.org:636",
Expand All @@ -235,7 +233,7 @@ servers as it can elongate the authentication process.
"groupBase" : null,
"groupScope" : "subtree",
"groupPattern" : null,
"groupMemberPattern" : null
"groupNameAttr" : null
}
]
}
Expand Down
105 changes: 79 additions & 26 deletions libcodechecker/libauth/cc_ldap.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"groupBase" : null,
"groupScope" : "subtree",
"groupPattern" : null,
"groupMemberPattern" : null
"groupNameAttr" : null
}
]
},
Expand Down Expand Up @@ -71,13 +71,14 @@
Root tree containing all the groups.
`groupPattern`
Group query pattern used. Must be a valid LDAP query expression.
`groupMemberPattern`
Group member pattern will be combined with the group patten to query user for
ldap group membership.
$USERDN$ will be automatically replaced by the queried user account DN.
Example configuration: *(member=$USERDN$)*
Group query pattern used LDAP query expression to find the group objects
a user is a member of. It must contain a `$USERDN$` pattern.
`$USERDN$` will be automatically replaced by the queried user account DN.
`groupNameAttr`
The attribute of the group object which contains the name of the group.
`groupScope`
Scope of the search performed. (Valid values are: base, one, subtree)
Expand Down Expand Up @@ -209,18 +210,18 @@ def __init__(self, ldap_config, who=None, cred=None):

self.connection = ldap.initialize(ldap_server)

LOG.error('Binding to LDAP server with user: ' + who if who else '')
LOG.debug('Binding to LDAP server with user: ' + who if who else '')

with ldap_error_handler():
if who is None or cred is None:
# Try anonymous bind.
res = self.connection.simple_bind_s()
LOG.error(res)
LOG.debug(res)
else:
# Bind with the given credentials
LOG.error(who)
LOG.debug(who)
res = self.connection.simple_bind_s(who, cred)
LOG.error(res)
LOG.debug(res)

def __enter__(self):
return self.connection
Expand Down Expand Up @@ -273,7 +274,6 @@ def auth_user(ldap_config, username=None, credentials=None):
if not service_user:
service_user = username
service_cred = credentials

with LDAPConnection(ldap_config, service_user, service_cred) as connection:

user_dn = get_user_dn(connection,
Expand All @@ -288,17 +288,51 @@ def auth_user(ldap_config, username=None, credentials=None):
LOG.error('Anonymous bind might not be enabled.')
LOG.error('Configured username: ' + service_user)
return False

# bind with the users dn to check group membership
with LDAPConnection(ldap_config, user_dn, credentials) as connection:
return True
LOG.info("User:" + username + " cannot be authenticated.")
return False


def get_groups(ldap_config, username, credentials):

account_base = ldap_config.get('accountBase')
if account_base is None:
LOG.error('Account base needs to be configured to query users')
return False

account_pattern = ldap_config.get('accountPattern')
if account_pattern is None:
LOG.error('No account pattern is defined to search for users.')
LOG.error('Please configure one.')
return False

account_pattern = account_pattern.replace('$USN$', username)

account_scope = ldap_config.get('accountScope', '')
account_scope = get_ldap_query_scope(account_scope)

service_user = ldap_config.get('username')
service_cred = ldap_config.get('password')
if not service_user:
service_user = username
service_cred = credentials

LOG.debug("creating LDAP connection. service user" + service_user)
with LDAPConnection(ldap_config, service_user, service_cred) as connection:
user_dn = get_user_dn(connection,
account_base,
account_pattern,
account_scope)

group_pattern = ldap_config.get('groupPattern', '')
if user_dn and group_pattern == '':
# User found and there is no group membership pattern to check.
return True
return []
group_pattern = group_pattern.replace('$USERDN$', user_dn)

LOG.debug('Checking for group membership.')
LOG.debug(group_pattern)

group_scope = ldap_config.get('groupScope', '')
group_scope = get_ldap_query_scope(group_scope)
Expand All @@ -307,17 +341,36 @@ def auth_user(ldap_config, username=None, credentials=None):
if group_base is None:
LOG.error('Group base needs to be configured to'
'query ldap groups.')
return False
return []

member_pattern = ldap_config.get('groupMemberPattern')
if member_pattern is None or member_pattern == '':
member_pattern = '(member=$USERDN$)'
group_name_attr = ldap_config.get('groupNameAttr')
if group_name_attr is None:
LOG.error('groupNameAttr needs to be configured to'
'query ldap groups.'
'Its value must be the name'
'attribute of the group.')
return []

member_pattern = member_pattern.replace('$USERDN$', user_dn)
member_query = '(& ' + group_pattern + member_pattern + ')'
# Attribute name must be ascii encoded
group_name_attr = group_name_attr.encode('ascii',
'ignore')
attr_list = [group_name_attr]

is_member = check_group_membership(connection,
group_base,
member_query,
group_scope)
return is_member
LOG.debug("Performing LDAP search for group:" + group_pattern +
"Group Name Attr:" + group_name_attr)

groups = []
with ldap_error_handler():
group_result = connection.search_s(group_base,
group_scope,
group_pattern,
attr_list)
if group_result:
for g in group_result:
groups.append(g[1][group_name_attr][0])

LOG.debug("groups:")
LOG.debug(groups)
return groups
LOG.error("Cannot get ldap groups for user:"+username)
return []
23 changes: 16 additions & 7 deletions libcodechecker/server/client_db_access_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class RequestHandler(SimpleHTTPRequestHandler):
Handle thrift and browser requests
Simply modified and extended version of SimpleHTTPRequestHandler
"""
auth_token = None

def __init__(self, request, client_address, server):
BaseHTTPRequestHandler.__init__(self,
Expand Down Expand Up @@ -127,6 +128,19 @@ def __check_auth_in_request(self):

return success

def end_headers(self):
# Sending the authentication cookie
# in every response if any.
# This will update the the session cookie
# on the clients to the newest.
if self.auth_token:
self.send_header(
"Set-Cookie",
"{0}={1}; Path=/".format(
session_manager.SESSION_COOKIE_NAME,
self.auth_token))
SimpleHTTPRequestHandler.end_headers(self)

def do_GET(self):
"""
Handles the webbrowser access (GET requests).
Expand All @@ -153,6 +167,8 @@ def do_GET(self):
self.wfile.write(error_body)
return
else:
if auth_session is not None:
self.auth_token = auth_session.token
product_endpoint, path = \
routing.split_client_GET_request(self.path)

Expand Down Expand Up @@ -213,13 +229,6 @@ def do_GET(self):
LOG.debug("Serving resource '{0}'".format(self.path))

self.send_response(200) # 200 OK
if auth_session is not None:
# Browsers get a standard cookie for session.
self.send_header(
"Set-Cookie",
"{0}={1}; Path=/".format(
session_manager.SESSION_COOKIE_NAME,
auth_session.token))

SimpleHTTPRequestHandler.do_GET(self)

Expand Down
4 changes: 2 additions & 2 deletions libcodechecker/session_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,8 +339,8 @@ def __try_auth_ldap(self, auth_string):
.get("authorities")
for ldap_conf in ldap_authorities:
if cc_ldap.auth_user(ldap_conf, username, password):
# TODO: Fetch the LDAP groups of user.
return {'username': username, 'groups': []}
groups = cc_ldap.get_groups(ldap_conf, username, password)
return {'username': username, 'groups': groups}

return False

Expand Down

0 comments on commit f515099

Please sign in to comment.