kennedy 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/.yardoc +0 -0
- data/LICENSE +20 -0
- data/MAIN.rdoc +23 -0
- data/README.markdown +17 -0
- data/Rakefile +52 -0
- data/VERSION +1 -0
- data/bin/kennedy-gen +13 -0
- data/doc/Kennedy.html +98 -0
- data/doc/Kennedy/Backends.html +94 -0
- data/doc/Kennedy/Backends/LDAP.html +471 -0
- data/doc/Kennedy/BadTicketException.html +92 -0
- data/doc/Kennedy/Granter.html +570 -0
- data/doc/Kennedy/Server.html +258 -0
- data/doc/Kennedy/Ticket.html +875 -0
- data/doc/_index.html +170 -0
- data/doc/class_list.html +97 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +23 -0
- data/doc/css/style.css +261 -0
- data/doc/file.README.html +69 -0
- data/doc/file_list.html +29 -0
- data/doc/index.html +69 -0
- data/doc/js/app.js +91 -0
- data/doc/js/full_list.js +39 -0
- data/doc/js/jquery.js +19 -0
- data/doc/method_list.html +152 -0
- data/doc/top-level-namespace.html +80 -0
- data/kennedy.gemspec +114 -0
- data/lib/kennedy.rb +2 -0
- data/lib/kennedy/backends/ldap.rb +35 -0
- data/lib/kennedy/generator.rb +69 -0
- data/lib/kennedy/granter.rb +52 -0
- data/lib/kennedy/instance_configuration.rb +58 -0
- data/lib/kennedy/server.rb +164 -0
- data/lib/kennedy/ticket.rb +97 -0
- data/logo.png +0 -0
- data/template/config.ru.erb +22 -0
- data/template/config/api_keys.yml.erb +1 -0
- data/template/config/backend.rb +0 -0
- data/template/config/encryption.yml.erb +2 -0
- data/template/config/sessions.yml.erb +1 -0
- data/test/granter_test.rb +93 -0
- data/test/ldap_backend_test.rb +66 -0
- data/test/server_test.rb +285 -0
- data/test/teststrap.rb +34 -0
- data/test/ticket_test.rb +84 -0
- metadata +177 -0
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'json'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module Kennedy
|
6
|
+
class BadTicketException < RuntimeError; end
|
7
|
+
|
8
|
+
# A ticket represents a time-constrained period in which an authenticated
|
9
|
+
# person can access a service
|
10
|
+
class Ticket
|
11
|
+
DefaultExpiry = 30 # In seconds
|
12
|
+
attr_reader :identifier
|
13
|
+
|
14
|
+
class << self
|
15
|
+
private :new
|
16
|
+
end
|
17
|
+
|
18
|
+
# Creates a new ticket with the given arguments
|
19
|
+
# @param [Hash] args The arguments to generate the ticket with
|
20
|
+
# @option args [String] :identifier An identifier to use in the ticket
|
21
|
+
# @option args [String] :iv An iv to use to encrypt and decrypt the ticket
|
22
|
+
# @option args [String] :passphrase A passphrase to encrypt and decrypt the ticket
|
23
|
+
# @option args [String] :expiry A length of time in seconds for which this ticket is valid
|
24
|
+
# after to_encrypted is called
|
25
|
+
def self.create(args = {})
|
26
|
+
identifier = args[:identifier] || raise(ArgumentError, "Ticket identifier must be given as :identifier")
|
27
|
+
ticket = new(:iv => args[:iv], :passphrase => args[:passphrase], :expiry => args[:expiry])
|
28
|
+
ticket.identifier = identifier
|
29
|
+
ticket
|
30
|
+
end
|
31
|
+
|
32
|
+
# Decrypts a ticket from the given arguments
|
33
|
+
# @param [Hash] args The arguments to build the ticket with
|
34
|
+
# @option args [String] :data An encrypted ticket
|
35
|
+
# @option args [String] :iv An IV to use to decrypt the ticket
|
36
|
+
# @option args [String] :passphrase A passphrase to use to decrypt the ticket
|
37
|
+
def self.from_encrypted(args = {})
|
38
|
+
data = args[:data] || raise(ArgumentError, "Data must be given as :data")
|
39
|
+
ticket = new(:iv => args[:iv], :passphrase => args[:passphrase])
|
40
|
+
ticket.decrypt(data)
|
41
|
+
ticket
|
42
|
+
end
|
43
|
+
|
44
|
+
# @param [Hash] args The arguments to construct the ticket with
|
45
|
+
# @option args [String] :iv An iv to use to encrypt and decrypt the ticket
|
46
|
+
# @option args [String] :passphrase A passphrase to encrypt and decrypt the ticket
|
47
|
+
def initialize(args = {})
|
48
|
+
@iv = args[:iv] || raise(ArgumentError, "Ticket encryption IV must be given as :iv")
|
49
|
+
@passphrase = args[:passphrase] || raise(ArgumentError, "Ticket encryption passphrase must be given as :passphrase")
|
50
|
+
@expiry = args[:expiry] || DefaultExpiry
|
51
|
+
end
|
52
|
+
|
53
|
+
def identifier=(identifier)
|
54
|
+
@identifier ||= identifier
|
55
|
+
end
|
56
|
+
|
57
|
+
# Generates an encrypted chunk of JSON with the identifier and expiration time for this
|
58
|
+
# ticket encoded in
|
59
|
+
# @return [String] An encrypted JSON string
|
60
|
+
def to_encrypted
|
61
|
+
cipher = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
|
62
|
+
cipher.encrypt
|
63
|
+
cipher.key = @passphrase
|
64
|
+
cipher.iv = @iv
|
65
|
+
encrypted = cipher.update(to_expiring_json)
|
66
|
+
encrypted << cipher.final
|
67
|
+
encrypted
|
68
|
+
end
|
69
|
+
|
70
|
+
# Decrypts the given ticket data
|
71
|
+
# @param data [String] The ticket data to decrypt
|
72
|
+
def decrypt(data)
|
73
|
+
cipher = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
|
74
|
+
cipher.decrypt
|
75
|
+
cipher.key = @passphrase
|
76
|
+
cipher.iv = @iv
|
77
|
+
decrypted = cipher.update(data)
|
78
|
+
decrypted << cipher.final
|
79
|
+
json = JSON.parse(decrypted)
|
80
|
+
self.identifier = json['identifier']
|
81
|
+
@expiry = Time.parse(json['expiry'])
|
82
|
+
rescue OpenSSL::Cipher::CipherError => e
|
83
|
+
raise Kennedy::BadTicketException, "Given data was not decryptable"
|
84
|
+
end
|
85
|
+
|
86
|
+
def expired?
|
87
|
+
!@expiry.nil? && (@expiry < Time.now)
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def to_expiring_json
|
93
|
+
{'identifier' => @identifier, 'expiry' => Time.now + @expiry}.to_json
|
94
|
+
end
|
95
|
+
|
96
|
+
end # Ticket
|
97
|
+
end # Kennedy
|
data/logo.png
ADDED
Binary file
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Rackup file auto-generated by kennedy-gen at <%= Time.now %>
|
2
|
+
here = File.dirname(__FILE__)
|
3
|
+
bundler_env = File.join(here, 'vendor/gems/environment.rb')
|
4
|
+
config_dir = File.join(here, 'config')
|
5
|
+
|
6
|
+
if File.exist?(bundler_env)
|
7
|
+
load bundler_env
|
8
|
+
else
|
9
|
+
require 'rubygems'
|
10
|
+
end
|
11
|
+
|
12
|
+
RACK_ENV = ENV["RACK_ENV"] ||= "development" unless defined?(RACK_ENV)
|
13
|
+
|
14
|
+
require 'kennedy/server'
|
15
|
+
require 'kennedy/instance_configuration'
|
16
|
+
|
17
|
+
config = Kennedy::InstanceConfiguration.load_config(config_dir)
|
18
|
+
Kennedy::ServerInstance = Kennedy::Server.create(:encryption => {:iv => config.encryption['iv'], :passphrase => config.encryption['passphrase']},
|
19
|
+
:backend => config.backend, :session_secret => config.session_secret,
|
20
|
+
:api_keys => config.api_keys, :require_ssl => !(ENV['RACK_ENV'] == 'development'))
|
21
|
+
|
22
|
+
run Kennedy::ServerInstance
|
@@ -0,0 +1 @@
|
|
1
|
+
"foo@example.com": <%= Digest::SHA1.hexdigest("#{Time.now}#{Time.now.usec}") %>
|
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
secret: <%= Digest::SHA1.hexdigest("#{Time.now}#{Time.now.usec}") %>
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'teststrap'
|
2
|
+
require 'digest/sha1'
|
3
|
+
|
4
|
+
context "kennedy granter" do
|
5
|
+
|
6
|
+
should "raise an exception if not given an IV" do
|
7
|
+
Kennedy::Granter.new(:passphrase => Digest::SHA1.hexdigest(Time.now.to_i.to_s),
|
8
|
+
:backend => Object.new)
|
9
|
+
end.raises(ArgumentError, "Encryption IV must be given as :iv")
|
10
|
+
|
11
|
+
should "raise an exception if not given a passphrase" do
|
12
|
+
Kennedy::Granter.new(:iv => Digest::SHA1.hexdigest(Time.now.to_i.to_s),
|
13
|
+
:backend => Object.new)
|
14
|
+
end.raises(ArgumentError, "Encryption passphrase must be given as :passphrase")
|
15
|
+
|
16
|
+
should "raise an exception if not given a backend" do
|
17
|
+
Kennedy::Granter.new(:iv => Digest::SHA1.hexdigest(Time.now.to_i.to_s),
|
18
|
+
:passphrase => Digest::SHA1.hexdigest(Time.now.to_i.to_s))
|
19
|
+
end.raises(ArgumentError, "Authentication backend must be given as :backend")
|
20
|
+
|
21
|
+
context "with valid arguments and a given backend" do
|
22
|
+
|
23
|
+
setup do
|
24
|
+
@granter = Kennedy::Granter.new(:iv => Digest::SHA1.hexdigest(Time.now.to_i.to_s),
|
25
|
+
:passphrase => Digest::SHA1.hexdigest(Time.now.to_i.to_s),
|
26
|
+
:backend => @backend = StubBackend.new)
|
27
|
+
end
|
28
|
+
|
29
|
+
should "use the given backend to authenticate" do
|
30
|
+
@granter.authenticate(:identifier => "foo", :password => "bar")
|
31
|
+
@backend.credentials
|
32
|
+
end.equals(["foo", "bar"])
|
33
|
+
|
34
|
+
should "return true with valid credentials" do
|
35
|
+
@granter.authenticate(:identifier => "foo", :password => "bar") == true
|
36
|
+
end
|
37
|
+
|
38
|
+
should "return false with invalid credentials" do
|
39
|
+
@granter.authenticate(:identifier => "foo", :password => "baz") == false
|
40
|
+
end
|
41
|
+
|
42
|
+
end # with valid arguments and a given backend
|
43
|
+
|
44
|
+
context "generating a ticket for a service" do
|
45
|
+
context "with default expiry time" do
|
46
|
+
setup do
|
47
|
+
@granter = Kennedy::Granter.new(:iv => Digest::SHA1.hexdigest(Time.now.to_i.to_s),
|
48
|
+
:passphrase => Digest::SHA1.hexdigest(Time.now.to_i.to_s),
|
49
|
+
:backend => @backend = StubBackend.new)
|
50
|
+
end
|
51
|
+
|
52
|
+
should "require an identifier to generate a ticket" do
|
53
|
+
@granter.generate_ticket
|
54
|
+
end.raises(ArgumentError, "An identifier must be given as :identifier")
|
55
|
+
|
56
|
+
should "return a ticket object when granting" do
|
57
|
+
@granter.generate_ticket(:identifier => "foo@example.com")
|
58
|
+
end.kind_of(Kennedy::Ticket)
|
59
|
+
|
60
|
+
end # with default expiry time
|
61
|
+
end # generating a ticket for a service
|
62
|
+
|
63
|
+
context "reading an encrypted ticket" do
|
64
|
+
setup do
|
65
|
+
@granter = Kennedy::Granter.new(:iv => Digest::SHA1.hexdigest(Time.now.to_i.to_s),
|
66
|
+
:passphrase => Digest::SHA1.hexdigest(Time.now.to_i.to_s),
|
67
|
+
:backend => @backend = StubBackend.new)
|
68
|
+
end
|
69
|
+
|
70
|
+
should "require data to read a ticket" do
|
71
|
+
@granter.read_ticket
|
72
|
+
end.raises(ArgumentError, "Data must be given as :data")
|
73
|
+
|
74
|
+
context "with gibberish data" do
|
75
|
+
should "raise an exception" do
|
76
|
+
@granter.read_ticket(:data => "bzzt")
|
77
|
+
end.raises(Kennedy::BadTicketException)
|
78
|
+
end
|
79
|
+
|
80
|
+
context "with valid data" do
|
81
|
+
setup do
|
82
|
+
encrypted_ticket = @granter.generate_ticket(:identifier => "foo@example.com").to_encrypted
|
83
|
+
@granter.read_ticket(:data => encrypted_ticket)
|
84
|
+
end
|
85
|
+
|
86
|
+
should "be a kennedy ticket" do
|
87
|
+
topic
|
88
|
+
end.kind_of(Kennedy::Ticket)
|
89
|
+
end
|
90
|
+
end # reading an encrypted ticket
|
91
|
+
|
92
|
+
end
|
93
|
+
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'teststrap'
|
2
|
+
|
3
|
+
context "kennedy ldap backend" do
|
4
|
+
|
5
|
+
should "require a :host argument" do
|
6
|
+
Kennedy::Backends::LDAP.new(:auth => {}, :base => "cn=foo")
|
7
|
+
end.raises(ArgumentError, "Host must be given as :host")
|
8
|
+
|
9
|
+
should "require an :auth argument" do
|
10
|
+
Kennedy::Backends::LDAP.new(:host => "example.com", :base => "cn=foo")
|
11
|
+
end.raises(ArgumentError, "Auth must be given as :auth")
|
12
|
+
|
13
|
+
should "require a :base argument" do
|
14
|
+
Kennedy::Backends::LDAP.new(:host => "example.com", :auth => {})
|
15
|
+
end.raises(ArgumentError, "Base must be given as :base")
|
16
|
+
|
17
|
+
should "raise if no filter block is given and authentication is attempted" do
|
18
|
+
backend = Kennedy::Backends::LDAP.new(:host => "example.com", :auth => {}, :base => "foo")
|
19
|
+
backend.authenticate("foo", "bar")
|
20
|
+
end.raises(ArgumentError, "Set a filter block on this object using the 'filter' writer")
|
21
|
+
|
22
|
+
context "trying to authenticate" do
|
23
|
+
|
24
|
+
setup do
|
25
|
+
@backend = Kennedy::Backends::LDAP.new(:host => "example.com", :auth => {}, :base => "foo")
|
26
|
+
@backend.filter = lambda do |identifier|
|
27
|
+
"(mail=#{identifier})"
|
28
|
+
end
|
29
|
+
ldap_conn = nil
|
30
|
+
@backend.instance_eval { ldap_conn = @ldap_conn = StubLDAP.new(nil) }
|
31
|
+
ldap_conn
|
32
|
+
end
|
33
|
+
|
34
|
+
should "use the given filter block to generate the search filter" do
|
35
|
+
@backend.authenticate("foo@example.com", "bar")
|
36
|
+
topic.bind_as_arguments[:filter]
|
37
|
+
end.equals("(mail=foo@example.com)")
|
38
|
+
|
39
|
+
should "use the given password to bind as" do
|
40
|
+
@backend.authenticate("foo@example.com", "bar")
|
41
|
+
topic.bind_as_arguments[:password]
|
42
|
+
end.equals("bar")
|
43
|
+
|
44
|
+
context "succesfully" do
|
45
|
+
setup do
|
46
|
+
@backend.instance_eval { @ldap_conn = StubLDAP.new(Object.new) }
|
47
|
+
end
|
48
|
+
|
49
|
+
should "return true when the backend returns a non-nil value" do
|
50
|
+
@backend.authenticate("foo@example.com", "bar") == true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "unsuccesfully" do
|
55
|
+
setup do
|
56
|
+
@backend.instance_eval { @ldap_conn = StubLDAP.new(nil) }
|
57
|
+
end
|
58
|
+
|
59
|
+
should "return false when the backend returns nil" do
|
60
|
+
@backend.authenticate("foo@example.com", "bar") == false
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
data/test/server_test.rb
ADDED
@@ -0,0 +1,285 @@
|
|
1
|
+
require 'teststrap'
|
2
|
+
require 'kennedy/server'
|
3
|
+
require 'digest/sha1'
|
4
|
+
require 'base64'
|
5
|
+
|
6
|
+
context "kennedy server" do
|
7
|
+
iv = Digest::SHA1.hexdigest("what")
|
8
|
+
passphrase = Digest::SHA1.hexdigest("qhat")
|
9
|
+
session_secret = "foobarbaz"
|
10
|
+
|
11
|
+
new_backend = lambda do
|
12
|
+
StubBackend.new
|
13
|
+
end
|
14
|
+
|
15
|
+
new_server = lambda do
|
16
|
+
Kennedy::Server.create(:encryption => {:iv => iv, :passphrase => passphrase},
|
17
|
+
:backend => new_backend, :session_secret => "foobarbaz",
|
18
|
+
:api_keys => {'foo@example.com' => 'password'})
|
19
|
+
end
|
20
|
+
|
21
|
+
encode_credentials = lambda do |username,password|
|
22
|
+
"Basic " + Base64.encode64("#{username}:#{password}")
|
23
|
+
end
|
24
|
+
|
25
|
+
should "use tamper-proof cookies" do
|
26
|
+
new_server[].middleware.detect do |mw|
|
27
|
+
mw[0] == Rack::Session::Cookie && !mw[1][0][:secret].nil?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "delete to /session" do
|
32
|
+
setup do
|
33
|
+
@server = Rack::MockRequest.new(new_server[])
|
34
|
+
end
|
35
|
+
|
36
|
+
should "not allow non-ssl connections" do
|
37
|
+
@server.delete('/session').status
|
38
|
+
end.equals(403)
|
39
|
+
|
40
|
+
context "via ssl" do
|
41
|
+
setup do
|
42
|
+
@server = SSLMockRequest.new(new_server[])
|
43
|
+
@server.delete('/session', 'CONTENT_TYPE' => 'application/json')
|
44
|
+
end
|
45
|
+
|
46
|
+
should "return a 200" do
|
47
|
+
topic.status
|
48
|
+
end.equals(200)
|
49
|
+
|
50
|
+
should "return success" do
|
51
|
+
JSON.parse(topic.body)['success']
|
52
|
+
end.equals('session_destroyed')
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context "post to /session" do
|
58
|
+
setup do
|
59
|
+
@server = Rack::MockRequest.new(new_server[])
|
60
|
+
end
|
61
|
+
|
62
|
+
should "not allow non-ssl connections" do
|
63
|
+
@server.post('/session').status
|
64
|
+
end.equals(403)
|
65
|
+
|
66
|
+
context "via ssl" do
|
67
|
+
setup do
|
68
|
+
@server = SSLMockRequest.new(new_server[])
|
69
|
+
end
|
70
|
+
|
71
|
+
should "not allow non-JSON requests" do
|
72
|
+
@server.post('/session').status
|
73
|
+
end.equals(415)
|
74
|
+
|
75
|
+
context "with invalid credentials" do
|
76
|
+
setup do
|
77
|
+
@server.post('/session', 'CONTENT_TYPE' => 'application/json', :input => {}.to_json)
|
78
|
+
end
|
79
|
+
|
80
|
+
should "return a 406" do
|
81
|
+
topic.status
|
82
|
+
end.equals(406)
|
83
|
+
|
84
|
+
should "return json" do
|
85
|
+
topic.content_type
|
86
|
+
end.equals("application/json")
|
87
|
+
|
88
|
+
should "return an error" do
|
89
|
+
JSON.parse(topic.body)['error']
|
90
|
+
end.equals('bad_credentials')
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
context "with valid credentials" do
|
95
|
+
setup do
|
96
|
+
@server.post('/session', 'CONTENT_TYPE' => 'application/json', :input => {'credentials' => {'identifier' => 'foo', 'password' => 'bar'}}.to_json)
|
97
|
+
end
|
98
|
+
|
99
|
+
should "return a 201" do
|
100
|
+
topic.status
|
101
|
+
end.equals(201)
|
102
|
+
|
103
|
+
should "return json" do
|
104
|
+
topic.content_type
|
105
|
+
end.equals("application/json")
|
106
|
+
|
107
|
+
should "return success" do
|
108
|
+
JSON.parse(topic.body)['success']
|
109
|
+
end.equals('session_created')
|
110
|
+
|
111
|
+
should "set a session cookie" do
|
112
|
+
topic.headers['Set-Cookie']
|
113
|
+
end.matches(/rack\.session=/)
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
end # post to /session
|
119
|
+
|
120
|
+
context "get to /session" do
|
121
|
+
setup do
|
122
|
+
@server = Rack::MockRequest.new(new_server[])
|
123
|
+
end
|
124
|
+
|
125
|
+
should "not allow non-ssl connections" do
|
126
|
+
@server.get('/session').status
|
127
|
+
end.equals(403)
|
128
|
+
|
129
|
+
context "via ssl" do
|
130
|
+
setup do
|
131
|
+
@server = SSLMockRequest.new(new_server[])
|
132
|
+
end
|
133
|
+
|
134
|
+
should "not allow non-JSON requests" do
|
135
|
+
@server.get('/session').status
|
136
|
+
end.equals(415)
|
137
|
+
|
138
|
+
context "when already logged in" do
|
139
|
+
setup do
|
140
|
+
response = @server.post('/session', 'CONTENT_TYPE' => 'application/json', :input => {'credentials' => {'identifier' => 'foo', 'password' => 'bar'}}.to_json)
|
141
|
+
cookie = response.headers['Set-Cookie'].split(";").first
|
142
|
+
@server.get('/session', 'CONTENT_TYPE' => 'application/json', 'HTTP_COOKIE' => cookie)
|
143
|
+
end
|
144
|
+
|
145
|
+
should "return json" do
|
146
|
+
topic.content_type
|
147
|
+
end.equals("application/json")
|
148
|
+
|
149
|
+
should "respond with a ticket" do
|
150
|
+
JSON.parse(topic.body)['ticket']
|
151
|
+
end.kind_of(String)
|
152
|
+
|
153
|
+
end # when already logged in
|
154
|
+
|
155
|
+
context "when not logged in" do
|
156
|
+
setup do
|
157
|
+
@server.get('/session', 'CONTENT_TYPE' => 'application/json')
|
158
|
+
end
|
159
|
+
|
160
|
+
should "return json" do
|
161
|
+
topic.content_type
|
162
|
+
end.equals("application/json")
|
163
|
+
|
164
|
+
should "return a 401" do
|
165
|
+
topic.status
|
166
|
+
end.equals(401)
|
167
|
+
|
168
|
+
end # when not logged in
|
169
|
+
end # via ssl
|
170
|
+
end # get to /session
|
171
|
+
|
172
|
+
context "post to /validation_request" do
|
173
|
+
setup do
|
174
|
+
@server = Rack::MockRequest.new(new_server[])
|
175
|
+
end
|
176
|
+
|
177
|
+
should "not allow non-ssl connection" do
|
178
|
+
@server.post('/validation_request').status
|
179
|
+
end.equals(403)
|
180
|
+
|
181
|
+
context "via ssl" do
|
182
|
+
setup do
|
183
|
+
@server = SSLMockRequest.new(new_server[])
|
184
|
+
end
|
185
|
+
|
186
|
+
should "not allow non-JSON requests" do
|
187
|
+
@server.post('/validation_request').status
|
188
|
+
end.equals(415)
|
189
|
+
|
190
|
+
context "with no API key" do
|
191
|
+
setup do
|
192
|
+
@server.post('/validation_request', 'CONTENT_TYPE' => 'application/json', :input => {'ticket' => '123'}.to_json)
|
193
|
+
end
|
194
|
+
|
195
|
+
should "return a 401" do
|
196
|
+
topic.status
|
197
|
+
end.equals(401)
|
198
|
+
|
199
|
+
should "return an error" do
|
200
|
+
JSON.parse(topic.body)['error']
|
201
|
+
end.equals('authentication_required')
|
202
|
+
end
|
203
|
+
|
204
|
+
context "with a bad API key" do
|
205
|
+
|
206
|
+
setup do
|
207
|
+
@server.post('/validation_request', 'CONTENT_TYPE' => 'application/json', 'HTTP_AUTHORIZATION'=> encode_credentials['foo@example.com', 'badpassword'],
|
208
|
+
:input => {'ticket' => '123'}.to_json)
|
209
|
+
end
|
210
|
+
|
211
|
+
should "return a 401" do
|
212
|
+
topic.status
|
213
|
+
end.equals(401)
|
214
|
+
|
215
|
+
should "return an error" do
|
216
|
+
JSON.parse(topic.body)['error']
|
217
|
+
end.equals('authentication_required')
|
218
|
+
|
219
|
+
end
|
220
|
+
|
221
|
+
context "with a valid ticket" do
|
222
|
+
setup do
|
223
|
+
granter = Kennedy::Granter.new(:iv => iv, :passphrase => passphrase, :backend => StubBackend.new)
|
224
|
+
ticket = granter.generate_ticket(:identifier => "foo@example.com")
|
225
|
+
@server.post('/validation_request', 'CONTENT_TYPE' => 'application/json', 'HTTP_AUTHORIZATION'=> encode_credentials['foo@example.com', 'password'],
|
226
|
+
:input => {'ticket' => Base64.encode64(ticket.to_encrypted)}.to_json)
|
227
|
+
end
|
228
|
+
|
229
|
+
should "return json" do
|
230
|
+
topic.content_type
|
231
|
+
end.equals("application/json")
|
232
|
+
|
233
|
+
should "return a 200" do
|
234
|
+
topic.status
|
235
|
+
end.equals(200)
|
236
|
+
|
237
|
+
should "return an identifier" do
|
238
|
+
JSON.parse(topic.body)['identifier']
|
239
|
+
end.equals('foo@example.com')
|
240
|
+
|
241
|
+
end
|
242
|
+
|
243
|
+
context "with gibberish" do
|
244
|
+
setup do
|
245
|
+
@server.post('/validation_request', 'CONTENT_TYPE' => 'application/json', 'HTTP_AUTHORIZATION'=> encode_credentials['foo@example.com', 'password'],
|
246
|
+
:input => {'ticket' => 'bzzt'}.to_json)
|
247
|
+
end
|
248
|
+
|
249
|
+
should "return json" do
|
250
|
+
topic.content_type
|
251
|
+
end.equals("application/json")
|
252
|
+
|
253
|
+
should "return a 406" do
|
254
|
+
topic.status
|
255
|
+
end.equals(406)
|
256
|
+
|
257
|
+
should "return an error" do
|
258
|
+
JSON.parse(topic.body)['error']
|
259
|
+
end.equals('bad_ticket')
|
260
|
+
|
261
|
+
end
|
262
|
+
|
263
|
+
context "with an expired ticket" do
|
264
|
+
setup do
|
265
|
+
ticket = Kennedy::Ticket.create(:identifier => "foo@example.com", :iv => iv, :expiry => -30, :passphrase => passphrase)
|
266
|
+
@server.post('/validation_request', 'CONTENT_TYPE' => 'application/json', 'HTTP_AUTHORIZATION'=> encode_credentials['foo@example.com', 'password'],
|
267
|
+
:input => {'ticket' => Base64.encode64(ticket.to_encrypted)}.to_json)
|
268
|
+
end
|
269
|
+
|
270
|
+
should "return json" do
|
271
|
+
topic.content_type
|
272
|
+
end.equals("application/json")
|
273
|
+
|
274
|
+
should "return a 406" do
|
275
|
+
topic.status
|
276
|
+
end.equals(406)
|
277
|
+
|
278
|
+
should "return an error" do
|
279
|
+
JSON.parse(topic.body)['error']
|
280
|
+
end.equals('expired_ticket')
|
281
|
+
end
|
282
|
+
end # via ssl
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|