I can't register for devise in rails - devise

I can't register, only login, In the server i see this:
An unauthorized connection attempt was rejected Failed to upgrade to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: keep-alive, Upgrade, HTTP_UPGRADE: websocket)
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
logger.add_tags 'ActionCable', current_user.name
end
protected
def find_verified_user
if verified_user = User.find_by(id: cookies.signed['user.id'])
verified_user
else
reject_unauthorized_connection
end
end
end
my routes
devise_for :users, controllers: {
registrations: 'users/registrations',
:omniauth_callbacks => "users/omniauth_callbacks"
}
mount ActionCable.server => '/cable'

Your connection.rb file probably has a line identified_by :current_user, and your def connect calls some variation of a custom find_verified_user method, which requires valid authentication as customized by your app within the same file. Your connection to ActionCable has thus been rejected when you are trying to create a user, which naturally means you were not making the request with whatever is required for valid authentication and user identification. So I guess the real question is: is there a reason you are trying to register a user through WebSocket?

I'm obviously late providing this answer but I'll share what worked for me. The connection.rb referencing cookies.signed[...] worked when I was connecting with ActionCable directly but would be rejected when trying to use the turbo_stream_from element. Here's the connection.rb that works for both, when using Devise:
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
end
protected
def find_verified_user
env["warden"].user || reject_unauthorized_connection
end
end
end

Related

Unauthorized connection attempt was rejected with Rails 5 ActionCable

Following the hartle tutorial here: https://www.learnenough.com/action-cable-tutorial#sec-upgrading_to_action_cable
When I get to Step 4, adding ActionCable the chat messages are not transmitted and I get the error:
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" IS NULL LIMIT ? [["LIMIT", 1]]
An unauthorized connection attempt was rejected
here are the relevant files:
room_channel.rb:
class RoomChannel < ApplicationCable::Channel
def subscribed
stream_from "room_channel"
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
end
messages controller:
class MessagesController < ApplicationController
before_action :logged_in_user
before_action :get_messages
def index
end
def create
message = current_user.messages.build(message_params)
if message.save
ActionCable.server.broadcast 'room_channel',
message: render_message(message)
message.mentions.each do |mention|
ActionCable.server.broadcast "room_channel_user_# {mention.id}",
mention: true
end
end
end
private
def get_messages
#messages = Message.for_display
#message = current_user.messages.build
end
def message_params
params.require(:message).permit(:content)
end
def render_message(message)
render(partial: 'message', locals: { message: message })
end
end
room.coffee:
App.room = App.cable.subscriptions.create "RoomChannel",
connected: ->
# Called when the subscription is ready for use on the server
disconnected: ->
# Called when the subscription has been terminated by the server
received: (data) ->
# Called when there's incoming data on the websocket for this channel
alert data.content
routes.rb:
Rails.application.routes.draw do
root 'messages#index'
resources :users
resources :messages
get '/login', to: 'sessions#new'
post '/login', to: 'sessions#create'
delete '/logout', to: 'sessions#destroy'
mount ActionCable.server, at: '/cable'
end
The reference branch works fine on my machine, but I can't get my tutorial branch to use AC.
Update:
Skipping down to Section 5 of the tutorial, I added connection.rb, which had been blank in the tutorial's beginning repo as follows:
connection.rb:
module ApplicationCable
class Connection < ActionCable::Connection::Base
include SessionsHelper
identified_by :message_user
def connect
self.message_user = find_verified_user
end
private
def find_verified_user
if logged_in?
current_user
else
reject_unauthorized_connection
end
end
end
end
And broadcasting seems to work in one direction. I have two tabs open. but only one works to broadcast messages. In the other, the console shows this error:
Error: Existing connection must be closed before opening action_cable.self-17ebe4af84895fa064a951f57476799066237d7bb5dc4dc351a8b01cca19cce9.js:231:19
Connection.prototype.open
http://localhost:3000/assets/action_cable.self-17ebe4af84895fa064a951f57476799066237d7bb5dc4dc351a8b01cca19cce9.js:231:19
bind/<
http://localhost:3000/assets/action_cable.self-17ebe4af84895fa064a951f57476799066237d7bb5dc4dc351a8b01cca19cce9.js:201:60
In the logs, with the above connection.rb, the search for null user is gone, showing this:
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
Registered connection (Z2lkOi8vY2hhdC1hcHAvVXNlci8x)
RoomChannel is transmitting the subscription confirmation
RoomChannel is streaming from room_channel
Started GET "/cable" for ::1 at 2018-12-29 08:04:31 -0500
Started GET "/cable/" [WebSocket] for ::1 at 2018-12-29 08:04:31 -0500
Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: keep-alive, Upgrade, HTTP_UPGRADE: websocket)

