diff --git a/README.md b/README.md index 6376974..30cfeb9 100755 --- a/README.md +++ b/README.md @@ -21,3 +21,87 @@ packages, like django, python3-saml, etc. virtualenv --python=/usr/bin/python3.5 venv source venv/bin/activate pip3 install git+ssh://git@git.webhosting.rug.nl:222/p253591/rug-website.git + +## Settings and X509 certificate + +Create a file `rugwebsite/settings.py`, with settings like these: + + import os + + BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + SECRET_KEY = '....' + DEBUG = True + LOGIN_REDIRECT_URL = '/' + + INSTALLED_APPS = ( + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'bootstrap4', + 'rugwebsite', + ) + + MIDDLEWARE_CLASSES = ( + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'django.middleware.security.SecurityMiddleware', + ) + + ROOT_URLCONF = 'urls' + + TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, + ] + + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, '..', 'db.sqlite3'), + } + } + + LANGUAGE_CODE = 'en-us' + TIME_ZONE = 'Europe/Amsterdam' + USE_I18N = True + USE_L10N = True + USE_TZ = True + + STATIC_URL = '/static/' + STATIC_ROOT = os.path.join(BASE_DIR, 'static') + + SAML_ROUTE = 'sso/saml/' + SAML_REDIRECT = '/' + SAML_USERS_MAP = [] + + SAML_PROVIDERS = [] + + +Make sure to fill in something random for `SECRET_KEY`, consult the dango documentation to make sure you're super +secure. + +Then you can use django-admin to complete the settings with SAML2 metadata, inclusing a private key and x509 +certificate. Run the following and **append** the output to `rugwebsite/settings.py` + + PYTHONPATH=. DJANGO_SETTINGS_MODULE=settings django-admin init-saml2-settings --country NL --city Groningen --organisation 'University of Groningen' --organisation-unit 'Research and Innovation Support' --common-name 'cosmo.service.rug.nl/rugwebsite' --state Groningen --support-name 'Research and Innovation Support' --support-email 'ris@list.rug.nl' --technical-name 'Research and Innovation Support' --technical-email 'ris@list.rug.nl' --base-url 'cosmo.service.rug.nl' --entity-id 'www.rug.nl/cosmo' + +You can change these settings if you like. \ No newline at end of file diff --git a/rugwebsite/__init__.py b/rugwebsite/__init__.py index 33a3ca1..71825a3 100644 --- a/rugwebsite/__init__.py +++ b/rugwebsite/__init__.py @@ -1 +1 @@ -__version__ = '0.1.19' \ No newline at end of file +__version__ = '0.1.20' \ No newline at end of file diff --git a/rugwebsite/management/commands/init-saml2-settings.py b/rugwebsite/management/commands/init-saml2-settings.py index cf14b81..4257358 100644 --- a/rugwebsite/management/commands/init-saml2-settings.py +++ b/rugwebsite/management/commands/init-saml2-settings.py @@ -34,14 +34,21 @@ class Command(BaseCommand): parser.add_argument('--expires-after-days', nargs='?', type=int, default=10 * 365, dest='expires') def handle(self, *args, **options): + for option in {'country', 'city', 'state', 'organisation', 'organisation-unit', 'common-name', 'support-name', + 'support-email', 'technical-name', 'technical-email', 'entity-id', 'base-url'}: + assert option in options and options[option] is not None and len(options[option]) == 1, "Exepecte one " \ + "value for option" \ + ": " + option + options[option] = options[option][0] + key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend = default_backend()) subject = issuer = x509.Name([ - x509.NameAttribute(NameOID.COUNTRY_NAME, options['country'][0]), - x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, options['state'][0]), - x509.NameAttribute(NameOID.LOCALITY_NAME, options['city'][0]), - x509.NameAttribute(NameOID.ORGANIZATION_NAME, options['organisation'][0]), - x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, options['organisation-unit'][0]), - x509.NameAttribute(NameOID.COMMON_NAME, options['common-name'][0]), + x509.NameAttribute(NameOID.COUNTRY_NAME, options['country']), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, options['state']), + x509.NameAttribute(NameOID.LOCALITY_NAME, options['city']), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, options['organisation']), + x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, options['organisation-unit']), + x509.NameAttribute(NameOID.COMMON_NAME, options['common-name']), ]) cert = ( x509.CertificateBuilder() diff --git a/rugwebsite/settings/default.py b/rugwebsite/settings/default.py index 6f6c685..7cecf1c 100755 --- a/rugwebsite/settings/default.py +++ b/rugwebsite/settings/default.py @@ -67,154 +67,10 @@ AUTHENTICATION_BACKENDS = [ 'django_saml2_pro_auth.auth.Backend' ] - SAML_ROUTE = 'sso/saml/' SAML_REDIRECT = '/' -SAML_USERS_MAP = [{ - "RuG": { - "email": dict(key="urn:mace:dir:attribute-def:mail", index=0), - "username": dict(key="urn:mace:dir:attribute-def:uid", index=0), - "first_name": dict(key="urn:mace:dir:attribute-def:gn", index=0), - "last_name": dict(key="urn:mace:dir:attribute-def:sn", index=0), - } -}] +SAML_USERS_MAP = [] -PRIVATE_KEY = """MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMqvdxxy/z9IXuxB -hHWdJ4XYji21XWybsFYPB2LxKoTB0919oCSj8WsW2aeSUW6DsdLki1tHnqwhTO2D -5YKyK0PLnF5UZQ6dTrJ7ybgzePAYPhETV+5rdTL9AwW4/wwkHfctidQK3/8ISCgW -2hEWgaQuqPXZxJPShybKzL1q1WLPAgMBAAECgYBZIAMOXXrjxt0GomCunyZL8sfC -TagBJAzMKtuipE2ABwM0uBL9SaHU5z1aNDrej8ZX5+tnffzFz/bV0lPPvqzK4CRm -KRNdYLcqXjlNPmaKvWDEJs6Fh6bZOycOENZEREjkfaVlvsAS7QJfHBuZIv+v+n9o -SE2YzOeCs76fdjjQMQJBAPQcsYEX11S1eAUtyoQKqf8v3CezNcM1BI2h7e3c52FS -rBrNJSYBKkBTL9Nm9+2K9LBV8nvhLsI2MVqiK2udk10CQQDUjk/pDWBS6olsgLby -5IlT4qWi1GLlXV07hAwD4I6Xk0SefZ3s8CgUUwaGnn2/5uZomeCirpCNVmqmyXuD -vLgbAkEAhVJae6faue/2YdW1glIUsEOiWKhe14NQPk5PFRcN47B0QJsEC/Kc8c69 -ExdslvbKVrhKG/BLSlSwtdBWKItCHQJAQCIIXmsYyyvU9xYHHVZzUQorq+ulQ0te -XBzFe03/+CAJLkD8q4bysN80Mt4TVxmWH61+J9e/6cVPPK/CQsdoTQJBANo+44+3 -j3n0K2eq9vDuttHbPB83APXMmjroEnuQF+sv5IK2VQENznoou/GqoflPUZXnzBxc -dFx3FLksqaZr5IM=""" - -X509 = """MIIDYDCCAsmgAwIBAgIBADANBgkqhkiG9w0BAQ0FADCBzDELMAkGA1UEBhMCbmwx -EjAQBgNVBAgMCUdyb25pbmdlbjEgMB4GA1UECgwXVW5pdmVyc2l0eSBvZiBHcm9u -aW5nZW4xKTAnBgNVBAMMIGNvc21vLnNlcnZpY2UucnVnLm5sL3J1Zy13ZWJzaXRl -MRIwEAYDVQQHDAlHcm9uaW5nZW4xKDAmBgNVBAsMH1Jlc2VhcmNoIGFuZCBJbm5v -dmF0aW9uIFN1cHBvcnQxHjAcBgkqhkiG9w0BCQEWD3Jpc0BsaXN0LnJ1Zy5ubDAe -Fw0xNzExMTQwODM4NTVaFw0yNzExMTIwODM4NTVaMIHMMQswCQYDVQQGEwJubDES -MBAGA1UECAwJR3JvbmluZ2VuMSAwHgYDVQQKDBdVbml2ZXJzaXR5IG9mIEdyb25p -bmdlbjEpMCcGA1UEAwwgY29zbW8uc2VydmljZS5ydWcubmwvcnVnLXdlYnNpdGUx -EjAQBgNVBAcMCUdyb25pbmdlbjEoMCYGA1UECwwfUmVzZWFyY2ggYW5kIElubm92 -YXRpb24gU3VwcG9ydDEeMBwGCSqGSIb3DQEJARYPcmlzQGxpc3QucnVnLm5sMIGf -MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDKr3cccv8/SF7sQYR1nSeF2I4ttV1s -m7BWDwdi8SqEwdPdfaAko/FrFtmnklFug7HS5ItbR56sIUztg+WCsitDy5xeVGUO -nU6ye8m4M3jwGD4RE1fua3Uy/QMFuP8MJB33LYnUCt//CEgoFtoRFoGkLqj12cST -0ocmysy9atVizwIDAQABo1AwTjAdBgNVHQ4EFgQUZeo8RVZu3DThn3/zFG0F9GY3 -ePcwHwYDVR0jBBgwFoAUZeo8RVZu3DThn3/zFG0F9GY3ePcwDAYDVR0TBAUwAwEB -/zANBgkqhkiG9w0BAQ0FAAOBgQA05TKxrECfo9riTAkSSJlr4mCO3rcRdeFy6r7w -84oASZdRsqyZDngQdR9QnMpIxuEt9jwoTe/5le6wq67hZtTKewZc/IhcZvbqxTmi -UWSCBCsT1tlzm8plg2B8mqS+Sp/b8ouRVaDrHbjXciL+831LmhRy1FJwEYKGwCZE -i1/B4Q==""" - -CSR = """MIICDTCCAXYCAQAwgcwxCzAJBgNVBAYTAm5sMRIwEAYDVQQIDAlHcm9uaW5nZW4x -IDAeBgNVBAoMF1VuaXZlcnNpdHkgb2YgR3JvbmluZ2VuMSkwJwYDVQQDDCBjb3Nt -by5zZXJ2aWNlLnJ1Zy5ubC9ydWctd2Vic2l0ZTESMBAGA1UEBwwJR3JvbmluZ2Vu -MSgwJgYDVQQLDB9SZXNlYXJjaCBhbmQgSW5ub3ZhdGlvbiBTdXBwb3J0MR4wHAYJ -KoZIhvcNAQkBFg9yaXNAbGlzdC5ydWcubmwwgZ8wDQYJKoZIhvcNAQEBBQADgY0A -MIGJAoGBAMqvdxxy/z9IXuxBhHWdJ4XYji21XWybsFYPB2LxKoTB0919oCSj8WsW -2aeSUW6DsdLki1tHnqwhTO2D5YKyK0PLnF5UZQ6dTrJ7ybgzePAYPhETV+5rdTL9 -AwW4/wwkHfctidQK3/8ISCgW2hEWgaQuqPXZxJPShybKzL1q1WLPAgMBAAGgADAN -BgkqhkiG9w0BAQ0FAAOBgQBClx4glTL7szKmUUFwgRa0LVpZh8b0TknJC3+6TLXo -I/4Ws3VSl/lTx1LU1ZR0JGvTF6WnrxpuXpyknZ3zRP7Ud5wYjIo7Moqcfr0Fsbpc -hv4a9zOzY7uuYesrOS5Bzr83BR0rvztlGbPAWnV2KpIODTLoEFTCHo+Ksprpvl18 -Zw==""" - -SAML_PROVIDER_METADATA_URL = 'https://tst-idp.id.rug.nl/nidp/saml2/metadata' - -import sys -from onelogin.saml2.xml_utils import OneLogin_Saml2_XML -if sys.version_info[0] == 2: - import urllib # python 2 -else: - assert sys.version_info[0] == 3 - import urllib.request as urllib # python 3 - -with urllib.urlopen(SAML_PROVIDER_METADATA_URL) as u: - RUG_PROVIDER_METADATA = u.read() - RUG_PROVIDER_X509CERT = OneLogin_Saml2_XML.query( - OneLogin_Saml2_XML.to_etree(RUG_PROVIDER_METADATA), - '/md:EntityDescriptor/ds:Signature/ds:KeyInfo/ds:X509Data/ds:X509Certificate' - ) - - assert len(RUG_PROVIDER_X509CERT) > 0, "Excepted a X509 RUG Provider Certificate" - assert len(RUG_PROVIDER_X509CERT) == 1, "Excepted no more than 1 X509 RUG Provider Certificate" - RUG_PROVIDER_X509CERT = RUG_PROVIDER_X509CERT[0].text.strip() - - - -SAML_PROVIDERS = [{ - "RuG": { - "strict": True, - "debug": True, - "custom_base_path": "", - "sp": { - "entityId": "https://cosmo.service.rug.nl/rug-website/saml2/metadata", - # "entityId": "https://cosmo.service.rug.nl/rugwebsite/saml/metadata?provider=RuG", - "assertionConsumerService": { - "url": "https://cosmo.service.rug.nl/rugwebsite/sso/saml/?provider=RuG&acs", - "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" - }, - "singleLogoutService": { - "url": "https://cosmo.service.rug.nl/rugwebsite/sso/saml/?provider=RuG", - "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" - }, - "NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", - "x509cert": X509, - "privateKey": PRIVATE_KEY, - }, - "idp": { - "entityId": "https://tst-idp.id.rug.nl/nidp/saml2/metadata", - "singleSignOnService": { - "url": "https://tst-idp.id.rug.nl/nidp/saml2/sso", - "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" - }, - "singleLogoutService": { - "url": "https://tst-idp.id.rug.nl/nidp/saml2/spslo", - "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" - }, - "x509cert": RUG_PROVIDER_X509CERT, - }, - "organization": { - "en-US": { - "name": "University of Groningen", - "displayname": "University of Groningen", - "url": "https://cosmo.service.rug.nl/rugwebsite/" - } - }, - "contact_person": { - "technical": { - "given_name": "Research and Innovation Support", - "email_address": "ris@list.rug.nl" - }, - "support": { - "given_name": "Research and Innovation Support", - "email_address": "ris@list.rug.nl" - } - }, - "security": { - "requestedAuthnContext": False, - "name_id_encrypted": False, - "authn_requests_signed": True, - "logout_requests_signed": False, - "logout_response_signed": False, - "sign_metadata": False, - "want_messages_signed": False, - "want_assertions_signed": True, - "want_name_id": True, - "want_name_id_encrypted": False, - "want_assertions_encrypted": True, - "signature_algorithm": "http://www.w3.org/2000/09/xmldsig#rsa-sha1", - "digest_algorithm": "http://www.w3.org/2000/09/xmldsig#rsa-sha1", - } - } -}] +SAML_PROVIDERS = []