zswag 1.9.1.dev5__tar.gz → 1.9.2.dev14__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/.gitignore +2 -0
  2. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/CMakeLists.txt +1 -1
  3. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/PKG-INFO +16 -5
  4. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/README.md +15 -4
  5. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/cmake/Dependencies.cmake +1 -1
  6. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/cmake/OpenSSL.cmake +5 -2
  7. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/httpcl/include/httpcl/http-settings.hpp +11 -0
  8. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/httpcl/src/http-settings.cpp +58 -0
  9. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswag.egg-info/PKG-INFO +16 -5
  10. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswag.egg-info/SOURCES.txt +9 -1
  11. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswag.egg-info/requires.txt +1 -1
  12. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswagcl/CMakeLists.txt +5 -1
  13. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswagcl/include/zswagcl/private/openapi-client.hpp +2 -0
  14. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswagcl/include/zswagcl/private/openapi-config.hpp +25 -26
  15. zswag-1.9.2.dev14/libs/zswagcl/include/zswagcl/private/openapi-oauth.hpp +60 -0
  16. zswag-1.9.2.dev14/libs/zswagcl/include/zswagcl/private/openapi-security.hpp +56 -0
  17. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswagcl/src/openapi-client.cpp +6 -36
  18. zswag-1.9.2.dev14/libs/zswagcl/src/openapi-config.cpp +14 -0
  19. zswag-1.9.2.dev14/libs/zswagcl/src/openapi-oauth.cpp +190 -0
  20. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswagcl/src/openapi-parser.cpp +50 -18
  21. zswag-1.9.2.dev14/libs/zswagcl/src/openapi-security.cpp +147 -0
  22. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswagcl/test/CMakeLists.txt +3 -1
  23. zswag-1.9.2.dev14/libs/zswagcl/test/src/oauth2-integration-test.cpp +291 -0
  24. zswag-1.9.2.dev14/libs/zswagcl/test/src/oauth2-test.cpp +465 -0
  25. zswag-1.9.2.dev14/libs/zswagcl/test/testdata/oauth2-openapi.yaml +59 -0
  26. zswag-1.9.2.dev14/libs/zswagcl/test/testdata/oauth2-settings.yaml +25 -0
  27. zswag-1.9.1.dev5/libs/zswagcl/src/openapi-config.cpp +0 -113
  28. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/.clang-format +0 -0
  29. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/.github/pull_request_template.md +0 -0
  30. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/.github/scripts/validate_version.py +0 -0
  31. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/.github/workflows/build-deploy.yml +0 -0
  32. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/LICENSE +0 -0
  33. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/deploy.sh +0 -0
  34. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/doc/zswag-architecture.mdj +0 -0
  35. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/doc/zswag-architecture.png +0 -0
  36. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/httpcl/CMakeLists.txt +0 -0
  37. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/httpcl/include/httpcl/http-client.hpp +0 -0
  38. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/httpcl/include/httpcl/log.hpp +0 -0
  39. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/httpcl/include/httpcl/uri.hpp +0 -0
  40. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/httpcl/src/http-client.cpp +0 -0
  41. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/httpcl/src/log.cpp +0 -0
  42. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/httpcl/src/uri.cpp +0 -0
  43. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/httpcl/test/CMakeLists.txt +0 -0
  44. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/httpcl/test/src/main.cpp +0 -0
  45. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/httpcl/test/src/uri.cpp +0 -0
  46. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/pyzswagcl/CMakeLists.txt +0 -0
  47. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/pyzswagcl/py-openapi-client.cpp +0 -0
  48. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/pyzswagcl/py-openapi-client.h +0 -0
  49. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/pyzswagcl/py-zswagcl.cpp +0 -0
  50. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswag/__init__.py +0 -0
  51. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswag/app.py +0 -0
  52. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswag/doc.py +0 -0
  53. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswag/gen.py +0 -0
  54. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswag/reflect.py +0 -0
  55. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswag/test/.gitignore +0 -0
  56. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswag/test/CMakeLists.txt +0 -0
  57. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswag/test/__init__.py +0 -0
  58. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswag/test/calc/__init__.py +0 -0
  59. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswag/test/calc/__main__.py +0 -0
  60. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswag/test/calc/api.yaml +0 -0
  61. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswag/test/calc/calculator.zs +0 -0
  62. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswag/test/calc/client.py +0 -0
  63. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswag/test/calc/server.py +0 -0
  64. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswag/test/client.cpp +0 -0
  65. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswag/test/test_nested_service/services.zs +0 -0
  66. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswag/test/test_openapi_generator.bash +0 -0
  67. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswag/test/test_openapi_generator_1.yaml +0 -0
  68. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswag/test/test_openapi_generator_2.yaml +0 -0
  69. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswag/test/test_openapi_generator_3.yaml +0 -0
  70. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswag/test/test_openapi_generator_base_config.yaml +0 -0
  71. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswag.egg-info/dependency_links.txt +0 -0
  72. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswag.egg-info/top_level.txt +0 -0
  73. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswagcl/include/zswagcl/export.hpp +0 -0
  74. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswagcl/include/zswagcl/oaclient.hpp +0 -0
  75. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswagcl/include/zswagcl/private/openapi-parameter-helper.hpp +0 -0
  76. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswagcl/include/zswagcl/private/openapi-parser.hpp +0 -0
  77. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswagcl/src/base64.cpp +0 -0
  78. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswagcl/src/base64.hpp +0 -0
  79. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswagcl/src/oaclient.cpp +0 -0
  80. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswagcl/src/openapi-parameter-helper.cpp +0 -0
  81. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswagcl/test/src/base64.cpp +0 -0
  82. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswagcl/test/src/http-service-format.cpp +0 -0
  83. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswagcl/test/src/main.cpp +0 -0
  84. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswagcl/test/src/oaclient.cpp +0 -0
  85. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswagcl/test/src/openapi-parameter-helper.cpp +0 -0
  86. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswagcl/test/src/service_client_test.zs +0 -0
  87. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswagcl/test/testdata/auth.yaml +0 -0
  88. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswagcl/test/testdata/config-template.json +0 -0
  89. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswagcl/test/testdata/config-with-auth.json +0 -0
  90. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/libs/zswagcl/test/testdata/dummy.json +0 -0
  91. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/requirements.txt +0 -0
  92. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/setup.cfg +0 -0
  93. {zswag-1.9.1.dev5 → zswag-1.9.2.dev14}/setup.py +0 -0
