Projet

Général

Profil

« Précédent | Suivant » 

Révision 280c79a9

Ajouté par Frédéric Péters il y a plus d'un an

misc: remove root directory changes (#72822)

Voir les différences:

auquotidien/modules/root.py
1
import urllib.parse
2

  
3
from quixote import get_publisher, get_response, get_request, redirect, get_session
4
from quixote.directory import Directory
5
from quixote.html import TemplateIO, htmltext
6

  
7
from wcs.qommon import _
8
from wcs.qommon.misc import get_variadic_url, simplify
9

  
10
import os
11
import re
12
import string
13

  
14
try:
15
    import lasso
16
except ImportError:
17
    pass
18

  
19 1
import wcs
20 2
import wcs.root
21 3
from wcs import qommon
22
from wcs.forms.root import RootDirectory as FormsRootDirectory
23
from wcs.qommon import N_, get_cfg, get_logger
24
from wcs.qommon import template
25
from wcs.qommon import errors
26
from wcs.qommon.form import *
27
from wcs.qommon import logger
28
from wcs.roles import logged_users_role
29

  
30
from wcs.qommon import emails
31
from wcs.qommon.sms import SMS
32
from wcs.categories import Category
33
from wcs.formdef import FormDef
34
from wcs.data_sources import NamedDataSource
35
from wcs.qommon.tokens import Token
36
from wcs.qommon.admin.emails import EmailsDirectory
37
from wcs.qommon.admin.texts import TextsDirectory
38 4

  
39 5
import wcs.forms.root
40
from wcs.workflows import Workflow
41 6
from wcs.forms.preview import PreviewDirectory
42 7

  
43 8
from .saml2 import Saml2Directory
44 9

  
45 10
OldRootDirectory = wcs.root.RootDirectory
46 11

  
47
import wcs.qommon.ident.password
48
import wcs.qommon.ident.idp
49

  
50

  
51
def category_get_homepage_position(self):
52
    if hasattr(self, 'homepage_position') and self.homepage_position:
53
        return self.homepage_position
54
    if self.url_name == 'consultations':
55
        return '2nd'
56
    return '1st'
57

  
58

  
59
Category.get_homepage_position = category_get_homepage_position
60

  
61

  
62
def category_get_limit(self):
63
    if hasattr(self, 'limit') and self.limit is not None:
64
        return self.limit
65
    return 7
66

  
67

  
68
Category.get_limit = category_get_limit
69

  
70
Category.TEXT_ATTRIBUTES = ['name', 'url_name', 'description', 'homepage_position']
71
Category.INT_ATTRIBUTES = ['position', 'limit']
72

  
73
OldRegisterDirectory = wcs.root.RegisterDirectory
74

  
75

  
76
class AlternateRegisterDirectory(OldRegisterDirectory):
77
    def _q_traverse(self, path):
78
        return OldRegisterDirectory._q_traverse(self, path)
79

  
80
    def _q_index(self):
81
        get_logger().info('register')
82
        ident_methods = get_cfg('identification', {}).get('methods', [])
83

  
84
        if len(ident_methods) == 0:
85
            idps = get_cfg('idp', {})
86
            if len(idps) == 0:
87
                return template.error_page(_('Authentication subsystem is not yet configured.'))
88
            ident_methods = ['idp']  # fallback to old behaviour; saml.
89

  
90
        if len(ident_methods) == 1:
91
            method = ident_methods[0]
92
        else:
93
            method = 'password'
94

  
95
        return wcs.qommon.ident.register(method)
96

  
97

  
98
OldLoginDirectory = wcs.root.LoginDirectory
99

  
100

  
101
class AlternateLoginDirectory(OldLoginDirectory):
102
    def _q_traverse(self, path):
103
        return OldLoginDirectory._q_traverse(self, path)
104

  
105
    def _q_index(self):
106
        get_logger().info('login')
107
        ident_methods = get_cfg('identification', {}).get('methods', [])
108

  
109
        if get_request().form.get('ReturnUrl'):
110
            get_request().form['next'] = get_request().form.pop('ReturnUrl')
111

  
112
        if 'IsPassive' in get_request().form and 'idp' in ident_methods:
113
            # if isPassive is given in query parameters, we restrict ourselves
114
            # to saml login.
115
            ident_methods = ['idp']
116

  
117
        if len(ident_methods) > 1 and 'idp' in ident_methods:
118
            # if there is more than one identification method, and there is a
119
            # possibility of SSO, if we got there as a consequence of an access
120
            # unauthorized url on admin/ or backoffice/, then idp auth method
121
            # is chosen forcefully.
122
            after_url = get_request().form.get('next')
123
            if after_url:
124
                root_url = get_publisher().get_root_url()
125
                after_path = urllib.parse.urlparse(after_url)[2]
126
                after_path = after_path[len(root_url) :]
127
                if after_path.startswith(str('admin')) or after_path.startswith(str('backoffice')):
128
                    ident_methods = ['idp']
129

  
130
        # don't display authentication system choice
131
        if len(ident_methods) == 1:
132
            method = ident_methods[0]
133
            try:
134
                return wcs.qommon.ident.login(method)
135
            except KeyError:
136
                get_logger().error('failed to login with method %s' % method)
137
                return errors.TraversalError()
138

  
139
        if sorted(ident_methods) == ['idp', 'password']:
140
            r = TemplateIO(html=True)
141
            get_response().breadcrumb.append(('login', _('Login')))
142
            identities_cfg = get_cfg('identities', {})
143
            form = Form(enctype='multipart/form-data', id='login-form', use_tokens=False)
144
            if identities_cfg.get('email-as-username', False):
145
                form.add(StringWidget, 'username', title=_('Email'), size=25, required=True)
146
            else:
147
                form.add(StringWidget, 'username', title=_('Username'), size=25, required=True)
148
            form.add(PasswordWidget, 'password', title=_('Password'), size=25, required=True)
149
            form.add_submit('submit', _('Connect'))
150
            if form.is_submitted() and not form.has_errors():
151
                tmp = wcs.qommon.ident.password.MethodDirectory().login_submit(form)
152
                if not form.has_errors():
153
                    return tmp
154

  
155
            r += htmltext('<div id="login-password">')
156
            r += get_session().display_message()
157
            r += form.render()
158

  
159
            base_url = get_publisher().get_root_url()
160
            r += htmltext('<p><a href="%sident/password/forgotten">%s</a></p>') % (
161
                base_url,
162
                _('Forgotten password ?'),
163
            )
164

  
165
            r += htmltext('</div>')
166

  
167
            # XXX: this part only supports a single IdP
168
            r += htmltext('<div id="login-sso">')
169
            r += TextsDirectory.get_html_text('aq-sso-text')
170
            form = Form(enctype='multipart/form-data', action='%sident/idp/login' % base_url)
171
            form.add_hidden('method', 'idp')
172
            for kidp, idp in get_cfg('idp', {}).items():
173
                p = lasso.Provider(
174
                    lasso.PROVIDER_ROLE_IDP,
175
                    misc.get_abs_path(idp['metadata']),
176
                    misc.get_abs_path(idp.get('publickey')),
177
                    None,
178
                )
179
                form.add_hidden('idp', p.providerId)
180
                break
181
            form.add_submit('submit', _('Connect'))
182

  
183
            r += form.render()
184
            r += htmltext('</div>')
185

  
186
            get_request().environ['REQUEST_METHOD'] = 'GET'
187

  
188
            r += htmltext(
189
                """<script type="text/javascript">
190
              document.getElementById('login-form')['username'].focus();
191
            </script>"""
192
            )
193
            return r.getvalue()
194
        else:
195
            return OldLoginDirectory._q_index(self)
196

  
197

  
198
OldIdentDirectory = wcs.root.IdentDirectory
199

  
200

  
201
class AlternateIdentDirectory(OldIdentDirectory):
202
    def _q_traverse(self, path):
203
        return OldIdentDirectory._q_traverse(self, path)
204

  
205

  
206
class AlternatePreviewDirectory(PreviewDirectory):
207
    def _q_traverse(self, path):
208
        return super(AlternatePreviewDirectory, self)._q_traverse(path)
209

  
210 12

  
211 13
class AlternateRootDirectory(OldRootDirectory):
212 14
    _q_exports = [
......
240 42
        'actions',
241 43
    ]
242 44

  
243
    register = AlternateRegisterDirectory()
244
    login = AlternateLoginDirectory()
245
    ident = AlternateIdentDirectory()
246 45
    saml = Saml2Directory()
247 46
    code = wcs.forms.root.TrackingCodesDirectory()
248
    preview = AlternatePreviewDirectory()
249

  
250
    def get_substitution_variables(self):
251
        return {'links': ''}
252

  
253
    def _q_traverse(self, path):
254
        self.feed_substitution_parts()
255

  
256
        response = get_response()
257
        if not hasattr(response, 'filter'):
258
            response.filter = {}
259

  
260
        response.filter['auquotidien'] = True
261
        if not path or (path[0] not in ('api', 'backoffice') and not get_request().is_json()):
262
            # api & backoffice have no use for a side box
263
            response.filter['gauche'] = lambda x: self.box_side(path)
264
        get_publisher().substitutions.feed(self)
265

  
266
        response.breadcrumb = [('', _('Home'))]
267

  
268
        if not self.admin:
269
            self.admin = get_publisher().admin_directory_class()
270

  
271
        if not self.backoffice:
272
            self.backoffice = get_publisher().backoffice_directory_class()
273

  
274
        return super()._q_traverse(path)
275

  
276
    def json(self):
277
        return FormsRootDirectory().json()
278

  
279
    def categories(self):
280
        return FormsRootDirectory().categories()
281

  
282
    def _q_index(self):
283
        if get_request().is_json():
284
            return FormsRootDirectory().json()
285

  
286
        root_url = get_publisher().get_root_url()
287
        if get_request().user and get_request().user.anonymous and get_request().user.lasso_dump:
288
            return redirect('%smyspace/new' % root_url)
289

  
290
        redirect_url = get_cfg('misc', {}).get('homepage-redirect-url')
291
        if redirect_url:
292
            return redirect(
293
                misc.get_variadic_url(redirect_url, get_publisher().substitutions.get_context_variables())
294
            )
295

  
296
        template.html_top()
297
        r = TemplateIO(html=True)
298
        get_response().filter['is_index'] = True
299

  
300
        r += htmltext('<div id="centre">')
301
        r += self.box_services(position='1st')
302
        r += htmltext('</div>')
303
        r += htmltext('<div id="droite">')
304
        r += self.myspace_snippet()
305
        r += self.box_services(position='2nd')
306
        r += self.consultations()
307
        r += htmltext('</div>')
308

  
309
        user = get_request().user
310
        if user and user.can_go_in_backoffice():
311
            get_response().filter['backoffice'] = True
312

  
313
        return r.getvalue()
314

  
315
    def services(self):
316
        template.html_top()
317
        return self.box_services(level=2)
318

  
319
    def box_services(self, level=3, position=None):
320
        ## Services
321
        if get_request().user and get_request().user.roles:
322
            accepted_roles = get_request().user.roles
323
        else:
324
            accepted_roles = []
325

  
326
        cats = Category.select(order_by='name')
327
        cats = [x for x in cats if x.url_name != 'consultations']
328
        Category.sort_by_position(cats)
329

  
330
        all_formdefs = FormDef.select(
331
            lambda x: not x.is_disabled() or x.disabled_redirection, order_by='name'
332
        )
333

  
334
        if position:
335
            t = self.display_list_of_formdefs(
336
                [x for x in cats if x.get_homepage_position() == position], all_formdefs, accepted_roles
337
            )
338
        else:
339
            t = self.display_list_of_formdefs(cats, all_formdefs, accepted_roles)
340

  
341
        if not t:
342
            return
343

  
344
        r = TemplateIO(html=True)
345

  
346
        if position == '2nd':
347
            r += htmltext('<div id="services-2nd">')
348
        else:
349
            r += htmltext('<div id="services">')
350
        if level == 2:
351
            r += htmltext('<h2>%s</h2>') % _('Services')
352
        else:
353
            r += htmltext('<h3>%s</h3>') % _('Services')
354

  
355
        if 'auquotidien-welcome-in-services' in get_response().filter.get('keywords', []):
356
            homepage_text = TextsDirectory.get_html_text('aq-home-page')
357
            if homepage_text:
358
                r += htmltext('<div id="home-page-intro">')
359
                r += homepage_text
360
                r += htmltext('</div>')
361

  
362
        r += htmltext('<ul>')
363
        r += t
364
        r += htmltext('</ul>')
365

  
366
        r += htmltext('</div>')
367
        return r.getvalue()
368

  
369
    def display_list_of_formdefs(self, cats, all_formdefs, accepted_roles):
370
        r = TemplateIO(html=True)
371
        for category in cats:
372
            if category.url_name == 'consultations':
373
                self.consultations_category = category
374
                continue
375
            formdefs = [x for x in all_formdefs if str(x.category_id) == str(category.id)]
376
            formdefs_advertise = []
377

  
378
            for formdef in formdefs[:]:
379
                if formdef.is_disabled():  # is a redirection
380
                    continue
381
                if not formdef.roles:
382
                    continue
383
                if not get_request().user:
384
                    if formdef.always_advertise:
385
                        formdefs_advertise.append(formdef)
386
                    formdefs.remove(formdef)
387
                    continue
388
                if logged_users_role().id in formdef.roles:
389
                    continue
390
                for q in accepted_roles:
391
                    if q in formdef.roles:
392
                        break
393
                else:
394
                    if formdef.always_advertise:
395
                        formdefs_advertise.append(formdef)
396
                    formdefs.remove(formdef)
397

  
398
            if not formdefs and not formdefs_advertise:
399
                continue
400

  
401
            keywords = {}
402
            for formdef in formdefs:
403
                for keyword in formdef.keywords_list:
404
                    keywords[keyword] = True
405

  
406
            r += htmltext('<li id="category-%s" data-keywords="%s">') % (
407
                category.url_name,
408
                ' '.join(keywords),
409
            )
410
            r += htmltext('<strong>')
411
            r += htmltext('<a href="%s/">') % category.url_name
412
            r += category.name
413
            r += htmltext('</a></strong>\n')
414
            r += category.get_description_html_text()
415
            r += htmltext('<ul>')
416
            limit = category.get_limit()
417
            for formdef in formdefs[:limit]:
418
                r += htmltext('<li data-keywords="%s">') % ' '.join(formdef.keywords_list)
419
                classes = []
420
                if formdef.is_disabled() and formdef.disabled_redirection:
421
                    classes.append('redirection')
422
                r += htmltext('<a class="%s" href="%s/%s/">%s</a>') % (
423
                    ' '.join(classes),
424
                    category.url_name,
425
                    formdef.url_name,
426
                    formdef.name,
427
                )
428
                r += htmltext('</li>\n')
429
            if len(formdefs) < limit:
430
                for formdef in formdefs_advertise[: limit - len(formdefs)]:
431
                    r += htmltext('<li class="required-authentication">')
432
                    r += htmltext('<a href="%s/%s/">%s</a>') % (
433
                        category.url_name,
434
                        formdef.url_name,
435
                        formdef.name,
436
                    )
437
                    r += htmltext('<span> (%s)</span>') % _('authentication required')
438
                    r += htmltext('</li>\n')
439
            if (len(formdefs) + len(formdefs_advertise)) > limit:
440
                r += htmltext('<li class="all-forms"><a href="%s/" title="%s">%s</a></li>') % (
441
                    category.url_name,
442
                    _('Access to all forms of the "%s" category') % category.name,
443
                    _('Access to all forms in this category'),
444
                )
445
            r += htmltext('</ul>')
446
            r += htmltext('</li>\n')
447

  
448
        return r.getvalue()
449

  
450
    def consultations(self):
451
        cats = [x for x in Category.select() if x.url_name == 'consultations']
452
        if not cats:
453
            return
454
        consultations_category = cats[0]
455
        formdefs = FormDef.select(
456
            lambda x: (
457
                str(x.category_id) == str(consultations_category.id)
458
                and (not x.is_disabled() or x.disabled_redirection)
459
            ),
460
            order_by='name',
461
        )
462
        if not formdefs:
463
            return
464
        ## Consultations
465
        r = TemplateIO(html=True)
466
        r += htmltext('<div id="consultations">')
467
        r += htmltext('<h3>%s</h3>') % _('Consultations')
468
        r += consultations_category.get_description_html_text()
469
        r += htmltext('<ul>')
470
        for formdef in formdefs:
471
            r += htmltext('<li>')
472
            r += htmltext('<a href="%s/%s/">%s</a>') % (
473
                consultations_category.url_name,
474
                formdef.url_name,
475
                formdef.name,
476
            )
477
            r += htmltext('</li>')
478
        r += htmltext('</ul>')
479
        r += htmltext('</div>')
480
        return r.getvalue()
481

  
482
    def box_side(self, path):
483
        r = TemplateIO(html=True)
484
        root_url = get_publisher().get_root_url()
485

  
486
        if (
487
            path == ['']
488
            and 'include-tracking-code-form' in get_response().filter.get('keywords', [])
489
            and self.has_anonymous_access_codes()
490
        ):
491
            r += htmltext('<form id="follow-form" action="%scode/load">') % root_url
492
            r += htmltext('<h3>%s</h3>') % _('Tracking code')
493
            r += htmltext('<input size="12" name="code" placeholder="%s"/>') % _('ex: RPQDFVCD')
494
            r += htmltext('<input type="submit" value="%s"/>') % _('Load')
495
            r += htmltext('</form>')
496

  
497
        cats = Category.select(order_by='name')
498
        cats = [x for x in cats if x.url_name != 'consultations' and x.get_homepage_position() == 'side']
499
        Category.sort_by_position(cats)
500
        if cats:
501
            r += htmltext('<div id="side-services">')
502
            r += htmltext('<h3>%s</h3>') % _('Services')
503
            r += htmltext('<ul>')
504
            for cat in cats:
505
                r += htmltext('<li><a href="%s/">%s</a></li>') % (cat.url_name, cat.name)
506
            r += htmltext('</ul>')
507
            r += htmltext('</div>')
508

  
509
        v = r.getvalue()
510
        if v:
511
            r = TemplateIO(html=True)
512
            r += htmltext('<div id="sidebox">')
513
            r += v
514
            r += htmltext('</div>')
515
            return r.getvalue()
516

  
517
        return None
518

  
519
    def has_anonymous_access_codes(self):
520
        return any((x for x in FormDef.select() if x.enable_tracking_codes))
521

  
522
    def myspace_snippet(self):
523
        r = TemplateIO(html=True)
524
        r += htmltext('<div id="myspace">')
525
        r += htmltext('<h3>%s</h3>') % _('My Space')
526
        r += htmltext('<ul>')
527
        if get_request().user and not get_request().user.anonymous:
528
            r += htmltext('  <li><a href="myspace/" id="member">%s</a></li>') % _(
529
                'Access to your personal space'
530
            )
531
            r += htmltext('  <li><a href="logout" id="logout">%s</a></li>') % _('Logout')
532
        else:
533
            r += htmltext('  <li><a href="register/" id="inscr">%s</a></li>') % _('Registration')
534
            r += htmltext('  <li><a href="login/" id="login">%s</a></li>') % _('Login')
535
        r += htmltext('</ul>')
536
        r += htmltext('</div>')
537
        return r.getvalue()
538 47

  
539 48

  
540 49
from qommon.publisher import get_publisher_class
541 50

  
542
get_publisher_class().root_directory_class = AlternateRootDirectory
543 51
get_publisher_class().after_login_url = 'myspace/'
544 52
get_publisher_class().use_sms_feature = True
545

  
546

  
547
TextsDirectory.register(
548
    'aq-sso-text',
549
    N_('Connecting with Identity Provider'),
550
    default=N_(
551
        '''<h3>Connecting with Identity Provider</h3>
552
<p>You can also use your identity provider to connect.
553
</p>'''
554
    ),
555
)
556

  
557
TextsDirectory.register('aq-home-page', N_('Home Page'), wysiwyg=True)
tests/test_admin_pages.py
81 81
    create_superuser()
82 82
    app = login(get_app(pub))
83 83
    # this makes sure the extension loaded properly
84
    resp = app.get('/backoffice/settings/texts/aq-home-page', status=200)
84
    assert 'auquotidien' in pub.translation_domains

Formats disponibles : Unified diff