129 lines
5.1 KiB
Ruby
Executable File
129 lines
5.1 KiB
Ruby
Executable File
require 'omniauth'
|
|
require 'omniauth-oauth2'
|
|
require 'json/jwt'
|
|
require 'uri'
|
|
|
|
module OmniAuth
|
|
module Strategies
|
|
class KeycloakOpenId < OmniAuth::Strategies::OAuth2
|
|
|
|
class Error < RuntimeError; end
|
|
class ConfigurationError < Error; end
|
|
class IntegrationError < Error; end
|
|
|
|
attr_reader :authorize_url
|
|
attr_reader :token_url
|
|
attr_reader :certs
|
|
|
|
def setup_phase
|
|
super
|
|
|
|
if @authorize_url.nil? || @token_url.nil?
|
|
prevent_site_option_mistake
|
|
|
|
realm = options.client_options[:realm].nil? ? options.client_id : options.client_options[:realm]
|
|
site = options.client_options[:site]
|
|
|
|
raise_on_failure = options.client_options.fetch(:raise_on_failure, false)
|
|
|
|
config_url = URI.join(site, "#{auth_url_base}/realms/#{realm}/.well-known/openid-configuration")
|
|
|
|
log :debug, "Going to get Keycloak configuration. URL: #{config_url}"
|
|
response = Faraday.get config_url
|
|
if (response.status == 200)
|
|
json = JSON.parse(response.body)
|
|
|
|
@certs_endpoint = json["jwks_uri"]
|
|
@userinfo_endpoint = json["userinfo_endpoint"]
|
|
@authorize_url = URI(json["authorization_endpoint"]).path
|
|
@token_url = URI(json["token_endpoint"]).path
|
|
|
|
log_config(json)
|
|
|
|
options.client_options.merge!({
|
|
authorize_url: @authorize_url,
|
|
token_url: @token_url
|
|
})
|
|
log :debug, "Going to get certificates. URL: #{@certs_endpoint}"
|
|
certs = Faraday.get @certs_endpoint
|
|
if (certs.status == 200)
|
|
json = JSON.parse(response.body)
|
|
@certs = json["keys"]
|
|
log :debug, "Successfully got certificate. Certificate length: #{@certs.length}"
|
|
else
|
|
message = "Coundn't get certificate. URL: #{@certs_endpoint}"
|
|
log :error, message
|
|
raise IntegrationError, message if raise_on_failure
|
|
end
|
|
else
|
|
message = "Keycloak configuration request failed with status: #{response.status}. " \
|
|
"URL: #{config_url}"
|
|
log :error, message
|
|
raise IntegrationError, message if raise_on_failure
|
|
end
|
|
end
|
|
end
|
|
|
|
def auth_url_base
|
|
return '/auth' unless options.client_options[:base_url]
|
|
base_url = options.client_options[:base_url]
|
|
return base_url if (base_url == '' || base_url[0] == '/')
|
|
|
|
raise ConfigurationError, "Keycloak base_url option should start with '/'. Current value: #{base_url}"
|
|
end
|
|
|
|
def prevent_site_option_mistake
|
|
site = options.client_options[:site]
|
|
return unless site =~ /\/auth$/
|
|
|
|
raise ConfigurationError, "Keycloak site parameter should not include /auth part, only domain. Current value: #{site}"
|
|
end
|
|
|
|
def log_config(config_json)
|
|
log_keycloak_config = options.client_options.fetch(:log_keycloak_config, false)
|
|
log :debug, "Successfully got Keycloak config"
|
|
log :debug, "Keycloak config: #{config_json}" if log_keycloak_config
|
|
log :debug, "Certs endpoint: #{@certs_endpoint}"
|
|
log :debug, "Userinfo endpoint: #{@userinfo_endpoint}"
|
|
log :debug, "Authorize url: #{@authorize_url}"
|
|
log :debug, "Token url: #{@token_url}"
|
|
end
|
|
|
|
def build_access_token
|
|
verifier = request.params["code"]
|
|
client.auth_code.get_token(verifier,
|
|
{:redirect_uri => callback_url.gsub(/\?.+\Z/, "")}
|
|
.merge(token_params.to_hash(:symbolize_keys => true)),
|
|
deep_symbolize(options.auth_token_params))
|
|
end
|
|
|
|
uid{ raw_info['sub'] }
|
|
|
|
info do
|
|
{
|
|
:name => raw_info['name'],
|
|
:email => raw_info['email'],
|
|
:first_name => raw_info['given_name'],
|
|
:last_name => raw_info['family_name']
|
|
}
|
|
end
|
|
|
|
extra do
|
|
{
|
|
'raw_info' => raw_info,
|
|
'id_token' => access_token['id_token']
|
|
}
|
|
end
|
|
|
|
def raw_info
|
|
id_token_string = access_token.token
|
|
jwks = JSON::JWK::Set.new(@certs)
|
|
id_token = JSON::JWT.decode id_token_string, jwks
|
|
id_token
|
|
end
|
|
|
|
OmniAuth.config.add_camelization('keycloak_openid', 'KeycloakOpenId')
|
|
end
|
|
end
|
|
end
|