iauthu 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|