Passed
Push — 2.x ( 385387...a6c268 )
by Ramon
07:34 queued 02:39
created

_getRolesInContext_cachekey()   A

Complexity

Conditions 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 1
nop 4
1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of SENAITE.CORE.
4
#
5
# SENAITE.CORE is free software: you can redistribute it and/or modify it under
6
# the terms of the GNU General Public License as published by the Free Software
7
# Foundation, version 2.
8
#
9
# This program is distributed in the hope that it will be useful, but WITHOUT
10
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12
# details.
13
#
14
# You should have received a copy of the GNU General Public License along with
15
# this program; if not, write to the Free Software Foundation, Inc., 51
16
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
#
18
# Copyright 2018-2022 by it's authors.
19
# Some rights reserved, see README and LICENSE.
20
21
import six
22
from bika.lims import api
23
from bika.lims import logger
24
from bika.lims.utils import get_client
25
from borg.localrole.default_adapter import DefaultLocalRoleAdapter
26
from plone.memoize import ram
27
from senaite.core.interfaces import IDynamicLocalRoles
28
from zope.component import getAdapters
29
from zope.interface import implementer
30
31
32
def _getRolesInContext_cachekey(method, self, context, principal_id):
33
    """Function that generates the key for volatile caching
34
    """
35
    return ".".join([
36
        principal_id,
37
        api.get_path(context),
38
        api.get_modification_date(context).ISO(),
39
    ])
40
41
42
class DynamicLocalRoleAdapter(DefaultLocalRoleAdapter):
43
    """Gives additional Member local roles based on current user and context
44
    This enables giving additional permissions on items out of the user's
45
    current traverse path
46
    """
47
48
    @ram.cache(_getRolesInContext_cachekey)
49
    def getRolesInContext(self, context, principal_id):
50
        """Returns the dynamically calculated 'local' roles for the given
51
        principal and context
52
        @param context: context to calculate roles for the given principal
53
        @param principal_id: User login id
54
        @return List of dynamically calculated local-roles for user and context
55
        """
56
        roles = set()
57
        path = api.get_path(context)
58
        adapters = getAdapters((context,), IDynamicLocalRoles)
59
        for name, adapter in adapters:
60
            local_roles = adapter.getRoles(principal_id)
61
            if local_roles:
62
                logger.info(u"{}::{}::{}: {}".format(name, path, principal_id,
63
                                                     repr(local_roles)))
64
            roles.update(local_roles)
65
        return list(roles)
66
67
    def getRoles(self, principal_id):
68
        """Returns both non-local and local roles for the given principal in
69
        current context
70
        @param principal_id: User login id
71
        @return: list of non-local and local roles for the user and context
72
        """
73
        default_roles = self._rolemap.get(principal_id, [])
74
        if not api.is_object(self.context):
75
            # We only apply dynamic local roles to valid objects
76
            return default_roles[:]
77
78
        # Extend with dynamically computed roles
79
        dynamic_roles = self.getRolesInContext(self.context, principal_id)
80
        return list(set(default_roles + dynamic_roles))
81
82
    def getAllRoles(self):
83
        roles = {}
84
        # Iterate through all members to extract their dynamic local role for
85
        # current context
86
        mtool = api.get_tool("portal_membership")
87
        for principal_id in mtool.listMemberIds():
88
            user_roles = self.getRoles(principal_id)
89
            if user_roles:
90
                roles.update({principal_id: user_roles})
91
        return six.iteritems(roles)
92
93
94
@implementer(IDynamicLocalRoles)
95
class ClientAwareLocalRoles(object):
96
    """Adapter for the assignment of dynamic local roles to users that are
97
    linked to a ClientContact for objects that belong to same client
98
    """
99
100
    def __init__(self, context):
101
        self.context = context
102
103
    def hasContact(self, client, principal_id):
104
        """Returns whether the client passed in has a contact linked to a user
105
        with the given principal_id
106
        """
107
        query = {
108
            "portal_type": "Contact",
109
            "getUsername": principal_id,
110
            "getParentUID": api.get_uid(client),
111
        }
112
        brains = api.search(query, catalog="portal_catalog")
113
        return len(brains) == 1
114
115
    def getRoles(self, principal_id):
116
        """Returns ["Owner"] local role if the user is linked to a Client
117
        Contact that belongs to the same client as the current context
118
        """
119
        # Get the client of current context, if any
120
        client = get_client(self.context)
121
        if not client:
122
            return []
123
124
        # Check if the user belongs to same client as context
125
        if not self.hasContact(client, principal_id):
126
            return []
127
128
        return ["Owner"]
129