@@ -28,6 +28,8 @@ share/python-wheels/
28
28
  .installed.cfg
29
29
  *.egg
30
30
  MANIFEST
31
+ cmake-build-*
32
+ venv*
31
33
 
32
34
  # PyInstaller
33
35
  # Usually these files are written by a python script from a template
@@ -10,7 +10,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
10
10
  # without requiring CMakeLists.txt updates between releases.
11
11
  # For official releases, this value should match the git tag.
12
12
  if(NOT DEFINED ZSWAG_VERSION)
13
- set(ZSWAG_VERSION 1.9.0)
13
+ set(ZSWAG_VERSION 1.9.1)
14
14
  endif()
15
15
 
16
16
  option(ZSWAG_BUILD_WHEELS "Enable zswag whl-output to WHEEL_DEPLOY_DIRECTORY." ON)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zswag
3
- Version: 1.9.1.dev5
3
+ Version: 1.9.2.dev14
4
4
  Summary: OpenAPI service transport layer for zserio.
5
5
  Home-page: https://github.com/klebert-engineering/zswag
6
6
  Author: Klebert Engineering
@@ -705,11 +705,13 @@ http-settings:
705
705
  basic-auth: # Basic auth credentials for matching requests.
706
706
  user: johndoe
707
707
  keychain: keychain-service-string
708
+ password: cleartext-password # alternative to keychain
708
709
  proxy: # Proxy settings for matching requests.
709
710
  host: localhost
710
711
  port: 8888
711
712
  user: test
712
713
  keychain: ...
714
+ password: cleartext-password # alternative to keychain
713
715
  cookies: # Additional Cookies for matching requests.
714
716
  key: value
715
717
  headers: # Additional Headers for matching requests.
@@ -717,6 +719,14 @@ http-settings:
717
719
  query: # Additional Query parameters for matching requests.
718
720
  key: value
719
721
  api-key: value # API Key as required by OpenAPI config - see description below.
