From 1461634f75141df6386223bba9446ddb7bbf9ab1 Mon Sep 17 00:00:00 2001
From: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
Date: Sat, 1 Aug 2020 18:42:31 +0200
Subject: [PATCH] add Apache expression option to OIDCUnAuthAction; see #479

thanks @raro42 and @marcstern; bump to 2.4.4rc6

Signed-off-by: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
---
 auth_openidc.conf      |   19 +++++++++++++++++--
 src/config.c           |   43 +++++++++++++++++++++++++++++++++++++++----
 src/mod_auth_openidc.c |    3 ++-
 src/mod_auth_openidc.h |    1 +
 test/stub.c            |   11 +++++++++++
 5 files changed, 70 insertions(+), 7 deletions(-)

--- a/auth_openidc.conf
+++ b/auth_openidc.conf
@@ -700,9 +700,24 @@
 # "407" means that HTTP 407 Proxy Authentication Required is returned
 # "410" means that HTTP 410 Gone is returned
 # "pass" means that an unauthenticated request will pass but claims will still be passed when a user happens to be authenticated already
+#
 # Useful in Location/Directory/Proxy path contexts that serve AJAX/Javascript calls and for "anonymous access"
-# When not defined the default "auth" is used.
-#OIDCUnAuthAction [auth|pass|401|407|410]
+#
+# Since verson 2.4.4 a boolean Apache expression as the second parameter to specify which requests
+# need to match to return the configured value in the first parameter to override the default, e.g.:
+#   OIDCUnAuthAction 401 "%{HTTP_USER_AGENT} =~ /curl/"
+# or:
+#   OIDCUnAuthAction auth true
+# See also: https://httpd.apache.org/docs/2.4/expr.html. Note that:
+#   a. the default is "auth" with auto-detection of XML HTTP requests (returning 401 for the latter)
+#   b. only one such primitive can be configured per location, overriding the default processing from a.
+#   c. whenever an expression is configured, auto-detection of non-browser requests will be disabled, so:
+#      the 1st example will no longer auto-detect XML HTTP requests and non "curl" user agents will fall through to the default "auth"
+#      the 2nd example above overrides the auto-detection from a., redirecting all requests
+#      note that because of the above an "auth" value with *any* expression ("true", or "false"(!), or other)
+#      will effectively merely disable auto-detection of non-browser requests and render "auth" 
+# When not defined the default is "auth" with auto-detection of XML HTTP requests, returning "401".
+#OIDCUnAuthAction [auth|pass|401|407|410] [<expr>]
 
 # Defines the action to be taken when an unauthorized request is made i.e. the user is authenticated but
 # does not meet the `Require claim *:*` directives or similar.
