oauth2-rack 0.0.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.
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ .DS_Store
2
+ .bundle
3
+ \#*
4
+ .\#*
5
+ *~
6
+ *swp
7
+ TAGS
8
+ .rvmrc
9
+ .yardoc
10
+ /doc/*
11
+ Gemfile.lock
12
+ *.gem
13
+ pkg/*
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+
5
+ gem 'libnotify'
6
+ gem 'rb-inotify'
7
+
data/Guardfile ADDED
@@ -0,0 +1,10 @@
1
+ # -*- ruby -*-
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'rspec', :version => 2 do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
7
+ watch(%r{^spec/support/(.+)\.rb$}) { "spec/" }
8
+ watch('spec/spec_helper.rb') { "spec/" }
9
+ end
10
+
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'rspec/core/rake_task'
2
+ require 'bundler/gem_tasks'
3
+
4
+ RSpec::Core::RakeTask.new
data/config.ru ADDED
@@ -0,0 +1,50 @@
1
+ $: << File.expand_path('../lib', __FILE__)
2
+ require 'oauth2/rack'
3
+
4
+ class AccessTokenIssuer
5
+ def self.call(opts = {})
6
+ opts.merge('access_token' => 'test')
7
+ end
8
+ end
9
+ class Authenticator
10
+ def self.call(opts = {})
11
+ if opts[:username]
12
+ authenticate_resource_owner(opts)
13
+ elsif opts[:client_id]
14
+ authenticate_client(opts)
15
+ end
16
+ end
17
+
18
+ def self.authenticate_resource_owner(opts)
19
+ OpenStruct.new(:username => opts[:username])
20
+ end
21
+
22
+ def self.authenticate_client(opts)
23
+ OpenStruct.new(:client_id => opts[:client_id])
24
+ end
25
+ end
26
+
27
+ map '/password/access_token' do
28
+ use OAuth2::Rack::Authentication::Client::HTTPBasic, :required => false, :authenticator => Authenticator
29
+ use OAuth2::Rack::Authentication::Client::RequestParams, :required => false, :authenticator => Authenticator
30
+
31
+ use OAuth2::Rack::Authentication::ResourceOwner::RequestParams, :authenticator => Authenticator
32
+
33
+ use OAuth2::Rack::Authorization::Password::AccessTokenIssuer, :issuer => AccessTokenIssuer
34
+
35
+ run proc { |env| [200, {'Content-Type' => 'text/html'}, ['Hello']] }
36
+ end
37
+
38
+ map '/client_credentials/access_token' do
39
+ use OAuth2::Rack::Authentication::Client::HTTPBasic, :required => false, :authenticator => Authenticator
40
+ use OAuth2::Rack::Authentication::Client::RequestParams, :required => false, :authenticator => Authenticator
41
+
42
+ use OAuth2::Rack::Authorization::ClientCredentials::AccessTokenIssuer, :issuer => AccessTokenIssuer
43
+
44
+ run proc { |env| [200, {'Content-Type' => 'text/html'}, ['Hello']] }
45
+ end
46
+
47
+
48
+ map '/inspect' do
49
+ run proc { |env| [200, {'Content-Type' => 'text/html'}, [env.inspect]] }
50
+ end
@@ -0,0 +1,6 @@
1
+ require 'oauth2'
2
+
3
+ client = OAuth2::Client.new('client_id', 'client_secret', :site => 'http://localhost:9393/', :token_url => '/password/access_token')
4
+ token = client.password.get_token('username', 'password')
5
+
6
+ p token
@@ -0,0 +1,61 @@
1
+ require 'oauth2/rack'
2
+
3
+ # 2.4.1. Client Password
4
+ class OAuth2::Rack::Authentication::Client::HTTPBasic
5
+ HEADER_KEYS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION']
6
+
7
+ def initialize(app, opts = {}, &authenticator)
8
+ @app = app
9
+ @realm = opts.delete(:realm)
10
+ @required = opts.fetch(:required, true)
11
+ opts.delete(:required)
12
+ @authenticator = authenticator || opts.delete(:authenticator)
13
+ end
14
+
15
+ def call(env)
16
+ return @app.call(env) if env.has_key?('oauth2.client')
17
+
18
+ key = HEADER_KEYS.find { |k| env.has_key?(k) }
19
+ auth_string = env[key]
20
+
21
+ if auth_string.nil?
22
+ return @required ? unauthorized : @app.call(env)
23
+ end
24
+
25
+ schema, credentials = auth_string.split(' ', 2)
26
+ if schema.downcase != 'basic'
27
+ return bad_request
28
+ end
29
+
30
+ client_id, client_secret = credentials.unpack('m*').first.split(':', 2)
31
+ client = @authenticator.call(:client_id => client_id, :client_secret => client_secret)
32
+ if client
33
+ env['oauth2.client'] = client
34
+ @app.call(env)
35
+ else
36
+ unauthorized
37
+ end
38
+ end
39
+
40
+ private
41
+ def authenticate(opts)
42
+ @authenticator && @authenticator.call(opts)
43
+ end
44
+
45
+ def unauthorized
46
+ [ 401,
47
+ { 'Content-Type' => 'text/plain',
48
+ 'Content-Length' => '0',
49
+ 'WWW-Authenticate' => 'Basic realm="%s"' % @realm },
50
+ []
51
+ ]
52
+ end
53
+
54
+ def bad_request
55
+ [ 400,
56
+ { 'Content-Type' => 'text/plain',
57
+ 'Content-Length' => '0' },
58
+ []
59
+ ]
60
+ end
61
+ end
@@ -0,0 +1,55 @@
1
+ require 'oauth2/rack'
2
+
3
+ # 2.4.1. Client Password
4
+ # Send client_id and client_secret in request params
5
+ class OAuth2::Rack::Authentication::Client::RequestParams
6
+ def initialize(app, opts = {}, &authenticator)
7
+ @app = app
8
+ @required = opts.fetch(:required, true)
9
+ opts.delete(:required)
10
+ @authenticator = authenticator || opts.delete(:authenticator)
11
+ end
12
+
13
+ def call(env)
14
+ return @app.call(env) if env.has_key?('oauth2.client')
15
+
16
+ @request = Rack::Request.new(env)
17
+
18
+ client_id = @request['client_id']
19
+ client_secret = @request['client_secret']
20
+ if client_id.nil? && client_secret.nil?
21
+ return @required ? unauthorized : @app.call(env)
22
+ elsif client_id.nil? || client_secret.nil?
23
+ return bad_request
24
+ end
25
+
26
+ client = @authenticator.call(:client_id => client_id, :client_secret => client_secret)
27
+ if client
28
+ env['oauth2.client'] = client
29
+ @app.call(env)
30
+ else
31
+ unauthorized
32
+ end
33
+ end
34
+
35
+ private
36
+ def authenticate(opts)
37
+ @authenticator && @authenticator.call(opts)
38
+ end
39
+
40
+ def unauthorized
41
+ [ 401,
42
+ { 'Content-Type' => 'text/plain',
43
+ 'Content-Length' => '0' },
44
+ []
45
+ ]
46
+ end
47
+
48
+ def bad_request
49
+ [ 400,
50
+ { 'Content-Type' => 'text/plain',
51
+ 'Content-Length' => '0' },
52
+ []
53
+ ]
54
+ end
55
+ end
@@ -0,0 +1,5 @@
1
+ # Client authentication
2
+ module OAuth2::Rack::Authentication::Client
3
+ autoload :HTTPBasic, 'oauth2/rack/authentication/client/http_basic'
4
+ autoload :RequestParams, 'oauth2/rack/authentication/client/request_params'
5
+ end
@@ -0,0 +1,54 @@
1
+ require 'oauth2/rack'
2
+
3
+ # Authenticate resource owner with request params
4
+ class OAuth2::Rack::Authentication::ResourceOwner::RequestParams
5
+ def initialize(app, opts = {}, &authenticator)
6
+ @app = app
7
+ @required = opts.fetch(:required, true)
8
+ opts.delete(:required)
9
+ @authenticator = authenticator || opts.delete(:authenticator)
10
+ end
11
+
12
+ def call(env)
13
+ return @app.call(env) if env.has_key?('oauth2.resource_owner')
14
+
15
+ @request = Rack::Request.new(env)
16
+
17
+ username = @request['username']
18
+ password = @request['password']
19
+ if username.nil? && password.nil?
20
+ return @required ? unauthorized : @app.call(env)
21
+ elsif username.nil? || password.nil?
22
+ return bad_request
23
+ end
24
+
25
+ resource_owner = @authenticator.call(:username => username, :password => password)
26
+ if resource_owner
27
+ env['oauth2.resource_owner'] = resource_owner
28
+ @app.call(env)
29
+ else
30
+ unauthorized
31
+ end
32
+ end
33
+
34
+ private
35
+ def authenticate(opts)
36
+ @authenticator && @authenticator.call(opts)
37
+ end
38
+
39
+ def unauthorized
40
+ [ 401,
41
+ { 'Content-Type' => 'text/plain',
42
+ 'Content-Length' => '0' },
43
+ []
44
+ ]
45
+ end
46
+
47
+ def bad_request
48
+ [ 400,
49
+ { 'Content-Type' => 'text/plain',
50
+ 'Content-Length' => '0' },
51
+ []
52
+ ]
53
+ end
54
+ end
@@ -0,0 +1,4 @@
1
+ # Client authentication
2
+ module OAuth2::Rack::Authentication::ResourceOwner
3
+ autoload :RequestParams, 'oauth2/rack/authentication/resource_owner/request_params'
4
+ end
@@ -0,0 +1,6 @@
1
+ # Middlewares for authorization server to authenticate client or user
2
+ module OAuth2::Rack::Authentication
3
+ autoload :Client, 'oauth2/rack/authentication/client'
4
+ autoload :ResourceOwner, 'oauth2/rack/authentication/resource_owner'
5
+ end
6
+
@@ -0,0 +1,58 @@
1
+ # @see 4.4.1 Client Credentials
2
+ require 'oauth2/rack'
3
+ require 'multi_json'
4
+
5
+ class OAuth2::Rack::Authorization::ClientCredentials::AccessTokenIssuer
6
+ def initialize(app, opts = {}, &issuer)
7
+ @app = app
8
+
9
+ @issuer = issuer || opts.delete(:issuer)
10
+ end
11
+
12
+ def call(env)
13
+ client = env['oauth2.client']
14
+ unless client
15
+ return error_response(:error => 'invalid_client')
16
+ end
17
+
18
+ request = Rack::Request.new(env)
19
+ unless request['grant_type'] == 'client_credentials'
20
+ return error_response(:error => 'invalid_request')
21
+ end
22
+
23
+ access_token = find_acccess_token(:grant_type => 'client_credentials',
24
+ :client => client,
25
+ :scope => request['scope'])
26
+
27
+ if access_token['error']
28
+ error_response(access_token)
29
+ else
30
+ successful_response(access_token)
31
+ end
32
+ end
33
+
34
+ private
35
+ def find_acccess_token(opts)
36
+ if @issuer
37
+ @issuer.call(opts)
38
+ end || { 'error' => 'unauthorized_client' }
39
+ end
40
+
41
+ def successful_response(response_object)
42
+ headers = {
43
+ 'Content-Type' => 'application/json;charset=UTF-8',
44
+ 'Cache-Control' => 'no-store',
45
+ 'Pragma' => 'no-cache'
46
+ }
47
+
48
+ [200, headers, [MultiJson.encode(response_object)]]
49
+ end
50
+
51
+ def error_response(response_object)
52
+ headers = {
53
+ 'Content-Type' => 'application/json;charset=UTF-8'
54
+ }
55
+
56
+ [400, headers, [MultiJson.encode(response_object)]]
57
+ end
58
+ end
@@ -0,0 +1,5 @@
1
+ # Middlewares for authorization server
2
+ module OAuth2::Rack::Authorization::ClientCredentials
3
+ autoload :AccessTokenIssuer, 'oauth2/rack/authorization/client_credentials/access_token_issuer'
4
+ end
5
+
@@ -0,0 +1,60 @@
1
+ # @see 4.3. Resource Owner Password Credentials
2
+ require 'oauth2/rack'
3
+ require 'multi_json'
4
+
5
+ class OAuth2::Rack::Authorization::Password::AccessTokenIssuer
6
+ def initialize(app, opts = {}, &issuer)
7
+ @app = app
8
+
9
+ @issuer = issuer || opts.delete(:issuer)
10
+ end
11
+
12
+ def call(env)
13
+ resource_owner = env['oauth2.resource_owner']
14
+ unless resource_owner
15
+ return error_response(:error => 'invalid_grant')
16
+ end
17
+
18
+ request = Rack::Request.new(env)
19
+ unless request['grant_type'] == 'password'
20
+ return error_response(:error => 'invalid_request')
21
+ end
22
+
23
+ # oauth2.client is set in client authentication
24
+ access_token = find_acccess_token(:grant_type => 'password',
25
+ :resource_owner => resource_owner,
26
+ :client => env['oath2.client'],
27
+ :scope => request['scope'])
28
+
29
+ if access_token['error']
30
+ error_response(access_token)
31
+ else
32
+ successful_response(access_token)
33
+ end
34
+ end
35
+
36
+ private
37
+ def find_acccess_token(opts)
38
+ if @issuer
39
+ @issuer.call(opts)
40
+ end || { 'error' => 'unauthorized_client' }
41
+ end
42
+
43
+ def successful_response(response_object)
44
+ headers = {
45
+ 'Content-Type' => 'application/json;charset=UTF-8',
46
+ 'Cache-Control' => 'no-store',
47
+ 'Pragma' => 'no-cache'
48
+ }
49
+
50
+ [200, headers, [MultiJson.encode(response_object)]]
51
+ end
52
+
53
+ def error_response(response_object)
54
+ headers = {
55
+ 'Content-Type' => 'application/json;charset=UTF-8'
56
+ }
57
+
58
+ [400, headers, [MultiJson.encode(response_object)]]
59
+ end
60
+ end
@@ -0,0 +1,5 @@
1
+ # Middlewares for authorization server
2
+ module OAuth2::Rack::Authorization::Password
3
+ autoload :AccessTokenIssuer, 'oauth2/rack/authorization/password/access_token_issuer'
4
+ end
5
+
@@ -0,0 +1,6 @@
1
+ # Middlewares for authorization server
2
+ module OAuth2::Rack::Authorization
3
+ autoload :ClientCredentials, 'oauth2/rack/authorization/client_credentials'
4
+ autoload :Password, 'oauth2/rack/authorization/password'
5
+ end
6
+
@@ -0,0 +1,9 @@
1
+ require 'oauth2/rack/version'
2
+
3
+ module OAuth2
4
+ # Oauth2::Rack module, the top namespace for all oauth2-rack modules and classes
5
+ module Rack
6
+ autoload :Authorization, 'oauth2/rack/authorization'
7
+ autoload :Authentication, 'oauth2/rack/authentication'
8
+ end
9
+ end
@@ -0,0 +1 @@
1
+ require 'oauth2/rack'
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+
3
+ describe OAuth2::Rack::Authentication::Client::HTTPBasic do
4
+ let(:client) { double('client').as_null_object }
5
+ let(:authenticator) { double('authenticator').as_null_object }
6
+ let(:opts) { Hash.new }
7
+ let(:chained_app_response) { [200, { 'Content-Type' => 'text/plain' }, []] }
8
+
9
+ def do_request
10
+ post '/', opts
11
+ end
12
+
13
+ context 'when oauth2.client is already set' do
14
+ it 'continues the app' do
15
+ opts['oauth2.client'] = client
16
+ chained_app.should_receive(:call).with(hash_including('oauth2.client' => client)).and_return(chained_app_response)
17
+ do_request
18
+ response.status.should eq(200)
19
+ end
20
+ end
21
+
22
+ context 'when auth header is not specified' do
23
+ context 'and http basic auth is required' do
24
+ it 'responds with 401 unauthorized' do
25
+ do_request
26
+ response.status.should eq(401)
27
+ end
28
+ end
29
+ context 'and http basic auth is optional' do
30
+ app { OAuth2::Rack::Authentication::Client::HTTPBasic.new(chained_app, :required => false) }
31
+ it 'continues the app' do
32
+ chained_app.should_receive(:call).with(hash_not_including('oauth2.client')).and_return(chained_app_response)
33
+ do_request
34
+ response.status.should eq(200)
35
+ end
36
+ end
37
+ end
38
+
39
+ context 'when schema is not basic' do
40
+ before { opts['HTTP_AUTHORIZATION'] = 'Digest xxx' }
41
+
42
+ it 'responds with 400 bad request' do
43
+ do_request
44
+ response.status.should eq(400)
45
+ end
46
+ end
47
+
48
+ context 'when schema is basic' do
49
+ app {
50
+ OAuth2::Rack::Authentication::Client::HTTPBasic.new(chained_app) do |opts|
51
+ authenticator.call(opts)
52
+ end
53
+ }
54
+ before { opts['HTTP_AUTHORIZATION'] = "Basic #{["user:pass"].pack('m*')}" }
55
+
56
+ context 'and credentials are valid' do
57
+ it 'sets oauth2.client in env' do
58
+ authenticator.should_receive(:call).with(:client_id => 'user', :client_secret => 'pass').and_return(client)
59
+ chained_app.should_receive(:call).with(hash_including('oauth2.client' => client)).and_return(chained_app_response)
60
+
61
+ do_request
62
+ end
63
+ end
64
+
65
+ context 'but credentials are invalid' do
66
+ it 'responds with 401 unauthorized' do
67
+ authenticator.should_receive(:call).with(:client_id => 'user', :client_secret => 'pass').and_return(nil)
68
+ chained_app.should_not_receive(:call)
69
+
70
+ do_request
71
+
72
+ response.status.should eq(401)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,86 @@
1
+ require 'spec_helper'
2
+
3
+ describe OAuth2::Rack::Authentication::Client::RequestParams do
4
+ let(:client) { double('client').as_null_object }
5
+ let(:authenticator) { double('authenticator').as_null_object }
6
+ let(:params) { Hash.new }
7
+ let(:opts) { Hash[:params => params] }
8
+ let(:chained_app_response) { [200, { 'Content-Type' => 'text/plain' }, []] }
9
+
10
+ def do_request
11
+ post '/', opts
12
+ end
13
+
14
+ context 'when oauth2.client is already set' do
15
+ it 'continues the app' do
16
+ opts['oauth2.client'] = client
17
+ chained_app.should_receive(:call).with(hash_including('oauth2.client' => client)).and_return(chained_app_response)
18
+ do_request
19
+ response.status.should eq(200)
20
+ end
21
+ end
22
+
23
+ context 'when client_id and client_secret are both missed' do
24
+ context 'and request params auth is required' do
25
+ it 'responds with 401 unauthorized' do
26
+ do_request
27
+ response.status.should eq(401)
28
+ end
29
+ end
30
+ context 'and request params auth is optional' do
31
+ app { OAuth2::Rack::Authentication::Client::RequestParams.new(chained_app, :required => false) }
32
+ it 'continues the app' do
33
+ chained_app.should_receive(:call).with(hash_not_including('oauth2.client')).and_return(chained_app_response)
34
+ do_request
35
+ response.status.should eq(200)
36
+ end
37
+ end
38
+ end
39
+
40
+ context 'when client_id is missed' do
41
+ before { params['client_secret'] = 'secret' }
42
+
43
+ it 'responds with 400 bad request' do
44
+ do_request
45
+ response.status.should eq(400)
46
+ end
47
+ end
48
+ context 'when client_secret is missed' do
49
+ before { params['client_id'] = 'client_x' }
50
+
51
+ it 'responds with 400 bad request' do
52
+ do_request
53
+ response.status.should eq(400)
54
+ end
55
+ end
56
+
57
+ context 'when client_id and client_secret are specified' do
58
+ app {
59
+ OAuth2::Rack::Authentication::Client::RequestParams.new(chained_app) do |opts|
60
+ authenticator.call(opts)
61
+ end
62
+ }
63
+ let(:credentials) { Hash[:client_id => 'client_x', :client_secret => 'secret'] }
64
+ before { params.merge! credentials }
65
+
66
+ context 'and credentials are valid' do
67
+ it 'sets oauth2.client in env' do
68
+ authenticator.should_receive(:call).with(credentials).and_return(client)
69
+ chained_app.should_receive(:call).with(hash_including('oauth2.client' => client)).and_return(chained_app_response)
70
+
71
+ do_request
72
+ end
73
+ end
74
+
75
+ context 'but credentials are invalid' do
76
+ it 'responds with 401 unauthorized' do
77
+ authenticator.should_receive(:call).with(credentials).and_return(nil)
78
+ chained_app.should_not_receive(:call)
79
+
80
+ do_request
81
+
82
+ response.status.should eq(401)
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,86 @@
1
+ require 'spec_helper'
2
+
3
+ describe OAuth2::Rack::Authentication::ResourceOwner::RequestParams do
4
+ let(:resource_owner) { double('resource_owner').as_null_object }
5
+ let(:authenticator) { double('authenticator').as_null_object }
6
+ let(:params) { Hash.new }
7
+ let(:opts) { Hash[:params => params] }
8
+ let(:chained_app_response) { [200, { 'Content-Type' => 'text/plain' }, []] }
9
+
10
+ def do_request
11
+ post '/', opts
12
+ end
13
+
14
+ context 'when oauth2.resource_owner is already set' do
15
+ it 'continues the app' do
16
+ opts['oauth2.resource_owner'] = resource_owner
17
+ chained_app.should_receive(:call).with(hash_including('oauth2.resource_owner' => resource_owner)).and_return(chained_app_response)
18
+ do_request
19
+ response.status.should eq(200)
20
+ end
21
+ end
22
+
23
+ context 'when username and password are both missed' do
24
+ context 'and request params auth is required' do
25
+ it 'responds with 401 unauthorized' do
26
+ do_request
27
+ response.status.should eq(401)
28
+ end
29
+ end
30
+ context 'and request params auth is optional' do
31
+ app { OAuth2::Rack::Authentication::ResourceOwner::RequestParams.new(chained_app, :required => false) }
32
+ it 'continues the app' do
33
+ chained_app.should_receive(:call).with(hash_not_including('oauth2.resource_owner')).and_return(chained_app_response)
34
+ do_request
35
+ response.status.should eq(200)
36
+ end
37
+ end
38
+ end
39
+
40
+ context 'when username is missed' do
41
+ before { params['password'] = 'secret' }
42
+
43
+ it 'responds with 400 bad request' do
44
+ do_request
45
+ response.status.should eq(400)
46
+ end
47
+ end
48
+ context 'when password is missed' do
49
+ before { params['username'] = 'user_x' }
50
+
51
+ it 'responds with 400 bad request' do
52
+ do_request
53
+ response.status.should eq(400)
54
+ end
55
+ end
56
+
57
+ context 'when username and password are specified' do
58
+ app {
59
+ OAuth2::Rack::Authentication::ResourceOwner::RequestParams.new(chained_app) do |opts|
60
+ authenticator.call(opts)
61
+ end
62
+ }
63
+ let(:credentials) { Hash[:username => 'user_x', :password => 'secret'] }
64
+ before { params.merge! credentials }
65
+
66
+ context 'and credentials are valid' do
67
+ it 'sets oauth2.resource_owner in env' do
68
+ authenticator.should_receive(:call).with(credentials).and_return(resource_owner)
69
+ chained_app.should_receive(:call).with(hash_including('oauth2.resource_owner' => resource_owner)).and_return(chained_app_response)
70
+
71
+ do_request
72
+ end
73
+ end
74
+
75
+ context 'but credentials are invalid' do
76
+ it 'responds with 401 unauthorized' do
77
+ authenticator.should_receive(:call).with(credentials).and_return(nil)
78
+ chained_app.should_not_receive(:call)
79
+
80
+ do_request
81
+
82
+ response.status.should eq(401)
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,99 @@
1
+ require 'spec_helper'
2
+
3
+ describe OAuth2::Rack::Authorization::ClientCredentials::AccessTokenIssuer do
4
+ let(:client) { double('client') }
5
+ let(:opts) { Hash.new }
6
+
7
+ def do_request
8
+ post '/', opts
9
+ end
10
+
11
+ context 'when client is authenticated' do
12
+ before { opts['oauth2.client'] = client }
13
+
14
+ context 'and grant_type is invalid' do
15
+ let(:params) { Hash[:grant_type => 'xclient_credentials'] }
16
+ before { opts[:params] = params }
17
+
18
+ it 'responds with invalid_request' do
19
+ do_request
20
+
21
+ response.status.should eq(400)
22
+ response_object['error'].should eq('invalid_request')
23
+ end
24
+ end
25
+
26
+ context 'and grant_type is valid' do
27
+ let(:params) { Hash[:grant_type => 'client_credentials'] }
28
+ before { opts[:params] = params }
29
+
30
+ context 'and issuer is not specified' do
31
+ it 'responds with unauthorized_client' do
32
+ do_request
33
+
34
+ response.status.should eq(400)
35
+ response_object['error'].should eq('unauthorized_client')
36
+ end
37
+ end
38
+
39
+ context 'and issuer is specified' do
40
+ let(:issuer) { double('issuer') }
41
+ let(:expected_find_opts) {
42
+ Hash[:grant_type => 'client_credentials',
43
+ :client => client,
44
+ :scope => nil]
45
+ }
46
+
47
+ app { OAuth2::Rack::Authorization::ClientCredentials::AccessTokenIssuer.new(chained_app) { |opts| issuer.call(opts) } }
48
+
49
+ context 'but token is not found for the client' do
50
+ context 'and error is returned' do
51
+ before {
52
+ issuer.should_receive(:call).with(expected_find_opts).and_return({'error' => 'customized_error'})
53
+ }
54
+ it 'responds with the that error' do
55
+ do_request
56
+
57
+ response.status.should eq(400)
58
+ response_object['error'].should eq('customized_error')
59
+ end
60
+ end
61
+ context 'and nothing is returned' do
62
+ before {
63
+ issuer.should_receive(:call).with(expected_find_opts).and_return(nil)
64
+ }
65
+ it 'responds with unauthorized_client' do
66
+ do_request
67
+
68
+ response.status.should eq(400)
69
+ response_object['error'].should eq('unauthorized_client')
70
+ end
71
+ end
72
+
73
+ end
74
+ context 'and token is found for the client' do
75
+ before {
76
+ issuer.should_receive(:call).with(expected_find_opts).and_return({:access_token => 'X'})
77
+ }
78
+
79
+ it 'responds with the found token' do
80
+ do_request
81
+
82
+ response.status.should eq(200)
83
+ response_object['access_token'].should eq('X')
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ end
90
+
91
+ context 'when client not authenticated' do
92
+ before { post '/' }
93
+
94
+ it 'responds with invalid_client' do
95
+ response.status.should eq(400)
96
+ response_object['error'].should eq('invalid_client')
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,101 @@
1
+ require 'spec_helper'
2
+
3
+ describe OAuth2::Rack::Authorization::Password::AccessTokenIssuer do
4
+ let(:resource_owner) { double('resource_owner') }
5
+ let(:client) { double('client') }
6
+ let(:opts) { Hash.new }
7
+
8
+ def do_request
9
+ post '/', opts
10
+ end
11
+
12
+ context 'when resource_owner is authenticated' do
13
+ before { opts['oauth2.resource_owner'] = resource_owner }
14
+
15
+ context 'and grant_type is invalid' do
16
+ let(:params) { Hash[:grant_type => 'xpassword'] }
17
+ before { opts[:params] = params }
18
+
19
+ it 'responds with invalid_request' do
20
+ do_request
21
+
22
+ response.status.should eq(400)
23
+ response_object['error'].should eq('invalid_request')
24
+ end
25
+ end
26
+
27
+ context 'and grant_type is valid' do
28
+ let(:params) { Hash[:grant_type => 'password'] }
29
+ before { opts[:params] = params }
30
+
31
+ context 'and issuer is not specified' do
32
+ it 'responds with unauthorized_client' do
33
+ do_request
34
+
35
+ response.status.should eq(400)
36
+ response_object['error'].should eq('unauthorized_client')
37
+ end
38
+ end
39
+
40
+ context 'and issuer is specified' do
41
+ let(:issuer) { double('issuer') }
42
+ let(:expected_find_opts) {
43
+ Hash[:grant_type => 'password',
44
+ :resource_owner => resource_owner,
45
+ :client => nil,
46
+ :scope => nil]
47
+ }
48
+
49
+ app { OAuth2::Rack::Authorization::Password::AccessTokenIssuer.new(chained_app) { |opts| issuer.call(opts) } }
50
+
51
+ context 'but token is not found for the resource owner' do
52
+ context 'and error is returned' do
53
+ before {
54
+ issuer.should_receive(:call).with(expected_find_opts).and_return({'error' => 'customized_error'})
55
+ }
56
+ it 'responds with the that error' do
57
+ do_request
58
+
59
+ response.status.should eq(400)
60
+ response_object['error'].should eq('customized_error')
61
+ end
62
+ end
63
+ context 'and nothing is returned' do
64
+ before {
65
+ issuer.should_receive(:call).with(expected_find_opts).and_return(nil)
66
+ }
67
+ it 'responds with unauthorized_client' do
68
+ do_request
69
+
70
+ response.status.should eq(400)
71
+ response_object['error'].should eq('unauthorized_client')
72
+ end
73
+ end
74
+
75
+ end
76
+ context 'and token is found for the client' do
77
+ before {
78
+ issuer.should_receive(:call).with(expected_find_opts).and_return({:access_token => 'X'})
79
+ }
80
+
81
+ it 'responds with the found token' do
82
+ do_request
83
+
84
+ response.status.should eq(200)
85
+ response_object['access_token'].should eq('X')
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ end
92
+
93
+ context 'when resource owner not authenticated' do
94
+ before { post '/' }
95
+
96
+ it 'responds with invalid_grant' do
97
+ response.status.should eq(400)
98
+ response_object['error'].should eq('invalid_grant')
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,11 @@
1
+ $: << File.expand_path('../../lib', __FILE__)
2
+ require 'oauth2/rack'
3
+ require 'multi_json'
4
+
5
+ # Requires supporting ruby files with custom matchers and macros, etc,
6
+ # in spec/support/ and its subdirectories.
7
+ Dir[File.expand_path("../support/**/*.rb", __FILE__)].each {|f| require f}
8
+
9
+ RSpec.configure do |config|
10
+ config.mock_with :rspec
11
+ end
@@ -0,0 +1,97 @@
1
+ require 'rack/mock'
2
+ require 'rack/utils'
3
+
4
+ module RackTestHelper
5
+ def self.included(klass)
6
+ klass.extend ClassMethods
7
+ end
8
+
9
+ attr_accessor :response
10
+
11
+ def chained_app
12
+ if defined?(@original_chained_app)
13
+ @original_chained_app
14
+ else
15
+ @original_chained_app = instance_eval(&self.class.chained_app)
16
+ end
17
+ end
18
+
19
+ def app
20
+ if defined?(@original_app)
21
+ @original_app
22
+ else
23
+ @original_app = instance_eval(&self.class.app)
24
+ end
25
+ end
26
+
27
+ def request
28
+ @request ||= Rack::MockRequest.new(app)
29
+ end
30
+
31
+ def response_object
32
+ return unless response
33
+
34
+ case response['Content-Type']
35
+ when %r{^application/json}
36
+ MultiJson::decode(response.body)
37
+ when %r{^application/x-www-form-urlencoded}
38
+ Rack::Utils.new.parse(response.body)
39
+ else
40
+ response.body
41
+ end
42
+ end
43
+
44
+ def get(uri, opts = {})
45
+ self.response = request.get(uri, opts)
46
+ end
47
+ def post(uri, opts = {})
48
+ self.response = request.post(uri, opts)
49
+ end
50
+ def put(uri, opts = {})
51
+ self.response = request.put(uri, opts)
52
+ end
53
+ def delete(uri, opts = {})
54
+ self.response = request.delete(uri, opts)
55
+ end
56
+
57
+ module ClassMethods
58
+ attr_reader :explicit_chained_app_block
59
+ def chained_app(&block)
60
+ block ? @explicit_chained_app_block = block : explicit_chained_app || implicit_chained_app
61
+ end
62
+
63
+ attr_reader :explicit_app_block
64
+ def app(&block)
65
+ block ? @explicit_app_block = block : explicit_app || implicit_app
66
+ end
67
+
68
+ def explicit_app
69
+ group = self
70
+ while group.respond_to?(:explicit_app_block)
71
+ return group.explicit_app_block if group.explicit_app_block
72
+ group = group.superclass
73
+ end
74
+ end
75
+
76
+ def implicit_app
77
+ described = describes || description
78
+ Class === described ? proc { described.new(chained_app) } : proc { described }
79
+ end
80
+
81
+ def explicit_chained_app
82
+ group = self
83
+ while group.respond_to?(:explicit_chained_app_block)
84
+ return group.explicit_chained_app_block if group.explicit_chained_app_block
85
+ group = group.superclass
86
+ end
87
+ end
88
+
89
+ def implicit_chained_app
90
+ proc { |env| [200, {"Content-Type" => 'text/html'}, ["<p>OK</p>"]] }
91
+ end
92
+ end
93
+ end
94
+
95
+ RSpec.configure do |config|
96
+ config.include RackTestHelper
97
+ end
metadata ADDED
@@ -0,0 +1,167 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: oauth2-rack
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ian Yang
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-08-26 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: multi_json
16
+ requirement: &8610000 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *8610000
25
+ - !ruby/object:Gem::Dependency
26
+ name: rack
27
+ requirement: &8609580 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *8609580
36
+ - !ruby/object:Gem::Dependency
37
+ name: rake
38
+ requirement: &8609160 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *8609160
47
+ - !ruby/object:Gem::Dependency
48
+ name: rspec
49
+ requirement: &8608740 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *8608740
58
+ - !ruby/object:Gem::Dependency
59
+ name: yard
60
+ requirement: &8608320 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *8608320
69
+ - !ruby/object:Gem::Dependency
70
+ name: shotgun
71
+ requirement: &8607900 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *8607900
80
+ - !ruby/object:Gem::Dependency
81
+ name: guard-rspec
82
+ requirement: &8607480 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: *8607480
91
+ - !ruby/object:Gem::Dependency
92
+ name: oauth2
93
+ requirement: &8607060 !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ type: :development
100
+ prerelease: false
101
+ version_requirements: *8607060
102
+ description: Rack middlewares for OAuth2 authorization server and resource server
103
+ email:
104
+ - me@iany.me
105
+ executables: []
106
+ extensions: []
107
+ extra_rdoc_files: []
108
+ files:
109
+ - .gitignore
110
+ - .rspec
111
+ - Gemfile
112
+ - Guardfile
113
+ - Rakefile
114
+ - config.ru
115
+ - examples/ruby-client/password_authorization.rb
116
+ - lib/oauth2-rack.rb
117
+ - lib/oauth2/rack.rb
118
+ - lib/oauth2/rack/authentication.rb
119
+ - lib/oauth2/rack/authentication/client.rb
120
+ - lib/oauth2/rack/authentication/client/http_basic.rb
121
+ - lib/oauth2/rack/authentication/client/request_params.rb
122
+ - lib/oauth2/rack/authentication/resource_owner.rb
123
+ - lib/oauth2/rack/authentication/resource_owner/request_params.rb
124
+ - lib/oauth2/rack/authorization.rb
125
+ - lib/oauth2/rack/authorization/client_credentials.rb
126
+ - lib/oauth2/rack/authorization/client_credentials/access_token_issuer.rb
127
+ - lib/oauth2/rack/authorization/password.rb
128
+ - lib/oauth2/rack/authorization/password/access_token_issuer.rb
129
+ - spec/oauth2/rack/authentication/client/http_basic_spec.rb
130
+ - spec/oauth2/rack/authentication/client/request_params_spec.rb
131
+ - spec/oauth2/rack/authentication/resource_owner/request_params_spec.rb
132
+ - spec/oauth2/rack/authorization/client_credentials/access_token_issuer_spec.rb
133
+ - spec/oauth2/rack/authorization/password/access_token_issuer_spec.rb
134
+ - spec/spec_helper.rb
135
+ - spec/support/rake_test_helper.rb
136
+ homepage: https://github.com/doitian/oauth2-rack
137
+ licenses: []
138
+ post_install_message:
139
+ rdoc_options: []
140
+ require_paths:
141
+ - lib
142
+ required_ruby_version: !ruby/object:Gem::Requirement
143
+ none: false
144
+ requirements:
145
+ - - ! '>='
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ required_rubygems_version: !ruby/object:Gem::Requirement
149
+ none: false
150
+ requirements:
151
+ - - ! '>='
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ requirements: []
155
+ rubyforge_project: oauth2-rack
156
+ rubygems_version: 1.8.6
157
+ signing_key:
158
+ specification_version: 3
159
+ summary: Rack middlewares for OAuth2 authorization server and resource server
160
+ test_files:
161
+ - spec/oauth2/rack/authentication/client/http_basic_spec.rb
162
+ - spec/oauth2/rack/authentication/client/request_params_spec.rb
163
+ - spec/oauth2/rack/authentication/resource_owner/request_params_spec.rb
164
+ - spec/oauth2/rack/authorization/client_credentials/access_token_issuer_spec.rb
165
+ - spec/oauth2/rack/authorization/password/access_token_issuer_spec.rb
166
+ - spec/spec_helper.rb
167
+ - spec/support/rake_test_helper.rb