722
+ oauth2:
723
+ clientId: my-client-id # For OAuth2 client-credentials auth
724
+ clientSecretKeychain: keychain-service-string # Prefer a keychain entry over cleartext
725
+ clientSecret: cleartext-secret # discouraged, use keychain
726
+ tokenUrl: https://issuer.example.com/oauth/token # optional override; defaults to tokenUrl from OpenAPI
727
+ refreshUrl: https://issuer.example.com/oauth/token # optional override; defaults to refreshUrl from OpenAPI, then tokenUrl
728
+ audience: https://api.example.com/ # Optional (some providers require it)
729
+ scope: ["orders.read", ...] # Optional scope override; defaults to OpenAPI setting per operation
720
730
  ```
721
731
 
722
732
  **Note:** For `proxy` configs, the credentials are optional.
@@ -965,10 +975,11 @@ constructor in C++/Python with the relevant detail.
965
975
 
966
976
  #### Component Support
967
977
 
968
- | Feature | C++ Client | Python Client | OAServer | zswag.gen |
969
- | ------------------ | ---------- | ------------- | -------- | --------- |
970
- | `HTTP Basic-Auth` `HTTP Bearer-Auth` `Cookie API-Key` `Header API-Key` `Query API-Key` | ✔️ | ✔️ | ✔️(**) | ✔️ |
971
- | `OpenID Connect` `OAuth2` | ❌️ | ❌️ | ✔️(**) | ❌️ |
978
+ | Feature | C++ Client | Python Client | OAServer | zswag.gen |
979
+ |----------------------------------------------------------------------------------------| ---------- | ------------- | -------- | --------- |
980
+ | `HTTP Basic-Auth` `HTTP Bearer-Auth` `Cookie API-Key` `Header API-Key` `Query API-Key` | ✔️ | ✔️ | ✔️(**) | ✔️ |
981
+ | `OAuth2[clientCredentials]` | ✔️ | ✔️ | ✔️(**) | ✔️ |
982
+ | `OpenID Connect` `OAuth2[authorizationCode]` `OAuth2[implicit]` `OAuth2[password]` | ❌️ | ❌️ | ✔️(**) | ❌️ |
972
983
 
973
984
  **(\*\*)**: The server support for all authentication schemes depends on your
974
985
  configuration of the WSGI server (Apache/Nginx/...) which wraps the zswag Flask app.
@@ -690,11 +690,13 @@ http-settings:
690
690
  basic-auth: # Basic auth credentials for matching requests.
691
691
  user: johndoe
692
692
  keychain: keychain-service-string
693
+ password: cleartext-password # alternative to keychain
693
694
  proxy: # Proxy settings for matching requests.
694
695
  host: localhost
695
696
  port: 8888
696
697
  user: test
697
698
  keychain: ...
699
+ password: cleartext-password # alternative to keychain
698
700
  cookies: # Additional Cookies for matching requests.
699
701
  key: value
700
702
  headers: # Additional Headers for matching requests.
@@ -702,6 +704,14 @@ http-settings:
702
704
  query: # Additional Query parameters for matching requests.
703
705
  key: value
704
706
  api-key: value # API Key as required by OpenAPI config - see description below.
707
+ oauth2:
708
+ clientId: my-client-id # For OAuth2 client-credentials auth
709
+ clientSecretKeychain: keychain-service-string # Prefer a keychain entry over cleartext
710
+ clientSecret: cleartext-secret # discouraged, use keychain
711
+ tokenUrl: https://issuer.example.com/oauth/token # optional override; defaults to tokenUrl from OpenAPI
712
+ refreshUrl: https://issuer.example.com/oauth/token # optional override; defaults to refreshUrl from OpenAPI, then tokenUrl
713
+ audience: https://api.example.com/ # Optional (some providers require it)
714
+ scope: ["orders.read", ...] # Optional scope override; defaults to OpenAPI setting per operation
705
715
  ```
706
716
 
707
717
  **Note:** For `proxy` configs, the credentials are optional.
@@ -950,10 +960,11 @@ constructor in C++/Python with the relevant detail.
950
960
 
951
961
  #### Component Support
952
962
 
953
- | Feature | C++ Client | Python Client | OAServer | zswag.gen |
954
- | ------------------ | ---------- | ------------- | -------- | --------- |
955
- | `HTTP Basic-Auth` `HTTP Bearer-Auth` `Cookie API-Key` `Header API-Key` `Query API-Key` | ✔️ | ✔️ | ✔️(**) | ✔️ |
956
- | `OpenID Connect` `OAuth2` | ❌️ | ❌️ | ✔️(**) | ❌️ |
963
+ | Feature | C++ Client | Python Client | OAServer | zswag.gen |
964
+ |----------------------------------------------------------------------------------------| ---------- | ------------- | -------- | --------- |
965
+ | `HTTP Basic-Auth` `HTTP Bearer-Auth` `Cookie API-Key` `Header API-Key` `Query API-Key` | ✔️ | ✔️ | ✔️(**) | ✔️ |
966
+ | `OAuth2[clientCredentials]` | ✔️ | ✔️ | ✔️(**) | ✔️ |
967
+ | `OpenID Connect` `OAuth2[authorizationCode]` `OAuth2[implicit]` `OAuth2[password]` | ❌️ | ❌️ | ✔️(**) | ❌️ |
957
968
 
958
969
  **(\*\*)**: The server support for all authentication schemes depends on your
959
970
  configuration of the WSGI server (Apache/Nginx/...) which wraps the zswag Flask app.
@@ -77,7 +77,7 @@ if(NOT TARGET yaml-cpp)
77
77
  FetchContent_Declare(
78
78
  yaml-cpp
79
79
  GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git
80
- GIT_TAG "2f86d13775d119edbb69af52e5f566fd65c6953b" # Latest commit on master as of Dec 2024
80
+ GIT_TAG "aa8d4e4750ec9fe9f8cc680eb90f1b15955c817e" # Latest commit on master as of Dec 2024
81
81
  GIT_SHALLOW TRUE
82
82
  )
83
83
  set(YAML_CPP_BUILD_TESTS OFF CACHE BOOL "" FORCE)
@@ -10,12 +10,15 @@ ProcessorCount(N)
10
10
  # Set OpenSSL install directory
11
11
  set(OPENSSL_INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR}/_deps/openssl-install)
12
12
 
13
+ # Use find_package so Perl executable can be set in cmake
14
+ find_package(Perl REQUIRED)
15
+
13
16
  # Determine platform-specific OpenSSL configuration
14
17
  if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
15
18
  if(CMAKE_SIZEOF_VOID_P EQUAL 8)
