From f58da357b7c72012d7d525f225f816ad922f7d9d Mon Sep 17 00:00:00 2001 From: Du Peng Date: Fri, 19 Jan 2024 13:55:32 +0100 Subject: [PATCH] (wip) saml sso --- .../api/auth_providers_controller.rb | 7 + .../authentication-provider/provider-form.tsx | 5 +- .../authentication-provider/saml-form.tsx | 45 ++++++ .../models/authentication-provider.ts | 10 +- app/models/saml_provider.rb | 11 ++ .../api/auth_providers/show.json.jbuilder | 6 + config/initializers/devise.rb | 7 + .../20240116163703_create_saml_providers.rb | 10 ++ db/structure.sql | 135 ++++++++++++++++-- lib/omni_auth/saml.rb | 3 + 10 files changed, 226 insertions(+), 13 deletions(-) create mode 100644 app/frontend/src/javascript/components/authentication-provider/saml-form.tsx create mode 100644 app/models/saml_provider.rb create mode 100644 db/migrate/20240116163703_create_saml_providers.rb create mode 100644 lib/omni_auth/saml.rb diff --git a/app/controllers/api/auth_providers_controller.rb b/app/controllers/api/auth_providers_controller.rb index e975e42f1..43616067b 100644 --- a/app/controllers/api/auth_providers_controller.rb +++ b/app/controllers/api/auth_providers_controller.rb @@ -105,6 +105,13 @@ class API::AuthProvidersController < API::APIController 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'] == SamlProvider.name + params.require(:auth_provider) + .permit(:id, :name, :providable_type, + providable_attributes: [:id, :sp_entity_id, :idp_sso_service_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/frontend/src/javascript/components/authentication-provider/provider-form.tsx b/app/frontend/src/javascript/components/authentication-provider/provider-form.tsx index 80abc0876..5eb8227b6 100644 --- a/app/frontend/src/javascript/components/authentication-provider/provider-form.tsx +++ b/app/frontend/src/javascript/components/authentication-provider/provider-form.tsx @@ -20,6 +20,7 @@ import { FabButton } from '../base/fab-button'; import AuthProviderAPI from '../../api/auth-provider'; import { OpenidConnectForm } from './openid-connect-form'; import { DatabaseForm } from './database-form'; +import { SamlForm } from './saml-form'; declare const Application: IApplication; @@ -27,7 +28,8 @@ declare const Application: IApplication; const METHODS = { DatabaseProvider: 'local_database', OAuth2Provider: 'oauth2', - OpenIdConnectProvider: 'openid_connect' + OpenIdConnectProvider: 'openid_connect', + SamlProvider: 'saml' }; interface ProviderFormProps { @@ -116,6 +118,7 @@ export const ProviderForm: React.FC = ({ action, provider, on currentFormValues={output.providable_attributes as OpenIdConnectProvider} formState={formState} setValue={setValue} />} + {providableType === 'SamlProvider' && } {providableType && providableType !== 'DatabaseProvider' && { + register: UseFormRegister, + formState: FormState, + strategyName?: string, +} + +/** + * Partial form to fill the OAuth2 settings for a new/existing authentication provider. + */ +export const SamlForm = ({ register, strategyName, formState }: SamlFormProps) => { + const { t } = useTranslation('admin'); + + /** + * Build the callback URL, based on the strategy name. + */ + const buildCallbackUrl = (): string => { + return `${window.location.origin}/users/auth/${strategyName}/callback`; + }; + + return ( +
+
+ + + +
+ ); +}; diff --git a/app/frontend/src/javascript/models/authentication-provider.ts b/app/frontend/src/javascript/models/authentication-provider.ts index a8c34d63c..59700726a 100644 --- a/app/frontend/src/javascript/models/authentication-provider.ts +++ b/app/frontend/src/javascript/models/authentication-provider.ts @@ -1,4 +1,4 @@ -export type ProvidableType = 'DatabaseProvider' | 'OAuth2Provider' | 'OpenIdConnectProvider'; +export type ProvidableType = 'DatabaseProvider' | 'OAuth2Provider' | 'OpenIdConnectProvider' | 'SamlProvider'; export interface AuthenticationProvider { id?: number, @@ -7,7 +7,7 @@ export interface AuthenticationProvider { providable_type: ProvidableType, strategy_name: string auth_provider_mappings_attributes: Array, - providable_attributes?: OAuth2Provider | OpenIdConnectProvider + providable_attributes?: OAuth2Provider | OpenIdConnectProvider | SamlProvider } export type mappingType = 'string' | 'text' | 'date' | 'integer' | 'boolean'; @@ -65,6 +65,12 @@ export interface OpenIdConnectProvider { extra_authorize_parameters?: string, } +export interface SamlProvider { + id?: string, + sp_entity_id: string, + idp_sso_service_url: string +} + export interface MappingFields { user: Array<[string, mappingType]>, profile: Array<[string, mappingType]> diff --git a/app/models/saml_provider.rb b/app/models/saml_provider.rb new file mode 100644 index 000000000..d444763cf --- /dev/null +++ b/app/models/saml_provider.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +# SAML Provider is a special type of AuthProvider which provides authentication through an external SSO server using +# the SAML protocol. + +class SamlProvider < ApplicationRecord + has_one :auth_provider, as: :providable, dependent: :destroy + + validates :sp_entity_id, presence: true + validates :idp_sso_service_url, presence: true +end diff --git a/app/views/api/auth_providers/show.json.jbuilder b/app/views/api/auth_providers/show.json.jbuilder index 2700aeed3..ff0535c57 100644 --- a/app/views/api/auth_providers/show.json.jbuilder +++ b/app/views/api/auth_providers/show.json.jbuilder @@ -19,3 +19,9 @@ if @provider.providable_type == OpenIdConnectProvider.name json.extra_authorize_params @provider.providable[:extra_authorize_params].to_json end end + +if @provider.providable_type == SamlProvider.name + json.providable_attributes do + json.extract! @provider.providable, :id, :sp_entity_id, :idp_sso_service_url + end +end diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index c2f2faceb..7a465d9f1 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -244,6 +244,13 @@ Devise.setup do |config| active_provider.oidc_config.merge( strategy_class: OmniAuth::Strategies::SsoOpenidConnectProvider ) + + when 'SamlProvider' + require_relative '../../lib/omni_auth/saml' + config.omniauth active_provider.strategy_name.to_sym, + active_provider.providable.sp_entity_id, + active_provider.providable.idp_sso_service_url, + strategy_class: OmniAuth::Strategies::SsoSamlProvider end end diff --git a/db/migrate/20240116163703_create_saml_providers.rb b/db/migrate/20240116163703_create_saml_providers.rb new file mode 100644 index 000000000..491ba9c40 --- /dev/null +++ b/db/migrate/20240116163703_create_saml_providers.rb @@ -0,0 +1,10 @@ +class CreateSamlProviders < ActiveRecord::Migration[7.0] + def change + create_table :saml_providers do |t| + t.string :sp_entity_id + t.string :idp_sso_service_url + + t.timestamps + end + end +end diff --git a/db/structure.sql b/db/structure.sql index 01fa2735d..62f1c8dec 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -9,6 +9,13 @@ SET xmloption = content; SET client_min_messages = warning; SET row_security = off; +-- +-- Name: public; Type: SCHEMA; Schema: -; Owner: - +-- + +-- *not* creating schema, since initdb creates it + + -- -- Name: fuzzystrmatch; Type: EXTENSION; Schema: -; Owner: - -- @@ -2235,6 +2242,41 @@ CREATE SEQUENCE public.payment_gateway_objects_id_seq ALTER SEQUENCE public.payment_gateway_objects_id_seq OWNED BY public.payment_gateway_objects.id; +-- +-- Name: payment_infos; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.payment_infos ( + id bigint NOT NULL, + data jsonb, + state character varying, + payment_for character varying, + service character varying, + statistic_profile_id bigint, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL +); + + +-- +-- Name: payment_infos_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.payment_infos_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: payment_infos_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.payment_infos_id_seq OWNED BY public.payment_infos.id; + + -- -- Name: payment_schedule_items; Type: TABLE; Schema: public; Owner: - -- @@ -3224,6 +3266,38 @@ CREATE SEQUENCE public.roles_id_seq ALTER SEQUENCE public.roles_id_seq OWNED BY public.roles.id; +-- +-- Name: saml_providers; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.saml_providers ( + id bigint NOT NULL, + sp_entity_id character varying, + idp_sso_service_url character varying, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL +); + + +-- +-- Name: saml_providers_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.saml_providers_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: saml_providers_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.saml_providers_id_seq OWNED BY public.saml_providers.id; + + -- -- Name: schema_migrations; Type: TABLE; Schema: public; Owner: - -- @@ -4316,8 +4390,8 @@ CREATE TABLE public.users ( is_allow_newsletter boolean, current_sign_in_ip inet, last_sign_in_ip inet, - mapped_from_sso character varying, validated_at timestamp without time zone, + mapped_from_sso character varying, supporting_documents_reminder_sent_at timestamp(6) without time zone ); @@ -4870,6 +4944,13 @@ ALTER TABLE ONLY public.organizations ALTER COLUMN id SET DEFAULT nextval('publi ALTER TABLE ONLY public.payment_gateway_objects ALTER COLUMN id SET DEFAULT nextval('public.payment_gateway_objects_id_seq'::regclass); +-- +-- Name: payment_infos id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.payment_infos ALTER COLUMN id SET DEFAULT nextval('public.payment_infos_id_seq'::regclass); + + -- -- Name: payment_schedule_items id; Type: DEFAULT; Schema: public; Owner: - -- @@ -5066,6 +5147,13 @@ ALTER TABLE ONLY public.reservations ALTER COLUMN id SET DEFAULT nextval('public ALTER TABLE ONLY public.roles ALTER COLUMN id SET DEFAULT nextval('public.roles_id_seq'::regclass); +-- +-- Name: saml_providers id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.saml_providers ALTER COLUMN id SET DEFAULT nextval('public.saml_providers_id_seq'::regclass); + + -- -- Name: settings id; Type: DEFAULT; Schema: public; Owner: - -- @@ -5799,6 +5887,14 @@ ALTER TABLE ONLY public.payment_gateway_objects ADD CONSTRAINT payment_gateway_objects_pkey PRIMARY KEY (id); +-- +-- Name: payment_infos payment_infos_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.payment_infos + ADD CONSTRAINT payment_infos_pkey PRIMARY KEY (id); + + -- -- Name: payment_schedule_items payment_schedule_items_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -6023,6 +6119,14 @@ ALTER TABLE ONLY public.roles ADD CONSTRAINT roles_pkey PRIMARY KEY (id); +-- +-- Name: saml_providers saml_providers_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.saml_providers + ADD CONSTRAINT saml_providers_pkey PRIMARY KEY (id); + + -- -- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -7004,6 +7108,13 @@ CREATE INDEX index_payment_gateway_objects_on_item_type_and_item_id ON public.pa CREATE INDEX index_payment_gateway_objects_on_payment_gateway_object_id ON public.payment_gateway_objects USING btree (payment_gateway_object_id); +-- +-- Name: index_payment_infos_on_statistic_profile_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_payment_infos_on_statistic_profile_id ON public.payment_infos USING btree (statistic_profile_id); + + -- -- Name: index_payment_schedule_items_on_invoice_id; Type: INDEX; Schema: public; Owner: - -- @@ -7795,14 +7906,6 @@ CREATE INDEX proof_of_identity_type_id_and_proof_of_identity_refusal_id ON publi CREATE UNIQUE INDEX unique_not_null_external_id ON public.invoicing_profiles USING btree (external_id) WHERE (external_id IS NOT NULL); --- --- Name: accounting_periods accounting_periods_del_protect; Type: RULE; Schema: public; Owner: - --- - -CREATE RULE accounting_periods_del_protect AS - ON DELETE TO public.accounting_periods DO INSTEAD NOTHING; - - -- -- Name: accounting_periods accounting_periods_upd_protect; Type: RULE; Schema: public; Owner: - -- @@ -7836,6 +7939,14 @@ ALTER TABLE ONLY public.payment_schedules ADD CONSTRAINT fk_rails_00308dc223 FOREIGN KEY (wallet_transaction_id) REFERENCES public.wallet_transactions(id); +-- +-- Name: payment_infos fk_rails_0308366a58; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.payment_infos + ADD CONSTRAINT fk_rails_0308366a58 FOREIGN KEY (statistic_profile_id) REFERENCES public.statistic_profiles(id); + + -- -- Name: cart_item_event_reservation_booking_users fk_rails_0964335a37; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -9182,8 +9293,10 @@ INSERT INTO "schema_migrations" (version) VALUES ('20230328094808'), ('20230328094809'), ('20230331132506'), +('20230509121907'), ('20230509161557'), ('20230510141305'), +('20230511080650'), ('20230511081018'), ('20230524080448'), ('20230524083558'), @@ -9199,11 +9312,13 @@ INSERT INTO "schema_migrations" (version) VALUES ('20230720085857'), ('20230728072726'), ('20230728090257'), +('20230825101952'), ('20230828073428'), ('20230831103208'), ('20230901090637'), ('20230907124230'), ('20231103093436'), -('20231108094433'); +('20231108094433'), +('20240116163703'); diff --git a/lib/omni_auth/saml.rb b/lib/omni_auth/saml.rb new file mode 100644 index 000000000..d63791c61 --- /dev/null +++ b/lib/omni_auth/saml.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require_relative 'strategies/saml_provider'