Projet

Général

Profil

Télécharger (60,3 ko) Statistiques
| Branche: | Révision:

root / larpe / tags / release-1.1.1 / larpe / admin / hosts.ptl @ d03cb81c

1
import os
2
import urllib
3
import urlparse
4

    
5
from quixote import redirect, get_request, get_response, get_publisher
6
from quixote.directory import Directory
7

    
8
import lasso
9

    
10
from qommon.admin.menu import html_top, command_icon
11
from qommon import get_cfg
12
from qommon.form import *
13
from qommon.misc import http_get_page, get_abs_path
14

    
15
from larpe import site_authentication
16
from larpe import errors
17
from larpe import misc
18
from larpe.hosts import Host
19
from larpe.admin.apache import Location
20
from larpe.admin.liberty_utils import *
21
from larpe.admin.apache import write_apache2_vhosts
22
from larpe.admin.forms_prefill import FormsDirectory
23
from larpe.Defaults import DATA_DIR
24
from larpe.plugins import site_authentication_plugins
25

    
26
def check_basic_configuration(form):
27
    get_publisher().reload_cfg()
28
    # Check reversed_hostname and reversed_directory
29
    reversed_hostname = form.get_widget('reversed_hostname').parse()
30
    reversed_directory = form.get_widget('reversed_directory').parse()
31
    if reversed_hostname == get_publisher().cfg['proxy_hostname'] and not reversed_directory:
32
        form.set_error('reversed_hostname',
33
            _('You must either choose a different hostname from Larpe or specify a reversed directory'))
34

    
35
def convert_label_to_name(label):
36
    '''Build host name from host label'''
37
    name = label.lower()
38
    invalid_characters = [' ', "'"]
39
    for char in invalid_characters:
40
        name = name.replace(char, '_')
41
    return name
42

    
43
class DictWidget(Widget):
44
    def render_content [html] (self):
45
        self.render_br = False
46
        if self.value['enabled'] is True:
47
            htmltag('input', xml_end=True, type='checkbox', name=self.name + '_enabled', checked='checked')
48
        else:
49
            htmltag('input', xml_end=True, type='checkbox', name=self.name + '_enabled')
50
        ' ' + self.name + '  '
51
        htmltag('input', xml_end=True, type='text', name=self.name, value=self.value['value'], size='35', **self.attrs)
52

    
53
    def _parse(self, request):
54
        enabled = request.form.get(self.name + '_enabled')
55
        value = request.form.get(self.name)
56
        self.value = { 'enabled': enabled, 'value': value }
57

    
58

    
59
class ConfigurationAssistant(Directory):
60
    _q_exports = ['start', 'check_new_address', 'modify_site_address_and_name',
61
                  'authentication_and_logout_adresses', 'check_auto_detected_configuration',
62
                  'credentials', 'send_authentication_request', 'check_authentication',
63
                  'see_authentication_response', 'see_response_html_page',
64
                  'see_bad_response_html_page', 'authentication_success_criteria',
65
                  'modify_authentication_request', 'auth_request_post_parameters',
66
                  'auth_request_http_headers', 'sso_init_link', 'metadatas',
67
                  'check_full_configuration', 'advanced_options']
68

    
69
    def __init__(self, host):
70
        self.host = host
71

    
72
    def html_top [html] (self, title):
73
        html_top('hosts', title)
74
        '<h2>%s</h2>' % title
75

    
76
    def start [html] (self):
77
        # Check the global domain name has been previously set
78
        get_publisher().reload_cfg()
79
        if not get_cfg('domain_names') or not get_cfg('domain_names')[0]:
80
            get_response().breadcrumb.append(('start', _('Basic configuration')))
81
            self.html_top(_('Need domain name configuration'))
82
            return htmltext(_('''Before configuring hosts, you must
83
<a href="../../../settings/domain_names">setup a global domain name</a> in
84
%(settings)s menu.''') % { 'settings': _('Settings') })
85

    
86
        form = self.form_start()
87

    
88
        if form.get_widget('cancel').parse():
89
            return redirect('../..')
90

    
91
        connection_failure = None
92
        if form.is_submitted() and not form.has_errors():
93
            try:
94
                self.submit_start_form(form)
95
            except Exception, e:
96
                connection_failure = e
97
            else:
98
                if form.get_widget('terminate').parse():
99
                    return redirect('..')
100
                return redirect('check_new_address')
101

    
102
        get_response().breadcrumb.append(('start', _('Basic configuration')))
103
        self.html_top(_('Step 1 - Basic configuration'))
104

    
105
        if connection_failure:
106
            '<div class="errornotice">%s</div>' % connection_failure
107

    
108
        form.render()
109

    
110
    def form_start(self):
111
        form = Form(enctype='multipart/form-data')
112
        form.add(UrlWidget, 'orig_site', title = _('Original site root address'), required = True,
113
                size = 50, value = self.host.orig_site,
114
                hint = _('If your site address is http://test.org/index.php, put http://test.org/ here'))
115
        get_publisher().reload_cfg()
116
        if get_cfg('proxy', {}).get('enabled'):
117
            form.add(CheckboxWidget, 'use_proxy', title = _('Use a proxy'),
118
                    hint = _("Uncheck it if Larpe doesn't need to use the proxy to connect to this site"),
119
                    value = self.host.use_proxy)
120
        else:
121
            form.add(HtmlWidget, htmltext('<p>%s</p>' % \
122
                _('''If Larpe needs to use a proxy to connect to this site, you must first configure
123
 it in <a href="../../../settings/proxy">global proxy parameters</a>.''')))
124
        form.add_submit('cancel', _('Cancel'))
125
        form.add_submit('submit', _('Next'))
126
        form.add_submit('terminate', _('Terminate'))
127
        return form
128

    
129
    def submit_start_form(self, form):
130
        fields = ['orig_site']
131
        use_proxy = get_cfg('proxy', {}).get('enabled')
132
        if use_proxy:
133
            fields += ['use_proxy']
134
        for f in fields:
135
            setattr(self.host, f, form.get_widget(f).parse())
136

    
137
        # If no proxy is setup yet, set use_proxy to False for new hosts in case a proxy is later configured
138
        if not use_proxy:
139
            self.host.use_proxy = False
140

    
141
        # Remove what is after the last '/'
142
        #self.host.orig_site = '/'.join(self.host.orig_site.split('/')[:-1])
143

    
144
        html_page = self.get_data_after_redirects(self.host.orig_site)
145

    
146
        if not self.host.label:
147
            # Look for html title in original site index page
148
            regexp = re.compile("""<title.*?>(.*?)</title>""", re.DOTALL | re.IGNORECASE)
149
            title = regexp.findall(html_page)
150
            if title:
151
                self.host.label = title[0]
152
            else:
153
                self.host.label = 'Untitled'
154
            # If another site already uses this site title, add trailings "_" until we find an available name
155
            existing_label = True
156
            while existing_label is True:
157
                for any_host in Host.select():
158
                    if any_host.id != self.host.id and self.host.label == any_host.label:
159
                        self.host.label += '_'
160
                        break
161
                else:
162
                    existing_label = False
163

    
164
            # Fill host.name attribute
165
            self.host.name = convert_label_to_name(self.host.label)
166

    
167
        if not self.host.scheme:
168
            # Get tokens from orig site url
169
            orig_scheme, rest = urllib.splittype(self.host.orig_site)
170
            orig_host, rest = urllib.splithost(rest)
171

    
172
            get_publisher().reload_cfg()
173
            # Set url scheme (HTTP or HTTPS)
174
            # TODO: Handle the option "Both"
175
            if get_cfg('sites_url_scheme'):
176
                self.host.scheme = get_cfg('sites_url_scheme')
177
            else:
178
                self.host.scheme = orig_scheme
179

    
180
        if not self.host.reversed_hostname:
181
            # Build a new domain name
