|  | @@ -1,37 +1,115 @@
 | 
	
		
			
			|  | 1 | +# -*- coding: utf-8 -*-
 | 
	
		
			
			| 1 | 2 |  import typing
 | 
	
		
			
			|  | 3 | +
 | 
	
		
			
			|  | 4 | +from json.decoder import JSONDecodeError
 | 
	
		
			
			|  | 5 | +from sqlalchemy.orm.exc import NoResultFound
 | 
	
		
			
			|  | 6 | +
 | 
	
		
			
			|  | 7 | +from pyramid.request import Request
 | 
	
		
			
			| 2 | 8 |  from pyramid.security import ALL_PERMISSIONS
 | 
	
		
			
			| 3 | 9 |  from pyramid.security import Allow
 | 
	
		
			
			| 4 |  | -from pyramid.security import Authenticated
 | 
	
		
			
			| 5 |  | -from tracim.lib.core.user import UserApi
 | 
	
		
			
			|  | 10 | +from pyramid.security import unauthenticated_userid
 | 
	
		
			
			|  | 11 | +
 | 
	
		
			
			| 6 | 12 |  from tracim.models.auth import Group
 | 
	
		
			
			|  | 13 | +from tracim.models.auth import User
 | 
	
		
			
			|  | 14 | +from tracim.models.data import Workspace
 | 
	
		
			
			|  | 15 | +from tracim.models.data import UserRoleInWorkspace
 | 
	
		
			
			|  | 16 | +from tracim.lib.core.user import UserApi
 | 
	
		
			
			| 7 | 17 |  from tracim.lib.core.workspace import WorkspaceApi
 | 
	
		
			
			|  | 18 | +from tracim.lib.core.userworkspace import RoleApi
 | 
	
		
			
			| 8 | 19 |  
 | 
	
		
			
			| 9 | 20 |  # INFO - G.M - 06-04-2018 - Auth for pyramid
 | 
	
		
			
			| 10 | 21 |  # based on this tutorial : https://docs.pylonsproject.org/projects/pyramid-cookbook/en/latest/auth/basic.html  # nopep8
 | 
	
		
			
			|  | 22 | +BASIC_AUTH_WEBUI_REALM = "tracim"
 | 
	
		
			
			| 11 | 23 |  
 | 
	
		
			
			|  | 24 | +# Global Permissions
 | 
	
		
			
			|  | 25 | +ADMIN_PERM = 'admin'
 | 
	
		
			
			|  | 26 | +MANAGE_GLOBAL_PERM = 'manage_global'
 | 
	
		
			
			|  | 27 | +USER_PERM = 'user'
 | 
	
		
			
			|  | 28 | +# Workspace-specific permission
 | 
	
		
			
			|  | 29 | +READ_PERM = 'read'
 | 
	
		
			
			|  | 30 | +CONTRIBUTE_PERM = 'contribute'
 | 
	
		
			
			|  | 31 | +MANAGE_CONTENT_PERM = 'manage_content'
 | 
	
		
			
			|  | 32 | +MANAGE_WORKSPACE_PERM = 'manage_workspace'
 | 
	
		
			
			| 12 | 33 |  
 | 
	
		
			
			| 13 |  | -def check_credentials(username, password, request) -> typing.Optional[dict]:
 | 
	
		
			
			| 14 |  | -    permissions = None
 | 
	
		
			
			|  | 34 | +
 | 
	
		
			
			|  | 35 | +def get_user(request: Request) -> typing.Optional[User]:
 | 
	
		
			
			|  | 36 | +    """
 | 
	
		
			
			|  | 37 | +    Get current pyramid user from request
 | 
	
		
			
			|  | 38 | +    :param request: pyramid request
 | 
	
		
			
			|  | 39 | +    :return:
 | 
	
		
			
			|  | 40 | +    """
 | 
	
		
			
			| 15 | 41 |      app_config = request.registry.settings['CFG']
 | 
	
		
			
			| 16 | 42 |      uapi = UserApi(None, session=request.dbsession, config=app_config)
 | 
	
		
			
			|  | 43 | +    user = None
 | 
	
		
			
			| 17 | 44 |      try:
 | 
	
		
			
			| 18 |  | -        user = uapi.get_one_by_email(username)
 | 
	
		
			
			| 19 |  | -        if user.validate_password(password):
 | 
	
		
			
			| 20 |  | -            permissions = []
 | 
	
		
			
			| 21 |  | -            for group in user.groups:
 | 
	
		
			
			| 22 |  | -                permissions.append(group.group_name)
 | 
	
		
			
			| 23 |  | -            # TODO - G.M - 06-04-2018 - Add workspace specific permission ?
 | 
	
		
			
			| 24 |  | -    # TODO - G.M - 06-04-2018 - Better catch for exception of bad password, bad
 | 
	
		
			
			| 25 |  | -    # user
 | 
	
		
			
			| 26 |  | -    except:
 | 
	
		
			
			|  | 45 | +        login = unauthenticated_userid(request)
 | 
	
		
			
			|  | 46 | +        user = uapi.get_one_by_email(login)
 | 
	
		
			
			|  | 47 | +    except NoResultFound:
 | 
	
		
			
			| 27 | 48 |          pass
 | 
	
		
			
			|  | 49 | +    return user
 | 
	
		
			
			|  | 50 | +
 | 
	
		
			
			|  | 51 | +
 | 
	
		
			
			|  | 52 | +def get_workspace(request: Request) -> typing.Optional[Workspace]:
 | 
	
		
			
			|  | 53 | +    """
 | 
	
		
			
			|  | 54 | +    Get current workspace from request
 | 
	
		
			
			|  | 55 | +    :param request: pyramid request
 | 
	
		
			
			|  | 56 | +    :return:
 | 
	
		
			
			|  | 57 | +    """
 | 
	
		
			
			|  | 58 | +    workspace = None
 | 
	
		
			
			|  | 59 | +    try:
 | 
	
		
			
			|  | 60 | +        if 'workspace_id' not in request.json_body:
 | 
	
		
			
			|  | 61 | +            return None
 | 
	
		
			
			|  | 62 | +        workspace_id = request.json_body['workspace_id']
 | 
	
		
			
			|  | 63 | +        wapi = WorkspaceApi(current_user=None, session=request.dbsession)
 | 
	
		
			
			|  | 64 | +        workspace = wapi.get_one(workspace_id)
 | 
	
		
			
			|  | 65 | +    except JSONDecodeError:
 | 
	
		
			
			|  | 66 | +        pass
 | 
	
		
			
			|  | 67 | +    except NoResultFound:
 | 
	
		
			
			|  | 68 | +        pass
 | 
	
		
			
			|  | 69 | +    return workspace
 | 
	
		
			
			|  | 70 | +
 | 
	
		
			
			|  | 71 | +
 | 
	
		
			
			|  | 72 | +def check_credentials(
 | 
	
		
			
			|  | 73 | +        login: str,
 | 
	
		
			
			|  | 74 | +        cleartext_password: str,
 | 
	
		
			
			|  | 75 | +        request: Request
 | 
	
		
			
			|  | 76 | +) -> typing.Optional[list]:
 | 
	
		
			
			|  | 77 | +    """
 | 
	
		
			
			|  | 78 | +    Check credential for pyramid basic_auth, checks also for
 | 
	
		
			
			|  | 79 | +    global and Workspace related permissions.
 | 
	
		
			
			|  | 80 | +    :param login: login of user
 | 
	
		
			
			|  | 81 | +    :param cleartext_password: user password in cleartext
 | 
	
		
			
			|  | 82 | +    :param request: Pyramid request
 | 
	
		
			
			|  | 83 | +    :return: None if auth failed, list of permissions if auth succeed
 | 
	
		
			
			|  | 84 | +    """
 | 
	
		
			
			|  | 85 | +    user = get_user(request)
 | 
	
		
			
			|  | 86 | +
 | 
	
		
			
			|  | 87 | +    # Do not accept invalid user
 | 
	
		
			
			|  | 88 | +    if not user \
 | 
	
		
			
			|  | 89 | +            or user.email != login \
 | 
	
		
			
			|  | 90 | +            or not user.validate_password(cleartext_password):
 | 
	
		
			
			|  | 91 | +        return None
 | 
	
		
			
			|  | 92 | +    permissions = []
 | 
	
		
			
			|  | 93 | +
 | 
	
		
			
			|  | 94 | +    # Global groups
 | 
	
		
			
			|  | 95 | +    for group in user.groups:
 | 
	
		
			
			|  | 96 | +        permissions.append(group.group_id)
 | 
	
		
			
			|  | 97 | +
 | 
	
		
			
			|  | 98 | +    # Current workspace related group
 | 
	
		
			
			|  | 99 | +    workspace = get_workspace(request)
 | 
	
		
			
			|  | 100 | +    if workspace:
 | 
	
		
			
			|  | 101 | +        roleapi = RoleApi(current_user=user, session=request.dbsession)
 | 
	
		
			
			|  | 102 | +        role = roleapi.get_one(
 | 
	
		
			
			|  | 103 | +            user_id=user.user_id,
 | 
	
		
			
			|  | 104 | +            workspace_id=workspace.workspace_id,
 | 
	
		
			
			|  | 105 | +        )
 | 
	
		
			
			|  | 106 | +        permissions.append(role)
 | 
	
		
			
			|  | 107 | +
 | 
	
		
			
			| 28 | 108 |      return permissions
 | 
	
		
			
			| 29 | 109 |  
 | 
	
		
			
			| 30 | 110 |  
 | 
	
		
			
			| 31 |  | -class Root:
 | 
	
		
			
			| 32 |  | -    # root
 | 
	
		
			
			| 33 |  | -    __acl__ = (
 | 
	
		
			
			| 34 |  | -        (Allow, Group.TIM_ADMIN_GROUPNAME, ALL_PERMISSIONS),
 | 
	
		
			
			| 35 |  | -        (Allow, Group.TIM_MANAGER_GROUPNAME, 'manager'),
 | 
	
		
			
			| 36 |  | -        (Allow, Group.TIM_USER_GROUPNAME, 'user'),
 | 
	
		
			
			| 37 |  | -    )
 | 
	
		
			
			|  | 111 | +class Root(object):
 | 
	
		
			
			|  | 112 | +    """
 | 
	
		
			
			|  | 113 | +    Root of all Pyramid requests, used to store global acl
 | 
	
		
			
			|  | 114 | +    """
 | 
	
		
			
			|  | 115 | +    __acl__ = ()
 |