Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions .bundle/config
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
---
BUNDLE_PATH: "/home/runner/work/client-ruby/client-ruby/vendor/bundle"
BUNDLE_IGNORE_FUNDING_REQUESTS: "true"
BUNDLE_JOBS: "4"
BUNDLE_DEPLOYMENT: "true"
BUNDLE_PATH: "./vendor/bundle"
17 changes: 0 additions & 17 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,6 @@ on:
ref:
required: true
type: string
secrets:
BASE_URL:
required: false
AUTH_TOKEN:
required: false
JWT_KEY:
required: false
CLIENT_ID:
required: false
CLIENT_SECRET:
required: false

defaults:
run:
Expand Down Expand Up @@ -49,12 +38,6 @@ jobs:

- name: Run Tests
run: bundle exec rake test
env:
BASE_URL: ${{ secrets.BASE_URL }}
AUTH_TOKEN: ${{ secrets.AUTH_TOKEN }}
JWT_KEY: ${{ secrets.JWT_KEY }}
CLIENT_ID: ${{ secrets.CLIENT_ID }}
CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }}

- name: Upload Results
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
Expand Down
2 changes: 1 addition & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ end
Rake::TestTask.new(:test) do |task|
task.libs << 'lib' << 'test' << 'spec'
task.pattern = %w[test/**/*_test.rb spec/**/*_spec.rb]
task.verbose = true
task.verbose = false
end

