Mercurial > kallithea
changeset 7696:077ba994ee03
auth: introduce AuthUser.make factory which can return None if user can't be authenticated
author | Mads Kiilerich <mads@kiilerich.com> |
---|---|
date | Thu, 03 Jan 2019 01:22:45 +0100 |
parents | 31aa5b6c107d |
children | 226893a56a81 |
files | kallithea/controllers/api/__init__.py kallithea/controllers/login.py kallithea/lib/auth.py kallithea/lib/auth_modules/__init__.py kallithea/lib/base.py |
diffstat | 5 files changed, 62 insertions(+), 52 deletions(-) [+] |
line wrap: on
line diff
--- a/kallithea/controllers/api/__init__.py Sun Apr 07 23:44:17 2019 +0200 +++ b/kallithea/controllers/api/__init__.py Thu Jan 03 01:22:45 2019 +0100 @@ -146,12 +146,11 @@ # check if we can find this session using api_key try: u = User.get_by_api_key(self._req_api_key) - if u is None: + auth_user = AuthUser.make(dbuser=u) + if auth_user is None: raise JSONRPCErrorResponse(retid=self._req_id, message='Invalid API key') - - auth_u = AuthUser(dbuser=u) - if not AuthUser.check_ip_allowed(auth_u, ip_addr): + if not AuthUser.check_ip_allowed(auth_user, ip_addr): raise JSONRPCErrorResponse(retid=self._req_id, message='request from IP:%s not allowed' % (ip_addr,)) else: @@ -161,6 +160,8 @@ raise JSONRPCErrorResponse(retid=self._req_id, message='Invalid API key') + request.authuser = auth_user + self._error = None try: self._func = self._find_method() @@ -179,11 +180,6 @@ func_kwargs = dict(itertools.izip_longest(reversed(arglist), reversed(defaults), fillvalue=default_empty)) - # this is little trick to inject logged in user for - # perms decorators to work they expect the controller class to have - # authuser attribute set - request.authuser = auth_u - # This attribute will need to be first param of a method that uses # api_key, which is translated to instance of user at that name USER_SESSION_ATTR = 'apiuser'
--- a/kallithea/controllers/login.py Sun Apr 07 23:44:17 2019 +0200 +++ b/kallithea/controllers/login.py Thu Jan 03 01:22:45 2019 +0100 @@ -102,8 +102,8 @@ # Exception itself h.flash(e, 'error') else: - log_in_user(user, c.form_result['remember'], - is_external_auth=False) + auth_user = log_in_user(user, c.form_result['remember'], is_external_auth=False) + # TODO: handle auth_user is None as failed authentication? raise HTTPFound(location=c.came_from) else: # redirect if already logged in
--- a/kallithea/lib/auth.py Sun Apr 07 23:44:17 2019 +0200 +++ b/kallithea/lib/auth.py Thu Jan 03 01:22:45 2019 +0100 @@ -398,6 +398,20 @@ "default". Use `is_anonymous` to check for both "default" and "no user". """ + @classmethod + def make(cls, dbuser=None, authenticating_api_key=None, is_external_auth=False): + """Create an AuthUser to be authenticated ... or return None if user for some reason can't be authenticated. + Checks that a non-None dbuser is provided and is active. + """ + if dbuser is None: + log.info('No db user for authentication') + return None + if not dbuser.active: + log.info('Db user %s not active', dbuser.username) + return None + return cls(dbuser=dbuser, authenticating_api_key=authenticating_api_key, + is_external_auth=is_external_auth) + def __init__(self, user_id=None, dbuser=None, authenticating_api_key=None, is_external_auth=False): self.is_external_auth = is_external_auth # container auth - don't show logout option @@ -575,14 +589,12 @@ @staticmethod def from_cookie(cookie): """ - Deserializes an `AuthUser` from a cookie `dict`. + Deserializes an `AuthUser` from a cookie `dict` ... or return None. """ - - au = AuthUser( - user_id=cookie.get('user_id'), + return AuthUser.make( + dbuser=UserModel().get(cookie.get('user_id')), is_external_auth=cookie.get('is_external_auth', False), ) - return au @classmethod def get_allowed_ips(cls, user_id, cache=False): @@ -871,18 +883,17 @@ def __init__(self, *perms): self.required_perms = set(perms) - def __call__(self, user, repo_name, purpose=None): + def __call__(self, authuser, repo_name, purpose=None): # repo_name MUST be unicode, since we handle keys in ok # dict by unicode repo_name = safe_unicode(repo_name) - user = AuthUser(user.user_id) try: - ok = user.permissions['repositories'][repo_name] in self.required_perms + ok = authuser.permissions['repositories'][repo_name] in self.required_perms except KeyError: ok = False - log.debug('Middleware check %s for %s for repo %s (%s): %s', user.username, self.required_perms, repo_name, purpose, ok) + log.debug('Middleware check %s for %s for repo %s (%s): %s', authuser.username, self.required_perms, repo_name, purpose, ok) return ok
--- a/kallithea/lib/auth_modules/__init__.py Sun Apr 07 23:44:17 2019 +0200 +++ b/kallithea/lib/auth_modules/__init__.py Thu Jan 03 01:22:45 2019 +0100 @@ -362,7 +362,7 @@ settings=plugin_settings) log.debug('Plugin %s extracted user `%s`', module, user) - if user is not None and not user.active: + if user is not None and not user.active: # give up, way before creating AuthUser log.error("Rejecting authentication of in-active user %s", user) continue
--- a/kallithea/lib/base.py Sun Apr 07 23:44:17 2019 +0200 +++ b/kallithea/lib/base.py Thu Jan 03 01:22:45 2019 +0100 @@ -117,14 +117,16 @@ Returns populated `AuthUser` object. """ + # It should not be possible to explicitly log in as the default user. + assert not user.is_default_user, user + + auth_user = AuthUser.make(dbuser=user, is_external_auth=is_external_auth) + if auth_user is None: + return None + user.update_lastlogin() meta.Session().commit() - auth_user = AuthUser(dbuser=user, - is_external_auth=is_external_auth) - # It should not be possible to explicitly log in as the default user. - assert not auth_user.is_default_user - # Start new session to prevent session fixation attacks. session.invalidate() session['authuser'] = cookie = auth_user.to_cookie() @@ -210,18 +212,15 @@ Returns (user, None) on successful authentication and authorization. Returns (None, wsgi_app) to send the wsgi_app response to the client. """ - # Check if anonymous access is allowed. + # Use anonymous access if allowed for action on repo. default_user = User.get_default_user(cache=True) - is_default_user_allowed = (default_user.active and - self._check_permission(action, default_user, repo_name, ip_addr)) - if is_default_user_allowed: - return default_user, None - - if not default_user.active: - log.debug('Anonymous access is disabled') + default_authuser = AuthUser.make(dbuser=default_user) + if default_authuser is None: + log.debug('No anonymous access at all') # move on to proper user auth else: - log.debug('Not authorized to access this ' - 'repository as anonymous user') + if self._check_permission(action, default_authuser, repo_name, ip_addr): + return default_authuser, None + log.debug('Not authorized to access this repository as anonymous user') username = None #============================================================== @@ -252,15 +251,14 @@ #============================================================== try: user = User.get_by_username_or_email(username) - if user is None or not user.active: - return None, webob.exc.HTTPForbidden() except Exception: log.error(traceback.format_exc()) return None, webob.exc.HTTPInternalServerError() - # check permissions for this repository - perm = self._check_permission(action, user, repo_name, ip_addr) - if not perm: + authuser = AuthUser.make(dbuser=user) + if authuser is None: + return None, webob.exc.HTTPForbidden() + if not self._check_permission(action, authuser, repo_name, ip_addr): return None, webob.exc.HTTPForbidden() return user, None @@ -285,7 +283,7 @@ return '/'.join(data) - def _check_permission(self, action, user, repo_name, ip_addr=None): + def _check_permission(self, action, authuser, repo_name, ip_addr=None): """ Checks permissions using action (push/pull) user and repository name @@ -295,7 +293,7 @@ :param repo_name: repository name """ # check IP - ip_allowed = AuthUser.check_ip_allowed(user, ip_addr) + ip_allowed = AuthUser.check_ip_allowed(authuser, ip_addr) if ip_allowed: log.info('Access for IP:%s allowed', ip_addr) else: @@ -303,7 +301,7 @@ if action == 'push': if not HasPermissionAnyMiddleware('repository.write', - 'repository.admin')(user, + 'repository.admin')(authuser, repo_name): return False @@ -311,7 +309,7 @@ #any other action need at least read permission if not HasPermissionAnyMiddleware('repository.read', 'repository.write', - 'repository.admin')(user, + 'repository.admin')(authuser, repo_name): return False @@ -392,6 +390,7 @@ """ Create an `AuthUser` object given the API key/bearer token (if any) and the value of the authuser session cookie. + Returns None if no valid user is found (like not active). """ # Authenticate by bearer token @@ -400,9 +399,9 @@ # Authenticate by API key if api_key is not None: - au = AuthUser(dbuser=User.get_by_api_key(api_key), - authenticating_api_key=api_key, is_external_auth=True) - if au.is_anonymous: + dbuser = User.get_by_api_key(api_key) + au = AuthUser.make(dbuser=dbuser, authenticating_api_key=api_key, is_external_auth=True) + if au is None or au.is_anonymous: log.warning('API key ****%s is NOT valid', api_key[-4:]) raise webob.exc.HTTPForbidden(_('Invalid API key')) return au @@ -429,12 +428,14 @@ if user_info is not None: username = user_info['username'] user = User.get_by_username(username, case_insensitive=True) - return log_in_user(user, remember=False, - is_external_auth=True) + return log_in_user(user, remember=False, is_external_auth=True) # User is default user (if active) or anonymous default_user = User.get_default_user(cache=True) - return AuthUser(dbuser=default_user) + authuser = AuthUser.make(dbuser=default_user) + if authuser is None: # fall back to anonymous + authuser = AuthUser(dbuser=default_user) # TODO: somehow use .make? + return authuser @staticmethod def _basic_security_checks(): @@ -490,7 +491,9 @@ bearer_token, session.get('authuser'), ) - + if authuser is None: + log.info('No valid user found') + raise webob.exc.HTTPForbidden() if not AuthUser.check_ip_allowed(authuser, request.ip_addr): raise webob.exc.HTTPForbidden()