Changeset - 077ba994ee03
[Not reviewed]
default
0 5 0
Mads Kiilerich - 7 years ago 2019-01-03 01:22:45
mads@kiilerich.com
auth: introduce AuthUser.make factory which can return None if user can't be authenticated
5 files changed with 62 insertions and 52 deletions:
0 comments (0 inline, 0 general)
kallithea/controllers/api/__init__.py
Show inline comments
 
@@ -146,12 +146,11 @@ class JSONRPCController(TGController):
 
        # 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 @@ class JSONRPCController(TGController):
 
            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 @@ class JSONRPCController(TGController):
 
        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'
kallithea/controllers/login.py
Show inline comments
 
@@ -102,8 +102,8 @@ class LoginController(BaseController):
 
                # 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
kallithea/lib/auth.py
Show inline comments
 
@@ -398,6 +398,20 @@ class AuthUser(object):
 
    "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 @@ class AuthUser(object):
 
    @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 @@ class HasPermissionAnyMiddleware(object)
 
    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
 

	
 

	
kallithea/lib/auth_modules/__init__.py
Show inline comments
 
@@ -362,7 +362,7 @@ def authenticate(username, password, env
 
                               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
 

	
kallithea/lib/base.py
Show inline comments
 
@@ -117,14 +117,16 @@ def log_in_user(user, remember, is_exter
 

	
 
    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 @@ class BaseVCSController(object):
 
        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 @@ class BaseVCSController(object):
 
        #==============================================================
 
        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 @@ class BaseVCSController(object):
 

	
 
        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 @@ class BaseVCSController(object):
 
        :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 @@ class BaseVCSController(object):
 

	
 
        if action == 'push':
 
            if not HasPermissionAnyMiddleware('repository.write',
 
                                              'repository.admin')(user,
 
                                              'repository.admin')(authuser,
 
                                                                  repo_name):
 
                return False
 

	
 
@@ -311,7 +309,7 @@ class BaseVCSController(object):
 
            #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 @@ class BaseController(TGController):
 
        """
 
        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 @@ class BaseController(TGController):
 

	
 
        # 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 @@ class BaseController(TGController):
 
                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 @@ class BaseController(TGController):
 
                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()
 

	
0 comments (0 inline, 0 general)