16
- set(OPENSSL_CONFIGURE_COMMAND perl ${OPENSSL_SOURCE_DIR}/Configure VC-WIN64A --prefix=${OPENSSL_INSTALL_DIR} --openssldir=${OPENSSL_INSTALL_DIR}/ssl no-shared no-tests)
19
+ set(OPENSSL_CONFIGURE_COMMAND ${PERL_EXECUTABLE} ${OPENSSL_SOURCE_DIR}/Configure VC-WIN64A --prefix=${OPENSSL_INSTALL_DIR} --openssldir=${OPENSSL_INSTALL_DIR}/ssl no-shared no-tests)
17
20
  else()
18
- set(OPENSSL_CONFIGURE_COMMAND perl ${OPENSSL_SOURCE_DIR}/Configure VC-WIN32 --prefix=${OPENSSL_INSTALL_DIR} --openssldir=${OPENSSL_INSTALL_DIR}/ssl no-shared no-tests)
21
+ set(OPENSSL_CONFIGURE_COMMAND ${PERL_EXECUTABLE} ${OPENSSL_SOURCE_DIR}/Configure VC-WIN32 --prefix=${OPENSSL_INSTALL_DIR} --openssldir=${OPENSSL_INSTALL_DIR}/ssl no-shared no-tests)
19
22
  endif()
20
23
  set(OPENSSL_BUILD_COMMAND nmake)
21
24
  set(OPENSSL_INSTALL_COMMAND nmake install_sw)
@@ -46,6 +46,16 @@ struct Config
46
46
  std::string keychain;
47
47
  };
48
48
 
49
+ struct OAuth2 {
50
+ std::string clientId;
51
+ std::string clientSecret;
52
+ std::string clientSecretKeychain;
53
+ std::string tokenUrlOverride; // optional
54
+ std::string refreshUrlOverride; // optional
55
+ std::string audience; // optional
56
+ std::vector<std::string> scopesOverride; // optional
57
+ };
58
+
49
59
  std::optional<std::string> scope;
50
60
  std::regex urlPattern;
51
61
  std::string urlPatternString;
@@ -53,6 +63,7 @@ struct Config
53
63
  std::map<std::string, std::string> cookies;
54
64
  std::optional<BasicAuthentication> auth;
55
65
  std::optional<Proxy> proxy;
66
+ std::optional<OAuth2> oauth2;
56
67
  std::optional<std::string> apiKey;
57
68
  Headers headers;
58
69
  Query query;
@@ -180,6 +180,25 @@ YAML::Node configToNode(Config const& config) {
180
180
  if (config.apiKey)
181
181
  result["api-key"] = *config.apiKey;
182
182
 
183
+ if (config.oauth2) {
184
+ YAML::Node oauth2Node;
185
+ if (!config.oauth2->clientId.empty())
186
+ oauth2Node["clientId"] = config.oauth2->clientId;
187
+ if (!config.oauth2->clientSecret.empty())
188
+ oauth2Node["clientSecret"] = config.oauth2->clientSecret;
189
+ if (!config.oauth2->clientSecretKeychain.empty())
190
+ oauth2Node["clientSecretKeychain"] = config.oauth2->clientSecretKeychain;
191
+ if (!config.oauth2->tokenUrlOverride.empty())
192
+ oauth2Node["tokenUrl"] = config.oauth2->tokenUrlOverride;
193
+ if (!config.oauth2->refreshUrlOverride.empty())
194
+ oauth2Node["refreshUrl"] = config.oauth2->refreshUrlOverride;
195
+ if (!config.oauth2->audience.empty())
196
+ oauth2Node["audience"] = config.oauth2->audience;
197
+ if (!config.oauth2->scopesOverride.empty())
198
+ oauth2Node["scopes"] = config.oauth2->scopesOverride;
199
+ result["oauth2"] = oauth2Node;
200
+ }
201
+
183
202
  return result;
184
203
  }
185
204
 
@@ -221,6 +240,27 @@ Config configFromNode(YAML::Node const& node)
221
240
  if (auto apiKey = node["api-key"])
222
241
  conf.apiKey = apiKey.as<std::string>();
223
242
 
243
+ if (auto oauth2Node = node["oauth2"]) {
244
+ Config::OAuth2 oauth2;
245
+ if (auto v = oauth2Node["clientId"])
246
+ oauth2.clientId = v.as<std::string>();
247
+ if (auto v = oauth2Node["clientSecret"])
248
+ oauth2.clientSecret = v.as<std::string>();
249
+ if (auto v = oauth2Node["clientSecretKeychain"])
250
+ oauth2.clientSecretKeychain = v.as<std::string>();
251
+ if (auto v = oauth2Node["tokenUrl"])
252
+ oauth2.tokenUrlOverride = v.as<std::string>();
253
+ if (auto v = oauth2Node["refreshUrl"])
254
+ oauth2.refreshUrlOverride = v.as<std::string>();
255
+ if (auto v = oauth2Node["audience"])
256
+ oauth2.audience = v.as<std::string>();
257
+ if (auto v = oauth2Node["scope"]) {
258
+ for (auto const& scope : v)
259
+ oauth2.scopesOverride.emplace_back(scope.as<std::string>());
260
+ }
261
+ conf.oauth2 = oauth2;
262
+ }
263
+
224
264
  return conf;
