Passed
Push — 2.x ( 53afe3...d668b2 )
by Ramon
05:59
created

ContactLoginDetailsView._link_user()   A

Complexity

Conditions 3

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 10
dl 0
loc 14
rs 9.9
c 0
b 0
f 0
cc 3
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-2021 by it's authors.
19
# Some rights reserved, see README and LICENSE.
20
21
import re
22
23
import transaction
24
from bika.lims import PMF
25
from bika.lims import api
26
from bika.lims import bikaMessageFactory as _
27
from bika.lims import logger
28
from bika.lims.api import security
29
from bika.lims.browser import BrowserView
30
from bika.lims.content.contact import Contact
31
from bika.lims.content.labcontact import LabContact
32
from plone.memoize import view
33
from plone.protect import CheckAuthenticator
34
from Products.CMFCore.utils import getToolByName
35
from Products.CMFPlone.controlpanel.browser.usergroups_usersoverview import \
36
    UsersOverviewControlPanel
37
from Products.CMFPlone.utils import safe_unicode
38
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
39
from senaite.core.config.groups import HIDDEN_GROUPS
40
from senaite.core.p3compat import cmp
41
42
43
class ContactLoginDetailsView(BrowserView):
44
    """Contact Login View
45
    """
46
    template = ViewPageTemplateFile("templates/login_details.pt")
47
48
    def __call__(self):
49
        request = self.request
50
        form = request.form
51
        CheckAuthenticator(form)
52
53
        self.newSearch = False
54
        self.searchstring = form.get("searchstring", "")
55
56
        if form.get("submitted"):
57
            logger.debug("Form Submitted: {}".format(form))
58
            if form.get("unlink_button", False):
59
                self._unlink_user()
60
            elif form.get("search_button", False):
61
                logger.debug("Search User")
62
                self.newSearch = True
63
            elif form.get("link_button", False):
64
                logger.debug("Link User")
65
                self._link_user(form.get("userid"))
66
            elif form.get("save_button", False):
67
                logger.debug("Create User")
68
                self._create_user()
69
70
        return self.template()
71
72
    @view.memoize
73
    def get_users(self):
74
        """Get all users of the portal
75
        """
76
        # We make use of the existing controlpanel `@@usergroup-userprefs`
77
        # view logic to make sure we get all users from all plugins (e.g. LDAP)
78
        users_view = UsersOverviewControlPanel(self.context, self.request)
79
        return users_view.doSearch("")
80
81
    def get_laboratory_groups(self):
82
        """Return the groups available for laboratory users
83
        """
84
        gtool = api.get_tool("portal_groups")
85
        groups = gtool.listGroupIds()
86
87
        # exclude hidden groups (Administrators, etc.)
88
        groups = filter(lambda group: group not in HIDDEN_GROUPS, groups)
89
90
        # sort them
91
        return sorted(groups)
92
93
    def get_user_properties(self):
94
        """Return the properties of the User
95
        """
96
97
        user = self.context.getUser()
98
99
        # No User linked, nothing to do
100
        if user is None:
101
            return {}
102
103
        out = {}
104
        plone_user = user.getUser()
105
        userid = plone_user.getId()
106
        for sheet in plone_user.listPropertysheets():
107
            ps = plone_user.getPropertysheet(sheet)
108
            out.update(dict(ps.propertyItems()))
109
110
        portal = api.get_portal()
111
        mtool = getToolByName(self.context, 'portal_membership')
112
113
        out["id"] = userid
114
        out["portrait"] = mtool.getPersonalPortrait(id=userid)
115
        out["edit_url"] = "{}/@@user-information?userid={}".format(
116
            portal.absolute_url(), userid)
117
118
        return out
119
120
    def get_contact_properties(self):
121
        """Return the properties of the Contact
122
        """
123
        contact = self.context
124
125
        return {
126
            "fullname": contact.getFullname(),
127
            "username": contact.getUsername(),
128
        }
129
130
    def linkable_users(self):
131
        """Search Plone users which are not linked to a contact or lab contact
132
        """
133
134
        # Only users with at nost these roles are displayed
135
        linkable_roles = {"Authenticated", "Member", "Client"}
136
137
        out = []
138
        for user in self.get_users():
139
            userid = user.get("id", None)
140
141
            if userid is None:
142
                continue
143
144
            # Skip users which are already linked to a Contact
145
            contact = Contact.getContactByUsername(userid)
146
            labcontact = LabContact.getContactByUsername(userid)
147
148
            if contact or labcontact:
149
                continue
150
            if self.is_contact():
151
                # Checking Plone user belongs to Client group only. Otherwise,
152
                # weird things could happen (a client contact assigned to a
153
                # user with labman privileges, different contacts from
154
                # different clients assigned to the same user, etc.)
155
                user_roles = security.get_roles(user=userid)
156
                if not linkable_roles.issuperset(set(user_roles)):
157
                    continue