WebSocket error occurred: wrong number of arguments (given 2, expected 1)

I try to create connection to cable server and subscribe on channel, but I get error with log:
Started GET "/cable" for 172.20.0.1 at 2017-05-27 08:29:39 +0000
Started GET "/cable/" [WebSocket] for 172.20.0.1 at 2017-05-27 08:29:39 +0000
Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: upgrade, HTTP_UPGRADE: websocket)
WebSocket error occurred: wrong number of arguments (given 2, expected 1)
My code:
// order_slots.coffee
jQuery(document).ready ->
//some jquery code that call create_channel function
create_channel = (order_id) ->
App.cable.subscriptions.create {
channel: "OrderSlotsChannel",
order_id: order_id
},
connected: ->
# Called when the subscription is ready for use on the server
disconnected: ->
# Called when the subscription has been terminated by the server
received: (data) ->
# Data received
Specific channel:
//order_slots_channel
class OrderSlotsChannel < ApplicationCable::Channel
def subscribed
stream_from "order_slots_#{params[:order_id]}_channel"
end
def unsubscribed; end
end
And ActionCable connection:
# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading.
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
logger.add_tags 'ActionCable', current_user.email
end
protected
def find_verified_user
verified_user = env['warden'].user
verified_user || reject_unauthorized_connection
end
end
end
ActionCable::Channel::Base - is just empty. I will appreciate any help. Thanks in advance
I solved this problem. The project used Passenger Phusion as application server and 5.0.x version badly combine with rails 5.1 and action cable. You should update passenger up to 5.1.x

Rails/Devise/SAML Metadata Incorrect (not working with PingFederate)

