views.py 8.81 KB
Newer Older
1
# copyright 2009-2021 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
Sylvain Thénault's avatar
Sylvain Thénault committed
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# contact http://www.logilab.fr -- mailto:contact@logilab.fr
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 2.1 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
"""anonymous registration form and views"""
Florent's avatar
Florent committed
17

18
19
from yams.schema import role_name

20
from cubicweb import mail, crypto
21
from cubicweb.view import StartupView
Aurelien Campeas's avatar
Aurelien Campeas committed
22
from cubicweb.predicates import anonymous_user
23
from cubicweb.web import Redirect, ValidationError, ProcessFormError
24
25
from cubicweb.web import controller, form, captcha
from cubicweb.web import formwidgets as fw, formfields as ff
Sylvain Thénault's avatar
Sylvain Thénault committed
26
from cubicweb.web.views import forms, basecomponents, urlrewrite
Florent's avatar
Florent committed
27

Katia Saurfelt's avatar
Katia Saurfelt committed
28
29
from six import text_type as unicode
from cubicweb import _
30
31


32
33
def qname(attr):
    return role_name(attr, 'subject')
Florent's avatar
Florent committed
34
35


Sylvain Thénault's avatar
Sylvain Thénault committed
36
class RegistrationForm(forms.FieldsForm):
Sylvain Thénault's avatar
Sylvain Thénault committed
37
    __regid__ = 'registration'
38
39
    domid = 'registrationForm'
    form_buttons = [fw.SubmitButton()]
Florent's avatar
Florent committed
40

Sylvain Thénault's avatar
Sylvain Thénault committed
41
42
    @property
    def action(self):
Sylvain Thénault's avatar
Sylvain Thénault committed
43
        return self._cw.build_url(u'registration_sendmail')
44

45
    # properly name fields according to validation errors that may be raised by
46
    # the register_user service
47
48
49
50
51
52
    login = ff.StringField(widget=fw.TextInput(), role='subject',
                           # we don't want to see 'authenticate'
                           label=_('i18n_register_login'),
                           required=True)
    upassword = ff.StringField(widget=fw.PasswordInput(), role='subject',
                               required=True)
53
54
    email_address = ff.StringField(widget=fw.TextInput(), role='subject',
                                   required=True, label=_('i18n_email_address'))
55
56
    firstname = ff.StringField(widget=fw.TextInput(), role='subject')
    surname = ff.StringField(widget=fw.TextInput(), role='subject')
57
    captcha = ff.StringField(widget=captcha.CaptchaWidget(), required=True,
Sylvain Thénault's avatar
Sylvain Thénault committed
58
59
                             label=_('captcha'),
                             help=_('please copy the letters from the image'))
60
61


Sylvain Thénault's avatar
Sylvain Thénault committed
62
class RegistrationFormView(form.FormViewMixIn, StartupView):
Sylvain Thénault's avatar
Sylvain Thénault committed
63
    __regid__ = 'registration'
64

Sylvain Thénault's avatar
Sylvain Thénault committed
65
    def call(self):
Sylvain Thénault's avatar
Sylvain Thénault committed
66
        form = self._cw.vreg['forms'].select('registration', self._cw)
67
        form.render(w=self.w, display_progress_div=False)
Florent's avatar
Florent committed
68

Sylvain Thénault's avatar
Sylvain Thénault committed
69

Sylvain Thénault's avatar
Sylvain Thénault committed
70
class RegistrationSendMailController(controller.Controller):
Sylvain Thénault's avatar
Sylvain Thénault committed
71
    __regid__ = 'registration_sendmail'
Sylvain Thénault's avatar
Sylvain Thénault committed
72
    content = _(u'''
73
Hello %(firstname-subject)s %(surname-subject)s,
Florent's avatar
Florent committed
74
75
76
77

thanks for registering on %(base_url)s.

Please click on the link below to activate your account :
78
%(url)s
Florent's avatar
Florent committed
79
80

See you soon on %(base_url)s !
Sylvain Thénault's avatar
Sylvain Thénault committed
81
82
''')
    subject = _(u'Confirm your registration on %(base_url)s')
