tokens 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.rdoc ADDED
@@ -0,0 +1,122 @@
1
+ = Tokens
2
+
3
+ == Usage
4
+
5
+ === Installation
6
+
7
+ You can use I18n-JS as plugin and gem. Choose what's best for you!
8
+
9
+ script/plugin install git://github.com/fnando/has_tokens.git
10
+
11
+ or
12
+
13
+ gem install tokens
14
+
15
+ === Setting up
16
+
17
+ Create a migration file with <tt>script/generate migration create_tokens</tt> or <tt>rails generate migration tokens</tt>.
18
+
19
+ class CreateTokens < ActiveRecord::Migration
20
+ def self.up
21
+ create_table :tokens do |t|
22
+ t.integer :tokenizable_id, :null => false
23
+ t.string :tokenizable_type, :name, :null => false
24
+ t.string :token, :limit => 40, :null => false
25
+ t.text :data, :null => true
26
+ t.datetime :expires_at, :null => true
27
+ t.datetime :created_at
28
+ end
29
+
30
+ add_index :tokens, :tokenizable_type
31
+ add_index :tokens, :tokenizable_id
32
+ add_index :tokens, :token
33
+ add_index :tokens, :expires_at
34
+ end
35
+
36
+ def self.down
37
+ drop_table :tokens
38
+ end
39
+ end
40
+
41
+ Run migrations with <tt>rake db:migrate</tt>. Add the method call
42
+ <tt>has_tokens</tt> to your model and be happy!
43
+
44
+ class User < ActiveRecord::Base
45
+ has_tokens
46
+ end
47
+
48
+ # create a new token; remember that the object need to be saved before creating
49
+ # the token because it depends on the id
50
+ user = User.create(:login => "fnando")
51
+
52
+ # uses the default expires_at (2 days from now)
53
+ user.add_token(:activate)
54
+
55
+ # uses custom expires_at
56
+ user.add_token(:activate, :expires_at => 10.days.from_now)
57
+
58
+ # uses the default size (12 characters)
59
+ user.add_token(:activate)
60
+
61
+ # uses custom size (up to 32)
62
+ user.add_token(:activate, :size => 20)
63
+
64
+ # create token with arbitrary data
65
+ data = {:action => 'do something'}
66
+ user.add_token(:activate, :data => data.to_json)
67
+
68
+ # find token by name
69
+ user.find_token_by_name(:reset_account)
70
+
71
+ # find token by hash
72
+ user.find_token("ea2f14aeac40")
73
+
74
+ # check if a token has expired
75
+ user.tokens.first.expired?
76
+
77
+ # find user by token
78
+ User.find_by_token(:activate, "ea2f14aeac40")
79
+
80
+ # remove all expired tokens except those with NULL values
81
+ Token.delete_expired
82
+
83
+ # generate a token as string, without saving it
84
+ User.generate_token
85
+
86
+ # remove a token by its name
87
+ user.remove_token(:activate)
88
+
89
+ # find user by token
90
+ User.find_by_token(:activate, 'ea2f14aeac40')
91
+
92
+ # find user by valid token (same name, same hash, not expired)
93
+ User.find_by_valid_token(:activate, 'ea2f14aeac40')
94
+
95
+ # find a token using class scope
96
+ User.find_token(:activate, 'ea2f14aeac40')
97
+
98
+ # Token hash
99
+ token.to_s #=> ea2f14aeac40
100
+
101
+ == License
102
+
103
+ Copyright (c) 2008 Nando Vieira, released under the MIT license
104
+
105
+ Permission is hereby granted, free of charge, to any person obtaining
106
+ a copy of this software and associated documentation files (the
107
+ "Software"), to deal in the Software without restriction, including
108
+ without limitation the rights to use, copy, modify, merge, publish,
109
+ distribute, sublicense, and/or sell copies of the Software, and to
110
+ permit persons to whom the Software is furnished to do so, subject to
111
+ the following conditions:
112
+
113
+ The above copyright notice and this permission notice shall be
114
+ included in all copies or substantial portions of the Software.
115
+
116
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
117
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
118
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
119
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
120
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
121
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
122
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ require "lib/tokens/version"
2
+
3
+ begin
4
+ require 'jeweler'
5
+
6
+ JEWEL = Jeweler::Tasks.new do |gem|
7
+ gem.name = "tokens"
8
+ gem.email = "fnando.vieira@gmail.com"
9
+ gem.homepage = "http://github.com/fnando/has_tokens"
10
+ gem.authors = ["Nando Vieira"]
11
+ gem.version = SimplesIdeias::Tokens::Version::STRING
12
+ gem.summary = "Generate named tokens on your ActiveRecord models."
13
+ gem.files = FileList["README.rdoc", "init.rb", "{lib,spec,source}/**/*", "Rakefile"]
14
+ end
15
+
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError => e
18
+ puts "[JEWELER] You can't build a gem until you install jeweler with `gem install jeweler`"
19
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'tokens'
@@ -0,0 +1,7 @@
1
+ require "digest/sha1"
2
+
3
+ class String
4
+ def to_sha1
5
+ Digest::SHA1.hexdigest(self.dup)
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ class Token < ActiveRecord::Base
2
+ belongs_to :tokenizable, :polymorphic => true
3
+
4
+ def hash
5
+ token
6
+ end
7
+
8
+ def to_s
9
+ hash
10
+ end
11
+
12
+ def expired?
13
+ expires_at && expires_at < Time.now
14
+ end
15
+
16
+ def self.delete_expired
17
+ delete_all ["expires_at < ? AND expires_at IS NOT NULL", Time.now]
18
+ end
19
+ end
@@ -0,0 +1,10 @@
1
+ module SimplesIdeias
2
+ module Tokens
3
+ module Version
4
+ MAJOR = 0
5
+ MINOR = 1
6
+ PATCH = 0
7
+ STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
8
+ end
9
+ end
10
+ end
data/lib/tokens.rb ADDED
@@ -0,0 +1,115 @@
1
+ require "active_record"
2
+ require "digest/sha1"
3
+ require "tokens/string_ext"
4
+ require "tokens/token"
5
+
6
+ module SimplesIdeias
7
+ module Tokens #:nodoc:
8
+ def self.included(base)
9
+ base.extend ClassMethods
10
+ end
11
+
12
+ module ClassMethods
13
+ def has_tokens
14
+ write_inheritable_attribute(:has_tokens_options, {
15
+ :token_type => ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s
16
+ })
17
+
18
+ class_inheritable_reader :has_tokens_options
19
+
20
+ has_many :tokens, :as => :tokenizable, :dependent => :destroy
21
+ include SimplesIdeias::Tokens::InstanceMethods
22
+ end
23
+
24
+ def generate_token(seed, size)
25
+ validity = Proc.new { |token| Token.find(:first, :conditions => {:token => token}).nil? }
26
+
27
+ begin
28
+ seed = Digest::SHA1.hexdigest(seed)
29
+ token = Digest::SHA1.hexdigest(seed)[0, size]
30
+ end while !validity.call(token)
31
+
32
+ token
33
+ end
34
+
35
+ # Find a token
36
+ # User.find_token(:activation, 'abcdefg')
37
+ # User.find_token(:name => activation, :token => 'abcdefg')
38
+ # User.find_token(:name => activation, :token => 'abcdefg', :tokenizable_id => 1)
39
+ def find_token(*args)
40
+ unless (options = args.first).is_a?(Hash)
41
+ options = {:name => args.first, :token => args.last.to_s}
42
+ end
43
+
44
+ options[:name] = options[:name].to_s
45
+ options.merge!({:tokenizable_type => has_tokens_options[:token_type]})
46
+ Token.find(:first, :conditions => options)
47
+ end
48
+
49
+ # Find object by token
50
+ # User.find_by_token(:activation, 'abcdefg')
51
+ def find_by_token(name, token)
52
+ t = find_token(:name => name.to_s, :token => token)
53
+ return nil unless t
54
+ t.tokenizable
55
+ end
56
+
57
+ # Find object by valid token (same name, same hash, not expired)
58
+ # User.find_by_valid_token(:activation, 'abcdefg')
59
+ def find_by_valid_token(name, token)
60
+ t = find_token(:name => name.to_s, :token => token)
61
+ return nil unless t && !t.expired? && t.hash == token
62
+ t.tokenizable
63
+ end
64
+ end
65
+
66
+ module InstanceMethods
67
+ # Object has a valid token (same name, same hash, not expired)
68
+ # @user.valid_token?(:activation, 'abcdefg')
69
+ def valid_token?(name, token)
70
+ t = find_token_by_name(name)
71
+ !!(t && !t.expired? && t.hash == token)
72
+ end
73
+
74
+ def remove_token(name)
75
+ Token.delete_all([
76
+ "tokenizable_id = ? AND tokenizable_type = ? AND name = ?",
77
+ self.id,
78
+ self.class.has_tokens_options[:token_type],
79
+ name.to_s
80
+ ])
81
+ end
82
+
83
+ def add_token(name, options={})
84
+ options = {
85
+ :expires_at => 2.days.from_now,
86
+ :size => 12,
87
+ :data => nil
88
+ }.merge(options)
89
+
90
+ remove_token(name)
91
+
92
+ seed = "--#{self.id}--#{self.object_id}--#{Time.now}--"
93
+
94
+ self.tokens.create(
95
+ :name => name.to_s,
96
+ :token => self.class.generate_token(seed, options[:size]),
97
+ :expires_at => options[:expires_at],
98
+ :data => options[:data]
99
+ )
100
+ end
101
+
102
+ def find_token_by_name(name)
103
+ self.tokens.find_by_name(name.to_s)
104
+ end
105
+
106
+ # Find a token
107
+ # @user.find_token(:activation, 'abcdefg')
108
+ def find_token(name, token)
109
+ self.class.find_token(:tokenizable_id => self.id, :name => name.to_s, :token => token)
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ ActiveRecord::Base.send(:include, SimplesIdeias::Tokens)
data/spec/schema.rb ADDED
@@ -0,0 +1,18 @@
1
+ ActiveRecord::Schema.define(:version => 0) do
2
+ create_table :users do |t|
3
+ t.string :name
4
+ end
5
+
6
+ create_table :posts do |t|
7
+ t.string :title
8
+ end
9
+
10
+ create_table :tokens do |t|
11
+ t.integer :tokenizable_id, :null => false
12
+ t.string :tokenizable_type, :name, :null => false
13
+ t.string :token, :limit => 40, :null => false
14
+ t.text :data, :null => true
15
+ t.datetime :expires_at, :null => true
16
+ t.datetime :created_at
17
+ end
18
+ end
@@ -0,0 +1,23 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__) + "/../lib"
2
+
3
+ require "spec"
4
+ require "ruby-debug"
5
+ require "active_record"
6
+ require "tokens"
7
+
8
+ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ":memory:")
9
+
10
+ load('schema.rb')
11
+
12
+ class Object
13
+ def self.unset_class(*args)
14
+ class_eval do
15
+ args.each do |klass|
16
+ eval(klass) rescue nil
17
+ remove_const(klass) if const_defined?(klass)
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ alias :doing :lambda
@@ -0,0 +1,164 @@
1
+ require "spec_helper"
2
+
3
+ class User < ActiveRecord::Base
4
+ has_tokens
5
+ end
6
+
7
+ class Post < ActiveRecord::Base
8
+ has_tokens
9
+ end
10
+
11
+ describe "has_tokens" do
12
+ before(:each) do
13
+ User.delete_all
14
+ Post.delete_all
15
+
16
+ @user = User.create(:name => "Homer")
17
+ @another_user = User.create(:name => "Bart")
18
+ @post = Post.create(:title => "How to make donuts")
19
+ @expire = 3.days.from_now
20
+ end
21
+
22
+ describe "- token" do
23
+ it "should be created" do
24
+ doing { @user.add_token(:uid) }.should change(Token, :count)
25
+ end
26
+
27
+ it "should be created for different users" do
28
+ @user.add_token(:uid).should be_valid
29
+ @another_user.add_token(:uid).should be_valid
30
+ end
31
+
32
+ it "should be created with expiration date" do
33
+ @user.add_token(:uid, :expires_at => @expire).expires_at.should == @expire
34
+ end
35
+
36
+ it "should be created with additional data" do
37
+ @user.add_token(:uid, :data => 'some value').data.should == 'some value'
38
+ end
39
+
40
+ it "should be created with custom size" do
41
+ @user.add_token(:uid, :size => 6).hash.size.should == 6
42
+ end
43
+
44
+ it "should find token by its name" do
45
+ token = @user.add_token(:uid)
46
+ @user.find_token_by_name(:uid).should == token
47
+ end
48
+
49
+ it "should be nil when no token is found" do
50
+ @user.find_token(:uid, 'abcdef').should be_nil
51
+ @user.find_token_by_name(:uid).should be_nil
52
+ end
53
+
54
+ it "should be a valid token" do
55
+ token = @user.add_token(:uid)
56
+ @user.valid_token?(:uid, token.hash).should be_true
57
+ end
58
+
59
+ it "should not be a valid token" do
60
+ @user.valid_token?(:uid, 'invalid').should be_false
61
+ end
62
+
63
+ it "should find token by its name and hash" do
64
+ token = @user.add_token(:uid)
65
+ @user.find_token(:uid, token.hash).should == token
66
+ end
67
+
68
+ it "should not be expired when have no expiration date" do
69
+ @user.add_token(:uid).should_not be_expired
70
+ end
71
+
72
+ it "should not be expired when have a future expiration date" do
73
+ @user.add_token(:uid, :expires_at => 3.days.from_now).should_not be_expired
74
+ end
75
+
76
+ it "should be expired" do
77
+ @user.add_token(:uid, :expires_at => 3.days.ago).should be_expired
78
+ end
79
+
80
+ it "should remove token" do
81
+ @user.add_token(:uid)
82
+ @user.remove_token(:uid).should > 0
83
+ end
84
+
85
+ it "should not remove other users tokens" do
86
+ @user.add_token(:uid)
87
+ @another_user.add_token(:uid)
88
+
89
+ @user.remove_token(:uid)
90
+
91
+ @user.find_token_by_name(:uid).should be_nil
92
+ @another_user.find_token_by_name(:uid).should be_an_instance_of(Token)
93
+ end
94
+
95
+ it "should not be duplicated" do
96
+ @user.add_token(:uid)
97
+ @user.add_token(:uid)
98
+
99
+ @user.tokens.find_all_by_name('uid').size.should == 1
100
+ end
101
+ end
102
+
103
+ it "should have tokens association" do
104
+ doing { @user.tokens }.should_not raise_error
105
+ end
106
+
107
+ it "should remove all expired tokens" do
108
+ doing {
109
+ %w(uid activation_code reset_password_code).each do |name|
110
+ @user.add_token(name, :expires_at => 3.days.ago)
111
+ end
112
+ }.should change(Token, :count).by(3)
113
+
114
+ Token.delete_expired.should == 3
115
+ end
116
+
117
+ it "should generate token without saving it" do
118
+ doing {
119
+ User.generate_token(Time.now.to_s, 32)
120
+ }.should_not change(Token, :count)
121
+ end
122
+
123
+ it "should generate token with custom size" do
124
+ User.generate_token(Time.now.to_s, 8).size.should == 8
125
+ end
126
+
127
+ it "should alias token method" do
128
+ token = @user.add_token(:uid)
129
+ token.hash.should == token.token
130
+ end
131
+
132
+ it "should find user by token" do
133
+ token = @user.add_token(:uid)
134
+ User.find_by_token(:uid, token.hash).should == @user
135
+ end
136
+
137
+ it "should return user by its valid token without expiration time" do
138
+ token = @user.add_token(:uid)
139
+ User.find_by_valid_token(:uid, token.hash).should == @user
140
+ end
141
+
142
+ it "should return user by its valid token with expiration time" do
143
+ token = @user.add_token(:uid, :expires_at => @expire)
144
+ User.find_by_valid_token(:uid, token.hash).should == @user
145
+ end
146
+
147
+ it "should find token using class method with one argument (hash only)" do
148
+ token = @user.add_token(:uid)
149
+ User.find_token(:name => :uid, :token => token.hash).should == token
150
+ end
151
+
152
+ it "should not conflict with other models" do
153
+ user_token = @user.add_token(:uid)
154
+ post_token = @post.add_token(:uid)
155
+
156
+ User.find_token(post_token.to_s).should == nil
157
+ User.find_token(:name => :uid)
158
+ end
159
+
160
+ it "to_s should return hash" do
161
+ token = @user.add_token(:uid)
162
+ token.to_s.should == token.hash
163
+ end
164
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tokens
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Nando Vieira
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-07-09 00:00:00 -03:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description:
22
+ email: fnando.vieira@gmail.com
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files:
28
+ - README.rdoc
29
+ files:
30
+ - README.rdoc
31
+ - Rakefile
32
+ - init.rb
33
+ - lib/tokens.rb
34
+ - lib/tokens/string_ext.rb
35
+ - lib/tokens/token.rb
36
+ - lib/tokens/version.rb
37
+ - spec/schema.rb
38
+ - spec/spec_helper.rb
39
+ - spec/tokens_spec.rb
40
+ has_rdoc: true
41
+ homepage: http://github.com/fnando/has_tokens
42
+ licenses: []
43
+
44
+ post_install_message:
45
+ rdoc_options:
46
+ - --charset=UTF-8
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ segments:
54
+ - 0
55
+ version: "0"
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ segments:
61
+ - 0
62
+ version: "0"
63
+ requirements: []
64
+
65
+ rubyforge_project:
66
+ rubygems_version: 1.3.6
67
+ signing_key:
68
+ specification_version: 3
69
+ summary: Generate named tokens on your ActiveRecord models.
70
+ test_files:
71
+ - spec/schema.rb
72
+ - spec/spec_helper.rb
73
+ - spec/tokens_spec.rb