apokalipto / devise_saml_authenticatable

Devise SAML 2.0 authentication strategy
MIT License
292 stars 153 forks source link

[Multiple Authentication] Devise authenticated methods via SSO requires a password if a user doesn't exist in DB. #187

Open R1cro opened 3 years ago

R1cro commented 3 years ago

I'll be as short as possible.

Rails: 5.1.4 Ruby: 2.2.3 (The very old shit, but this is work project environment...)

I'm working with Devise gem and our project is using :database_authenticatable. Now we are going to implement SSO (SAML or OIDC).

So, I added the :saml_authenticatable. With existing :validatable devise option unable to authenticate a NEW user via SSO (SAML) - a password is required from devise validation.

I've already looked at some of the issues (#132 and #177) here and identified the following:

So, I use this code (/config/initializers/devise.rb):

  config.saml_route_helper_prefix = 'saml'
  config.saml_create_user = true
  config.saml_update_resource_hook = ->(user, response, auth_value) {
    Devise.saml_default_update_resource_hook.call(user, response, auth_value)
    user.update!(password: 'SOME_KIND_OF_PASSWORD_GOES_HERE!')
  }

As mentioned from #177, that user password validation happens before the hook is called.

Also, I saw this guide: https://github.com/apokalipto/devise_saml_authenticatable/wiki/Supporting-multiple-authentication-strategies, but I'm not sure about Controller stuff.

Anyway, I did this:

/controllers/saml_sessions_controller.rb

class SamlSessionsController < Devise::SamlSessionsController
  after_action :store_winning_strategy, only: :create

  private

  def store_winning_strategy
    warden.session(:user)[:strategy] = warden
                                           .winning_strategies[:user]
                                           .class.name.demodulize.underscore.to_sym
  end
end

/routes.rb

devise_for :users, :controllers => { :saml_sessions => "saml_sessions"}

...but my RoR skills are not so good to know is that "patching" thing done correctly by me.

My devise config:

Devise.setup do |config|

 # other devise config

  config.saml_route_helper_prefix = 'saml'
  config.saml_create_user = true
  config.saml_update_resource_hook = ->(user, response, auth_value) {
    Devise.saml_default_update_resource_hook.call(user, response, auth_value)
    user.update!(password: 'SOME_KIND_OF_PASSWORD_GOES_HERE!')
  }
  config.saml_update_user = true
  config.saml_default_user_key = :email
  config.saml_session_index_key = :session_index
  config.saml_use_subject = true
  config.idp_settings_adapter = nil
  config.saml_configure do |settings|
     settings.assertion_consumer_service_url     = "http://localhost:3000/users/saml/auth"
     settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
     settings.name_identifier_format             = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
     settings.issuer                             = "http://localhost:3000/users/saml/metadata"
     settings.authn_context                      = ""
     settings.idp_slo_target_url                 = ""
     settings.idp_sso_target_url                 = "XXX/sso/saml"
     settings.idp_cert_fingerprint               = '9B:71:7F:FC:59:XXX'
     settings.idp_cert_fingerprint_algorithm     = 'http://www.w3.org/2000/09/xmldsig#sha256'
  end

  config.warden do |manager|
    manager.failure_app = CustomFailure
  end
end

My routes config:

 devise_for :users, :controllers => { :saml_sessions => "saml_sessions"}

User model:

 devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable,
         :saml_authenticatable, :trackable

Any solutions for this validation issue?

adamstegman commented 3 years ago

Thanks for the detailed question! Do you have an idea of when the validation is occurring? For new users, it seems pretty straightforward: https://github.com/apokalipto/devise_saml_authenticatable/blob/b12a43f9fce077f9e31398a6185be21a9577474a/lib/devise_saml_authenticatable/model.rb#L61-L69.

I'll try to take a look at this myself soon if you don't have a chance.

adamstegman commented 3 years ago

Ah, the issue is in the custom hook. You need to set the password before calling the original hook:

  config.saml_update_resource_hook = ->(user, response, auth_value) {
    user.assign_attributes(password: 'SOME_KIND_OF_PASSWORD_GOES_HERE')
    Devise.saml_default_update_resource_hook.call(user, response, auth_value)
  }
vedant-jain03 commented 2 years ago

Ah, the issue is in the custom hook. You need to set the password before calling the original hook:

  config.saml_update_resource_hook = ->(user, response, auth_value) {
    user.assign_attributes(password: 'SOME_KIND_OF_PASSWORD_GOES_HERE')
    Devise.saml_default_update_resource_hook.call(user, response, auth_value)
  }

It is not working @adamstegman!

Aeramor commented 1 year ago

I can confirm this does work:

    config.saml_update_resource_hook = ->(user, response, auth_value) {
      user.password = SecureRandom.uuid
      Devise.saml_default_update_resource_hook.call(user, response, auth_value)
    }

I had other errors though, in my case it was attribute mapping was off. Once I fixed that users are created even though I have database_authenticatable.