From 9bdd73dda9cae49391bd030aa38040fb2247a5dd Mon Sep 17 00:00:00 2001 From: Hannes Herrmann Date: Sat, 24 Aug 2024 12:01:06 +0200 Subject: [PATCH 1/4] fix(feature-flags): allow to use api without interceptor feature --- Cargo.toml | 2 +- src/api/clients.rs | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index af5c289..5b2cacc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ credentials = ["dep:jsonwebtoken", "dep:openidconnect", "dep:reqwest", "dep:serd ## new convenience functions to create a gRPC client with interceptors. ## The interceptors provide easy access to an authenticated ZITADEL API client. ## The interceptors work with the credentials from this crate. -interceptors = ["api", "credentials", "dep:time", "dep:tokio"] +interceptors = ["credentials", "dep:time", "dep:tokio"] ## This feature enables caching of the OIDC discovery and introspection results. ## By default, only the in-memory cache is available. To use a different cache, diff --git a/src/api/clients.rs b/src/api/clients.rs index f0a44d5..9b0d324 100644 --- a/src/api/clients.rs +++ b/src/api/clients.rs @@ -11,6 +11,7 @@ use tonic::service::Interceptor; use tonic::transport::{Channel, Endpoint}; use tonic::{Request, Status}; +#[cfg(feature = "interceptors")] use crate::api::interceptors::{AccessTokenInterceptor, ServiceAccountInterceptor}; use crate::api::zitadel::oidc::v2beta::oidc_service_client::OidcServiceClient; use crate::api::zitadel::org::v2beta::organization_service_client::OrganizationServiceClient; @@ -18,6 +19,7 @@ use crate::api::zitadel::session::v2beta::session_service_client::SessionService use crate::api::zitadel::settings::v2beta::settings_service_client::SettingsServiceClient; use crate::api::zitadel::system::v1::system_service_client::SystemServiceClient; use crate::api::zitadel::user::v2beta::user_service_client::UserServiceClient; +#[cfg(feature = "interceptors")] use crate::credentials::{AuthenticationOptions, ServiceAccount}; use super::zitadel::{ @@ -33,6 +35,7 @@ custom_error! { ConnectionError = "could not connect to provided endpoint", } +#[cfg(feature = "interceptors")] enum AuthType { None, AccessToken(String), @@ -56,6 +59,7 @@ impl ChainedInterceptor { } } + #[cfg(feature = "interceptors")] pub(crate) fn add_interceptor(mut self, interceptor: Box) -> Self { self.interceptors.push(interceptor); self @@ -77,6 +81,7 @@ impl Interceptor for ChainedInterceptor { /// an authentication method. pub struct ClientBuilder { api_endpoint: String, + #[cfg(feature = "interceptors")] auth_type: AuthType, } @@ -85,6 +90,7 @@ impl ClientBuilder { pub fn new(api_endpoint: &str) -> Self { Self { api_endpoint: api_endpoint.to_string(), + #[cfg(feature = "interceptors")] auth_type: AuthType::None, } } @@ -313,7 +319,9 @@ impl ClientBuilder { } fn get_chained_interceptor(&self) -> ChainedInterceptor { + #[allow(unused_mut)] let mut interceptor = ChainedInterceptor::new(); + #[cfg(feature = "interceptors")] match &self.auth_type { AuthType::AccessToken(token) => { interceptor = From a5c90a62367b0415a49a189d33d19e03750a3dca Mon Sep 17 00:00:00 2001 From: Hannes Herrmann Date: Sat, 24 Aug 2024 12:07:18 +0200 Subject: [PATCH 2/4] feat: hide each api behind features + switch to release v2 apis --- .gitignore | 1 + Cargo.toml | 89 ++++++++++++++++++++++++++++++++++++++++++++-- buf.gen.yaml | 6 ++-- src/api/clients.rs | 40 +++++++++++++++------ src/lib.rs | 2 +- 5 files changed, 120 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index 84a5eb3..0f66c11 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ Cargo.lock temp* src/api/generated +.idea diff --git a/Cargo.toml b/Cargo.toml index 5b2cacc..3d0cd08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,8 +21,38 @@ default = [] ## Feature that enables support for the [actix framework](https://actix.rs/). actix = ["credentials", "oidc", "dep:actix-web"] -## The API feature enables the gRPC service clients to access the ZITADEL API. -api = ["dep:prost", "dep:prost-types", "dep:tonic", "dep:tonic-types", "dep:pbjson-types"] +## The API feature enables all gRPC service clients to access the ZITADEL API. +api = [ + "api-admin-v1", + "api-auth-v1", + "api-management-v1", + "api-system-v1", + "api-oidc-v2", + "api-org-v2", + "api-session-v2", + "api-settings-v2", + "api-user-v2" +] +## The API feature enables all gRPC service clients to access the respective ZITADEL API. +api-admin-v1 = ["api-common", "zitadel-admin-v1" ] +## The API feature enables all gRPC service clients to access the respective ZITADEL API. +api-auth-v1 = ["api-common", "zitadel-auth-v1" ] +## The API feature enables all gRPC service clients to access the respective ZITADEL API. +api-management-v1 = ["api-common", "zitadel-v1-v1" ] +## The API feature enables all gRPC service clients to access the respective ZITADEL API. +api-system-v1 = ["api-common", "zitadel-system-v1", "zitadel-authn-v1" ] +## The API feature enables all gRPC service clients to access the respective ZITADEL API. +api-oidc-v2 = ["api-common", "zitadel-oidc-v2" ] +## The API feature enables all gRPC service clients to access the respective ZITADEL API. +api-org-v2 = ["api-common", "zitadel-org-v2", "zitadel-user-v2" ] +## The API feature enables all gRPC service clients to access the respective ZITADEL API. +api-session-v2 = ["api-common", "zitadel-session-v2" ] +## The API feature enables all gRPC service clients to access the respective ZITADEL API. +api-settings-v2 = ["api-common", "zitadel-settings-v2" ] +## The API feature enables all gRPC service clients to access the respective ZITADEL API. +api-user-v2 = ["api-common", "zitadel-user-v2" ] +api-common = ["dep:prost", "dep:prost-types", "dep:tonic", "dep:tonic-types", "dep:pbjson-types" ] + ## Feature that enables support for the [axum framework](https://docs.rs/axum/latest/axum/). axum = ["credentials", "oidc", "dep:axum", "dep:axum-extra"] @@ -54,6 +84,59 @@ oidc = ["credentials", "dep:base64-compat"] ## Refer to the rocket module for more information. rocket = ["credentials", "oidc", "dep:rocket"] +# @@protoc_deletion_point(features) +# This section is automatically generated by protoc-gen-prost-crate. +# Changes in this area may be lost on regeneration. +proto_full = ["zitadel-action-v1","zitadel-admin-v1","zitadel-app-v1","zitadel-auth-v1","zitadel-authn-v1","zitadel-change-v1","zitadel-event-v1","zitadel-feature-v1","zitadel-feature-v2","zitadel-feature-v2beta","zitadel-idp-v1","zitadel-idp-v2","zitadel-instance-v1","zitadel-management-v1","zitadel-member-v1","zitadel-metadata-v1","zitadel-milestone-v1","zitadel-object-v2","zitadel-object-v2beta","zitadel-object-v3alpha","zitadel-oidc-v2","zitadel-oidc-v2beta","zitadel-org-v1","zitadel-org-v2","zitadel-org-v2beta","zitadel-policy-v1","zitadel-project-v1","zitadel-protoc_gen_zitadel-v2","zitadel-quota-v1","zitadel-resources-action-v3alpha","zitadel-resources-object-v3alpha","zitadel-resources-webkey-v3alpha","zitadel-session-v2","zitadel-session-v2beta","zitadel-settings-object-v3alpha","zitadel-settings-v1","zitadel-settings-v2","zitadel-settings-v2beta","zitadel-system-v1","zitadel-text-v1","zitadel-user-schema-v3alpha","zitadel-user-v1","zitadel-user-v2","zitadel-user-v2beta","zitadel-user-v3alpha","zitadel-v1","zitadel-v1-v1"] +"zitadel-action-v1" = ["zitadel-v1"] +"zitadel-admin-v1" = ["zitadel-event-v1","zitadel-idp-v1","zitadel-instance-v1","zitadel-management-v1","zitadel-member-v1","zitadel-milestone-v1","zitadel-org-v1","zitadel-policy-v1","zitadel-settings-v1","zitadel-text-v1","zitadel-v1","zitadel-v1-v1"] +"zitadel-app-v1" = ["zitadel-v1"] +"zitadel-auth-v1" = ["zitadel-change-v1","zitadel-idp-v1","zitadel-metadata-v1","zitadel-org-v1","zitadel-policy-v1","zitadel-user-v1","zitadel-v1"] +"zitadel-authn-v1" = ["zitadel-v1"] +"zitadel-change-v1" = ["zitadel-v1"] +"zitadel-event-v1" = ["zitadel-v1"] +"zitadel-feature-v1" = [] +"zitadel-feature-v2" = ["zitadel-object-v2"] +"zitadel-feature-v2beta" = ["zitadel-object-v2beta"] +"zitadel-idp-v1" = ["zitadel-v1"] +"zitadel-idp-v2" = ["zitadel-object-v2"] +"zitadel-instance-v1" = ["zitadel-v1"] +"zitadel-management-v1" = ["zitadel-action-v1","zitadel-app-v1","zitadel-authn-v1","zitadel-change-v1","zitadel-idp-v1","zitadel-member-v1","zitadel-metadata-v1","zitadel-org-v1","zitadel-policy-v1","zitadel-project-v1","zitadel-text-v1","zitadel-user-v1","zitadel-v1"] +"zitadel-member-v1" = ["zitadel-user-v1","zitadel-v1"] +"zitadel-metadata-v1" = ["zitadel-v1"] +"zitadel-milestone-v1" = [] +"zitadel-object-v2" = [] +"zitadel-object-v2beta" = [] +"zitadel-object-v3alpha" = [] +"zitadel-oidc-v2" = ["zitadel-object-v2"] +"zitadel-oidc-v2beta" = ["zitadel-object-v2beta"] +"zitadel-org-v1" = ["zitadel-v1"] +"zitadel-org-v2" = ["zitadel-object-v2"] +"zitadel-org-v2beta" = ["zitadel-object-v2beta"] +"zitadel-policy-v1" = ["zitadel-idp-v1","zitadel-v1"] +"zitadel-project-v1" = ["zitadel-v1"] +"zitadel-protoc_gen_zitadel-v2" = [] +"zitadel-quota-v1" = [] +"zitadel-resources-action-v3alpha" = ["zitadel-object-v3alpha","zitadel-resources-object-v3alpha"] +"zitadel-resources-object-v3alpha" = ["zitadel-object-v3alpha"] +"zitadel-resources-webkey-v3alpha" = ["zitadel-object-v3alpha","zitadel-resources-object-v3alpha"] +"zitadel-session-v2" = ["zitadel-object-v2","zitadel-v1"] +"zitadel-session-v2beta" = ["zitadel-object-v2beta","zitadel-v1"] +"zitadel-settings-object-v3alpha" = ["zitadel-object-v3alpha"] +"zitadel-settings-v1" = ["zitadel-v1"] +"zitadel-settings-v2" = ["zitadel-object-v2"] +"zitadel-settings-v2beta" = ["zitadel-object-v2beta"] +"zitadel-system-v1" = ["zitadel-feature-v1","zitadel-instance-v1","zitadel-member-v1","zitadel-quota-v1","zitadel-v1"] +"zitadel-text-v1" = ["zitadel-v1"] +"zitadel-user-schema-v3alpha" = ["zitadel-object-v2"] +"zitadel-user-v1" = ["zitadel-v1"] +"zitadel-user-v2" = ["zitadel-object-v2"] +"zitadel-user-v2beta" = ["zitadel-object-v2beta"] +"zitadel-user-v3alpha" = ["zitadel-object-v2"] +"zitadel-v1" = [] +"zitadel-v1-v1" = ["zitadel-authn-v1","zitadel-idp-v1","zitadel-management-v1","zitadel-org-v1","zitadel-v1"] +# @@protoc_insertion_point(features) + [dependencies] actix-web = { version = "4.5.1", optional = true } async-trait = { version = "0.1.80", optional = true } @@ -90,4 +173,4 @@ tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread"] } tower = { version = "0.4.13" } [package.metadata.docs.rs] -all-features = true +all-features = true \ No newline at end of file diff --git a/buf.gen.yaml b/buf.gen.yaml index 2d508bd..8664ea3 100644 --- a/buf.gen.yaml +++ b/buf.gen.yaml @@ -15,8 +15,8 @@ plugins: - extern_path=.google.protobuf=::pbjson_types - no_server - name: prost-crate - out: src/api/generated + out: . strategy: all opt: - - no_features - - include_file=mod.rs + - gen_crate + - include_file=src/api/generated/mod.rs diff --git a/src/api/clients.rs b/src/api/clients.rs index 9b0d324..fa24b8f 100644 --- a/src/api/clients.rs +++ b/src/api/clients.rs @@ -13,21 +13,30 @@ use tonic::{Request, Status}; #[cfg(feature = "interceptors")] use crate::api::interceptors::{AccessTokenInterceptor, ServiceAccountInterceptor}; -use crate::api::zitadel::oidc::v2beta::oidc_service_client::OidcServiceClient; -use crate::api::zitadel::org::v2beta::organization_service_client::OrganizationServiceClient; -use crate::api::zitadel::session::v2beta::session_service_client::SessionServiceClient; -use crate::api::zitadel::settings::v2beta::settings_service_client::SettingsServiceClient; + +#[cfg(feature = "api-oidc-v2")] +use crate::api::zitadel::oidc::v2::oidc_service_client::OidcServiceClient; +#[cfg(feature = "api-org-v2")] +use crate::api::zitadel::org::v2::organization_service_client::OrganizationServiceClient; +#[cfg(feature = "api-session-v2")] +use crate::api::zitadel::session::v2::session_service_client::SessionServiceClient; +#[cfg(feature = "api-settings-v2")] +use crate::api::zitadel::settings::v2::settings_service_client::SettingsServiceClient; +#[cfg(feature = "api-user-v2")] +use crate::api::zitadel::user::v2::user_service_client::UserServiceClient; + +#[cfg(feature = "api-admin-v1")] +use crate::api::zitadel::admin::v1::admin_service_client::AdminServiceClient; +#[cfg(feature = "api-auth-v1")] +use crate::api::zitadel::auth::v1::auth_service_client::AuthServiceClient; +#[cfg(feature = "api-management-v1")] +use crate::api::zitadel::management::v1::management_service_client::ManagementServiceClient; +#[cfg(feature = "api-system-v1")] use crate::api::zitadel::system::v1::system_service_client::SystemServiceClient; -use crate::api::zitadel::user::v2beta::user_service_client::UserServiceClient; + #[cfg(feature = "interceptors")] use crate::credentials::{AuthenticationOptions, ServiceAccount}; -use super::zitadel::{ - admin::v1::admin_service_client::AdminServiceClient, - auth::v1::auth_service_client::AuthServiceClient, - management::v1::management_service_client::ManagementServiceClient, -}; - custom_error! { /// Errors that may occur when creating a client. pub ClientError @@ -133,6 +142,7 @@ impl ClientBuilder { /// This function returns a [`ClientError`] if the provided API endpoint /// cannot be parsed into a valid URL or if the connection to the endpoint /// is not possible. + #[cfg(feature = "api-admin-v1")] pub async fn build_admin_client( &self, ) -> Result>, Box> @@ -154,6 +164,7 @@ impl ClientBuilder { /// This function returns a [`ClientError`] if the provided API endpoint /// cannot be parsed into a valid URL or if the connection to the endpoint /// is not possible. + #[cfg(feature = "api-auth-v1")] pub async fn build_auth_client( &self, ) -> Result>, Box> @@ -175,6 +186,7 @@ impl ClientBuilder { /// This function returns a [`ClientError`] if the provided API endpoint /// cannot be parsed into a valid URL or if the connection to the endpoint /// is not possible. + #[cfg(feature = "api-management-v1")] pub async fn build_management_client( &self, ) -> Result< @@ -198,6 +210,7 @@ impl ClientBuilder { /// This function returns a [`ClientError`] if the provided API endpoint /// cannot be parsed into a valid URL or if the connection to the endpoint /// is not possible. + #[cfg(feature = "api-oidc-v2")] pub async fn build_oidc_client( &self, ) -> Result>, Box> @@ -219,6 +232,7 @@ impl ClientBuilder { /// This function returns a [`ClientError`] if the provided API endpoint /// cannot be parsed into a valid URL or if the connection to the endpoint /// is not possible. + #[cfg(feature = "api-org-v2")] pub async fn build_organization_client( &self, ) -> Result< @@ -242,6 +256,7 @@ impl ClientBuilder { /// This function returns a [`ClientError`] if the provided API endpoint /// cannot be parsed into a valid URL or if the connection to the endpoint /// is not possible. + #[cfg(feature = "api-session-v2")] pub async fn build_session_client( &self, ) -> Result>, Box> @@ -263,6 +278,7 @@ impl ClientBuilder { /// This function returns a [`ClientError`] if the provided API endpoint /// cannot be parsed into a valid URL or if the connection to the endpoint /// is not possible. + #[cfg(feature = "api-settings-v2")] pub async fn build_settings_client( &self, ) -> Result< @@ -286,6 +302,7 @@ impl ClientBuilder { /// This function returns a [`ClientError`] if the provided API endpoint /// cannot be parsed into a valid URL or if the connection to the endpoint /// is not possible. + #[cfg(feature = "api-system-v1")] pub async fn build_system_client( &self, ) -> Result>, Box> @@ -307,6 +324,7 @@ impl ClientBuilder { /// This function returns a [`ClientError`] if the provided API endpoint /// cannot be parsed into a valid URL or if the connection to the endpoint /// is not possible. + #[cfg(feature = "api-user-v2")] pub async fn build_user_client( &self, ) -> Result>, Box> diff --git a/src/lib.rs b/src/lib.rs index b28d771..05e1129 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,7 +37,7 @@ #[cfg(feature = "actix")] pub mod actix; -#[cfg(feature = "api")] +#[cfg(feature = "api-common")] pub mod api; #[cfg(feature = "axum")] pub mod axum; From 03f6f329672292dfdefe146c8b931269b1d1e599 Mon Sep 17 00:00:00 2001 From: Hannes Herrmann Date: Sat, 24 Aug 2024 12:13:23 +0200 Subject: [PATCH 3/4] fix(deps): upgrade grpc packages --- Cargo.toml | 11 +++++------ src/api/clients.rs | 9 ++++++++- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3d0cd08..526eeac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -147,9 +147,9 @@ custom_error = "1.9.2" document-features = { version = "0.2.8", optional = true } jsonwebtoken = { version = "9.3.0", optional = true } openidconnect = { version = "3.5.0", optional = true } -pbjson-types = { version = "0.6", optional = true } -prost = { version = "0.12.4", optional = true } -prost-types = { version = "0.12.4", optional = true } +pbjson-types = { version = "0.7.0", optional = true } +prost = { version = "0.13.1", optional = true } +prost-types = { version = "0.13.1", optional = true } reqwest = { version = "0.11.27", features = ["json", "rustls-tls"], default-features = false, optional = true } rocket = { version = "0.5.0", optional = true } serde = { version = "1.0.200", features = ["derive"], optional = true } @@ -160,12 +160,11 @@ tokio = { version = "1.37.0", optional = true, features = [ "macros", "rt-multi-thread", ] } -tonic = { version = "0.11", features = [ +tonic = { version = "0.12.1", features = [ "tls", "tls-roots", - "tls-roots-common", ], optional = true } -tonic-types = { version = "0.11", optional = true } +tonic-types = { version = "0.12.1", optional = true } [dev-dependencies] chrono = "0.4.38" diff --git a/src/api/clients.rs b/src/api/clients.rs index fa24b8f..96bc4a2 100644 --- a/src/api/clients.rs +++ b/src/api/clients.rs @@ -8,7 +8,7 @@ use std::error::Error; use custom_error::custom_error; use tonic::codegen::InterceptedService; use tonic::service::Interceptor; -use tonic::transport::{Channel, Endpoint}; +use tonic::transport::{Channel, ClientTlsConfig, Endpoint}; use tonic::{Request, Status}; #[cfg(feature = "interceptors")] @@ -42,6 +42,7 @@ custom_error! { pub ClientError InvalidUrl = "the provided url is invalid", ConnectionError = "could not connect to provided endpoint", + TlsInitializationError = "could not setup tls connection", } #[cfg(feature = "interceptors")] @@ -363,6 +364,12 @@ impl ClientBuilder { async fn get_channel(api_endpoint: &str) -> Result { Endpoint::from_shared(api_endpoint.to_string()) .map_err(|_| ClientError::InvalidUrl)? + .tls_config( + ClientTlsConfig::default() + .assume_http2(true) + .with_native_roots(), + ) + .map_err(|_| ClientError::TlsInitializationError)? .connect() .await .map_err(|_| ClientError::ConnectionError) From e31e4ccfa1d12837bf701ce7e160bf014983829d Mon Sep 17 00:00:00 2001 From: Hannes Herrmann Date: Mon, 26 Aug 2024 13:49:17 +0200 Subject: [PATCH 4/4] fix(clippy): fix clippy warnings --- src/oidc/introspection/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oidc/introspection/mod.rs b/src/oidc/introspection/mod.rs index 62fa3e0..ad0a28d 100644 --- a/src/oidc/introspection/mod.rs +++ b/src/oidc/introspection/mod.rs @@ -36,7 +36,7 @@ custom_error! { /// by default and [additional claims](https://zitadel.com/docs/apis/openidoauth/claims) /// if requested by scope: /// - When scope contains `urn:zitadel:iam:user:resourceowner`, the fields prefixed with -/// `resource_owner_` are set. +/// `resource_owner_` are set. /// - When scope contains `urn:zitadel:iam:user:metadata`, the metadata hashmap will be /// filled with the user metadata. #[derive(Clone, Debug, Serialize, Deserialize, Default)]