Start SAML implementation
This commit is contained in:
parent
8359a98fdd
commit
860c497c86
10 changed files with 129 additions and 3 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -4,4 +4,5 @@ db.sqlite3
|
|||
db.sqlite3-journal
|
||||
venv/
|
||||
config.ini
|
||||
/static/
|
||||
/static/
|
||||
/certificates/
|
|
@ -1,7 +1,9 @@
|
|||
[App]
|
||||
Debug = 0
|
||||
Hosts = ["kumidc.local"]
|
||||
BaseURL = "https://kumidc.local/"
|
||||
# StaticDir = /var/www/html/kumidc/static
|
||||
# CertificateDir = /etc/ssl/kumidc/
|
||||
|
||||
# [MySQL]
|
||||
# Database = kumidc
|
||||
|
|
0
core/management/commands/__init__.py
Normal file
0
core/management/commands/__init__.py
Normal file
47
core/management/commands/createsamlcert.py
Normal file
47
core/management/commands/createsamlcert.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
from django.conf import settings
|
||||
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
from cryptography.hazmat.primitives import serialization, hashes
|
||||
from cryptography import x509
|
||||
from cryptography.x509.oid import NameOID
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Generates self-signed certificate for SAML IdP'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('-f', '--force', action='store_true', help="Force re-creation of certificates if the files already exist")
|
||||
parser.add_argument('--commonname', type=str, help="Common Name to use for certificate, default: KumiDC", default="KumiDC")
|
||||
parser.add_argument('--country', type=str, help="Country Code to use for the certificate, default: US", default="US")
|
||||
parser.add_argument('--state', type=str, help="State name to use for the certificate, default: New York", default="New York")
|
||||
parser.add_argument('--locality', type=str, help="Locality name to use for the certificate, default: New York City", default="New York City")
|
||||
parser.add_argument('--organization', type=str, help="Organization name to use for the certificate, default: KumiDC", default="KumiDC")
|
||||
parser.add_argument('--validity-days', type=int, help="How many days the certificate should be \"valid\" for, default: 3650", default=3650)
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
if (settings.CERTIFICATE_DIR / "saml.key").exists() or (settings.CERTIFICATE_DIR / "saml.crt").exists():
|
||||
if not kwargs["force"]:
|
||||
print(f"Error: saml.crt and/or saml.key already in CERTIFICATE_DIR ({settings.CERTIFICATE_DIR}). Add --force to create new key pair.")
|
||||
|
||||
key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||
|
||||
subject = issuer = x509.Name([
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, kwargs["commonname"]),
|
||||
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, kwargs["state"]),
|
||||
x509.NameAttribute(NameOID.COUNTRY_NAME, kwargs["country"]),
|
||||
x509.NameAttribute(NameOID.LOCALITY_NAME, kwargs["locality"]),
|
||||
x509.NameAttribute(NameOID.ORGANIZATION_NAME, kwargs["organization"]),
|
||||
])
|
||||
|
||||
cert = x509.CertificateBuilder().subject_name(subject).issuer_name(issuer).public_key(key.public_key()).serial_number(x509.random_serial_number()).not_valid_before(datetime.utcnow()).not_valid_after(datetime.utcnow() + timedelta(days=3650)).add_extension(x509.SubjectAlternativeName([x509.DNSName(name) for name in settings.ALLOWED_HOSTS]), critical=False).sign(key, hashes.SHA256())
|
||||
|
||||
settings.CERTIFICATE_DIR.mkdir(exist_ok=True)
|
||||
|
||||
with open(settings.CERTIFICATE_DIR / "saml.key", "wb") as keyfile:
|
||||
keyfile.write(key.private_bytes(serialization.Encoding.PEM, serialization.PrivateFormat.TraditionalOpenSSL, serialization.NoEncryption()))
|
||||
|
||||
with open(settings.CERTIFICATE_DIR / "saml.crt", "wb") as certfile:
|
||||
certfile.write(cert.public_bytes(serialization.Encoding.PEM))
|
0
core/saml/__init__.py
Normal file
0
core/saml/__init__.py
Normal file
6
core/saml/processors.py
Normal file
6
core/saml/processors.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
from djangosaml2idp.processors import BaseProcessor
|
||||
|
||||
|
||||
class SAMLProcessor(BaseProcessor):
|
||||
def enable_multifactor(self, user):
|
||||
return user.totpsecret.exists() and user.totpsecret.active
|
7
frontend/views/saml.py
Normal file
7
frontend/views/saml.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
from djangosaml2idp.views import ProcessMultiFactorView
|
||||
|
||||
from authentication.mixins.timeout import TimeoutMixin
|
||||
|
||||
|
||||
class SAMLMultiFactorView(TimeoutMixin, ProcessMultiFactorView):
|
||||
pass
|
|
@ -1,9 +1,15 @@
|
|||
from django.urls import reverse_lazy
|
||||
|
||||
from pathlib import Path
|
||||
from urllib.parse import urljoin
|
||||
|
||||
import json
|
||||
|
||||
import saml2
|
||||
|
||||
from saml2.saml import NAMEID_FORMAT_EMAILADDRESS, NAMEID_FORMAT_UNSPECIFIED
|
||||
from saml2.sigver import get_xmlsec_binary
|
||||
|
||||
from autosecretkey import AutoSecretKey
|
||||
|
||||
|
||||
|
@ -16,7 +22,9 @@ SECRET_KEY = CONFIG_FILE.secret_key
|
|||
DEBUG = CONFIG_FILE.config.getboolean("App", "Debug", fallback=False)
|
||||
|
||||
ALLOWED_HOSTS = json.loads(CONFIG_FILE.config["App"]["Hosts"])
|
||||
BASE_URL = CONFIG_FILE.config["App"]["BaseURL"]
|
||||
|
||||
CERTIFICATE_DIR = Path(CONFIG_FILE.config.get("App", "CertificateDir", fallback=BASE_DIR / "certificates"))
|
||||
|
||||
# Application definition
|
||||
|
||||
|
@ -35,7 +43,9 @@ INSTALLED_APPS = [
|
|||
'core',
|
||||
'authentication',
|
||||
'frontend',
|
||||
|
||||
'oidc_provider',
|
||||
'djangosaml2idp',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
@ -148,6 +158,52 @@ OIDC_TEMPLATES = {
|
|||
'authorize': 'frontend/oidc/authorize.html'
|
||||
}
|
||||
|
||||
# SAML Configuration
|
||||
|
||||
SAML_IDP_CONFIG = {
|
||||
'debug' : DEBUG,
|
||||
'xmlsec_binary': get_xmlsec_binary(['/opt/local/bin', '/usr/bin']),
|
||||
'entityid': urljoin(BASE_URL, '/saml/metadata/'),
|
||||
'description': 'KumiDC',
|
||||
|
||||
'service': {
|
||||
'idp': {
|
||||
'name': 'KumiDC',
|
||||
'endpoints': {
|
||||
'single_sign_on_service': [
|
||||
#(urljoin(BASE_URL, '/saml/sso/post/'), saml2.BINDING_HTTP_POST),
|
||||
(urljoin(BASE_URL, '/saml/sso/redirect/'), saml2.BINDING_HTTP_REDIRECT),
|
||||
],
|
||||
"single_logout_service": [
|
||||
#(urljoin(BASE_URL, "/saml/slo/post/"), saml2.BINDING_HTTP_POST),
|
||||
(urljoin(BASE_URL, "/saml/slo/redirect/"), saml2.BINDING_HTTP_REDIRECT)
|
||||
],
|
||||
},
|
||||
'name_id_format': [NAMEID_FORMAT_EMAILADDRESS, NAMEID_FORMAT_UNSPECIFIED],
|
||||
'sign_response': True,
|
||||
'sign_assertion': True,
|
||||
'want_authn_requests_signed': True,
|
||||
},
|
||||
},
|
||||
|
||||
# Signing
|
||||
'key_file': str(CERTIFICATE_DIR / 'saml.key'),
|
||||
'cert_file': str(CERTIFICATE_DIR / 'saml.crt'),
|
||||
|
||||
# Encryption
|
||||
'encryption_keypairs': [{
|
||||
'key_file': str(CERTIFICATE_DIR / 'saml.key'),
|
||||
'cert_file': str(CERTIFICATE_DIR / 'saml.crt'),
|
||||
}],
|
||||
|
||||
'valid_for': 365 * 24,
|
||||
}
|
||||
|
||||
SAML_IDP_SP_FIELD_DEFAULT_PROCESSOR = 'core.saml.processors.SAMLProcessor'
|
||||
SAML_IDP_MULTIFACTOR_VIEW = "frontend.views.saml.SAMLMultiFactorView"
|
||||
|
||||
SAML_AUTHN_SIGN_ALG = saml2.xmldsig.SIG_RSA_SHA256
|
||||
SAML_AUTHN_DIGEST_ALG = saml2.xmldsig.DIGEST_SHA256
|
||||
|
||||
# Session Timeouts
|
||||
|
||||
|
|
|
@ -4,9 +4,12 @@ from django.views.generic import RedirectView
|
|||
|
||||
|
||||
urlpatterns = [
|
||||
re_path(r'^openid/', include('oidc_provider.urls', namespace='oidc_provider')),
|
||||
re_path(r'^saml/', include('djangosaml2idp.urls', namespace="djangosaml2idp")),
|
||||
|
||||
path('admin/login/', RedirectView.as_view(url=reverse_lazy("auth:login"), query_string=True)),
|
||||
path('admin/', admin.site.urls),
|
||||
re_path(r'^openid/', include('oidc_provider.urls', namespace='oidc_provider')),
|
||||
|
||||
path('auth/', include(("authentication.urls", "auth"))),
|
||||
path('', include(("frontend.urls", "frontend"))),
|
||||
]
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
Django<4
|
||||
|
||||
git+https://github.com/juanifioren/django-oidc-provider
|
||||
django-oidc-provider
|
||||
djangosaml2idp
|
||||
|
||||
dbsettings
|
||||
django-autosecretkey
|
||||
|
||||
git+https://github.com/IdentityPython/pysaml2
|
||||
|
||||
cryptography
|
||||
pyotp
|
||||
django-timezone-field
|
||||
django-phonenumber-field[phonenumbers]
|
||||
|
|
Loading…
Reference in a new issue