rack-oauth2_utils 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 +5 -0
- data/Gemfile +28 -0
- data/README.mkd +52 -0
- data/Rakefile +7 -0
- data/lib/rack-oauth2_utils/middleware.rb +45 -0
- data/lib/rack-oauth2_utils/oauth_request.rb +41 -0
- data/lib/rack-oauth2_utils/test_store.rb +39 -0
- data/lib/rack-oauth2_utils/version.rb +5 -0
- data/lib/rack-oauth2_utils.rb +10 -0
- data/rack-oauth2_utils.gemspec +25 -0
- data/test/middleware_test.rb +136 -0
- data/test/test_helper.rb +14 -0
- metadata +101 -0
data/Gemfile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in rack-oauth2_utils.gemspec
|
4
|
+
#gemspec
|
5
|
+
|
6
|
+
gem 'rack'
|
7
|
+
|
8
|
+
group :test do
|
9
|
+
gem 'minitest'
|
10
|
+
gem "rack-test"
|
11
|
+
end
|
12
|
+
|
13
|
+
# use Rack::OAuth2::Access::Middleware, :store => Rack::OAuth2::Access::MemoryStore
|
14
|
+
#
|
15
|
+
#
|
16
|
+
# run App
|
17
|
+
#
|
18
|
+
# before do
|
19
|
+
#
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# Rack::OAuth2::Access::AppHelpers
|
23
|
+
#
|
24
|
+
# before do
|
25
|
+
# account = Account.find(oauth.identity)
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# get '/' do
|
data/README.mkd
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# Rack OAuth Utils
|
2
|
+
|
3
|
+
Simple Rack middleware that catches OAuth2 access tokens and validates identity
|
4
|
+
|
5
|
+
This gem only covers the simple use of "using a token". You must implement the authorization and "getting a token" part in your app.
|
6
|
+
|
7
|
+
## USAGE
|
8
|
+
|
9
|
+
class API < Sinatra::Base
|
10
|
+
use Rack::OAuth2Utils::Middleware, :store => SomeKeyValueStore
|
11
|
+
|
12
|
+
helpers do
|
13
|
+
|
14
|
+
def authorized?
|
15
|
+
!!identity
|
16
|
+
end
|
17
|
+
|
18
|
+
def identity
|
19
|
+
requets.env['oauth.identity']
|
20
|
+
end
|
21
|
+
|
22
|
+
def current_account
|
23
|
+
Account.find(identity) if authorized?
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
get '/private' do
|
29
|
+
if authorized?
|
30
|
+
content_type 'application/json'
|
31
|
+
current_account.to_json
|
32
|
+
else
|
33
|
+
halt 403, 'Access forbidden'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
:store is anything that responds to [], []= and delete. Can be Redis, PStore, some ORM wrapper, etc.
|
40
|
+
|
41
|
+
Store is expected to store access_tokens mapped to some identity string (for example account IDs).
|
42
|
+
|
43
|
+
There is a test store based on PStore (filesystem. Do no use in production):
|
44
|
+
|
45
|
+
store = Rack::OAuth2Utils::TestStore.new('tmp/access_tokens.store')
|
46
|
+
|
47
|
+
store['some_access_token'] = 'some_account_id'
|
48
|
+
|
49
|
+
It is up to you how you store tokens and identities.
|
50
|
+
|
51
|
+
|
52
|
+
See test/middlewate_test.rb for details
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
module Rack
|
2
|
+
module OAuth2Utils
|
3
|
+
|
4
|
+
class Middleware
|
5
|
+
|
6
|
+
attr_reader :store
|
7
|
+
|
8
|
+
def initialize(app, options = {})
|
9
|
+
@app = app
|
10
|
+
@store = options[:store] || {}
|
11
|
+
@realm = options[:realm]
|
12
|
+
@logger = options[:logger]
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
request = OAuthRequest.new(env)
|
17
|
+
logger = @logger || env["rack.logger"]
|
18
|
+
|
19
|
+
# If not oauth header / param, leave it up to the app.
|
20
|
+
return @app.call(env) unless request.oauth?
|
21
|
+
|
22
|
+
# Fetch identity
|
23
|
+
if identity = store[request.access_token] # identity found, forward to backend
|
24
|
+
env["oauth.identity"] = identity
|
25
|
+
logger.info "RO2U: Authorized #{identity}" if logger
|
26
|
+
else # invalid token
|
27
|
+
logger.info "RO2U: Invalid token" if logger
|
28
|
+
return unauthorized(request)
|
29
|
+
end
|
30
|
+
@app.call(env)
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
# Returns WWW-Authenticate header.
|
36
|
+
def unauthorized(request)
|
37
|
+
challenge = 'OAuth realm="%s"' % (@realm || request.host)
|
38
|
+
challenge << ', error="invalid_token", error_description="The access token is invalid."'
|
39
|
+
return [401, { "WWW-Authenticate" => challenge, 'Content-Type' => 'text/plain' }, ['The access token is invalid.']]
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# Wraps Rack::Request to expose Basic and OAuth authentication
|
2
|
+
# credentials.
|
3
|
+
# Borrowed from https://github.com/flowtown/rack-oauth2-server
|
4
|
+
#
|
5
|
+
module Rack
|
6
|
+
module OAuth2Utils
|
7
|
+
|
8
|
+
class OAuthRequest < Rack::Request
|
9
|
+
|
10
|
+
AUTHORIZATION_KEYS = %w{HTTP_AUTHORIZATION X-HTTP_AUTHORIZATION X_HTTP_AUTHORIZATION}
|
11
|
+
|
12
|
+
# Returns authorization header.
|
13
|
+
def authorization_header
|
14
|
+
@authorization_header ||= (
|
15
|
+
h = AUTHORIZATION_KEYS.inject(nil) { |auth, key| auth || @env[key] }
|
16
|
+
if h && h[/^oauth/i]
|
17
|
+
h.gsub(/\n/, "").split[1]
|
18
|
+
else
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
def authorization_param
|
25
|
+
@authorization_param ||= self.GET['oauth_token']
|
26
|
+
end
|
27
|
+
|
28
|
+
# True if authentication scheme is OAuth.
|
29
|
+
def oauth?
|
30
|
+
authorization_header || authorization_param
|
31
|
+
end
|
32
|
+
|
33
|
+
# If OAuth, returns access token.
|
34
|
+
#
|
35
|
+
def access_token
|
36
|
+
@access_token ||= oauth?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'pstore'
|
2
|
+
module Rack
|
3
|
+
module OAuth2Utils
|
4
|
+
|
5
|
+
# Test persistent store. Stores to FS using PStore
|
6
|
+
# Not meant for production!
|
7
|
+
#
|
8
|
+
class TestStore
|
9
|
+
def initialize(file_path = '.')
|
10
|
+
@store = PStore.new(file_path)
|
11
|
+
end
|
12
|
+
|
13
|
+
def []=(key, value)
|
14
|
+
@store.transaction do
|
15
|
+
@store[key] = value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def [](key)
|
20
|
+
@store.transaction do
|
21
|
+
@store[key]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def delete(key)
|
26
|
+
@store.transaction do
|
27
|
+
@store.delete(key)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def roots
|
32
|
+
@store.transaction do
|
33
|
+
@store.roots
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "rack-oauth2_utils/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "rack-oauth2_utils"
|
7
|
+
s.version = Rack::OAuth2Utils::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Ismael Celis"]
|
10
|
+
s.email = ["ismaelct@gmail.com"]
|
11
|
+
s.homepage = ""
|
12
|
+
s.summary = %q{Middleware for catching OAuth2 access tokens in Rack apps}
|
13
|
+
s.description = %q{Simple Rack middleware that catches OAuth2 access tokens and validates identity}
|
14
|
+
|
15
|
+
s.rubyforge_project = "rack-oauth2_utils"
|
16
|
+
|
17
|
+
s.add_dependency 'rack', ">= 1.2.2"
|
18
|
+
s.add_development_dependency "bundler", ">= 1.0.0"
|
19
|
+
s.add_development_dependency "minitest"
|
20
|
+
|
21
|
+
s.files = `git ls-files`.split("\n")
|
22
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
23
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
24
|
+
s.require_paths = ["lib"]
|
25
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/test_helper')
|
2
|
+
|
3
|
+
describe Rack::OAuth2Utils::Middleware do
|
4
|
+
|
5
|
+
include Rack::Test::Methods
|
6
|
+
|
7
|
+
OK_RESPONSE = [200, {'Content-Type' => 'text/plain'}, ['Hello world']]
|
8
|
+
FORBIDDEN_RESPONSE = [403, {'Content-Type' => 'text/plain'}, ['Nono']]
|
9
|
+
|
10
|
+
def app
|
11
|
+
@app ||= Rack::Builder.new do
|
12
|
+
# Simple token / identity store
|
13
|
+
use Rack::OAuth2Utils::Middleware, :store => {
|
14
|
+
# token # identity
|
15
|
+
'aaaaa' => 'ismasan',
|
16
|
+
'bbbbb' => 'sachi'
|
17
|
+
}
|
18
|
+
# Public endpoint
|
19
|
+
map('/public'){
|
20
|
+
run lambda {|env| OK_RESPONSE }
|
21
|
+
}
|
22
|
+
# Private, o auth protected
|
23
|
+
map('/private'){
|
24
|
+
run lambda {|env|
|
25
|
+
if env['oauth.identity']
|
26
|
+
OK_RESPONSE
|
27
|
+
else
|
28
|
+
FORBIDDEN_RESPONSE
|
29
|
+
end
|
30
|
+
}
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "no token" do
|
36
|
+
|
37
|
+
describe 'public resource' do
|
38
|
+
before {get '/public'}
|
39
|
+
|
40
|
+
it 'should return 200 Ok' do
|
41
|
+
last_response.status.must_equal 200
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should return body' do
|
45
|
+
last_response.body.must_equal 'Hello world'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe 'private resource' do
|
50
|
+
before {get '/private'}
|
51
|
+
|
52
|
+
it 'should return 200 Ok' do
|
53
|
+
last_response.status.must_equal 403
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'should return body' do
|
57
|
+
last_response.body.must_equal 'Nono'
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe 'with invalid token' do
|
63
|
+
|
64
|
+
before {
|
65
|
+
header "Authorization", "OAuth invalidtoken"
|
66
|
+
}
|
67
|
+
|
68
|
+
describe 'public resource' do
|
69
|
+
before {get '/public'}
|
70
|
+
|
71
|
+
it 'should return 401 Unauthorized' do
|
72
|
+
last_response.status.must_equal 401
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'should return WWW-Authenticate header with realm and error info' do
|
76
|
+
last_response.headers['WWW-Authenticate'].must_equal "OAuth realm=\"example.org\", error=\"invalid_token\", error_description=\"The access token is invalid.\""
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe 'private resource' do
|
81
|
+
before {get '/private'}
|
82
|
+
|
83
|
+
it 'should return 401 Unauthorized' do
|
84
|
+
last_response.status.must_equal 401
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'should return WWW-Authenticate header with realm and error info' do
|
88
|
+
last_response.headers['WWW-Authenticate'].must_equal "OAuth realm=\"example.org\", error=\"invalid_token\", error_description=\"The access token is invalid.\""
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe 'with valid token' do
|
94
|
+
|
95
|
+
before {
|
96
|
+
header "Authorization", "OAuth aaaaa"
|
97
|
+
}
|
98
|
+
|
99
|
+
describe 'public resource' do
|
100
|
+
before {get '/public'}
|
101
|
+
|
102
|
+
it 'should return 200 Ok' do
|
103
|
+
last_response.status.must_equal 200
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'should return body' do
|
107
|
+
last_response.body.must_equal 'Hello world'
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe 'private resource' do
|
112
|
+
before {get '/private'}
|
113
|
+
|
114
|
+
it 'should return 200 Ok' do
|
115
|
+
last_response.status.must_equal 200
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'should return body' do
|
119
|
+
last_response.body.must_equal 'Hello world'
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe 'with valid token as query param' do
|
125
|
+
before {get '/private', 'oauth_token' => 'aaaaa'}
|
126
|
+
|
127
|
+
it 'should return 200 Ok' do
|
128
|
+
last_response.status.must_equal 200
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'should return body' do
|
132
|
+
last_response.body.must_equal 'Hello world'
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
Bundler.setup :default, :test
|
4
|
+
|
5
|
+
ENV["RACK_ENV"] = "test"
|
6
|
+
|
7
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
8
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
9
|
+
|
10
|
+
require 'rack-oauth2_utils'
|
11
|
+
require "rack/test"
|
12
|
+
|
13
|
+
|
14
|
+
require 'minitest/autorun'
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack-oauth2_utils
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ismael Celis
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-07-07 00:00:00 +01:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: rack
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 1.2.2
|
25
|
+
type: :runtime
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
prerelease: false
|
30
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - ">="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: 1.0.0
|
36
|
+
type: :development
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: minitest
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: "0"
|
47
|
+
type: :development
|
48
|
+
version_requirements: *id003
|
49
|
+
description: Simple Rack middleware that catches OAuth2 access tokens and validates identity
|
50
|
+
email:
|
51
|
+
- ismaelct@gmail.com
|
52
|
+
executables: []
|
53
|
+
|
54
|
+
extensions: []
|
55
|
+
|
56
|
+
extra_rdoc_files: []
|
57
|
+
|
58
|
+
files:
|
59
|
+
- .gitignore
|
60
|
+
- Gemfile
|
61
|
+
- README.mkd
|
62
|
+
- Rakefile
|
63
|
+
- lib/rack-oauth2_utils.rb
|
64
|
+
- lib/rack-oauth2_utils/middleware.rb
|
65
|
+
- lib/rack-oauth2_utils/oauth_request.rb
|
66
|
+
- lib/rack-oauth2_utils/test_store.rb
|
67
|
+
- lib/rack-oauth2_utils/version.rb
|
68
|
+
- rack-oauth2_utils.gemspec
|
69
|
+
- test/middleware_test.rb
|
70
|
+
- test/test_helper.rb
|
71
|
+
has_rdoc: true
|
72
|
+
homepage: ""
|
73
|
+
licenses: []
|
74
|
+
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options: []
|
77
|
+
|
78
|
+
require_paths:
|
79
|
+
- lib
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: "0"
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: "0"
|
92
|
+
requirements: []
|
93
|
+
|
94
|
+
rubyforge_project: rack-oauth2_utils
|
95
|
+
rubygems_version: 1.6.2
|
96
|
+
signing_key:
|
97
|
+
specification_version: 3
|
98
|
+
summary: Middleware for catching OAuth2 access tokens in Rack apps
|
99
|
+
test_files:
|
100
|
+
- test/middleware_test.rb
|
101
|
+
- test/test_helper.rb
|