create settings
This commit is contained in:
parent
6737e5b68c
commit
00478e626b
@ -1,2 +1,7 @@
|
||||
# RuG website template with SAML2 login
|
||||
|
||||
|
||||
# CENTOS dependencies
|
||||
|
||||
yum install libxml2-devel libxslt-devel python34-devel xmlsec1-devel libmcrypt libmcrypt-devel xmlsec1-openssl
|
||||
|
||||
|
Binary file not shown.
83
rugwebsite/management/commands/init-saml2-settings.py
Normal file
83
rugwebsite/management/commands/init-saml2-settings.py
Normal file
@ -0,0 +1,83 @@
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
import datetime
|
||||
import os.path
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography import x509
|
||||
from cryptography.x509.oid import NameOID
|
||||
|
||||
try:
|
||||
import urllib # python 2
|
||||
except:
|
||||
import urllib.request as urllib # python 3
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Create SAML2 settings, they are printed to standard out and it is up to de developer or deployer to add' \
|
||||
'them to the django settings.'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('--country', required=True, nargs=1, type=str, dest='country', help='For example: US, NL, DE, etc')
|
||||
parser.add_argument('--city', required=True, nargs=1, type=str, dest='city', help='For example: London or Groningen')
|
||||
parser.add_argument('--state', required=True, nargs=1, type=str, dest='state', help='For example: State or province, for example California or Groningen.')
|
||||
parser.add_argument('--organisation', required=True, nargs=1, type=str, dest='organisation', help='Typically \'University of Groningen\'')
|
||||
parser.add_argument('--organisation-unit', required=True, nargs=1, type=str, dest='organisation-unit', help='For example: \'Research and Innovation\' Support or \'Faculty of smart people\'')
|
||||
parser.add_argument('--common-name', required=True, nargs=1, type=str, dest='common-name')
|
||||
parser.add_argument('--alternative', nargs='*', type=str, dest='alternatives')
|
||||
parser.add_argument('--support-name', required=True, nargs=1, type=str, dest='support_name')
|
||||
parser.add_argument('--support-email', required=True, nargs=1, type=str, dest='support_email')
|
||||
parser.add_argument('--technical-name', required=True, nargs=1, type=str, dest='technical_name')
|
||||
parser.add_argument('--technical-email', required=True, nargs=1, type=str, dest='technical_email')
|
||||
parser.add_argument('--entity-id', required=True, nargs=1, type=str, dest='entity_id', help='Used as an identifyer of your service, typically the url to your service: www.rug.nl/yourservice')
|
||||
parser.add_argument('--base-url', required=True, nargs=1, type=str, dest='base_url', help='The base url of your service, the route <base_url>sso/saml/ should exist.')
|
||||
parser.add_argument('--expires-after-days', nargs='?', type=int, default=10 * 365, dest='expires')
|
||||
|
||||
def handle(self, *args, **options):
|
||||
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]),
|
||||
])
|
||||
cert = (
|
||||
x509.CertificateBuilder()
|
||||
.subject_name(subject)
|
||||
.issuer_name(issuer)
|
||||
.public_key(key.public_key())
|
||||
.serial_number(x509.random_serial_number())
|
||||
.not_valid_before(datetime.datetime.utcnow())
|
||||
.not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=options['expires']))
|
||||
.add_extension(x509.SubjectAlternativeName([
|
||||
x509.DNSName(alternative)
|
||||
for alternative in options['alternatives']
|
||||
] if options['alternatives'] is not None else []), critical = False)
|
||||
.sign(key, hashes.SHA256(), default_backend())
|
||||
)
|
||||
settings_template = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'settings_template.py')
|
||||
|
||||
key_text = key.private_bytes(encoding=serialization.Encoding.PEM,
|
||||
format = serialization.PrivateFormat.TraditionalOpenSSL,
|
||||
encryption_algorithm = serialization.NoEncryption()).decode('utf-8')
|
||||
x509_text = cert.public_bytes(encoding=serialization.Encoding.PEM).decode('utf-8')
|
||||
|
||||
assert (
|
||||
key_text.startswith('-----BEGIN RSA PRIVATE KEY-----\n') and
|
||||
key_text.endswith('\n-----END RSA PRIVATE KEY-----\n')), "Private key starts and ends improperly, should " \
|
||||
"be -----BEGIN RSA PRIVATE KEY----- and " \
|
||||
"-----END RSA PRIVATE KEY-----"
|
||||
assert (
|
||||
x509_text.startswith('-----BEGIN CERTIFICATE-----\n') and
|
||||
x509_text.endswith('\n-----END CERTIFICATE-----\n')), "Certificate starts and ends improperly, should " \
|
||||
"be -----BEGIN CERTIFICATE----- and " \
|
||||
"-----END CERTIFICATE-----"
|
||||
key_text = key_text[len('-----BEGIN RSA PRIVATE KEY-----\n'):-len('\n-----END RSA PRIVATE KEY-----\n')]
|
||||
x509_text = x509_text[len('-----BEGIN CERTIFICATE-----\n'):-len('\n-----END CERTIFICATE-----\n')]
|
||||
|
||||
|
||||
with open(settings_template, 'r') as f:
|
||||
print(f.read().format(private_key=key_text , x509=x509_text, **options))
|
||||
|
@ -1,19 +0,0 @@
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.conf import settings
|
||||
|
||||
try:
|
||||
import urllib # python 2
|
||||
except:
|
||||
import urllib.request as urllib # python 3
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Initialize saml2 authentication files'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
pass
|
||||
|
||||
def handle(self, *args, **options):
|
||||
|
||||
urllib.urlretrieve(settings.SAML_PROVIDER_METADATA_URL, os.path.join())
|
||||
|
131
rugwebsite/management/commands/settings_template.py
Normal file
131
rugwebsite/management/commands/settings_template.py
Normal file
@ -0,0 +1,131 @@
|
||||
|
||||
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
'django_saml2_pro_auth.auth.Backend'
|
||||
]
|
||||
|
||||
# identifier for SAML to the service provider.
|
||||
ENTITY_ID = '{entity_id}'
|
||||
|
||||
# Important to make sure redirects and such work properly
|
||||
BASE_URL = '{base_url}'
|
||||
|
||||
# This support information is used for the SAML2 service provider contact information
|
||||
TECHNICAL_NAME = '{technical_name}'
|
||||
TECHNICAL_EMAIL = '{technical_email}'
|
||||
SUPPORT_NAME = '{support_name}'
|
||||
SUPPORT_EMAIL = '{support_email}'
|
||||
|
||||
ORGANISATION = '{organisation}'
|
||||
|
||||
|
||||
SAML_ROUTE = BASE_URL + 'sso/saml/'
|
||||
# redirection after successful SAML2 login
|
||||
SAML_REDIRECT = BASE_URL + '/'
|
||||
|
||||
# Mapping used to move the SAML2 attributes to the django-auth user database
|
||||
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),
|
||||
}}
|
||||
}}]
|
||||
|
||||
#Private key stripped from the ---BEGIN ... and ---END ... part
|
||||
PRIVATE_KEY = """{private_key}"""
|
||||
|
||||
# Idem for the certificate
|
||||
X509 = """{x509}"""
|
||||
|
||||
# RuG metadata url, should not change unless you want another service provider.
|
||||
SAML_PROVIDER_METADATA_URL = 'https://tst-idp.id.rug.nl/nidp/saml2/metadata'
|
||||
|
||||
#Code to get the RuG identity provider certificate
|
||||
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()
|
||||
|
||||
|
||||
# Construction of the service provider metadata.
|
||||
SAML_PROVIDERS = [{{
|
||||
"RuG": {{
|
||||
"strict": True,
|
||||
"debug": True,
|
||||
"custom_base_path": "",
|
||||
"sp": {{
|
||||
"entityId": ENTITY_ID,
|
||||
"assertionConsumerService": {{
|
||||
"url": BASE_URL + "/sso/saml/?provider=RuG&acs",
|
||||
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
||||
}},
|
||||
"singleLogoutService": {{
|
||||
"url": BASE_URL + "/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": ORGANISATION,
|
||||
"displayname": ORGANISATION,
|
||||
"url": BASE_URL
|
||||
}}
|
||||
}},
|
||||
"contact_person": {{
|
||||
"technical": {{
|
||||
"given_name": TECHNICAL_NAME,
|
||||
"email_address": TECHNICAL_EMAIL
|
||||
}},
|
||||
"support": {{
|
||||
"given_name": SUPPORT_NAME,
|
||||
"email_address": SUPPORT_EMAIL
|
||||
}}
|
||||
}},
|
||||
"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",
|
||||
}}
|
||||
}}
|
||||
}}]
|
@ -61,8 +61,6 @@ USE_TZ = True
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
|
||||
LOGOUT_URL = '/logout/'
|
||||
LOGIN_URL = '/login/'
|
||||
|
||||
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
@ -76,7 +74,7 @@ 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:givenName", 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),
|
||||
}
|
||||
}]
|
||||
|
Loading…
Reference in New Issue
Block a user