From 56955b57c267bf3dcbec2abf634434ba4adeac67 Mon Sep 17 00:00:00 2001 From: Hans Zandbelt Date: Thu, 16 Nov 2017 15:52:30 +0100 Subject: [PATCH] release 2.3.3; add support for OIDCPassUserInfoAs add support for passing userinfo as a JSON object or (when available) as a JWT with OIDCPassUserInfoAs; closes #311 Signed-off-by: Hans Zandbelt --- ChangeLog | 4 ++ auth_openidc.conf | 8 ++++ configure.ac | 2 +- src/config.c | 41 ++++++++++++++++---- src/mod_auth_openidc.c | 86 +++++++++++++++++++++++++++++++----------- src/mod_auth_openidc.h | 18 +++++++-- src/parse.c | 68 ++++++++++++++++++++++++++++++--- src/parse.h | 1 + src/proto.c | 30 ++++++++------- src/session.c | 13 ++++++- 10 files changed, 217 insertions(+), 54 deletions(-) diff --git a/ChangeLog b/ChangeLog index 886845c1..4bd806ef 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +11/16/2017 +- add support for passing userinfo as a JSON object or JWT; see #311 +- release 2.3.3 + 11/13/2017 - add support for authentication to the introspection endpoint with a bearer token using OIDCOAuthIntrospectionClientAuthBearerToken; thanks @cristichiru - bump to 2.3.3rc3 diff --git a/auth_openidc.conf b/auth_openidc.conf index 30d747e7..4897dded 100644 --- a/auth_openidc.conf +++ b/auth_openidc.conf @@ -642,6 +642,14 @@ # When not defined the default "claims" is used. #OIDCPassIDTokenAs [claims|payload|serialized]+ +# Define the way(s) in which the claims resolved from the userinfo endpoint are passed to the application according to OIDCPassClaimsAs. +# Must be one or several of: +# "claims" : the userinfo claims are passed in individual headers/environment variables +# "json" : a self-contained userinfo JSON object is passed in the "OIDC_userinfo_json" header/environment variable +# "jwt" : a signed/encrypted JWT (if available!) optionally resolved from the userinfo endpoint is passed in the "OIDC_userinfo_jwt" header/environment variable +# When not defined the default "claims" is used. +#OIDCPassUserInfoAs [claims|json|jwt]+ + # Define the way in which the claims and tokens are passed to the application environment: # "none": no claims/tokens are passed # "environment": claims/tokens are passed as environment variables diff --git a/configure.ac b/configure.ac index 93872ec5..8fc93126 100644 --- a/configure.ac +++ b/configure.ac @@ -1,4 +1,4 @@ -AC_INIT([mod_auth_openidc],[2.3.3rc3],[hans.zandbelt@zmartzone.eu]) +AC_INIT([mod_auth_openidc],[2.3.3],[hans.zandbelt@zmartzone.eu]) AC_SUBST(NAMEVER, AC_PACKAGE_TARNAME()-AC_PACKAGE_VERSION()) diff --git a/src/config.c b/src/config.c index 2555ee70..e16520de 100644 --- a/src/config.c +++ b/src/config.c @@ -207,6 +207,7 @@ #define OIDCCryptoPassphrase "OIDCCryptoPassphrase" #define OIDCClaimDelimiter "OIDCClaimDelimiter" #define OIDCPassIDTokenAs "OIDCPassIDTokenAs" +#define OIDCPassUserInfoAs "OIDCPassUserInfoAs" #define OIDCOAuthClientID "OIDCOAuthClientID" #define OIDCOAuthClientSecret "OIDCOAuthClientSecret" #define OIDCOAuthIntrospectionClientAuthBearerToken "OIDCOAuthIntrospectionClientAuthBearerToken" @@ -720,6 +721,18 @@ static const char * oidc_set_pass_idtoken_as(cmd_parms *cmd, void *dummy, return OIDC_CONFIG_DIR_RV(cmd, rv); } +/* + * define how to pass the userinfo/claims in HTTP headers + */ +static const char * oidc_set_pass_userinfo_as(cmd_parms *cmd, void *dummy, + const char *v1, const char *v2, const char *v3) { + oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( + cmd->server->module_config, &auth_openidc_module); + const char *rv = oidc_parse_pass_userinfo_as(cmd->pool, v1, v2, v3, + &cfg->pass_userinfo_as); + return OIDC_CONFIG_DIR_RV(cmd, rv); +} + /* * define which method of pass an OAuth Bearer token is accepted */ @@ -969,12 +982,13 @@ const char *oidc_set_auth_request_method(cmd_parms *cmd, void *struct_ptr, /* * set the introspection authorization static bearer token */ -static const char *oidc_set_client_auth_bearer_token(cmd_parms *cmd, void *struct_ptr, - const char *args) { +static const char *oidc_set_client_auth_bearer_token(cmd_parms *cmd, + void *struct_ptr, const char *args) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); char *w = ap_getword_conf(cmd->pool, &args); - cfg->oauth.introspection_client_auth_bearer_token = (*w == '\0' || *args != 0) ? "" : w; + cfg->oauth.introspection_client_auth_bearer_token = + (*w == '\0' || *args != 0) ? "" : w; return NULL; } @@ -1094,6 +1108,7 @@ void *oidc_create_server_config(apr_pool_t *pool, server_rec *svr) { c->remote_user_claim.reg_exp = NULL; c->remote_user_claim.replace = NULL; c->pass_idtoken_as = OIDC_PASS_IDTOKEN_AS_CLAIMS; + c->pass_userinfo_as = OIDC_PASS_USERINFO_AS_CLAIMS; c->cookie_http_only = OIDC_DEFAULT_COOKIE_HTTPONLY; c->cookie_same_site = OIDC_DEFAULT_COOKIE_SAME_SITE; @@ -1334,8 +1349,8 @@ void *oidc_merge_server_config(apr_pool_t *pool, void *BASE, void *ADD) { add->oauth.introspection_endpoint_auth != NULL ? add->oauth.introspection_endpoint_auth : base->oauth.introspection_endpoint_auth; - c->oauth.introspection_client_auth_bearer_token = - add->oauth.introspection_client_auth_bearer_token != NULL ? + c->oauth.introspection_client_auth_bearer_token = + add->oauth.introspection_client_auth_bearer_token != NULL ? add->oauth.introspection_client_auth_bearer_token : base->oauth.introspection_client_auth_bearer_token; c->oauth.introspection_token_param_name = @@ -1486,6 +1501,9 @@ void *oidc_merge_server_config(apr_pool_t *pool, void *BASE, void *ADD) { c->pass_idtoken_as = add->pass_idtoken_as != OIDC_PASS_IDTOKEN_AS_CLAIMS ? add->pass_idtoken_as : base->pass_idtoken_as; + c->pass_userinfo_as = + add->pass_userinfo_as != OIDC_PASS_USERINFO_AS_CLAIMS ? + add->pass_userinfo_as : base->pass_userinfo_as; c->cookie_http_only = add->cookie_http_only != OIDC_DEFAULT_COOKIE_HTTPONLY ? add->cookie_http_only : base->cookie_http_only; @@ -1832,7 +1850,8 @@ static int oidc_check_config_openid_openidc(server_rec *s, oidc_cfg *c) { OIDCProviderAuthorizationEndpoint); } else { apr_uri_parse(s->process->pconf, c->provider.metadata_url, &r_uri); - if ((r_uri.scheme == NULL) || (apr_strnatcmp(r_uri.scheme, "https") != 0)) { + if ((r_uri.scheme == NULL) + || (apr_strnatcmp(r_uri.scheme, "https") != 0)) { oidc_swarn(s, "the URL scheme (%s) of the configured " OIDCProviderMetadataURL " SHOULD be \"https\" for security reasons!", r_uri.scheme); @@ -2105,7 +2124,8 @@ static int oidc_post_config(apr_pool_t *pool, apr_pool_t *p1, apr_pool_t *p2, } #endif /* OPENSSL_NO_THREADID */ #endif /* defined(OPENSSL_THREADS) && APR_HAS_THREADS */ - apr_pool_cleanup_register(pool, s, oidc_cleanup_parent, apr_pool_cleanup_null); + apr_pool_cleanup_register(pool, s, oidc_cleanup_parent, + apr_pool_cleanup_null); server_rec *sp = s; while (sp != NULL) { @@ -2167,7 +2187,7 @@ static void oidc_child_init(apr_pool_t *p, server_rec *s) { } sp = sp->next; } - apr_pool_cleanup_register(p, s, oidc_cleanup_child, apr_pool_cleanup_null); + apr_pool_cleanup_register(p, s, oidc_cleanup_child, apr_pool_cleanup_null); } /* @@ -2479,6 +2499,11 @@ const command_rec oidc_config_cmds[] = { NULL, RSRC_CONF, "The format in which the id_token is passed in (a) header(s); must be one or more of: claims|payload|serialized"), + AP_INIT_TAKE123(OIDCPassUserInfoAs, + oidc_set_pass_userinfo_as, + NULL, + RSRC_CONF, + "The format in which the userinfo is passed in (a) header(s); must be one or more of: claims|json|jwt"), AP_INIT_TAKE1(OIDCOAuthClientID, oidc_set_string_slot, diff --git a/src/mod_auth_openidc.c b/src/mod_auth_openidc.c index 9508c122..a20ee547 100644 --- a/src/mod_auth_openidc.c +++ b/src/mod_auth_openidc.c @@ -1035,8 +1035,9 @@ apr_byte_t oidc_get_provider_from_session(request_rec *r, oidc_cfg *c, /* * store claims resolved from the userinfo endpoint in the session */ -static void oidc_store_userinfo_claims(request_rec *r, oidc_session_t *session, - oidc_provider_t *provider, const char *claims) { +static void oidc_store_userinfo_claims(request_rec *r, oidc_cfg *c, + oidc_session_t *session, oidc_provider_t *provider, const char *claims, + const char *userinfo_jwt) { oidc_debug(r, "enter"); @@ -1049,12 +1050,18 @@ static void oidc_store_userinfo_claims(request_rec *r, oidc_session_t *session, */ oidc_session_set_userinfo_claims(r, session, claims); + if (c->session_type != OIDC_SESSION_TYPE_CLIENT_COOKIE) { + /* this will also clear the entry if a JWT was not returned at this point */ + oidc_session_set_userinfo_jwt(r, session, userinfo_jwt); + } + } else { /* * clear the existing claims because we could not refresh them */ oidc_session_set_userinfo_claims(r, session, NULL); + oidc_session_set_userinfo_jwt(r, session, NULL); } /* store the last refresh time if we've configured a userinfo refresh interval */ @@ -1117,7 +1124,7 @@ static apr_byte_t oidc_refresh_access_token(request_rec *r, oidc_cfg *c, */ static const char *oidc_retrieve_claims_from_userinfo_endpoint(request_rec *r, oidc_cfg *c, oidc_provider_t *provider, const char *access_token, - oidc_session_t *session, char *id_token_sub) { + oidc_session_t *session, char *id_token_sub, char **userinfo_jwt) { oidc_debug(r, "enter"); @@ -1155,7 +1162,7 @@ static const char *oidc_retrieve_claims_from_userinfo_endpoint(request_rec *r, /* try to get claims from the userinfo endpoint using the provided access token */ char *result = NULL; if (oidc_proto_resolve_userinfo(r, c, provider, id_token_sub, access_token, - &result) == FALSE) { + &result, userinfo_jwt) == FALSE) { /* see if we have an existing session and we are refreshing the user info claims */ if (session != NULL) { @@ -1167,7 +1174,7 @@ static const char *oidc_retrieve_claims_from_userinfo_endpoint(request_rec *r, /* try again with the new access token */ if (oidc_proto_resolve_userinfo(r, c, provider, id_token_sub, - access_token, &result) == FALSE) { + access_token, &result, userinfo_jwt) == FALSE) { oidc_error(r, "resolving user info claims with the refreshed access token failed, nothing will be stored in the session"); @@ -1204,6 +1211,7 @@ static apr_byte_t oidc_refresh_claims_from_userinfo_endpoint(request_rec *r, oidc_provider_t *provider = NULL; const char *claims = NULL; const char *access_token = NULL; + char *userinfo_jwt = NULL; /* get the current provider info */ if (oidc_get_provider_from_session(r, cfg, session, &provider) == FALSE) @@ -1234,10 +1242,11 @@ static apr_byte_t oidc_refresh_claims_from_userinfo_endpoint(request_rec *r, /* retrieve the current claims */ claims = oidc_retrieve_claims_from_userinfo_endpoint(r, cfg, - provider, access_token, session, NULL); + provider, access_token, session, NULL, &userinfo_jwt); /* store claims resolved from userinfo endpoint */ - oidc_store_userinfo_claims(r, session, provider, claims); + oidc_store_userinfo_claims(r, cfg, session, provider, claims, + userinfo_jwt); /* indicated something changed */ return TRUE; @@ -1385,9 +1394,37 @@ static int oidc_handle_existing_session(request_rec *r, oidc_cfg *cfg, /* copy id_token and claims from session to request state and obtain their values */ oidc_copy_tokens_to_request_state(r, session, &s_id_token, &s_claims); - /* set the claims in the app headers */ - if (oidc_set_app_claims(r, cfg, session, s_claims) == FALSE) - return HTTP_INTERNAL_SERVER_ERROR; + if ((cfg->pass_userinfo_as & OIDC_PASS_USERINFO_AS_CLAIMS)) { + /* set the userinfo claims in the app headers */ + if (oidc_set_app_claims(r, cfg, session, s_claims) == FALSE) + return HTTP_INTERNAL_SERVER_ERROR; + } + + if ((cfg->pass_userinfo_as & OIDC_PASS_USERINFO_AS_JSON_OBJECT)) { + /* pass the userinfo JSON object to the app in a header or environment variable */ + oidc_util_set_app_info(r, OIDC_APP_INFO_USERINFO_JSON, s_claims, + OIDC_DEFAULT_HEADER_PREFIX, pass_headers, pass_envvars); + } + + if ((cfg->pass_userinfo_as & OIDC_PASS_USERINFO_AS_JWT)) { + if (cfg->session_type != OIDC_SESSION_TYPE_CLIENT_COOKIE) { + /* get the compact serialized JWT from the session */ + const char *s_userinfo_jwt = oidc_session_get_userinfo_jwt(r, + session); + if (s_userinfo_jwt != NULL) { + /* pass the compact serialized JWT to the app in a header or environment variable */ + oidc_util_set_app_info(r, OIDC_APP_INFO_USERINFO_JWT, + s_userinfo_jwt, + OIDC_DEFAULT_HEADER_PREFIX, pass_headers, pass_envvars); + } else { + oidc_debug(r, + "configured to pass userinfo in a JWT, but no such JWT was found in the session (probably no such JWT was returned from the userinfo endpoint)"); + } + } else { + oidc_error(r, + "session type \"client-cookie\" does not allow storing/passing a userinfo JWT; use \"" OIDCSessionType " server-cache\" for that"); + } + } if ((cfg->pass_idtoken_as & OIDC_PASS_IDTOKEN_AS_CLAIMS)) { /* set the id_token in the app headers */ @@ -1596,7 +1633,7 @@ static apr_byte_t oidc_save_in_session(request_rec *r, oidc_cfg *c, const char *remoteUser, const char *id_token, oidc_jwt_t *id_token_jwt, const char *claims, const char *access_token, const int expires_in, const char *refresh_token, const char *session_state, const char *state, - const char *original_url) { + const char *original_url, const char *userinfo_jwt) { /* store the user in the session */ session->remote_user = remoteUser; @@ -1645,7 +1682,7 @@ static apr_byte_t oidc_save_in_session(request_rec *r, oidc_cfg *c, provider->end_session_endpoint); /* store claims resolved from userinfo endpoint */ - oidc_store_userinfo_claims(r, session, provider, claims); + oidc_store_userinfo_claims(r, c, session, provider, claims, userinfo_jwt); /* see if we have an access_token */ if (access_token != NULL) { @@ -1838,6 +1875,7 @@ static int oidc_handle_authorization_response(request_rec *r, oidc_cfg *c, int expires_in = oidc_parse_expires_in(r, apr_table_get(params, OIDC_PROTO_EXPIRES_IN)); + char *userinfo_jwt = NULL; /* * optionally resolve additional claims against the userinfo endpoint @@ -1845,7 +1883,7 @@ static int oidc_handle_authorization_response(request_rec *r, oidc_cfg *c, */ const char *claims = oidc_retrieve_claims_from_userinfo_endpoint(r, c, provider, apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN), NULL, - jwt->payload.sub); + jwt->payload.sub, &userinfo_jwt); /* restore the original protected URL that the user was trying to access */ const char *original_url = oidc_proto_state_get_original_url(proto_state); @@ -1882,7 +1920,8 @@ static int oidc_handle_authorization_response(request_rec *r, oidc_cfg *c, apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN), expires_in, apr_table_get(params, OIDC_PROTO_REFRESH_TOKEN), apr_table_get(params, OIDC_PROTO_SESSION_STATE), - apr_table_get(params, OIDC_PROTO_STATE), original_url) == FALSE) + apr_table_get(params, OIDC_PROTO_STATE), original_url, + userinfo_jwt) == FALSE) return HTTP_INTERNAL_SERVER_ERROR; } else { @@ -2580,17 +2619,21 @@ static int oidc_handle_logout(request_rec *r, oidc_cfg *c, if (id_token_hint != NULL) { logout_request = apr_psprintf(r->pool, "%s%sid_token_hint=%s", logout_request, - strchr(logout_request ? logout_request : "", OIDC_CHAR_QUERY) != NULL ? - OIDC_STR_AMP : OIDC_STR_QUERY, - oidc_util_escape_string(r, id_token_hint)); + strchr(logout_request ? logout_request : "", + OIDC_CHAR_QUERY) != NULL ? + OIDC_STR_AMP : + OIDC_STR_QUERY, + oidc_util_escape_string(r, id_token_hint)); } if (url != NULL) { logout_request = apr_psprintf(r->pool, "%s%spost_logout_redirect_uri=%s", logout_request, - strchr(logout_request ? logout_request : "", OIDC_CHAR_QUERY) != NULL ? - OIDC_STR_AMP : OIDC_STR_QUERY, - oidc_util_escape_string(r, url)); + strchr(logout_request ? logout_request : "", + OIDC_CHAR_QUERY) != NULL ? + OIDC_STR_AMP : + OIDC_STR_QUERY, + oidc_util_escape_string(r, url)); } url = logout_request; } @@ -2875,8 +2918,7 @@ static int oidc_handle_refresh_token_request(request_rec *r, oidc_cfg *c, return_to = apr_psprintf(r->pool, "%s%serror_code=%s", return_to, strchr(return_to ? return_to : "", OIDC_CHAR_QUERY) ? OIDC_STR_AMP : - OIDC_STR_QUERY, - oidc_util_escape_string(r, error_code)); + OIDC_STR_QUERY, oidc_util_escape_string(r, error_code)); /* add the redirect location header */ oidc_util_hdr_out_location_set(r, return_to); diff --git a/src/mod_auth_openidc.h b/src/mod_auth_openidc.h index fb9f3654..ce65b413 100644 --- a/src/mod_auth_openidc.h +++ b/src/mod_auth_openidc.h @@ -123,11 +123,18 @@ APLOG_USE_MODULE(auth_openidc); /* pass id_token as individual claims in headers (default) */ #define OIDC_PASS_IDTOKEN_AS_CLAIMS 1 -/* pass id_token payload as JSON object in header*/ +/* pass id_token payload as JSON object in header */ #define OIDC_PASS_IDTOKEN_AS_PAYLOAD 2 -/* pass id_token in compact serialized format in header*/ +/* pass id_token in compact serialized format in header */ #define OIDC_PASS_IDTOKEN_AS_SERIALIZED 4 +/* pass userinfo as individual claims in headers (default) */ +#define OIDC_PASS_USERINFO_AS_CLAIMS 1 +/* pass userinfo payload as JSON object in header */ +#define OIDC_PASS_USERINFO_AS_JSON_OBJECT 2 +/* pass userinfo as a JWT in header (when returned as a JWT) */ +#define OIDC_PASS_USERINFO_AS_JWT 4 + #define OIDC_OAUTH_ACCEPT_TOKEN_IN_DEFAULT 0 /* accept bearer token in header (default) */ #define OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER 1 @@ -376,6 +383,7 @@ typedef struct oidc_cfg { char *claim_prefix; oidc_remote_user_claim_t remote_user_claim; int pass_idtoken_as; + int pass_userinfo_as; int cookie_http_only; int cookie_same_site; @@ -551,6 +559,8 @@ apr_byte_t oidc_oauth_get_bearer_token(request_rec *r, const char **access_token #define OIDC_APP_INFO_ACCESS_TOKEN_EXP "access_token_expires" #define OIDC_APP_INFO_ID_TOKEN "id_token" #define OIDC_APP_INFO_ID_TOKEN_PAYLOAD "id_token_payload" +#define OIDC_APP_INFO_USERINFO_JSON "userinfo_json" +#define OIDC_APP_INFO_USERINFO_JWT "userinfo_jwt" typedef json_t oidc_proto_state_t; @@ -588,7 +598,7 @@ int oidc_proto_authorization_request(request_rec *r, struct oidc_provider_t *pro apr_byte_t oidc_proto_is_post_authorization_response(request_rec *r, oidc_cfg *cfg); apr_byte_t oidc_proto_is_redirect_authorization_response(request_rec *r, oidc_cfg *cfg); apr_byte_t oidc_proto_refresh_request(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, const char *rtoken, char **id_token, char **access_token, char **token_type, int *expires_in, char **refresh_token); -apr_byte_t oidc_proto_resolve_userinfo(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, const char *id_token_sub, const char *access_token, char **response); +apr_byte_t oidc_proto_resolve_userinfo(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, const char *id_token_sub, const char *access_token, char **response, char **userinfo_jwt); apr_byte_t oidc_proto_account_based_discovery(request_rec *r, oidc_cfg *cfg, const char *acct, char **issuer); apr_byte_t oidc_proto_url_based_discovery(request_rec *r, oidc_cfg *cfg, const char *url, char **issuer); apr_byte_t oidc_proto_parse_idtoken(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, const char *id_token, const char *nonce, oidc_jwt_t **jwt, apr_byte_t is_code_flow); @@ -792,6 +802,8 @@ apr_byte_t oidc_session_save(request_rec *r, oidc_session_t *z, apr_byte_t first apr_byte_t oidc_session_kill(request_rec *r, oidc_session_t *z); apr_byte_t oidc_session_free(request_rec *r, oidc_session_t *z); +void oidc_session_set_userinfo_jwt(request_rec *r, oidc_session_t *z, const char *userinfo_jwt); +const char * oidc_session_get_userinfo_jwt(request_rec *r, oidc_session_t *z); void oidc_session_set_userinfo_claims(request_rec *r, oidc_session_t *z, const char *claims); const char * oidc_session_get_userinfo_claims(request_rec *r, oidc_session_t *z); json_t *oidc_session_get_userinfo_claims_json(request_rec *r, oidc_session_t *z); diff --git a/src/parse.c b/src/parse.c index 4851b340..3cef3207 100644 --- a/src/parse.c +++ b/src/parse.c @@ -414,7 +414,8 @@ const char *oidc_valid_response_type(apr_pool_t *pool, const char *arg) { if (oidc_proto_flow_is_supported(pool, arg) == FALSE) { return apr_psprintf(pool, "oidc_valid_response_type: type must be one of %s", - apr_array_pstrcat(pool, oidc_proto_supported_flows(pool), OIDC_CHAR_PIPE)); + apr_array_pstrcat(pool, oidc_proto_supported_flows(pool), + OIDC_CHAR_PIPE)); } return NULL; } @@ -456,7 +457,8 @@ const char *oidc_valid_signed_response_alg(apr_pool_t *pool, const char *arg) { "unsupported/invalid signing algorithm '%s'; must be one of [%s]", arg, apr_array_pstrcat(pool, - oidc_jose_jws_supported_algorithms(pool), OIDC_CHAR_PIPE)); + oidc_jose_jws_supported_algorithms(pool), + OIDC_CHAR_PIPE)); } return NULL; } @@ -470,7 +472,8 @@ const char *oidc_valid_encrypted_response_alg(apr_pool_t *pool, const char *arg) "unsupported/invalid encryption algorithm '%s'; must be one of [%s]", arg, apr_array_pstrcat(pool, - oidc_jose_jwe_supported_algorithms(pool), OIDC_CHAR_PIPE)); + oidc_jose_jwe_supported_algorithms(pool), + OIDC_CHAR_PIPE)); } return NULL; } @@ -484,7 +487,8 @@ const char *oidc_valid_encrypted_response_enc(apr_pool_t *pool, const char *arg) "unsupported/invalid encryption type '%s'; must be one of [%s]", arg, apr_array_pstrcat(pool, - oidc_jose_jwe_supported_encryptions(pool), OIDC_CHAR_PIPE)); + oidc_jose_jwe_supported_encryptions(pool), + OIDC_CHAR_PIPE)); } return NULL; } @@ -699,6 +703,58 @@ const char *oidc_parse_pass_idtoken_as(apr_pool_t *pool, const char *v1, return NULL; } +#define OIDC_PASS_USERINFO_AS_CLAIMS_STR "claims" +#define OIDC_PASS_USERINFO_AS_JSON_OBJECT_STR "json" +#define OIDC_PASS_USERINFO_AS_JWT_STR "jwt" + +/* + * convert a "pass userinfo as" value to an integer + */ +static int oidc_parse_pass_userinfo_as_str2int(const char *v) { + if (apr_strnatcmp(v, OIDC_PASS_USERINFO_AS_CLAIMS_STR) == 0) + return OIDC_PASS_USERINFO_AS_CLAIMS; + if (apr_strnatcmp(v, OIDC_PASS_USERINFO_AS_JSON_OBJECT_STR) == 0) + return OIDC_PASS_USERINFO_AS_JSON_OBJECT; + if (apr_strnatcmp(v, OIDC_PASS_USERINFO_AS_JWT_STR) == 0) + return OIDC_PASS_USERINFO_AS_JWT; + return -1; +} + +/* + * parse a "pass id token as" value from the provided strings + */ +const char *oidc_parse_pass_userinfo_as(apr_pool_t *pool, const char *v1, + const char *v2, const char *v3, int *int_value) { + static char *options[] = { + OIDC_PASS_USERINFO_AS_CLAIMS_STR, + OIDC_PASS_USERINFO_AS_JSON_OBJECT_STR, + OIDC_PASS_USERINFO_AS_JWT_STR, + NULL }; + const char *rv = NULL; + rv = oidc_valid_string_option(pool, v1, options); + if (rv != NULL) + return rv; + *int_value = oidc_parse_pass_userinfo_as_str2int(v1); + + if (v2 == NULL) + return NULL; + + rv = oidc_valid_string_option(pool, v2, options); + if (rv != NULL) + return rv; + *int_value |= oidc_parse_pass_userinfo_as_str2int(v2); + + if (v3 == NULL) + return NULL; + + rv = oidc_valid_string_option(pool, v3, options); + if (rv != NULL) + return rv; + *int_value |= oidc_parse_pass_userinfo_as_str2int(v3); + + return NULL; +} + #define OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER_STR "header" #define OIDC_OAUTH_ACCEPT_TOKEN_IN_POST_STR "post" #define OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY_STR "query" @@ -1087,7 +1143,6 @@ const char *oidc_token_binding_policy2str(apr_pool_t *pool, int v) { return NULL; } - /* * check token binding policy string value */ @@ -1104,7 +1159,8 @@ const char *oidc_valid_token_binding_policy(apr_pool_t *pool, const char *arg) { /* * parse token binding policy */ -const char *oidc_parse_token_binding_policy(apr_pool_t *pool, const char *arg, int *policy) { +const char *oidc_parse_token_binding_policy(apr_pool_t *pool, const char *arg, + int *policy) { const char *rv = oidc_valid_token_binding_policy(pool, arg); if (rv != NULL) return rv; diff --git a/src/parse.h b/src/parse.h index 146384b2..0f9ad65e 100644 --- a/src/parse.h +++ b/src/parse.h @@ -100,6 +100,7 @@ const char *oidc_parse_session_inactivity_timeout(apr_pool_t *pool, const char * const char *oidc_parse_session_max_duration(apr_pool_t *pool, const char *arg, int *int_value); const char *oidc_parse_enc_kid_key_tuple(apr_pool_t *pool, const char *tuple, char **kid, char **key, int *key_len, apr_byte_t triplet); const char *oidc_parse_pass_idtoken_as(apr_pool_t *pool, const char *v1, const char *v2, const char *v3, int *int_value); +const char *oidc_parse_pass_userinfo_as(apr_pool_t *pool, const char *v1, const char *v2, const char *v3, int *int_value); const char *oidc_parse_accept_oauth_token_in(apr_pool_t *pool, const char *arg, int *b_value, apr_hash_t *list_options); const char *oidc_accept_oauth_token_in2str(apr_pool_t *pool, apr_byte_t v); const char *oidc_parse_claim_required(apr_pool_t *pool, const char *arg, int *is_required); diff --git a/src/proto.c b/src/proto.c index 212d02ce..d61ffc28 100644 --- a/src/proto.c +++ b/src/proto.c @@ -1812,11 +1812,13 @@ static apr_byte_t oidc_proto_endpoint_auth_client_secret_jwt(request_rec *r, static apr_byte_t oidc_proto_endpoint_access_token_bearer(request_rec *r, oidc_cfg *cfg, apr_table_t *params, char **bearer_auth_str) { - const char *token = strcmp(cfg->oauth.introspection_client_auth_bearer_token, "") == 0 ? - apr_table_get(params, cfg->oauth.introspection_token_param_name): + const char *token = + strcmp(cfg->oauth.introspection_client_auth_bearer_token, "") == 0 ? + apr_table_get(params, + cfg->oauth.introspection_token_param_name) : cfg->oauth.introspection_client_auth_bearer_token; *bearer_auth_str = apr_psprintf(r->pool, "%s", token); - + return TRUE; } @@ -1857,9 +1859,10 @@ apr_byte_t oidc_proto_token_endpoint_auth(request_rec *r, oidc_cfg *cfg, const char *token_endpoint_auth, const char *client_id, const char *client_secret, const char *audience, apr_table_t *params, char **basic_auth_str, char **bearer_auth_str) { - + if (cfg->oauth.introspection_client_auth_bearer_token != NULL) - return oidc_proto_endpoint_access_token_bearer(r, cfg, params, bearer_auth_str); + return oidc_proto_endpoint_access_token_bearer(r, cfg, params, + bearer_auth_str); oidc_debug(r, "token_endpoint_auth=%s", token_endpoint_auth); @@ -1879,8 +1882,8 @@ apr_byte_t oidc_proto_token_endpoint_auth(request_rec *r, oidc_cfg *cfg, // if no client_secret is set and we don't authenticate using private_key_jwt, // we can only be a public client since the other methods require a client_secret - if ((client_secret == NULL) && (apr_strnatcmp(token_endpoint_auth, - OIDC_PROTO_PRIVATE_KEY_JWT) != 0)) { + if ((client_secret == NULL) && (apr_strnatcmp(token_endpoint_auth, + OIDC_PROTO_PRIVATE_KEY_JWT) != 0)) { oidc_debug(r, "no client secret set and not using private_key_jwt, assume we are a public client"); return oidc_proto_endpoint_auth_none(r, client_id, params); @@ -1926,7 +1929,8 @@ static apr_byte_t oidc_proto_token_endpoint_request(request_rec *r, /* add the token endpoint authentication credentials */ if (oidc_proto_token_endpoint_auth(r, cfg, provider->token_endpoint_auth, provider->client_id, provider->client_secret, - provider->token_endpoint_url, params, &basic_auth, &bearer_auth) == FALSE) + provider->token_endpoint_url, params, &basic_auth, + &bearer_auth) == FALSE) return FALSE; /* add any configured extra static parameters to the token endpoint */ @@ -2039,7 +2043,7 @@ apr_byte_t oidc_proto_refresh_request(request_rec *r, oidc_cfg *cfg, static apr_byte_t oidc_user_info_response_validate(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, char **response, - json_t **claims) { + json_t **claims, char **userinfo_jwt) { oidc_debug(r, "enter: userinfo_signed_response_alg=%s, userinfo_encrypted_response_alg=%s, userinfo_encrypted_response_enc=%s", @@ -2116,9 +2120,9 @@ static apr_byte_t oidc_user_info_response_validate(request_rec *r, "successfully verified signed JWT returned from userinfo endpoint: %s", jwt->payload.value.str); + *userinfo_jwt = apr_pstrdup(r->pool, *response); *claims = json_deep_copy(jwt->payload.value.json); *response = apr_pstrdup(r->pool, jwt->payload.value.str); - oidc_jwt_destroy(jwt); return TRUE; @@ -2234,7 +2238,7 @@ static apr_byte_t oidc_proto_resolve_composite_claims(request_rec *r, */ apr_byte_t oidc_proto_resolve_userinfo(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, const char *id_token_sub, - const char *access_token, char **response) { + const char *access_token, char **response, char **userinfo_jwt) { oidc_debug(r, "enter, endpoint=%s, access_token=%s", provider->userinfo_endpoint_url, access_token); @@ -2262,8 +2266,8 @@ apr_byte_t oidc_proto_resolve_userinfo(request_rec *r, oidc_cfg *cfg, } json_t *claims = NULL; - if (oidc_user_info_response_validate(r, cfg, provider, response, - &claims) == FALSE) + if (oidc_user_info_response_validate(r, cfg, provider, response, &claims, + userinfo_jwt) == FALSE) return FALSE; if (oidc_proto_resolve_composite_claims(r, cfg, claims) == TRUE) diff --git a/src/session.c b/src/session.c index ddf33055..3d81fe8b 100644 --- a/src/session.c +++ b/src/session.c @@ -361,8 +361,10 @@ apr_byte_t oidc_session_set(request_rec *r, oidc_session_t *z, const char *key, /* * session object keys */ -/* key for storing the claims in the session context */ +/* key for storing the userinfo claims in the session context */ #define OIDC_SESSION_KEY_USERINFO_CLAIMS "uic" +/* key for storing the userinfo JWT in the session context */ +#define OIDC_SESSION_KEY_USERINFO_JWT "uij" /* key for storing the id_token in the session context */ #define OIDC_SESSION_KEY_IDTOKEN_CLAIMS "idc" /* key for storing the raw id_token in the session context */ @@ -499,6 +501,15 @@ json_t *oidc_session_get_userinfo_claims_json(request_rec *r, oidc_session_t *z) return oidc_session_get_str2json(r, z, oidc_session_get_userinfo_claims); } +void oidc_session_set_userinfo_jwt(request_rec *r, oidc_session_t *z, + const char *s_userinfo_jwt) { + oidc_session_set(r, z, OIDC_SESSION_KEY_USERINFO_JWT, s_userinfo_jwt); +} + +const char * oidc_session_get_userinfo_jwt(request_rec *r, oidc_session_t *z) { + return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_USERINFO_JWT); +} + /* * id_token claims */