Inbound indentity improvements and roadmap - GluuFederation/gluu-passport GitHub Wiki
Old 3.1.x roadmap page can be found here.
Overall target
Deliver an integral solution for inbound identity capable of dealing with SAML IDPs, OpenID Connect OPs, and OAuth social login providers.
Problems to tackle (may have overlaps):
- IDP onboarding (ie. the process of "adding" an external IDP to a Gluu installation).
- Attribute mapping
- Flow implementation
- Discovery (WAYF) automation
What we have so far
Our product can be manipulated in ways that a number of inbound identity needs can be fulfiled. However, it can be quite hard at times to get to the setup desired.
I have compiled a list of awkward things:
-
Configuration is scattered through several files and LDAP:
passport-saml-config.json,passport-config.json,passport-inbound-idp-initiated.json,ou=oxpassport,ou=configuration,inum=...,ou=appliances,o=gluu. Even sometimes, our customers have to edit Node code! -
The way SAML IDPs configuration is supplied was not very well laid out (this is just one of many examples https://support.gluu.org/authentication/6399/how-to-disable-requestedauthncontext-in-saml-request-to-idp)
-
IDP-initiated flow has generated a lot of attention (and tickets!) lately, but our support for it is still beta: it doesn't handle relay state, it simply redirects to a unique url at the end, uses a dedicated endpoint for the flow...
-
There is a big chain of intermediate attribute mappings before the actual profile is saved to LDAP: first, passport strategies (except saml) internally have their own attribute set, second, we use a separate one in our node code, then a further mapping is established via custom script parameters. In the case of SAML, node code does a double mapping due to the "reverse" element in
passport-saml-config.json. This mapping tangle external provider -> passport strategy -> node -> custom script -> LDAP has to be simplified. -
As a consequence of the previous and our node code not being very good at handling attributes, supporting a new attribute (e.g.
favoriteColor) is painful -
Node project has:
- lots of repetitive code. Even in our docs we promote this!
- pitfalls in exception handling and error messages
- more redirects and endpoints than needed.
- a session store mechanism not suitable for production environments, see MemoryStore here
-
It's often hard to know why sometimes things are not work as expected because there are so many pieces involved. In this regard I don't think there is much we can do.
In conclusion, the work required to overhaul a setup is very time consuming and error prone due to so much manual steps.
Goals
This document is intended to outline steps in order to alleviate some of the problems listed above and take Passport to a level where we can achieve the goal we have pursued from the beginning:
Simplicity...
Simple to set up, configure, use, maintain, evolve, etc...
I have condensed the following general tasks which may allow us to move in that direction.
-
Centralize all configuration in LDAP. Configuration data includes: external providers info, attribute mapping stuff, idp-initiated info (e.g a pre-built oidc client if needed), and low level config (ie. what we have in
passport-config.jsoncurrently) -
Redefine where and how attribute mapping takes place. We have to devise a more sophisticated way to deal with this. One which may bring the level of flexibility/automation needed. No more customers editing code or params at different places.
-
Redefine how adding external providers should work. We are currently onboarding only social providers and OPs via UI; adding SAML IDPs this way is a MUST. We need to offer a richer onboarding process where admins can enter behavioral parameters such as those currently supplied via
passport-saml-config.jsonin the case of SAML IDPs -
Refactor (and reduce the amount of) Node code.
-
Quick review of flows/cust scripts. Our couple of scripts gain complexity after every release.
-
Smooth upgrade. These changes will for sure break backwards compat. Automatic upgrade is needed.
...so how to achieve?
Below I describe some preliminar ideas on how to tackle this...
Centralize config in LDAP + management in oxTrust
I propose to handle this similarly as we do for oxAuth/oxTrust configuration: big jsons. They should be under branch ou=oxpassport,ou=configuration,inum=...,ou=appliances,o=gluu
I think there should be at least 3 json:
- Low level general configuration
- External providers config
Attribute mapping profiles(more on this later)- IDP-initiated flows configuration
Actually all data can be collapsed into a single json, that way we don't have to create new attributes in LDAP schema, and simply use already existing gluuPassportConfiguration.
I propose to add an Inbound identity section to oxTrust menu when passport is enabled. It would have 3 items (the 3 bullets above?)
Redefine how adding external providers should work
When admins click the menu item for "external providers", they will be presented a form similar to what we already have for adding social providers. However:
- Providers should be enabled/disabled
- Providers should be assigned a displayName. Currently we use the name for identification purposes!.
- They should be bound to a type: one of oauth / openidconnect / oxd / SAML IDP
- Depending on the selection, different fields may appear. As an example, selecting
oauthwill allow users to fill passportStrategyId/clientID/clientSecret,openidconnectwill displayauthorizationEndpoint, and so on...
Applicable fields will be coupled with supported passport.js options depending on the provider type. There should be room for extra fields too. Our form currently supports this.
This gist has an example of json configuration for some providers.
Every time an oauth provider is added we should warn about installing the appropriate node library (can we detect if it's installed?).
Redefine where and how attribute mapping takes place
Mapping should take place at a single point, IMO custom script, that is, node code passing the user profile exactly as received by the provider, and then the script doing the translation to LDAP attribute.
Mappings should be built upon the idea of profiles. This way different providers can reuse the same mapping, or create their own mapping based upon an existing one.
The above means that out of the box we should offer some ready-to-use profiles. I propose 3: one for every type of provider (oauth, openidconnect, and SAML). The SAML one is already depicted in the reverseMapping section of passport-saml-config.json.
...how should this work?
In the "external providers" page, admins can assign to every provider an already existing mapping profile. That's it. Then the cust script will take care of translation.
Here's a sample gist depicting sample attribute mappings.
Probably, we will have to mark one attribute in any given mapping to be paired to LDAP uid because it is required for provisioning.
---- Important update!!!
Please read the addendum
Refactor Node code
About passport
Probably passport.js is the only one project instrumental in enabling support for a big variety of provider flavors. I don't like 3 things:
- Adding an oauth provider forces admins to install libraries, and restart
- Despite its age,
passport-samlseems to have lot of complaints from community (see the issues) - When something goes wrong, I always end up reading javascript code
However, it's the best thing we have so far. Not to mention in other languages/platforms there are no projects that can get close to what passport.js can do.
We'll have to live with it. I discarded the Simov's grant project I shared with the team because:
- https://github.com/simov/grant/issues/99
- No SAML support
Potential enhancements
There is a lot to do here:
- Endpoints
/passport/passportstrategiesand/passport/saml_configcan be removed. We can inject this data in XHTML files via working parameters of cust scripts - IMO there is no need to create a separate file every time a strategy is added, nor editing other 2 additional files.
- Casa stuff should live in a separate module
I'd like to minimize the amount of Javascript as much as possible, and make the node app serve the most basic functionality. If there is something that other component can do (e.g. a cust script), make that component do the job.
Quick review of flows
Ideally we should be able to simplify cust scripts bit, for instance:
- by externalizing some features via an oxcore library
- by reusing logic in a script in
/opt/gluu/python/libs
Another idea is using separate scripts:
- Standard passport_social with no second step (ie no requiring user to input missing attribute)
- Standard passport_saml as above
- A script to only handle idp_initiated flow
- A passport_social script with second step (ie prompts user in case a required attribute is missing)
A need that arose recently was a way to parameterize attribute update behaviour. Currently, all attributes of the user logging in are updated, even the absent ones end up flushed in local LDAP. When we leveraged our passport flow for Casa's account linking plugin we noticed this is not ideal since we are interested only in the linking (oxExternalUid) not the profile changes.
IDP-initiated
We need to agree ASAP what we will change from current idp-initiated flow. Below is what I would do IMO:
- Create an OIDC client for idp-initiated scenario when passport is installed (setup.py)
- In the ACS URL (aka passport callback endpoint), detect if this is idp-initiated or not. I suggest how to do so here.
- When it is not, continue with standard flow
- When it's idp-initiated, generate an authorization request as we do currently. Previously, if
relayStatewas detected in SAML response, store its value in a short-lived cookie - The redirect URI of the (pre-created) client will correspond to a simple xhtml page part of oxauth. It's a page that customers can edit if they want. We can offer as default implementation the following: if cookie
relayStateexists, do a simple redirect to that location (after clearing the cookie).
Parameters of the authorization request generated are governed by json configuration mentioned above. Example here ( basically the same as in current passport-inbound-idp-initiated.json).
Smooth upgrade
Reference doc here.