diff --git a/src/mod_sso/mod_sso.c b/src/mod_sso/mod_sso.c index be54c9678a0211cc94100dc3e6257784215d3ae2..6f6df90f697833bd3703226e12103003e10b9a6e 100644 --- a/src/mod_sso/mod_sso.c +++ b/src/mod_sso/mod_sso.c @@ -52,16 +52,37 @@ typedef struct { // Note: public_key is a binary buffer (non zero-terminated). const unsigned char *public_key; - // All known groups. + // All known groups (2.4: unused). apr_array_header_t *groups; } modsso_config; -typedef struct { - apr_array_header_t *user_groups; -} modsso_request_notes; - typedef const char *(*CMD_HAND_TYPE) (); +static char *groups_array_to_commasep_string(apr_pool_t *p, apr_array_header_t *groups) +{ + return apr_array_pstrcat(p, groups, ','); +} + +static char *groups_charp_to_string(apr_pool_t *p, const char **groups) { + apr_array_header_t *arr = apr_array_make(p, 1, sizeof(char *)); + const char **gptr; + for (gptr = groups; *gptr; gptr++) { + *(const char **)apr_array_push(arr) = *gptr; + } + return groups_array_to_commasep_string(p, arr); +} + +static char **groups_array_to_charpp(apr_pool_t *p, apr_array_header_t *groups) { + int i; + char **pp, **ptr; + pp = (char **)apr_palloc(p, sizeof(char *) * (groups->nelts + 1)); + for (ptr = pp, i = 0; i < groups->nelts; i++) { + *ptr++ = APR_ARRAY_IDX(groups, i, char *); + } + *ptr = NULL; + return pp; +} + /** * Create a modsso_config structure. * @@ -155,25 +176,6 @@ static const char *set_modsso_public_key_file(cmd_parms *parms, void *mconfig, c return NULL; } -static apr_array_header_t *parse_commasep_groups(apr_pool_t *pool, const char *commaseplist) { - apr_array_header_t *arr = apr_array_make(pool, 1, sizeof(const char *)); - char *tokenizerCtx = NULL, *group; - char *tmp = apr_pstrdup(pool, commaseplist); - group = apr_strtok(tmp, ",", &tokenizerCtx); - do { - *(const char **)apr_array_push(arr) = group; - group = apr_strtok(NULL, ",", &tokenizerCtx); - } while (group != NULL); - return arr; -} - -static const char *set_modsso_groups(cmd_parms *parms, void *mconfig, const char *arg) -{ - modsso_config *s_cfg = (modsso_config *)mconfig; - s_cfg->groups = parse_commasep_groups(parms->pool, arg); - return NULL; -} - static const command_rec mod_sso_cmds[] = { AP_INIT_TAKE1("SSOLoginServer", (CMD_HAND_TYPE) set_modsso_login_server, @@ -188,9 +190,6 @@ static const command_rec mod_sso_cmds[] = AP_INIT_TAKE1("SSOPublicKeyFile", (CMD_HAND_TYPE) set_modsso_public_key_file, NULL, RSRC_CONF, "SSOPublicKeyFile (string) Location of the login server public key"), - AP_INIT_TAKE1("SSOGroups", (CMD_HAND_TYPE) set_modsso_groups, - NULL, RSRC_CONF, - "SSOGroups (string) comma-separated list of all the groups that we might want to check membership for"), {NULL} }; @@ -427,9 +426,6 @@ static int mod_sso_method_handler(request_rec *r) ap_get_module_config(r->per_dir_config, &sso_module); uri = r->uri; - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, - "sso: handler \"%s\"", r->handler); - // Return immediately if there's nothing to do (check the AuthType) type = ap_auth_type(r); if (!type || strcasecmp(type, "SSO") != 0) { @@ -502,13 +498,13 @@ static int mod_sso_method_handler(request_rec *r) return DECLINED; } +#if MODULE_MAGIC_NUMBER_MAJOR < 20100714 struct modsso_auth_req { apr_array_header_t *groups; apr_array_header_t *users; int any_user; }; -#if MODULE_MAGIC_NUMBER_MAJOR < 20100714 static int array_contains(apr_array_header_t *arr, const char *s) { int i; @@ -588,28 +584,11 @@ static void mod_sso_parse_requirements(request_rec *r, } #endif -static char *encode_groups(apr_pool_t *p, apr_array_header_t *groups) -{ - /** - apr_array_header_t *arr = apr_array_make(p, (groups->nelts - 1) * 2 - 1, sizeof(const char *)); - int i; - - for (i = 0; i < groups->nelts; i++) { - if (i > 0) { - *(const char **)apr_array_push(arr) = ","; - } - *(const char **)apr_array_push(arr) = ((const char **)groups->elts)[i]; - } - return apr_array_pstrcat(p, arr, 0); - **/ - return apr_array_pstrcat(p, groups, ','); -} - static int redirect_to_login_server(request_rec *r, const char *login_server, const char *service_host, const char *service, - apr_array_header_t *groups) + const char **groups) { char *dest, *login_url; dest = full_uri(r, service_host); @@ -620,11 +599,11 @@ static int redirect_to_login_server(request_rec *r, "&d=", modsso_url_encode(r->pool, dest), NULL); - if (!apr_is_empty_array(groups)) { + if (groups) { login_url = apr_pstrcat(r->pool, login_url, "&g=", - encode_groups(r->pool, groups), + groups_charp_to_string(r->pool, groups), NULL); } ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server, @@ -634,6 +613,7 @@ static int redirect_to_login_server(request_rec *r, return http_redirect(r, login_url); } +#if 0 static char *pkey_to_string(const unsigned char *pkey, char *buf) { static const char *hex = "0123456789ABCDEF"; char *o = buf; @@ -646,6 +626,7 @@ static char *pkey_to_string(const unsigned char *pkey, char *buf) { *o = '\0'; return buf; } +#endif /** * Apache authentication handler for mod_sso. @@ -691,23 +672,13 @@ static int mod_sso_check_access_ex(request_rec *r) return DECLINED; } -static char *parse_ticket_groups(apr_pool_t *pool, char **groups) { - apr_array_header_t *arr = apr_array_make(pool, 1, sizeof(char *)); - if (groups) { - while (*groups) { - *(char **)apr_array_push(arr) = apr_pstrdup(pool, *groups); - groups++; - } - } - return apr_array_pstrcat(pool, arr, ','); -} - static int mod_sso_check_user_id(request_rec *r) { const char *type, *sso_cookie_name, *sso_cookie; const char *service = NULL, *service_host = NULL, *service_path = NULL; int retval, err, do_redirect = 1; + const char **required_groups; modsso_config *s_cfg = (modsso_config *) ap_get_module_config(r->per_dir_config, &sso_module); //apr_array_header_t *sso_validate_groups = NULL; @@ -729,9 +700,6 @@ static int mod_sso_check_user_id(request_rec *r) } } - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, - "sso (check_user_id): handler '%s' uri '%s'", r->handler, r->uri); - sso_cookie_name = get_cookie_name(r); // Check if the required parameters are defined. @@ -746,11 +714,15 @@ static int mod_sso_check_user_id(request_rec *r) return HTTP_BAD_REQUEST; } + // Fetch the list of desired groups set (eventually) by group_check_authorization. + required_groups = (const char **)apr_table_get(r->notes, "SSO_REQUIRED_GROUPS"); + // Test for valid cookie sso_cookie = get_cookie(r, sso_cookie_name); if (sso_cookie != NULL) { sso_ticket_t t; +#if 0 // Print some debugging information about the service { char pkeybuf[512]; @@ -763,26 +735,20 @@ static int mod_sso_check_user_id(request_rec *r) r->uri, service, s_cfg->service, host_hdr, sso_cookie, pkey_to_string(s_cfg->public_key, pkeybuf)); } +#endif err = sso_ticket_open(&t, sso_cookie, s_cfg->public_key); if (err != SSO_OK) { ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r->server, "sso: ticket decoding error: %s", sso_strerror(err)); } else { - // TODO: remove this so as to skip group membership check in sso_validate. - /* if (s_cfg->groups != NULL) { */ - /* sso_validate_groups = apr_array_copy(r->pool, s_cfg->groups); */ - /* *(const char **)apr_array_push(sso_validate_groups) = NULL; */ - /* } */ - - err = sso_validate(t, s_cfg->service, s_cfg->domain, - //apr_is_empty_array(s_cfg->groups) ? NULL : (const char **)sso_validate_groups - NULL); + err = sso_validate(t, s_cfg->service, s_cfg->domain, required_groups); if (err != SSO_OK) { ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r->server, "sso: validation error: %s", sso_strerror(err)); } else { - apr_table_setn(r->notes, "SSO_GROUPS", parse_ticket_groups(r->pool, t->groups)); + // Don't do this: t->groups wil be freed by sso_ticket_free + // apr_table_setn(r->notes, "SSO_GROUPS", (char *)(t->groups)); apr_table_setn(r->subprocess_env, "SSO_SERVICE", apr_pstrdup(r->pool, service)); r->user = apr_pstrdup(r->pool, t->user); @@ -801,7 +767,7 @@ static int mod_sso_check_user_id(request_rec *r) } // Redirect to login server - return redirect_to_login_server(r, s_cfg->login_server, service_host, service, s_cfg->groups); + return redirect_to_login_server(r, s_cfg->login_server, service_host, service, required_groups); } #else static int mod_sso_check_user_id(request_rec *r) @@ -912,7 +878,7 @@ static int mod_sso_check_user_id(request_rec *r) } // Redirect to login server - return redirect_to_login_server(r, s_cfg->login_server, service_host, service, auth.groups); + return redirect_to_login_server(r, s_cfg->login_server, service_host, service, apr_is_empty_array(auth.groups) ? NULL : (const char **)auth.groups->elts); } #endif /* apache 2.2 */ @@ -920,78 +886,69 @@ static int mod_sso_check_user_id(request_rec *r) * Apache authorization check callback for mod_sso. */ #if MODULE_MAGIC_NUMBER_MAJOR >= 20100714 -static authz_status sso_check_authorization(request_rec *r, const char *require_args, const void *parsed_require_args) -{ - const char *uri, *type; - const char *service = NULL, *service_host = NULL, - *service_path = NULL; - char *sso_logout_path, *sso_login_path; - modsso_config *s_cfg; - - // We already did everything in mod_sso_check_user_id(), - // so just succeed (if SSO is active). - type = ap_auth_type(r); - if (type && !strcasecmp(type, "SSO") && r->user) { - return OK; +static apr_array_header_t *required_groups_array(request_rec *r, const void *parsed_require_args) { + const ap_expr_info_t *expr = parsed_require_args; + const char *err = NULL; + const char *require, *w, *t; + apr_array_header_t *grouparr = apr_array_make(r->pool, 1, sizeof(char *)); + + require = ap_expr_str_exec(r, expr, &err); + if (err) { + return NULL; } - // XXX check groups via e.g. 'Require sso-groups' or 'Require valid-group' - s_cfg = (modsso_config *) - ap_get_module_config(r->per_dir_config, &sso_module); - uri = r->uri; - if (parse_service(r, s_cfg, &service, &service_host, &service_path) != 0) { - ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, - "sso (auth_checker): could not parse service \"%s\"", - s_cfg->service); - return HTTP_BAD_REQUEST; + t = require; + while ((w = ap_getword_conf(r->pool, &t)) && w[0]) { + *(const char **)apr_array_push(grouparr) = w; } + return grouparr; +} - sso_logout_path = apr_pstrcat(r->pool, service_path, "sso_logout", NULL); - sso_login_path = apr_pstrcat(r->pool, service_path, "sso_login", NULL); - if (!strcmp(uri, sso_logout_path) || !strcmp(uri, sso_login_path)) { - return OK; +static char **required_groups_charpp(request_rec *r, const void *parsed_require_args) { + apr_array_header_t *arr = required_groups_array(r, parsed_require_args); + if (!arr) { + return NULL; } - - return DECLINED; + return groups_array_to_charpp(r->pool, arr); } +// This function will be called twice: first, before any authn +// handlers are executed, and we return AUTHZ_DENIED_NO_USER to tell +// Apache that we need a user. This should cause it to invoke +// mod_sso_check_user_id, and then call this function again. static authz_status group_check_authorization(request_rec *r, const char *require_args, const void *parsed_require_args) { - const ap_expr_info_t *expr = parsed_require_args; - const char *err = NULL; - const char *group_str, *require, *w, *t; - apr_array_header_t *user_groups = NULL; - int i; - - // Retrieve the comma-separated group list from r->notes. - group_str = apr_table_get(r->notes, "SSO_GROUPS"); - if (!group_str) { - return DECLINED; + // Do we have a user? All ok then! We assume that the request was + // validated by mod_sso_check_user_id using the value of + // SSO_REQUIRED_GROUPS we set earlier. + if (r->user) { + return AUTHZ_GRANTED; } - user_groups = parse_commasep_groups(r->pool, group_str); - require = ap_expr_str_exec(r, expr, &err); - if (err) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02592) - "mod_sso authorize: require group: Can't " - "evaluate require expression: %s", err); - return AUTHZ_DENIED; - } + // Set the list of groups in the request notes, so that the + // sso authn handler can retrieve it and validate the ticket. + apr_table_setn(r->notes, "SSO_REQUIRED_GROUPS", + (char *)required_groups_charpp(r, parsed_require_args)); + return AUTHZ_DENIED_NO_USER; +} - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, - "sso (check_group): user '%s' user_groups '%s'", r->user, group_str); +static const char *group_parse_config(cmd_parms *cmd, const char *require_line, + const void **parsed_require_line) +{ + const char *expr_err = NULL; + ap_expr_info_t *expr; - t = require; - while ((w = ap_getword_conf(r->pool, &t)) && w[0]) { - // Check if w is in user_groups. - for (i = 0; i < user_groups->nelts; i++) { - char *el = APR_ARRAY_IDX(user_groups, i, char *); - if (!strcasecmp(w, el)) { - return AUTHZ_GRANTED; - } - } + expr = ap_expr_parse_cmd(cmd, require_line, AP_EXPR_FLAG_STRING_RESULT, + &expr_err, NULL); + + if (expr_err) { + return apr_pstrcat(cmd->temp_pool, + "Cannot parse expression in require line: ", + expr_err, NULL); } - - return DECLINED; + + *parsed_require_line = expr; + + return NULL; } #else @@ -1031,16 +988,10 @@ static int mod_sso_auth_checker(request_rec *r) #endif #if MODULE_MAGIC_NUMBER_MAJOR >= 20100714 -static const authz_provider authz_sso_provider = -{ - &sso_check_authorization, - NULL, -}; - static const authz_provider authz_sso_group_provider = { &group_check_authorization, - NULL, + &group_parse_config, }; #endif @@ -1056,9 +1007,8 @@ static void mod_sso_register_hooks (apr_pool_t *p) { ap_hook_handler(mod_sso_method_handler, NULL, NULL, APR_HOOK_FIRST); #if MODULE_MAGIC_NUMBER_MAJOR >= 20100714 - ap_hook_check_authn(mod_sso_check_user_id, NULL, NULL, APR_HOOK_MIDDLE, AP_AUTH_INTERNAL_PER_CONF); + ap_hook_check_authn(mod_sso_check_user_id, NULL, NULL, APR_HOOK_FIRST, AP_AUTH_INTERNAL_PER_CONF); ap_hook_check_access_ex(mod_sso_check_access_ex, NULL, NULL, APR_HOOK_MIDDLE, AP_AUTH_INTERNAL_PER_CONF); - ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, SSO_REQUIRE_NAME, "0", &authz_sso_provider, AP_AUTH_INTERNAL_PER_CONF); ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "group", "0", &authz_sso_group_provider, AP_AUTH_INTERNAL_PER_CONF); #else static const char * const authzSucc[] = { "mod_sso.c", NULL }; diff --git a/src/mod_sso/test/httpd_integration_test.py b/src/mod_sso/test/httpd_integration_test.py index 939ff6e6718f30b68cfd6f94945a2fcc18f5d617..e691c9d4ada441c40270a14498c6b107bae548bc 100755 --- a/src/mod_sso/test/httpd_integration_test.py +++ b/src/mod_sso/test/httpd_integration_test.py @@ -141,9 +141,10 @@ class HttpdIntegrationTest(unittest.TestCase): def mkcookie(tkt): return "SSO_test=%s" % tkt - # For Apache 2.2, set this to the empty string (we do not use the - # SSOGroup directive, so only the requested groups are generated). - extra_groups = "&g=group1,group2,group3" + # Set to a non-empty string when testing the SSOGroups directive + # (normally only the requested groups are generated). + #extra_groups = "&g=group1,group2,group3" + extra_groups = '' # Tests have a name so that we can recognize failures. checks = [ @@ -185,6 +186,10 @@ class HttpdIntegrationTest(unittest.TestCase): "cookie": mkcookie(self._ticket()), "status": 200, "body": "ok"}), + #("protected-group with cookie wrong group -> unauthorized", + # {"url": "/protected-group/index.html", + # "cookie": mkcookie(self._ticket(group="group2")), + # "status": 401}), ("protected-group with cookie wrong group -> redirect", {"url": "/protected-group/index.html", "cookie": mkcookie(self._ticket(group="group2")), @@ -213,7 +218,7 @@ class HttpdIntegrationTest(unittest.TestCase): ] for name, check in checks: for i in xrange(10): - print 'CHECKING', check + print 'CHECKING %s (%d of 10)' % (name, i), check status, body, location = _query(check["url"], host=check.get("http_host"), cookie=check.get("cookie")) diff --git a/src/mod_sso/test/test-httpd-2.4.conf b/src/mod_sso/test/test-httpd-2.4.conf index cdaf267b2c3f47910ab47fad4ad0ff2ecdec5d06..40a2923bfe30b515ff0cb70a02207030bf6a24a4 100644 --- a/src/mod_sso/test/test-httpd-2.4.conf +++ b/src/mod_sso/test/test-httpd-2.4.conf @@ -17,7 +17,7 @@ LogLevel debug SSOLoginServer login.example.com SSODomain example.com SSOPublicKeyFile ${TESTROOT}/public.key -SSOGroups group1,group2,group3 +#SSOGroups group1,group2,group3 DocumentRoot ${TESTROOT}/htdocs <Directory "${TESTROOT}/htdocs"> @@ -25,6 +25,8 @@ DocumentRoot ${TESTROOT}/htdocs AuthName test SSOService service.example.com/ require valid-user + + AllowOverride All </Directory> <Location "/other-service">