225
265
  }
226
266
  }
@@ -520,5 +560,23 @@ Config& Config::operator |= (Config const& other) {
520
560
  proxy = other.proxy;
521
561
  if (other.apiKey)
522
562
  apiKey = other.apiKey;
563
+ if (other.oauth2) {
564
+ if (!oauth2)
565
+ oauth2.emplace();
566
+ if (!other.oauth2->clientId.empty())
567
+ oauth2->clientId = other.oauth2->clientId;
568
+ if (!other.oauth2->clientSecretKeychain.empty())
569
+ oauth2->clientSecretKeychain = other.oauth2->clientSecretKeychain;
570
+ if (!other.oauth2->clientSecret.empty())
571
+ oauth2->clientSecret = other.oauth2->clientSecret;
572
+ if (!other.oauth2->tokenUrlOverride.empty())
573
+ oauth2->tokenUrlOverride = other.oauth2->tokenUrlOverride;
574
+ if (!other.oauth2->refreshUrlOverride.empty())
575
+ oauth2->refreshUrlOverride = other.oauth2->refreshUrlOverride;
576
+ if (!other.oauth2->audience.empty())
577
+ oauth2->audience = other.oauth2->audience;
578
+ if (!other.oauth2->scopesOverride.empty())
579
+ oauth2->scopesOverride = other.oauth2->scopesOverride;
580
+ }
523
581
  return *this;
524
582
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zswag
3
- Version: 1.9.1.dev5
3
+ Version: 1.9.2.dev14
4
4
  Summary: OpenAPI service transport layer for zserio.
5
5
  Home-page: https://github.com/klebert-engineering/zswag
6
6
  Author: Klebert Engineering
@@ -705,11 +705,13 @@ http-settings:
705
705
  basic-auth: # Basic auth credentials for matching requests.
706
706
  user: johndoe
707
707
  keychain: keychain-service-string
708
+ password: cleartext-password # alternative to keychain
708
709
  proxy: # Proxy settings for matching requests.
709
710
  host: localhost
710
711
  port: 8888
711
712
  user: test
712
713
  keychain: ...
714
+ password: cleartext-password # alternative to keychain
713
715
  cookies: # Additional Cookies for matching requests.
714
716
  key: value
715
717
  headers: # Additional Headers for matching requests.
@@ -717,6 +719,14 @@ http-settings:
717
719
  query: # Additional Query parameters for matching requests.
718
720
  key: value
719
721
  api-key: value # API Key as required by OpenAPI config - see description below.
722
+ oauth2:
723
+ clientId: my-client-id # For OAuth2 client-credentials auth
724
+ clientSecretKeychain: keychain-service-string # Prefer a keychain entry over cleartext
725
+ clientSecret: cleartext-secret # discouraged, use keychain
726
+ tokenUrl: https://issuer.example.com/oauth/token # optional override; defaults to tokenUrl from OpenAPI
727
+ refreshUrl: https://issuer.example.com/oauth/token # optional override; defaults to refreshUrl from OpenAPI, then tokenUrl
728
+ audience: https://api.example.com/ # Optional (some providers require it)
729
+ scope: ["orders.read", ...] # Optional scope override; defaults to OpenAPI setting per operation
720
730
  ```
721
731
 
722
732
  **Note:** For `proxy` configs, the credentials are optional.
@@ -965,10 +975,11 @@ constructor in C++/Python with the relevant detail.
965
975
 
966
976
  #### Component Support
967
977
 
968
- | Feature | C++ Client | Python Client | OAServer | zswag.gen |
969
- | ------------------ | ---------- | ------------- | -------- | --------- |
970
- | `HTTP Basic-Auth` `HTTP Bearer-Auth` `Cookie API-Key` `Header API-Key` `Query API-Key` | ✔️ | ✔️ | ✔️(**) | ✔️ |
971
- | `OpenID Connect` `OAuth2` | ❌️ | ❌️ | ✔️(**) | ❌️ |
978
+ | Feature | C++ Client | Python Client | OAServer | zswag.gen |
979
+ |----------------------------------------------------------------------------------------| ---------- | ------------- | -------- | --------- |
980
+ | `HTTP Basic-Auth` `HTTP Bearer-Auth` `Cookie API-Key` `Header API-Key` `Query API-Key` | ✔️ | ✔️ | ✔️(**) | ✔️ |
981
+ | `OAuth2[clientCredentials]` | ✔️ | ✔️ | ✔️(**) | ✔️ |
982
+ | `OpenID Connect` `OAuth2[authorizationCode]` `OAuth2[implicit]` `OAuth2[password]` | ❌️ | ❌️ | ✔️(**) | ❌️ |
972
983
 
973
984
  **(\*\*)**: The server support for all authentication schemes depends on your
974
985
  configuration of the WSGI server (Apache/Nginx/...) which wraps the zswag Flask app.
@@ -60,23 +60,31 @@ libs/zswagcl/include/zswagcl/export.hpp
60
60
  libs/zswagcl/include/zswagcl/oaclient.hpp
61
61
  libs/zswagcl/include/zswagcl/private/openapi-client.hpp
62
62
  libs/zswagcl/include/zswagcl/private/openapi-config.hpp
63
+ libs/zswagcl/include/zswagcl/private/openapi-oauth.hpp
63
64
  libs/zswagcl/include/zswagcl/private/openapi-parameter-helper.hpp
64
65
  libs/zswagcl/include/zswagcl/private/openapi-parser.hpp
66
+ libs/zswagcl/include/zswagcl/private/openapi-security.hpp
65
67
  libs/zswagcl/src/base64.cpp
66
68
  libs/zswagcl/src/base64.hpp
67
69
  libs/zswagcl/src/oaclient.cpp
68
70
  libs/zswagcl/src/openapi-client.cpp
69
71
  libs/zswagcl/src/openapi-config.cpp
72
+ libs/zswagcl/src/openapi-oauth.cpp
70
73
  libs/zswagcl/src/openapi-parameter-helper.cpp
71
74
  libs/zswagcl/src/openapi-parser.cpp
75
+ libs/zswagcl/src/openapi-security.cpp
72
76
  libs/zswagcl/test/CMakeLists.txt
73
77
  libs/zswagcl/test/src/base64.cpp
74
78
  libs/zswagcl/test/src/http-service-format.cpp
75
79
  libs/zswagcl/test/src/main.cpp
76
80
  libs/zswagcl/test/src/oaclient.cpp
81
+ libs/zswagcl/test/src/oauth2-integration-test.cpp
82
+ libs/zswagcl/test/src/oauth2-test.cpp
77
83
  libs/zswagcl/test/src/openapi-parameter-helper.cpp
78
84
  libs/zswagcl/test/src/service_client_test.zs
79
85
  libs/zswagcl/test/testdata/auth.yaml
80
86
  libs/zswagcl/test/testdata/config-template.json
81
87
  libs/zswagcl/test/testdata/config-with-auth.json
82
- libs/zswagcl/test/testdata/dummy.json
88
+ libs/zswagcl/test/testdata/dummy.json
89
+ libs/zswagcl/test/testdata/oauth2-openapi.yaml
90
+ libs/zswagcl/test/testdata/oauth2-settings.yaml
@@ -1,4 +1,4 @@
1
- pyzswagcl==1.9.1.dev5
1
+ pyzswagcl==1.9.2.dev14
2
2
  connexion~=2.14.2
3
3
  requests
4
4
  zserio<3.0.0
@@ -10,13 +10,17 @@ add_library(zswagcl SHARED
10
10
  include/zswagcl/private/openapi-parameter-helper.hpp
11
11
  include/zswagcl/private/openapi-parser.hpp
12
12
  include/zswagcl/oaclient.hpp
13
+ include/zswagcl/private/openapi-security.hpp
14
+ include/zswagcl/private/openapi-oauth.hpp
13
15
 
14
16
  src/base64.cpp
15
17
  src/openapi-client.cpp
16
18
  src/openapi-config.cpp
17
19
  src/openapi-parameter-helper.cpp
18
20
  src/openapi-parser.cpp
19
- src/oaclient.cpp)
21
+ src/openapi-security.cpp
22
+ src/oaclient.cpp
23
+ src/openapi-oauth.cpp)
20
24
 
21
25
  target_link_libraries(zswagcl
22
26
  PUBLIC
@@ -5,6 +5,7 @@
5
5
  #include "openapi-parser.hpp"
6
6
  #include "openapi-config.hpp"
7
7
  #include "openapi-parameter-helper.hpp"
8
+ #include "openapi-security.hpp"
8
9
 
9
10
  #include "httpcl/uri.hpp"
10
11
  #include "httpcl/http-client.hpp"
@@ -17,6 +18,7 @@ class OpenAPIClient
17
18
  public:
18
19
  OpenAPIConfig config_;
19
20
  httpcl::Config httpConfig_;
21
+ AuthRegistry authHandlers_;
20
22
 
21
23
  OpenAPIClient(OpenAPIConfig config,
22
24
  httpcl::Config httpConfig,
@@ -21,46 +21,45 @@ struct OpenAPIConfig
21
21
  Header
22
22
  };
23
23
 
24
- struct SecurityScheme {
25
- std::string id;
26
- explicit SecurityScheme(std::string id);
27
- virtual ~SecurityScheme() = default;
28
- virtual bool checkOrApply(httpcl::Config& config, std::string& err) const = 0;
24
+ enum class SecuritySchemeType {
25
+ HttpBasic,
26
+ HttpBearer,
27
+ ApiKeyQuery,
28
+ ApiKeyHeader,
29
+ ApiKeyCookie,
30
+ OAuth2ClientCredentials
29
31
  };
30
32
 
33
+ struct SecurityScheme
34
+ {
35
+ SecuritySchemeType type;
36
+ std::string oauthTokenUrl; // For oauth2
37
+ std::string oauthRefreshUrl; // For oauth2
38
+ std::map<std::string, std::string> oauthScopes; // Scope -> Description
39
+ std::string apiKeyName; // Header/Query/Cookie name
40
+ std::string id; // Scheme name for introspection/debugging
41
+ };
31
42
  using SecuritySchemePtr = std::shared_ptr<SecurityScheme>;
32
43
 
33
44
  /**
34
- * Security Scheme References
45
+ * Security Scheme References by operations.
35
46
  *
36
47
  * Disjunctive normal form ([A [AND B]+][ OR C [AND D]+]+) of required
37
48
  * security schemes. An empty vector is used to encode that no auth
38
49
  * scheme is required.
39
50
  */
40
- using SecurityAlternatives = std::vector<std::vector<SecuritySchemePtr>>;
51
+ struct SecurityRequirement
52
+ {
53
+ SecuritySchemePtr scheme;
54
+ // For each operation, a subset of scopes may be defined which are required for it.
55
+ std::vector<std::string> scopes;
56
+ };
57
+ using SecurityAlternative = std::vector<SecurityRequirement>;
58
+ using SecurityAlternatives = std::vector<SecurityAlternative>;
41
59
 
42
60
  /**
43
61
  * Supported Authentication Schemes
44
62
  */
45
- struct BasicAuth : public SecurityScheme {
46
- explicit BasicAuth(std::string id);
47
- bool checkOrApply(httpcl::Config& config, std::string& err) const override;
48
- };
49
- struct BearerAuth : public SecurityScheme {
50
- explicit BearerAuth(std::string id);
51
- bool checkOrApply(httpcl::Config& config, std::string& err) const override;
52
- };
53
- struct APIKeyAuth : public SecurityScheme {
54
- explicit APIKeyAuth(std::string id, ParameterLocation location, std::string keyName);
55
- bool checkOrApply(httpcl::Config& config, std::string& err) const override;
56
- ParameterLocation location = ParameterLocation::Header;
57
- std::string keyName;
58
- };
59
- struct CookieAuth : public SecurityScheme {
60
- explicit CookieAuth(std::string id, std::string cookieName);
61
- bool checkOrApply(httpcl::Config& config, std::string& err) const override;
62
- std::string cookieName;
63
- };
64
63
 
65
64
  struct Parameter {
66
65
  ParameterLocation location = ParameterLocation::Query;
@@ -0,0 +1,60 @@
1
+ #pragma once
2
+
3
+ #include "openapi-security.hpp"
4
+
5
+ namespace zswagcl
6
+ {
7
+
8
+ class OAuth2ClientCredentialsHandler final : public ISecurityHandler
9
+ {
10
+ public:
11
+ bool satisfy(
12
+ const OpenAPIConfig::SecurityRequirement& req,
13
+ AuthContext const& ctx,
14
+ std::string& mismatchReason) override;
15
+
16
+ private:
17
+ // OAuth2 grant type constants
18
+ static constexpr const char* GRANT_TYPE_CLIENT_CREDENTIALS = "client_credentials";
19
+ static constexpr const char* GRANT_TYPE_REFRESH_TOKEN = "refresh_token";
20
+
21
+ struct TokenKey
22
+ {
23
+ std::string tokenUrl, clientId, audience, scopeKey;
24
+ bool operator==(const TokenKey& o) const
25
+ {
26
+ return tokenUrl == o.tokenUrl && clientId == o.clientId && audience == o.audience &&
27
+ scopeKey == o.scopeKey;
28
+ }
29
+ };
30
+
31
+ struct TokenKeyHash
32
+ {
33
+ size_t operator()(const TokenKey& k) const
34
+ {
35
+ std::hash<std::string> H;
36
+ return H(k.tokenUrl) ^ H(k.clientId) ^ H(k.audience) ^ H(k.scopeKey);
37
+ }
38
+ };
39
+
40
+ struct MintedToken
41
+ {
42
+ std::string accessToken;
43
+ std::string refreshToken; // may be empty (most CC flows don’t return one)
44
+ std::chrono::steady_clock::time_point expiresAt;
45
+ };
46
+
47
+ std::shared_mutex m_;
48
+ std::unordered_map<TokenKey, MintedToken, TokenKeyHash> cache_;
49
+
50
+ // Method for both token fetch and refresh requests
51
+ MintedToken requestToken(
52
+ AuthContext const& httpCtx,
53
+ const httpcl::Config::OAuth2& oauthConfig,
54
+ const std::string& resolvedTokenUrl,
55
+ const std::string& grantType,
56
+ const std::vector<std::string>& resolvedScopes = {},
57
+ const std::string& refreshToken = "");
58
+ };
59
+
60
+ } // namespace zswagcl
@@ -0,0 +1,56 @@
1
+ #pragma once
2
+
3
+ #include <memory>
4
+ #include <unordered_map>
5
+
6
+ #include "httpcl/http-client.hpp"
7
+ #include "httpcl/http-settings.hpp"
8
+ #include "openapi-config.hpp"
9
+
10
+ namespace zswagcl
11
+ {
12
+
13
+ struct AuthContext
14
+ {
15
+ httpcl::IHttpClient& httpClient;
16
+ const std::string& targetResourceUri;
17
+ httpcl::Settings const& httpSettings;
18
+ httpcl::Config& resultHttpConfigWithAuthorization;
19
+ };
20
+
21
+ class ISecurityHandler
22
+ {
23
+ public:
24
+ virtual ~ISecurityHandler() = default;
25
+
26
+ /**
27
+ * Checks if current HTTP settings can satisfy the derived Security scheme handler.
28
+ * Potentially modifies the AuthContext::httpConfig.
29
+ */
30
+ virtual bool satisfy(
31
+ const OpenAPIConfig::SecurityRequirement& req,
32
+ AuthContext const& ctx,
33
+ std::string& mismatchReason) = 0;
34
+ };
35
+
36
+ class AuthRegistry
37
+ {
38
+ public:
39
+ /**
40
+ * Construct an auth registry. Instantiates and registers handlers for all known security types.
41
+ */
42
+ AuthRegistry();
43
+
44
+ /**
45
+ * Try to satisfy an OR of AND-sets. Throws if none can be satisfied.
46
+ */
47
+ void satisfySecurity(
48
+ const OpenAPIConfig::SecurityAlternatives& alts,
49
+ AuthContext const& ctx);
50
+
51
+ private:
52
+ std::unordered_map<OpenAPIConfig::SecuritySchemeType, std::shared_ptr<ISecurityHandler>>
53
+ handlers_;
54
+ };
55
+
56
+ } // namespace zswagcl
@@ -79,40 +79,6 @@ void resolveHeaderAndQueryParameters(httpcl::Config& result,
79
79
  }
80
80
  }
81
81
 
82
- void checkSecurityAlternativesAndApplyApiKey(OpenAPIConfig::SecurityAlternatives const& alts, httpcl::Config& conf)
83
- {
84
- if (alts.empty())
85
- return; // Nothing to check
86
-
87
- bool anyAlternativeMatched = false;
88
- std::stringstream error;
89
- error << "The provided HTTP configuration does not satisfy authentication requirements:\n";
90
-
91
- int i = 0;
92
- for (auto const& schemeSet : alts)
93
- {
94
- bool matched = true;
95
-
96
- for (auto const& scheme : schemeSet) {
97
- std::string reasonForMismatch;
98
- if (!scheme->checkOrApply(conf, reasonForMismatch)) {
99
- error << " In security configuration " << i << ": " << reasonForMismatch << "\n";
100
- matched = false;
101
- break;
102
- }
103
- }
104
-
105
- if (matched) {
106
- anyAlternativeMatched = true;
107
- break;
108
- }
109
- ++i;
110
- }
111
-
112
- if (!anyAlternativeMatched)
113
- throw std::runtime_error(error.str());
114
- }
115
-
116
82
  }
117
83
 
118
84
  OpenAPIClient::OpenAPIClient(OpenAPIConfig config,
@@ -167,11 +133,15 @@ std::string OpenAPIClient::call(const std::string& methodIdent,
167
133
  // Throws if the http config does not fulfill any allowed scheme.
168
134
  if (method.security) {
169
135
  httpcl::log().debug("{} Checking required security schemes for method ...", debugContext);
170
- checkSecurityAlternativesAndApplyApiKey(*method.security, httpConfig);
136
+ authHandlers_.satisfySecurity(
137
+ *method.security,
138
+ {*client_, builtUri, settings_, httpConfig});
171
139
  }
172
140
  else {
173
141
  httpcl::log().debug("{} Checking default security scheme ...", debugContext);
174
- checkSecurityAlternativesAndApplyApiKey(config_.defaultSecurityScheme, httpConfig);
142
+ authHandlers_.satisfySecurity(
143
+ config_.defaultSecurityScheme,
144
+ {*client_, builtUri, settings_, httpConfig});
175
145
  }
176
146
 
177
147
  const auto& httpMethod = method.httpMethod;
@@ -0,0 +1,14 @@
1
+ #include <regex>
2
+
3
+ #include "zswagcl/private/openapi-config.hpp"
4
+ #include "zswagcl/export.hpp"
5
+ #include "stx/format.h"
6
+
7
+ namespace zswagcl
8
+ {
9
+
10
+ ZSWAGCL_EXPORT const std::string ZSERIO_OBJECT_CONTENT_TYPE = "application/x-zserio-object";
11
+ ZSWAGCL_EXPORT const std::string ZSERIO_REQUEST_PART = "x-zserio-request-part";
12
+ ZSWAGCL_EXPORT const std::string ZSERIO_REQUEST_PART_WHOLE = "*";
13
+
14
+ }