From 9b90ff04822abfdc30f8f6aee851749ce521e543 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Fri, 11 Jun 2021 08:47:55 +0200 Subject: [PATCH 01/50] [bug] unable to process stripe payments with 3DS authentication --- CHANGELOG.md | 4 ++++ app/controllers/api/payments_controller.rb | 2 +- package.json | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e1efa9f2..e2387fa73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog Fab-manager +## v4.7.13 2020 June 11 + +- Fix a bug: unable to process stripe payments with 3DS authentication + ## v4.7.12 2021 June 09 - Fix a bug: unable to process stripe payments diff --git a/app/controllers/api/payments_controller.rb b/app/controllers/api/payments_controller.rb index 2d5df145e..8b0104461 100644 --- a/app/controllers/api/payments_controller.rb +++ b/app/controllers/api/payments_controller.rb @@ -34,7 +34,7 @@ class API::PaymentsController < API::ApiController }, { api_key: Setting.get('stripe_secret_key') } ) elsif params[:payment_intent_id].present? - intent = Stripe::PaymentIntent.confirm(params[:payment_intent_id], api_key: Setting.get('stripe_secret_key')) + intent = Stripe::PaymentIntent.confirm(params[:payment_intent_id], {}, { api_key: Setting.get('stripe_secret_key') }) end rescue Stripe::CardError => e # Display error on client diff --git a/package.json b/package.json index 33aad27af..29b1a46cb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fab-manager", - "version": "4.7.12", + "version": "4.7.13", "description": "Fab-manager is the FabLab management solution. It provides a comprehensive, web-based, open-source tool to simplify your administrative tasks and your marker's projects.", "keywords": [ "fablab", From cb4bf36358d9a471c02f48d2e3d710cd2d9656b5 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 30 Sep 2021 14:38:32 +0200 Subject: [PATCH 02/50] updated sassc to 2.4.0 --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index b4562b85d..e626a3dda 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -140,7 +140,7 @@ GEM i18n (>= 1.6, < 2) faraday (0.17.3) multipart-post (>= 1.2, < 3) - ffi (1.15.1) + ffi (1.15.4) foreman (0.87.0) forgery (0.7.0) friendly_id (5.1.0) @@ -341,7 +341,7 @@ GEM rubyzip (>= 1.3.0) rubyzip (2.3.0) safe_yaml (1.0.5) - sassc (2.2.1) + sassc (2.4.0) ffi (~> 1.9) seed_dump (3.3.1) activerecord (>= 4) From 405afc7333b8f37e0603bc1aecb8f97edff2821a Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 30 Sep 2021 14:38:46 +0200 Subject: [PATCH 03/50] Version 4.7.14 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2387fa73..d4e19837e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog Fab-manager +## v4.7.14 2021 September 30 + +- Fix a bug: update sassc to 2.4.0 to try to fix #270 + ## v4.7.13 2020 June 11 - Fix a bug: unable to process stripe payments with 3DS authentication diff --git a/package.json b/package.json index 29b1a46cb..4460a198d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fab-manager", - "version": "4.7.13", + "version": "4.7.14", "description": "Fab-manager is the FabLab management solution. It provides a comprehensive, web-based, open-source tool to simplify your administrative tasks and your marker's projects.", "keywords": [ "fablab", From c00b8c6cdd63c3d088c3e74453d226198aabbf37 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 30 Sep 2021 14:54:26 +0200 Subject: [PATCH 04/50] try to downgrade sassc to 2.1.0 --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index daf0fbad7..4a2a60c58 100644 --- a/Gemfile +++ b/Gemfile @@ -138,4 +138,4 @@ gem 'icalendar' gem 'tzinfo-data' # compilation of dynamic stylesheets (home page & theme) -gem 'sassc' +gem 'sassc', '= 2.1.0' diff --git a/Gemfile.lock b/Gemfile.lock index e626a3dda..cf7d223c9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -341,7 +341,7 @@ GEM rubyzip (>= 1.3.0) rubyzip (2.3.0) safe_yaml (1.0.5) - sassc (2.4.0) + sassc (2.1.0) ffi (~> 1.9) seed_dump (3.3.1) activerecord (>= 4) @@ -484,7 +484,7 @@ DEPENDENCIES rubocop (~> 0.61.1) rubyXL rubyzip (>= 1.3.0) - sassc + sassc (= 2.1.0) seed_dump sha3 sidekiq (>= 6.0.7) From 830f2b7ebe1c7f4f85f26c0b1c1132f5292418da Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 28 Mar 2022 12:27:33 +0200 Subject: [PATCH 05/50] (dependency) add omniauth_openid_connect --- Gemfile | 1 + Gemfile.lock | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/Gemfile b/Gemfile index adc249b89..6ebb76f72 100644 --- a/Gemfile +++ b/Gemfile @@ -69,6 +69,7 @@ gem 'devise', '>= 4.6.0' gem 'omniauth', '~> 1.9.0' gem 'omniauth-oauth2' +gem 'omniauth_openid_connect' gem 'omniauth-rails_csrf_protection', '~> 0.1' gem 'rolify' diff --git a/Gemfile.lock b/Gemfile.lock index 23bd799a8..8593bac38 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -50,6 +50,7 @@ GEM tzinfo (~> 1.1) addressable (2.8.0) public_suffix (>= 2.0.2, < 5.0) + aes_key_wrap (1.1.0) afm (0.2.2) ansi (1.5.0) api-pagination (4.8.2) @@ -57,12 +58,14 @@ GEM rails (>= 4.1) arel (9.0.0) ast (2.4.0) + attr_required (1.0.1) awesome_print (1.8.0) axiom-types (0.1.1) descendants_tracker (~> 0.0.4) ice_nine (~> 0.11.0) thread_safe (~> 0.3, >= 0.3.1) bcrypt (3.1.13) + bindata (2.4.10) bindex (0.8.1) bootsnap (1.4.6) msgpack (~> 1.0) @@ -164,6 +167,7 @@ GEM httparty (0.20.0) mime-types (~> 3.0) multi_xml (>= 0.5.2) + httpclient (2.8.3) i18n (1.10.0) concurrent-ruby (~> 1.0) icalendar (2.5.3) @@ -179,6 +183,10 @@ GEM jbuilder_cache_multi (0.1.0) jbuilder (>= 1.5.0, < 3) json (2.3.1) + json-jwt (1.13.0) + activesupport (>= 4.2) + aes_key_wrap + bindata jsonpath (1.1.0) multi_json jwt (2.2.1) @@ -249,6 +257,20 @@ GEM omniauth-rails_csrf_protection (0.1.2) actionpack (>= 4.2) omniauth (>= 1.3.1) + omniauth_openid_connect (0.4.0) + addressable (~> 2.5) + omniauth (>= 1.9, < 3) + openid_connect (~> 1.1) + openid_connect (1.3.0) + activemodel + attr_required (>= 1.0.0) + json-jwt (>= 1.5.0) + rack-oauth2 (>= 1.6.1) + swd (>= 1.0.0) + tzinfo + validate_email + validate_url + webfinger (>= 1.0.1) openlab_ruby (0.0.7) httparty (~> 0.20) orm_adapter (0.5.0) @@ -280,6 +302,12 @@ GEM raabro (1.4.0) racc (1.6.0) rack (2.2.3) + rack-oauth2 (1.19.0) + activesupport + attr_required + httpclient + json-jwt (>= 1.11.0) + rack (>= 2.1.0) rack-proxy (0.7.2) rack rack-test (1.1.0) @@ -396,6 +424,10 @@ GEM sprockets (>= 3.0.0) ssrf_filter (1.0.7) stripe (5.29.0) + swd (1.3.0) + activesupport (>= 3) + attr_required (>= 0.0.5) + httpclient (>= 2.4) sync (0.5.0) sys-filesystem (1.3.3) ffi @@ -419,6 +451,12 @@ GEM tzinfo (>= 1.0.0) unicode-display_width (1.4.1) uniform_notifier (1.14.2) + validate_email (0.1.6) + activemodel (>= 3.0) + mail (>= 2.2.5) + validate_url (1.0.13) + activemodel (>= 3.0.0) + public_suffix vcr (6.0.0) virtus (1.0.5) axiom-types (~> 0.1) @@ -432,6 +470,9 @@ GEM activemodel (>= 5.0) bindex (>= 0.4.0) railties (>= 5.0) + webfinger (1.2.0) + activesupport + httpclient (>= 2.4) webmock (3.8.2) addressable (>= 2.3.6) crack (>= 0.3.2) @@ -483,6 +524,7 @@ DEPENDENCIES omniauth (~> 1.9.0) omniauth-oauth2 omniauth-rails_csrf_protection (~> 0.1) + omniauth_openid_connect openlab_ruby pdf-reader pg From 8495e2a7a048a5262ab5a46bca7f266b106032d0 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 28 Mar 2022 19:50:36 +0200 Subject: [PATCH 06/50] (db) table to save OpenID connect configuration Also refactored OAuth2Mapping to allow usage with any types of providers --- .../api/auth_providers_controller.rb | 15 +++-- .../controllers/admin/authentications.js | 10 ++-- .../authentications/_oauth2_mapping.html | 4 +- app/models/auth_provider.rb | 9 ++- app/models/auth_provider_mapping.rb | 7 +++ app/models/database_provider.rb | 4 -- app/models/o_auth2_mapping.rb | 7 --- app/models/o_auth2_provider.rb | 9 --- app/models/open_id_connect_provider.rb | 7 +++ .../_auth_provider.json.jbuilder | 3 + .../api/auth_providers/show.json.jbuilder | 5 +- config/initializers/devise.rb | 4 +- ...141618_create_open_id_connect_providers.rb | 36 +++++++++++ ...uth2_mappings_to_auth_provider_mappings.rb | 11 ++++ ...provider_id_from_auth_provider_mappings.rb | 31 ++++++++++ db/schema.rb | 59 ++++++++++++++----- .../strategies/sso_oauth2_provider.rb | 4 +- ...appings.yml => auth_provider_mappings.yml} | 0 test/integration/auth_providers_test.rb | 38 ++++++------ 19 files changed, 187 insertions(+), 76 deletions(-) create mode 100644 app/models/auth_provider_mapping.rb delete mode 100644 app/models/o_auth2_mapping.rb create mode 100644 app/models/open_id_connect_provider.rb create mode 100644 db/migrate/20220328141618_create_open_id_connect_providers.rb create mode 100644 db/migrate/20220328144305_rename_o_auth2_mappings_to_auth_provider_mappings.rb create mode 100644 db/migrate/20220328145017_migrate_o_auth2_provider_id_from_auth_provider_mappings.rb rename test/fixtures/{o_auth2_mappings.yml => auth_provider_mappings.yml} (100%) diff --git a/app/controllers/api/auth_providers_controller.rb b/app/controllers/api/auth_providers_controller.rb index f83915e9f..a79c0588a 100644 --- a/app/controllers/api/auth_providers_controller.rb +++ b/app/controllers/api/auth_providers_controller.rb @@ -80,14 +80,13 @@ class API::AuthProvidersController < API::ApiController if params['auth_provider']['providable_type'] == DatabaseProvider.name params.require(:auth_provider).permit(:name, :providable_type) elsif params['auth_provider']['providable_type'] == OAuth2Provider.name - params.require(:auth_provider) - .permit(:name, :providable_type, - providable_attributes: [:id, :base_url, :token_endpoint, :authorization_endpoint, :logout_endpoint, - :profile_url, :client_id, :client_secret, :scopes, - o_auth2_mappings_attributes: [:id, :local_model, :local_field, :api_field, - :api_endpoint, :api_data_type, :_destroy, - transformation: [:type, :format, :true_value, - :false_value, mapping: %i[from to]]]]) + params.require(:auth_provider) + .permit(:name, :providable_type, + providable_attributes: %i[id base_url token_endpoint authorization_endpoint logout_endpoint + profile_url client_id client_secret scopes], + auth_provider_mappings_attributes: [:id, :local_model, :local_field, :api_field, :api_endpoint, :api_data_type, + :_destroy, transformation: [:type, :format, :true_value, :false_value, + mapping: %i[from to]]]) end end end diff --git a/app/frontend/src/javascript/controllers/admin/authentications.js b/app/frontend/src/javascript/controllers/admin/authentications.js index 9bbe4a3d3..8288bd19d 100644 --- a/app/frontend/src/javascript/controllers/admin/authentications.js +++ b/app/frontend/src/javascript/controllers/admin/authentications.js @@ -34,7 +34,7 @@ const findIdxById = function (elements, id) { /** * For OAuth2 authentications, mapping the user's ID is mandatory. This function will check that this mapping * is effective and will return false otherwise - * @param mappings {Array} expected: $scope.provider.providable_attributes.o_auth2_mappings_attributes + * @param mappings {Array} expected: $scope.provider.auth_provider_mappings_attributes * @returns {Boolean} true if the mapping is declared */ const check_oauth2_id_is_mapped = function (mappings) { @@ -246,8 +246,8 @@ Application.Controllers.controller('NewAuthenticationController', ['$scope', '$s $scope.updateProvidable = function () { // === OAuth2Provider === if ($scope.provider.providable_type === 'OAuth2Provider') { - if (typeof $scope.provider.providable_attributes.o_auth2_mappings_attributes === 'undefined') { - return $scope.provider.providable_attributes.o_auth2_mappings_attributes = []; + if (typeof $scope.provider.auth_provider_mappings_attributes === 'undefined') { + return $scope.provider.auth_provider_mappings_attributes = []; } } }; @@ -274,7 +274,7 @@ Application.Controllers.controller('NewAuthenticationController', ['$scope', '$s // === OAuth2Provider === } else if ($scope.provider.providable_type === 'OAuth2Provider') { // check the ID mapping - if (!check_oauth2_id_is_mapped($scope.provider.providable_attributes.o_auth2_mappings_attributes)) { + if (!check_oauth2_id_is_mapped($scope.provider.auth_provider_mappings_attributes)) { growl.error(_t('app.admin.authentication_new.it_is_required_to_set_the_matching_between_User.uid_and_the_API_to_add_this_provider')); return false; } @@ -330,7 +330,7 @@ Application.Controllers.controller('EditAuthenticationController', ['$scope', '$ */ $scope.updateProvider = function () { // check the ID mapping - if (!check_oauth2_id_is_mapped($scope.provider.providable_attributes.o_auth2_mappings_attributes)) { + if (!check_oauth2_id_is_mapped($scope.provider.auth_provider_mappings_attributes)) { growl.error(_t('app.admin.authentication_edit.it_is_required_to_set_the_matching_between_User.uid_and_the_API_to_add_this_provider')); return false; } diff --git a/app/frontend/templates/admin/authentications/_oauth2_mapping.html b/app/frontend/templates/admin/authentications/_oauth2_mapping.html index d6d37811c..48a370eb6 100644 --- a/app/frontend/templates/admin/authentications/_oauth2_mapping.html +++ b/app/frontend/templates/admin/authentications/_oauth2_mapping.html @@ -14,7 +14,7 @@ - + {{m.local_model}} {{m.local_field}} {{m.api_endpoint}} @@ -72,7 +72,7 @@ required/> - + diff --git a/app/models/auth_provider.rb b/app/models/auth_provider.rb index 318340048..29fdf0a0f 100644 --- a/app/models/auth_provider.rb +++ b/app/models/auth_provider.rb @@ -18,6 +18,9 @@ class AuthProvider < ApplicationRecord belongs_to :providable, polymorphic: true, dependent: :destroy accepts_nested_attributes_for :providable + has_many :auth_provider_mappings, dependent: :destroy + accepts_nested_attributes_for :auth_provider_mappings, allow_destroy: true + before_create :set_initial_state def build_providable(params) @@ -75,7 +78,11 @@ class AuthProvider < ApplicationRecord ## Return the user's profile fields that are currently managed from the SSO ## @return [Array] def sso_fields - providable.protected_fields + fields = [] + auth_provider_mappings.each do |mapping| + fields.push(mapping.local_model + '.' + mapping.local_field) + end + fields end ## Return the link the user have to follow to edit his profile on the SSO diff --git a/app/models/auth_provider_mapping.rb b/app/models/auth_provider_mapping.rb new file mode 100644 index 000000000..4f4c19d56 --- /dev/null +++ b/app/models/auth_provider_mapping.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +# AuthProviderMapping defines the relationship between a database field (saving user's data) +# and an external API, that is authorized through an external SSO (like oAuth 2.0). +class AuthProviderMapping < ApplicationRecord + belongs_to :auth_provider +end diff --git a/app/models/database_provider.rb b/app/models/database_provider.rb index 6c4c9fb95..6b625603d 100644 --- a/app/models/database_provider.rb +++ b/app/models/database_provider.rb @@ -5,10 +5,6 @@ class DatabaseProvider < ApplicationRecord has_one :auth_provider, as: :providable, dependent: :destroy - def protected_fields - [] - end - def profile_url '/#!/dashboard/profile' end diff --git a/app/models/o_auth2_mapping.rb b/app/models/o_auth2_mapping.rb deleted file mode 100644 index 6f097dea7..000000000 --- a/app/models/o_auth2_mapping.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -# OAuth2Mapping defines a database field, saving user's data, that is mapped to an external API, that is authorized -# through an external SSO of type oAuth 2 -class OAuth2Mapping < ApplicationRecord - belongs_to :o_auth2_provider -end diff --git a/app/models/o_auth2_provider.rb b/app/models/o_auth2_provider.rb index 75f847d7d..a91647b85 100644 --- a/app/models/o_auth2_provider.rb +++ b/app/models/o_auth2_provider.rb @@ -4,18 +4,9 @@ # the oAuth 2.0 protocol. class OAuth2Provider < ApplicationRecord has_one :auth_provider, as: :providable - has_many :o_auth2_mappings, dependent: :destroy - accepts_nested_attributes_for :o_auth2_mappings, allow_destroy: true def domain URI(base_url).scheme + '://' + URI(base_url).host end - def protected_fields - fields = [] - o_auth2_mappings.each do |mapping| - fields.push(mapping.local_model + '.' + mapping.local_field) - end - fields - end end diff --git a/app/models/open_id_connect_provider.rb b/app/models/open_id_connect_provider.rb new file mode 100644 index 000000000..a631a26a7 --- /dev/null +++ b/app/models/open_id_connect_provider.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +# OpenIdConnectProvider is a special type of AuthProvider which provides authentication through an external SSO server using +# the OpenID Connect protocol. +class OpenIdConnectProvider < ApplicationRecord + has_one :auth_provider, as: :providable +end diff --git a/app/views/api/auth_providers/_auth_provider.json.jbuilder b/app/views/api/auth_providers/_auth_provider.json.jbuilder index 325094b72..8889c959d 100644 --- a/app/views/api/auth_providers/_auth_provider.json.jbuilder +++ b/app/views/api/auth_providers/_auth_provider.json.jbuilder @@ -1,3 +1,6 @@ # frozen_string_literal: true json.extract! auth_provider, :id, :name, :status, :providable_type, :strategy_name +json.auth_provider_mappings_attributes @provider.auth_provider_mappings do |m| + json.extract! m, :id, :local_model, :local_field, :api_field, :api_endpoint, :api_data_type, :transformation +end diff --git a/app/views/api/auth_providers/show.json.jbuilder b/app/views/api/auth_providers/show.json.jbuilder index 78e7397b1..183fad96b 100644 --- a/app/views/api/auth_providers/show.json.jbuilder +++ b/app/views/api/auth_providers/show.json.jbuilder @@ -1,3 +1,5 @@ +# frozen_string_literal: true + json.partial! 'api/auth_providers/auth_provider', auth_provider: @provider # OAuth 2.0 @@ -5,8 +7,5 @@ json.partial! 'api/auth_providers/auth_provider', auth_provider: @provider if @provider.providable_type == OAuth2Provider.name json.providable_attributes do json.extract! @provider.providable, :id, :base_url, :token_endpoint, :authorization_endpoint, :profile_url, :client_id, :client_secret, :scopes - json.o_auth2_mappings_attributes @provider.providable.o_auth2_mappings do |m| - json.extract! m, :id, :local_model, :local_field, :api_field, :api_endpoint, :api_data_type, :transformation - end end end diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 95df71d50..32aa13e4a 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -231,7 +231,9 @@ Devise.setup do |config| require_relative '../../lib/omni_auth/omni_auth' active_provider = AuthProvider.active if active_provider.providable_type == OAuth2Provider.name - config.omniauth OmniAuth::Strategies::SsoOauth2Provider.name.to_sym, active_provider.providable.client_id, active_provider.providable.client_secret + config.omniauth OmniAuth::Strategies::SsoOauth2Provider.name.to_sym, + active_provider.providable.client_id, + active_provider.providable.client_secret end # ==> Warden configuration diff --git a/db/migrate/20220328141618_create_open_id_connect_providers.rb b/db/migrate/20220328141618_create_open_id_connect_providers.rb new file mode 100644 index 000000000..9d04d1ce0 --- /dev/null +++ b/db/migrate/20220328141618_create_open_id_connect_providers.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +# This migration allow configuration of OpenID Connect providers +class CreateOpenIdConnectProviders < ActiveRecord::Migration[5.2] + def change + create_table :open_id_connect_providers do |t| + t.string :issuer + t.boolean :discovery + t.string :client_auth_method + t.string :scope + t.string :response_type + t.string :response_type + t.string :response_mode + t.string :display + t.string :prompt + t.boolean :send_scope_to_token_endpoint + t.string :post_logout_redirect_uri + t.string :uid_field + t.string :extra_authorize_params + t.string :allow_authorize_params + t.string :client_identifier + t.string :client_secret + t.string :client_redirect_uri + t.string :client_scheme + t.string :client_host + t.string :client_port + t.string :client_authorization_endpoint + t.string :client_token_endpoint + t.string :client_userinfo_endpoint + t.string :client_jwks_uri + t.string :client_end_session_endpoint + + t.timestamps + end + end +end diff --git a/db/migrate/20220328144305_rename_o_auth2_mappings_to_auth_provider_mappings.rb b/db/migrate/20220328144305_rename_o_auth2_mappings_to_auth_provider_mappings.rb new file mode 100644 index 000000000..f64d47a41 --- /dev/null +++ b/db/migrate/20220328144305_rename_o_auth2_mappings_to_auth_provider_mappings.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +# This migration renames the OAuth2Mappings table to AuthProviderMappings because the +# field mapping is common to all kinds of single-sign-on providers. +class RenameOAuth2MappingsToAuthProviderMappings < ActiveRecord::Migration[5.2] + def change + rename_table :o_auth2_mappings, :auth_provider_mappings + add_reference :auth_provider_mappings, :auth_provider, index: true, foreign_key: true + + end +end diff --git a/db/migrate/20220328145017_migrate_o_auth2_provider_id_from_auth_provider_mappings.rb b/db/migrate/20220328145017_migrate_o_auth2_provider_id_from_auth_provider_mappings.rb new file mode 100644 index 000000000..cc659e48c --- /dev/null +++ b/db/migrate/20220328145017_migrate_o_auth2_provider_id_from_auth_provider_mappings.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +# Previously, the AuthProviderMapping was saving an o_auth2_provider_id. +# This migration migrates that data to bind the mappings directly to an AuthProvider as this table is now protocol-generic. +class MigrateOAuth2ProviderIdFromAuthProviderMappings < ActiveRecord::Migration[5.2] + def up + execute <<~SQL + UPDATE auth_provider_mappings + SET auth_provider_id = auth_providers.id + FROM o_auth2_providers + INNER JOIN auth_providers ON auth_providers.providable_id = o_auth2_providers.id + AND auth_providers.providable_type = 'OAuth2Provider' + WHERE auth_provider_mappings.o_auth2_provider_id = o_auth2_providers.id + SQL + + remove_reference :auth_provider_mappings, :o_auth2_provider, index: true, foreign_key: true + end + + def down + add_reference :auth_provider_mappings, :o_auth2_provider, index: true, foreign_key: true + + execute <<~SQL + UPDATE auth_provider_mappings + SET o_auth2_provider_id = o_auth2_providers.id + FROM o_auth2_providers + INNER JOIN auth_providers ON auth_providers.providable_id = o_auth2_providers.id + AND auth_providers.providable_type = 'OAuth2Provider' + WHERE auth_provider_mappings.auth_provider_id = auth_providers.id + SQL + end +end diff --git a/db/schema.rb b/db/schema.rb index 18ea2c102..e0f8c2602 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2022_03_22_135836) do +ActiveRecord::Schema.define(version: 2022_03_28_145017) do # These are extensions that must be enabled in order to support this database enable_extension "fuzzystrmatch" @@ -72,6 +72,19 @@ ActiveRecord::Schema.define(version: 2022_03_22_135836) do t.datetime "updated_at" end + create_table "auth_provider_mappings", id: :serial, force: :cascade do |t| + t.string "local_field" + t.string "api_field" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "local_model" + t.string "api_endpoint" + t.string "api_data_type" + t.jsonb "transformation" + t.bigint "auth_provider_id" + t.index ["auth_provider_id"], name: "index_auth_provider_mappings_on_auth_provider_id" + end + create_table "auth_providers", id: :serial, force: :cascade do |t| t.string "name" t.string "status" @@ -369,19 +382,6 @@ ActiveRecord::Schema.define(version: 2022_03_22_135836) do t.index ["receiver_id"], name: "index_notifications_on_receiver_id" end - create_table "o_auth2_mappings", id: :serial, force: :cascade do |t| - t.integer "o_auth2_provider_id" - t.string "local_field" - t.string "api_field" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.string "local_model" - t.string "api_endpoint" - t.string "api_data_type" - t.jsonb "transformation" - t.index ["o_auth2_provider_id"], name: "index_o_auth2_mappings_on_o_auth2_provider_id" - end - create_table "o_auth2_providers", id: :serial, force: :cascade do |t| t.string "base_url" t.string "token_endpoint" @@ -411,6 +411,35 @@ ActiveRecord::Schema.define(version: 2022_03_22_135836) do t.datetime "updated_at", null: false end + create_table "open_id_connect_providers", force: :cascade do |t| + t.string "issuer" + t.boolean "discovery" + t.string "client_auth_method" + t.string "scope" + t.string "response_type" + t.string "response_mode" + t.string "display" + t.string "prompt" + t.boolean "send_scope_to_token_endpoint" + t.string "post_logout_redirect_uri" + t.string "uid_field" + t.string "extra_authorize_params" + t.string "allow_authorize_params" + t.string "client_identifier" + t.string "client_secret" + t.string "client_redirect_uri" + t.string "client_scheme" + t.string "client_host" + t.string "client_port" + t.string "client_authorization_endpoint" + t.string "client_token_endpoint" + t.string "client_userinfo_endpoint" + t.string "client_jwks_uri" + t.string "client_end_session_endpoint" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "organizations", id: :serial, force: :cascade do |t| t.string "name" t.datetime "created_at", null: false @@ -983,6 +1012,7 @@ ActiveRecord::Schema.define(version: 2022_03_22_135836) do end add_foreign_key "accounting_periods", "users", column: "closed_by" + add_foreign_key "auth_provider_mappings", "auth_providers" add_foreign_key "availability_tags", "availabilities" add_foreign_key "availability_tags", "tags" add_foreign_key "event_price_categories", "events" @@ -1000,7 +1030,6 @@ ActiveRecord::Schema.define(version: 2022_03_22_135836) do add_foreign_key "invoices", "statistic_profiles" add_foreign_key "invoices", "wallet_transactions" add_foreign_key "invoicing_profiles", "users" - add_foreign_key "o_auth2_mappings", "o_auth2_providers" add_foreign_key "organizations", "invoicing_profiles" add_foreign_key "payment_gateway_objects", "payment_gateway_objects" add_foreign_key "payment_schedule_items", "invoices" diff --git a/lib/omni_auth/strategies/sso_oauth2_provider.rb b/lib/omni_auth/strategies/sso_oauth2_provider.rb index af59688ad..8d9cfd9e7 100644 --- a/lib/omni_auth/strategies/sso_oauth2_provider.rb +++ b/lib/omni_auth/strategies/sso_oauth2_provider.rb @@ -58,7 +58,7 @@ module OmniAuth::Strategies @raw_info ||= {} logger.debug "[raw_info] @raw_infos = #{@raw_info&.to_json}" unless @raw_info.size.positive? - OmniAuth::Strategies::SsoOauth2Provider.active_provider.providable.o_auth2_mappings.each do |mapping| + OmniAuth::Strategies::SsoOauth2Provider.active_provider.auth_provider_mappings.each do |mapping| logger.debug "mapping = #{mapping&.to_json}" next if @raw_info.key?(mapping.api_endpoint.to_sym) @@ -78,7 +78,7 @@ module OmniAuth::Strategies @parsed_info ||= {} logger.debug "[parsed_info] @parsed_info = #{@parsed_info.to_json}" unless @parsed_info.size.positive? - OmniAuth::Strategies::SsoOauth2Provider.active_provider.providable.o_auth2_mappings.each do |mapping| + OmniAuth::Strategies::SsoOauth2Provider.active_provider.auth_provider_mappings.each do |mapping| raw_data = ::JsonPath.new(mapping.api_field).on(raw_info[mapping.api_endpoint.to_sym]).first logger.debug "@parsed_info[#{local_sym(mapping)}] mapped from #{raw_data}" diff --git a/test/fixtures/o_auth2_mappings.yml b/test/fixtures/auth_provider_mappings.yml similarity index 100% rename from test/fixtures/o_auth2_mappings.yml rename to test/fixtures/auth_provider_mappings.yml diff --git a/test/integration/auth_providers_test.rb b/test/integration/auth_providers_test.rb index d985ab580..f55afc51d 100644 --- a/test/integration/auth_providers_test.rb +++ b/test/integration/auth_providers_test.rb @@ -22,24 +22,24 @@ class AuthProvidersTest < ActionDispatch::IntegrationTest base_url: 'https://github.com/login/oauth/', profile_url: 'https://github.com/settings/profile', client_id: ENV.fetch('OAUTH_CLIENT_ID') { 'github-oauth-app-id' }, - client_secret: ENV.fetch('OAUTH_CLIENT_SECRET') { 'github-oauth-app-secret' }, - o_auth2_mappings_attributes: [ - { - api_data_type: 'json', - api_endpoint: 'https://api.github.com/user', - api_field: 'id', - local_field: 'uid', - local_model: 'user' - }, - { - api_data_type: 'json', - api_endpoint: 'https://api.github.com/user', - api_field: 'html_url', - local_field: 'github', - local_model: 'profile' - } - ] - } + client_secret: ENV.fetch('OAUTH_CLIENT_SECRET') { 'github-oauth-app-secret' } + }, + auth_provider_mappings_attributes: [ + { + api_data_type: 'json', + api_endpoint: 'https://api.github.com/user', + api_field: 'id', + local_field: 'uid', + local_model: 'user' + }, + { + api_data_type: 'json', + api_endpoint: 'https://api.github.com/user', + api_field: 'html_url', + local_field: 'github', + local_model: 'profile' + } + ] } }.to_json, headers: default_headers @@ -56,7 +56,7 @@ class AuthProvidersTest < ActionDispatch::IntegrationTest assert_equal name, provider[:name] assert_equal db_provider.id, provider[:id] assert_equal 'pending', provider[:status] - assert_equal 2, provider[:providable_attributes][:o_auth2_mappings_attributes].length + assert_equal 2, provider[:auth_provider_mappings_attributes].length # now let's activate this new provider Fablab::Application.load_tasks if Rake::Task.tasks.empty? From bd68c5e7e8d5a22f31e5d3160b1b84d0d857acfb Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 29 Mar 2022 12:18:16 +0200 Subject: [PATCH 07/50] (authentication) configure devise/omniauth to use the OpenIdConnect configuration --- app/models/open_id_connect_provider.rb | 24 ++++++++++++++++++++++++ config/initializers/devise.rb | 2 ++ 2 files changed, 26 insertions(+) diff --git a/app/models/open_id_connect_provider.rb b/app/models/open_id_connect_provider.rb index a631a26a7..793e5974f 100644 --- a/app/models/open_id_connect_provider.rb +++ b/app/models/open_id_connect_provider.rb @@ -4,4 +4,28 @@ # the OpenID Connect protocol. class OpenIdConnectProvider < ApplicationRecord has_one :auth_provider, as: :providable + + validates :issuer, presence: true + validates :client_identifier, presence: true + validates :client_secret, presence: true + validates :client_host, presence: true + + validates :client_scheme, inclusion: { in: %w[http https] } + validates :client_port, numericality: { only_integer: true, greater_than: 0, less_than: 65_535 } + validates :response_type, inclusion: { in: %w[code id_token], allow_nil: true } + validates :response_mode, inclusion: { in: %w[query fragment form_post web_message], allow_nil: true } + validates :display, inclusion: { in: %w[page popup touch wap], allow_nil: true } + validates :prompt, inclusion: { in: %w[none login consent select_account], allow_nil: true } + + def config + OpenIdConnectProvider.columns.map(&:name).filter { |n| !n.start_with?('client_') }.map do |n| + [n, send(n)] + end.push(['client_options', client_config]).to_h + end + + def client_config + OpenIdConnectProvider.columns.map(&:name).filter { |n| n.start_with?('client_') }.map do |n| + [n.sub('client_', ''), send(n)] + end.to_h + end end diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 32aa13e4a..dfe81d119 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -234,6 +234,8 @@ Devise.setup do |config| config.omniauth OmniAuth::Strategies::SsoOauth2Provider.name.to_sym, active_provider.providable.client_id, active_provider.providable.client_secret + elsif active_provider.providable_type == OpenIdConnectProvider.name + config.omniauth :openid_connect, active_provider.config end # ==> Warden configuration From 58d0d306024b7421a4d76abfbc024171a20f0f57 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 29 Mar 2022 13:25:50 +0200 Subject: [PATCH 08/50] (bug) return auth provider mappings from the API --- app/views/api/auth_providers/_auth_provider.json.jbuilder | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/api/auth_providers/_auth_provider.json.jbuilder b/app/views/api/auth_providers/_auth_provider.json.jbuilder index 8889c959d..588a98df2 100644 --- a/app/views/api/auth_providers/_auth_provider.json.jbuilder +++ b/app/views/api/auth_providers/_auth_provider.json.jbuilder @@ -1,6 +1,6 @@ # frozen_string_literal: true json.extract! auth_provider, :id, :name, :status, :providable_type, :strategy_name -json.auth_provider_mappings_attributes @provider.auth_provider_mappings do |m| +json.auth_provider_mappings_attributes auth_provider.auth_provider_mappings do |m| json.extract! m, :id, :local_model, :local_field, :api_field, :api_endpoint, :api_data_type, :transformation end From d4be62d0b861965fe43fb40867b10ccf70e9f312 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 30 Mar 2022 10:24:25 +0200 Subject: [PATCH 09/50] (db) add profile_url to OpenIdConnectProvider --- app/models/open_id_connect_provider.rb | 2 +- ...141618_create_open_id_connect_providers.rb | 1 + db/schema.rb | 19 ++++++++++--------- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/app/models/open_id_connect_provider.rb b/app/models/open_id_connect_provider.rb index 793e5974f..39e4bf4c1 100644 --- a/app/models/open_id_connect_provider.rb +++ b/app/models/open_id_connect_provider.rb @@ -18,7 +18,7 @@ class OpenIdConnectProvider < ApplicationRecord validates :prompt, inclusion: { in: %w[none login consent select_account], allow_nil: true } def config - OpenIdConnectProvider.columns.map(&:name).filter { |n| !n.start_with?('client_') }.map do |n| + OpenIdConnectProvider.columns.map(&:name).filter { |n| !n.start_with?('client_') && n != 'profile_url' }.map do |n| [n, send(n)] end.push(['client_options', client_config]).to_h end diff --git a/db/migrate/20220328141618_create_open_id_connect_providers.rb b/db/migrate/20220328141618_create_open_id_connect_providers.rb index 9d04d1ce0..c2ede56f7 100644 --- a/db/migrate/20220328141618_create_open_id_connect_providers.rb +++ b/db/migrate/20220328141618_create_open_id_connect_providers.rb @@ -29,6 +29,7 @@ class CreateOpenIdConnectProviders < ActiveRecord::Migration[5.2] t.string :client_userinfo_endpoint t.string :client_jwks_uri t.string :client_end_session_endpoint + t.string :profile_url t.timestamps end diff --git a/db/schema.rb b/db/schema.rb index e0f8c2602..197d2e96c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -19,8 +19,8 @@ ActiveRecord::Schema.define(version: 2022_03_28_145017) do enable_extension "unaccent" create_table "abuses", id: :serial, force: :cascade do |t| - t.string "signaled_type" t.integer "signaled_id" + t.string "signaled_type" t.string "first_name" t.string "last_name" t.string "email" @@ -49,8 +49,8 @@ ActiveRecord::Schema.define(version: 2022_03_28_145017) do t.string "locality" t.string "country" t.string "postal_code" - t.string "placeable_type" t.integer "placeable_id" + t.string "placeable_type" t.datetime "created_at" t.datetime "updated_at" end @@ -64,8 +64,8 @@ ActiveRecord::Schema.define(version: 2022_03_28_145017) do end create_table "assets", id: :serial, force: :cascade do |t| - t.string "viewable_type" t.integer "viewable_id" + t.string "viewable_type" t.string "attachment" t.string "type" t.datetime "created_at" @@ -146,8 +146,8 @@ ActiveRecord::Schema.define(version: 2022_03_28_145017) do end create_table "credits", id: :serial, force: :cascade do |t| - t.string "creditable_type" t.integer "creditable_id" + t.string "creditable_type" t.integer "plan_id" t.integer "hours" t.datetime "created_at" @@ -369,15 +369,15 @@ ActiveRecord::Schema.define(version: 2022_03_28_145017) do create_table "notifications", id: :serial, force: :cascade do |t| t.integer "receiver_id" - t.string "attached_object_type" t.integer "attached_object_id" + t.string "attached_object_type" t.integer "notification_type_id" t.boolean "is_read", default: false t.datetime "created_at" t.datetime "updated_at" t.string "receiver_type" t.boolean "is_send", default: false - t.jsonb "meta_data", default: "{}" + t.jsonb "meta_data", default: {} t.index ["notification_type_id"], name: "index_notifications_on_notification_type_id" t.index ["receiver_id"], name: "index_notifications_on_receiver_id" end @@ -436,6 +436,7 @@ ActiveRecord::Schema.define(version: 2022_03_28_145017) do t.string "client_userinfo_endpoint" t.string "client_jwks_uri" t.string "client_end_session_endpoint" + t.string "profile_url" t.datetime "created_at", null: false t.datetime "updated_at", null: false end @@ -571,8 +572,8 @@ ActiveRecord::Schema.define(version: 2022_03_28_145017) do create_table "prices", id: :serial, force: :cascade do |t| t.integer "group_id" t.integer "plan_id" - t.string "priceable_type" t.integer "priceable_id" + t.string "priceable_type" t.integer "amount" t.datetime "created_at", null: false t.datetime "updated_at", null: false @@ -682,8 +683,8 @@ ActiveRecord::Schema.define(version: 2022_03_28_145017) do t.text "message" t.datetime "created_at" t.datetime "updated_at" - t.string "reservable_type" t.integer "reservable_id" + t.string "reservable_type" t.integer "nb_reserve_places" t.integer "statistic_profile_id" t.index ["reservable_type", "reservable_id"], name: "index_reservations_on_reservable_type_and_reservable_id" @@ -692,8 +693,8 @@ ActiveRecord::Schema.define(version: 2022_03_28_145017) do create_table "roles", id: :serial, force: :cascade do |t| t.string "name" - t.string "resource_type" t.integer "resource_id" + t.string "resource_type" t.datetime "created_at" t.datetime "updated_at" t.index ["name", "resource_type", "resource_id"], name: "index_roles_on_name_and_resource_type_and_resource_id" From 08ce18d93fb638a0444dffa0bd486ce10a198a79 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 30 Mar 2022 11:23:29 +0200 Subject: [PATCH 10/50] (db) distringuish between oid client configuration and client_auth_method --- app/models/open_id_connect_provider.rb | 6 ++--- ...141618_create_open_id_connect_providers.rb | 22 +++++++++---------- db/schema.rb | 22 +++++++++---------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/app/models/open_id_connect_provider.rb b/app/models/open_id_connect_provider.rb index 39e4bf4c1..4069e00f1 100644 --- a/app/models/open_id_connect_provider.rb +++ b/app/models/open_id_connect_provider.rb @@ -18,14 +18,14 @@ class OpenIdConnectProvider < ApplicationRecord validates :prompt, inclusion: { in: %w[none login consent select_account], allow_nil: true } def config - OpenIdConnectProvider.columns.map(&:name).filter { |n| !n.start_with?('client_') && n != 'profile_url' }.map do |n| + OpenIdConnectProvider.columns.map(&:name).filter { |n| !n.start_with?('client__') && n != 'profile_url' }.map do |n| [n, send(n)] end.push(['client_options', client_config]).to_h end def client_config - OpenIdConnectProvider.columns.map(&:name).filter { |n| n.start_with?('client_') }.map do |n| - [n.sub('client_', ''), send(n)] + OpenIdConnectProvider.columns.map(&:name).filter { |n| n.start_with?('client__') }.map do |n| + [n.sub('client__', ''), send(n)] end.to_h end end diff --git a/db/migrate/20220328141618_create_open_id_connect_providers.rb b/db/migrate/20220328141618_create_open_id_connect_providers.rb index c2ede56f7..6a6b22cd1 100644 --- a/db/migrate/20220328141618_create_open_id_connect_providers.rb +++ b/db/migrate/20220328141618_create_open_id_connect_providers.rb @@ -18,17 +18,17 @@ class CreateOpenIdConnectProviders < ActiveRecord::Migration[5.2] t.string :uid_field t.string :extra_authorize_params t.string :allow_authorize_params - t.string :client_identifier - t.string :client_secret - t.string :client_redirect_uri - t.string :client_scheme - t.string :client_host - t.string :client_port - t.string :client_authorization_endpoint - t.string :client_token_endpoint - t.string :client_userinfo_endpoint - t.string :client_jwks_uri - t.string :client_end_session_endpoint + t.string :client__identifier + t.string :client__secret + t.string :client__redirect_uri + t.string :client__scheme + t.string :client__host + t.string :client__port + t.string :client__authorization_endpoint + t.string :client__token_endpoint + t.string :client__userinfo_endpoint + t.string :client__jwks_uri + t.string :client__end_session_endpoint t.string :profile_url t.timestamps diff --git a/db/schema.rb b/db/schema.rb index 197d2e96c..fcb7dd7d4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -425,17 +425,17 @@ ActiveRecord::Schema.define(version: 2022_03_28_145017) do t.string "uid_field" t.string "extra_authorize_params" t.string "allow_authorize_params" - t.string "client_identifier" - t.string "client_secret" - t.string "client_redirect_uri" - t.string "client_scheme" - t.string "client_host" - t.string "client_port" - t.string "client_authorization_endpoint" - t.string "client_token_endpoint" - t.string "client_userinfo_endpoint" - t.string "client_jwks_uri" - t.string "client_end_session_endpoint" + t.string "client__identifier" + t.string "client__secret" + t.string "client__redirect_uri" + t.string "client__scheme" + t.string "client__host" + t.string "client__port" + t.string "client__authorization_endpoint" + t.string "client__token_endpoint" + t.string "client__userinfo_endpoint" + t.string "client__jwks_uri" + t.string "client__end_session_endpoint" t.string "profile_url" t.datetime "created_at", null: false t.datetime "updated_at", null: false From ecccf6a4ec04a46203826b3e2228e5c4fbb31498 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 30 Mar 2022 11:31:05 +0200 Subject: [PATCH 11/50] (api) openid provider configuration api --- app/controllers/api/auth_providers_controller.rb | 13 ++++++++++++- app/views/api/auth_providers/show.json.jbuilder | 10 ++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/auth_providers_controller.rb b/app/controllers/api/auth_providers_controller.rb index a79c0588a..d53a15de8 100644 --- a/app/controllers/api/auth_providers_controller.rb +++ b/app/controllers/api/auth_providers_controller.rb @@ -82,11 +82,22 @@ class API::AuthProvidersController < API::ApiController elsif params['auth_provider']['providable_type'] == OAuth2Provider.name params.require(:auth_provider) .permit(:name, :providable_type, - providable_attributes: %i[id base_url token_endpoint authorization_endpoint logout_endpoint + providable_attributes: %i[id base_url token_endpoint authorization_endpoint profile_url client_id client_secret scopes], auth_provider_mappings_attributes: [:id, :local_model, :local_field, :api_field, :api_endpoint, :api_data_type, :_destroy, transformation: [:type, :format, :true_value, :false_value, mapping: %i[from to]]]) + elsif params['auth_provider']['providable_type'] == OpenIdConnectProvider.name + params.require(:auth_provider) + .permit(:name, :providable_type, + providable_attributes: %i[id issuer discovery client_auth_method scope response_type response_mode display prompt + send_scope_to_token_endpoint post_logout_redirect_uri uid_field extra_authorize_params + allow_authorize_params client__identifier client__secret client__redirect_uri + client__scheme client__host client__port client__authorization_endpoint client__token_endpoint + client__userinfo_endpoint client__jwks_uri client__end_session_endpoint profile_url], + auth_provider_mappings_attributes: [:id, :local_model, :local_field, :api_field, :api_endpoint, :api_data_type, + :_destroy, transformation: [:type, :format, :true_value, :false_value, + mapping: %i[from to]]]) end end end diff --git a/app/views/api/auth_providers/show.json.jbuilder b/app/views/api/auth_providers/show.json.jbuilder index 183fad96b..bdcbcc9bb 100644 --- a/app/views/api/auth_providers/show.json.jbuilder +++ b/app/views/api/auth_providers/show.json.jbuilder @@ -9,3 +9,13 @@ if @provider.providable_type == OAuth2Provider.name json.extract! @provider.providable, :id, :base_url, :token_endpoint, :authorization_endpoint, :profile_url, :client_id, :client_secret, :scopes end end + +if @provider.providable_type == OpenIdConnectProvider.name + json.providable_attributes do + json.extract! @provider.providable, :id, :issuer, :discovery, :client_auth_method, :scope, :response_type, :response_mode, :display, + :prompt, :send_scope_to_token_endpoint, :post_logout_redirect_uri, :uid_field, :extra_authorize_params, + :allow_authorize_params, :client__identifier, :client__secret, :client__redirect_uri, :client__scheme, + :client__host, :client__port, :client__authorization_endpoint, :client__token_endpoint, :client__userinfo_endpoint, + :client__jwks_uri, :client__end_session_endpoint, :profile_url + end +end From f17aa0d7ea05e7642daf18375cea01507e5abcc6 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 30 Mar 2022 12:22:20 +0200 Subject: [PATCH 12/50] (dependency) updated nodejs --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53f5d10ed..ae7d591c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,10 @@ # Changelog Fab-manager +- Updated nodejs version to 16.13.2 for dev environment, to reflect production version - Changed the apparence of the modal dialogs (React): no more logo and the close button appears in full-text in the top right corner. - Use react-hook-form to manage and validate forms - New text editor - Change font family to "Work Sans" -- Updated Node to 16.13.2 - Updated eslint to v8 and eslint related packages to their latest versions - Updated typescript to v4.6.3 - Webpack overlay will now report eslint issues From 1657e9dc8f8070163528a85756da87014bf0f575 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 30 Mar 2022 15:26:07 +0200 Subject: [PATCH 13/50] (doc) minimum docker version --- CHANGELOG.md | 1 + doc/production_readme.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae7d591c8..141203811 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog Fab-manager +- Updated the documentation about the minimum docker version - Updated nodejs version to 16.13.2 for dev environment, to reflect production version - Changed the apparence of the modal dialogs (React): no more logo and the close button appears in full-text in the top right corner. - Use react-hook-form to manage and validate forms diff --git a/doc/production_readme.md b/doc/production_readme.md index cb76da9b8..856d37e50 100644 --- a/doc/production_readme.md +++ b/doc/production_readme.md @@ -50,7 +50,7 @@ This might work on other linux systems, and CPU architectures but this is untest `curl` and `bash` are needed to retrieve and run the automated deployment scripts. Then the various scripts will check for their own dependencies. -Moreover, the main software dependencies to run fab-manager are [Docker](https://docs.docker.com/engine/installation/linux/docker-ce/debian/) and [Docker Compose](https://docs.docker.com/compose/install/) +Moreover, the main software dependencies to run fab-manager are [Docker](https://docs.docker.com/engine/installation/linux/docker-ce/debian/) v20.0 or above and [Docker Compose](https://docs.docker.com/compose/install/) They can be easily installed using the [`prepare-vps.sleede.com` script below](#prepare-the-server). From f68c8a492e689c8bc727eade3fc8fe928144248c Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 30 Mar 2022 18:01:19 +0200 Subject: [PATCH 14/50] WIP: front-end form for openid configuration --- .../src/javascript/api/auth-provider.ts | 31 +++++++++ .../authentication-provider/provider-form.tsx | 27 ++++++++ .../models/authentication-provider.ts | 68 +++++++++++++++++++ app/models/auth_provider.rb | 2 +- app/models/open_id_connect_provider.rb | 11 ++- 5 files changed, 132 insertions(+), 7 deletions(-) create mode 100644 app/frontend/src/javascript/api/auth-provider.ts create mode 100644 app/frontend/src/javascript/components/authentication-provider/provider-form.tsx create mode 100644 app/frontend/src/javascript/models/authentication-provider.ts diff --git a/app/frontend/src/javascript/api/auth-provider.ts b/app/frontend/src/javascript/api/auth-provider.ts new file mode 100644 index 000000000..ec00b2fbd --- /dev/null +++ b/app/frontend/src/javascript/api/auth-provider.ts @@ -0,0 +1,31 @@ +import { AuthenticationProvider } from '../models/authentication-provider'; +import { AxiosResponse } from 'axios'; +import apiClient from './clients/api-client'; + +export default class AuthProviderAPI { + static async index (): Promise> { + const res: AxiosResponse> = await apiClient.get('/api/auth_providers'); + return res?.data; + } + + static async get (id: number): Promise { + const res: AxiosResponse = await apiClient.get(`/api/auth_providers/${id}`); + return res?.data; + } + + static async create (authProvider: AuthenticationProvider): Promise { + const res: AxiosResponse = await apiClient.post('/api/auth_providers', authProvider); + return res?.data; + } + + static async update (authProvider: AuthenticationProvider): Promise { + const res: AxiosResponse = await apiClient.put(`/api/auth_providers/${authProvider.id}`, authProvider); + return res?.data; + } + + static async delete (id: number): Promise { + await apiClient.delete(`/api/auth_providers/${id}`); + } + + static async mappingFields(): Promise<> +} diff --git a/app/frontend/src/javascript/components/authentication-provider/provider-form.tsx b/app/frontend/src/javascript/components/authentication-provider/provider-form.tsx new file mode 100644 index 000000000..3bbd9c5a8 --- /dev/null +++ b/app/frontend/src/javascript/components/authentication-provider/provider-form.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { useForm, SubmitHandler } from 'react-hook-form'; +import { AuthenticationProvider } from '../../models/authentication-provider'; + +interface ProviderFormProps { + provider?: AuthenticationProvider, + onError: (message: string) => void, + onSuccess: (message: string) => void, +} + +export const ProviderForm: React.FC = ({ provider, onError, onSuccess }) => { + const { handleSubmit } = useForm({ defaultValues: { ...provider } }); + + const onSubmit: SubmitHandler = (data: AuthenticationProvider) => { + if (data) { + onSuccess('Provider created successfully'); + } else { + onError('Failed to created provider'); + } + }; + + return ( +
+ +
+ ); +}; diff --git a/app/frontend/src/javascript/models/authentication-provider.ts b/app/frontend/src/javascript/models/authentication-provider.ts new file mode 100644 index 000000000..806d1405c --- /dev/null +++ b/app/frontend/src/javascript/models/authentication-provider.ts @@ -0,0 +1,68 @@ +export interface AuthenticationProvider { + id?: number, + name: string, + status: 'active' | 'previous' | 'pending' + providable_type: 'DatabaseProvider' | 'OAuth2Provider' | 'OpenIdConnectProvider', + strategy_name: string + auth_provider_mappings_attributes: Array, + providable_attributes?: OAuth2Provider | OpenIdConnectProvider +} + +export interface AuthenticationProviderMapping { + id?: number, + local_model: 'user' | 'profile', + local_field: string, + api_field: string, + api_endpoint: string, + api_data_type: 'json', + transformation: { + type: 'string' | 'text' | 'date' | 'integer' | 'boolean', + format: 'iso8601' | 'rfc2822' | 'rfc3339' | 'timestamp-s' | 'timestamp-ms', + true_value: string, + false_value: string, + mapping: { + from: string, + to: number + } + } +} + +export interface OAuth2Provider { + id?: string, + base_url: string, + token_endpoint: string, + authorization_endpoint: string, + profile_url: string, + client_id: string, + client_secret: string, + scopes: string +} + +export interface OpenIdConnectProvider { + id?: string, + issuer: string, + discovery: boolean, + client_auth_method?: string, + scope?: string, + response_type?: 'code' | 'id_token', + response_mode?: 'query' | 'fragment' | 'form_post' | 'web_message', + display?: 'page' | 'popup' | 'touch' | 'wap', + prompt?: 'none' | 'login' | 'consent' | 'select_account', + send_scope_to_token_endpoint?: string, + post_logout_redirect_uri?: string, + uid_field?: string, + extra_authorize_params?: string, + allow_authorize_params?: string, + client__identifier: string, + client__secret: string, + client__redirect_uri?: string, + client__scheme: 'http' | 'https', + client__host: string, + client__port: number, + client__authorization_endpoint?: string, + client__token_endpoint?: string, + client__userinfo_endpoint?: string, + client__jwks_uri?: string, + client__end_session_endpoint?: string, + profile_url?: string +} diff --git a/app/models/auth_provider.rb b/app/models/auth_provider.rb index 29fdf0a0f..6a2db7851 100644 --- a/app/models/auth_provider.rb +++ b/app/models/auth_provider.rb @@ -13,7 +13,7 @@ class AuthProvider < ApplicationRecord end end - PROVIDABLE_TYPES = %w[DatabaseProvider OAuth2Provider].freeze + PROVIDABLE_TYPES = %w[DatabaseProvider OAuth2Provider OpenIdConnectProvider].freeze belongs_to :providable, polymorphic: true, dependent: :destroy accepts_nested_attributes_for :providable diff --git a/app/models/open_id_connect_provider.rb b/app/models/open_id_connect_provider.rb index 4069e00f1..cf2e805b2 100644 --- a/app/models/open_id_connect_provider.rb +++ b/app/models/open_id_connect_provider.rb @@ -6,12 +6,11 @@ class OpenIdConnectProvider < ApplicationRecord has_one :auth_provider, as: :providable validates :issuer, presence: true - validates :client_identifier, presence: true - validates :client_secret, presence: true - validates :client_host, presence: true - - validates :client_scheme, inclusion: { in: %w[http https] } - validates :client_port, numericality: { only_integer: true, greater_than: 0, less_than: 65_535 } + validates :client__identifier, presence: true + validates :client__secret, presence: true + validates :client__host, presence: true + validates :client__scheme, inclusion: { in: %w[http https] } + validates :client__port, numericality: { only_integer: true, greater_than: 0, less_than: 65_535 } validates :response_type, inclusion: { in: %w[code id_token], allow_nil: true } validates :response_mode, inclusion: { in: %w[query fragment form_post web_message], allow_nil: true } validates :display, inclusion: { in: %w[page popup touch wap], allow_nil: true } From 1a8dc390f3b76c97cffea3b3ba6ba871ba8665ea Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 4 Apr 2022 11:55:14 +0200 Subject: [PATCH 15/50] (front) export form component to angular --- .../authentication-provider/provider-form.tsx | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/app/frontend/src/javascript/components/authentication-provider/provider-form.tsx b/app/frontend/src/javascript/components/authentication-provider/provider-form.tsx index 3bbd9c5a8..55bdd18a1 100644 --- a/app/frontend/src/javascript/components/authentication-provider/provider-form.tsx +++ b/app/frontend/src/javascript/components/authentication-provider/provider-form.tsx @@ -1,6 +1,12 @@ import React from 'react'; import { useForm, SubmitHandler } from 'react-hook-form'; +import { react2angular } from 'react2angular'; import { AuthenticationProvider } from '../../models/authentication-provider'; +import { Loader } from '../base/loader'; +import { IApplication } from '../../models/application'; +import { RHFInput } from '../base/rhf-input'; + +declare const Application: IApplication; interface ProviderFormProps { provider?: AuthenticationProvider, @@ -9,7 +15,7 @@ interface ProviderFormProps { } export const ProviderForm: React.FC = ({ provider, onError, onSuccess }) => { - const { handleSubmit } = useForm({ defaultValues: { ...provider } }); + const { handleSubmit, register } = useForm({ defaultValues: { ...provider } }); const onSubmit: SubmitHandler = (data: AuthenticationProvider) => { if (data) { @@ -21,7 +27,17 @@ export const ProviderForm: React.FC = ({ provider, onError, o return (
- + ); }; + +const ProviderFormWrapper: React.FC = ({ provider, onError, onSuccess }) => { + return ( + + + + ); +}; + +Application.Components.component('providerForm', react2angular(ProviderFormWrapper, ['provider', 'onSuccess', 'onError'])); From 431d733ffe10f788ce7eea0f58bbf8832db13a38 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 4 Apr 2022 18:19:59 +0200 Subject: [PATCH 16/50] (wip) rhf select --- .../authentication-provider/provider-form.tsx | 13 +++++++----- .../javascript/components/base/fab-select.tsx | 20 +++++++++++++++++++ .../javascript/components/base/rhf-input.tsx | 4 ++-- .../controllers/admin/authentications.js | 14 +++++++++++++ .../templates/admin/authentications/new.html | 2 ++ 5 files changed, 46 insertions(+), 7 deletions(-) create mode 100644 app/frontend/src/javascript/components/base/fab-select.tsx diff --git a/app/frontend/src/javascript/components/authentication-provider/provider-form.tsx b/app/frontend/src/javascript/components/authentication-provider/provider-form.tsx index 55bdd18a1..4d1e426b3 100644 --- a/app/frontend/src/javascript/components/authentication-provider/provider-form.tsx +++ b/app/frontend/src/javascript/components/authentication-provider/provider-form.tsx @@ -5,17 +5,20 @@ import { AuthenticationProvider } from '../../models/authentication-provider'; import { Loader } from '../base/loader'; import { IApplication } from '../../models/application'; import { RHFInput } from '../base/rhf-input'; +import { useTranslation } from 'react-i18next'; declare const Application: IApplication; interface ProviderFormProps { + action: 'create' | 'update', provider?: AuthenticationProvider, onError: (message: string) => void, onSuccess: (message: string) => void, } -export const ProviderForm: React.FC = ({ provider, onError, onSuccess }) => { +export const ProviderForm: React.FC = ({ action, provider, onError, onSuccess }) => { const { handleSubmit, register } = useForm({ defaultValues: { ...provider } }); + const { t } = useTranslation('shared'); const onSubmit: SubmitHandler = (data: AuthenticationProvider) => { if (data) { @@ -27,17 +30,17 @@ export const ProviderForm: React.FC = ({ provider, onError, o return (
- + ); }; -const ProviderFormWrapper: React.FC = ({ provider, onError, onSuccess }) => { +const ProviderFormWrapper: React.FC = ({ action, provider, onError, onSuccess }) => { return ( - + ); }; -Application.Components.component('providerForm', react2angular(ProviderFormWrapper, ['provider', 'onSuccess', 'onError'])); +Application.Components.component('providerForm', react2angular(ProviderFormWrapper, ['action', 'provider', 'onSuccess', 'onError'])); diff --git a/app/frontend/src/javascript/components/base/fab-select.tsx b/app/frontend/src/javascript/components/base/fab-select.tsx new file mode 100644 index 000000000..132904ea4 --- /dev/null +++ b/app/frontend/src/javascript/components/base/fab-select.tsx @@ -0,0 +1,20 @@ +import React, { SelectHTMLAttributes } from 'react'; +import Select from 'react-select'; +import { Controller } from 'react-hook-form'; + +interface FabSelectProps extends SelectHTMLAttributes { + className?: string +} + +export const FabSelect: React.FC = ({ className }) => { + return ( +
+ + ); }; diff --git a/app/frontend/src/javascript/components/base/fab-multi-select.tsx b/app/frontend/src/javascript/components/base/fab-multi-select.tsx new file mode 100644 index 000000000..077b69b39 --- /dev/null +++ b/app/frontend/src/javascript/components/base/fab-multi-select.tsx @@ -0,0 +1,50 @@ +import React, { SelectHTMLAttributes } from 'react'; +import Select from 'react-select'; +import { Controller, Path } from 'react-hook-form'; +import { Control } from 'react-hook-form/dist/types/form'; +import { FieldValues } from 'react-hook-form/dist/types/fields'; +import { FieldPath } from 'react-hook-form/dist/types/path'; +import { FieldPathValue, UnpackNestedValue } from 'react-hook-form/dist/types'; + +interface FabSelectProps extends SelectHTMLAttributes { + id: string, + label?: string, + className?: string, + control: Control, + placeholder?: string, + options: Array>, + valuesDefault?: Array, +} + +/** + * Option format, expected by react-select + * @see https://github.com/JedWatson/react-select + */ +type selectOption = { value: TOptionValue, label: string }; + +/** + * This component is a wrapper around react-select to use with react-hook-form. + * It is a multi-select component. + */ +export const FabMultiSelect = ({ id, label, className, control, placeholder, options, valuesDefault }: FabSelectProps) => { + return ( +