diff --git a/README.md b/README.md index f57ddc2..5f37aff 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ _Note: the `authenticate_user` method uses the `current_user` method. Overwritin You can do the exact same thing for any entity. E.g. for `Admin`, use `authenticate_admin` and `current_admin` instead. -If you're using a namespaced model, Knock won't be able to infer it automatically from the method name. Instead you can use `authenticate_for` directly like this: +If you're using a namespaced model, Knock won't be able to infer it automatically from the method name. Instead you can use `set_authenticate_for` directly like this: ```ruby class ApplicationController < ActionController::Base @@ -128,7 +128,7 @@ class ApplicationController < ActionController::Base private def authenticate_v1_user - authenticate_for V1::User + set_authenticate_for V1::User end end ``` @@ -143,6 +143,15 @@ Then you get the current user by calling `current_v1_user` instead of `current_u ### Customization +#### Soft (Optional) Authentication + +For use in controllers where authentication is optional, use `soft_authenticate_user` instead of `authenticate_user` and use `set_soft_authenticate_for V1::User` instead of `set_authenticate_for V1::User` as in the examples in the [Usage section](#Usage) above. + +Users will have access even if no token or an invalid token is passed. + +NB: `current_user` will only be available if a valid token is passed and authenticated. + + #### Via the entity model The entity model (e.g. `User`) can implement specific methods to provide diff --git a/lib/knock/authenticable.rb b/lib/knock/authenticable.rb index 96d8a43..c1d3171 100644 --- a/lib/knock/authenticable.rb +++ b/lib/knock/authenticable.rb @@ -5,6 +5,14 @@ def authenticate_for entity_class public_send(getter_name) end + def set_authenticate_for entity_class + unauthorized_entity(entity_class.to_s) unless authenticate_for entity_class + end + + def set_soft_authenticate_for entity_class + authenticate_for entity_class + end + private def token @@ -12,11 +20,16 @@ def token end def method_missing(method, *args) - prefix, entity_name = method.to_s.split('_', 2) + values = method.to_s.split('_', 3) + if (values.size) == 3 && ("#{values.first}_#{values.second}" == 'soft_authenticate') + prefix, entity_name = 'soft_authenticate', values.last + else + prefix, entity_name = method.to_s.split('_', 2) + end case prefix when 'authenticate' unauthorized_entity(entity_name) unless authenticate_entity(entity_name) - when 'current' + when 'current', 'soft_authenticate' authenticate_entity(entity_name) else super diff --git a/test/dummy/app/controllers/admin_custom_auth_soft_controller.rb b/test/dummy/app/controllers/admin_custom_auth_soft_controller.rb new file mode 100644 index 0000000..b815ae0 --- /dev/null +++ b/test/dummy/app/controllers/admin_custom_auth_soft_controller.rb @@ -0,0 +1,12 @@ +class AdminCustomAuthSoftController < ApplicationController + before_action :soft_authenticate_admin + + def index + head :ok + end + + private + def soft_authenticate_admin + set_soft_authenticate_for Admin + end +end diff --git a/test/dummy/app/controllers/admin_custom_auth_strict_controller.rb b/test/dummy/app/controllers/admin_custom_auth_strict_controller.rb new file mode 100644 index 0000000..1de1494 --- /dev/null +++ b/test/dummy/app/controllers/admin_custom_auth_strict_controller.rb @@ -0,0 +1,13 @@ +class AdminCustomAuthStrictController < ApplicationController + before_action :authenticate_admin + + + def index + head :ok + end + + private + def authenticate_admin + set_authenticate_for Admin + end +end diff --git a/test/dummy/app/controllers/soft_admin_controller.rb b/test/dummy/app/controllers/soft_admin_controller.rb new file mode 100644 index 0000000..07b00a1 --- /dev/null +++ b/test/dummy/app/controllers/soft_admin_controller.rb @@ -0,0 +1,9 @@ +class SoftAdminController < ApplicationController + before_action :soft_authenticate_admin + + + def index + head :ok + end + +end diff --git a/test/dummy/config/routes.rb b/test/dummy/config/routes.rb index a24acf9..8ff19ca 100644 --- a/test/dummy/config/routes.rb +++ b/test/dummy/config/routes.rb @@ -5,11 +5,14 @@ resource :current_user resources :admin_protected + resources :soft_admin, only: [:index] resources :composite_name_entity_protected resources :custom_unauthorized_entity resources :guest_protected resources :protected_resources resources :vendor_protected + resources :admin_custom_auth_strict, only: [:index] + resources :admin_custom_auth_soft, only: [:index] namespace :v1 do resources :test_namespaced diff --git a/test/dummy/db/migrate/20150713101607_create_users.rb b/test/dummy/db/migrate/20150713101607_create_users.rb index e2fdb6f..7c99889 100644 --- a/test/dummy/db/migrate/20150713101607_create_users.rb +++ b/test/dummy/db/migrate/20150713101607_create_users.rb @@ -1,4 +1,4 @@ -class CreateUsers < ActiveRecord::Migration +class CreateUsers < ActiveRecord::Migration[4.2] def change create_table :users do |t| t.string :email, unique: true, null: false diff --git a/test/dummy/db/migrate/20160519075733_create_admins.rb b/test/dummy/db/migrate/20160519075733_create_admins.rb index 31bfd1a..9d72a80 100644 --- a/test/dummy/db/migrate/20160519075733_create_admins.rb +++ b/test/dummy/db/migrate/20160519075733_create_admins.rb @@ -1,4 +1,4 @@ -class CreateAdmins < ActiveRecord::Migration +class CreateAdmins < ActiveRecord::Migration[4.2] def change create_table :admins do |t| t.string :email diff --git a/test/dummy/db/migrate/20160522051816_create_vendors.rb b/test/dummy/db/migrate/20160522051816_create_vendors.rb index f020eb3..317114a 100644 --- a/test/dummy/db/migrate/20160522051816_create_vendors.rb +++ b/test/dummy/db/migrate/20160522051816_create_vendors.rb @@ -1,4 +1,4 @@ -class CreateVendors < ActiveRecord::Migration +class CreateVendors < ActiveRecord::Migration[4.2] def change create_table :vendors do |t| t.string :email diff --git a/test/dummy/db/migrate/20160522181712_create_composite_name_entities.rb b/test/dummy/db/migrate/20160522181712_create_composite_name_entities.rb index 23a843a..002acfb 100644 --- a/test/dummy/db/migrate/20160522181712_create_composite_name_entities.rb +++ b/test/dummy/db/migrate/20160522181712_create_composite_name_entities.rb @@ -1,4 +1,4 @@ -class CreateCompositeNameEntities < ActiveRecord::Migration +class CreateCompositeNameEntities < ActiveRecord::Migration[4.2] def change create_table :composite_name_entities do |t| t.string :email diff --git a/test/dummy/db/migrate/20161127203222_create_v1_users.rb b/test/dummy/db/migrate/20161127203222_create_v1_users.rb index 8e7dd58..7fc13d3 100644 --- a/test/dummy/db/migrate/20161127203222_create_v1_users.rb +++ b/test/dummy/db/migrate/20161127203222_create_v1_users.rb @@ -1,4 +1,4 @@ -class CreateV1Users < ActiveRecord::Migration +class CreateV1Users < ActiveRecord::Migration[4.2] def change create_table :v1_users do |t| diff --git a/test/dummy/test/controllers/admin_custom_auth_soft_controller_test.rb b/test/dummy/test/controllers/admin_custom_auth_soft_controller_test.rb new file mode 100644 index 0000000..04cc061 --- /dev/null +++ b/test/dummy/test/controllers/admin_custom_auth_soft_controller_test.rb @@ -0,0 +1,55 @@ +require 'test_helper' + +class AdminCustomAuthSoftControllerTest < ActionController::TestCase + def valid_auth + @admin = admins(:one) + @token = Knock::AuthToken.new(payload: { sub: @admin.id }).token + @request.env['HTTP_AUTHORIZATION'] = "Bearer #{@token}" + end + + def invalid_token_auth + @token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9' + @request.env['HTTP_AUTHORIZATION'] = "Bearer #{@token}" + end + + def invalid_entity_auth + @token = Knock::AuthToken.new(payload: { sub: 0 }).token + @request.env['HTTP_AUTHORIZATION'] = "Bearer #{@token}" + end + + test "responds with success" do + get :index + assert_response :success + end + + test "responds with success to invalid token" do + invalid_token_auth + get :index + assert_response :success + end + + test "responds with success to invalid entity" do + invalid_entity_auth + get :index + assert_response :success + end + + test "responds with success if authenticated" do + valid_auth + get :index + assert_response :success + end + + test "has a current_admin after authentication" do + valid_auth + get :index + assert_response :success + assert @controller.current_admin.id == @admin.id + end + + test "has no current_admin if no authentication" do + get :index + assert_response :success + assert @controller.current_admin.nil? + end +end diff --git a/test/dummy/test/controllers/admin_custom_auth_strict_controller_test.rb b/test/dummy/test/controllers/admin_custom_auth_strict_controller_test.rb new file mode 100644 index 0000000..3f08dff --- /dev/null +++ b/test/dummy/test/controllers/admin_custom_auth_strict_controller_test.rb @@ -0,0 +1,49 @@ +require 'test_helper' + +class AdminCustomAuthStrictControllerTest < ActionController::TestCase + def valid_auth + @admin = admins(:one) + @token = Knock::AuthToken.new(payload: { sub: @admin.id }).token + @request.env['HTTP_AUTHORIZATION'] = "Bearer #{@token}" + end + + def invalid_token_auth + @token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9' + @request.env['HTTP_AUTHORIZATION'] = "Bearer #{@token}" + end + + def invalid_entity_auth + @token = Knock::AuthToken.new(payload: { sub: 0 }).token + @request.env['HTTP_AUTHORIZATION'] = "Bearer #{@token}" + end + + test "responds with unauthorized" do + get :index + assert_response :unauthorized + end + + test "responds with unauthorized to invalid token" do + invalid_token_auth + get :index + assert_response :unauthorized + end + + test "responds with unauthorized to invalid entity" do + invalid_entity_auth + get :index + assert_response :unauthorized + end + + test "responds with success if authenticated" do + valid_auth + get :index + assert_response :success + end + + test "has a current_admin after authentication" do + valid_auth + get :index + assert_response :success + assert @controller.current_admin.id == @admin.id + end +end diff --git a/test/dummy/test/controllers/soft_admin_controller_test.rb b/test/dummy/test/controllers/soft_admin_controller_test.rb new file mode 100644 index 0000000..564b305 --- /dev/null +++ b/test/dummy/test/controllers/soft_admin_controller_test.rb @@ -0,0 +1,55 @@ +require 'test_helper' + +class SoftAdminControllerTest < ActionController::TestCase + def valid_auth + @admin = admins(:one) + @token = Knock::AuthToken.new(payload: { sub: @admin.id }).token + @request.env['HTTP_AUTHORIZATION'] = "Bearer #{@token}" + end + + def invalid_token_auth + @token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9' + @request.env['HTTP_AUTHORIZATION'] = "Bearer #{@token}" + end + + def invalid_entity_auth + @token = Knock::AuthToken.new(payload: { sub: 0 }).token + @request.env['HTTP_AUTHORIZATION'] = "Bearer #{@token}" + end + + test "responds with success" do + get :index + assert_response :success + end + + test "responds with success to invalid token" do + invalid_token_auth + get :index + assert_response :success + end + + test "responds with success to invalid entity" do + invalid_entity_auth + get :index + assert_response :success + end + + test "responds with success if authenticated" do + valid_auth + get :index + assert_response :success + end + + test "has a current_admin after authentication" do + valid_auth + get :index + assert_response :success + assert @controller.current_admin.id == @admin.id + end + + test "has no current_admin if no authentication" do + get :index + assert_response :success + assert @controller.current_admin.nil? + end +end