oauth20 0.1.1

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.
@@ -0,0 +1,65 @@
1
+ module OAuth2
2
+
3
+ # Token request instance represents one request for a new token.
4
+ # Each token request is done on behalf of specific user and for
5
+ # one specific application. Parameters sent along have to match
6
+ # to the grant type used.
7
+ #
8
+ # If any requred param is not present, or request is other way
9
+ # malformed, the response error is returned. If everything is correct
10
+ # new access token is issued and returned in response.
11
+ #
12
+ class TokenRequest
13
+ attr_reader :client_secret, :grant_type, :options, :client, :user_id, :code
14
+
15
+ # Initialize new token request.
16
+ #
17
+ # @param [String] Secret key of the OAuth2::Client.
18
+ # @param [String] Request grant type.
19
+ # @param [Hash] Additional params requred for specific grant type.
20
+ #
21
+ def initialize(client_secret, grant_type, options)
22
+ @client_secret = client_secret
23
+ @grant_type = grant_type
24
+ @options = options
25
+
26
+ validate!
27
+ end
28
+
29
+ def response
30
+ TokenResponse.new(self)
31
+ end
32
+
33
+ # Validate request params to match ones specified by protocol for a given
34
+ # grant type.
35
+ #
36
+ # @throw [AuthError] Exception thrown in case of any error.
37
+ #
38
+ def validate!
39
+ case @grant_type
40
+ when 'token'
41
+ raise AuthError.new(OAuth2::ERROR_INVALID_REQUEST) unless @options[:code] && @options[:redirect_uri]
42
+
43
+ @code = AuthCode.find_by_key(@options[:code])
44
+
45
+ if @code.used?
46
+ access_token = OAuth2::AccessToken.find_by_key(@code.access_token)
47
+ access_token.revoke!
48
+ raise AuthError.new(OAuth2::ERROR_INVALID_GRANT, 'expired_or_invalid_auth_code')
49
+ end
50
+
51
+ raise AuthError.new(OAuth2::ERROR_INVALID_GRANT, 'expired_or_invalid_auth_code') if @code.nil? || @code.expired?
52
+
53
+ @client = Client.find_by_key(@code.client_key)
54
+ raise AuthError.new(OAuth2::ERROR_INVALID_GRANT, 'invalid_client_credentials') if !@client.secret.eql?(@client_secret)
55
+
56
+ @user_id = @code.user_id
57
+
58
+ else
59
+ raise AuthError.new(OAuth2::ERROR_UNSUPPORTED_GRANT_TYPE)
60
+ end
61
+
62
+ true
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,13 @@
1
+ module OAuth2
2
+ class TokenResponse
3
+ def initialize(request)
4
+ @access_token = AccessToken.new({:client_key => request.client.key, :user_id => request.user_id})
5
+ @access_token.save
6
+ request.code.invalidate(@access_token)
7
+ end
8
+
9
+ def to_json
10
+ @access_token.to_json
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,22 @@
1
+ module OAuth2
2
+ class User
3
+ attr_reader :email, :password
4
+
5
+ def initialize(data)
6
+ @email = data[:email]
7
+ @password = data[:password]
8
+ end
9
+
10
+ def save
11
+ OAuth2::Storage.instance.user_save(self)
12
+ end
13
+
14
+ def self.find_by_email(email)
15
+ OAuth2::Storage.instance.user_find_by_email(email)
16
+ end
17
+
18
+ def self.all
19
+ OAuth2::Storage.instance.user_all()
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,12 @@
1
+ module OAuth2
2
+ module Utils
3
+
4
+ #
5
+ # Generate new SHA1 encoded key with a given length.
6
+ # @param [String] Length of the key to generate.
7
+ #
8
+ def self.generate_key(length = 16)
9
+ Digest::SHA1.hexdigest(Time.now.to_s + rand(12341234).to_s)[1..length]
10
+ end
11
+ end
12
+ end
data/lib/oauth20.rb ADDED
@@ -0,0 +1,15 @@
1
+ require 'oauth20/access_token'
2
+ require 'oauth20/auth_code'
3
+ require 'oauth20/auth_error'
4
+ require 'oauth20/auth_request'
5
+ require 'oauth20/auth_response'
6
+ require 'oauth20/client'
7
+ require 'oauth20/token_request'
8
+ require 'oauth20/token_response'
9
+ require 'oauth20/user'
10
+ require 'oauth20/utils'
11
+
12
+ require 'oauth20/storage'
13
+ require 'oauth20/storages/strategy'
14
+ require 'oauth20/storages/mysql_strategy'
15
+ require 'oauth20/storages/redis_strategy'
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Authorization code request" do
4
+ it "should return authorization code" do
5
+
6
+ OAuth2::Storage.instance.strategy = OAuth2::Storages::RedisStrategy.new('')
7
+
8
+ client_key = OAuth2::Utils.generate_key
9
+ client_secret = OAuth2::Utils.generate_key
10
+
11
+ OAuth2::Client.any_instance.stubs(:save).returns(true)
12
+ OAuth2::AuthCode.any_instance.stubs(:save).returns(true)
13
+
14
+ client = OAuth2::Client.new('test', {
15
+ :redirect_uri => 'http://localhost:3000',
16
+ :key => client_key,
17
+ :secret => client_secret
18
+ })
19
+ client.save
20
+ OAuth2::Client.stubs(:find_by_key).returns(client)
21
+
22
+ req = OAuth2::AuthRequest.new(client_key, 'code', {:redirect_uri => 'http://localhost:3000'})
23
+ req.validate!
24
+
25
+ req.user = OAuth2::User.new({:email => 'petrjanda@me.com'})
26
+
27
+ req.response.to_url
28
+ end
29
+ end
@@ -0,0 +1,87 @@
1
+ require 'spec_helper'
2
+
3
+ describe OAuth2::AccessToken do
4
+ before do
5
+ Timecop.freeze
6
+ OAuth2::Utils.stubs(:generate_key).returns('1234')
7
+
8
+ @data = {
9
+ :key=> '1234',
10
+ :expires_in => 3600,
11
+ :scope => 'posts',
12
+ :token_type => 'Beared',
13
+ :expires_at => Time.now + 3600,
14
+ :created_at => Time.now,
15
+ :client_key => '1234',
16
+ :user_id => 12
17
+ }
18
+
19
+ @access_token = OAuth2::AccessToken.new(@data)
20
+ end
21
+
22
+ describe 'attributes' do
23
+ subject { @access_token }
24
+
25
+ its(:key) { should == '1234'}
26
+ its(:scope) { should == 'posts'}
27
+ its(:client_key) { should == '1234' }
28
+ its(:user_id) { should == 12}
29
+ its(:expires_in) { should == 3600 }
30
+ its(:created_at) { should == Time.now }
31
+ its(:expires_at) { should == Time.now + 3600 }
32
+ its(:token_type) { should == 'Beared' }
33
+
34
+ after do
35
+ Timecop.return
36
+ end
37
+ end
38
+
39
+ describe 'expired?' do
40
+ context 'expired token' do
41
+ it 'should return true' do
42
+ Timecop.travel(Time.now - OAuth2::AccessToken::EXPIRES_IN - 1)
43
+ token = OAuth2::AccessToken.new(@data)
44
+ Timecop.return
45
+
46
+ token.expired?.should == true
47
+ end
48
+ end
49
+
50
+ context 'new token' do
51
+ it 'should return true for already expired token' do
52
+ token = OAuth2::AccessToken.new(@data)
53
+ token.expired?.should == false
54
+ end
55
+ end
56
+ end
57
+
58
+ describe 'revoke!' do
59
+ it 'should expire token' do
60
+ OAuth2::Storage.instance.stubs(:access_token_save).returns(true)
61
+ token = OAuth2::AccessToken.new(@data)
62
+ token.revoke!
63
+ token.expired?.should == true
64
+ end
65
+ end
66
+
67
+ describe 'to_json' do
68
+ it 'should return oauth protocol valid data' do
69
+ token = OAuth2::AccessToken.new(@data)
70
+ json = token.to_json
71
+
72
+ JSON.parse(json).should == {
73
+ 'access_token' => token.key,
74
+ 'expires_in' => token.expires_in,
75
+ 'token_type' => 'Beared'
76
+ }
77
+ end
78
+ end
79
+
80
+ describe 'save' do
81
+ it 'should call proper storage metod' do
82
+ token = OAuth2::AccessToken.new(@data)
83
+ OAuth2::Storage.instance.expects(:access_token_save).with(token).once
84
+ token.save
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ describe OAuth2::AuthCode do
4
+
5
+ before do
6
+ @data = {
7
+ :client_key => '1234',
8
+ :user_id => 1,
9
+ :created_at => Time.now,
10
+ :expires_at => Time.now + OAuth2::AuthCode::EXPIRES_IN,
11
+ :key => '1234'
12
+ }
13
+
14
+ @code = OAuth2::AuthCode.new(@data)
15
+ end
16
+
17
+ describe "should store params" do
18
+ subject { @code }
19
+
20
+ its(:key) { should == @data[:key] }
21
+ its(:created_at) { should == @data[:created_at] }
22
+ its(:expires_at) { should == @data[:expires_at] }
23
+ its(:access_token) { should == @data[:access_token] }
24
+ its(:user_id) { should == @data[:user_id] }
25
+ its(:client_key) { should == @data[:client_key] }
26
+ end
27
+
28
+
29
+ describe 'expired?' do
30
+ context 'expired token' do
31
+ it 'should return true' do
32
+ Timecop.travel(Time.now - OAuth2::AuthCode::EXPIRES_IN - 1000)
33
+ @data[:created_at] = Time.now
34
+ @data[:expires_at] = Time.now + OAuth2::AuthCode::EXPIRES_IN
35
+ code = OAuth2::AuthCode.new(@data)
36
+ Timecop.return
37
+
38
+ code.expired?.should == true
39
+ end
40
+ end
41
+
42
+ context 'new token' do
43
+ it 'should return true for already expired token' do
44
+ code = OAuth2::AuthCode.new(@data)
45
+ code.expired?.should == false
46
+ end
47
+ end
48
+ end
49
+
50
+ after do
51
+ Timecop.return
52
+ end
53
+
54
+ describe 'save' do
55
+ it 'should call proper storage method' do
56
+ OAuth2::Storage.instance.expects(:auth_code_save).with(@code).returns(true)
57
+ @code.save
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+
3
+ describe OAuth2::AuthRequest do
4
+ before do
5
+ @client = Object.new
6
+ @client.stubs(:key).returns('1234')
7
+ @client.stubs(:redirect_uri).returns('http://localhost:3000')
8
+ OAuth2::Client.stubs(:find_by_key).returns(@client)
9
+
10
+ @request = OAuth2::AuthRequest.new('1', 'code', {
11
+ :redirect_uri => 'http://www.google.com',
12
+ :scope => 'post',
13
+ :state => 'a'
14
+ })
15
+
16
+ @request.user = {}
17
+ end
18
+
19
+ describe "should store params" do
20
+ subject { @request }
21
+
22
+ its(:client_id) { should == '1' }
23
+ its(:response_type) { should == 'code' }
24
+ its(:redirect_uri) { should == 'http://www.google.com' }
25
+ its(:scope) { should == 'post' }
26
+ its(:state) { should == 'a' }
27
+ its(:client) { should == @client }
28
+ end
29
+
30
+ describe "response" do
31
+ context 'for no user' do
32
+ before do
33
+ @request.unstub(:user)
34
+ end
35
+
36
+ it 'should throw an exception for unknown user' do
37
+ lambda {
38
+ OAuth2::AuthRequest.new('1', 'code', {
39
+ :redirect_uri => 'http://www.google.com',
40
+ :scope => 'post',
41
+ :state => 'a'
42
+ }).response
43
+ }.should raise_exception('access_denied')
44
+ end
45
+ end
46
+
47
+ context 'with user' do
48
+ before do
49
+ @request.stubs(:validate!).returns(true)
50
+ OAuth2::AuthCode.any_instance.stubs(:save).returns(true)
51
+ end
52
+
53
+ subject { @request }
54
+
55
+ its(:response) { should be_instance_of OAuth2::AuthResponse }
56
+ end
57
+ end
58
+
59
+ describe "validate!" do
60
+ it "should raise invalid response type exception" do
61
+ lambda {
62
+ OAuth2::AuthRequest.new('1', nil).validate!
63
+ }.should raise_exception('invalid_request')
64
+ end
65
+
66
+ it "should raise invalid client exception" do
67
+ OAuth2::Client.stubs(:find_by_key).returns(nil)
68
+ lambda {
69
+ OAuth2::AuthRequest.new('1', 'code', {
70
+ :redirect_uri => 'http://www.google.com',
71
+ :scope => 'post',
72
+ :state => 'a'
73
+ }).validate!
74
+ }.should raise_exception('invalid_client')
75
+ end
76
+
77
+
78
+ it "should raise invalid request" do
79
+ lambda {
80
+ OAuth2::AuthRequest.new('1', 'token').validate!
81
+ }.should raise_exception('unsupported_response_type')
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe OAuth2::AuthResponse do
4
+ before do
5
+ @client = Object.new
6
+ @client.stubs(:key).returns('1234')
7
+ @client.stubs(:redirect_uri).returns('http://www.google.com/cb')
8
+ OAuth2::Client.stubs(:find_by_key).returns(@client)
9
+ OAuth2::AuthCode.any_instance.expects(:save).returns(true)
10
+
11
+ req = OAuth2::AuthRequest.new('1', 'code', {
12
+ :redirect_uri => 'http://www.google.com/cb',
13
+ :scope => 'post',
14
+ :state => 'a'
15
+ })
16
+
17
+ OAuth2::Utils.stubs(:generate_key).returns('1234')
18
+
19
+ @res = OAuth2::AuthResponse.new(req)
20
+ end
21
+
22
+ describe "to_url" do
23
+ it "should be valid" do
24
+ @res.to_url.should == 'http://www.google.com/cb?code=1234&state=a&scope=post'
25
+ end
26
+
27
+ it 'should not generate code twice' do
28
+ @res.to_url
29
+ OAuth2::AuthCode.expects(:new).never
30
+ @res.to_url
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe OAuth2::Client do
4
+ before do
5
+ OAuth2::Utils.stubs(:generate_key).returns('1234')
6
+
7
+ @client = OAuth2::Client.new('finance', {
8
+ :redirect_uri => 'http://www.google.com/cb'
9
+ })
10
+ end
11
+
12
+ describe 'attributes' do
13
+ subject { @client }
14
+
15
+ its(:name) { should == 'finance' }
16
+ its(:key) { should == '1234' }
17
+ its(:secret) { should == '1234' }
18
+ its(:redirect_uri) { should == 'http://www.google.com/cb' }
19
+ its(:client_type) { should == 'confidential' }
20
+ end
21
+
22
+ describe 'create access token' do
23
+ it 'should return new access token' do
24
+ user = OAuth2::User.new({:email => 'petrjanda@me.com'})
25
+ @client.create_access_token(user).should be_instance_of OAuth2::AccessToken
26
+ end
27
+ end
28
+
29
+ describe 'find' do
30
+ before do
31
+ OAuth2::Storage.instance.stubs(:client_find_by_key).returns(@client)
32
+ end
33
+
34
+ it 'should call storage' do
35
+ OAuth2::Storage.instance.expects(:client_find_by_key).with('1234').returns(@client)
36
+ OAuth2::Client.find_by_key('1234')
37
+ end
38
+
39
+
40
+ it 'should return instance of Client' do
41
+ OAuth2::Client.find_by_key('1234').should be_instance_of OAuth2::Client
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe OAuth2::Storage do
4
+ it 'should not create two instances' do
5
+ OAuth2::Storage.instance.should == OAuth2::Storage.instance
6
+ end
7
+
8
+ it 'should delegate valid methods to the strategy' do
9
+ # strategy = Object.new
10
+ # strategy.stubs(:access_token_save).returns(true)
11
+ # OAuth2::Storage.instance.strategy = strategy
12
+
13
+ # OAuth2::Storage.instance.access_token_save.should == true
14
+ end
15
+ end
@@ -0,0 +1,174 @@
1
+ require 'spec_helper'
2
+ require 'pp'
3
+
4
+ describe OAuth2::Storages::RedisStrategy do
5
+ before do
6
+ @redis = Object.new
7
+ Redis.stubs(:new).returns(@redis)
8
+ OAuth2::Storages::RedisStrategy.any_instance.stubs(:redis).returns(@redis)
9
+ OAuth2::Storage.instance.strategy = OAuth2::Storages::RedisStrategy.new('')
10
+ end
11
+
12
+ describe 'access_token' do
13
+ before do
14
+ @data = {
15
+ :client_key => '1234',
16
+ :user_id => 1,
17
+ :expires_in => 3600,
18
+ :created_at => Time.now.to_i,
19
+ :expires_at => Time.now.to_i + 3600,
20
+ :scope => nil,
21
+ :key => '1234',
22
+ :token_type => 'Beared'
23
+ }
24
+
25
+ @access_token = OAuth2::AccessToken.new(@data)
26
+ end
27
+
28
+ describe 'save' do
29
+ it 'should call proper set' do
30
+ @redis.expects(:set).with("access_token:#{@data[:key]}", @data.to_json).returns(true)
31
+ OAuth2::Storage.instance.access_token_save(@access_token)
32
+ end
33
+ end
34
+
35
+ describe 'find_by_key' do
36
+ before do
37
+ @redis.stubs(:get).returns(@data.to_json)
38
+ end
39
+
40
+ it 'should call valid get' do
41
+ @redis.expects(:get).with('access_token:1234').returns(@data.to_json)
42
+ OAuth2::Storage.instance.access_token_find_by_key('1234')
43
+ end
44
+
45
+ describe 'response' do
46
+ before do
47
+ @access_token = OAuth2::Storage.instance.access_token_find_by_key('1234')
48
+ end
49
+
50
+ subject { @access_token }
51
+
52
+ it { should be_instance_of OAuth2::AccessToken }
53
+
54
+ its(:key) { should == @data[:key] }
55
+ its(:scope) { should == @data[:scope] }
56
+ its(:client_key) { should == @data[:client_key] }
57
+ its(:user_id) { should == @data[:user_id] }
58
+ its(:expires_in) { should == @data[:expires_in] }
59
+ its(:created_at) { should == Time.at(@data[:created_at]) }
60
+ its(:expires_at) { should == Time.at(@data[:expires_at]) }
61
+ its(:token_type) { should == @data[:token_type] }
62
+ end
63
+ end
64
+ end
65
+
66
+ describe 'auth_code' do
67
+ before do
68
+ @data = {
69
+ :client_key => '1234',
70
+ :user_id => 1,
71
+ :created_at => Time.now.to_i,
72
+ :access_token => nil,
73
+ :expires_at => Time.now.to_i + 3600,
74
+ :key => '1234'
75
+ }
76
+
77
+ @auth_code = OAuth2::AuthCode.new(@data)
78
+ end
79
+
80
+ describe 'save' do
81
+ it 'should call proper set' do
82
+ @redis.expects(:set).with("auth_code:#{@data[:key]}", @data.to_json).returns(true)
83
+ OAuth2::Storage.instance.auth_code_save(@auth_code)
84
+ end
85
+ end
86
+
87
+ describe 'find_by_key' do
88
+ before do
89
+ @redis.stubs(:get).returns(@data.to_json)
90
+ end
91
+
92
+ it 'should call valid get' do
93
+ @redis.expects(:get).with('auth_code:1234').returns(@data.to_json)
94
+ OAuth2::Storage.instance.auth_code_find_by_key('1234')
95
+ end
96
+
97
+ describe 'response' do
98
+ before do
99
+ @auth_code = OAuth2::Storage.instance.auth_code_find_by_key('1234')
100
+ end
101
+
102
+ subject { @auth_code }
103
+
104
+ it { should be_instance_of OAuth2::AuthCode }
105
+
106
+ its(:key) { should == @data[:key] }
107
+ its(:client_key) { should == @data[:client_key] }
108
+ its(:user_id) { should == @data[:user_id] }
109
+ its(:created_at) { should == Time.at(@data[:created_at]) }
110
+ its(:expires_at) { should == Time.at(@data[:expires_at]) }
111
+ end
112
+ end
113
+ end
114
+
115
+
116
+ describe 'client' do
117
+ before do
118
+ @options = {
119
+ :key => '1234',
120
+ :secret => '2344',
121
+ :redirect_uri => 'http://google.com'
122
+ }
123
+
124
+ @client = OAuth2::Client.new('test', @options)
125
+
126
+ @data = @options.merge({
127
+ :name => 'test'
128
+ })
129
+ end
130
+
131
+ describe 'client_save' do
132
+ it 'should store client hash' do
133
+ # Not reliable - json format constantly change
134
+ @redis.expects(:set)#.with('client:1234', @data.to_json).returns(true)
135
+ @redis.expects(:set).with('client_secret:2344:key', '1234').returns(true)
136
+
137
+ OAuth2::Storage.instance.client_save(@client)
138
+ end
139
+ end
140
+
141
+ describe 'client_find_by_key' do
142
+ before do
143
+ @redis.stubs(:get).returns(@data.to_json)
144
+ end
145
+
146
+ it 'should call valid get' do
147
+ @redis.expects(:get).with('client:1234').returns(@data.to_json)
148
+ OAuth2::Storage.instance.client_find_by_key('1234')
149
+ end
150
+
151
+ describe 'response' do
152
+ before do
153
+ @client = OAuth2::Storage.instance.client_find_by_key('1234')
154
+ end
155
+
156
+ subject { @client }
157
+
158
+ it { should be_instance_of OAuth2::Client }
159
+
160
+ its(:key) { should == @options[:key] }
161
+ its(:secret) { should == @options[:secret] }
162
+ its(:redirect_uri) { should == @options[:redirect_uri] }
163
+ end
164
+ end
165
+
166
+ describe 'client_find_by_secret' do
167
+ it 'should call valid get' do
168
+ @redis.expects(:get).with('client_secret:2345:key').returns('1234')
169
+ @redis.expects(:get).with('client:1234').returns(@data.to_json)
170
+ OAuth2::Storage.instance.client_find_by_secret('2345')
171
+ end
172
+ end
173
+ end
174
+ end