182
            short_name = orig_host.split('.')[0]
183
            if short_name == 'www':
184
                short_name = orig_host.split('.')[1]
185
            self.host.reversed_hostname = '%s.%s' % (short_name, get_cfg('domain_names')[0])
186
            # If another site already uses this domain name, add some trailing "_" until we find an available name
187
            existing_domain = True
188
            while existing_domain is True:
189
                for any_host in Host.select():
190
                    if any_host.id != self.host.id and self.host.reversed_hostname == any_host.reversed_hostname:
191
                        self.host.reversed_hostname += '-'
192
                        break
193
                else:
194
                    existing_domain = False
195
            self.host.reversed_directory = None
196

    
197
        if not self.host.new_url:
198
            # New url for this host
199
            self.host.new_url = '%s://%s%s/' % (self.host.scheme, self.host.reversed_hostname, get_request().environ['SCRIPT_NAME'])
200
            # FIXME: Check if the new domain name already exists
201

    
202
            # New url for this host
203
            #        self.host.new_url = '%s://%s%s/' % (self.host.scheme, self.host.reversed_hostname, get_request().environ['SCRIPT_NAME'])
204
            #        if self.host.reversed_directory is not None:
205
            #            self.host.new_url += '%s/' % self.host.reversed_directory
206

    
207
        self.host.store()
208
        write_apache2_vhosts()
209

    
210
    # XXX: Should use the FancyURLopener class instead when it supports proxies
211
    def get_data_after_redirects(self, start_url):
212
        if not start_url:
213
            return ''
214
        status = 302
215
        location = None
216
        while status // 100 == 3:
217
            if location is None:
218
                url = start_url
219
            elif location.startswith('http'):
220
                # Location is an absolute path
221
                url = location
222
            else:
223
                # Location is a relative path
224
                url = urlparse.urljoin(start_url, location)
225
            response, status, data, auth_headers = http_get_page(url, use_proxy=self.host.use_proxy)
226
            location = response.getheader('Location', None)
227
        return data
228

    
229
    def create_dirs(self):
230
        # Hack : sites must use the configuration which is stored in main Larpe directory,
231
        # but they need to have a directory named with their hostname, which will contain the
232
        # main domain name for Larpe so they know where is the main configuration
233
        hostname_dir = get_abs_path(os.path.join('..', self.host.reversed_hostname))
234
        if not os.path.exists(hostname_dir):
235
            os.mkdir(hostname_dir)
236
        # Load the configuration from the main directory
237
        get_publisher().reload_cfg()
238
        # Write it in the site directory
239
        get_publisher().write_cfg(hostname_dir)
240

    
241
        # Storage directories
242
        if not self.host.reversed_directory:
243
            reversed_dir = 'default'
244
        else:
245
            reversed_dir = self.host.reversed_directory
246
        self.host.site_dir = \
247
            os.path.join(get_publisher().app_dir, 'sp', self.host.reversed_hostname, reversed_dir)
248
        user_dir = os.path.join(self.host.site_dir, 'users')
249
        token_dir = os.path.join(self.host.site_dir, 'tokens')
250
        filter_dir = os.path.join(self.host.site_dir, 'filters')
251
        for dir in (self.host.site_dir, user_dir, token_dir, filter_dir):
252
            if not os.path.isdir(dir):
253
                os.makedirs(dir)
254

    
255
    def generate_ssl_keys(self):
256
        # Generate SSL keys
257
        private_key_path = os.path.join(self.host.site_dir, 'private_key.pem')
258
        public_key_path = os.path.join(self.host.site_dir, 'public_key.pem')
259
        if not os.path.isfile(private_key_path) or not os.path.isfile(public_key_path):
260
            set_provider_keys(private_key_path, public_key_path)
261
        self.host.private_key = private_key_path
262
        self.host.public_key = public_key_path
263

    
264
    def generate_metadatas(self):
265
        metadata_cfg = {}
266

    
267
        # Organization name
268
        self.host.organization_name = self.host.label
269
        metadata_cfg['organization_name'] = self.host.organization_name
270

    
271
        # Base URL
272
        base_url = '%s://%s%s/liberty/%s/liberty' % (self.host.scheme,
273
                                                     self.host.reversed_hostname,
274
                                                     get_request().environ['SCRIPT_NAME'],
275
                                                     self.host.name)
276
        metadata_cfg['base_url'] = base_url
277
        self.host.base_url = base_url
278

    
279
        if lasso.SAML2_SUPPORT:
280
            saml2_base_url = '%s://%s%s/liberty/%s/saml' % (self.host.scheme,
281
                                                         self.host.reversed_hostname,
282
                                                         get_request().environ['SCRIPT_NAME'],
283
                                                         self.host.name)
284
            metadata_cfg['saml2_base_url'] = saml2_base_url
285
            self.host.saml2_base_url = saml2_base_url
286

    
287
        # Provider Id
288
        provider_id = '%s/metadata' % base_url
289
        metadata_cfg['provider_id'] = provider_id
290
        self.host.provider_id = provider_id
291

    
292
        if lasso.SAML2_SUPPORT:
293
            saml2_provider_id = '%s/metadata' % saml2_base_url
294
            metadata_cfg['saml2_provider_id'] = saml2_provider_id
295
            self.host.saml2_provider_id = saml2_provider_id
296

    
297
        # Read public key
298
        public_key = ''
299
        if self.host.public_key is not None and os.path.exists(self.host.public_key):
300
            metadata_cfg['signing_public_key'] = open(self.host.public_key).read()
301

    
302
        # Write metadatas
303
        metadata_path = os.path.join(self.host.site_dir, 'metadata.xml')
304
        open(metadata_path, 'w').write(get_metadata(metadata_cfg))
305
        self.host.metadata = metadata_path
306

    
307
        if lasso.SAML2_SUPPORT:
308
            saml2_metadata_path = os.path.join(self.host.site_dir, 'saml2_metadata.xml')
309
            open(saml2_metadata_path, 'w').write(get_saml2_metadata(metadata_cfg))
310
            self.host.saml2_metadata = saml2_metadata_path
311

    
312
    def check_new_address [html] (self):
313
        form = Form(enctype='multipart/form-data')
314
        form.add_submit('cancel', _('Previous'))
315
        form.add_submit('submit', _('Next'))
316
        form.add_submit('terminate', _('Terminate'))
317

    
318
        if form.get_widget('cancel').parse():
319
            return redirect('start')
320

    
321
        if form.is_submitted():
322
            self.create_dirs()
323
            if self.host.private_key is None:
324
                self.generate_ssl_keys()
325
            self.generate_metadatas()
326
            self.host.store()
327

    
328
            if form.get_widget('terminate').parse():
329
                return redirect('..')
330
            return redirect('authentication_and_logout_adresses')
331

    
332
        get_response().breadcrumb.append(('check_new_address', _('Check site address and name')))
333
        self.html_top(_('Step 2 - Check the new site address works'))
334

    
335
        '<h3>%s</h3>' % _('DNS configuration')
336

    
337
        '<p>%s</p>' % _('''Before opening the following link, ensure you have configured your DNS
338
for this address. If you don't have a DNS server and you just want to test Larpe, add this
339
domain name in the file "/etc/hosts".''')
340

    
341
        '<p>%s</p>' % _('''Then you can open this link in a new window or tab and see if your site
342
is displayed. If it's ok, you can click the "%(next)s" button. Otherwise, click the "%(previous)s"
343
button and check your settings.''') % {'next': _('Next'), 'previous': _('Previous')}
344

    
345
        '<h3>%s</h3>' % _('Site adress and name')
346

    
347
        '<p>%s' % _('The new address of this site is ')
348
        '<a href="%s">%s</a><br/>' % (self.host.new_url, self.host.new_url)
349
        '%s</p>' % _('The name of this site is "%s".') % self.host.label