Let me preface this question by saying that I'm new to SAML and barely understand how it works.
The Setup
I'm using the devise_saml_authenticatable gem with a Rails 4 app to achieve SSO. The Rails app acts as the service provider (SP). To test my setup, I created a OneLogin developer account and set up a SAML Test Connector (IdP w/attr w/ sign response) using the following attributes:
Configuration Tab
Audience: mysubdomain.onelogin.com
Recipient: http://mysubdomain.myapp.local:3000/saml/auth
ACS (Consumer) URL Validator: ^http://mysubdomain.myapp.local:3000/saml/auth$
ACS (Consumer) URL: http://mysubdomain.myapp.local:3000/saml/auth
Single Logout URL: http://mysubdomain.myapp.local:3000/saml/idp_sign_out
SSO Tab
Issuer URL: https://app.onelogin.com/saml/metadata/589819
SAML 2.0 Endpoint (HTTP): https://mysubdomain.onelogin.com/trust/saml2/http-post/sso/589819
SLO Endpoint (HTTP): https://mysubdomain.onelogin.com/trust/saml2/http-redirect/slo/589819
SAML Signature Algorithm: SHA-1
SHA Fingerprint: 60:9D:18:56:B9:80:D4:25:63:C1:CC:57:6D:B9:06:7C:78:BB:2C:F1
X.509 Certificate:
-----BEGIN CERTIFICATE-----
MIIEFzCCAv+gAwIBAgIUQYRVa1MQpUh0gJaznmXSF/SPqnowDQYJKoZIhvcNAQEF
BQAwWDELMAkGA1UEBhMCVVMxETAPBgNVBAoMCEZpcm1QbGF5MRUwEwYDVQQLDAxP
bmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgOTI1MzEwHhcN
MTYwOTIxMTU0NzQwWhcNMjEwOTIyMTU0NzQwWjBYMQswCQYDVQQGEwJVUzERMA8G
A1UECgwIRmlybVBsYXkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW
T25lTG9naW4gQWNjb3VudCA5MjUzMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBALGVgocBj0ciHM3uKlWIcofPhOtzfJw1XpAdNynAvPtbCl7WE5+sLBoQ
ZF+oZ7Dl+wRW6DHMJCl9DdKcOaQA6/gr5bwt78IzZ8hWMoKQEPih+E0km6rKLYA8
M52vxtJxGs8Iqx60QvPEePQFMOA+xg73OExfM7W5LnXwNz/Pxgsr3lBif5oCC76j
SaTCFroV+TSjfOaYMW/lZrsS79KRIzA9I5XwUBe3bC8bsfQmZXgddCrkQUNSGGaS
7/jtFUlQ94+lAL+l3yoAiNAE6+mt48qqmyLfkKibXvnZ8dwuO272wpY4fEM+vFRy
pYrTajqvhY3hYIq8dLw3ominE5VECl8CAwEAAaOB2DCB1TAMBgNVHRMBAf8EAjAA
MB0GA1UdDgQWBBSxiuvTPxwOhh2pupID+tuyKCeceTCBlQYDVR0jBIGNMIGKgBSx
iuvTPxwOhh2pupID+tuyKCeceaFcpFowWDELMAkGA1UEBhMCVVMxETAPBgNVBAoM
CEZpcm1QbGF5MRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxv
Z2luIEFjY291bnQgOTI1MzGCFEGEVWtTEKVIdICWs55l0hf0j6p6MA4GA1UdDwEB
/wQEAwIHgDANBgkqhkiG9w0BAQUFAAOCAQEAYBe+5d3zpLZ7fcf3l3rXYeIxcpN+
9D2YZCbxsrBhY2Am4YE9nN+RaJXeDqeRBNtpayCZVxfHnXexRo1n7wxwTmosiydi
9yE7SY2xZf+3feQreF25atnn4tzVhxYONaX1njZMIt/TNa7A9aeDfHSD+vwSuYYB
hGxKT6HOkEAEBiXCZ/FcVNiB0D8bRwQhiJ3BTzXDfqHrmq8QYdn3Ejlqo62vMl6W
XeMXUoyv6cUc64Ap6E+XtEQI1E8YB5R8GtTs3Y1Oa2dD6yWyCyVJ20+Hi7IWAqXC
EfqstqXB7FoQ2rAt39cepnu1SOarvEYDMwYIaVNF3hoyodBybJJsAwAnCQ==
-----END CERTIFICATE-----
In my devise.rb I have the following configuration:
config.saml_create_user = false
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 = IdPSettingsAdapter
config.idp_entity_id_reader = DeviseSamlAuthenticatable::DefaultIdpEntityIdReader
Here is my IdPSettingsAdapter:
class IdPSettingsAdapter
def self.settings(idp_entity_id)
company = Company.find_by(idp_entity_id: idp_entity_id)
if company.present?
{
assertion_consumer_service_url: company.assertion_consumer_service_url,
assertion_consumer_service_binding: company.assertion_consumer_service_binding,
name_identifier_format: company.name_identifier_format,
issuer: company.issuer,
idp_entity_id: company.idp_entity_id,
authn_context: company.authn_context,
idp_slo_target_url: company.idp_slo_target_url,
idp_sso_target_url: company.idp_sso_target_url,
idp_cert_fingerprint: company.idp_cert_fingerprint
}
else
{}
end
end
end
Note that my user model Contact belongs_to Company, and that the SSO settings are stored in the Company model.
Here are my saml routes:
devise_for :contacts, skip: :saml_authenticatable, controllers: {
registrations: "registrations",
sessions: "sessions",
passwords: "passwords",
confirmations: "confirmations"
}
devise_scope :contact do
get '/sign_in' => 'sessions#new'
get '/sign_out' => 'sessions#destroy'
# SSO Routes
get 'saml/sign_in' => 'saml_sessions#new', as: :new_user_sso_session
post 'saml/auth' => 'saml_sessions#create', as: :user_sso_session
get 'saml/sign_out' => 'saml_sessions#destroy', as: :destroy_user_sso_session
get 'saml/metadata' => 'saml_sessions#metadata', as: :metadata_user_sso_session
match 'saml/idp_sign_out' => 'saml_sessions#idp_sign_out', via: [:get, :post]
end
Lastly here is my SamlSessionsController:
require "ruby-saml"
class SamlSessionsController < SessionsController
include DeviseSamlAuthenticatable::SamlConfig
skip_before_filter :verify_authenticity_token, raise: false
before_action :authorize_viewer, except: [:metadata]
protect_from_forgery with: :null_session, except: :create
def new
idp_entity_id = Company.friendly.find(#_request.env['HTTP_HOST'].split('.')[0]).idp_entity_id
request = OneLogin::RubySaml::Authrequest.new
action = request.create(saml_config(idp_entity_id))
redirect_to action
end
def metadata
idp_entity_id = Company.friendly.find(#_request.env['HTTP_HOST'].split('.')[0]).idp_entity_id
meta = OneLogin::RubySaml::Metadata.new
render :xml => meta.generate(saml_config(idp_entity_id)), content_type: 'application/samlmetadata+xml'
end
def create
#idp_entity_id = Company.friendly.find(#_request.env['HTTP_HOST'].split('.')[0]).idp_entity_id
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], settings: saml_config(#idp_entity_id))
if !response.is_valid?
puts "SAML FAILED WITH ERROR: "
puts response.errors
end
super
end
def idp_sign_out
company = Company.friendly.find(request.subdomain.downcase)
idp_entity_id = Company.friendly.find(#_request.env['HTTP_HOST'].split('.')[0]).idp_entity_id
if params[:SAMLRequest] && Devise.saml_session_index_key
saml_config = saml_config(idp_entity_id)
logout_request = OneLogin::RubySaml::SloLogoutrequest.new(params[:SAMLRequest], settings: saml_config(idp_entity_id))
resource_class.reset_session_key_for(logout_request.name_id)
# binding.pry
sign_out current_contact if contact_signed_in?
redirect_to company.after_slo_url.present? ? company.after_slo_url : 'https://' + company.issuer
# redirect_to generate_idp_logout_response(saml_config(idp_entity_id), logout_request.id)
elsif params[:SAMLResponse]
#Currently Devise handles the session invalidation when the request is made.
#To support a true SP initiated logout response, the request ID would have to be tracked and session invalidated
#based on that.
if Devise.saml_sign_out_success_url
redirect_to Devise.saml_sign_out_success_url
else
redirect_to action: :new
end
else
head :invalid_request
end
end
protected
# Override devise to send user to IdP logout for SLO
def after_sign_out_path_for(_)
request = OneLogin::RubySaml::Logoutrequest.new
request.create(saml_config)
end
def generate_idp_logout_response(saml_config, logout_request_id)
OneLogin::RubySaml::SloLogoutresponse.new.create(saml_config, logout_request_id, nil)
end
end
The Problem
When I manually save map the settings from my OneLogin adapter to my Company model (see screenshot), I'm able to authenticate as a user of my app using OneLogin as the identity provider (IdP). However now I need to provide a client with the XML metadata representing the app's setup. When I go to /saml/metadata.xml, I get the following configuration, which according to my client, is incorrect. The client didn't offer any further details about what the problem is. They are using PingFederate, if that matters.
<?xml version='1.0' encoding='UTF-8'?>
<md:EntityDescriptor ID='_a3581975-b73d-4784-a106-bafd61e15f87' xmlns:md='urn:oasis:names:tc:SAML:2.0:metadata'>
<md:SPSSODescriptor AuthnRequestsSigned='false' WantAssertionsSigned='false' protocolSupportEnumeration='urn:oasis:names:tc:SAML:2.0:protocol'>
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>
<md:AssertionConsumerService Binding='urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST' Location='https://mysubdomain.myapp.local:3000/saml/auth' index='0' isDefault='true'/>
</md:SPSSODescriptor>
</md:EntityDescriptor>
My question is, what am I doing wrong here and how can I correct it? As I said, I barely understand how SAML works under the hood.
There is no EntityID defined on that metadata XML.
If you try to verify the XML on a validation tool you will get
Line: 2 | Column: 0 --> Element
'{urn:oasis:names:tc:SAML:2.0:metadata}EntityDescriptor': The
attribute 'entityID' is required but missing.
If you review ruby-saml code, the EntityID is added to the metadata XML if a settings.issuer is defined. Can you verify if that data is provided? Maybe company.issuer that I see at IdPSettingsAdapter class has an empty value.

Rails Sorcery Bug? Creates Duplicate User Accounts

The example sorcery code shown on github appears to me to create duplicate accounts if it is extended to allow for multiple sign in methods (which is the whole point of oauth). You can see in the snipit here that create_from() will be called if login_from() does not succeed.
GITHUB AT at https://github.com/NoamB/sorcery-example-app/blob/master/app/controllers/oauths_controller.rb
def callback
provider = params[:provider]
begin
if #user = login_from(provider)
redirect_to root_path, :notice => "Logged in from #{provider.titleize}!"
else
begin
#user = create_from(provider)
Investigating the source code for create_from in all cases a new User Account record will be created. This would not be correct, if a User account record already exists.
My question: What sorcery methods should be called on the first facebook connect, if a User account has been created by some means other than facebook. login_from will fail, and create_from will generate a duplicate usser record?
You can use def create_and_validate_from(provider).
It will validate if the users email/username already exist. If its true, that he will store infos into a session and can be rendered into registration form.
And if you wish to add some provider to your account you can use def add_provider_to_user(provider).
Several requests have come through for an answer to this question, so I am providing the answer that Andy Mejia part of my team eventually arrived at for this question. We used the source within sorcery to adapt the following functions:
# Returns the hash that contains the information that was passed back from Facebook.
# It only makes sense to call this method on the callback action.
#
# Example hash:
# {:user_info=>{:id=>"562515238", :name=>"Andrés Mejía-Posada", :first_name=>"Andrés", :last_name=>"Mejía-Posada", :link=>"http://www.facebook.com/andmej", :username=>"andmej", :gender=>"male", :email=>"andmej#gmail.com", :timezone=>-5, :locale=>"en_US", :verified=>true, :updated_time=>"2011-12-31T21:39:24+0000"}, :uid=>"562515238"}
def get_facebook_hash
provider = Rails.application.config.sorcery.facebook
access_token = provider.process_callback(params, session)
hash = provider.get_user_hash
hash.merge!(:access_token => access_token.token)
hash.each { |k, v| v.symbolize_keys! if v.is_a?(Hash) }
end
# Method added to the User Account model class
def update_attributes_from_facebook!(facebook_hash)
self.first_name = facebook_hash[:user_info][:first_name] if self.first_name.blank?
self.last_name = facebook_hash[:user_info][:last_name] if self.last_name.blank?
self.facebook_access_token = facebook_hash[:access_token]
self.email ||= facebook_hash[:user_info][:email]
unless facebook_authentication?
authentications.create!(:provider => "facebook", :uid => facebook_hash[:uid])
end
self.build_facebook_profile if facebook_profile.blank?
save!
self.facebook_profile.delay.fetch_from_facebook! # Get API data
end
To show these code in context, I am also including logic from our controller:
def callback
provider = params[:provider]
old_session = session.clone # The session gets reset when we login, so let's backup the data we need
begin
if #user = login_from(provider) # User had already logged in through Facebook before
restore_session(old_session) # Cleared during login
else
# If there's already an user with this email, just hook this Facebook account into it.
#user = UserAccount.with_insensitive_email(get_facebook_hash[:user_info][:email]).first
# If there's no existing user, let's create a new account from scratch.
#user ||= create_from(provider) # Be careful, validation is turned off because Sorcery is a bitch!
login_without_authentication(#user)
end
#user.update_attributes_from_facebook!(get_facebook_hash)
rescue ::OAuth2::Error => e
p e
puts e.message
puts e.backtrace
redirect_to after_login_url_for(#user), :alert => "Failed to login from #{provider.titleize}!"
return
end
redirect_to after_login_url_for(#user)
end
I hope this solution is helpful to others.
I came across the same problem. While I have not found a direct solution via Sorcery, I did the following which seems to work:
#user = create_from(params[:provider]) do |user|
User.where(:twitter_id => user.twitter_id).first.blank?
end
This teqnique requires that you have twitter_id in the User model. You can also do it the other way around with the Authentication model instead. Such as:
#user = create_from(params[:provider]) do |user|
Authentication.where(:uid => user.twitter_id).first.blank?
end
If the block returns false, then it doesn't create the user. Avoiding any duplicates.
Note, the block for create_from does not work with 0.7.12. It works with 0.7.13.

Disabling sessions for a single controller has been deprecated

The problem is;
class ApplicationController < ActionController::Base
# Pick a unique cookie name to distinguish our session data from others'
session :session_key => '_simple_blog'
#session :disabled => true
private #------------
def authorize_access
if !session[:user_id]
flash[:notice] = "Please log in."
redirect_to(:controller => 'staff', :action => 'login')
return false
end
end
end
the error message is
DEPRECATION WARNING: Disabling sessions for a single controller has been deprecated. Sessions are now lazy loaded. So if you don't access them, consider them off. You can still modify the session cookie options with request.session_options.
Can somebody point em in the right direction.
Thanks
You are receiving this warning because you are explicitly loading the session context via the session method. You should instead use request.session_options[:session_key] = 'new_session_key' from within an action, as the framework now lazily loads the context if necessary (as you saw). If you want to do this for all actions, create a method and use before_filter:
class ApplicationController < ActionController::Base
before_filter :setup_session_key
protected
def setup_session_key
# Pick a unique cookie name to distinguish our session data from others'
request.session_options[:session_key] = '_simple_blog'
end
end