tokens 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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