350
        '<p>%s</p>' % htmltext(_('''You can also <a href="modify_site_address_and_name">
351
modify the address or the name of this site</a>'''))
352

    
353
        form.render()
354

    
355
    def modify_site_address_and_name [html] (self):
356
        form = self.form_modify_site_address_and_name()
357

    
358
        if form.get_widget('cancel').parse():
359
            return redirect('check_new_address')
360

    
361
        if form.is_submitted() and not form.has_errors():
362
            label = form.get_widget('label').parse()
363
            name = convert_label_to_name(label)
364
            for any_host in Host.select():
365
                if any_host.id != self.host.id and name == any_host.name:
366
                    form.set_error('label', _('An host with the same name already exists'))
367
                    break
368

    
369
        if form.is_submitted() and not form.has_errors():
370
            self.submit_modify_site_address_and_name_form(form)
371
            return redirect('check_new_address')
372

    
373
        get_response().breadcrumb.append(('modify_site_address_and_name', _('Modify site address and name')))
374
        self.html_top(_('Modify site address and name'))
375

    
376
        form.render()
377

    
378
    def form_modify_site_address_and_name(self):
379
        form = Form(enctype='multipart/form-data')
380
        form.add(UrlWidget, 'new_url', title = _('Address'), required = True,
381
                size = 50, value = self.host.new_url)
382
        form.add(StringWidget, 'label', title = _('Name'), required = True,
383
                size = 50, value = self.host.label)
384
        form.add_submit('submit', _('Submit'))
385
        form.add_submit('cancel', _('Cancel'))
386
        return form
387

    
388
    def submit_modify_site_address_and_name_form(self, form):
389
        fields = ['new_url', 'label']
390
        for f in fields:
391
            setattr(self.host, f, form.get_widget(f).parse())
392

    
393
        # Split url to retrieve components
394
        tokens = urlparse.urlparse(self.host.new_url)
395
        self.host.scheme = tokens[0]
396
        self.host.reversed_hostname = tokens[1]
397
        self.host.reversed_directory = tokens[2]
398
        if self.host.reversed_directory.startswith('/'):
399
            self.host.reversed_directory = self.host.reversed_directory[1:]
400

    
401
        # Fill host.name attribute
402
        self.host.name = convert_label_to_name(self.host.label)
403

    
404
        self.host.store()
405
        write_apache2_vhosts()
406

    
407
    def authentication_and_logout_adresses [html] (self):
408
        form = self.form_authentication_and_logout_adresses()
409

    
410
        if form.get_widget('cancel').parse():
411
            return redirect('check_new_address')
412

    
413
        if form.is_submitted() and not form.has_errors():
414
            self.submit_authentication_and_logout_adresses_form(form)
415
            if form.get_widget('terminate').parse():
416
                return redirect('..')
417
            return redirect('check_auto_detected_configuration')
418

    
419
        get_response().breadcrumb.append(('authentication_and_logout_adresses', _('Authentication and logout')))
420
        self.html_top(_('Step 3 - Configure authentication and logout pages'))
421

    
422
        form.render()
423

    
424
    def form_authentication_and_logout_adresses(self):
425
        form = Form(enctype='multipart/form-data')
426
        form.add(ValidUrlWidget, 'auth_url', title = _('Authentication form page address'),
427
                hint = _('Address of a page on the site which contains the authentication form'),
428
                required = True, size = 50, value = self.host.auth_url)
429
        form.add(ValidUrlWidget, 'logout_url', title = _('Logout address'), required = False,
430
                hint = _('Address of the logout link on the site'),
431
                size = 50, value = self.host.logout_url)
432
        form.add_submit('cancel', _('Previous'))
433
        form.add_submit('submit', _('Next'))
434
        form.add_submit('terminate', _('Terminate'))
435
        return form
436

    
437
    def submit_authentication_and_logout_adresses_form(self, form):
438
        fields = ['auth_url', 'logout_url']
439
        for f in fields:
440
            setattr(self.host, f, form.get_widget(f).parse())
441
        self.host.auth_form_url = self.host.auth_url
442

    
443
        if not self.host.http_headers:
444
            self.host.http_headers = {
445
                'Content-Type': { 'enabled': True, 'value': 'application/x-www-form-urlencoded', 'immutable': False },
446
                'X-Forwarded-For': { 'enabled': True, 'value': _('(computed automatically)'), 'immutable': True },
447
                'X-Forwarded-Host': { 'enabled': True, 'value': self.host.reversed_hostname, 'immutable': False },
448
            }
449

    
450
        self.auto_detect_configuration()
451
        self.host.store()
452
        write_apache2_vhosts()
453

    
454
    def check_auto_detected_configuration [html] (self):
455
        plugins_name = site_authentication_plugins.get_plugins_name()
456
        plugins_name.append(None)
457
        plugins_name.sort()
458
        form = Form(enctype='multipart/form-data')
459
        form.add(SingleSelectWidget, 'plugin', title = _('Plugin'), required = False,
460
                hint = _('You can force a plugin'),
461
                value = self.host.site_authentication_plugin,
462
                options = plugins_name)
463
        form.add_submit('cancel', _('Previous'))
464
        form.add_submit('submit', _('Next'))
465
        form.add_submit('terminate', _('Terminate'))
466

    
467
        if form.get_widget('cancel').parse():
468
            return redirect('authentication_and_logout_adresses')
469

    
470
        if form.is_submitted():
471
            self.host.site_authentication_plugin = form.get_widget('plugin').parse()
472
            self.host.store()
473
            if form.get_widget('terminate').parse():
474
                write_apache2_vhosts()
475
                return redirect('..')
476
            return redirect('credentials')
477

    
478
        get_response().breadcrumb.append(('check_auto_detected_configuration', _('Auto detected configuration')))
479
        self.html_top(_('Step 4 - Check automatically detected configuration for the authentication form'))
480

    
481
        host_attrs = (
482
            ('auth_check_url', _('Address where the authentication form must be sent')),
483
            ('login_field_name', _('Name of the login field')),
484
            ('password_field_name', _('Name of the password field')),
485
        )
486

    
487
        html_fields = ''
488
        success = True
489
        for attr, name in host_attrs:
490
            color = 'black'
491
            if attr in ('auth_check_url', 'login_field_name', 'password_field_name') and \
492
                    not getattr(self.host, str(attr)):
493
                color = 'red'
494
                success = False
495
            html_fields += '<div style="margin-bottom: 0.1em; font-weight: bold; color: %s;">%s</div>' % (color, name)
496
            html_fields += '<div style="margin-left: 1em; margin-bottom: 0.3em; color: %s;">%s</div>' % \
497
                (color, getattr(self.host, str(attr)))
498
            if getattr(self.host, str(attr)) == '':
499
                html_fields += '<br />'
500

    
501
        '<p>'
502
        if success:
503
            htmltext(_('''\
504
The following authentication form parameters have been detected. If they look right, you can go to the next step.
505
If you think they are wrong, go back and check your settings then try again.
506
'''))
507
        else:
508
            htmltext(_('''\
509
The following authentication form parameters in red haven't been correctly detected. Go back and check
510
your settings then try again.
511
'''))
512
        '</p>'
513

    
514
        html_fields
515
        form.render()
516

    
517
    def credentials [html] (self):
518
        form = self.form_credentials()
519

    
520
        if form.get_widget('cancel').parse():
521
            return redirect('check_auto_detected_configuration')
522

    
523
        if form.is_submitted() and not form.has_errors():
524
            self.submit_credentials_form(form)
525
            if form.get_widget('terminate').parse():
526
                return redirect('..')
527
            return redirect('send_authentication_request')
528

    
529
        get_response().breadcrumb.append(('credentials', _('Credentials')))
530
        self.html_top(_('Step 5 - Fill in a valid username/password for this site'))
531
        form.render()
532

    
533
    def form_credentials(self):
