simple_oauth2 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +25 -0
  3. data/.coveralls.yml +1 -0
  4. data/.gitignore +26 -0
  5. data/.hound.yml +4 -0
  6. data/.rubocop.yml +5 -0
  7. data/.rubocop_todo.yml +12 -0
  8. data/.travis.yml +31 -0
  9. data/Gemfile +18 -0
  10. data/LICENSE +21 -0
  11. data/README.md +11 -0
  12. data/Rakefile +11 -0
  13. data/gemfiles/nobrainer.rb +15 -0
  14. data/lib/simple_oauth2/configuration/class_accessors.rb +36 -0
  15. data/lib/simple_oauth2/configuration/constants.rb +36 -0
  16. data/lib/simple_oauth2/configuration.rb +169 -0
  17. data/lib/simple_oauth2/generators/authorization.rb +64 -0
  18. data/lib/simple_oauth2/generators/base.rb +31 -0
  19. data/lib/simple_oauth2/generators/token.rb +71 -0
  20. data/lib/simple_oauth2/helpers.rb +40 -0
  21. data/lib/simple_oauth2/mixins/nobrainer/access_grant.rb +62 -0
  22. data/lib/simple_oauth2/mixins/nobrainer/access_token.rb +98 -0
  23. data/lib/simple_oauth2/mixins/nobrainer/client.rb +43 -0
  24. data/lib/simple_oauth2/resource/bearer.rb +20 -0
  25. data/lib/simple_oauth2/responses.rb +62 -0
  26. data/lib/simple_oauth2/scopes.rb +59 -0
  27. data/lib/simple_oauth2/strategies/authorization_code.rb +22 -0
  28. data/lib/simple_oauth2/strategies/base.rb +61 -0
  29. data/lib/simple_oauth2/strategies/client_credentials.rb +21 -0
  30. data/lib/simple_oauth2/strategies/code.rb +25 -0
  31. data/lib/simple_oauth2/strategies/password.rb +21 -0
  32. data/lib/simple_oauth2/strategies/refresh_token.rb +53 -0
  33. data/lib/simple_oauth2/strategies/token.rb +24 -0
  34. data/lib/simple_oauth2/uniq_token.rb +20 -0
  35. data/lib/simple_oauth2/version.rb +26 -0
  36. data/lib/simple_oauth2.rb +62 -0
  37. data/logo.png +0 -0
  38. data/simple_oauth2.gemspec +22 -0
  39. data/spec/configuration/config_spec.rb +181 -0
  40. data/spec/configuration/version_spec.rb +11 -0
  41. data/spec/dummy/endpoints/authorization.rb +15 -0
  42. data/spec/dummy/endpoints/custom_authorization.rb +21 -0
  43. data/spec/dummy/endpoints/custom_token.rb +21 -0
  44. data/spec/dummy/endpoints/status.rb +51 -0
  45. data/spec/dummy/endpoints/token.rb +22 -0
  46. data/spec/dummy/orm/nobrainer/app/config/db.rb +8 -0
  47. data/spec/dummy/orm/nobrainer/app/models/access_grant.rb +3 -0
  48. data/spec/dummy/orm/nobrainer/app/models/access_token.rb +3 -0
  49. data/spec/dummy/orm/nobrainer/app/models/client.rb +3 -0
  50. data/spec/dummy/orm/nobrainer/app/models/user.rb +11 -0
  51. data/spec/dummy/orm/nobrainer/app/twitter.rb +51 -0
  52. data/spec/dummy/orm/nobrainer/config.ru +37 -0
  53. data/spec/dummy/simple_oauth2_config.rb +7 -0
  54. data/spec/requests/flows/authorization_code_spec.rb +177 -0
  55. data/spec/requests/flows/client_credentials_spec.rb +163 -0
  56. data/spec/requests/flows/code_spec.rb +98 -0
  57. data/spec/requests/flows/password_spec.rb +183 -0
  58. data/spec/requests/flows/refresh_token_spec.rb +282 -0
  59. data/spec/requests/flows/token_spec.rb +113 -0
  60. data/spec/requests/protected_resources_spec.rb +65 -0
  61. data/spec/requests/revoke_token_spec.rb +90 -0
  62. data/spec/spec_helper.rb +51 -0
  63. data/spec/support/helper.rb +11 -0
  64. metadata +125 -0
