kennedy 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/.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
|
+
|