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,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
|
+
|