diff --git a/features/option_fallback_to_devise_is_configurable.feature b/features/option_fallback_to_devise_is_configurable.feature new file mode 100644 index 00000000..6a7d603a --- /dev/null +++ b/features/option_fallback_to_devise_is_configurable.feature @@ -0,0 +1,203 @@ +# See https://github.com/gonzalo-bulnes/simple_token_authentication/pull/#61 +Feature: The `acts_as_token_authentication_handler` filter has a fallback_to_devise option + As a developer + In order to build safe API authentication by token + And to keep being able to use token authentication in non-API scnearii + I want the fallback_to_devise option to be available at a controller level + + @rspec + Scenario: Fallback to Devise is enabled by default + Given I have a dummy app with a Devise-enabled User + And a scaffolded PrivatePost + And I prepare the test database + And the `authenticate_user!` and `sign_in` methods always raise an exception + And User `acts_as_token_authenticatable` + And PrivatePostsController `acts_as_token_authentication_handler_for` User + And I write to "spec/factories/users.rb" with: + """ + FactoryGirl.define do + sequence :email do |n| + "user#{n}@factory.com" + end + + factory :user do + email + password "password" + password_confirmation "password" + end + end + """ + And I write to "spec/requests/private_posts_controller_spec.rb" with: + """ + require 'spec_helper' + + describe "PrivatePostsController" do + describe "GET /private_posts" do + + context "when the required headers are missing in the request (and no query params are used)" do + + it "does fallback to Devise authentication" do + user = FactoryGirl.create(:user \ + ,email: 'alice@example.com' \ + ,authentication_token: 'ExaMpLeTokEn' ) + + # `sign_in` is configured to raise an exception when called, + # see spec/dummy/app/controllers/application_controller.rb + lambda do + # see https://github.com/rspec/rspec-rails/issues/65 + # and http://guides.rubyonrails.org/testing.html#helpers-available-for-integration-tests + request_via_redirect 'GET', private_posts_path, nil, { 'X-User-Email' => user.email } + end.should raise_exception(RuntimeError, "`authenticate_user!` was called.") + end + end + + end + end + """ + + And I silence the PrivatePostsController spec errors + + When I run `rspec --format documentation` + Then the exit status should be 0 + And the output should match: + """ + PrivatePostsController + GET /private_posts + """ + And the output should contain: + """ + when the required headers are missing in the request (and no query params are used) + does fallback to Devise authentication + """ + + @rspec + Scenario: Fallback to Devise can be disabled for a specific controller + Given I have a dummy app with a Devise-enabled User + And a scaffolded PrivatePost + And a scaffolded ApiPrivatePost + And I prepare the test database + And the `authenticate_user!` and `sign_in` methods always raise an exception + And User `acts_as_token_authenticatable` + And PrivatePostsController `acts_as_token_authentication_handler_for` User with options: + """ + fallback_to_devise: true + """ + And ApiPrivatePostsController `acts_as_token_authentication_handler_for` User with options: + """ + fallback_to_devise: false + """ + And I write to "spec/factories/users.rb" with: + """ + FactoryGirl.define do + sequence :email do |n| + "user#{n}@factory.com" + end + + factory :user do + email + password "password" + password_confirmation "password" + end + end + """ + And I write to "spec/requests/private_posts_controller_spec.rb" with: + """ + require 'spec_helper' + + describe "PrivatePostsController" do + describe "GET /private_posts" do + + context "when the required headers are missing in the request (and no query params are used)" do + + it "does fallback to Devise authentication" do + user = FactoryGirl.create(:user \ + ,email: 'alice@example.com' \ + ,authentication_token: 'ExaMpLeTokEn' ) + + # `sign_in` is configured to raise an exception when called, + # see spec/dummy/app/controllers/application_controller.rb + lambda do + # see https://github.com/rspec/rspec-rails/issues/65 + # and http://guides.rubyonrails.org/testing.html#helpers-available-for-integration-tests + request_via_redirect 'GET', private_posts_path, nil, { 'X-User-Email' => user.email } + end.should raise_exception(RuntimeError, "`authenticate_user!` was called.") + end + end + + end + end + """ + And I write to "spec/requests/api_private_posts_controller_spec.rb" with: + """ + require 'spec_helper' + + describe "ApiPrivatePostsController" do + describe "GET /api_private_posts" do + + context "when the required headers are set in the request" do + + it "performs token authentication as usual" do + user = FactoryGirl.create(:user \ + ,email: 'alice@example.com' \ + ,authentication_token: 'ExaMpLeTokEn' ) + + # `sign_in` is configured to raise an exception when called, + # see spec/dummy/app/controllers/application_controller.rb + lambda do + # see https://github.com/rspec/rspec-rails/issues/65 + # and http://guides.rubyonrails.org/testing.html#helpers-available-for-integration-tests + request_via_redirect 'GET', api_private_posts_path, nil, { 'X-User-Email' => user.email, 'X-User-Token' => user.authentication_token } + end.should raise_exception(RuntimeError, "`sign_in` was called.") + end + end + + context "when the required headers are missing in the request (and no query params are used)" do + + it "does not fallback to Devise authentication" do + user = FactoryGirl.create(:user \ + ,email: 'alice@example.com' \ + ,authentication_token: 'ExaMpLeTokEn' ) + + # `sign_in` is configured to raise an exception when called, + # see spec/dummy/app/controllers/application_controller.rb + lambda do + # see https://github.com/rspec/rspec-rails/issues/65 + # and http://guides.rubyonrails.org/testing.html#helpers-available-for-integration-tests + request_via_redirect 'GET', api_private_posts_path, nil, { 'X-User-Email' => user.email } + end.should_not raise_exception(RuntimeError, "`authenticate_user!` was called.") + end + end + + end + end + """ + + And I silence the PrivatePostsController spec errors + + When I run `rspec --format documentation` + Then the exit status should be 0 + And the output should contain: + """ + PrivatePostsController + GET /private_posts + """ + And the output should contain: + """ + when the required headers are missing in the request (and no query params are used) + does fallback to Devise authentication + """ + And the output should contain: + """ + ApiPrivatePostsController + GET /api_private_posts + """ + And the output should contain: + """ + when the required headers are set in the request + performs token authentication as usual + """ + And the output should contain: + """ + when the required headers are missing in the request (and no query params are used) + does not fallback to Devise authentication + """ diff --git a/features/step_definitions/dummy_app_steps.rb b/features/step_definitions/dummy_app_steps.rb index d363dbeb..32d7039f 100644 --- a/features/step_definitions/dummy_app_steps.rb +++ b/features/step_definitions/dummy_app_steps.rb @@ -307,6 +307,83 @@ def private_post_params } end +Given /^(\w+) `acts_as_token_authentication_handler_for` (\w+) with options:$/ do |controller, model, options| + # Caution: model should be a singular camel-cased name but could be pluralized or underscored. + # Caution: controller must be a camel cased name: e.g. CamelCasedController + + controller_back = controller + controller = controller.gsub(/Controller/, '').singularize + + steps %Q{ + And I overwrite "app/controllers/#{controller_back.underscore}.rb" with: + """ + class #{controller_back} < ApplicationController + + # Please do notice that this controller DOES call `acts_as_authentication_handler` with options. + # See test/dummy/spec/requests/posts_specs.rb + acts_as_token_authentication_handler_for #{model.singularize.camelize}, #{options} + + before_action :set_#{controller.underscore}, only: [:show, :edit, :update, :destroy] + + # GET /#{controller.underscore} + def index + @#{controller.pluralize.underscore} = #{controller}.all + end + + # GET /#{controller.underscore}/1 + def show + end + + # GET /#{controller.underscore}/new + def new + @#{controller.underscore} = #{controller}.new + end + + # GET /#{controller.underscore}/1/edit + def edit + end + + # POST /#{controller.underscore} + def create + @#{controller.underscore} = #{controller}.new(#{controller.underscore}_params) + + if @#{controller.underscore}.save + redirect_to @#{controller.underscore}, notice: '#{controller} was successfully created.' + else + render action: 'new' + end + end + + # PATCH/PUT /#{controller.underscore}/1 + def update + if @#{controller.underscore}.update(#{controller.underscore}_params) + redirect_to @#{controller.underscore}, notice: '#{controller} was successfully updated.' + else + render action: 'edit' + end + end + + # DELETE /#{controller.underscore}/1 + def destroy + @#{controller.underscore}.destroy + redirect_to #{controller.pluralize.underscore}_url, notice: '#{controller} was successfully destroyed.' + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_#{controller.underscore} + @#{controller.underscore} = #{controller}.find(params[:id]) + end + + # Only allow a trusted parameter "white list" through. + def #{controller.underscore}_params + params.require(:#{controller.underscore}).permit(:title, :body) + end + end + """ + } +end + Given /^PrivatePostsController `acts_as_token_authentication_handler_for` (\w+)$/ do |model| # Caution: model should be a singular camel-cased name but could be pluralized or underscored. @@ -380,7 +457,7 @@ def private_post_params } end -Given /^I silence the PrivatePostsController spec errors$/ do +Given /^I silence the (\w+) spec errors$/ do |controller| puts """ Errors should never pass silently. Unless explicitly silenced. @@ -388,20 +465,20 @@ def private_post_params """ steps %Q{ - And I overwrite "spec/controllers/private_posts_controller_spec.rb" with: + And I overwrite "spec/controllers/#{controller.underscore}_spec.rb" with: """ require 'spec_helper' - describe PrivatePostsController do + describe #{controller} do # This should return the minimal set of attributes required to create a valid - # PrivatePost. As you add validations to PrivatePost, be sure to + # #{controller}. As you add validations to #{controller}, be sure to # adjust the attributes here as well. let(:valid_attributes) { { "title" => "MyString" } } # This should return the minimal set of values that should be in the session # in order to pass any filters (e.g. authentication) defined in - # PrivatePostsController. Be sure to keep this updated too. + # #{controller}. Be sure to keep this updated too. let(:valid_session) { {} } describe "actions" do