diff --git a/.evergreen/config_generator/components/oidc.py b/.evergreen/config_generator/components/oidc.py index 5cc7a74704..bc58934ea4 100644 --- a/.evergreen/config_generator/components/oidc.py +++ b/.evergreen/config_generator/components/oidc.py @@ -35,6 +35,30 @@ def task_groups(): script='./drivers-evergreen-tools/.evergreen/auth_oidc/teardown.sh', ) ], + ), + EvgTaskGroup( + name='test-oidc-azure-task-group', + tasks=['oidc-azure-auth-test-task'], + setup_group_can_fail_task=True, + setup_group_timeout_secs=60 * 60, # 1 hour + teardown_group_can_fail_task=True, + teardown_group_timeout_secs=180, # 3 minutes + setup_group=[ + FetchDET.call(), + ec2_assume_role(role_arn='${aws_test_secrets_role}'), + bash_exec( + command_type=EvgCommandType.SETUP, + include_expansions_in_env=['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_SESSION_TOKEN'], + env={"AZUREOIDC_VMNAME_PREFIX": "CDRIVER"}, + script='./drivers-evergreen-tools/.evergreen/auth_oidc/azure/create-and-setup-vm.sh', + ), + ], + teardown_group=[ + bash_exec( + command_type=EvgCommandType.SETUP, + script='./drivers-evergreen-tools/.evergreen/auth_oidc/azure/delete-vm.sh', + ) + ], ) ] @@ -59,6 +83,29 @@ def tasks(): ), RunTests.call(), ], + ), + EvgTask( + name='oidc-azure-auth-test-task', + run_on=['debian11-small'], # TODO: switch to 'debian11-latest' after DEVPROD-23011 fixed. + commands=[ + FetchSource.call(), + bash_exec( + working_dir="mongoc", + add_expansions_to_env=True, + command_type=EvgCommandType.TEST, + script='.evergreen/scripts/oidc-azure-compile.sh', + ), + expansions_update(file="mongoc/oidc-remote-test-expansion.yml"), + bash_exec( + add_expansions_to_env=True, + command_type=EvgCommandType.TEST, + env={ + "AZUREOIDC_DRIVERS_TAR_FILE": "${OIDC_TEST_TARBALL}", + "AZUREOIDC_TEST_CMD": "source ./env.sh && ./.evergreen/scripts/oidc-azure-test.sh" + }, + script='./drivers-evergreen-tools/.evergreen/auth_oidc/azure/run-driver-test.sh', + ), + ], ) ] @@ -68,7 +115,6 @@ def variants(): BuildVariant( name='oidc', display_name='OIDC', - run_on=[find_small_distro('ubuntu2404').name], - tasks=[EvgTaskRef(name='test-oidc-task-group')], + tasks=[EvgTaskRef(name='test-oidc-task-group'), EvgTaskRef(name='test-oidc-azure-task-group')], ), ] diff --git a/.evergreen/generated_configs/task_groups.yml b/.evergreen/generated_configs/task_groups.yml index eb5085e801..466dcb6671 100644 --- a/.evergreen/generated_configs/task_groups.yml +++ b/.evergreen/generated_configs/task_groups.yml @@ -1,4 +1,36 @@ task_groups: + - name: test-oidc-azure-task-group + setup_group: + - func: fetch-det + - command: ec2.assume_role + params: + role_arn: ${aws_test_secrets_role} + - command: subprocess.exec + type: setup + params: + binary: bash + env: + AZUREOIDC_VMNAME_PREFIX: CDRIVER + include_expansions_in_env: + - AWS_ACCESS_KEY_ID + - AWS_SECRET_ACCESS_KEY + - AWS_SESSION_TOKEN + args: + - -c + - ./drivers-evergreen-tools/.evergreen/auth_oidc/azure/create-and-setup-vm.sh + setup_group_can_fail_task: true + setup_group_timeout_secs: 3600 + tasks: + - oidc-azure-auth-test-task + teardown_group: + - command: subprocess.exec + type: setup + params: + binary: bash + args: + - -c + - ./drivers-evergreen-tools/.evergreen/auth_oidc/azure/delete-vm.sh + teardown_group_timeout_secs: 180 - name: test-oidc-task-group setup_group: - func: fetch-det diff --git a/.evergreen/generated_configs/tasks.yml b/.evergreen/generated_configs/tasks.yml index e368108ddc..8cede36f32 100644 --- a/.evergreen/generated_configs/tasks.yml +++ b/.evergreen/generated_configs/tasks.yml @@ -4221,6 +4221,34 @@ tasks: - { key: MONGODB_VERSION, value: latest } - { key: TOPOLOGY, value: replica_set } - func: run-tests + - name: oidc-azure-auth-test-task + run_on: + - debian11-small + commands: + - func: fetch-source + - command: subprocess.exec + type: test + params: + binary: bash + working_dir: mongoc + add_expansions_to_env: true + args: + - -c + - .evergreen/scripts/oidc-azure-compile.sh + - command: expansions.update + params: + file: mongoc/oidc-remote-test-expansion.yml + - command: subprocess.exec + type: test + params: + binary: bash + add_expansions_to_env: true + env: + AZUREOIDC_DRIVERS_TAR_FILE: ${OIDC_TEST_TARBALL} + AZUREOIDC_TEST_CMD: source ./env.sh && ./.evergreen/scripts/oidc-azure-test.sh + args: + - -c + - ./drivers-evergreen-tools/.evergreen/auth_oidc/azure/run-driver-test.sh - name: openssl-compat-1.0.2-shared-ubuntu2404-gcc run_on: ubuntu2404-large tags: [openssl-compat, openssl-1.0.2, openssl-shared, ubuntu2404, gcc] diff --git a/.evergreen/generated_configs/variants.yml b/.evergreen/generated_configs/variants.yml index cc35dc5664..1ca08ad91d 100644 --- a/.evergreen/generated_configs/variants.yml +++ b/.evergreen/generated_configs/variants.yml @@ -255,10 +255,9 @@ buildvariants: - name: mock-server-test - name: oidc display_name: OIDC - run_on: - - ubuntu2404-small tasks: - name: test-oidc-task-group + - name: test-oidc-azure-task-group - name: openssl-compat-matrix display_name: OpenSSL Compatibility Matrix tasks: diff --git a/.evergreen/scripts/oidc-azure-compile.sh b/.evergreen/scripts/oidc-azure-compile.sh new file mode 100755 index 0000000000..21282ff118 --- /dev/null +++ b/.evergreen/scripts/oidc-azure-compile.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +set -o errexit +set -o pipefail +set -o nounset + +# shellcheck source=.evergreen/scripts/use-tools.sh +. "$(dirname "${BASH_SOURCE[0]}")/use-tools.sh" paths # Sets MONGOC_DIR + +cd "$MONGOC_DIR" + +if [[ "${distro_id:?}" == "debian11-small" ]]; then + # Temporary workaround for lack of uv on `debian11`. TODO: remove after DEVPROD-23011 is resolved. + uv_dir="$(mktemp -d)" + python3 -m virtualenv "${uv_dir:?}" + # shellcheck source=/dev/null + (. "${uv_dir:?}/bin/activate" && python -m pip install uv) + PATH="${uv_dir:?}/bin:${PATH:-}" + command -V uv >/dev/null +fi + +. .evergreen/scripts/install-build-tools.sh +install_build_tools +export CMAKE_GENERATOR="Ninja" + +# Use ccache if able. +. .evergreen/scripts/find-ccache.sh +find_ccache_and_export_vars "$(pwd)" || true + +echo "Compile test-libmongoc ... begin" +# Disable unnecessary dependencies. test-libmongoc is copied to a remote host for testing, which may not have all dependent libraries. +cmake \ + -DENABLE_SASL=OFF \ + -DENABLE_SNAPPY=OFF \ + -DENABLE_ZSTD=OFF \ + -DENABLE_ZLIB=OFF \ + -DENABLE_SRV=OFF \ + -DENABLE_CLIENT_SIDE_ENCRYPTION=OFF \ + -DENABLE_EXAMPLES=OFF \ + -DENABLE_SRV=OFF \ + -S. -Bcmake-build +cmake --build cmake-build --target test-libmongoc --parallel +echo "Compile test-libmongoc ... end" + +# Create tarball for remote testing. +echo "Creating test-libmongoc tarball ... begin" + +# Copy test binary and JSON test files. All JSON test files are needed to start test-libmongoc. +tar -czf test-libmongoc.tar.gz \ + .evergreen/scripts/oidc-azure-test.sh \ + ./cmake-build/src/libmongoc/test-libmongoc \ + src/libmongoc/tests/json \ + src/libbson/tests/json +echo "Creating test-libmongoc tarball ... end" + +cat < oidc-remote-test-expansion.yml +OIDC_TEST_TARBALL: ${MONGOC_DIR}/test-libmongoc.tar.gz +EOT diff --git a/.evergreen/scripts/oidc-azure-test.sh b/.evergreen/scripts/oidc-azure-test.sh new file mode 100644 index 0000000000..71249ed148 --- /dev/null +++ b/.evergreen/scripts/oidc-azure-test.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -o errexit +set -o pipefail +set -o nounset + +export MONGOC_TEST_OIDC="ON" +export MONGOC_TEST_USER="$OIDC_ADMIN_USER" +export MONGOC_TEST_PASSWORD="$OIDC_ADMIN_PWD" +export MONGOC_AZURE_RESOURCE="$AZUREOIDC_RESOURCE" + +# Install required OpenSSL runtime library. +sudo apt install -y libssl-dev + +./cmake-build/src/libmongoc/test-libmongoc -d --match '/auth/unified/*' --match '/oidc/*' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a75dcdaa65..e3a3f310de 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -321,6 +321,7 @@ To run test cases with large allocations, set: * `MONGOC_TEST_LARGE_ALLOCATIONS=on` This may result in sudden test suite termination due to allocation failure. Use with caution. * `MONGOC_TEST_OIDC=on` to test OIDC using a test environment described [here](https://github.com/mongodb-labs/drivers-evergreen-tools/tree/d7a7337b384392a09fbe7fc80a7244e6f1226c18/.evergreen/auth_oidc). +* `MONGOC_AZURE_RESOURCE=` to test OIDC using an Azure test environment described [here](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/d7a7337b384392a09fbe7fc80a7244e6f1226c18/.evergreen/auth_oidc/azure/README.md). All tests should pass before submitting a patch. diff --git a/src/libmongoc/src/mongoc/mcd-azure.c b/src/libmongoc/src/mongoc/mcd-azure.c index 18730311db..a9c04cd4d1 100644 --- a/src/libmongoc/src/mongoc/mcd-azure.c +++ b/src/libmongoc/src/mongoc/mcd-azure.c @@ -23,17 +23,23 @@ #define AZURE_API_VERSION "2018-02-01" -static const char *const DEFAULT_METADATA_PATH = - "/metadata/identity/oauth2/" - "token?api-version=" AZURE_API_VERSION "&resource=https%3A%2F%2Fvault.azure.net"; +static const char *const DEFAULT_METADATA_PATH = "/metadata/identity/oauth2/token?api-version=" AZURE_API_VERSION; -void +bool mcd_azure_imds_request_init(mcd_azure_imds_request *req, + const char *token_resource, const char *const opt_imds_host, int opt_port, - const char *const opt_extra_headers) + const char *const opt_extra_headers, + const char *const opt_client_id) { BSON_ASSERT_PARAM(req); + BSON_ASSERT_PARAM(token_resource); + + bool ok = false; + char *encoded_token_resource = NULL; + mcommon_string_append_t path = {0}; + _mongoc_http_request_init(&req->req); // The HTTP host of the IMDS server req->req.host = req->_owned_host = bson_strdup(opt_imds_host ? opt_imds_host : "169.254.169.254"); @@ -50,9 +56,33 @@ mcd_azure_imds_request_init(mcd_azure_imds_request *req, req->req.extra_headers = req->_owned_headers = bson_strdup_printf("Metadata: true\r\n" "Accept: application/json\r\n%s", opt_extra_headers ? opt_extra_headers : ""); - // The default path is suitable. In the future, we may want to add query - // parameters to disambiguate a managed identity. - req->req.path = req->_owned_path = bson_strdup(DEFAULT_METADATA_PATH); + // Build the path with query parameters. + encoded_token_resource = mongoc_percent_encode(token_resource); + if (!encoded_token_resource) { + goto fail; + } + + mcommon_string_new_as_append(&path); + + if (!mcommon_string_append(&path, DEFAULT_METADATA_PATH) || + !mcommon_string_append_printf(&path, "&resource=%s", encoded_token_resource)) { + goto fail; + } + + if (opt_client_id) { + if (!mcommon_string_append_printf(&path, "&client_id=%s", opt_client_id)) { + goto fail; + } + } + + req->req.path = req->_owned_path = mcommon_string_from_append_destroy_with_steal(&path); + path = (mcommon_string_append_t){0}; + + ok = true; +fail: + bson_free(encoded_token_resource); + mcommon_string_from_append_destroy(&path); + return ok; } void @@ -156,11 +186,15 @@ mcd_azure_access_token_destroy(mcd_azure_access_token *c) bool mcd_azure_access_token_from_imds(mcd_azure_access_token *const out, + const char *token_resource, const char *const opt_imds_host, int opt_port, const char *opt_extra_headers, + int opt_timeout_ms, + const char *opt_client_id, bson_error_t *error) { + BSON_ASSERT_PARAM(token_resource); BSON_ASSERT_PARAM(out); bool okay = false; @@ -172,9 +206,17 @@ mcd_azure_access_token_from_imds(mcd_azure_access_token *const out, _mongoc_http_response_init(&resp); mcd_azure_imds_request req = MCD_AZURE_IMDS_REQUEST_INIT; - mcd_azure_imds_request_init(&req, opt_imds_host, opt_port, opt_extra_headers); + if (!mcd_azure_imds_request_init(&req, token_resource, opt_imds_host, opt_port, opt_extra_headers, opt_client_id)) { + _mongoc_set_error(error, MONGOC_ERROR_AZURE, MONGOC_ERROR_KMS_SERVER_HTTP, "Failed to initialize request"); + goto fail; + } + + int timeout_ms = 3 * 1000; // Default 3 second timeout + if (opt_timeout_ms > 0) { + timeout_ms = opt_timeout_ms; + } - if (!_mongoc_http_send(&req.req, 3 * 1000, false, NULL, &resp, error)) { + if (!_mongoc_http_send(&req.req, timeout_ms, false, NULL, &resp, error)) { goto fail; } diff --git a/src/libmongoc/src/mongoc/mcd-azure.h b/src/libmongoc/src/mongoc/mcd-azure.h index 5db11d7282..1bfcf874b5 100644 --- a/src/libmongoc/src/mongoc/mcd-azure.h +++ b/src/libmongoc/src/mongoc/mcd-azure.h @@ -24,6 +24,8 @@ #include #include +#define MCD_TOKEN_RESOURCE_VAULT "https://vault.azure.net" + /** * @brief An Azure OAuth2 access token obtained from the Azure API */ @@ -88,18 +90,24 @@ typedef struct mcd_azure_imds_request { * @brief Initialize a new IMDS HTTP request * * @param out The object to initialize + * @param token_resource Percent encoded and passed as the "resource" query parameter. * @param opt_imds_host (Optional) the IP host of the IMDS server * @param opt_port (Optional) The port of the IMDS HTTP server (default is 80) * @param opt_extra_headers (Optional) Set extra HTTP headers for the request + * @param opt_client_id (Optional) Added as the "client_id" query parameter. + * + * @note the request must later be destroyed with mcd_azure_imds_request_destroy, even on error. * - * @note the request must later be destroyed with mcd_azure_imds_request_destroy - * @note Currently only supports the vault.azure.net resource + * @retval true Upon success. + * @retval false Otherwise. */ -void +bool mcd_azure_imds_request_init(mcd_azure_imds_request *req, + const char *token_resource, const char *const opt_imds_host, int opt_port, - const char *const opt_extra_headers); + const char *const opt_extra_headers, + const char *opt_client_id); /** * @brief Destroy an IMDS request created with mcd_azure_imds_request_init() @@ -115,20 +123,25 @@ mcd_azure_imds_request_destroy(mcd_azure_imds_request *req); * * @param out The output parameter for the obtained token. Must later be * destroyed + * @param token_resource Percent encoded and passed as the "resource" query parameter. * @param opt_imds_host (Optional) Override the IP host of the IMDS server * @param opt_port (Optional) The port of the IMDS HTTP server (default is 80) * @param opt_extra_headers (Optional) Set extra HTTP headers for the request + * @param opt_timeout_ms (Optional) The timeout for the request in milliseconds + * @param opt_client_id (Optional) Added as the "client_id" query parameter. * @param error Output parameter for errors * @retval true Upon success * @retval false Otherwise. Sets an error via `error` * - * @note Currently only supports the vault.azure.net resource */ bool mcd_azure_access_token_from_imds(mcd_azure_access_token *const out, + const char *token_resource, const char *const opt_imds_host, int opt_port, const char *opt_extra_headers, + int opt_timeout_ms, + const char *opt_client_id, bson_error_t *error); #endif // MCD_AZURE_H_INCLUDED diff --git a/src/libmongoc/src/mongoc/mcd-time.h b/src/libmongoc/src/mongoc/mcd-time.h index 1385bdcddf..8beeb0536f 100644 --- a/src/libmongoc/src/mongoc/mcd-time.h +++ b/src/libmongoc/src/mongoc/mcd-time.h @@ -163,6 +163,18 @@ mcd_get_milliseconds(mcd_duration d) return d._rep / 1000; } +/** + * @brief Obtain the count of full microseconds encoded in the given duration + * + * @param d An abstract duration + * @return int64_t The number of microseconds in 'd' + */ +static BSON_INLINE int64_t +mcd_get_microseconds(mcd_duration d) +{ + return d._rep; +} + /** * @brief Obtain a point-in-time relative to a base time offset by the given * duration (which may be negative). diff --git a/src/libmongoc/src/mongoc/mongoc-client-pool.c b/src/libmongoc/src/mongoc/mongoc-client-pool.c index ff859e574b..e200e31662 100644 --- a/src/libmongoc/src/mongoc/mongoc-client-pool.c +++ b/src/libmongoc/src/mongoc/mongoc-client-pool.c @@ -697,7 +697,7 @@ mongoc_client_pool_set_oidc_callback(mongoc_client_pool_t *pool, const mongoc_oi BSON_ASSERT_PARAM(pool); BSON_ASSERT_PARAM(callback); - if (mongoc_oidc_cache_get_callback(pool->topology->oidc_cache)) { + if (mongoc_oidc_cache_has_user_callback(pool->topology->oidc_cache)) { MONGOC_ERROR("mongoc_client_pool_set_oidc_callback can only be called once per pool"); return false; } @@ -707,6 +707,6 @@ mongoc_client_pool_set_oidc_callback(mongoc_client_pool_t *pool, const mongoc_oi return false; } - mongoc_oidc_cache_set_callback(pool->topology->oidc_cache, callback); + mongoc_oidc_cache_set_user_callback(pool->topology->oidc_cache, callback); return true; } diff --git a/src/libmongoc/src/mongoc/mongoc-client.c b/src/libmongoc/src/mongoc/mongoc-client.c index 4e8d94f952..ff38d16b79 100644 --- a/src/libmongoc/src/mongoc/mongoc-client.c +++ b/src/libmongoc/src/mongoc/mongoc-client.c @@ -2731,7 +2731,7 @@ mongoc_client_set_oidc_callback(mongoc_client_t *client, const mongoc_oidc_callb BSON_ASSERT_PARAM(client); BSON_ASSERT_PARAM(callback); - if (mongoc_oidc_cache_get_callback(client->topology->oidc_cache)) { + if (mongoc_oidc_cache_has_user_callback(client->topology->oidc_cache)) { MONGOC_ERROR("mongoc_client_set_oidc_callback can only be called once per client"); return false; } @@ -2742,6 +2742,6 @@ mongoc_client_set_oidc_callback(mongoc_client_t *client, const mongoc_oidc_callb return false; } - mongoc_oidc_cache_set_callback(client->topology->oidc_cache, callback); + mongoc_oidc_cache_set_user_callback(client->topology->oidc_cache, callback); return true; } diff --git a/src/libmongoc/src/mongoc/mongoc-cluster-oidc.c b/src/libmongoc/src/mongoc/mongoc-cluster-oidc.c index 62c49489f8..dd2c753797 100644 --- a/src/libmongoc/src/mongoc/mongoc-cluster-oidc.c +++ b/src/libmongoc/src/mongoc/mongoc-cluster-oidc.c @@ -150,16 +150,6 @@ _mongoc_cluster_auth_node_oidc(mongoc_cluster_t *cluster, bool ok = false; char *access_token = NULL; - // From spec: "If both ENVIRONMENT and an OIDC Callback [...] are provided the driver MUST raise an error." - bson_t authMechanismProperties = BSON_INITIALIZER; - mongoc_uri_get_mechanism_properties(cluster->client->uri, &authMechanismProperties); - if (mongoc_oidc_cache_get_callback(cluster->client->topology->oidc_cache) && - bson_has_field(&authMechanismProperties, "ENVIRONMENT")) { - SET_ERROR("MONGODB-OIDC requested with both ENVIRONMENT and an OIDC Callback. Use one or the other."); - goto done; - } - - bool is_cache = false; access_token = mongoc_oidc_cache_get_token(cluster->client->topology->oidc_cache, &is_cache, error); if (!access_token) { diff --git a/src/libmongoc/src/mongoc/mongoc-crypt.c b/src/libmongoc/src/mongoc/mongoc-crypt.c index 183badb41e..3517275ad1 100644 --- a/src/libmongoc/src/mongoc/mongoc-crypt.c +++ b/src/libmongoc/src/mongoc/mongoc-crypt.c @@ -815,9 +815,12 @@ static bool _request_new_azure_token(mcd_azure_access_token *out, bson_error_t *error) { return mcd_azure_access_token_from_imds(out, + MCD_TOKEN_RESOURCE_VAULT, NULL, // Use the default host 0, // Default port as well NULL, // No extra headers + 0, // Default timeout. + NULL, // No client ID. error); } diff --git a/src/libmongoc/src/mongoc/mongoc-oidc-cache-private.h b/src/libmongoc/src/mongoc/mongoc-oidc-cache-private.h index 0f3ae5745e..859119ecf7 100644 --- a/src/libmongoc/src/mongoc/mongoc-oidc-cache-private.h +++ b/src/libmongoc/src/mongoc/mongoc-oidc-cache-private.h @@ -20,6 +20,8 @@ #include #include +struct _mongoc_uri_t; // Forward declaration. + // 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. @@ -28,14 +30,19 @@ 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. +// mongoc_oidc_cache_apply_env_from_uri tries to set a callback if the URI includes an ENVIRONMENT. +// Assumes `uri` was already validated with a call to `mongoc_uri_finalize_auth`. +void +mongoc_oidc_cache_apply_env_from_uri(mongoc_oidc_cache_t *cache, const struct _mongoc_uri_t *uri); + +// mongoc_oidc_cache_set_user_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_set_user_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_has_user_callback returns true if a custom callback was set. +bool +mongoc_oidc_cache_has_user_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. diff --git a/src/libmongoc/src/mongoc/mongoc-oidc-cache.c b/src/libmongoc/src/mongoc/mongoc-oidc-cache.c index 96ece4e264..9b9ddc3c42 100644 --- a/src/libmongoc/src/mongoc/mongoc-oidc-cache.c +++ b/src/libmongoc/src/mongoc/mongoc-oidc-cache.c @@ -14,11 +14,15 @@ * limitations under the License. */ +#include #include // MC_DISABLE_CAST_QUAL_WARNING_BEGIN #include #include #include #include +#include + +#include #include #include @@ -26,8 +30,13 @@ #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; + // user_callback is owned. NULL if unset. Not guarded by lock. Set before requesting tokens. + // If both user_callback and env_callback are set, an error occurs when requesting a token. + mongoc_oidc_callback_t *user_callback; + + // env_callback is owned. NULL if unset. Not guarded by lock. Set before requesting tokens. + // If both user_callback and env_callback are set, an error occurs when requesting a token. + mongoc_oidc_env_callback_t *env_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; @@ -56,25 +65,67 @@ mongoc_oidc_cache_new(void) } void -mongoc_oidc_cache_set_callback(mongoc_oidc_cache_t *cache, const mongoc_oidc_callback_t *cb) +mongoc_oidc_cache_apply_env_from_uri(mongoc_oidc_cache_t *cache, const mongoc_uri_t *uri) +{ + BSON_ASSERT_PARAM(cache); + BSON_ASSERT_PARAM(uri); + + const char *mechanism = mongoc_uri_get_auth_mechanism(uri); + if (!mechanism || 0 != strcmp(mechanism, "MONGODB-OIDC")) { + // Not using OIDC. + return; + } + + const char *username = mongoc_uri_get_username(uri); + bson_t mechanism_properties; + if (!mongoc_uri_get_mechanism_properties(uri, &mechanism_properties)) { + // Not configured with OIDC environment. + return; + } + + bson_iter_t iter; + const char *environment = NULL; + if (bson_iter_init_find(&iter, &mechanism_properties, "ENVIRONMENT") && BSON_ITER_HOLDS_UTF8(&iter)) { + environment = bson_iter_utf8(&iter, NULL); + } + + const mongoc_oidc_env_t *env = mongoc_oidc_env_find(environment); + BSON_ASSERT(env); // Checked in mongoc_uri_finalize_auth. + BSON_ASSERT(!(username && !mongoc_oidc_env_supports_username(env))); // Checked in mongoc_uri_finalize_auth. + + const char *token_resource = NULL; + if (bson_iter_init_find(&iter, &mechanism_properties, "TOKEN_RESOURCE") && BSON_ITER_HOLDS_UTF8(&iter)) { + BSON_ASSERT(BSON_ITER_HOLDS_UTF8(&iter)); + token_resource = bson_iter_utf8(&iter, NULL); // Checked in mongoc_uri_finalize_auth. + } + + BSON_ASSERT((token_resource != NULL) == + mongoc_oidc_env_requires_token_resource(env)); // Checked in mongoc_uri_finalize_auth. + + BSON_ASSERT(!cache->env_callback); // Not set yet. + cache->env_callback = mongoc_oidc_env_callback_new(env, token_resource, username); +} + +void +mongoc_oidc_cache_set_user_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); + if (cache->user_callback) { + mongoc_oidc_callback_destroy(cache->user_callback); } - cache->callback = cb ? mongoc_oidc_callback_copy(cb) : NULL; + cache->user_callback = cb ? mongoc_oidc_callback_copy(cb) : NULL; } -const mongoc_oidc_callback_t * -mongoc_oidc_cache_get_callback(const mongoc_oidc_cache_t *cache) +bool +mongoc_oidc_cache_has_user_callback(const mongoc_oidc_cache_t *cache) { BSON_ASSERT_PARAM(cache); - return cache->callback; + return cache->user_callback; } void @@ -98,7 +149,8 @@ mongoc_oidc_cache_destroy(mongoc_oidc_cache_t *cache) } bson_free(cache->token); bson_shared_mutex_destroy(&cache->lock); - mongoc_oidc_callback_destroy(cache->callback); + mongoc_oidc_callback_destroy(cache->user_callback); + mongoc_oidc_env_callback_destroy(cache->env_callback); bson_free(cache); } @@ -145,11 +197,20 @@ mongoc_oidc_cache_get_token(mongoc_oidc_cache_t *cache, bool *found_in_cache, bs *found_in_cache = false; - if (!cache->callback) { + if (!cache->user_callback && !cache->env_callback) { SET_ERROR("MONGODB-OIDC requested, but no callback set"); return NULL; } + // From spec: "If both ENVIRONMENT and an OIDC Callback [...] are provided the driver MUST raise an error." + if (cache->user_callback && cache->env_callback) { + SET_ERROR("MONGODB-OIDC requested with both ENVIRONMENT and an OIDC Callback. Use one or the other."); + return NULL; + } + + const mongoc_oidc_callback_t *callback = + cache->user_callback ? cache->user_callback : mongoc_oidc_env_callback_inner(cache->env_callback); + token = mongoc_oidc_cache_get_cached_token(cache); if (NULL != token) { *found_in_cache = true; @@ -159,7 +220,7 @@ mongoc_oidc_cache_get_token(mongoc_oidc_cache_t *cache, bool *found_in_cache, bs // 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)); + mongoc_oidc_callback_params_set_user_data(params, mongoc_oidc_callback_get_user_data(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. @@ -186,12 +247,16 @@ mongoc_oidc_cache_get_token(mongoc_oidc_cache_t *cache, bool *found_in_cache, bs } // Call callback: - cred = mongoc_oidc_callback_get_fn(cache->callback)(params); + cred = mongoc_oidc_callback_get_fn(callback)(params); cache->last_called = mlib_now(); cache->ever_called = true; if (!cred) { + if (mongoc_oidc_callback_params_get_cancelled_with_timeout(params)) { + SET_ERROR("MONGODB-OIDC callback was cancelled due to timeout"); + goto unlock_and_return; + } SET_ERROR("MONGODB-OIDC callback failed"); goto unlock_and_return; } diff --git a/src/libmongoc/src/mongoc/mongoc-oidc-env-private.h b/src/libmongoc/src/mongoc/mongoc-oidc-env-private.h index 5d583ec465..c033247004 100644 --- a/src/libmongoc/src/mongoc/mongoc-oidc-env-private.h +++ b/src/libmongoc/src/mongoc/mongoc-oidc-env-private.h @@ -40,7 +40,7 @@ bool mongoc_oidc_env_requires_token_resource(const mongoc_oidc_env_t *env); mongoc_oidc_env_callback_t * -mongoc_oidc_env_callback_new(const mongoc_oidc_env_t *env, const char *token_resource); +mongoc_oidc_env_callback_new(const mongoc_oidc_env_t *env, const char *token_resource, const char *username); void mongoc_oidc_env_callback_destroy(mongoc_oidc_env_callback_t *env_callback); diff --git a/src/libmongoc/src/mongoc/mongoc-oidc-env.c b/src/libmongoc/src/mongoc/mongoc-oidc-env.c index 524f41afd6..8eabea9233 100644 --- a/src/libmongoc/src/mongoc/mongoc-oidc-env.c +++ b/src/libmongoc/src/mongoc/mongoc-oidc-env.c @@ -16,8 +16,12 @@ #include +#include #include +#include +#include + struct _mongoc_oidc_env_t { const char *name; mongoc_oidc_callback_fn_t callback_fn; @@ -28,6 +32,7 @@ struct _mongoc_oidc_env_t { struct _mongoc_oidc_env_callback_t { mongoc_oidc_callback_t *inner; // Contains non-owning user_data pointer back to this mongoc_oidc_env_callback_t char *token_resource; + char *username; }; static mongoc_oidc_credential_t * @@ -41,9 +46,50 @@ mongoc_oidc_env_fn_test(mongoc_oidc_callback_params_t *params) static mongoc_oidc_credential_t * mongoc_oidc_env_fn_azure(mongoc_oidc_callback_params_t *params) { - BSON_UNUSED(params); - // TODO (CDRIVER-4489) - return NULL; + BSON_ASSERT_PARAM(params); + + bson_error_t error; + mcd_azure_access_token token = {0}; + mongoc_oidc_credential_t *ret = NULL; + mongoc_oidc_env_callback_t *callback = mongoc_oidc_callback_params_get_user_data(params); + BSON_ASSERT(callback); + + int max_duration_ms = 0; + const int64_t *timeout_us = mongoc_oidc_callback_params_get_timeout(params); + if (timeout_us) { + int64_t remaining_ms = (*timeout_us - bson_get_monotonic_time()) / 1000; + if (remaining_ms <= 0) { + // No time remaining. Immediately fail. + mongoc_oidc_callback_params_cancel_with_timeout(params); + goto fail; + } + if (mlib_narrow(&max_duration_ms, remaining_ms)) { + // Requested timeout too large to fit. Cap at INT_MAX. + max_duration_ms = mlib_maxof(int); + } + } + + if (!mcd_azure_access_token_from_imds(&token, + callback->token_resource, + NULL, // Use the default host + 0, // Default port as well + NULL, // No extra headers + max_duration_ms, + callback->username, // Optional client id + &error)) { + MONGOC_ERROR("Failed to obtain Azure OIDC access token: %s", error.message); + goto fail; + } + + ret = mongoc_oidc_credential_new_with_expires_in(token.access_token, mcd_get_microseconds(token.expires_in)); + if (!ret) { + MONGOC_ERROR("Failed to process Azure OIDC access token"); + goto fail; + } + +fail: + mcd_azure_access_token_destroy(&token); + return ret; } static mongoc_oidc_credential_t * @@ -107,16 +153,18 @@ mongoc_oidc_env_requires_token_resource(const mongoc_oidc_env_t *env) } mongoc_oidc_env_callback_t * -mongoc_oidc_env_callback_new(const mongoc_oidc_env_t *env, const char *token_resource) +mongoc_oidc_env_callback_new(const mongoc_oidc_env_t *env, const char *token_resource, const char *username) { BSON_ASSERT_PARAM(env); BSON_OPTIONAL_PARAM(token_resource); + BSON_OPTIONAL_PARAM(username); mongoc_oidc_env_callback_t *env_callback = bson_malloc(sizeof *env_callback); // Note that the callback's user_data points back to this containing mongoc_oidc_env_callback_t. // We expect that the inner callback can only be destroyed via mongoc_oidc_env_callback_destroy. *env_callback = (mongoc_oidc_env_callback_t){.inner = mongoc_oidc_callback_new_with_user_data(env->callback_fn, env_callback), - .token_resource = bson_strdup(token_resource)}; + .token_resource = bson_strdup(token_resource), + .username = bson_strdup(username)}; return env_callback; } @@ -127,6 +175,7 @@ mongoc_oidc_env_callback_destroy(mongoc_oidc_env_callback_t *env_callback) BSON_ASSERT(mongoc_oidc_callback_get_user_data(env_callback->inner) == (void *)env_callback); mongoc_oidc_callback_destroy(env_callback->inner); bson_free(env_callback->token_resource); + bson_free(env_callback->username); bson_free(env_callback); } } diff --git a/src/libmongoc/src/mongoc/mongoc-topology.c b/src/libmongoc/src/mongoc/mongoc-topology.c index a21b140a12..32286be1a7 100644 --- a/src/libmongoc/src/mongoc/mongoc-topology.c +++ b/src/libmongoc/src/mongoc/mongoc-topology.c @@ -565,6 +565,9 @@ mongoc_topology_new(const mongoc_uri_t *uri, bool single_threaded) if (!mongoc_uri_finalize(topology->uri, &topology->scanner->error)) { topology->valid = false; + } else { + // URI is valid. Try to apply to OIDC environment. + mongoc_oidc_cache_apply_env_from_uri(topology->oidc_cache, topology->uri); } td->max_hosts = mongoc_uri_get_option_as_int32(uri, MONGOC_URI_SRVMAXHOSTS, 0); diff --git a/src/libmongoc/src/mongoc/mongoc-util-private.h b/src/libmongoc/src/mongoc/mongoc-util-private.h index 60b19031ca..03eceb617a 100644 --- a/src/libmongoc/src/mongoc/mongoc-util-private.h +++ b/src/libmongoc/src/mongoc/mongoc-util-private.h @@ -266,6 +266,11 @@ _mongoc_verify_windows_version(DWORD major_version, DWORD minor_version, DWORD b #endif +// mongoc_percent_encode percent encodes `str` according to RFC 3986. The caller must free the returned string. +// Returns NULL on failure. +char * +mongoc_percent_encode(const char *str); + BSON_END_DECLS #endif /* MONGOC_UTIL_PRIVATE_H */ diff --git a/src/libmongoc/src/mongoc/mongoc-util.c b/src/libmongoc/src/mongoc/mongoc-util.c index ad6930b815..8e372d32fd 100644 --- a/src/libmongoc/src/mongoc/mongoc-util.c +++ b/src/libmongoc/src/mongoc/mongoc-util.c @@ -1094,3 +1094,64 @@ _mongoc_verify_windows_version(DWORD major_version, DWORD minor_version, DWORD b } #endif + +static bool +needs_percent_encoding(unsigned char c) +{ + // Unreserved characters according to RFC 3986: + // ALPHA / DIGIT / "-" / "." / "_" / "~" + if (c >= 'A' && c <= 'Z') { + return false; + } + if (c >= 'a' && c <= 'z') { + return false; + } + if (c >= '0' && c <= '9') { + return false; + } + if (c == '-' || c == '.' || c == '_' || c == '~') { + return false; + } + return true; +} + +char * +mongoc_percent_encode(const char *str) +{ + BSON_ASSERT_PARAM(str); + + size_t str_len = strlen(str); + size_t encoded_len = 0u; + + for (char *i = (char *)str; *i; i++) { + if (needs_percent_encoding((unsigned char)*i)) { + encoded_len += 3u; + } else { + encoded_len += 1u; + } + } + + encoded_len += 1u; // null terminator + if (encoded_len < str_len) { + // Overflow + return NULL; + } + + char *encoded = bson_malloc(encoded_len); + char *o = encoded; // output pointer + + for (char *i = (char *)str; *i; i++) { + if (needs_percent_encoding((unsigned char)*i)) { + int req = bson_snprintf(o, 4, "%%%02X", (unsigned char)*i); + // Expect no truncation. + BSON_ASSERT(req == 3); + o += 3u; + } else { + *o = *i; + o += 1; + } + } + + *o = '\0'; + return encoded; +} diff --git a/src/libmongoc/tests/test-mcd-azure-imds.c b/src/libmongoc/tests/test-mcd-azure-imds.c index e208c9638f..0724726536 100644 --- a/src/libmongoc/tests/test-mcd-azure-imds.c +++ b/src/libmongoc/tests/test-mcd-azure-imds.c @@ -39,7 +39,7 @@ _test_http_req(void) { // Test generating an HTTP request for the IMDS server mcd_azure_imds_request req; - mcd_azure_imds_request_init(&req, "example.com", 9879, ""); + ASSERT(mcd_azure_imds_request_init(&req, MCD_TOKEN_RESOURCE_VAULT, "example.com", 9879, "", NULL)); mcommon_string_append_t req_str; mcommon_string_new_as_append(&req_str); _mongoc_http_render_request_head(&req_str, &req.req); @@ -77,7 +77,7 @@ _run_http_test_case(const char *case_, mcd_azure_access_token token = {0}; char *const header = bson_strdup_printf("X-MongoDB-HTTP-TestParams: case=%s\r\n", case_); - mcd_azure_access_token_from_imds(&token, host.host, host.port, header, &error); + mcd_azure_access_token_from_imds(&token, MCD_TOKEN_RESOURCE_VAULT, host.host, host.port, header, 0, NULL, &error); bson_free(header); mcd_azure_access_token_destroy(&token); ASSERT_ERROR_CONTAINS(error, expect_domain, expect_code, expect_error_message); diff --git a/src/libmongoc/tests/test-mongoc-oidc-cache.c b/src/libmongoc/tests/test-mongoc-oidc-cache.c index 93df56f6a4..e9cd7dd773 100644 --- a/src/libmongoc/tests/test-mongoc-oidc-cache.c +++ b/src/libmongoc/tests/test-mongoc-oidc-cache.c @@ -59,7 +59,7 @@ test_oidc_cache_works(void) { mongoc_oidc_callback_t *cb = mongoc_oidc_callback_new(oidc_callback_fn); mongoc_oidc_callback_set_user_data(cb, &ctx); - mongoc_oidc_cache_set_callback(cache, cb); + mongoc_oidc_cache_set_user_callback(cache, cb); mongoc_oidc_callback_destroy(cb); } @@ -122,7 +122,7 @@ test_oidc_cache_waits_between_calls(void) { mongoc_oidc_callback_t *cb = mongoc_oidc_callback_new(oidc_callback_fn); mongoc_oidc_callback_set_user_data(cb, &ctx); - mongoc_oidc_cache_set_callback(cache, cb); + mongoc_oidc_cache_set_user_callback(cache, cb); mongoc_oidc_callback_destroy(cb); } @@ -178,20 +178,20 @@ test_oidc_cache_set_callback(void) { mongoc_oidc_cache_t *cache = mongoc_oidc_cache_new(); - ASSERT(!mongoc_oidc_cache_get_callback(cache)); + ASSERT(!mongoc_oidc_cache_has_user_callback(cache)); // Can set a callback: { mongoc_oidc_callback_t *cb = mongoc_oidc_callback_new(oidc_callback_fn); - mongoc_oidc_cache_set_callback(cache, cb); - ASSERT(mongoc_oidc_callback_get_fn(mongoc_oidc_cache_get_callback(cache)) == oidc_callback_fn); + mongoc_oidc_cache_set_user_callback(cache, cb); + ASSERT(mongoc_oidc_cache_has_user_callback(cache)); mongoc_oidc_callback_destroy(cb); } // Can clear a callback: { - mongoc_oidc_cache_set_callback(cache, NULL); - ASSERT(!mongoc_oidc_cache_get_callback(cache)); + mongoc_oidc_cache_set_user_callback(cache, NULL); + ASSERT(!mongoc_oidc_cache_has_user_callback(cache)); } mongoc_oidc_cache_destroy(cache); @@ -222,7 +222,7 @@ test_oidc_cache_set_sleep(void) { mongoc_oidc_callback_t *cb = mongoc_oidc_callback_new(oidc_callback_fn); mongoc_oidc_callback_set_user_data(cb, &ctx); - mongoc_oidc_cache_set_callback(cache, cb); + mongoc_oidc_cache_set_user_callback(cache, cb); mongoc_oidc_callback_destroy(cb); } @@ -300,7 +300,7 @@ test_oidc_cache_propagates_error(void) { mongoc_oidc_callback_t *cb = mongoc_oidc_callback_new(oidc_callback_fn); mongoc_oidc_callback_set_user_data(cb, &ctx); - mongoc_oidc_cache_set_callback(cache, cb); + mongoc_oidc_cache_set_user_callback(cache, cb); mongoc_oidc_callback_destroy(cb); } diff --git a/src/libmongoc/tests/test-mongoc-oidc.c b/src/libmongoc/tests/test-mongoc-oidc.c index 4e3a487f48..619a61a41e 100644 --- a/src/libmongoc/tests/test-mongoc-oidc.c +++ b/src/libmongoc/tests/test-mongoc-oidc.c @@ -279,6 +279,28 @@ test_oidc_bad_config(void *unused) mongoc_oidc_callback_destroy(cb); mongoc_client_pool_destroy(pool); } + + // Expect error on unsupported ENVIRONMENT passed (URI string) + { + mongoc_uri_t *uri = mongoc_uri_new_with_error( + "mongodb://localhost:27017/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:bad", &error); + ASSERT(!uri); + ASSERT_ERROR_CONTAINS(error, MONGOC_ERROR_COMMAND, MONGOC_ERROR_COMMAND_INVALID_ARG, "unrecognized ENVIRONMENT"); + mongoc_uri_destroy(uri); + } + + // Expect error on unsupported ENVIRONMENT passed (URI setter) + { + mongoc_uri_t *uri = mongoc_uri_new_with_error("mongodb://localhost:27017/?authMechanism=MONGODB-OIDC", &error); + ASSERT_OR_PRINT(uri, error); + // URI setter skips validation in URI string parsing, but is validated during client construction. + mongoc_uri_set_mechanism_properties(uri, tmp_bson(BSON_STR({"ENVIRONMENT" : "bad"}))); + mongoc_client_t *client = mongoc_client_new_from_uri_with_error(uri, &error); + ASSERT(!client); + ASSERT_ERROR_CONTAINS(error, MONGOC_ERROR_COMMAND, MONGOC_ERROR_COMMAND_INVALID_ARG, "unrecognized ENVIRONMENT"); + mongoc_client_destroy(client); + mongoc_uri_destroy(uri); + } } // test_oidc_delays tests the minimum required time between OIDC calls. @@ -797,12 +819,86 @@ PROSE_TEST(4, 5, "Reauthentication Succeeds when a Session is involved") test_fixture_destroy(tf); } +PROSE_TEST(5, 1, "Azure With No Username") +{ + // Create URI: + mongoc_uri_t *uri = mongoc_uri_new("mongodb://localhost:27017/?retryReads=false"); + { + mongoc_uri_set_auth_mechanism(uri, "MONGODB-OIDC"); + bson_t props = BSON_INITIALIZER; + BSON_APPEND_UTF8(&props, "ENVIRONMENT", "azure"); + char *token_resource = test_framework_getenv_required("MONGOC_AZURE_RESOURCE"); + BSON_APPEND_UTF8(&props, "TOKEN_RESOURCE", token_resource); + bson_free(token_resource); + mongoc_uri_set_mechanism_properties(uri, &props); + bson_destroy(&props); + } + + bson_error_t error; + mongoc_client_t *client = mongoc_client_new_from_uri_with_error(uri, &error); + ASSERT_OR_PRINT(client, error); + + // Expect auth to succeed: + ASSERT_OR_PRINT(do_find(client, &error), error); + + mongoc_client_destroy(client); + mongoc_uri_destroy(uri); +} + +PROSE_TEST(5, 2, "Azure With Bad Username") +{ + // Create URI: + mongoc_uri_t *uri = mongoc_uri_new("mongodb://bad@localhost:27017/?retryReads=false"); + { + mongoc_uri_set_auth_mechanism(uri, "MONGODB-OIDC"); + bson_t props = BSON_INITIALIZER; + BSON_APPEND_UTF8(&props, "ENVIRONMENT", "azure"); + char *token_resource = test_framework_getenv_required("MONGOC_AZURE_RESOURCE"); + BSON_APPEND_UTF8(&props, "TOKEN_RESOURCE", token_resource); + bson_free(token_resource); + mongoc_uri_set_mechanism_properties(uri, &props); + bson_destroy(&props); + } + + bson_error_t error; + mongoc_client_t *client = mongoc_client_new_from_uri_with_error(uri, &error); + ASSERT_OR_PRINT(client, error); + + // Expect auth to fail: + ASSERT(!do_find(client, &error)); + ASSERT_ERROR_CONTAINS(error, MONGOC_ERROR_CLIENT, MONGOC_ERROR_CLIENT_AUTHENTICATE, "failed"); + + mongoc_client_destroy(client); + mongoc_uri_destroy(uri); +} + +static bool +is_testing_azure_oidc(void) +{ + char *token_resource = test_framework_getenv("MONGOC_AZURE_RESOURCE"); + if (!token_resource) { + return false; + } + bson_free(token_resource); + return true; +} + static int skip_if_no_oidc(void) { + if (is_testing_azure_oidc()) { + // OIDC tests asserting callback counts cannot run when callback is set by environment. + return 0; + } return test_framework_is_oidc() ? 1 : 0; } +static int +skip_if_no_azure_oidc(void) +{ + return is_testing_azure_oidc() ? 1 : 0; +} + void test_oidc_auth_install(TestSuite *suite) { @@ -863,4 +959,10 @@ test_oidc_auth_install(TestSuite *suite) TestSuite_AddFull(suite, "/oidc/prose/4.5/single", test_oidc_prose_4_5, NULL, &single, skip_if_no_oidc); TestSuite_AddFull(suite, "/oidc/prose/4.5/pooled", test_oidc_prose_4_5, NULL, &pooled, skip_if_no_oidc); + + TestSuite_AddFull(suite, "/oidc/prose/5.1/single", test_oidc_prose_5_1, NULL, &single, skip_if_no_azure_oidc); + TestSuite_AddFull(suite, "/oidc/prose/5.1/pooled", test_oidc_prose_5_1, NULL, &pooled, skip_if_no_azure_oidc); + + TestSuite_AddFull(suite, "/oidc/prose/5.2/single", test_oidc_prose_5_2, NULL, &single, skip_if_no_azure_oidc); + TestSuite_AddFull(suite, "/oidc/prose/5.2/pooled", test_oidc_prose_5_2, NULL, &pooled, skip_if_no_azure_oidc); } diff --git a/src/libmongoc/tests/test-mongoc-util.c b/src/libmongoc/tests/test-mongoc-util.c index 8bb4101c26..acaaad3d5d 100644 --- a/src/libmongoc/tests/test-mongoc-util.c +++ b/src/libmongoc/tests/test-mongoc-util.c @@ -130,6 +130,34 @@ test_hex_to_bin(void) ASSERT_WITH_MSG(!hex_to_bin("ZZ", &len), "non-hex digits is an error"); } +static void +test_percent_encode(void) +{ + // Simple case: + { + char *got = mongoc_percent_encode("hello world!"); + ASSERT(got); + ASSERT_CMPSTR(got, "hello%20world%21"); + bson_free(got); + } + + // Empty string: + { + char *got = mongoc_percent_encode(""); + ASSERT(got); + ASSERT_CMPSTR(got, ""); + bson_free(got); + } + + // All reserved characters: + { + char *got = mongoc_percent_encode("!#$"); + ASSERT(got); + ASSERT_CMPSTR(got, "%21%23%24"); + bson_free(got); + } +} + void test_util_install(TestSuite *suite) { @@ -139,4 +167,5 @@ test_util_install(TestSuite *suite) TestSuite_Add(suite, "/Util/wire_server_versions", test_wire_server_versions); TestSuite_Add(suite, "/Util/bin_to_hex", test_bin_to_hex); TestSuite_Add(suite, "/Util/hex_to_bin", test_hex_to_bin); + TestSuite_Add(suite, "/Util/percent_encode", test_percent_encode); } diff --git a/src/libmongoc/tests/unified/entity-map.c b/src/libmongoc/tests/unified/entity-map.c index 089d033a76..8cc86302db 100644 --- a/src/libmongoc/tests/unified/entity-map.c +++ b/src/libmongoc/tests/unified/entity-map.c @@ -950,6 +950,15 @@ entity_client_new(entity_map_t *em, bson_t *bson, bson_error_t *error) bson_free(test_password); } + char *azure_resource = test_framework_getenv("MONGOC_AZURE_RESOURCE"); + const bool testing_azure_oidc = azure_resource != NULL; + if (uri_requests_oidc && testing_azure_oidc) { + bson_t *mech_props = BCON_NEW("ENVIRONMENT", "azure", "TOKEN_RESOURCE", BCON_UTF8(azure_resource)); + mongoc_uri_set_mechanism_properties(uri, mech_props); + bson_destroy(mech_props); + } + bson_free(azure_resource); + if (!mongoc_uri_has_option(uri, MONGOC_URI_HEARTBEATFREQUENCYMS)) { can_reduce_heartbeat = true; } @@ -960,7 +969,7 @@ entity_client_new(entity_map_t *em, bson_t *bson, bson_error_t *error) client = test_framework_client_new_from_uri(uri, api); - if (uri_requests_oidc) { + if (uri_requests_oidc && !testing_azure_oidc) { test_framework_set_oidc_callback(client); }