diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 4f4a6454..6445f93c 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -8,7 +8,7 @@ else () FetchContent_Declare(tlsuv GIT_REPOSITORY https://github.com/openziti/tlsuv.git - GIT_TAG v0.26.1 + GIT_TAG main ) FetchContent_MakeAvailable(tlsuv) diff --git a/inc_internal/oidc.h b/inc_internal/oidc.h new file mode 100644 index 00000000..438898e9 --- /dev/null +++ b/inc_internal/oidc.h @@ -0,0 +1,77 @@ +// +// Copyright NetFoundry 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 +// +// https://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 ZITI_SDK_OIDC_H +#define ZITI_SDK_OIDC_H + +#include + +#include +#include "tlsuv/http.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct oidc_client_s oidc_client_t; +typedef void (*oidc_config_cb)(oidc_client_t *, int, const char *); +typedef void (*oidc_token_cb)(oidc_client_t *, int, const char *access_token); +typedef void (*oidc_close_cb)(oidc_client_t *); + + +typedef enum { + oidc_native, + oidc_external, +} oidc_auth_mode; + +struct oidc_client_s { + void *data; + tlsuv_http_t http; + + const char *client_id; + oidc_auth_mode mode; + oidc_config_cb config_cb; + oidc_token_cb token_cb; + oidc_close_cb close_cb; + + void *config; + void *tokens; + uv_timer_t *timer; +}; + +// init +int oidc_client_init(uv_loop_t *loop, oidc_client_t *clt, const char *url, tls_context *tls); + +int oidc_client_set_id(oidc_client_t *clt, const char *client_id); + +// configure client +int oidc_client_configure(oidc_client_t *clt, oidc_config_cb); + +// acquire access token and start refresh cycle +// oidc_token_cb will be called on first auth and on every refresh +int oidc_client_start(oidc_client_t *clt, oidc_token_cb); + +// force token refresh ahead of normal cycle, error if called prior to oidc_client_start +int oidc_client_refresh(oidc_client_t *clt); + +// close +int oidc_client_close(oidc_client_t *clt, oidc_close_cb); + +#ifdef __cplusplus +}; +#endif + +#endif //ZITI_SDK_OIDC_H diff --git a/inc_internal/utils.h b/inc_internal/utils.h index bb99ec35..553b6f4f 100644 --- a/inc_internal/utils.h +++ b/inc_internal/utils.h @@ -106,7 +106,7 @@ if (COND(ex)(ERR(ex))) { ERFILE(ex) = __FILENAME__; ERLINE(ex) = __LINE__; _##ex -#define container_of(ptr, type, member) ((type *) ((ptr) - offsetof(type, member))) +#define container_of(ptr, type, member) ((type *) ((char*)(ptr) - offsetof(type, member))) #define CLOSE_AND_NULL(h) do{ if (h) { \ if (!uv_is_closing((uv_handle_t*)(h))) uv_close((uv_handle_t*)(h), (uv_close_cb)free); \ diff --git a/inc_internal/ziti_ctrl.h b/inc_internal/ziti_ctrl.h index a3b9fb78..3a376ac0 100644 --- a/inc_internal/ziti_ctrl.h +++ b/inc_internal/ziti_ctrl.h @@ -19,7 +19,6 @@ #include #include "internal_model.h" #include "ziti/ziti_model.h" -#include "zt_internal.h" #ifdef __cplusplus extern "C" { diff --git a/includes/ziti/ziti_model.h b/includes/ziti/ziti_model.h index 129933bb..b9d2bb06 100644 --- a/includes/ziti/ziti_model.h +++ b/includes/ziti/ziti_model.h @@ -59,10 +59,15 @@ XX(path, string, none, path, __VA_ARGS__) #define ZITI_API_VERSIONS_MODEL(XX, ...) \ XX(edge, api_path, map, edge, __VA_ARGS__) +#define ZITI_CTRL_CAP_ENUM(XX, ...) \ +XX(HA_CONTROLLER, __VA_ARGS__) \ +XX(OIDC_AUTH, __VA_ARGS__) + #define ZITI_VERSION_MODEL(XX, ...) \ XX(version, string, none, version, __VA_ARGS__) \ XX(revision, string, none, revision, __VA_ARGS__) \ XX(build_date, string, none, buildDate, __VA_ARGS__) \ +XX(capabilities, ziti_ctrl_cap, list, capabilities, __VA_ARGS__) \ XX(api_versions, ziti_api_versions, ptr, apiVersions, __VA_ARGS__) #define ZITI_IDENTITY_MODEL(XX, ...) \ @@ -206,6 +211,8 @@ ZITI_FUNC int ziti_port_match(int port, const model_list *port_range_list); DECLARE_ENUM(ziti_session_type, ZITI_SESSION_TYPE_ENUM) +DECLARE_ENUM(ziti_ctrl_cap, ZITI_CTRL_CAP_ENUM) + DECLARE_MODEL(api_path, ZITI_API_PATH_MODEL) DECLARE_MODEL(ziti_api_versions, ZITI_API_VERSIONS_MODEL) diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 1c452568..27efbc7e 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -16,6 +16,8 @@ if (NOT sodium_libs) message(FATAL_ERROR "could not find required library[sodium]") endif () +find_package(json-c CONFIG REQUIRED) + set(ZITI_HEADER_FILES ${PROJECT_SOURCE_DIR}/includes/ziti/errors.h ${PROJECT_SOURCE_DIR}/includes/ziti/ziti.h @@ -53,6 +55,7 @@ SET(ZITI_SRC_FILES authenticators.c crypto.c bind.c + oidc.c ) SET(ZITI_INCLUDE_DIRS @@ -86,7 +89,7 @@ function(config_ziti_library target) target_sources(${target} PRIVATE ${ZITI_PRIVATE_SRC_FILES}) - target_link_libraries(${target} PUBLIC tlsuv ${sodium_libs}) + target_link_libraries(${target} PUBLIC tlsuv ${sodium_libs} json-c::json-c) if (CMAKE_SYSTEM_NAME MATCHES "Linux") target_link_libraries(${target} PUBLIC atomic) diff --git a/library/internal_model.c b/library/internal_model.c index 6fc9bbe4..cde0a662 100644 --- a/library/internal_model.c +++ b/library/internal_model.c @@ -43,6 +43,8 @@ typedef uint32_t in_addr_t; IMPL_ENUM(ziti_enrollment_method, ZITI_ENROLLMENT_METHOD) +IMPL_ENUM(ziti_ctrl_cap, ZITI_CTRL_CAP_ENUM) + IMPL_MODEL(ziti_posture_query, ZITI_POSTURE_QUERY_MODEL) IMPL_MODEL(ziti_posture_query_set, ZITI_POSTURE_QUERY_SET_MODEL) diff --git a/library/oidc.c b/library/oidc.c new file mode 100644 index 00000000..c849c7d4 --- /dev/null +++ b/library/oidc.c @@ -0,0 +1,389 @@ +// +// Copyright NetFoundry 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 +// +// https://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 +#include +#include +#include +#include "ziti/ziti_log.h" +#include "utils.h" +#include "ziti/errors.h" + +#define code_len 8 +#define code_verifier_len sodium_base64_ENCODED_LEN(code_len, sodium_base64_VARIANT_URLSAFE_NO_PADDING) +#define code_challenge_len sodium_base64_ENCODED_LEN(crypto_hash_sha256_BYTES, sodium_base64_VARIANT_URLSAFE_NO_PADDING) + +#define default_cb_url "http://localhost:18889/auth/callback" +#define default_client_id "native" + +typedef struct oidc_req oidc_req; + +typedef void (*oidc_cb)(oidc_req *, int status, json_object *resp); + +static const char *get_endpoint_path(oidc_client_t *clt, const char *key); + +static void oidc_client_set_tokens(oidc_client_t *clt, json_object *tok_json); + +static void refresh_time_cb(uv_timer_t *t); + +struct oidc_req { + oidc_client_t *client; + json_tokener *parser; + oidc_cb cb; + void *ctx; +}; + +typedef struct auth_req { + oidc_client_t *clt; + oidc_token_cb cb; + char code_verifier[code_verifier_len]; + char code_challenge[code_challenge_len]; + json_tokener *json_parser; +} auth_req; + +static oidc_req *new_oidc_req(oidc_client_t *clt, oidc_cb cb, void *ctx) { + oidc_req *res = calloc(1, sizeof(*res)); + res->client = clt; + res->cb = cb; + + res->parser = json_tokener_new(); + res->ctx = ctx; + return res; +} + +static void complete_oidc_req(oidc_req *req, int err, json_object *obj) { + req->cb(req, err, obj); + json_tokener_free(req->parser); + free(req); +} + +static void json_parse_cb(tlsuv_http_req_t *r, char *data, ssize_t len) { + oidc_req *req = r->data; + if (req == NULL) { + return; + } + + if (len > 0) { + if (req) { + json_object *res = json_tokener_parse_ex(req->parser, data, (int) len); + if (res) { + complete_oidc_req(req, 0, res); + r->data = NULL; + } + } + return; + } + + if (len == UV_EOF) { + if (req) { + complete_oidc_req(req, UV_EOF, NULL); + } + return; + } +} + +static void parse_cb(tlsuv_http_resp_t *resp, void *ctx) { + if (resp->code == HTTP_STATUS_OK) { + resp->body_cb = json_parse_cb; + return; + } + + oidc_client_t *oidc = ctx; + if (oidc->config_cb) { + oidc->config_cb(oidc, resp->code, resp->status); + oidc->config_cb = NULL; + } + +} + +int oidc_client_init(uv_loop_t *loop, oidc_client_t *clt, const char *url, tls_context *tls) { + assert(clt != NULL); + assert(url != NULL); + + clt->config = NULL; + clt->tokens = NULL; + clt->config_cb = NULL; + clt->token_cb = NULL; + clt->close_cb = NULL; + clt->client_id = default_client_id; + + int rc = tlsuv_http_init(loop, &clt->http, url); + if (rc != 0) { + return rc; + } + tlsuv_http_set_ssl(&clt->http, tls); + + clt->timer = calloc(1, sizeof(*clt->timer)); + uv_timer_init(loop, clt->timer); + clt->timer->data = clt; + uv_unref((uv_handle_t *) clt->timer); + + return 0; +} + +static void internal_config_cb(oidc_req *req, int status, json_object *resp) { + oidc_client_t *clt = req->client; + + if (status == 0) { + assert(json_object_get_type(resp) == json_type_object); + clt->config = resp; + } + + if (clt->config_cb) { + clt->config_cb(clt, status, NULL); + } + clt->config_cb = NULL; +} + +int oidc_client_configure(oidc_client_t *clt, oidc_config_cb cb) { + clt->config_cb = cb; + oidc_req *req = new_oidc_req(clt, internal_config_cb, NULL); + tlsuv_http_req(&clt->http, "GET", "/.well-known/openid-configuration", parse_cb, req); + return 0; +} + +static auth_req *new_auth_req(oidc_client_t *clt) { + auth_req *req = calloc(1, sizeof(*req)); + req->clt = clt; + + uint8_t code[8]; + uv_random(NULL, NULL, code, sizeof(code), 0, NULL); + sodium_bin2base64(req->code_verifier, sizeof(req->code_verifier), + code, sizeof(code), sodium_base64_VARIANT_URLSAFE_NO_PADDING); + uint8_t hash[crypto_hash_sha256_BYTES]; + crypto_hash_sha256(hash, (const uint8_t *) req->code_verifier, strlen(req->code_verifier)); + sodium_bin2base64(req->code_challenge, sizeof(req->code_challenge), + hash, sizeof(hash), sodium_base64_VARIANT_URLSAFE_NO_PADDING); + return req; +} + +static void free_auth_req(auth_req *req) { + if (req == NULL) return; + + if (req->json_parser) { + json_tokener_free(req->json_parser); + } + free(req); +} + +static void failed_auth_req(auth_req *req, const char *error) { + ZITI_LOG(WARN, "OIDC authorization failed: %s", error); + req->clt->token_cb(req->clt, ZITI_AUTHENTICATION_FAILED, NULL); + free_auth_req(req); +} + +static void parse_token_cb(tlsuv_http_req_t *r, char *body, ssize_t len) { + auth_req *req = r->data; + int err; + if (len > 0) { + if (req->json_parser) { + json_object *j = json_tokener_parse_ex(req->json_parser, body, (int) len); + + if (j != NULL) { + oidc_client_set_tokens(req->clt, j); + r->data = NULL; + r->resp.body_cb = NULL; + free_auth_req(req); + } else { + if ((err = json_tokener_get_error(req->json_parser)) != json_tokener_continue) { + r->data = NULL; + r->resp.body_cb = NULL; + failed_auth_req(req, json_tokener_error_desc(err)); + } + } + } + return; + } + + // error before req is complete + if (req) { + failed_auth_req(req, uv_strerror((int) len)); + } +} + +static void token_cb(tlsuv_http_resp_t *http_resp, void *ctx) { + auth_req *req = ctx; + ZITI_LOG(DEBUG, "%d %s", http_resp->code, http_resp->status); + if (http_resp->code == 200) { + req->json_parser = json_tokener_new(); + http_resp->body_cb = parse_token_cb; + } else { + failed_auth_req(req, http_resp->status); + } +} + +static void code_cb(tlsuv_http_resp_t *http_resp, void *ctx) { + auth_req *req = ctx; + if (http_resp->code / 100 == 3) { + const char *redirect = tlsuv_http_resp_header(http_resp, "Location"); + struct tlsuv_url_s uri; + tlsuv_parse_url(&uri, redirect); + char *code = strstr(uri.query, "code="); + code += strlen("code="); + + ZITI_LOG(DEBUG, "requesting token"); + const char *path = get_endpoint_path(req->clt, "token_endpoint"); + tlsuv_http_req_t *token_req = tlsuv_http_req(&req->clt->http, "POST", path, token_cb, req); + token_req->data = req; + tlsuv_http_req_form(token_req, 8, (tlsuv_http_pair[]) { + {"code", code}, + {"grant_type", "authorization_code"}, + {"code_verifier", req->code_verifier}, + {"code_challenge", req->code_challenge}, + {"code_challenge_method", "S256"}, + {"client_id", req->clt->client_id}, + {"scopes", "openid offline_access"}, + {"redirect_uri", default_cb_url} + }); + } else { + failed_auth_req(req, http_resp->status); + } +} + +static void login_cb(tlsuv_http_resp_t *http_resp, void *ctx) { + auth_req *req = ctx; + if (http_resp->code / 100 == 3) { + const char *redirect = tlsuv_http_resp_header(http_resp, "Location"); + struct tlsuv_url_s uri; + tlsuv_parse_url(&uri, redirect); + + tlsuv_http_req(&req->clt->http, "GET", uri.path, code_cb, req); + } else { + failed_auth_req(req, http_resp->status); + } +} + +static void auth_cb(tlsuv_http_resp_t *http_resp, void *ctx) { + auth_req *req = ctx; + if (http_resp->code / 100 == 3) { + const char *redirect = tlsuv_http_resp_header(http_resp, "Location"); + struct tlsuv_url_s uri; + tlsuv_parse_url(&uri, redirect); + char *p = strstr(uri.query, "authRequestID="); + p += strlen("authRequestID="); + + ZITI_LOG(DEBUG, "logging in with cert auth"); + tlsuv_http_req_t *login_req = tlsuv_http_req(&req->clt->http, "POST", "/oidc/login/cert", login_cb, req); + tlsuv_http_req_form(login_req, 1, &(tlsuv_http_pair) {"id", p}); + } else { + failed_auth_req(req, http_resp->status); + } +} + +int oidc_client_start(oidc_client_t *clt, oidc_token_cb cb) { + clt->token_cb = cb; + ZITI_LOG(DEBUG, "requesting authentication code"); + auth_req *req = new_auth_req(clt); + + const char *path = get_endpoint_path(clt, "authorization_endpoint"); + tlsuv_http_req_t *http_req = tlsuv_http_req(&clt->http, "POST", path, auth_cb, req); + tlsuv_http_req_header(http_req, "Accept", "*/*"); + int rc = tlsuv_http_req_form(http_req, 6, (tlsuv_http_pair[]) { + {"client_id", "native"}, + {"scope", "openid offline_access"}, + {"response_type", "code"}, + {"redirect_uri", default_cb_url}, + {"code_challenge", req->code_challenge}, + {"code_challenge_method", "S256"}, + }); + return rc; +} + +static void http_close_cb(tlsuv_http_t *h) { + oidc_client_t *clt = container_of(h, struct oidc_client_s, http); + + oidc_close_cb cb = clt->close_cb; + json_object_put(clt->config); + json_object_put(clt->tokens); + cb(clt); +} + +int oidc_client_refresh(oidc_client_t *clt) { + if (clt->timer == NULL || uv_is_closing((const uv_handle_t *) clt->timer)) { + return UV_EINVAL; + } + + uv_ref((uv_handle_t *) clt->timer); + return uv_timer_start(clt->timer, refresh_time_cb, 0, 0); +} + +int oidc_client_close(oidc_client_t *clt, oidc_close_cb cb) { + if (clt->close_cb) { + return UV_EALREADY; + } + clt->close_cb = cb; + tlsuv_http_close(&clt->http, http_close_cb); + uv_close((uv_handle_t *) clt->timer, (uv_close_cb) free); + clt->timer = NULL; +} + +static void oidc_client_set_tokens(oidc_client_t *clt, json_object *tok_json) { + if (clt->tokens) { + json_object_put(clt->tokens); + } + + clt->tokens = tok_json; + if (clt->token_cb) { + struct json_object *access_token = json_object_object_get(clt->tokens, "access_token"); + if (access_token) { + clt->token_cb(clt, ZITI_OK, json_object_get_string(access_token)); + } + } + struct json_object *refresher = json_object_object_get(clt->tokens, "refresh_token"); + struct json_object *ttl = json_object_object_get(clt->tokens, "expires_in"); + if (refresher && ttl) { + int32_t t = json_object_get_int(ttl); + if (t > 15) { + t -= 15; + } + ZITI_LOG(DEBUG, "scheduling token refresh in %d seconds", t); + uv_timer_start(clt->timer, refresh_time_cb, t * 1000, 0); + } +} + +static void refresh_cb(oidc_req *req, int status, json_object *resp) { + if (status == 0) { + ZITI_LOG(DEBUG, "token refresh success"); + oidc_client_set_tokens(req->client, resp); + } +} + +static void refresh_time_cb(uv_timer_t *t) { + uv_unref((uv_handle_t *) t); + oidc_client_t *clt = t->data; + + const char *path = get_endpoint_path(clt, "token_endpoint"); + struct json_object *tok = json_object_object_get(clt->tokens, "refresh_token"); + oidc_req *refresh_req = new_oidc_req(clt, refresh_cb, clt); + + tlsuv_http_req_t *req = tlsuv_http_req(&clt->http, "POST", path, parse_cb, refresh_req); + tlsuv_http_req_form(req, 4, (tlsuv_http_pair[]) { + {"client_id", clt->client_id}, + {"grant_type", "refresh_token"}, + {"refresh_token", json_object_get_string(tok)}, + {"scopes", "openid offline_access"}, + }); +} + +static const char *get_endpoint_path(oidc_client_t *clt, const char *key) { + assert(clt->config); + struct json_object *json = json_object_object_get(clt->config, key); + assert(json); + const char *url = json_object_get_string(json); + struct tlsuv_url_s u; + tlsuv_parse_url(&u, url); + return u.path; +} \ No newline at end of file diff --git a/library/ziti_ctrl.c b/library/ziti_ctrl.c index b591f09e..ea40e5e0 100644 --- a/library/ziti_ctrl.c +++ b/library/ziti_ctrl.c @@ -132,7 +132,7 @@ static void ctrl_paging_req(struct ctrl_resp *resp); static void ctrl_default_cb(void *s, const ziti_error *e, struct ctrl_resp *resp); -static void ctrl_body_cb(tlsuv_http_req_t *req, const char *b, ssize_t len); +static void ctrl_body_cb(tlsuv_http_req_t *req, char *b, ssize_t len); static tlsuv_http_req_t * start_request(tlsuv_http_t *http, const char *method, const char *path, tlsuv_http_resp_cb cb, struct ctrl_resp *resp) { @@ -285,11 +285,11 @@ static void ctrl_service_cb(ziti_service **services, ziti_error *e, struct ctrl_ free(services); } -static void free_body_cb(tlsuv_http_req_t *req, const char *body, ssize_t len) { +static void free_body_cb(tlsuv_http_req_t *req, char *body, ssize_t len) { free((char *) body); } -static void ctrl_body_cb(tlsuv_http_req_t *req, const char *b, ssize_t len) { +static void ctrl_body_cb(tlsuv_http_req_t *req, char *b, ssize_t len) { struct ctrl_resp *resp = req->data; ziti_controller *ctrl = resp->ctrl; diff --git a/programs/sample_http_link/sample_http_link.c b/programs/sample_http_link/sample_http_link.c index 0477a463..77780345 100644 --- a/programs/sample_http_link/sample_http_link.c +++ b/programs/sample_http_link/sample_http_link.c @@ -45,7 +45,7 @@ void resp_cb(tlsuv_http_resp_t *resp, void *data) { printf("\n"); } -void body_cb(tlsuv_http_req_t *req, const char *body, ssize_t len) { +void body_cb(tlsuv_http_req_t *req, char *body, ssize_t len) { if (len == UV_EOF) { printf("\n\n====================\nRequest completed\n"); ziti_shutdown(ziti); diff --git a/tests/integ/CMakeLists.txt b/tests/integ/CMakeLists.txt index 7458ffbe..84565ceb 100644 --- a/tests/integ/CMakeLists.txt +++ b/tests/integ/CMakeLists.txt @@ -9,8 +9,21 @@ execute_process(COMMAND ${EXPECTOR} -v) find_program(GOLANG_EXE NAMES go REQUIRED) +set(test_client_json ${CMAKE_CURRENT_BINARY_DIR}/test-client.json) +set(test_server_json ${CMAKE_CURRENT_BINARY_DIR}/test-server.json) + +CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/test-data.h.in + ${CMAKE_CURRENT_BINARY_DIR}/include/test-data.h + @ONLY +) + add_executable(integ-tests - main.cpp) + main.cpp + oidc-tests.cpp +) +target_include_directories(integ-tests + PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/include + PRIVATE ${ziti-sdk_SOURCE_DIR}/inc_internal) target_link_libraries(integ-tests PRIVATE ziti PRIVATE Catch2::Catch2WithMain @@ -22,7 +35,7 @@ else () set_property(TARGET integ-tests PROPERTY CXX_STANDARD 14) endif () -set(ZITI_CLI_VER "v0.31.0" CACHE STRING "ziti version for integration tests") +set(ZITI_CLI_VER "ha-staging" CACHE STRING "ziti version for integration tests") add_custom_target(ziti-cli ALL COMMAND ${CMAKE_COMMAND} -E env GOBIN=${CMAKE_CURRENT_BINARY_DIR} ${GOLANG_EXE} install github.com/openziti/ziti/ziti@${ZITI_CLI_VER} diff --git a/tests/integ/bootstrap.exp b/tests/integ/bootstrap.exp index eccdd93f..1699a7c0 100644 --- a/tests/integ/bootstrap.exp +++ b/tests/integ/bootstrap.exp @@ -1,6 +1,6 @@ #!expect -set timeout 20 +set timeout 60 proc abort errs { puts "test failed: $errs" exit 2 diff --git a/tests/integ/oidc-tests.cpp b/tests/integ/oidc-tests.cpp new file mode 100644 index 00000000..40167790 --- /dev/null +++ b/tests/integ/oidc-tests.cpp @@ -0,0 +1,92 @@ +// +// Copyright NetFoundry 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 +// +// https://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 +#include +#include +#include "oidc.h" +#include "ziti/ziti_log.h" +#include + +#include + +TEST_CASE("ha-oidc", "[integ]") { + auto l = uv_loop_new(); + ziti_log_init(l, 4, NULL); + ziti_config cfg; + REQUIRE(ziti_load_config(&cfg, TEST_CLIENT) == ZITI_OK); + auto tls = default_tls_context(cfg.id.ca, strlen(cfg.id.ca)); + tls_cert cert; + tlsuv_private_key_t key; + tls->load_cert(&cert, cfg.id.cert, strlen(cfg.id.cert)); + tls->load_key(&key, cfg.id.key, strlen(cfg.id.key)); + tls->set_own_cert(tls, key, cert); + + oidc_client_t oidcClient; + oidc_client_init(l, &oidcClient, cfg.controller_url, tls); + bool called = false; + oidcClient.data = &called; + + oidc_client_configure(&oidcClient, [](oidc_client_t *clt, int status, const char *err) { + CHECK(status == 0); + CHECK(err == nullptr); + *(bool *) clt->data = true; + }); + + uv_run(l, UV_RUN_DEFAULT); + + CHECK(called); + CHECK(oidcClient.config != NULL); + + std::string token; + oidcClient.data = &token; + oidc_client_start(&oidcClient, [](oidc_client_t *clt, int status, const char *token) { + auto out = (std::string *) clt->data; + REQUIRE(status == 0); + *out = token; + }); + + uv_run(l, UV_RUN_DEFAULT); + + REQUIRE(!token.empty()); + + std::string old = token; + token.clear(); + + oidc_client_refresh(&oidcClient); + uv_run(l, UV_RUN_DEFAULT); + + REQUIRE(!token.empty()); + REQUIRE(token != old); + + bool closed = false; + oidcClient.data = &closed; + oidc_client_close(&oidcClient, [](oidc_client_t *clt){ + *(bool*)clt->data = true; + }); + + uv_run(l, UV_RUN_DEFAULT); + REQUIRE(closed); + +// key->free(key); + tls->free_cert(&cert); + tls->free_ctx(tls); + + free_ziti_config(&cfg); + + uv_loop_close(l); + free(l); +} \ No newline at end of file diff --git a/tests/integ/test-data.h.in b/tests/integ/test-data.h.in new file mode 100644 index 00000000..1056ff0f --- /dev/null +++ b/tests/integ/test-data.h.in @@ -0,0 +1,3 @@ + +#define TEST_CLIENT "@test_client_json@" +#define TEST_SERVER "@test_server_json@" \ No newline at end of file diff --git a/tests/ziti_src_tests.cpp b/tests/ziti_src_tests.cpp index a9b83e8c..544befe2 100644 --- a/tests/ziti_src_tests.cpp +++ b/tests/ziti_src_tests.cpp @@ -60,7 +60,7 @@ TEST_CASE("httpbin.ziti:ziti_src", "[integ]") { auto t = (source_test*)ctx; t->code = resp->code; - resp->body_cb = [](tlsuv_http_req_t *req, const char *body, ssize_t len){ + resp->body_cb = [](tlsuv_http_req_t *req, char *body, ssize_t len){ auto t = (source_test*)req->data; if (len > 0) t->body.append(body, len); diff --git a/vcpkg.json b/vcpkg.json index e4f00eac..26789c58 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -6,7 +6,8 @@ "openssl", "zlib", "llhttp", - "libsodium" + "libsodium", + "json-c" ], "features": { "test": {