From ab152261bba61845c31fdaec8e860c019fcfcd43 Mon Sep 17 00:00:00 2001 From: Okeke Arinze Date: Sat, 3 Jun 2017 12:39:03 +0100 Subject: [PATCH 1/8] Add soft authentication functionality (optional authentication) --- lib/knock/authenticable.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/knock/authenticable.rb b/lib/knock/authenticable.rb index 96d8a43..67f32bd 100644 --- a/lib/knock/authenticable.rb +++ b/lib/knock/authenticable.rb @@ -13,10 +13,11 @@ def token def method_missing(method, *args) prefix, entity_name = method.to_s.split('_', 2) + prefix = 'soft_authenticate' if (!args.empty? && args.first == 'soft') 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 From 82e22c7018af42f297458492192bfbae5b91181b Mon Sep 17 00:00:00 2001 From: Okeke Arinze Date: Sat, 3 Jun 2017 13:38:58 +0100 Subject: [PATCH 2/8] Accept method soft_authenticate_entity for soft authentication. Add test. All tests passed --- lib/knock/authenticable.rb | 13 ++++- .../app/controllers/soft_admin_controller.rb | 12 ++++ test/dummy/config/routes.rb | 1 + .../controllers/soft_admin_controller_test.rb | 55 +++++++++++++++++++ 4 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 test/dummy/app/controllers/soft_admin_controller.rb create mode 100644 test/dummy/test/controllers/soft_admin_controller_test.rb diff --git a/lib/knock/authenticable.rb b/lib/knock/authenticable.rb index 67f32bd..6f2f4bf 100644 --- a/lib/knock/authenticable.rb +++ b/lib/knock/authenticable.rb @@ -12,12 +12,19 @@ def token end def method_missing(method, *args) - prefix, entity_name = method.to_s.split('_', 2) - prefix = 'soft_authenticate' if (!args.empty? && args.first == 'soft') + 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 + + #prefix = 'soft_authenticate' if (!args.empty? && args.first.to_s == 'soft') + #print prefix + ' ' + entity_name case prefix when 'authenticate' unauthorized_entity(entity_name) unless authenticate_entity(entity_name) - when 'current' || 'soft_authenticate' + when 'current', 'soft_authenticate' authenticate_entity(entity_name) else super 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..c6b173b --- /dev/null +++ b/test/dummy/app/controllers/soft_admin_controller.rb @@ -0,0 +1,12 @@ +class SoftAdminController < ApplicationController + # before_action do + # authenticate_admin :soft + # end + before_action :soft_authenticate_admin #:soft} + + + def index + head :ok + end + +end diff --git a/test/dummy/config/routes.rb b/test/dummy/config/routes.rb index a24acf9..0dabb9f 100644 --- a/test/dummy/config/routes.rb +++ b/test/dummy/config/routes.rb @@ -5,6 +5,7 @@ resource :current_user resources :admin_protected + resources :soft_admin resources :composite_name_entity_protected resources :custom_unauthorized_entity resources :guest_protected 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 From d211dfb0da7396d5c3bcaa9397d5df832e13b057 Mon Sep 17 00:00:00 2001 From: Okeke Arinze Date: Sat, 3 Jun 2017 13:40:00 +0100 Subject: [PATCH 3/8] remove stray comments --- lib/knock/authenticable.rb | 3 --- test/dummy/app/controllers/soft_admin_controller.rb | 5 +---- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/knock/authenticable.rb b/lib/knock/authenticable.rb index 6f2f4bf..0491b92 100644 --- a/lib/knock/authenticable.rb +++ b/lib/knock/authenticable.rb @@ -18,9 +18,6 @@ def method_missing(method, *args) else prefix, entity_name = method.to_s.split('_', 2) end - - #prefix = 'soft_authenticate' if (!args.empty? && args.first.to_s == 'soft') - #print prefix + ' ' + entity_name case prefix when 'authenticate' unauthorized_entity(entity_name) unless authenticate_entity(entity_name) diff --git a/test/dummy/app/controllers/soft_admin_controller.rb b/test/dummy/app/controllers/soft_admin_controller.rb index c6b173b..07b00a1 100644 --- a/test/dummy/app/controllers/soft_admin_controller.rb +++ b/test/dummy/app/controllers/soft_admin_controller.rb @@ -1,8 +1,5 @@ class SoftAdminController < ApplicationController - # before_action do - # authenticate_admin :soft - # end - before_action :soft_authenticate_admin #:soft} + before_action :soft_authenticate_admin def index From d56b4edb29e67d64e06af8343b192b2249157705 Mon Sep 17 00:00:00 2001 From: Okeke Arinze Date: Sat, 3 Jun 2017 14:06:46 +0100 Subject: [PATCH 4/8] Fix security flaw where strict authentication doesn't take place when user uses authenticate_for directly. wrapper functions over authenticate_for for strict and soft authentications created to fix this issue. --- lib/knock/authenticable.rb | 8 ++++++++ test/dummy/app/controllers/admin_protected_controller.rb | 2 +- test/dummy/app/controllers/application_controller.rb | 3 +++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/knock/authenticable.rb b/lib/knock/authenticable.rb index 0491b92..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 diff --git a/test/dummy/app/controllers/admin_protected_controller.rb b/test/dummy/app/controllers/admin_protected_controller.rb index 78fa3db..b672cb4 100644 --- a/test/dummy/app/controllers/admin_protected_controller.rb +++ b/test/dummy/app/controllers/admin_protected_controller.rb @@ -1,5 +1,5 @@ class AdminProtectedController < ApplicationController - before_action :authenticate_admin + before_action :authenticate_trump def index head :ok diff --git a/test/dummy/app/controllers/application_controller.rb b/test/dummy/app/controllers/application_controller.rb index a84526f..32299c7 100644 --- a/test/dummy/app/controllers/application_controller.rb +++ b/test/dummy/app/controllers/application_controller.rb @@ -4,4 +4,7 @@ class ApplicationController < ActionController::Base protect_from_forgery with: :null_session include Knock::Authenticable + def authenticate_trump + set_authenticate_for Admin + end end From 743ebacc7d2c2db2acc6476be141cbc8af1d9d71 Mon Sep 17 00:00:00 2001 From: Okeke Arinze Date: Sat, 3 Jun 2017 14:24:02 +0100 Subject: [PATCH 5/8] add test. all tests passed --- .../admin_custom_auth_soft_controller.rb | 12 ++++ .../admin_custom_auth_strict_controller.rb | 13 +++++ .../controllers/admin_protected_controller.rb | 2 +- .../app/controllers/application_controller.rb | 3 - test/dummy/config/routes.rb | 4 +- .../admin_custom_auth_soft_controller_test.rb | 55 +++++++++++++++++++ ...dmin_custom_auth_strict_controller_test.rb | 49 +++++++++++++++++ 7 files changed, 133 insertions(+), 5 deletions(-) create mode 100644 test/dummy/app/controllers/admin_custom_auth_soft_controller.rb create mode 100644 test/dummy/app/controllers/admin_custom_auth_strict_controller.rb create mode 100644 test/dummy/test/controllers/admin_custom_auth_soft_controller_test.rb create mode 100644 test/dummy/test/controllers/admin_custom_auth_strict_controller_test.rb 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/admin_protected_controller.rb b/test/dummy/app/controllers/admin_protected_controller.rb index b672cb4..78fa3db 100644 --- a/test/dummy/app/controllers/admin_protected_controller.rb +++ b/test/dummy/app/controllers/admin_protected_controller.rb @@ -1,5 +1,5 @@ class AdminProtectedController < ApplicationController - before_action :authenticate_trump + before_action :authenticate_admin def index head :ok diff --git a/test/dummy/app/controllers/application_controller.rb b/test/dummy/app/controllers/application_controller.rb index 32299c7..a84526f 100644 --- a/test/dummy/app/controllers/application_controller.rb +++ b/test/dummy/app/controllers/application_controller.rb @@ -4,7 +4,4 @@ class ApplicationController < ActionController::Base protect_from_forgery with: :null_session include Knock::Authenticable - def authenticate_trump - set_authenticate_for Admin - end end diff --git a/test/dummy/config/routes.rb b/test/dummy/config/routes.rb index 0dabb9f..8ff19ca 100644 --- a/test/dummy/config/routes.rb +++ b/test/dummy/config/routes.rb @@ -5,12 +5,14 @@ resource :current_user resources :admin_protected - resources :soft_admin + 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/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 From 66588e120c0d4810ca5c699d41abd6bec120aa40 Mon Sep 17 00:00:00 2001 From: Okeke Arinze Date: Sat, 3 Jun 2017 14:40:18 +0100 Subject: [PATCH 6/8] Update README.md to reflect changes --- README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f57ddc2..5cae913 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,13 @@ 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 From fd8caeab004f5d8c031ff928504e0484ea892a15 Mon Sep 17 00:00:00 2001 From: Okeke Arinze Date: Sat, 3 Jun 2017 14:42:22 +0100 Subject: [PATCH 7/8] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5cae913..5f37aff 100644 --- a/README.md +++ b/README.md @@ -145,8 +145,10 @@ Then you get the current user by calling `current_v1_user` instead of `current_u #### 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. +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. From b66e40f7f70d177ca80d77cca788a85a2490e3cc Mon Sep 17 00:00:00 2001 From: Okeke Arinze Date: Sat, 3 Jun 2017 15:06:47 +0100 Subject: [PATCH 8/8] add version number to dummy app migration files. Rails 5+ requires it. Travis threw an error on that --- test/dummy/db/migrate/20150713101607_create_users.rb | 2 +- test/dummy/db/migrate/20160519075733_create_admins.rb | 2 +- test/dummy/db/migrate/20160522051816_create_vendors.rb | 2 +- .../db/migrate/20160522181712_create_composite_name_entities.rb | 2 +- test/dummy/db/migrate/20161127203222_create_v1_users.rb | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) 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|