158
            userdata = {
159
                "userid": userid,
160
                "email": user.get("email"),
161
                "fullname": user.get("title"),
162
            }
163
164
            # filter out users which do not match the searchstring
165
            if self.searchstring:
166
                s = self.searchstring.lower()
167
                if not any(
168
                        map(lambda v: re.search(s, str(v).lower()),
0 ignored issues
show
introduced by
The variable s does not seem to be defined for all execution paths.
Loading history...
169
                            userdata.values())):
170
                    continue
171
172
            # update data (maybe for later use)
173
            userdata.update(user)
174
175
            # Append the userdata for the results
176
            out.append(userdata)
177
178
        out.sort(lambda x, y: cmp(x["fullname"], y["fullname"]))
179
        return out
180
181
    def is_contact(self):
182
        """Check if the current context is a Contact
183
        """
184
        if self.context.portal_type == "Contact":
185
            return True
186
        return False
187
188
    def is_labcontact(self):
189
        """Check if the current context is a LabContact
190
        """
191
        if self.context.portal_type == "LabContact":
192
            return True
193
        return False
194
195
    def _link_user(self, userid):
196
        """Link an existing user to the current Contact
197
        """
198
        # check if we have a selected user from the search-list
199
        if userid:
200
            try:
201
                self.context.setUser(userid)
202
                self.add_status_message(
203
                    _("User linked to this Contact"), "info")
204
            except ValueError as e:
205
                self.add_status_message(e, "error")
206
        else:
207
            self.add_status_message(
208
                _("Please select a User from the list"), "info")
209
210
    def _unlink_user(self):
211
        """Unlink and delete the User from the current Contact
212
        """
213
        self.context.unlinkUser()
214
        self.add_status_message(_("Unlinked User"), "info")
215
216
    def add_status_message(self, message, severity="info"):
217
        """Set a portal message
218
        """
219
        self.context.plone_utils.addPortalMessage(message, severity)
220
221
    def _create_user(self):
222
        """Create a new user
223
        """
224
225
        def error(field, message):
226
            if field:
227
                message = "%s: %s" % (field, message)
228
            self.context.plone_utils.addPortalMessage(message, 'error')
229
            return self.request.response.redirect(
230
                self.context.absolute_url() + "/login_details")
231
232
        form = self.request.form
233
        contact = self.context
234
235
        password = safe_unicode(form.get('password', '')).encode('utf-8')
236
        username = safe_unicode(form.get('username', '')).encode('utf-8')
237
        confirm = form.get('confirm', '')
238
        email = safe_unicode(form.get('email', '')).encode('utf-8')
239
240
        if not username:
241
            return error('username', PMF("Input is required but not given."))
242
243
        if not email:
244
            return error('email', PMF("Input is required but not given."))
245
246
        reg_tool = self.context.portal_registration
247
        # properties = self.context.portal_properties.site_properties
248
        # if properties.validate_email:
249
        #     password = reg_tool.generatePassword()
250
        # else:
251
        if password != confirm:
252
            return error('password', PMF("Passwords do not match."))
253
254
        if not password:
255
            return error('password', PMF("Input is required but not given."))
256
257
        if not confirm:
258
            return error('password', PMF("Passwords do not match."))
259
260
        if len(password) < 5:
261
            return error('password', PMF("Passwords must contain at least 5 "
262
                                         "characters."))
263
        for user in self.get_users():
264
            userid = user.get("id", None)
265
            if userid is None:
266
                continue
267
            user_obj = api.get_user(userid)
268
            if user_obj.getUserName() == username:
269
                msg = "Username {} already exists, please, choose " \
270
                      "another one.".format(username)
271
                return error(None, msg)
272
273
        try:
274
            reg_tool.addMember(username,
275
                               password,
276
                               properties={
277
                                   'username': username,
278
                                   'email': email,
279
                                   'fullname': username})
280
        except ValueError as msg:
281
            return error(None, msg)
282
283
        # set the user to the contact
284
        contact.setUser(username)
285
286
        # Additional groups for LabContact users only!
287
        # -> This is not visible in the Client Contact Form
288
        if "groups" in self.request and self.request["groups"]:
289
            groups = self.request["groups"]
290
            if not type(groups) in (list, tuple):
291
                groups = [groups, ]
292
            for group in groups:
293
                group = self.portal_groups.getGroupById(group)
294
                group.addMember(username)
295
296
        if self.request.get('mail_me', 0):
297
            try:
298
                reg_tool.registeredNotify(username)
299
            except Exception:
300
                transaction.abort()
301
                message = _("SMTP server disconnected. User creation aborted.")
302
                return error(None, message)
303
304
        message = _("Member registered and linked to the current Contact.")
305
        self.context.plone_utils.addPortalMessage(message, 'info')
306
        return self.request.response.redirect(
307
            self.context.absolute_url() + "/login_details")
308
309
    def tabindex(self):
310
        i = 0
311
        while True:
312
            i += 1
313
            yield i
314