indigo-iam / iam

INDIGO Identity and Access Management Service
https://indigo-iam.github.io/
Other
99 stars 43 forks source link

RFE: Extend the `/userinfo` endpoint in order to return optional group claims in all circumstances #451

Open glpatcern opened 2 years ago

glpatcern commented 2 years ago

Hello, as discussed in the escape AAI chat I report here an unexpected behavior of the userinfo endpoint as implemented by iam-escape.

Context: my identity at iam-escape belongs to the /escape WLCG group as well to the /escape/test optional group. When I get a fresh token without specifying any optional group, and I use it against the /userinfo endpoint, I get:

$ escapeToken=`oidc-token escape -s 'profile wlcg.groups'`
$ curl -s -H "Authorization: Bearer ${escapeToken}" https://iam-escape.cloud.cnaf.infn.it/userinfo | jq
{
  "name": "Giuseppe Lo Presti",
  "preferred_username": "lopresti",
  "given_name": "Giuseppe",
  "family_name": "Lo Presti",
  "updated_at": 1628606681,
  "wlcg.groups": [
    "/escape"
  ]
}

So far so good. But if I ask for an optional group claim, I get:

$ escapeToken=`oidc-token escape -s 'profile wlcg.groups:/escape/test'`
$ curl -s -H "Authorization: Bearer ${escapeToken}" https://iam-escape.cloud.cnaf.infn.it/userinfo | jq
{
  "name": "Giuseppe Lo Presti",
  "preferred_username": "lopresti",
  "given_name": "Giuseppe",
  "family_name": "Lo Presti",
  "updated_at": 1628606681,
}

That is the group claims are totally missing.

Interestingly, the result is the same (no claims returned) in a mixed request such as:

$ escapeToken=`oidc-token escape -s 'profile wlcg.groups:/escape wlcg.groups:/escape/test'`

Shouldn't the userinfo query return all the claims, including the optional ones?

To be noted that if I take the raw ${escapeToken} and JWT-decode it, the claims are there as expected. Therefore it's only the /userinfo iam-escape endpoint that apparently drops them from the response.

federicaagostini commented 2 years ago

Hello Giuseppe, I don't have a clear explanation about this issue, but if you include the wlcg.groups scope in the token then the userinfo endpoint returns what you are asking for:

$ BT=$(oidc-token escape-monitoring -s 'profile wlcg.groups wlcg.groups:/escape/test')
$ echo $BT | cut -d. -f2 | base64 -d 2>/dev/null | jq
{
  "wlcg.ver": "1.0",
  "sub": "a50a555d-12e0-426d-a68b-7cb3741102ce",
  "aud": "https://wlcg.cern.ch/jwt/v1/any",
  "nbf": 1643394687,
  "scope": "wlcg.groups:/escape/test profile wlcg.groups",
  "iss": "https://iam-escape.cloud.cnaf.infn.it/",
  "exp": 1643398287,
  "iat": 1643394687,
  "jti": "d1a0c807-1fa2-46e9-9602-83c1ec356a60",
  "client_id": "4985e3ad-b9dc-45ae-8ba1-0c823620a11d",
  "wlcg.groups": [
    "/escape/test",
    "/escape",
    "/escape/pilots"
  ]
}
$ curl -s -H "Authorization: Bearer $BT" https://iam-escape.cloud.cnaf.infn.it/userinfo | jq
{
  "name": "Federica Agostini",
  "preferred_username": "fagostini",
  "given_name": "Federica",
  "family_name": "Agostini",
  "updated_at": 1642612862,
  "wlcg.groups": [
    "/escape/test",
    "/escape",
    "/escape/pilots"
  ]
}
glpatcern commented 2 years ago

Hello Federica,

Interesting finding: apparently the presence of the wlcg.groups scope is necessary in addition to the specific wlcg.groups:/group/name scope. I confirm it works for me as well, e.g.:

escapeToken=`oidc-token escape -s 'profile email wlcg.groups wlcg.groups:/escape/ScienceMesh'`
curl -s -H "Authorization: Bearer $escapeToken" https://iam-escape.cloud.cnaf.infn.it/userinfo | jq .
{
  "name": "Giuseppe Lo Presti",
  "preferred_username": "lopresti",
  "given_name": "Giuseppe",
  "family_name": "Lo Presti",
  "updated_at": 1628606681,
  "email": "giuseppe.lopresti@cern.ch",
  "email_verified": true,
  "wlcg.groups": [
    "/escape/ScienceMesh",
    "/escape"
  ]
}

That's definitely a good solution for me. However I'd still consider the behavior I described earlier as confusing, in particular for end users: maybe a request such as oidc-token escape -s wlcg.groups:/some/subgroup should fail as opposed to return a "valid" token that intrinsically includes the requested claims (in the JWT payload), but that does not play well with the /userinfo validation query. Or the /userinfo endpoint should be extended to support those.

glpatcern commented 2 years ago

At the same time I understand from offline discussions that the support of optional groups for more fine-grained authorization schemes is still at an early stage even within ESCAPE. So all this is more food for thoughts for possible enhancements than a critical bug, and it's definitely open for discussion. I'll rename the title.

federicaagostini commented 2 years ago

Hi Giuseppe, the syntax for the token request that you mention is the one agreed in the WLCG Common JWT Profiles (section Scope-Based Group Selection) and in particular "If not explicitly included, the non-parametric ​wlcg.groups​ scope is implicitly added at the end of the requested scopes list whenever any group scopes are included."

For instance, a token request like oidc-token escape -s wlcg.groups:/some/group will return an access token with an ordered wlcg.groups claim which shows the /some/group as first and the default groups afterwards.

Anyway, this could look like incoherent with the behavior of the /userinfo endpoint; as you say, the discussion is still at early stage and open for enhancements.

glpatcern commented 2 years ago

OK, so my expectation was correct: a token request like oidc-token escape -s wlcg.groups:/some/group should be equivalent to oidc-token escape -s wlcg.groups -s wlcg.groups:/some/group, and it follows that it should be equivalently handled by the /userinfo endpoint. Which is not the case...