Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat/generic-oidc-claims: add the ability to set email and id_from_idp from separate OIDC claims in the generic OIDC idp #1153

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
4 changes: 4 additions & 0 deletions fence/config-default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,11 @@ OPENID_CONNECT:
token_endpoint: ''
jwks_uri: ''
user_id_field: '' # optional (default "sub"); claims field to get the user_id from
id_from_idp_field: '' # optional (default "sub"); claims field to get the id_from_idp from
email_field: '' # optional (default "email"); claims field to get the user email from
# If your IDP doesn't support the email_verified claim, set this to True to
# accept the email address claim anyway
assume_emails_verified: False
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

required_email_verified makes more sense here. It should default to false so we don't throw this new requirement unexpectedly on existing fence deployments and disrupt login.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reused the existing setting from the cognito code. Happy to refactor this if you'd like.
Also, see #1153 (comment)

scope: '' # optional (default "openid")
multifactor_auth_claim_info: # optional, include if you're using arborist to enforce mfa on a per-file level
claim: '' # claims field that indicates mfa, either the acr or acm claim.
Expand Down
38 changes: 26 additions & 12 deletions fence/resources/openid/idp_oauth2.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,23 +161,37 @@ def get_auth_info(self, code):
user OR "error" field with details of the error.
"""
user_id_field = self.settings.get("user_id_field", "sub")
user_email_field = self.settings.get("user_email_field", "email")
id_from_idp_field = self.settings.get("id_from_idp_field", "sub")
try:
token_endpoint = self.get_value_from_discovery_doc("token_endpoint", "")
jwks_endpoint = self.get_value_from_discovery_doc("jwks_uri", "")
claims = self.get_jwt_claims_identity(token_endpoint, jwks_endpoint, code)

if claims.get(user_id_field):
if user_id_field == "email" and not claims.get("email_verified"):
return {"error": "Email is not verified"}
return {
user_id_field: claims[user_id_field],
"mfa": self.has_mfa_claim(claims),
}
else:
self.logger.exception(
f"Can't get {user_id_field} from claims: {claims}"
)
return {"error": f"Can't get {user_id_field} from claims"}
fields = {user_id_field, user_email_field, id_from_idp_field}

user_auth_info = {}

for field in fields:
if claims.get(field):

# Field is email, but isn't verified and we aren't assuming all emails are verified
if field == "email" and not (claims.get("email_verified") or self.settings.get("assume_emails_verified")):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should, be default, assume that email verification isn't a requirement. There is not a requirement in the OAuth2 or OIDC spec that requires email verification so this will break standards in our authentication service.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original code would always return an error in the case that the email_verified claim wasn't present, and didn't give you a way to disable that, so I haven't changed the default behaviour here.

Again, happy to tweak that if desired.

return {"error": "Email is not verified"}

# We got the field, so append it to our dictionary
user_auth_info[field] = claims[field]

# Append the mfa field
user_auth_info["mfa"] = self.has_mfa_claim(claims)

# Debug
self.logger.debug(
f"Oauth2ClientBase get_auth_info returning {user_auth_info}"
)

# Return what we have assembled
return user_auth_info

except Exception as e:
self.logger.exception(f"Can't get user info from {self.idp}: {e}")
Expand Down