Assign group automatically for eduGAIN identities depending in IdP

Dear all,

we plan to use the eduGAIN AAI for login (directly) with Indico, but only want to allow some things (event creation, room booking, etc.) to those who actually come from our home institution. For this, it seems ideal to create a group and fill it with all identities which log in with a given eduPersonScopedAffiliation (or another field which allows to deduce the actual IdP directly).

Is there a way to implement this in Indico, or in flask-multipass?

I found Category restricted to authenticated users? which is similar and uses an Indico plugin to solve this task, but unless I’m mistaken, at this point only the fields which have become part of the Indico account can be accessed (and not other, more machine-readable fields exported by Shibboleth). So this would only work with the mail domain, but matching against the actual IdP seems safer.

Since we can’t have an LDAP for all eduGAIN IDs, we also can not use this for group management.

I did not fully grasp flask-multipass yet — but maybe it is possible to e.g. reimplement Group::has_member and have it return a group name if some field communicated by Shibboleth matches a given IdP? Or can we only access the identity string at this point? Can this be done in the AuthProvider / IdentityProvider instead?

Any pointers appreciated, maybe somebody has already solved this task and I’m just not aware of it :slight_smile: . If there’s no alternative, using a match with the mail domain may also work, but matching against the actual IdP seems safer, if possible.

Cheers,
Oliver

No, taking groups from login tokens is not something that’s supported in Indico - mainly because typically sessions are long-lived and group membership may change. Also, IIRC some places in the codebase assumes that group membership is available “on the fly” and not only in the context of a logged-in user.

FWIW, as long as the email or identifier of the user can be used to determine whether they can do something, you could somewhat easily use a plugin to populate a local group based on this. Here’s a snipped from a custom plugin I wrote some time ago that does exactly that:

from wtforms_sqlalchemy.fields import QuerySelectField

from indico.core import signals
from indico.core.plugins import IndicoPlugin
from indico.core.settings.converters import ModelConverter
from indico.modules.groups.models.groups import LocalGroup
from indico.web.forms.base import IndicoForm


class SettingsForm(IndicoForm):
    sso_group = QuerySelectField('XXX Users Group', allow_blank=True,
                                 query_factory=lambda: LocalGroup.query, get_label='name',
                                 description='The group to which anyone logging in with a XXX account is added.')


class XXXPlugin(IndicoPlugin):
    """XXX

    Provides utilities for XXX Indico
    """

    configurable = True
    settings_form = SettingsForm
    default_settings = {
        'sso_group': None,
    }
    settings_converters = {
        'sso_group': ModelConverter(LocalGroup),
    }

    def init(self):
        super().init()
        self.connect(signals.users.logged_in, self._user_logged_in)

    def _user_logged_in(self, user, identity, admin_impersonation, **kwargs):
        if admin_impersonation:
            return
        group = self.settings.get('sso_group')
        if not group:
            return
        if identity.provider == 'shib-sso' and identity.identifier.endswith('@XXX.ch'):
            group.members.add(user)

Passing the eduPersonScopedAffiliation to Indico and thus your plugin would probaly be tricky though. I think he easiest option would be creating your own multipass backend inheriting from the default saml/shibboleth one and including this in the multipass_data (which you could then access in your plugin via the multipass_data on the user’s Identity).

1 Like

Thanks! That very first statement clarifies a lot.

Thanks a lot for sharing the plugin code, likely, we will go with something like this (we’ll have to see how / if we can tune the identifier). The approach using multipass_data sounds like the most flexible (but not easiest) solution, reading through the code, it seems this may require adaptation of more parts, e.g.:

would likely bail out (since Shibboleth does not support refresh).

So my plan would be as follows:

  • We set up eduGAIN first. We’ll be using shibd, so we should have some control over the identifier.
  • If the identifier proves unique enough, the plugin approach matching on a part of the identifier seems easiest. We should be able to get eduPersonPrincipalName which has a domain part, so maybe that will already be sufficient.
  • If not, we’ll likely come back to storing multipass_data. In that case, we’d try to upstream the necessary change to store the data optionally (since it does not change existing behaviour, but adds functionality, it may be helpful for others). We’d then still need the local, specific plugin, but at least we could upstream the multipass_data part :wink: .

Thanks a lot (as always), I’ll update this when we have a working setup :wink: .

Ah yes, that supports_refresh check bothered me as well when I used multipass_data for something similar… maybe we should just get rid of it… in my case I could implement refresh but of course it’s not a good idea to set it to True when there’s literally no possibility of refreshing…

We set up eduGAIN first. We’ll be using shibd, so we should have some control over the identifier.

I’m not sure how much control you have over it. It’s most likely coming from edugain since it needs to be unique for each user regardless of which underlying IdP they use. In any case, the identifier is what Indico uses to associate a login with a user so it needs to be absolutely unique.

1 Like

The advantage when using shibd as middleman is that we can select which attribute to map (and how to map it), and I think even combinations can be made. However, I checked several other eduGAIN services, and most seem to use eduPersonPrincipalName as unique identifier, which luckily has an enforced domain part, and is supposed to be unique.
It seems eduPersonTargetedID (which contains the IdP URL explicitly) would be even nicer (since it’s pseudonymized and unique), but it is not commonly exposed.

So maybe eduPersonPrincipalName will already fulfill all our requirements :wink: .