task default: :test
2 changes: 1 addition & 1 deletion devbox.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"shell": {
"init_hook": [
"lefthook install",
"bundle install"
"bundle install --quiet"
],
"scripts": {
"format": [
Expand Down
89 changes: 89 additions & 0 deletions etc/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
services:
db:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_USER: root
POSTGRES_PASSWORD: postgres
networks:
- storage
healthcheck:
test: [ "CMD-SHELL", "pg_isready", "-d", "db_prod" ]
interval: 10s
timeout: 60s
retries: 5
start_period: 10s
volumes:
- data:/var/lib/postgresql/data:rw

zitadel-init:
restart: 'no'
networks:
- storage
image: 'ghcr.io/zitadel/zitadel:latest'
command: 'init --config /example-zitadel-config.yaml --config /example-zitadel-secrets.yaml'
depends_on:
db:
condition: 'service_healthy'
volumes:
- './example-zitadel-config.yaml:/example-zitadel-config.yaml:ro'
- './example-zitadel-secrets.yaml:/example-zitadel-secrets.yaml:ro'
- './zitadel_output:/var/zitadel_output:rw'

zitadel-setup:
restart: 'no'
networks:
- storage
image: 'ghcr.io/zitadel/zitadel:latest-debug'
user: root
entrypoint: '/bin/bash'
command: [ "-c", "/app/zitadel setup --config /example-zitadel-config.yaml --config /example-zitadel-secrets.yaml --steps /example-zitadel-init-steps.yaml --masterkey \"my_test_masterkey_0123456789ABEF\" && echo \"--- ZITADEL SETUP COMPLETE ---\" && echo \"Personal Access Token (PAT) will be in ./zitadel_output/pat.txt on your host.\" && echo \"Service Account Key will be in ./zitadel_output/sa-key.json on your host.\" && echo \"OAuth Client ID and Secret will be in 'zitadel' service logs (grep for 'Application created').\"" ]
environment:
- PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/app
depends_on:
zitadel-init:
condition: 'service_completed_successfully'
restart: false
volumes:
- './zitadel_output:/var/zitadel_output:rw'
- './example-zitadel-config.yaml:/example-zitadel-config.yaml:ro'
- './example-zitadel-secrets.yaml:/example-zitadel-secrets.yaml:ro'
- './example-zitadel-init-steps.yaml:/example-zitadel-init-steps.yaml:ro'

zitadel:
restart: 'unless-stopped'
networks:
- backend
- storage
image: 'ghcr.io/zitadel/zitadel:latest'
command: >
start --config /example-zitadel-config.yaml
--config /example-zitadel-secrets.yaml
--masterkey my_test_masterkey_0123456789ABEF
depends_on:
zitadel-setup:
condition: 'service_completed_successfully'
restart: true
volumes:
- './example-zitadel-config.yaml:/example-zitadel-config.yaml:ro'
- './example-zitadel-secrets.yaml:/example-zitadel-secrets.yaml:ro'
- './zitadel_output:/var/zitadel_output:rw'
ports:
- "8099:8080"
healthcheck:
test: [
"CMD", "/app/zitadel", "ready",
"--config", "/example-zitadel-config.yaml",
"--config", "/example-zitadel-secrets.yaml"
]
interval: 10s
timeout: 60s
retries: 5
start_period: 10s

networks:
storage: { }
backend: { }

volumes:
data: { }
17 changes: 17 additions & 0 deletions etc/example-zitadel-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
ExternalSecure: false
ExternalDomain: localhost
ExternalPort: 8080
TLS.Enabled: false
Database:
postgres:
Host: 'db'
Port: 5432
Database: zitadel
User.SSL.Mode: 'disable'
Admin.SSL.Mode: 'disable'
OIDC:
DefaultLoginURLV2: "/ui/v2/login/login?authRequest="
DefaultLogoutURLV2: "/ui/v2/login/logout?post_logout_redirect="
SAML.DefaultLoginURLV2: "/ui/v2/login/login?authRequest="
LogStore.Access.Stdout.Enabled: true
DefaultInstance.LoginPolicy.MfaInitSkipLifetime: "0s"
35 changes: 35 additions & 0 deletions etc/example-zitadel-init-steps.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
FirstInstance:
MachineKeyPath: '/var/zitadel_output/sa-key.json'
PatPath: '/var/zitadel_output/pat.txt'
Org:
Human:
PasswordChangeRequired: false
Username: zitadel-admin@zitadel.localhost
Password: Password1!
Machine:
Machine:
Username: api-user
Name: Combined API User
MachineKey:
ExpirationDate: '2030-01-01T00:00:00Z'
Type: 1
Pat:
ExpirationDate: '2030-01-01T00:00:00Z'
Applications:
- OIDC:
RedirectUris:
- http://localhost:8080/callback
- http://127.0.0.1:8080/callback
ResponseTypes:
- CODE
- ID_TOKEN
- TOKEN
GrantTypes:
- AUTHORIZATION_CODE
- IMPLICIT
- REFRESH_TOKEN
- CLIENT_CREDENTIALS
AuthMethodType: POST
Name: 'MyOAuthAPIClient'
Type: 'WEB'
DefaultInstance.LoginPolicy.MfaInitSkipLifetime: "0s"
8 changes: 8 additions & 0 deletions etc/example-zitadel-secrets.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Database:
postgres:
User:
Username: 'zitadel_user'
Password: 'zitadel'
Admin:
Username: 'root'
Password: 'postgres'
16 changes: 4 additions & 12 deletions spec/auth/use_access_token_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

require 'minitest/autorun'
require_relative '../spec_helper'
require_relative '../base_spec'

# SettingsService Integration Tests (Personal Access Token)
#
Expand All @@ -13,24 +14,15 @@
#
# Each test runs in isolation: the client is instantiated in each example to
# guarantee a clean, stateless call.
describe 'Zitadel SettingsService (Personal Access Token)' do
let(:base_url) { ENV.fetch('BASE_URL') { raise 'BASE_URL not set' } }
let(:valid_token) { ENV.fetch('AUTH_TOKEN') { raise 'AUTH_TOKEN not set' } }
let(:zitadel_client) do
Zitadel::Client::Zitadel.with_access_token(
base_url,
valid_token
)
end

class UseAccessTokenSpec < BaseSpec
it 'retrieves general settings with valid token' do
client = zitadel_client
client = Zitadel::Client::Zitadel.with_access_token(@base_url, @auth_token)
client.settings.settings_service_get_general_settings
end

it 'raises an ApiError with invalid token' do
client = Zitadel::Client::Zitadel.with_access_token(
base_url,
@base_url,
'invalid'
)
assert_raises(Zitadel::Client::ZitadelError) do
Expand Down
74 changes: 62 additions & 12 deletions spec/auth/use_client_credentials_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

require 'minitest/autorun'
require_relative '../spec_helper'
require_relative '../base_spec'
require 'net/http'
require 'uri'
require 'json'

# SettingsService Integration Tests (Client Credentials)
#
Expand All @@ -13,26 +17,72 @@
#
# Each test runs in isolation: the client is instantiated in each example to
# guarantee a clean, stateless call.
describe 'Zitadel SettingsService (Client Credentials)' do
let(:base_url) { ENV.fetch('BASE_URL') { raise 'BASE_URL not set' } }
let(:client_id) { ENV.fetch('CLIENT_ID') { raise 'CLIENT_ID not set' } }
let(:client_secret) { ENV.fetch('CLIENT_SECRET') { raise 'CLIENT_SECRET not set' } }
let(:zitadel_client) do
Zitadel::Client::Zitadel.with_client_credentials(
base_url,
client_id,
client_secret
)
class UseClientCredentialsSpec < BaseSpec
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
def generate_user_secret(token, login_name = 'api-user')
user_id_uri = URI("http://localhost:8099/management/v1/global/users/_by_login_name?loginName=#{URI.encode_www_form_component(login_name)}")

user_id_http = Net::HTTP.new(user_id_uri.host, user_id_uri.port)
user_id_request = Net::HTTP::Get.new(user_id_uri)
user_id_request['Authorization'] = "Bearer #{token}"
user_id_request['Accept'] = 'application/json'

user_id_response = user_id_http.request(user_id_request)

unless user_id_response.is_a?(Net::HTTPSuccess)
raise "API call to retrieve user failed for login name: '#{login_name}'. Response: #{user_id_response.body}"
end

user_response_map = JSON.parse(user_id_response.body)
user_id = user_response_map.dig('user', 'id')

if user_id && !user_id.empty?
secret_uri = URI("http://localhost:8099/management/v1/users/#{user_id}/secret")

secret_http = Net::HTTP.new(secret_uri.host, secret_uri.port)
secret_request = Net::HTTP::Put.new(secret_uri)
secret_request['Authorization'] = "Bearer #{token}"
secret_request['Content-Type'] = 'application/json'
secret_request['Accept'] = 'application/json'
secret_request.body = '{}'

secret_response = secret_http.request(secret_request)

unless secret_response.is_a?(Net::HTTPSuccess)
raise "API call to generate secret failed for user ID: '#{user_id}'. Response: #{secret_response.body}"
end

secret_data = JSON.parse(secret_response.body)
client_id = secret_data['clientId']
client_secret = secret_data['clientSecret']

if client_id && !client_id.empty? && client_secret && !client_secret.empty?
return { clientId: client_id, clientSecret: client_secret }
end

puts secret_response.body
raise "API response for secret is missing 'clientId' or 'clientSecret'."

else
puts user_id_response.body
raise "Could not parse a valid user ID from API response for login name: '#{login_name}'."
end
end
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity

it 'retrieves general settings with valid credentials' do
client = zitadel_client
credentials = generate_user_secret(@auth_token, 'api-user')
client = Zitadel::Client::Zitadel.with_client_credentials(
@base_url,
credentials[:clientId],
credentials[:clientSecret]
)
client.settings.settings_service_get_general_settings
end

it 'raises an ApiError with invalid credentials' do
client = Zitadel::Client::Zitadel.with_client_credentials(
base_url,
@base_url,
'invalid',
'invalid'
)
Expand Down
22 changes: 4 additions & 18 deletions spec/auth/use_private_key_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require 'minitest/autorun'
require_relative '../spec_helper'
require 'tempfile'
require_relative '../base_spec'

# SettingsService Integration Tests (Private Key Assertion)
#
Expand All @@ -14,31 +15,16 @@
#
# Each test runs in isolation: the client is instantiated in each example to
# guarantee a clean, stateless call.
describe 'Zitadel SettingsService (Private Key Assertion)' do
let(:base_url) { ENV.fetch('BASE_URL') { raise 'BASE_URL not set' } }
let(:jwt_file) do
file = Tempfile.new(%w[jwt .json])
file.write(ENV.fetch('JWT_KEY') { raise 'JWT_KEY not set' })
file.flush
file.close
file
end
let(:zitadel_client) do
Zitadel::Client::Zitadel.with_private_key(
base_url,
jwt_file.path
)
end

class UsePrivateKeySpec < BaseSpec
it 'retrieves general settings with valid private key' do
client = zitadel_client
client = Zitadel::Client::Zitadel.with_private_key(@base_url, @jwt_key)
client.settings.settings_service_get_general_settings
end

it 'raises an ApiError with invalid private key' do
client = Zitadel::Client::Zitadel.with_private_key(
'https://zitadel.cloud',
jwt_file.path
@jwt_key
)
assert_raises(Zitadel::Client::ZitadelError) do
client.settings.settings_service_get_general_settings
Expand Down
Loading
Loading