534
        form = Form(enctype='multipart/form-data')
535
        form.add(StringWidget, 'username', title = _('Username'), required = True,
536
                size = 30, value = self.host.valid_username)
537
        form.add(PasswordWidget, 'password', title = _('Password'), required = True,
538
                size = 30, value = self.host.valid_password)
539
        for name, values in self.host.select_fields.iteritems():
540
            options = []
541
            if values:
542
                for value in values:
543
                    options.append(value)
544
                form.add(SingleSelectWidget, name, title = name.capitalize(),
545
                    value = values[0], options = options)
546
        form.add_submit('cancel', _('Previous'))
547
        form.add_submit('submit', _('Next'))
548
        form.add_submit('terminate', _('Terminate'))
549
        return form
550

    
551
    def submit_credentials_form(self, form):
552
        self.host.valid_username = form.get_widget('username').parse()
553
        self.host.valid_password = form.get_widget('password').parse()
554
        self.host.valid_select = {}
555
        for name, values in self.host.select_fields.iteritems():
556
            if form.get_widget(name):
557
                self.host.valid_select[name] = form.get_widget(name).parse()
558

    
559
        self.host.store()
560

    
561
    def send_authentication_request(self):
562
        site_auth = site_authentication.get_site_authentication(self.host)
563

    
564
        # Request with good credentials
565
        self.host.auth_request_status, self.host.auth_request_data = site_auth.local_auth_check_dispatch(
566
            self.host.valid_username, self.host.valid_password, self.host.valid_select)
567
        self.host.auth_request_success, self.host.auth_request_return_content = \
568
            site_auth.check_auth(self.host.auth_request_status, self.host.auth_request_data)
569

    
570
        # Request with bad credentials
571
        self.host.auth_bad_request_status, self.host.auth_bad_request_data = site_auth.local_auth_check_dispatch(
572
            'this_is_a_bad_login', 'this_is_a_bad_password', {})
573
        self.host.auth_bad_request_success, self.host.auth_bad_request_return_content = \
574
            site_auth.check_auth(self.host.auth_bad_request_status, self.host.auth_bad_request_data)
575

    
576
        self.host.store()
577

    
578
        return redirect('check_authentication')
579

    
580
    def check_authentication [html] (self):
581
        form = Form(enctype='multipart/form-data')
582
        form.add_submit('cancel', _('Previous'))
583
        form.add_submit('submit', _('Next'))
584
        form.add_submit('terminate', _('Terminate'))
585

    
586
        if form.get_widget('cancel').parse():
587
            return redirect('credentials')
588

    
589
        if form.is_submitted():
590
            if form.get_widget('terminate').parse():
591
                return redirect('..')
592
            return redirect('sso_init_link')
593

    
594
        get_response().breadcrumb.append(('check_authentication', _('Check authentication')))
595
        self.html_top(_('Step 6 - Check the authentication process'))
596

    
597
        if self.host.auth_request_success:
598
            good_cred_status = 'Success <span style="color: green">[OK]</span>'
599
        else:
600
            good_cred_status = 'Failed <span style="color: red">[KO]</span'
601

    
602
        if not self.host.auth_bad_request_success:
603
            bad_cred_status = 'Failed <span style="color: green">[OK]</span>'
604
        else:
605
            bad_cred_status = 'Success <span style="color: red">[KO]</span>'
606

    
607
        '<p>Results of authentication requests :</p>\n'
608
        '<ul>\n'
609
        '\t<li>With good credentials : %s</li>' % good_cred_status
610
        '\t<li>With bad credentials : %s</li>' % bad_cred_status
611
        '</ul>\n'
612

    
613
        if self.host.auth_request_success and not self.host.auth_bad_request_success :
614
            '<p>%s</p>\n' % _('Authentication succeeded ! You can go to the next step.')
615
        else:
616
            '<p>%s</p>\n' % _('Authentication has failed. To resolve this problem, you can :')
617
            '<ul>\n'
618
            '\t<li><a href="send_authentication_request">%s</a></li>\n' % \
619
                _('Try authentication again')
620
            '\t<li><a href="see_authentication_response">%s</a></li>\n' % \
621
                _('See the response of the authentication requests')
622
            '\t<li><a href="modify_authentication_request">%s</a></li>\n' % \
623
                _('Modify the parameters of the authentication requests')
624
            '\t<li><a href="authentication_success_criteria">%s</a></li>\n' % \
625
                _('Change the way Larpe detects the authentication is successful or not')
626
            '\t<li>%s</li>\n' % _('Go back and change your username and/or password')
627
            '</ul>\n'
628

    
629
        form.render()
630

    
631
    def see_authentication_response [html] (self):
632
        get_response().breadcrumb.append(('see_authentication_response', _('Authentication response')))
633
        self.html_top(_('Authentication response'))
634

    
635
        '<h3>%s</h3>' % _('Response of the request with good credentials')
636

    
637
        '<div style="margin-bottom: 0.1em; font-weight: bold; color: black;">%s</div>' % \
638
            str(_('HTTP status code'))
639
        '<div style="margin-left: 1em; margin-bottom: 0.3em; color: black;">%s (%s)</div>' % \
640
            (self.host.auth_request_status, status_reasons[self.host.auth_request_status])
641

    
642
        '<dl>'
643
        '<dt><a href="see_bad_response_html_page">%s</a></dt>' % _('See HTML page')
644
        '</dl>'
645

    
646
        '<h3>%s</h3>' % _('Response of the request with bad credentials')
647

    
648
        '<div style="margin-bottom: 0.1em; font-weight: bold; color: black;">%s</div>' % \
649
            str(_('HTTP status code'))
650
        '<div style="margin-left: 1em; margin-bottom: 0.3em; color: black;">%s (%s)</div>' % \
651
            (self.host.auth_bad_request_status, status_reasons[self.host.auth_bad_request_status])
652

    
653
        '<dl>'
654
        '<dt><a href="see_response_html_page">%s</a></dt>' % _('See HTML page')
655
        '</dl>'
656

    
657
        '<div class="buttons"><a href="check_authentication"><input type="button" value="%s" /></a></div><br />' % _('Back')
658

    
659
    def see_response_html_page (self):
660
        return self.host.auth_request_data
661

    
662
    def see_bad_response_html_page (self):
663
        return self.host.auth_bad_request_data
664

    
665
    def authentication_success_criteria [html] (self):
666
        form = self.form_authentication_success_criteria()
667

    
668
        if form.get_widget('cancel').parse():
669
            return redirect('check_authentication')
670

    
671
        if form.is_submitted() and not form.has_errors():
672
            self.submit_authentication_success_criteria_form(form)
673
            return redirect('check_authentication')
674

    
675
        get_response().breadcrumb.append(('authentication_success_criteria', _('Authentication success criteria')))
676
        self.html_top(_('Authentication success criteria'))
677

    
678
        form.render()
679

    
680
    def form_authentication_success_criteria(self):
681
        form = Form(enctype='multipart/form-data')
682
        form.add(RadiobuttonsWidget, 'auth_system', title = _('Authentication system of the original site'),
683
                options=[
684
                    ('password', _('Check the existence of a password field'), 'password'),
685
                    ('match_text', _('Match some text to detect an authentication failure'), 'match_text'),
686
                ],
687
                sort=False,
688
                delim=htmltext('<br />'),
689
                value = self.host.auth_system)
690
        form.add(RegexStringWidget, 'auth_match_text', title = _('Text to match in case of authentication failure'),
691
                required = False, size = 50, value = self.host.auth_match_text)
692
        form.add_submit('submit', _('Submit'))
693
        form.add_submit('cancel', _('Cancel'))
694
        return form
695

    
696
    def submit_authentication_success_criteria_form(self, form):
697
        for f in ('auth_system', 'auth_match_text'):
698
            value = form.get_widget(f).parse()
699
            setattr(self.host, f, value)
