apokalipto / devise_saml_authenticatable

Devise SAML 2.0 authentication strategy
MIT License
297 stars 155 forks source link

ActiveRecord::RecordInvalid "Password must exist" #132

Open VincentSim opened 5 years ago

VincentSim commented 5 years ago

Hi all,

I don't undestand why I've got this issue -> ActiveRecord::RecordInvalid "Password must exist" I use google app saml

Here is my routes.rb

Rails.application.routes.draw do

  devise_for :users,  skip: :saml_authenticatable, controllers: { registrations: "user/registrations", sessions: "user/sessions"  }
  as :user do
    get 'experts/sign_up', to: 'user/registrations#new_expert'
  end

   # opt-in saml_authenticatable
  devise_scope :user do
    scope "users", controller: 'devise/saml_sessions' do
      get :new, path: "saml/sign_in", as: :new_user_sso_session
      post :create, path: "saml/auth", as: :user_sso_session
      get :destroy, path: "sign_out", as: :destroy_user_sso_session
      get :metadata, path: "saml/metadata", as: :metadata_user_sso_session
      match :idp_sign_out, path: "saml/idp_sign_out", via: [:get, :post]
    end
  end
end

and my devise.rb

# Configure with your SAML settings (see ruby-saml's README for more information: https://github.com/onelogin/ruby-saml).
  server_url = 'https://localhost:3000'
  config.saml_configure do |settings|
    # assertion_consumer_service_url is required starting with ruby-saml 1.4.3: https://github.com/onelogin/ruby-saml#updating-from-142-to-143
    settings.assertion_consumer_service_url     = "#{server_url}/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:1.1:nameid-format:emailAddress"
    settings.issuer                             = "#{server_url}/users/saml/metadata"
    settings.authn_context                      = ""
    settings.idp_slo_target_url                 = ""

    # similar to https://accounts.google.com/o/saml2/idp?idpid=xxxxxx
    settings.idp_sso_target_url                 = "https://accounts.google.com/o/saml2/idp?idpid=C02f7ydiq"

    settings.idp_cert                           = <<-CERT.chomp
      -----BEGIN CERTIFICATE-----

      CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
      MIIBCgKCAQEAs87ETynlv8yuF3ivvYYz4vav6aoiD3V4/J4ewlq6XdirMNICPhKhQnr+3sdfNafy
      LYLq4V2RgoZu7+y2/oq3quc7R6H83X1pTK75XXJcdgbskBmDcmdqUEL0Q/r19+KZsJipVdkeuGU8
      NvVOHTIyvPySL0/8vLj/Z33aSibHCPguCr6oTLvcORqH+xY9JVR2tT1EGyyEsYUrsWLx2ICZL1tv

      -----END CERTIFICATE-----
      CERT

  end
adamstegman commented 5 years ago

It sounds like there's a model validation for password. Can you share your model code too?

VincentSim commented 5 years ago

yes

class User < ApplicationRecord

  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :saml_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

# .....
end
adamstegman commented 5 years ago

validatable is adding that validation for you. If you want to allow either form of authentication, you'll need to write your own validations so that password is not required. You'll probably also want to only allow database login for people who have passwords!

VincentSim commented 5 years ago

How it is possible to generate a password for each sign up with saml_auth ? I don't understand the flow to create a user.

adamstegman commented 5 years ago

That's an option. Here's the create user flow: https://github.com/apokalipto/devise_saml_authenticatable/blob/master/lib/devise_saml_authenticatable/model.rb#L54-L66.

If you set devise.saml_create_user = true, you can set devise.saml_update_resource_hook to be a proc that accepts the user and generates a password. In config/initializers/devise.rb:

Devise.setup do |config|
  config.saml_create_user = true
  config.saml_update_resource_hook = ->(user, response, auth_value) {
    # Maintain the default behavior of setting attributes from the SAML response
    Devise.saml_default_update_resource_hook.call(user, response, auth_value)

    # Add your behavior to generate a password
    user.update!(password: '12345')
  }
end
VincentSim commented 5 years ago

I have still this error :(

AbstractController::ActionNotFound (The action 'create' could not be found for Devise::SamlSessionsController):
adamstegman commented 5 years ago

That's surprising, because the Devise::SamlSessionsController inherits from Devise::SessionsController, which definitely implements create. Have you modified either of those classes in your codebase?

VincentSim commented 5 years ago

@adamstegman yes I have override devise controller like this.

class User::RegistrationsController < Devise::RegistrationsController
  # before_action :configure_sign_up_params, only: [:create]
  # before_action :configure_account_update_params, only: [:update]

  # GET /resource/sign_up
  def new
    @list_email = params[:email]
    super
  end

  def new_expert
    new
  end

  # POST /resource
  def create
    super
  end

  # GET /resource/edit
  # def edit
  #   super
  # end

  # PUT /resource
  # def update
  #   super
  # end

  # DELETE /resource
  # def destroy
  #   super
  # end

  # GET /resource/cancel
  # Forces the session data which is usually expired after sign
  # in to be expired now. This is useful if the user wants to
  # cancel oauth signing in/up in the middle of the process,
  # removing all OAuth session data.
  # def cancel
  #   super
  # end

  protected

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_sign_up_params
  #   devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute])
  # end

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_account_update_params
  #   devise_parameter_sanitizer.permit(:account_update, keys: [:attribute])
  # end
  #def after_sign_up_path_for(resource)
  # end
  # The path used after sign up.
  # def after_sign_up_path_for(resource)
  #   super(resource)
  # end

  # The path used after sign up for inactive accounts.
  # def after_inactive_sign_up_path_for(resource)
  #   super(resource)
  # end
end
VincentSim commented 5 years ago

and my routes.rb is

devise_for :users,  skip: :saml_authenticatable, controllers: { registrations: "user/registrations", sessions: "user/sessions"  }
  as :user do
    get 'experts/sign_up', to: 'user/registrations#new_expert'
  end
adamstegman commented 5 years ago
  1. Why do you have skip: :saml_authenticatable in your routes? I'm not sure that's causing the error, but it does seem odd.
  2. Have you made any changes to the Devise::SamlSessionsController or Devise::SessionsController classes?
silvermind commented 2 years ago

it seems from the validatable module. had to allow null on the password db column and add to the devise model:

  def password_required?
    false
  end