Module: KeycloakApiRails::Testing

Defined in:
lib/keycloak-api-rails/testing.rb

Defined Under Namespace

Modules: Helpers Classes: PublicKeyResolver

Constant Summary collapse

KEY_SIZE =
2048
KEY_ID =
"keycloak-api-rails-testing"
ALGORITHM =
:RS256
DEFAULT_VALIDITY_IN_SECONDS =
3600
MONITOR =
Monitor.new

Class Method Summary collapse

Class Method Details

.auth_headers_for(**options) ⇒ Object

The 'Authorization' header a client would send, e.g. { "Authorization" => "Bearer eyJ0..." }. It accepts the same parameters as '.token_for'.



103
104
105
# File 'lib/keycloak-api-rails/testing.rb', line 103

def auth_headers_for(**options)
  { "Authorization" => "Bearer #{token_for(**options)}" }
end

.private_keyObject

The RSA key pair used to sign the tokens forged by this module. It is generated once per process: generating a key is by far the slowest operation of a test suite that uses tokens.



48
49
50
# File 'lib/keycloak-api-rails/testing.rb', line 48

def private_key
  MONITOR.synchronize { @private_key ||= OpenSSL::PKey::RSA.generate(KEY_SIZE) }
end

.public_keysObject



56
57
58
# File 'lib/keycloak-api-rails/testing.rb', line 56

def public_keys
  MONITOR.synchronize { @public_keys ||= JSON::JWK::Set.new(JSON::JWK.new(private_key.public_key, kid: KEY_ID)) }
end

.signing_keyObject



52
53
54
# File 'lib/keycloak-api-rails/testing.rb', line 52

def signing_key
  MONITOR.synchronize { @signing_key ||= JSON::JWK.new(private_key, kid: KEY_ID) }
end

.stub_public_keys!Object

Makes the library validate the tokens forged by this module. Assigning 'KeycloakApiRails.public_key_resolver = nil' restores the regular resolver.



62
63
64
# File 'lib/keycloak-api-rails/testing.rb', line 62

def stub_public_keys!
  KeycloakApiRails.public_key_resolver = PublicKeyResolver.new(public_keys)
end

.token_for(sub: SecureRandom.uuid, email: nil, locale: nil, authorized_party: nil, roles: [], resource_roles: {}, issued_at: Time.now, expires_at: nil, claims: {}) ⇒ Object

Forges a signed token, similar to the ones emitted by Keycloak. Every claim this library reads has a dedicated parameter; any other claim can be passed through 'claims', which is also how the attributes declared in 'config.custom_attributes' are set.



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/keycloak-api-rails/testing.rb', line 69

def token_for(sub: SecureRandom.uuid,
              email: nil,
              locale: nil,
              authorized_party: nil,
              roles: [],
              resource_roles: {},
              issued_at: Time.now,
              expires_at: nil,
              claims: {})
  payload = {
    "sub"    => sub,
    "email"  => email,
    "locale" => locale,
    "azp"    => authorized_party,
    "iat"    => issued_at.to_i,
    "exp"    => (expires_at || (issued_at.to_i + DEFAULT_VALIDITY_IN_SECONDS)).to_i
  }.reject { |_, value| value.nil? }

  payload["realm_access"]    = { "roles" => roles.map(&:to_s) } unless roles.nil? || roles.empty?
  payload["resource_access"] = build_resource_access(resource_roles) unless resource_roles.nil? || resource_roles.empty?

  unless claims.key?(:iss) || claims.key?("iss")
    config_realm_id = KeycloakApiRails.config.realm_id
    realm_id = config_realm_id.is_a?(String) ? config_realm_id : "master"
    payload["iss"] = File.join(KeycloakApiRails.config.server_url.to_s, "realms", realm_id)
  end

  claims.each { |name, value| payload[name.to_s] = value }

  JSON::JWT.new(payload).sign(signing_key, ALGORITHM).to_s
end