Skip to content

Commit

Permalink
Merge pull request #8904 from CitizenLabDotCo/TAN-2518-oslo-id-porten
Browse files Browse the repository at this point in the history
TAN-2518 - SSO with Oslo ID-Porten (key cloak)
  • Loading branch information
jamesspeake authored Oct 10, 2024
2 parents 0f2a127 + b028982 commit 79e0ee2
Show file tree
Hide file tree
Showing 24 changed files with 749 additions and 6 deletions.
21 changes: 21 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,27 @@ fe-up:
up:
make -j 2 be-up fe-up

# For testing different SSO methods
be-up-claveunica:
docker compose down
BASE_DEV_URI=https://claveunica-h2dkc.loca.lt docker compose up -d
lt --print-requests --port 3000 --subdomain claveunica-h2dkc

be-up-nemlogin:
docker compose down
BASE_DEV_URI=https://nemlogin-k3kd.loca.lt docker compose up -d
lt --print-requests --port 3000 --subdomain nemlogin-k3kd

be-up-idaustria:
docker compose down
BASE_DEV_URI=https://idaustria-g3fy.loca.lt docker compose up -d
lt --print-requests --port 3000 --subdomain idaustria-g3fy

be-up-keycloak:
docker compose down
BASE_DEV_URI=https://keycloak-r3tyu.loca.lt docker compose up -d
lt --print-requests --port 3000 --subdomain keycloak-r3tyu

