Skip to content

Commit 016415b

Browse files
authored
CDRIVER-4489 add mongoc_oidc_cache_t (#2119)
`mongoc_oidc_cache_t` is an internal component intended to be used for OIDC auth. Implements the "Client Cache" (not the "Connection Cache") from the auth spec.
1 parent ce96c89 commit 016415b

File tree

7 files changed

+677
-1
lines changed

7 files changed

+677
-1
lines changed

src/common/src/common-macros-private.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,16 @@
9696
#define MC_DISABLE_IMPLICIT_WARNING_END
9797
#endif
9898

99+
// Disable the -Wcast-qual warning
100+
#if defined(__GNUC__)
101+
#define MC_DISABLE_CAST_QUAL_WARNING_BEGIN MC_PRAGMA_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic ignored \"-Wcast-qual\"")
102+
#define MC_DISABLE_CAST_QUAL_WARNING_END MC_PRAGMA_DIAGNOSTIC_POP
103+
#elif defined(__clang__)
104+
#define MC_DISABLE_CAST_QUAL_WARNING_BEGIN MC_PRAGMA_DIAGNOSTIC_PUSH _Pragma("clang diagnostic ignored \"-Wcast-qual\"")
105+
#define MC_DISABLE_CAST_QUAL_WARNING_END MC_PRAGMA_DIAGNOSTIC_POP
106+
#else
107+
#define MC_DISABLE_CAST_QUAL_WARNING_BEGIN
108+
#define MC_DISABLE_CAST_QUAL_WARNING_END
109+
#endif
110+
99111
#endif /* MONGO_C_DRIVER_COMMON_MACROS_PRIVATE_H */

src/libmongoc/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,7 @@ set (MONGOC_SOURCES
594594
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-log-and-monitor-private.c
595595
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-memcmp.c
596596
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-cmd.c
597+
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-oidc-cache.c
597598
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-oidc-callback.c
598599
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-oidc-env.c
599600
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-opcode.c
@@ -1092,6 +1093,7 @@ set (test-libmongoc-sources
10921093
${PROJECT_SOURCE_DIR}/tests/test-mongoc-max-staleness.c
10931094
${PROJECT_SOURCE_DIR}/tests/test-mongoc-mongos-pinning.c
10941095
${PROJECT_SOURCE_DIR}/tests/test-mongoc-oidc-callback.c
1096+
${PROJECT_SOURCE_DIR}/tests/test-mongoc-oidc-cache.c
10951097
${PROJECT_SOURCE_DIR}/tests/test-mongoc-opts.c
10961098
${PROJECT_SOURCE_DIR}/tests/test-mongoc-primary-stepdown.c
10971099
${PROJECT_SOURCE_DIR}/tests/test-mongoc-queue.c
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2009-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#ifndef MONGOC_OIDC_CACHE_PRIVATE_H
18+
#define MONGOC_OIDC_CACHE_PRIVATE_H
19+
20+
#include <mongoc/mongoc-oidc-callback.h>
21+
#include <mongoc/mongoc-sleep.h>
22+
23+
// mongoc_oidc_cache_t implements the OIDC spec "Client Cache".
24+
// Stores the OIDC callback, cache, and lock.
25+
// Expected to be shared among all clients in a pool.
26+
typedef struct mongoc_oidc_cache_t mongoc_oidc_cache_t;
27+
28+
mongoc_oidc_cache_t *
29+
mongoc_oidc_cache_new(void);
30+
31+
// mongoc_oidc_cache_set_callback sets the token callback.
32+
// Not thread safe. Call before any authentication can occur.
33+
void
34+
mongoc_oidc_cache_set_callback(mongoc_oidc_cache_t *cache, const mongoc_oidc_callback_t *cb);
35+
36+
// mongoc_oidc_cache_get_callback gets the token callback.
37+
const mongoc_oidc_callback_t *
38+
mongoc_oidc_cache_get_callback(const mongoc_oidc_cache_t *cache);
39+
40+
// mongoc_oidc_cache_set_usleep_fn sets a custom sleep function.
41+
// Not thread safe. Call before any authentication can occur.
42+
void
43+
mongoc_oidc_cache_set_usleep_fn(mongoc_oidc_cache_t *cache, mongoc_usleep_func_t usleep_fn, void *usleep_data);
44+
45+
// mongoc_oidc_cache_get_token returns a token or NULL on error. Thread safe.
46+
// Sets *found_in_cache to indicate if the returned token came from the cache or callback.
47+
// Calls sleep if needed to enforce 100ms delay between calls to the callback.
48+
char *
49+
mongoc_oidc_cache_get_token(mongoc_oidc_cache_t *cache, bool *found_in_cache, bson_error_t *error);
50+
51+
// mongoc_oidc_cache_get_cached_token returns a cached token or NULL if none is cached. Thread safe.
52+
char *
53+
mongoc_oidc_cache_get_cached_token(const mongoc_oidc_cache_t *cache);
54+
55+
// mongoc_oidc_cache_set_cached_token overwrites the cached token. Useful for tests. Thread safe.
56+
void
57+
mongoc_oidc_cache_set_cached_token(mongoc_oidc_cache_t *cache, const char *token);
58+
59+
// mongoc_oidc_cache_invalidate_token invalidates the cached token if it matches `token`. Thread safe.
60+
void
61+
mongoc_oidc_cache_invalidate_token(mongoc_oidc_cache_t *cache, const char *token);
62+
63+
void
64+
mongoc_oidc_cache_destroy(mongoc_oidc_cache_t *);
65+
66+
#endif // MONGOC_OIDC_CACHE_PRIVATE_H
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
/*
2+
* Copyright 2009-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include <common-macros-private.h> // MC_DISABLE_CAST_QUAL_WARNING_BEGIN
18+
#include <common-thread-private.h>
19+
#include <mongoc/mongoc-error-private.h>
20+
#include <mongoc/mongoc-oidc-cache-private.h>
21+
#include <mongoc/mongoc-oidc-callback-private.h>
22+
23+
#include <mlib/duration.h>
24+
#include <mlib/time_point.h>
25+
26+
#define SET_ERROR(...) _mongoc_set_error(error, MONGOC_ERROR_CLIENT, MONGOC_ERROR_CLIENT_AUTHENTICATE, __VA_ARGS__)
27+
28+
struct mongoc_oidc_cache_t {
29+
// callback is owned. NULL if unset. Not guarded by lock. Set before requesting tokens.
30+
mongoc_oidc_callback_t *callback;
31+
32+
// usleep_fn is used to sleep between calls to the callback. Not guarded by lock. Set before requesting tokens.
33+
mongoc_usleep_func_t usleep_fn;
34+
void *usleep_data;
35+
36+
// lock is used to prevent concurrent calls to callback. Guards access to token, last_called, and ever_called.
37+
bson_shared_mutex_t lock;
38+
39+
// token is a cached OIDC access token.
40+
char *token;
41+
42+
// last_call tracks the time just after the last call to the callback.
43+
mlib_time_point last_called;
44+
45+
// ever_called is set to true after the first call to the callback.
46+
bool ever_called;
47+
};
48+
49+
mongoc_oidc_cache_t *
50+
mongoc_oidc_cache_new(void)
51+
{
52+
mongoc_oidc_cache_t *oidc = bson_malloc0(sizeof(mongoc_oidc_cache_t));
53+
oidc->usleep_fn = mongoc_usleep_default_impl;
54+
bson_shared_mutex_init(&oidc->lock);
55+
return oidc;
56+
}
57+
58+
void
59+
mongoc_oidc_cache_set_callback(mongoc_oidc_cache_t *cache, const mongoc_oidc_callback_t *cb)
60+
{
61+
BSON_ASSERT_PARAM(cache);
62+
BSON_OPTIONAL_PARAM(cb);
63+
64+
BSON_ASSERT(!cache->ever_called);
65+
66+
if (cache->callback) {
67+
mongoc_oidc_callback_destroy(cache->callback);
68+
}
69+
cache->callback = cb ? mongoc_oidc_callback_copy(cb) : NULL;
70+
}
71+
72+
const mongoc_oidc_callback_t *
73+
mongoc_oidc_cache_get_callback(const mongoc_oidc_cache_t *cache)
74+
{
75+
BSON_ASSERT_PARAM(cache);
76+
77+
return cache->callback;
78+
}
79+
80+
void
81+
mongoc_oidc_cache_set_usleep_fn(mongoc_oidc_cache_t *cache, mongoc_usleep_func_t usleep_fn, void *usleep_data)
82+
{
83+
BSON_ASSERT_PARAM(cache);
84+
BSON_OPTIONAL_PARAM(usleep_fn);
85+
BSON_OPTIONAL_PARAM(usleep_data);
86+
87+
BSON_ASSERT(!cache->ever_called);
88+
89+
cache->usleep_fn = usleep_fn ? usleep_fn : mongoc_usleep_default_impl;
90+
cache->usleep_data = usleep_data;
91+
}
92+
93+
void
94+
mongoc_oidc_cache_destroy(mongoc_oidc_cache_t *cache)
95+
{
96+
if (!cache) {
97+
return;
98+
}
99+
bson_free(cache->token);
100+
bson_shared_mutex_destroy(&cache->lock);
101+
mongoc_oidc_callback_destroy(cache->callback);
102+
bson_free(cache);
103+
}
104+
105+
char *
106+
mongoc_oidc_cache_get_cached_token(const mongoc_oidc_cache_t *cache)
107+
{
108+
BSON_ASSERT_PARAM(cache);
109+
110+
// Cast away const to lock. This function is logically const (read-only).
111+
MC_DISABLE_CAST_QUAL_WARNING_BEGIN
112+
bson_shared_mutex_lock_shared(&((mongoc_oidc_cache_t *)cache)->lock);
113+
char *token = bson_strdup(cache->token);
114+
bson_shared_mutex_unlock_shared(&((mongoc_oidc_cache_t *)cache)->lock);
115+
MC_DISABLE_CAST_QUAL_WARNING_END
116+
return token;
117+
}
118+
119+
void
120+
mongoc_oidc_cache_set_cached_token(mongoc_oidc_cache_t *cache, const char *token)
121+
{
122+
BSON_ASSERT_PARAM(cache);
123+
BSON_OPTIONAL_PARAM(token);
124+
125+
char *old_token;
126+
127+
// Lock to update token:
128+
{
129+
bson_shared_mutex_lock(&cache->lock);
130+
old_token = cache->token;
131+
cache->token = bson_strdup(token);
132+
bson_shared_mutex_unlock(&cache->lock);
133+
}
134+
bson_free(old_token);
135+
}
136+
137+
char *
138+
mongoc_oidc_cache_get_token(mongoc_oidc_cache_t *cache, bool *found_in_cache, bson_error_t *error)
139+
{
140+
BSON_ASSERT_PARAM(cache);
141+
BSON_ASSERT_PARAM(found_in_cache);
142+
BSON_OPTIONAL_PARAM(error);
143+
144+
char *token = NULL;
145+
146+
*found_in_cache = false;
147+
148+
if (!cache->callback) {
149+
SET_ERROR("MONGODB-OIDC requested, but no callback set");
150+
return NULL;
151+
}
152+
153+
token = mongoc_oidc_cache_get_cached_token(cache);
154+
if (NULL != token) {
155+
*found_in_cache = true;
156+
return token;
157+
}
158+
159+
// Prepare to call callback outside of lock:
160+
mongoc_oidc_credential_t *cred = NULL;
161+
mongoc_oidc_callback_params_t *params = mongoc_oidc_callback_params_new();
162+
mongoc_oidc_callback_params_set_user_data(params, mongoc_oidc_callback_get_user_data(cache->callback));
163+
// From spec: "If CSOT is not applied, then the driver MUST use 1 minute as the timeout."
164+
// The timeout parameter (when set) is meant to be directly compared against bson_get_monotonic_time(). It is a
165+
// time point, not a duration.
166+
mongoc_oidc_callback_params_set_timeout(
167+
params, mlib_microseconds_count(mlib_time_add(mlib_now(), mlib_duration(60, s)).time_since_monotonic_start));
168+
169+
// Obtain write-lock:
170+
{
171+
bson_shared_mutex_lock(&cache->lock);
172+
// Check if another thread populated cache between checking cached token and obtaining write lock:
173+
if (cache->token) {
174+
*found_in_cache = true;
175+
token = bson_strdup(cache->token);
176+
goto unlock_and_return;
177+
}
178+
179+
// From spec: "Wait until it has been at least 100ms since the last callback invocation"
180+
if (cache->ever_called) {
181+
mlib_duration since_last_call = mlib_time_difference(mlib_now(), cache->last_called);
182+
if (mlib_duration_cmp(since_last_call, <, (100, ms))) {
183+
mlib_duration to_sleep = mlib_duration((100, ms), minus, since_last_call);
184+
cache->usleep_fn(mlib_microseconds_count(to_sleep), cache->usleep_data);
185+
}
186+
}
187+
188+
// Call callback:
189+
cred = mongoc_oidc_callback_get_fn(cache->callback)(params);
190+
191+
cache->last_called = mlib_now();
192+
cache->ever_called = true;
193+
194+
if (!cred) {
195+
SET_ERROR("MONGODB-OIDC callback failed");
196+
goto unlock_and_return;
197+
}
198+
199+
token = bson_strdup(mongoc_oidc_credential_get_access_token(cred));
200+
cache->token = bson_strdup(token); // Cache a copy.
201+
202+
unlock_and_return:
203+
bson_shared_mutex_unlock(&cache->lock);
204+
}
205+
mongoc_oidc_callback_params_destroy(params);
206+
mongoc_oidc_credential_destroy(cred);
207+
return token;
208+
}
209+
210+
void
211+
mongoc_oidc_cache_invalidate_token(mongoc_oidc_cache_t *cache, const char *token)
212+
{
213+
BSON_ASSERT_PARAM(cache);
214+
BSON_ASSERT_PARAM(token);
215+
216+
char *old_token = NULL;
217+
218+
// Lock to clear token
219+
{
220+
bson_shared_mutex_lock(&cache->lock);
221+
if (cache->token && 0 == strcmp(cache->token, token)) {
222+
old_token = cache->token;
223+
cache->token = NULL;
224+
}
225+
bson_shared_mutex_unlock(&cache->lock);
226+
}
227+
228+
bson_free(old_token);
229+
}

src/libmongoc/src/mongoc/mongoc-oidc-callback.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ struct _mongoc_oidc_callback_t {
2828
struct _mongoc_oidc_callback_params_t {
2929
void *user_data;
3030
char *username;
31-
int64_t timeout; // Guarded by timeout_is_set.
31+
int64_t timeout; // Guarded by timeout_is_set. In microseconds since monotonic clock start.
3232
int32_t version;
3333
bool cancelled_with_timeout;
3434
bool timeout_is_set;

src/libmongoc/tests/test-libmongoc-main.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ main(int argc, char *argv[])
160160
TEST_INSTALL(test_service_gcp_install);
161161
TEST_INSTALL(test_mcd_nsinfo_install);
162162
TEST_INSTALL(test_bulkwrite_install);
163+
TEST_INSTALL(test_mongoc_oidc_install);
163164
TEST_INSTALL(test_mongoc_oidc_callback_install);
164165
TEST_INSTALL(test_secure_channel_install);
165166
TEST_INSTALL(test_stream_tracker_install);

0 commit comments

Comments
 (0)