openid_active_record_store 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,63 @@
1
+ = openid_active_record_store
2
+
3
+ http://rubygems.org/gems/openid_active_record_store
4
+ {Project}[http://rubygems.org/gems/openid_active_record_store]
5
+ {Wiki}[http://wiki.github.com/raggi/openid_active_record_store/]
6
+ {Source Code}[http://github.com/raggi/openid_active_record_store/]
7
+ {Issues}[http://github.com/raggi/openid_active_record_store/issues]
8
+
9
+ == DESCRIPTION:
10
+
11
+ A rails engine for OpenID/Omniauth that writes to ActiveRecord for the OpenID data. Forked from an old project by Kazuyoshi Tlacaelel.
12
+
13
+ == FEATURES/PROBLEMS:
14
+
15
+ * Simple
16
+ * Lowish test coverage
17
+ * Binary column may not work well with some adapters
18
+
19
+ == SYNOPSIS:
20
+
21
+ rake openid_active_record_store:install:migrations
22
+ rake db:migrate
23
+
24
+ # Omniauth example:
25
+ Rails.application.config.middleware.use(
26
+ OmniAuth::Strategies::GoogleApps,
27
+ OpenID::Store::ActiveRecord.new,
28
+ { :name => 'example', :domain => 'example.org' }
29
+ )
30
+
31
+ == REQUIREMENTS:
32
+
33
+ * Rails 3+
34
+ * OpenID
35
+
36
+ == INSTALL:
37
+
38
+ * gem install openid_active_record_store
39
+
40
+ == LICENSE:
41
+
42
+ (The MIT License)
43
+
44
+ Copyright (c) 2011 Kazuyoshi Tlacaelel, James Tucker, Wildfire Interactive Inc
45
+
46
+ Permission is hereby granted, free of charge, to any person obtaining
47
+ a copy of this software and associated documentation files (the
48
+ 'Software'), to deal in the Software without restriction, including
49
+ without limitation the rights to use, copy, modify, merge, publish,
50
+ distribute, sublicense, and/or sell copies of the Software, and to
51
+ permit persons to whom the Software is furnished to do so, subject to
52
+ the following conditions:
53
+
54
+ The above copyright notice and this permission notice shall be
55
+ included in all copies or substantial portions of the Software.
56
+
57
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
58
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
59
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
60
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
61
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
62
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
63
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env rake
2
+ require 'rake/testtask'
3
+ require 'rdoc/task'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the openid_store_active_record plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.ruby_opts << '-rubygems'
11
+ t.libs += %w[test]
12
+ t.pattern = 'test/**/*_test.rb'
13
+ t.verbose = true
14
+ t.warning = true
15
+ end
16
+
17
+ desc 'Generate documentation for the openid_store_active_record plugin.'
18
+ RDoc::Task.new(:rdoc) do |rdoc|
19
+ rdoc.rdoc_dir = 'rdoc'
20
+ rdoc.title = 'OpenidStoreActiveRecord'
21
+ rdoc.options << '--line-numbers' << '--inline-source'
22
+ rdoc.rdoc_files.include('README')
23
+ rdoc.rdoc_files.include('lib/**/*.rb')
24
+ end
25
+
26
+ desc "build gem"
27
+ task :gem do
28
+ sh "gem build openid_active_record_store.gemspec"
29
+ end
@@ -0,0 +1,5 @@
1
+ require 'active_record'
2
+
3
+ class OpenidAbstract < ActiveRecord::Base
4
+ self.abstract_class = true
5
+ end
@@ -0,0 +1,2 @@
1
+ class OpenidAssociation < OpenidAbstract
2
+ end
@@ -0,0 +1,8 @@
1
+ class OpenidNonce < OpenidAbstract
2
+
3
+ # attempt to scan timestamps (integers) first for fast access.
4
+ def self.exists_by_target?(timestamp, salt, target)
5
+ where(:timestamp => timestamp, :target => target).size > 0
6
+ end
7
+
8
+ end
@@ -0,0 +1,22 @@
1
+ class CreateOpenidAssociations < ActiveRecord::Migration
2
+
3
+ def self.up
4
+ create_table :openid_associations do |t|
5
+ t.datetime :issued_at
6
+ t.integer :lifetime
7
+ t.string :assoc_type
8
+ t.text :handle
9
+ t.binary :secret
10
+
11
+ t.string :target, :size => 32
12
+ t.text :server_url
13
+
14
+ t.timestamps
15
+ end
16
+ end
17
+
18
+ def self.down
19
+ drop_table :openid_associations
20
+ end
21
+
22
+ end
@@ -0,0 +1,17 @@
1
+ class CreateOpenidNonces < ActiveRecord::Migration
2
+
3
+ def self.up
4
+ create_table :openid_nonces do |t|
5
+ t.integer :timestamp
6
+ t.string :salt
7
+ t.string :target, :size => 32
8
+ t.text :server_url
9
+ t.timestamps
10
+ end
11
+ end
12
+
13
+ def self.down
14
+ drop_table :openid_nonces
15
+ end
16
+
17
+ end
@@ -0,0 +1,121 @@
1
+ require 'openid/util'
2
+ require 'openid/store/interface'
3
+ require 'openid/association'
4
+ require 'openssl'
5
+
6
+ module OpenID
7
+ module Store
8
+ class ActiveRecord < Interface
9
+
10
+ # Put a Association object into storage.
11
+ # When implementing a store, don't assume that there are any limitations
12
+ # on the character set of the server_url. In particular, expect to see
13
+ # unescaped non-url-safe characters in the server_url field.
14
+ def store_association(server_url, association)
15
+ OpenidAssociation.create!(
16
+ :server_url => server_url,
17
+ :target => targetize(server_url),
18
+ :handle => association.handle,
19
+ :secret => association.secret,
20
+ :issued_at => association.issued,
21
+ :lifetime => association.lifetime,
22
+ :assoc_type => association.assoc_type
23
+ )
24
+ true
25
+ end
26
+
27
+ # Returns a Association object from storage that matches
28
+ # the server_url. Returns nil if no such association is found or if
29
+ # the one matching association is expired. (Is allowed to GC expired
30
+ # associations when found.)
31
+ def get_association(server_url, handle=nil)
32
+ oas = OpenidAssociation.find_all_by_target targetize(server_url)
33
+ return nil if oas.empty?
34
+ unless handle.nil?
35
+ return nil unless oas.collect(&:handle).include? handle
36
+ return build_association(oas.find { |oa| oa.handle == handle })
37
+ end
38
+ oas.sort_by(&:issued_at).collect { |oa| build_association(oa) }.last
39
+ end
40
+
41
+ # If there is a matching association, remove it from the store and
42
+ # return true, otherwise return false.
43
+ def remove_association(server_url, handle)
44
+ oas = OpenidAssociation.find_all_by_target targetize(server_url)
45
+ return false unless oas.collect(&:handle).include? handle
46
+ oas.find_all { |oa| oa.handle == handle }.each(&:delete).size > 0
47
+ end
48
+
49
+ # Return true if the nonce has not been used before, and store it
50
+ # for a while to make sure someone doesn't try to use the same value
51
+ # again. Return false if the nonce has already been used or if the
52
+ # timestamp is not current.
53
+ # You can use OpenID::Store::Nonce::SKEW for your timestamp window.
54
+ # server_url: URL of the server from which the nonce originated
55
+ # timestamp: time the nonce was created in seconds since unix epoch
56
+ # salt: A random string that makes two nonces issued by a server in
57
+ # the same second unique
58
+ def use_nonce(server_url, timestamp, salt)
59
+ return false if (timestamp - Time.now.to_i).abs > Nonce.skew
60
+ params = [timestamp, salt, targetize(server_url)]
61
+ return false if OpenidNonce.exists_by_target?(*params)
62
+ return create_nonce(server_url, timestamp, salt)
63
+ end
64
+
65
+ # Remove expired nonces and associations from the store
66
+ # Not called during normal library operation, this method is for store
67
+ # admins to keep their storage from filling up with expired data
68
+ def cleanup
69
+ cleanup_nonces
70
+ cleanup_associations
71
+ end
72
+
73
+ # Remove expired associations from the store
74
+ # Not called during normal library operation, this method is for store
75
+ # admins to keep their storage from filling up with expired data
76
+ def cleanup_associations
77
+ oas = OpenidAssociation.all.collect do |oa|
78
+ oa.id if build_association(oa).expires_in == 0
79
+ end
80
+ OpenidAssociation.delete oas.compact
81
+ end
82
+
83
+ # Remove expired nonces from the store
84
+ # Discards any nonce that is old enough that it wouldn't pass use_nonce
85
+ # Not called during normal library operation, this method is for store
86
+ # admins to keep their storage from filling up with expired data
87
+ def cleanup_nonces
88
+ now = Time.now.to_i
89
+ nonces = OpenidNonce.all
90
+ ids = nonces.collect { |n| n.id if (n.timestamp - now).abs > Nonce.skew }
91
+ OpenidNonce.delete ids.compact
92
+ end
93
+
94
+ private
95
+
96
+ def targetize(server_url)
97
+ OpenSSL::Digest::MD5.hexdigest(server_url)
98
+ end
99
+
100
+ def build_association(open_id_association)
101
+ OpenID::Association.new(
102
+ open_id_association.handle,
103
+ open_id_association.secret,
104
+ open_id_association.issued_at,
105
+ open_id_association.lifetime,
106
+ open_id_association.assoc_type
107
+ )
108
+ end
109
+
110
+ def create_nonce(server_url, timestamp, salt)
111
+ OpenidNonce.create!(
112
+ :target => targetize(server_url),
113
+ :server_url => server_url,
114
+ :timestamp => timestamp
115
+ )
116
+ true
117
+ end
118
+
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,33 @@
1
+ require 'rails/engine'
2
+ require 'openid/store/active_record'
3
+
4
+ module OpenidActiveRecordStore
5
+ class Engine < Rails::Engine
6
+ config.eager_load_paths << File.expand_path("../../app/models", __FILE__)
7
+ end
8
+ class Railtie < Rails::Railtie
9
+ rake_tasks do
10
+ namespace :openid_active_record_store do
11
+ namespace :install do
12
+
13
+ files = File.expand_path("../../db/migrate/*.rb", __FILE__)
14
+ sources = FileList[files]
15
+ targets = sources.map do |source|
16
+ ts = Time.now.to_f.to_s.sub('.', '')
17
+ "db/migrate/#{ts}_#{File.basename(source)}"
18
+ end
19
+
20
+ desc "install migrations"
21
+ task :migrations => targets
22
+
23
+ sources.zip(targets).each do |source, target|
24
+ file target => source do
25
+ cp source, target
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+ end unless Gem::Version.new(Rails.version) >= Gem::Version.new('3.1.0')
32
+ end
33
+ end
@@ -0,0 +1,237 @@
1
+ require 'test_helper'
2
+ require 'openid/store/nonce'
3
+
4
+ class OpenidStoreActiveRecordTest < ActiveSupport::TestCase
5
+
6
+ # ============================================================================
7
+ # TESTING SCENARIO
8
+ # ============================================================================
9
+
10
+ setup :prepare_scenario, :clean_tables
11
+ teardown :destroy_scenario
12
+
13
+ def prepare_scenario
14
+ @store = OpenID::Store::ActiveRecord.new
15
+ @@allowed_nonce = '0123456789abcdefghijklmnopqrst' +
16
+ 'uvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
17
+ @@allowed_handle = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQ' +
18
+ 'RSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
19
+ end
20
+
21
+ def clean_tables
22
+ # super duper make sure talbes are empty
23
+ OpenidAssociation.all.each { |ao| ao.destroy }
24
+ OpenidNonce.all.each { |nonce| nonce.destroy }
25
+ end
26
+
27
+ def destroy_scenario
28
+ @@allowed_handle = @@allowed_nonce = @store = nil
29
+ end
30
+
31
+ # ============================================================================
32
+ # TESTS BELOW BROUGHT FROM THE ORIGINAL 'ruby-openid' (store) test suite
33
+ # these methods test the association interactivity with a store object
34
+ # ============================================================================
35
+
36
+ def _gen_secret(n, chars=nil)
37
+ OpenID::CryptUtil.random_string(n, chars)
38
+ end
39
+
40
+ def _gen_handle(n)
41
+ OpenID::CryptUtil.random_string(n, @@allowed_handle)
42
+ end
43
+
44
+ def _gen_assoc(issued_at, lifetime=600)
45
+ secret = _gen_secret(20)
46
+ handle = _gen_handle(128)
47
+ OpenID::Association.new(handle, secret, Time.now + issued_at, lifetime,
48
+ 'HMAC-SHA1')
49
+ end
50
+
51
+ def _check_retrieve(url, handle=nil, expected=nil)
52
+ ret_assoc = @store.get_association(url, handle)
53
+
54
+ if expected.nil?
55
+ assert_nil(ret_assoc)
56
+ else
57
+ %w[assoc_type handle issued lifetime secret].each do |prop|
58
+ ex, actual = expected.send(prop), ret_assoc.send(prop)
59
+ if ex.kind_of?(Time)
60
+ ex, actual = ex.to_i, actual.to_i
61
+ end
62
+ assert_equal ex, actual, "#{prop} doesn't match"
63
+ end
64
+ end
65
+ end
66
+
67
+ def _check_remove(url, handle, expected)
68
+ present = @store.remove_association(url, handle)
69
+ assert_equal(expected, present)
70
+ end
71
+
72
+ def test_store
73
+ server_url = "http://www.myopenid.com/openid"
74
+ assoc = _gen_assoc(issued_at=0)
75
+
76
+ # Make sure that a missing association returns no result
77
+ _check_retrieve(server_url)
78
+
79
+ # Check that after storage, getting returns the same result
80
+ @store.store_association(server_url, assoc)
81
+ _check_retrieve(server_url, nil, assoc)
82
+
83
+ # more than once
84
+ _check_retrieve(server_url, nil, assoc)
85
+
86
+ # Storing more than once has no ill effect
87
+ @store.store_association(server_url, assoc)
88
+ _check_retrieve(server_url, nil, assoc)
89
+
90
+ # Removing an association that does not exist returns not present
91
+ _check_remove(server_url, assoc.handle + 'x', false)
92
+
93
+ # Removing an association that does not exist returns not present
94
+ _check_remove(server_url + 'x', assoc.handle, false)
95
+
96
+ # Removing an association that is present returns present
97
+ _check_remove(server_url, assoc.handle, true)
98
+
99
+ # but not present on subsequent calls
100
+ _check_remove(server_url, assoc.handle, false)
101
+
102
+ # Put assoc back in the store
103
+ @store.store_association(server_url, assoc)
104
+
105
+ # More recent and expires after assoc
106
+ assoc2 = _gen_assoc(issued_at=1)
107
+ @store.store_association(server_url, assoc2)
108
+
109
+ # After storing an association with a different handle, but the
110
+ # same server_url, the handle with the later expiration is returned.
111
+ _check_retrieve(server_url, nil, assoc2)
112
+
113
+ # We can still retrieve the older association
114
+ _check_retrieve(server_url, assoc.handle, assoc)
115
+
116
+ # Plus we can retrieve the association with the later expiration
117
+ # explicitly
118
+ _check_retrieve(server_url, assoc2.handle, assoc2)
119
+
120
+ # More recent, and expires earlier than assoc2 or assoc. Make sure
121
+ # that we're picking the one with the latest issued date and not
122
+ # taking into account the expiration.
123
+ assoc3 = _gen_assoc(issued_at=2, lifetime=100)
124
+ @store.store_association(server_url, assoc3)
125
+
126
+ _check_retrieve(server_url, nil, assoc3)
127
+ _check_retrieve(server_url, assoc.handle, assoc)
128
+ _check_retrieve(server_url, assoc2.handle, assoc2)
129
+ _check_retrieve(server_url, assoc3.handle, assoc3)
130
+
131
+ _check_remove(server_url, assoc2.handle, true)
132
+
133
+ _check_retrieve(server_url, nil, assoc3)
134
+ _check_retrieve(server_url, assoc.handle, assoc)
135
+ _check_retrieve(server_url, assoc2.handle, nil)
136
+ _check_retrieve(server_url, assoc3.handle, assoc3)
137
+
138
+ _check_remove(server_url, assoc2.handle, false)
139
+ _check_remove(server_url, assoc3.handle, true)
140
+
141
+ _check_retrieve(server_url, nil, assoc)
142
+ _check_retrieve(server_url, assoc.handle, assoc)
143
+ _check_retrieve(server_url, assoc2.handle, nil)
144
+ _check_retrieve(server_url, assoc3.handle, nil)
145
+
146
+ _check_remove(server_url, assoc2.handle, false)
147
+ _check_remove(server_url, assoc.handle, true)
148
+ _check_remove(server_url, assoc3.handle, false)
149
+
150
+ _check_retrieve(server_url, nil, nil)
151
+ _check_retrieve(server_url, assoc.handle, nil)
152
+ _check_retrieve(server_url, assoc2.handle, nil)
153
+ _check_retrieve(server_url, assoc3.handle, nil)
154
+
155
+ _check_remove(server_url, assoc2.handle, false)
156
+ _check_remove(server_url, assoc.handle, false)
157
+ _check_remove(server_url, assoc3.handle, false)
158
+
159
+ assocValid1 = _gen_assoc(-3600, 7200)
160
+ assocValid2 = _gen_assoc(-5)
161
+ assocExpired1 = _gen_assoc(-7200, 3600)
162
+ assocExpired2 = _gen_assoc(-7200, 3600)
163
+
164
+ @store.cleanup_associations
165
+ @store.store_association(server_url + '1', assocValid1)
166
+ @store.store_association(server_url + '1', assocExpired1)
167
+ @store.store_association(server_url + '2', assocExpired2)
168
+ @store.store_association(server_url + '3', assocValid2)
169
+
170
+ cleaned = @store.cleanup_associations()
171
+ assert_equal(2, cleaned, "cleaned up associations")
172
+ end
173
+
174
+ # ============================================================================
175
+ # am including open id here because the nonce module is not being mocked
176
+ # ============================================================================
177
+ include OpenID
178
+
179
+ # ============================================================================
180
+ # TESTS BELOW BROUGHT FROM THE ORIGINAL 'ruby-openid' (store) test suite
181
+ # these methods test the nonce interactivity with a store object
182
+ # ============================================================================
183
+
184
+ def _check_use_nonce(nonce, expected, server_url, msg='')
185
+ stamp, salt = Nonce::split_nonce(nonce)
186
+ actual = @store.use_nonce(server_url, stamp, salt)
187
+ assert_equal(expected, actual, msg)
188
+ end
189
+
190
+ def test_nonce
191
+ server_url = "http://www.myopenid.com/openid"
192
+ [server_url, ''].each{|url|
193
+ nonce1 = Nonce::mk_nonce
194
+
195
+ _check_use_nonce(nonce1, true, url, "#{url}: nonce allowed by default")
196
+ _check_use_nonce(nonce1, false, url, "#{url}: nonce not allowed twice")
197
+ _check_use_nonce(nonce1, false, url, "#{url}: nonce not allowed third time")
198
+
199
+ # old nonces shouldn't pass
200
+ old_nonce = Nonce::mk_nonce(3600)
201
+ _check_use_nonce(old_nonce, false, url, "Old nonce #{old_nonce.inspect} passed")
202
+
203
+ }
204
+
205
+ now = Time.now.to_i
206
+ old_nonce1 = Nonce::mk_nonce(now - 20000)
207
+ old_nonce2 = Nonce::mk_nonce(now - 10000)
208
+ recent_nonce = Nonce::mk_nonce(now - 600)
209
+
210
+ orig_skew = Nonce.skew
211
+ Nonce.skew = 0
212
+ count = @store.cleanup_nonces
213
+ Nonce.skew = 1000000
214
+ ts, salt = Nonce::split_nonce(old_nonce1)
215
+ assert(@store.use_nonce(server_url, ts, salt), "oldnonce1")
216
+ ts, salt = Nonce::split_nonce(old_nonce2)
217
+ assert(@store.use_nonce(server_url, ts, salt), "oldnonce2")
218
+ ts, salt = Nonce::split_nonce(recent_nonce)
219
+ assert(@store.use_nonce(server_url, ts, salt), "recent_nonce")
220
+
221
+
222
+ Nonce.skew = 1000
223
+ cleaned = @store.cleanup_nonces
224
+ assert_equal(2, cleaned, "Cleaned #{cleaned} nonces")
225
+
226
+ Nonce.skew = 100000
227
+ ts, salt = Nonce::split_nonce(old_nonce1)
228
+ assert(@store.use_nonce(server_url, ts, salt), "oldnonce1 after cleanup")
229
+ ts, salt = Nonce::split_nonce(old_nonce2)
230
+ assert(@store.use_nonce(server_url, ts, salt), "oldnonce2 after cleanup")
231
+ ts, salt = Nonce::split_nonce(recent_nonce)
232
+ assert(!@store.use_nonce(server_url, ts, salt), "recent_nonce after cleanup")
233
+
234
+ Nonce.skew = orig_skew
235
+ end
236
+
237
+ end
@@ -0,0 +1,24 @@
1
+ require 'test/unit'
2
+ require 'openid_active_record_store'
3
+ require 'active_record'
4
+
5
+ db = {
6
+ :adapter => :mysql2,
7
+ :database => 'openid_active_record_store'
8
+ }
9
+
10
+ # XXX yes, there are better ways. patches please!
11
+
12
+ system "echo 'drop database #{db[:database]};' | mysql5 -uroot" rescue nil
13
+ system "echo 'create database #{db[:database]};' | mysql5 -uroot"
14
+
15
+ ActiveRecord::Base.establish_connection db
16
+
17
+ Dir['app/models/*.rb'].each do |model|
18
+ require File.expand_path(model)
19
+ end
20
+
21
+ Dir['db/migrations/*.rb'].each do |migration|
22
+ require migration
23
+ Object.const_get(File.basename(migration, '.rb').camelize).up
24
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: openid_active_record_store
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - James Tucker
14
+ - Kazuyoshi Tlacaelel
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2011-05-24 00:00:00 -07:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: rails
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ hash: 5
31
+ segments:
32
+ - 3
33
+ version: "3"
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ description: An ActiveRecord store for OpenID, forked from its original author for rails 3 support
37
+ email: info@wildfireapp.com
38
+ executables: []
39
+
40
+ extensions: []
41
+
42
+ extra_rdoc_files: []
43
+
44
+ files:
45
+ - README.rdoc
46
+ - Rakefile
47
+ - app/models/openid_abstract.rb
48
+ - app/models/openid_association.rb
49
+ - app/models/openid_nonce.rb
50
+ - db/migrate/create_openid_associations.rb
51
+ - db/migrate/create_openid_nonces.rb
52
+ - lib/openid/store/active_record.rb
53
+ - lib/openid_active_record_store.rb
54
+ - test/openid_store_active_record_test.rb
55
+ - test/test_helper.rb
56
+ has_rdoc: true
57
+ homepage: http://github.com/wildfireapp/openid_active_record_store
58
+ licenses:
59
+ - MIT
60
+ post_install_message:
61
+ rdoc_options: []
62
+
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ hash: 3
71
+ segments:
72
+ - 0
73
+ version: "0"
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ hash: 3
80
+ segments:
81
+ - 0
82
+ version: "0"
83
+ requirements: []
84
+
85
+ rubyforge_project:
86
+ rubygems_version: 1.5.2
87
+ signing_key:
88
+ specification_version: 3
89
+ summary: An ActiveRecord store for OpenID
90
+ test_files: []
91
+