700

    
701
        self.host.store()
702

    
703
    def modify_authentication_request [html] (self):
704
        get_response().breadcrumb.append(('modify_authentication_request', _('Authentication request')))
705
        self.html_top(_('Modify the parameters of the authentication requests'))
706

    
707
        '<dl>'
708
        '<dt><a href="auth_request_post_parameters">%s</a></dt> <dd>%s</dd>' % (
709
                _('Modify POST parameters'), _('Configure the form attributes that will be sent within the authentication POST requests'))
710
        '<dt><a href="auth_request_http_headers">%s</a></dt> <dd>%s</dd>' % (
711
                _('Modify HTTP headers'), _('Configure the HTTP headers of the authentication requests made by Larpe'))
712
        '</dl>'
713
        '<div class="buttons"><a href="check_authentication"><input type="button" value="%s" /></a></div><br />' % _('Back')
714

    
715
    def auth_request_post_parameters [html] (self):
716
        form = self.form_auth_request_post_parameters()
717

    
718
        if form.get_widget('cancel').parse():
719
            return redirect('modify_authentication_request')
720

    
721
        if form.is_submitted() and not form.has_errors():
722
            self.submit_auth_request_post_parameters_form(form)
723
            return redirect('modify_authentication_request')
724

    
725
        get_response().breadcrumb.append(('auth_request_post_parameters', _('POST parameters')))
726
        self.html_top(_('Configure POST parameters'))
727

    
728
        '<p>%s</p>' % _('''Here are the detected form fields that will be sent as parameters of the
729
authentication POST request. You can desactivate some or all of them, or change their value.''')
730

    
731
        form.render()
732

    
733
    def form_auth_request_post_parameters(self):
734
        form = Form(enctype='multipart/form-data')
735
        for name, value in self.host.post_parameters.iteritems():
736
            if value['immutable']:
737
                form.add(DictWidget, name, value, disabled = 'disabled')
738
            else:
739
                form.add(DictWidget, name, value)
740
        form.add_submit('submit', _('Submit'))
741
        form.add_submit('cancel', _('Cancel'))
742
        return form
743

    
744
    def submit_auth_request_post_parameters_form(self, form):
745
        for name, old_value in self.host.post_parameters.iteritems():
746
            value = form.get_widget(name).parse()
747
            if value['enabled'] == 'on':
748
                old_value['enabled'] = True
749
            else:
750
                old_value['enabled'] = False
751
            if old_value['immutable'] is False:
752
                old_value['value'] = value['value']
753
            self.host.post_parameters[name] = old_value
754
        self.host.store()
755

    
756
    def auth_request_http_headers [html] (self):
757
        form = self.form_auth_request_http_headers()
758

    
759
        if form.get_widget('cancel').parse():
760
            return redirect('modify_authentication_request')
761

    
762
        if form.is_submitted() and not form.has_errors():
763
            self.submit_auth_request_http_headers_form(form)
764
            return redirect('modify_authentication_request')
765

    
766
        get_response().breadcrumb.append(('auth_request_http_headers', _('HTTP headers')))
767
        self.html_top(_('Configure HTTP headers'))
768

    
769
        '<p>%s</p>' % _('''Here are the HTTP headers that will be sent within the authentication
770
POST request. You can desactivate some or all of them, or change their value.''')
771

    
772
        form.render()
773

    
774
    def form_auth_request_http_headers(self):
775
        form = Form(enctype='multipart/form-data')
776
        for name, value in self.host.http_headers.iteritems():
777
            if value['immutable']:
778
                form.add(DictWidget, name, value, disabled = 'disabled')
779
            else:
780
                form.add(DictWidget, name, value)
781
        form.add(HtmlWidget, htmltext('<p>%s</p>' % \
782
            _('The headers "Host", "Accept-Encoding" and "Content-Length" will also automatically be sent.')))
783
        if get_cfg('proxy', {}).get('enabled') and self.host.use_proxy:
784
            form.add(HtmlWidget, htmltext('<p>%s</p>' % \
785
                _('''As Larpe uses a proxy for this site, the headers "Proxy-Authorization",
786
"Proxy-Connection" and "Keep-Alive" will be sent as well.''')))
787
        form.add_submit('submit', _('Submit'))
788
        form.add_submit('cancel', _('Cancel'))
789
        return form
790

    
791
    def submit_auth_request_http_headers_form(self, form):
792
        for name, old_value in self.host.http_headers.iteritems():
793
            value = form.get_widget(name).parse()
794
            if value['enabled'] == 'on':
795
                old_value['enabled'] = True
796
            else:
797
                old_value['enabled'] = False
798
            if old_value['immutable'] is False:
799
                old_value['value'] = value['value']
800
            self.host.http_headers[name] = old_value
801
        self.host.store()
802

    
803
    def generate_apache_filters(self):
804
        self.host.apache_python_paths = []
805
        self.host.apache_output_python_filters = []
806
        site_auth = site_authentication.get_site_authentication(self.host)
807
        output_filters = site_auth.output_filters
808
        replace_login_form = self.host.auth_form_places == 'form_everywhere' and \
809
                self.host.auth_form_action
810
        if replace_login_form and not 'output_replace_form' in output_filters:
811
            output_filters.append('output_replace_form')
812
        if output_filters:
813
            location = Location(self.host)
814
            conf = { 'auth_form_action': self.host.auth_form_action,
815
                    'name': self.host.name,
816
                    'larpe_dir': get_publisher().app_dir,
817
                    'logout_url': location.new_logout_url,
818
                    'login_url': location.new_auth_url }
819
            # Set Python filter path for Apache configuration
820
            python_path = os.path.join(self.host.site_dir, 'filters')
821
            if python_path not in self.host.apache_python_paths:
822
                self.host.apache_python_paths.append(python_path)
823

    
824
            for filter in output_filters:
825
                python_file = open(
826
                        os.path.join(self.host.site_dir, 'filters', filter + ".py"),
827
                        'w')
828
                python_file.write(
829
                        open(os.path.join(DATA_DIR, "filters", filter + ".py")).read() % conf
830
                        )
831
                if not filter in self.host.apache_output_python_filters:
832
                    self.host.apache_output_python_filters.append(filter)
833

    
834
    def sso_init_link [html] (self):
835
        form = self.form_sso_init_link()
836

    
837
        if form.get_widget('cancel').parse():
838
            return redirect('check_authentication')
839

    
840
        if form.is_submitted() and not form.has_errors():
841
            self.submit_sso_init_link_form(form)
842
            if form.get_widget('terminate').parse():
843
                return redirect('..')
844
            return redirect('metadatas')
845

    
846
        get_response().breadcrumb.append(('sso_init_link', _('SSO initiation')))
847
        self.html_top(_('Step 7 - Configure how a Single Sign On can be initiated'))
848

    
849
        '<p>%s\n' % _('Most sites use one of the following 2 ways to allow users to initialise an authentication :')
850
        '\t<ol>\n'
851
        '\t\t<li>%s</li>\n' % \
852
            _('''The site has a single authentication page. It redirects users to this page when
853
they click a "Login" button or try to access a page which require users to be authenticated.''')
854
        '\t\t<li>%s</li>\n' % \
855
            _('''The site includes an authentication form in most or all of his pages. Users can
856
authenticate on any of these pages, and don't need to be redirected to a separate authentication page.''')
857
        '\t</ol>\n'
858
        '</p>\n'
859

    
860
        '<p>%s</p>' % _('Select the way your site works :')
861

    
862
        form.render()
863

    
864
    def form_sso_init_link(self):
865
        form = Form(enctype='multipart/form-data')
866
        form.add(RadiobuttonsWidget, 'auth_form_places',
867
                options=[
868
                    ('form_once', _('The site has a single authentication page'), 'form_once'),
869
                    ('form_everywhere', _('The site includes an authentication form in most or all pages'), 'form_everywhere'),
870
                ],
871
                sort=False, required = True, delim=htmltext('<br />'), value = self.host.auth_form_places)
