rack-pubcookie 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +8 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +37 -0
- data/README.md +73 -0
- data/Rakefile +10 -0
- data/ext/openssl/evp.c +36 -0
- data/ext/openssl/extconf.rb +9 -0
- data/lib/rack/pubcookie/aes.rb +16 -0
- data/lib/rack/pubcookie/auth.rb +136 -0
- data/lib/rack/pubcookie/des.rb +31 -0
- data/lib/rack/pubcookie/fake.rb +16 -0
- data/lib/rack/pubcookie/version.rb +5 -0
- data/lib/rack/pubcookie.rb +11 -0
- data/lib/rack-pubcookie.rb +1 -0
- data/rack-pubcookie.gemspec +26 -0
- data/spec/fixtures/granting.crt +16 -0
- data/spec/fixtures/invalid.crt +33 -0
- data/spec/fixtures/test.com +32 -0
- data/spec/rack/pubcookie/auth_spec.rb +131 -0
- data/spec/spec_helper.rb +29 -0
- metadata +153 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
rack-pubcookie (0.0.1)
|
5
|
+
activesupport
|
6
|
+
rack
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: http://rubygems.org/
|
10
|
+
specs:
|
11
|
+
activesupport (3.0.1)
|
12
|
+
diff-lcs (1.1.2)
|
13
|
+
nokogiri (1.4.3.1)
|
14
|
+
rack (1.2.1)
|
15
|
+
rack-test (0.5.6)
|
16
|
+
rack (>= 1.0)
|
17
|
+
rspec (2.0.1)
|
18
|
+
rspec-core (~> 2.0.1)
|
19
|
+
rspec-expectations (~> 2.0.1)
|
20
|
+
rspec-mocks (~> 2.0.1)
|
21
|
+
rspec-core (2.0.1)
|
22
|
+
rspec-expectations (2.0.1)
|
23
|
+
diff-lcs (>= 1.1.2)
|
24
|
+
rspec-mocks (2.0.1)
|
25
|
+
rspec-core (~> 2.0.1)
|
26
|
+
rspec-expectations (~> 2.0.1)
|
27
|
+
|
28
|
+
PLATFORMS
|
29
|
+
ruby
|
30
|
+
|
31
|
+
DEPENDENCIES
|
32
|
+
activesupport
|
33
|
+
nokogiri
|
34
|
+
rack
|
35
|
+
rack-pubcookie!
|
36
|
+
rack-test
|
37
|
+
rspec
|
data/README.md
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
rack-pubcookie
|
2
|
+
==============
|
3
|
+
|
4
|
+
This is an implementation of the [pubcookie](http://pubcookie.org) protocol over Rack in Ruby.
|
5
|
+
|
6
|
+
Installation
|
7
|
+
------------
|
8
|
+
|
9
|
+
In a Gemfile: `gem 'rack-pubcookie'`
|
10
|
+
|
11
|
+
Otherwise: `gem install rack-pubcookie`
|
12
|
+
|
13
|
+
The gem can be required through either `rack-pubcookie` or `rack/pubcookie`
|
14
|
+
|
15
|
+
Usage
|
16
|
+
-----
|
17
|
+
|
18
|
+
To use pubcookie in your application, you'll need a few things.
|
19
|
+
|
20
|
+
* The URL of the login server
|
21
|
+
* A FQDN for which you have an SSL certificate and which the server is running off of
|
22
|
+
* An application ID (normally this is gotten from the login server provider)
|
23
|
+
* A keyfile. This is generated via the `keyclient` program provided by the official pubcookie source code. Currently there is no equivalent of said program in this gem, so you'll need to create this on another server or acquire it from your login provider
|
24
|
+
* The granting certificate. This is the x509 .crt/cert file which your login provider signs all cookies with. You need a copy of the public key (.crt) file to verify cookies
|
25
|
+
* An SSL-enabled server. Pubcookie will not redirect to an `http` host, but rather only an `https` one
|
26
|
+
|
27
|
+
Once these six pieces have been obtained, you can then use it like this:
|
28
|
+
|
29
|
+
<pre>
|
30
|
+
# This is located in config.ru
|
31
|
+
require 'rack/pubcookie'
|
32
|
+
|
33
|
+
use Rack::Pubcookie::Auth, @login_server, @hostname, @appid, @keyfile_path,
|
34
|
+
@granting_certificate_path
|
35
|
+
|
36
|
+
# @login_server => 'login.example.com[:port]' (port optional)
|
37
|
+
# @hostname => 'myapp.example.com[:port]' (port optional)
|
38
|
+
# @appid => 'myappid'
|
39
|
+
# @keyfile_path => '/path/to/key/file'
|
40
|
+
# @granting_certificate_path => '/path/to/granting.crt'
|
41
|
+
|
42
|
+
run Your::Application
|
43
|
+
</pre>
|
44
|
+
|
45
|
+
Then, in your application, if you need the user authenticated and they're not currently, then you need to redirect them to `/auth/pubcookie`. This will then redirect the user to the login server (with some extra variables and things).
|
46
|
+
|
47
|
+
Once authenticated, the user will then be redirected to `/auth/pubcookie/callback`. If we are able to decrypt the response from the login server, the `REMOTE_USER` environment variable will be set in the request for use.
|
48
|
+
|
49
|
+
The response to `/auth/pubcookie/callback` will also set the session cookie for the current user. It has the `secure` flag, so it will only be sent on HTTPS connections.
|
50
|
+
|
51
|
+
Also, every request where the pubcookie session cookie is present, the `REMOTE_USER` variable will be set. This only applies to `https` connections before the expiration date of the cookie
|
52
|
+
|
53
|
+
License
|
54
|
+
----
|
55
|
+
Copyright (c) 2010 Alex Crichton
|
56
|
+
|
57
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
58
|
+
of this software and associated documentation files (the "Software"), to deal
|
59
|
+
in the Software without restriction, including without limitation the rights
|
60
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
61
|
+
copies of the Software, and to permit persons to whom the Software is
|
62
|
+
furnished to do so, subject to the following conditions:
|
63
|
+
|
64
|
+
The above copyright notice and this permission notice shall be included in
|
65
|
+
all copies or substantial portions of the Software.
|
66
|
+
|
67
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
68
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
69
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
70
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
71
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
72
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
73
|
+
THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
desc "Build the C extensions"
|
5
|
+
task :build_extensions do
|
6
|
+
Dir.chdir(File.expand_path('../ext/openssl', __FILE__)) do
|
7
|
+
sh 'make distclean' if File.exists? 'Makefile'
|
8
|
+
sh 'ruby extconf.rb && make'
|
9
|
+
end
|
10
|
+
end
|
data/ext/openssl/evp.c
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
#include <openssl/evp.h>
|
2
|
+
#include <openssl/x509.h>
|
3
|
+
#include <ruby.h>
|
4
|
+
|
5
|
+
#define GetX509(obj, x509) Data_Get_Struct(obj, X509, x509)
|
6
|
+
|
7
|
+
#ifndef RUBY_19
|
8
|
+
# define RSTRING_LEN(s) (RSTRING(s)->len)
|
9
|
+
# define RSTRING_PTR(s) (RSTRING(s)->ptr)
|
10
|
+
#endif
|
11
|
+
|
12
|
+
VALUE evp_verify_md5(VALUE self, VALUE cert, VALUE signature, VALUE str) {
|
13
|
+
X509 *x509;
|
14
|
+
EVP_MD_CTX ctx;
|
15
|
+
EVP_PKEY *key;
|
16
|
+
|
17
|
+
GetX509(cert, x509);
|
18
|
+
key = X509_extract_key(x509);
|
19
|
+
|
20
|
+
EVP_VerifyInit(&ctx, EVP_md5());
|
21
|
+
EVP_VerifyUpdate(&ctx, RSTRING_PTR(str), RSTRING_LEN(str));
|
22
|
+
|
23
|
+
int ret_val = EVP_VerifyFinal(&ctx,
|
24
|
+
(unsigned char*) RSTRING_PTR(signature),
|
25
|
+
(unsigned int) RSTRING_LEN(signature),
|
26
|
+
key);
|
27
|
+
|
28
|
+
return ret_val == 1 ? Qtrue : Qfalse;
|
29
|
+
}
|
30
|
+
|
31
|
+
Init_evp() {
|
32
|
+
VALUE cOpenSSL = rb_define_module("OpenSSL");
|
33
|
+
VALUE cEVP = rb_define_module_under(cOpenSSL, "EVP");
|
34
|
+
|
35
|
+
rb_define_singleton_method(cEVP, "verify_md5", evp_verify_md5, 3);
|
36
|
+
}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Rack
|
2
|
+
module Pubcookie
|
3
|
+
module AES
|
4
|
+
|
5
|
+
def aes_decrypt bytes, index1, index2
|
6
|
+
# When an example of an AES encrypted cookie is acquired, the method for
|
7
|
+
# decryption is outlined at https://wiki.doit.wisc.edu/confluence/display/WEBISO/Pubcookie+Granting+Reply+Interface
|
8
|
+
# For now, I have no examples of AES encrypted cookies, so with nothing
|
9
|
+
# to test on I wouldn't be sure if this were working.
|
10
|
+
|
11
|
+
raise 'I need an example!'
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'active_support/core_ext/object/to_query'
|
2
|
+
require 'openssl'
|
3
|
+
require 'openssl/evp'
|
4
|
+
require 'base64'
|
5
|
+
|
6
|
+
module Rack
|
7
|
+
module Pubcookie
|
8
|
+
class Auth
|
9
|
+
|
10
|
+
include AES
|
11
|
+
include DES
|
12
|
+
|
13
|
+
def initialize app, login_server, host, appid, keyfile, granting_cert,
|
14
|
+
opts = {}
|
15
|
+
@app = app
|
16
|
+
@login_server = login_server
|
17
|
+
@host = host
|
18
|
+
@appid = appid
|
19
|
+
@keyfile = keyfile
|
20
|
+
@granting = OpenSSL::X509::Certificate.new(::File.read(granting_cert))
|
21
|
+
::File.open(@keyfile, 'rb'){ |f| @key = f.read.bytes.to_a }
|
22
|
+
|
23
|
+
@options = opts
|
24
|
+
@options[:expires_after] = 24 * 3600 # 24 hrs
|
25
|
+
end
|
26
|
+
|
27
|
+
def call env
|
28
|
+
request = Rack::Request.new env
|
29
|
+
|
30
|
+
if request.path == '/auth/pubcookie'
|
31
|
+
response = Rack::Response.new login_page_html
|
32
|
+
else
|
33
|
+
request.env['REMOTE_USER'] = extract_username request
|
34
|
+
status, headers, body = @app.call(request.env)
|
35
|
+
response = Rack::Response.new body, status, headers
|
36
|
+
|
37
|
+
if !request.params['pubcookie_g'].nil? &&
|
38
|
+
request.params['pubcookie_g'] != request.cookies['pubcookie_g']
|
39
|
+
response.set_cookie 'pubcookie_g', :path => '/', :secure => true,
|
40
|
+
:value => request.params['pubcookie_g']
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
response.finish
|
45
|
+
end
|
46
|
+
|
47
|
+
protected
|
48
|
+
|
49
|
+
def extract_username request
|
50
|
+
# If coments below refer to a URL, they mean this one:
|
51
|
+
# http://svn.cac.washington.edu/viewvc/pubcookie/trunk/src/pubcookie.h?view=markup
|
52
|
+
cookie = request.params['pubcookie_g'] || request.cookies['pubcookie_g']
|
53
|
+
|
54
|
+
return nil if cookie.nil?
|
55
|
+
|
56
|
+
bytes = Base64.decode64(cookie).bytes.to_a
|
57
|
+
index2 = bytes.pop
|
58
|
+
index1 = bytes.pop
|
59
|
+
|
60
|
+
if true # Should eventually check for aes vs des encryption...
|
61
|
+
decrypted = des_decrypt bytes, index1, index2
|
62
|
+
else
|
63
|
+
decrypted = aes_decrypt bytes, index1, index2
|
64
|
+
end
|
65
|
+
|
66
|
+
return nil if decrypted.nil?
|
67
|
+
|
68
|
+
# These values are all from the pubcookie source. For more info, see the
|
69
|
+
# above URL. The relevant size definitions are around line 42 and the
|
70
|
+
# struct begins on line 69 ish
|
71
|
+
user, version, appsrvid, appid, type, creds, pre_sess_tok,
|
72
|
+
create_ts, last_ts = decrypted.unpack('A42A4A40A128aaINN')
|
73
|
+
|
74
|
+
create_ts = Time.at create_ts
|
75
|
+
last_ts = Time.at last_ts
|
76
|
+
|
77
|
+
if Time.now < create_ts + @options[:expires_after] && appid == @appid
|
78
|
+
user
|
79
|
+
else
|
80
|
+
nil
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# For a better description on what each of these values are, go to
|
85
|
+
# https://wiki.doit.wisc.edu/confluence/display/WEBISO/Pubcookie+Granting+Request+Interface
|
86
|
+
def request_login_arguments
|
87
|
+
args = {
|
88
|
+
:one => @host, # FQDN of our host
|
89
|
+
:two => @appid, # Our AppID for pubcookie
|
90
|
+
:three => 1, # ?
|
91
|
+
:four => 'a5', # Version/encryption?
|
92
|
+
:five => 'GET', # method, even though we lie?
|
93
|
+
:six => @host, # our host domain name
|
94
|
+
:seven => '/auth/pubcookie/callback', # Where to return
|
95
|
+
:eight => '', # ?
|
96
|
+
:nine => 1, # Probably should be different...
|
97
|
+
:hostname => @host, # Pubcookie needs it 3 times...
|
98
|
+
:referer => '(null)', # Just don't bother
|
99
|
+
:sess_re => 0, # Don't force re-authentication
|
100
|
+
:pre_sess_tok => Kernel.rand(2000000), # Just a random 32bit number
|
101
|
+
:flag => 0, # ?
|
102
|
+
:file => '' # ?
|
103
|
+
}
|
104
|
+
|
105
|
+
args[:seven] = Base64.encode64(args[:seven]).chomp
|
106
|
+
args
|
107
|
+
end
|
108
|
+
|
109
|
+
def login_page_html
|
110
|
+
input_val = Base64.encode64 request_login_arguments.to_query
|
111
|
+
input_val = input_val.gsub("\n", '')
|
112
|
+
|
113
|
+
# Curious why exactly this template? This was taken from the pubcookie
|
114
|
+
# source. We just do the same thing here...
|
115
|
+
<<-HTML
|
116
|
+
<html>
|
117
|
+
<head></head>
|
118
|
+
<body onLoad="document.relay.submit()">
|
119
|
+
<form method='post' action="https://#{@login_server}" name='relay'>
|
120
|
+
<input type='hidden' name='pubcookie_g_req' value="#{input_val}">
|
121
|
+
<input type='hidden' name='post_stuff' value="">
|
122
|
+
<input type='hidden' name='relay_url' value="https://#{@host}/auth/pubcookie/callback">
|
123
|
+
<noscript>
|
124
|
+
<p align='center'>You do not have Javascript turned on, please click the button to continue.
|
125
|
+
<p align='center'>
|
126
|
+
<input type='submit' name='go' value='Continue'>
|
127
|
+
</p>
|
128
|
+
</noscript>
|
129
|
+
</form>
|
130
|
+
</html>
|
131
|
+
HTML
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Rack
|
2
|
+
module Pubcookie
|
3
|
+
module DES
|
4
|
+
|
5
|
+
def des_decrypt bytes, index1, index2
|
6
|
+
# In the URL of #extract_username, the initial IVEC is defined around
|
7
|
+
# line 63 and for some reason only the first byte is used in the xor'ing
|
8
|
+
ivec = @key[index2, 8]
|
9
|
+
ivec = ivec.map{ |i| i ^ 0x4c }
|
10
|
+
|
11
|
+
key = @key[index1, 8]
|
12
|
+
|
13
|
+
c = OpenSSL::Cipher.new('des-cfb')
|
14
|
+
c.decrypt
|
15
|
+
c.key = key.pack('c*')
|
16
|
+
c.iv = ivec.pack('c*')
|
17
|
+
|
18
|
+
# This should be offset by the size of the granting key? Not sure...
|
19
|
+
signature = c.update(bytes[0..127].pack('c*'))
|
20
|
+
decrypted = c.update(bytes[128..-1].pack('c*'))
|
21
|
+
|
22
|
+
if OpenSSL::EVP.verify_md5(@granting, signature, decrypted)
|
23
|
+
decrypted
|
24
|
+
else
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'rack/pubcookie'
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path('../lib', __FILE__)
|
3
|
+
require 'rack/pubcookie/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'rack-pubcookie'
|
7
|
+
s.version = Rack::Pubcookie::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ['Alex Crichton']
|
10
|
+
s.email = ['alex@alexcrichton.com']
|
11
|
+
s.homepage = 'http://github.com/alexcrichton/rack-pubcookie'
|
12
|
+
s.summary = 'An implentation of pubcookie based on Rack in Ruby'
|
13
|
+
s.description = 'Pubcookie finally leaves the world of apache!'
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.extensions = ['ext/openssl/extconf.rb']
|
18
|
+
s.require_paths = ['lib', 'ext']
|
19
|
+
|
20
|
+
s.add_dependency 'rack'
|
21
|
+
s.add_dependency 'activesupport'
|
22
|
+
|
23
|
+
s.add_development_dependency 'rspec'
|
24
|
+
s.add_development_dependency 'rack-test'
|
25
|
+
s.add_development_dependency 'nokogiri'
|
26
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIChjCCAe+gAwIBAgIJAOSRfhmxiDrWMA0GCSqGSIb3DQEBBQUAMFwxCzAJBgNV
|
3
|
+
BAYTAlVTMRUwEwYDVQQIDAxQZW5uc3lsdmFuaWExEzARBgNVBAcMClBpdHRzYnVy
|
4
|
+
Z2gxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0xMDEwMjcy
|
5
|
+
MzUyMjNaFw0xMDExMjYyMzUyMjNaMFwxCzAJBgNVBAYTAlVTMRUwEwYDVQQIDAxQ
|
6
|
+
ZW5uc3lsdmFuaWExEzARBgNVBAcMClBpdHRzYnVyZ2gxITAfBgNVBAoMGEludGVy
|
7
|
+
bmV0IFdpZGdpdHMgUHR5IEx0ZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA
|
8
|
+
x9lZVZApmH7H9PMH6rcTTq7A/wXU0WmVGkorlcZ9oyyUGUbG4zOSpYRWES6U6/Ds
|
9
|
+
h3NJDEZ59Vr1O5aeQ6/hP0yfTuBpEwTiDHZptLTdS6O94WvrsqCyjO7mjs4NGMaI
|
10
|
+
hun8i7VnC2ks/s37w5tlUBHAvee3jC1sZh9kxwio7ZcCAwEAAaNQME4wHQYDVR0O
|
11
|
+
BBYEFCCFiEdU1jEHksMoXauyb5dqrdovMB8GA1UdIwQYMBaAFCCFiEdU1jEHksMo
|
12
|
+
Xauyb5dqrdovMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAofqrTpwX
|
13
|
+
T6Z55kTWGQWqh1CpF0M2G8vMoD61ZjwbUfiPMm/oQs7tiKyAKBK6XUcJBDAI85ev
|
14
|
+
jboQYpTJmgv3BJa5mSdBPwfznbW3kPHIW2I9e44woWZUQpj504f8mge8FIS8nH8o
|
15
|
+
KgPO0SdEYE7yi1UktyY4VlUNm+x9ImjcFOE=
|
16
|
+
-----END CERTIFICATE-----
|
@@ -0,0 +1,33 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIFszCCA5sCCQCl4nWG581TtjANBgkqhkiG9w0BAQUFADCBsjELMAkGA1UEBhMC
|
3
|
+
VVMxDTALBgNVBAgTBElvd2ExEzARBgNVBAcTCkRlcyBNb2luZXMxFjAUBgNVBAoT
|
4
|
+
DUNyaWNodG9uIEluYy4xDTALBgNVBAsTBEFsZXgxMzAxBgNVBAMTKkFsZXggQ3Jp
|
5
|
+
Y2h0b24gU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTEjMCEGCSqGSIb3DQEJ
|
6
|
+
ARYUYWRjcmljaHRvbkBnbWFpbC5jb20wHhcNMDkwNzAyMDMzOTA3WhcNMTkwNjMw
|
7
|
+
MDMzOTA3WjCBgzELMAkGA1UEBhMCVVMxDTALBgNVBAgTBElvd2ExEzARBgNVBAcT
|
8
|
+
CkRlcyBNb2luZXMxFjAUBgNVBAoTDUNyaWNodG9uIEluYy4xEjAQBgNVBAMTCWxv
|
9
|
+
Y2FsaG9zdDEkMCIGCSqGSIb3DQEJARYVYWxleEBhbGV4Y3JpY2h0b24uY29tMIIC
|
10
|
+
IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA41aMpO3Z9mxxovHKZ+oH8Xsn
|
11
|
+
YYzQm+q/DqJc7Wg+8ig7TOsjACI7FK3e/5DCBU7djs/7Jfp03YGvhK4dNcq2MVQ2
|
12
|
+
BG0+Fze5+bsuY2LRoDs21ix9eKccf2Tu/SGMzA1nKzW0I/pTrRhT1BxUbj5irmt8
|
13
|
+
hoED7Bm5Z/Uwyp5HUdGnA3NW8aJdoU2LANCArWnt3482QHdv+5X2kVaMIrW6looa
|
14
|
+
J4XpGzBZJ5/Wiq4L8UmiNaD1pOPjyCZkxhR24utTftJMwkhl8/hPlBf4/v2hd7IZ
|
15
|
+
GXtrtWKyb9rH/kXo55p2QC5D4F8l2ZJur3fN2hMhOjhcINsFsicIx7DcQKjG1cKF
|
16
|
+
qTH14qaKak54rCpSq6t9ZhFnefpvGEe2iEy2FcMAt5ma6OmZn8AxbICBm2R8PFwB
|
17
|
+
Fq/rK2Q6PnrH5z1JlWxt2YBJOLxu0vTssmBdy6l16iK6Z57HeybYwYYhuEpZBjuN
|
18
|
+
F4UOK7eMn5CeQxGqWIqPvoKAMIMPmFr3IfAlDepP59rTtH9D8lcosp6pVopJPyGj
|
19
|
+
lvTUzT0BaO7jgAG0KMYSZ45lYlZ4jYr8rjxFBURVumVeqMYkBdyjgEA6I2F2KLus
|
20
|
+
E2/qQBLMpMG0KrR/Xgg2LzfhiZ4kvYsvn1gmCAhTNEdVfck0F0euQN6NwoRnFq6z
|
21
|
+
4EGC6jeGFxzFfcNCgBsCAwEAATANBgkqhkiG9w0BAQUFAAOCAgEAODArc6lGKnlx
|
22
|
+
mEdUrCddGWZDNH9C2MI0goG9W4+8jBcrQeQ6orGvcta9yHH+4wfMOBj43zYvfOfT
|
23
|
+
3oJcm2PGWa8RQ1E+gsdvTay3+hspYgRg8NHCe3tKLM6tvnLD99rmXG9LrxraebsR
|
24
|
+
T1AQekoaLBZZBqmbfKMDsTSHeBAob1H9+A8/A/V6tm+M5ayPbo9X8HuvJJsqr5Xj
|
25
|
+
eirzyjN99qMncQxhYrcmGflgib2N8kRmwdx7QimTYyOi6TUHQQdbrUZsSMiYN7vT
|
26
|
+
R1zypt0JkgZsrRG4ZLOs3oWQo52gOwxxas/a73ib6vRGImG3FUrddgpgu52I0MR5
|
27
|
+
1VsvGQE6Nxkr3QCHwA+q9/9GksCqI5rbKkDYf4agqL1jItX7zfj4PL5sXeX5h90Q
|
28
|
+
SysXSy5vUv/npHiZwvZgg7BHWZQ8NxlZ3bkPKZYY+rIDKIwAQc9NHCzjw7bcKEhZ
|
29
|
+
o4w44XxsHF1HOPXFwlCg96j6Sq8xLCSxvhh7giCkKCmHcSZcoofb4pyUq5xvB2ev
|
30
|
+
6H56Jvza0qrTT5LglWPdd7UeDwNqbsyZOxGy93l2KE69s0z4ejuNzzGGlYDJid6I
|
31
|
+
iJ5dNwOsVXGQBTXu7+EpCtzD61F4t3mR3KGoSCo50f/QC1R0km1sF6PdV226SwKz
|
32
|
+
wANRDTA9mXpJpurKtol8sUOQ80mZ8K4=
|
33
|
+
-----END CERTIFICATE-----
|
@@ -0,0 +1,32 @@
|
|
1
|
+
key key key key key key key key key key key key key key key key
|
2
|
+
key key key key key key key key key key key key key key key key
|
3
|
+
key key key key key key key key key key key key key key key key
|
4
|
+
key key key key key key key key key key key key key key key key
|
5
|
+
key key key key key key key key key key key key key key key key
|
6
|
+
key key key key key key key key key key key key key key key key
|
7
|
+
key key key key key key key key key key key key key key key key
|
8
|
+
key key key key key key key key key key key key key key key key
|
9
|
+
key key key key key key key key key key key key key key key key
|
10
|
+
key key key key key key key key key key key key key key key key
|
11
|
+
key key key key key key key key key key key key key key key key
|
12
|
+
key key key key key key key key key key key key key key key key
|
13
|
+
key key key key key key key key key key key key key key key key
|
14
|
+
key key key key key key key key key key key key key key key key
|
15
|
+
key key key key key key key key key key key key key key key key
|
16
|
+
key key key key key key key key key key key key key key key key
|
17
|
+
key key key key key key key key key key key key key key key key
|
18
|
+
key key key key key key key key key key key key key key key key
|
19
|
+
key key key key key key key key key key key key key key key key
|
20
|
+
key key key key key key key key key key key key key key key key
|
21
|
+
key key key key key key key key key key key key key key key key
|
22
|
+
key key key key key key key key key key key key key key key key
|
23
|
+
key key key key key key key key key key key key key key key key
|
24
|
+
key key key key key key key key key key key key key key key key
|
25
|
+
key key key key key key key key key key key key key key key key
|
26
|
+
key key key key key key key key key key key key key key key key
|
27
|
+
key key key key key key key key key key key key key key key key
|
28
|
+
key key key key key key key key key key key key key key key key
|
29
|
+
key key key key key key key key key key key key key key key key
|
30
|
+
key key key key key key key key key key key key key key key key
|
31
|
+
key key key key key key key key key key key key key key key key
|
32
|
+
key key key key key key key key key key key key key key key key
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Rack::Pubcookie::Auth do
|
4
|
+
|
5
|
+
include Rack::Test::Methods
|
6
|
+
|
7
|
+
def app
|
8
|
+
Rack::Builder.new {
|
9
|
+
use Rack::Pubcookie::Auth, 'example.com', 'myhost.com', 'testappid',
|
10
|
+
Rack::Test.fixture_path + '/test.com',
|
11
|
+
Rack::Test.fixture_path + '/granting.crt'
|
12
|
+
run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['llama']] }
|
13
|
+
}.to_app
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "a valid session" do
|
17
|
+
before :each do
|
18
|
+
Time.stub(:at).and_return Time.now
|
19
|
+
end
|
20
|
+
|
21
|
+
it "renders a login page to send a request to the login server" do
|
22
|
+
Kernel.stub(:rand).and_return(100)
|
23
|
+
|
24
|
+
get '/auth/pubcookie'
|
25
|
+
|
26
|
+
doc = Nokogiri::HTML(last_response.body)
|
27
|
+
forms = doc.css('form')
|
28
|
+
forms.length.should == 1
|
29
|
+
form = forms.first
|
30
|
+
|
31
|
+
form['method'].should == 'post'
|
32
|
+
form['action'].should == 'https://example.com'
|
33
|
+
|
34
|
+
inputs = form.css('input[type=hidden]')
|
35
|
+
inputs.size.should == 3
|
36
|
+
|
37
|
+
inputs[0]['name'].should == 'pubcookie_g_req'
|
38
|
+
pubcookie_g = Rack::Utils.parse_query Base64.decode64(inputs[0]['value'])
|
39
|
+
pubcookie_g.should == {
|
40
|
+
"one" => "myhost.com", "two" => "testappid", "three" => "1",
|
41
|
+
"four" => "a5", "five" => "GET", "six" => "myhost.com",
|
42
|
+
"seven" => Base64.encode64('/auth/pubcookie/callback').chomp,
|
43
|
+
"eight" => "", "nine" => "1", "hostname" => "myhost.com",
|
44
|
+
"referer" => "(null)", "sess_re" => "0", "pre_sess_tok" => "100",
|
45
|
+
"flag" => "0", "file" => ""
|
46
|
+
}
|
47
|
+
|
48
|
+
inputs[1]['name'].should == 'post_stuff'
|
49
|
+
inputs[1]['value'].should == ''
|
50
|
+
|
51
|
+
inputs[2]['name'].should == 'relay_url'
|
52
|
+
inputs[2]['value'].should == 'https://myhost.com/auth/pubcookie/callback'
|
53
|
+
end
|
54
|
+
|
55
|
+
it "parses the response cookie and sets the REMOTE_USER varible" do
|
56
|
+
post '/auth/pubcookie/callback',
|
57
|
+
'pubcookie_g' => Rack::Test.sample_response
|
58
|
+
|
59
|
+
last_request.env['REMOTE_USER'].should == 'testing'
|
60
|
+
end
|
61
|
+
|
62
|
+
it "authenticates based off of the cookie given" do
|
63
|
+
escaped = Rack::Utils.escape Rack::Test.sample_response
|
64
|
+
set_cookie "pubcookie_g=#{escaped}"
|
65
|
+
|
66
|
+
post '/auth/pubcookie/callback'
|
67
|
+
|
68
|
+
last_request.env['REMOTE_USER'].should == 'testing'
|
69
|
+
end
|
70
|
+
|
71
|
+
it "sets a cookie based on the response from the login server" do
|
72
|
+
escaped = Rack::Utils.escape Rack::Test.sample_response
|
73
|
+
|
74
|
+
post '/auth/pubcookie/callback',
|
75
|
+
'pubcookie_g' => Rack::Test.sample_response
|
76
|
+
|
77
|
+
last_response.headers['Set-Cookie'].should eql(
|
78
|
+
"pubcookie_g=#{escaped}; path=/; secure")
|
79
|
+
end
|
80
|
+
|
81
|
+
it "doesn't have the Set-Cookie header when authenticated by a cookie" do
|
82
|
+
set_cookie "pubcookie_g=#{Rack::Test.sample_response}"
|
83
|
+
|
84
|
+
post '/auth/pubcookie/callback'
|
85
|
+
|
86
|
+
last_response.headers['Set-Cookie'].should be_nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "invalid authentications" do
|
91
|
+
it "doesn't tamper with any variables if no authentication is present" do
|
92
|
+
post '/auth/pubcookie/callback'
|
93
|
+
|
94
|
+
last_response.headers['Set-Cookie'].should be_nil
|
95
|
+
last_request.env['REMOTE_USER'].should be_nil
|
96
|
+
end
|
97
|
+
|
98
|
+
it "doesn't allow an expired valid authentication cookie" do
|
99
|
+
# The login cookie contains a struct with the issued at timestamp as a
|
100
|
+
# 32 bit integer. We use Time.at on the integer to get the time object for
|
101
|
+
# when it was issued. By stubbing that, we can simulate the cookie being
|
102
|
+
# issued 2 days ago without having to work around different cookies and
|
103
|
+
# such
|
104
|
+
Time.stub(:at).and_return Time.now - 48 * 3600
|
105
|
+
|
106
|
+
post '/auth/pubcookie/callback',
|
107
|
+
'pubcookie_g' => Rack::Test.sample_response
|
108
|
+
|
109
|
+
last_request.env['REMOTE_USER'].should be_nil
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe "an invalid signature" do
|
114
|
+
def app
|
115
|
+
Rack::Builder.new {
|
116
|
+
use Rack::Pubcookie::Auth, 'example.com', 'myhost.com', 'testappid',
|
117
|
+
Rack::Test.fixture_path + '/test.com',
|
118
|
+
Rack::Test.fixture_path + '/invalid.crt'
|
119
|
+
run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['llama']] }
|
120
|
+
}.to_app
|
121
|
+
end
|
122
|
+
|
123
|
+
it "realizes the cookie has an invalid signature and discards the cookie" do
|
124
|
+
post '/auth/pubcookie/callback',
|
125
|
+
'pubcookie_g' => Rack::Test.sample_response
|
126
|
+
|
127
|
+
last_request.env['REMOTE_USER'].should be_nil
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
Bundler.require :default, :development
|
4
|
+
|
5
|
+
require 'rspec/core'
|
6
|
+
require 'rack/test'
|
7
|
+
|
8
|
+
RSpec.configure do |c|
|
9
|
+
c.color_enabled = true
|
10
|
+
end
|
11
|
+
|
12
|
+
module Rack
|
13
|
+
module Test
|
14
|
+
|
15
|
+
def self.fixture_path
|
16
|
+
::File.expand_path('../fixtures', __FILE__)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.sample_response
|
20
|
+
# This data was pre-constructed using DES encryption and a dummy test
|
21
|
+
# server. Slightly magical, but the setup was just a test pubcookie server
|
22
|
+
# and a local nginx server handling SSL requests proxing to a small app.
|
23
|
+
# All of the certificates were signed correctly and whatnot and everything
|
24
|
+
# worked out somehow eventually to the cookie below.
|
25
|
+
"6zDplapzi0TAIG3mzfff99EII888gkBUo2fywh1f52ff6Hq4QlrlZ6HbMVSUph7X0x9q0JxbZIfcVjRB9ir92WoihBBRuM4ES/MkqfZs2coB8KeNWQMBls+Nl19IYXv7dJoFjQ8lfBkzgV8BzQ9CIETQqT6AF+7vZBdUddKwu+9kCVtaVk4Pl7Vl0TfzYHCp831xrNxepLx6xtSg33l05CsXtaTVXhm/9khsU8/ONb5xEu21YM5D2OjM1cTUvan3bbCnRHVpqrq1fo8+cNeufP7lUGPoMEjEzjTTLpEXTsfvshX8Ojgl2mowxhbGIkC+MRJuKEAQV9kvdnFmdMWFnMo2j5KJzeiNoRvWg0DfVon8Ya7JAAt+PkpnGu6FwzOHYvYoj+7t1HJIW/iyjXfXjOhzRDSY22YYEOIgqfodCzSURi5hrmUwSsAtXTmpqHrTDN4smIfEUQvQAIMlEhnH2ocSztbJFw=="
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack-pubcookie
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Alex Crichton
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-10-28 00:00:00 -04:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rack
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
version: "0"
|
31
|
+
type: :runtime
|
32
|
+
version_requirements: *id001
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: activesupport
|
35
|
+
prerelease: false
|
36
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 0
|
43
|
+
version: "0"
|
44
|
+
type: :runtime
|
45
|
+
version_requirements: *id002
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
prerelease: false
|
49
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
segments:
|
55
|
+
- 0
|
56
|
+
version: "0"
|
57
|
+
type: :development
|
58
|
+
version_requirements: *id003
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: rack-test
|
61
|
+
prerelease: false
|
62
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
segments:
|
68
|
+
- 0
|
69
|
+
version: "0"
|
70
|
+
type: :development
|
71
|
+
version_requirements: *id004
|
72
|
+
- !ruby/object:Gem::Dependency
|
73
|
+
name: nokogiri
|
74
|
+
prerelease: false
|
75
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
segments:
|
81
|
+
- 0
|
82
|
+
version: "0"
|
83
|
+
type: :development
|
84
|
+
version_requirements: *id005
|
85
|
+
description: Pubcookie finally leaves the world of apache!
|
86
|
+
email:
|
87
|
+
- alex@alexcrichton.com
|
88
|
+
executables: []
|
89
|
+
|
90
|
+
extensions:
|
91
|
+
- ext/openssl/extconf.rb
|
92
|
+
extra_rdoc_files: []
|
93
|
+
|
94
|
+
files:
|
95
|
+
- .gitignore
|
96
|
+
- Gemfile
|
97
|
+
- Gemfile.lock
|
98
|
+
- README.md
|
99
|
+
- Rakefile
|
100
|
+
- ext/openssl/evp.c
|
101
|
+
- ext/openssl/extconf.rb
|
102
|
+
- lib/rack-pubcookie.rb
|
103
|
+
- lib/rack/pubcookie.rb
|
104
|
+
- lib/rack/pubcookie/aes.rb
|
105
|
+
- lib/rack/pubcookie/auth.rb
|
106
|
+
- lib/rack/pubcookie/des.rb
|
107
|
+
- lib/rack/pubcookie/fake.rb
|
108
|
+
- lib/rack/pubcookie/version.rb
|
109
|
+
- rack-pubcookie.gemspec
|
110
|
+
- spec/fixtures/granting.crt
|
111
|
+
- spec/fixtures/invalid.crt
|
112
|
+
- spec/fixtures/test.com
|
113
|
+
- spec/rack/pubcookie/auth_spec.rb
|
114
|
+
- spec/spec_helper.rb
|
115
|
+
has_rdoc: true
|
116
|
+
homepage: http://github.com/alexcrichton/rack-pubcookie
|
117
|
+
licenses: []
|
118
|
+
|
119
|
+
post_install_message:
|
120
|
+
rdoc_options: []
|
121
|
+
|
122
|
+
require_paths:
|
123
|
+
- lib
|
124
|
+
- ext
|
125
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
126
|
+
none: false
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
segments:
|
131
|
+
- 0
|
132
|
+
version: "0"
|
133
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
134
|
+
none: false
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
segments:
|
139
|
+
- 0
|
140
|
+
version: "0"
|
141
|
+
requirements: []
|
142
|
+
|
143
|
+
rubyforge_project:
|
144
|
+
rubygems_version: 1.3.7
|
145
|
+
signing_key:
|
146
|
+
specification_version: 3
|
147
|
+
summary: An implentation of pubcookie based on Rack in Ruby
|
148
|
+
test_files:
|
149
|
+
- spec/fixtures/granting.crt
|
150
|
+
- spec/fixtures/invalid.crt
|
151
|
+
- spec/fixtures/test.com
|
152
|
+
- spec/rack/pubcookie/auth_spec.rb
|
153
|
+
- spec/spec_helper.rb
|