Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
93312c9
CDRIVER-4489 add `mongoc_oidc_cache_t`
kevinAlbs Sep 17, 2025
a6b14df
remove unnecessary precondition
kevinAlbs Sep 19, 2025
c1b4da4
use `const` in `mongoc_oidc_cache_get_callback`
kevinAlbs Sep 19, 2025
4ee3ef0
rename `is_cache` => `found_in_cache` for clarity
kevinAlbs Sep 19, 2025
05de8a1
make `mongoc_oidc_cache_get_cached_token` logically const
kevinAlbs Sep 19, 2025
86bd7d0
rename `mongoc_oidc_cache_invalidate_cached_token` to `mongoc_oidc_ca…
kevinAlbs Sep 19, 2025
2c3c33e
clarify `mongoc_oidc_cache_invalidate_token` may be called when a tok…
kevinAlbs Sep 19, 2025
f50b55b
use `bson_shared_mutex_t`
kevinAlbs Sep 19, 2025
535c5f2
add asserts in setters
kevinAlbs Sep 19, 2025
23ad9d2
reduce redundant NULL checks
kevinAlbs Sep 19, 2025
0dd4599
use `mlib_now()` for timeout arg
kevinAlbs Sep 24, 2025
69e2492
assert time passed is less than 100ms before second call to callback
kevinAlbs Sep 24, 2025
28141c0
assert contents of tokens
kevinAlbs Sep 24, 2025
4ba7062
define `PLACEHOLDER_TOKEN` macro
kevinAlbs Sep 24, 2025
1b889c1
move assert
kevinAlbs Sep 24, 2025
5f36c0a
isolate test for delay between calls. Expect 90ms delay.
kevinAlbs Sep 24, 2025
9a1b0c5
use less "if" in comment
kevinAlbs Sep 25, 2025
e7d5410
add `MC_DISABLE_CAST_QUAL_WARNING_*` macros and ignore warning
kevinAlbs Sep 25, 2025
e82dfb0
deallocate outside of lock
kevinAlbs Sep 25, 2025
61eab11
prepare callback params and deallocate outside of lock
kevinAlbs Sep 25, 2025
c9013e0
use scope around locks
kevinAlbs Sep 25, 2025
818c433
deallocate outside of lock
kevinAlbs Sep 25, 2025
51adf05
use `(60, s)` to work around CDRIVER-6102
kevinAlbs Sep 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/common/src/common-macros-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,16 @@
#define MC_DISABLE_IMPLICIT_WARNING_END
#endif

// Disable the -Wcast-qual warning
#if defined(__GNUC__)
#define MC_DISABLE_CAST_QUAL_WARNING_BEGIN MC_PRAGMA_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic ignored \"-Wcast-qual\"")
#define MC_DISABLE_CAST_QUAL_WARNING_END MC_PRAGMA_DIAGNOSTIC_POP
#elif defined(__clang__)
#define MC_DISABLE_CAST_QUAL_WARNING_BEGIN MC_PRAGMA_DIAGNOSTIC_PUSH _Pragma("clang diagnostic ignored \"-Wcast-qual\"")
#define MC_DISABLE_CAST_QUAL_WARNING_END MC_PRAGMA_DIAGNOSTIC_POP
#else
#define MC_DISABLE_CAST_QUAL_WARNING_BEGIN
#define MC_DISABLE_CAST_QUAL_WARNING_END
#endif

#endif /* MONGO_C_DRIVER_COMMON_MACROS_PRIVATE_H */
2 changes: 2 additions & 0 deletions src/libmongoc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,7 @@ set (MONGOC_SOURCES
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-log-and-monitor-private.c
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-memcmp.c
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-cmd.c
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-oidc-cache.c
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-oidc-callback.c
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-oidc-env.c
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-opcode.c
Expand Down Expand Up @@ -1092,6 +1093,7 @@ set (test-libmongoc-sources
${PROJECT_SOURCE_DIR}/tests/test-mongoc-max-staleness.c
${PROJECT_SOURCE_DIR}/tests/test-mongoc-mongos-pinning.c
${PROJECT_SOURCE_DIR}/tests/test-mongoc-oidc-callback.c
${PROJECT_SOURCE_DIR}/tests/test-mongoc-oidc-cache.c
${PROJECT_SOURCE_DIR}/tests/test-mongoc-opts.c
${PROJECT_SOURCE_DIR}/tests/test-mongoc-primary-stepdown.c
${PROJECT_SOURCE_DIR}/tests/test-mongoc-queue.c
Expand Down
66 changes: 66 additions & 0 deletions src/libmongoc/src/mongoc/mongoc-oidc-cache-private.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright 2009-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#ifndef MONGOC_OIDC_CACHE_PRIVATE_H
#define MONGOC_OIDC_CACHE_PRIVATE_H

#include <mongoc/mongoc-oidc-callback.h>
#include <mongoc/mongoc-sleep.h>

// mongoc_oidc_cache_t implements the OIDC spec "Client Cache".
// Stores the OIDC callback, cache, and lock.
// Expected to be shared among all clients in a pool.
typedef struct mongoc_oidc_cache_t mongoc_oidc_cache_t;

mongoc_oidc_cache_t *
mongoc_oidc_cache_new(void);

// mongoc_oidc_cache_set_callback sets the token callback.
// Not thread safe. Call before any authentication can occur.
void
mongoc_oidc_cache_set_callback(mongoc_oidc_cache_t *cache, const mongoc_oidc_callback_t *cb);

// mongoc_oidc_cache_get_callback gets the token callback.
const mongoc_oidc_callback_t *
mongoc_oidc_cache_get_callback(const mongoc_oidc_cache_t *cache);

// mongoc_oidc_cache_set_usleep_fn sets a custom sleep function.
// Not thread safe. Call before any authentication can occur.
void
mongoc_oidc_cache_set_usleep_fn(mongoc_oidc_cache_t *cache, mongoc_usleep_func_t usleep_fn, void *usleep_data);

// mongoc_oidc_cache_get_token returns a token or NULL on error. Thread safe.
// Sets *found_in_cache to indicate if the returned token came from the cache or callback.
// Calls sleep if needed to enforce 100ms delay between calls to the callback.
char *
mongoc_oidc_cache_get_token(mongoc_oidc_cache_t *cache, bool *found_in_cache, bson_error_t *error);

// mongoc_oidc_cache_get_cached_token returns a cached token or NULL if none is cached. Thread safe.
char *
mongoc_oidc_cache_get_cached_token(const mongoc_oidc_cache_t *cache);

// mongoc_oidc_cache_set_cached_token overwrites the cached token. Useful for tests. Thread safe.
void
mongoc_oidc_cache_set_cached_token(mongoc_oidc_cache_t *cache, const char *token);

// mongoc_oidc_cache_invalidate_token invalidates the cached token if it matches `token`. Thread safe.
void
mongoc_oidc_cache_invalidate_token(mongoc_oidc_cache_t *cache, const char *token);

void
mongoc_oidc_cache_destroy(mongoc_oidc_cache_t *);

#endif // MONGOC_OIDC_CACHE_PRIVATE_H
229 changes: 229 additions & 0 deletions src/libmongoc/src/mongoc/mongoc-oidc-cache.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
/*
* Copyright 2009-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include <common-macros-private.h> // MC_DISABLE_CAST_QUAL_WARNING_BEGIN
#include <common-thread-private.h>
#include <mongoc/mongoc-error-private.h>
#include <mongoc/mongoc-oidc-cache-private.h>
#include <mongoc/mongoc-oidc-callback-private.h>

#include <mlib/duration.h>
#include <mlib/time_point.h>

#define SET_ERROR(...) _mongoc_set_error(error, MONGOC_ERROR_CLIENT, MONGOC_ERROR_CLIENT_AUTHENTICATE, __VA_ARGS__)

struct mongoc_oidc_cache_t {
// callback is owned. NULL if unset. Not guarded by lock. Set before requesting tokens.
mongoc_oidc_callback_t *callback;

// usleep_fn is used to sleep between calls to the callback. Not guarded by lock. Set before requesting tokens.
mongoc_usleep_func_t usleep_fn;
void *usleep_data;

// lock is used to prevent concurrent calls to callback. Guards access to token, last_called, and ever_called.
bson_shared_mutex_t lock;

// token is a cached OIDC access token.
char *token;

// last_call tracks the time just after the last call to the callback.
mlib_time_point last_called;

// ever_called is set to true after the first call to the callback.
bool ever_called;
};

mongoc_oidc_cache_t *
mongoc_oidc_cache_new(void)
{
mongoc_oidc_cache_t *oidc = bson_malloc0(sizeof(mongoc_oidc_cache_t));
oidc->usleep_fn = mongoc_usleep_default_impl;
bson_shared_mutex_init(&oidc->lock);
return oidc;
}

void
mongoc_oidc_cache_set_callback(mongoc_oidc_cache_t *cache, const mongoc_oidc_callback_t *cb)
{
BSON_ASSERT_PARAM(cache);
BSON_OPTIONAL_PARAM(cb);

BSON_ASSERT(!cache->ever_called);

if (cache->callback) {
mongoc_oidc_callback_destroy(cache->callback);
}
cache->callback = cb ? mongoc_oidc_callback_copy(cb) : NULL;
}

const mongoc_oidc_callback_t *
mongoc_oidc_cache_get_callback(const mongoc_oidc_cache_t *cache)
{
BSON_ASSERT_PARAM(cache);

return cache->callback;
}

void
mongoc_oidc_cache_set_usleep_fn(mongoc_oidc_cache_t *cache, mongoc_usleep_func_t usleep_fn, void *usleep_data)
{
BSON_ASSERT_PARAM(cache);
BSON_OPTIONAL_PARAM(usleep_fn);
BSON_OPTIONAL_PARAM(usleep_data);

BSON_ASSERT(!cache->ever_called);

cache->usleep_fn = usleep_fn ? usleep_fn : mongoc_usleep_default_impl;
cache->usleep_data = usleep_data;
}

void
mongoc_oidc_cache_destroy(mongoc_oidc_cache_t *cache)
{
if (!cache) {
return;
}
bson_free(cache->token);
bson_shared_mutex_destroy(&cache->lock);
mongoc_oidc_callback_destroy(cache->callback);
bson_free(cache);
}

char *
mongoc_oidc_cache_get_cached_token(const mongoc_oidc_cache_t *cache)
{
BSON_ASSERT_PARAM(cache);

// Cast away const to lock. This function is logically const (read-only).
MC_DISABLE_CAST_QUAL_WARNING_BEGIN
bson_shared_mutex_lock_shared(&((mongoc_oidc_cache_t *)cache)->lock);
char *token = bson_strdup(cache->token);
bson_shared_mutex_unlock_shared(&((mongoc_oidc_cache_t *)cache)->lock);
MC_DISABLE_CAST_QUAL_WARNING_END
return token;
}

void
mongoc_oidc_cache_set_cached_token(mongoc_oidc_cache_t *cache, const char *token)
{
BSON_ASSERT_PARAM(cache);
BSON_OPTIONAL_PARAM(token);

char *old_token;

// Lock to update token:
{
bson_shared_mutex_lock(&cache->lock);
old_token = cache->token;
cache->token = bson_strdup(token);
bson_shared_mutex_unlock(&cache->lock);
}
bson_free(old_token);
}

char *
mongoc_oidc_cache_get_token(mongoc_oidc_cache_t *cache, bool *found_in_cache, bson_error_t *error)
{
BSON_ASSERT_PARAM(cache);
BSON_ASSERT_PARAM(found_in_cache);
BSON_OPTIONAL_PARAM(error);

char *token = NULL;

*found_in_cache = false;

if (!cache->callback) {
SET_ERROR("MONGODB-OIDC requested, but no callback set");
return NULL;
}

token = mongoc_oidc_cache_get_cached_token(cache);
if (NULL != token) {
*found_in_cache = true;
return token;
}

// Prepare to call callback outside of lock:
mongoc_oidc_credential_t *cred = NULL;
mongoc_oidc_callback_params_t *params = mongoc_oidc_callback_params_new();
mongoc_oidc_callback_params_set_user_data(params, mongoc_oidc_callback_get_user_data(cache->callback));
// From spec: "If CSOT is not applied, then the driver MUST use 1 minute as the timeout."
// The timeout parameter (when set) is meant to be directly compared against bson_get_monotonic_time(). It is a
// time point, not a duration.
mongoc_oidc_callback_params_set_timeout(
params, mlib_microseconds_count(mlib_time_add(mlib_now(), mlib_duration(60, s)).time_since_monotonic_start));

// Obtain write-lock:
{
bson_shared_mutex_lock(&cache->lock);
// Check if another thread populated cache between checking cached token and obtaining write lock:
if (cache->token) {
*found_in_cache = true;
token = bson_strdup(cache->token);
goto unlock_and_return;
}

// From spec: "Wait until it has been at least 100ms since the last callback invocation"
if (cache->ever_called) {
mlib_duration since_last_call = mlib_time_difference(mlib_now(), cache->last_called);
if (mlib_duration_cmp(since_last_call, <, (100, ms))) {
mlib_duration to_sleep = mlib_duration((100, ms), minus, since_last_call);
cache->usleep_fn(mlib_microseconds_count(to_sleep), cache->usleep_data);
}
}

// Call callback:
cred = mongoc_oidc_callback_get_fn(cache->callback)(params);

cache->last_called = mlib_now();
cache->ever_called = true;

if (!cred) {
SET_ERROR("MONGODB-OIDC callback failed");
goto unlock_and_return;
}

token = bson_strdup(mongoc_oidc_credential_get_access_token(cred));
cache->token = bson_strdup(token); // Cache a copy.

unlock_and_return:
bson_shared_mutex_unlock(&cache->lock);
}
mongoc_oidc_callback_params_destroy(params);
mongoc_oidc_credential_destroy(cred);
return token;
}

void
mongoc_oidc_cache_invalidate_token(mongoc_oidc_cache_t *cache, const char *token)
{
BSON_ASSERT_PARAM(cache);
BSON_ASSERT_PARAM(token);

char *old_token = NULL;

// Lock to clear token
{
bson_shared_mutex_lock(&cache->lock);
if (cache->token && 0 == strcmp(cache->token, token)) {
old_token = cache->token;
cache->token = NULL;
}
bson_shared_mutex_unlock(&cache->lock);
}

bson_free(old_token);
}
2 changes: 1 addition & 1 deletion src/libmongoc/src/mongoc/mongoc-oidc-callback.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ struct _mongoc_oidc_callback_t {
struct _mongoc_oidc_callback_params_t {
void *user_data;
char *username;
int64_t timeout; // Guarded by timeout_is_set.
int64_t timeout; // Guarded by timeout_is_set. In microseconds since monotonic clock start.
int32_t version;
bool cancelled_with_timeout;
bool timeout_is_set;
Expand Down
1 change: 1 addition & 0 deletions src/libmongoc/tests/test-libmongoc-main.c
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ main(int argc, char *argv[])
TEST_INSTALL(test_service_gcp_install);
TEST_INSTALL(test_mcd_nsinfo_install);
TEST_INSTALL(test_bulkwrite_install);
TEST_INSTALL(test_mongoc_oidc_install);
TEST_INSTALL(test_mongoc_oidc_callback_install);
TEST_INSTALL(test_secure_channel_install);
TEST_INSTALL(test_stream_tracker_install);
Expand Down
Loading