872
        form.add_submit('cancel', _('Previous'))
873
        form.add_submit('submit', _('Next'))
874
        form.add_submit('terminate', _('Terminate'))
875
        return form
876

    
877
    def submit_sso_init_link_form(self, form):
878
        fields = [ 'auth_form_places', ]
879
        for f in fields:
880
            setattr(self.host, f, form.get_widget(f).parse())
881
        self.host.auth_form_url = self.host.auth_url
882
        self.generate_apache_filters()
883
        self.host.store()
884
        write_apache2_vhosts()
885

    
886
    def metadatas [html] (self):
887
        form = Form(enctype='multipart/form-data')
888
        form.add_submit('cancel', _('Previous'))
889
        form.add_submit('submit', _('Next'))
890
        form.add_submit('terminate', _('Terminate'))
891

    
892
        if form.get_widget('cancel').parse():
893
            return redirect('sso_init_link')
894

    
895
        if form.is_submitted():
896
            if form.get_widget('terminate').parse():
897
                return redirect('..')
898
            return redirect('advanced_options')
899

    
900
        get_response().breadcrumb.append(('metadatas', _('Metadatas')))
901
        self.html_top(_('Step 8 - Metadatas of %(site_name)s' % {'site_name': self.host.name}))
902

    
903
        '<p>%s</p>' % \
904
            _('''Download the metadatas and the public key for this site and
905
upload them on your identity provider in order to use Liberty Alliance features.''')
906

    
907
        '<dl>'
908
        if hasattr(self.host, str('base_url')):
909
            if lasso.SAML2_SUPPORT:
910
                saml2_metadata_url = '%s/metadata.xml' % self.host.saml2_base_url
911
                '<dt><a href="%s">%s</a></dt> <dd>%s</dd>' % (
912
                            saml2_metadata_url,
913
                            _('SAML 2.0 Metadata'),
914
                            _('Download SAML 2.0 metadata file'))
915
            metadata_url = '%s/metadata.xml' % self.host.base_url
916
            '<dt><a href="%s">%s</a></dt> <dd>%s</dd>' % (
917
                        metadata_url,
918
                        _('ID-FF 1.2 Metadata'),
919
                        _('Download ID-FF 1.2 metadata file'))
920
        else:
921
            '<p>%s</p>' % _('No metadata has been generated for this host.')
922

    
923
        if hasattr(self.host, str('base_url')) and self.host.public_key and os.path.exists(self.host.public_key):
924
            public_key_url = '%s/public_key' % self.host.base_url
925
            '<dt><a href="%s">%s</a></dt> <dd>%s</dd>' % (
926
                        public_key_url,
927
                        _('Public key'),
928
                        _('Download SSL Public Key file'))
929
        else:
930
            '<p>%s</p>' % _('No public key has been generated for this host.')
931
        '</dl>'
932

    
933
        form.render()
934

    
935
    def advanced_options [html] (self):
936
        form = self.form_advanced_options()
937

    
938
        if form.get_widget('cancel').parse():
939
            return redirect('metadatas')
940

    
941
        if not form.is_submitted() or form.has_errors():
942
            get_response().breadcrumb.append(('advanced_options', _('Advanced options')))
943
            self.html_top(_('Step 9 - Advanced options'))
944

    
945
            '<p>%s</p>' % _('Configure advanced options to setup the last details of your site.')
946
            '<p>%s</p>' % _('''If you don't know what to configure here, just click %(next)s and
947
come here later if needed.''') % {'next': _('Next')}
948

    
949
            form.render()
950
        else:
951
            self.submit_advanced_options_form(form)
952
            if form.get_widget('terminate').parse():
953
                return redirect('..')
954
            return redirect('check_full_configuration')
955

    
956
    def form_advanced_options(self):
957
        form = Form(enctype='multipart/form-data')
958
        form.add(CheckboxWidget, 'redirect_root_to_login',
959
                title=_('Redirect the root URL of the site to the login page.'),
960
                value = self.host.redirect_root_to_login)
961
        form.add(UrlOrAbsPathWidget, 'return_url', title = _('Return address'),
962
                hint = _('Where the user will be redirected after a successful authentication'),
963
                required = False, size = 50, value = self.host.return_url)
964
        form.add(UrlOrAbsPathWidget, 'root_url', title = _('Error address'),
965
                hint = _('Where the user will be redirected after a disconnection or an error'),
966
                required = False, size = 50, value = self.host.root_url)
967
        form.add(UrlOrAbsPathWidget, 'initiate_sso_url', title = _('URL which must initiate the SSO'),
968
                hint = _('''Address which must initiate the SSO. If empty, defaults to the previously
969
specified "%s"''') % _('Authentication form page address'),
970
                required = False, size = 50, value = self.host.initiate_sso_url)
971
        form.add(CheckboxWidget, 'proxy-html', title = _('Apache HTML proxy'),
972
                hint = _('''Converts urls in the HTML pages according to the host new domain name.
973
Disabled by default because it makes some sites not work correctly.'''),
974
                value = 'proxy-html' in self.host.apache_output_filters)
975

    
976
        form.add_submit('cancel', _('Previous'))
977
        form.add_submit('submit', _('Next'))
978
        form.add_submit('terminate', _('Terminate'))
979
        return form
980

    
981
    def submit_advanced_options_form(self, form):
982
        old_redirect_root_to_login = self.host.redirect_root_to_login
983

    
984
        for f in ('redirect_root_to_login', 'return_url', 'root_url', 'initiate_sso_url'):
985
            value = form.get_widget(f).parse()
986
            setattr(self.host, f, value)
987

    
988
        f = 'proxy-html'
989
        value = form.get_widget(f).parse()
990
        if value is True and f not in self.host.apache_output_filters:
991
            self.host.apache_output_filters.append(f)
992
        if value is False and f in self.host.apache_output_filters:
993
            self.host.apache_output_filters.remove(f)
994

    
995
        self.host.store()
996

    
997
        if self.host.initiate_sso_url or self.host.redirect_root_to_login is not old_redirect_root_to_login:
998
            write_apache2_vhosts()
999

    
1000
    def check_full_configuration [html] (self):
1001
        form = Form(enctype='multipart/form-data')
1002
        form.add_submit('cancel', _('Previous'))
1003
        form.add_submit('submit', _('Finish'))
1004

    
1005
        if form.get_widget('cancel').parse():
1006
            return redirect('advanced_options')
1007

    
1008
        if form.is_submitted():
1009
            return redirect('../..')
1010

    
1011
        get_response().breadcrumb.append(('check_full_configuration', _('Check everything works')))
1012
        self.html_top(_('Step 10 - Check everything works'))
1013

    
1014
        '<p>%s</p>' % \
1015
            _('''Now you can fully test your site, start from the home page, initiate a
1016
Single Sign On, federate your identities and do a Single Logout.''')
1017

    
1018
        '<p>%s' % _('The address of your site is : ')
1019
        '<a href="%s">%s</a>' % (self.host.new_url, self.host.new_url)
1020
        '</p>'
1021

    
1022
        '<p>%s</p>' % \
1023
            _('''If everything works, click the "%(finish)s" button, otherwise you can go
1024
back and check your settings.''') % { 'finish': _('Finish') }
1025

    
1026
        form.render()
1027

    
1028
    def auto_detect_configuration(self):
1029
        # Reset previous detected values
1030
        self.host.auth_form = None
1031
        self.host.auth_check_url = None
1032
        self.host.login_field_name = None
1033
        self.host.password_field_name = None
1034
        if not self.host.post_parameters:
1035
            self.host.post_parameters = {}
1036

    
1037
        self.parse_page(self.host.auth_form_url)
1038

    
1039
    def parse_page(self, page_url):
1040
        # Get the authentication page
