iauthu 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/History.txt +4 -0
- data/Manifest.txt +15 -0
- data/README.txt +68 -0
- data/Rakefile +20 -0
- data/bin/iauthu +161 -0
- data/lib/iauthu/authenticator/base.rb +63 -0
- data/lib/iauthu/authenticator/chained.rb +108 -0
- data/lib/iauthu/authenticator/ldap.rb +79 -0
- data/lib/iauthu/request.rb +90 -0
- data/lib/iauthu/server.rb +153 -0
- data/lib/iauthu/site.rb +82 -0
- data/lib/iauthu/version.rb +9 -0
- data/lib/iauthu.rb +9 -0
- data/template/iauthu.conf.tmpl +82 -0
- data/test/test_iauthu.rb +0 -0
- metadata +124 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
History.txt
|
2
|
+
Manifest.txt
|
3
|
+
README.txt
|
4
|
+
Rakefile
|
5
|
+
bin/iauthu
|
6
|
+
lib/iauthu.rb
|
7
|
+
lib/iauthu/authenticator/base.rb
|
8
|
+
lib/iauthu/authenticator/chained.rb
|
9
|
+
lib/iauthu/authenticator/ldap.rb
|
10
|
+
lib/iauthu/request.rb
|
11
|
+
lib/iauthu/server.rb
|
12
|
+
lib/iauthu/site.rb
|
13
|
+
lib/iauthu/version.rb
|
14
|
+
template/iauthu.conf.tmpl
|
15
|
+
test/test_iauthu.rb
|
data/README.txt
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
= IAuthU
|
2
|
+
|
3
|
+
* http://github.com/rheimbuch/iauthu
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
IAuthU provides a basic iTunesU authentication server, along with libraries for building iTunesU authentication servers into your own application.
|
8
|
+
|
9
|
+
== FEATURES/PROBLEMS:
|
10
|
+
|
11
|
+
* Features
|
12
|
+
* Support for iTunesU authentication system.
|
13
|
+
* Plug-able authentication back-ends.
|
14
|
+
* Includes authentication back-ends for basic LDAP and chained authentication sources.
|
15
|
+
* Supports running as CGI, FastCGI, WEBrick, Mongrel, etc using Rack
|
16
|
+
|
17
|
+
== SYNOPSIS:
|
18
|
+
|
19
|
+
* Generate the config file in /etc/iauthu/iauthu.conf (see `iauthu -h` for all options)
|
20
|
+
|
21
|
+
$ sudo iauthu -g
|
22
|
+
|
23
|
+
* Edit /etc/iauthu/iauth.conf and fill in your iTunesU authentication settings.
|
24
|
+
|
25
|
+
* To run as a standalone server:
|
26
|
+
|
27
|
+
$ iauthu
|
28
|
+
|
29
|
+
* To run as a cgi, symlink the iauthu binary into your cgi-bin directory or any directory that allows executing cgi scripts:
|
30
|
+
|
31
|
+
$ ln -s /usr/bin/iauthu /usr/lib/cgi-bin/iauthu.cgi
|
32
|
+
|
33
|
+
|
34
|
+
== REQUIREMENTS:
|
35
|
+
|
36
|
+
* Rack
|
37
|
+
* Markaby
|
38
|
+
* Ruby-HMAC
|
39
|
+
* Net/LDAP
|
40
|
+
|
41
|
+
== INSTALL:
|
42
|
+
|
43
|
+
gem install iauthu
|
44
|
+
|
45
|
+
== LICENSE:
|
46
|
+
|
47
|
+
(The MIT License)
|
48
|
+
|
49
|
+
Copyright (c) 2008 Ryan Heimbuch
|
50
|
+
|
51
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
52
|
+
a copy of this software and associated documentation files (the
|
53
|
+
'Software'), to deal in the Software without restriction, including
|
54
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
55
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
56
|
+
permit persons to whom the Software is furnished to do so, subject to
|
57
|
+
the following conditions:
|
58
|
+
|
59
|
+
The above copyright notice and this permission notice shall be
|
60
|
+
included in all copies or substantial portions of the Software.
|
61
|
+
|
62
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
63
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
64
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
65
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
66
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
67
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
68
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'hoe'
|
5
|
+
require './lib/iauthu.rb'
|
6
|
+
|
7
|
+
Dir['tasks/**/*.rake'].each { |rake| load rake }
|
8
|
+
|
9
|
+
Hoe.new('iauthu', IAuthU::VERSION) do |p|
|
10
|
+
p.rubyforge_name = 'iauthu' # if different than lowercase project name
|
11
|
+
p.developer('Ryan Heimbuch', 'rheimbuch@gmail.com')
|
12
|
+
p.remote_rdoc_dir = '' # Release to root
|
13
|
+
p.extra_deps << ['rack', '>=0.3.0']
|
14
|
+
p.extra_deps << ['mongrel', '>=1.1.5']
|
15
|
+
p.extra_deps << ['markaby', '>=0.5']
|
16
|
+
p.extra_deps << ['ruby-hmac', '>=0.3.1']
|
17
|
+
p.extra_deps << ['ruby-net-ldap', '>=0.0.4']
|
18
|
+
end
|
19
|
+
|
20
|
+
# vim: syntax=Ruby
|
data/bin/iauthu
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
|
3
|
+
require 'rubygems'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'optparse'
|
6
|
+
require 'ostruct'
|
7
|
+
require 'erb'
|
8
|
+
require 'iauthu'
|
9
|
+
|
10
|
+
GLOBAL_CONFIG = {:trace => false}
|
11
|
+
|
12
|
+
|
13
|
+
module IAuthU
|
14
|
+
module Command # :nodoc:
|
15
|
+
|
16
|
+
SYSTEM_CONFIG_FILE = "/etc/iauthu/iauthu.conf"
|
17
|
+
|
18
|
+
def self.call(args)
|
19
|
+
options = Options.new(args)
|
20
|
+
|
21
|
+
config_file = args.shift
|
22
|
+
config_file = SYSTEM_CONFIG_FILE unless config_file && !config_file.empty?
|
23
|
+
|
24
|
+
|
25
|
+
if options.opts["generate"]
|
26
|
+
# Generate a config file
|
27
|
+
self.generate_config(config_file, options.opts)
|
28
|
+
else
|
29
|
+
# Run the server
|
30
|
+
self.run_server(config_file, options.opts)
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.run_server(config, options={})
|
37
|
+
|
38
|
+
if config && File.exist?(config)
|
39
|
+
file = File.read(config)
|
40
|
+
server = IAuthU::Server.build { eval(file) }
|
41
|
+
if ENV['REQUEST_METHOD'] # Detect if running as a cgi
|
42
|
+
server.runner = IAuthU::Server::Builder::RUNNERS[:cgi]
|
43
|
+
end
|
44
|
+
server.run
|
45
|
+
else
|
46
|
+
err = ""
|
47
|
+
raise "No config file is available.\n" \
|
48
|
+
"Specify a config file or run `iauthu -g [FILE]` to generate a config file."
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.generate_config(config, options={})
|
53
|
+
if File.exist?(config) && !options["force"]
|
54
|
+
raise "Config file '#{config}' already exists: will not overwrite."
|
55
|
+
end
|
56
|
+
vars = OpenStruct.new options
|
57
|
+
template_dir = File.join(File.dirname(__FILE__), "..", "template")
|
58
|
+
template = File.read(File.join(template_dir, 'iauthu.conf.tmpl'))
|
59
|
+
result = ERB.new(template).result(vars.send(:binding))
|
60
|
+
unless File.exist?(File.dirname(config))
|
61
|
+
FileUtils.mkdir_p(File.dirname(config))
|
62
|
+
end
|
63
|
+
File.open(config, "w") do |f|
|
64
|
+
f.puts result
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
class Options # :nodoc:
|
70
|
+
def initialize(args)
|
71
|
+
@opts = {}
|
72
|
+
parser.parse!(args)
|
73
|
+
end
|
74
|
+
|
75
|
+
attr_reader :parser
|
76
|
+
|
77
|
+
def opts
|
78
|
+
@opts
|
79
|
+
end
|
80
|
+
|
81
|
+
def config
|
82
|
+
@opts
|
83
|
+
end
|
84
|
+
|
85
|
+
def banner
|
86
|
+
parser.to_s
|
87
|
+
end
|
88
|
+
|
89
|
+
def parser
|
90
|
+
@parser ||= OptionParser.new do |opts|
|
91
|
+
opts.banner = "Usage: iauthu [CONFIG_FILE]"
|
92
|
+
opts.separator ""
|
93
|
+
opts.separator "If [CONFIG_FILE] is absent, iauthu will look for ./iauthu.conf and /etc/iauthu/iauthu.conf"
|
94
|
+
opts.separator ""
|
95
|
+
|
96
|
+
opts.on("-g", "--generate", "Generate a config file.") do
|
97
|
+
config["generate"] = true
|
98
|
+
end
|
99
|
+
|
100
|
+
opts.on("--trace", "Enable error tracebacks and debugging output.") do |trace|
|
101
|
+
GLOBAL_CONFIG[:trace] = trace
|
102
|
+
end
|
103
|
+
|
104
|
+
opts.separator ""
|
105
|
+
opts.separator "The following options apply only to config file generation."
|
106
|
+
opts.separator ""
|
107
|
+
|
108
|
+
opts.on("-f", "--force", "Force generation & overwrite config file." ) do |force|
|
109
|
+
config["force"] = force
|
110
|
+
end
|
111
|
+
|
112
|
+
opts.on("--url [URL]", "Set the iTunesU authentication url.") do |url|
|
113
|
+
config["url"] = url
|
114
|
+
end
|
115
|
+
|
116
|
+
opts.on("--debug-suffix [SUFFIX]", "Set the iTunesU debug suffix.") do |str|
|
117
|
+
config["debug_suffix"] = str
|
118
|
+
end
|
119
|
+
|
120
|
+
cred_help = "Set the iTunesU credentials. Format: shortName1::LongCred1;shortName2::LongCred2"
|
121
|
+
opts.on("--cred [CREDSTR]", cred_help) do |str|
|
122
|
+
creds = {}
|
123
|
+
str.split(",").each do |pair|
|
124
|
+
k,v = pair.split("::")
|
125
|
+
k.strip!
|
126
|
+
v.strip!
|
127
|
+
creds[k.to_sym] = v
|
128
|
+
end
|
129
|
+
config["creds"] = creds
|
130
|
+
end
|
131
|
+
|
132
|
+
opts.on("--secret [SECRET]", "Set the iTunesU shared secret.") do |str|
|
133
|
+
config["shared_secret"] = str
|
134
|
+
end
|
135
|
+
|
136
|
+
opts.separator ""
|
137
|
+
|
138
|
+
opts.on_tail("-v", "--version", "Display the software version.") do
|
139
|
+
puts IAuthU::VERSION
|
140
|
+
exit
|
141
|
+
end
|
142
|
+
|
143
|
+
opts.on_tail("-h", "--help", "Show this help message.") do
|
144
|
+
puts opts
|
145
|
+
exit
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
begin
|
156
|
+
IAuthU::Command.call(ARGV)
|
157
|
+
rescue => ex
|
158
|
+
puts ex.message
|
159
|
+
raise ex if GLOBAL_CONFIG[:trace]
|
160
|
+
exit
|
161
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module IAuthU
|
2
|
+
module Authenticator # :nodoc:
|
3
|
+
|
4
|
+
=begin rdoc
|
5
|
+
IAuthU uses 'Authentication' objects to validate and authenticate users.
|
6
|
+
Any object can be used for authentication as long as it follows the following
|
7
|
+
conventions:
|
8
|
+
- has a #call method that accepts a username and password
|
9
|
+
- the #call method returns an identity hash in the form:
|
10
|
+
{ :username => 'john.doe',
|
11
|
+
:display_name => 'John Doe',
|
12
|
+
:email => 'john.doe@montana.edu',
|
13
|
+
:identifier => '',
|
14
|
+
:credentials => [:admin, :user] }
|
15
|
+
- the identity hash MUST contain a :username entry
|
16
|
+
- if authentication fails the #call method should return nil
|
17
|
+
|
18
|
+
This convention allows the use of lambda objects as authenticators:
|
19
|
+
lambda {|user,pass|
|
20
|
+
if user == 'foo'
|
21
|
+
{:username => user, :credentials => [:admin]}
|
22
|
+
end
|
23
|
+
}
|
24
|
+
|
25
|
+
There are also optional behavior methods that authentication objects
|
26
|
+
should, but are not required to implement:
|
27
|
+
#required:: specifies that an authentication object is required. In an
|
28
|
+
authentication chain, failure of authenticator objects that are required
|
29
|
+
causes the entire chain to fail.
|
30
|
+
#sufficient:: specifies that an authentication object is sufficient for
|
31
|
+
authentication. In an auth chain, authenticator objects that are sufficient
|
32
|
+
and successfully authenticate halt the execution of the chain and cause the
|
33
|
+
chain to return a successful authentication.
|
34
|
+
|
35
|
+
If you require custom authentication, subclass IAuthU::Authenticator::Base
|
36
|
+
and override #call to perform your custom authentication. You may also use
|
37
|
+
any object that implements #call as specified above. If you require multiple
|
38
|
+
authentication steps, you may compose authenticators together using
|
39
|
+
IAuthU::Authenticator::Chained.
|
40
|
+
=end
|
41
|
+
class Base
|
42
|
+
def call(username, password)
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
|
46
|
+
def required
|
47
|
+
@required ||= false
|
48
|
+
end
|
49
|
+
|
50
|
+
def required=(bool)
|
51
|
+
@required = !!bool
|
52
|
+
end
|
53
|
+
|
54
|
+
def sufficient
|
55
|
+
@sufficient ||= false
|
56
|
+
end
|
57
|
+
|
58
|
+
def sufficient=(bool)
|
59
|
+
@sufficient = !!bool
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'base')
|
2
|
+
module IAuthU
|
3
|
+
module Authenticator # :nodoc:
|
4
|
+
|
5
|
+
=begin rdoc
|
6
|
+
The Chained Authenticator allows multiple authentication objects to
|
7
|
+
be combined. The order in which authenticators are added is the order
|
8
|
+
in which they are executed. The identity hash that each authenticator
|
9
|
+
returns is combined with the one returned by the previous authenticator.
|
10
|
+
|
11
|
+
local_auth = IAuthU::Authenticator::Chained.build {
|
12
|
+
#Use builtin htaccess authenticator
|
13
|
+
use IAuthU::Authenticator::FileBased.new('/etc/itunesu/user.auth'), :required => true
|
14
|
+
|
15
|
+
#Call a custom authenticator
|
16
|
+
use CustomLDAPAuth.new
|
17
|
+
|
18
|
+
#Add default user credential to all identities
|
19
|
+
use lambda {|user,pass| {:credentials => [:user]}}
|
20
|
+
}
|
21
|
+
=end
|
22
|
+
class Chained < Base
|
23
|
+
|
24
|
+
=begin rdoc
|
25
|
+
Allows easy construction of chained authenticators. The format for
|
26
|
+
specifying the authentication chain is:
|
27
|
+
use someAuthenticator [, {:required => true|false, :sufficient => true|false}]
|
28
|
+
=end
|
29
|
+
def self.build(&block)
|
30
|
+
chained = Builder.new
|
31
|
+
chained.instance_eval(&block)
|
32
|
+
chained.auth
|
33
|
+
end
|
34
|
+
|
35
|
+
# Creates a new Chained authentication object. A list of authenticators
|
36
|
+
# can be passed. This will create an authentication chain compoased of
|
37
|
+
# the passed authenticators.
|
38
|
+
def initialize(*args)
|
39
|
+
@chain = args || []
|
40
|
+
end
|
41
|
+
|
42
|
+
# Append an authenticator to the authentication chain.
|
43
|
+
def <<(authenticator)
|
44
|
+
add authenticator
|
45
|
+
end
|
46
|
+
|
47
|
+
# Append an authenticator to the authentication chain. Optionally specifiy
|
48
|
+
# if the authenticator is required or sufficient for the chain. required and
|
49
|
+
# sufficient both default to false.
|
50
|
+
# chain.add MyAuth.new # MyAuth is not required and not sufficient
|
51
|
+
# chain.add MyAuth.new, :required => true, :sufficient => false
|
52
|
+
def add(authenticator, opts={})
|
53
|
+
authenticator.required = !!opts["required"] if opts["required"] && authenticator.respond_to?('required=')
|
54
|
+
authenticator.sufficient = !!opts["sufficient"] if opts ["sufficient"] && authenticator.respond_to?('sufficient=')
|
55
|
+
@chain << authenticator
|
56
|
+
end
|
57
|
+
|
58
|
+
# Invoke the authentication chain
|
59
|
+
def call(username,password)
|
60
|
+
auths = @chain.clone
|
61
|
+
identity = {}
|
62
|
+
until auths.empty?
|
63
|
+
auth = auths.shift
|
64
|
+
new_ident = auth.call(username,password)
|
65
|
+
if new_ident.nil? && auth.respond_to?(:required) && auth.required
|
66
|
+
#Authentication failed for a required authenticator
|
67
|
+
return nil
|
68
|
+
end
|
69
|
+
identity = merge_identities(identity, new_ident)
|
70
|
+
if identity && auth.respond_to?(:sufficient) && auth.sufficient
|
71
|
+
#This authenticator is sufficient; do not continue down auth chain.
|
72
|
+
break
|
73
|
+
end
|
74
|
+
end
|
75
|
+
return nil if identity.empty?
|
76
|
+
identity
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
def merge_identities(orig, other)
|
81
|
+
ident = {}.merge(orig)
|
82
|
+
return ident if other.nil? || other.empty?
|
83
|
+
creds = other.delete("credentials") || []
|
84
|
+
ident["credentials"] ||= []
|
85
|
+
ident["credentials"] << creds
|
86
|
+
ident["credentials"].flatten!.uniq!
|
87
|
+
|
88
|
+
ident.merge(other)
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
class Builder # :nodoc:
|
93
|
+
def initialize
|
94
|
+
@auth = Chained.new
|
95
|
+
end
|
96
|
+
|
97
|
+
attr_reader :auth
|
98
|
+
|
99
|
+
def use(authenticator=nil, opts={}, &block)
|
100
|
+
if block && !authenticator
|
101
|
+
authenticator = block
|
102
|
+
end
|
103
|
+
@auth.add authenticator, opts
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'net/ldap'
|
3
|
+
|
4
|
+
module IAuthU
|
5
|
+
module Authenticator # :nodoc:
|
6
|
+
class LDAP
|
7
|
+
def self.build(&block)
|
8
|
+
b = Builder.new
|
9
|
+
b.instance_eval(&block)
|
10
|
+
b.ldap_auth
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(opts)
|
14
|
+
@config = opts
|
15
|
+
@login_format = @config.delete(:login_format) || "%s"
|
16
|
+
@credentials = @config.delete(:credentials) || []
|
17
|
+
use_ssl = @config.delete(:use_ssl) || false
|
18
|
+
server_names = @config.delete(:servers) || []
|
19
|
+
|
20
|
+
@servers = server_names.map do |name|
|
21
|
+
conf = @config.clone
|
22
|
+
conf[:host] = name
|
23
|
+
server = Net::LDAP.new(conf)
|
24
|
+
server.encryption(:simple_tls) if use_ssl
|
25
|
+
server
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def call(user,pass)
|
30
|
+
@servers.each do |s|
|
31
|
+
s.auth(@login_format % user, pass)
|
32
|
+
ident = {"username" => user}
|
33
|
+
ident["credentials"] = @credentials
|
34
|
+
return ident if s.bind
|
35
|
+
end
|
36
|
+
return nil
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
class Builder
|
41
|
+
def initialize
|
42
|
+
@config = {}
|
43
|
+
end
|
44
|
+
|
45
|
+
def servers(*svrs)
|
46
|
+
@config[:servers] = svrs
|
47
|
+
end
|
48
|
+
|
49
|
+
def port(num)
|
50
|
+
@config[:port] = num
|
51
|
+
end
|
52
|
+
|
53
|
+
def base(str)
|
54
|
+
@config[:base] = str
|
55
|
+
end
|
56
|
+
|
57
|
+
def login_format(str)
|
58
|
+
@config[:login_format] = str
|
59
|
+
end
|
60
|
+
|
61
|
+
def use_ssl(bool=true)
|
62
|
+
@config[:use_ssl]
|
63
|
+
end
|
64
|
+
|
65
|
+
def config
|
66
|
+
@config
|
67
|
+
end
|
68
|
+
|
69
|
+
def credentials(creds)
|
70
|
+
@config[:credentials] = creds.to_a
|
71
|
+
end
|
72
|
+
|
73
|
+
def ldap_auth
|
74
|
+
LDAP.new(config)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'net/http'
|
3
|
+
require 'net/https'
|
4
|
+
require 'hmac-sha2'
|
5
|
+
require 'cgi'
|
6
|
+
require 'logger'
|
7
|
+
|
8
|
+
module IAuthU
|
9
|
+
|
10
|
+
=begin rdoc
|
11
|
+
IAuthU::Request performs the ITunesU authentication step. Usually you will
|
12
|
+
not create a Request object manually, but will instead recieve it from
|
13
|
+
a Site object. A Request object defers the actual connection to ITunesU
|
14
|
+
until the #call method is invoked.
|
15
|
+
=end
|
16
|
+
class Request
|
17
|
+
def initialize(user, creds, site)
|
18
|
+
@user = user
|
19
|
+
@creds = creds.to_a
|
20
|
+
#raise MissingCredentialsError, "Credentials cannot be empty." if @creds.empty?
|
21
|
+
@site = site
|
22
|
+
@debug = false
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_accessor :debug
|
26
|
+
|
27
|
+
def logger
|
28
|
+
CONFIG[:logger]
|
29
|
+
end
|
30
|
+
|
31
|
+
def call
|
32
|
+
logger.info "Sending request for: #{@user.inspect}"
|
33
|
+
token, data = get_authorization_token(@user, @creds, @site.shared_secret)
|
34
|
+
invoke_action(site_url, data, token)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def site_url
|
40
|
+
debug ? "#{@site.url}#{@site.debug_suffix}" : @site.url
|
41
|
+
end
|
42
|
+
|
43
|
+
def identity_string(identity)
|
44
|
+
display_name = identity["display_name"]
|
45
|
+
email_address = identity["email"]
|
46
|
+
username = identity["username"]
|
47
|
+
user_identifier = identity["identifier"]
|
48
|
+
"\"#{display_name}\" <#{email_address}> (#{username}) [#{user_identifier}]"
|
49
|
+
end
|
50
|
+
|
51
|
+
def get_authorization_token(identity, credentials, key)
|
52
|
+
identity = identity_string(identity)
|
53
|
+
credentials = credentials * ';'
|
54
|
+
token = {}
|
55
|
+
expiration = Time.new().to_i
|
56
|
+
|
57
|
+
#Need to escape using CGI.escape; URI.escape doesn't escape '()<>' properly
|
58
|
+
cred = CGI.escape(credentials)
|
59
|
+
id = CGI.escape(identity)
|
60
|
+
exp = CGI.escape(expiration.to_s)
|
61
|
+
|
62
|
+
buffer = "credentials=#{cred}&identity=#{id}&time=#{exp}"
|
63
|
+
|
64
|
+
sig = HMAC::SHA256.new(key)
|
65
|
+
sig << buffer
|
66
|
+
buffer = buffer + "&signature=#{sig}"
|
67
|
+
|
68
|
+
token = {
|
69
|
+
'credentials' => credentials,
|
70
|
+
'identity' => identity,
|
71
|
+
'time' => expiration.to_s,
|
72
|
+
'signature' => sig.to_s
|
73
|
+
}
|
74
|
+
logger.debug "Request Token: #{token.inspect}"
|
75
|
+
logger.debug "Request Params: #{buffer.inspect}"
|
76
|
+
return token, buffer
|
77
|
+
end
|
78
|
+
|
79
|
+
def invoke_action(site_url, data, token_hdrs)
|
80
|
+
uri = URI.parse(site_url)
|
81
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
82
|
+
http.use_ssl = true
|
83
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
84
|
+
#puts token_hdrs.inspect
|
85
|
+
logger.debug "Request sent to: #{site_url}"
|
86
|
+
response = http.request_post(uri.path, data, token_hdrs)
|
87
|
+
return response
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require File.join(File.dirname(__FILE__), 'site')
|
3
|
+
require File.join(File.dirname(__FILE__), 'authenticator/chained')
|
4
|
+
require 'rack'
|
5
|
+
require 'rack/showexceptions'
|
6
|
+
require 'rack/request'
|
7
|
+
require 'rack/response'
|
8
|
+
require 'markaby'
|
9
|
+
require 'logger'
|
10
|
+
|
11
|
+
module IAuthU
|
12
|
+
|
13
|
+
class Server
|
14
|
+
def self.build(&block)
|
15
|
+
b = Builder.new
|
16
|
+
b.instance_eval(&block)
|
17
|
+
b.server
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
@runner = Rack::Handler::WEBrick
|
22
|
+
@port = 9292
|
23
|
+
@login_page = login_form
|
24
|
+
end
|
25
|
+
attr_accessor :site, :auth, :runner, :port, :login_page
|
26
|
+
|
27
|
+
def logger
|
28
|
+
CONFIG[:logger]
|
29
|
+
end
|
30
|
+
|
31
|
+
def run
|
32
|
+
raise "Site config is required" unless @site
|
33
|
+
raise "Auth config is required" unless @auth
|
34
|
+
logger.info "Running IAuthU with: #{@runner}"
|
35
|
+
@runner.run(Rack::ShowExceptions.new(Rack::Lint.new(self)), :Port => @port)
|
36
|
+
end
|
37
|
+
|
38
|
+
def call(env)
|
39
|
+
req = Rack::Request.new(env)
|
40
|
+
user, pass = req.POST["username"], req.POST["password"]
|
41
|
+
# if user && pass
|
42
|
+
# logged_in, result = login(user, pass)
|
43
|
+
# else
|
44
|
+
# logged_in = false
|
45
|
+
# end
|
46
|
+
# result = login_page unless logged_in
|
47
|
+
# res = Rack::Response.new
|
48
|
+
# res.write result
|
49
|
+
# res.finish
|
50
|
+
unless req.POST["username"] && req.POST["password"]
|
51
|
+
Rack::Response.new.finish do |res|
|
52
|
+
res.write login_page
|
53
|
+
end
|
54
|
+
else
|
55
|
+
Rack::Response.new.finish do |res|
|
56
|
+
logged_in, result = login(req.POST["username"], req.POST["password"])
|
57
|
+
#puts result
|
58
|
+
res.write result
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def login(user, pass)
|
66
|
+
identity = @auth.call(user,pass)
|
67
|
+
if identity
|
68
|
+
req = @site.authentication_request(identity)
|
69
|
+
resp = req.call
|
70
|
+
[:true, resp.body]
|
71
|
+
else
|
72
|
+
[:false, login_page]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def login_form
|
77
|
+
page_title = "iTunesU Login"
|
78
|
+
m = Markaby::Builder.new
|
79
|
+
m.html do
|
80
|
+
head { title page_title}
|
81
|
+
body do
|
82
|
+
h1 page_title
|
83
|
+
form(:method => 'post') do
|
84
|
+
p {
|
85
|
+
label("Username: ", :for => 'username')
|
86
|
+
input(:type => 'text', :name => 'username', :id => 'username')
|
87
|
+
}
|
88
|
+
p {
|
89
|
+
label("Password: ", :for => 'password')
|
90
|
+
input(:type => 'password', :name => 'password', :id => 'password')
|
91
|
+
}
|
92
|
+
input(:type => 'submit', :value => 'Login')
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
m.to_s
|
97
|
+
end
|
98
|
+
|
99
|
+
class Builder
|
100
|
+
RUNNERS = { :cgi => Rack::Handler::CGI,
|
101
|
+
#:fastcgi => Rack::Handler::FastCGI,
|
102
|
+
:webrick => Rack::Handler::WEBrick,
|
103
|
+
:mongrel => Rack::Handler::Mongrel
|
104
|
+
}
|
105
|
+
|
106
|
+
def initialize
|
107
|
+
@server = Server.new
|
108
|
+
end
|
109
|
+
|
110
|
+
attr_reader :server
|
111
|
+
|
112
|
+
def site(&block)
|
113
|
+
@server.site = Site.build(&block)
|
114
|
+
end
|
115
|
+
|
116
|
+
def auth(&block)
|
117
|
+
@server.auth = Authenticator::Chained.build(&block)
|
118
|
+
end
|
119
|
+
|
120
|
+
def login_page(str_or_file=nil, &block)
|
121
|
+
return if str_or_file == :default
|
122
|
+
if str_or_file
|
123
|
+
if File.exist?(str_or_file)
|
124
|
+
File.open(str_or_file) do |f|
|
125
|
+
@server.login_page = f.read
|
126
|
+
end
|
127
|
+
else
|
128
|
+
@server.login_page = str_or_file
|
129
|
+
end
|
130
|
+
return
|
131
|
+
else
|
132
|
+
@server.login_page = block.call.to_s
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def logger(dest)
|
137
|
+
if dest == :default
|
138
|
+
dest = STDERR
|
139
|
+
end
|
140
|
+
if dest.kind_of?(IO) || dest.kind_of?(String) || dest.kind_of?(Symbol)
|
141
|
+
CONFIG[:logger] = Logger.new(dest)
|
142
|
+
else
|
143
|
+
CONFIG[:logger] = dest
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def run(type, opts={})
|
148
|
+
@server.runner = RUNNERS[type] || RUNNERS[:webrick]
|
149
|
+
@server.port ||= opts[:port]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
data/lib/iauthu/site.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'request')
|
2
|
+
require 'logger'
|
3
|
+
module IAuthU
|
4
|
+
|
5
|
+
=begin rdoc
|
6
|
+
IAuthU::Site represents your remote ITunesU site.
|
7
|
+
|
8
|
+
site = IAuthU::Site.build {
|
9
|
+
url "https://deimos.apple.com/WebObjects/Core.woa/Browse/montana.edu"
|
10
|
+
debug_suffix "/aqc392"
|
11
|
+
shared_secret "WQBMFYFFH7SBPVFXMCEBG43WC2ANW7LQ"
|
12
|
+
cred :admin, "Administrator\@urn:mace:itunesu.com:sites:montana.edu"
|
13
|
+
cred :user, "Authenticated\@urn:mace:itunesu.com:sites:montana.edu"
|
14
|
+
}
|
15
|
+
=end
|
16
|
+
class Site
|
17
|
+
def self.build(&block)
|
18
|
+
builder = Builder.new
|
19
|
+
builder.instance_eval(&block)
|
20
|
+
builder.site
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(opts={})
|
24
|
+
@debug = false
|
25
|
+
opts = {:url => "", :debug_suffix => "", :shared_secret => "", :credentials => {}}.merge(opts)
|
26
|
+
opts.keys.each{|var| instance_variable_set "@#{var.to_s}", opts[var]}
|
27
|
+
end
|
28
|
+
attr_accessor :url, :debug_suffix, :debug, :shared_secret, :credentials
|
29
|
+
|
30
|
+
def logger
|
31
|
+
CONFIG[:logger]
|
32
|
+
end
|
33
|
+
|
34
|
+
def authentication_request(user)
|
35
|
+
raise SettingsError, "Site url must be set before sending request." unless url && !url.empty?
|
36
|
+
user = user.clone
|
37
|
+
creds = user.delete("credentials") || []
|
38
|
+
tmp = []
|
39
|
+
creds.each{|c| tmp << credentials[c]}
|
40
|
+
|
41
|
+
begin
|
42
|
+
req = Request.new(user, tmp, self)
|
43
|
+
rescue Request::MissingCredentialsError
|
44
|
+
raise MissingCredentialsError, "Credentials entry is missing in: #{user.inspect}"
|
45
|
+
end
|
46
|
+
req.debug = debug
|
47
|
+
req
|
48
|
+
end
|
49
|
+
|
50
|
+
class SettingsError < RuntimeError # :nodoc:
|
51
|
+
end
|
52
|
+
|
53
|
+
class Builder # :nodoc:
|
54
|
+
def initialize
|
55
|
+
@site = Site.new
|
56
|
+
end
|
57
|
+
|
58
|
+
attr_reader :site
|
59
|
+
|
60
|
+
def url(url)
|
61
|
+
@site.url = url
|
62
|
+
end
|
63
|
+
|
64
|
+
def debug_suffix(suffix)
|
65
|
+
@site.debug_suffix = suffix
|
66
|
+
end
|
67
|
+
|
68
|
+
def debug(bool=true)
|
69
|
+
@site.debug = bool
|
70
|
+
end
|
71
|
+
|
72
|
+
def shared_secret(str)
|
73
|
+
@site.shared_secret = str
|
74
|
+
end
|
75
|
+
|
76
|
+
def cred(name, credential)
|
77
|
+
name = name.to_sym
|
78
|
+
@site.credentials[name] = credential.to_s
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/lib/iauthu.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
require 'iauthu/server'
|
5
|
+
require 'logger'
|
6
|
+
module IAuthU
|
7
|
+
VERSION = '0.0.1'
|
8
|
+
CONFIG = {:logger => Logger.new(STDERR)}
|
9
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# Specify how IAuthU should run.
|
2
|
+
# Available options are: :cgi, :webrick, :mongrel
|
3
|
+
# Note: if a cgi environment is detected, IAuthU will run in
|
4
|
+
# cgi mode regardless of this setting.
|
5
|
+
|
6
|
+
run :webrick, :port => 9292
|
7
|
+
|
8
|
+
# Specify the log destination:
|
9
|
+
# * :default == STDERR
|
10
|
+
# * "/path/to/logfile"
|
11
|
+
# * a Logger object
|
12
|
+
# note: using STDOUT will interfere with cgi output
|
13
|
+
|
14
|
+
logger :default # :default | "/var/log/iauthu.log" | Logger.new("/var/logs/iauthu.log")
|
15
|
+
|
16
|
+
|
17
|
+
# Specify the Login Page
|
18
|
+
# Options include:
|
19
|
+
# * :default - displays the builtin login form
|
20
|
+
# * "/path/to/file.html" - displays specified file
|
21
|
+
# * "<html><title>...." - use a raw html string
|
22
|
+
# * {...} - run a ruby block to generate the page.
|
23
|
+
# For example, consider using markaby
|
24
|
+
|
25
|
+
login_page :default
|
26
|
+
|
27
|
+
site {
|
28
|
+
# Specify your iTunesU url
|
29
|
+
url "<%= url || "https://deimos.apple.com/WebObjects/Core.woa/Browse/foo.edu" %>"
|
30
|
+
|
31
|
+
# Specify your debug suffix if you wish to use iTunesU authentication
|
32
|
+
# in debug mode.
|
33
|
+
<%= "#" unless debug_suffix %> debug_suffix "<%= debug_suffix || "/debug_suffix" %>"
|
34
|
+
|
35
|
+
# Enable iTunesU authentication debugging. Must specify the debug_suffix.
|
36
|
+
debug <%= debug ? "true" : "false" %>
|
37
|
+
|
38
|
+
# Shared authentication secret
|
39
|
+
shared_secret "<%= shared_secret || "SHARED-SECRET" %>"
|
40
|
+
|
41
|
+
# Define your iTunesU login credentials. The alias you specify
|
42
|
+
# can be used to refer to that credential later in the authentication
|
43
|
+
# section.
|
44
|
+
#
|
45
|
+
# Example:
|
46
|
+
# cred :alias, "FullCred@urn:mace:itunesu.com:sites:urschool.edu"
|
47
|
+
<% unless creds.nil?
|
48
|
+
creds.keys.each do |key| %>
|
49
|
+
cred <%= key.inspect %>, <%= creds[key].inspect %>
|
50
|
+
<% end
|
51
|
+
end %>
|
52
|
+
}
|
53
|
+
|
54
|
+
auth {
|
55
|
+
|
56
|
+
# Authenticate all users
|
57
|
+
use {|user, pass|
|
58
|
+
{"display_name" => user,
|
59
|
+
"username" => user,
|
60
|
+
"credentials" => []
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
# # Uncomment to Authenticate Users from LDAP
|
65
|
+
# require 'iauthu/authenticator/ldap'
|
66
|
+
# use IAuthU::Authenticator::LDAP.build {
|
67
|
+
# servers "ldap.urschool.edu"
|
68
|
+
# login_format "uid=%s,ou=People,o=ldap.urschool.edu,o=cp"
|
69
|
+
# credentials [:user]
|
70
|
+
# }
|
71
|
+
|
72
|
+
|
73
|
+
# # Uncomment to create a 'test' admin user
|
74
|
+
# use {|user, pass|
|
75
|
+
# if user == 'test' && pass == 'test'
|
76
|
+
# {"display_name" => "Site Administrator",
|
77
|
+
# "email" => "acg-support@montana.edu",
|
78
|
+
# "credentials" => [:admin]}
|
79
|
+
# end
|
80
|
+
# }
|
81
|
+
}
|
82
|
+
|
data/test/test_iauthu.rb
ADDED
File without changes
|
metadata
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: iauthu
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ryan Heimbuch
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-06-11 00:00:00 -06:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rack
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.3.0
|
23
|
+
version:
|
24
|
+
- !ruby/object:Gem::Dependency
|
25
|
+
name: mongrel
|
26
|
+
version_requirement:
|
27
|
+
version_requirements: !ruby/object:Gem::Requirement
|
28
|
+
requirements:
|
29
|
+
- - ">="
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: 1.1.5
|
32
|
+
version:
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: markaby
|
35
|
+
version_requirement:
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: "0.5"
|
41
|
+
version:
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: ruby-hmac
|
44
|
+
version_requirement:
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: 0.3.1
|
50
|
+
version:
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: ruby-net-ldap
|
53
|
+
version_requirement:
|
54
|
+
version_requirements: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: 0.0.4
|
59
|
+
version:
|
60
|
+
- !ruby/object:Gem::Dependency
|
61
|
+
name: hoe
|
62
|
+
version_requirement:
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 1.5.3
|
68
|
+
version:
|
69
|
+
description: IAuthU provides a basic iTunesU authentication server, along with libraries for building iTunesU authentication servers into your own application.
|
70
|
+
email:
|
71
|
+
- rheimbuch@gmail.com
|
72
|
+
executables:
|
73
|
+
- iauthu
|
74
|
+
extensions: []
|
75
|
+
|
76
|
+
extra_rdoc_files:
|
77
|
+
- History.txt
|
78
|
+
- Manifest.txt
|
79
|
+
- README.txt
|
80
|
+
files:
|
81
|
+
- History.txt
|
82
|
+
- Manifest.txt
|
83
|
+
- README.txt
|
84
|
+
- Rakefile
|
85
|
+
- bin/iauthu
|
86
|
+
- lib/iauthu.rb
|
87
|
+
- lib/iauthu/authenticator/base.rb
|
88
|
+
- lib/iauthu/authenticator/chained.rb
|
89
|
+
- lib/iauthu/authenticator/ldap.rb
|
90
|
+
- lib/iauthu/request.rb
|
91
|
+
- lib/iauthu/server.rb
|
92
|
+
- lib/iauthu/site.rb
|
93
|
+
- lib/iauthu/version.rb
|
94
|
+
- template/iauthu.conf.tmpl
|
95
|
+
- test/test_iauthu.rb
|
96
|
+
has_rdoc: true
|
97
|
+
homepage: http://github.com/rheimbuch/iauthu
|
98
|
+
post_install_message:
|
99
|
+
rdoc_options:
|
100
|
+
- --main
|
101
|
+
- README.txt
|
102
|
+
require_paths:
|
103
|
+
- lib
|
104
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: "0"
|
109
|
+
version:
|
110
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
111
|
+
requirements:
|
112
|
+
- - ">="
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: "0"
|
115
|
+
version:
|
116
|
+
requirements: []
|
117
|
+
|
118
|
+
rubyforge_project: iauthu
|
119
|
+
rubygems_version: 1.0.1
|
120
|
+
signing_key:
|
121
|
+
specification_version: 2
|
122
|
+
summary: IAuthU provides a basic iTunesU authentication server, along with libraries for building iTunesU authentication servers into your own application.
|
123
|
+
test_files:
|
124
|
+
- test/test_iauthu.rb
|