Skip to content

Commit

Permalink
[server] Add regex_groups authentication option
Browse files Browse the repository at this point in the history
This creates new groups that users can belong to if their username
matches some regular expression.

Makes it possible to have all users belonging to a group in order to set
default permissions for products.

Resolves github issue Ericsson#3072.
  • Loading branch information
jimis committed Jan 10, 2021
1 parent c66b9c5 commit 6a093ee
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 16 deletions.
27 changes: 27 additions & 0 deletions docs/web/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Table of Contents
* [<i>PAM</i> authentication](#pam-authentication)
* [<i>LDAP</i> authentication](#ldap-authentication)
* [Configuration options](#configuration-options)
* Membership in custom groups with [<i>regex_groups</i>](#regex_groups-authentication)
* [Client-side configuration](#client-side-configuration)
* [Web-browser client](#web-browser-client)
* [Command-line client](#command-line-client)
Expand Down Expand Up @@ -285,6 +286,32 @@ servers as it can elongate the authentication process.
}
~~~

## Membership in custom groups with <a name="regex_groups-authentication">regex_groups</a>

Many regular expressions can be listed to define a group. Please note that the
regular expressions are searched in the whole username string, so they should
be properly anchored if you want to match only in the beginning or in the
end. Regular expression matching follows the rules of Python's
[re.search()](https://docs.python.org/3/library/re.html).

The following example will create a group named `everybody` that contains
every user regardless of the authentication method, and a group named `admins`
that contains the user `root`, all usernames containing the string "whatever",
and all usernames starting with `admin_` or ending with `_admin`.

~~~{.json}
"regex_groups": {
"enabled" : true,
"groups" : {
"everybody" : [ ".*" ],
"admins" : [ "whatever", "^root$", "^admin_", "_admin$" ]
}
}
~~~

When we manage permissions on the GUI we can give permission to these
groups. For more information [see](permissions.md#managing-permissions).

----

# Client-side configuration <a name="client-side-configuration"></a>
Expand Down
61 changes: 46 additions & 15 deletions web/server/codechecker_server/session_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import hashlib
import json
import os
import re
import uuid

from codechecker_common.logger import get_logger
Expand Down Expand Up @@ -195,6 +196,21 @@ def __init__(self, configuration_file, root_sha, force_auth=False):
# Save the root SHA into the configuration (but only in memory!)
self.__auth_config['method_root'] = root_sha

try:
self.__regex_groups_enabled = \
self.__auth_config['regex_groups'].get('enabled')
except KeyError:
self.__regex_groups_enabled = False

# Pre-compile the regular expressions of 'regex_groups'
if 'regex_groups' in self.__auth_config:
regex_groups = self.__auth_config['regex_groups'] \
.get('groups', [])
d = dict()
for group_name, regex_list in regex_groups.items():
d[group_name] = [re.compile(r) for r in regex_list]
self.__group_regexes_compiled = d

# If no methods are configured as enabled, disable authentication.
if scfg_dict['authentication'].get('enabled'):
found_auth_method = False
Expand Down Expand Up @@ -333,23 +349,22 @@ def __handle_validation(self, auth_string):
This validation object contains two keys: username and groups.
"""
validation = self.__try_auth_root(auth_string)
if validation:
return validation

validation = self.__try_auth_dictionary(auth_string)
if validation:
return validation
validation = self.__try_auth_root(auth_string) \
or self.__try_auth_dictionary(auth_string) \
or self.__try_auth_pam(auth_string) \
or self.__try_auth_ldap(auth_string)
if not validation:
return False

validation = self.__try_auth_pam(auth_string)
if validation:
return validation
# If a validation method is enabled and regex_groups is enabled too,
# we will extend the 'groups'.
extra_groups = self.__try_regex_groups(validation['username'])
if extra_groups:
already_groups = set(validation['groups'])
validation['groups'] = list(already_groups | extra_groups)

validation = self.__try_auth_ldap(auth_string)
if validation:
return validation

return False
LOG.debug('User validation details: %s', str(validation))
return validation

def __is_method_enabled(self, method):
return method not in UNSUPPORTED_METHODS and \
Expand Down Expand Up @@ -480,6 +495,22 @@ def __update_groups(self, user_name, groups):

return False

def __try_regex_groups(self, username):
"""
Return a set of groups that the user belongs to, depending on whether
the username matches the regular expression of the group.
"""
matching_groups = set()
if self.__regex_groups_enabled:
for group_name, regex_list \
in self.__group_regexes_compiled.items() \
:
for r in regex_list:
if re.search(r, username):
matching_groups.add(group_name)
return matching_groups

@staticmethod
def get_user_name(auth_string):
return auth_string.split(':')[0]
Expand Down
6 changes: 6 additions & 0 deletions web/server/config/server_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@
"groups": [
"adm", "cc-users"
]
},
"regex_groups": {
"enabled" : false,
"groups" : {
"admins_custom_group" : [ "whatever", "^root$", "^admin_", "_admin$" ]
}
}
}
}
49 changes: 49 additions & 0 deletions web/tests/functional/authentication/test_authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,55 @@ def test_group_auth(self):
result = auth_client.destroySession()
self.assertTrue(result, "Server did not allow us to destroy session.")

def test_regex_groups(self):
# First login as root.
self.sessionToken = auth_client.performLogin("Username:Password",
"root:root")
self.assertIsNotNone(self.sessionToken,
"root was unable to login!")

# Then give SUPERUSER privs to admins_custom_group.
authd_auth_client = \
env.setup_auth_client(self._test_workspace,
session_token=self.sessionToken)
ret = authd_auth_client.addPermission(Permission.SUPERUSER,
"admins_custom_group",
True, None)
self.assertTrue(ret)

result = auth_client.destroySession()
self.assertTrue(result, "Server did not allow us to destroy session.")

# Login as a user who is in admins_custom_group.
sessionToken = auth_client.performLogin("Username:Password",
"regex_admin:blah")
self.assertIsNotNone(sessionToken,
"Valid credentials didn't give us a token!")

# Do something privileged.
client = env.setup_viewer_client(self._test_workspace,
session_token=sessionToken)
self.assertIsNotNone(client.allowsStoringAnalysisStatistics(),
"Privileged call failed.")

result = auth_client.destroySession()
self.assertTrue(result, "Server did not allow us to destroy session.")

# Finally try to do the same with an unprivileged user.
sessionToken = auth_client.performLogin("Username:Password",
"john:doe")
self.assertIsNotNone(sessionToken,
"Valid credentials didn't give us a token!")

client = env.setup_viewer_client(self._test_workspace,
session_token=sessionToken)
self.assertIsNone(client.allowsStoringAnalysisStatistics(),
"Privileged call from unprivileged user"
"did not fail!")

result = auth_client.destroySession()
self.assertTrue(result, "Server did not allow us to destroy session.")

def test_personal_access_tokens(self):
""" Test personal access token commands. """
codechecker_cfg = self._test_cfg['codechecker_cfg']
Expand Down
3 changes: 2 additions & 1 deletion web/tests/libtest/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,9 +353,10 @@ def enable_auth(workspace):
scfg_dict["authentication"]["method_dictionary"]["enabled"] = True
scfg_dict["authentication"]["method_dictionary"]["auths"] = \
["cc:test", "john:doe", "admin:admin123", "colon:my:password",
"admin_group_user:admin123"]
"admin_group_user:admin123", "regex_admin:blah"]
scfg_dict["authentication"]["method_dictionary"]["groups"] = \
{"admin_group_user": ["admin_GROUP"]}
scfg_dict["authentication"]["regex_groups"]["enabled"] = True

with open(server_cfg_file, 'w',
encoding="utf-8", errors="ignore") as scfg:
Expand Down

0 comments on commit 6a093ee

Please sign in to comment.