diff --git a/mandaye/__init__.py b/mandaye/__init__.py index 6634d0e..53649f8 100644 --- a/mandaye/__init__.py +++ b/mandaye/__init__.py @@ -1,4 +1,4 @@ -VERSION=0.4 +__version__='0.8.0' import os diff --git a/mandaye/auth/authform.py b/mandaye/auth/authform.py index 45a5be0..b69fb20 100644 --- a/mandaye/auth/authform.py +++ b/mandaye/auth/authform.py @@ -15,7 +15,7 @@ from datetime import datetime from lxml.html import fromstring from urlparse import parse_qs -from mandaye import config, VERSION +from mandaye import config, __version__ from mandaye.exceptions import MandayeException from mandaye.log import logger from mandaye.http import HTTPResponse, HTTPHeader, HTTPRequest @@ -32,45 +32,41 @@ except ImportError: class AuthForm(object): - def __init__(self, form_values, site_name): + def __init__(self, env, mapper): """ - form_values: dict example : - { - 'login_url': '/login', - 'post_url': '/login', - 'form_attrs': { 'name': 'form40', }, - 'username_field': 'user', - 'password_field': 'pass', - 'post_fields': ['birthdate', 'card_number'] - } - login_url, form_attrs, post_fields and username_field are obligatory - site_name: str with the site name + env: WSGI environment + mapper: mapper's module like mandaye.mappers.linuxfr """ - if not form_values.has_key('form_headers'): - form_values['form_headers'] = { + self.env = env + self.urls = mapper.urls + self.site_name = self.env["mandaye.config"]["site_name"] + self.form_values = mapper.form_values + if not self.form_values.has_key('form_headers'): + self.form_values['form_headers'] = { 'Content-Type': 'application/x-www-form-urlencoded', - 'User-Agent': 'Mozilla/5.0 Mandaye/%s' % VERSION + 'User-Agent': 'Mozilla/5.0 Mandaye/%s' % __version__ } - if not form_values.has_key('post_fields') or \ - not form_values.has_key('username_field') or \ - not form_values.has_key('login_url'): + if not self.form_values.has_key('post_fields') or \ + not self.form_values.has_key('username_field') or \ + not self.form_values.has_key('login_url'): logger.critical("Bad configuration: AuthForm form_values dict must have \ this keys: post_fields and username_field") raise MandayeException, 'AuthForm bad configuration' - if not form_values.has_key('form_attrs') and \ - not form_values.has_key('post_url'): + if not self.form_values.has_key('form_attrs') and \ + not self.form_values.has_key('post_url'): logger.critical("Bad configuration: you must set form_attrs or post_url") - if config.encrypt_secret and not form_values.has_key('password_field'): + if config.encrypt_secret and not self.form_values.has_key('password_field'): logger.critical("Bad configuration: AuthForm form_values dict must have a \ a password_field key if you want to encode a password.") raise MandayeException, 'AuthForm bad configuration' - self.login_url = form_values.get('login_url') - self.form_values = form_values + self.login_url = self.form_values.get('login_url') if not self.form_values.has_key('post_fields'): self.form_values['post_fields'] = [] - self.site_name = site_name + + def get_default_mapping(self): + return [] def encrypt_pwd(self, password): """ This method allows you to encrypt a password @@ -207,7 +203,7 @@ a password_field key if you want to encode a password.") if request.msg: if not unique_id: logger.warning("Association failed: user isn't login on Mandaye") - return _302(values.get('connection_url')) + return _302(self.urls.get('connection_url')) if type(request.msg) == str: post = parse_qs(request.msg, request) else: @@ -221,7 +217,7 @@ a password_field key if you want to encode a password.") if not post.has_key(field): logger.info('Association auth failed: form not correctly filled') qs['type'] = 'badlogin' - return _302(values.get('associate_url') + "?%s" % urllib.urlencode(qs)) + return _302(self.urls.get('associate_url') + "?%s" % urllib.urlencode(qs)) post_values[field] = post[field][0] response = self.replay(env, post_values) if eval(condition): @@ -232,7 +228,7 @@ a password_field key if you want to encode a password.") return response logger.info('Auth failed: Bad password or login') qs['type'] = 'badlogin' - return _302(values.get('associate_url') + "?%s" % urllib.urlencode(qs)) + return _302(self.urls.get('associate_url') + "?%s" % urllib.urlencode(qs)) def _login_sp_user(self, sp_user, env, condition, values): """ Log in sp user @@ -257,7 +253,7 @@ a password_field key if you want to encode a password.") else: return response else: - return _302(values.get('associate_url') + "?type=failed") + return _302(self.urls.get('associate_url') + "?type=failed") def login(self, env, values, condition, request, response): """ Automatic login on a site with a form @@ -282,7 +278,7 @@ a password_field key if you want to encode a password.") 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") + return _302(self.urls.get('associate_url') + "?type=first") return self._login_sp_user(sp_user, env, condition, values) def logout(self, env, values, request, response): @@ -333,7 +329,7 @@ a password_field key if you want to encode a password.") idp_user = backend.ManagerIDPUser.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 _302(self.urls.get('associate_url')) return self._login_sp_user(sp_user, env, 'response.code==302', values) def disassociate(self, env, values, request, response): diff --git a/mandaye/auth/saml2.py b/mandaye/auth/saml2.py index 55e8027..1a241ee 100644 --- a/mandaye/auth/saml2.py +++ b/mandaye/auth/saml2.py @@ -9,87 +9,120 @@ from urlparse import parse_qs from mandaye import config, utils from mandaye.saml import saml2utils from mandaye.auth.authform import AuthForm -from mandaye.exceptions import MandayeSamlException +from mandaye.exceptions import MandayeSamlException, ImproperlyConfigured from mandaye.response import _302, _401 from mandaye.log import logger from mandaye.http import HTTPResponse, HTTPHeader, HTTPRequest from mandaye.server import get_response +""" +Mandaye saml2 authentification support + +To use it you must set the following options into your +virtual host : + * saml2_idp_metadata: a link to your idp metadata + * saml2_signature_public_key: a path to your public key + * saml2_signature_private_key: a path to your private key + +Optional options : + * saml2_sp_logout_url: the url to logout the service provider + * saml2_sp_logout_method: GET or POST + * saml2_authnresp_binding: only post is supported for now + * saml2_authnreq_http_method: only http_redirect at the moment + * saml2_name_identifier_format: only persistent at the moment +""" + +END_POINTS_PATH = { + 'metadata': '/mandaye/metadata', + 'single_sign_on_post': '/mandaye/singleSignOnPost', + 'single_logout': '/mandaye/singleLogout', + 'single_logout_return': '/mandaye/singleLogoutReturn', +} + class SAML2Auth(AuthForm): """ SAML 2 authentification """ - def __init__(self, form_values, site_name, saml2_config): + def __init__(self, env, mapper): """ saml2_config: saml 2 config module + env: WSGI environment + mapper: mapper's module like mandaye.mappers.linuxfr """ - self.config = saml2_config + self.env = env + for param in ('saml2_idp_metadata', + 'saml2_signature_public_key', + 'saml2_signature_private_key'): + if not self.env['mandaye.config'].has_key(param): + err = 'you must set %s option in vhost : %s' % \ + (param, self.env['mandaye.vhost']) + logger.error(err) + raise ImproperlyConfigured, err + public_key = self._get_file_content( + self.env['mandaye.config']['saml2_signature_public_key'] + ) + private_key = self._get_file_content( + self.env['mandaye.config']['saml2_signature_private_key'] + ) + self.config = { + 'saml2_idp_metadata': self.env['mandaye.config']['saml2_idp_metadata'], + 'saml2_signature_public_key': public_key, + 'saml2_signature_private_key': private_key, + 'saml2_sp_logout_url': None, + 'saml2_sp_logout_method': 'GET', + 'saml2_authnresp_binding': lasso.SAML2_METADATA_BINDING_POST, + 'saml2_authnreq_http_method': lasso.HTTP_METHOD_REDIRECT, + 'saml2_name_identifier_format': lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT + } + self.metadata_map = ( ('AssertionConsumerService', lasso.SAML2_METADATA_BINDING_POST , - self.config.END_POINTS_PATH['single_sign_on_post'] + END_POINTS_PATH['single_sign_on_post'] ), ('SingleLogoutService', lasso.SAML2_METADATA_BINDING_REDIRECT, - self.config.END_POINTS_PATH['single_logout'], - self.config.END_POINTS_PATH['single_logout_return']), + END_POINTS_PATH['single_logout'], + END_POINTS_PATH['single_logout_return']), ) - self.metadata_options = { 'key': self.config.SAML_SIGNATURE_PUBLIC_KEY } - super(SAML2Auth, self).__init__(form_values, site_name) + self.metadata_options = { 'key': public_key } + super(SAML2Auth, self).__init__(env, mapper) + def _get_file_content(self, path): + if not os.path.isabs(path): + path = os.path.join(config.config_root, + path) + if not os.path.exists(path): + err = "%s: file %s doesn't exist" % \ + (path, self.env['mandaye.vhost']) + logger.error(err) + raise ImproperlyConfigured, err + with open(path, 'r') as f: + content = f.read() + return content def get_default_mapping(self): return [ { - 'path': r'/mandaye/login$', - 'method': 'GET', - 'response': [{ - 'filter': self.login, - 'values': { - 'associate_url': '/mandaye/associate', - }, - 'condition': 'response.code==302', - },] - }, - { - 'path': r'/mandaye/sso$', - 'method': 'GET', - 'response': [{ - 'filter': self.sso, - }] - }, - { - 'path': r'/mandaye/slo$', - 'method': 'GET', - 'response': [{ - 'filter': self.slo, - }] - }, - { - 'path': r'%s$' % self.config.END_POINTS_PATH['metadata'], + 'path': r'%s$' % END_POINTS_PATH['metadata'], 'method': 'GET', 'response': [{ 'filter': self.metadata, }] }, { - 'path': r'%s$' % self.config.END_POINTS_PATH['single_sign_on_post'], + 'path': r'%s$' % END_POINTS_PATH['single_sign_on_post'], 'method': 'POST', - 'response': [{ - 'filter': self.single_sign_on_post, - 'values': { - 'login_url': '/mandaye/login', - } - }] + 'response': [{'filter': self.single_sign_on_post}] }, { - 'path': r'%s$' % self.config.END_POINTS_PATH['single_logout'], + 'path': r'%s$' % END_POINTS_PATH['single_logout'], 'method': 'GET', 'response': [{ 'filter': self.single_logout, }] }, { - 'path': r'%s$' % self.config.END_POINTS_PATH['single_logout_return'], + 'path': r'%s$' % END_POINTS_PATH['single_logout_return'], 'method': 'GET', 'response': [{ 'filter': self.single_logout_return, @@ -112,7 +145,7 @@ class SAML2Auth(AuthForm): next_url = values['next_url'] req_cookies = request.cookies - if not self.config.SP_LOGOUT_URL: + if not self.config['saml2_sp_logout_url']: logger.warning('SP_LOGOUT_URL not set into saml2 configuration only removing cookies') for cookie in req_cookies.values(): cookie['expires'] = 'Thu, 01 Jan 1970 00:00:01 GMT' @@ -121,12 +154,12 @@ class SAML2Auth(AuthForm): return _302(next_url, req_cookies) else: return _302('/', req_cookies) - if self.config.SP_LOGOUT_METHOD == 'POST': + if self.config['saml2_sp_logout_method'] == 'POST': headers = HTTPHeader({'Content-Type': ['application/x-www-form-urlencoded']}) else: headers = HTTPHeader() - request = HTTPRequest(req_cookies, headers, self.config.SP_LOGOUT_METHOD) - response = get_response(env, request, self.config.SP_LOGOUT_URL) + request = HTTPRequest(req_cookies, headers, self.config['saml2_sp_logout_method']) + response = get_response(env, request, self.config['saml2_sp_logout_url']) if next_url: return _302(next_url, response.cookies) else: @@ -134,31 +167,31 @@ class SAML2Auth(AuthForm): def _get_idp_metadata_file_path(self): metadata_file_path = None - if self.config.IDP_METADATA: + if self.config['saml2_idp_metadata']: metadata_file_path = os.path.join(config.data_dir, - self.config.IDP_METADATA.\ + self.config['saml2_idp_metadata'].\ replace('://', '_').\ replace('/', '_') ) if not os.path.isfile(metadata_file_path): try: - response = urllib2.urlopen(self.config.IDP_METADATA) + response = urllib2.urlopen(self.config['saml2_idp_metadata']) metadata = response.read() response.close() except Exception, e: logger.error("Unable to fetch metadata %s: %s" % \ - (self.config.IDP_METADATA, str(e))) + (self.config['saml2_idp_metadata'], str(e))) raise MandayeSamlException("Unable to find metadata: %s" % str(e)) - metadata_file = open(metadata_file_path, 'a+') + metadata_file = open(metadata_file_path, 'w') metadata_file.write(metadata) metadata_file.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'] + metadata_path = END_POINTS_PATH['metadata'] single_sign_on_post_path = \ - self.config.END_POINTS_PATH['single_sign_on_post'] + 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) @@ -168,15 +201,15 @@ class SAML2Auth(AuthForm): if env['beaker.session'].has_key('unique_id'): return _302('/') qs = parse_qs(env['QUERY_STRING']) - target_idp = self.config.IDP_METADATA + target_idp = self.config['saml2_idp_metadata'] metadata_file_path = self._get_idp_metadata_file_path() if not metadata_file_path: raise MandayeSamlException("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: metadata url is %s' % self.config['saml2_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) + self.config['saml2_signature_private_key']) if not server: raise MandayeSamlException("sso: error creating server object.") logger.debug('sso: mandaye server object created') @@ -184,15 +217,15 @@ class SAML2Auth(AuthForm): login = lasso.Login(server) if not login: raise MandayeSamlException("sso: error creating login object.") - http_method = self.config.AUTHNREQ_HTTP_METHOD + http_method = self.config['saml2_authnreq_http_method'] try: login.initAuthnRequest(target_idp, http_method) except lasso.Error, error: raise MandayeSamlException("sso: Error initiating request %s" % lasso.strError(error[0])) - 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 + login.request.nameIDPolicy.format = self.config['saml2_name_identifier_format'] + login.request.nameIDPolicy.allowCreate = True + login.request.nameIDPolicy.spNameQualifier = None + login.request.protocolBinding = self.config['saml2_authnresp_binding'] if qs.has_key('next_url'): login.msgRelayState = qs['next_url'][0] try: @@ -213,7 +246,7 @@ class SAML2Auth(AuthForm): if not metadata_file_path: raise MandayeSamlException("single_sign_on_post: Unable to load provider") server = lasso.Server.newFromBuffers(self._get_metadata(env), - self.config.SAML_SIGNATURE_PRIVATE_KEY) + self.config['saml2_signature_private_key']) if not server: raise MandayeSamlException("singleSignOnPost: error creating server object") server.addProvider(lasso.PROVIDER_ROLE_IDP, metadata_file_path) @@ -230,11 +263,7 @@ class SAML2Auth(AuthForm): raise MandayeSamlException("singleSignOnPost: Missing response") message = params[lasso.SAML2_FIELD_RESPONSE][0] logger.debug('sso: message posted %s' % message) - - try: - login.processAuthnResponseMsg(message) - except: - raise MandayeSamlException("singleSignOnPost: Unable to proces authnresponse message") + login.processAuthnResponseMsg(message) subject_confirmation = utils.get_absolute_uri(env) if not env['beaker.session'].has_key('request_id'): @@ -268,16 +297,16 @@ class SAML2Auth(AuthForm): next_url = values['next_url'] if next_url: - return _302("%s?next_url=%s" % (values['login_url'], next_url)) + return _302("%s?next_url=%s" % (self.urls.get('login_url'), next_url)) else: - return _302(values['login_url']) + return _302(self.urls.get('login_url')) def slo(self, env, values, request, response): """ Single Logout SP initiated by redirected """ logger.debug('slo: new slo request') - target_idp = self.config.IDP_METADATA + target_idp = self.config['saml2_idp_metadata'] metadata_file_path = self._get_idp_metadata_file_path() if not metadata_file_path: raise MandayeSamlException("slo: Unable to load provider.") @@ -285,7 +314,7 @@ class SAML2Auth(AuthForm): logger.debug('slo: metadata file path %s' % metadata_file_path) server = lasso.Server.newFromBuffers(self._get_metadata(env), - self.config.SAML_SIGNATURE_PRIVATE_KEY) + self.config['saml2_signature_private_key']) if not server: raise MandayeSamlException("slo: Error creating server object") logger.debug('slo: mandaye server object created') @@ -339,7 +368,7 @@ class SAML2Auth(AuthForm): Redirect without query string") server = lasso.Server.newFromBuffers(self._get_metadata(env), - self.config.SAML_SIGNATURE_PRIVATE_KEY) + self.config['saml2_signature_private_key']) if not server: logger.warning('single_logout: Service provider not configured') return _401('single_logout: Service provider not configured') @@ -378,7 +407,7 @@ class SAML2Auth(AuthForm): """ metadata_file_path = self._get_idp_metadata_file_path() server = lasso.Server.newFromBuffers(self._get_metadata(env), - self.config.SAML_SIGNATURE_PRIVATE_KEY) + self.config['saml2_signature_private_key']) server.addProvider(lasso.PROVIDER_ROLE_IDP, metadata_file_path) if not server: logger.error('single_logout_return: Error creating server object.') diff --git a/mandaye/configs/saml2.py b/mandaye/configs/saml2.py deleted file mode 100644 index 2a97747..0000000 --- a/mandaye/configs/saml2.py +++ /dev/null @@ -1,79 +0,0 @@ - -import lasso - -# END_POINTS -END_POINTS_PATH = { - 'metadata': '/mandaye/metadata', - 'single_sign_on_post': '/mandaye/singleSignOnPost', - 'single_logout': '/mandaye/singleLogout', - 'single_logout_return': '/mandaye/singleLogoutReturn', -} - -# 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 - -## Configure local SP logout -# If None remove cookies to logout -SP_LOGOUT_URL = None -SP_LOGOUT_METHOD = 'GET' - -# 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-----''' - diff --git a/mandaye/dispatcher.py b/mandaye/dispatcher.py index 7e27fe4..c243a16 100644 --- a/mandaye/dispatcher.py +++ b/mandaye/dispatcher.py @@ -1,4 +1,5 @@ +import copy import re from urlparse import urlparse @@ -12,21 +13,18 @@ from mandaye.exceptions import ImproperlyConfigured # TODO: add an external url mapping -def import_mapping(path): - if not path or not '.' in path: - return path - i = path.rfind('.') - module, attr = path[:i], path[i+1:] - +def import_mapping(name): + if not name: + return dict() + if not config.mappers.has_key(name): + logger.error("mapper %s not found" % name) + return dict() + module = config.mappers[name] try: - mod = import_module(module) + mapper = import_module(module) except ImportError, e: - raise ImproperlyConfigured('Error importing mapping %s: "%s"' % (path, e)) - try: - mapping = getattr(mod, attr) - except AttributeError: - raise ImproperlyConfigured('Module "%s" does not define a "%s" mapping' % (module, attr)) - return mapping + raise ImproperlyConfigured('Error importing mapping %s: "%s"' % (module, e)) + return mapper class Dispatcher(object): @@ -34,17 +32,27 @@ class Dispatcher(object): It allows you to launch the right filter on the reqest and the response """ - def __init__(self, env, target_url, mapping=dict()): + def __init__(self, env, target_url, mapper_name=None): """ env: wsgi environ target_url: the full url of your destination - mapping: dict with the site mapping + mepper: python module with the mapper """ self.target = urlparse(target_url) self.env = env self.env['target'] = self.target - self.mapping = import_mapping(mapping) + self.mapper_name = mapper_name + self.mapper = import_mapping(mapper_name) self.filter = MandayeFilter(self.env, self.target) - self.req_mapping = self._parse_mapping(self.mapping) + auth_type = self.env['mandaye.config']['auth_type'] + path = config.authentifications[auth_type] + i = path.rfind('.') + module, attr = path[:i], path[i+1:] + module = import_module(module) + Auth = getattr(module, attr) + self.auth = Auth(env, self.mapper) + mapping = copy.deepcopy(self.mapper.mapping) + mapping.extend(self.auth.get_default_mapping()) + self.req_mapping = self._parse_mapping(mapping) def __get_mappings_hooks(self, mapper, req_mapping): """ fill the request mapping with the right hooks @@ -55,7 +63,10 @@ class Dispatcher(object): for hookname in req_mapping: if mapper.has_key(hookname): if isinstance(req_mapping[hookname], list): - req_mapping[hookname].extend(mapper[hookname]) + for entry in mapper[hookname]: + if entry.has_key('auth'): + entry['filter'] = getattr(self.auth, entry['auth']) + req_mapping[hookname].append(entry) else: req_mapping[hookname] = mapper[hookname] return req_mapping @@ -67,7 +78,7 @@ class Dispatcher(object): req_mapping = { 'on_request': [], 'on_response': [], - 'response': None, + 'response': [], 'target': None, 'redirect': None, 'decompress': config.auto_decompress @@ -75,15 +86,15 @@ class Dispatcher(object): if not mapping: return req_mapping - for mapper in mapping: - if mapper.has_key('path'): - if isinstance(mapper['path'], str): - if re.match(mapper['path'], self.env['PATH_INFO']): - req_mapping = self.__get_mappings_hooks(mapper, req_mapping) + for entry in mapping: + if entry.has_key('path'): + if isinstance(entry['path'], str): + if re.match(entry['path'], self.env['PATH_INFO']): + req_mapping = self.__get_mappings_hooks(entry, req_mapping) else: - for path in mapper['path']: + for path in entry['path']: if re.match(path, self.env['PATH_INFO']): - req_mapping = self.__get_mappings_hooks(mapper, req_mapping) + req_mapping = self.__get_mappings_hooks(entry, req_mapping) else: logger.warning('Config error: you need to specify paths in your mapping') return req_mapping diff --git a/mandaye/filters/default.py b/mandaye/filters/default.py index da7b124..9794337 100644 --- a/mandaye/filters/default.py +++ b/mandaye/filters/default.py @@ -41,7 +41,7 @@ class MandayeFilter(object): if key == 'location': location = value[0].replace(self.target.geturl(), "%s://%s" % (self.env['mandaye.scheme'], self.env["HTTP_HOST"])) - response.headers.addheader(key, location) + response.headers['location'] = [location] for name in blacklist: if response.headers.has_key(name): del response.headers[name] diff --git a/mandaye/global_config.py b/mandaye/global_config.py index d7f8192..f4da5c7 100644 --- a/mandaye/global_config.py +++ b/mandaye/global_config.py @@ -9,13 +9,11 @@ storage_backend = "mandaye.backends.sql" ## SQL Backend config # Database configuration -# rfc 1738 http://rfc.net/rfc1738.html +# http://docs.sqlalchemy.org/en/rel_0_7/core/engines.html +# rfc 1738 https://tools.ietf.org/html/rfc1738 +# dialect+driver://username:password@host:port/database db_url = 'sqlite:///test.db' -# Needed if ssl is activated -ssl = False -keyfile = '' -certfile = '' # Log configuration import logging @@ -34,6 +32,8 @@ log_backup = 52 # Template directory template_directory = os.path.join(_PROJECT_PATH, 'mandaye/templates') +# Configuration directory +config_root = os.path.join(_PROJECT_PATH, 'conf.d') # Static path static_url = '/mandaye/static' # Static folder @@ -64,11 +64,17 @@ encrypt_sp_password = False # Must be a 16, 24, or 32 bytes long encrypt_secret = '' -hosts = {} - alembic_cfg = os.path.join(_PROJECT_PATH, 'mandaye/alembic.ini') alembic_script_path = os.path.join(_PROJECT_PATH, 'mandaye/alembic') +# supported authentification +authentifications = { + 'saml2': 'mandaye.auth.SAML2Auth' +} + +# supported mappers +mappers = {} + # beaker session configuration session_opts = { 'session.type': 'file', @@ -77,3 +83,8 @@ session_opts = { 'session.data_dir': '/var/tmp/beaker' } +# Needed if ssl is activated +ssl = False +keyfile = '' +certfile = '' + diff --git a/mandaye/http.py b/mandaye/http.py index 795aacd..5a61e82 100644 --- a/mandaye/http.py +++ b/mandaye/http.py @@ -33,10 +33,7 @@ class HTTPHeader(dict): def addheader(self, name, value): """ name: field name value: string with the field value """ - if isinstance(value, str): - self[name.lower()] = [value] - else: - raise TypeError('Header value must be a string') + self[name.lower()] = [value] def delheader(self, name): """ name: field name diff --git a/mandaye/configs/__init__.py b/mandaye/mappers/__init__.py similarity index 100% rename from mandaye/configs/__init__.py rename to mandaye/mappers/__init__.py diff --git a/mandaye/server.py b/mandaye/server.py index 4d70bef..2b614df 100644 --- a/mandaye/server.py +++ b/mandaye/server.py @@ -1,22 +1,17 @@ import Cookie +import json import urllib2 import random import re import os -import sys import time -import traceback -from beaker.middleware import SessionMiddleware -from cgi import escape from md5 import md5 from urlparse import urlparse -from static import Cling - -import mandaye from mandaye import config +from mandaye.exceptions import ImproperlyConfigured from mandaye.dispatcher import Dispatcher from mandaye.log import logger, format_logging_handlers from mandaye.handlers.default import MandayeRedirectHandler, MandayeErrorHandler @@ -84,7 +79,6 @@ class MandayeApp(object): self.env = None self.dispatcher = None - def __call__(self, env, start_response): """ called by the WSGI server env: standard WSGI env @@ -106,23 +100,39 @@ class MandayeApp(object): (self.env['REMOTE_ADDR'], self.env['REQUEST_METHOD'], self.env['wsgi.url_scheme'], self.env['HTTP_HOST'], self.env['PATH_INFO'])) - if config.hosts.has_key(local_host): - for mapper in config.hosts[local_host]: - if re.match(mapper.get('path'), path_info): - if mapper.get('force_ssl'): + for conf in os.listdir(config.config_root): + conf_file = os.path.join(config.config_root, conf) + if os.path.isfile(conf_file): + with open(conf_file, 'r') as f: + conf = json.loads(f.read()) + for param in ['site_name', 'location', 'target', 'server_name', 'mapper', 'auth_type']: + if not conf.has_key(param): + error = 'you must set %s option in vhost : %s' % \ + (param, conf_file) + logger.error(error) + raise ImproperlyConfigured, error + if not config.mappers.has_key(conf['mapper']): + err = '%s: mapper %s not found' % \ + (conf_file, conf['mapper']) + logger.error(err) + raise ImproperlyConfigured, err + self.env['mandaye.auth_type'] = conf['mapper'] + if not config.authentifications.has_key(conf['auth_type']): + err = '%s: authentification %s not found' % \ + (conf_file, conf['auth_type']) + logger.error(err) + raise ImproperlyConfigured, err + if local_host in conf['server_name'] and \ + re.match(re.compile(conf['location']), path_info): + if conf.get('force_ssl'): self.env['mandaye.scheme'] = 'https' - if mapper.has_key('target'): - self.dispatcher = Dispatcher(self.env, mapper.get('target'), - mapper.get('mapping')) - elif mapper.has_key('static'): - env['PATH_INFO'] = re.sub('^' + mapper.get('path'), '', - env['PATH_INFO']) - response = Cling(mapper.get('static')).__call__(env, start_response) + self.env['mandaye.config'] = conf + self.env['mandaye.vhost'] = conf_file + self.dispatcher = Dispatcher(self.env, conf['target'], + conf['mapper']) + response = self.on_request(start_response) if not response: - if self.dispatcher: - response = self.on_request(start_response) - else: - response = self.on_response(start_response, _404(env['PATH_INFO'])) + response = self.on_response(start_response, _404(env['PATH_INFO'])) sql_session.commit() except Exception, e: sql_session.rollback() @@ -186,7 +196,7 @@ class MandayeApp(object): self.env['REQUEST_METHOD'] = request.req_method self.env['wsgi.input'] = request.msg self.dispatcher = Dispatcher(self.env, self.env['target'].geturl(), - self.dispatcher.mapping) + self.dispatcher.mapper_name) response = self.dispatcher.get_response(request) else: response = get_response(self.env, request, request.target) diff --git a/mandaye/skel/conf.d/certs/saml.crt b/mandaye/skel/conf.d/certs/saml.crt new file mode 100644 index 0000000..683ba7e --- /dev/null +++ b/mandaye/skel/conf.d/certs/saml.crt @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxdbs+ZLkuz0DISpAKhHn +WvNBSW4G0xmlUyZcjUWDQlJH7wC3yxhjioQ2oFpxqcuNf5ft/E1E5KUTqZhcKyX9 +i7XCmhPoea/fmYH3Egxbucv7++sM+TyZpUWbA0TZHBYAjcUPR/1HTcEz3bl0SqB0 +EdjhN5PpXPu1p4pGDPXc4aIkEpFU3mlK+TlV5SrivEqNS/SI14VA9g2WWdJk4+CK +PgozCfeiFtaiu2zem4uQSmd5AG0f0Av4jzxgut22owFYi9PV+Yl0cWoMOUphAwsR +RE4gckEqbhLYluAy+VglgzfT4YCXBQ6o23EH0Z0tW28KnIYEY4dQkLca9YRAKhHc +ywIDAQAB +-----END PUBLIC KEY----- diff --git a/mandaye/skel/conf.d/certs/saml.key b/mandaye/skel/conf.d/certs/saml.key new file mode 100644 index 0000000..12c4a0a --- /dev/null +++ b/mandaye/skel/conf.d/certs/saml.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAxdbs+ZLkuz0DISpAKhHnWvNBSW4G0xmlUyZcjUWDQlJH7wC3 +yxhjioQ2oFpxqcuNf5ft/E1E5KUTqZhcKyX9i7XCmhPoea/fmYH3Egxbucv7++sM ++TyZpUWbA0TZHBYAjcUPR/1HTcEz3bl0SqB0EdjhN5PpXPu1p4pGDPXc4aIkEpFU +3mlK+TlV5SrivEqNS/SI14VA9g2WWdJk4+CKPgozCfeiFtaiu2zem4uQSmd5AG0f +0Av4jzxgut22owFYi9PV+Yl0cWoMOUphAwsRRE4gckEqbhLYluAy+VglgzfT4YCX +BQ6o23EH0Z0tW28KnIYEY4dQkLca9YRAKhHcywIDAQABAoIBAHS7XPXhW36zAD64 +XEW2bKj4cOQvvG0ga7EFKITeqBUg0XrPFKMMD+eyHT0+QGSsSyAm9+/vc5/pWxGt +aWy4LMMbiug4qOnsAOXljm+ixRh6qIK67Nu+ivW+fTlPjT8KKGd+B4c1hbX2MnE4 +NMq3o+TH8BNH/eC0UDm715tcEmk6pUSBH3lq3CG7W1TyVjC3FGJcjBAj/X6J45lE +skJHt9d67KG/MwmzuyoI+U9q2b3jSzoIGzzQQaOItGx3OefRjqWeUyDlUWobuFNV +Lky+XjmOFJC0voQsUiV2mBSJejHmfuLjJfE+W/HrRc3YwftxCp+emaFshs56U4Ob +UWu2F9kCgYEA7livJ1nYhHVyYueX6kWKTkBCzcwQO0agLsuYpspDjKGqgUOlFHXW +9CS+DPi/r086iRYLwmGuaFAnNQJqS3ofjowj9/iZCGD/qe6jj9zMmokWDl1FALYe +jT3Eg1HLfhe8hddA815yheL5uIVw3t34TTaQuokN86nkcv/bJ53SW4UCgYEA1H4v +jk88pCNnADqmAnXNbuhPK+w6llre159vtStgKaJrcCZiTejFVpffpdp1b8hU21S2 +lg/FgXHgvrdfwq+uZ+lRNJGyCX3mqe3uXWn6d42A/7tgmRDW4NXtxwelV8MTpwHw +nS4hwmDyLyYMupyBlw5Iv7N3XmDBJu/tsEPMgA8CgYBP5MpRlnxNalD9dkQl80l5 +EXFTKqQGOpZXGUgCIKqj6U0OJ26efSGglPBfyMH4McadTRaEAdpEfRmnWzfmNPl+ +/trPtDUX6evJOoT5JDoxUuJhzkHjCykSjzHgEvrzOWGoO486BN6+omayw4giLKWe +vDunS2mx07EQG1OK5AwvQQKBgCZY21YwQH5SkTz+WIUrIza3n8oKaIxHu91nvW4R +dNouoHrtwmHS9wHoiIjSwsy4d2/ZetXb5MW2eluQlix5Ld08wtXc0SdbXCwgbxrW +jEfU9omwE/+rhUuv76gyXglXgA1skTKcZ6U/f5U4paVrpwtOnZxS0+DpTxIqzFc5 +9QbLAoGAeqLr0vm4SKnvtwK9F/Q784Rc8Ygq56vUcQIZ81yL4BsE0h6fuTHcSq+H +NhO5mQFr+CcitGDE48/CRxfw1HYpk+KOtRzY+EdKGAKEu26sUSh7GNCw3TkOvPTo +E/RgydWsPwjJBDp03z87cITfaoyqoIWLtEmUTeDY8m5dGu0EBzk= +-----END RSA PRIVATE KEY----- diff --git a/mandaye/skel/conf.d/linuxfr_saml_example b/mandaye/skel/conf.d/linuxfr_saml_example new file mode 100644 index 0000000..32182cc --- /dev/null +++ b/mandaye/skel/conf.d/linuxfr_saml_example @@ -0,0 +1,11 @@ +{{ + "site_name": "linuxfr", + "server_name": ["linuxfrsaml.local:8000"], + "location": "/", + "target": "https://linuxfr.org", + "mapper": "linuxfr", + "auth_type": "saml2", + "saml2_idp_metadata": "http://www.identity-hub.com/idp/saml2/metadata", + "saml2_signature_public_key": "certs/saml.crt", + "saml2_signature_private_key": "certs/saml.key" +}} diff --git a/mandaye/skel/example.module/__init__.py b/mandaye/skel/example.module/__init__.py index 1bb629e..40692a7 100644 --- a/mandaye/skel/example.module/__init__.py +++ b/mandaye/skel/example.module/__init__.py @@ -1 +1 @@ -__version__="0.1" +__version__="0.1.0" diff --git a/mandaye/skel/example.module/config.py b/mandaye/skel/example.module/config.py index 2b9c47c..e062998 100644 --- a/mandaye/skel/example.module/config.py +++ b/mandaye/skel/example.module/config.py @@ -3,15 +3,12 @@ import os _PROJECT_PATH = os.path.join(os.path.dirname(__file__), '..') -## Virtual hosts configuration -hosts = {{}} - ## SQL Backend config # Database configuration # http://docs.sqlalchemy.org/en/rel_0_7/core/engines.html # rfc 1738 https://tools.ietf.org/html/rfc1738 # dialect+driver://username:password@host:port/database -db_url = 'sqlite:///' + os.path.join(_PROJECT_PATH, 'test.db') +db_url = 'sqlite:///' + os.path.join(_PROJECT_PATH, '{project_name}.db') ## Log configuration debug = False @@ -29,6 +26,8 @@ log_backup = 52 ## PATH # Template directory template_directory = os.path.join(_PROJECT_PATH, '{project_name}/templates') +# Configuration directory +config_root = os.path.join(_PROJECT_PATH, 'conf.d') # Static url static_url = '/mandaye/static' # Static folder @@ -38,15 +37,12 @@ data_dir = os.path.join(_PROJECT_PATH, 'data') # Email notification configuration email_notification = False -email_prefix = '[Mandaye CAM]' +email_prefix = '[Mandaye {project_name}]' smtp_host = 'localhost' smtp_port = 25 email_from = 'traceback@entrouvert.com' email_to = ['admin@localhost'] -# platform : should be prod, recette or dev -platform = "prod" - # Use long traceback with xtraceback use_long_trace = True @@ -60,6 +56,16 @@ encrypt_sp_password = False # Must be a 16, 24, or 32 bytes long encrypt_secret = '' +# Supported authentification +authentifications = {{ + 'saml2': 'mandaye.auth.saml2.SAML2Auth' +}} + +# sp mappers +mappers = {{ + 'linuxfr': '{project_name}.mappers.linuxfr_example' +}} + # Beaker session configuration session_opts = {{ 'session.type': 'file', @@ -72,59 +78,6 @@ session_opts = {{ # Only mandaye.backends.sql at the moment storage_backend = "mandaye.backends.sql" -# Needed if ssl is activated -ssl = False -keyfile = '' -certfile = '' - -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-----''' - # Import local config try: from ..{project_name}.local_config import * diff --git a/mandaye/skel/example.module/configs/linuxfr_saml_example.py b/mandaye/skel/example.module/configs/linuxfr_saml_example.py deleted file mode 100644 index 1165127..0000000 --- a/mandaye/skel/example.module/configs/linuxfr_saml_example.py +++ /dev/null @@ -1,49 +0,0 @@ - -from {project_name}.auth.example import MyAuthSAML -from {project_name}.filters.example import ReplayFilter - -from mandaye.configs import saml2 as saml2_config - -form_values = {{ - 'login_url': '/compte/connexion', - 'form_attrs': {{ 'id': 'new_account' }}, - 'post_fields': ['account[login]', 'account[password]'], - 'username_field': 'account[login]', - 'password_field': 'account[password]', -}} - -auth = MyAuthSAML(form_values, 'linuxfr', saml2_config) - -linuxfr_mapping = [ - {{ - 'path': r'/mandaye/associate$', - 'method': 'GET', - 'on_response': [{{ - 'filter': ReplayFilter.associate, - 'values': {{ - 'action': '/mandaye/associate', - 'template': 'associate.html', - 'sp_name': 'Linux FR', - 'login_name': form_values['username_field'], - 'password_name': form_values['password_field'], - }}, - }},] - }}, - {{ - 'path': r'/mandaye/associate$', - 'method': 'POST', - 'response': [ - {{ - 'filter': auth.associate_submit, - 'values': {{ - 'connection_url': '/mandaye/sso', - 'associate_url': '/mandaye/associate', - }}, - 'condition': "response.code==302" - }}, - ] - }}, - ] - -linuxfr_mapping.extend(auth.get_default_mapping()) - diff --git a/mandaye/skel/example.module/mappers/__init__.py b/mandaye/skel/example.module/mappers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mandaye/skel/example.module/mappers/linuxfr_example.py b/mandaye/skel/example.module/mappers/linuxfr_example.py new file mode 100644 index 0000000..8e004db --- /dev/null +++ b/mandaye/skel/example.module/mappers/linuxfr_example.py @@ -0,0 +1,88 @@ + +""" +You need to defined 3 variables : + +* form_values (defined the login form values): +form_values = {{ + 'login_url': '/login', + 'post_url': '/login', + 'form_attrs': {{ 'name': 'form40', }}, + 'username_field': 'user', + 'password_field': 'pass', + 'post_fields': ['birthdate', 'card_number'] +}} +login_url, form_attrs, post_fields and username_field are obligatory +* urls (a dictionnary with urls) : + urls = {{ + 'associate_url': '/mandaye/associate', + 'connection_url': '/mandaye/sso', + 'login_url': '/mandaye/login' + }} +* mapping +""" + +from {project_name}.filters.example import ReplayFilter + +form_values = {{ + 'login_url': '/compte/connexion', + 'form_attrs': {{ 'id': 'new_account' }}, + 'post_fields': ['account[login]', 'account[password]'], + 'username_field': 'account[login]', + 'password_field': 'account[password]', +}} + +urls = {{ + 'associate_url': '/mandaye/associate', + 'connection_url': '/mandaye/sso', + 'login_url': '/mandaye/login' +}} + +mapping = [ + {{ + 'path': r'/mandaye/login$', + 'method': 'GET', + 'response': [{{ + 'auth': 'login', + 'condition': 'response.code==302', + }},] + }}, + {{ + 'path': r'/mandaye/sso$', + 'method': 'GET', + 'response': [{{ + 'auth': 'sso', + }}] + }}, + {{ + 'path': r'/mandaye/slo$', + 'method': 'GET', + 'response': [{{ + 'auth': 'slo', + }}] + }}, + {{ + 'path': r'/mandaye/associate$', + 'method': 'GET', + 'on_response': [{{ + 'filter': ReplayFilter.associate, + 'values': {{ + 'action': urls['associate_url'], + 'template': 'associate.html', + 'sp_name': 'Linux FR', + 'login_name': form_values['username_field'], + 'password_name': form_values['password_field'], + }}, + }},] + }}, + {{ + 'path': r'/mandaye/associate$', + 'method': 'POST', + 'response': [ + {{ + 'auth': 'associate_submit', + 'condition': "response.code==302" + }}, + ] + }}, + ] + diff --git a/mandaye/skel/example.module/wsgi.py b/mandaye/skel/example.module/wsgi.py index b967d5a..6f54e55 100644 --- a/mandaye/skel/example.module/wsgi.py +++ b/mandaye/skel/example.module/wsgi.py @@ -5,9 +5,13 @@ from mandaye.server import MandayeApp from {project_name} import config from beaker.middleware import SessionMiddleware +from whitenoise import WhiteNoise -os.environ['MANDAYE_CONFIG_MODULE'] = '{project_name}.config' +os.environ['MANDAYE_CONFIG_MODULE'] = 'test.config' + +from mandaye import config application = SessionMiddleware(MandayeApp(), config.session_opts) +application_dev = WhiteNoise(application, root=config.static_root, prefix=config.static_url) diff --git a/mandaye/skel/requirements.txt b/mandaye/skel/requirements.txt index 6f5b2a0..8096cbb 100644 --- a/mandaye/skel/requirements.txt +++ b/mandaye/skel/requirements.txt @@ -1,2 +1,3 @@ gunicorn>=0.17 -mandaye>=0.7.1 +mandaye>=0.8.0 +whitenoise>=1.0 diff --git a/mandaye/skel/server.py b/mandaye/skel/server.py index 2bd3447..c65a14e 100755 --- a/mandaye/skel/server.py +++ b/mandaye/skel/server.py @@ -15,8 +15,8 @@ from gunicorn.app.wsgiapp import WSGIApplication class MandayeWSGIApplication(WSGIApplication): def init(self, parser, opts, args): - self.cfg.set("default_proc_name", "{project_name}.wsgi:application") - self.app_uri = "{project_name}.wsgi:application" + self.cfg.set("default_proc_name", "{project_name}.wsgi:application_dev") + self.app_uri = "{project_name}.wsgi:application_dev" def main(): """ The ``gunicorn`` command line runner for launcing Gunicorn with diff --git a/mandaye/skel/setup.py b/mandaye/skel/setup.py index 925833c..00bd187 100644 --- a/mandaye/skel/setup.py +++ b/mandaye/skel/setup.py @@ -14,7 +14,8 @@ import {project_name} install_requires=[ 'gunicorn>=0.17', - 'mandaye>=0.7.1', + 'mandaye>=0.8.0', + 'whitenoise>=1.0' ] def get_version(): diff --git a/requirements.txt b/requirements.txt index 552acaa..791ce0f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,3 @@ xtraceback>=0.3 sqlalchemy>=0.7,<0.8 alembic>=0.5.0 Mako>=0.4 -static diff --git a/scripts/mandaye-admin.py b/scripts/mandaye-admin.py index d8a17c1..083b574 100755 --- a/scripts/mandaye-admin.py +++ b/scripts/mandaye-admin.py @@ -47,14 +47,11 @@ def main(): shutil.copytree(skel, project_name) for root, dirs, files in os.walk(project_name): if not "templates" in root and not "static" in root: - print root for filename in files: file_path = os.path.join(root, filename) - print file_path with open(file_path, "r") as f: content = f.read() with open(file_path, "w") as f: - print content.format(project_name=project_name) f.write(content.format(project_name=project_name)) shutil.move(modue_example, module) diff --git a/setup.py b/setup.py index d97b266..0f6d51c 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,6 @@ install_requires=[ 'sqlalchemy>=0.7.3', 'lxml>=2.0', 'xtraceback>=0.3', - 'static', ] if version < '2.7': @@ -37,7 +36,7 @@ def get_version(): version = result.split()[0][1:] return version.replace('-','.') import mandaye - return mandaye.VERSION + return mandaye.__version__ setup(name="mandaye", version=get_version(),