This repository has been archived on 2023-02-21. You can view files and clone it, but cannot push or open issues or pull requests.
larpe/larpe/branches/idwsf/larpe/admin/hosts.ptl

1840 lines
83 KiB
Plaintext

import os
import urllib
import urlparse
from quixote import redirect, get_request, get_response, get_publisher
from quixote.directory import Directory
import lasso
from larpe.qommon import get_cfg
from larpe.qommon.form import *
from larpe.qommon.misc import http_get_page, get_abs_path
import site_authentication
from larpe import errors
from larpe import misc
from larpe.hosts import Host
from larpe.admin.liberty_utils import *
#from larpe.filter import filter_misc
from larpe.admin.apache import write_apache2_vhosts
from larpe.admin.forms_prefill import FormsDirectory
from menu import *
def check_basic_configuration(form):
get_publisher().reload_cfg()
# Check reversed_hostname and reversed_directory
reversed_hostname = form.get_widget('reversed_hostname').parse()
reversed_directory = form.get_widget('reversed_directory').parse()
if reversed_hostname == get_publisher().cfg['proxy_hostname'] and not reversed_directory:
form.set_error('reversed_hostname',
_('You must either choose a different hostname from Larpe or specify a reversed directory'))
def check_minimal_configuration(form):
# Check auth_url and auth_form_page_url
auth_url = form.get_widget('auth_url').parse()
auth_form_page_url = form.get_widget('auth_form_page_url').parse()
if auth_url and auth_form_page_url:
form.set_error('auth_form_page_url',
_('"Authentication page" and "Authentication form page" are incompatible. Only fill one of them.'))
def convert_label_to_name(label):
'''Build host name from host label'''
name = label.lower()
invalid_characters = [' ', "'"]
for char in invalid_characters:
name = name.replace(char, '_')
return name
class DictWidget(Widget):
def render_content [html] (self):
self.render_br = False
if self.value['enabled'] is True:
htmltag('input', xml_end=True, type='checkbox', name=self.name + '_enabled', checked='checked')
else:
htmltag('input', xml_end=True, type='checkbox', name=self.name + '_enabled')
' ' + self.name + '  '
htmltag('input', xml_end=True, type='text', name=self.name, value=self.value['value'], size='35', **self.attrs)
def _parse(self, request):
enabled = request.form.get(self.name + '_enabled')
value = request.form.get(self.name)
self.value = { 'enabled': enabled, 'value': value }
class ConfigurationAssistant(Directory):
_q_exports = ['start', 'check_new_address', 'modify_site_address_and_name', 'authentication_and_logout_adresses',
'check_auto_detected_configuration', 'credentials', 'check_authentication', 'send_authentication_request',
'see_authentication_response', 'see_response_html_page', 'authentication_success_criteria',
'modify_authentication_request', 'auth_request_post_parameters', 'auth_request_http_headers',
'sso_init_link', 'metadatas', 'check_full_configuration', 'advanced_options']
def __init__(self, host):
self.host = host
def start [html] (self):
# Check the global domain name has been previously set
get_publisher().reload_cfg()
if not get_cfg('domain_names') or not get_cfg('domain_names')[0]:
html_top('preamble', title=_('Need domain name configuration'))
return htmltext(_('Before configuring hosts, you must <a href="../../../settings/domain_names">setup a global domain name</a> in %(settings)s menu.') % { 'settings': _('Settings') })
form = self.form_start()
if form.get_widget('cancel').parse():
return redirect('../..')
connection_failure = None
if form.is_submitted() and not form.has_errors():
try:
self.submit_start_form(form)
except Exception, e:
connection_failure = e
else:
return redirect('check_new_address')
html_top('step1', title=_('Step 1 - Basic configuration'))
'<h2>%s</h2>' % _('Step 1 - Basic configuration')
if connection_failure:
'<div class="errornotice">%s</div>' % connection_failure
form.render()
def form_start(self):
form = Form(enctype='multipart/form-data')
form.add(UrlWidget, 'orig_site', title = _('Original site root address'), required = True,
size = 50, value = self.host.orig_site,
hint = _('If your site address is http://test.org/index.php, put http://test.org/ here'))
get_publisher().reload_cfg()
if get_cfg('use_proxy'):
form.add(CheckboxWidget, 'use_proxy', title = _('Use a proxy'),
hint = _("Uncheck it if Larpe doesn't need to use the proxy to connect to this site"),
value = self.host.use_proxy)
else:
form.add(HtmlWidget, htmltext('<p>%s</p>' % \
_('If Larpe needs to use a proxy to connect to this site, you must first configure it in <a href="../../../settings/proxy">global proxy parameters</a>.')))
form.add_submit('cancel', _('Cancel'))
form.add_submit('submit', _('Next'))
return form
def submit_start_form(self, form):
fields = ['orig_site']
if get_cfg('use_proxy'):
fields += ['use_proxy']
for f in fields:
setattr(self.host, f, form.get_widget(f).parse())
# If no proxy is setup yet, set use_proxy to False for new hosts in case a proxy is later configured
if not get_cfg('use_proxy'):
self.host.use_proxy = False
# Remove what is after the last '/'
#self.host.orig_site = '/'.join(self.host.orig_site.split('/')[:-1])
html_page = self.get_data_after_redirects(self.host.orig_site)
if not self.host.label:
# Look for html title in original site index page
regexp = re.compile("""<title.*?>(.*?)</title>""", re.DOTALL | re.IGNORECASE)
title = regexp.findall(html_page)
if title:
self.host.label = title[0]
else:
self.host.label = 'Untitled'
# If another site already uses this site title, add trailings "_" until we find an available name
existing_label = True
while existing_label is True:
for any_host in Host.select():
if any_host.id != self.host.id and self.host.label == any_host.label:
self.host.label += '_'
break
else:
existing_label = False
# Fill host.name attribute
self.host.name = convert_label_to_name(self.host.label)
if not self.host.scheme:
# Get tokens from orig site url
orig_scheme, rest = urllib.splittype(self.host.orig_site)
orig_host, rest = urllib.splithost(rest)
get_publisher().reload_cfg()
# Set url scheme (HTTP or HTTPS)
# TODO: Handle the option "Both"
if get_cfg('sites_url_scheme'):
self.host.scheme = get_cfg('sites_url_scheme')
else:
self.host.scheme = orig_scheme
if not self.host.reversed_hostname:
# Build a new domain name
short_name = orig_host.split('.')[0]
if short_name == 'www':
short_name = orig_host.split('.')[1]
self.host.reversed_hostname = '%s.%s' % (short_name, get_cfg('domain_names')[0])
# If another site already uses this domain name, add some trailing "_" until we find an available name
existing_domain = True
while existing_domain is True:
for any_host in Host.select():
if any_host.id != self.host.id and self.host.reversed_hostname == any_host.reversed_hostname:
self.host.reversed_hostname += '-'
break
else:
existing_domain = False
self.host.reversed_directory = None
if not self.host.new_url:
# New url for this host
self.host.new_url = '%s://%s%s/' % (self.host.scheme, self.host.reversed_hostname, get_request().environ['SCRIPT_NAME'])
# FIXME: Check if the new domain name already exists
# New url for this host
# self.host.new_url = '%s://%s%s/' % (self.host.scheme, self.host.reversed_hostname, get_request().environ['SCRIPT_NAME'])
# if self.host.reversed_directory is not None:
# self.host.new_url += '%s/' % self.host.reversed_directory
self.host.store()
write_apache2_vhosts()
# XXX: Should use the FancyURLopener class instead when it supports proxies
def get_data_after_redirects(self, start_url):
if not start_url:
return ''
status = 302
location = None
while status // 100 == 3:
if location is None:
url = start_url
elif location.startswith('http'):
# Location is an absolute path
url = location
else:
# Location is a relative path
url = urlparse.urljoin(start_url, location)
response, status, data, auth_headers = http_get_page(url, use_proxy=self.host.use_proxy)
location = response.getheader('Location', None)
return data
def create_dirs(self):
# Hack : sites must use the configuration which is stored in main Larpe directory,
# but they need to have a directory named with their hostname, which will contain the
# main domain name for Larpe so they know where is the main configuration
hostname_dir = get_abs_path(os.path.join('..', self.host.reversed_hostname))
if not os.path.exists(hostname_dir):
os.mkdir(hostname_dir)
# Load the configuration from the main directory
get_publisher().reload_cfg()
# Write it in the site directory
get_publisher().write_cfg(hostname_dir)
# Storage directories
if not self.host.reversed_directory:
reversed_dir = 'default'
else:
reversed_dir = self.host.reversed_directory
self.host.site_dir = \
os.path.join(get_publisher().app_dir, 'sp', self.host.reversed_hostname, reversed_dir)
user_dir = os.path.join(self.host.site_dir, 'users')
token_dir = os.path.join(self.host.site_dir, 'tokens')
filter_dir = os.path.join(self.host.site_dir, 'filters')
for dir in (self.host.site_dir, user_dir, token_dir, filter_dir):
if not os.path.isdir(dir):
os.makedirs(dir)
def generate_ssl_keys(self):
# Generate SSL keys
private_key_path = os.path.join(self.host.site_dir, 'private_key.pem')
public_key_path = os.path.join(self.host.site_dir, 'public_key.pem')
if not os.path.isfile(private_key_path) or not os.path.isfile(public_key_path):
set_provider_keys(private_key_path, public_key_path)
self.host.private_key = private_key_path
self.host.public_key = public_key_path
def generate_metadatas(self):
metadata_cfg = {}
# Organization name
self.host.organization_name = self.host.label
metadata_cfg['organization_name'] = self.host.organization_name
# Base URL
base_url = '%s://%s%s/liberty/%s/liberty' % (self.host.scheme,
self.host.reversed_hostname,
get_request().environ['SCRIPT_NAME'],
self.host.name)
metadata_cfg['base_url'] = base_url
self.host.base_url = base_url
if lasso.SAML2_SUPPORT:
saml2_base_url = '%s://%s%s/liberty/%s/saml' % (self.host.scheme,
self.host.reversed_hostname,
get_request().environ['SCRIPT_NAME'],
self.host.name)
metadata_cfg['saml2_base_url'] = saml2_base_url
self.host.saml2_base_url = saml2_base_url
# Provider Id
provider_id = '%s/metadata' % base_url
metadata_cfg['provider_id'] = provider_id
self.host.provider_id = provider_id
if lasso.SAML2_SUPPORT:
saml2_provider_id = '%s/metadata' % saml2_base_url
metadata_cfg['saml2_provider_id'] = saml2_provider_id
self.host.saml2_provider_id = saml2_provider_id
# Read public key
public_key = ''
if self.host.public_key is not None and os.path.exists(self.host.public_key):
metadata_cfg['signing_public_key'] = open(self.host.public_key).read()
# Write metadatas
metadata_path = os.path.join(self.host.site_dir, 'metadata.xml')
open(metadata_path, 'w').write(get_metadata(metadata_cfg))
self.host.metadata = metadata_path
if lasso.SAML2_SUPPORT:
saml2_metadata_path = os.path.join(self.host.site_dir, 'saml2_metadata.xml')
open(saml2_metadata_path, 'w').write(get_saml2_metadata(metadata_cfg))
self.host.saml2_metadata = saml2_metadata_path
def check_new_address [html] (self):
form = Form(enctype='multipart/form-data')
form.add_submit('cancel', _('Previous'))
form.add_submit('submit', _('Next'))
if form.get_widget('cancel').parse():
return redirect('start')
if form.is_submitted():
self.create_dirs()
if self.host.private_key is None:
self.generate_ssl_keys()
self.generate_metadatas()
self.host.store()
return redirect('authentication_and_logout_adresses')
html_top('step2', title=_('Step 2 - Check the new site address works'))
'<h2>%s</h2>' % _('Step 2 - Check the new site address works')
htmltext(_('''\
<p>Before opening the following link, ensure you have configured your DNS for this address. If you don't
have a DNS server and you just want to test Larpe, add this domain name in the file "/etc/hosts".</p>
<p>Then you can open this link in a new window or tab and see if your site is displayed. If it's ok,
you can click the "%(next)s" button. Otherwise, click the "%(previous)s" button and check your settings.</p>
''') % {'next': _('Next'), 'previous': _('Previous')})
'<p>'
htmltext(_('The new address of this site is '))
'<a href="%s">%s</a><br/>' % (self.host.new_url, self.host.new_url)
htmltext(_('The name of this site is "%s".') % self.host.label)
'</p><p>'
htmltext(_('You can also <a href="modify_site_address_and_name">modify the address or the name of this site</a>'))
'</p>'
form.render()
def modify_site_address_and_name [html] (self):
form = self.form_modify_site_address_and_name()
if form.get_widget('cancel').parse():
return redirect('check_new_address')
if form.is_submitted() and not form.has_errors():
label = form.get_widget('label').parse()
name = convert_label_to_name(label)
for any_host in Host.select():
if any_host.id != self.host.id and name == any_host.name:
form.set_error('label', _('An host with the same name already exists'))
break
if form.is_submitted() and not form.has_errors():
self.submit_modify_site_address_and_name_form(form)
return redirect('check_new_address')
html_top('modify_site_address_and_name', title=_('Modify site address and name'))
'<h2>%s</h2>' % _('Modify site address and name')
form.render()
def form_modify_site_address_and_name(self):
form = Form(enctype='multipart/form-data')
form.add(UrlWidget, 'new_url', title = _('Address'), required = True,
size = 50, value = self.host.new_url)
form.add(StringWidget, 'label', title = _('Name'), required = True,
size = 50, value = self.host.label)
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
return form
def submit_modify_site_address_and_name_form(self, form):
fields = ['new_url', 'label']
for f in fields:
setattr(self.host, f, form.get_widget(f).parse())
# Split url to retrieve components
tokens = urlparse.urlparse(self.host.new_url)
self.host.scheme = tokens[0]
self.host.reversed_hostname = tokens[1]
self.host.reversed_directory = tokens[2]
if self.host.reversed_directory.startswith('/'):
self.host.reversed_directory = self.host.reversed_directory[1:]
# Fill host.name attribute
self.host.name = convert_label_to_name(self.host.label)
self.host.store()
write_apache2_vhosts()
def authentication_and_logout_adresses [html] (self):
form = self.form_authentication_and_logout_adresses()
if form.get_widget('cancel').parse():
return redirect('check_new_address')
if form.is_submitted() and not form.has_errors():
self.submit_authentication_and_logout_adresses_form(form)
return redirect('check_auto_detected_configuration')
html_top('step3', title=_('Step 3 - Configure authentication and logout pages'))
'<h2>%s</h2>' % _('Step 3 - Configure authentication and logout pages')
form.render()
def form_authentication_and_logout_adresses(self):
form = Form(enctype='multipart/form-data')
form.add(ValidUrlWidget, 'auth_url', title = _('Authentication form page address'),
hint = _('Address of a page on the site which contains the authentication form'),
required = True, size = 50, value = self.host.auth_url)
form.add(ValidUrlWidget, 'logout_url', title = _('Logout address'), required = False,
hint = _('Address of the logout link on the site'),
size = 50, value = self.host.logout_url)
form.add_submit('cancel', _('Previous'))
form.add_submit('submit', _('Next'))
return form
def submit_authentication_and_logout_adresses_form(self, form):
fields = ['auth_url', 'logout_url']
for f in fields:
setattr(self.host, f, form.get_widget(f).parse())
self.host.auth_form_url = self.host.auth_url
if not self.host.http_headers:
self.host.http_headers = {
'Content-Type': { 'enabled': True, 'value': 'application/x-www-form-urlencoded', 'immutable': False },
'X-Forwarded-For': { 'enabled': True, 'value': _('(computed automatically)'), 'immutable': True },
'X-Forwarded-Host': { 'enabled': True, 'value': self.host.reversed_hostname, 'immutable': False },
}
self.auto_detect_configuration()
self.host.store()
write_apache2_vhosts()
def check_auto_detected_configuration [html] (self):
form = Form(enctype='multipart/form-data')
form.add_submit('cancel', _('Previous'))
form.add_submit('submit', _('Next'))
if form.get_widget('cancel').parse():
return redirect('authentication_and_logout_adresses')
if form.is_submitted():
return redirect('credentials')
html_top('step4', title=_('Step 4 - Check automatically detected configuration for the authentication form'))
'<h2>%s</h2>' % _('Step 4 - Check automatically detected configuration for the authentication form')
host_attrs = (
('auth_check_url', _('Address where the authentication form must be sent')),
('login_field_name', _('Name of the login field')),
('password_field_name', _('Name of the password field')),
)
html_fields = ''
success = True
for attr, name in host_attrs:
color = 'black'
if attr in ('auth_check_url', 'login_field_name', 'password_field_name') and \
not getattr(self.host, str(attr)):
color = 'red'
success = False
html_fields += '<div style="margin-bottom: 0.1em; font-weight: bold; color: %s;">%s</div>' % (color, name)
html_fields += '<div style="margin-left: 1em; margin-bottom: 0.3em; color: %s;">%s</div>' % \
(color, getattr(self.host, str(attr)))
if getattr(self.host, str(attr)) == '':
html_fields += '<br />'
'<p>'
if success:
htmltext(_('''\
The following authentication form parameters have been detected. If they look right, you can go to the next step.
If you think they are wrong, go back and check your settings then try again.
'''))
else:
htmltext(_('''\
The following authentication form parameters in red haven't been correctly detected. Go back and check
your settings then try again.
'''))
'</p>'
html_fields
form.render()
def credentials [html] (self):
form = self.form_credentials()
if form.get_widget('cancel').parse():
return redirect('check_auto_detected_configuration')
if form.is_submitted() and not form.has_errors():
self.submit_credentials_form(form)
return redirect('send_authentication_request')
html_top('step5', title=_('Step 5 - Fill in a valid username/password for this site'))
'<h2>%s</h2>' % _('Step 5 - Fill in a valid username/password for this site')
form.render()
def form_credentials(self):
form = Form(enctype='multipart/form-data')
form.add(StringWidget, 'username', title = _('Username'), required = True,
size = 30, value = self.host.valid_username)
form.add(PasswordWidget, 'password', title = _('Password'), required = True,
size = 30, value = self.host.valid_password)
for name, values in self.host.select_fields.iteritems():
options = []
if values:
for value in values:
options.append(value)
form.add(SingleSelectWidget, name, title = name.capitalize(),
value = values[0], options = options)
form.add_submit('cancel', _('Previous'))
form.add_submit('submit', _('Next'))
return form
def submit_credentials_form(self, form):
self.host.valid_username = form.get_widget('username').parse()
self.host.valid_password = form.get_widget('password').parse()
self.host.valid_select = {}
for name, values in self.host.select_fields.iteritems():
if form.get_widget(name):
self.host.valid_select[name] = form.get_widget(name).parse()
self.host.store()
def send_authentication_request(self):
site_auth = site_authentication.get_site_authentication(self.host)
self.host.auth_request_status, self.host.auth_request_data = site_auth.local_auth_check_dispatch(
self.host.valid_username, self.host.valid_password, self.host.valid_select)
self.host.auth_request_success, self.host.auth_request_return_content = \
site_auth.check_auth(self.host.auth_request_status, self.host.auth_request_data)
self.host.store()
return redirect('check_authentication')
def check_authentication [html] (self):
form = Form(enctype='multipart/form-data')
form.add_submit('cancel', _('Previous'))
form.add_submit('submit', _('Next'))
if form.get_widget('cancel').parse():
return redirect('credentials')
if form.is_submitted():
return redirect('sso_init_link')
html_top('step6', title=_('Step 6 - Check the authentication process'))
'<h2>%s</h2>' % _('Step 6 - Check the authentication process')
if self.host.auth_request_success:
htmltext(_('''<p>Authentication succeeded ! You can go to the next step.</p>'''))
else:
htmltext(_('''\
<p>Authentication has failed. To resolve this problem, you can :</p>
<ul>
<li><a href="send_authentication_request">Try authentication again</a></li>
<li><a href="see_authentication_response">See the response of the authentication request from the site</a></li>
<li><a href="modify_authentication_request">Modify the parameters of the authentication request</a></li>
<li><a href="authentication_success_criteria">Change the way Larpe detects the authentication is successful or not</a></li>
<li>Go back and change your username and/or password</li>
</ul>
'''))
form.render()
def see_authentication_response [html] (self):
html_top('see_authentication_response', title=_('Authentication response'))
'<h2>%s</h2>' % _('Authentication response')
color = str('black')
name = str(_('HTTP status code'))
'<div style="margin-bottom: 0.1em; font-weight: bold; color: %s;">%s</div>' % (color, name)
'<div style="margin-left: 1em; margin-bottom: 0.3em; color: %s;">%s (%s)</div>' % \
(color, self.host.auth_request_status, status_reasons[self.host.auth_request_status])
'<dl>'
'<dt><a href="see_response_html_page">%s</a></dt>' % _('See HTML page')
'</dl>'
'<div class="buttons"><a href="check_authentication"><input type="button" value="%s" /></a></div><br />' % _('Back')
def see_response_html_page (self):
return self.host.auth_request_data
def authentication_success_criteria [html] (self):
form = self.form_authentication_success_criteria()
if form.get_widget('cancel').parse():
return redirect('check_authentication')
if form.is_submitted() and not form.has_errors():
self.submit_authentication_success_criteria_form(form)
return redirect('check_authentication')
html_top('authentication_success_criteria', title=_('Criteria of authentication success'))
'<h2>%s</h2>' % _('Criteria of authentication success')
form.render()
def form_authentication_success_criteria(self):
form = Form(enctype='multipart/form-data')
form.add(RadiobuttonsWidget, 'auth_system', title = _('Authentication system of the original site'),
options=[
('password', _('Check the existence of a password field'), 'password'),
('match_text', _('Match some text to detect an authentication failure'), 'match_text'),
],
sort=False,
delim=htmltext('<br />'),
value = self.host.auth_system)
form.add(RegexStringWidget, 'auth_match_text', title = _('Text to match in case of authentication failure'),
required = False, size = 50, value = self.host.auth_match_text)
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
return form
def submit_authentication_success_criteria_form(self, form):
for f in ('auth_system', 'auth_match_text'):
value = form.get_widget(f).parse()
setattr(self.host, f, value)
self.host.store()
def modify_authentication_request [html] (self):
html_top('modify_authentication_request', title=_('Modify the parameters of the authentication request'))
'<h2>%s</h2>' % _('Modify the parameters of the authentication request')
'<dl>'
'<dt><a href="auth_request_post_parameters">%s</a></dt> <dd>%s</dd>' % (
_('Modify POST parameters'), _('Configure the form attributes that will be sent within the authentication POST requests'))
'<dt><a href="auth_request_http_headers">%s</a></dt> <dd>%s</dd>' % (
_('Modify HTTP headers'), _('Configure the HTTP headers of the authentication requests made by Larpe'))
'</dl>'
'<div class="buttons"><a href="check_authentication"><input type="button" value="%s" /></a></div><br />' % _('Back')
def auth_request_post_parameters [html] (self):
form = self.form_auth_request_post_parameters()
if form.get_widget('cancel').parse():
return redirect('modify_authentication_request')
if form.is_submitted() and not form.has_errors():
self.submit_auth_request_post_parameters_form(form)
return redirect('modify_authentication_request')
html_top('auth_request_post_parameters', title=_('Configure POST parameters'))
'<h2>%s</h2>' % _('Configure POST parameters')
'<p>'
htmltext(_('''Here are the detected form fields that will be sent as parameters of the authentication POST
request. You can desactivate some or all of them, or change their value.'''))
'</p>'
form.render()
def form_auth_request_post_parameters(self):
form = Form(enctype='multipart/form-data')
for name, value in self.host.post_parameters.iteritems():
if value['immutable']:
form.add(DictWidget, name, value, disabled = 'disabled')
else:
form.add(DictWidget, name, value)
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
return form
def submit_auth_request_post_parameters_form(self, form):
for name, old_value in self.host.post_parameters.iteritems():
value = form.get_widget(name).parse()
if value['enabled'] == 'on':
old_value['enabled'] = True
else:
old_value['enabled'] = False
if old_value['immutable'] is False:
old_value['value'] = value['value']
self.host.post_parameters[name] = old_value
self.host.store()
def auth_request_http_headers [html] (self):
form = self.form_auth_request_http_headers()
if form.get_widget('cancel').parse():
return redirect('modify_authentication_request')
if form.is_submitted() and not form.has_errors():
self.submit_auth_request_http_headers_form(form)
return redirect('modify_authentication_request')
html_top('auth_request_http_headers', title=_('Configure HTTP headers'))
'<h2>%s</h2>' % _('Configure HTTP headers')
'<p>'
htmltext(_('''Here are the HTTP headers that will be sent within the authentication POST
request. You can desactivate some or all of them, or change their value.'''))
'</p>'
form.render()
def form_auth_request_http_headers(self):
form = Form(enctype='multipart/form-data')
for name, value in self.host.http_headers.iteritems():
if value['immutable']:
form.add(DictWidget, name, value, disabled = 'disabled')
else:
form.add(DictWidget, name, value)
form.add(HtmlWidget, htmltext('<p>%s</p>' % \
_('The headers "Host", "Accept-Encoding" and "Content-Length" will also automatically be sent.')))
if get_cfg('use_proxy') and self.host.use_proxy:
form.add(HtmlWidget, htmltext('<p>%s</p>' % \
_('As Larpe uses a proxy for this site, the headers "Proxy-Authorization", "Proxy-Connection" and "Keep-Alive" will be sent as well.')))
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
return form
def submit_auth_request_http_headers_form(self, form):
for name, old_value in self.host.http_headers.iteritems():
value = form.get_widget(name).parse()
if value['enabled'] == 'on':
old_value['enabled'] = True
else:
old_value['enabled'] = False
if old_value['immutable'] is False:
old_value['value'] = value['value']
self.host.http_headers[name] = old_value
self.host.store()
def generate_apache_filter (self):
# Set Python filter path for Apache configuration
if not hasattr(self.host, 'apache_python_paths'):
self.host.apache_python_paths = []
python_path = os.path.join(self.host.site_dir, 'filters')
if python_path not in self.host.apache_python_paths:
self.host.apache_python_paths.append(python_path)
# Write Python filter
python_file = open(os.path.join(self.host.site_dir, 'filters', 'output_replace_form.py'), 'w')
python_file.write(open(os.path.join(get_publisher().data_dir, 'output_filter_base.py'), 'r').read())
python_file.write('''\
def filter_page(filter, page):
current_form = re.compile('<form .*?action="%(auth_form_action)s".*?>.*?</form>', re.DOTALL)
return current_form.sub('<form method="post" action="/liberty/%(name)s/login"><input type="submit" value="Connexion" /></form>', page)
''' % { 'auth_form_action': self.host.auth_form_action, 'name': self.host.name })
python_file.close()
# Set Python filter for Apache configuration
if not hasattr(self.host, 'apache_output_python_filters'):
self.host.apache_output_python_filters = []
if not 'output_replace_form' in self.host.apache_output_python_filters:
self.host.apache_output_python_filters.append('output_replace_form')
def sso_init_link [html] (self):
form = self.form_sso_init_link()
if form.get_widget('cancel').parse():
return redirect('check_authentication')
if form.is_submitted() and not form.has_errors():
self.submit_sso_init_link_form(form)
return redirect('metadatas')
html_top('step7', title=_('Step 7 - Configure how a Single Sign On can be initiated'))
'<h2>%s</h2>' % _('Step 7 - Configure how a Single Sign On can be initiated')
'<p>'
htmltext(_('''\
Most sites use one of the following 2 ways to allow users to initialise an authentication :
<ol>
<li>The site has a single authentication page. It redirects users to this page when they click a "Login" button or try to access a page which require users to be authenticated.</li>
<li>The site includes an authentication form in most or all of his pages. Users can authenticate on any of these pages, and don't need to be redirected to a separate authentication page.</li>
</ol>'''))
'</p>'
'<p>'
htmltext(_('''\
Larpe needs to change this part of the site in a different way depending on the usual way the site works. It can either :
<ol>
<li>Redirect the user to the Single Sign On url instead of the previous authentication page.</li>
<li>Replace the form included in pages with a simple button. When users press this button, they will be redirected to the Single Sign On url.</li>
</ol>'''))
'</p>'
form.render()
def form_sso_init_link(self):
form = Form(enctype='multipart/form-data')
form.add(RadiobuttonsWidget, 'auth_form_places', title = _('Authentication page or form'),
options=[
('form_once', _('The site has a single authentication page'), 'form_once'),
('form_everywhere', _('The site includes an authentication form in most or all pages'), 'form_everywhere'),
],
sort=False, required = True, delim=htmltext('<br />'), value = self.host.auth_form_places)
form.add_submit('cancel', _('Previous'))
form.add_submit('submit', _('Next'))
return form
def submit_sso_init_link_form(self, form):
fields = [ 'auth_form_places', ]
for f in fields:
setattr(self.host, f, form.get_widget(f).parse())
self.host.auth_form_url = self.host.auth_url
if self.host.auth_form_places == 'form_everywhere' and self.host.auth_form_action:
self.generate_apache_filter()
else:
if hasattr(self.host, 'apache_output_python_filters'):
del self.host.apache_output_python_filters
# Add mod proxy html
# TODO: add an option somewhere to disable it
if not hasattr(self.host, 'apache_output_filters'):
self.host.apache_output_filters = []
if 'proxy-html' not in self.host.apache_output_filters:
self.host.apache_output_filters.append('proxy-html')
self.host.store()
write_apache2_vhosts()
def metadatas [html] (self):
form = Form(enctype='multipart/form-data')
form.add_submit('cancel', _('Previous'))
form.add_submit('submit', _('Next'))
if form.get_widget('cancel').parse():
return redirect('sso_init_link')
if form.is_submitted():
return redirect('check_full_configuration')
html_top('step8', title=_('Step 8 - Configure the site metadatas on your identity provider'))
'<h2>%s</h2>' % _('Step 8 - Configure the site metadatas on your identity provider')
'<p>'
htmltext(_('''Download the metadatas and the public key for this site and
upload them on your identity provider in order to use Liberty Alliance features'''))
'</p>'
'<dl>'
if hasattr(self.host, str('base_url')):
if lasso.SAML2_SUPPORT:
saml2_metadata_url = '%s/metadata.xml' % self.host.saml2_base_url
'<dt><a href="%s">%s</a></dt> <dd>%s</dd>' % (
saml2_metadata_url,
_('Service Provider SAML 2.0 Metadata'),
_('Download Service Provider SAML 2.0 Metadata file'))
metadata_url = '%s/metadata.xml' % self.host.base_url
'<dt><a href="%s">%s</a></dt> <dd>%s</dd>' % (
metadata_url,
_('Service Provider Metadata'),
_('Download Service Provider ID-FF 1.2 Metadata file'))
else:
'<p>%s</p>' % _('No metadata has been generated for this host.')
if hasattr(self.host, str('base_url')) and self.host.public_key and os.path.exists(self.host.public_key):
public_key_url = '%s/public_key' % self.host.base_url
'<dt><a href="%s">%s</a></dt> <dd>%s</dd>' % (
public_key_url,
_('Public key'),
_('Download Service Provider SSL Public Key file'))
else:
'<p>%s</p>' % _('No public key has been generated for this host.')
'</dl>'
form.render()
def check_full_configuration [html] (self):
form = Form(enctype='multipart/form-data')
form.add_submit('cancel', _('Previous'))
form.add_submit('submit', _('Finish'))
if form.get_widget('cancel').parse():
return redirect('metadatas')
if form.is_submitted():
return redirect('../..')
html_top('step9', title=_('Step 9 - Check everything works'))
'<h2>%s</h2>' % _('Step 9 - Check everything works')
'<p>'
htmltext(_('''Now you can fully test your site, start from the home page, initiate a Single Sign On,
federate your identities and do a Single Logout.'''))
'</p>'
'<p>'
htmltext(_('The address of your site is : '))
'<a href="%s">%s</a>' % (self.host.new_url, self.host.new_url)
'</p>'
'<p>'
htmltext(_('''If everything works, click the "%(finish)s" button, otherwise you can go back and
check your settings or <a href=advanced_options>configure some advanced options</a>.''')) % { 'finish': _('Finish') }
'</p>'
form.render()
def advanced_options [html] (self):
form = self.form_advanced_options()
if form.get_widget('cancel').parse():
return redirect('check_full_configuration')
if not form.is_submitted() or form.has_errors():
get_response().breadcrumb.append( ('advanced_options', _('Advanced options')) )
html_top('hosts', title = _('Advanced options'))
'<h2>%s</h2>' % _('Advanced options')
form.render()
else:
self.submit_advanced_options_form(form)
return redirect('check_full_configuration')
def form_advanced_options(self):
form = Form(enctype='multipart/form-data')
form.add(UrlOrAbsPathWidget, 'initiate_sso_url', title = _('URL which must initiate the SSO'),
hint = _('''Address which must initiate the SSO. If empty, defaults to the previously
specified "%s"''') % _('Authentication form page address'),
required = False, size = 50, value = self.host.initiate_sso_url)
form.add(CheckboxWidget, 'redirect_root_to_login',
title=_('Redirect the root URL of the site to the login page.'),
value = self.host.redirect_root_to_login)
form.add(UrlOrAbsPathWidget, 'return_url', title = _('Return address'),
hint = _('Where the user will be redirected after a successful authentication'),
required = False, size = 50, value = self.host.return_url)
form.add(UrlOrAbsPathWidget, 'root_url', title = _('Error address'),
hint = _('Where the user will be redirected after a disconnection or an error'),
required = False, size = 50, value = self.host.root_url)
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
return form
def submit_advanced_options_form(self, form):
old_redirect_root_to_login = self.host.redirect_root_to_login
for f in ('initiate_sso_url', 'redirect_root_to_login', 'return_url', 'root_url'):
value = form.get_widget(f).parse()
setattr(self.host, f, value)
self.host.store()
if self.host.initiate_sso_url or self.host.redirect_root_to_login is not old_redirect_root_to_login:
write_apache2_vhosts()
def auto_detect_configuration(self):
# """Guess other SP parameters"""
# if self.host.auth_url is not None:
# # Separate auth page
# self.host.auth_form_url = self.host.auth_url
# else:
# if self.host.auth_form_page_url is not None:
# # Auth form is not on index page
# self.host.auth_form_url = self.host.auth_form_page_url
# else:
# # Auth form is on index page
# self.host.auth_form_url = self.host.orig_site
# Reset previous detected values
self.host.auth_form = None
self.host.auth_check_url = None
self.host.login_field_name = None
self.host.password_field_name = None
if not self.host.post_parameters:
self.host.post_parameters = {}
self.parse_page(self.host.auth_form_url)
def parse_page(self, page_url):
# Get the authentication page
try:
response, status, page, auth_header = http_get_page(page_url, use_proxy=self.host.use_proxy)
except Exception, msg:
print msg
return
# Check if this site uses HTTP authentication
# if status == 401:
# if auth_header.startswith('Basic'):
# self.host.auth_mode = 'http_basic'
# else:
# self.host.auth_mode = 'unsupported'
# self.host.store()
# return
if page is None:
return
#raise FormError, ('auth_check_url', '%s : %s' % (_('Failed to get page'), self.host.auth_form_url))
# Default authentication mode
self.host.auth_mode = 'form'
self.host.site_authentication_plugin = site_authentication.guess_site_authentication_class(page)
self.parse_frames(page)
self.parse_forms(page)
if self.host.auth_form is not None:
self.parse_form_action()
input_fields = self.parse_input_fields()
self.parse_login_field(input_fields)
self.parse_password_field(input_fields)
self.parse_select_fields(input_fields)
self.parse_other_fields(input_fields)
def parse_frames(self, page):
'''If there are frames, parse them recursively'''
regexp = re.compile("""<frame.*?src=["'](.*?)["'][^>]*?>""", re.DOTALL | re.IGNORECASE)
found_frames = regexp.findall(page)
if found_frames:
for frame_url in found_frames:
if frame_url.startswith('http'):
frame_full_url = frame_url
else:
page_url_tokens = page_url.split('/')
page_url_tokens[-1] = frame_url
frame_full_url = '/'.join(page_url_tokens)
self.parse_page(frame_full_url)
def parse_forms(self, page):
'''Search for an authentication form'''
# Get all forms
regexp = re.compile("""<form.*?</form>""", re.DOTALL | re.IGNORECASE)
found_forms = regexp.findall(page)
if not found_forms:
return
#raise FormError, ('auth_check_url', '%s : %s' % (_('Failed to find any form'), self.host.auth_form_url))
# Get the first form with a password field
for found_form in found_forms:
regexp = re.compile("""<input[^>]*?type=["']?password["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
if regexp.search(found_form) is not None:
self.host.auth_form = found_form
break
def parse_form_action(self):
'''Get the action url of the form'''
regexp = re.compile("""<form.*?action=["']?(.*?)["']?[\s>].*?>""", re.DOTALL | re.IGNORECASE)
self.host.auth_form_action = regexp.findall(self.host.auth_form)[0]
# FIXME: Find a Python module which unescapes html entities
self.host.auth_check_url = self.host.auth_form_action.replace('&amp;', '&')
if not self.host.auth_check_url.startswith('http'):
if self.host.auth_check_url.startswith('/'):
if self.host.orig_site.startswith('https'):
orig_site_root = 'https://%s' % urllib.splithost(self.host.orig_site[6:])[0]
else:
orig_site_root = 'http://%s' % urllib.splithost(self.host.orig_site[5:])[0]
self.host.auth_check_url = orig_site_root + self.host.auth_check_url
else:
auth_form_url_tokens = self.host.auth_form_url.split('/')
auth_form_url_tokens[-1] = self.host.auth_check_url
self.host.auth_check_url = '/'.join(auth_form_url_tokens)
def parse_input_fields(self):
'''Get all input fields'''
regexp = re.compile("""<input[^>]*?>""", re.DOTALL | re.IGNORECASE)
return regexp.findall(self.host.auth_form)
def parse_login_field(self, input_fields):
'''Get login field name'''
try:
regexp = re.compile("""<input[^>]*?type=["']?text["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
text_fields = regexp.findall(self.host.auth_form)
login_field = ''
if text_fields:
login_field = text_fields[0]
else:
for field in input_fields:
if re.search("""type=["']?""", field, re.DOTALL | re.IGNORECASE) is None:
login_field = field
break
regexp = re.compile("""name=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
self.host.login_field_name = regexp.findall(login_field)[0]
if not self.host.post_parameters.has_key(self.host.login_field_name):
self.host.post_parameters[self.host.login_field_name] = \
{ 'enabled': True, 'value': _('(filled by users)'), 'immutable': True }
self.host.store()
except IndexError, e:
self.host.login_field_name = None
print 'Error handling login field : %s' % e
def parse_password_field(self, input_fields):
'''Get password field name'''
try:
regexp = re.compile("""<input[^>]*?type=["']?password["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
password_field = regexp.findall(self.host.auth_form)[0]
regexp = re.compile("""name=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
self.host.password_field_name = regexp.findall(password_field)[0]
if not self.host.post_parameters.has_key(self.host.password_field_name):
self.host.post_parameters[self.host.password_field_name] = \
{ 'enabled': True, 'value': _('(filled by users)'), 'immutable': True }
except IndexError, e:
self.host.password_field_name = None
print 'Error handling password field : %s' % e
def parse_select_fields(self, input_fields):
'''Add select fields to host attributes'''
# First added for Imuse (Rennes)
regexp = re.compile("""<select.*?</select>""", re.DOTALL | re.IGNORECASE)
self.host.select_fields = {}
for field in regexp.findall(self.host.auth_form):
try:
regexp = re.compile("""name=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
name = regexp.findall(field)[0]
regexp = re.compile("""<option[^>]*?>.*?</option>""", re.DOTALL | re.IGNORECASE)
options = regexp.findall(field)
values = []
for option in options:
regexp = re.compile("""<option[^>]*?>(.*?)</option>""", re.DOTALL | re.IGNORECASE)
option_label = regexp.findall(option)
regexp = re.compile("""value=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
option_value = regexp.findall(option)
if option_label:
if not option_value:
option_value = option_label
values.append((option_value[0], option_label[0]))
else:
print >> sys.stderr, 'W: Could not parse select options'
self.host.select_fields[name] = values
if not self.host.post_parameters.has_key(name):
self.host.post_parameters[name] = \
{ 'enabled': True, 'value': _('(filled by users)'), 'immutable': True }
except IndexError, e:
continue
def parse_other_fields(self, input_fields):
'''Get the default value of all other fields'''
self.host.other_fields = {}
# Get hidden fields
regexp = re.compile("""<input[^>]*?type=["']?hidden["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
other_fields = regexp.findall(self.host.auth_form)
# Only get first submit field
regexp = re.compile("""<input[^>]*?type=["']?submit["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
found = regexp.findall(self.host.auth_form)
if found:
if other_fields:
other_fields.append(found[0])
else:
other_fields = found[0]
for field in other_fields:
try:
regexp = re.compile("""name=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
name = regexp.findall(field)[0]
regexp = re.compile("""value=["'](.*?)["'][\s/>]""", re.DOTALL | re.IGNORECASE)
value = regexp.findall(field)[0]
self.host.other_fields[name] = value
if not self.host.post_parameters.has_key(name):
self.host.post_parameters[name] = { 'enabled': True, 'value': value, 'immutable': False }
except IndexError, e:
continue
class HostUI:
def __init__(self, host):
self.host = host
def form_edit(self):
# FIXME : homogeneise the size of the fields
form = Form(enctype='multipart/form-data')
form.add(StringWidget, 'label', title = _('Site name'), required = True,
size = 30, value = self.host.label)
form.add(UrlWidget, 'orig_site', title = _('Original site root address'), required = True,
size = 50, value = self.host.orig_site)
form.add(ValidUrlWidget, 'auth_url', title = _('Authentication page'),
hint = _('If there is a separate authentication page'),
required = False, size = 70, value = self.host.auth_url)
form.add(ValidUrlWidget, 'auth_form_page_url',
title = _('Authentication form page'),
hint = _('If the authentication form is not in a separate page and not in the index page either'),
required = False, size = 70, value = self.host.auth_form_page_url)
form.add(ValidUrlWidget, 'logout_url', title = _('Logout address'), required = False,
size = 70, value = self.host.logout_url)
form.add(StringWidget, 'reversed_hostname', title = _('Reversed host name'),
size = 30, required = True, value = self.host.reversed_hostname)
form.add(StringWidget, 'reversed_directory', title = _('Reversed directory'),
size = 30, required = False, value = self.host.reversed_directory)
form.add(CheckboxWidget, 'use_ssl', title = _('Use SSL'),
hint = _('This only affects the connection between the browser and Larpe'),
value = self.host.use_ssl)
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
return form
def submit_edit_form(self, form):
metadata_cfg = {}
for f in ('label', 'orig_site', 'auth_url', 'auth_form_page_url', 'logout_url', 'reversed_hostname',
'reversed_directory', 'use_ssl'):
widget = form.get_widget(f)
setattr(self.host, f, widget.parse())
# Get the special use_proxy attribute if it exists
if hasattr(widget, 'use_proxy'):
self.host.use_proxy = widget.use_proxy
self.host.organization_name = self.host.label
metadata_cfg['organization_name'] = self.host.organization_name
# Build host name from host label
self.host.name = self.host.label.lower()
invalid_characters = [' ', "'"]
for char in invalid_characters:
self.host.name = self.host.name.replace(char, '_')
# Set url scheme (ie protocol) according to SSL usage
if self.host.use_ssl:
self.host.scheme = 'https'
else:
self.host.scheme = 'http'
# Liberty Alliance / SAML parameters
base_url = '%s://%s%s/liberty/%s/liberty' % (self.host.scheme,
self.host.reversed_hostname,
get_request().environ['SCRIPT_NAME'],
self.host.name)
metadata_cfg['base_url'] = base_url
self.host.base_url = base_url
if lasso.SAML2_SUPPORT:
saml2_base_url = '%s://%s%s/liberty/%s/saml' % (self.host.scheme,
self.host.reversed_hostname,
get_request().environ['SCRIPT_NAME'],
self.host.name)
metadata_cfg['saml2_base_url'] = saml2_base_url
self.host.saml2_base_url = saml2_base_url
provider_id = '%s/metadata' % base_url
metadata_cfg['provider_id'] = provider_id
self.host.provider_id = provider_id
if lasso.SAML2_SUPPORT:
saml2_provider_id = '%s/metadata' % saml2_base_url
metadata_cfg['saml2_provider_id'] = saml2_provider_id
self.host.saml2_provider_id = saml2_provider_id
# Storage directories
if self.host.reversed_directory is None:
reversed_dir = 'default'
else:
reversed_dir = self.host.reversed_directory
site_dir = os.path.join(get_publisher().app_dir, 'sp',
self.host.reversed_hostname, reversed_dir)
user_dir = os.path.join(site_dir, 'users')
token_dir = os.path.join(site_dir, 'tokens')
for dir in (site_dir, user_dir, token_dir):
if not os.path.isdir(dir):
os.makedirs(dir)
metadata_cfg['site_dir'] = site_dir
self.host.site_dir = site_dir
# Tweaking for larpe vhosts
hostname_dir = get_abs_path(os.path.join('..', self.host.reversed_hostname))
if not os.path.exists(hostname_dir):
os.mkdir(hostname_dir)
# Load the configuration from the main directory
get_publisher().reload_cfg()
get_publisher().write_cfg(hostname_dir)
# Generate SSL keys
private_key_path = os.path.join(site_dir, 'private_key.pem')
public_key_path = os.path.join(site_dir, 'public_key.pem')
if not os.path.isfile(private_key_path) or not os.path.isfile(public_key_path):
set_provider_keys(private_key_path, public_key_path)
self.host.private_key = private_key_path
self.host.public_key = public_key_path
# Read public key
public_key = ''
if self.host.public_key is not None and os.path.exists(self.host.public_key):
metadata_cfg['signing_public_key'] = open(self.host.public_key).read()
# Write metadatas
metadata_path = os.path.join(site_dir, 'metadata.xml')
open(metadata_path, 'w').write(get_metadata(metadata_cfg))
self.host.metadata = metadata_path
if lasso.SAML2_SUPPORT:
saml2_metadata_path = os.path.join(site_dir, 'saml2_metadata.xml')
open(saml2_metadata_path, 'w').write(get_saml2_metadata(metadata_cfg))
self.host.saml2_metadata = saml2_metadata_path
# Use default idps
# idp_dir = os.path.join(get_publisher().app_dir, 'idp')
# self.host.idps = os.listdir(idp_dir)
self.host.store()
for attr in ('auth_check_url', 'login_field_name', 'password_field_name'):
if not hasattr(self.host, attr) or not getattr(self.host, attr):
self.auto_detect_configuration()
break
write_apache2_vhosts()
def auto_detect_configuration(self):
"""Guess other SP parameters"""
if self.host.auth_url is not None:
# Separate auth page
self.host.auth_form_url = self.host.auth_url
else:
if self.host.auth_form_page_url is not None:
# Auth form is not on index page
self.host.auth_form_url = self.host.auth_form_page_url
else:
# Auth form is on index page
self.host.auth_form_url = self.host.orig_site
# Reset previous detected values
self.host.auth_form = None
self.host.auth_check_url = None
self.host.login_field_name = None
self.host.password_field_name = None
self.host.store()
self.parse_page(self.host.auth_form_url)
def parse_page(self, page_url):
# Get the authentication page
try:
response, status, page, auth_header = http_get_page(page_url, use_proxy=self.host.use_proxy)
except Exception, msg:
print msg
return
# Check if this site uses HTTP authentication
if status == 401:
if auth_header.startswith('Basic'):
self.host.auth_mode = 'http_basic'
else:
self.host.auth_mode = 'unsupported'
self.host.store()
return
if page is None:
return
#raise FormError, ('auth_check_url', '%s : %s' % (_('Failed to get page'), self.host.auth_form_url))
self.host.site_authentication_plugin = site_authentication.guess_site_authentication_class(page)
# If there are frames, parse them recursively
regexp = re.compile("""<frame.*?src=["'](.*?)["'][^>]*?>""", re.DOTALL | re.IGNORECASE)
found_frames = regexp.findall(page)
if found_frames:
for frame_url in found_frames:
if frame_url.startswith('http'):
frame_full_url = frame_url
else:
page_url_tokens = page_url.split('/')
page_url_tokens[-1] = frame_url
frame_full_url = '/'.join(page_url_tokens)
self.parse_page(frame_full_url)
# Default authentication mode
self.host.auth_mode = 'form'
# Get all forms
regexp = re.compile("""<form.*?</form>""", re.DOTALL | re.IGNORECASE)
found_forms = regexp.findall(page)
if not found_forms:
return
#raise FormError, ('auth_check_url', '%s : %s' % (_('Failed to find any form'), self.host.auth_form_url))
# Get the first form with a password field
found = False
for found_form in found_forms:
regexp = re.compile("""<input[^>]*?type=["']?password["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
if regexp.search(found_form) is not None:
self.host.auth_form = found_form
found = True
break
if not found:
return
#raise FormError, ('auth_check_url', _('Failed to find the authentication form'))
# If we found a form, search for all needed information
# Get the action url of the form
regexp = re.compile("""<form.*?action=["']?(.*?)["']?[\s>].*?>""", re.DOTALL | re.IGNORECASE)
self.host.auth_check_url = regexp.findall(self.host.auth_form)[0]
# FIXME : Find a module which unescapes html entities
self.host.auth_check_url = self.host.auth_check_url.replace('&amp;', '&')
if not self.host.auth_check_url.startswith('http'):
if self.host.auth_check_url.startswith('/'):
if self.host.orig_site.startswith('https'):
orig_site_root = 'https://%s' % urllib.splithost(self.host.orig_site[6:])[0]
else:
orig_site_root = 'http://%s' % urllib.splithost(self.host.orig_site[5:])[0]
self.host.auth_check_url = orig_site_root + self.host.auth_check_url
else:
auth_form_url_tokens = self.host.auth_form_url.split('/')
auth_form_url_tokens[-1] = self.host.auth_check_url
self.host.auth_check_url = '/'.join(auth_form_url_tokens)
# Get all inputs
regexp = re.compile("""<input[^>]*?>""", re.DOTALL | re.IGNORECASE)
inputs = regexp.findall(self.host.auth_form)
# Get login field name
try:
regexp = re.compile("""<input[^>]*?type=["']?text["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
text_fields = regexp.findall(self.host.auth_form)
login_field = ''
if text_fields:
login_field = text_fields[0]
else:
for field in inputs:
if re.search("""type=["']?""", field, re.DOTALL | re.IGNORECASE) is None:
login_field = field
break
regexp = re.compile("""name=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
self.host.login_field_name = regexp.findall(login_field)[0]
except IndexError, e:
self.host.login_field_name = None
print 'Error handling login field : %s' % e
# Get password field name
try:
regexp = re.compile("""<input[^>]*?type=["']?password["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
password_field = regexp.findall(self.host.auth_form)[0]
regexp = re.compile("""name=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
self.host.password_field_name = regexp.findall(password_field)[0]
except IndexError, e:
self.host.password_field_name = None
print 'Error handling password field : %s' % e
# Get the default value of all other fields
self.host.other_fields = {}
# Get hidden and submit fields
regexp = re.compile("""<input[^>]*?type=["']?(?:hidden|submit)["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
other_fields = regexp.findall(self.host.auth_form)
for field in other_fields:
try:
regexp = re.compile("""name=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
name = regexp.findall(field)[0]
regexp = re.compile("""value=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
value = regexp.findall(field)[0]
self.host.other_fields[name] = value
except IndexError, e:
continue
# Add select fields to host attributes
# First added for Imuse (Rennes)
regexp = re.compile("""<select.*?</select>""", re.DOTALL | re.IGNORECASE)
self.host.select_fields = {}
for field in regexp.findall(page):
try:
regexp = re.compile("""name=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
name = regexp.findall(field)[0]
regexp = re.compile("""<option[^>]*?>.*?</option>""", re.DOTALL | re.IGNORECASE)
options = regexp.findall(field)
values = []
for option in options:
regexp = re.compile("""value=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
option_value = regexp.findall(option)
regexp = re.compile("""<option[^>]*?>(.*?)</option>""", re.DOTALL | re.IGNORECASE)
option_label = regexp.findall(option)
if option_value and option_label:
values.append((option_value[0], option_label[0]))
self.host.select_fields[name] = values
except IndexError, e:
continue
# print 'action : %s' % self.host.auth_check_url
# print 'login field : %s' % self.host.login_field_name
# print 'password field : %s' % self.host.password_field_name
# print 'other fields : %s' % self.host.other_fields
self.host.store()
def form_auto_detected_configuration(self):
form = Form(enctype='multipart/form-data')
form.add(RadiobuttonsWidget, 'auth_mode', title = _('Authentication mode'),
options=[
('form', _('Form'), 'form'),
('http_basic', _('HTTP Basic'), 'http_basic'),
],
sort=False,
required = True,
value = self.host.auth_mode)
form.add(ValidUrlWidget, 'auth_check_url', title = _('Address where the authentication form must be sent'),
required = False, size = 70, value = self.host.auth_check_url)
form.add(StringWidget, 'login_field_name', title = _('Name of the login field'),
required = False, size = 30, value = self.host.login_field_name)
form.add(StringWidget, 'password_field_name', title = _('Name of the password field'),
required = False, size = 30, value = self.host.password_field_name)
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
return form
def submit_auto_detected_configuration_form(self, form):
for f in ('auth_mode', 'auth_check_url', 'login_field_name', 'password_field_name'):
value = form.get_widget(f).parse()
setattr(self.host, f, value)
# Ensure auth_check_url is a full url
if 'auth_mode' == 'form':
if not self.host.auth_check_url.startswith('http'):
if self.host.auth_check_url.startswith('/'):
if self.host.orig_site.endswith('/'):
self.host.auth_check_url = self.host.orig_site + self.host.auth_check_url[1:]
else:
self.host.auth_check_url = self.host.orig_site + self.host.auth_check_url
else:
auth_form_url_tokens = self.host.auth_form_url.split('/')
auth_form_url_tokens[-1] = self.host.auth_check_url
self.host.auth_check_url = '/'.join(auth_form_url_tokens)
self.host.store()
def form_advanced_configuration(self):
form = Form(enctype='multipart/form-data')
form.add(UrlOrAbsPathWidget, 'return_url', title = _('Return address'),
hint = _('Where the user will be redirected after a successful authentication'),
required = False, size = 50, value = self.host.return_url)
form.add(UrlOrAbsPathWidget, 'root_url', title = _('Error address'),
hint = _('Where the user will be redirected after a disconnection or an error'),
required = False, size = 50, value = self.host.root_url)
form.add(CheckboxWidget, 'redirect_root_to_login',
title=_('Redirect the root url of the site to the login page.'),
value = self.host.redirect_root_to_login)
form.add(CheckboxWidget, 'send_hidden_fields', title=_('Send authentication form hidden fields'),
value = self.host.send_hidden_fields)
form.add(RadiobuttonsWidget, 'auth_system', title = _('Authentication system of the original site'),
options=[
('password', _('Check the existence of a password field'), 'password'),
('match_text', _('Match some text to detect an authentication failure'), 'match_text'),
],
sort=False,
delim=htmltext('<br />'),
value = self.host.auth_system)
form.add(RegexStringWidget, 'auth_match_text', title = _('Text to match in case of authentication failure'),
required = False, size = 50, value = self.host.auth_match_text)
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
return form
def submit_advanced_configuration_form(self, form):
old_redirect_root_to_login = self.host.redirect_root_to_login
for f in ('return_url', 'root_url', 'redirect_root_to_login', 'send_hidden_fields', 'auth_system', 'auth_match_text'):
value = form.get_widget(f).parse()
setattr(self.host, f, value)
self.host.store()
if self.host.redirect_root_to_login is not old_redirect_root_to_login:
write_apache2_vhosts()
def form_apache_filters(self):
form = Form(enctype='multipart/form-data')
if not hasattr(self.host, 'apache_output_filters'):
self.host.apache_output_filters = [ 'proxy-html' ]
self.host.store()
form.add(CheckboxWidget, 'proxy-html', title = _('HTML proxy'),
hint = _('Converts urls in the html pages according to the host new domain name'),
value = 'proxy-html' in self.host.apache_output_filters)
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
return form
def submit_apache_filters_form(self, form):
f = 'proxy-html'
value = form.get_widget(f).parse()
if value is True and f not in self.host.apache_output_filters:
self.host.apache_output_filters.append(f)
if value is False and f in self.host.apache_output_filters:
self.host.apache_output_filters.remove(f)
self.host.store()
write_apache2_vhosts()
class HostPage(Directory):
_q_exports = ['', 'minimal_configuration', 'auto_detect_configuration', 'auto_detected_configuration', 'advanced_configuration', 'apache_filters', 'see_current_configuration', 'delete']
def __init__(self, host_id):
self.host = Host.get(host_id)
self.host_ui = HostUI(self.host)
get_response().breadcrumb.append((host_id + '/', self.host.label))
def _q_lookup(self, component):
if component == 'configuration_assistant':
return ConfigurationAssistant(self.host)
elif component == 'forms_prefill':
return FormsDirectory(self.host)
def _q_index [html] (self):
get_publisher().reload_cfg()
html_top('hosts', title = self.host.label)
'<h2>%s</h2>' % _('Reverse Proxy')
'<dl>'
'<dt><a href="minimal_configuration">%s</a></dt> <dd>%s</dd>' % (
_('Minimal Configuration'), _('Configure the minimum parameters to set up a reverse proxy for this site'))
'<dt><a href="auto_detected_configuration">%s</a></dt> <dd>%s</dd>' % (
_('Auto Detected Configuration'), _('Check the auto detected parameters and change them if necessary'))
'<dt><a href="advanced_configuration">%s</a></dt> <dd>%s</dd>' % (
_('Advanced Configuration'), _("Configure advanced parameters if it doesn't work with minimal configuration"))
'<dt><a href="apache_filters">%s</a></dt> <dd>%s</dd>' % (
_('Apache filters'), _('Select what apache filters to use'))
'<dt><a href="see_current_configuration">%s</a></dt> <dd>%s</dd>' % (
_('See Current Configuration'), _('See the current configuration of this host'))
'</dl>'
if lasso.SAML2_SUPPORT:
'<h2>Liberty Alliance & SAML 2.0</h2>'
else:
'<h2>Liberty Alliance</h2>'
'<dl>'
if hasattr(self.host, str('base_url')):
if lasso.SAML2_SUPPORT:
saml2_metadata_url = '%s/metadata.xml' % self.host.saml2_base_url
'<dt><a href="%s">%s</a></dt> <dd>%s</dd>' % (
saml2_metadata_url,
_('Service Provider SAML 2.0 Metadata'),
_('Download Service Provider SAML 2.0 Metadata file'))
metadata_url = '%s/metadata.xml' % self.host.base_url
'<dt><a href="%s">%s</a></dt> <dd>%s</dd>' % (
metadata_url,
_('Service Provider ID-FF 1.2 Metadata'),
_('Download Service Provider ID-FF 1.2 Metadata file'))
else:
'<p>%s</p>' % _('No metadata has been generated for this host.')
if hasattr(self.host, str('base_url')) and self.host.public_key and os.path.exists(self.host.public_key):
public_key_url = '%s/public_key' % self.host.base_url
'<dt><a href="%s">%s</a></dt> <dd>%s</dd>' % (
public_key_url,
_('Public key'),
_('Download Service Provider SSL Public Key file'))
else:
'<p>%s</p>' % _('No public key has been generated for this host.')
'</dl>'
'<h2>%s</h2>' % _('Form prefilling with ID-WSF')
'<dl>'
'<dt><a href="forms_prefill/">%s</a></dt> <dd>%s</dd>' % (
_('Forms'), _('Configure the forms to prefill'))
'</dl>'
def minimal_configuration [html] (self):
form = self.host_ui.form_edit()
if form.get_widget('cancel').parse():
return redirect('.')
if form.is_submitted() and not form.has_errors():
check_minimal_configuration(form)
if form.is_submitted() and not form.has_errors():
self.host_ui.submit_edit_form(form)
return redirect('see_current_configuration')
get_response().breadcrumb.append( ('minimal_configuration', _('Edit')) )
html_top('hosts', title = _('Edit Host'))
'<h2>%s</h2>' % _('Edit Host')
form.render()
def auto_detect_configuration (self):
self.host_ui.auto_detect_configuration()
return redirect('auto_detected_configuration')
def auto_detected_configuration [html] (self, detect=False):
form = self.host_ui.form_auto_detected_configuration()
if form.get_widget('cancel').parse():
return redirect('.')
if not form.is_submitted() or form.has_errors():
get_response().breadcrumb.append( ('auto_detected_configuration', _('Auto Detected Configuration')) )
html_top('hosts', title = _('Auto Detected Configuration'))
'<h2>%s</h2>' % _('Auto Detected Configuration')
'<div><a href="auto_detect_configuration"><input type="button" value="%s" /></a> %s </div><br />' \
% (_('Auto detect'), _('Warning, this will erase your custom modifications !'))
form.render()
else:
self.host_ui.submit_auto_detected_configuration_form(form)
return redirect('see_current_configuration')
def advanced_configuration [html] (self):
form = self.host_ui.form_advanced_configuration()
if form.get_widget('cancel').parse():
return redirect('.')
if not form.is_submitted() or form.has_errors():
get_response().breadcrumb.append( ('advanced_configuration', _('Advanced Configuration')) )
html_top('hosts', title = _('Advanced Configuration'))
'<h2>%s</h2>' % _('Advanced Configuration')
form.render()
else:
self.host_ui.submit_advanced_configuration_form(form)
return redirect('see_current_configuration')
def apache_filters [html] (self):
form = self.host_ui.form_apache_filters()
if form.get_widget('cancel').parse():
return redirect('.')
if not form.is_submitted() or form.has_errors():
get_response().breadcrumb.append( ('apache_filters', _('Apache filters')) )
html_top('hosts', title = _('Apache filters'))
'<h2>%s</h2>' % _('Apache filters')
form.render()
else:
self.host_ui.submit_apache_filters_form(form)
return redirect('see_current_configuration')
def see_current_configuration [html] (self):
get_response().breadcrumb.append( ('see_current_configuration', _('See Current Configuration')) )
html_top('hosts', title = _('Current Host Configuration'))
'<h2>%s</h2>' % _('Current Host Configuration')
for attr in ('auth_check_url', 'login_field_name', 'password_field_name'):
if not getattr(self.host, str(attr)):
'<div class="errornotice">%s</div>' % _("This site is not fully configured yet. \
The following red fields don't have a correct value.")
break
# New url for this host
url = '%s://%s%s/' % (self.host.scheme, self.host.reversed_hostname, get_request().environ['SCRIPT_NAME'])
if self.host.reversed_directory is not None:
url += '%s/' % self.host.reversed_directory
'<div style="margin-bottom: 0.1em; font-weight: bold;">%s</div>' % _('New url for this host')
'<div style="margin-left: 1em; margin-bottom: 0.3em;"><a href="%s">%s</a></div>' % (url, url)
get_publisher().reload_cfg()
if get_cfg('use_proxy'):
'<div style="margin-bottom: 0.1em; font-weight: bold;">%s</div>' % _('Use proxy')
'<div style="margin-left: 1em; margin-bottom: 0.3em;">%s</div>' % self.host.use_proxy
host_attrs_minimal = (
('label', _('Site name')),
('orig_site', _('Original site root address')),
('auth_url', _('Authentication page')),
('auth_form_page_url', _('Authentication form page')),
('logout_url', _('Logout address')),
('reversed_hostname', _('Reversed host name')),
('reversed_directory', _('Reversed directory')),
('use_ssl', _('Use SSL'))
)
host_attrs_auto = (
('auth_mode', _('Authentication mode')),
('auth_check_url', _('Address where the authentication form must be sent')),
('login_field_name', _('Name of the login field')),
('password_field_name', _('Name of the password field')),
('site_authentication_plugin', _('Plugin used for site specific authentication behaviour'))
)
host_attrs_advanced = (
('return_url', _('Return address')),
('root_url', _('Root address')),
('redirect_root_to_login', _('Redirect the root url of the site to the login page.')),
('auth_system', _('Authentication system of the original site')),
('auth_match_text', _('Text to match in case of authentication failure')),
('send_hidden_fields', _('Send authentication form hidden fields'))
)
host_menus = (
(host_attrs_minimal, '<a href="minimal_configuration">%s</a>' % _('Minimal Configuration')),
(host_attrs_auto, '<a href="auto_detected_configuration">%s</a>' % _('Auto Detected Configuration')),
(host_attrs_advanced, '<a href="advanced_configuration">%s</a>' % _('Advanced Configuration')),
)
for host_attrs, category in host_menus:
'<h3>%s</h3>' % category
for attr, name in host_attrs:
color = 'black'
if attr in ('auth_check_url', 'login_field_name', 'password_field_name') and \
not getattr(self.host, str(attr)):
color = 'red'
'<div style="margin-bottom: 0.1em; font-weight: bold; color: %s;">%s</div>' % (color, name)
'<div style="margin-left: 1em; margin-bottom: 0.3em; color: %s;">%s</div>' % \
(color, getattr(self.host, str(attr)))
if getattr(self.host, str(attr)) == '':
'<br />'
'<div class="buttons"><a href="."><input type="button" value="%s" /></a></div><br />' % _('Back')
def delete [html] (self):
form = Form(enctype='multipart/form-data')
form.widgets.append(HtmlWidget('<p>%s</p>' % _(
'You are about to irrevocably delete this host.')))
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
if form.get_widget('cancel').parse():
return redirect('..')
if not form.is_submitted() or form.has_errors():
get_response().breadcrumb.append(('delete', _('Delete')))
html_top('hosts', title = _('Delete Host'))
'<h2>%s : %s</h2>' % (_('Delete Host'), self.host.label)
form.render()
else:
self.host.remove_self()
write_apache2_vhosts()
return redirect('..')
class HostsDirectory(Directory):
_q_exports = ['', 'new']
def _q_index [html] (self):
get_response().breadcrumb.append(('hosts/', _('Hosts')))
html_top('hosts', title = _('Hosts'))
"""<ul id="nav-hosts-admin">
<li><a href="new">%s</a></li>
</ul>""" % _('New Host')
'<ul class="biglist">'
for host in Host.select(lambda x: x.name != 'larpe', order_by = 'label'):
if not host.name:
continue
if not hasattr(host, str('scheme')):
host.scheme = str('http')
'<li>'
'<strong class="label">%s</strong>' % host.label
if hasattr(host, str('new_url')) and host.new_url:
url = host.new_url
else:
# Compat with older Larpe versions
url = '%s://%s%s/' % (host.scheme, host.reversed_hostname, get_request().environ['SCRIPT_NAME'])
if host.reversed_directory is not None:
url += '%s/' % host.reversed_directory
'<br /><a href="%s">%s</a>' % (url, url)
'<p class="commands">'
command_icon('%s/' % host.id, 'edit')
command_icon('%s/delete' % host.id, 'remove')
'</p></li>'
'</ul>'
def new [html] (self):
if not os.path.isdir(os.path.join(get_publisher().app_dir, str('idp'))):
html_top('hosts', title = _('New Host'))
html = '<h2>%s</h2>' % _('New Host')
html += 'You must <a href="%s/admin/settings/liberty_idp/">' % misc.get_root_url()
html += 'configure an Identity Provider</a> first<br /><br />'
html += '<a href="."><input type="button" value="%s" /></a>' % _('Back')
return html
get_response().breadcrumb.append(('hosts/', _('Hosts')))
get_response().breadcrumb.append(('new', _('New')) )
host = Host()
host.store()
# configuration_assistant = ConfigurationAssistant(host)
return redirect('%s/configuration_assistant/start' % host.id)
def _q_lookup(self, component):
get_response().breadcrumb.append(('hosts/', _('Hosts')))
return HostPage(component)