Writing a Test/Method for HTTP Digest Authentication - ruby-on-rails

How would I write a method to be used in rspec testing to access pages that require a username and password for HTTP Digest Authentication. For example, this test...
it "edit" do
http_login
post :edit, id: #post
assigns[:post].should eq(#post)
end
needs http_login method to be something like this...
def http_login
user = {"username" =>
Digest::MD5.hexdigest(["username","Application","password"].join(":"))}
request.env['HTTP_AUTHORIZATION'] =
ActionController::HttpAuthentication::Digest.encode_credentials(?,?,?,?)
end
My question is what do I put in the four arguments for the encode credentials. The arguments are going to be http_method, credentials, password, password_is_ha1 but I'm unsure how to write http_method and credentials to implement in the tests.

Solution here: https://gist.github.com/1282275
recopied here for posterity
# Adds support for http digest authentication in Rails 3
# Inspired by: http://lightyearsoftware.com/2009/04/testing-http-digest-authentication-in-rails/
# Place this code in test/test_helper.rb
# In your test, call authenticate_with_http_digest prior to calling get, post, put or delete
# Tested with Rails 3.0.7
class ActionController::TestCase
require 'digest/md5'
def authenticate_with_http_digest(user = API_USERNAME, password = API_PASSWORD, realm = API_REALM)
ActionController::Base.class_eval { include ActionController::Testing }
#controller.instance_eval %Q(
alias real_process_with_new_base_test process_with_new_base_test
def process_with_new_base_test(request, response)
credentials = {
:uri => request.url,
:realm => "#{realm}",
:username => "#{user}",
:nonce => ActionController::HttpAuthentication::Digest.nonce(request.env['action_dispatch.secret_token']),
:opaque => ActionController::HttpAuthentication::Digest.opaque(request.env['action_dispatch.secret_token'])
}
request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Digest.encode_credentials(request.request_method, credentials, "#{password}", false)
real_process_with_new_base_test(request, response)
end
)
end
end

Here's a solution for RSpec 3.1 and Rails 4 HTTP Digest Auth testing: https://gist.github.com/murbanski/6b971a3edc91b562acaf

Related

How do I bypass recaptcha verification in rails controller w/ RSPEC?

In my rails UsersController - users#sign_up action, I perform verification to ensure the user has a valid recaptcha v3 token before moving on to the rest of the controller logic. If the recaptcha verification fails then the controller returns and responds with an error message. However, my rspec tests are failing because I am unsure how to mock / bypass the verification in the controller.
spec/requests/auth_spec.rb:
RSpec.describe "Authentication Requests", type: :request do
context "sign up user" do
it "fails to sign up a user without email address" do
headers = { :CONTENT_TYPE => "application/json" }
post "/api/v1/sign_up", :params => { :email => nil, :password => "password123"}.to_json, :headers => headers
expect(response.header['Content-Type']).to include('application/json')
expect(response_body_to_json).to eq({"error"=>"Failed to create user"})
end
end
end
The test is failing when I post to /api/v1/sign_up because there are missing params for the recaptcha token. As far as I understand, it isn't possible to mock a recaptcha v3 token. Therefore it would be preferable to have verify_recaptcha return true for the rspec test.
controllers/api/v1/users_controller:
def sign_up
# Rspec fails here with missing params error
return if !verify_recaptcha('sign_up', recaptcha_params[:token])
#user = User.new(user_credential_params)
if #user.valid?
# Handle success/fail logic
end
end
private
def user_credential_params
params.permit(:email, :password)
end
def recaptcha_params
params.permit(:token)
end
controllers/concerns/users_helper.rb:
def verify_recaptcha(recaptcha_action, token)
secret_key = Rails.application.credentials.RECAPTCHA[:SECRET_KEY]
uri = URI.parse("https://www.google.com/recaptcha/api/siteverify?secret=#{secret_key}&response=#{token}")
response = Net::HTTP.get_response(uri)
json = JSON.parse(response.body)
recaptcha_valid = json['success'] && json['score'] > 0.5 && json['action'] == recaptcha_action
if !recaptcha_valid
render :json => { :error_msg => 'Authentication Failure' }, :status => :unauthorized
return false
end
return true
end
Can I stub / mock the verify_recaptcha method that comes from the users_helper concern to return true? Is there a better way to accomplish this?
I did due diligence before asking this question and I found this post: mocking/stubbing a controller recaptcha method with rspec in rails.
This was the answer for that post:
allow(controller).to receive(:verify_recaptcha).and_return(true)
The above didnt work for me because individual had their verify_recaptcha method inside of ApplicationController.rb (which seems a little dirty in my opinion). Given that my verify_recaptcha method is inside of a concern, I am not sure how to access the concern via Rspec.
You can try adding UserController.expects(:verify_recaptcha).returns(true) to your test.
This will bypass the recaptcha or Just try finding where the verify_recaptcha method exists and then write controller or class name before the expect method in
UserController.expects(:verify_recaptcha).returns(true)