Florent's avatar
Florent committed
83

Sylvain Thénault's avatar
Sylvain Thénault committed
84
85
    def publish(self, rset=None):
        data = self.checked_data()
86
        recipient = data[qname('email_address')]
Sylvain Thénault's avatar
Sylvain Thénault committed
87
        msg = self.build_email(recipient, data)
Sylvain Thénault's avatar
Sylvain Thénault committed
88
        self._cw.vreg.config.sendmails([(msg, (recipient,))])
Sylvain Thénault's avatar
Sylvain Thénault committed
89
        raise Redirect(self.success_redirect_url())
Florent's avatar
Florent committed
90
91

    def checked_data(self):
Sylvain Thénault's avatar
Sylvain Thénault committed
92
93
94
        '''only basic data check here (required attributes and password
        confirmation check)
        '''
95
        form = self._cw.vreg['forms'].select('registration', self._cw)
Sylvain Thénault's avatar
Sylvain Thénault committed
96
        form.formvalues = {}  # init fields value cache
Sylvain Thénault's avatar
Sylvain Thénault committed
97
98
        data = {}
        errors = {}
99
100
101
102
103
        for field in form.fields:
            try:
                for field, value in field.process_posted(form):
                    if value is not None:
                        data[field.role_name()] = value
Sylvain Thénault's avatar
Sylvain Thénault committed
104
            except ProcessFormError as exc:
105
                errors[field.role_name()] = unicode(exc)
106
107
        if errors:
            raise ValidationError(None, errors)
Florent's avatar
Florent committed
108
109
        return data

Sylvain Thénault's avatar
Sylvain Thénault committed
110
    def build_email(self, recipient, data):
Sylvain Thénault's avatar
Sylvain Thénault committed
111
        activationurl = self.activation_url(data)  # build url before modifying data
112
113
114
115
        data.setdefault(qname('firstname'), '')
        data.setdefault(qname('surname'), '')
        if not (data.get(qname('firstname')) or data.get(qname('surname'))):
            data[qname('firstname')] = data[qname('login')]
116
        data.update({'base_url': self._cw.base_url(secure=True),
117
                     'url': activationurl})
Sylvain Thénault's avatar
Sylvain Thénault committed
118
119
        content = self._cw._(self.content) % data
        subject = self._cw._(self.subject) % data
Sylvain Thénault's avatar
Sylvain Thénault committed
120
        return mail.format_mail({}, [recipient], content=content,
Sylvain Thénault's avatar
Sylvain Thénault committed
121
                                subject=subject, config=self._cw.vreg.config)
Florent's avatar
Florent committed
122

Sylvain Thénault's avatar
Sylvain Thénault committed
123
    def activation_url(self, data):
124
        data.pop(qname('upassword') + '-confirm', None)
125
        key = crypto.encrypt(data, self._cw.vreg.config['registration-cypher-seed'])
126
        return self._cw.build_url('registration_confirm', key=key, __secure__=True)
Florent's avatar
Florent committed
127

Sylvain Thénault's avatar
Sylvain Thénault committed
128
    def success_redirect_url(self):
Sylvain Thénault's avatar
Sylvain Thénault committed
129
        msg = self._cw._(u'Your registration email has been sent. Follow '
Sylvain Thénault's avatar
Sylvain Thénault committed
130
                         'instructions in there to activate your account.')
Sylvain Thénault's avatar
Sylvain Thénault committed
131
        return self._cw.build_url('', __message=msg)
Florent's avatar
Florent committed
132
133


Sylvain Thénault's avatar
Sylvain Thénault committed
134
class RegistrationConfirmController(controller.Controller):
Sylvain Thénault's avatar
Sylvain Thénault committed
135
    __regid__ = 'registration_confirm'
Florent's avatar
Florent committed
136
137

    def publish(self, rset=None):
Sylvain Thénault's avatar
Sylvain Thénault committed
138
        req = self._cw
Florent's avatar
Florent committed
139
        try:
140
141
            data = crypto.decrypt(req.form['key'],
                                  req.vreg.config['registration-cypher-seed'])
