dpward / rpms / sssd

Forked from rpms/sssd 3 years ago
Clone
Blob Blame History Raw
From 1b4b03720c409b183debe0e0532b1009301e9cb2 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
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 <pbrezina@redhat.com>
Reviewed-by: Sumit Bose <sbose@redhat.com>
(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