From ac499f9555bf6aaaddd6f7de1058dcbd25fe5979 Mon Sep 17 00:00:00 2001 From: cizmarty <88806046+cizmarty@users.noreply.github.com> Date: Tue, 30 Apr 2024 14:13:19 +0200 Subject: [PATCH] Feature/eid login (#656) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Handle case when actor key value can be null Co-authored-by: Lucia Janíková <43437253+luciajanikova@users.noreply.github.com> * Handle case when actor key value can be null Co-authored-by: Lucia Janíková <43437253+luciajanikova@users.noreply.github.com> * Add login button, expect callback URL from UPVS to be always /login for safety reasons * Fix logout redirect * Improve login button, fetch assertion * Persist subject data from assertion * Disallow subject login for non-full representation * Update app/views/sessions/insufficient_representation.html.erb Co-authored-by: Jano Suchal * Remove index which is not necessary --------- Co-authored-by: Ahmed Al Hafoudh Co-authored-by: Lucia Janíková <43437253+luciajanikova@users.noreply.github.com> Co-authored-by: Jano Suchal --- Gemfile.lock | 4 +- app/assets/images/eid-sk.svg | 2 +- app/controllers/sessions_controller.rb | 28 ++++++- app/models/eid_token.rb | 2 +- app/models/upvs/assertion.rb | 73 +++++++++++++++++++ app/views/components/_header.html.erb | 3 - app/views/eid/onboarding/new.html.erb | 4 + app/views/profiles/show.html.erb | 33 +++++++++ .../insufficient_representation.html.erb | 9 +++ app/views/sessions/new.html.erb | 4 + config/initializers/omniauth.rb | 1 + config/routes.rb | 2 + ...0240427124856_add_subject_data_to_users.rb | 7 ++ db/structure.sql | 62 ++-------------- lib/omniauth/strategies/eid.rb | 2 +- 15 files changed, 172 insertions(+), 64 deletions(-) create mode 100644 app/models/upvs/assertion.rb create mode 100644 app/views/sessions/insufficient_representation.html.erb create mode 100644 db/migrate/20240427124856_add_subject_data_to_users.rb diff --git a/Gemfile.lock b/Gemfile.lock index fad1fa79..dbcd617e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -255,7 +255,7 @@ GEM snaky_hash (~> 2.0) version_gem (~> 1.1) oj (3.14.2) - omniauth (2.1.1) + omniauth (2.1.2) hashie (>= 3.4.6) rack (>= 2.2.3) rack-protection @@ -295,7 +295,7 @@ GEM que (>= 1) sinatra racc (1.7.1) - rack (2.2.8) + rack (2.2.9) rack-protection (3.0.5) rack rack-proxy (0.7.6) diff --git a/app/assets/images/eid-sk.svg b/app/assets/images/eid-sk.svg index 842451ed..3d4bdd54 100644 --- a/app/assets/images/eid-sk.svg +++ b/app/assets/images/eid-sk.svg @@ -16,5 +16,5 @@ - Prihlásiť cez EID + Cez slovensko.sk \ No newline at end of file diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index ef33e634..816e9074 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -11,6 +11,11 @@ def create end if new_eid_identity? + unless fully_represents_subject? + render :insufficient_representation, locals: { eid_token: eid_token } + return + end + render :new_eid_identity, locals: { eid_token: eid_token } return end @@ -25,7 +30,18 @@ def create notice = user.previously_new_record? ? 'first_time_login' : 'Prihlásenie úspešné. Vitajte!' if eid_identity_approval? - user.update!(eid_sub: eid_sub_from_auth) + assertion = Upvs::Assertion.assertion(eid_token) + + if assertion + user.update!( + eid_sub: eid_sub_from_auth, + subject_name: assertion.subject_name, + subject_cin: assertion.subject_cin, + subject_edesk_number: assertion.subject_edesk_number, + ) + else + user.update!(eid_sub: eid_sub_from_auth) + end end unless should_keep_eid_token_in_session?(user.eid_sub) @@ -57,7 +73,10 @@ def logout def destroy if should_perform_eid_logout? - redirect_to eid_token.generate_logout_url(expires_in: 5.minutes) + eid_logout_url = eid_token.generate_logout_url(expires_in: 5.minutes) + reset_session + + redirect_to eid_logout_url else logout end @@ -95,4 +114,9 @@ def after_login_redirect_path return session[:after_login_callback] if session[:after_login_callback]&.start_with?("/") # Only allow local redirects root_path end + + def fully_represents_subject? + assertion = Upvs::Assertion.assertion(eid_token) + assertion&.fully_represents_subject? + end end diff --git a/app/models/eid_token.rb b/app/models/eid_token.rb index af15ef7e..4840c226 100644 --- a/app/models/eid_token.rb +++ b/app/models/eid_token.rb @@ -14,7 +14,7 @@ def decoded_token end def sub - decoded_token&.first&.fetch('actor')&.fetch('sub') + decoded_token&.first&.fetch('sub') end def name diff --git a/app/models/upvs/assertion.rb b/app/models/upvs/assertion.rb new file mode 100644 index 00000000..607bd08d --- /dev/null +++ b/app/models/upvs/assertion.rb @@ -0,0 +1,73 @@ +module Upvs + class Assertion + include ActiveModel::Model + attr_accessor(:raw, :subject_name, :subject_id, :subject_cin, :subject_edesk_number, :delegation_type) + + DELEGATION_TYPES = { + legal_representation: '0', + full_representation: '1', + partial_representation: '2', + } + + def fully_represents_subject? + delegation_type&.to_s&.in?(full_representations) + end + + def self.new_from_xml(raw:) + return unless raw + + doc = Nokogiri::XML(raw) + return unless doc + + doc.remove_namespaces! + doc_attrs = doc.xpath('//Assertion/AttributeStatement/Attribute') + return unless doc_attrs + + new( + raw:, + subject_name: doc_attrs.detect{|n| n['Name'] == 'Subject.FormattedName' }&.xpath('AttributeValue')&.text, + subject_id: doc_attrs.detect{|n| n['Name'] == 'SubjectID' }&.xpath('AttributeValue')&.text, + subject_cin: doc_attrs.detect{|n| n['Name'] == 'Subject.ICO' }&.xpath('AttributeValue')&.text, + subject_edesk_number: doc_attrs.detect{|n| n['Name'] == 'Subject.eDeskNumber' }&.xpath('AttributeValue')&.text, + delegation_type: doc_attrs.detect{|n| n['Name'] == 'DelegationType' }&.xpath('AttributeValue')&.text, + ) + end + + def self.assertion(eid_token, client: Faraday, url: "#{ENV.fetch('AUTH_EID_BASE_URL')}/api/upvs/assertion?token=#{eid_token&.api_token}") + new_from_xml(raw: get_from_sk_api(client, url, eid_token)) + end + + def self.get_from_sk_api(client, url, eid_token) + headers = { + "Accept": "application/samlassertion+xml", + "AUTHORIZATION": "Bearer #{eid_token&.api_token}", + } + + response = client.get(url, {}, headers) + error = begin + JSON.parse(response.body) + rescue StandardError + nil + end + if error && error['message'] + return nil + end + response.body + rescue StandardError => _e + raise + nil + end + + private + + def full_representations + [ + DELEGATION_TYPES[:legal_representation], + DELEGATION_TYPES[:full_representation], + ] + end + + class SkApiError < StandardError + end + end +end diff --git a/app/views/components/_header.html.erb b/app/views/components/_header.html.erb index 4a0e0c49..e29a5757 100644 --- a/app/views/components/_header.html.erb +++ b/app/views/components/_header.html.erb @@ -27,9 +27,6 @@ <%= current_user.email %> - <% if eid_token&.valid? %> - EID - <% end %>