# Run it with:
# make c
# # or
Expand Down
1 change: 1 addition & 0 deletions back/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ commercial_engines = [
'id_franceconnect',
'id_gent_rrn',
'id_id_card_lookup',
'id_keycloak',
'id_nemlog_in',
'id_oostende_rrn',
# Some engines actually register an authentication method rather
Expand Down
9 changes: 9 additions & 0 deletions back/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,14 @@ PATH
savon (>= 2.12, < 2.15)
verification

PATH
remote: engines/commercial/id_keycloak
specs:
id_keycloak (0.1.0)
omniauth_openid_connect (~> 0.7.1)
rails (~> 7.0)
verification

PATH
remote: engines/commercial/id_nemlog_in
specs:
Expand Down Expand Up @@ -1288,6 +1296,7 @@ DEPENDENCIES
id_gent_rrn!
id_hoplr!
id_id_card_lookup!
id_keycloak!
id_nemlog_in!
id_oostende_rrn!
id_vienna_saml!
Expand Down
2 changes: 2 additions & 0 deletions back/config/initializers/omniauth.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
end

# See https://github.com/omniauth/omniauth/wiki/Resolving-CVE-2015-9284
# TODO: Change all implementations to use POST requests
OmniAuth.config.allowed_request_methods = %i[post get]
OmniAuth.config.silence_get_warning = true

OmniAuth.config.full_host = lambda { |_env|
AppConfiguration.instance&.base_backend_uri
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true

require 'citizen_lab/mixins/feature_specification'

module IdKeycloak
module FeatureSpecification
extend CitizenLab::Mixins::FeatureSpecification

def self.feature_name
'keycloak_login'
end

def self.feature_title
'Keycloak (ID-Porten) Login'
end

def self.feature_description
'Allow users to authenticate with a Norwegian ID-Porten (via Keycloak) account.'
end

def self.allowed_by_default
false
end

def self.enabled_by_default
false
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# frozen_string_literal: true

module IdKeycloak
class KeycloakOmniauth < OmniauthMethods::Base
include KeycloakVerification

def profile_to_user_attrs(auth)
{
first_name: auth.info.first_name,
last_name: auth.info.last_name,
email: auth.info.email,
locale: AppConfiguration.instance.closest_locale_to('nb-NO') # No need to get the locale from the provider
}
end

# @param [AppConfiguration] configuration
def omniauth_setup(configuration, env)
return unless Verification::VerificationService.new.active?(configuration, name)

options = env['omniauth.strategy'].options

options[:scope] = %i[openid]
options[:response_type] = :code
options[:issuer] = issuer
options[:client_options] = {
identifier: config[:client_id],
secret: config[:client_secret],
redirect_uri: "#{configuration.base_backend_uri}/auth/keycloak/callback",

# NOTE: Cannot use auto discovery as .well-known/openid-configuration is not on the root of the domain
client_signing_alg: :RS256,
authorization_endpoint: "#{issuer}/protocol/openid-connect/auth",
token_endpoint: "#{issuer}/protocol/openid-connect/token",
introspection_endpoint: "#{issuer}/protocol/openid-connect/token/introspect",
userinfo_endpoint: "#{issuer}/protocol/openid-connect/userinfo",
jwks_uri: "#{issuer}/protocol/openid-connect/certs"
}
end

def email_always_present?
false
end

def verification_prioritized?
true
end

def email_confirmed?(auth)
# Response will tell us if the email is verified
auth&.info&.email_verified
end

def filter_auth_to_persist(auth)
auth_to_persist = auth.deep_dup
auth_to_persist.tap { |h| h.delete(:credentials) }
end

def issuer
"https://#{config[:domain]}/auth/realms/idporten"
end

def updateable_user_attrs
super + %i[first_name last_name]
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# frozen_string_literal: true

module IdKeycloak
module KeycloakVerification
include Verification::VerificationMethod

def verification_method_type
:omniauth
end

def id
'd6938fe6-4bee-4490-b80c-b14dafb5da1b'
end

def name
'keycloak'
end

def config_parameters
%i[
ui_method_name
domain
client_id
client_secret
]
end

def config_parameters_schema
{
ui_method_name: {
type: 'string',
description: 'The name this verification method will have in the UI',
default: 'ID-Porten'
}
}
end

def exposed_config_parameters
[
:ui_method_name
]
end

def locked_attributes
%i[first_name last_name]
end

def other_attributes
%i[email]
end

def profile_to_uid(auth)
auth['uid']
end

def updateable_user_attrs
super + %i[first_name last_name]
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

KEYCLOAK_SETUP_PROC = lambda do |env|
IdKeycloak::KeycloakOmniauth.new.omniauth_setup(AppConfiguration.instance, env)
end

Rails.application.config.middleware.use OmniAuth::Builder do
provider :openid_connect, setup: KEYCLOAK_SETUP_PROC, name: 'keycloak', issuer: IdKeycloak::KeycloakOmniauth.new.method(:issuer)
end
23 changes: 23 additions & 0 deletions back/engines/commercial/id_keycloak/id_keycloak.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

$LOAD_PATH.push File.expand_path('lib', __dir__)

# Maintain your gem's version:
require 'id_keycloak/version'

# Describe your gem and declare its dependencies:
Gem::Specification.new do |s|
s.name = 'id_keycloak'
s.version = IdKeycloak::VERSION
s.summary = 'Verification using Keycloak (ID Porten)'
s.authors = ['Go Vocal']
s.licenses = [Gem::Licenses::NONSTANDARD] # ['Go Vocal Commercial License V2']
s.files = Dir['{app,config,db,lib}/**/*', 'Rakefile', 'README.md']

s.add_dependency 'rails', '~> 7.0'
s.add_dependency 'verification'
s.add_dependency 'omniauth_openid_connect', '~> 0.7.1'

s.add_development_dependency 'rspec_api_documentation'
s.add_development_dependency 'rspec-rails'
end
7 changes: 7 additions & 0 deletions back/engines/commercial/id_keycloak/lib/id_keycloak.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

require 'id_keycloak/engine'

module IdKeycloak
# Your code goes here...
end
15 changes: 15 additions & 0 deletions back/engines/commercial/id_keycloak/lib/id_keycloak/engine.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

module IdKeycloak
class Engine < ::Rails::Engine
isolate_namespace IdKeycloak

config.to_prepare do
AppConfiguration::Settings.add_feature(IdKeycloak::FeatureSpecification)

keycloak = KeycloakOmniauth.new
Verification.add_method(keycloak)
AuthenticationService.add_method('keycloak', keycloak)
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

module IdKeycloak
VERSION = '0.1.0'
end
Loading

0 comments on commit 79e0ee2

Please sign in to comment.