--- a/src/config.c
+++ b/src/config.c
@@ -272,6 +272,7 @@ typedef struct oidc_dir_cfg {
 	char *cookie;
 	char *authn_header;
 	int unauth_action;
+	ap_expr_info_t *unauth_expression;
 	int unautz_action;
 	apr_array_header_t *pass_cookies;
 	apr_array_header_t *strip_cookies;
@@ -857,10 +858,20 @@ static const char * oidc_set_pass_claims
  * define how to act on unauthenticated requests
  */
 static const char * oidc_set_unauth_action(cmd_parms *cmd, void *m,
-		const char *arg) {
+		const char *arg1, const char *arg2) {
 	oidc_dir_cfg *dir_cfg = (oidc_dir_cfg *) m;
-	const char *rv = oidc_parse_unauth_action(cmd->pool, arg,
+	const char *expr_err = NULL;
+	const char *rv = oidc_parse_unauth_action(cmd->pool, arg1,
 			&dir_cfg->unauth_action);
+	if ((rv == NULL) && (arg2 != NULL)) {
+		dir_cfg->unauth_expression = ap_expr_parse_cmd(cmd, arg2,
+				AP_EXPR_FLAG_DONT_VARY & AP_EXPR_FLAG_RESTRICTED, &expr_err,
+				NULL);
+		if (expr_err != NULL) {
+			rv = apr_pstrcat(cmd->temp_pool, "cannot parse expression: ",
+					expr_err, NULL);
+		}
+	}
 	return OIDC_CONFIG_DIR_RV(cmd, rv);
 }
 
@@ -1630,6 +1641,7 @@ void *oidc_create_dir_config(apr_pool_t
 	c->cookie_path = OIDC_CONFIG_STRING_UNSET;
 	c->authn_header = OIDC_CONFIG_STRING_UNSET;
 	c->unauth_action = OIDC_CONFIG_POS_INT_UNSET;
+	c->unauth_expression = NULL;
 	c->unautz_action = OIDC_CONFIG_POS_INT_UNSET;
 	c->pass_cookies = NULL;
 	c->strip_cookies = NULL;
@@ -1757,9 +1769,29 @@ apr_array_header_t *oidc_dir_cfg_strip_c
 int oidc_dir_cfg_unauth_action(request_rec *r) {
 	oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
 			&auth_openidc_module);
+
+	int rc = 0;
+	const char *err_str = NULL;
 	if (dir_cfg->unauth_action == OIDC_CONFIG_POS_INT_UNSET)
 		return OIDC_DEFAULT_UNAUTH_ACTION;
-	return dir_cfg->unauth_action;
+
+	if (dir_cfg->unauth_expression == NULL)
+		return dir_cfg->unauth_action;
+
+	rc = ap_expr_exec(r, dir_cfg->unauth_expression, &err_str);
+
+	if (rc < 0) {
+		oidc_warn(r, "executing expression failed");
+		return OIDC_DEFAULT_UNAUTH_ACTION;
+	}
+
+	return (rc > 0) ? dir_cfg->unauth_action : OIDC_DEFAULT_UNAUTH_ACTION;
+}
+
+apr_byte_t oidc_dir_cfg_unauth_expr_is_set(request_rec *r) {
+	oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
+			&auth_openidc_module);
+	return (dir_cfg->unauth_expression != NULL) ? TRUE : FALSE;
 }
 
 int oidc_dir_cfg_unautz_action(request_rec *r) {
@@ -1804,6 +1836,9 @@ void *oidc_merge_dir_config(apr_pool_t *
 	c->unauth_action =
 			add->unauth_action != OIDC_CONFIG_POS_INT_UNSET ?
 					add->unauth_action : base->unauth_action;
+	c->unauth_expression =
+			add->unauth_expression != NULL ?
+					add->unauth_expression : base->unauth_expression;
 	c->unautz_action =
 			add->unautz_action != OIDC_CONFIG_POS_INT_UNSET ?
 					add->unautz_action : base->unautz_action;
@@ -2776,7 +2811,7 @@ const command_rec oidc_config_cmds[] = {
 				(void *) APR_OFFSETOF(oidc_dir_cfg, cookie),
 				RSRC_CONF|ACCESS_CONF|OR_AUTHCFG,
 				"Define the cookie name for the session cookie."),
-		AP_INIT_TAKE1(OIDCUnAuthAction,
+		AP_INIT_TAKE12(OIDCUnAuthAction,
 				oidc_set_unauth_action,
 				(void *) APR_OFFSETOF(oidc_dir_cfg, unauth_action),
 				RSRC_CONF|ACCESS_CONF|OR_AUTHCFG,
--- a/src/mod_auth_openidc.c
+++ b/src/mod_auth_openidc.c
@@ -1008,7 +1008,8 @@ static int oidc_handle_unauthenticated_u
 		 * won't redirect the user and thus avoid creating a state cookie
 		 * for a non-browser (= Javascript) call that will never return from the OP
 		 */
-		if (oidc_is_xml_http_request(r) == TRUE)
+		if ((oidc_dir_cfg_unauth_expr_is_set(r) == FALSE)
+				&& (oidc_is_xml_http_request(r) == TRUE))
 			return HTTP_UNAUTHORIZED;
 	}
 
--- a/src/mod_auth_openidc.h
+++ b/src/mod_auth_openidc.h
@@ -686,6 +686,7 @@ int oidc_cfg_dir_preserve_post(request_r
 apr_array_header_t *oidc_dir_cfg_pass_cookies(request_rec *r);
 apr_array_header_t *oidc_dir_cfg_strip_cookies(request_rec *r);
 int oidc_dir_cfg_unauth_action(request_rec *r);
+apr_byte_t oidc_dir_cfg_unauth_expr_is_set(request_rec *r);
 int oidc_dir_cfg_unautz_action(request_rec *r);
 char *oidc_dir_cfg_path_auth_request_params(request_rec *r);
 char *oidc_dir_cfg_path_scope(request_rec *r);
--- a/test/stub.c
+++ b/test/stub.c
@@ -86,6 +86,12 @@ AP_DECLARE(int) ap_is_initial_req(reques
 	return 0;
 }
 
+AP_DECLARE(ap_expr_info_t *) ap_expr_parse_cmd_mi(const cmd_parms *cmd, const char *expr,
+		unsigned int flags, const char **err, ap_expr_lookup_fn_t *lookup_fn,
+		int module_index) {
+	return NULL;
+}
+
 #if MODULE_MAGIC_NUMBER_MAJOR >= 20100714
 AP_DECLARE(void) ap_log_error_(const char *file, int line, int module_index, int level,
 		apr_status_t status, const server_rec *s, const char *fmt, ...) {
@@ -123,6 +129,11 @@ AP_DECLARE(void) ap_log_rerror(const cha
 AP_DECLARE(void) ap_note_auth_failure(request_rec *r) {
 }
 
+AP_DECLARE(int) ap_expr_exec(request_rec *r, const ap_expr_info_t *expr,
+			const char **err) {
+	return 0;
+}
+
 AP_DECLARE(apr_status_t) ap_pass_brigade(ap_filter_t *filter,
 		apr_bucket_brigade *bucket) {
 	return APR_SUCCESS;