142
143
            login = data[qname('login')]
            password = data.pop(qname('upassword'))
Katia Saurfelt's avatar
Katia Saurfelt committed
144
        except Exception:
Sylvain Thénault's avatar
Sylvain Thénault committed
145
146
            msg = req._(u'Invalid registration data. Please try registering again.')
            raise Redirect(req.build_url(u'register', __message=msg))
147
148
        if self._cw.user.login == login:
            # already logged in (e.g. regstration link replayed twice in the browser)
149
            raise Redirect(self.success_redirect_url(self._cw.user.name()))
Sylvain Thénault's avatar
Sylvain Thénault committed
150
        req.form = data  # hijack for proper validation error handling
151
        err = None
152
        try:
153
154
155
156
157
158
159
            with self.appli.repo.internal_cnx() as cnx:
                cnx.call_service('register_user',
                                 login=login, password=password,
                                 email=data.get(qname('email_address')),
                                 firstname=data.get(qname('firstname')),
                                 surname=data.get(qname('surname')))
                cnx.commit()
Katia Saurfelt's avatar
Katia Saurfelt committed
160
        except ValidationError as err:
161
162
163
164
            if 'login' in err.errors and 'unicity constraint' in err.errors['login']:
                err.errors.pop('', None)
                err.errors.pop('login', None)
                err.errors['login-subject'] = _(
165
                    'the value "{}" is already used, use another one').format(login)
166
                err.entity = None
167
168
169
170
171
            # XXX TEMPORARY HACK to allow registration links to work more than
            # once. This is required because some email clients (e.g. kmail)
            # start by downloading the url to find the mimetype of the resource
            # and then execute the appropriate action (e.g. open the url in the
            # default browser) based on the mimetype.
172
            if err.errors.keys() != ['login-subject']:
173
                raise
174
        # try to connect using the provided credentials
Florent's avatar
Florent committed
175
        try:
176
177
178
179
180
            from cubicweb import repoapi
            cnx = repoapi.connect(self.appli.repo, login, password=password)
            with cnx:
                name = cnx.user.name()
            raise Redirect(self.success_redirect_url(name))
Katia Saurfelt's avatar
WIp    
Katia Saurfelt committed
181
        except Exception as err:
182
            if err is not None:
183
184
185
                # both registration and login failed, re-raise the previous
                # ValidationError
                raise err
186
            raise
Florent's avatar
Florent committed
187

188
    def success_redirect_url(self, name):
189
        login = self._cw.build_url('login')
Sylvain Thénault's avatar
Sylvain Thénault committed
190
        msg = self._cw._(u'Congratulations, your registration is complete. '
191
192
                         'You can now <a href="{}">login</a>.').format(login)
        return self._cw.build_url('', __message=msg)
Sylvain Thénault's avatar
Sylvain Thénault committed
193
194


195
196
197
198
199
200
class RegisterLink(basecomponents.HeaderComponent):
    __regid__ = 'registration.registerlink'
    __select__ = basecomponents.HeaderComponent.__select__ & anonymous_user()
    context = 'header-right'

    def render(self, w):
201
202
        self._cw.add_css('cubicweb.pictograms.css')
        w(u'<a class="logout icon-user-add" title="%s" href="%s"></a>' % (
Sylvain Thénault's avatar
Sylvain Thénault committed
203
204
            self._cw._('i18n_register_user'), self._cw.build_url('register'), ))

Florent's avatar
Florent committed
205

Sylvain Thénault's avatar
Sylvain Thénault committed
206
# urls #########################################################################
Florent's avatar
Florent committed
207

Sylvain Thénault's avatar
Sylvain Thénault committed
208
class RegistrationSimpleReqRewriter(urlrewrite.SimpleReqRewriter):
209
    ignore_baseclass_rules = True
Florent's avatar
Florent committed
210
    rules = [
Sylvain Thénault's avatar
Sylvain Thénault committed
211
        ('/register', dict(vid='registration')),
Sylvain Thénault's avatar
Sylvain Thénault committed
212
    ]