rack-oauth-wrap 0.5.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/lib/rack/auth/wrap.rb +80 -0
- data/lib/simplewebtoken.rb +8 -0
- data/lib/swt/exceptions.rb +13 -0
- data/lib/swt/simple_web_token_handler.rb +84 -0
- data/rakefile +51 -0
- data/spec/rack/auth/wrap_request_spec.rb +37 -0
- data/spec/rack/auth/wrap_spec.rb +59 -0
- data/spec/specs_config.rb +9 -0
- data/spec/swt/simple_web_token_handler_spec.rb +184 -0
- metadata +73 -0
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'rack/auth/abstract/handler'
|
2
|
+
require 'rack/auth/abstract/request'
|
3
|
+
require 'lib/simplewebtoken'
|
4
|
+
|
5
|
+
module Rack
|
6
|
+
module Auth
|
7
|
+
# Rack::Auth::WRAP implements oAuth WRAP Authentication, as per draft-hardt-oauth-01.
|
8
|
+
# This is a preliminary version based on the Jan 15, 2010 Web Resource Access Profiles as
|
9
|
+
# developed by the IETF.
|
10
|
+
#
|
11
|
+
# Initialize with the Rack application that you want protecting,
|
12
|
+
# and a set of parameters that enables specific checks. The only mandatory parameter
|
13
|
+
# is **:shared_secret** which is required for HMAC-SHA256 processing.
|
14
|
+
#
|
15
|
+
# See also: SimpleWebToken::SimpleWebTokenHandler
|
16
|
+
class WRAP < AbstractHandler
|
17
|
+
# Middleware Gem Versioning
|
18
|
+
VERSION = "0.5.1"
|
19
|
+
|
20
|
+
# Creates a new instance of Rack::Auth::WRAP, the opts can be used
|
21
|
+
# as the following.
|
22
|
+
#
|
23
|
+
# use Rack::Auth::WRAP, :shared_secret => *secret*,
|
24
|
+
# :trusted_issuers => "http://sts.mycomp.com",
|
25
|
+
# :audiences => "http://app.domain.com"
|
26
|
+
#
|
27
|
+
# The parameters on the sample above are the only one that are currently supported
|
28
|
+
# by the SimpleWebToken handler. For more information see SimpleWebToken::SimpleWebTokenHandler
|
29
|
+
def initialize(app, opts = {})
|
30
|
+
@app = app
|
31
|
+
@opts = opts
|
32
|
+
end
|
33
|
+
|
34
|
+
# Authenticates the request when it has the HTTP_AUTHORIZATION header,
|
35
|
+
# and if the header has WRAP as the authentication format.
|
36
|
+
#
|
37
|
+
# NOTE: it is sent by the client as Authorization, but Rack maps it to
|
38
|
+
# HTTP_AUTHORIZATION.</strong>
|
39
|
+
#
|
40
|
+
# If the user is successfuly authenticated the resulting token is
|
41
|
+
# stored on REMOTE_USER into the enviroment. (We didn't want to couple it with session)
|
42
|
+
def call(env)
|
43
|
+
request = Request.new(env)
|
44
|
+
|
45
|
+
if(request.provided? and request.is_wrap?)
|
46
|
+
return unauthorized('WRAP') unless token_handler.valid?(request.token)
|
47
|
+
env['REMOTE_USER'] = token_handler.parse(request.token)
|
48
|
+
end
|
49
|
+
|
50
|
+
return @app.call(env)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns a singleton instance of the SimpleWebToken::SimpleWebTokenHandler based on
|
54
|
+
# the options provided when initializing the middleware.
|
55
|
+
def token_handler
|
56
|
+
@token_handler ||= SimpleWebToken::SimpleWebTokenHandler.new(@opts)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Internal class used to parse the current request based on
|
60
|
+
# the enviroment parameters.
|
61
|
+
class Request < Rack::Auth::AbstractRequest
|
62
|
+
def initialize(env)
|
63
|
+
super(env)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns a value indicating whether the Authentication Scheme sent by
|
67
|
+
# the user is WRAP.
|
68
|
+
def is_wrap?
|
69
|
+
self.scheme == :wrap
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns the token contained inside the access_token parameter
|
73
|
+
# on the Authorization header, when it's using the WRAP Scheme.
|
74
|
+
def token
|
75
|
+
CGI.unescape(self.params[/access_token=([^&]+)/, 1])
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module SimpleWebToken
|
2
|
+
class InvalidOption < StandardError
|
3
|
+
def initialize(missing_option)
|
4
|
+
super("You did not provide one of the required parameters. Please provide the :#{missing_option}.")
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class InvalidToken < StandardError
|
9
|
+
def initialize
|
10
|
+
super("The token you are trying to parse is invalid. Cannot parse invalid Tokens")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module SimpleWebToken
|
2
|
+
# Handler for parsing, validating and creating (soon) Simple Web Tokens
|
3
|
+
# as it stated by the protocol under development of the IEFT v0.9.5.1.
|
4
|
+
class SimpleWebTokenHandler
|
5
|
+
attr_accessor :shared_secret, :trusted_issuers, :audiences
|
6
|
+
|
7
|
+
# Creates a new instance of the SimpleWebTokenHandler.
|
8
|
+
#
|
9
|
+
# Valid options include:
|
10
|
+
#
|
11
|
+
# -__:shared_secret__ the HMAC:SHA256 key shared between parties.
|
12
|
+
# -__:trusted_issuers__ the URI(s) of the issuers to be validated on the Issue value of the token.
|
13
|
+
# -__:audiences__ the URI(s) of the audiences (apps) to be validated on the Audience value of the token.
|
14
|
+
#
|
15
|
+
# __Only :shared_secret__ is required, the other values aren't present then no check
|
16
|
+
# is performed.
|
17
|
+
def initialize(opts = {})
|
18
|
+
raise InvalidOption, :shared_secret unless opts[:shared_secret]
|
19
|
+
self.shared_secret = opts[:shared_secret]
|
20
|
+
self.trusted_issuers = opts[:trusted_issuers]
|
21
|
+
self.audiences = opts[:audiences]
|
22
|
+
end
|
23
|
+
|
24
|
+
# Validates the signature by doing a symmetric signature comparison,
|
25
|
+
# between the value sent as HMACSHA256 on the token and the generated
|
26
|
+
# using the shared_key provided.
|
27
|
+
def valid_signature?(token)
|
28
|
+
return false unless token =~ /&HMACSHA256=(.*)$/
|
29
|
+
original_signature = CGI.unescape(token[/&HMACSHA256=(.*)$/, 1])
|
30
|
+
bare_token = token.gsub(/&HMACSHA256=(.*)$/, '')
|
31
|
+
signature = Base64.encode64(HMAC::SHA256.new(Base64.decode64(shared_secret)).update(bare_token.toutf8).digest).strip
|
32
|
+
return original_signature == signature
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns a value indicating whether the __Issuer__ value of the token
|
36
|
+
# is contained on the trusted_issuer list for the application.
|
37
|
+
def valid_issuer?(token)
|
38
|
+
issuer = token[/&?Issuer=([^&]+)/, 1]
|
39
|
+
[trusted_issuers].flatten.include?(CGI.unescape(issuer))
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns a value indicating whether the __Audience__ value of the token
|
43
|
+
# is contained on the audiences list of the application.
|
44
|
+
def valid_audience?(token)
|
45
|
+
audience = token[/&?Audience=([^&]+)/, 1]
|
46
|
+
[audiences].flatten.include?(CGI.unescape(audience))
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns a value indicating whether the __ExpiresOn__ value of the token
|
50
|
+
# is older than now.
|
51
|
+
def expired?(token)
|
52
|
+
expires_on = token[/&?ExpiresOn=([^&]+)/, 1]
|
53
|
+
expires_on.to_i < Time.now.to_i
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns a value indicating whether the token is valid, the calculation
|
57
|
+
# is done as the sum of all the other validations (when values for checking are provided)
|
58
|
+
def valid?(token)
|
59
|
+
valid = valid_signature?(token)
|
60
|
+
valid &&= valid_issuer?(token) if (trusted_issuers)
|
61
|
+
valid &&= valid_audience?(token) if (audiences)
|
62
|
+
valid &&= !expired?(token)
|
63
|
+
return valid
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns a key-value pair (hash) with the token values parsed.
|
67
|
+
#
|
68
|
+
# __NOTE__: multi-valued claims (provided as comma separated values,
|
69
|
+
# like checkboxes on HTML forms) are returned like arrays.
|
70
|
+
def parse(token)
|
71
|
+
raise InvalidToken unless valid?(token)
|
72
|
+
token.split('&').map{|p| p.split('=') } \
|
73
|
+
.inject({}){|t, i| t.merge!(CGI.unescape(i[0]) => value_for(CGI.unescape(i[1])))}
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
# Returns an array if the value is multi-valued
|
78
|
+
# else returns a the value plain.
|
79
|
+
def value_for(value)
|
80
|
+
values = value.split(',').map{|i| i.strip}
|
81
|
+
return values.size == 1 ? values.first() : values
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
data/rakefile
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'spec/rake/spectask'
|
4
|
+
require 'rake/gempackagetask'
|
5
|
+
require 'rake/rdoctask'
|
6
|
+
|
7
|
+
require 'lib/rack/auth/wrap'
|
8
|
+
|
9
|
+
namespace :test do
|
10
|
+
Spec::Rake::SpecTask.new('run_with_rcov') do |t|
|
11
|
+
t.spec_files = FileList['spec/rack/auth/*.rb', 'spec/swt/*.rb'].reject{|f| f.include?('functional')}
|
12
|
+
t.rcov = true
|
13
|
+
t.rcov_opts = ['--text-report', '--exclude', "exclude.*/.gem,spec,Library,#{ENV['GEM_HOME']}", '--sort', 'coverage' ]
|
14
|
+
t.spec_opts = ["--colour", "--loadby random", "--format progress", "--backtrace"]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
namespace :docs do
|
19
|
+
Rake::RDocTask.new do |t|
|
20
|
+
t.rdoc_dir = 'sdk/public/'
|
21
|
+
t.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
|
22
|
+
t.options << '--charset' << 'utf-8'
|
23
|
+
t.rdoc_files.include('README.rdoc')
|
24
|
+
t.rdoc_files.include('lib/**/*.rb')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
namespace :dist do
|
30
|
+
spec = Gem::Specification.new do |s|
|
31
|
+
s.name = 'rack-oauth-wrap'
|
32
|
+
s.version = Gem::Version.new(Rack::Auth::WRAP::VERSION)
|
33
|
+
s.summary = "Rack Middleware for authenticating users using oAuth WRAP Protocol"
|
34
|
+
s.description = "A simple implementation of Web Resource Authorization Protocol (WRAP) for Rack as middleware."
|
35
|
+
s.email = 'johnny.halife@me.com'
|
36
|
+
s.author = 'Johnny G. Halife & Juan Pablo Garcia Dalolla'
|
37
|
+
s.homepage = 'http://rack-oauth-wrap.heroku.com'
|
38
|
+
s.require_paths = ["lib"]
|
39
|
+
s.files = FileList['rakefile', 'lib/**/*.rb']
|
40
|
+
s.test_files = Dir['spec/**/*']
|
41
|
+
s.has_rdoc = true
|
42
|
+
s.rdoc_options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
|
43
|
+
|
44
|
+
# Dependencies
|
45
|
+
s.add_dependency 'ruby-hmac'
|
46
|
+
end
|
47
|
+
|
48
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
49
|
+
pkg.need_tar = true
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec/specs_config'
|
2
|
+
require 'lib/rack/auth/wrap'
|
3
|
+
|
4
|
+
describe "Request behavior for Client calls protected resource using HTTP Header" do
|
5
|
+
it "should tell if the request is using WRAP Authentication method" do
|
6
|
+
env = Rack::MockRequest.env_for("/", 'HTTP_AUTHORIZATION' => 'WRAP access_token=invalid_token')
|
7
|
+
request = Rack::Auth::WRAP::Request.new(env)
|
8
|
+
request.is_wrap?.should == true
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should tell if the request isn't using WRAP Authentication method" do
|
12
|
+
env = Rack::MockRequest.env_for("/", 'HTTP_AUTHORIZATION' => 'MD5 an_invalid_hash')
|
13
|
+
request = Rack::Auth::WRAP::Request.new(env)
|
14
|
+
request.is_wrap?.should == false
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should tell whether the request is given or not" do
|
18
|
+
wrap_env = Rack::MockRequest.env_for("/", 'HTTP_AUTHORIZATION' => 'WRAP with_token')
|
19
|
+
wrap_request = Rack::Auth::WRAP::Request.new(wrap_env)
|
20
|
+
wrap_request.provided?.should == true
|
21
|
+
|
22
|
+
non_wrap_env = Rack::MockRequest.env_for("/")
|
23
|
+
non_wrap_request = Rack::Auth::WRAP::Request.new(non_wrap_env)
|
24
|
+
non_wrap_request.provided?.should == false
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should return the token unescaped from the request" do
|
28
|
+
simple_web_token = {'Issuer' => 'http://myidentityprovider2/',
|
29
|
+
'Audience' => 'http://site/',
|
30
|
+
'ExpiresOn' => (Time.now.to_i + 60).to_s}.map{|k, v| "#{k}=#{CGI.escape(v)}"}.join("&")
|
31
|
+
|
32
|
+
env = Rack::MockRequest.env_for("/", 'HTTP_AUTHORIZATION' => "WRAP access_token=#{CGI.escape(simple_web_token)}")
|
33
|
+
request = Rack::Auth::WRAP::Request.new(env)
|
34
|
+
request.token.should == simple_web_token
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'spec/specs_config'
|
2
|
+
require 'lib/rack/auth/wrap'
|
3
|
+
|
4
|
+
describe "OAuth WRAP v.0.9.7.2 Authentication Mechanism using SWT on Rack Module" do
|
5
|
+
it "should return 401 with WWW-Authenticate: WRAP when a token is invalid" do
|
6
|
+
env = Rack::MockRequest.env_for("/", 'Authorization' => 'WRAP access_token=invalid_token')
|
7
|
+
|
8
|
+
(mock_app = mock).expects(:call).with(env).never
|
9
|
+
(mock_validator = mock).expects(:valid?).with("invalid_token").returns(false)
|
10
|
+
|
11
|
+
(mock_request = mock).stubs(:token).returns("invalid_token")
|
12
|
+
mock_request.stubs(:provided?).returns(true)
|
13
|
+
mock_request.stubs(:is_wrap?).returns(true)
|
14
|
+
|
15
|
+
Rack::Auth::WRAP::Request.expects(:new).with(env).returns(mock_request)
|
16
|
+
SimpleWebToken::SimpleWebTokenHandler.expects(:new).with(:shared_secret => "foo_bar").returns(mock_validator)
|
17
|
+
|
18
|
+
response_code, headers, body = Rack::Auth::WRAP.new(mock_app, :shared_secret => "foo_bar").call(env)
|
19
|
+
|
20
|
+
response_code.should == 401
|
21
|
+
headers['WWW-Authenticate'].should == 'WRAP'
|
22
|
+
headers['Content-Length'].should == '0'
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should not run assertions when not provided" do
|
26
|
+
env = Rack::MockRequest.env_for("/")
|
27
|
+
(mock_app = mock).expects(:call).with(env).returns([200, {'Content-Length' => '0'}]).once
|
28
|
+
|
29
|
+
SimpleWebToken::SimpleWebTokenHandler.expects(:new).never
|
30
|
+
response_code, headers, body = Rack::Auth::WRAP.new(mock_app, :shared_secret => "foo_bar").call(env)
|
31
|
+
|
32
|
+
response_code.should == 200
|
33
|
+
headers['Content-Length'].should == '0'
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should assign pased token to REMOTE_USER when valid" do
|
37
|
+
env = Rack::MockRequest.env_for("/", 'Authorization' => 'WRAP access_token=token')
|
38
|
+
|
39
|
+
(mock_app = mock).expects(:call).with(env).returns([200, {'Content-Length' => '0'}]).once
|
40
|
+
|
41
|
+
(mock_handler = mock).expects(:valid?).with("token").returns(true)
|
42
|
+
(mock_handler).expects(:parse).with("token").returns({'UserName' => "us3r", "ExpiresOn" => "1234098765"})
|
43
|
+
|
44
|
+
(mock_request = mock).stubs(:token).returns("token")
|
45
|
+
mock_request.stubs(:provided?).returns(true)
|
46
|
+
mock_request.stubs(:is_wrap?).returns(true)
|
47
|
+
|
48
|
+
Rack::Auth::WRAP::Request.expects(:new).with(env).returns(mock_request)
|
49
|
+
SimpleWebToken::SimpleWebTokenHandler.expects(:new).with(:shared_secret => "foo_bar").returns(mock_handler)
|
50
|
+
|
51
|
+
response_code, headers, body = Rack::Auth::WRAP.new(mock_app, :shared_secret => "foo_bar").call(env)
|
52
|
+
|
53
|
+
response_code.should == 200
|
54
|
+
headers['Content-Length'].should == '0'
|
55
|
+
env['REMOTE_USER'].nil?.should == false
|
56
|
+
env['REMOTE_USER']['UserName'].should == 'us3r'
|
57
|
+
env['REMOTE_USER']['ExpiresOn'].nil?.should == false
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
require 'spec/specs_config'
|
2
|
+
require 'lib/simplewebtoken'
|
3
|
+
|
4
|
+
describe "simple web token hanlder behavior" do
|
5
|
+
before do
|
6
|
+
@shared_secret = "N4QeKa3c062VBjnVK6fb+rnwURkcwGXh7EoNK34n0uM="
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should validate hmac256 signature of the token" do
|
10
|
+
simple_web_token = {'Issuer' => 'http://myidentityprovider/',
|
11
|
+
'Audience' => 'http://myapp'}.map{|k, v| "#{k}=#{CGI.escape(v)}"}.join("&")
|
12
|
+
|
13
|
+
signature = Base64.encode64(HMAC::SHA256.new(Base64.decode64(@shared_secret)).update(simple_web_token.toutf8).digest).strip
|
14
|
+
simple_web_token += "&HMACSHA256=#{CGI.escape(signature)}"
|
15
|
+
|
16
|
+
handler = SimpleWebToken::SimpleWebTokenHandler.new(:shared_secret => @shared_secret)
|
17
|
+
handler.valid_signature?(simple_web_token).should == true
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should validate the issuer when a single one given" do
|
21
|
+
simple_web_token = {'Issuer' => 'http://myidentityprovider/',
|
22
|
+
'Audience' => 'http://myapp'}.map{|k, v| "#{k}=#{CGI.escape(v)}"}.join("&")
|
23
|
+
|
24
|
+
handler = SimpleWebToken::SimpleWebTokenHandler.new(:shared_secret => @shared_secret, :trusted_issuers => 'http://myidentityprovider/')
|
25
|
+
handler.valid_issuer?(simple_web_token).should == true
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should validate the issuer when a multiple issuers are trusted" do
|
29
|
+
simple_web_token = {'Issuer' => 'http://myidentityprovider2/',
|
30
|
+
'Audience' => 'http://myapp'}.map{|k, v| "#{k}=#{CGI.escape(v)}"}.join("&")
|
31
|
+
|
32
|
+
trusted_issuers = ["http://myidentityprovider/", "http://myidentityprovider2/"]
|
33
|
+
handler = SimpleWebToken::SimpleWebTokenHandler.new(:shared_secret => @shared_secret, :trusted_issuers => trusted_issuers)
|
34
|
+
handler.valid_issuer?(simple_web_token).should == true
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should validate the audience when a single audience is provided" do
|
38
|
+
simple_web_token = {'Issuer' => 'http://myidentityprovider2/',
|
39
|
+
'Audience' => 'http://myapp'}.map{|k, v| "#{k}=#{CGI.escape(v)}"}.join("&")
|
40
|
+
|
41
|
+
handler = SimpleWebToken::SimpleWebTokenHandler.new(:shared_secret => @shared_secret, :audiences => 'http://myapp')
|
42
|
+
handler.valid_audience?(simple_web_token).should == true
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should validate the audience when a multiple audiences are provided" do
|
46
|
+
simple_web_token = {'Issuer' => 'http://myidentityprovider2/',
|
47
|
+
'Audience' => 'http://site/'}.map{|k, v| "#{k}=#{CGI.escape(v)}"}.join("&")
|
48
|
+
|
49
|
+
audiences = ["http://site/", "http://mysitealias/"]
|
50
|
+
handler = SimpleWebToken::SimpleWebTokenHandler.new(:shared_secret => @shared_secret, :audiences => audiences)
|
51
|
+
handler.valid_audience?(simple_web_token).should == true
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should validate if it's expired" do
|
55
|
+
simple_web_token = {'Issuer' => 'http://myidentityprovider2/',
|
56
|
+
'ExpiresOn' => (Time.now.to_i + 60).to_s}.map{|k, v| "#{k}=#{CGI.escape(v)}"}.join("&")
|
57
|
+
|
58
|
+
handler = SimpleWebToken::SimpleWebTokenHandler.new(:shared_secret => @shared_secret)
|
59
|
+
handler.expired?(simple_web_token).should == false
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should tell that the token is expired" do
|
63
|
+
simple_web_token = {'Issuer' => 'http://myidentityprovider2/',
|
64
|
+
'ExpiresOn' => (Time.now.to_i - 60).to_s}.map{|k, v| "#{k}=#{CGI.escape(v)}"}.join("&")
|
65
|
+
|
66
|
+
signature = Base64.encode64(HMAC::SHA256.new(Base64.decode64(@shared_secret)).update(simple_web_token.toutf8).digest).strip
|
67
|
+
simple_web_token += "&HMACSHA256=#{CGI.escape(signature)}"
|
68
|
+
|
69
|
+
handler = SimpleWebToken::SimpleWebTokenHandler.new(:shared_secret => @shared_secret)
|
70
|
+
handler.expired?(simple_web_token).should == true
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should throw an exception when no shared secret is provided" do
|
74
|
+
lambda {SimpleWebToken::SimpleWebTokenHandler.new}.should raise_error SimpleWebToken::InvalidOption
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should tell that a token is valid when all their components are valid" do
|
78
|
+
simple_web_token = {'Issuer' => 'http://myidentityprovider2/',
|
79
|
+
'Audience' => 'http://site/',
|
80
|
+
'ExpiresOn' => (Time.now.to_i + 60).to_s}.map{|k, v| "#{k}=#{CGI.escape(v)}"}.join("&")
|
81
|
+
|
82
|
+
signature = Base64.encode64(HMAC::SHA256.new(Base64.decode64(@shared_secret)).update(simple_web_token.toutf8).digest).strip
|
83
|
+
simple_web_token += "&HMACSHA256=#{CGI.escape(signature)}"
|
84
|
+
|
85
|
+
handler = SimpleWebToken::SimpleWebTokenHandler.new(:shared_secret => @shared_secret)
|
86
|
+
handler.valid?(simple_web_token).should == true
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should tell that a token is invalid when no HMAC signature is provided" do
|
90
|
+
simple_web_token = {'Issuer' => 'http://myidentityprovider2/',
|
91
|
+
'Audience' => 'http://site/',
|
92
|
+
'ExpiresOn' => (Time.now.to_i + 60).to_s}.map{|k, v| "#{k}=#{CGI.escape(v)}"}.join("&")
|
93
|
+
|
94
|
+
handler = SimpleWebToken::SimpleWebTokenHandler.new(:shared_secret => @shared_secret)
|
95
|
+
handler.valid?(simple_web_token).should == false
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should tell that a token is invalid when the token is expired" do
|
99
|
+
simple_web_token = {'Issuer' => 'http://myidentityprovider2/',
|
100
|
+
'Audience' => 'http://site/',
|
101
|
+
'ExpiresOn' => (Time.now.to_i - 60).to_s}.map{|k, v| "#{k}=#{CGI.escape(v)}"}.join("&")
|
102
|
+
|
103
|
+
signature = Base64.encode64(HMAC::SHA256.new(Base64.decode64(@shared_secret)).update(simple_web_token.toutf8).digest).strip
|
104
|
+
simple_web_token += "&HMACSHA256=#{CGI.escape(signature)}"
|
105
|
+
|
106
|
+
handler = SimpleWebToken::SimpleWebTokenHandler.new(:shared_secret => @shared_secret)
|
107
|
+
handler.valid?(simple_web_token).should == false
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should tell that a token is invalid when audience isn't trusted" do
|
111
|
+
simple_web_token = {'Issuer' => 'http://myidentityprovider2/',
|
112
|
+
'Audience' => 'http://site/',
|
113
|
+
'ExpiresOn' => (Time.now.to_i + 60).to_s}.map{|k, v| "#{k}=#{CGI.escape(v)}"}.join("&")
|
114
|
+
|
115
|
+
signature = Base64.encode64(HMAC::SHA256.new(Base64.decode64(@shared_secret)).update(simple_web_token.toutf8).digest).strip
|
116
|
+
simple_web_token += "&HMACSHA256=#{CGI.escape(signature)}"
|
117
|
+
|
118
|
+
handler = SimpleWebToken::SimpleWebTokenHandler.new(:shared_secret => @shared_secret,
|
119
|
+
:audiences => "http://untrusted_audience/")
|
120
|
+
handler.valid?(simple_web_token).should == false
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should tell that a token is invalid when audience isn't trusted" do
|
124
|
+
simple_web_token = {'Issuer' => 'http://myidentityprovider2/',
|
125
|
+
'Audience' => 'http://site/',
|
126
|
+
'ExpiresOn' => (Time.now.to_i + 60).to_s}.map{|k, v| "#{k}=#{CGI.escape(v)}"}.join("&")
|
127
|
+
|
128
|
+
signature = Base64.encode64(HMAC::SHA256.new(Base64.decode64(@shared_secret)).update(simple_web_token.toutf8).digest).strip
|
129
|
+
simple_web_token += "&HMACSHA256=#{CGI.escape(signature)}"
|
130
|
+
|
131
|
+
handler = SimpleWebToken::SimpleWebTokenHandler.new(:shared_secret => @shared_secret,
|
132
|
+
:trusted_issuers => "http://untrusted_issuer")
|
133
|
+
handler.valid?(simple_web_token).should == false
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should raise invalid token exception while trying to parse a token that isnt valid" do
|
137
|
+
simple_web_token = {'Issuer' => 'http://myidentityprovider2/',
|
138
|
+
'Audience' => 'http://site/',
|
139
|
+
'ExpiresOn' => (Time.now.to_i + 60).to_s}.map{|k, v| "#{k}=#{CGI.escape(v)}"}.join("&")
|
140
|
+
|
141
|
+
signature = Base64.encode64(HMAC::SHA256.new(Base64.decode64(@shared_secret)).update(simple_web_token.toutf8).digest).strip
|
142
|
+
simple_web_token += "&HMACSHA256=#{CGI.escape(signature)}"
|
143
|
+
|
144
|
+
handler = SimpleWebToken::SimpleWebTokenHandler.new(:shared_secret => @shared_secret,
|
145
|
+
:trusted_issuers => "http://untrusted_issuer")
|
146
|
+
|
147
|
+
lambda{ handler.parse(simple_web_token) }.should raise_error SimpleWebToken::InvalidToken
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
it "should return a dictionary when parsing a valid token" do
|
152
|
+
simple_web_token = {'Issuer' => 'http://myidentityprovider2/',
|
153
|
+
'Audience' => 'http://site/',
|
154
|
+
'org.security.email' => "myemail@mydomain.com",
|
155
|
+
'ExpiresOn' => (Time.now.to_i + 60).to_s}.map{|k, v| "#{CGI.escape(k)}=#{CGI.escape(v)}"}.join("&")
|
156
|
+
|
157
|
+
signature = Base64.encode64(HMAC::SHA256.new(Base64.decode64(@shared_secret)).update(simple_web_token.toutf8).digest).strip
|
158
|
+
simple_web_token += "&HMACSHA256=#{CGI.escape(signature)}"
|
159
|
+
|
160
|
+
handler = SimpleWebToken::SimpleWebTokenHandler.new(:shared_secret => @shared_secret)
|
161
|
+
token = handler.parse(simple_web_token)
|
162
|
+
|
163
|
+
token.nil?.should == false
|
164
|
+
token['Audience'].should == "http://site/"
|
165
|
+
token['org.security.email'].should == "myemail@mydomain.com"
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should return a values as tokens when sent as csv" do
|
169
|
+
simple_web_token = {'Issuer' => 'http://myidentityprovider2/',
|
170
|
+
'Audience' => 'http://site/',
|
171
|
+
'org.security.email' => "myemail@mydomain.com",
|
172
|
+
'Roles' => 'roleA, roleB, role1, role2',
|
173
|
+
'ExpiresOn' => (Time.now.to_i + 60).to_s}.map{|k, v| "#{CGI.escape(k)}=#{CGI.escape(v)}"}.join("&")
|
174
|
+
|
175
|
+
signature = Base64.encode64(HMAC::SHA256.new(Base64.decode64(@shared_secret)).update(simple_web_token.toutf8).digest).strip
|
176
|
+
simple_web_token += "&HMACSHA256=#{CGI.escape(signature)}"
|
177
|
+
|
178
|
+
handler = SimpleWebToken::SimpleWebTokenHandler.new(:shared_secret => @shared_secret)
|
179
|
+
token = handler.parse(simple_web_token)
|
180
|
+
|
181
|
+
token.nil?.should == false
|
182
|
+
token['Roles'].should == ["roleA", "roleB", "role1", "role2"]
|
183
|
+
end
|
184
|
+
end
|
metadata
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack-oauth-wrap
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Johnny G. Halife & Juan Pablo Garcia Dalolla
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-02-19 00:00:00 -03:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: ruby-hmac
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description: A simple implementation of Web Resource Authorization Protocol (WRAP) for Rack as middleware.
|
26
|
+
email: johnny.halife@me.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files: []
|
32
|
+
|
33
|
+
files:
|
34
|
+
- rakefile
|
35
|
+
- lib/rack/auth/wrap.rb
|
36
|
+
- lib/simplewebtoken.rb
|
37
|
+
- lib/swt/exceptions.rb
|
38
|
+
- lib/swt/simple_web_token_handler.rb
|
39
|
+
has_rdoc: true
|
40
|
+
homepage: http://rack-oauth-wrap.heroku.com
|
41
|
+
licenses: []
|
42
|
+
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options:
|
45
|
+
- --line-numbers
|
46
|
+
- --inline-source
|
47
|
+
- -A cattr_accessor=object
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: "0"
|
55
|
+
version:
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: "0"
|
61
|
+
version:
|
62
|
+
requirements: []
|
63
|
+
|
64
|
+
rubyforge_project:
|
65
|
+
rubygems_version: 1.3.5
|
66
|
+
signing_key:
|
67
|
+
specification_version: 3
|
68
|
+
summary: Rack Middleware for authenticating users using oAuth WRAP Protocol
|
69
|
+
test_files:
|
70
|
+
- spec/rack/auth/wrap_request_spec.rb
|
71
|
+
- spec/rack/auth/wrap_spec.rb
|
72
|
+
- spec/specs_config.rb
|
73
|
+
- spec/swt/simple_web_token_handler_spec.rb
|