diff options
Diffstat (limited to 'rhodecode/lib/auth.py')
-rw-r--r-- | rhodecode/lib/auth.py | 190 |
1 files changed, 181 insertions, 9 deletions
diff --git a/rhodecode/lib/auth.py b/rhodecode/lib/auth.py index d234e3e2..151a720e 100644 --- a/rhodecode/lib/auth.py +++ b/rhodecode/lib/auth.py @@ -45,7 +45,8 @@ from rhodecode.lib.auth_ldap import AuthLdap from rhodecode.model import meta from rhodecode.model.user import UserModel -from rhodecode.model.db import Permission, RhodeCodeSetting, User +from rhodecode.model.db import Permission, RhodeCodeSetting, User, UserIpMap +from rhodecode.lib.caching_query import FromCache log = logging.getLogger(__name__) @@ -269,21 +270,34 @@ def login_container_auth(username): return user -def get_container_username(environ, config): +def get_container_username(environ, config, clean_username=False): + """ + Get's the container_auth username (or email). It tries to get username + from REMOTE_USER if container_auth_enabled is enabled, if that fails + it tries to get username from HTTP_X_FORWARDED_USER if proxypass_auth_enabled + is enabled. clean_username extracts the username from this data if it's + having @ in it. + + :param environ: + :param config: + :param clean_username: + """ username = None if str2bool(config.get('container_auth_enabled', False)): from paste.httpheaders import REMOTE_USER username = REMOTE_USER(environ) + log.debug('extracted REMOTE_USER:%s' % (username)) if not username and str2bool(config.get('proxypass_auth_enabled', False)): username = environ.get('HTTP_X_FORWARDED_USER') + log.debug('extracted HTTP_X_FORWARDED_USER:%s' % (username)) - if username: + if username and clean_username: # Removing realm and domain from username username = username.partition('@')[0] username = username.rpartition('\\')[2] - log.debug('Received username %s from container' % username) + log.debug('Received username %s from container' % username) return username @@ -313,11 +327,12 @@ class AuthUser(object): in """ - def __init__(self, user_id=None, api_key=None, username=None): + def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None): self.user_id = user_id self.api_key = None self.username = username + self.ip_addr = ip_addr self.name = '' self.lastname = '' @@ -380,6 +395,24 @@ class AuthUser(object): def is_admin(self): return self.admin + @property + def ip_allowed(self): + """ + Checks if ip_addr used in constructor is allowed from defined list of + allowed ip_addresses for user + + :returns: boolean, True if ip is in allowed ip range + """ + #check IP + allowed_ips = AuthUser.get_allowed_ips(self.user_id, cache=True) + if check_ip_access(source_ip=self.ip_addr, allowed_ips=allowed_ips): + log.debug('IP:%s is in range of %s' % (self.ip_addr, allowed_ips)) + return True + else: + log.info('Access for IP:%s forbidden, ' + 'not in %s' % (self.ip_addr, allowed_ips)) + return False + def __repr__(self): return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username, self.is_authenticated) @@ -406,6 +439,17 @@ class AuthUser(object): api_key = cookie_store.get('api_key') return AuthUser(user_id, api_key, username) + @classmethod + def get_allowed_ips(cls, user_id, cache=False): + _set = set() + user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id) + if cache: + user_ips = user_ips.options(FromCache("sql_cache_short", + "get_user_ips_%s" % user_id)) + for ip in user_ips: + _set.add(ip.ip_addr) + return _set or set(['0.0.0.0/0']) + def set_available_permissions(config): """ @@ -450,6 +494,15 @@ class LoginRequired(object): def __wrapper(self, func, *fargs, **fkwargs): cls = fargs[0] user = cls.rhodecode_user + loc = "%s:%s" % (cls.__class__.__name__, func.__name__) + + #check IP + ip_access_ok = True + if not user.ip_allowed: + from rhodecode.lib import helpers as h + h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr))), + category='warning') + ip_access_ok = False api_access_ok = False if self.api_access: @@ -458,9 +511,9 @@ class LoginRequired(object): api_access_ok = True else: log.debug("API KEY token not valid") - loc = "%s:%s" % (cls.__class__.__name__, func.__name__) + log.debug('Checking if %s is authenticated @ %s' % (user.username, loc)) - if user.is_authenticated or api_access_ok: + if (user.is_authenticated or api_access_ok) and ip_access_ok: reason = 'RegularAuth' if user.is_authenticated else 'APIAuth' log.info('user %s is authenticated and granted access to %s ' 'using %s' % (user.username, loc, reason) @@ -682,12 +735,12 @@ class PermsFunction(object): return False self.user_perms = user.permissions if self.check_permissions(): - log.debug('Permission granted for user: %s @ %s', user, + log.debug('Permission to %s granted for user: %s @ %s', self.repo_name, user, check_Location or 'unspecified location') return True else: - log.debug('Permission denied for user: %s @ %s', user, + log.debug('Permission to %s denied for user: %s @ %s', self.repo_name, user, check_Location or 'unspecified location') return False @@ -821,3 +874,122 @@ class HasPermissionAnyMiddleware(object): ) ) return False + + +#============================================================================== +# SPECIAL VERSION TO HANDLE API AUTH +#============================================================================== +class _BaseApiPerm(object): + def __init__(self, *perms): + self.required_perms = set(perms) + + def __call__(self, check_location='unspecified', user=None, repo_name=None): + cls_name = self.__class__.__name__ + check_scope = 'user:%s, repo:%s' % (user, repo_name) + log.debug('checking cls:%s %s %s @ %s', cls_name, + self.required_perms, check_scope, check_location) + if not user: + log.debug('Empty User passed into arguments') + return False + + ## process user + if not isinstance(user, AuthUser): + user = AuthUser(user.user_id) + + if self.check_permissions(user.permissions, repo_name): + log.debug('Permission to %s granted for user: %s @ %s', repo_name, + user, check_location) + return True + + else: + log.debug('Permission to %s denied for user: %s @ %s', repo_name, + user, check_location) + return False + + def check_permissions(self, perm_defs, repo_name): + """ + implement in child class should return True if permissions are ok, + False otherwise + + :param perm_defs: dict with permission definitions + :param repo_name: repo name + """ + raise NotImplementedError() + + +class HasPermissionAllApi(_BaseApiPerm): + def __call__(self, user, check_location=''): + return super(HasPermissionAllApi, self)\ + .__call__(check_location=check_location, user=user) + + def check_permissions(self, perm_defs, repo): + if self.required_perms.issubset(perm_defs.get('global')): + return True + return False + + +class HasPermissionAnyApi(_BaseApiPerm): + def __call__(self, user, check_location=''): + return super(HasPermissionAnyApi, self)\ + .__call__(check_location=check_location, user=user) + + def check_permissions(self, perm_defs, repo): + if self.required_perms.intersection(perm_defs.get('global')): + return True + return False + + +class HasRepoPermissionAllApi(_BaseApiPerm): + def __call__(self, user, repo_name, check_location=''): + return super(HasRepoPermissionAllApi, self)\ + .__call__(check_location=check_location, user=user, + repo_name=repo_name) + + def check_permissions(self, perm_defs, repo_name): + + try: + self._user_perms = set( + [perm_defs['repositories'][repo_name]] + ) + except KeyError: + log.warning(traceback.format_exc()) + return False + if self.required_perms.issubset(self._user_perms): + return True + return False + + +class HasRepoPermissionAnyApi(_BaseApiPerm): + def __call__(self, user, repo_name, check_location=''): + return super(HasRepoPermissionAnyApi, self)\ + .__call__(check_location=check_location, user=user, + repo_name=repo_name) + + def check_permissions(self, perm_defs, repo_name): + + try: + _user_perms = set( + [perm_defs['repositories'][repo_name]] + ) + except KeyError: + log.warning(traceback.format_exc()) + return False + if self.required_perms.intersection(_user_perms): + return True + return False + + +def check_ip_access(source_ip, allowed_ips=None): + """ + Checks if source_ip is a subnet of any of allowed_ips. + + :param source_ip: + :param allowed_ips: list of allowed ips together with mask + """ + from rhodecode.lib import ipaddr + log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips)) + if isinstance(allowed_ips, (tuple, list, set)): + for ip in allowed_ips: + if ipaddr.IPAddress(source_ip) in ipaddr.IPNetwork(ip): + return True + return False |