Skip to content

Commit

Permalink
release 2.3.3; add support for OIDCPassUserInfoAs
Browse files Browse the repository at this point in the history
add support for passing userinfo as a JSON object or (when available) as
a JWT with OIDCPassUserInfoAs; closes #311

Signed-off-by: Hans Zandbelt <[email protected]>
  • Loading branch information
zandbelt committed Nov 16, 2017
1 parent f831e6e commit 56955b5
Show file tree
Hide file tree
Showing 10 changed files with 217 additions and 54 deletions.
4 changes: 4 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -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
Expand Down
8 changes: 8 additions & 0 deletions auth_openidc.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
AC_INIT([mod_auth_openidc],[2.3.3rc3],[[email protected]])
AC_INIT([mod_auth_openidc],[2.3.3],[[email protected]])

AC_SUBST(NAMEVER, AC_PACKAGE_TARNAME()-AC_PACKAGE_VERSION())

Expand Down
41 changes: 33 additions & 8 deletions src/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
}

/*
Expand Down Expand Up @@ -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,
Expand Down
86 changes: 64 additions & 22 deletions src/mod_auth_openidc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand All @@ -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 */
Expand Down Expand Up @@ -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");

Expand Down Expand Up @@ -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) {
Expand All @@ -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");
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -1838,14 +1875,15 @@ 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
* parsed claims are not actually used here but need to be parsed anyway for error checking purposes
*/
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);
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down
Loading

0 comments on commit 56955b5

Please sign in to comment.