class Decidim::Msad::Authentication::Authenticator
Attributes
Public Class Methods
# File lib/decidim/msad/authentication/authenticator.rb, line 9 def initialize(organization, oauth_hash) @organization = organization @oauth_hash = oauth_hash @new_user = false end
Public Instance Methods
User is only identified in case they were already logged in during the authentication flow. This can happen in case the service allows public registrations and authorization through MSAD is enabled in the user's profile. This adds a new identity to an existing user unless the identity is already bound to another user profile which would happen e.g. in the following situation:
-
The user registered to the service through
OmniAuth
for the first time using thisOmniAuth
identity. -
Next time they came to the service, they created a new user account in
Decidim
using the registration form with another email address. -
Now, they sign in to the service using the manually created account.
-
They go to the authorization view to authorize themselves through MSAD (which adds the authorization metadata information that might be required in order to perform actions).
-
Now the person has two accounts, one of which is already bound to a user's
OmniAuth
identity. -
There is a conflict because we cannot bind the same identity to two different user profiles (which could lead to thefts).
-
This method will notice it and the
OmniAuth
login information will not be stored to the user's authorization metadata.
# File lib/decidim/msad/authentication/authenticator.rb, line 95 def identify_user!(user) identity = user.identities.find_by( organization: organization, provider: oauth_data[:provider], uid: user_identifier ) return identity if identity # Check that the identity is not already bound to another user. id = ::Decidim::Identity.find_by( organization: organization, provider: oauth_data[:provider], uid: user_identifier ) raise IdentityBoundToOtherUserError if id user.identities.create!( organization: organization, provider: oauth_data[:provider], uid: user_identifier ) end
Keeps the user data in sync with the federation server after everything else is done and the user is just aboud to be redirected further to the next page after the successful login. This is called on every successful login callback request.
# File lib/decidim/msad/authentication/authenticator.rb, line 158 def update_user!(user) user_changed = false if user.email != verified_email user_changed = true user.email = verified_email user.skip_reconfirmation! end user.newsletter_notifications_at = Time.now if user_newsletter_subscription?(user) user.save! if user_changed end
# File lib/decidim/msad/authentication/authenticator.rb, line 26 def user_params_from_oauth_hash return nil if oauth_data.empty? return nil if user_identifier.blank? { provider: oauth_data[:provider], uid: user_identifier, name: oauth_data[:info][:name], # The nickname is automatically "parametrized" by Decidim core from # the name string, i.e. it will be in correct format. nickname: oauth_data[:info][:nickname] || oauth_data[:info][:name], oauth_signature: user_signature, avatar_url: oauth_data[:info][:image], raw_data: oauth_hash } end
Validate gets run very early in the authentication flow as it's the first method to call before anything else is done. The purpose of this method is to check that the authentication data returned by the AD federation service is valid and contains all the information that we would expect. Therefore, it “validates” that the authentication can be performed.
# File lib/decidim/msad/authentication/authenticator.rb, line 49 def validate! raise ValidationError, "No SAML data provided" unless saml_attributes actual_attributes = saml_attributes.attributes actual_attributes.delete("fingerprint") raise ValidationError, "No SAML data provided" if actual_attributes.blank? data_blank = actual_attributes.all? { |_k, val| val.blank? } raise ValidationError, "Invalid SAML data" if data_blank raise ValidationError, "Invalid person dentifier" if person_identifier_digest.blank? # Check if there is already an existing identity which is bound to an # existing user record. If the identity is not found or the user # record bound to that identity no longer exists, the signed in user # is a new user. id = ::Decidim::Identity.find_by( organization: organization, provider: oauth_data[:provider], uid: user_identifier ) @new_user = id ? id.user.blank? : true true end
# File lib/decidim/msad/authentication/authenticator.rb, line 15 def verified_email @verified_email ||= begin if oauth_data[:info][:email] oauth_data[:info][:email] else domain = ::Decidim::Msad.auto_email_domain || organization.host "msad-#{person_identifier_digest}@#{domain}" end end end
Protected Instance Methods
# File lib/decidim/msad/authentication/authenticator.rb, line 204 def metadata_collector @metadata_collector ||= ::Decidim::Msad::Verification::Manager.metadata_collector_for( saml_attributes ) end
# File lib/decidim/msad/authentication/authenticator.rb, line 183 def oauth_data @oauth_data ||= oauth_hash.slice(:provider, :uid, :info) end
Digested format of the person's identifier to be used in the auto-generated emails. This is used so that the actual identifier is not revealed directly to the end user.
# File lib/decidim/msad/authentication/authenticator.rb, line 219 def person_identifier_digest return if user_identifier.blank? @person_identifier_digest ||= Digest::MD5.hexdigest( "MSAD:#{user_identifier}:#{Rails.application.secrets.secret_key_base}" ) end
# File lib/decidim/msad/authentication/authenticator.rb, line 187 def saml_attributes @saml_attributes ||= oauth_hash[:extra][:raw_info] end
# File lib/decidim/msad/authentication/authenticator.rb, line 191 def user_identifier @user_identifier ||= oauth_data[:uid] end
Create a unique signature for the user that will be used for the granted authorization.
# File lib/decidim/msad/authentication/authenticator.rb, line 197 def user_signature @user_signature ||= ::Decidim::OmniauthRegistrationForm.create_signature( oauth_data[:provider], user_identifier ) end