openid-store-redis 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.3
5
+ - 2.0.0
6
+ - jruby-19mode
7
+ - rbx-19mode
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in openid-store-redis.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Rally Software Development Corp
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # OpenID::Store::Redis [![Build Status](https://travis-ci.org/RallySoftware/openid-store-redis.png)](https://travis-ci.org/RallySoftware/openid-store-redis)
2
+
3
+ A Redis storage backend for ruby-openid
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'openid-store-redis'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install openid-store-redis
18
+
19
+ ## Usage
20
+
21
+ Set up OpenID consumer (or provider) using Redis store
22
+
23
+ ```ruby
24
+ redis = Redis.new
25
+ store = OpenID::Store::Redis.new(redis)
26
+ @consumer = OpenID::Consumer.new(session, store)
27
+ ```
28
+
29
+ If you're using Omniauth
30
+
31
+ ```ruby
32
+ use OmniAuth::Builder do
33
+ provider :open_id, :store => OpenID::Store::Redis.new(Redis.new)
34
+ end
35
+ ```
36
+
37
+ Redis store defaults to ```Redis.current``` when Redis client is not given as
38
+ argument to store.
39
+
40
+ ## Contributing
41
+
42
+ 1. Fork it
43
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
44
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
45
+ 4. Push to the branch (`git push origin my-new-feature`)
46
+ 5. Create new Pull Request
47
+
48
+ ## Copyright
49
+
50
+ © Rally Software Development Corp. Released under MIT license, see
51
+ [LICENSE](https://github.com/RallySoftware/openid-redis-store/blob/master/LICENSE.txt)
52
+ for details.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rspec/core'
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec) do |spec|
6
+ spec.pattern = FileList['spec/**/*_spec.rb']
7
+ end
8
+
9
+ task :default => :spec
10
+
@@ -0,0 +1,5 @@
1
+ module OpenID
2
+ module Store
3
+ REDIS_VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,90 @@
1
+ require "openid/store/redis/version"
2
+ require "openid/store/interface"
3
+ require "redis"
4
+
5
+ module OpenID
6
+ module Store
7
+ class Redis < Interface
8
+ attr_reader :prefix
9
+ def initialize(client = ::Redis.current, prefix = "openid-store")
10
+ @redis = client
11
+ @prefix = prefix
12
+ end
13
+
14
+ # Store an Association in Redis
15
+ def store_association(server_url, association)
16
+ serialized = serialize(association)
17
+ [nil, association.handle].each do |handle|
18
+ key = assoc_key(server_url, handle)
19
+ @redis.setex(key, association.lifetime, serialized)
20
+ end
21
+ end
22
+
23
+ # Fetch and deserialize an Association object from Redis
24
+ def get_association(server_url, handle=nil)
25
+ if serialized = @redis.get(assoc_key(server_url, handle))
26
+ deserialize(serialized)
27
+ else
28
+ nil
29
+ end
30
+ end
31
+
32
+ # Remove matching association from Redis
33
+ #
34
+ # return true when data is removed, otherwise false
35
+ def remove_association(url, handle)
36
+ deleted = @redis.del(assoc_key(url, handle))
37
+ assoc = get_association(url)
38
+ if assoc && assoc.handle == handle
39
+ deleted + @redis.del(assoc_key(url))
40
+ else
41
+ deleted
42
+ end > 0
43
+ end
44
+
45
+
46
+ # Use nonce and store that it has been used in Redis temporarily
47
+ #
48
+ # Returns true if nonce has not been used before and is still usable,
49
+ def use_nonce(server_url, timestamp, salt)
50
+ return false if (timestamp - Time.now.to_i).abs > Nonce.skew
51
+ ts = timestamp.to_s # base 10 seconds since epoch
52
+ nonce_key = prefix + ':n:' + server_url + ':' + ts + ':' + salt
53
+ if @redis.setnx(nonce_key, '')
54
+ @redis.expire(nonce_key, Nonce.skew + 5)
55
+ true
56
+ else
57
+ false
58
+ end
59
+ end
60
+
61
+ def cleanup_nonces
62
+ end
63
+
64
+ def cleanup
65
+ end
66
+
67
+ def cleanup_associations
68
+ end
69
+
70
+ private
71
+
72
+ def assoc_key(server_url, assoc_handle=nil)
73
+ key = prefix + ':a:' + server_url
74
+ if assoc_handle
75
+ key + ':' + assoc_handle
76
+ else
77
+ key
78
+ end
79
+ end
80
+
81
+ def serialize(assoc)
82
+ Marshal.dump(assoc)
83
+ end
84
+
85
+ def deserialize(assoc_str)
86
+ Marshal.load(assoc_str)
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1 @@
1
+ require 'openid/store/redis'
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'openid/store/redis/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "openid-store-redis"
8
+ spec.version = OpenID::Store::REDIS_VERSION
9
+ spec.authors = ["Ville Lautanala"]
10
+ spec.email = ["lautis@gmail.com"]
11
+ spec.description = %q{Use Redis to store OpenID associations and nonces with ruby-openid}
12
+ spec.summary = %q{A Redis storage backend for ruby-openid}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "ruby-openid", ">= 2.1.7"
22
+ spec.add_runtime_dependency "redis", "~> 3.0"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.3"
25
+ spec.add_development_dependency "rake"
26
+ spec.add_development_dependency "rspec", "~> 2.13"
27
+ spec.add_development_dependency "mock_redis"
28
+ end
@@ -0,0 +1,129 @@
1
+ require 'spec_helper'
2
+ require 'securerandom'
3
+ require 'openid/association'
4
+ require 'openid/store/nonce'
5
+ Nonce = OpenID::Nonce
6
+
7
+ describe OpenID::Store::Redis do
8
+ let(:redis) { MockRedis.new }
9
+ let(:url) { 'https://example.com/' + SecureRandom.base64 }
10
+ let(:handle) { SecureRandom.base64 }
11
+
12
+ subject { OpenID::Store::Redis.new(redis) }
13
+
14
+ describe '#get_association' do
15
+ it 'returns nil when association does not exist' do
16
+ subject.get_association(url).should be_nil
17
+ end
18
+
19
+ it 'returns nil when association with handle does not exist' do
20
+ server_url = 'test/url'
21
+ handle = 'test/handle'
22
+ subject.get_association(url, handle).should be_nil
23
+ end
24
+
25
+ it 'returns association when exists' do
26
+ assn = association(0)
27
+ redis.set('openid-store:a:' + url, Marshal.dump(assn))
28
+ subject.get_association(url).handle.should eql assn.handle
29
+ subject.get_association(url).secret.should eql assn.secret
30
+ end
31
+
32
+ it 'returns association with handle' do
33
+ assoc = association(0)
34
+ redis.set('openid-store:a:' + url + ":" + handle, Marshal.dump(assoc))
35
+ subject.get_association(url, handle).handle.should eql assoc.handle
36
+ subject.get_association(url, handle).secret.should eql assoc.secret
37
+ end
38
+ end
39
+
40
+ describe '#store_association' do
41
+ it 'stores association to redis' do
42
+ assoc = association(0)
43
+ serialized = Marshal.dump(assoc)
44
+ subject.store_association(url, assoc)
45
+ redis.get('openid-store:a:' + url).should eql(serialized)
46
+ end
47
+
48
+ it 'stores with handle' do
49
+ assoc = association(0)
50
+ serialized = Marshal.dump(assoc)
51
+ subject.store_association(url, assoc)
52
+ redis.get('openid-store:a:' + url + ':' + assoc.handle).should eql(serialized)
53
+ end
54
+
55
+ it 'expires the association after lifetime' do
56
+ assoc = association(0)
57
+ subject.store_association(url, assoc)
58
+ ttl = redis.ttl('openid-store:a:' + url + ':' + assoc.handle)
59
+ ttl.should eql(assoc.lifetime)
60
+ end
61
+ end
62
+
63
+ describe '#remove_association' do
64
+ let(:assoc) { association(0) }
65
+ before :each do
66
+ redis.set('openid-store:a:' + url + ":" + assoc.handle, Marshal.dump(assoc))
67
+ redis.set('openid-store:a:' + url, Marshal.dump(assoc))
68
+ end
69
+
70
+ it 'removes association from redis' do
71
+ subject.remove_association(url, assoc.handle)
72
+ redis.get('openid-store:a:' + url + ":" + assoc.handle).should be_nil
73
+ redis.get('openid-store:a:' + url).should be_nil
74
+ end
75
+
76
+ it 'does not remove when handles do not match' do
77
+ subject.remove_association(url, assoc.handle + 'fail')
78
+ redis.get('openid-store:a:' + url + ":" + assoc.handle).should_not be_nil
79
+ end
80
+
81
+ it 'returns true when data is removed' do
82
+ subject.remove_association(url, assoc.handle).should be(true)
83
+ end
84
+
85
+ it 'returns false when data is not removed' do
86
+ subject.remove_association(url + 'fail', assoc.handle).should be(false)
87
+ end
88
+ end
89
+
90
+ describe '#use_nonce' do
91
+ it 'allows nonce to be used once' do
92
+ timestamp, salt = Nonce::split_nonce(Nonce::mk_nonce)
93
+ subject.use_nonce(url, timestamp.to_i, salt).should be_true
94
+ end
95
+
96
+ it 'does not allow multiple uses of nonce' do
97
+ timestamp, salt = Nonce::split_nonce(Nonce::mk_nonce)
98
+ subject.use_nonce(url, timestamp.to_i, salt)
99
+ subject.use_nonce(url, timestamp.to_i, salt).should be_false
100
+ end
101
+
102
+ it 'creates nonce if time is within skew' do
103
+ now = Time.now
104
+ timestamp = now.to_f + OpenID::Nonce.skew - 1
105
+ subject.use_nonce(url, timestamp, 'salt').should be_true
106
+ end
107
+
108
+ it 'returns false if time is beyond skew' do
109
+ now = Time.now
110
+ timestamp = now.to_f + OpenID::Nonce.skew + 1
111
+ subject.use_nonce(url, timestamp, 'salt').should be_false
112
+ end
113
+
114
+ it 'removes nonce from redis after skew timeout' do
115
+ ts, salt = Nonce::split_nonce(Nonce::mk_nonce)
116
+ subject.use_nonce(url, ts.to_i, salt)
117
+ ttl = redis.ttl('openid-store:n:' + url + ':' + ts.to_s + ':' + salt)
118
+ ttl.should eql(OpenID::Nonce.skew + 5)
119
+ end
120
+ end
121
+ end
122
+
123
+ def association(issued, lifetime=600)
124
+ OpenID::Association.new(SecureRandom.hex(128),
125
+ SecureRandom.base64(20),
126
+ Time.now + issued,
127
+ lifetime,
128
+ 'HMAC-SHA1')
129
+ end
@@ -0,0 +1,11 @@
1
+ # encoding: UTF-8
2
+ require "openid-store-redis"
3
+ require "rspec"
4
+ require "mock_redis"
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+ end
metadata ADDED
@@ -0,0 +1,162 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: openid-store-redis
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.0
6
+ platform: ruby
7
+ authors:
8
+ - Ville Lautanala
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-05-08 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ version_requirements: !ruby/object:Gem::Requirement
16
+ none: false
17
+ requirements:
18
+ - - ! '>='
19
+ - !ruby/object:Gem::Version
20
+ version: 2.1.7
21
+ name: ruby-openid
22
+ prerelease: false
23
+ requirement: !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ! '>='
27
+ - !ruby/object:Gem::Version
28
+ version: 2.1.7
29
+ type: :runtime
30
+ - !ruby/object:Gem::Dependency
31
+ version_requirements: !ruby/object:Gem::Requirement
32
+ none: false
33
+ requirements:
34
+ - - ~>
35
+ - !ruby/object:Gem::Version
36
+ version: '3.0'
37
+ name: redis
38
+ prerelease: false
39
+ requirement: !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ version: '3.0'
45
+ type: :runtime
46
+ - !ruby/object:Gem::Dependency
47
+ version_requirements: !ruby/object:Gem::Requirement
48
+ none: false
49
+ requirements:
50
+ - - ~>
51
+ - !ruby/object:Gem::Version
52
+ version: '1.3'
53
+ name: bundler
54
+ prerelease: false
55
+ requirement: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ~>
59
+ - !ruby/object:Gem::Version
60
+ version: '1.3'
61
+ type: :development
62
+ - !ruby/object:Gem::Dependency
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ name: rake
70
+ prerelease: false
71
+ requirement: !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ - !ruby/object:Gem::Dependency
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ~>
83
+ - !ruby/object:Gem::Version
84
+ version: '2.13'
85
+ name: rspec
86
+ prerelease: false
87
+ requirement: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ~>
91
+ - !ruby/object:Gem::Version
92
+ version: '2.13'
93
+ type: :development
94
+ - !ruby/object:Gem::Dependency
95
+ version_requirements: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ! '>='
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ name: mock_redis
102
+ prerelease: false
103
+ requirement: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ! '>='
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ type: :development
110
+ description: Use Redis to store OpenID associations and nonces with ruby-openid
111
+ email:
112
+ - lautis@gmail.com
113
+ executables: []
114
+ extensions: []
115
+ extra_rdoc_files: []
116
+ files:
117
+ - .gitignore
118
+ - .travis.yml
119
+ - Gemfile
120
+ - LICENSE.txt
121
+ - README.md
122
+ - Rakefile
123
+ - lib/openid-store-redis.rb
124
+ - lib/openid/store/redis.rb
125
+ - lib/openid/store/redis/version.rb
126
+ - openid-store-redis.gemspec
127
+ - spec/openid/store/redis_spec.rb
128
+ - spec/spec_helper.rb
129
+ homepage: ''
130
+ licenses:
131
+ - MIT
132
+ post_install_message:
133
+ rdoc_options: []
134
+ require_paths:
135
+ - lib
136
+ required_ruby_version: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ segments:
142
+ - 0
143
+ hash: 881735339377992324
144
+ version: '0'
145
+ required_rubygems_version: !ruby/object:Gem::Requirement
146
+ none: false
147
+ requirements:
148
+ - - ! '>='
149
+ - !ruby/object:Gem::Version
150
+ segments:
151
+ - 0
152
+ hash: 881735339377992324
153
+ version: '0'
154
+ requirements: []
155
+ rubyforge_project:
156
+ rubygems_version: 1.8.23
157
+ signing_key:
158
+ specification_version: 3
159
+ summary: A Redis storage backend for ruby-openid
160
+ test_files:
161
+ - spec/openid/store/redis_spec.rb
162
+ - spec/spec_helper.rb