pennersr / django-allauth

Integrated set of Django applications addressing authentication, registration, account management as well as 3rd party (social) account authentication.
https://allauth.org
MIT License
9.11k stars 2.98k forks source link

how to add support for SAML federations #3749

Open MichaelMenge opened 2 months ago

MichaelMenge commented 2 months ago

I want to allow authentication by all IDPs in a saml federation (e.g. DFN-AAI or eduGAIN).

I am willing to try to implement this feature myself, but I am not a programmer but a system administrator. Therefore i would like to get feedback on how to best implement the support for SAML federations.

At the moment a manual configuration for each idp in the federation is possible, but:

My goals are:

Below is a skeleton example configuration

SOCIALACCOUNT_PROVIDERS = {
  "saml": {
    ...
    "APPS": [
      {
        "name": "eduGAIN"
        "client_id": "edugain"
        "settings": {
           # Default mapping
           "attribute_mapping": {
              "uid": "http://schemas.auth0.com/clientID",
              "email_verified": "http://schemas.auth0.com/email_verified",
              "email": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
           },
        "federation": {
           # metadata aggregate
           "metadata_url":   "https://mds.edugain.org/edugain-v2.xml",
           # List of idp entity_ids to exclude               
           "exclude_entity_ids": [],
           # Array of entity_ids and new attribute_mapping to overwrite the default mapping
           "overwrite_mapping": [].
           "ds_url": "https://wayf.aai.dfn.de/DFN-AAI-eduGAIN/wayf/www/WAYF.php",
        },
       "sp": {
         ...
       },
      "advanced": {
        ...
      },
     }
    ]
  }
}        
pennersr commented 2 months ago

I think it would make most sense to override the list_apps() adapter method:

https://docs.allauth.org/en/latest/socialaccount/adapter.html#allauth.socialaccount.adapter.DefaultSocialAccountAdapter.list_apps

By default, it reads SocialApp records from the database, as well as apps configured in settings. But, there is nothing stopping you from doing:

def list_apps(self, request, provider=None, client_id=None):
  apps = super().list_apps(request, provider=provider, client_id=client_id)
  for some_config in some_source:
    app = SocialApp(provider="saml", provider_id=...)  # convert some_config
    if client_id  and client_id != app.client_id:
      continue
    if provider and app.provider_id != provider and app.provider != provider:
      continue
    apps.append(app)
  return apps

As for this:

The List of hundreds of idps would clutter the "accounts/login" page

Isn't that just a matter of altering the login template? Though, we could streamline that with supporting app.settings['visible'] = False.

Would this approach address your needs?

MichaelMenge commented 2 months ago

@pennersr Thank you for your input. I had not considered the option to override the default adapter.

While i still think that the support for saml federations in gjango-allauth might be useful in the long run, I agree that overriding list_apps and changing the login template is the faster and direct approach.

pennersr commented 2 months ago

Ok, let's try it this way first.

MichaelMenge commented 1 month ago

@pennersr I was able to configure multiple IdP with my own list_apps() method as long as each had a different client_id. But this leads to a problem regarding the client_id/organization slug in the acs URLs The sls URLs may have the same problem, but I have not tested this.

The SP Metadata information is managed via a national service portal and is shared with all IdPs in the DFN AAI / eduGAIN. The IdPs check if the URLs used are listed in the metadata information for the SP. I can provide multiple acs and sls URLs but wildcards are not supported. At the moment there are ~400 IdPs in the DFN AAI and ~5600 in eduGAIN. Adding and managing that many URLs in my SP metadata information is not feasible.

So for the saml authentication to work with federations, i see two possible implementations

  1. a special acs URL should be used, that is independent of the client_id.
  2. multiple IdPs with the same client_id should be supported.

The SAML Response has to be parsed in both cases to extract the entityID of the IdP, to look up the configuration as the client_id is not provided with the URL in the first case, or is not sufficient access the correct configuration.

At the moment I tend to the independent special URL implementation, as it seems to have the least impact on existing code.

pennersr commented 1 month ago

Just to be sure that I am fully grasping the issue -- you are implementing the SP side of things. So you have ~400 IdPs, meaning, You have 400 acs urls /accounts/saml/{1..400}/acs/, as well as 400 metada urls /accounts/saml/{1..400}/metadata/. So, the issue is that you need to manually configure those 400 metadata urls over at the portal? Because here you mention:

Adding and managing that many URLs in my SP metadata information is not feasible.

... suggesting that you are not managing those in the portal but in your SP metadata somehow?

But suppose there is just one special acs URL and a user is using that to authenticate. How could you lookup the IdP without having it available in the URL?

pennersr commented 1 month ago

Thinking out loud how this could be supported out of the box:

I guess this is what you were suggesting in your initial comment? I am not sure though what "ds_url": "https://wayf.aai.dfn.de/DFN-AAI-eduGAIN/wayf/www/WAYF.php" is meant for?

MichaelMenge commented 1 month ago

first, I am new to saml authentication and I am a sysadmin and not a professional programmer, so take my information with a grain of salt.

Just to be sure that I am fully grasping the issue -- you are implementing the SP side of things. So you have ~400 IdPs, meaning, You have 400 acs urls /accounts/saml/{1..400}/acs/, as well as 400 metada urls /accounts/saml/{1..400}/metadata/. So, the issue is that you need to manually configure those 400 metadata urls over at the portal? Because here you mention:

Yes I am only implementing the SP side. For security reasons the IdPs do not fetch the metadata from SP, but use the metadata provided by the federation via the portal. So as far as I know the metadata url is not relevant.

Adding and managing that many URLs in my SP metadata information is not feasible.

... suggesting that you are not managing those in the portal but in your SP metadata somehow?

But suppose there is just one special acs URL and a user is using that to authenticate. How could you lookup the IdP without having it available in the URL?

The IdP entetyID is included in the SAMLResponse

Thinking out loud how this could be supported out of the box:

* You would only setup one SAML app, with the actual IdP information left blank.

My Initial idea was to provide a metadata file with all the IdPs , or I could create a configuration for each IdP a my own list_apps() method. The later case might be usefull to overwrite some settings e.g. Attribute Mappings

* Then,  over at the ACS view, we could dynamically extract the issuer using `base64.b64decode(request.POST['SAMLResponse'])`.

yes,

* Then, we would need to fetch this URL https://mds.edugain.org/edugain-v2.xml and see if the issuer is known.

I would use a local copy with only the IdP metadata, but yes, Use the entiryID to extract the config parameters like certs,

* If known, we can dynamically put together the `settings.idp` configuration of the app, and continue as is now.

I guess this is what you were suggesting in your initial comment? I am not sure though what "ds_url": "https://wayf.aai.dfn.de/DFN-AAI-eduGAIN/wayf/www/WAYF.php" is meant for?

The discovery service url aka "Where Are You From" is a fancy version of the IdP selection that uses cookies and geoip information to pre-select the last used, or the most likely IdP and lets you search for your organisation if you want to use an other IdP