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('&', '&')
|
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
|
|