[correu] Add registration for SAML client

merge-requests/6/head
Evilham 2022-07-10 20:31:57 +02:00
parent 7bcc08bc6d
commit 4324812807
No known key found for this signature in database
GPG Key ID: AE3EE30D970886BF
4 changed files with 193 additions and 5 deletions

8
dd-ctl
View File

@ -476,11 +476,9 @@ saml_certificates(){
echo " --> Setting up SAML for wordpress"
docker exec -ti dd-sso-admin sh -c "export PYTHONWARNINGS='ignore:Unverified HTTPS request' && cd /admin/saml_scripts/ && python3 wordpress_saml.py"
# SAML PLUGIN MOODLE
# echo "To add SAML to moodle:"
# echo "1.-Activate SAML plugin in moodle extensions, regenerate certificate, lock certificate"
# echo "2.-Then run: docker exec -ti dd-sso-admin python3 /admin/nextcloud_saml.py"
# echo "3.-"
# SAML PLUGIN EMAIL
echo " --> Setting up SAML for email"
docker exec -ti dd-sso-admin sh -c "export PYTHONWARNINGS='ignore:Unverified HTTPS request' && cd /admin/saml_scripts/ && python3 email_saml.py"
}
wait_for_moodle(){

View File

@ -0,0 +1,187 @@
#
# Copyright © 2022 Evilham
#
# This file is part of DD
#
# DD is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# DD is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License
# along with DD. If not, see <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: AGPL-3.0-or-later
import logging as log
import os
import time
import traceback
from typing import Any, Dict, Optional
from lib.keycloak_client import KeycloakClient
try:
# Python 3.9+
from functools import cache # type: ignore # Currently targetting 3.8
except ImportError:
# Up to python 3.8
from functools import cached_property as cache
app: Dict[str, Dict[str, str]] = {}
app["config"] = {}
class SamlService(object):
"""
Generic class to manage a SAML service on keycloak
"""
keycloak: KeycloakClient
domain: str = os.environ["DOMAIN"]
def __init__(self):
self.keycloak = KeycloakClient()
@cache
def public_cert(self) -> str:
"""
Read the public SAML certificate as used by keycloak
"""
ready = False
basepath = os.path.dirname(__file__)
while not ready:
# TODO: Check why they were using a loop
try:
with open(
os.path.abspath(
os.path.join(basepath, "../saml_certs/public.cert")
),
"r",
) as crt:
app["config"]["PUBLIC_CERT"] = crt.read()
ready = True
except IOError:
log.warning("Could not get public certificate for SAML. Retrying...")
log.warning(
" You should generate them: /admin/saml_certs # openssl req -nodes -new -x509 -keyout private.key -out public.cert"
)
time.sleep(2)
except:
log.error(traceback.format_exc())
log.info("Got public SAML certificate")
return app["config"]["PUBLIC_CERT"]
def get_client(self, client_id: str) -> Any:
# TODO: merge with keycloak_config.py
self.keycloak.connect()
k = self.keycloak.keycloak_admin
clients = k.get_clients()
client = next(filter(lambda c: c["clientId"] == client_id, clients), None)
return (k, client)
def set_client(self, client_id: str, client_overrides: Dict[str, Any]) -> str:
(k, client) = self.get_client(client_id)
if client is None:
client_uid = k.create_client(client_overrides)
else:
client_uid = client["id"]
k.update_client(client_uid, client_overrides)
return client_id
def configure(self) -> None:
pass
class EmailSaml(SamlService):
client_name: str = "correu"
client_description: str = "Client for the DD-managed email service"
email_domain: str
def __init__(self, email_domain: str, enabled: bool = False):
super(EmailSaml, self).__init__()
self.email_domain = email_domain
@property
def enabled(self) -> bool:
return bool(self.email_domain)
def configure(self) -> None:
srv_base = f"https://correu.{self.domain}"
client_id = f"{srv_base}/metadata/"
client_overrides: Dict[str, Any] = {
"name": self.client_name,
"description": self.client_description,
"clientId": client_id,
"baseUrl": f"{srv_base}/login",
"enabled": self.enabled,
"redirectUris": [
f"{srv_base}/*",
],
"webOrigins": [srv_base],
"consentRequired": False,
"protocol": "saml",
"attributes": {
"saml.assertion.signature": True,
"saml_idp_initiated_sso_relay_state": f"{srv_base}/login",
"saml_assertion_consumer_url_redirect": f"{srv_base}/acs",
"saml.force.post.binding": True,
"saml.multivalued.roles": False,
"saml.encrypt": False,
"saml_assertion_consumer_url_post": f"{srv_base}/acs",
"saml.server.signature": True,
"saml_idp_initiated_sso_url_name": f"{srv_base}/acs",
"saml.server.signature.keyinfo.ext": False,
"exclude.session.state.from.auth.response": False,
"saml_single_logout_service_url_redirect": f"{srv_base}/ls",
"saml.signature.algorithm": "RSA_SHA256",
"saml_force_name_id_format": False,
"saml.client.signature": False,
"tls.client.certificate.bound.access.tokens": False,
"saml.authnstatement": True,
"display.on.consent.screen": False,
"saml_name_id_format": "username",
"saml_signature_canonicalization_method": "http://www.w3.org/2001/10/xml-exc-c14n#",
"saml.onetimeuse.condition": False,
},
"protocolMappers": [
{
"name": "username",
"protocol": "saml",
"protocolMapper": "saml-user-property-mapper",
"consentRequired": False,
"config": {
"attribute.nameformat": "Basic",
"user.attribute": "username",
"friendly.name": "username",
"attribute.name": "username",
},
},
{
"name": "email",
"protocol": "saml",
"protocolMapper": "saml-user-property-mapper",
"consentRequired": False,
"config": {
"attribute.nameformat": "Basic",
"user.attribute": "email",
"friendly.name": "email",
"attribute.name": "email",
},
},
],
}
self.set_client(client_id, client_overrides)
if __name__ == "__main__":
email_domain = os.environ.get("MANAGED_EMAIL_DOMAIN", "")
log.info("Configuring SAML client for Email")
EmailSaml(email_domain).configure()

View File

@ -48,3 +48,4 @@ services:
environment:
- VERIFY="false" # In development do not verify certificates
- DOMAIN=${DOMAIN}
- MANAGED_EMAIL_DOMAIN=${MANAGED_EMAIL_DOMAIN}

View File

@ -22,6 +22,8 @@
TITLE="DD"
TITLE_SHORT="DD"
DOMAIN=mydomain.com
# If defined, DD will be managing email for this domain
#MANAGED_EMAIL_DOMAIN=${DOMAIN}
LETSENCRYPT_DNS=
LETSENCRYPT_EMAIL=
# Generate letsencrypt certificate for root domain