rack-oauth-wrap 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|