rack-pubcookie 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
4
+
5
+ ext/**/*.bundle
6
+ ext/**/Makefile
7
+ ext/**/*.so
8
+ ext/**/*.o
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source :rubygems
2
+
3
+ # Specify your gem's dependencies in rack-pubcookie.gemspec
4
+ gemspec :require => 'rack/pubcookie'
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,9 @@
1
+ require 'mkmf'
2
+
3
+ if RUBY_VERSION =~ /1\.9/
4
+ $CFLAGS << ' -DRUBY_19'
5
+ end
6
+
7
+ if have_header('openssl/evp.h') && have_header('openssl/x509.h')
8
+ create_makefile('evp')
9
+ end
@@ -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,16 @@
1
+ module Rack
2
+ module Pubcookie
3
+ class Fake
4
+
5
+ def initialize app, username
6
+ @app, @username = app, username
7
+ end
8
+
9
+ def call env
10
+ env['REMOTE_USER'] = @username
11
+ @app.call env
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,5 @@
1
+ module Rack
2
+ module Pubcookie
3
+ VERSION = '0.0.1'
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ require 'rack'
2
+
3
+ module Rack
4
+ module Pubcookie
5
+ autoload :VERSION, 'rack/pubcookie/version'
6
+
7
+ autoload :Auth, 'rack/pubcookie/auth'
8
+ autoload :AES, 'rack/pubcookie/aes'
9
+ autoload :DES, 'rack/pubcookie/des'
10
+ end
11
+ 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
@@ -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