Uninitialized constant CustomTokenResponse(NameError) and Unauthorized Access for Valid Token

I have been trying to migrate my rails app to api and i just installed doorkeeper gem for authentication and it generates me a token for a valid user. Now i have two problems
It shows unauthorized access in logs when i try to get page even though my access token is correct
I tried to put a custom token response for login and logout because it wasn't showing me any but it is now throwing me an error when i tried to run rails server
I have followed this guide for the process
https://scotch.io/#jiggs/rails-api-doorkeeper-devise
# frozen_string_literal: true
#Doorkeeper.rb
Doorkeeper.configure do
# Change the ORM that doorkeeper will use (needs plugins)
orm :active_record
# This block will be called to check whether the resource owner is authenticated or not.
resource_owner_authenticator do
# raise "Please configure doorkeeper resource_owner_authenticator block located in #{__FILE__}"
# Put your resource owner authentication logic here.
# Example implementation:
current_user || warden.authenticate!(:scope => :user)
end
resource_owner_from_credentials do |routes|
user = User.find_for_database_authentication(:email => params[:email])
if user && user.valid_for_authentication? { user.valid_password?(params[:password]) }
user
end
end
skip_authorization do
true
end
Doorkeeper::OAuth::TokenResponse.send :prepend, CustomTokenResponse
Doorkeeper::OAuth::ErrorResponse.send :prepend, CustomTokenErrorResponse
# WWW-Authenticate Realm (default "Doorkeeper").
#
# realm "Doorkeeper"
end
# lib/custom_token_response.rb
module CustomTokenResponse
def body
user_details = User.find(#token.resource_owner_id)
# call original `#body` method and merge its result with the additional data hash
super.merge({
status_code: 200,
message: I18n.t('devise.sessions.signed_in'),
result: user_details
})
end
end
# lib/custom_token_error_response.rb
module CustomTokenErrorResponse
def body
{
status_code: 401,
message: I18n.t('devise.failure.invalid', authentication_keys: User.authentication_keys.join('/')),
result: []
}
# or merge with existing values by
# super.merge({key: value})
end
end
1: from /home/rubyians/.rvm/gems/ruby-2.6.3/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/active_support.rb:8:in without_bootsnap_cache'
/home/rubyians/.rvm/gems/ruby-2.6.3/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/active_support.rb:79:inblock in load_missing_constant': uninitialized constant CustomTokenResponse (NameError)

How to setup angular-rails 4.2 - devise registration/authentication?

Can you give an advice or recommend some resources related to this topic? I understand how to it in a theory. But I also heard about jwt etc. What are the best practices to implement device/angular/rails role based auth/registration?
The short answer is to read this blog post which goes into details of how the concept is minimally implemented
This would be a long code answer, but I plan to write separate blog post on how to implement it in much more details...
but for now, here is how I implemented it in some project...
First the angular app part, you can use something like Satellizer which plays nicely...
here is the angular auth module in the front-end app
# coffeescript
config = (
$authProvider
$stateProvider
) ->
$authProvider.httpInterceptor = true # to automatically add the headers for auth
$authProvider.baseUrl = "http://path.to.your.api/"
$authProvider.loginRedirect = '/profile' # front-end route after login
$authProvider.logoutRedirect = '/' # front-end route after logout
$authProvider.signupRedirect = '/sign_in'
$authProvider.loginUrl = '/auth/sign_in' # api route for sign_in
$authProvider.signupUrl = '/auth/sign_up' # api route for sign_up
$authProvider.loginRoute = 'sign_in' # front-end route for login
$authProvider.signupRoute = 'sign_up' # front-end route for sign_up
$authProvider.signoutRoute = 'sign_out' # front-end route for sign_out
$authProvider.tokenRoot = 'data'
$authProvider.tokenName = 'token'
$authProvider.tokenPrefix = 'front-end-prefix-in-localstorage'
$authProvider.authHeader = 'Authorization'
$authProvider.authToken = 'Bearer'
$authProvider.storage = 'localStorage'
# state configurations for the routes
$stateProvider
.state 'auth',
url: '/'
abstract: true
templateUrl: 'modules/auth/auth.html'
data:
permissions:
only: ['guest']
redirectTo: 'profile'
.state 'auth.sign_up',
url: $authProvider.signupRoute
views:
'sign_up#auth':
templateUrl: 'modules/auth/sign_up.html'
controller: 'AuthenticationCtrl'
controllerAs: 'vm'
.state 'auth.sign_in',
url: $authProvider.loginRoute
views:
'sign_in#auth':
templateUrl: 'modules/auth/sign_in.html'
controller: 'AuthenticationCtrl'
controllerAs: 'vm'
this is the basic configurations for satellizer... as for the authentication controller... it's something like following
#signIn = (email, password, remember_me) ->
$auth.login
email: email
password: password
remember_me: remember_me
.then(success, error)
return
#signUp = (name, email, password) ->
$auth.signup
name: name
email: email
password: password
.then(success, error)
return
this is the basics for authenticating
as for the backend (RoR API) you should first allow CORS for the front-end app. and add gem 'jwt' to your gemfile.
second implement the API controller and the authentication controller
for example it might look something like the following
class Api::V1::ApiController < ApplicationController
# The API responds only to JSON
respond_to :json
before_action :authenticate_user!
protected
def authenticate_user!
http_authorization_header?
authenticate_request
set_current_user
end
# Bad Request if http authorization header missing
def http_authorization_header?
fail BadRequestError, 'errors.auth.missing_header' unless authorization_header
true
end
def authenticate_request
decoded_token ||= AuthenticationToken.decode(authorization_header)
#auth_token ||= AuthenticationToken.where(id: decoded_token['id']).
first unless decoded_token.nil?
fail UnauthorizedError, 'errors.auth.invalid_token' if #auth_token.nil?
end
def set_current_user
#current_user ||= #auth_token.user
end
# JWT's are stored in the Authorization header using this format:
# Bearer some_random_string.encoded_payload.another_random_string
def authorization_header
return #authorization_header if defined? #authorization_header
#authorization_header =
begin
if request.headers['Authorization'].present?
request.headers['Authorization'].split(' ').last
else
nil
end
end
end
end
class Api::V1::AuthenticationsController < Api::V1::ApiController
skip_before_action :authenticate_user!, only: [:sign_up, :sign_in]
def sign_in
# getting the current user from sign in request
#current_user ||= User.find_by_credentials(auth_params)
fail UnauthorizedError, 'errors.auth.invalid_credentials' unless #current_user
generate_auth_token(auth_params)
render :authentication, status: 201
end
def sign_out
# this auth token is assigned via api controller from headers
#auth_token.destroy!
head status: 204
end
def generate_auth_token(params)
#auth_token = AuthenticationToken.generate(#current_user, params[:remember_me])
end
end
The AuthenticationToken is a model used to keep track of the JWT tokens ( for session management like facebook)
here is the implementation for the AuthenticationToken model
class AuthenticationToken < ActiveRecord::Base
## Relations
belongs_to :user
## JWT wrappers
def self.encode(payload)
AuthToken.encode(payload)
end
def self.decode(token)
AuthToken.decode(token)
end
# generate and save new authentication token for the user
def self.generate(user, remember_me = false)
#auth_token = user.authentication_tokens.create
#auth_token.token = AuthToken.generate(#auth_token.id, remember_me)
#auth_token.save!
#auth_token
end
# check if a token can be used or not
# used by background job to clear the authentication collection
def expired?
AuthToken.decode(token).nil?
end
end
it uses a wrapper called AuthToken which wraps the JWT functionality
here is it's implementation
# wrapper around JWT to encapsulate it's code
# and exception handling and don't polute the AuthenticationToken model
class AuthToken
def self.encode(payload)
JWT.encode(payload, Rails.application.secrets.secret_key_base)
end
def self.decode(token)
payload = JWT.decode(token, Rails.application.secrets.secret_key_base)[0]
rescue JWT::ExpiredSignature
# It will raise an error if it is not a token that was generated
# with our secret key or if the user changes the contents of the payload
Rails.logger.info "Expired Token"
nil
rescue
Rails.logger.warn "Invalid Token"
nil
end
def self.generate(token_id, remember_me = false)
exp = remember_me ? 6.months.from_now : 6.hours.from_now
payload = { id: token_id.to_s, exp: exp.to_i }
self.encode(payload)
end
end

Simple Token Authentication Signout for Rails JSON API

I have a Rails App (that is mainly a JSON API)
I'm Using Devise for authentication using JSON request from whatever source (web , mobile)
and I'm using Simple Token Authentication to authenticate users using HTTP headers.
I'm not sure how the implementation should look like, but I have drafted an implementation that almost works.
There is only one problem. and that is when user tries to sign out... typically it should invalidate the authentication token for the user... but it doesn't, I'm not sure where is the problem really... whether it's with the Devise or the Simple Token Auth... so any help is greatly appreciated.
but here is the code
# router
devise_for :users, controllers: { sessions: 'sessions',
registrations: 'registrations' }
api vendor_string: 'app', default_version: 1, path: '', format: 'json' do
version 1 do
cache as: 'v1' do
resource :some_resource
end
end
the session controller is like this
class SessionsController < Devise::SessionsController
respond_to :json
skip_filter :verify_signed_out_user, only: :destroy
def create
self.resource = warden.authenticate!(scope: resource_name)
render :create, status: :created
end
def destroy
current_user.authentication_token = nil
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ take note of this line
current_user.save!
super
end
end
the previous noted line seems to have a problem... when a user provide a wrong token header, it's still working and the current user refers to the user who shouldn't be authenticated in the first place.. for example here are 2 calls fro, the specs
describe 'DELETE sessions#destroy' do
let(:user) { Fabricate(:confirmed_user) }
let(:auth_token) { user.authentication_token }
describe 'with request headers' do
context 'valid credentials' do
it 'Returns 204' do
delete '/users/sign_out', {}, {
HTTP_CONTENT_TYPE: 'application/json',
HTTP_ACCEPT: "application/vnd.app+json; version=1",
"X-User-Email" => user.email,
"X-User-Token" => user.authentication_token
}
user.reload
expect(response.status).to eq 204
expect(user.authentication_token).not_to eq auth_token
#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this is ok cause it's the valid user
end
end
context 'invalid credentials' do
it 'Returns 204' do
delete '/users/sign_out', {}, {
HTTP_CONTENT_TYPE: 'application/json',
HTTP_ACCEPT: "application/vnd.app+json; version=1",
"X-User-Email" => user.email,
"X-User-Token" => 'Invalid'
}
user.reload
expect(response.status).to eq 204
expect(user.authentication_token).to eq auth_token
#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this FAILS
# why did the user get new auth token when didn't sign out ????
end
end
end
this is also reported on Github
and for completeness here is the application controller
class ApplicationController < ActionController::Base
# The API responds only to JSON
respond_to :json
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
# default to protect_from_forgery with: :exception
protect_from_forgery with: :null_session
# Token Authenticatable for API
acts_as_token_authentication_handler_for User
end
From simple_authentication_token page on github, the current_user.authentication_token will automatically generated if it was blank (nil) on each time current_user will be saved.
Assuming user is an instance of User, which is token authenticatable: each time user will be saved, and user.authentication_token.blank? it receives a new and unique authentication token (via Devise.friendly_token).
Update
Add acts_as_token_authentication_handler_for User in your sessions_controller.rb
Please read on https://github.com/gonzalo-bulnes/simple_token_authentication/issues/224 . I think that is normal behaviour. You need delete the token on the client side(device)

REST Client for Ruby Gives ActionController::InvalidAuthenticityToken

I have a RESTful Rails application with a resource called "Foo". I'm trying to use REST Client to do a put:
resource = RestClient::Resource.new 'http://localhost:3000/foos/1', :user => 'me', :password => 'secret'
resource.put :name => 'somethingwitty', :content_type => 'application/xml'
But my app raises:
ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):
/usr/lib/ruby/gems/1.8/gems/actionpack-2.2.2/lib/action_controller/request_forgery_protection.rb:86:in `verify_authenticity_token'
It seems like my app isn't getting the message that this is an XML request and that the AuthenticityToken should be ignored. Maybe I'm not using REST Client correctly. Any ideas on why I'm getting the exception?
Try putting an :only => [:update, :delete, :create] on the protect_from_forgery line in your application controller.
More info: http://ryandaigle.com/articles/2007/9/24/what-s-new-in-edge-rails-better-cross-site-request-forging-prevention
Use something like:
resource.put '<foo><name>somethingwitty</name></foo>', :content_type => 'application/xml'
I think you need to make two changes;
(a) Use the rails routing to tag this as an XML request
(b) Use HTTP Basic Authentication to authenticate the request.
This means changing your URL above to include the username, password like this
me:secret#localhost:3000/foos/1.xml
also note .xml bit
I guess that somewhere on your server-side you have code that authenticates in-bound requests via a before filter. This needs to work something like this ...
#
# If you haven't authenticated already then you are either
# reqirected to the logon screen (for HTML formats) or
# the browser prompts you. You are always allowed to pass
# the username/password in the URL
#
def login_required
#current_user = valid_session?
unless #current_user
if params["format"]
#
# If you specify a format you must authenticate now
#
do_basic_authentication
else
display_logon_screen
end
end
end
#
# Ask Rails for the login and password then authenticate as if this
# were a new login.
#
def do_basic_authentication
user = authenticate_with_http_basic do |login, password|
User.authenticate(login, password)
end
if user
current_user(#current_user = user)
else
request_http_basic_authentication
end
end
That's from our own app and is triggered by a before_filter in ApplicationController.
Also, I don't think you need the :content_type => 'application/xml'. What I normally do is just call post or put directly like this ..
response = RestClient.post URI.encode(url), :record => args
where the url contains the basic authentication and the ".xml"
Happy coding
Chris
Since your application is a Rails app, it might be easier to use ActiveResource for the client.
Something like:
require 'active_resource'
class Foo < ActiveResource::Base
self.site = 'http://localhost:3000/'
end
foo = Foo.new(:name => 'somethingwitty')
foo.save
You can read up on how to do the authentication on the rdoc site.

Resources