diff --git a/Gemfile b/Gemfile index ebfc313f9..b53c139bd 100644 --- a/Gemfile +++ b/Gemfile @@ -32,7 +32,6 @@ gem 'discard' gem 'dotenv-rails', require: 'dotenv/rails-now' # dotenv should always be loaded before rails gem 'dry-monads' gem 'elastic-apm' -gem 'faraday-jwt' gem 'flipper' gem 'flipper-active_record' gem 'flipper-ui' diff --git a/Gemfile.lock b/Gemfile.lock index 8b03303a1..13cd7e0b2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -116,7 +116,6 @@ GEM axlsx_styler (1.1.0) activesupport (>= 3.1) caxlsx (>= 2.0.2) - base64 (0.2.0) bcrypt (3.1.19) benchmark-ips (2.12.0) better_html (1.0.16) @@ -127,7 +126,7 @@ GEM html_tokenizer (~> 0.0.6) parser (>= 2.4) smart_properties - bindata (2.4.15) + bindata (2.4.10) bindex (0.8.1) bootsnap (1.9.3) msgpack (~> 1.0) @@ -175,7 +174,7 @@ GEM css_parser (1.9.0) addressable daemons (1.3.1) - date (3.3.4) + date (3.3.3) deep_cloneable (3.2.0) activerecord (>= 3.1.0, < 8) delayed_cron_job (0.7.4) @@ -238,16 +237,6 @@ GEM excon (0.102.0) factory_bot (6.1.0) activesupport (>= 5.0.0) - faraday (2.7.12) - base64 - faraday-net_http (>= 2.0, < 3.1) - ruby2_keywords (>= 0.0.4) - faraday-follow_redirects (0.3.0) - faraday (>= 1, < 3) - faraday-jwt (0.1.0) - faraday (~> 2.0) - json-jwt (~> 1.16) - faraday-net_http (3.0.2) ffi (1.16.3) ffi-compiler (1.0.1) ffi (>= 1.0.0) @@ -336,6 +325,7 @@ GEM domain_name (~> 0.5) http-form_data (2.3.0) http_accept_language (2.1.1) + httpclient (2.8.3) i18n (1.14.1) concurrent-ruby (~> 1.0) i18n-tasks (1.0.9) @@ -364,12 +354,10 @@ GEM railties (>= 4.2.0) thor (>= 0.14, < 2.0) json (2.5.1) - json-jwt (1.16.3) + json-jwt (1.13.0) activesupport (>= 4.2) aes_key_wrap bindata - faraday (~> 2.0) - faraday-follow_redirects json_schemer (0.2.17) ecma-re-validator (~> 0.3) hana (~> 1.3) @@ -445,14 +433,14 @@ GEM multi_json (1.15.0) mustermann (3.0.0) ruby2_keywords (~> 0.0.1) - net-imap (0.4.7) + net-imap (0.3.7) date net-protocol net-pop (0.1.2) net-protocol - net-protocol (0.2.2) + net-protocol (0.2.1) timeout - net-smtp (0.4.0) + net-smtp (0.3.3) net-protocol netrc (0.11.0) nio4r (2.5.9) @@ -460,19 +448,16 @@ GEM mini_portile2 (~> 2.8.2) racc (~> 1.4) open4 (1.3.4) - openid_connect (2.2.0) + openid_connect (1.3.0) activemodel attr_required (>= 1.0.0) - faraday (~> 2.0) - faraday-follow_redirects - json-jwt (>= 1.16) - net-smtp - rack-oauth2 (~> 2.2) - swd (~> 2.0) + json-jwt (>= 1.5.0) + rack-oauth2 (>= 1.6.1) + swd (>= 1.0.0) tzinfo validate_email validate_url - webfinger (~> 2.0) + webfinger (>= 1.0.1) orm_adapter (0.5.0) parallel (1.23.0) parsby (1.1.1) @@ -506,7 +491,7 @@ GEM pry (>= 0.13, < 0.15) pry-rails (0.3.9) pry (>= 0.10.4) - public_suffix (5.0.4) + public_suffix (5.0.3) puma (6.3.1) nio4r (~> 2.0) pundit (2.2.0) @@ -518,11 +503,10 @@ GEM rack (>= 1.0, < 3) rack-mini-profiler (3.0.0) rack (>= 1.2.0) - rack-oauth2 (2.2.0) + rack-oauth2 (1.19.0) activesupport attr_required - faraday (~> 2.0) - faraday-follow_redirects + httpclient json-jwt (>= 1.11.0) rack (>= 2.1.0) rack-protection (3.0.5) @@ -739,11 +723,10 @@ GEM stackprof (0.2.21) strong_migrations (0.8.0) activerecord (>= 5.2) - swd (2.0.2) + swd (1.3.0) activesupport (>= 3) attr_required (>= 0.0.5) - faraday (~> 2.0) - faraday-follow_redirects + httpclient (>= 2.4) sysexits (1.2.0) temple (0.8.2) terminal-table (3.0.2) @@ -752,7 +735,7 @@ GEM thread_safe (0.3.6) tilt (2.0.11) timecop (0.9.4) - timeout (0.4.1) + timeout (0.4.0) ttfunk (1.7.0) turbo-rails (1.3.2) actionpack (>= 6.0.0) @@ -771,7 +754,7 @@ GEM validate_email (0.1.6) activemodel (>= 3.0) mail (>= 2.2.5) - validate_url (1.0.15) + validate_url (1.0.13) activemodel (>= 3.0.0) public_suffix vcr (6.1.0) @@ -797,10 +780,9 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - webfinger (2.1.2) + webfinger (1.2.0) activesupport - faraday (~> 2.0) - faraday-follow_redirects + httpclient (>= 2.4) webmock (3.11.2) addressable (>= 2.3.6) crack (>= 0.3.2) @@ -867,7 +849,6 @@ DEPENDENCIES dry-monads elastic-apm factory_bot - faraday-jwt flipper flipper-active_record flipper-ui diff --git a/app/services/agent_connect_service.rb b/app/services/agent_connect_service.rb index 20812df61..bb41170d0 100644 --- a/app/services/agent_connect_service.rb +++ b/app/services/agent_connect_service.rb @@ -13,8 +13,8 @@ class AgentConnectService uri = client.authorization_uri( scope: [:openid, :email], - state:, - nonce:, + state: state, + nonce: nonce, acr_values: 'eidas1' ) diff --git a/config/env.example b/config/env.example index b449c23e9..a3624b572 100644 --- a/config/env.example +++ b/config/env.example @@ -56,6 +56,7 @@ FC_PARTICULIER_BASE_URL="" AGENT_CONNECT_ID="" AGENT_CONNECT_SECRET="" AGENT_CONNECT_BASE_URL="" +AGENT_CONNECT_JWKS="" AGENT_CONNECT_REDIRECT="" # External service: integration with HelpScout (optional) diff --git a/config/initializers/open_id_connect.rb b/config/initializers/open_id_connect.rb index 0a3627a39..5266653b8 100644 --- a/config/initializers/open_id_connect.rb +++ b/config/initializers/open_id_connect.rb @@ -1,3 +1,61 @@ -OpenIDConnect.http_config do |config| - config.response :jwt +OpenIDConnect.debug! +OpenIDConnect.logger = Rails.logger +Rack::OAuth2.logger = Rails.logger +# Webfinger.logger = Rails.logger +SWD.logger = Rails.logger + +# the openid_connect gem does not support +# jwt format in the userinfo call. +# A PR is open to improve the situation +# https://github.com/nov/openid_connect/pull/54 +module OpenIDConnect + class AccessToken < Rack::OAuth2::AccessToken::Bearer + private + + def jwk_loader + JSON.parse(URI.parse(ENV['AGENT_CONNECT_JWKS']).read).deep_symbolize_keys + end + + def decode_jwt(requested_host, jwt) + agent_connect_host = URI.parse(ENV['AGENT_CONNECT_BASE_URL']).host + + if requested_host == agent_connect_host + # rubocop:disable Lint/UselessAssignment + JWT.decode(jwt, key = nil, verify = true, { algorithms: ['ES256'], jwks: jwk_loader })[0] + # rubocop:enable Lint/UselessAssignment + else + raise "unknwon host : #{requested_host}" + end + end + + def resource_request + res = yield + case res.status + when 200 + hash = case parse_type_and_subtype(res.content_type) + when 'application/jwt' + requested_host = URI.parse(client.userinfo_endpoint).host + decode_jwt(requested_host, res.body) + when 'application/json' + JSON.parse(res.body) + end + hash&.with_indifferent_access + when 400 + raise BadRequest.new('API Access Faild', res) + when 401 + raise Unauthorized.new('Access Token Invalid or Expired', res) + when 403 + raise Forbidden.new('Insufficient Scope', res) + else + raise HttpError.new(res.status, 'Unknown HttpError', res) + end + end + + # https://datatracker.ietf.org/doc/html/rfc2045#section-5.1 + # - type and subtype are the first member + # they are case insensitive + def parse_type_and_subtype(content_type) + content_type.split(';')[0].strip.downcase + end + end end