diff --git a/Makefile b/Makefile index 87e4389..3c9dbba 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,30 @@ all: true install: - install -d $(DESTDIR)/etc/authentic2/config.d/ - install -m 644 amue.conf $(DESTDIR)/etc/authentic2/config.d/ - - + install -d $(DESTDIR)/etc/amue-authentic2 + install -m 644 amue.conf $(DESTDIR)/etc/amue-authentic2/ + install -m 644 gunicorn-cfg.py $(DESTDIR)/etc/amue-authentic2/ + install -d $(DESTDIR)/usr/share/dbconfig-common/scripts/amue-authentic2/install + install -T -m 755 syncdb.sh $(DESTDIR)/usr/share/dbconfig-common/scripts/amue-authentic2/install/pgsql + install -d $(DESTDIR)/usr/share/amue-authentic2/templates + install db.conf $(DESTDIR)/usr/share/amue-authentic2/templates/ + install -d $(DESTDIR)/usr/share/pyshared/ + cp -R amue $(DESTDIR)/usr/share/pyshared/ + install -d $(DESTDIR)/usr/lib/amue-authentic2 + install -m 755 run.sh $(DESTDIR)/usr/lib/amue-authentic2 + install -m 755 manage.sh $(DESTDIR)/usr/lib/amue-authentic2 + install -m 755 reload.sh $(DESTDIR)/usr/lib/amue-authentic2 + # supervisor + install -d $(DESTDIR)/etc/supervisor/conf.d/ + install -T -m 644 supervisor.conf $(DESTDIR)/etc/supervisor/conf.d/amue-authentic2.conf + # stud + install -d $(DESTDIR)/etc/stud/ + install -T -m 644 stud-amue-idp-test.conf $(DESTDIR)/etc/stud/idp.conf + install -d $(DESTDIR)/etc/ssl/private/ + install -T -m 644 wildcard-amue.fr.cert $(DESTDIR)/etc/ssl/private/idp.pem + # haproxy + install -d $(DESTDIR)/etc/haproxy/ + install -T -m 644 haproxy.cfg $(DESTDIR)/etc/haproxy/haproxy-amue.cfg + # nginx + install -d $(DESTDIR)/etc/nginx/sites-available/ + install -T -m 644 idp.amue.fr.nginx $(DESTDIR)/etc/nginx/sites-available/idp.amue.fr diff --git a/amue.conf b/amue.conf index e69de29..b2dbad0 100644 --- a/amue.conf +++ b/amue.conf @@ -0,0 +1,15 @@ +# do not remove this line, it imports db configuration from dbconfig-common +. /etc/amue-authentic2/db.conf + +export A2_HOMEPAGE_URL=http://www.amue.fr/ +export CACHE_BACKEND='{"BACKEND": "django.core.cache.backends.memcached.MemcachedCache", "LOCATION": "127.0.0.1:11211"}' +export USE_X_FORWARDER_HOST=1 +export ALLOWED_HOSTS="idp-test.amue.fr:idp.amue.fr" +export LDAP_AUTH_SETTINGS='[{ + "url": "ldap://ldap.amue.fr", + "basedn": "OU=AMUE,DC=wan,DC=amue,DC=fr", + "binddn": "ldap.read@amue.fr", + "bindpw": "", + "user_filter": "sAMAccountName=%s", + "active_directory": true + }]' diff --git a/amue/__init__.py b/amue/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/amue/settings.py b/amue/settings.py new file mode 100644 index 0000000..3b1cabb --- /dev/null +++ b/amue/settings.py @@ -0,0 +1,401 @@ +# Django settings for authentic project. +import os +from django.core.exceptions import ImproperlyConfigured +import json + +gettext_noop = lambda s: s + +DEBUG = 'DEBUG' in os.environ +DEBUG_PROPAGATE_EXCEPTIONS = 'DEBUG_PROPAGATE_EXCEPTIONS' in os.environ +USE_DEBUG_TOOLBAR = 'USE_DEBUG_TOOLBAR' in os.environ +TEMPLATE_DEBUG = DEBUG + +PROJECT_PATH = os.path.join(os.path.dirname(__file__), '..') +PROJECT_NAME = 'amue-authentic2' + +ADMINS = () +if 'ADMINS' in os.environ: + ADMINS = filter(None, os.environ.get('ADMINS').split(':')) + ADMINS = [ admin.split(';') for admin in ADMINS ] + for admin in ADMINS: + assert len(admin) == 2, 'ADMINS setting must be a colon separated list of name and emails separated by a semi-colon' + assert '@' in admin[1], 'ADMINS setting pairs second value must be emails' + +MANAGERS = ADMINS + + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(PROJECT_PATH, PROJECT_NAME + '.db'), + } +} + +for key in os.environ: + if key.startswith('DATABASE_'): + prefix, db_key = key.split('_', 1) + DATABASES['default'][db_key] = os.environ[key] + +# Hey Entr'ouvert is in France !! +TIME_ZONE = 'Europe/Paris' +LANGUAGE_CODE = 'fr' +SITE_ID = 1 +USE_I18N = True + +LANGUAGES = ( + ('en', gettext_noop('English')), + ('fr', gettext_noop('French')), +) +USE_L10N = True + +# Static files + +STATIC_ROOT = os.environ.get('STATIC_ROOT', '/var/lib/%s/static' % PROJECT_NAME) +STATIC_URL = os.environ.get('STATIC_URL', '/static/') +if 'STATICFILES_DIRS' in os.environ: + STATICFILES_DIRS = os.environ['STATICFILES_DIRS'].split(':') +else: + STATICFILES_DIRS = ('/var/lib/%s/extra-static/' % PROJECT_NAME,) + +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', +) + +TEMPLATE_CONTEXT_PROCESSORS = ( + 'django.contrib.auth.context_processors.auth', + 'django.core.context_processors.debug', + 'django.core.context_processors.i18n', + 'django.core.context_processors.media', + 'django.core.context_processors.request', + 'django.contrib.messages.context_processors.messages', + 'django.core.context_processors.static', + 'authentic2.context_processors.federations_processor', +) + +MIDDLEWARE_CLASSES = ( + 'django.middleware.common.CommonMiddleware', + 'django.middleware.http.ConditionalGetMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.middleware.locale.LocaleMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.transaction.TransactionMiddleware', + 'authentic2.idp.middleware.DebugMiddleware' +) + +ROOT_URLCONF = 'authentic2.urls' + +if os.environ.get('TEMPLATE_DIRS'): + TEMPLATE_DIRS = os.environ['TEMPLATE_DIRS'].split(':') +else: + TEMPLATE_DIRS = ('/var/lib/%s/templates' % PROJECT_NAME,) + + +INSTALLED_APPS = ( + 'authentic2', + 'authentic2.nonce', + 'authentic2.saml', + 'authentic2.idp', + 'authentic2.idp.saml', + 'authentic2.auth2_auth', + 'authentic2.attribute_aggregator', + 'authentic2.disco_service', + 'admin_tools', + 'admin_tools.theming', + 'admin_tools.menu', + 'admin_tools.dashboard', + 'django.contrib.staticfiles', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.admin', + 'django.contrib.sites', + 'registration', + 'south', +) + +MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage' + + + +# Registration settings +ACCOUNT_ACTIVATION_DAYS = int(os.environ.get('ACCOUNT_ACTIVATION_DAYS', 3)) +PASSWORD_RESET_TIMEOUT_DAYS = int(os.environ.get('PASSWORD_RESET_TIMEOUT_DAYS', 3)) +if 'AUTHENTIC2_ALLOW_ACCOUNT_DELETION' in os.environ: + AUTHENTIC2_ALLOW_ACCOUNT_DELETION = True + +# authentication +AUTHENTICATION_BACKENDS = ( + 'django.contrib.auth.backends.ModelBackend', +) + +# sessions +SESSION_EXPIRE_AT_BROWSER_CLOSE = 'SESSION_EXPIRE_AT_BROWSER_CLOSE' in os.environ +SESSION_COOKIE_AGE = int(os.environ.get('SESSION_COOKIE_AGE', 36000)) # one day of work +SESSION_COOKIE_NAME = os.environ.get('SESSION_COOKIE_NAME', 'sessionid') +SESSION_COOKIE_PATH = os.environ.get('SESSION_COOKIE_PATH', '/') +SESSION_COOKIE_SECURE = 'SESSION_COOKIE_SECURE' in os.environ + +# email settings +EMAIL_HOST = os.environ.get('EMAIL_HOST', 'localhost') +EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER', '') +EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD', '') +EMAIL_PORT = int(os.environ.get('EMAIL_PORT', 25)) +EMAIL_SUBJECT_PREFIX = os.environ.get('EMAIL_SUBJECT_PREFIX', '[Authentic]') +EMAIL_USE_TLS = 'EMAIL_USE_TLS' in os.environ +SERVER_EMAIL = os.environ.get('SERVER_EMAIL', 'root@localhost') +DEFAULT_FROM_EMAIL = os.environ.get('DEFAULT_FROM_EMAIL', 'webmaster@localhost') + +# web & network settings +if 'ALLOWED_HOSTS' in os.environ: + ALLOWED_HOSTS = os.environ['ALLOWED_HOSTS'].split(':') +USE_X_FORWARDED_HOST = 'USE_X_FORWARDED_HOST' in os.environ +SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') + +LOGIN_REDIRECT_URL = os.environ.get('LOGIN_REDIRECT_URL', '/') +LOGIN_URL = os.environ.get('LOGIN_URL', '/login') +LOGOUT_URL = os.environ.get('LOGOUT_URL', '/accounts/logout') + +if 'INTERNAL_IPS' in os.environ: + INTERNAL_IPS = os.environ['INTERNAL_IPS'].split(':') +else: + INTERNAL_IPS = ('127.0.0.1',) + +# misc +SECRET_KEY = os.environ.get('SECRET_KEY', '0!=(1kc6kri-ui+tmj@mr+*0bvj!(p*r0duu2n=)7@!p=pvf9n') +DEBUG_TOOLBAR_CONFIG = {'INTERCEPT_REDIRECTS': False} + +# Authentic2 settings + +DISCO_SERVICE = 'DISCO_SERVICE' in os.environ +DISCO_USE_OF_METADATA = 'DISCO_USE_OF_METADATA' in os.environ + +DISCO_SERVICE_NAME = os.environ.get('DISCO_SERVICE_NAME', "http://www.identity-hub.com/disco_service/disco") +DISCO_RETURN_ID_PARAM = "entityID" +SHOW_DISCO_IN_MD = 'SHOW_DISCO_IN_MD' in os.environ +USE_DISCO_SERVICE = 'USE_DISCO_SERVICE' in os.environ + +########################### +# Authentication settings +########################### + +# Only RSA private keys are currently supported +AUTH_FRONTENDS = ( 'authentic2.auth2_auth.backend.LoginPasswordBackend',) +SSLAUTH_CREATE_USER = 'SSLAUTH_CREATE_USER' in os.environ +AUTHENTICATION_EVENT_EXPIRATION = int(os.environ.get('AUTHENTICATION_EVENT_EXPIRATION', 3600*24*7)) + +############################# +# Identity Provider settings +############################# + +# List of IdP backends, mainly used to show available services in the homepage +# of user, and to handle SLO for each protocols +IDP_BACKENDS = [ ] + +# You MUST changes these keys, they are just for testing ! +LOCAL_METADATA_CACHE_TIMEOUT = int(os.environ.get('LOCAL_METADATA_CACHE_TIMEOUT', 600)) +SAML_SIGNATURE_PUBLIC_KEY = os.environ.get('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 = os.environ.get('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-----''') + +# Whether to autoload SAML 2.0 identity providers and services metadata +# Only https URLS are accepted. +# Can be none, sp, idp or both +SAML_METADATA_AUTOLOAD = os.environ.get('SAML_METADATA_AUTOLOAD', 'none') + +PUSH_PROFILE_UPDATES = 'PUSH_PROFILE_UPDATES' in os.environ + +################################## +# LDAP Configuration +################################## +if 'LDAP_AUTH_SETTINGS' in os.environ: + try: + LDAP_AUTH_SETTINGS = json.loads(os.environ['LDAP_AUTH_SETTINGS']) + except Exception, e: + raise ImproperlyConfigured('LDAP_AUTH_SETTINGS is not a JSON document', e) +else: + LDAP_AUTH_SETTINGS = [] +################################## +# Cache configuration +################################## +CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + }, +} +if 'CACHE_BACKEND' in os.environ: + CACHES['default'] = json.loads(os.environ['CACHE_BACKEND']) + + +# Logging settings + +LOGGING = { + 'version': 1, + 'disable_existing_loggers': True, + 'filters': { + 'cleaning': { + '()': 'authentic2.utils.CleanLogMessage', + }, + }, + 'formatters': { + 'verbose': { + 'format': '[%(asctime)s] %(levelname)s %(name)s: %(message)s', + 'datefmt': '%Y-%m-%d %a %H:%M:%S' + }, + }, + 'handlers': { + 'null': { + 'level':'DEBUG', + 'class':'django.utils.log.NullHandler', + }, + 'console': { + 'level':'DEBUG', + 'class':'logging.StreamHandler', + 'formatter': 'verbose', + 'filters': ['cleaning'], + }, + 'syslog': { + 'address': '/dev/log', + 'level':'INFO', + 'class':'logging.handlers.SysLogHandler', + 'filters': ['cleaning'], + }, + 'mail_admins': { + 'level': 'ERROR', + 'class': 'django.utils.log.AdminEmailHandler', + 'include_html': True, + 'filters': ['cleaning'], + } + }, + 'loggers': { + # disable default handlers + 'django.request': { + 'handlers': [], + 'propagate': True, + }, + '': { + 'handlers': ['mail_admins', 'syslog'] + (['console'] if DEBUG else []), + 'level': 'DEBUG' if DEBUG else 'INFO', + } + }, +} + +# add sentry handler if environment contains SENTRY_DSN +if 'SENTRY_DSN' in os.environ: + try: + import raven + except ImportError: + raise ImproperlyConfigured('SENTRY_DSN environment variable is set but raven is not installed.') + SENTRY_DSN = os.environ['SENTRY_DSN'] + LOGGING['handlers']['sentry'] = { + 'level': 'ERROR', + 'class': 'raven.handlers.logging.SentryHandler', + 'dsn': SENTRY_DSN, + } + LOGGING['loggers']['']['handlers'].append('sentry') + +SOUTH_TESTS_MIGRATE = False + +# Admin tools +ADMIN_TOOLS_INDEX_DASHBOARD = 'authentic2.dashboard.CustomIndexDashboard' +ADMIN_TOOLS_APP_INDEX_DASHBOARD = 'authentic2.dashboard.CustomAppIndexDashboard' +ADMIN_TOOLS_MENU = 'authentic2.menu.CustomMenu' + +AUTH_SAML2 = 'AUTH_SAML2' in os.environ +AUTH_OPENID = 'AUTH_OPENID' in os.environ +AUTH_SSL = 'AUTH_SSL' in os.environ +IDP_SAML2 = 'IDP_SAML2' in os.environ +IDP_OPENID = 'IDP_OPENID' in os.environ +IDP_CAS = 'IDP_CAS' in os.environ + +try: + from local_settings import * +except ImportError, e: + if 'local_settings' in e.args[0]: + pass + +if USE_DEBUG_TOOLBAR: + try: + import debug_toolbar + MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',) + INSTALLED_APPS += ('debug_toolbar',) + except ImportError: + print "Debug toolbar missing, not loaded" + +if AUTH_SAML2: + INSTALLED_APPS += ('authentic2.authsaml2',) + AUTHENTICATION_BACKENDS += ( + 'authentic2.authsaml2.backends.AuthSAML2PersistentBackend', + 'authentic2.authsaml2.backends.AuthSAML2TransientBackend') + AUTH_FRONTENDS += ('authentic2.authsaml2.frontend.AuthSAML2Frontend',) + IDP_BACKENDS += ('authentic2.authsaml2.backends.AuthSAML2Backend',) + DISPLAY_MESSAGE_ERROR_PAGE = True + +if AUTH_OPENID: + INSTALLED_APPS += ('authentic2.auth2_auth.auth2_openid', 'django_authopenid',) + AUTH_FRONTENDS += ('authentic2.auth2_auth.auth2_openid.backend.OpenIDFrontend',) + +if AUTH_SSL: + AUTHENTICATION_BACKENDS += ('authentic2.auth2_auth.auth2_ssl.backend.SSLBackend',) + AUTH_FRONTENDS += ('authentic2.auth2_auth.auth2_ssl.frontend.SSLFrontend',) + INSTALLED_APPS += ('authentic2.auth2_auth.auth2_ssl',) + +if IDP_SAML2: + IDP_BACKENDS += ('authentic2.idp.saml.backend.SamlBackend',) + +if IDP_OPENID: + INSTALLED_APPS += ('authentic2.idp.idp_openid',) + TEMPLATE_CONTEXT_PROCESSORS += ('authentic2.idp.idp_openid.context_processors.openid_meta',) + +if IDP_CAS: + INSTALLED_APPS += ('authentic2.idp.idp_cas',) + +if LDAP_AUTH_SETTINGS: + AUTHENTICATION_BACKENDS += ('authentic2.backends.LDAPBackend',) diff --git a/amue/wsgi.py b/amue/wsgi.py new file mode 100644 index 0000000..33cc5f3 --- /dev/null +++ b/amue/wsgi.py @@ -0,0 +1,24 @@ +""" +WSGI config for a authentic2 project. + +This module contains the WSGI application used by Django's development server +and any production WSGI deployments. It should expose a module-level variable +named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover +this application via the ``WSGI_APPLICATION`` setting. + +Usually you will have the standard Django WSGI application here, but it also +might make sense to replace the whole Django WSGI application with a custom one +that later delegates to the Django one. For example, you could introduce WSGI +middleware here, or combine a Django application with an application of another +framework. + +""" +import os + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "amue.settings") + +# This application object is used by any WSGI server configured to use this +# file. This includes Django's development server, if the WSGI_APPLICATION +# setting points here. +from django.core.wsgi import get_wsgi_application +application = get_wsgi_application() diff --git a/db.conf b/db.conf new file mode 100644 index 0000000..27708ab --- /dev/null +++ b/db.conf @@ -0,0 +1,7 @@ +#!/bin/sh + +export DATABASE_ENGINE='django.db.backends.postgresql_psycopg2' +export DATABASE_NAME='_DBC_DBNAME_' +export DATABASE_USER='_DBC_DBUSER_' +export DATABASE_PASSWORD='_DBC_DBPASS_' +export DATABASE_HOST='localhost' diff --git a/debian/amue-authentic2.conffile b/debian/amue-authentic2.conffile new file mode 100644 index 0000000..1866a4c --- /dev/null +++ b/debian/amue-authentic2.conffile @@ -0,0 +1 @@ +/etc/amue-authentic2/amue.conf diff --git a/debian/amue-authentic2.config b/debian/amue-authentic2.config new file mode 100644 index 0000000..f4a60e3 --- /dev/null +++ b/debian/amue-authentic2.config @@ -0,0 +1,13 @@ +#!/bin/sh +# config maintainer script for foo-pgsql + +# source debconf stuff +. /usr/share/debconf/confmodule +# source dbconfig-common shell library, and call the hook function +if [ -f /usr/share/dbconfig-common/dpkg/config.pgsql ]; then + . /usr/share/dbconfig-common/dpkg/config.pgsql + dbc_pgsql_createdb_encoding="UTF8" + dbc_go amue-authentic2 $@ +fi + +#DEBHELPER# diff --git a/debian/amue-authentic2.dirs b/debian/amue-authentic2.dirs new file mode 100644 index 0000000..25e2868 --- /dev/null +++ b/debian/amue-authentic2.dirs @@ -0,0 +1,2 @@ +/var/lib/amue-authentic2/extra-static +/var/lib/amue-authentic2/templates diff --git a/debian/amue-authentic2.install b/debian/amue-authentic2.install new file mode 100644 index 0000000..7aaa3ba --- /dev/null +++ b/debian/amue-authentic2.install @@ -0,0 +1,6 @@ +/etc/amue-authentic2/* +/usr/share/dbconfig-common/scripts/amue-authentic2/install/* +/usr/share/amue-authentic2/templates/* +/usr/share/pyshared/amue/* +/usr/lib/amue-authentic2/* +/etc/supervisor/conf.d/* diff --git a/debian/amue-authentic2.postinst b/debian/amue-authentic2.postinst new file mode 100644 index 0000000..751c7e4 --- /dev/null +++ b/debian/amue-authentic2.postinst @@ -0,0 +1,54 @@ +#!/bin/sh +# postinst maintainer script for foo-pgsql + +USER=amue-authentic2 +GROUP=amue-authentic2 + +case "$1" in + configure) + if ! getent group $GROUP > /dev/null 2>&1; then + echo -n "Adding group $GROUP.." + addgroup --quiet --system $GROUP + echo "..done" + fi + if ! getent passwd $USER >/dev/null; then + echo Adding user $USER... + adduser --quiet --system --gecos "AMUE authentic2 system user" \ + --ingroup $GROUP \ + --no-create-home \ + --home /var/lib/amue-authentic2 --shell /usr/sbin/nologin $USER + fi + mkdir -p /var/log/amue-authentic2 + chown amue-authentic2 /var/log/amue-authentic2 + ;; +esac + +# source debconf stuff +. /usr/share/debconf/confmodule +# source dbconfig-common shell library, and call the hook function +if [ -f /usr/share/dbconfig-common/dpkg/postinst.pgsql ]; then + . /usr/share/dbconfig-common/dpkg/postinst.pgsql + dbc_generate_include=template:/etc/amue-authentic2/db.conf + dbc_generate_include_args="-o template_infile=/usr/share/amue-authentic2/templates/db.conf -U" + dbc_generate_include_owner="amue-authentic2" + dbc_generate_include_perms="640" + dbc_go amue-authentic2 $@ +fi + +case "$1" in + configure) + echo Updating static files... + /usr/lib/amue-authentic2/manage.sh collectstatic --noinput + if [ -e /etc/nginx/sites-enabled/idp.amue.fr ]; then + echo Installed nginx virtualhost... + ln -s /etc/nginx/sites-availables/idp.amue.fr /etc/nginx/sites-enabled/ + invoke-rc.d nginx reload + fi + echo Restarting amue-authentic... + /usr/bin/supervisorctl update + /usr/bin/supervisorctl restart amue-authentic2 + ;; +esac + + +#DEBHELPER# diff --git a/debian/amue-authentic2.postrm b/debian/amue-authentic2.postrm new file mode 100755 index 0000000..df263ac --- /dev/null +++ b/debian/amue-authentic2.postrm @@ -0,0 +1,22 @@ +#!/bin/sh +# config maintainer script for foo-pgsql + +# source debconf stuff +. /usr/share/debconf/confmodule +# source dbconfig-common shell library, and call the hook function +if [ -f /usr/share/dbconfig-common/dpkg/postrm.pgsql ]; then + . /usr/share/dbconfig-common/dpkg/postrm.pgsql + dbc_go amue-authentic2 $@ +fi + +DBCONF=/etc/amue-authentic2/db.conf +if [ "$1" = "purge" ]; then + rm -f $DBCONF + if which ucf >/dev/null 2>&1; then + ucf --purge $DBCONF + fi +fi + +#DEBHELPER# + + diff --git a/debian/amue-haproxy.install b/debian/amue-haproxy.install new file mode 100644 index 0000000..0c70b2f --- /dev/null +++ b/debian/amue-haproxy.install @@ -0,0 +1,3 @@ +/etc/stud/idp.conf +/etc/ssl/private/idp.pem +/etc/haproxy/haproxy-amue.cfg diff --git a/debian/amue-haproxy.postinst b/debian/amue-haproxy.postinst new file mode 100644 index 0000000..421000e --- /dev/null +++ b/debian/amue-haproxy.postinst @@ -0,0 +1,22 @@ +#!/bin/sh +# postinst maintainer script for foo-pgsql + +case "$1" in + configure) + if ! readlink -q /etc/haproxy/haproxy.cfg; then + echo Using amue-haproxy configuration for haproxy \(symlink to /usr/share/amue-haproxy/\)... + ln -b -s /etc/haproxy/haproxy-amue.cfg /etc/haproxy/haproxy.cfg + fi + if grep -q ENABLED=0 /etc/default/haproxy; then + echo Activating HAProxy... + sed -i 's/ENABLED=0/ENABLED=1/' /etc/default/haproxy + fi + echo Restarting stud and haproxy... + invoke-rc.d stud stop + sleep 1 + invoke-rc.d stud start + invoke-rc.d haproxy restart + ;; +esac + +#DEBHELPER# diff --git a/debian/control b/debian/control index 2d8eb94..100f518 100644 --- a/debian/control +++ b/debian/control @@ -8,6 +8,13 @@ Homepage: https://dev.entrouvert.org/projects/amue/ Package: amue-authentic2 Architecture: all -Depends: authentic2 +Depends: ${misc:Depends}, python-authentic2, + dbconfig-common, nginx, gunicorn, supervisor +Recommends: postgresql-client Description: AMUE settings for the Authentic2 identity server Gather all needed settings for using Authentic2 at AMUE + +Package: amue-haproxy +Architecture: all +Depends: ${misc:Depends}, stud, haproxy +Description: AMUE settings for a proxy server diff --git a/debian/rules b/debian/rules index 7df73a8..b2eebb9 100755 --- a/debian/rules +++ b/debian/rules @@ -1,3 +1,3 @@ #!/usr/bin/make -f %: - dh $@ + dh --with python2 $@ diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..89ae9db --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (native) diff --git a/gunicorn-cfg.py b/gunicorn-cfg.py new file mode 100644 index 0000000..c00c818 --- /dev/null +++ b/gunicorn-cfg.py @@ -0,0 +1,11 @@ +import multiprocessing + + +debug = True +bind = 'unix:/run/amue-authentic2.sock' +workers = multiprocessing.cpu_count() * 2 + 1 +worker_class = 'gevent' +timeout = 10 +user = 'amue-authentic2' +group = 'amue-authentic2' +proc_name = 'amue-authentic2' diff --git a/haproxy.cfg b/haproxy.cfg new file mode 100644 index 0000000..b2e9a7e --- /dev/null +++ b/haproxy.cfg @@ -0,0 +1,49 @@ +global + log /dev/log local0 + log /dev/log local1 notice + chroot /var/lib/haproxy + user haproxy + group haproxy + daemon + +defaults + log global + mode http + option httplog + contimeout 5000 + clitimeout 50000 + srvtimeout 50000 + errorfile 400 /etc/haproxy/errors/400.http + errorfile 403 /etc/haproxy/errors/403.http + errorfile 408 /etc/haproxy/errors/408.http + errorfile 500 /etc/haproxy/errors/500.http + errorfile 502 /etc/haproxy/errors/502.http + errorfile 503 /etc/haproxy/errors/503.http + errorfile 504 /etc/haproxy/errors/504.http + +frontend unsecured + bind *:80 + acl idp_test hdr(host) -i idp-test.amue.fr + acl idp hdr(host) -i idp.amue.fr + redirect location https://idp-test.amue.fr if idp_test + redirect location https://idp-test.amue.fr if idp + +frontend http_frontend + bind *:8100 + mode http + option httpclose + option forwardfor + reqadd X-Forwarded-Proto:\ https + acl idp_test hdr(host) -i idp-test.amue.fr + acl idp hdr(host) -i idp.amue.fr + use_backend idp_test_backend if idp_test + use_backend idp_backend if idp + +backend idp_test_backend + mode http + server s1 idp-test-backend.amue.fr:8000 check + +backend idp_backend + mode http + server s1 idp-paris-backend.amue.fr:8000 check + server s2 idp-montpellier-backend.amue.fr:8000 check diff --git a/idp.amue.fr.nginx b/idp.amue.fr.nginx new file mode 100644 index 0000000..8bea636 --- /dev/null +++ b/idp.amue.fr.nginx @@ -0,0 +1,28 @@ +server { + listen 8000; + server_name idp-test.amue.fr; + server_name idp.amue.fr; + + root html; + index index.html index.htm; + + location / { + proxy_pass http://unix:/run/amue-authentic2.sock:/; + + client_max_body_size 5k; + client_body_buffer_size 1m; + proxy_intercept_errors on; + proxy_buffering on; + proxy_buffer_size 128k; + proxy_buffers 256 16k; + proxy_busy_buffers_size 256k; + proxy_temp_file_write_size 256k; + proxy_max_temp_file_size 0; + proxy_read_timeout 300; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-Protocol https; + } + location /static { + alias /var/lib/amue-authentic2/static/; + } +} diff --git a/ldap.sh b/ldap.sh new file mode 100755 index 0000000..17411cb --- /dev/null +++ b/ldap.sh @@ -0,0 +1,6 @@ +URL='ldap://194.167.237.11' +LOGIN='ldap.read@amue.fr' +PASS='1DapR&@d#103' +BASEDN='DC=wan,DC=amue,DC=fr' + +ldapsearch -z 3 -H $URL -b "$BASEDN" -D $LOGIN -w "$PASS" "$@" diff --git a/manage.sh b/manage.sh new file mode 100755 index 0000000..1c1ca2f --- /dev/null +++ b/manage.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +. /etc/amue-authentic2/amue.conf + +export DJANGO_SETTINGS_MODULE=amue.settings + +django-admin $@ diff --git a/reload.sh b/reload.sh new file mode 100755 index 0000000..5e46a74 --- /dev/null +++ b/reload.sh @@ -0,0 +1,6 @@ +#!/bin/sh +PID="`sudo supervisorctl status | grep RUNNING | awk '{ print $4 }' | sed 's/,//'`" + +if [ "x$PID" != "x" ]; then + kill -HUP $PID +fi diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..0b7aa24 --- /dev/null +++ b/run.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +. /etc/amue-authentic2/amue.conf + +exec /usr/bin/gunicorn -c /etc/amue-authentic2/gunicorn-cfg.py amue.wsgi:application diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..d650bf7 --- /dev/null +++ b/setup.py @@ -0,0 +1,129 @@ +#! /usr/bin/env python + +import glob +import re +import sys +import os + +from setuptools import setup, find_packages +from setuptools.command.install_lib import install_lib as _install_lib +from distutils.command.build import build as _build +from distutils.command.sdist import sdist +from distutils.cmd import Command + +class compile_translations(Command): + description = 'compile message catalogs to MO files via django compilemessages' + user_options = [] + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + try: + from django.core.management.commands.compilemessages import \ + compile_messages + for path in []: + if not os.path.exists(os.path.join(path, 'locale')): + continue + curdir = os.getcwd() + os.chdir(os.path.realpath(path)) + compile_messages(stderr=sys.stderr) + os.chdir(curdir) + except ImportError: + print + sys.stderr.write('!!! Please install Django >= 1.4 to build translations') + print + print + +class build(_build): + sub_commands = [('compile_translations', None)] + _build.sub_commands + +class eo_sdist(sdist): + + def run(self): + print "creating VERSION file" + if os.path.exists('VERSION'): + os.remove('VERSION') + version = get_version() + version_file = open('VERSION', 'w') + version_file.write(version) + version_file.close() + sdist.run(self) + print "removing VERSION file" + if os.path.exists('VERSION'): + os.remove('VERSION') + +class install_lib(_install_lib): + def run(self): + self.run_command('compile_translations') + _install_lib.run(self) + +def get_version(): + + version = None + if os.path.exists('VERSION'): + version_file = open('VERSION', 'r') + version = version_file.read() + version_file.close() + return version + for d in glob.glob('*'): + if not os.path.isdir(d): + continue + module_file = os.path.join(d, '__init__.py') + if not os.path.exists(module_file): + continue + for v in re.findall("""__version__ *= *['"](.*)['"]""", + open(module_file).read()): + assert version is None + version = v + if version: + break + assert version is not None + if os.path.exists('.git'): + import subprocess + p = subprocess.Popen(['git','describe','--dirty','--match=v*'], + stdout=subprocess.PIPE) + result = p.communicate()[0] + assert p.returncode == 0, 'git returned non-zero' + new_version = result.split()[0][1:] + assert not new_version.endswith('-dirty'), 'git workdir is not clean' + assert new_version.split('-')[0] == version, '__version__ must match the last git annotated tag' + version = new_version.replace('-', '.') + return version + + +setup(name="amue-authentic2", + version=get_version(), + license="AGPLv3 or later", + description="AMUE Authentic2", + author="Entr'ouvert", + author_email="info@entrouvert.org", + maintainer="Benjamin Dauvergne", + maintainer_email="info@entrouvert.com", + include_package_data=True, + url='http://dev.entrouvert.org/projects/amue', + package_data={ + '': [ + 'templates/**.html', + 'templates/**.txt', + 'static/**.png', + 'static/**.gif', + 'static/**.css', + 'static/**.js', + 'locale/**.mo', + 'fixtures/*.json', + ] + }, + packages=find_packages(), + scripts=(), + install_requires=[ + ], + dependency_links = [ + ], + cmdclass={'build': build, 'install_lib': install_lib, + 'compile_translations': compile_translations, + 'sdist': eo_sdist}, +) diff --git a/stud-amue-idp-test.conf b/stud-amue-idp-test.conf new file mode 100644 index 0000000..8768c27 --- /dev/null +++ b/stud-amue-idp-test.conf @@ -0,0 +1,2 @@ +CERT=/etc/ssl/private/idp.pem +OPTIONS="-f *,443 -b 127.0.0.1,8100" diff --git a/supervisor.conf b/supervisor.conf new file mode 100644 index 0000000..bde5d83 --- /dev/null +++ b/supervisor.conf @@ -0,0 +1,7 @@ +[program:amue-authentic2] +command=/usr/lib/amue-authentic2/run.sh +autostart=true +autorestart=true +stdout_logfile=syslog +stderr_logfile=syslog + diff --git a/syncdb.sh b/syncdb.sh new file mode 100755 index 0000000..bd46748 --- /dev/null +++ b/syncdb.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +. /etc/amue-authentic2/amue.conf + +python /usr/lib/authentic2/manage.py syncdb --noinput +python /usr/lib/authentic2/manage.py migrate diff --git a/wildcard-amue.fr.cert b/wildcard-amue.fr.cert new file mode 100644 index 0000000..54a6153 --- /dev/null +++ b/wildcard-amue.fr.cert @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIICITCCAYoCCQCFgT08G4PMkTANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJG +UjETMBEGA1UECAwKU29tZS1TdGF0ZTEOMAwGA1UEBwwFUGFyaXMxDTALBgNVBAoM +BEFNVUUxEjAQBgNVBAMMCSouYW11ZS5mcjAeFw0xMzExMTIxMjIzNTlaFw0xMzEx +MTMxMjIzNTlaMFUxCzAJBgNVBAYTAkZSMRMwEQYDVQQIDApTb21lLVN0YXRlMQ4w +DAYDVQQHDAVQYXJpczENMAsGA1UECgwEQU1VRTESMBAGA1UEAwwJKi5hbXVlLmZy +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDS27JrsBeqJJ892r427vh1jcrz +1JO37rMxgQQAp3xwK9YS29uYMjYY3ABX3HnjwhAuloR1sngV+wgpvY+/rqxhLlTG +b3hTfrFaXpzvSQSmTTzo2DDPz/Gwt6I2+ahpl5HSmec/2u1pKnYRILrvOUgCO+O5 +LCuwuMC4vnX7JSxs8wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAIt+GUR9VWYzf9kg +NlNa4CbVV6RSe1YUxE1G/S6i9z5JBrPLBemPxUidNjb28jVDFSyxHfkuseSECUet +qUVSe51rAIqwDmLyIxhEZTqOCoM9BZJQHsOEhQ/s5K/6cAJMNesBYWrk7pxcTkze +aTDb5E23mPDHjToJskTuzMRUWXa7 +-----END CERTIFICATE----- +-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQDS27JrsBeqJJ892r427vh1jcrz1JO37rMxgQQAp3xwK9YS29uY +MjYY3ABX3HnjwhAuloR1sngV+wgpvY+/rqxhLlTGb3hTfrFaXpzvSQSmTTzo2DDP +z/Gwt6I2+ahpl5HSmec/2u1pKnYRILrvOUgCO+O5LCuwuMC4vnX7JSxs8wIDAQAB +AoGBALvdDewwKgVnN5GOkKa05x0lRctUfIAF5hWXEw/aKV5vT/3hcJb7NYOUj6G4 +R8kKoAxCAqYyahd7X1yBDdAEOoOEDlqtZ11wNglKl3nHgN27UmEvaXtfxEhv4X8n +52e3+hLnQLulYJxg/ANNi7MKqiqYC5Og3NvR2sM/YwBSq++BAkEA+WpUHDO2/x1y +vI8xCZUvrzsVHV+sKm+2CeEFlj8/u9nkAeweyeqFpL7F2dXxHR3yJTRS5uAyV8vX +vgraadE6oQJBANhsx7uUUr0Qzqp+l/6oo1zY1N7e4FDHK+nxWrwqEy0yjHDN6gi5 +S3VnlgOMDMUB9Vomc9nyf3aB3WNoCKeXMxMCQBCEThWgBxpV5Oc/xEuSKZo9G2Ta +lRgqVa/JywjsH1hdUZAfBtrwQPFsAMYwOMto1ERKdsL7TdoqkZrwNQ6U4IECQQCw +/DRSJ7eJuaboMmJl9M6zbPaX07epF1fIFoHXAqlv+rhyv1G2FKGqvy0kdXEz4qgc +Mvnmr7Kg3Q6I7li1hKZVAkEArClTNqDZv3Net23nKlIUnFDw0HBB0a7GwtccxZfh +cTqt22hnt7QmUYV1q6RtjKS+J3b0nHufghesySiQh/8rCQ== +-----END RSA PRIVATE KEY-----