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,80 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
2
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
3
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
4
|
+
<head>
|
5
|
+
<meta name="Content-Type" content="text/html; charset=UTF-8" />
|
6
|
+
<title>Top Level Namespace</title>
|
7
|
+
<link rel="stylesheet" href="css/style.css" type="text/css" media="screen" charset="utf-8" />
|
8
|
+
<link rel="stylesheet" href="css/common.css" type="text/css" media="screen" charset="utf-8" />
|
9
|
+
|
10
|
+
<script type="text/javascript" charset="utf-8">
|
11
|
+
relpath = '';
|
12
|
+
if (relpath != '') relpath += '/';
|
13
|
+
</script>
|
14
|
+
<script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
|
15
|
+
<script type="text/javascript" charset="utf-8" src="js/app.js"></script>
|
16
|
+
|
17
|
+
</head>
|
18
|
+
<body>
|
19
|
+
<div id="header">
|
20
|
+
<div id="menu">
|
21
|
+
|
22
|
+
<a href="_index.html">Index</a> »
|
23
|
+
|
24
|
+
|
25
|
+
<span class="title">Top Level Namespace</span>
|
26
|
+
|
27
|
+
</div>
|
28
|
+
|
29
|
+
<div id="search">
|
30
|
+
<a id="class_list_link" href="#">Namespace List</a>
|
31
|
+
<a id="method_list_link" href="#">Method List</a>
|
32
|
+
<a id ="file_list_link" href="#">File List</a>
|
33
|
+
</div>
|
34
|
+
|
35
|
+
<div class="clear"></div>
|
36
|
+
</div>
|
37
|
+
|
38
|
+
<iframe id="search_frame"></iframe>
|
39
|
+
|
40
|
+
<div id="content"><h1>Top Level Namespace
|
41
|
+
|
42
|
+
|
43
|
+
</h1>
|
44
|
+
|
45
|
+
<dl class="box">
|
46
|
+
|
47
|
+
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
</dl>
|
54
|
+
<div class="clear"></div>
|
55
|
+
|
56
|
+
<h2>Defined Under Namespace</h2>
|
57
|
+
<p class="children">
|
58
|
+
|
59
|
+
|
60
|
+
<strong class="modules">Modules:</strong> <a title="Kennedy" href="Kennedy.html">Kennedy</a>
|
61
|
+
|
62
|
+
|
63
|
+
|
64
|
+
|
65
|
+
</p>
|
66
|
+
|
67
|
+
|
68
|
+
|
69
|
+
|
70
|
+
|
71
|
+
</div>
|
72
|
+
|
73
|
+
<div id="footer">
|
74
|
+
Generated on Tue Dec 8 16:39:39 2009 by
|
75
|
+
<a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool">yard</a>
|
76
|
+
0.4.0 (ruby-1.8.7).
|
77
|
+
</div>
|
78
|
+
|
79
|
+
</body>
|
80
|
+
</html>
|
data/kennedy.gemspec
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{kennedy}
|
8
|
+
s.version = "0.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["gabrielg"]
|
12
|
+
s.date = %q{2009-12-09}
|
13
|
+
s.description = %q{Kennedy is out for Castronaut. A simple single-sign-on client and server library.}
|
14
|
+
s.email = %q{gabriel.gironda@gmail.com}
|
15
|
+
s.executables = ["kennedy-gen", "kennedy-gen"]
|
16
|
+
s.extra_rdoc_files = [
|
17
|
+
"LICENSE",
|
18
|
+
"README.markdown"
|
19
|
+
]
|
20
|
+
s.files = [
|
21
|
+
".document",
|
22
|
+
".gitignore",
|
23
|
+
".yardoc",
|
24
|
+
"LICENSE",
|
25
|
+
"MAIN.rdoc",
|
26
|
+
"README.markdown",
|
27
|
+
"Rakefile",
|
28
|
+
"VERSION",
|
29
|
+
"bin/kennedy-gen",
|
30
|
+
"doc/Kennedy.html",
|
31
|
+
"doc/Kennedy/Backends.html",
|
32
|
+
"doc/Kennedy/Backends/LDAP.html",
|
33
|
+
"doc/Kennedy/BadTicketException.html",
|
34
|
+
"doc/Kennedy/Granter.html",
|
35
|
+
"doc/Kennedy/Server.html",
|
36
|
+
"doc/Kennedy/Ticket.html",
|
37
|
+
"doc/_index.html",
|
38
|
+
"doc/class_list.html",
|
39
|
+
"doc/css/common.css",
|
40
|
+
"doc/css/full_list.css",
|
41
|
+
"doc/css/style.css",
|
42
|
+
"doc/file.README.html",
|
43
|
+
"doc/file_list.html",
|
44
|
+
"doc/index.html",
|
45
|
+
"doc/js/app.js",
|
46
|
+
"doc/js/full_list.js",
|
47
|
+
"doc/js/jquery.js",
|
48
|
+
"doc/method_list.html",
|
49
|
+
"doc/top-level-namespace.html",
|
50
|
+
"kennedy.gemspec",
|
51
|
+
"lib/kennedy.rb",
|
52
|
+
"lib/kennedy/backends/ldap.rb",
|
53
|
+
"lib/kennedy/generator.rb",
|
54
|
+
"lib/kennedy/granter.rb",
|
55
|
+
"lib/kennedy/instance_configuration.rb",
|
56
|
+
"lib/kennedy/server.rb",
|
57
|
+
"lib/kennedy/ticket.rb",
|
58
|
+
"logo.png",
|
59
|
+
"template/config.ru.erb",
|
60
|
+
"template/config/api_keys.yml.erb",
|
61
|
+
"template/config/backend.rb",
|
62
|
+
"template/config/encryption.yml.erb",
|
63
|
+
"template/config/sessions.yml.erb",
|
64
|
+
"test/granter_test.rb",
|
65
|
+
"test/ldap_backend_test.rb",
|
66
|
+
"test/server_test.rb",
|
67
|
+
"test/teststrap.rb",
|
68
|
+
"test/ticket_test.rb"
|
69
|
+
]
|
70
|
+
s.homepage = %q{http://github.com/gabrielg/kennedy}
|
71
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
72
|
+
s.require_paths = ["lib"]
|
73
|
+
s.rubygems_version = %q{1.3.5}
|
74
|
+
s.summary = %q{A simple single-sign-on client and server library.}
|
75
|
+
s.test_files = [
|
76
|
+
"test/granter_test.rb",
|
77
|
+
"test/ldap_backend_test.rb",
|
78
|
+
"test/server_test.rb",
|
79
|
+
"test/teststrap.rb",
|
80
|
+
"test/ticket_test.rb"
|
81
|
+
]
|
82
|
+
|
83
|
+
if s.respond_to? :specification_version then
|
84
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
85
|
+
s.specification_version = 3
|
86
|
+
|
87
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
88
|
+
s.add_development_dependency(%q<riot>, [">= 0"])
|
89
|
+
s.add_development_dependency(%q<maruku>, [">= 0"])
|
90
|
+
s.add_development_dependency(%q<yard>, [">= 0"])
|
91
|
+
s.add_runtime_dependency(%q<ruby-net-ldap>, [">= 0"])
|
92
|
+
s.add_runtime_dependency(%q<json>, [">= 0"])
|
93
|
+
s.add_runtime_dependency(%q<sinatra>, [">= 0"])
|
94
|
+
s.add_runtime_dependency(%q<rack>, [">= 0"])
|
95
|
+
else
|
96
|
+
s.add_dependency(%q<riot>, [">= 0"])
|
97
|
+
s.add_dependency(%q<maruku>, [">= 0"])
|
98
|
+
s.add_dependency(%q<yard>, [">= 0"])
|
99
|
+
s.add_dependency(%q<ruby-net-ldap>, [">= 0"])
|
100
|
+
s.add_dependency(%q<json>, [">= 0"])
|
101
|
+
s.add_dependency(%q<sinatra>, [">= 0"])
|
102
|
+
s.add_dependency(%q<rack>, [">= 0"])
|
103
|
+
end
|
104
|
+
else
|
105
|
+
s.add_dependency(%q<riot>, [">= 0"])
|
106
|
+
s.add_dependency(%q<maruku>, [">= 0"])
|
107
|
+
s.add_dependency(%q<yard>, [">= 0"])
|
108
|
+
s.add_dependency(%q<ruby-net-ldap>, [">= 0"])
|
109
|
+
s.add_dependency(%q<json>, [">= 0"])
|
110
|
+
s.add_dependency(%q<sinatra>, [">= 0"])
|
111
|
+
s.add_dependency(%q<rack>, [">= 0"])
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
data/lib/kennedy.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module Kennedy
|
2
|
+
module Backends
|
3
|
+
class LDAP
|
4
|
+
attr_writer :filter
|
5
|
+
|
6
|
+
# Creates a new LDAP auth backend with the given arguments
|
7
|
+
# @param [Hash] args The arguments to construct the backend with
|
8
|
+
# @option args [String] :host The LDAP server host to connect to
|
9
|
+
# @option args [Hash] :auth The auth method ruby-net-ldap should use
|
10
|
+
# @option args [String] :base The treebase to check against
|
11
|
+
def initialize(args = {})
|
12
|
+
@host = args[:host] || raise(ArgumentError, "Host must be given as :host")
|
13
|
+
@auth = args[:auth] || raise(ArgumentError, "Auth must be given as :auth")
|
14
|
+
@base = args[:base] || raise(ArgumentError, "Base must be given as :base")
|
15
|
+
@filter = lambda { raise(ArgumentError, "Set a filter block on this object using the 'filter' writer") }
|
16
|
+
end
|
17
|
+
|
18
|
+
# Authenticates the given credentials against LDAP
|
19
|
+
# @param [String] identifier The identifier to filter on
|
20
|
+
# @param [String] password The password to use
|
21
|
+
# @return [true, false] A boolean indicating authentication success
|
22
|
+
def authenticate(identifier, password)
|
23
|
+
filter_string = @filter.call(identifier)
|
24
|
+
!!ldap_conn.bind_as(:filter => filter_string, :password => password)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def ldap_conn
|
30
|
+
@ldap_conn ||= Net::LDAP.new(:host => @host, :auth => @auth, :base => @base)
|
31
|
+
end
|
32
|
+
|
33
|
+
end # LDAP
|
34
|
+
end # Backends
|
35
|
+
end # Kennedy
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'erb'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'digest/sha1'
|
5
|
+
|
6
|
+
module Kennedy
|
7
|
+
class Generator
|
8
|
+
TemplateDir = (Pathname(__FILE__).parent.parent.parent + "template").expand_path
|
9
|
+
TemplateType = ".erb"
|
10
|
+
|
11
|
+
def run(arguments)
|
12
|
+
@app_name = arguments.first
|
13
|
+
raise ArgumentError, "An app name must be given" if @app_name.nil? || @app_name.empty?
|
14
|
+
create_destination_directory
|
15
|
+
copy_files
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def create_destination_directory
|
21
|
+
@dest = Pathname(@app_name)
|
22
|
+
log_create(@dest)
|
23
|
+
@dest.mkpath
|
24
|
+
end
|
25
|
+
|
26
|
+
def copy_files
|
27
|
+
Pathname.glob("#{template_dir}/**/*").each do |pn|
|
28
|
+
next if pn.directory?
|
29
|
+
relative = pn.relative_path_from(TemplateDir)
|
30
|
+
dest_path = @dest + relative
|
31
|
+
unless dest_path.dirname.exist?
|
32
|
+
log_create(dest_path.dirname)
|
33
|
+
dest_path.dirname.mkpath
|
34
|
+
end
|
35
|
+
is_template?(pn) ? evaluate_and_write_template(pn, dest_path) : copy_file(pn, dest_path)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def is_template?(path)
|
40
|
+
Pathname(path).extname == template_type
|
41
|
+
end
|
42
|
+
|
43
|
+
def evaluate_and_write_template(path, dest)
|
44
|
+
dest = dest.sub(/#{Regexp.escape(dest.extname)}$/, "")
|
45
|
+
log_create(dest)
|
46
|
+
dest.open('w') do |f|
|
47
|
+
f << ERB.new(path.read).result(binding)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def copy_file(path, dest)
|
52
|
+
log_create(dest)
|
53
|
+
FileUtils.cp_r(path.expand_path.to_s, dest.expand_path.to_s)
|
54
|
+
end
|
55
|
+
|
56
|
+
def template_type
|
57
|
+
TemplateType
|
58
|
+
end
|
59
|
+
|
60
|
+
def template_dir
|
61
|
+
TemplateDir
|
62
|
+
end
|
63
|
+
|
64
|
+
def log_create(path)
|
65
|
+
puts "Creating '#{path}'"
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'kennedy/ticket'
|
2
|
+
|
3
|
+
module Kennedy
|
4
|
+
# Granter is used to authenticate credentials and grant tickets to services
|
5
|
+
# once a client has been authenticated.
|
6
|
+
class Granter
|
7
|
+
|
8
|
+
# @param [Hash] args The arguments to create the granter with
|
9
|
+
# @option args [String] :iv The AES-256 initialization vector to use for encryption and decryption
|
10
|
+
# @option args [String] :passphrase The AES-256 passphrase to use for encryption and decryption
|
11
|
+
# @option args [Object] :backend An instance of a backend to use for authentication
|
12
|
+
def initialize(args = {})
|
13
|
+
@iv = args[:iv] || raise(ArgumentError, "Encryption IV must be given as :iv")
|
14
|
+
@passphrase = args[:passphrase] || raise(ArgumentError, "Encryption passphrase must be given as :passphrase")
|
15
|
+
@backend = args[:backend] || raise(ArgumentError, "Authentication backend must be given as :backend")
|
16
|
+
end
|
17
|
+
|
18
|
+
# Authenticates the given credentials against the current backend
|
19
|
+
# @param [Hash] args The arguments to authenticate with
|
20
|
+
# @option args [String] :identifier The identifier (email address, for example) to use for authentication
|
21
|
+
# @option args [String] :password The password to use for authentication
|
22
|
+
# @return [true, false] A boolean indication of whether authentication was successful or not
|
23
|
+
def authenticate(args = {})
|
24
|
+
!!@backend.authenticate(args[:identifier], args[:password])
|
25
|
+
end
|
26
|
+
|
27
|
+
# Generates a ticket object to pass back to clients requesting authentication
|
28
|
+
# @param [Hash] args The arguments to generate the ticket with
|
29
|
+
# @option args [String] :identifier The identifier (email address, for example) the ticket grants access for
|
30
|
+
# @return [Kennedy::Ticket] A Kennedy::Ticket object
|
31
|
+
def generate_ticket(args = {})
|
32
|
+
identifier = args[:identifier] || raise(ArgumentError, "An identifier must be given as :identifier")
|
33
|
+
new_ticket(identifier)
|
34
|
+
end
|
35
|
+
|
36
|
+
def read_ticket(args = {})
|
37
|
+
data = args[:data] || raise(ArgumentError, "Data must be given as :data")
|
38
|
+
decrypt_ticket(args[:data])
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def decrypt_ticket(data)
|
44
|
+
Kennedy::Ticket.from_encrypted(:data => data, :iv => @iv, :passphrase => @passphrase)
|
45
|
+
end
|
46
|
+
|
47
|
+
def new_ticket(identifier)
|
48
|
+
Kennedy::Ticket.create(:identifier => identifier, :iv => @iv, :passphrase => @passphrase)
|
49
|
+
end
|
50
|
+
|
51
|
+
end # Granter
|
52
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
module Kennedy
|
5
|
+
class InstanceConfiguration
|
6
|
+
attr_reader :backend, :encryption, :api_keys
|
7
|
+
|
8
|
+
RequiredFiles = %w[backend.rb
|
9
|
+
encryption.yml
|
10
|
+
sessions.yml
|
11
|
+
api_keys.yml]
|
12
|
+
|
13
|
+
class << self
|
14
|
+
private :new
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(config_dir)
|
18
|
+
@config_dir = config_dir
|
19
|
+
load_backend
|
20
|
+
load_encryption
|
21
|
+
load_sessions
|
22
|
+
load_api_keys
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.load_config(config_dir)
|
26
|
+
config_dir = Pathname(config_dir)
|
27
|
+
raise ArgumentError, "Config dir '#{config_dir}' does not exist" unless config_dir.exist?
|
28
|
+
RequiredFiles.each do |rf|
|
29
|
+
expected = config_dir + rf
|
30
|
+
raise ArgumentError, "Expected config file '#{expected}' to exist" unless expected.exist?
|
31
|
+
end
|
32
|
+
new(config_dir)
|
33
|
+
end
|
34
|
+
|
35
|
+
def session_secret
|
36
|
+
@sessions['secret']
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def load_backend
|
42
|
+
@backend = eval(File.read(@config_dir + "backend.rb"))
|
43
|
+
end
|
44
|
+
|
45
|
+
def load_encryption
|
46
|
+
@encryption = YAML.load_file(@config_dir + "encryption.yml")
|
47
|
+
end
|
48
|
+
|
49
|
+
def load_sessions
|
50
|
+
@sessions = YAML.load_file(@config_dir + "sessions.yml")
|
51
|
+
end
|
52
|
+
|
53
|
+
def load_api_keys
|
54
|
+
@api_keys = YAML.load_file(@config_dir + "api_keys.yml")
|
55
|
+
end
|
56
|
+
|
57
|
+
end # InstanceConfiguration
|
58
|
+
end # Kennedy
|
@@ -0,0 +1,164 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'json'
|
3
|
+
require 'base64'
|
4
|
+
require 'rack/session/cookie'
|
5
|
+
require 'kennedy'
|
6
|
+
|
7
|
+
module Kennedy
|
8
|
+
class Server < Sinatra::Base
|
9
|
+
disable :session
|
10
|
+
|
11
|
+
# Creates a new subclass of Kennedy::Server with the given options
|
12
|
+
# @param [Hash] opts The options to use when building the subclass
|
13
|
+
# @option opts [Hash] :encryption The IV and passphrase to use when generating tickets, given as
|
14
|
+
# :iv and :passphrase keys in a Hash.
|
15
|
+
# @option opts [Object] :backend An instance of a backend to use for authentication.
|
16
|
+
# @option opts [String] :session_secret A secret for Rack::Session::Cookie to use when generating session
|
17
|
+
# cookies.
|
18
|
+
# @option opts [Hash] :api_keys A hash of key-value pairs to use as API users/keys with HTTP basic
|
19
|
+
# authentication
|
20
|
+
#
|
21
|
+
def self.create(opts = {})
|
22
|
+
sc = Class.new(self)
|
23
|
+
sc.instance_eval do
|
24
|
+
opts.each { |k,v| set k.to_sym, v }
|
25
|
+
raise ArgumentError, "A session secret must be set with :session_secret" unless defined?(session_secret)
|
26
|
+
add_cookie_middleware
|
27
|
+
set :api_keys, (defined?(api_keys) ? api_keys : {})
|
28
|
+
set :require_ssl, (defined?(require_ssl) ? require_ssl : true)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Ensures all connections come in over SSL
|
33
|
+
before do
|
34
|
+
next unless require_ssl?
|
35
|
+
unless (request.env['HTTP_X_FORWARDED_PROTO'] || request.env['rack.url_scheme']) == 'https'
|
36
|
+
halt 403, "Only SSL connections are accepted."
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Ensures all connections come in requesting JSON
|
41
|
+
before do
|
42
|
+
unless request.content_type == 'application/json'
|
43
|
+
halt 415, "Only JSON requests are accepted."
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Parses request body as JSON
|
48
|
+
before do
|
49
|
+
begin
|
50
|
+
@json = JSON.parse(request.body.read)
|
51
|
+
rescue
|
52
|
+
@json = {}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Takes incoming requests with a 'ticket' property in the JSON body and decrypts the ticket,
|
57
|
+
# returning an identifier as the 'identifier' property in the JSON response body if the
|
58
|
+
# ticket is valid and unexpired.
|
59
|
+
post "/validation_request" do
|
60
|
+
content_type "application/json"
|
61
|
+
require_api_authentication
|
62
|
+
begin
|
63
|
+
encrypted_ticket = Base64.decode64(@json['ticket'])
|
64
|
+
ticket = granter.read_ticket(:data => encrypted_ticket)
|
65
|
+
if ticket.expired?
|
66
|
+
[406, {'error' => 'expired_ticket'}.to_json]
|
67
|
+
else
|
68
|
+
[200, {'identifier' => ticket.identifier}.to_json]
|
69
|
+
end
|
70
|
+
rescue => e
|
71
|
+
[406, {'error' => 'bad_ticket'}.to_json]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Takes incoming requests and generates an encrypted and Base64 encoded ticket in the 'ticket'
|
76
|
+
# property of the JSON response if the user has a valid session.
|
77
|
+
get '/session' do
|
78
|
+
content_type "application/json"
|
79
|
+
if session['identifier']
|
80
|
+
ticket = granter.generate_ticket(:identifier => session['identifier'])
|
81
|
+
[200, {'ticket' => Base64.encode64(ticket.to_encrypted)}.to_json]
|
82
|
+
else
|
83
|
+
[401, {'error' => 'authentication_required'}.to_json]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Creates a session if authentication with the given credentials passed as 'identifier' and
|
88
|
+
# 'password' in the JSON body is successful.
|
89
|
+
post '/session' do
|
90
|
+
credentials = @json['credentials']
|
91
|
+
content_type "application/json"
|
92
|
+
if credentials.nil? || !granter.authenticate(:identifier => credentials['identifier'], :password => credentials['password'])
|
93
|
+
[406, {'error' => 'bad_credentials'}.to_json]
|
94
|
+
else
|
95
|
+
session['identifier'] = credentials['identifier']
|
96
|
+
[201, {'success' => 'session_created'}.to_json]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Destroys an existing session.
|
101
|
+
delete '/session' do
|
102
|
+
request.session.clear
|
103
|
+
[200, {'success' => 'session_destroyed'}.to_json]
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def auth
|
109
|
+
@auth ||= Rack::Auth::Basic::Request.new(request.env)
|
110
|
+
end
|
111
|
+
|
112
|
+
def authorized?
|
113
|
+
request.env['REMOTE_USER']
|
114
|
+
end
|
115
|
+
|
116
|
+
def unauthorized!(realm = "Kennedy")
|
117
|
+
headers 'WWW-Authenticate' => %(Basic realm="#{realm}")
|
118
|
+
throw :halt, [401, {'error' => 'authentication_required'}.to_json]
|
119
|
+
end
|
120
|
+
|
121
|
+
def bad_request!
|
122
|
+
throw :halt, [400, {'error' => 'bad_request'}.to_json]
|
123
|
+
end
|
124
|
+
|
125
|
+
def authorize(username, password)
|
126
|
+
api_keys.has_key?(username) && api_keys[username] == password
|
127
|
+
end
|
128
|
+
|
129
|
+
def require_api_authentication
|
130
|
+
return if authorized?
|
131
|
+
unauthorized! unless auth.provided?
|
132
|
+
bad_request! unless auth.basic?
|
133
|
+
unauthorized! unless authorize(*auth.credentials)
|
134
|
+
request.env['REMOTE_USER'] = auth.username
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.add_cookie_middleware
|
138
|
+
use Rack::Session::Cookie, :secret => session_secret
|
139
|
+
end
|
140
|
+
|
141
|
+
def granter
|
142
|
+
@granter ||= Kennedy::Granter.new(:iv => encryption[:iv], :passphrase => encryption[:passphrase],
|
143
|
+
:backend => backend)
|
144
|
+
end
|
145
|
+
|
146
|
+
def encryption
|
147
|
+
self.class.encryption
|
148
|
+
end
|
149
|
+
|
150
|
+
def backend
|
151
|
+
self.class.backend
|
152
|
+
end
|
153
|
+
|
154
|
+
def api_keys
|
155
|
+
self.class.api_keys
|
156
|
+
end
|
157
|
+
|
158
|
+
def require_ssl?
|
159
|
+
!!options.require_ssl
|
160
|
+
end
|
161
|
+
|
162
|
+
end # Server
|
163
|
+
end # Kennedy
|
164
|
+
|