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.
authentic-old/authentic/root.ptl

1348 lines
53 KiB
Plaintext
Raw Normal View History

import re
import os
2005-06-01 22:02:27 +02:00
import random
import string
2006-11-13 12:19:45 +01:00
import urllib
import base64
import Cookie
2005-06-01 22:02:27 +02:00
import lasso
from quixote import get_request, get_response, get_session, get_session_manager, get_publisher, redirect, get_field
from quixote.html import htmltext
from quixote.directory import Directory
2005-05-30 15:20:50 +02:00
from quixote.util import StaticDirectory
from authentic.form import *
2007-12-29 18:04:31 +01:00
from qommon import get_cfg, get_logger
from qommon import errors
2007-03-15 15:19:52 +01:00
from qommon import emails
from qommon.admin.emails import EmailsDirectory
from qommon.admin.texts import TextsDirectory
2007-01-17 16:07:40 +01:00
from qommon import template
from qommon.tokens import Token
import authentic.identities as identities
import authentic.misc as misc
import authentic.liberty.root as liberty_root
import authentic.liberty.saml2 as saml2_root
import authentic.liberty.idwsf2 as idwsf2_root
import authentic.admin.root as root
import admin.configuration as configuration
try:
import cas
except ImportError:
cas = None
import datetime
import login_token
2005-07-11 15:13:02 +02:00
2005-06-01 20:44:32 +02:00
class LoginError(Exception):
pass
2005-06-01 22:02:27 +02:00
class RegistrationError(Exception):
pass
class ForgotPassword(RuntimeError):
pass
2006-11-13 12:19:45 +01:00
class CookieSetterDirectory(Directory):
_q_exports = ['', 'idpintro']
def _q_index [html] (self):
template.html_top()
_('This domain is not for humans, it is only used to set identity '
'provider discovery cookie.')
def idpintro(self):
tok = get_request().form.get('tok')
token = Token.get(tok)
session = get_session_manager().get(token.session_id)
request = get_request()
try:
intro_cookie = request.cookies['_saml_idp']
except KeyError:
intro_cookie = ''
intro_cookie_q = urllib.unquote(intro_cookie)
splitted_cookie = [x for x in intro_cookie_q.split(' ') if x]
2006-12-07 16:12:41 +01:00
succinct_id = base64.encodestring(token.provider_id).strip()
2006-11-13 12:19:45 +01:00
if succinct_id in splitted_cookie:
splitted_cookie.remove(succinct_id)
splitted_cookie.append(succinct_id)
new_cookie = urllib.quote(' '.join(splitted_cookie))
if new_cookie != intro_cookie:
response = get_response()
response.set_cookie('_saml_idp', new_cookie,
domain = '.' + token.common_domain, path = '/',
expires = Cookie._getdate(3*365*86400))
token.remove_self()
return redirect(token.next_url)
class RootDirectory(Directory):
_q_exports = ['', 'admin', 'liberty', 'login', 'logout', 'change_password', 'register',
'forgot_password', 'update_info', 'saml', 'singleLogout',
2009-12-14 12:34:33 +01:00
'federations', 'login_local', 'login_ssl', 'associate_certificate',
'themes', 'disco', 'cas', 'forgot_identifier']
admin = root.RootDirectory()
disco = idwsf2_root.DiscoveryServiceDirectory()
saml = saml2_root.RootDirectory()
liberty = liberty_root.RootDirectory()
2009-12-14 12:34:33 +01:00
themes = template.ThemesDirectory()
if cas:
cas = cas.CASDirectory()
def _q_traverse(self, path):
if get_request().environ.get('HTTPS') and not get_request().session.ssl and get_request().session.id:
get_session_manager().expire_session()
get_request().session = get_session_manager().get_session()
2006-11-13 12:19:45 +01:00
fn = os.path.join(get_publisher().app_dir, 'common_cookie')
if os.path.exists(fn):
# on special domain to set cookie, nothing else, let's change root
get_publisher().app_dir = open(fn).read()
return CookieSetterDirectory()._q_traverse(path)
session = get_session()
if session:
get_request().user = session.get_user()
else:
get_request().user = None
response = get_response()
response.filter = {}
if not hasattr(response, 'breadcrumb'):
response.breadcrumb = [ ('', _('Home')) ]
2010-04-19 18:32:00 +02:00
return Directory._q_traverse(self, path)
def _q_lookup(self, component):
2009-09-07 11:44:06 +02:00
if component in ('css','images'):
return StaticDirectory(os.path.join(get_publisher().data_dir, 'web', component), follow_symlinks = True)
if component == 'qo':
dirname = os.path.join(get_publisher().data_dir, 'qommon')
return StaticDirectory(dirname, follow_symlinks = True)
if get_publisher().WEBROOT_DIR:
dirname = os.path.abspath(os.path.join(get_publisher().WEBROOT_DIR, component))
if os.path.exists(dirname):
return StaticDirectory(dirname, follow_symlinks = True)
raise errors.TraversalError()
def _q_index [html] (self):
session = get_session()
if not session or not session.user:
return self.login()
try:
2008-02-26 17:40:20 +01:00
identities.get_store().load_identities()
except identities.IdentityStoreException:
return template.error_page(_('Failed to connect to identities storage.'))
2008-02-26 17:40:20 +01:00
identities.get_store().connect(session)
try:
2008-02-26 17:40:20 +01:00
identity = identities.get_store().get_identity(session.user)
except KeyError:
# identity no longer available; perhaps identity store changed ?
return self.logout()
alternate_homepage_url = configuration.get_configuration(str('homepage')).get(str('alternate_homepage_url'))
if alternate_homepage_url:
return redirect(alternate_homepage_url)
identities_cfg = get_cfg('identities', {})
branding_cfg = get_cfg('branding', {})
passwords_cfg = get_cfg('passwords', {})
ssl_cfg = get_cfg('ssl', {})
template.html_top(_('Account Management'))
get_response().breadcrumb.append( ('', _('Account Management')) )
allow_certificate_federation = ssl_cfg.get('allow_certificate_federation', False)
vars = {
'can_change_password': str(passwords_cfg.get('can_change', False)),
'creation_mode': identities_cfg.get('creation'),
'identity_label': str(identity),
'idp_sso_list': str(self.get_idp_sso_list()),
'federations_list': str(self.get_idp_federations_list(identity)),
'admin': identity.is_admin(),
'show_federations': get_cfg('idp', {}).get('defederation', False),
}
if allow_certificate_federation:
vars['allow_certificate_federation_url'] = htmltext('https://' + get_request().environ['HTTP_HOST'] + get_request().environ['SCRIPT_NAME'] + '/associate_certificate')
certificates = [ x for x in identity.accounts if isinstance(x, identities.CertificateAccount) ]
certificate_list = '<p>' + _('Certificates federated:') + '\n<ol>\n'
for x in certificates:
certificate_list = certificate_list + '<li>' + htmltext(x.dn or x.certificate_sha1) + '</li>\n'
certificate_list = certificate_list + '</ol>\n</p>\n'
if certificates:
vars['certificate_list'] = certificate_list
return template.process_template(
str(TextsDirectory.get_html_text('account')), vars)
def federations [html] (self):
if not get_cfg('idp', {}).get('defederation', False):
return template.error_page(_('You are not allowed to manipulate your federations'))
session = get_session()
if not session or not session.user:
return redirect(get_request().environ['SCRIPT_NAME'] + '/login')
try:
2008-02-26 17:40:20 +01:00
identities.get_store().load_identities()
except identities.IdentityStoreException:
return template.error_page(_('Failed to connect to identities storage.'))
2008-02-26 17:40:20 +01:00
identities.get_store().connect(session)
try:
2008-02-26 17:40:20 +01:00
identity = identities.get_store().get_identity(session.user)
except KeyError:
# identity no longer available; perhaps identity store changed ?
return self.logout()
identities_cfg = get_cfg('identities', {})
branding_cfg = get_cfg('branding', {})
passwords_cfg = get_cfg('passwords', {})
template.html_top(_('Federations'))
get_response().breadcrumb.append( ('federations', _('Federations')) )
vars = {
'identity_label': str(identity),
'federations_list': str(self.get_idp_federations_list(identity))
}
return template.process_template(
str(TextsDirectory.get_html_text('federations')), vars)
def get_idp_sso_list [html] (self):
if not get_cfg('providers', {}).items():
return ''
'<ul>'
for klp, lp in get_cfg('providers', {}).items():
if lp['role'] == lasso.PROVIDER_ROLE_IDP:
continue # only allows initiated login to service providers
if lp.get('idp_initiated_sso', True) is False:
continue
try:
provider, label = misc.get_provider_and_label(klp)
except KeyError:
continue
if hasattr(provider, str('getProtocolConformance')) and \
provider.getProtocolConformance() == lasso.PROTOCOL_SAML_2_0:
url = 'saml/sp/%s/login' % klp
else:
url = 'liberty/sp/%s/login' % klp
'<li><a href="%s">%s</a></li>' % (
url, htmltext(_('Log on %s') % label))
'</ul>'
def get_idp_federations_list [html] (self, identity):
if not identity.lasso_dump:
return None
'<ul class="FederationsWidget">'
server = misc.get_lasso_server()
lasso_identity = lasso.Identity.newFromDump(identity.lasso_dump)
2008-01-14 13:30:07 +01:00
for k in lasso_identity.providerIds:
klp = misc.get_provider_key(k)
try:
provider, label = misc.get_provider_and_label(klp)
except KeyError:
continue
if hasattr(provider, str('getProtocolConformance')) and \
provider.getProtocolConformance() == lasso.PROTOCOL_SAML_2_0:
url = 'saml/sp/%s/terminate' % klp
else:
url = 'liberty/sp/%s/terminate' % klp
'<li>%s <a href="%s" class="terminate">%s</a></li>' % (
2007-01-10 16:36:12 +01:00
htmltext(label), url, _('Terminate Federation'))
'</ul>'
@misc.protect_form_from_get_parameters
def change_password [html] (self):
pwd_cfg = configuration.get_configuration(str('passwords'))
if not pwd_cfg.get('can_change'):
2005-06-01 22:02:27 +02:00
raise errors.AccessForbiddenError()
session = get_session()
if not session or not session.user:
raise errors.AccessUnauthorizedError()
2008-02-26 17:40:20 +01:00
identities.get_store().load_identities()
identities.get_store().connect(session)
try:
2008-02-26 17:40:20 +01:00
identity = identities.get_store().get_identity(session.user)
except KeyError:
raise errors.AccessError()
for account in identity.accounts:
if hasattr(account, str('password')):
break
else:
return template.error_page(_('No password for this identity'))
2005-08-11 15:21:55 +02:00
pw_account = account
form = Form()
form.keep_referer()
form.add(PasswordWidget, "password", title=_("Current Password"), required=True)
form.add(PasswordWidget, "new_password", title=_("New Password"), required=True)
form.add(PasswordWidget, "new2_password", title=_("New Password (confirm)"), required=True)
form.add_submit("submit", _("Submit"))
form.add_submit("cancel", _("Cancel"))
if get_request().get_method() == 'GET':
get_request().form = {}
2005-09-11 18:19:05 +02:00
if form.get_submit() == 'cancel':
return misc.redirect_to_return_url() or \
misc.redirect_to_referer(form) or \
misc.redirect_home()
if form.is_submitted() and not form.has_errors():
# check current password
account = identities.PasswordAccount()
2008-02-26 17:40:20 +01:00
account.username = identities.get_store().get_identity(session.user).accounts[0].username
account.password = form.get_widget('password').parse()
2008-02-26 17:40:20 +01:00
if not identities.get_store().get_identity_for_account(account):
form.set_error('password', _('Wrong password'))
# check new password is ok
new_password = form.get_widget('new_password').parse()
new2_password = form.get_widget('new2_password').parse()
if new_password != new2_password:
form.set_error('new2_password', _('Passwords do not match'))
else:
min_length = pwd_cfg.get('min_length')
if len(new_password) < min_length:
form.set_error('new_password',
_('Password is too short. It must be at least %d characters.') % min_length)
max_length = pwd_cfg.get('max_length')
if max_length and len(new_password) > max_length:
form.set_error('new_password',
_('Password is too long. It must be at most %d characters.') % max_length)
if new_password == account.password:
form.set_error('new_password', _('New password is the same as the old one'))
if form.is_submitted() and not form.has_errors():
2007-12-29 18:04:31 +01:00
get_logger().info('changed password')
2005-08-11 15:21:55 +02:00
pw_account.password = str(form.get_widget('new_password').parse())
2008-02-26 17:40:20 +01:00
identities.get_store().save(identity)
return misc.redirect_to_return_url() or \
misc.redirect_to_referer(form) or \
misc.redirect_home()
if not form.is_submitted():
2007-12-29 18:04:31 +01:00
get_logger().info('changing password page')
else:
2007-12-29 18:04:31 +01:00
get_logger().info('changing password page (had_errors)')
form.get_widget('new2_password').set_value('')
template.html_top(_('Changing Password'))
get_response().breadcrumb.append( ('', _('Account Management')) )
get_response().breadcrumb.append( ('change_password', _('Changing Password')) )
vars = {
'change_password_form': str(form.render())
}
return template.process_template(
str(TextsDirectory.get_html_text('change_password')), vars)
def get_login_form(self,cancel=False):
identities_cfg = get_cfg('identities', {})
passwords_cfg = get_cfg('passwords', {})
login_cfg = get_cfg('login', {})
ssl_cfg = get_cfg('ssl', {})
form = Form(enctype="multipart/form-data", id = "login", method = 'post', use_tokens = False)
if identities_cfg.get('email-as-username', False):
# keep it as StringWidget since every identity may not be using
# their email address (example: admin account)
form.add(StringWidget, "username", title = _("Email"), size=20, required = True)
else:
form.add(StringWidget, "username", title = _("Username"), size=20, required = True)
form.add(PasswordWidget, "password", title = _("Password"), size=20, required = True)
if get_cfg('idp', {}).get('idff_proxy'):
2005-06-01 20:44:32 +02:00
options = [('', _('None'))]
for klp, lp in get_cfg('providers', {}).items():
if lp['role'] == lasso.PROVIDER_ROLE_SP:
2005-06-01 20:44:32 +02:00
continue
try:
p, label = misc.get_provider_and_label(klp)
except KeyError:
2005-06-01 20:44:32 +02:00
continue
options.append((p.providerId, label))
if len(options) > 1:
form.add(SingleSelectWidget, "idp", title = _('Proxies request to'), options = options)
2005-06-01 20:44:32 +02:00
form.add_submit('submit', _('Log in'))
if ssl_cfg.get('allow_ssl_login', False):
form.add_submit('ssl', _('Log in using SSL certificate'))
if login_cfg.get('cancel_button') and cancel:
form.add_submit('cancel', _('Cancel'))
return form
def login_local(self):
if not get_cfg('idp', {}).get('direct_proxy'):
raise errors.TraversalError()
return self.login(allow_direct = False)
def login [html] (self, allow_direct = True): # only works with PasswordAccount for now
request = get_request()
# Already logged no login token (not a ForceAuthn login)
if request.user is not None and (not get_field('LoginToken')
or login_token.LoginToken.get(get_field('LoginToken'), ignore_errors=True) is None):
return misc.redirect_home()
if allow_direct and get_cfg('idp', {}).get('direct_proxy'):
for klp, lp in get_cfg('providers', {}).items():
if lp['role'] == lasso.PROVIDER_ROLE_SP:
continue
try:
provider, label = misc.get_provider_and_label(klp)
except KeyError:
continue
2007-12-29 18:04:31 +01:00
get_logger().info('login request, direct proxying to %s' % provider.providerId)
if hasattr(provider, str('getProtocolConformance')) and \
provider.getProtocolConformance() == lasso.PROTOCOL_SAML_2_0:
liberty_backend = self.saml
else:
liberty_backend = self.liberty
liberty_backend.perform_proxy_login(provider.providerId,
relay_state = get_field('LoginToken'))
identities_cfg = get_cfg('identities', {})
passwords_cfg = get_cfg('passwords', {})
branding_cfg = get_cfg('branding', {})
ssl_cfg = get_cfg('ssl', {})
cancel=get_request().form.has_key('cancelURL')
save_form=None
if get_request().get_method() == 'GET':
save_form=get_request().form
get_request().form={}
form = self.get_login_form(cancel=cancel)
if form.is_submitted() and form.get_widget('idp') and form.get_widget('idp').parse():
2007-10-18 19:51:26 +02:00
remote_idp = form.get_widget('idp').parse()
2007-12-29 18:04:31 +01:00
get_logger().info('login page, proxying to %s' % remote_idp)
2007-10-18 19:51:26 +02:00
remote_idp_key = misc.get_provider_key(remote_idp)
2007-12-06 19:10:11 +01:00
provider, label = misc.get_provider_and_label(remote_idp_key)
2007-10-18 19:51:26 +02:00
if hasattr(provider, str('getProtocolConformance')) and \
provider.getProtocolConformance() == lasso.PROTOCOL_SAML_2_0:
return self.saml.perform_proxy_login(remote_idp)
2007-10-18 19:51:26 +02:00
else:
return self.liberty.perform_proxy_login(remote_idp)
2005-06-01 20:44:32 +02:00
if form.is_submitted() and form.get_submit() == 'cancel':
2007-12-29 18:04:31 +01:00
get_logger().info('login page, cancel')
return self.login_cancel()
if form.is_submitted() and form.get_submit() == 'ssl' and ssl_cfg.get('allow_ssl_login', False):
return misc.redirect_with_same_qs(str(get_request().environ['SCRIPT_NAME'] + '/login_ssl'))
authentication_failure = None
if form.is_submitted() and not form.has_errors():
2005-06-01 20:44:32 +02:00
try:
return self.login_submit(form)
except LoginError:
authentication_failure = _('Authentication Failure')
2007-12-29 18:04:31 +01:00
get_logger().info('login page (after failed attempt)')
else:
2007-12-29 18:04:31 +01:00
get_logger().info('login page')
template.html_top(_('Login'))
get_response().breadcrumb.append( ('login', _('Login')) )
2005-06-01 20:44:32 +02:00
vars = {
'login_form': str(form.render()),
'authentication_failure': authentication_failure,
'cookies': get_request().cookies,
'lost_password_behaviour': passwords_cfg.get('lost_password_behaviour', 'nothing'),
'creation_mode': identities_cfg.get('creation'),
}
get_request().form=save_form
return template.process_template(
str(TextsDirectory.get_html_text('login')), vars)
def login_ssl (self):
request = get_request()
response = get_response()
session = get_session()
environ = request.environ
self.close_header(response)
ssl_cfg = get_cfg('ssl', {})
allow_ssl_login = ssl_cfg.get('allow_ssl_login', False)
if not environ.get('HTTPS'):
return redirect(request.environ['SCRIPT_NAME'] + '/')
ssl_session_id = environ.get('SSL_SESSION_ID')
ssl_client_cert = environ.get('SSL_CLIENT_CERT')
ssl_client_s_dn = environ.get('SSL_CLIENT_S_DN')
ssl_client_verify = environ.get('SSL_CLIENT_VERIFY','')
if not allow_ssl_login:
raise errors.TraversalError()
allow_certificate_federation = ssl_cfg.get('allow_certificate_federation', False)
# Find an identity
identity = None
if ssl_client_s_dn and ssl_client_verify == 'OK':
id = ssl_client_s_dn
dn_to_id_regexp = ssl_cfg.get('dn_to_id_regexp')
if dn_to_id_regexp:
try:
result = re.search(dn_to_id_regexp, id)
id = result.group(1)
except:
get_logger().warn('Wrong dn to id regexp, must at least one subgroup, dn: %s regexp: %s' % (ssl_client_s_dn, dn_to_id_regexp))
raise errors.AccessForbiddenError()
try:
identity = identities.get_store().get_identity(id)
except KeyError:
try:
identity = identities.get_store().get_identity_for_username(id)
except KeyError:
pass
if identity:
get_logger().info('Mapped identity from certificate with dn: %s to account %s' % (ssl_client_s_dn, identity.id))
if not identity and allow_certificate_federation and ssl_client_cert: # and not ssl_client_verify.startswith('FAILED:')
account = identities.CertificateAccount(certificate = ssl_client_cert)
identity = identities.get_store().get_identity_for_account(account)
if not identity:
get_logger().warn('SSL Authentification failure')
return misc.redirect_home()
if identity.disabled:
get_logger().warn('SSL Authentification Failure: identity %s disabled' % identity.id)
return self.login_success(identity.id, 'client-certificate')
def close_header(self, response):
response.set_header('Connection', 'close')
def associate_certificate [html] (self):
request = get_request()
response = get_response()
self.close_header(response)
session = get_session()
environ = request.environ
ssl_cfg = get_cfg('ssl', {})
ssl_session_id = environ.get('SSL_SESSION_ID')
ssl_client_cert = environ.get('SSL_CLIENT_CERT')
ssl_client_s_dn = environ.get('SSL_CLIENT_S_DN')
ssl_client_verify = environ.get('SSL_CLIENT_VERIFY','')
https = environ.get('HTTPS')
if not https:
retry_url = 'https://' + request.get_server() + request.get_path()
return template.error_page(_('This action needs an HTTPS connection'),
continue_to = (retry_url , _('Home')))
if not ssl_client_cert:
return template.error_page(_('This action needs that you present an SSL certificate, maybe the server is not configured to ask one ?')
, continue_to = ('.', _('Home')))
allow_certificate_federation = ssl_cfg.get('allow_certificate_federation', False)
allow_ssl_login = ssl_cfg.get('allow_ssl_login', False)
if allow_certificate_federation and allow_ssl_login and session.user:
if ssl_client_cert:
# Support only one certificate federation
account = identities.CertificateAccount(certificate = ssl_client_cert, dn = ssl_client_s_dn)
identity = identities.get_store().get_identity(session.user)
identity.accounts = [ x for x in identity.accounts if not isinstance(x, identities.CertificateAccount) ]
identity.accounts.append(account)
identity.store()
return redirect('.')
else:
raise errors.TraversalError()
def more_login_text(self):
pass # placeholder to be subclassed XXX: inoperant now
2005-06-01 20:44:32 +02:00
def login_submit(self, form):
2008-02-26 17:40:20 +01:00
identities.get_store().load_identities()
2005-06-01 20:44:32 +02:00
account = identities.PasswordAccount()
account.username = form.get_widget('username').parse()
account.password = form.get_widget('password').parse()
try:
2008-02-26 17:40:20 +01:00
identity = identities.get_store().get_identity_for_account(account)
2005-06-01 20:44:32 +02:00
except identities.IdentityStoreException:
# aie
if os.path.exists(os.path.join(get_publisher().app_dir, 'emergency')):
session = get_session()
session.set_user(1)
if session.after_url:
after_url = session.after_url
session.after_url = None
return redirect(after_url)
2006-12-20 12:54:14 +01:00
return redirect(get_request().environ['SCRIPT_NAME'] + '/')
return template.error_page(_('Failed to connect to identities storage.'))
2005-06-01 20:44:32 +02:00
if identity is None:
passwords_cfg = get_cfg('passwords', {})
if passwords_cfg.get('log_wrong_passwords', False):
get_logger().warn('Authentication Failure (un: %s pw: %s)' % (
account.username.replace('\n', ' ').replace('\r', ' '),
account.password.replace('\n', ' ').replace('\r', ' ')))
else:
get_logger().warn('Authentication Failure (un: %s)' % (
account.username.replace('\n', ' ').replace('\r', ' ')))
2005-06-01 20:44:32 +02:00
raise LoginError()
2006-03-28 12:03:35 +02:00
if identity.disabled:
2007-12-29 18:04:31 +01:00
get_logger().warn('Authentication Failure (un: %s) (disabled)' % (
2006-03-28 12:03:35 +02:00
account.username.replace('\n', ' ').replace('\r', ' ')))
raise LoginError()
2006-11-08 23:44:36 +01:00
if get_request().environ.get('HTTPS') == 'on':
authentication_method = 'password-on-https'
2006-11-08 23:44:36 +01:00
else:
authentication_method = 'password'
result = self.login_success(identity.id, authentication_method)
identities.get_store().init_session(get_session(), account)
return result
def login_cancel(self):
login_token_id = get_field('LoginToken')
if login_token_id:
login_token.LoginToken.set_authentication_result(login_token_id,
False)
return misc.redirect_to_return_url('cancel') or \
misc.redirect_to_return_url() or \
misc.redirect_home()
def redirect_to_common_domain_setter_url(self):
common_domain = get_cfg('idp', {}).get('common_domain')
common_domain_setter_url = get_cfg('idp', {}).get('common_domain_setter_url')
if common_domain_setter_url and common_domain:
token = Token(expiration_delay = 600) # ten minutes
token.session_id = session.id
token.protocol = 'local'
server = misc.get_lasso_server(protocol = 'saml2')
token.provider_id = server.providerId
token.common_domain = common_domain
token.next_url = get_request().get_url(-1)
token.store()
session.lasso_login_dump = None
return redirect(common_domain_setter_url + '?tok=%s' % token.id)
return None
def login_success(self, user, method):
# Setup current session
get_session_manager().expire_session()
get_session().set_user(user)
get_session().authentication_method = method
get_request().user = user
get_session().authentication_instant = datetime.datetime.utcnow()
login_token_id = get_field('LoginToken')
if login_token_id:
login_token.LoginToken.set_authentication_result(login_token_id,
True, user = user, method = method)
get_session().__dict__.setdefault('login_tokens',[]) \
.append(login_token_id)
return misc.redirect_to_return_url('ok') or \
misc.redirect_to_return_url() or \
misc.redirect_to_after_url() or \
self.redirect_to_common_domain_setter_url() or \
misc.redirect_home()
def singleLogout(self):
return self.logout()
def logout(self):
# if a user is still present, try a liberty or saml logout
if get_session().user and get_session().lasso_session_dump:
if get_session().saml2:
return self.saml.slo_idp()
else:
return self.liberty.singleLogout()
else:
# expire session
get_logger().info('logout')
get_session_manager().expire_session()
return misc.redirect_home()
def register [html] (self):
identities_cfg = get_cfg('identities', {})
2006-03-28 12:03:35 +02:00
if not identities_cfg.get('creation') in ('self', 'moderated'):
raise errors.TraversalError()
2005-06-01 22:02:27 +02:00
form = Form(enctype="multipart/form-data")
for field in identities.get_store_class().fields:
field.add_to_form(form) and None
has_email = 'email' in [x.key for x in identities.get_store_class().fields]
if not has_email or not identities_cfg.get('email-as-username', False):
form.add(ValidatedStringWidget, "username", title=_("Username"),
size=30, required=True,
regex=identities.get_store().username_regex)
2005-06-01 22:02:27 +02:00
passwords_cfg = get_cfg('passwords', {})
if passwords_cfg.get('can_change', False) and not passwords_cfg.get('generate', False):
2005-06-01 22:02:27 +02:00
form.add(PasswordWidget, "password", title=_("Password"), size=30, required=True)
else:
2005-06-03 17:16:44 +02:00
form.add(HtmlWidget, '<p>%s</p>' % _('A password will be mailed to you.'))
if passwords_cfg.get('lost_password_behaviour', 'nothing') == 'dumb_question':
dumb_questions_options = [(str(x), _(identities.dumb_questions[x])) \
for x in identities.available_dumb_questions]
2005-06-03 17:16:44 +02:00
form.add(HtmlWidget, '<p>%s</p>' % _('If you forget your password...'))
form.add(SingleSelectWidget, 'dumb_question',
title = _('Security question'), required=True,
options = [(None, _('[Select a question]'))] + dumb_questions_options)
2005-06-03 17:16:44 +02:00
form.add(StringWidget, 'smart_answer', title = _('Your answer'), required=True)
2005-06-01 22:02:27 +02:00
form.add_submit("submit", _("Submit"))
2005-09-11 18:19:05 +02:00
form.add_submit("cancel", _("Cancel"))
if form.get_submit() == 'cancel':
return redirect('.')
2005-06-01 22:02:27 +02:00
if form.is_submitted() and not form.has_errors():
try:
return self.register_submit(form)
2005-06-01 22:02:27 +02:00
except RegistrationError:
pass
template.html_top(_('Registration'))
get_response().breadcrumb.append( ('register', _('Registration')) )
vars = {
'register_form': str(form.render())
}
return template.process_template(
str(TextsDirectory.get_html_text('register')), vars)
2005-06-01 22:02:27 +02:00
def register_submit(self, form):
identities_cfg = get_cfg('identities', {})
pwd_cfg = configuration.get_configuration('passwords')
store = identities.get_store()
2005-06-01 22:02:27 +02:00
account = identities.PasswordAccount()
has_email = 'email' in [x.key for x in identities.get_store_class().fields]
if identities_cfg.get('email-as-username', False):
2005-06-01 22:02:27 +02:00
account.username = form.get_widget('email').parse()
else:
account.username = form.get_widget('username').parse()
if pwd_cfg.get('lost_password_behaviour') == 'dumb_question':
2005-06-03 17:16:44 +02:00
account.dumb_question = form.get_widget('dumb_question').parse()
account.smart_answer = form.get_widget('smart_answer').parse()
if store.has_identity_with_username(account.username):
if identities_cfg.get('email-as-username', False):
form.get_widget('email').set_error(_('That address is already in use'))
2005-06-01 22:02:27 +02:00
else:
form.get_widget('username').set_error(_('That username is already in use'))
2005-06-01 22:02:27 +02:00
raise RegistrationError()
if pwd_cfg.get('can_change') and not pwd_cfg.get('generate'):
password = form.get_widget('password').parse()
2005-06-01 22:02:27 +02:00
if len(account.password) < min_pw_length:
form.set_error('password',
_('Password is too short. It must be at least %d characters.') % \
2005-06-01 22:02:27 +02:00
min_pw_length)
raise RegistrationError()
if max_pw_length and len(account.password) > max_pw_length:
form.set_error('password',
_('Password is too long. It must be at most %d characters.') % \
2005-06-01 22:02:27 +02:00
max_pw_length)
raise RegistrationError()
else:
password = store.create_password(for_account=account.username)
account.password = store.hash_password(password)
2005-06-01 22:02:27 +02:00
identity = store.get_identity_class()()
if not identity.id:
identity.id = account.username # or sequence ?
2005-06-01 22:02:27 +02:00
for field in identities.get_store_class().fields:
if form.get_widget(field.key):
setattr(identity, field.key, form.get_widget(field.key).parse())
identity.accounts = [account]
self.pre_registration_callback(identity)
try:
store.add(identity)
except identities.AlreadyExists, e:
if e.args:
keys = e.args[0]
for key in keys:
form.set_error(key, _('This value must be unique but it already exists for another user'))
raise RegistrationError()
2006-03-28 12:03:35 +02:00
if identities_cfg.get('creation') == 'moderated':
identity.disabled = True
2007-03-15 15:19:52 +01:00
else:
try:
self.email_password(identity, welcome = True,
password = password)
except errors.EmailError:
store.remove(identity)
get_logger().error('Error emailing password to user (%s)' % identity.email)
return template.error_page(
_('An error occured and your password could not be send. '
'Is your email address correct?'))
except ForgotPassword, e:
message = e.args[0]
get_logger().error('Registration password sending failed for %s: %s' % (account.username, message))
return template.error_page(message)
if identities_cfg.get('notify-on-register', False):
self.notify_registration(identity)
if store.count() == 0:
# first user created gets admin role
identity.roles = [identities.ROLE_ADMIN]
store.save(identity)
2007-12-29 18:04:31 +01:00
get_logger().info('User created new identity (%s)' % identity)
self.registration_callback(identity)
if identities_cfg.get('creation') == 'self' and not pwd_cfg.get('generate'):
2007-03-15 15:19:52 +01:00
return redirect(get_request().environ['SCRIPT_NAME'] + '/login')
elif identities_cfg.get('creation') == 'moderated':
return self.moderated_answer()
else:
2007-03-15 15:19:52 +01:00
return self.register_done_password_sent()
2006-03-28 12:03:35 +02:00
def pre_registration_callback(self, identity):
pass # for derivatives to override
def registration_callback(self, identity):
pass # for derivatives to override
2006-03-28 12:03:35 +02:00
def moderated_answer [html] (self):
template.html_top(_('Registration'))
'Registration is moderated.'
2005-06-03 17:16:44 +02:00
def notify_registration(self, identity):
identities_cfg = get_cfg('identities', {})
admins = identities.get_store().administrators()
admin_emails = [x.email for x in admins if x.email]
if not admin_emails:
return
2007-03-15 15:19:52 +01:00
data = {
'hostname': get_request().get_server(),
'identity': str(identity),
'email': identity.email,
'email_as_username': str(identities_cfg.get('email-as-username', False)),
'username': identity.accounts[0].username,
'service': get_session().service,
2007-03-15 15:19:52 +01:00
}
2007-03-15 15:19:52 +01:00
emails.custom_ezt_email('new-registration-admin-notification', data,
admin_emails, fire_and_forget = True)
def register_done_password_sent [html] (self):
template.html_top(_('Registration Completed'))
get_response().breadcrumb.append( ('registration', _('Registration Completed')) )
branding_cfg = get_cfg('branding', {})
return template.process_template(
str(TextsDirectory.get_html_text('register_completed')), {})
2005-06-03 17:16:44 +02:00
def email_password(self, identity, welcome = False, password = None):
store = identities.get_store()
2005-06-03 17:16:44 +02:00
for account in identity.accounts:
if hasattr(account, 'username'):
break
else:
account = None
2005-06-01 22:02:27 +02:00
2005-06-03 17:16:44 +02:00
if not account:
raise ForgotPassword(_('Your account has no password, it \
certainly uses another kind of authentication, contact an administrator.'))
2005-06-03 17:16:44 +02:00
if not identity.email:
raise ForgotPassword(_('Your account has no email, contact an \
administrator'))
2005-06-03 17:16:44 +02:00
identities_cfg = configuration.get_configuration('identities')
pwd_cfg = configuration.get_configuration('passwords')
if not password:
password = account.password
2011-01-01 10:25:28 +01:00
if password is None or identities.get_pwd_hashing_scheme() != 'clear' or \
pwd_cfg.get('generate_on_remind') or \
identities.get_hash_format(password) is not None:
password = store.create_password(for_account=account.username)
account.password = store.hash_password(password)
store.save(identity)
2005-07-10 15:46:01 +02:00
2007-03-15 15:19:52 +01:00
data = {
'hostname': get_request().get_server(),
'identity': str(identity),
'email': identity.email,
'email_as_username': str(identities_cfg.get('email-as-username')),
2007-03-15 15:19:52 +01:00
'username': identity.accounts[0].username,
'password': password,
'service': get_session().service,
2007-03-15 15:19:52 +01:00
}
2005-06-01 22:02:27 +02:00
2007-03-15 15:19:52 +01:00
if welcome:
emails.custom_ezt_email('welcome-email', data, identity.email)
2007-03-15 15:19:52 +01:00
else:
emails.custom_ezt_email('password-email', data, identity.email)
2005-06-03 17:16:44 +02:00
def forgot_password_message [html] (self):
template.html_top(_('Lost Password'))
return template.process_template(
str(TextsDirectory.get_html_text('lost_password_mailed')), vars)
def forgot_password_dumb_question [html] (self, form):
identities_cfg = get_cfg('identities', {})
username = form.get_widget('username').parse()
2008-02-26 17:40:20 +01:00
identity = identities.get_store().get_identity_for_username(username)
if identity:
for account in identity.accounts:
if hasattr(account, str('username')):
break
else:
raise 'XXX'
else:
account = identities.PasswordAccount()
if not get_session().question_key:
get_session().question_key = random.choice(identities.available_dumb_questions)
account.dumb_question = get_session().question_key
get_session().question_key = account.dumb_question
form = Form(enctype="multipart/form-data", use_tokens = False)
form.add_hidden("username", username)
if not hasattr(account, str('dumb_question')):
return template.error_page('No password question for this identity')
if not identities.dumb_questions.has_key(account.dumb_question):
return template.error_page('No password question for this identity')
if identities_cfg.get('email-as-username', False):
form.add(HtmlWidget, '<p>%s: %s</p>' % (_('Email'), username))
else:
form.add(HtmlWidget, '<p>%s: %s</p>' % (_('Username'), username))
form.add(StringWidget, "answer",
title = _(identities.dumb_questions[account.dumb_question]),
required = True, size = 30)
answer = form.get_widget('answer').parse()
user_answer = account.smart_answer or ''
if answer and str(answer).lower().strip() != str(user_answer).lower().strip():
return template.error_page(_('Wrong answer or inexistant user'))
form.set_error('answer', _('Wrong answer'))
if not get_request().form.has_key('answer'):
form.set_error('answer', None)
if not form.has_errors() and get_request().form.has_key('answer'):
return self.forgot_password_submit(form) or \
self.forgot_password_message()
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
vars = {
'lost_password_question_form': str(form.render())
}
return template.process_template(
str(TextsDirectory.get_html_text('lost_password_question')), vars)
2005-06-03 17:16:44 +02:00
def forgot_password [html] (self):
get_response().breadcrumb.append( ('forgot_password', _('Lost Password')) )
passwords_cfg = get_cfg('passwords', {})
2005-06-01 22:02:27 +02:00
2005-06-03 17:16:44 +02:00
behaviour = passwords_cfg.get('lost_password_behaviour', 'nothing')
if behaviour == 'nothing':
raise errors.AccessForbiddenError()
2005-08-09 11:15:35 +02:00
form = Form(enctype="multipart/form-data", use_tokens = False)
identities_cfg = get_cfg('identities', {})
2005-06-03 17:16:44 +02:00
if identities_cfg.get('email-as-username', False):
form.add(EmailWidget, "username", title= _('Email'), size=30, required=True)
2005-06-03 17:16:44 +02:00
else:
form.add(StringWidget, "username", title=_("Username"), size=30, required=True)
form.add_submit("submit", _("Submit"))
2005-09-11 18:19:05 +02:00
form.add_submit("cancel", _("Cancel"))
if form.get_submit() == 'cancel':
return redirect('.')
2005-06-03 17:16:44 +02:00
if form.is_submitted() and not form.has_errors():
if behaviour == 'dumb_question':
return self.forgot_password_dumb_question(form)
2005-06-03 17:16:44 +02:00
else:
return self.forgot_password_submit(form) or \
self.forgot_password_message()
2005-06-03 17:16:44 +02:00
else:
vars = {
'lost_password_behaviour': str(behaviour),
'lost_password_form': str(form.render()),
'generate_on_remind': passwords_cfg.get('generate_on_remind', False)
}
2007-09-11 12:02:55 +02:00
template.html_top(_('Lost Password'))
return template.process_template(
str(TextsDirectory.get_html_text('lost_password')), vars)
2005-06-03 17:16:44 +02:00
def forgot_password_submit(self, form):
username = form.get_widget('username').parse()
try:
identity = identities.get_store().get_identity_for_username(
username,throw=True)
except identities.TooMuchAccounts:
get_logger().warning('Forgot password: more than one account for identifier %r' % username)
return template.error_page(htmltext(_('There is more than one accounts for the identifier <em>%s</em>, try to \
<a href="/forgot_identifier">ask for all your accounts</a>')) % username)
2005-06-03 17:16:44 +02:00
if not identity:
get_logger().warning('Forgot password: no account for username %r' % username)
return template.error_page(_('Your identity %r is unknown.') % username)
try:
self.email_password(identity)
except errors.EmailError:
get_logger().error('lost password -> email password (%s) (failure)' % username)
return template.error_page(_('An error occured and your password could not be send.'))
except ForgotPassword, e:
message = e.args[0]
get_logger().error('lost password -> %s: %s' % (username, message))
return template.error_page(message)
get_logger().info('lost password -> email password of %s to %s' % (username, identity.email))
@misc.protect_form_from_get_parameters
def update_info [html] (self):
identities_cfg = get_cfg('identities', {})
if identities_cfg.get('creation') != 'self':
raise errors.TraversalError()
session = get_session()
if not session or not session.user:
raise errors.AccessForbiddenError()
form = Form(enctype="multipart/form-data")
form.keep_referer()
skip_email = identities_cfg.get('email-as-username', False)
2008-02-26 17:40:20 +01:00
identity = identities.get_store().get_identity(session.user)
for field in identities.get_store_class().fields:
if field.key == 'email' and skip_email:
continue
field.add_to_form(form, identity = identity) and None
form.add_submit("submit", _("Submit"))
form.add_submit("cancel", _("Cancel"))
if get_request().get_method() == 'GET':
get_request().form = {}
2005-09-11 18:19:05 +02:00
if form.get_submit() == 'cancel':
return misc.redirect_to_return_url() or \
misc.redirect_to_referer(form) or \
misc.redirect_home()
if form.is_submitted() and not form.has_errors():
return self.update_info_submit(form, identity)
template.html_top(_('Updating Personal Information'))
2005-12-01 12:14:59 +01:00
get_response().breadcrumb.append( ('', _('Account Management')) )
get_response().breadcrumb.append( ('update_info', _('Updating Personal Information')) )
vars = {
'info_form': str(form.render())
}
return template.process_template(
str(TextsDirectory.get_html_text('update_info')), vars)
def update_info_submit(self, form, identity):
identities_cfg = get_cfg('identities', {})
skip_email = identities_cfg.get('email-as-username', False)
for field in identities.get_store_class().fields:
if field.key == 'email' and skip_email:
continue
if form.get_widget(field.key):
setattr(identity, field.key, form.get_widget(field.key).parse())
2008-02-26 17:40:20 +01:00
identities.get_store().save(identity)
get_logger().info('Updated identity %r' % identity.id)
return misc.redirect_to_return_url() or \
misc.redirect_to_referer(form) or \
misc.redirect_home()
def forgot_identifier(self):
get_response().breadcrumb.append( ('forgot_identifier', _('Lost Account Name')) )
form = Form(enctype="multipart/form-data", use_tokens = False)
form.add(EmailWidget, "email", title= _('Email'), size=30, required=True)
form.add_submit("submit", _("Submit"))
form.add_submit("cancel", _("Cancel"))
if form.get_submit() == 'cancel':
return redirect('.')
if form.is_submitted() and not form.has_errors():
res, error = self.forgot_identifier_submit(form)
if not error:
return res
form.set_error('email', error)
vars = {
'lost_identifier_form': str(form.render())
}
template.html_top(_('Lost Account Name'))
return template.process_template(
str(TextsDirectory.get_html_text('lost_identifier')), vars)
def forgot_identifier_submit(self, form):
email = form.get_widget('email').parse()
idents = identities.get_store().get_identities_by_attributes({
'email': email })
usernames = []
for ident in idents:
for account in ident.accounts:
if hasattr(account, 'username'):
usernames.append(account.username)
if not usernames:
return None, _('There is no account with this email')
data = {
'hostname': get_request().get_server(),
'email': email,
'usernames': usernames,
'service': get_session().service,
}
get_logger().info('Forgot identifier: reminded email %r of its accounts' % email)
try:
emails.custom_ezt_email('identifier-email', data, email)
except:
return None, _('Error when sending the mail')
get_session().message = ('info', _('Your identifiers have been send to %s'))
return misc.redirect_home(), None
TextsDirectory.register('account',
N_('Account Management'),
hint = N_('Available variables: identity_label, idp_sso_list, show_federations, federations_list, certificate_list, allow_certificate_federation'),
default = N_('''\
<h2 class="identity-title">[identity_label]</h2>
[is creation_mode "self"]
<p><a href="update_info">Update Personal Information</a></p>
[end]
[is can_change_password "True"]
2010-06-07 13:41:29 +02:00
<p><a href="change_password">Change Password</a></p>
[end]
[idp_sso_list]
<p id="logout"><a href="singleLogout">Logout</a></p>
[is show_federations "True"]
[if-any federations_list]
<p id="federations"><a href="federations">Federations</a></p>
[end]
[if-any allow_certificate_federation_url]
<p><a href="[allow_certificate_federation_url]">Associate a certificate to this account<a/></p>
[if-any certificate_list]
[certificate_list]
[end]
[end]
[end]
[is admin "True"]
<p id="admin"><a href="admin">Administration</a></p>
[end]
'''))
TextsDirectory.register('register',
N_('Registration'),
hint = N_('Available variable: register_form'),
default = N_('''\
[register_form]
'''))
TextsDirectory.register('register_completed',
N_('Registration Completed'),
default = N_('''\
<p>Your password has been mailed to you.</p>
<p><a href="login">Login page</a></p>
'''))
TextsDirectory.register('change_password',
N_('Changing Password'),
hint = N_('Available variable: change_password_form'),
default = N_('''\
<div id="identity-content">
[change_password_form]
</div>
'''))
TextsDirectory.register('lost_password',
N_('Lost Password'),
hint = N_('Available variables: lost_password_form, lost_password_behaviour, generate_on_remind'),
default = N_('''\
[is lost_password_behaviour "dumb_question"]
[is generate_on_remind "True"]<p>Fill the form to get a <strong>new</strong> password mailed back to you.</p>
[else]<p>Fill the form to get your password mailed back to you.</p>
[end]
[end]
[is lost_password_behaviour "email_reminder"]
[is generate_on_remind "True"]<p>A <strong>new</strong> password will be mailed back to you.</p>
[else]<p>Your password will be mailed back to you.</p>
[end]
[end]
[lost_password_form]
'''))
TextsDirectory.register('lost_identifier',
N_('Lost Account Name'),
hint = N_('Available variables: lost_identifier_form'),
default = N_('''\
<p>Give your email to get back the list of your accounts.</p>
[lost_identifier_form]
'''))
TextsDirectory.register('lost_password_question',
N_('Lost Password Question'),
hint = N_('Available variable: lost_password_question_form'),
default = N_('''\
[lost_password_question_form]
'''))
TextsDirectory.register('lost_password_mailed',
N_('Lost Password (mailed)'),
default = N_('''\
<p>Your password has been mailed back to you.</p>
<p><a href="login">Login</a>
'''))
TextsDirectory.register('update_info',
N_('Updating Personal Information'),
hint = N_('Available variable: info_form'),
default = N_('''\
[info_form]
'''))
TextsDirectory.register('login',
N_('Login'),
hint = N_('Available variables: login_form, authentication_failure'),
default = N_('''\
<div id="login-form">
[if-any authentication_failure]
<div class="errornotice">
[authentication_failure]
</div>
[end]
[login_form]
[if-any cookies]
<p id="cookies" class="error-notice">
You must accept cookies to authenticate.
</p>
[end]
[is lost_password_behaviour "nothing"][else]
<p id="forgot-password"><a href="forgot_password">Forgot your password?</a></p>
[end]
<p id="forgot-identifier"><a href="forgot_identifier">Forgot your account name?</a></p>
[is creation_mode "self"]
<p id="register"><a href="register">Register a new account</a></p>
[end]
[is creation_mode "moderated"]
<p id="register"><a href="register">Register a new account</a></p>
[end]
<script type="text/javascript">
[" document.forms[0]['username'].focus(); "]
</script>
</div>
'''))
TextsDirectory.register('federations',
N_('Federations'),
hint = N_('Available variables: identity_label, federations_list'),
default = N_('''\
[if-any federations_list]
[federations_list]
[end]
<a href=".">Back to home</a>
'''))
2007-03-15 15:19:52 +01:00
EmailsDirectory.register('welcome-email',
N_('Welcome Email'),
N_('Available variables: %s') % 'hostname, identity, username, password, email_as_username, service',
2007-03-15 15:19:52 +01:00
default_subject = N_('Welcome to [hostname]'),
default_body = N_('''\
Welcome to [hostname],
Your password is: [password]
'''))
EmailsDirectory.register('password-email',
2007-03-15 15:22:06 +01:00
N_('Password Reminder'),
N_('Available variables: %s') % 'hostname, identity, email, username, password, email_as_username, service',
2007-03-15 15:19:52 +01:00
default_subject = N_('Your account on [hostname]'),
default_body = N_('''\
Hello,
You asked for your password on [hostname].
[is email_as_username "True"]
Your username is: [email]
[else]
Your username is: [username]
[end]
2007-03-15 15:19:52 +01:00
Your password is: [password]
'''))
EmailsDirectory.register('new-registration-admin-notification',
N_('Notification of new registration to administrators'),
N_('Available variables: %s') % 'hostname, identity, email, email_as_username, username, service',
2007-03-15 15:19:52 +01:00
default_subject = N_('New Registration'),
default_body = N_('''\
Hello,
2007-03-15 15:19:52 +01:00
A new user registered on [hostname].
- Identity: [identity]
[if-any email] - Email: [email][end]
[is email_as_username "False"] - Username: [username][end]
'''))
EmailsDirectory.register('identifier-email',
N_('Identifier Email'),
N_('Available variables: %s') % 'hostname, usernames, email, service',
default_subject = N_('List of identifiers for [email] on [hostname]'),
default_body = N_('''\
You asked for all usernames linked to address [email] on [hostname].
These usernames are: [for usernames]
- [usernames][end]
'''))