From c1e8bf04100391f194b413fc56b56c6101ed278b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20B=C3=BChler?= Date: Fri, 15 Mar 2024 15:30:26 +0100 Subject: [PATCH] feat: upgrade deps and add other proto clients (#223) This upgrades the dependencies, removes renovate (since all deps are wider ranges) and adds other service clients --- .github/workflows/release.yml | 12 +- .github/workflows/testing.yml | 40 +-- .gitmodules | 15 - .pubignore | 2 + .releaserc.json | 2 +- analysis_options.yaml | 1 + buf.gen.yaml | 7 + external/googleapis | 1 - external/grpc-gateway | 1 - external/protobuf | 1 - external/protoc-gen-validate | 1 - external/zitadel | 1 - justfile | 15 + lib/src/credentials/service_account.dart | 26 +- lib/src/grpc/clients_io.dart | 392 +++++++++++++++++++++-- lib/src/grpc/clients_web.dart | 341 ++++++++++++++++++-- pubspec.yaml | 25 +- renovate.json | 5 - test/clients_io_test.dart | 34 +- test/clients_web_test.dart | 34 +- tool/barrel_file_generator.dart | 77 +++++ tool/generate_barrel_files.dart | 64 ---- tool/generate_grpc.sh | 24 -- 23 files changed, 887 insertions(+), 234 deletions(-) delete mode 100644 .gitmodules create mode 100644 buf.gen.yaml delete mode 160000 external/googleapis delete mode 160000 external/grpc-gateway delete mode 160000 external/protobuf delete mode 160000 external/protoc-gen-validate delete mode 160000 external/zitadel create mode 100644 justfile delete mode 100644 renovate.json create mode 100644 tool/barrel_file_generator.dart delete mode 100755 tool/generate_barrel_files.dart delete mode 100755 tool/generate_grpc.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ec1d1b0..49aeecf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,17 +13,19 @@ jobs: steps: - uses: actions/checkout@v3 with: - submodules: true token: ${{ secrets.DEPLOY_TOKEN }} - uses: dart-lang/setup-dart@v1 with: sdk: stable - - uses: arduino/setup-protoc@v1 + - uses: bufbuild/buf-setup-action@v1 with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - - name: enable protoc dart - run: dart pub global activate protoc_plugin + github_token: ${{ secrets.GITHUB_TOKEN }} + - uses: extractions/setup-just@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + - run: just install-tools - run: dart pub get + - run: just - name: release uses: cycjimmy/semantic-release-action@v3 with: diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index cd2a255..c736a66 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -7,31 +7,25 @@ on: jobs: lint_and_test: - strategy: - fail-fast: true - matrix: - sdk: - - 2.18.0 - - stable name: Linting and Testing runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - with: - submodules: true - uses: dart-lang/setup-dart@v1 with: - sdk: ${{ matrix.sdk }} + sdk: stable + - uses: bufbuild/buf-setup-action@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + - uses: extractions/setup-just@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} - uses: arduino/setup-protoc@v1 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - - name: enable protoc dart - run: dart pub global activate protoc_plugin - - name: build gRPC - run: ./tool/generate_grpc.sh + - run: just install-tools - run: dart pub get - - name: build barrel files - run: ./tool/generate_barrel_files.dart + - run: just - run: dart analyze - run: dart test @@ -54,21 +48,21 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - with: - submodules: true - uses: dart-lang/setup-dart@v1 with: sdk: stable + - uses: bufbuild/buf-setup-action@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + - uses: extractions/setup-just@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} - uses: arduino/setup-protoc@v1 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - - name: enable protoc dart - run: dart pub global activate protoc_plugin + - run: just install-tools - run: dart pub get - - name: build gRPC - run: ./tool/generate_grpc.sh - - name: build barrel files - run: ./tool/generate_barrel_files.dart + - run: just - run: echo '### Publish Pub Package Result' >> $GITHUB_STEP_SUMMARY - run: dart pub publish --dry-run >> $GITHUB_STEP_SUMMARY continue-on-error: true diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 6e96a41..0000000 --- a/.gitmodules +++ /dev/null @@ -1,15 +0,0 @@ -[submodule "external/zitadel"] - path = external/zitadel - url = https://github.com/zitadel/zitadel -[submodule "external/googleapis"] - path = external/googleapis - url = https://github.com/googleapis/googleapis -[submodule "external/grpc-gateway"] - path = external/grpc-gateway - url = https://github.com/grpc-ecosystem/grpc-gateway -[submodule "external/protoc-gen-validate"] - path = external/protoc-gen-validate - url = https://github.com/envoyproxy/protoc-gen-validate -[submodule "external/protobuf"] - path = external/protobuf - url = https://github.com/protocolbuffers/protobuf diff --git a/.pubignore b/.pubignore index 6022161..ca05b7c 100644 --- a/.pubignore +++ b/.pubignore @@ -9,4 +9,6 @@ pubspec.lock external/ renovate.json +.releaserc.json analysis_options.yaml +justfile diff --git a/.releaserc.json b/.releaserc.json index 81974e7..c2c3888 100644 --- a/.releaserc.json +++ b/.releaserc.json @@ -15,7 +15,7 @@ "@semantic-release/exec", { "verifyConditionsCmd": "if [ -z \"$CREDENTIALS\" ]; then exit 1; fi && mkdir -p ~/.config/dart && echo \"$CREDENTIALS\" > ~/.config/dart/pub-credentials.json", - "prepareCmd": "./tool/generate_grpc.sh && ./tool/generate_barrel_files.dart && sed -i 's/^version: .*$/version: ${nextRelease.version}/' pubspec.yaml", + "prepareCmd": "sed -i 's/^version: .*$/version: ${nextRelease.version}/' pubspec.yaml", "publishCmd": "dart pub publish -f" } ], diff --git a/analysis_options.yaml b/analysis_options.yaml index d76e899..8cc6ead 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -16,3 +16,4 @@ analyzer: - lib/src/grpc/generated - external/ - example/ + - tool/ diff --git a/buf.gen.yaml b/buf.gen.yaml new file mode 100644 index 0000000..1230425 --- /dev/null +++ b/buf.gen.yaml @@ -0,0 +1,7 @@ +version: v1 +managed: + enabled: true +plugins: + - plugin: dart + out: lib/src/grpc/generated + opt: grpc diff --git a/external/googleapis b/external/googleapis deleted file mode 160000 index 6782d08..0000000 --- a/external/googleapis +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6782d08a0fa56c39070f9d1337d629eaecfe2fc1 diff --git a/external/grpc-gateway b/external/grpc-gateway deleted file mode 160000 index 8abb8b6..0000000 --- a/external/grpc-gateway +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8abb8b68c02c67b729f7ed2e6cac8a8637282b89 diff --git a/external/protobuf b/external/protobuf deleted file mode 160000 index 39f7631..0000000 --- a/external/protobuf +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 39f7631f4784051ceb8a86cacb66dca4fcc6b3f2 diff --git a/external/protoc-gen-validate b/external/protoc-gen-validate deleted file mode 160000 index 4966ec4..0000000 --- a/external/protoc-gen-validate +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4966ec495858351565af36604f0115e1be5aacaf diff --git a/external/zitadel b/external/zitadel deleted file mode 160000 index 057ac92..0000000 --- a/external/zitadel +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 057ac926c8822c980068ee852dfaa0522ebe1d48 diff --git a/justfile b/justfile new file mode 100644 index 0000000..b196a3e --- /dev/null +++ b/justfile @@ -0,0 +1,15 @@ +proto_dir := "./lib/src/grpc/generated" + +default: clean generate-grpc + +clean: + @rm -rf {{proto_dir}} + +generate-grpc: && generate-barrel-files + buf generate https://github.com/zitadel/zitadel.git --path ./proto/zitadel --include-imports --include-wkt + +generate-barrel-files: + dart tool/barrel_file_generator.dart + +install-tools: + dart pub global activate protoc_plugin diff --git a/lib/src/credentials/service_account.dart b/lib/src/credentials/service_account.dart index 9f7792d..27db1e9 100644 --- a/lib/src/credentials/service_account.dart +++ b/lib/src/credentials/service_account.dart @@ -50,7 +50,9 @@ class ServiceAccount { } static String _discoveryEndpoint(String endpoint) => - endpoint.endsWith(discoveryEndpointPath) ? endpoint : '$endpoint$discoveryEndpointPath'; + endpoint.endsWith(discoveryEndpointPath) + ? endpoint + : '$endpoint$discoveryEndpointPath'; /// Create a JSON map from the application. Map toJson() => { @@ -92,7 +94,8 @@ class ServiceAccount { /// 'https://issuer.zitadel.ch', /// AuthenticationOptions(apiAccess: true)); /// ``` - Future authenticate(String audience, [AuthenticationOptions? options]) async { + Future authenticate(String audience, + [AuthenticationOptions? options]) async { options ??= AuthenticationOptions(); final discoveryResponse = await get( @@ -114,7 +117,8 @@ class ServiceAccount { ); if (response.statusCode > 299) { - throw Exception('An error occurred. Status Code: ${response.statusCode}.'); + throw Exception( + 'An error occurred. Status Code: ${response.statusCode}.'); } final json = jsonDecode(utf8.decode(response.bodyBytes)); @@ -127,13 +131,18 @@ class ServiceAccount { 'iss': userId, 'sub': userId, 'iat': DateTime.now().toUtc().millisecondsSinceEpoch ~/ 1000, - 'exp': DateTime.now().toUtc().add(Duration(hours: 1)).millisecondsSinceEpoch ~/ 1000, + 'exp': DateTime.now() + .toUtc() + .add(Duration(hours: 1)) + .millisecondsSinceEpoch ~/ + 1000, 'aud': audience, }); final builder = JsonWebSignatureBuilder(); builder.jsonContent = claims.toJson(); - builder.addRecipient(JsonWebKey.fromPem(key, keyId: keyId), algorithm: 'RS256'); + builder.addRecipient(JsonWebKey.fromPem(key, keyId: keyId), + algorithm: 'RS256'); final signature = builder.build(); @@ -181,8 +190,9 @@ class AuthenticationOptions { String createScopes() => [ 'openid', if (apiAccess) apiAccessScope, - for (var scope in additionalScopes) scope, - for (var audience in projectAudiences) 'urn:zitadel:iam:org:project:id:$audience:aud', - for (var role in roles) 'urn:zitadel:iam:org:project:role:$role', + for (final scope in additionalScopes) scope, + for (final audience in projectAudiences) + 'urn:zitadel:iam:org:project:id:$audience:aud', + for (final role in roles) 'urn:zitadel:iam:org:project:role:$role', ].join(' '); } diff --git a/lib/src/grpc/clients_io.dart b/lib/src/grpc/clients_io.dart index 518aec5..269484f 100644 --- a/lib/src/grpc/clients_io.dart +++ b/lib/src/grpc/clients_io.dart @@ -3,9 +3,32 @@ import 'package:grpc/grpc.dart'; import 'generated/zitadel/admin.pbgrpc.dart'; import 'generated/zitadel/auth.pbgrpc.dart'; import 'generated/zitadel/management.pbgrpc.dart'; +import 'generated/zitadel/oidc/v2beta/oidc_service.pbgrpc.dart'; +import 'generated/zitadel/org/v2beta/org_service.pbgrpc.dart'; +import 'generated/zitadel/session/v2beta/session_service.pbgrpc.dart'; +import 'generated/zitadel/settings/v2beta/settings_service.pbgrpc.dart'; +import 'generated/zitadel/system.pbgrpc.dart'; +import 'generated/zitadel/user/v2beta/user_service.pbgrpc.dart'; + +typedef _ClientCtor = T Function(ClientChannel channel, + {CallOptions? options, Iterable? interceptors}); + +T _createClient(String apiEndpoint, _ClientCtor ctor, + {int port = 443, + Map? metadata, + Iterable? metadataProviders, + Iterable? interceptors}) { + final channel = ClientChannel(apiEndpoint, + port: port, options: ChannelOptions(idleTimeout: Duration(minutes: 1))); + return ctor(channel, + options: CallOptions( + metadata: metadata, + providers: metadataProviders?.toList(growable: false)), + interceptors: interceptors); +} /// Create a new [AdminServiceClient] to access the -/// [Admin API](https://docs.zitadel.com/docs/apis/proto/admin) of ZITADEL. +/// Admin API of ZITADEL. /// /// The client can be configured by adding default metadata and/or interceptors. /// Also with the [metadataProviders] one can add a list of providers that will @@ -41,18 +64,21 @@ import 'generated/zitadel/management.pbgrpc.dart'; /// ); /// ``` AdminServiceClient createAdminClient(String apiEndpoint, - {int port = 443, - Map? metadata, - Iterable? metadataProviders, - Iterable? interceptors}) { - final channel = ClientChannel(apiEndpoint, port: port, options: ChannelOptions(idleTimeout: Duration(minutes: 1))); - return AdminServiceClient(channel, - options: CallOptions(metadata: metadata, providers: metadataProviders?.toList(growable: false)), - interceptors: interceptors); -} + {int port = 443, + Map? metadata, + Iterable? metadataProviders, + Iterable? interceptors}) => + _createClient( + apiEndpoint, + AdminServiceClient.new, + port: port, + metadata: metadata, + metadataProviders: metadataProviders, + interceptors: interceptors, + ); /// Create a new [AuthServiceClient] to access the -/// [Admin API](https://docs.zitadel.com/docs/apis/proto/admin) of ZITADEL. +/// Auth API of ZITADEL. /// /// The client can be configured by adding default metadata and/or interceptors. /// Also with the [metadataProviders] one can add a list of providers that will @@ -88,18 +114,21 @@ AdminServiceClient createAdminClient(String apiEndpoint, /// ); /// ``` AuthServiceClient createAuthClient(String apiEndpoint, - {int port = 443, - Map? metadata, - Iterable? metadataProviders, - Iterable? interceptors}) { - final channel = ClientChannel(apiEndpoint, port: port, options: ChannelOptions(idleTimeout: Duration(minutes: 1))); - return AuthServiceClient(channel, - options: CallOptions(metadata: metadata, providers: metadataProviders?.toList(growable: false)), - interceptors: interceptors); -} + {int port = 443, + Map? metadata, + Iterable? metadataProviders, + Iterable? interceptors}) => + _createClient( + apiEndpoint, + AuthServiceClient.new, + port: port, + metadata: metadata, + metadataProviders: metadataProviders, + interceptors: interceptors, + ); /// Create a new [ManagementServiceClient] to access the -/// [Admin API](https://docs.zitadel.com/docs/apis/proto/admin) of ZITADEL. +/// Management API of ZITADEL. /// /// The client can be configured by adding default metadata and/or interceptors. /// Also with the [metadataProviders] one can add a list of providers that will @@ -135,12 +164,315 @@ AuthServiceClient createAuthClient(String apiEndpoint, /// ); /// ``` ManagementServiceClient createManagementClient(String apiEndpoint, - {int port = 443, - Map? metadata, - Iterable? metadataProviders, - Iterable? interceptors}) { - final channel = ClientChannel(apiEndpoint, port: port, options: ChannelOptions(idleTimeout: Duration(minutes: 1))); - return ManagementServiceClient(channel, - options: CallOptions(metadata: metadata, providers: metadataProviders?.toList(growable: false)), - interceptors: interceptors); -} + {int port = 443, + Map? metadata, + Iterable? metadataProviders, + Iterable? interceptors}) => + _createClient( + apiEndpoint, + ManagementServiceClient.new, + port: port, + metadata: metadata, + metadataProviders: metadataProviders, + interceptors: interceptors, + ); + +/// Create a new [OIDCServiceClient] to access the +/// OIDC API of ZITADEL. +/// +/// The client can be configured by adding default metadata and/or interceptors. +/// Also with the [metadataProviders] one can add a list of providers that will +/// be called for each RPC call. For example, the provider may attach access tokens from static +/// or service account sources. +/// +/// ### Example +/// +/// Simple client: +/// ```dart +/// final client = createOIDCClient('api.zitadel.ch'); +/// ``` +/// +/// Client with static access token: +/// ```dart +/// final client = createOIDCClient( +/// 'api.zitadel.ch', +/// metadataProviders: [accessTokenProvider('token')], +/// ); +/// ``` +/// +/// Client with service account access token: +/// ```dart +/// final client = createOIDCClient( +/// 'api.zitadel.ch', +/// metadataProviders: [ +/// serviceAccountProvider( +/// 'https://audience.zitadel.ch', +/// serviceAccount, +/// AuthenticationOptions(apiAccess: true), +/// ), +/// ], +/// ); +/// ``` +OIDCServiceClient createOIDCClient(String apiEndpoint, + {int port = 443, + Map? metadata, + Iterable? metadataProviders, + Iterable? interceptors}) => + _createClient( + apiEndpoint, + OIDCServiceClient.new, + port: port, + metadata: metadata, + metadataProviders: metadataProviders, + interceptors: interceptors, + ); + +/// Create a new [OrganizationServiceClient] to access the +/// Organization API of ZITADEL. +/// +/// The client can be configured by adding default metadata and/or interceptors. +/// Also with the [metadataProviders] one can add a list of providers that will +/// be called for each RPC call. For example, the provider may attach access tokens from static +/// or service account sources. +/// +/// ### Example +/// +/// Simple client: +/// ```dart +/// final client = createOrganizationClient('api.zitadel.ch'); +/// ``` +/// +/// Client with static access token: +/// ```dart +/// final client = createOrganizationClient( +/// 'api.zitadel.ch', +/// metadataProviders: [accessTokenProvider('token')], +/// ); +/// ``` +/// +/// Client with service account access token: +/// ```dart +/// final client = createOrganizationClient( +/// 'api.zitadel.ch', +/// metadataProviders: [ +/// serviceAccountProvider( +/// 'https://audience.zitadel.ch', +/// serviceAccount, +/// AuthenticationOptions(apiAccess: true), +/// ), +/// ], +/// ); +/// ``` +OrganizationServiceClient createOrganizationClient(String apiEndpoint, + {int port = 443, + Map? metadata, + Iterable? metadataProviders, + Iterable? interceptors}) => + _createClient( + apiEndpoint, + OrganizationServiceClient.new, + port: port, + metadata: metadata, + metadataProviders: metadataProviders, + interceptors: interceptors, + ); + +/// Create a new [SessionServiceClient] to access the +/// Session API of ZITADEL. +/// +/// The client can be configured by adding default metadata and/or interceptors. +/// Also with the [metadataProviders] one can add a list of providers that will +/// be called for each RPC call. For example, the provider may attach access tokens from static +/// or service account sources. +/// +/// ### Example +/// +/// Simple client: +/// ```dart +/// final client = createSessionClient('api.zitadel.ch'); +/// ``` +/// +/// Client with static access token: +/// ```dart +/// final client = createSessionClient( +/// 'api.zitadel.ch', +/// metadataProviders: [accessTokenProvider('token')], +/// ); +/// ``` +/// +/// Client with service account access token: +/// ```dart +/// final client = createSessionClient( +/// 'api.zitadel.ch', +/// metadataProviders: [ +/// serviceAccountProvider( +/// 'https://audience.zitadel.ch', +/// serviceAccount, +/// AuthenticationOptions(apiAccess: true), +/// ), +/// ], +/// ); +/// ``` +SessionServiceClient createSessionClient(String apiEndpoint, + {int port = 443, + Map? metadata, + Iterable? metadataProviders, + Iterable? interceptors}) => + _createClient( + apiEndpoint, + SessionServiceClient.new, + port: port, + metadata: metadata, + metadataProviders: metadataProviders, + interceptors: interceptors, + ); + +/// Create a new [SettingsServiceClient] to access the +/// Settings API of ZITADEL. +/// +/// The client can be configured by adding default metadata and/or interceptors. +/// Also with the [metadataProviders] one can add a list of providers that will +/// be called for each RPC call. For example, the provider may attach access tokens from static +/// or service account sources. +/// +/// ### Example +/// +/// Simple client: +/// ```dart +/// final client = createSettingsClient('api.zitadel.ch'); +/// ``` +/// +/// Client with static access token: +/// ```dart +/// final client = createSettingsClient( +/// 'api.zitadel.ch', +/// metadataProviders: [accessTokenProvider('token')], +/// ); +/// ``` +/// +/// Client with service account access token: +/// ```dart +/// final client = createSettingsClient( +/// 'api.zitadel.ch', +/// metadataProviders: [ +/// serviceAccountProvider( +/// 'https://audience.zitadel.ch', +/// serviceAccount, +/// AuthenticationOptions(apiAccess: true), +/// ), +/// ], +/// ); +/// ``` +SettingsServiceClient createSettingsClient(String apiEndpoint, + {int port = 443, + Map? metadata, + Iterable? metadataProviders, + Iterable? interceptors}) => + _createClient( + apiEndpoint, + SettingsServiceClient.new, + port: port, + metadata: metadata, + metadataProviders: metadataProviders, + interceptors: interceptors, + ); + +/// Create a new [SystemServiceClient] to access the +/// System API of ZITADEL. +/// +/// The client can be configured by adding default metadata and/or interceptors. +/// Also with the [metadataProviders] one can add a list of providers that will +/// be called for each RPC call. For example, the provider may attach access tokens from static +/// or service account sources. +/// +/// ### Example +/// +/// Simple client: +/// ```dart +/// final client = createSystemClient('api.zitadel.ch'); +/// ``` +/// +/// Client with static access token: +/// ```dart +/// final client = createSystemClient( +/// 'api.zitadel.ch', +/// metadataProviders: [accessTokenProvider('token')], +/// ); +/// ``` +/// +/// Client with service account access token: +/// ```dart +/// final client = createSystemClient( +/// 'api.zitadel.ch', +/// metadataProviders: [ +/// serviceAccountProvider( +/// 'https://audience.zitadel.ch', +/// serviceAccount, +/// AuthenticationOptions(apiAccess: true), +/// ), +/// ], +/// ); +/// ``` +SystemServiceClient createSystemClient(String apiEndpoint, + {int port = 443, + Map? metadata, + Iterable? metadataProviders, + Iterable? interceptors}) => + _createClient( + apiEndpoint, + SystemServiceClient.new, + port: port, + metadata: metadata, + metadataProviders: metadataProviders, + interceptors: interceptors, + ); + +/// Create a new [UserServiceClient] to access the +/// User API of ZITADEL. +/// +/// The client can be configured by adding default metadata and/or interceptors. +/// Also with the [metadataProviders] one can add a list of providers that will +/// be called for each RPC call. For example, the provider may attach access tokens from static +/// or service account sources. +/// +/// ### Example +/// +/// Simple client: +/// ```dart +/// final client = createUserClient('api.zitadel.ch'); +/// ``` +/// +/// Client with static access token: +/// ```dart +/// final client = createUserClient( +/// 'api.zitadel.ch', +/// metadataProviders: [accessTokenProvider('token')], +/// ); +/// ``` +/// +/// Client with service account access token: +/// ```dart +/// final client = createUserClient( +/// 'api.zitadel.ch', +/// metadataProviders: [ +/// serviceAccountProvider( +/// 'https://audience.zitadel.ch', +/// serviceAccount, +/// AuthenticationOptions(apiAccess: true), +/// ), +/// ], +/// ); +/// ``` +UserServiceClient createUserClient(String apiEndpoint, + {int port = 443, + Map? metadata, + Iterable? metadataProviders, + Iterable? interceptors}) => + _createClient( + apiEndpoint, + UserServiceClient.new, + port: port, + metadata: metadata, + metadataProviders: metadataProviders, + interceptors: interceptors, + ); diff --git a/lib/src/grpc/clients_web.dart b/lib/src/grpc/clients_web.dart index 735a5f2..e5fa7ab 100644 --- a/lib/src/grpc/clients_web.dart +++ b/lib/src/grpc/clients_web.dart @@ -3,9 +3,29 @@ import 'package:grpc/grpc_web.dart'; import 'generated/zitadel/admin.pbgrpc.dart'; import 'generated/zitadel/auth.pbgrpc.dart'; import 'generated/zitadel/management.pbgrpc.dart'; +import 'generated/zitadel/oidc/v2beta/oidc_service.pbgrpc.dart'; +import 'generated/zitadel/org/v2beta/org_service.pbgrpc.dart'; +import 'generated/zitadel/session/v2beta/session_service.pbgrpc.dart'; +import 'generated/zitadel/settings/v2beta/settings_service.pbgrpc.dart'; +import 'generated/zitadel/system.pbgrpc.dart'; +import 'generated/zitadel/user/v2beta/user_service.pbgrpc.dart'; + +typedef _ClientCtor = T Function(GrpcWebClientChannel channel, + {CallOptions? options}); + +T _createClient(String apiEndpoint, _ClientCtor ctor, + {int port = 443, + Map? metadata, + Iterable? metadataProviders}) { + final channel = GrpcWebClientChannel.xhr(Uri.parse(apiEndpoint)); + return ctor(channel, + options: CallOptions( + metadata: metadata, + providers: metadataProviders?.toList(growable: false))); +} /// Create a new [AdminServiceClient] to access the -/// [Admin API](https://docs.zitadel.com/docs/apis/proto/admin) of ZITADEL. +/// Admin API of ZITADEL. /// /// The client can be configured by adding default metadata. /// Also with the [metadataProviders] one can add a list of providers that will @@ -41,14 +61,17 @@ import 'generated/zitadel/management.pbgrpc.dart'; /// ); /// ``` AdminServiceClient createAdminClient(String apiEndpoint, - {Map? metadata, Iterable? metadataProviders}) { - final channel = GrpcWebClientChannel.xhr(Uri.parse(apiEndpoint)); - return AdminServiceClient(channel, - options: CallOptions(metadata: metadata, providers: metadataProviders?.toList(growable: false))); -} + {Map? metadata, + Iterable? metadataProviders}) => + _createClient( + apiEndpoint, + AdminServiceClient.new, + metadata: metadata, + metadataProviders: metadataProviders, + ); /// Create a new [AuthServiceClient] to access the -/// [Admin API](https://docs.zitadel.com/docs/apis/proto/admin) of ZITADEL. +/// Auth API of ZITADEL. /// /// The client can be configured by adding default metadata. /// Also with the [metadataProviders] one can add a list of providers that will @@ -84,14 +107,17 @@ AdminServiceClient createAdminClient(String apiEndpoint, /// ); /// ``` AuthServiceClient createAuthClient(String apiEndpoint, - {Map? metadata, Iterable? metadataProviders}) { - final channel = GrpcWebClientChannel.xhr(Uri.parse(apiEndpoint)); - return AuthServiceClient(channel, - options: CallOptions(metadata: metadata, providers: metadataProviders?.toList(growable: false))); -} + {Map? metadata, + Iterable? metadataProviders}) => + _createClient( + apiEndpoint, + AuthServiceClient.new, + metadata: metadata, + metadataProviders: metadataProviders, + ); /// Create a new [ManagementServiceClient] to access the -/// [Admin API](https://docs.zitadel.com/docs/apis/proto/admin) of ZITADEL. +/// Management API of ZITADEL. /// /// The client can be configured by adding default metadata. /// Also with the [metadataProviders] one can add a list of providers that will @@ -127,8 +153,287 @@ AuthServiceClient createAuthClient(String apiEndpoint, /// ); /// ``` ManagementServiceClient createManagementClient(String apiEndpoint, - {Map? metadata, Iterable? metadataProviders}) { - final channel = GrpcWebClientChannel.xhr(Uri.parse(apiEndpoint)); - return ManagementServiceClient(channel, - options: CallOptions(metadata: metadata, providers: metadataProviders?.toList(growable: false))); -} + {Map? metadata, + Iterable? metadataProviders}) => + _createClient( + apiEndpoint, + ManagementServiceClient.new, + metadata: metadata, + metadataProviders: metadataProviders, + ); + +/// Create a new [OIDCServiceClient] to access the +/// OIDC API of ZITADEL. +/// +/// The client can be configured by adding default metadata. +/// Also with the [metadataProviders] one can add a list of providers that will +/// be called for each RPC call. For example, the provider may attach access tokens from static +/// or service account sources. +/// +/// ### Example +/// +/// Simple client: +/// ```dart +/// final client = createOIDCClient('https://api.zitadel.ch'); +/// ``` +/// +/// Client with static access token: +/// ```dart +/// final client = createOIDCClient( +/// 'https://api.zitadel.ch', +/// metadataProviders: [accessTokenProvider('token')], +/// ); +/// ``` +/// +/// Client with service account access token: +/// ```dart +/// final client = createOIDCClient( +/// 'https://api.zitadel.ch', +/// metadataProviders: [ +/// serviceAccountProvider( +/// 'https://audience.zitadel.ch', +/// serviceAccount, +/// AuthenticationOptions(apiAccess: true), +/// ), +/// ], +/// ); +/// ``` +OIDCServiceClient createOIDCClient(String apiEndpoint, + {Map? metadata, + Iterable? metadataProviders}) => + _createClient( + apiEndpoint, + OIDCServiceClient.new, + metadata: metadata, + metadataProviders: metadataProviders, + ); + +/// Create a new [OrganizationServiceClient] to access the +/// Organization API of ZITADEL. +/// +/// The client can be configured by adding default metadata. +/// Also with the [metadataProviders] one can add a list of providers that will +/// be called for each RPC call. For example, the provider may attach access tokens from static +/// or service account sources. +/// +/// ### Example +/// +/// Simple client: +/// ```dart +/// final client = createOrganizationClient('https://api.zitadel.ch'); +/// ``` +/// +/// Client with static access token: +/// ```dart +/// final client = createOrganizationClient( +/// 'https://api.zitadel.ch', +/// metadataProviders: [accessTokenProvider('token')], +/// ); +/// ``` +/// +/// Client with service account access token: +/// ```dart +/// final client = createOrganizationClient( +/// 'https://api.zitadel.ch', +/// metadataProviders: [ +/// serviceAccountProvider( +/// 'https://audience.zitadel.ch', +/// serviceAccount, +/// AuthenticationOptions(apiAccess: true), +/// ), +/// ], +/// ); +/// ``` +OrganizationServiceClient createOrganizationClient(String apiEndpoint, + {Map? metadata, + Iterable? metadataProviders}) => + _createClient( + apiEndpoint, + OrganizationServiceClient.new, + metadata: metadata, + metadataProviders: metadataProviders, + ); + +/// Create a new [SessionServiceClient] to access the +/// Session API of ZITADEL. +/// +/// The client can be configured by adding default metadata. +/// Also with the [metadataProviders] one can add a list of providers that will +/// be called for each RPC call. For example, the provider may attach access tokens from static +/// or service account sources. +/// +/// ### Example +/// +/// Simple client: +/// ```dart +/// final client = createSessionClient('https://api.zitadel.ch'); +/// ``` +/// +/// Client with static access token: +/// ```dart +/// final client = createSessionClient( +/// 'https://api.zitadel.ch', +/// metadataProviders: [accessTokenProvider('token')], +/// ); +/// ``` +/// +/// Client with service account access token: +/// ```dart +/// final client = createSessionClient( +/// 'https://api.zitadel.ch', +/// metadataProviders: [ +/// serviceAccountProvider( +/// 'https://audience.zitadel.ch', +/// serviceAccount, +/// AuthenticationOptions(apiAccess: true), +/// ), +/// ], +/// ); +/// ``` +SessionServiceClient createSessionClient(String apiEndpoint, + {Map? metadata, + Iterable? metadataProviders}) => + _createClient( + apiEndpoint, + SessionServiceClient.new, + metadata: metadata, + metadataProviders: metadataProviders, + ); + +/// Create a new [SettingsServiceClient] to access the +/// Settings API of ZITADEL. +/// +/// The client can be configured by adding default metadata. +/// Also with the [metadataProviders] one can add a list of providers that will +/// be called for each RPC call. For example, the provider may attach access tokens from static +/// or service account sources. +/// +/// ### Example +/// +/// Simple client: +/// ```dart +/// final client = createSettingsClient('https://api.zitadel.ch'); +/// ``` +/// +/// Client with static access token: +/// ```dart +/// final client = createSettingsClient( +/// 'https://api.zitadel.ch', +/// metadataProviders: [accessTokenProvider('token')], +/// ); +/// ``` +/// +/// Client with service account access token: +/// ```dart +/// final client = createSettingsClient( +/// 'https://api.zitadel.ch', +/// metadataProviders: [ +/// serviceAccountProvider( +/// 'https://audience.zitadel.ch', +/// serviceAccount, +/// AuthenticationOptions(apiAccess: true), +/// ), +/// ], +/// ); +/// ``` +SettingsServiceClient createSettingsClient(String apiEndpoint, + {Map? metadata, + Iterable? metadataProviders}) => + _createClient( + apiEndpoint, + SettingsServiceClient.new, + metadata: metadata, + metadataProviders: metadataProviders, + ); + +/// Create a new [SystemServiceClient] to access the +/// System API of ZITADEL. +/// +/// The client can be configured by adding default metadata. +/// Also with the [metadataProviders] one can add a list of providers that will +/// be called for each RPC call. For example, the provider may attach access tokens from static +/// or service account sources. +/// +/// ### Example +/// +/// Simple client: +/// ```dart +/// final client = createSystemClient('https://api.zitadel.ch'); +/// ``` +/// +/// Client with static access token: +/// ```dart +/// final client = createSystemClient( +/// 'https://api.zitadel.ch', +/// metadataProviders: [accessTokenProvider('token')], +/// ); +/// ``` +/// +/// Client with service account access token: +/// ```dart +/// final client = createSystemClient( +/// 'https://api.zitadel.ch', +/// metadataProviders: [ +/// serviceAccountProvider( +/// 'https://audience.zitadel.ch', +/// serviceAccount, +/// AuthenticationOptions(apiAccess: true), +/// ), +/// ], +/// ); +/// ``` +SystemServiceClient createSystemClient(String apiEndpoint, + {Map? metadata, + Iterable? metadataProviders}) => + _createClient( + apiEndpoint, + SystemServiceClient.new, + metadata: metadata, + metadataProviders: metadataProviders, + ); + +/// Create a new [UserServiceClient] to access the +/// User API of ZITADEL. +/// +/// The client can be configured by adding default metadata. +/// Also with the [metadataProviders] one can add a list of providers that will +/// be called for each RPC call. For example, the provider may attach access tokens from static +/// or service account sources. +/// +/// ### Example +/// +/// Simple client: +/// ```dart +/// final client = createUserClient('https://api.zitadel.ch'); +/// ``` +/// +/// Client with static access token: +/// ```dart +/// final client = createUserClient( +/// 'https://api.zitadel.ch', +/// metadataProviders: [accessTokenProvider('token')], +/// ); +/// ``` +/// +/// Client with service account access token: +/// ```dart +/// final client = createUserClient( +/// 'https://api.zitadel.ch', +/// metadataProviders: [ +/// serviceAccountProvider( +/// 'https://audience.zitadel.ch', +/// serviceAccount, +/// AuthenticationOptions(apiAccess: true), +/// ), +/// ], +/// ); +/// ``` +UserServiceClient createUserClient(String apiEndpoint, + {Map? metadata, + Iterable? metadataProviders}) => + _createClient( + apiEndpoint, + UserServiceClient.new, + metadata: metadata, + metadataProviders: metadataProviders, + ); diff --git a/pubspec.yaml b/pubspec.yaml index 4be16f4..c4a59a4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,20 +1,25 @@ name: zitadel version: 3.0.38 description: >- - Dart package for ZITADEL relatet matters. Contains the compiled grpc service clients - to access the ZITADEL API. -repository: https://github.com/caos/zitadel-dart + Dart package for ZITADEL relatet matters. Contains the compiled grpc service + clients to access the ZITADEL API. + +repository: https://github.com/smartive/zitadel-dart + environment: - sdk: '>=2.18.0 <3.0.0' + sdk: '>=3.0.0 <4.0.0' dependencies: - fixnum: ^1.0.1 - grpc: ^3.0.2 - http: ^0.13.5 + fixnum: ^1.0.0 + grpc: ^3.0.0 + http: ^1.0.0 jose: ^0.3.2 - protobuf: ^2.1.0 + protobuf: ^3.0.0 dev_dependencies: - lints: ^2.0.0 - path: ^1.8.3 + build_runner: ^2.4.4 + code_builder: + dart_style: + lints: ^3.0.0 + path: test: ^1.21.6 diff --git a/renovate.json b/renovate.json deleted file mode 100644 index 71c582b..0000000 --- a/renovate.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": ["github>smartive/renovate-config", "github>smartive/renovate-config:with-submodules"], - "ignorePaths": ["**/example/**"] -} diff --git a/test/clients_io_test.dart b/test/clients_io_test.dart index 5c2dda3..b8a7a50 100644 --- a/test/clients_io_test.dart +++ b/test/clients_io_test.dart @@ -1,19 +1,15 @@ @TestOn('vm') import 'package:test/test.dart'; +import 'package:zitadel/api/clients.dart'; import 'package:zitadel/api/zitadel/admin.dart' as admin; import 'package:zitadel/api/zitadel/auth.dart' as auth; -import 'package:zitadel/api/clients.dart'; import 'package:zitadel/api/zitadel/management.dart' as management; import 'test_data.dart'; void main() { group('Auth Client', () { - test('create a client', () { - final _ = createAuthClient(zitadelApiUrl); - }); - test('successfully call API', () async { final client = createAuthClient(zitadelApiUrl); await client.healthz(auth.HealthzRequest()); @@ -21,10 +17,6 @@ void main() { }); group('Admin Client', () { - test('create a client', () { - final _ = createAdminClient(zitadelApiUrl); - }); - test('successfully call API', () async { final client = createAdminClient(zitadelApiUrl); await client.healthz(admin.HealthzRequest()); @@ -32,13 +24,29 @@ void main() { }); group('Management Client', () { - test('create a client', () { - final _ = createManagementClient(zitadelApiUrl); - }); - test('successfully call API', () async { final client = createManagementClient(zitadelApiUrl); await client.healthz(management.HealthzRequest()); }); }); + + group('Client constructors', () { + final ctors = [ + createAdminClient, + createAuthClient, + createManagementClient, + createOIDCClient, + createOrganizationClient, + createSessionClient, + createSettingsClient, + createSystemClient, + createUserClient, + ]; + + for (final ctor in ctors) { + test('create a client with $ctor', () { + final _ = ctor(zitadelApiUrl); + }); + } + }); } diff --git a/test/clients_web_test.dart b/test/clients_web_test.dart index 8c70885..c0a5f90 100644 --- a/test/clients_web_test.dart +++ b/test/clients_web_test.dart @@ -1,19 +1,15 @@ @TestOn('browser') import 'package:test/test.dart'; +import 'package:zitadel/api/clients.dart'; import 'package:zitadel/api/zitadel/admin.dart' as admin; import 'package:zitadel/api/zitadel/auth.dart' as auth; -import 'package:zitadel/api/clients.dart'; import 'package:zitadel/api/zitadel/management.dart' as management; import 'test_data.dart'; void main() { group('Auth Client', () { - test('create a client', () { - final _ = createAuthClient(zitadelAudience); - }); - test('successfully call API', () async { final client = createAuthClient(zitadelAudience); await client.healthz(auth.HealthzRequest()); @@ -21,10 +17,6 @@ void main() { }); group('Admin Client', () { - test('create a client', () { - final _ = createAdminClient(zitadelAudience); - }); - test('successfully call API', () async { final client = createAdminClient(zitadelAudience); await client.healthz(admin.HealthzRequest()); @@ -32,13 +24,29 @@ void main() { }); group('Management Client', () { - test('create a client', () { - final _ = createManagementClient(zitadelAudience); - }); - test('successfully call API', () async { final client = createManagementClient(zitadelAudience); await client.healthz(management.HealthzRequest()); }); }); + + group('Client constructors', () { + final ctors = [ + createAdminClient, + createAuthClient, + createManagementClient, + createOIDCClient, + createOrganizationClient, + createSessionClient, + createSettingsClient, + createSystemClient, + createUserClient, + ]; + + for (final ctor in ctors) { + test('create a client with $ctor', () { + final _ = ctor(zitadelApiUrl); + }); + } + }); } diff --git a/tool/barrel_file_generator.dart b/tool/barrel_file_generator.dart new file mode 100644 index 0000000..dba7b2c --- /dev/null +++ b/tool/barrel_file_generator.dart @@ -0,0 +1,77 @@ +#!/usr/bin/env dart + +import 'dart:io'; + +import 'package:path/path.dart' as p; + +final apiDir = Directory('lib/api'); + +final googleBaseDir = Directory('lib/src/grpc/generated/google/protobuf/'); +final googleRelativeDir = Directory('lib/api/google/'); +final zitadelBaseDir = Directory('lib/src/grpc/generated/zitadel/'); +final zitadelRelativeDir = Directory('lib/api/zitadel/'); + +String libCode(String libName, List files) => + '''// GENERATED CODE - DO NOT MODIFY BY HAND +/// Reexport of generated resources for the gRPC $libName library. +library $libName; + +${files.map((f) => "export '$f';").join('\n')} +'''; + +Future>> groupFiles(Directory dir, Directory dirPrefix, + [Map>? seed]) async { + var group = seed ?? >{}; + + await for (final entry in dir.list()) { + if (entry case final Directory d) { + final dirName = p.basename(d.path); + group = await groupFiles( + d, Directory(p.join(dirPrefix.path, dirName)), group); + continue; + } + + final relativePath = p.posix.relative(entry.path, from: dir.path); + final relativeName = p.posix.join( + dirPrefix.path, + '${relativePath.split('.').first}.dart', + ); + + group[relativeName] ??= []; + group[relativeName]!.add(p.relative(entry.path, from: dirPrefix.path)); + } + + return group; +} + +Future main() async { + print('Create barrel files for ZITADEL API usage'); + + print('Clear existing files.'); + await for (final entry in apiDir.list().where((e) => + e is Directory || + (p.basename(e.path) != 'clients.dart' && + p.extension(e.path) == '.dart'))) { + await entry.delete(recursive: true); + } + + print('Create barrels for google API files.'); + for (final MapEntry(:key, :value) + in (await groupFiles(googleBaseDir, googleRelativeDir)).entries) { + final libName = p.basenameWithoutExtension(key); + final file = File(key); + + await file.create(recursive: true); + await file.writeAsString(libCode(libName, value)); + } + + print('Create barrels for ZITADEL API files.'); + for (final MapEntry(:key, :value) + in (await groupFiles(zitadelBaseDir, zitadelRelativeDir)).entries) { + final libName = p.basenameWithoutExtension(key); + final file = File(key); + + await file.create(recursive: true); + await file.writeAsString(libCode(libName, value)); + } +} diff --git a/tool/generate_barrel_files.dart b/tool/generate_barrel_files.dart deleted file mode 100755 index d6a6d38..0000000 --- a/tool/generate_barrel_files.dart +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env dart - -import 'dart:io'; - -import 'package:path/path.dart' as p; - -final zitadelApiFolder = Directory('lib/api/zitadel'); -final googleApiFolder = Directory('lib/api/google'); -final googleGrpcFolder = Directory('lib/src/grpc/generated/google/protobuf'); -final zitadelGrpcFolder = Directory('lib/src/grpc/generated/zitadel'); - -const ignoredNames = [ - 'system', - 'v1', -]; - -Future>> groupFiles(Directory dir, Directory relativeDir) async { - final group = >{}; - for (final f in await dir.list(followLinks: false).where((f) => f is File).toList()) { - final name = p.basename(f.path); - final ext = p.extension(f.path, 2); - final nameWithoutExt = name.replaceAll(ext, ''); - - if (ignoredNames.contains(nameWithoutExt)) { - continue; - } - - group[nameWithoutExt] ??= []; - group[nameWithoutExt]!.add(p.relative(f.path, from: relativeDir.path).replaceAll('\\', '/')); - } - - return group; -} - -String libCode(String libName, List files) => '''// GENERATED CODE - DO NOT MODIFY BY HAND -/// Reexport of generated resources for the gRPC $libName library. -library $libName; - -${files.map((f) => "export '$f';").join('\n')} -'''; - -Future main() async { - print('Generating Export Barrel Files for API folder.'); - try { - await zitadelApiFolder.delete(recursive: true); - await googleApiFolder.delete(recursive: true); - } catch (_) {} - await zitadelApiFolder.create(); - await googleApiFolder.create(); - - for (final group in [ - [googleGrpcFolder, googleApiFolder], - [zitadelGrpcFolder, zitadelApiFolder] - ]) { - for (final entry in (await groupFiles(group[0], group[1])).entries) { - final name = entry.key; - final files = entry.value; - final file = File(p.join(group[1].path, '$name.dart')); - - await file.create(); - await file.writeAsString(libCode(name, files)); - } - } -} diff --git a/tool/generate_grpc.sh b/tool/generate_grpc.sh deleted file mode 100755 index a1b75c3..0000000 --- a/tool/generate_grpc.sh +++ /dev/null @@ -1,24 +0,0 @@ -#! /bin/sh - -echo $PWD - -GEN_PATH=lib/src/grpc/generated - -echo "Remove generated stuff." -rm -rf $GEN_PATH - -echo "Generate grpc" -mkdir -p $GEN_PATH -protoc \ - -I=external/zitadel/proto \ - -I=external/protoc-gen-validate \ - -I=external/grpc-gateway \ - -I=external/googleapis \ - -I=external/protobuf/src \ - --dart_out=grpc:$GEN_PATH \ - $(find external/zitadel/proto -type f -name "*.proto" | tr '\n' ' ') \ - external/protobuf/src/google/protobuf/duration.proto \ - external/protobuf/src/google/protobuf/struct.proto \ - external/protobuf/src/google/protobuf/timestamp.proto \ - external/protobuf/src/google/protobuf/empty.proto \ - external/protobuf/src/google/protobuf/wrappers.proto