1041
        try:
1042
            response, status, page, auth_header = http_get_page(page_url, use_proxy=self.host.use_proxy)
1043
        except Exception, msg:
1044
            print msg
1045
            return
1046

    
1047
        if page is None:
1048
            return
1049
            #raise FormError, ('auth_check_url', '%s : %s' % (_('Failed to get page'), self.host.auth_form_url))
1050

    
1051
        # Default authentication mode
1052
        self.host.auth_mode = 'form'
1053

    
1054
        if not self.host.site_authentication_plugin:
1055
            self.host.site_authentication_plugin = site_authentication_plugins.auto_detect(page)
1056
        self.parse_frames(page)
1057
        self.parse_forms(page)
1058
        if self.host.auth_form is not None:
1059
            self.parse_form_action()
1060
            input_fields = self.parse_input_fields()
1061
            self.parse_login_field(input_fields)
1062
            self.parse_password_field()
1063
            self.parse_select_fields()
1064
            self.parse_other_fields()
1065

    
1066
    def parse_frames(self, page):
1067
        '''If there are frames, parse them recursively'''
1068
        regexp = re.compile("""<frame.*?src=["'](.*?)["'][^>]*?>""", re.DOTALL | re.IGNORECASE)
1069
        found_frames = regexp.findall(page)
1070
        if found_frames:
1071
            for frame_url in found_frames:
1072
                if frame_url.startswith('http'):
1073
                    frame_full_url = frame_url
1074
                else:
1075
                    page_url_tokens = frame_url.split('/')
1076
                    page_url_tokens[-1] = frame_url
1077
                    frame_full_url = '/'.join(page_url_tokens)
1078
                self.parse_page(frame_full_url)
1079

    
1080
    def parse_forms(self, page):
1081
        '''Search for an authentication form'''
1082
        # Get all forms
1083
        regexp = re.compile("""<form.*?</form>""", re.DOTALL | re.IGNORECASE)
1084
        found_forms = regexp.findall(page)
1085
        if not found_forms:
1086
            return
1087
            #raise FormError, ('auth_check_url', '%s : %s' % (_('Failed to find any form'), self.host.auth_form_url))
1088

    
1089
        # Get the first form with a password field
1090
        for found_form in found_forms:
