I'm building an API using Rails and Devise. My sessions controller inherits from the following base controller
api/base_controller.rb
module Api
class BaseController < ApplicationController
skip_before_filter :verify_authenticity_token
before_filter :authenticate_user_from_token!
respond_to :json
private
def authenticate_user_from_token!
user_token = params[:auth_token].presence
user = user_token && User.find_by_authentication_token(user_token)
if user
sign_in user, store: false
else
render :json => {:success => false, :message => "Error with your credentials", :status => 401}
end
end
end
end
My sessions controller's destroy action below:
api/sessions_controller.rb
before_filter :authenticate_user_from_token!, :except => [:create]
def destroy
current_user.reset_authentication_token
render :json => {
:success => true,
:status => 200
}
end
This works perfectly when testing the api via curl. But, I can't get my Rspec tests for the destroy action to pass. From Rspec the sign_in user call is failing, so the response is a redirect. I haven't had any success trying to stub the sign_in method.
Rspec test:
describe "DELETE destroy" do
before(:each) do
#user1 = User.create!(:email => 'example#gmail.com', :password => 'helloworld', :password_confirmation => 'helloworld')
end
it "should render success json" do
delete :destroy, :auth_token => #user1.authentication_token
json = JSON.parse(response.body)
json.should include('success' => true, 'status' => 200)
end
###this fails because the response is a redirect to the sign_in page
end
How should I go about mocking the sign_in method called from within the base controller?
Add a spec/support/devise.rb file with this content:
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
end
Also, check in your test.log wether it's actually using the json format. I had a similar problem and found out that I had to force the format :json in my spec call parameters.
Andreamazz pointed me to the test.logs which revealed that the user I had created was confirmed (I'm using Devise confirmable). I use user.confirm! in the before(:each) and everything is passing.
describe "DELETE destroy" do
before(:each) do
#user1 = User.create!(:email => 'example#gmail.com', :password => 'helloworld', :password_confirmation => 'helloworld')
#user1.confirm!
end
it "should render success json" do
delete :destroy, :auth_token => #user1.authentication_token
json = JSON.parse(response.body)
json.should include('success' => true, 'status' => 200)
end
end
Thanks!
Related
I have the following rails_spec test:
require 'rails_helper'
RSpec.describe UsersController, type: :controller do
fixtures :users, :clients
include AuthHelper
before do
#request.headers["Content-Type"] = 'application/json'
#request.headers["Accept"] = 'application/json'
end
describe '#create' do
context 'for a user with a valid authorization token' do
context 'having admin privileges' do
before do
init_headers_for_admin
end
context 'submitted with a valid set of parameters' do
it 'should create a new user having the submitted parameters' do
post :create, params: {
:name => 'Roland Deschain',
:email => 'roland#foamfactorybrewing.com',
:role => 'user',
:username => 'roland'
}
expect(response).to have_http_status(:ok)
user = json
expect(user).to_not be_nil
expect(user.name).to eq('Roland Deschain')
expect(user.email).to eq('roland#foamfactorybrewing.com')
expect(user.role).to eq('user')
expect(user.username).to eq('roland')
expect(user.created_at).to eq(user.updated_at)
end
end
I also have the following controller definition:
def create
# get_required_fields.each { |field|
# unless params.has_key? field
# render :json => { :error => 'A ' + field.to_s + ' must be specified in order to create a new User'}, :status => :unprocessable_entity and return
# end
# }
username = params[:username]
unless User.find_by_username(params[:username]) == nil
render :json => { :error => I18n.t('error_username_conflict') }, :status => :conflict and return
end
unless User.find_by_email(params[:email]) == nil
render :json => { :error => I18n.t('error_email_conflict') }, :status => :conflict and return
end
end
However, when I run the test, the params variable is always an empty hash. If I uncomment the code that detects whether I have the required parameters, it always returns an UNPROCESSABLE_ENTITY, rather than the NO_CONTENT I would expect if a username, name, and email are present. I'm not quite sure why, but I can't seem to get it to pass actual data via the post method. I've been looking at this for a while, and trying to debug it, but I can't seem to get it to pass the parameters correctly.
I'm using Rails 5 and rspec_rails 3.8.1.
EDIT:
Something is really wrong here, because if I simplify create to be:
def create
unless params.has_key?('name') && params.has_key?('username') && params.has_key?('email')
render :json => {:error => I18n.t('error_must_specify_username_email_and_name')}, :status => :unprocessable_entity and return
end
end
And execute the same spec as before (obviously expecting it to return no_content, and not unprocessable_entity), I end up with the same thing happening - in debug mode, it looks like the params variable upon entering this method is:
{"controller"=>"users", "action"=>"create", "user"=>{}}
So, first off, it's setting the user parameter, which isn't exactly what I intended, and moreover, it's setting it to an empty hash. I'm not sure what's causing this. I feel like it's something I'm forgetting to do from Rails 5.0, because this pattern seems to work fine in Rails 4.0.
EDIT 2:
I modified the test to be:
it 'should create a new user record' do
post :create, params: { user: #user }, as: :json
expect(response).to have_http_status(:ok)
expect(json.name).to eq(#user.name)
expect(json.email).to eq(#user.email)
expect(json.username).to eq(#user.username)
expect(json.role).to eq(#user.role)
end
And modified the UsersController class to look like:
class UsersController < ApplicationController
include AuthHelper
before_action :validate_api_key_for_auth, only: :login
before_action :authenticate_as_admin, only: [:create]
def create
p user_params
unless user_params.has_key?('name') && user_params.has_key?('username') && user_params.has_key?('email')
render :json => {:error => I18n.t('error_must_specify_username_email_and_name')}, :status => :unprocessable_entity and return
end
end
def user_params
params[:user].permit([:username, :name, :email, :role]).to_h
end
end
It is printing out: {} for the users hash from user_params, but I don't understand why this is the case.
EDIT 3:
authenticate_as_admin is screwing up the parameters somehow. If I change the definition of authenticate_as_admin to true, it shows the correct hash and returns no_content as expected. The definition of the authenticate_as_admin function is below (defined in ApplicationController):
def authenticate_as_admin
authenticate_as :admin
end
def authenticate_as(role)
# First, get the headers for the request
authorization_header = get_authorization_header
unless authorization_header
render :json => {:error => I18n.t('error_must_be_logged_in')}, :status => :forbidden and return false
end
auth_token = authorization_header.split[1]
decoded_token = decode_authorization_header authorization_header
unless decoded_token
render :json => {:error => I18n.t('error_invalid_token')}, :status => :forbidden and return false
end
payload = decoded_token[0]
user_id = payload['user_id']
auth_token_expiration_date = payload['expiration_date']
#authenticated_user = User.find(user_id) rescue nil
unless #authenticated_user and #authenticated_user.auth_token == auth_token
render :json => {:error => I18n.t('error_must_be_logged_in')}, :status => :forbidden and return false
end
if auth_token_expiration_date < DateTime.now
render :json => {:error => I18n.t('error_auth_token_expired')}, :status => :forbidden and return false
end
unless #authenticated_user.role_at_least? role
render :json => {:error => I18n.t('error_insufficient_permission')}, :status => :forbidden and return false
end
true
end
I can't seem to see why this would muck up the params, though, especially considering this worked fine in Rails 4.2. Anyone see anything I'm missing?
EDIT 4:
I feel like I'm on the right track. It seems as though if the authenticate_as() function returns false, I get this behavior. If a before_action returns false, what should be the expected result of a controller action? I expected that the method called in the before_action would perform the appropriate render ... call, and the before_action would silently fail, never calling the function that would have been called. I'm still not quite sure why the function is failing, but I'm trying to understand the behavior that should happen. Perhaps authentication checking isn't a great use-case for before_actions in Rails 5?
Update Not valid for rails 5
Try this,
post :create, {
:name => 'Roland Deschain',
:email => 'roland#foamfactorybrewing.com',
:role => 'user',
:username => 'roland'
}
when you have the params: {} it gets sent as a nested hash, { params: {...} }
EDIT 1
Instead of passing params to post :create try stubbing out params call in controller, something like
allow(subject).to receive(:params).and_return(params)
and inside describe block,
let(:params) { ActionController::Parameters.new({ :name => '', :email => '' ... }) }
or just a regular hash if you don't need ActionController::Parameters
and call the :create without any parameters,
post :create
see if this helps, I am using this pattern in our specs in another project which is on route to rails 5.
I am attempting to get a user registration endpoint setup for my rails application so that I can access the app's functionality in an iOS rendition. I've gone ahead and namespaced my API, and so far have managed to get user authentication working using Devise and JWT's.
This is great, however, I also need to ability to register a user via the API. To be frank, I have no idea how to correctly implement this. Several Google searches either bring up outdated articles, use the deprecated token authenticatable, or have never been answered.
Below is the code that I believe pertains most to this question:
routes.rb (Namespaced section for API)
namespace :api do
namespace :v1 do
devise_for :users, controllers: { registrations: 'api/v1/registrations' }
resources :classrooms
resources :notifications
end
end
end
registrations_controller.rb (API contorller)
class Api::V1::RegistrationsController < Devise::RegistrationsController
respond_to :json
def create
if params[:email].nil?
render :status => 400,
:json => {:message => 'User request must contain the user email.'}
return
elsif params[:password].nil?
render :status => 400,
:json => {:message => 'User request must contain the user password.'}
return
end
if params[:email]
duplicate_user = User.find_by_email(params[:email])
unless duplicate_user.nil?
render :status => 409,
:json => {:message => 'Duplicate email. A user already exists with that email address.'}
return
end
end
#user = User.create(user_params)
if #user.save!
render :json => {:user => #user}
else
render :status => 400,
:json => {:message => #user.errors.full_messages}
end
end
private
# Never trust parameters from the scary internet, only allow the white list through.
def user_params
devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute, :first_name, :last_name, :access_code])
end
end
End Point for registration
http://localhost:3000/api/v1/users
Sample Postman response
{
"message": [
"Email can't be blank",
"Password can't be blank",
"Access code is invalid [Beta]."
]
}
Any help would greatly be appreciated, as I am keen on learning more (and getting this to work!).
UPDATE 1
Here is what I get on the server after making a post request to generate a user...
Started POST "/api/v1/users" for 127.0.0.1 at 2017-02-22 09:22:11 -0800
Processing by Api::V1::RegistrationsController#create as */*
Parameters: {"user"=>{"email"=>"user#sampleapi.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]", "access_code"=>"uiux"}}
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."email" IS NULL LIMIT $1 [["LIMIT", 1]]
Completed 400 Bad Request in 2ms (Views: 0.2ms | ActiveRecord: 0.4ms)
Updated Registrations_controller
class Api::V1::RegistrationsController < Devise::RegistrationsController
before_action :configure_sign_up_params, only: [:create]
respond_to :json
def create
#user = build_resource(sign_up_params)
if #user.persisted?
# We know that the user has been persisted to the database, so now we can create our empty profile
if resource.active_for_authentication?
sign_up(:user, #user)
render :json => {:user => #user}
else
expire_data_after_sign_in!
render :json => {:message => 'signed_up_but_#{#user.inactive_message}'}
end
else
if params[:user][:email].nil?
render :status => 400,
:json => {:message => 'User request must contain the user email.'}
return
elsif params[:user][:password].nil?
render :status => 400,
:json => {:message => 'User request must contain the user password.'}
return
end
if params[:user][:email]
duplicate_user = User.find_by_email(params[:email])
unless duplicate_user.nil?
render :status => 409,
:json => {:message => 'Duplicate email. A user already exists with that email address.'}
return
end
end
render :status => 400,
:json => {:message => resource.errors.full_messages}
end
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, :first_name, :last_name, :access_code])
end
end
I'm pretty sure my main issue at this point is the format of my params, so any push in the right direction for this would be great. I did find this post but am finding it a little difficult to follow in terms of what got their API to work...
Here is 2 solution, choose one you like.
Override devise_parameter_sanitizer:
class ApplicationController < ActionController::Base
protected
def devise_parameter_sanitizer
if resource_class == User
User::ParameterSanitizer.new(User, :user, params)
else
super # Use the default one
end
end
end
Override sign_up_params:
def sign_up_params
params.require(:user).permit(:email, :password, :password_confirmation)
end
Why?
If you go deeping to Devise ParameterSanitizer, the resource_name will be :api_v1_user, not just :user because of your routes:
namespace :api do
namespace :v1 do
devise_for :users, controllers: { registrations: 'api/v1/registrations' }
end
end
Error resource_name will cause sign_up_params always return empty hash {}
Why don't you try something like this:
user = User.create(sign_up_params)
if user.save
render status: 200, json: #controller_blablabla.to_json
else
render :status => 400,
:json => {:message => #user.errors.full_messages}
end
or even better. You might use something like tiddle gem to make session more secure:
respond_to :json
def create
user = User.create(sign_up_params)
if user.save
token = Tiddle.create_and_return_token(user, request)
render json: user.as_json(authentication_token: token, email:
user.email), status: :created
return
else
warden.custom_failure!
render json: user.errors, status: :unprocessable_entity
end
end
You might use httpie --form to make the request:
http --form POST :3000/users/sign_up Accept:'application/vnd.sign_up.v1+json' user[email]='he#llo.com' user[username]='hello' user[password]='123456789' user[password_confirmation]='123456789'
do not forget:
def sign_up_params
params.require(:user).permit(:username, :email, :password, :password_confirmation)
end
I don't know what i'm missing, let me know if i'm wrong or something is wrong and i did't realize!
Regards!
Why not use the simple_token_authentication gem ?
Extremely simple to setup:
# Gemfile
gem "simple_token_authentication"
bundle install
rails g migration AddTokenToUsers "authentication_token:string{30}:uniq"
rails db:migrate
# app/models/user.rb
class User < ApplicationRecord
acts_as_token_authenticatable
# [...]
end
In your routes:
# config/routes.rb
Rails.application.routes.draw do
# [...]
namespace :api, defaults: { format: :json } do
namespace :v1 do
resources :classrooms
resources :notifications
end
end
end
In your controllers:
# app/controllers/api/v1/classrooms_controller.rb
class Api::V1::ClassroomsController < Api::V1::BaseController
acts_as_token_authentication_handler_for User
# [...]
end
Example call using the RestClient gem:
url = "http://localhost:3000/api/v1/classrooms/"
params = {user_email: 'john#doe.com', user_token: '5yx-APbH2cmb11p69UiV'}
request = RestClient.get url, :params => params
For existing users who don't have a token:
user = User.find_by_email("john#doe.com")
user.save
user.reload.authentication_token
I'm trying to use devise to authenticate a simple email/password login so users can access the rest of the API via auth tokens. I'm running into trouble with devise simply returning You need to sign in or sign up before continuing. Here's my code:
class LoginController < ApplicationController
respond_to :json
def login
resource = warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#failure")
render :status => 200,
:json => { :success => true,
:info => "Logged in",
:user => current_user
}
end
def failure
render :status => 401,
:json => { :success => false,
:info => "Login Failed",
:data => {} }
end
def resource_name
:user
end
def resource
#resource ||= User.new
end
def devise_mapping
#devise_mapping ||= Devise.mappings[:user]
end
end
routes.rb
devise_scope :user do
post 'register' => 'registration#create', :as => :registers
post 'login' => 'login#login', :as => :login
end
I'm sending the following post data:
{
"user" : {
"email" : "testPost4#fake.com",
"password" : "Password1"
}
}
Having browsed various posts I've added:
config.navigational_formats = [:json]
to the devise.rb file but it didn't solve the problem.
Adding skip_before_filter :authenticate_user! doesn't work either.
I wasn't able to get this working so I have reverted to the much simpler approach of checking and signing in manually.
def login
params = login_params
user = User.find_by_email(params['email'].downcase)
if user.valid_password?(params['password']) then
sign_in(user)
success
else
failure
end
end
I followed the rails cast of omniauth to create authentication for twitter ( http://railscasts.com/episodes/235-omniauth-part-1?view=comments ). It works fine in development but I can't get rspec to detect that I have created the authentication. Here is my snippet for create function in my Authentication controller:
def create
begin
auth_hash = request.env["omniauth.auth"]
#auth = current_user.authentications.build( :provider => auth_hash['provider'],
:uid => auth_hash['uid'],
:name => auth_hash['user_info']['name'],
:nickname => auth_hash['user_info']['nickname'],
:image => auth_hash['user_info']['image']
)
if #auth.provider.downcase == "twitter"
#auth.auth_token = auth_hash['credentials']['token']
#auth.secret_token = auth_hash['credentials']['secret']
#auth.site = auth_hash['user_info']['urls']['Twitter']
elsif #auth.provider == "Facebook"
end
rescue
redirect_to current_user, :flash => { :error => "Missing oauth data!! Check with administrator!"}
else
if #auth.save
msg = "Authentication success"
else
msg = "Already have authentication"
end
redirect_to current_user, :notice => msg
end
end
included in my routes:
match '/auth/:provider/callback' => 'authentications#create'
I have the following setup in my rspec_helper:
OmniAuth.config.test_mode = true
OmniAuth.config.add_mock(:twitter, { :provider => "twitter",
:uid => "1234",
:user_info => { :name => "Bob hope",
:nickname => "bobby",
:urls => {:Twitter => "www.twitter.com/bobster"}},
:credentials => { :auth_token => "lk2j3lkjasldkjflk3ljsdf"} })
Here is my rspec code that is not working:
describe "Post 'create'" do
before(:each) do
#user = Factory(:user)
sign_in #user
end
describe "success" do
before(:each) do
request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter]
end
it "should create authentication" do
lambda do
post :create, :provider => "twitter"
response.should redirect_to(#user)
end.should change(Authentication, :count).by(1)
end
end
end
error i get is:
1) AuthenticationsController Post 'create' success should create authentication
Failure/Error: lambda do
count should have been changed by 1, but was changed by 0
# ./spec/controllers/authentications_controller_spec.rb:57
I've checked everything and cannot figure what I am doing wrong. Can anyone help?
i finally figured what was wrong. in my mock :auth_token is suppose to be :token. That was causing the failed validation.
Shouldn't that be a get request?
describe "success" do
before(:each) do
request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter]
end
it "should create authentication" do
lambda do
get :create, :provider => "twitter"
response.should redirect_to(#user)
end.should change(Authentication, :count).by(1)
end
end
I am having difficulty getting a rspec test for a controller to pass. I would like to test that the POST create action works. I am using rails (3.0.3), cancan (1.4.1), devise (1.1.5), rspec (2.3.0)
The model is dead simple
class Account < ActiveRecord::Base
attr_accessible :name
end
The controller is standard as well (straight out of scaffolding)
class AccountsController < ApplicationController
before_filter :authenticate_user!, :except => [:show, :index]
load_and_authorize_resource
...
def create
#account = Account.new(params[:account])
respond_to do |format|
if #account.save
format.html { redirect_to(#account, :notice => 'Account was successfully created.') }
format.xml { render :xml => #account, :status => :created, :location => #account }
else
format.html { render :action => "new" }
format.xml { render :xml => #account.errors, :status => :unprocessable_entity }
end
end
end
and the rspec test I would like to pass is (excuse the title, perhaps not the most appropriate one)
it "should call create on account when POST create is called" do
#user = Factory.create(:user)
#user.admin = true
#user.save
sign_in #user #this is an admin
post :create, :account => {"name" => "Jimmy Johnes"}
response.should be_success
sign_out #user
end
Yet all I get is
AccountsController get index should call create on account when POST create is called
Failure/Error: response.should be_success
expected success? to return true, got false
# ./spec/controllers/accounts_controller_spec.rb:46
Other actions can be tested and do pass (i.e. GET new)
here is the test for GET new
it "should allow logged in admin to call new on account controller" do
#user = Factory.create(:user)
#user.admin=true
#user.save
sign_in #user #this is an admin
get :new
response.should be_success
sign_out #user
end
and for completion here is the ability file
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
if user.admin?
can :manage, :all
else
can :read, :all
end
end
end
Any ideas? My guess is that I am using the wrong rspec expectation, since the code does work (it is just that the test does not perform as desired!)
response.should be_success returns true if the response code is in the range 200-299. But the create action redirects, so the response code gets set to 302, thus the failure.
You can test this by using response.should redirect_to. Check the output of the standard RSpec controller generator for an example, which might look like this:
it "redirects to the created account" do
Account.stub(:new) { mock_account(:save => true) }
post :create, :account => {}
response.should redirect_to(account_url(mock_account))
end
The rspec test that got the test to pass was (thanks to zetetic's advice):
it "should call create on account when POST create is called" do
#user = Factory.create(:user)
#user.admin = true
#user.save
sign_in #user #this is an admin
account = mock_model(Account, :attributes= => true, :save => true)
Account.stub(:new) { account }
post :create, :account => {}
response.should redirect_to(account_path(account))
sign_out #user
end