Passed
Push — 2.x ( 5710a9...27b54e )
by Ramon
04:59
created

ClientShareableLocalRoles.__init__()   A

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 2
rs 10
c 0
b 0
f 0
cc 1
nop 2
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.behaviors import IClientShareableBehavior
28
from senaite.core.interfaces import IDynamicLocalRoles
29
from zope.component import getAdapters
30
from zope.interface import implementer
31
32
33
def _getRolesInContext_cachekey(method, self, context, principal_id):
34
    """Function that generates the key for volatile caching
35
    """
36
    # We need the cachekey to change when global roles of a given user change
37
    user = api.get_user(principal_id)
38
    roles = user and ":".join(sorted(user.getRoles())) or ""
39
    return ".".join([
40
        principal_id,
41
        roles,
42
        api.get_path(context),
43
        api.get_modification_date(context).ISO(),
44
    ])
45
46
47
class DynamicLocalRoleAdapter(DefaultLocalRoleAdapter):
48
    """Gives additional Member local roles based on current user and context
49
    This enables giving additional permissions on items out of the user's
50
    current traverse path
51
    """
52
53
    @ram.cache(_getRolesInContext_cachekey)
54
    def getRolesInContext(self, context, principal_id):
55
        """Returns the dynamically calculated 'local' roles for the given
56
        principal and context
57
        @param context: context to calculate roles for the given principal
58
        @param principal_id: User login id
59
        @return List of dynamically calculated local-roles for user and context
60
        """
61
        if not api.get_user(principal_id):
62
            # principal_id can be a group name, but we consider users only
63
            return []
64
65
        roles = set()
66
        path = api.get_path(context)
67
        adapters = getAdapters((context,), IDynamicLocalRoles)
68
        for name, adapter in adapters:
69
            local_roles = adapter.getRoles(principal_id)
70
            if local_roles:
71
                logger.info(u"{}::{}::{}: {}".format(name, path, principal_id,
72
                                                     repr(local_roles)))
73
            roles.update(local_roles)
74
        return list(roles)
75
76
    def getRoles(self, principal_id):
77
        """Returns both non-local and local roles for the given principal in
78
        current context
79
        @param principal_id: User login id
80
        @return: list of non-local and local roles for the user and context
81
        """
82
        default_roles = self._rolemap.get(principal_id, [])
83
        if not api.is_object(self.context):
84
            # We only apply dynamic local roles to valid objects
85
            return default_roles[:]
86
87
        # Extend with dynamically computed roles
88
        dynamic_roles = self.getRolesInContext(self.context, principal_id)
89
        return list(set(default_roles + dynamic_roles))
90
91
    def getAllRoles(self):
92
        roles = {}
93
        # Iterate through all members to extract their dynamic local role for
94
        # current context
95
        mtool = api.get_tool("portal_membership")
96
        for principal_id in mtool.listMemberIds():
97
            user_roles = self.getRoles(principal_id)
98
            if user_roles:
99
                roles.update({principal_id: user_roles})
100
        return six.iteritems(roles)
101
102
103
@implementer(IDynamicLocalRoles)
104
class ClientAwareLocalRoles(object):
105
    """Adapter for the assignment of dynamic local roles to users that are
106
    linked to a ClientContact for objects that belong to same client
107
    """
108
109
    def __init__(self, context):
110
        self.context = context
111
112
    def hasContact(self, client, principal_id):
113
        """Returns whether the client passed in has a contact linked to a user
114
        with the given principal_id
115
        """
116
        query = {
117
            "portal_type": "Contact",
118
            "getUsername": principal_id,
119
            "getParentUID": api.get_uid(client),
120
        }
121
        brains = api.search(query, catalog="portal_catalog")
122
        return len(brains) == 1
123
124
    def getRoles(self, principal_id):
125
        """Returns ["Owner"] local role if the user is linked to a Client
126
        Contact that belongs to the same client as the current context
127
        """
128
        # Get the client of current context, if any
129
        client = get_client(self.context)
130
        if not client:
131
            return []
132
133
        # Check if the user belongs to same client as context
134
        if not self.hasContact(client, principal_id):
135
            return []
136
137
        return ["Owner"]
138
139
140
@implementer(IDynamicLocalRoles)
141
class ClientShareableLocalRoles(object):
142
    """Adapter for the assignment of roles for content shared across clients
143
    """
144
145
    def __init__(self, context):
146
        self.context = context
147
148
    def getRoles(self, principal_id):
149
        """Returns ["ClientGuest"] local role if the current context is
150
        shareable across clients and the user for the principal_id belongs to
151
        one of the clients for which the context can be shared
152
        """
153
        # Get the clients this context is shared with
154
        behavior = IClientShareableBehavior(self.context)
155
        clients = filter(api.is_uid, behavior.getRawClients())
156
        if not clients:
157
            return []
158
159
        # Check if the user belongs to at least one of the clients
160
        # this context is shared with
161
        query = {
162
            "portal_type": "Contact",
163
            "getUsername": principal_id,
164
            "getParentUID": clients,
165
        }
166
        brains = api.search(query, catalog="portal_catalog")
167
        if len(brains) == 0:
168
            return []
169
170
        return ["ClientGuest"]
171