@@ -0,0 +1,62 @@
1
+ require 'rack/oauth2'
2
+
3
+ require 'simple_oauth2/configuration/class_accessors'
4
+ require 'simple_oauth2/configuration/constants'
5
+ require 'simple_oauth2/configuration'
6
+ require 'simple_oauth2/scopes'
7
+ require 'simple_oauth2/uniq_token'
8
+ require 'simple_oauth2/resource/bearer'
9
+
10
+ # Mixins
11
+ if defined?(NoBrainer::Document)
12
+ require 'simple_oauth2/mixins/nobrainer/access_token'
13
+ require 'simple_oauth2/mixins/nobrainer/access_grant'
14
+ require 'simple_oauth2/mixins/nobrainer/client'
15
+ end
16
+
17
+ # Authorization Grants aka Flows (Strategies)
18
+ require 'simple_oauth2/strategies/base'
19
+ require 'simple_oauth2/strategies/password'
20
+ require 'simple_oauth2/strategies/authorization_code'
21
+ require 'simple_oauth2/strategies/refresh_token'
22
+ require 'simple_oauth2/strategies/code'
23
+ require 'simple_oauth2/strategies/token'
24
+ require 'simple_oauth2/strategies/client_credentials'
25
+
26
+ # Generators
27
+ require 'simple_oauth2/generators/base'
28
+ require 'simple_oauth2/generators/token'
29
+ require 'simple_oauth2/generators/authorization'
30
+
31
+ # Helpers
32
+ require 'simple_oauth2/helpers'
33
+
34
+ # Responses
35
+ require 'simple_oauth2/responses'
36
+
37
+ # Simple namespace for the gem
38
+ module Simple
39
+ # Main Simple::OAuth2 module
40
+ module OAuth2
41
+ class << self
42
+ # Simple::OAuth2 configuration
43
+ #
44
+ # @return [Simple::OAuth2::Configuration] configuration object with default values
45
+ #
46
+ def config
47
+ @config ||= Simple::OAuth2::Configuration.new
48
+ end
49
+
50
+ # Configures Simple::OAuth2.
51
+ # Yields Simple::OAuth2::Configuration instance to the block
52
+ def configure
53
+ yield config if block_given?
54
+ end
55
+
56
+ # Simple::OAuth2 default middleware
57
+ def middleware
58
+ Simple::OAuth2::Resource::Bearer
59
+ end
60
+ end
61
+ end
62
+ end
data/logo.png ADDED
Binary file
@@ -0,0 +1,22 @@
1
+ $LOAD_PATH.push File.expand_path('../lib', __FILE__)
2
+
3
+ require 'simple_oauth2/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'simple_oauth2'
7
+ s.version = Simple::OAuth2.gem_version
8
+ s.date = '2017-01-03'
9
+ s.summary = 'OAuth2 authorization'
10
+ s.description = 'A flexible OAuth2 server authorization'
11
+ s.authors = ['Volodimir Partytskyi']
12
+ s.email = 'volodimir.partytskyi@gmail.com'
13
+ s.homepage = 'https://github.com/simple-oauth2/simple_oauth2'
14
+ s.license = 'MIT'
15
+
16
+ s.require_paths = %w(lib)
17
+ s.files = `git ls-files`.split($RS)
18
+
19
+ s.required_ruby_version = '>= 2.2.2'
20
+
21
+ s.add_runtime_dependency 'rack-oauth2', '~> 1.3.0', '>= 1.3.0'
22
+ end
@@ -0,0 +1,181 @@
1
+ require 'spec_helper'
2
+
3
+ describe Simple::OAuth2::Configuration do
4
+ let(:config) { described_class.new }
5
+
6
+ class CustomClient
7
+ def self.authenticate(_key, _secret = nil)
8
+ 'Test'
9
+ end
10
+ end
11
+
12
+ class CustomAccessToken
13
+ def self.create_for(_client, _resource_owner, _scopes = nil); end
14
+
15
+ def self.authenticate(_token, _type = nil)
16
+ 'Test'
17
+ end
18
+
19
+ def client; end
20
+
21
+ def resource_owner; end
22
+
23
+ def expired?; end
24
+
25
+ def revoked?; end
26
+
27
+ def revoke!(_revoked_at = Time.now); end
28
+
29
+ def to_bearer_token; end
30
+ end
31
+
32
+ class CustomResourceOwner
33
+ def self.oauth_authenticate(_client, _username, _password)
34
+ 'Test'
35
+ end
36
+ end
37
+
38
+ context 'default config' do
39
+ it { expect(config.access_token_lifetime).to eq(7200) }
40
+ it { expect(config.authorization_code_lifetime).to eq(1800) }
41
+
42
+ it { expect(config.realm).to eq(Simple::OAuth2::Configuration::DEFAULT_REALM) }
43
+ it { expect(config.allowed_grant_types).to eq(%w(password authorization_code refresh_token client_credentials)) }
44
+
45
+ it { expect(config.issue_refresh_token).to be_falsey }
46
+ it { expect(config.on_refresh).to eq(:nothing) }
47
+
48
+ it { expect(config.scopes_validator_class_name).to eq(Simple::OAuth2::Scopes.name) }
49
+
50
+ it { expect { config.resource_owner_authenticator.call('test') }.to raise_error(RuntimeError) }
51
+ end
52
+
53
+ context 'custom config' do
54
+ class CustomScopesValidator
55
+ def self.valid?(_access_token, _scopes)
56
+ false
57
+ end
58
+ end
59
+
60
+ class CustomTokenGenerator
61
+ def self.generate(options = {})
62
+ if options[:custom]
63
+ 'custom_token'
64
+ else
65
+ 'default_token'
66
+ end
67
+ end
68
+ end
69
+
70
+ before do
71
+ config.access_token_class_name = 'CustomAccessToken'
72
+ config.resource_owner_class_name = 'CustomResourceOwner'
73
+ config.client_class_name = 'CustomClient'
74
+ config.access_grant_class_name = 'Object'
75
+ config.scopes_validator_class_name = 'CustomScopesValidator'
76
+ end
77
+
78
+ after do
79
+ config.access_token_class_name = 'AccessToken'
80
+ config.resource_owner_class_name = 'ResourceOwner'
81
+ config.client_class_name = 'Client'
82
+ config.access_grant_class_name = 'AccessGrant'
83
+ config.scopes_validator_class_name = 'ScopesValidator'
84
+ end
85
+
86
+ context 'works with custom token generator' do
87
+ before do
88
+ Simple::OAuth2.configure do |c|
89
+ c.token_generator_class_name = 'CustomTokenGenerator'
90
+ end
91
+ end
92
+
93
+ after do
94
+ Simple::OAuth2.configure do |c|
95
+ c.token_generator_class_name = Simple::OAuth2::UniqToken.name
96
+ end
97
+ end
98
+
99
+ it { expect(Simple::OAuth2.config.token_generator.generate).to eq('default_token') }
100
+ it { expect(Simple::OAuth2.config.token_generator.generate(custom: true)).to eq('custom_token') }
101
+ end
102
+
103
+ context 'invokes custom scopes validator' do
104
+ it { expect(config.scopes_validator.valid?(nil, nil)).to be_falsey }
105
+ end
106
+
107
+ context 'works with custom Access Token class' do
108
+ it { expect(config.access_token_class.authenticate('')).to eq('Test') }
109
+ end
110
+
111
+ context 'works with custom Client class' do
112
+ it { expect(config.client_class.authenticate('')).to eq('Test') }
113
+ end
114
+
115
+ context 'works with custom Resource Owner class' do
116
+ it { expect(config.resource_owner_class.oauth_authenticate('', '', '')).to eq('Test') }
117
+ end
118
+
119
+ context 'works with custom token authenticator' do
120
+ let(:request) { AccessToken.create }
121
+
122
+ before do
123
+ Simple::OAuth2.configure do |c|
124
+ c.token_authenticator do
125
+ raise ArgumentError, 'Test'
126
+ end
127
+ end
128
+ end
129
+
130
+ after do
131
+ Simple::OAuth2.configure do |c|
132
+ c.token_authenticator = config.default_token_authenticator
133
+ end
134
+ end
135
+
136
+ it { expect { config.token_authenticator.call }.to raise_error(ArgumentError) }
137
+ end
138
+
139
+ context 'works with custom on_refresh callback' do
140
+ let(:token) { AccessToken.create }
141
+
142
+ before do
143
+ Simple::OAuth2.configure do |c|
144
+ c.on_refresh do |access_token|
145
+ access_token.update(scopes: 'test')
146
+ end
147
+ end
148
+ end
149
+
150
+ after(:all) do
151
+ Simple::OAuth2.configure do |c|
152
+ c.on_refresh = :nothing
153
+ end
154
+ end
155
+
156
+ it do
157
+ expect { Simple::OAuth2::Strategies::RefreshToken.send(:run_callback_on_refresh_token, token) }
158
+ .to change { token.scopes }.to('test')
159
+ end
160
+ end
161
+
162
+ context 'raises an error with invalid on_refresh callback' do
163
+ before do
164
+ Simple::OAuth2.configure do |c|
165
+ c.on_refresh = 'invalid'
166
+ end
167
+ end
168
+
169
+ after(:all) do
170
+ Simple::OAuth2.configure do |c|
171
+ c.on_refresh = :nothing
172
+ end
173
+ end
174
+
175
+ it do
176
+ expect { Simple::OAuth2::Strategies::RefreshToken.send(:run_callback_on_refresh_token, nil) }
177
+ .to raise_error(ArgumentError)
178
+ end
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Simlpe::OAuth2 Version' do
4
+ context 'has a version string' do
5
+ it { expect(Simple::OAuth2::VERSION::STRING).to be_present }
6
+ end
7
+
8
+ context 'returns version as an instance of Gem::Version' do
9
+ it { expect(Simple::OAuth2.gem_version).to be_an_instance_of(Gem::Version) }
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ module Twitter
2
+ module Endpoints
3
+ module Authorization
4
+ def call(env)
5
+ response = Simple::OAuth2::Generators::Authorization.generate_for(env)
6
+
7
+ status = response.status
8
+ headers = response.headers
9
+ body = JSON.generate(response.body)
10
+
11
+ [status, headers, [body]]
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ module Twitter
2
+ module Endpoints
3
+ module CustomAuthorization
4
+ def request
5
+ @request
6
+ end
7
+
8
+ def call(env)
9
+ @request = Rack::Request.new(env)
10
+
11
+ response = Simple::OAuth2::Generators::Authorization.generate_for(env, &:unsupported_response_type!)
12
+
13
+ status = response.status
14
+ headers = response.headers
15
+ body = JSON.generate(response.body)
16
+
17
+ [status, headers, [body]]
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ module Twitter
2
+ module Endpoints
3
+ module CustomToken
4
+ def request
5
+ @request
6
+ end
7
+
8
+ def call(env)
9
+ @request = Rack::Request.new(env)
10
+
11
+ response = Simple::OAuth2::Generators::Token.generate_for(env, &:unsupported_grant_type!)
12
+
13
+ status = response.status
14
+ headers = response.headers
15
+ body = JSON.generate(response.body)
16
+
17
+ [status, headers, [body]]
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,51 @@
1
+ module Twitter
2
+ module Endpoints
3
+ module StatusSingleScope
4
+ def request
5
+ @request
6
+ end
7
+
8
+ def call(env)
9
+ @request = Rack::Request.new(env)
10
+ @current_resource_owner = nil # Refresh cache instance variable
11
+ @current_access_token = nil # Refresh cache instance variable
12
+
13
+ access_token_required! :read
14
+ body = JSON.dump(value: 'Access read', current_user_name: current_resource_owner.username)
15
+ [200, { 'content-type' => 'application/json' }, [body]]
16
+ end
17
+ end
18
+
19
+ module StatusMultipleScopes
20
+ def request
21
+ @request
22
+ end
23
+
24
+ def call(env)
25
+ @request = Rack::Request.new(env)
26
+ @current_resource_owner = nil # Refresh cache instance variable
27
+ @current_access_token = nil # Refresh cache instance variable
28
+
29
+ access_token_required! :read, :write
30
+ body = JSON.dump(value: 'Access read, write', current_user_name: current_resource_owner.username)
31
+ [200, { 'content-type' => 'application/json' }, [body]]
32
+ end
33
+ end
34
+
35
+ module Status
36
+ def request
37
+ @request
38
+ end
39
+
40
+ def call(env)
41
+ @request = Rack::Request.new(env)
42
+ @current_resource_owner = nil # Refresh cache instance variable
43
+ @current_access_token = nil # Refresh cache instance variable
44
+
45
+ access_token_required!
46
+ body = JSON.dump(value: 'Access', current_user_name: current_resource_owner.username)
47
+ [200, { 'content-type' => 'application/json' }, [body]]
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,22 @@
1
+ module Twitter
2
+ module Endpoints
3
+ module Token
4
+ def call(env)
5
+ response = Simple::OAuth2::Generators::Token.generate_for(env)
6
+
7
+ status = response.status
8
+ headers = response.headers
9
+ body = JSON.generate(response.body)
10
+
11
+ [status, headers, [body]]
12
+ end
13
+ end
14
+
15
+ module RevokeToken
16
+ def call(env)
17
+ params = Rack::Request.new(env).params
18
+ Simple::OAuth2::Generators::Token.revoke(params['token'], env)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,8 @@
1
+ NoBrainer::Document::PrimaryKey.__send__(:remove_const, :DEFAULT_PK_NAME)
2
+ NoBrainer::Document::PrimaryKey.__send__(:const_set, :DEFAULT_PK_NAME, :_id_)
3
+
4
+ NoBrainer.configure do |config|
5
+ config.reset!
6
+ config.rethinkdb_url = "rethinkdb://localhost/#{"#{ENV['ORM']}_#{ENV['RAILS_ENV']}"}"
7
+ config.environment = ENV['RAILS_ENV']
8
+ end
@@ -0,0 +1,3 @@
1
+ class AccessGrant
2
+ include Simple::OAuth2::NoBrainer::AccessGrant
3
+ end
@@ -0,0 +1,3 @@
1
+ class AccessToken
2
+ include Simple::OAuth2::NoBrainer::AccessToken
3
+ end
@@ -0,0 +1,3 @@
1
+ class Client
2
+ include Simple::OAuth2::NoBrainer::Client
3
+ end
@@ -0,0 +1,11 @@
1
+ class User
2
+ include NoBrainer::Document
3
+
4
+ field :username, type: String, index: true
5
+ field :encrypted_password, type: String
6
+
7
+ def self.oauth_authenticate(_client, username, password)
8
+ user = where(username: username.to_s).first
9
+ user if user && user.encrypted_password == password
10
+ end
11
+ end
@@ -0,0 +1,51 @@
1
+ require File.expand_path('../../../../../../lib/simple_oauth2', __FILE__)
2
+
3
+ require_relative 'models/access_token'
4
+ require_relative 'models/access_grant'
5
+ require_relative 'models/client'
6
+ require_relative 'models/user'
7
+
8
+ require_relative '../../../endpoints/authorization'
9
+ require_relative '../../../endpoints/custom_authorization'
10
+ require_relative '../../../endpoints/token'
11
+ require_relative '../../../endpoints/custom_token'
12
+ require_relative '../../../endpoints/status'
13
+
14
+ load File.expand_path('../config/db.rb', __FILE__)
15
+ load File.expand_path('../../../../simple_oauth2_config.rb', __FILE__)
16
+
17
+ include Simple::OAuth2::Helpers
18
+
19
+ module Twitter
20
+ class Token
21
+ extend Twitter::Endpoints::Token
22
+ end
23
+
24
+ class RevokeToken
25
+ extend Twitter::Endpoints::RevokeToken
26
+ end
27
+
28
+ class CustomToken
29
+ extend Twitter::Endpoints::CustomToken
30
+ end
31
+
32
+ class Authorization
33
+ extend Twitter::Endpoints::Authorization
34
+ end
35
+
36
+ class CustomAuthorization
37
+ extend Twitter::Endpoints::CustomAuthorization
38
+ end
39
+
40
+ class Status
41
+ extend Twitter::Endpoints::Status
42
+ end
43
+
44
+ class StatusSingleScope
45
+ extend Twitter::Endpoints::StatusSingleScope
46
+ end
47
+
48
+ class StatusMultipleScopes
49
+ extend Twitter::Endpoints::StatusMultipleScopes
50
+ end
51
+ end
@@ -0,0 +1,37 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+
3
+ require 'app/twitter'
4
+
5
+ use Simple::OAuth2.middleware
6
+
7
+ map '/oauth/token' do
8
+ run Twitter::Token
9
+ end
10
+
11
+ map '/oauth/revoke_token' do
12
+ run Twitter::RevokeToken
13
+ end
14
+
15
+ map '/oauth/authorization' do
16
+ run Twitter::Authorization
17
+ end
18
+
19
+ map '/oauth/custom_token' do
20
+ run Twitter::CustomToken
21
+ end
22
+
23
+ map '/oauth/custom_authorization' do
24
+ run Twitter::CustomAuthorization
25
+ end
26
+
27
+ map '/api/v1/status' do
28
+ run Twitter::Status
29
+ end
30
+
31
+ map '/api/v1/status/single_scope' do
32
+ run Twitter::StatusSingleScope
33
+ end
34
+
35
+ map '/api/v1/status/multiple_scopes' do
36
+ run Twitter::StatusMultipleScopes
37
+ end
@@ -0,0 +1,7 @@
1
+ Simple::OAuth2.configure do |config|
2
+ config.resource_owner_authenticator do |_request|
3
+ User.first
4
+ end
5
+
6
+ config.realm = 'Custom Realm'
7
+ end