From 1b4b03720c409b183debe0e0532b1009301e9cb2 Mon Sep 17 00:00:00 2001 From: Jakub Hrozek Date: Sun, 19 Nov 2017 22:47:00 +0100 Subject: [PATCH 82/83] CACHE_REQ: Use the domain-locator request to only search domains where the entry was found MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Uses the internal cache_req interface around the getAccountDomain to only search the domain returned by the cache_req_locate_domain_recv() request. If that request returns that no domain matched, all domains (belonging to the currently processed main domain) are skipped by setting the per-type negative cache. if a domain is reported as containing an object, all domains except that one are marked with the negative cache entries. Resolves: https://pagure.io/SSSD/sssd/issue/3468 Reviewed-by: Pavel Březina Reviewed-by: Sumit Bose (cherry picked from commit f2a5e29f063f9d623c1336d76f4b2bc500c1a5e2) --- src/responder/common/cache_req/cache_req.c | 402 +++++- src/responder/common/cache_req/cache_req_domain.h | 1 + src/tests/cmocka/test_responder_cache_req.c | 1373 +++++++++++++++++++++ 3 files changed, 1758 insertions(+), 18 deletions(-) diff --git a/src/responder/common/cache_req/cache_req.c b/src/responder/common/cache_req/cache_req.c index 110df561101be538e3f0496addfa2e14e42ea918..ad9bc040dd999a205713141e6a1512e47b69c45e 100644 --- a/src/responder/common/cache_req/cache_req.c +++ b/src/responder/common/cache_req/cache_req.c @@ -363,6 +363,53 @@ static void cache_req_global_ncache_add(struct cache_req *cr) return; } +static bool cache_req_check_acct_domain_lookup_type(struct cache_req *cr, + struct sss_domain_info *dom) +{ + struct sss_domain_info *head; + int nret; + + head = get_domains_head(dom); + if (head == NULL) { + return false; + } + + nret = sss_ncache_check_domain_locate_type(cr->rctx->ncache, + head, + cr->plugin->name); + if (nret == ENOENT) { + return true; + } + return false; +} + +static errno_t cache_req_set_acct_domain_lookup_type(struct cache_req *cr, + struct sss_domain_info *dom) +{ + struct sss_domain_info *head; + + head = get_domains_head(dom); + if (head == NULL) { + return EINVAL; + } + + return sss_ncache_set_domain_locate_type(cr->rctx->ncache, + head, + cr->plugin->name); +} + +static void cache_req_domain_set_locate_flag(struct cache_req_domain *domains, + struct cache_req *cr) +{ + struct cache_req_domain *crd_iter; + + DLIST_FOR_EACH(crd_iter, domains) { + if (cache_req_check_acct_domain_lookup_type(cr, crd_iter->domain)) { + crd_iter->locate_domain = true; + } + } +} + static bool cache_req_assume_upn(struct cache_req *cr) { @@ -391,6 +438,227 @@ cache_req_assume_upn(struct cache_req *cr) return true; } +struct cache_req_locate_dom_state { + /* input data */ + struct tevent_context *ev; + struct cache_req *cr; + struct cache_req_domain *req_domains; + + /* Return values in case the first cache lookup succeeds */ + struct ldb_result *result; + bool dp_success; +}; + +static void cache_req_locate_dom_cache_done(struct tevent_req *subreq); +static void cache_req_locate_dom_done(struct tevent_req *subreq); +static void cache_req_locate_dom_mark_neg_all( + struct cache_req_locate_dom_state *state); +static void cache_req_locate_dom_mark_neg_domains( + struct cache_req_locate_dom_state *state, + const char *found_domain_name); + +static struct tevent_req *cache_req_locate_dom_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct cache_req *cr, + struct cache_req_domain *req_domains) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct cache_req_locate_dom_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct cache_req_locate_dom_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + state->ev = ev; + state->cr = cr; + state->req_domains = req_domains; + + /* It is wasteful to run the domain locator request if the results are + * present in the cache, because the domain locator always contacts + * the DP. Therefore, first run a cache-only search and only if the + * requested data is not available, run the locator + * + * FIXME - this could be optimized further if we are running the + * second iteration with cache_first, then we don't need to search + * again + */ + subreq = cache_req_search_send(state, + state->ev, + state->cr, + false, /* Don't bypass cache */ + true); /* Do bypass DP */ + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + tevent_req_set_callback(subreq, cache_req_locate_dom_cache_done, req); + + return req; + +immediately: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void cache_req_locate_dom_cache_done(struct tevent_req *subreq) +{ + struct cache_req_locate_dom_state *state = NULL; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct cache_req_locate_dom_state); + + ret = cache_req_search_recv(state, subreq, &state->result, &state->dp_success); + talloc_zfree(subreq); + + switch (ret) { + case EOK: + /* Just finish the request and let the caller handle the result */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Result found in the cache\n"); + tevent_req_done(req); + return; + case ENOENT: + /* Not cached and locator was requested, run the locator + * DP request plugin + */ + subreq = cache_req_locate_domain_send(state, + state->ev, + state->cr); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, cache_req_locate_dom_done, req); + return; + default: + DEBUG(SSSDBG_OP_FAILURE, + "cache_req_search_recv returned [%d]: %s\n", ret, sss_strerror(ret)); + break; + } + + tevent_req_error(req, ret); + return; +} + +static void cache_req_locate_dom_done(struct tevent_req *subreq) +{ + struct cache_req_locate_dom_state *state; + struct tevent_req *req; + errno_t ret; + char *found_domain_name; + int nret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct cache_req_locate_dom_state); + + ret = cache_req_locate_domain_recv(state, subreq, &found_domain_name); + talloc_zfree(subreq); + switch (ret) { + case ERR_GET_ACCT_DOM_NOT_SUPPORTED: + nret = cache_req_set_acct_domain_lookup_type(state->cr, + state->cr->domain); + if (nret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to disable domain locating functionality for %s\n", + state->cr->plugin->name); + } + DEBUG(SSSDBG_CONF_SETTINGS, + "Disabled domain locating functionality for %s\n", + state->cr->plugin->name); + break; + case ERR_NOT_FOUND: + cache_req_locate_dom_mark_neg_all(state); + break; + case EOK: + cache_req_locate_dom_mark_neg_domains(state, found_domain_name); + break; + default: + /* We explicitly ignore errors here */ + break; + } + + tevent_req_done(req); + return; +} + +static void cache_req_locate_dom_mark_neg_all( + struct cache_req_locate_dom_state *state) +{ + struct cache_req_domain *iter; + + DLIST_FOR_EACH(iter, state->req_domains) { + if (get_domains_head(state->cr->domain) != get_domains_head(iter->domain)) { + /* Only add to negative cache for domains from the same "main" + * domain" */ + continue; + } + cache_req_search_ncache_add_to_domain(state->cr, iter->domain); + } +} + +static void cache_req_locate_dom_mark_neg_domains( + struct cache_req_locate_dom_state *state, + const char *found_domain_name) +{ + struct sss_domain_info *found_domain; + struct cache_req_domain *iter; + + found_domain = find_domain_by_name(get_domains_head(state->cr->domain), + found_domain_name, + true); + if (found_domain == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot find domain %s\n", found_domain_name); + return; + } + + /* Set negcache in all subdomains of the one being examined + * except the found one */ + DLIST_FOR_EACH(iter, state->req_domains) { + if (strcasecmp(found_domain_name, + iter->domain->name) == 0) { + continue; + } + + if (get_domains_head(found_domain) != get_domains_head(iter->domain)) { + /* Don't set negative cache for domains outside the main + * domain/subdomain tree b/c the locator request is not + * authoritative for them + */ + continue; + } + cache_req_search_ncache_add_to_domain(state->cr, iter->domain); + } +} + +static errno_t cache_req_locate_dom_cache_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ldb_result **_result, + bool *_dp_success) +{ + struct cache_req_locate_dom_state *state; + + state = tevent_req_data(req, struct cache_req_locate_dom_state); + + if (_dp_success != NULL) { + *_dp_success = state->dp_success; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_result != NULL) { + *_result = talloc_steal(mem_ctx, state->result); + } + + return EOK; +} + struct cache_req_search_domains_state { /* input data */ struct tevent_context *ev; @@ -398,6 +666,7 @@ struct cache_req_search_domains_state { /* work data */ struct cache_req_domain *cr_domain; + struct cache_req_domain *req_domains; struct sss_domain_info *selected_domain; struct cache_req_result **results; size_t num_results; @@ -408,6 +677,10 @@ struct cache_req_search_domains_state { }; static errno_t cache_req_search_domains_next(struct tevent_req *req); +static errno_t cache_req_handle_result(struct tevent_req *req, + struct ldb_result *result); + +static void cache_req_search_domains_locate_done(struct tevent_req *subreq); static void cache_req_search_domains_done(struct tevent_req *subreq); @@ -417,6 +690,7 @@ cache_req_search_domains_send(TALLOC_CTX *mem_ctx, struct cache_req *cr, struct cache_req_domain *cr_domain, bool check_next, + bool first_iteration, bool bypass_cache, bool bypass_dp) { @@ -435,11 +709,23 @@ cache_req_search_domains_send(TALLOC_CTX *mem_ctx, state->cr = cr; state->cr_domain = cr_domain; + state->req_domains = cr_domain; state->check_next = check_next; state->dp_success = true; state->bypass_cache = bypass_cache; state->bypass_dp = bypass_dp; + if (cr->plugin->dp_get_domain_send_fn != NULL + && ((state->check_next && cr_domain->next != NULL) + || (state->bypass_cache && !first_iteration))) { + /* If the request is not qualified with a domain name AND + * there are multiple domains to search OR if this is the second + * pass during the "check-cache-first" schema, it makes sense + * to try to run the domain-locator plugin + */ + cache_req_domain_set_locate_flag(cr_domain, cr); + } + ret = cache_req_search_domains_next(req); if (ret == EAGAIN) { return req; @@ -510,12 +796,23 @@ static errno_t cache_req_search_domains_next(struct tevent_req *req) return ret; } + if (state->cr_domain->locate_domain) { + subreq = cache_req_locate_dom_send(state, + state->ev, + cr, + state->req_domains); + if (subreq == NULL) { + return ENOMEM; + } + tevent_req_set_callback(subreq, cache_req_search_domains_locate_done, req); + return EAGAIN; + } + subreq = cache_req_search_send(state, state->ev, cr, state->bypass_cache, state->bypass_dp); if (subreq == NULL) { return ENOMEM; } - tevent_req_set_callback(subreq, cache_req_search_domains_done, req); /* we will continue with the following domain the next time */ @@ -549,6 +846,89 @@ static errno_t cache_req_search_domains_next(struct tevent_req *req) return ENOENT; } +static void cache_req_search_domains_locate_done(struct tevent_req *subreq) +{ + struct cache_req_search_domains_state *state; + struct ldb_result *result = NULL; + struct tevent_req *req; + bool dp_success; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct cache_req_search_domains_state); + + ret = cache_req_locate_dom_cache_recv(state, subreq, &result, &dp_success); + talloc_zfree(subreq); + + /* Remember if any DP request fails, but here it shouldn't matter + * as the only DP request that should realistically happen is midpoint + * refresh */ + state->dp_success = !dp_success ? false : state->dp_success; + + /* Don't locate the domain again */ + state->cr_domain->locate_domain = false; + + switch (ret) { + case EOK: + if (result != NULL) { + /* Handle result as normally */ + ret = cache_req_handle_result(req, result); + if (ret != EAGAIN) { + goto done; + } + } + break; + default: + /* Some serious error has happened. Finish. */ + goto done; + } + + /* This is a domain less search, continue with the next domain. */ + ret = cache_req_search_domains_next(req); + +done: + switch (ret) { + case EOK: + tevent_req_done(req); + break; + case EAGAIN: + break; + default: + tevent_req_error(req, ret); + break; + } + return; +} + +static errno_t cache_req_handle_result(struct tevent_req *req, + struct ldb_result *result) +{ + struct cache_req_search_domains_state *state; + errno_t ret; + + state = tevent_req_data(req, struct cache_req_search_domains_state); + + /* We got some data from this search. Save it. */ + ret = cache_req_create_and_add_result(state, + state->cr, + state->selected_domain, + result, + state->cr->data->name.lookup, + &state->results, + &state->num_results); + if (ret != EOK) { + /* We were unable to save data. */ + return ret; + } + + if (!state->check_next || !state->cr->plugin->search_all_domains) { + /* We are not interested in more results. */ + return EOK; + } + + return EAGAIN; +} + static void cache_req_search_domains_done(struct tevent_req *subreq) { struct cache_req_search_domains_state *state; @@ -568,25 +948,10 @@ static void cache_req_search_domains_done(struct tevent_req *subreq) switch (ret) { case EOK: - /* We got some data from this search. Save it. */ - ret = cache_req_create_and_add_result(state, - state->cr, - state->selected_domain, - result, - state->cr->data->name.lookup, - &state->results, - &state->num_results); - if (ret != EOK) { - /* We were unable to save data. */ + ret = cache_req_handle_result(req, result); + if (ret != EAGAIN) { goto done; } - - if (!state->check_next || !state->cr->plugin->search_all_domains) { - /* We are not interested in more results. */ - ret = EOK; - goto done; - } - break; case ENOENT: if (state->check_next == false) { @@ -1030,6 +1395,7 @@ cache_req_search_domains(struct tevent_req *req, subreq = cache_req_search_domains_send(state, state->ev, state->cr, cr_domain, check_next, + state->first_iteration, bypass_cache, bypass_dp); if (subreq == NULL) { return ENOMEM; diff --git a/src/responder/common/cache_req/cache_req_domain.h b/src/responder/common/cache_req/cache_req_domain.h index ebdc71dd635d5d8a5d06e30e96c5d4101b6d98bf..5769b6aee309d9ba3edd5bb73a3cef6dc3193fdc 100644 --- a/src/responder/common/cache_req/cache_req_domain.h +++ b/src/responder/common/cache_req/cache_req_domain.h @@ -26,6 +26,7 @@ struct cache_req_domain { struct sss_domain_info *domain; bool fqnames; + bool locate_domain; struct cache_req_domain *prev; struct cache_req_domain *next; diff --git a/src/tests/cmocka/test_responder_cache_req.c b/src/tests/cmocka/test_responder_cache_req.c index f075480a019e476407a3081a795c3c289455aca8..0ee0070d0c9fbb89020f522b2f7613f1076a8cbb 100644 --- a/src/tests/cmocka/test_responder_cache_req.c +++ b/src/tests/cmocka/test_responder_cache_req.c @@ -27,6 +27,7 @@ #include "tests/cmocka/common_mock_resp.h" #include "db/sysdb.h" #include "responder/common/cache_req/cache_req.h" +#include "db/sysdb_private.h" /* new_subdomain() */ #define TESTS_PATH "tp_" BASE_FILE_STEM #define TEST_CONF_DB "test_responder_cache_req_conf.ldb" @@ -63,6 +64,11 @@ struct test_group { test_multi_domain_setup, \ test_multi_domain_teardown) +#define new_subdomain_test(test) \ + cmocka_unit_test_setup_teardown(test_ ## test, \ + test_subdomain_setup, \ + test_subdomain_teardown) + #define run_cache_req(ctx, send_fn, done_fn, dom, crp, lookup, expret) do { \ TALLOC_CTX *req_mem_ctx; \ struct tevent_req *req; \ @@ -110,6 +116,7 @@ struct cache_req_test_ctx { struct sss_test_ctx *tctx; struct resp_ctx *rctx; struct sss_nc_ctx *ncache; + struct sss_domain_info *subdomain; struct cache_req_result *result; bool dp_called; @@ -120,6 +127,8 @@ struct cache_req_test_ctx { bool create_user2; bool create_group1; bool create_group2; + bool create_subgroup1; + bool create_subuser1; }; const char *domains[] = {"responder_cache_req_test_a", @@ -128,6 +137,8 @@ const char *domains[] = {"responder_cache_req_test_a", "responder_cache_req_test_d", NULL}; +const char *subdomain_name = "responder_cache_req_test_a_sub"; + struct cli_protocol_version *register_cli_protocol_version(void) { static struct cli_protocol_version version[] = { @@ -487,6 +498,26 @@ __wrap_sss_dp_get_account_send(TALLOC_CTX *mem_ctx, prepare_group(ctx->tctx->dom, &groups[1], 1000, time(NULL)); } + if (ctx->create_subgroup1) { + struct sss_domain_info *domain = NULL; + + domain = find_domain_by_name(ctx->tctx->dom, + subdomain_name, + true); + assert_non_null(domain); + prepare_group(domain, &groups[0], 1000, time(NULL)); + } + + if (ctx->create_subuser1) { + struct sss_domain_info *domain = NULL; + + domain = find_domain_by_name(ctx->tctx->dom, + subdomain_name, + true); + assert_non_null(domain); + prepare_user(domain, &users[0], 1000, time(NULL)); + } + return test_req_succeed_send(mem_ctx, rctx->ev); } @@ -581,6 +612,67 @@ static int test_multi_domain_teardown(void **state) return 0; } +static int test_subdomain_setup(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + int ret; + const char *const testdom[4] = { subdomain_name, "TEST_A.SUB", "test_a", "S-3" }; + + assert_true(leak_check_setup()); + + test_dom_suite_setup(TESTS_PATH); + + test_ctx = talloc_zero(global_talloc_context, struct cache_req_test_ctx); + assert_non_null(test_ctx); + *state = test_ctx; + + test_ctx->tctx = create_dom_test_ctx(test_ctx, TESTS_PATH, TEST_CONF_DB, + TEST_DOM_NAME, TEST_ID_PROVIDER, NULL); + assert_non_null(test_ctx->tctx); + + test_ctx->rctx = mock_rctx(test_ctx, test_ctx->tctx->ev, + test_ctx->tctx->dom, NULL); + assert_non_null(test_ctx->rctx); + + ret = sss_ncache_init(test_ctx, 10, 0, &test_ctx->ncache); + assert_int_equal(ret, EOK); + + test_ctx->subdomain = new_subdomain(test_ctx, test_ctx->tctx->dom, + testdom[0], testdom[1], testdom[2], testdom[3], + false, false, NULL, NULL, 0, + test_ctx->tctx->confdb); + assert_non_null(test_ctx->subdomain); + + ret = sysdb_subdomain_store(test_ctx->tctx->sysdb, + testdom[0], testdom[1], testdom[2], testdom[3], + false, false, NULL, 0, NULL); + assert_int_equal(ret, EOK); + + ret = sysdb_update_subdomains(test_ctx->tctx->dom, + test_ctx->tctx->confdb); + assert_int_equal(ret, EOK); + + *state = test_ctx; + check_leaks_push(test_ctx); + return 0; +} + +static int test_subdomain_teardown(void **state) +{ + struct cache_req_test_ctx *test_ctx; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + talloc_zfree(test_ctx->result); + talloc_zfree(test_ctx->rctx->cr_domains); + + assert_true(check_leaks_pop(test_ctx)); + talloc_zfree(test_ctx); + test_dom_suite_cleanup(TESTS_PATH, TEST_CONF_DB, TEST_DOM_NAME); + assert_true(leak_check_teardown()); + return 0; +} + void test_user_by_name_multiple_domains_found(void **state) { struct cache_req_test_ctx *test_ctx = NULL; @@ -974,6 +1066,7 @@ void test_user_by_id_multiple_domains_found(void **state) /* Mock values. */ will_return_always(__wrap_sss_dp_get_account_send, test_ctx); will_return_always(sss_dp_req_recv, 0); + will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED); /* Test. */ run_user_by_id(test_ctx, NULL, 0, ERR_OK); @@ -990,12 +1083,317 @@ void test_user_by_id_multiple_domains_notfound(void **state) /* Mock values. */ will_return_always(__wrap_sss_dp_get_account_send, test_ctx); will_return_always(sss_dp_req_recv, 0); + will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED); /* Test. */ run_user_by_id(test_ctx, NULL, 0, ENOENT); assert_true(test_ctx->dp_called); } +void test_user_by_id_multiple_domains_locator_cache_valid(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + struct sss_domain_info *domain = NULL; + const char *locator_domain; + TALLOC_CTX *tmp_ctx; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */ + locator_domain = talloc_strdup(tmp_ctx, "responder_cache_req_test_d"); + assert_non_null(locator_domain); + + /* Setup user. */ + domain = find_domain_by_name(test_ctx->tctx->dom, + "responder_cache_req_test_d", true); + assert_non_null(domain); + prepare_user(domain, &users[0], 1000, time(NULL)); + + will_return(sss_dp_get_account_domain_recv, EOK); + will_return(sss_dp_get_account_domain_recv, locator_domain); + will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED); + + will_return_always(__wrap_sss_dp_get_account_send, test_ctx); + will_return_always(sss_dp_req_recv, EOK); + + /* Test. */ + run_user_by_id(test_ctx, NULL, 0, ERR_OK); + /* Even though the locator tells us to skip all domains except d, the domains + * are standalone and the result of the locator request is only valid within + * the subdomains + */ + assert_true(test_ctx->dp_called); + check_user(test_ctx, &users[0], domain); + + talloc_free(tmp_ctx); +} + +void test_user_by_id_multiple_domains_locator_cache_expired(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + struct sss_domain_info *domain = NULL; + const char *locator_domain; + TALLOC_CTX *tmp_ctx; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */ + locator_domain = talloc_strdup(tmp_ctx, "responder_cache_req_test_d"); + assert_non_null(locator_domain); + + /* Setup user. */ + domain = find_domain_by_name(test_ctx->tctx->dom, + "responder_cache_req_test_d", true); + assert_non_null(domain); + prepare_user(domain, &users[0], -1000, time(NULL)); + + will_return_always(__wrap_sss_dp_get_account_send, test_ctx); + will_return_always(sss_dp_req_recv, EOK); + + will_return(sss_dp_get_account_domain_recv, EOK); + will_return(sss_dp_get_account_domain_recv, locator_domain); + will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED); + + /* Test. */ + run_user_by_id(test_ctx, NULL, 0, ERR_OK); + + assert_true(test_ctx->dp_called); + check_user(test_ctx, &users[0], domain); + + talloc_free(tmp_ctx); +} + +void test_user_by_id_sub_domains_locator_cache_valid(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + struct sss_domain_info *domain = NULL; + const char *locator_domain; + TALLOC_CTX *tmp_ctx; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */ + locator_domain = talloc_strdup(tmp_ctx, subdomain_name); + assert_non_null(locator_domain); + + /* Setup user. */ + domain = find_domain_by_name(test_ctx->tctx->dom, + subdomain_name, + true); + assert_non_null(domain); + prepare_user(domain, &users[0], 1000, time(NULL)); + + will_return(sss_dp_get_account_domain_recv, EOK); + will_return(sss_dp_get_account_domain_recv, locator_domain); + + /* Test. */ + run_user_by_id(test_ctx, NULL, 0, ERR_OK); + + /* Even though the ID is present in the last domain, + * we're not calling sss_dp_get_account_send, + * because the locator will cause cache_req to skip + * all domains except _d + */ + assert_false(test_ctx->dp_called); + check_user(test_ctx, &users[0], domain); + + talloc_free(tmp_ctx); +} + +void test_user_by_id_sub_domains_locator_cache_expired(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + struct sss_domain_info *domain = NULL; + const char *locator_domain; + TALLOC_CTX *tmp_ctx; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */ + locator_domain = talloc_strdup(tmp_ctx, subdomain_name); + assert_non_null(locator_domain); + + /* Setup user. */ + domain = find_domain_by_name(test_ctx->tctx->dom, + subdomain_name, + true); + assert_non_null(domain); + prepare_user(domain, &users[0], -1000, time(NULL)); + + /* Note - DP will only be called once (so, we're not using will_return_always) + * because the locator will tell us which domain to look into. For the recv + * function, we use always b/c internally it mocks several values. + */ + will_return(__wrap_sss_dp_get_account_send, test_ctx); + will_return_always(sss_dp_req_recv, 0); + + will_return(sss_dp_get_account_domain_recv, EOK); + will_return(sss_dp_get_account_domain_recv, locator_domain); + + /* Test. */ + run_user_by_id(test_ctx, NULL, 0, ERR_OK); + + assert_true(test_ctx->dp_called); + check_user(test_ctx, &users[0], domain); + + talloc_free(tmp_ctx); +} + +void test_user_by_id_sub_domains_locator_cache_midpoint(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + struct sss_domain_info *domain = NULL; + const char *locator_domain; + TALLOC_CTX *tmp_ctx; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */ + locator_domain = talloc_strdup(tmp_ctx, subdomain_name); + assert_non_null(locator_domain); + + /* Setup user. */ + domain = find_domain_by_name(test_ctx->tctx->dom, + subdomain_name, + true); + assert_non_null(domain); + prepare_user(domain, &users[0], 50, time(NULL) - 26); + + /* Note - DP will only be called once and we're not waiting + * for the results (so, we're not mocking _recv) + */ + will_return(__wrap_sss_dp_get_account_send, test_ctx); + + will_return(sss_dp_get_account_domain_recv, EOK); + will_return(sss_dp_get_account_domain_recv, locator_domain); + + /* Test. */ + run_user_by_id(test_ctx, NULL, 50, ERR_OK); + + assert_true(test_ctx->dp_called); + check_user(test_ctx, &users[0], domain); + + talloc_free(tmp_ctx); +} + +void test_user_by_id_sub_domains_locator_missing_found(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + struct sss_domain_info *domain = NULL; + const char *locator_domain; + TALLOC_CTX *tmp_ctx; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */ + locator_domain = talloc_strdup(tmp_ctx, subdomain_name); + assert_non_null(locator_domain); + + /* Note - DP will only be called once (so, we're not using will_return_always) + * because the locator will tell us which domain to look into. For the recv + * function, we use always b/c internally it mocks several values. + */ + will_return(__wrap_sss_dp_get_account_send, test_ctx); + will_return_always(sss_dp_req_recv, 0); + + will_return(sss_dp_get_account_domain_recv, EOK); + will_return(sss_dp_get_account_domain_recv, locator_domain); + + /* Test. */ + test_ctx->create_subuser1 = true; + run_user_by_id(test_ctx, NULL, 0, ERR_OK); + + assert_true(test_ctx->dp_called); + + domain = find_domain_by_name(test_ctx->tctx->dom, + subdomain_name, + true); + assert_non_null(domain); + check_user(test_ctx, &users[0], domain); + + talloc_free(tmp_ctx); +} + +void test_user_by_id_sub_domains_locator_missing_notfound(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + will_return(sss_dp_get_account_domain_recv, ERR_NOT_FOUND); + + /* Test. */ + run_user_by_id(test_ctx, NULL, 0, ENOENT); + assert_false(test_ctx->dp_called); +} + +void test_user_by_id_sub_domains_locator_cache_expired_two_calls(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + struct sss_domain_info *domain = NULL; + const char *locator_domain; + TALLOC_CTX *tmp_ctx; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */ + locator_domain = talloc_strdup(tmp_ctx, subdomain_name); + assert_non_null(locator_domain); + + /* Setup user. */ + domain = find_domain_by_name(test_ctx->tctx->dom, + subdomain_name, + true); + assert_non_null(domain); + test_ctx->create_subuser1 = true; + prepare_user(domain, &users[0], -1000, time(NULL)); + + /* Note - DP will only be called once (so, we're not using will_return_always) + * because the locator will tell us which domain to look into. For the recv + * function, we use always b/c internally it mocks several values. + */ + will_return(__wrap_sss_dp_get_account_send, test_ctx); + will_return_always(sss_dp_req_recv, 0); + + will_return(sss_dp_get_account_domain_recv, EOK); + will_return(sss_dp_get_account_domain_recv, locator_domain); + + /* Test. */ + run_user_by_id(test_ctx, NULL, 0, ERR_OK); + assert_true(test_ctx->dp_called); + check_user(test_ctx, &users[0], domain); + + /* Request the same user again */ + test_ctx->tctx->done = false; + talloc_zfree(test_ctx->result); + + run_user_by_id(test_ctx, NULL, 0, ERR_OK); + check_user(test_ctx, &users[0], domain); + + talloc_free(tmp_ctx); +} + void test_user_by_id_cache_valid(void **state) { struct cache_req_test_ctx *test_ctx = NULL; @@ -1332,6 +1730,7 @@ void test_group_by_id_multiple_domains_found(void **state) /* Mock values. */ will_return_always(__wrap_sss_dp_get_account_send, test_ctx); will_return_always(sss_dp_req_recv, 0); + will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED); /* Test. */ run_group_by_id(test_ctx, NULL, 0, ERR_OK); @@ -1348,12 +1747,318 @@ void test_group_by_id_multiple_domains_notfound(void **state) /* Mock values. */ will_return_always(__wrap_sss_dp_get_account_send, test_ctx); will_return_always(sss_dp_req_recv, 0); + will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED); /* Test. */ run_group_by_id(test_ctx, NULL, 0, ENOENT); assert_true(test_ctx->dp_called); } +void test_group_by_id_multiple_domains_locator_cache_valid(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + struct sss_domain_info *domain = NULL; + const char *locator_domain; + TALLOC_CTX *tmp_ctx; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */ + locator_domain = talloc_strdup(tmp_ctx, "responder_cache_req_test_d"); + assert_non_null(locator_domain); + + /* Setup group. */ + domain = find_domain_by_name(test_ctx->tctx->dom, + "responder_cache_req_test_d", true); + assert_non_null(domain); + prepare_group(domain, &groups[0], 1000, time(NULL)); + + will_return(sss_dp_get_account_domain_recv, EOK); + will_return(sss_dp_get_account_domain_recv, locator_domain); + will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED); + + will_return_always(__wrap_sss_dp_get_account_send, test_ctx); + will_return_always(sss_dp_req_recv, EOK); + + /* Test. */ + run_group_by_id(test_ctx, NULL, 0, ERR_OK); + + /* Even though the locator tells us to skip all domains except d, the domains + * are standalone and the result of the locator request is only valid within + * the subdomains + */ + assert_true(test_ctx->dp_called); + check_group(test_ctx, &groups[0], domain); + + talloc_free(tmp_ctx); +} + +void test_group_by_id_multiple_domains_locator_cache_expired(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + struct sss_domain_info *domain = NULL; + const char *locator_domain; + TALLOC_CTX *tmp_ctx; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */ + locator_domain = talloc_strdup(tmp_ctx, "responder_cache_req_test_d"); + assert_non_null(locator_domain); + + /* Setup group. */ + domain = find_domain_by_name(test_ctx->tctx->dom, + "responder_cache_req_test_d", true); + assert_non_null(domain); + prepare_group(domain, &groups[0], -1000, time(NULL)); + + will_return_always(__wrap_sss_dp_get_account_send, test_ctx); + will_return_always(sss_dp_req_recv, EOK); + + will_return(sss_dp_get_account_domain_recv, EOK); + will_return(sss_dp_get_account_domain_recv, locator_domain); + will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED); + + /* Test. */ + run_group_by_id(test_ctx, NULL, 0, ERR_OK); + + assert_true(test_ctx->dp_called); + check_group(test_ctx, &groups[0], domain); + + talloc_free(tmp_ctx); +} + +void test_group_by_id_sub_domains_locator_cache_valid(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + struct sss_domain_info *domain = NULL; + const char *locator_domain; + TALLOC_CTX *tmp_ctx; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */ + locator_domain = talloc_strdup(tmp_ctx, subdomain_name); + assert_non_null(locator_domain); + + /* Setup group. */ + domain = find_domain_by_name(test_ctx->tctx->dom, + subdomain_name, + true); + assert_non_null(domain); + prepare_group(domain, &groups[0], 1000, time(NULL)); + + will_return(sss_dp_get_account_domain_recv, EOK); + will_return(sss_dp_get_account_domain_recv, locator_domain); + + /* Test. */ + run_group_by_id(test_ctx, NULL, 0, ERR_OK); + + /* Even though the ID is present in the last domain, + * we're not calling sss_dp_get_account_send, + * because the locator will cause cache_req to skip + * all domains except _d + */ + assert_false(test_ctx->dp_called); + check_group(test_ctx, &groups[0], domain); + + talloc_free(tmp_ctx); +} + +void test_group_by_id_sub_domains_locator_cache_expired(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + struct sss_domain_info *domain = NULL; + const char *locator_domain; + TALLOC_CTX *tmp_ctx; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */ + locator_domain = talloc_strdup(tmp_ctx, subdomain_name); + assert_non_null(locator_domain); + + /* Setup group. */ + domain = find_domain_by_name(test_ctx->tctx->dom, + subdomain_name, + true); + assert_non_null(domain); + prepare_group(domain, &groups[0], -1000, time(NULL)); + + /* Note - DP will only be called once (so, we're not using will_return_always) + * because the locator will tell us which domain to look into. For the recv + * function, we use always b/c internally it mocks several values. + */ + will_return(__wrap_sss_dp_get_account_send, test_ctx); + will_return_always(sss_dp_req_recv, 0); + + will_return(sss_dp_get_account_domain_recv, EOK); + will_return(sss_dp_get_account_domain_recv, locator_domain); + + /* Test. */ + run_group_by_id(test_ctx, NULL, 0, ERR_OK); + + assert_true(test_ctx->dp_called); + check_group(test_ctx, &groups[0], domain); + + talloc_free(tmp_ctx); +} + +void test_group_by_id_sub_domains_locator_cache_midpoint(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + struct sss_domain_info *domain = NULL; + const char *locator_domain; + TALLOC_CTX *tmp_ctx; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */ + locator_domain = talloc_strdup(tmp_ctx, subdomain_name); + assert_non_null(locator_domain); + + /* Setup group. */ + domain = find_domain_by_name(test_ctx->tctx->dom, + subdomain_name, + true); + assert_non_null(domain); + prepare_group(domain, &groups[0], 50, time(NULL) - 26); + + /* Note - DP will only be called once and we're not waiting + * for the results (so, we're not mocking _recv) + */ + will_return(__wrap_sss_dp_get_account_send, test_ctx); + + will_return(sss_dp_get_account_domain_recv, EOK); + will_return(sss_dp_get_account_domain_recv, locator_domain); + + /* Test. */ + run_group_by_id(test_ctx, NULL, 50, ERR_OK); + + assert_true(test_ctx->dp_called); + check_group(test_ctx, &groups[0], domain); + + talloc_free(tmp_ctx); +} + +void test_group_by_id_sub_domains_locator_missing_found(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + struct sss_domain_info *domain = NULL; + const char *locator_domain; + TALLOC_CTX *tmp_ctx; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */ + locator_domain = talloc_strdup(tmp_ctx, subdomain_name); + assert_non_null(locator_domain); + + /* Note - DP will only be called once (so, we're not using will_return_always) + * because the locator will tell us which domain to look into. For the recv + * function, we use always b/c internally it mocks several values. + */ + will_return(__wrap_sss_dp_get_account_send, test_ctx); + will_return_always(sss_dp_req_recv, 0); + + will_return(sss_dp_get_account_domain_recv, EOK); + will_return(sss_dp_get_account_domain_recv, locator_domain); + + /* Test. */ + test_ctx->create_subgroup1 = true; + run_group_by_id(test_ctx, NULL, 0, ERR_OK); + + assert_true(test_ctx->dp_called); + + domain = find_domain_by_name(test_ctx->tctx->dom, + subdomain_name, + true); + assert_non_null(domain); + check_group(test_ctx, &groups[0], domain); + + talloc_free(tmp_ctx); +} + +void test_group_by_id_sub_domains_locator_missing_notfound(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + will_return(sss_dp_get_account_domain_recv, ERR_NOT_FOUND); + + /* Test. */ + run_group_by_id(test_ctx, NULL, 0, ENOENT); + assert_false(test_ctx->dp_called); +} + +void test_group_by_id_sub_domains_locator_cache_expired_two_calls(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + struct sss_domain_info *domain = NULL; + const char *locator_domain; + TALLOC_CTX *tmp_ctx; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */ + locator_domain = talloc_strdup(tmp_ctx, subdomain_name); + assert_non_null(locator_domain); + + /* Setup group. */ + domain = find_domain_by_name(test_ctx->tctx->dom, + subdomain_name, + true); + assert_non_null(domain); + test_ctx->create_subgroup1 = true; + prepare_group(domain, &groups[0], -1000, time(NULL)); + + /* Note - DP will only be called once (so, we're not using will_return_always) + * because the locator will tell us which domain to look into. For the recv + * function, we use always b/c internally it mocks several values. + */ + will_return(__wrap_sss_dp_get_account_send, test_ctx); + will_return_always(sss_dp_req_recv, 0); + + will_return(sss_dp_get_account_domain_recv, EOK); + will_return(sss_dp_get_account_domain_recv, locator_domain); + + /* Test. */ + run_group_by_id(test_ctx, NULL, 0, ERR_OK); + assert_true(test_ctx->dp_called); + check_group(test_ctx, &groups[0], domain); + + /* Request the same group again */ + test_ctx->tctx->done = false; + talloc_zfree(test_ctx->result); + + run_group_by_id(test_ctx, NULL, 0, ERR_OK); + check_group(test_ctx, &groups[0], domain); + + talloc_free(tmp_ctx); +} + void test_group_by_id_cache_valid(void **state) { struct cache_req_test_ctx *test_ctx = NULL; @@ -2311,6 +3016,7 @@ void test_object_by_id_user_multiple_domains_found(void **state) /* Mock values. */ will_return_always(__wrap_sss_dp_get_account_send, test_ctx); will_return_always(sss_dp_req_recv, 0); + will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED); /* Test. */ run_object_by_id(test_ctx, NULL, users[0].uid, attrs, 0, ERR_OK); @@ -2328,6 +3034,7 @@ void test_object_by_id_user_multiple_domains_notfound(void **state) /* Mock values. */ will_return_always(__wrap_sss_dp_get_account_send, test_ctx); will_return_always(sss_dp_req_recv, 0); + will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED); /* Test. */ run_object_by_id(test_ctx, NULL, users[0].uid, attrs, 0, ENOENT); @@ -2476,6 +3183,7 @@ void test_object_by_id_group_multiple_domains_found(void **state) /* Mock values. */ will_return_always(__wrap_sss_dp_get_account_send, test_ctx); will_return_always(sss_dp_req_recv, 0); + will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED); /* Test. */ run_object_by_id(test_ctx, NULL, groups[0].gid, attrs, 0, ERR_OK); @@ -2493,12 +3201,641 @@ void test_object_by_id_group_multiple_domains_notfound(void **state) /* Mock values. */ will_return_always(__wrap_sss_dp_get_account_send, test_ctx); will_return_always(sss_dp_req_recv, 0); + will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED); /* Test. */ run_object_by_id(test_ctx, NULL, groups[0].gid, attrs, 0, ENOENT); assert_true(test_ctx->dp_called); } +void test_object_by_id_user_multiple_domains_locator_cache_valid(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + struct sss_domain_info *domain = NULL; + const char *locator_domain; + TALLOC_CTX *tmp_ctx; + const char *attrs[] = SYSDB_PW_ATTRS; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */ + locator_domain = talloc_strdup(tmp_ctx, "responder_cache_req_test_d"); + assert_non_null(locator_domain); + + /* Setup user. */ + domain = find_domain_by_name(test_ctx->tctx->dom, + "responder_cache_req_test_d", true); + assert_non_null(domain); + prepare_user(domain, &users[0], 1000, time(NULL)); + + will_return(sss_dp_get_account_domain_recv, EOK); + will_return(sss_dp_get_account_domain_recv, locator_domain); + will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED); + + will_return_always(__wrap_sss_dp_get_account_send, test_ctx); + will_return_always(sss_dp_req_recv, EOK); + + /* Test. */ + run_object_by_id(test_ctx, NULL, users[0].uid, attrs, 0, ERR_OK); + /* Even though the locator tells us to skip all domains except d, the domains + * are standalone and the result of the locator request is only valid within + * the subdomains + */ + assert_true(test_ctx->dp_called); + check_user(test_ctx, &users[0], domain); + + talloc_free(tmp_ctx); +} + +void test_object_by_id_user_multiple_domains_locator_cache_expired(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + struct sss_domain_info *domain = NULL; + const char *locator_domain; + TALLOC_CTX *tmp_ctx; + const char *attrs[] = SYSDB_PW_ATTRS; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */ + locator_domain = talloc_strdup(tmp_ctx, "responder_cache_req_test_d"); + assert_non_null(locator_domain); + + /* Setup user. */ + domain = find_domain_by_name(test_ctx->tctx->dom, + "responder_cache_req_test_d", true); + assert_non_null(domain); + prepare_user(domain, &users[0], -1000, time(NULL)); + + will_return_always(__wrap_sss_dp_get_account_send, test_ctx); + will_return_always(sss_dp_req_recv, EOK); + + will_return(sss_dp_get_account_domain_recv, EOK); + will_return(sss_dp_get_account_domain_recv, locator_domain); + will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED); + + /* Test. */ + run_object_by_id(test_ctx, NULL, users[0].uid, attrs, 0, ERR_OK); + + assert_true(test_ctx->dp_called); + check_user(test_ctx, &users[0], domain); + + talloc_free(tmp_ctx); +} + +void test_object_by_id_user_sub_domains_locator_cache_valid(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + struct sss_domain_info *domain = NULL; + const char *locator_domain; + TALLOC_CTX *tmp_ctx; + const char *attrs[] = SYSDB_PW_ATTRS; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */ + locator_domain = talloc_strdup(tmp_ctx, subdomain_name); + assert_non_null(locator_domain); + + /* Setup user. */ + domain = find_domain_by_name(test_ctx->tctx->dom, + subdomain_name, + true); + assert_non_null(domain); + prepare_user(domain, &users[0], 1000, time(NULL)); + + will_return(sss_dp_get_account_domain_recv, EOK); + will_return(sss_dp_get_account_domain_recv, locator_domain); + + /* Test. */ + run_object_by_id(test_ctx, NULL, users[0].uid, attrs, 0, ERR_OK); + + /* Even though the ID is present in the last domain, + * we're not calling sss_dp_get_account_send, + * because the locator will cause cache_req to skip + * all domains except _d + */ + assert_false(test_ctx->dp_called); + check_user(test_ctx, &users[0], domain); + + talloc_free(tmp_ctx); +} + +void test_object_by_id_user_sub_domains_locator_cache_expired(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + struct sss_domain_info *domain = NULL; + const char *locator_domain; + TALLOC_CTX *tmp_ctx; + const char *attrs[] = SYSDB_PW_ATTRS; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */ + locator_domain = talloc_strdup(tmp_ctx, subdomain_name); + assert_non_null(locator_domain); + + /* Setup user. */ + domain = find_domain_by_name(test_ctx->tctx->dom, + subdomain_name, + true); + assert_non_null(domain); + prepare_user(domain, &users[0], -1000, time(NULL)); + + /* Note - DP will only be called once (so, we're not using will_return_always) + * because the locator will tell us which domain to look into. For the recv + * function, we use always b/c internally it mocks several values. + */ + will_return(__wrap_sss_dp_get_account_send, test_ctx); + will_return_always(sss_dp_req_recv, 0); + + will_return(sss_dp_get_account_domain_recv, EOK); + will_return(sss_dp_get_account_domain_recv, locator_domain); + + /* Test. */ + run_object_by_id(test_ctx, NULL, users[0].uid, attrs, 0, ERR_OK); + + assert_true(test_ctx->dp_called); + check_user(test_ctx, &users[0], domain); + + talloc_free(tmp_ctx); +} + +void test_object_by_id_user_sub_domains_locator_cache_midpoint(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + struct sss_domain_info *domain = NULL; + const char *locator_domain; + TALLOC_CTX *tmp_ctx; + const char *attrs[] = SYSDB_PW_ATTRS; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */ + locator_domain = talloc_strdup(tmp_ctx, subdomain_name); + assert_non_null(locator_domain); + + /* Setup user. */ + domain = find_domain_by_name(test_ctx->tctx->dom, + subdomain_name, + true); + assert_non_null(domain); + prepare_user(domain, &users[0], 50, time(NULL) - 26); + + /* Note - DP will only be called once and we're not waiting + * for the results (so, we're not mocking _recv) + */ + will_return(__wrap_sss_dp_get_account_send, test_ctx); + + will_return(sss_dp_get_account_domain_recv, EOK); + will_return(sss_dp_get_account_domain_recv, locator_domain); + + /* Test. */ + run_object_by_id(test_ctx, NULL, users[0].uid, attrs, 50, ERR_OK); + + assert_true(test_ctx->dp_called); + check_user(test_ctx, &users[0], domain); + + talloc_free(tmp_ctx); +} + +void test_object_by_id_user_sub_domains_locator_missing_found(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + struct sss_domain_info *domain = NULL; + const char *locator_domain; + TALLOC_CTX *tmp_ctx; + const char *attrs[] = SYSDB_PW_ATTRS; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */ + locator_domain = talloc_strdup(tmp_ctx, subdomain_name); + assert_non_null(locator_domain); + + /* Note - DP will only be called once (so, we're not using will_return_always) + * because the locator will tell us which domain to look into. For the recv + * function, we use always b/c internally it mocks several values. + */ + will_return(__wrap_sss_dp_get_account_send, test_ctx); + will_return_always(sss_dp_req_recv, 0); + + will_return(sss_dp_get_account_domain_recv, EOK); + will_return(sss_dp_get_account_domain_recv, locator_domain); + + /* Test. */ + test_ctx->create_subuser1 = true; + run_object_by_id(test_ctx, NULL, users[0].uid, attrs, 0, ERR_OK); + + assert_true(test_ctx->dp_called); + + domain = find_domain_by_name(test_ctx->tctx->dom, + subdomain_name, + true); + assert_non_null(domain); + check_user(test_ctx, &users[0], domain); + + talloc_free(tmp_ctx); +} + +void test_object_by_id_user_sub_domains_locator_missing_notfound(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + const char *attrs[] = SYSDB_PW_ATTRS; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + will_return(sss_dp_get_account_domain_recv, ERR_NOT_FOUND); + + /* The test won't even ask the DP for the object, just iterate + * over the domains using the negative cache and quit + */ + run_object_by_id(test_ctx, NULL, users[0].uid, attrs, 0, ENOENT); + assert_false(test_ctx->dp_called); +} + +void test_object_by_id_user_sub_domains_locator_cache_expired_two_calls(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + struct sss_domain_info *domain = NULL; + const char *locator_domain; + TALLOC_CTX *tmp_ctx; + const char *attrs[] = SYSDB_PW_ATTRS; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */ + locator_domain = talloc_strdup(tmp_ctx, subdomain_name); + assert_non_null(locator_domain); + + /* Setup user. */ + domain = find_domain_by_name(test_ctx->tctx->dom, + subdomain_name, + true); + assert_non_null(domain); + test_ctx->create_subuser1 = true; + prepare_user(domain, &users[0], -1000, time(NULL)); + + /* Note - DP will only be called once (so, we're not using will_return_always) + * because the locator will tell us which domain to look into. For the recv + * function, we use always b/c internally it mocks several values. + */ + will_return(__wrap_sss_dp_get_account_send, test_ctx); + will_return_always(sss_dp_req_recv, 0); + + will_return(sss_dp_get_account_domain_recv, EOK); + will_return(sss_dp_get_account_domain_recv, locator_domain); + + /* Test. */ + run_object_by_id(test_ctx, NULL, users[0].uid, attrs, 0, EOK); + assert_true(test_ctx->dp_called); + check_user(test_ctx, &users[0], domain); + + /* Request the same user again */ + test_ctx->tctx->done = false; + talloc_zfree(test_ctx->result); + + run_object_by_id(test_ctx, NULL, users[0].uid, attrs, 0, EOK); + check_user(test_ctx, &users[0], domain); + + talloc_free(tmp_ctx); +} + +void test_object_by_id_group_multiple_domains_locator_cache_valid(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + struct sss_domain_info *domain = NULL; + const char *locator_domain; + TALLOC_CTX *tmp_ctx; + const char *attrs[] = SYSDB_PW_ATTRS; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */ + locator_domain = talloc_strdup(tmp_ctx, "responder_cache_req_test_d"); + assert_non_null(locator_domain); + + /* Setup group. */ + domain = find_domain_by_name(test_ctx->tctx->dom, + "responder_cache_req_test_d", true); + assert_non_null(domain); + prepare_group(domain, &groups[0], 1000, time(NULL)); + + will_return(sss_dp_get_account_domain_recv, EOK); + will_return(sss_dp_get_account_domain_recv, locator_domain); + will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED); + + will_return_always(__wrap_sss_dp_get_account_send, test_ctx); + will_return_always(sss_dp_req_recv, EOK); + + /* Test. */ + run_object_by_id(test_ctx, NULL, groups[0].gid, attrs, 0, ERR_OK); + /* Even though the locator tells us to skip all domains except d, the domains + * are standalone and the result of the locator request is only valid within + * the subdomains + */ + assert_true(test_ctx->dp_called); + check_group(test_ctx, &groups[0], domain); + + talloc_free(tmp_ctx); +} + +void test_object_by_id_group_multiple_domains_locator_cache_expired(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + struct sss_domain_info *domain = NULL; + const char *locator_domain; + TALLOC_CTX *tmp_ctx; + const char *attrs[] = SYSDB_PW_ATTRS; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */ + locator_domain = talloc_strdup(tmp_ctx, "responder_cache_req_test_d"); + assert_non_null(locator_domain); + + /* Setup group. */ + domain = find_domain_by_name(test_ctx->tctx->dom, + "responder_cache_req_test_d", true); + assert_non_null(domain); + prepare_group(domain, &groups[0], -1000, time(NULL)); + + will_return_always(__wrap_sss_dp_get_account_send, test_ctx); + will_return_always(sss_dp_req_recv, EOK); + + will_return(sss_dp_get_account_domain_recv, EOK); + will_return(sss_dp_get_account_domain_recv, locator_domain); + will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED); + + /* Test. */ + run_object_by_id(test_ctx, NULL, groups[0].gid, attrs, 0, ERR_OK); + + assert_true(test_ctx->dp_called); + check_group(test_ctx, &groups[0], domain); + + talloc_free(tmp_ctx); +} + +void test_object_by_id_group_sub_domains_locator_cache_valid(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + struct sss_domain_info *domain = NULL; + const char *locator_domain; + TALLOC_CTX *tmp_ctx; + const char *attrs[] = SYSDB_PW_ATTRS; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */ + locator_domain = talloc_strdup(tmp_ctx, subdomain_name); + assert_non_null(locator_domain); + + /* Setup group. */ + domain = find_domain_by_name(test_ctx->tctx->dom, + subdomain_name, + true); + assert_non_null(domain); + prepare_group(domain, &groups[0], 1000, time(NULL)); + + will_return(sss_dp_get_account_domain_recv, EOK); + will_return(sss_dp_get_account_domain_recv, locator_domain); + + /* Test. */ + run_object_by_id(test_ctx, NULL, groups[0].gid, attrs, 0, ERR_OK); + + /* Even though the ID is present in the last domain, + * we're not calling sss_dp_get_account_send, + * because the locator will cause cache_req to skip + * all domains except _d + */ + assert_false(test_ctx->dp_called); + check_group(test_ctx, &groups[0], domain); + + talloc_free(tmp_ctx); +} + +void test_object_by_id_group_sub_domains_locator_cache_expired(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + struct sss_domain_info *domain = NULL; + const char *locator_domain; + TALLOC_CTX *tmp_ctx; + const char *attrs[] = SYSDB_PW_ATTRS; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */ + locator_domain = talloc_strdup(tmp_ctx, subdomain_name); + assert_non_null(locator_domain); + + /* Setup group. */ + domain = find_domain_by_name(test_ctx->tctx->dom, + subdomain_name, + true); + assert_non_null(domain); + prepare_group(domain, &groups[0], -1000, time(NULL)); + + /* Note - DP will only be called once (so, we're not using will_return_always) + * because the locator will tell us which domain to look into. For the recv + * function, we use always b/c internally it mocks several values. + */ + will_return(__wrap_sss_dp_get_account_send, test_ctx); + will_return_always(sss_dp_req_recv, 0); + + will_return(sss_dp_get_account_domain_recv, EOK); + will_return(sss_dp_get_account_domain_recv, locator_domain); + + /* Test. */ + run_object_by_id(test_ctx, NULL, groups[0].gid, attrs, 0, ERR_OK); + + assert_true(test_ctx->dp_called); + check_group(test_ctx, &groups[0], domain); + + talloc_free(tmp_ctx); +} + +void test_object_by_id_group_sub_domains_locator_cache_midpoint(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + struct sss_domain_info *domain = NULL; + const char *locator_domain; + TALLOC_CTX *tmp_ctx; + const char *attrs[] = SYSDB_PW_ATTRS; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */ + locator_domain = talloc_strdup(tmp_ctx, subdomain_name); + assert_non_null(locator_domain); + + /* Setup group. */ + domain = find_domain_by_name(test_ctx->tctx->dom, + subdomain_name, + true); + assert_non_null(domain); + prepare_group(domain, &groups[0], 50, time(NULL) - 26); + + /* Note - DP will only be called once and we're not waiting + * for the results (so, we're not mocking _recv) + */ + will_return(__wrap_sss_dp_get_account_send, test_ctx); + + will_return(sss_dp_get_account_domain_recv, EOK); + will_return(sss_dp_get_account_domain_recv, locator_domain); + + /* Test. */ + run_object_by_id(test_ctx, NULL, groups[0].gid, attrs, 50, ERR_OK); + + assert_true(test_ctx->dp_called); + check_group(test_ctx, &groups[0], domain); + + talloc_free(tmp_ctx); +} + +void test_object_by_id_group_sub_domains_locator_missing_found(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + struct sss_domain_info *domain = NULL; + const char *locator_domain; + TALLOC_CTX *tmp_ctx; + const char *attrs[] = SYSDB_PW_ATTRS; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */ + locator_domain = talloc_strdup(tmp_ctx, subdomain_name); + assert_non_null(locator_domain); + + /* Note - DP will only be called once (so, we're not using will_return_always) + * because the locator will tell us which domain to look into. For the recv + * function, we use always b/c internally it mocks several values. + */ + will_return(__wrap_sss_dp_get_account_send, test_ctx); + will_return_always(sss_dp_req_recv, 0); + + will_return(sss_dp_get_account_domain_recv, EOK); + will_return(sss_dp_get_account_domain_recv, locator_domain); + + /* Test. */ + test_ctx->create_subgroup1 = true; + run_object_by_id(test_ctx, NULL, groups[0].gid, attrs, 0, ERR_OK); + + assert_true(test_ctx->dp_called); + + domain = find_domain_by_name(test_ctx->tctx->dom, + subdomain_name, + true); + assert_non_null(domain); + check_group(test_ctx, &groups[0], domain); + + talloc_free(tmp_ctx); +} + +void test_object_by_id_group_sub_domains_locator_missing_notfound(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + const char *attrs[] = SYSDB_PW_ATTRS; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + will_return(sss_dp_get_account_domain_recv, ERR_NOT_FOUND); + + /* The test won't even ask the DP for the object, just iterate + * over the domains using the negative cache and quit + */ + run_object_by_id(test_ctx, NULL, groups[0].gid, attrs, 0, ENOENT); + assert_false(test_ctx->dp_called); +} + +void test_object_by_id_group_sub_domains_locator_cache_expired_two_calls(void **state) +{ + struct cache_req_test_ctx *test_ctx = NULL; + struct sss_domain_info *domain = NULL; + const char *locator_domain; + TALLOC_CTX *tmp_ctx; + const char *attrs[] = SYSDB_PW_ATTRS; + + test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx); + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */ + locator_domain = talloc_strdup(tmp_ctx, subdomain_name); + assert_non_null(locator_domain); + + /* Setup group. */ + domain = find_domain_by_name(test_ctx->tctx->dom, + subdomain_name, + true); + assert_non_null(domain); + test_ctx->create_subgroup1 = true; + prepare_group(domain, &groups[0], -1000, time(NULL)); + + /* Note - DP will only be called once (so, we're not using will_return_always) + * because the locator will tell us which domain to look into. For the recv + * function, we use always b/c internally it mocks several values. + */ + will_return(__wrap_sss_dp_get_account_send, test_ctx); + will_return_always(sss_dp_req_recv, 0); + + will_return(sss_dp_get_account_domain_recv, EOK); + will_return(sss_dp_get_account_domain_recv, locator_domain); + + /* Test. */ + run_object_by_id(test_ctx, NULL, groups[0].gid, attrs, 0, EOK); + assert_true(test_ctx->dp_called); + check_group(test_ctx, &groups[0], domain); + + /* Request the same group again */ + test_ctx->tctx->done = false; + talloc_zfree(test_ctx->result); + + run_object_by_id(test_ctx, NULL, groups[0].gid, attrs, 0, EOK); + check_group(test_ctx, &groups[0], domain); + + talloc_free(tmp_ctx); +} + int main(int argc, const char *argv[]) { poptContext pc; @@ -2557,6 +3894,24 @@ int main(int argc, const char *argv[]) new_multi_domain_test(group_by_id_multiple_domains_found), new_multi_domain_test(group_by_id_multiple_domains_notfound), + new_multi_domain_test(group_by_id_multiple_domains_locator_cache_valid), + new_multi_domain_test(group_by_id_multiple_domains_locator_cache_expired), + new_subdomain_test(group_by_id_sub_domains_locator_cache_valid), + new_subdomain_test(group_by_id_sub_domains_locator_cache_expired), + new_subdomain_test(group_by_id_sub_domains_locator_cache_midpoint), + new_subdomain_test(group_by_id_sub_domains_locator_missing_found), + new_subdomain_test(group_by_id_sub_domains_locator_missing_notfound), + new_subdomain_test(group_by_id_sub_domains_locator_cache_expired_two_calls), + + new_multi_domain_test(user_by_id_multiple_domains_locator_cache_valid), + new_multi_domain_test(user_by_id_multiple_domains_locator_cache_expired), + new_subdomain_test(user_by_id_sub_domains_locator_cache_valid), + new_subdomain_test(user_by_id_sub_domains_locator_cache_expired), + new_subdomain_test(user_by_id_sub_domains_locator_cache_midpoint), + new_subdomain_test(user_by_id_sub_domains_locator_missing_found), + new_subdomain_test(user_by_id_sub_domains_locator_missing_notfound), + new_subdomain_test(user_by_id_sub_domains_locator_cache_expired_two_calls), + new_single_domain_test(user_by_recent_filter_valid), new_single_domain_test(users_by_recent_filter_valid), new_single_domain_test(group_by_recent_filter_valid), @@ -2603,6 +3958,24 @@ int main(int argc, const char *argv[]) new_single_domain_test(object_by_id_group_missing_notfound), new_multi_domain_test(object_by_id_group_multiple_domains_found), new_multi_domain_test(object_by_id_group_multiple_domains_notfound), + + new_multi_domain_test(object_by_id_user_multiple_domains_locator_cache_valid), + new_multi_domain_test(object_by_id_user_multiple_domains_locator_cache_expired), + new_subdomain_test(object_by_id_user_sub_domains_locator_cache_valid), + new_subdomain_test(object_by_id_user_sub_domains_locator_cache_expired), + new_subdomain_test(object_by_id_user_sub_domains_locator_cache_midpoint), + new_subdomain_test(object_by_id_user_sub_domains_locator_missing_found), + new_subdomain_test(object_by_id_user_sub_domains_locator_missing_notfound), + new_subdomain_test(object_by_id_user_sub_domains_locator_cache_expired_two_calls), + + new_multi_domain_test(object_by_id_group_multiple_domains_locator_cache_valid), + new_multi_domain_test(object_by_id_group_multiple_domains_locator_cache_expired), + new_subdomain_test(object_by_id_group_sub_domains_locator_cache_valid), + new_subdomain_test(object_by_id_group_sub_domains_locator_cache_expired), + new_subdomain_test(object_by_id_group_sub_domains_locator_cache_midpoint), + new_subdomain_test(object_by_id_group_sub_domains_locator_missing_found), + new_subdomain_test(object_by_id_group_sub_domains_locator_missing_notfound), + new_subdomain_test(object_by_id_group_sub_domains_locator_cache_expired_two_calls), }; /* Set debug level to invalid value so we can deside if -d 0 was used. */ -- 2.14.3