djangosaml2 icon indicating copy to clipboard operation
djangosaml2 copied to clipboard

Multi-tenant - tenant configured IDP parameters

Open LiteWait opened this issue 3 years ago • 6 comments

Our mutli-tenant single page application can have 1000's of tenants. We can not know which of these tenants want to use SAML 2 for authentication, nor can we know what their SAML2 IDP setup is.

I assume there is no support for gathering the IDP/SAML2 parameters dynamically (e.g. setup is stored in the DB by tenant) BUT it appears it wouldn't be that difficult to implement.

Am I correct?

LiteWait avatar Nov 15 '22 13:11 LiteWait

you can customize your login page and passing your custom parameters to your auth backend, this latter would select the saml2 authz or any other

feel free to share your thoughs here, probably I've miss something

peppelinux avatar Nov 15 '22 13:11 peppelinux

Actually we are using Django tenants middleware, we know the domain so we can set the DB schema for that tenant. The trick I think is to pull the IDP/SAML config from the DB rather than something that is hard-coded in settings.

On Tue, Nov 15, 2022 at 8:42 AM Giuseppe De Marco @.***> wrote:

you can customize your login page and passing your custom parameters to your auth backend, this latter would select the saml2 authz or any other

feel free to share your thoughs here, probably I've miss something

— Reply to this email directly, view it on GitHub https://github.com/IdentityPython/djangosaml2/issues/357#issuecomment-1315327283, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAG3J3JUJG3XTDDDTXDXCIDWIOHKZANCNFSM6AAAAAASA5N4IY . You are receiving this because you authored the thread.Message ID: @.***>

LiteWait avatar Nov 15 '22 13:11 LiteWait

is this it? https://github.com/django-tenants/django-tenants

peppelinux avatar Nov 15 '22 15:11 peppelinux

Yes we forked it. Same concept, shared schema has mapping to domain to DB schema name. Not really part of the problem, my assumption is that the code that picks up the IDP configuration must look at the DB vs. settings.py

On Tue, Nov 15, 2022 at 10:39 AM Giuseppe De Marco @.***> wrote:

is this it? https://github.com/django-tenants/django-tenants

— Reply to this email directly, view it on GitHub https://github.com/IdentityPython/djangosaml2/issues/357#issuecomment-1315492088, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAG3J3PHZV7QGNZ7MVWDXQTWIOVEBANCNFSM6AAAAAASA5N4IY . You are receiving this because you authored the thread.Message ID: @.***>

LiteWait avatar Nov 15 '22 15:11 LiteWait

well, you're asking for a way to get the IDP configuration from the DB instead that from settings

we can have this, would you like to push a PR to start the implementation of this feature? that would be for a major release

peppelinux avatar Nov 15 '22 15:11 peppelinux

Here's a rough spec of how I implemented multi-tenancy with djangosaml2.

In my implementation, I have an IdentityProvider model that stores the xml_metadata_url against a tenant.

from copy import deepcopy
from urllib.parse import unquote

from django.conf import settings
from django.core.exceptions import ValidationError
from saml2.config import SPConfig
from saml2.mdstore import SourceNotFound

def saml_config_loader(request):
    config = deepcopy(settings.SAML_CONFIG)

    # in case of login, we need to let user select the IdP.
    if request.resolver_match.url_name == "saml2_login":
        requested_idp_url_encoded = request.GET.get("idp")
        if not requested_idp_url_encoded:
            raise SourceNotFound
        requested_idp_url = unquote(requested_idp_url_encoded)
        try:
            validate_url(requested_idp_url)
        except ValidationError:
            raise SourceNotFound
        if not IdentityProvider.objects.filter(
            xml_metadata_url=requested_idp_url
        ).exists():
            raise SourceNotFound

        config["metadata"]["remote"] = [{"url": requested_idp_url}]

    # in case of ACS, we need to have all available IdPs in the metadata
    # so the correct one can be chosen by djangosaml2.
    elif request.resolver_match.url_name == "saml2_acs":
        config["metadata"]["remote"] = [
            {"url": url}
            for url in IdentityProvider.objects.values_list(
                "xml_metadata_url", flat=True
            )
        ]

    # delete `idp`, `scoping` and `idphint` or any other query param
    # to prevent djangosaml2 from processing it
    request.GET = {}

    return SPConfig().load(config)

In Django settings.py,

SAML_CONFIG_LOADER = "contrib.saml2.config.saml_config_loader"

SAML_CONFIG["metadata"] = {} # empty because dynamically loaded using saml_config_loader

eshaan7 avatar Apr 12 '23 08:04 eshaan7