Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spike: add models aliases option #162

Merged
merged 7 commits into from
Apr 24, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ class ApplicationController < ActionController::Base # or ActionController::API
# acts_as_token_authentication_handler_for SpecialUser, fallback_to_devise: false
# acts_as_token_authentication_handler_for User # the last fallback is up to you

# Aliases can be defined for namespaced models:
#
# acts_as_token_authentication_handler_for Customer::Representative, as: :facilitator
# acts_as_token_authentication_handler_for SpecialUser, as: :user
#
# When defined, aliases are used to define both the params and the header names to watch.
# E.g. facilitator_token, X-Facilitator-Token

# ...
end
```
Expand Down
2 changes: 1 addition & 1 deletion lib/simple_token_authentication.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ module SimpleTokenAuthentication

def self.ensure_models_can_act_as_token_authenticatables model_adapters
model_adapters.each do |model_adapter|
model_adapter.base_class.send :include, SimpleTokenAuthentication::ActsAsTokenAuthenticatable
model_adapter.base_class.send :extend, SimpleTokenAuthentication::ActsAsTokenAuthenticatable
end
end

Expand Down
47 changes: 6 additions & 41 deletions lib/simple_token_authentication/acts_as_token_authenticatable.rb
Original file line number Diff line number Diff line change
@@ -1,49 +1,14 @@
require 'active_support/concern'
require 'simple_token_authentication/token_generator'
require 'simple_token_authentication/token_authenticatable'

module SimpleTokenAuthentication
module ActsAsTokenAuthenticatable
extend ::ActiveSupport::Concern

# Please see https://gist.github.com/josevalim/fb706b1e933ef01e4fb6
# before editing this file, the discussion is very interesting.
# This module ensures that no TokenAuthenticatable behaviour
# is added before the class actually `acts_as_token_authenticatable`.

included do
private :generate_authentication_token
private :token_suitable?
private :token_generator
end

# Set an authentication token if missing
#
# Because it is intended to be used as a filter,
# this method is -and should be kept- idempotent.
def ensure_authentication_token
if authentication_token.blank?
self.authentication_token = generate_authentication_token(token_generator)
end
end

def generate_authentication_token(token_generator)
loop do
token = token_generator.generate_token
break token if token_suitable?(token)
end
end

def token_suitable?(token)
self.class.where(authentication_token: token).count == 0
end

# Private: Get one (always the same) object which behaves as a token generator
def token_generator
@token_generator ||= TokenGenerator.new
end

module ClassMethods
def acts_as_token_authenticatable(options = {})
before_save :ensure_authentication_token
end
def acts_as_token_authenticatable(options = {})
include SimpleTokenAuthentication::TokenAuthenticatable
before_save :ensure_authentication_token
end
end
end
4 changes: 2 additions & 2 deletions lib/simple_token_authentication/entities_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

module SimpleTokenAuthentication
class EntitiesManager
def find_or_create_entity(model)
def find_or_create_entity(model, model_alias=nil)
@entities ||= {}
@entities[model.name] ||= Entity.new(model)
@entities[model.name] ||= Entity.new(model, model_alias)
end
end
end
9 changes: 5 additions & 4 deletions lib/simple_token_authentication/entity.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
module SimpleTokenAuthentication
class Entity
def initialize model
def initialize(model, model_alias=nil)
@model = model
@name = model.name
@name_underscore = model_alias.to_s unless model_alias.nil?
end

def model
Expand All @@ -14,7 +15,7 @@ def name
end

def name_underscore
name.underscore
@name_underscore || name.underscore
end

# Private: Return the name of the header to watch for the token authentication param
Expand All @@ -23,7 +24,7 @@ def token_header_name
&& token_header_name = SimpleTokenAuthentication.header_names["#{name_underscore}".to_sym][:authentication_token]
token_header_name
else
"X-#{name}-Token"
"X-#{name_underscore.camelize}-Token"
end
end

Expand All @@ -33,7 +34,7 @@ def identifier_header_name
&& identifier_header_name = SimpleTokenAuthentication.header_names["#{name_underscore}".to_sym][identifier]
identifier_header_name
else
"X-#{name}-#{identifier.to_s.camelize}"
"X-#{name_underscore.camelize}-#{identifier.to_s.camelize}"
end
end

Expand Down
43 changes: 43 additions & 0 deletions lib/simple_token_authentication/token_authenticatable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
require 'active_support/concern'
require 'simple_token_authentication/token_generator'

module SimpleTokenAuthentication
module TokenAuthenticatable
extend ::ActiveSupport::Concern

# Please see https://gist.github.com/josevalim/fb706b1e933ef01e4fb6
# before editing this file, the discussion is very interesting.

included do
private :generate_authentication_token
private :token_suitable?
private :token_generator
end

# Set an authentication token if missing
#
# Because it is intended to be used as a filter,
# this method is -and should be kept- idempotent.
def ensure_authentication_token
if authentication_token.blank?
self.authentication_token = generate_authentication_token(token_generator)
end
end

def generate_authentication_token(token_generator)
loop do
token = token_generator.generate_token
break token if token_suitable?(token)
end
end

def token_suitable?(token)
self.class.where(authentication_token: token).count == 0
end

# Private: Get one (always the same) object which behaves as a token generator
def token_generator
@token_generator ||= TokenGenerator.new
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ module ClassMethods
#
# Returns nothing.
def handle_token_authentication_for(model, options = {})
entity = entities_manager.find_or_create_entity(model)
model_alias = options[:as] || options['as']
entity = entities_manager.find_or_create_entity(model, model_alias)
options = SimpleTokenAuthentication.parse_options(options)
define_token_authentication_helpers_for(entity, fallback_authentication_handler)
set_token_authentication_hooks(entity, options)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,22 @@ def generate_token
it_behaves_like 'a token generator'
end

describe 'A token authenticatable class (or one of its children)' do
describe 'Any class which extends SimpleTokenAuthentication::ActsAsTokenAuthenticatable (or any if its children)' do

after(:each) do
ensure_examples_independence
end

before(:each) do
define_test_subjects_for_inclusion_of(SimpleTokenAuthentication::ActsAsTokenAuthenticatable)
define_test_subjects_for_extension_of(SimpleTokenAuthentication::ActsAsTokenAuthenticatable)
end

it 'doesn\'t behave like a token authenticatable', public: true do
stub_const('SimpleTokenAuthentication::TokenAuthenticatable', Module.new)

@subjects.each do |subject|
expect(subject).not_to be_include SimpleTokenAuthentication::TokenAuthenticatable
end
end

it 'responds to :acts_as_token_authenticatable', public: true do
Expand All @@ -31,77 +39,45 @@ def generate_token
end
end

describe 'which supports the :before_save hook' do

context 'when it acts as token authenticatable' do
it 'ensures its instances have an authentication token before being saved (1)', rspec_3_error: true, public: true do
some_class = @subjects.first

expect(some_class).to receive(:before_save).with(:ensure_authentication_token)
some_class.acts_as_token_authenticatable
end

it 'ensures its instances have an authentication token before being saved (2)', rspec_3_error: true, public: true do
some_child_class = @subjects.last
context 'when it explicitely acts as a token authenticatable' do

expect(some_child_class).to receive(:before_save).with(:ensure_authentication_token)
some_child_class.acts_as_token_authenticatable
end
end
end
it 'behaves like a token authenticatable (1)', rspec_3_error: true, public: true do
stub_const('SimpleTokenAuthentication::TokenAuthenticatable', Module.new)

describe 'instance' do
some_class = @subjects.first
allow(some_class).to receive(:before_save)

it 'responds to :ensure_authentication_token', protected: true do
@subjects.map!{ |subject| subject.new }
@subjects.each do |subject|
expect(subject).to respond_to :ensure_authentication_token
end
some_class.acts_as_token_authenticatable
expect(some_class).to be_include SimpleTokenAuthentication::TokenAuthenticatable
end

context 'when some authentication tokens are already in use' do

before(:each) do
TOKENS_IN_USE = ['ExampleTok3n', '4notherTokeN']
it 'behaves like a token authenticatable (2)', rspec_3_error: true, public: true do
stub_const('SimpleTokenAuthentication::TokenAuthenticatable', Module.new)

@subjects.each do |k|
k.class_eval do
some_child_class = @subjects.last
allow(some_child_class).to receive(:before_save)

def initialize(args={})
@authentication_token = args[:authentication_token]
@token_generator = DummyTokenGenerator.new(
tokens_to_be_generated: TOKENS_IN_USE + ['Dist1nCt-Tok3N'])
end
some_child_class.acts_as_token_authenticatable
expect(some_child_class).to be_include SimpleTokenAuthentication::TokenAuthenticatable
end
end

def authentication_token=(value)
@authentication_token = value
end
describe '.acts_as_token_authenticatable' do

def authentication_token
@authentication_token
end
context 'when the class supports the :before_save hook' do

# the 'ExampleTok3n' is already in use
def token_suitable?(token)
not TOKENS_IN_USE.include? token
end
it 'ensures its instances have an authentication token before being saved (1)', rspec_3_error: true, public: true do
some_class = @subjects.first

def token_generator
@token_generator
end
end
end
@subjects.map!{ |subject| subject.new }
expect(some_class).to receive(:before_save).with(:ensure_authentication_token)
some_class.acts_as_token_authenticatable
end

it 'ensures its authentication token is unique', public: true do
@subjects.each do |subject|
subject.ensure_authentication_token
it 'ensures its instances have an authentication token before being saved (2)', rspec_3_error: true, public: true do
some_child_class = @subjects.last

expect(subject.authentication_token).not_to eq 'ExampleTok3n'
expect(subject.authentication_token).not_to eq '4notherTokeN'
expect(subject.authentication_token).to eq 'Dist1nCt-Tok3N'
end
expect(some_child_class).to receive(:before_save).with(:ensure_authentication_token)
some_child_class.acts_as_token_authenticatable
end
end
end
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,5 @@
require 'spec_helper'

def ignore_cucumber_hack
skip_rails_test_environment_code
end

# Skip the code intended to be run in the Rails test environment
def skip_rails_test_environment_code
rails = double()
stub_const('Rails', rails)
allow(rails).to receive_message_chain(:env, :test?).and_return(false)
end

describe 'Any class which extends SimpleTokenAuthentication::ActsAsTokenAuthenticationHandler (or any if its children)' do

after(:each) do
Expand Down
17 changes: 13 additions & 4 deletions spec/lib/simple_token_authentication/entities_manager_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@
stub_const('SuperUser', super_user)
end

context 'when a model is provided for the first time' do
context 'when a model is provided for the first time', token_authenticatable_aliases_option: true do

it 'creates an Entity instance for the model', private: true do
expect(SimpleTokenAuthentication::Entity).to receive(:new).with(SuperUser)
expect(SimpleTokenAuthentication::Entity).to receive(:new).with(SuperUser, nil) # no alias
expect(subject.find_or_create_entity(SuperUser)).to eq 'an Entity instance'
end

Expand All @@ -35,13 +35,22 @@
stub_const('Admin', admin)
# ensure its Entity instance exists
subject.find_or_create_entity(Admin)
allow(SimpleTokenAuthentication::Entity).to receive(:new).and_return('some new Entity instance')
allow(SimpleTokenAuthentication::Entity).to receive(:new).with(SuperUser, nil).and_return('some new Entity instance')
allow(SimpleTokenAuthentication::Entity).to receive(:new).with(SuperUser, 'some_alias').and_return('some new Entity instance with an alias')
end

it 'creates an Entity instance for the model', private: true do
expect(SimpleTokenAuthentication::Entity).to receive(:new).with(SuperUser)
expect(SimpleTokenAuthentication::Entity).to receive(:new).with(SuperUser, nil)
expect(subject.find_or_create_entity(SuperUser)).to eq 'some new Entity instance'
end

context 'when a model alias was provided' do

it 'creates an Entity instance with that alias for the model', private: true do
expect(SimpleTokenAuthentication::Entity).to receive(:new).with(SuperUser, 'some_alias')
expect(subject.find_or_create_entity(SuperUser, 'some_alias')).to eq 'some new Entity instance with an alias'
end
end
end
end

Expand Down
6 changes: 6 additions & 0 deletions spec/lib/simple_token_authentication/entity_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@
expect(@subject.name_underscore).to be_instance_of String
expect(@subject.name_underscore).to eq @subject.name_underscore.underscore
end

it 'can be predefined', token_authenticatable_aliases_option: true do
@subject = SimpleTokenAuthentication::Entity.new(SuperUser, 'incognito_super_user')

expect(@subject.name_underscore).to eq 'incognito_super_user'
end
end

describe '#token_header_name', protected: true do
Expand Down
Loading