1091
            regexp = re.compile("""<input[^>]*?type=["']?password["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
1092
            if regexp.search(found_form) is not None:
1093
                self.host.auth_form = found_form
1094
                break
1095

    
1096
    def parse_form_action(self):
1097
        '''Get the action url of the form'''
1098
        regexp = re.compile("""<form.*?action=["']?(.*?)["']?[\s>].*?>""", re.DOTALL | re.IGNORECASE)
1099
        self.host.auth_form_action = regexp.findall(self.host.auth_form)[0]
1100
        # FIXME: Find a Python module which unescapes html entities
1101
        self.host.auth_check_url = self.host.auth_form_action.replace('&amp;', '&')
1102
        if not self.host.auth_check_url.startswith('http'):
1103
            if self.host.auth_check_url.startswith('/'):
1104
                if self.host.orig_site.startswith('https'):
1105
                    orig_site_root = 'https://%s' % urllib.splithost(self.host.orig_site[6:])[0]
1106
                else:
1107
                    orig_site_root = 'http://%s' % urllib.splithost(self.host.orig_site[5:])[0]
1108
                self.host.auth_check_url = orig_site_root + self.host.auth_check_url
1109
            else:
1110
                auth_form_url_tokens = self.host.auth_form_url.split('/')
1111
                auth_form_url_tokens[-1] = self.host.auth_check_url
1112
                self.host.auth_check_url = '/'.join(auth_form_url_tokens)
1113

    
1114
    def parse_input_fields(self):
1115
        '''Get all input fields'''
1116
        regexp = re.compile("""<input[^>]*?>""", re.DOTALL | re.IGNORECASE)
1117
        return regexp.findall(self.host.auth_form)
1118

    
1119
    def parse_login_field(self, input_fields):
1120
        '''Get login field name'''
1121
        try:
1122
            regexp = re.compile("""<input[^>]*?type=["']?text["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
1123
            text_fields = regexp.findall(self.host.auth_form)
1124
            login_field = ''
1125
            if text_fields:
1126
                login_field = text_fields[0]
1127
            else:
1128
                for field in input_fields:
1129
                    if re.search("""type=["']?""", field, re.DOTALL | re.IGNORECASE) is None:
1130
                        login_field = field
1131
                        break
1132
            regexp = re.compile("""name=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
1133
            self.host.login_field_name = regexp.findall(login_field)[0]
1134
            if not self.host.post_parameters.has_key(self.host.login_field_name):
1135
                self.host.post_parameters[self.host.login_field_name] = \
1136
                    { 'enabled': True, 'value': _('(filled by users)'), 'immutable': True }
1137
                self.host.store()
1138
        except IndexError, e:
1139
            self.host.login_field_name = None
1140
            print 'Error handling login field : %s' % e
1141

    
1142
    def parse_password_field(self):
1143
        '''Get password field name'''
1144
        try:
1145
            regexp = re.compile("""<input[^>]*?type=["']?password["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
1146
            password_field = regexp.findall(self.host.auth_form)[0]
1147
            regexp = re.compile("""name=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
1148
            self.host.password_field_name = regexp.findall(password_field)[0]
1149
            if not self.host.post_parameters.has_key(self.host.password_field_name):
1150
                self.host.post_parameters[self.host.password_field_name] = \
1151
                    { 'enabled': True, 'value': _('(filled by users)'), 'immutable': True }
1152
        except IndexError, e:
1153
            self.host.password_field_name = None
1154
            print 'Error handling password field : %s' % e
1155

    
1156
    def parse_select_fields(self):
1157
        '''Add select fields to host attributes'''
1158
        # First added for Imuse (Rennes)
1159
        regexp = re.compile("""<select.*?</select>""", re.DOTALL | re.IGNORECASE)
1160
        self.host.select_fields = {}
1161
        for field in regexp.findall(self.host.auth_form):
1162
            try:
1163
                regexp = re.compile("""name=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
1164
                name = regexp.findall(field)[0]
1165
                regexp = re.compile("""<option[^>]*?>.*?</option>""", re.DOTALL | re.IGNORECASE)
1166
                options = regexp.findall(field)
1167
                values = []
1168
                for option in options:
1169
                    regexp = re.compile("""<option[^>]*?>(.*?)</option>""", re.DOTALL | re.IGNORECASE)
1170
                    option_label = regexp.findall(option)
1171
                    regexp = re.compile("""value=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
1172
                    option_value = regexp.findall(option)
1173
                    if option_label:
1174
                        if not option_value:
1175
                            option_value = option_label
1176
                        values.append((option_value[0], option_label[0]))
1177
                    else:
1178
                        print >> sys.stderr, 'W: Could not parse select options'
1179
                self.host.select_fields[name] = values
1180
                if not self.host.post_parameters.has_key(name):
1181
                    self.host.post_parameters[name] = \
1182
                        { 'enabled': True, 'value': _('(filled by users)'), 'immutable': True }
1183
            except IndexError, e:
1184
                continue
1185

    
1186
    def parse_other_fields(self):
1187
        '''Get the default value of all other fields'''
1188
        self.host.other_fields = {}
1189

    
1190
        # Get hidden fields
1191
        regexp = re.compile("""<input[^>]*?type=["']?hidden["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
1192
        other_fields = regexp.findall(self.host.auth_form)
1193

    
1194
        # Only get first submit field
1195
        regexp = re.compile("""<input[^>]*?type=["']?submit["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
1196
        found = regexp.findall(self.host.auth_form)
1197
        if found:
1198
            if other_fields:
1199
                other_fields.append(found[0])
1200
            else:
1201
                other_fields = found[0]
1202

    
1203
        for field in other_fields:
1204
            try:
1205
                regexp = re.compile("""name=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
1206
                name = regexp.findall(field)[0]
1207
                regexp = re.compile("""value=["'](.*?)["'][\s/>]""", re.DOTALL | re.IGNORECASE)
1208
                value = regexp.findall(field)[0]
1209
                self.host.other_fields[name] = value
1210
                if not self.host.post_parameters.has_key(name):
1211
                    self.host.post_parameters[name] = { 'enabled': True, 'value': value, 'immutable': False }
1212
            except IndexError, e:
1213
                continue
1214

    
1215

    
1216
class HostPage(Directory):
1217
    _q_exports = ['', 'delete']
1218

    
1219
    def __init__(self, host_id):
1220
        self.host = Host.get(host_id)
1221
        get_response().breadcrumb.append((host_id + '/', self.host.label))
1222

    
1223
    def _q_lookup(self, component):
1224
        if component == 'configuration_assistant':
1225
            return ConfigurationAssistant(self.host)
1226
        elif component == 'forms_prefill':
1227
            return FormsDirectory(self.host)
1228

    
1229
    def _q_index [html] (self):
1230
        get_publisher().reload_cfg()
1231
        html_top('hosts', title = self.host.label)
1232

    
1233
        '<h2>%s</h2>' % _('Configuration assistant')
1234

    
1235
        '<dl>'
1236

    
1237
        '<dt><a href="configuration_assistant/start">%s</a></dt> <dd>%s</dd>' % (
1238
                _('Address of the original site'), _('Configure the root address of the site'))
1239

    
1240
        '<dt><a href="configuration_assistant/check_new_address">%s</a></dt> <dd>%s</dd>' % (
1241
                _('New address and name'), _('Configure the new address and name of this site'))
1242

    
1243
        '<dt><a href="configuration_assistant/authentication_and_logout_adresses">%s</a></dt> <dd>%s</dd>' % (
1244
                _('Authentication and logout addresses'), _('Configure the authentication and logout addresses of the original site'))
1245

    
1246
        '<dt><a href="configuration_assistant/check_auto_detected_configuration">%s</a></dt> <dd>%s</dd>' % (
1247
                _('Check auto detected configuration'), _('Check the automatically detected configuration is right'))
1248

    
1249
        '<dt><a href="configuration_assistant/credentials">%s</a></dt> <dd>%s</dd>' % (
1250
                _('Credentials'), _('Configure some valid credentials to authenticate on the original site'))
1251

    
1252
        '<dt><a href="configuration_assistant/send_authentication_request">%s</a></dt> <dd>%s</dd>' % (
1253
                _('Retry authentication'), _('Retry sending an authentication request to the site to check if your new parameters work well'))
1254

    
1255
        '<dt><a href="configuration_assistant/see_authentication_response">%s</a></dt> <dd>%s</dd>' % (
1256
                _('Check authentication response'), _('Check the response from the latest authentication request'))
1257

    
1258
        '<dt><a href="configuration_assistant/authentication_success_criteria">%s</a></dt> <dd>%s</dd>' % (
1259
                _('Configure authentication success criteria'), _('Specify how Larpe knows if the authentication has succeeded or not'))
1260

    
1261
        '<dt><a href="configuration_assistant/modify_authentication_request">%s</a></dt> <dd>%s</dd>' % (
1262
                _('Modify authentication request'), _('Modify POST fields or HTTP headers of the authentication request'))
1263

    
1264
        '<dt><a href="configuration_assistant/sso_init_link">%s</a></dt> <dd>%s</dd>' % (
1265
                _('Configure how a Single Sign On can be initiated'), _('Configure how a Single Sign On can be initiated'))
1266

    
1267
        '<dt><a href="configuration_assistant/metadatas">%s</a></dt> <dd>%s</dd>' % (
1268
                _('Metadatas and key'), _('Download SAML 2.0 or ID-FF metadatas and SSL public key'))
1269

    
1270
        '<dt><a href="configuration_assistant/advanced_options">%s</a></dt> <dd>%s</dd>' % (
1271
                _('Adavanced options'), _('Configure advanced options to setup the last details of your site'))
1272

    
1273
        '</dl>'
1274

    
1275
        '<h2>%s</h2>' % _('Form prefilling with ID-WSF')
1276

    
1277
        '<dl>'
1278
        '<dt><a href="forms_prefill/">%s</a></dt> <dd>%s</dd>' % (
1279
                _('Forms'), _('Configure the forms to prefill'))
1280
        '</dl>'
1281

    
1282
    def delete [html] (self):
1283
        form = Form(enctype='multipart/form-data')
1284
        form.widgets.append(HtmlWidget('<p>%s</p>' % _(
1285
                        'You are about to irrevocably delete this host.')))
1286
        form.add_submit('submit', _('Submit'))
1287
        form.add_submit('cancel', _('Cancel'))
1288
        if form.get_widget('cancel').parse():
1289
            return redirect('..')
1290
        if not form.is_submitted() or form.has_errors():
1291
            get_response().breadcrumb.append(('delete', _('Delete')))
1292
            html_top('hosts', title = _('Delete Host'))
1293
            '<h2>%s : %s</h2>' % (_('Delete Host'), self.host.label)
1294
            form.render()
1295
        else:
1296
            self.host.remove_self()
1297
            write_apache2_vhosts()
1298
            return redirect('..')
1299

    
1300

    
1301
class HostsDirectory(Directory):
1302
    _q_exports = ['', 'new']
1303

    
1304
    def _q_index [html] (self):
1305
        get_response().breadcrumb.append(('hosts/', _('Hosts')))
1306
        html_top('hosts', title = _('Hosts'))
1307
        """<ul id="nav-hosts-admin">
1308
          <li><a href="new">%s</a></li>
1309
        </ul>""" % _('New Host')
1310

    
1311
        '<ul class="biglist">'
1312

    
1313
        for host in Host.select(lambda x: x.name != 'larpe', order_by = 'label'):
1314
            if not host.name:
1315
                continue
1316
            if not hasattr(host, str('scheme')):
1317
                host.scheme = str('http')
1318
            '<li>'
1319
            '<strong class="label">%s</strong>' % host.label
1320
            if hasattr(host, str('new_url')) and host.new_url:
1321
                url = host.new_url
1322
            else:
1323
                # Compat with older Larpe versions
1324
                url = '%s://%s%s/' % (host.scheme, host.reversed_hostname, get_request().environ['SCRIPT_NAME'])
1325
                if host.reversed_directory is not None:
1326
                    url += '%s/' % host.reversed_directory
1327
            '<br /><a href="%s">%s</a>' % (url, url)
1328
            '<p class="commands">'
1329
            command_icon('%s/' % host.id, 'edit')
1330
            command_icon('%s/delete' % host.id, 'remove')
1331
            '</p></li>'
1332
        '</ul>'
1333

    
1334
    def new [html] (self):
1335
        if not os.path.isdir(os.path.join(get_publisher().app_dir, str('idp'))):
1336
            html_top('hosts', title = _('New Host'))
1337
            html = '<h2>%s</h2>' % _('New Host')
1338
            html += 'You must <a href="%s/admin/settings/liberty_idp/">' % misc.get_root_url()
1339
            html += 'configure an Identity Provider</a> first<br /><br />'
1340
            html += '<a href="."><input type="button" value="%s" /></a>' % _('Back')
1341
            return html
1342

    
1343
        get_response().breadcrumb.append(('hosts/', _('Hosts')))
1344
        get_response().breadcrumb.append(('new', _('New')) )
1345
        host = Host()
1346
        host.store()
1347
        return redirect('%s/configuration_assistant/start' % host.id)
1348

    
1349
    def _q_lookup(self, component):
1350
        get_response().breadcrumb.append(('hosts/', _('Hosts')))
1351
        return HostPage(component)
1352

    
(5-5/9)