Merge branch 'saml2_sp'

Integrate saml2 into Mandaye and merge branche 'sam2_sp'
This commit is contained in:
Jérôme Schneider 2013-05-21 23:36:46 +02:00
commit 3cf7195297
29 changed files with 1094 additions and 36 deletions

View File

@ -23,8 +23,7 @@ from mandaye.response import _500, _302, _401
from mandaye.response import template_response
from mandaye.server import get_response
from mandaye.config.backend import ManagerIDPUser, ManagerSPUser,\
ManagerServiceProvider
from mandaye.config import backend
try:
from Crypto.Cipher import AES
@ -192,16 +191,16 @@ a password_field key if you want to encode a password.")
sp_login = post_values[self.form_values['username_field']]
if config.encrypt_sp_password:
self._encrypt_pwd(post_values)
service_provider = ManagerServiceProvider.get_or_create(self.site_name)
idp_user = ManagerIDPUser.get_or_create(unique_id)
sp_user = ManagerSPUser.get_or_create(sp_login, post_values,
service_provider = backend.ManagerServiceProvider.get_or_create(self.site_name)
idp_user = backend.ManagerIDPUser.get_or_create(unique_id)
sp_user = backend.ManagerSPUser.get_or_create(sp_login, post_values,
idp_user, service_provider)
sp_user.login = sp_login
sp_user.post_values = post_values
sp_user.idp_user = idp_user
sp_user.last_connection = datetime.now()
sp_user.service_provider = service_provider
ManagerSPUser.save()
backend.ManagerSPUser.save()
env['beaker.session']['unique_id'] = unique_id
env['beaker.session'][self.site_name] = sp_user.id
env['beaker.session'].save()
@ -251,7 +250,7 @@ a password_field key if you want to encode a password.")
response = self.replay(env, post_values)
if condition and eval(condition):
sp_user.last_connection = datetime.now()
ManagerSPUser.save()
backend.ManagerSPUser.save()
env['beaker.session'][self.site_name] = sp_user.id
env['beaker.session'].save()
return response
@ -261,9 +260,9 @@ a password_field key if you want to encode a password.")
def login(self, env, values, condition, request, response):
""" Automatic login on a site with a form
"""
logger.debug('Trying to login on Mandaye')
# Specific method to get current idp unique id
unique_id = self.get_current_unique_id(env)
logger.debug('Trying to login on Mandaye')
if not unique_id:
return _401('Access denied: invalid token')
@ -276,9 +275,9 @@ a password_field key if you want to encode a password.")
logger.debug('User %s successfully login' % env['beaker.session']['unique_id'])
idp_user = ManagerIDPUser.get(unique_id)
service_provider = ManagerServiceProvider.get(self.site_name)
sp_user = ManagerSPUser.get_last_connected(idp_user, service_provider)
idp_user = backend.ManagerIDPUser.get(unique_id)
service_provider = backend.ManagerServiceProvider.get(self.site_name)
sp_user = backend.ManagerSPUser.get_last_connected(idp_user, service_provider)
if not sp_user:
logger.debug('User %s is not associate' % env['beaker.session']['unique_id'])
return _302(values.get('associate_url') + "?type=first")
@ -303,9 +302,9 @@ a password_field key if you want to encode a password.")
if not login or not qs.has_key('id'):
return _401('Access denied: beaker session invalid or not qs id')
id = qs['id'][0]
service_provider = ManagerServiceProvider.get(self.site_name)
idp_user = ManagerServiceProvider.get(unique_id)
sp_user = ManagerSPUser.get_last_connected(idp_user, service_provider)
service_provider = backend.ManagerServiceProvider.get(self.site_name)
idp_user = backend.ManagerServiceProvider.get(unique_id)
sp_user = backend.ManagerSPUser.get_last_connected(idp_user, service_provider)
if not sp_user:
return _302(values.get('associate_url'))
return self._login_sp_user(sp_user, env, 'response.code==302', values)
@ -324,9 +323,9 @@ a password_field key if you want to encode a password.")
if not login or not qs.has_key('id'):
return _401('Access denied: beaker session invalid or not id')
id = qs['id'][0]
sp_user = ManagerSPUser.get_by_id(id)
sp_user = backend.ManagerSPUser.get_by_id(id)
if sp_user:
ManagerSPUser.delete(sp_user)
backend.ManagerSPUser.delete(sp_user)
if qs.has_key('logout'):
self.logout(env, values, request, response)
return _302(values.get('next_url'))

164
mandaye/auth/saml2.py Normal file
View File

@ -0,0 +1,164 @@
import datetime
import os
import urllib2
import lasso
from authentic2.saml import saml2utils
from urlparse import parse_qs
from mandaye import config, utils
from mandaye.auth.authform import AuthForm
from mandaye.response import _302, _500
from mandaye.log import logger
from mandaye.template import serve_template
from mandaye.http import HTTPResponse, HTTPHeader
class SAML2Auth(AuthForm):
""" SAML 2 authentification
"""
def __init__(self, form_values, site_name, saml2_config):
""" saml2_config: saml 2 config module
"""
self.config = saml2_config
self.metadata_map = (
('AssertionConsumerService',
lasso.SAML2_METADATA_BINDING_POST ,
self.config.END_POINTS_PATH['single_sign_on_post']
),
)
self.metadata_options = { 'key': self.config.SAML_SIGNATURE_PUBLIC_KEY }
super(SAML2Auth, self).__init__(form_values, site_name)
def get_current_unique_id(self, env):
return env['beaker.session']['unique_id']
def _get_idp_metadata_file_path(self):
metadata_file_path = None
if self.config.IDP_METADATA:
metadata_file_path = os.path.join(config.data_dir,
self.config.IDP_METADATA.\
replace('://', '_').\
replace('/', '_')
)
if not os.path.isfile(metadata_file_path):
try:
response = urllib2.urlopen(self.config.IDP_METADATA)
metadata = response.read()
except:
return _500('sso', 'Unable to find metadata.')
metadata_file = open(metadata_file_path, 'a+')
metadata_file.write(metadata)
metadata.close()
return metadata_file_path
def _get_metadata(self, env):
url_prefix = env['mandaye.scheme'] + '://' + env['HTTP_HOST']
metadata_path = self.config.END_POINTS_PATH['metadata']
single_sign_on_post_path = \
self.config.END_POINTS_PATH['single_sign_on_post']
metagen = saml2utils.Saml2Metadata(url_prefix + metadata_path,
url_prefix = url_prefix)
metagen.add_sp_descriptor(self.metadata_map, self.metadata_options)
return str(metagen)
def sso(self, env, values, request, response):
target_idp = None
metadata_file_path = self._get_idp_metadata_file_path()
if not metadata_file_path:
return _500('sso', 'Unable to load provider.')
logger.debug('sso: target_idp is %s' % target_idp)
logger.debug('sso: metadata url is %s' % self.config.IDP_METADATA)
logger.debug('sso: mandaye metadata are %s' % self._get_metadata(env))
server = lasso.Server.newFromBuffers(self._get_metadata(env),
self.config.SAML_SIGNATURE_PRIVATE_KEY)
if not server:
return _500('sso', 'Error creating server object.')
logger.debug('sso: mandaye server object created')
server.addProvider(lasso.PROVIDER_ROLE_IDP, metadata_file_path)
login = lasso.Login(server)
if not login:
return _500('sso', 'Error creating login object.')
http_method = self.config.AUTHNREQ_HTTP_METHOD
try:
login.initAuthnRequest(target_idp, http_method)
except lasso.Error, error:
return _500('sso', 'Error initiating request.', error)
login.request.nameIDPolicy.format = self.config.SAML2_NAME_IDENTIFIER_FORMAT
login.request.nameIDPolicy.allowCreate = self.config.ALLOW_CREATE
login.request.nameIDPolicy.spNameQualifier = self.config.SP_NAME_QUALIFIER
login.request.protocolBinding = self.config.AUTHNRESP_BINDING
try:
login.buildAuthnRequestMsg()
except lasso.Error, error:
return _500('sso', 'Error initiating request.', error)
logger.debug('sso: set request id in session %s' % login.request.iD)
env['beaker.session']['request_id'] = login.request.iD
env['beaker.session'].save()
if not login.msgUrl:
return _500('sso', 'Enable to perform sso by redirection.')
return _302(login.msgUrl)
def single_sign_on_post(self, env, values, request, response):
target_idp = None
metadata_file_path = self._get_idp_metadata_file_path()
if not metadata_file_path:
return _500('single_sign_on_post', 'Unable to load provider.')
server = lasso.Server.newFromBuffers(self._get_metadata(env),
self.config.SAML_SIGNATURE_PRIVATE_KEY)
if not server:
return _500('singleSignOnPost', 'Error creating server object.')
server.addProvider(lasso.PROVIDER_ROLE_IDP, metadata_file_path)
login = lasso.Login(server)
if not login:
return _500('singleSignOnPost', 'Error creating login object.')
if env['REQUEST_METHOD'] != 'POST':
return _500('singleSignOnPost', 'Not a POST request.')
msg = env['wsgi.input']
params = parse_qs(msg.read())
if not params or not lasso.SAML2_FIELD_RESPONSE in params.keys():
return _500('singleSignOnPost', 'Missing response.')
message = params[lasso.SAML2_FIELD_RESPONSE][0]
logger.debug('sso: message posted %s' % message)
try:
login.processAuthnResponseMsg(message)
except:
return _500('singleSignOnPost', 'Unable to proces authnresponse message.')
subject_confirmation = utils.get_absolute_uri(env)
saml_request_id = env['beaker.session']['request_id']
check = saml2utils.authnresponse_checking(login, subject_confirmation,
logger, saml_request_id=saml_request_id)
if not check:
return _500('singleSignOnPost', 'error checking authn response %s' % env)
logger.debug('sso: response successfully checked')
try:
login.acceptSso()
except lasso.Error, error:
return _500('sso', 'Error validating sso.', error)
logger.debug('sso: sso accepted, session validation')
env['beaker.session']['validated'] = True
attributes = saml2utils.get_attributes_from_assertion(login.assertion,
logger)
env['beaker.session']['attributes'] = attributes
env['beaker.session']['unique_id'] = login.nameIdentifier.content
env['beaker.session'].save()
return _302('/')
def metadata(self, env, values, request, response):
title='metadata'
headers = HTTPHeader({'Content-Type': ['text/xml']})
return HTTPResponse(200, 'Found', headers,
self._get_metadata(env))

View File

@ -5,7 +5,7 @@ from mandaye.exceptions import ImproperlyConfigured
# rfc 1738 http://rfc.net/rfc1738.html
db_url = 'sqlite:///test.db'
# Default local backend
# Default backend
import mandaye.backends.sql
backend = mandaye.backends.sql
@ -15,7 +15,7 @@ keyfile = ''
certfile = ''
# Log configuration
debug = False
debug = True
syslog = False
log_file = '/var/log/mandaye/mandaye.log'
log_level = logging.INFO
@ -33,6 +33,8 @@ template_directory = 'mandaye/templates'
# Static folder
static_root = 'mandaye/static'
# Data dir
data_dir = '/var/tmp'
# Email notification configuration
email_notification = False
@ -55,10 +57,11 @@ encrypt_sp_password = False
encrypt_secret = ''
hosts = {
'localhost:8000': [
'proxy.roleid.lan:8088': [
{'path': r'/',
'target': 'http://perdu.com',
'mapping': None
'mapping': None,
'local': 'test'
},
],
}
@ -86,10 +89,10 @@ hosts = {
# beaker session
session_opts = {
'session.type': 'file',
'session.cookie_expires': True,
'session.timeout': 3600,
'session.data_dir': '/var/tmp/beaker',
'session.type': 'file',
'session.cookie_expires': True,
'session.timeout': 3600,
'session.data_dir': '/var/tmp/beaker'
}
# token timeout

71
mandaye/configs/saml2.py Normal file
View File

@ -0,0 +1,71 @@
import lasso
# END_POINTS
END_POINTS_PATH = {
'metadata': '/mandaye/metadata',
'single_sign_on_post': '/mandaye/singleSignOnPost'
}
# Now only support for a single IdP
IDP_METADATA = ("http://www.identity-hub.net/idp/saml2/metadata")
# SAML2 SP options
AUTHNREQ_HTTP_METHOD = lasso.HTTP_METHOD_REDIRECT
# Only persistent make sense
SAML2_NAME_IDENTIFIER_FORMAT = lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT
ALLOW_CREATE = True
SP_NAME_QUALIFIER = None
# Only POST is supported for now
AUTHNRESP_BINDING = lasso.SAML2_METADATA_BINDING_POST
# FIXME: change this keys
SAML_SIGNATURE_PUBLIC_KEY = '''-----BEGIN CERTIFICATE-----
MIIDIzCCAgugAwIBAgIJANUBoick1pDpMA0GCSqGSIb3DQEBBQUAMBUxEzARBgNV
BAoTCkVudHJvdXZlcnQwHhcNMTAxMjE0MTUzMzAyWhcNMTEwMTEzMTUzMzAyWjAV
MRMwEQYDVQQKEwpFbnRyb3V2ZXJ0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAvxFkfPdndlGgQPDZgFGXbrNAc/79PULZBuNdWFHDD9P5hNhZn9Kqm4Cp
06Pe/A6u+g5wLnYvbZQcFCgfQAEzziJtb3J55OOlB7iMEI/T2AX2WzrUH8QT8NGh
ABONKU2Gg4XiyeXNhH5R7zdHlUwcWq3ZwNbtbY0TVc+n665EbrfV/59xihSqsoFr
kmBLH0CoepUXtAzA7WDYn8AzusIuMx3n8844pJwgxhTB7Gjuboptlz9Hri8JRdXi
VT9OS9Wt69ubcNoM6zuKASmtm48UuGnhj8v6XwvbjKZrL9kA+xf8ziazZfvvw/VG
Tm+IVFYB7d1x457jY5zjjXJvNysoowIDAQABo3YwdDAdBgNVHQ4EFgQUeF8ePnu0
fcAK50iBQDgAhHkOu8kwRQYDVR0jBD4wPIAUeF8ePnu0fcAK50iBQDgAhHkOu8mh
GaQXMBUxEzARBgNVBAoTCkVudHJvdXZlcnSCCQDVAaInJNaQ6TAMBgNVHRMEBTAD
AQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAy8l3GhUtpPHx0FxzbRHVaaUSgMwYKGPhE
IdGhqekKUJIx8et4xpEMFBl5XQjBNq/mp5vO3SPb2h2PVSks7xWnG3cvEkqJSOeo
fEEhkqnM45b2MH1S5uxp4i8UilPG6kmQiXU2rEUBdRk9xnRWos7epVivTSIv1Ncp
lG6l41SXp6YgIb2ToT+rOKdIGIQuGDlzeR88fDxWEU0vEujZv/v1PE1YOV0xKjTT
JumlBc6IViKhJeo1wiBBrVRIIkKKevHKQzteK8pWm9CYWculxT26TZ4VWzGbo06j
o2zbumirrLLqnt1gmBDvDvlOwC/zAAyL4chbz66eQHTiIYZZvYgy
-----END CERTIFICATE-----'''
SAML_SIGNATURE_PRIVATE_KEY = '''-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAvxFkfPdndlGgQPDZgFGXbrNAc/79PULZBuNdWFHDD9P5hNhZ
n9Kqm4Cp06Pe/A6u+g5wLnYvbZQcFCgfQAEzziJtb3J55OOlB7iMEI/T2AX2WzrU
H8QT8NGhABONKU2Gg4XiyeXNhH5R7zdHlUwcWq3ZwNbtbY0TVc+n665EbrfV/59x
ihSqsoFrkmBLH0CoepUXtAzA7WDYn8AzusIuMx3n8844pJwgxhTB7Gjuboptlz9H
ri8JRdXiVT9OS9Wt69ubcNoM6zuKASmtm48UuGnhj8v6XwvbjKZrL9kA+xf8ziaz
Zfvvw/VGTm+IVFYB7d1x457jY5zjjXJvNysoowIDAQABAoIBAQCj8t2iKXya10HG
V6Saaeih8aftoLBV38VwFqqjPU0+iKqDpk2JSXBhjI6s7uFIsaTNJpR2Ga1qvns1
hJQEDMQSLhJvXfBgSkHylRWCpJentr4E3D7mnw5pRsd61Ev9U+uHcdv/WHP4K5hM
xsdiwXNXD/RYd1Q1+6bKrCuvnNJVmWe0/RV+r3T8Ni5xdMVFbRWt/VEoE620XX6c
a9TQPiA5i/LRVyie+js7Yv+hVjGOlArtuLs6ECQsivfPrqKLOBRWcofKdcf+4N2e
3cieUqwzC15C31vcMliD9Hax9c1iuTt9Q3Xzo20fOSazAnQ5YBEExyTtrFBwbfQu
ku6hp81pAoGBAN6bc6iJtk5ipYpsaY4ZlbqdjjG9KEXB6G1MExPU7SHXOhOF0cDH
/pgMsv9hF2my863MowsOj3OryVhdQhwA6RrV263LRh+JU8NyHV71BwAIfI0BuVfj
6r24KudwtUcvMr9pJIrJyMAMaw5ZyNoX7YqFpS6fcisSJYdSBSoxzrzVAoGBANu6
xVeMqGavA/EHSOQP3ipDZ3mnWbkDUDxpNhgJG8Q6lZiwKwLoSceJ8z0PNY3VetGA
RbqtqBGfR2mcxHyzeqVBpLnXZC4vs/Vy7lrzTiHDRZk2SG5EkHMSKFA53jN6S/nJ
JWpYZC8lG8w4OHaUfDHFWbptxdGYCgY4//sjeiuXAoGBANuhurJ99R5PnA8AOgEW
4zD1hLc0b4ir8fvshCIcAj9SUB20+afgayRv2ye3Dted1WkUL4WYPxccVhLWKITi
rRtqB03o8m3pG3kJnUr0LIzu0px5J/o8iH3ZOJOTE3iBa+uI/KHmxygc2H+XPGFa
HGeAxuJCNO2kAN0Losbnz5dlAoGAVsCn94gGWPxSjxA0PC7zpTYVnZdwOjbPr/pO
LDE0cEY9GBq98JjrwEd77KibmVMm+Z4uaaT0jXiYhl8pyJ5IFwUS13juCbo1z/u/
ldMoDvZ8/R/MexTA/1204u/mBecMJiO/jPw3GdIJ5phv2omHe1MSuSNsDfN8Sbap
gmsgaiMCgYB/nrTk89Fp7050VKCNnIt1mHAcO9cBwDV8qrJ5O3rIVmrg1T6vn0aY
wRiVcNacaP+BivkrMjr4BlsUM6yH4MOBsNhLURiiCL+tLJV7U0DWlCse/doWij4U
TKX6tp6oI+7MIJE6ySZ0cBqOiydAkBePZhu57j6ToBkTa0dbHjn1WA==
-----END RSA PRIVATE KEY-----'''

View File

@ -0,0 +1,62 @@
form_values = {
'form_url': '/compte/connexion',
'form_attrs': { 'id': 'new_account' },
'post_fields': ['account[login]', 'account[password]'],
'username_field': 'account[login]',
'password_field': 'account[password]',
}
from mandaye.auth.saml2 import SAML2Auth
from mandaye.configs import saml2 as saml2_config
auth = SAML2Auth(form_values, 'linuxfr', saml2_config)
linuxfr_mapping = [
{
'path': r'/mandaye/logout',
'on_response': [{
'filter': auth.logout,
},]
},
{
'path': r'/mandaye/login$',
'method': 'GET',
'response': [{
'filter': auth.login,
'values': {
'associate_url': '/mandaye/associate',
},
'condition': 'response.code==302',
},]
},
{
'path': r'/mandaye/sso$',
'method': 'GET',
'response': [{
'filter': auth.sso,
'values': {
'next_url': '/mandaye/login',
'metadata_url': '/mandaye/metadata',
}
}]
},
{
'path': r'%s$' % saml2_config.END_POINTS_PATH['metadata'],
'method': 'GET',
'on_response': [{
'filter': auth.metadata,
}]
},
{
'path': r'%s' % saml2_config.END_POINTS_PATH['single_sign_on_post'],
'method': 'POST',
'response': [{
'filter': auth.single_sign_on_post,
}]
},
]

140
mandaye/filters/saml2.py Normal file
View File

@ -0,0 +1,140 @@
import datetime
import os
import config
from mandaye.views import homepage, logout
from urlparse import parse_qs
import lasso
from authentic2.saml import saml2utils
import session_utils
import utils
from mandaye.response import _302, _500
from mandaye.log import logger
from template import serve_template
from http import HTTPResponse, HTTPHeader
metadata_map = (
('AssertionConsumerService',
lasso.SAML2_METADATA_BINDING_POST ,
'/singleSignOnPost'),
)
metadata_options = { 'key': config.SAML_SIGNATURE_PUBLIC_KEY }
class SAML2:
def sso(self, env, values, request, response):
target_idp = None
metadata_file_path = None
if config.AUTO_IDP:
target_idp, metadata_file_path = config.AUTO_IDP
if not os.path.isfile(metadata_file_path):
return _500('sso', 'Metadata file not found for %s.' % target_idp)
else:
return _500('sso', 'Unable to load provider.')
logger.debug('sso: target_idp is %s' % target_idp)
logger.debug('sso: metadata file path is %s' % metadata_file_path)
logger.debug('sso: mandaye metadata are %s' % get_metadata())
server = lasso.Server.newFromBuffers(get_metadata(),
config.SAML_SIGNATURE_PRIVATE_KEY)
if not server:
return _500('sso', 'Error creating server object.')
logger.debug('sso: mandaye server object created')
server.addProvider(lasso.PROVIDER_ROLE_IDP, metadata_file_path)
login = lasso.Login(server)
if not login:
return _500('sso', 'Error creating login object.')
http_method = config.AUTHNREQ_HTTP_METHOD
try:
login.initAuthnRequest(target_idp, http_method)
except lasso.Error, error:
return _500('sso', 'Error initiating request.', error)
login.request.nameIDPolicy.format = config.SAML2_NAME_IDENTIFIER_FORMAT
login.request.nameIDPolicy.allowCreate = config.ALLOW_CREATE
login.request.nameIDPolicy.spNameQualifier = config.SP_NAME_QUALIFIER
login.request.protocolBinding = config.AUTHNRESP_BINDING
try:
login.buildAuthnRequestMsg()
except lasso.Error, error:
return _500('sso', 'Error initiating request.', error)
logger.debug('sso: set request id in session %s' % login.request.iD)
session_utils.set_item_in_session(env, 'request_id', login.request.iD)
env['beaker.session']['unique_id'] = login.request.iD
env['beaker.session'].save()
ipy.msgUrl:
return _500('sso', 'Enable to perform sso by redirection.')
return _302(login.msgUrl)
def logout(self, env, values, request, response):
logout(env)
def single_sign_on_post(self, env, values, request, response):
target_idp = None
metadata_file_path = None
if config.AUTO_IDP:
target_idp, metadata_file_path = config.AUTO_IDP
else:
return _500('singleSignOnPost', 'Unable to load provider.')
server = lasso.Server.newFromBuffers(get_metadata(),
config.SAML_SIGNATURE_PRIVATE_KEY)
if not server:
return _500('singleSignOnPost', 'Error creating server object.')
server.addProvider(lasso.PROVIDER_ROLE_IDP, metadata_file_path)
login = lasso.Login(server)
if not login:
return _500('singleSignOnPost', 'Error creating login object.')
if env['REQUEST_METHOD'] != 'POST':
return _500('singleSignOnPost', 'Not a POST request.')
msg = env['wsgi.input']
params = parse_qs(msg.read())
if not params or not lasso.SAML2_FIELD_RESPONSE in params.keys():
return _500('singleSignOnPost', 'Missing response.')
message = params[lasso.SAML2_FIELD_RESPONSE][0]
logger.debug('sso: message posted %s' % message)
try:
login.processAuthnResponseMsg(message)
except:
return _500('singleSignOnPost', 'Unable to proces authnresponse message.')
subject_confirmation = utils.get_absolute_uri(env)
saml_request_id = session_utils.get_item_in_session(env, 'request_id')
check = saml2utils.authnresponse_checking(login, subject_confirmation,
logger, saml_request_id=saml_request_id)
if not check:
return _500('singleSignOnPost', 'error checking authn response %s' % env)
logger.debug('sso: response successfully checked')
try:
login.acceptSso()
except lasso.Error, error:
return _500('sso', 'Error validating sso.', error)
logger.debug('sso: sso accepted, session validation')
session_utils.validate_session(env)
attributes = saml2utils.get_attributes_from_assertion(login.assertion,
logger)
session_utils.set_item_in_session(env, 'attributes', attributes)
return _302('/')
def _get_metadata():
metagen = saml2utils.Saml2Metadata(config.PROXY_SAML2_ENTITYID,
url_prefix = config.SAML2_EP_BASE)
metagen.add_sp_descriptor(metadata_map, metadata_options)
return str(metagen)
def metadata(self, env, values, request, response):
title='metadata'
headers = HTTPHeader({'Content-Type': ['text/xml']})
return HTTPResponse(200, 'Found', headers,
get_metadata())

21
mandaye/session_utils.py Normal file
View File

@ -0,0 +1,21 @@
def validate_session(env):
env['beaker.session']['validated'] = True
def invalidate_session(env):
env['beaker.session']['validated'] = False
def is_session_valid(env):
if 'validated' in env['beaker.session'] and \
env['beaker.session']['validated']:
return True
return False
def set_item_in_session(env, key, value):
env['beaker.session'][key] = value
def get_item_in_session(env, key):
return env['beaker.session'].get(key)
def del_item_in_session(env, key):
if key in env['beaker.session']:
del env['beaker.session'][key]

View File

@ -0,0 +1,498 @@
/* theme derived and inspired by TerraFirma
* <http://www.oswd.org/design/information/id/3557/>
*/
html, body {
margin: 0;
font-family: sans-serif;
font-size: 12px;
}
body#iframe {
background: white;
}
html {
background: #F9F9F7 url(../images/a1.gif) repeat-x;
color: #44b2cb;
}
a
{
color: #44b2cb;
text-decoration: underline;
}
a:hover
{
text-decoration: none;
}
div#wrap {
background: white;
width: 640px;
margin: 5em auto;
padding: 15px;
-moz-border-radius: 6px;
-webkit-border-radius:6px;
-moz-box-shadow: 0 0 4px rgba(0,0,0,0.75);
-webkit-box-shadow: 0 0 4px rgba(0,0,0,0.75);
position: relative;
}
#header
{
position: absolute;
background: url(../images/a8.png) repeat-x;
-moz-border-radius: 6px 0 0 6px;
-webkit-border-radius: 6px 0 0 6px;
width: 450px;
height: 92px;
color: #fff;
padding-left: 20px;
}
#header h1
{
font-size: 23px;
letter-spacing: -1px;
padding-top: 30px;
margin: 0;
}
#header span
{
margin: 0;
font-size: 13px;
font-weight: normal;
color: #FCE2CA;
}
#splash
{
position: absolute;
right: 20px;
background: url(../images/eo.png) no-repeat;
width: 153px;
height: 92px;
-moz-border-radius: 0 6px 6px 0;
-webkit-border-radius: 0 6px 6px 0;
}
div#content {
margin: 1em 1ex;
margin-top: 130px;
padding: 1ex;
}
div#content h2 {
margin-top: 0;
font-weight: normal;
color: #656551;
font-size: 18px;
letter-spacing: -1px;
line-height: 25px;
margin-bottom: 20px;
padding: 0 0 10px 15px;
position: relative;
top: 4px;
background: url(../images/a22.gif) bottom repeat-x;
}
#footer
{
font-size: 70%;
position: relative;
clear: both;
height: 66px;
text-align: center;
line-height: 66px;
background-image: url(../images/a8.png);
color: #fff;
}
#footer a
{
color: #8C8C73;
}
form#login-form p {
float: left;
width: 40%;
}
form#login-form input.submit {
float: right;
width: 18%;
margin-top: 30px;
}
div.login-actions {
clear: both;
padding-top: 1em;
}
div.login-actions p {
margin: 0;
}
form p {
margin: 0 0 1em 0;
}
form p label {
display: block;
}
form p input,
form p textarea {
margin-left: 10px;
}
ul.messages {
margin: 0;
padding: 0;
list-style: none;
}
ul.messages li.error {
color: #e80404;
}
ul.errorlist {
margin: 0;
padding: 0;
color: #e80404;
list-style: none;
}
input, textarea {
padding: 5px;
border: 1px solid #cccccc;
color:#666666;
background: white;
color: black;
}
textarea:focus, input[type="text"]:focus, input[type="password"]:focus {
border: 1px solid #4690d6;
color:#333333;
}
input[type=submit] {
color: #ffffff;
background:#4690d6;
border: 1px solid #2a567f;
font-weight: bold;
padding: 2px 8px 2px 8px;
margin: 0;
cursor: pointer;
}
input[type=submit]:hover {
border-color: #0e1d2b;
}
form#login-form ul.errorlist {
margin-bottom: 1em;
width: 80%;
font-weight: normal;
}
/* OpenID Stuff */
#openid_btns, #openid_btns br {
clear: both;
}
#openid_highlight a {
border: 1px solid #888;
}
#openid_input_area input[type=submit] {
padding-top: 0;
margin-top: 0;
margin-left: 1em;
}
.openid_large_btn {
width: 100px;
height: 60px;
border: 1px solid #DDD;
margin: 3px;
float: left;
}
.openid_small_btn {
width: 24px;
height: 24px;
border: 1px solid #DDD;
margin: 3px;
float: left;
}
a.openid_large_btn:focus {
outline: none;
}
a.openid_large_btn:focus {
-moz-outline-style: none;
}
.openid_selected {
border: 4px solid #DDD;
}
#openid_input_area {
clear: both;
padding-top: 2.5em;
}
li.indented {
margin-left: 50px;
}
ul.NoBullet {
list-style-type: none;
}
div#content h4 {
margin-bottom: 5px;
margin-top: 30px;
}
div#content p {
margin-top: 0;
}
div.errors {
margin: 0;
padding: 0;
color: #e80404;
list-style: none;
}
div#breadcrumb {
font-size: 80%;
margin-bottom: 1em;
}
div#user {
position: absolute;
top: 115px;
right: 12px;
}
a#logout {
font-size: 100%;
}
.ui-tabs .ui-tabs-hide {
display: none;
}
h4 {
padding-left: 0.5em;
}
h4 + div, div#profile {
padding-left: 1em;
}
div#menu {
position: relative;
background: #46461F url(../images/a17.gif) repeat-x;
height: 67px;
padding: 0px 20px 0px 5px;
margin: 136px 0px 0px 0px;
}
#menu ul
{
padding: 0;
margin: 0;
}
#menu ul li
{
display: inline;
line-height: 52px;
padding-left: 3px;
}
#menu ul li.first
{
border-left: 0px;
}
#menu ul li a
{
background-color: transparent;
background-repeat: repeat-x;
padding: 8px 12px 8px 12px;
font-size: 12px;
color: #fff;
font-weight: bold;
}
#menu ul li a:hover
{
background: #fff url(../images/a18.gif) repeat-x top;
color: #4A4A24;
text-decoration: none;
}
#eo
{
position: absolute;
top: 0px;
line-height: 52px;
color: #BDBDA2;
right: 30px;
font-weight: bold;
font-size: 12px;
letter-spacing: -1px;
}
#eo a {
color: inherit;
text-decoration: none;
}
ul#tab-nav {
list-style: none;
padding: 0;
width: 160px;
float: left;
}
ul#tab-nav li {
line-height: 300%;
position: relative;
right: -1px;
border: 1px solid transparent;
}
ul#tab-nav li.ui-tabs-selected {
border: 1px solid #ccc;
border-right: 1px solid white;
}
ul#tab-nav a {
display: block;
padding-left: 1ex;
outline: none;
-moz-user-focus:ignore;
}
ul#tab-nav a:hover {
}
ul#tab-nav a:active {
}
/* XXX: add a class to divs, so it works in IE */
div#tabs > div {
border: 1px solid #ccc;
float: left;
width: 420px;
padding: 10px;
min-height: 26em;
}
a.bigbutton {
display: block;
-moz-border-radius: 6px;
-webkit-border-radius:6px;
border: 1px solid black;
margin: 2em 0;
line-height: 300%;
text-align: center;
text-decoration: none;
font-weight: bold;
-webkit-box-shadow: 0 0 4px rgba(0,0,0,0.75);
-moz-box-shadow: 0 0 4px rgba(0,0,0,0.75);
}
a.bigbutton:hover {
background: #eee;
}
div#providers {
display: none;
}
#modalOverlay {
height:100%;
width:100%;
position:fixed;
left:0;
top:0;
z-index:3000;
background-color: rgba(0, 0, 0, 0.8);
cursor:wait;
}
div#popup {
display: none;
position:fixed;
width:500px;
left:50%;
margin-left:-250px;
z-index:3100;
top: 10%;
}
div#popup div {
position: relative;
margin: 0;
background: white;
border: 1px solid black;
border-color: #333 black black #333;
}
div#popup h2 {
text-align: center;
}
div#popup ul {
max-height: 70px;
overflow: auto;
margin: 0 1em 1em 1em;
padding: 0 1em 1em 1em;
}
div#popup h3 {
margin-bottom: 4px;
padding-left: 10px;
}
div#popup p {
margin: 5px;
}
div#popup a#close {
float: right;
padding: 1ex;
}
a.roleid_button {
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
background: #5C5C5C;
color: #44b2cb;
font-weight: bold;
padding-top: 5px;
padding-bottom: 5px;
padding-right: 10px;
padding-left: 10px;
margin: 0;
cursor: pointer;
text-decoration: none;
}
a.roleid_button:hover {
background: black;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 781 B

View File

@ -11,5 +11,4 @@ def serve_template(templatename, **kwargs):
""" serve a template
"""
mytemplate = mylookup.get_template(templatename)
return mytemplate.render_unicode(**kwargs)
return mytemplate.render_unicode(**kwargs).encode('utf-8', 'replace')

View File

@ -0,0 +1,31 @@
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html>
<head>
<link rel="stylesheet" href="/static/css/style.css" />
<title>${title}</title>
</head>
<body>
<div id="wrap">
<div id="header">
<h1>Mandaye</h1>
<span>seamless SSO...</span>
</div>
<div id="splash"></div>
<div id="content">
<h1>${title}</h1>
<p><a href='logout'>Logout</a></p>
% if nameid and issuer:
<p>You are known as <strong>${nameid}</strong> from <strong>${issuer}</strong></p>
% if attributes:
<p>What we now about you is <strong>${attributes}</strong></p>
% endif
% else:
<p>The user is logged but the user nameid en issuer id are missing.</p>
% endif
</div>
<div id="footer">
Copyright &copy; 2013 Entr'ouvert
</div>
</div>
</body>
</html>

View File

@ -1,10 +1,24 @@
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html>
<head>
<title>${title}</title>
<head>
<link rel="stylesheet" href="/static/css/style.css" />
<title>${title}</title>
</head>
<body>
<h1>${title}</h1>
<p>${body}</p>
<body>
<div id="wrap">
<div id="header">
<h1>Mandaye</h1>
<span>seamless SSO...</span>
</div>
<div id="splash"></div>
<div id="content">
<h1>${title}</h1>
<p>${body}</p>
<p><a href='/'>Back to home</a></p>
</div>
<div id="footer">
Copyright &copy; 2013 Entr'ouvert
</div>
</div>
</body>
</html>
</html>

View File

@ -0,0 +1,10 @@
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html>
<head>
<title>${title}</title>
</head>
<body>
<h1>${title}</h1>
<p>${body}</p>
</body>
</html>

3
mandaye/utils.py Normal file
View File

@ -0,0 +1,3 @@
def get_absolute_uri(env):
return env['mandaye.scheme'] + '://' + env['HTTP_HOST'] + \
env['PATH_INFO']

43
mandaye/views.py Normal file
View File

@ -0,0 +1,43 @@
import config
import session_utils
import saml2_endpoints
from http import HTTPResponse, HTTPHeader
from template import serve_template
from mandaye.log import logger
def homepage(env):
title='Mandaye homepage'
headers = HTTPHeader({'Content-Type': ['text/html']})
if not config.SAML2_SUPPORT:
return HTTPResponse(200, 'Found', headers,
serve_template("response.html",
title=title, body='SAML2 not available'))
if not session_utils.is_session_valid(env):
return saml2_endpoints.sso(env)
attributes = session_utils.get_item_in_session(env, 'attributes')
nameid = None
issuer = None
if attributes:
nameid = attributes.get('__nameid')
issuer = attributes.get('__issuer')
return HTTPResponse(200, 'Found', headers,
serve_template("homepage.html",
title=title, nameid=nameid, issuer=issuer, attributes=attributes))
def logout(env):
if not session_utils.is_session_valid(env):
result = "You were not loggeg in."
else:
session_utils.invalidate_session(env)
result = "You are now logged out."
title='Logout page'
headers = HTTPHeader({'Content-Type': ['text/html']})
return HTTPResponse(200, 'Found', headers,
serve_template("response.html",
title=title, body=result))

View File

@ -65,10 +65,10 @@ def main():
repository=mandaye.migration.__path__[0])
logger.info("Database created")
if options.cryptpwd:
from mandaye.config.backend import ManagerSPUserSQL
for user in ManagerSPUserSQL.all():
from mandaye.config.backend import ManagerSPUser
for user in ManagerSPUser.all():
user.password = encrypt_pwd(user.password)
ManagerSPUserSQL.save()
ManagerSPUser.save()
if __name__ == "__main__":
main()