omniauth-dice 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ require 'omniauth/dice/version'
2
+ require 'omniauth/strategies/dice'
3
+ require 'string'
data/lib/string.rb ADDED
@@ -0,0 +1,17 @@
1
+ #
2
+ # Extend the core String class to include `.to_snake`
3
+ #
4
+ class String
5
+ # Attempts to convert a string into a formatted_snake_case_string
6
+ def to_snake
7
+ self.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
8
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
9
+ .tr('-', '_')
10
+ .downcase
11
+ end
12
+
13
+ # Alias to .to_snake
14
+ def underscore
15
+ self.to_snake
16
+ end
17
+ end
@@ -0,0 +1,50 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'omniauth/dice/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'omniauth-dice'
8
+ spec.version = Omniauth::Dice::VERSION
9
+ spec.authors = ['Steven Haddox']
10
+ spec.email = ['steven.haddox@gmail.com']
11
+ spec.summary = %q{DN Interoperable Conversion Expert Strategy}
12
+ spec.description = %q{Simple gem to enable rack powered Ruby apps to authenticate via REST with an enterprise CAS authentication server via X509 client certificates.}
13
+ spec.homepage = "https://github.com/stevenhaddox/omniauth-dice"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.required_ruby_version = '>= 1.9.3'
22
+
23
+ spec.add_development_dependency 'awesome_print'
24
+ spec.add_development_dependency 'bundler'
25
+ spec.add_development_dependency 'capybara'
26
+ spec.add_development_dependency 'coveralls'
27
+ spec.add_development_dependency 'rack_session_access'
28
+ spec.add_development_dependency 'redcarpet'
29
+ spec.add_development_dependency 'rspec'
30
+ spec.add_development_dependency 'rack'
31
+ spec.add_development_dependency 'rack-test'
32
+ spec.add_development_dependency 'rake', '~> 10.0'
33
+ spec.add_development_dependency 'rubocop'
34
+ spec.add_development_dependency 'simplecov'
35
+ spec.add_development_dependency 'simplecov-badge'
36
+ spec.add_development_dependency 'webmock'
37
+ spec.add_development_dependency 'yard'
38
+
39
+ spec.add_dependency 'cert_munger', '~> 0.1'
40
+ spec.add_dependency 'dnc', '~> 0.1'
41
+ spec.add_dependency 'excon'
42
+ spec.add_dependency 'faraday'
43
+ spec.add_dependency 'faraday_middleware'
44
+ spec.add_dependency 'logging', '~> 1.8'
45
+ spec.add_dependency 'multi_xml'
46
+ spec.add_dependency 'omniauth', '~> 1.0'
47
+
48
+ spec.cert_chain = ['certs/stevenhaddox.pem']
49
+ spec.signing_key = File.expand_path("~/.gem/certs/gem-private_key.pem") if $0 =~ /gem\z/
50
+ end
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'openssl'
4
+ require 'awesome_print'
5
+
6
+ root_key = OpenSSL::PKey::RSA.new 2048 # the CA's public/private key
7
+ root_ca = OpenSSL::X509::Certificate.new
8
+ root_ca.version = 2 # cf. RFC 5280 - to make it a "v3" certificate
9
+ root_ca.serial = 1
10
+ root_ca.subject = OpenSSL::X509::Name.parse "/DC=org/DC=ruby-lang/CN=Ruby CA"
11
+ root_ca.issuer = root_ca.subject # root CA's are "self-signed"
12
+ root_ca.public_key = root_key.public_key
13
+ root_ca.not_before = Time.now
14
+ root_ca.not_after = root_ca.not_before + 2 * 365 * 24 * 60 * 60 # 2 years validity
15
+ ef = OpenSSL::X509::ExtensionFactory.new
16
+ ef.subject_certificate = root_ca
17
+ ef.issuer_certificate = root_ca
18
+ root_ca.add_extension(ef.create_extension("basicConstraints","CA:TRUE",true))
19
+ root_ca.add_extension(ef.create_extension("keyUsage","keyCertSign, cRLSign", true))
20
+ root_ca.add_extension(ef.create_extension("subjectKeyIdentifier","hash",false))
21
+ root_ca.add_extension(ef.create_extension("authorityKeyIdentifier","keyid:always",false))
22
+ root_ca.sign(root_key, OpenSSL::Digest::SHA256.new)
23
+
24
+ key = OpenSSL::PKey::RSA.new 2048
25
+ cert = OpenSSL::X509::Certificate.new
26
+ cert.version = 2
27
+ cert.serial = 2
28
+ cert.subject = OpenSSL::X509::Name.parse "/DC=org/DC=ruby-lang/CN=Ruby certificate rbcert"
29
+ cert.issuer = root_ca.subject # root CA is the issuer
30
+ cert.public_key = key.public_key
31
+ cert.not_before = Time.now
32
+ cert.not_after = cert.not_before + 1 * 365 * 24 * 60 * 60 # 1 years validity
33
+ ef = OpenSSL::X509::ExtensionFactory.new
34
+ ef.subject_certificate = cert
35
+ ef.issuer_certificate = root_ca
36
+ cert.add_extension(ef.create_extension("keyUsage","digitalSignature", true))
37
+ cert.add_extension(ef.create_extension("subjectKeyIdentifier","hash",false))
38
+ cert.sign(root_key, OpenSSL::Digest::SHA256.new)
39
+
40
+ File.write('ruby_user.crt', cert)
41
+ File.write('ruby_user.pub', cert.public_key)
@@ -0,0 +1,20 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDQDCCAiigAwIBAgIBAjANBgkqhkiG9w0BAQsFADBCMRMwEQYKCZImiZPyLGQB
3
+ GRYDb3JnMRkwFwYKCZImiZPyLGQBGRYJcnVieS1sYW5nMRAwDgYDVQQDDAdSdWJ5
4
+ IENBMB4XDTE0MTAyMTE0MTU0NFoXDTE1MTAyMTE0MTU0NFowUjETMBEGCgmSJomT
5
+ 8ixkARkWA29yZzEZMBcGCgmSJomT8ixkARkWCXJ1YnktbGFuZzEgMB4GA1UEAwwX
6
+ UnVieSBjZXJ0aWZpY2F0ZSByYmNlcnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
7
+ ggEKAoIBAQDA2Cb1QoNx7ghJv/q7yzAr89zyQbOr7wJN+X9/qfRTZi7o31JRrO21
8
+ 4VFAQeLdf2InfH/yGcME/6dRm0Y9priOShPkRTrerxD8Xf7df/u/R2cUW6WcQUuB
9
+ fFHp8f1FcYuwxsX2gFNpboukRfWSgIvkXG+BAl/HSUjPyjiEYYTk2nkjOUmFObA8
10
+ ip7xJ2n+ZyHl4SggvC+C8QmDQt3dBsZ3C2BovtJ6YQRMJjaEXGWl2fxJfgnJ94aq
11
+ /M3TP5OUvnwDh8MJq4LuPNPc2PBAAvrQ0SFUaZxlxt9cWtnEGhT6jejBWrik8BNk
12
+ YGrU9HIpYeg4a9i5GRDZ6s/xlZ4X+pjlAgMBAAGjMTAvMA4GA1UdDwEB/wQEAwIH
13
+ gDAdBgNVHQ4EFgQUPGVKgxbUCl/OiPHnzeN3qR5BMe4wDQYJKoZIhvcNAQELBQAD
14
+ ggEBAFnSbIvnYubRuEcGux5m1y2UlzSg/GyNDhnWsfPqQQ7FfolFTk6W8+xKuRSg
15
+ foiuV2Od0n1nBMd9ePzoF5QabMHCK1Al2o/P7PkUPgmuAnWWdTLikNtt6H6v9WLe
16
+ R9Ng37xv9jOVFOQjYCYvc94cJe1OBXW7ppkRY3kUQQbIrO7Imwi8gB8KT4qwu/Ld
17
+ Xt53I/aFMQ0JSkqhJ4Bhff/Ol2RcDjEt4U6Hne6lRVB9qCAz0gE8MN4HagWOlS1U
18
+ Nxd+cde+JIVBHuDz63gExGPCdLBRL/Mi3LORpZT41/ryw3JCqTir0dgPXsVTtJPr
19
+ iMZ2EcsmShyBUKKj2AIok5gZzgM=
20
+ -----END CERTIFICATE-----
@@ -0,0 +1,9 @@
1
+ -----BEGIN PUBLIC KEY-----
2
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwNgm9UKDce4ISb/6u8sw
3
+ K/Pc8kGzq+8CTfl/f6n0U2Yu6N9SUaztteFRQEHi3X9iJ3x/8hnDBP+nUZtGPaa4
4
+ jkoT5EU63q8Q/F3+3X/7v0dnFFulnEFLgXxR6fH9RXGLsMbF9oBTaW6LpEX1koCL
5
+ 5FxvgQJfx0lIz8o4hGGE5Np5IzlJhTmwPIqe8Sdp/mch5eEoILwvgvEJg0Ld3QbG
6
+ dwtgaL7SemEETCY2hFxlpdn8SX4JyfeGqvzN0z+TlL58A4fDCauC7jzT3NjwQAL6
7
+ 0NEhVGmcZcbfXFrZxBoU+o3owVq4pPATZGBq1PRyKWHoOGvYuRkQ2erP8ZWeF/qY
8
+ 5QIDAQAB
9
+ -----END PUBLIC KEY-----
@@ -0,0 +1,27 @@
1
+ {
2
+ "citizenshipStatus": "US",
3
+ "country": "USA",
4
+ "dn": "cn=pr. twilight sparkle,ou=c001,ou=mlp,ou=pny,o=princesses of celestia,c=us",
5
+ "email": "twilight@example.org",
6
+ "firstName": "twilight",
7
+ "lastName": "sparkle",
8
+ "fullName": "twilight sparkle",
9
+ "grantBy": [
10
+ "princess celestia"
11
+ ],
12
+ "organizations": [
13
+ "princesses",
14
+ "librarians",
15
+ "unicorns"
16
+ ],
17
+ "uid": "twilight.sparkle",
18
+ "dutyorg": "ponyville library",
19
+ "visas": [
20
+ "EQUESTRIA",
21
+ "CLOUDSDALE"
22
+ ],
23
+ "affiliations": [
24
+ "WONDERBOLTS"
25
+ ],
26
+ "telephoneNumber": "555-555-5555"
27
+ }
@@ -0,0 +1,20 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <userinfo>
3
+ <citizenshipStatus>US</citizenshipStatus>
4
+ <country>USA</country>
5
+ <dn>cn=pr. twilight sparkle,ou=c001,ou=mlp,ou=pny,o=princesses of celestia,c=us</dn>
6
+ <email>twilight@example.org</email>
7
+ <firstName>twilight</firstName>
8
+ <lastName>sparkle</lastName>
9
+ <fullName>twilight sparkle</fullName>
10
+ <grantBy>princess celestia</grantBy>
11
+ <organizations>princesses</organizations>
12
+ <organizations>librarians</organizations>
13
+ <organizations>unicorns</organizations>
14
+ <uid>twilight.sparkle</uid>
15
+ <dutyorg>ponyville library</dutyorg>
16
+ <visas>EQUASTRIA</visas>
17
+ <visas>CLOUDSDALE</visas>
18
+ <affiliations>WONDERBOLTS</affiliations>
19
+ <telephoneNumber>555-555-5555</telephoneNumber>
20
+ </userinfo>
@@ -0,0 +1,201 @@
1
+ require 'spec_helper'
2
+ require'dnc'
3
+
4
+ class MockDice; end
5
+ describe OmniAuth::Strategies::Dice, type: :strategy do
6
+ attr_accessor :app
7
+ let(:auth_hash) { full_auth_hash }
8
+ let!(:user_cert) { File.read('spec/certs/ruby_user.crt') }
9
+ let!(:raw_dn) { '/DC=org/DC=ruby-lang/CN=Ruby certificate rbcert' }
10
+ let!(:user_dn) { DN.new(dn_string: '/DC=org/DC=ruby-lang/CN=Ruby certificate rbcert') }
11
+ let(:raw_issuer_dn) { '/DC=org/DC=ruby-lang/CN=Ruby CA' }
12
+ let(:issuer_dn) { 'CN=RUBY CA,DC=RUBY-LANG,DC=ORG' }
13
+ let!(:valid_user_json) { File.read('spec/fixtures/valid_auth.json') }
14
+ let(:valid_user_xml) { File.read('spec/fixtures/valid_auth.xml') }
15
+
16
+ def full_auth_hash
17
+ {
18
+ "provider"=>"Dice",
19
+ "uid"=>"cn=ruby certificate rbcert,dc=ruby-lang,dc=org",
20
+ "extra" => {
21
+ "raw_info" => valid_user_json
22
+ },
23
+ "info" => {
24
+ "dn" => "cn=pr. twilight sparkle,ou=c001,ou=mlp,ou=pny,o=princesses of celestia,c=us",
25
+ "email" => "twilight@example.org",
26
+ "first_name" => "twilight",
27
+ "last_name" => "sparkle",
28
+ "full_name" => "twilight sparkle",
29
+ "common_name" => "pr. twilight sparkle",
30
+ "name" => "pr. twilight sparkle",
31
+ "citizenship_status" => "US",
32
+ "country" => "USA",
33
+ "grant_by" => [
34
+ "princess celestia"
35
+ ],
36
+ "organizations" => [
37
+ "princesses",
38
+ "librarians",
39
+ "unicorns"
40
+ ],
41
+ "uid" => "twilight.sparkle",
42
+ "dutyorg" => "ponyville library",
43
+ "visas" => [
44
+ "EQUESTRIA",
45
+ "CLOUDSDALE"
46
+ ],
47
+ "affiliations" => [
48
+ "WONDERBOLTS"
49
+ ],
50
+ "telephone_number" => "555-555-5555",
51
+ "primary_visa?" => true,
52
+ "likely_npe?" => false
53
+ }
54
+ }
55
+ end
56
+
57
+ # customize rack app for testing, if block is given, reverts to default
58
+ # rack app after testing is done
59
+ def set_app!(dice_options = {})
60
+ dice_options = {:model => MockDice}.merge(dice_options)
61
+ old_app = self.app
62
+ self.app = Rack::Builder.app do
63
+ use Rack::Session::Cookie, :secret => '1337geeks'
64
+ use RackSessionAccess::Middleware
65
+ use OmniAuth::Strategies::Dice, dice_options
66
+ run lambda{|env| [404, {'env' => env}, ["HELLO!"]]}
67
+ end
68
+ if block_given?
69
+ yield
70
+ self.app = old_app
71
+ end
72
+ self.app
73
+ end
74
+
75
+ before(:all) do
76
+ defaults={
77
+ cas_server: 'http://example.org',
78
+ authentication_path: '/dn'
79
+ }
80
+ set_app!(defaults)
81
+ end
82
+
83
+
84
+ describe '#request_phase' do
85
+ it 'should fail without a client DN' do
86
+ expect { get '/auth/dice' }.to raise_error(OmniAuth::Error, 'You need a valid DN to authenticate.')
87
+ end
88
+
89
+ it "should set the client & issuer's DN (from certificate)" do
90
+ header 'Ssl-Client-Cert', user_cert
91
+ get '/auth/dice'
92
+ expect(last_request.env['HTTP_SSL_CLIENT_CERT']).to eq(user_cert)
93
+ expect(last_request.url).to eq('http://example.org/auth/dice')
94
+ expect(last_request.env['rack.session']['omniauth.params']['user_dn']).to eq(user_dn.to_s)
95
+ expect(last_request.env['rack.session']['omniauth.params']['issuer_dn']).to eq(issuer_dn)
96
+ expect(last_response.location).to eq('http://example.org/auth/dice/callback')
97
+ end
98
+
99
+ it "should set the client's DN (from header)" do
100
+ header 'Ssl-Client-S-Dn', raw_dn
101
+ get '/auth/dice'
102
+ expect(last_request.env['HTTP_SSL_CLIENT_S_DN']).to eq(raw_dn)
103
+ expect(last_request.url).to eq('http://example.org/auth/dice')
104
+ expect(last_request.env['rack.session']['omniauth.params']['user_dn']).to eq(user_dn.to_s)
105
+ expect(last_request.env['rack.session']['omniauth.params']['issuer_dn']).to be_nil
106
+ expect(last_response.location).to eq('http://example.org/auth/dice/callback')
107
+ end
108
+
109
+ it "should set the issuer's DN (from header)" do
110
+ header 'Ssl-Client-S-Dn', raw_dn
111
+ header 'Ssl-Client-I-Dn', raw_issuer_dn
112
+ get '/auth/dice'
113
+ expect(last_request.env['HTTP_SSL_CLIENT_I_DN']).to eq(raw_issuer_dn)
114
+ expect(last_request.url).to eq('http://example.org/auth/dice')
115
+ expect(last_request.env['rack.session']['omniauth.params']['issuer_dn']).to eq(issuer_dn)
116
+ expect(last_response.location).to eq('http://example.org/auth/dice/callback')
117
+ end
118
+ end
119
+
120
+ describe '#callback_phase' do
121
+ before(:each) do
122
+ set_app!({
123
+ cas_server: 'https://example.org:3000',
124
+ authentication_path: '/dn',
125
+ dnc_options: { transformation: 'downcase' },
126
+ ssl_config: {
127
+ ca_file: 'spec/certs/CA.pem',
128
+ client_cert: 'spec/certs/client.pem',
129
+ client_key: 'spec/certs/key.np.pem'
130
+ },
131
+ primary_visa: 'CLOUDSDALE'
132
+ })
133
+
134
+ stub_request(:get, "https://example.org:3000/dn/cn=ruby%20certificate%20rbcert,dc=ruby-lang,dc=org/info.json?issuerDN=cn=ruby%20ca,dc=ruby-lang,dc=org").
135
+ with(:headers => {'Accept'=>'application/json', 'Content-Type'=>'application/json', 'Host'=>'example.org:3000', 'User-Agent'=>/^Faraday via Ruby.*$/, 'X-Xsrf-Useprotection'=>'false'}).
136
+ to_return(status: 200, body: valid_user_json, headers: {})
137
+ end
138
+
139
+ context 'success' do
140
+ it 'should return a 200 with a JSON object of user information on success' do
141
+ header 'Ssl-Client-Cert', user_cert
142
+ get '/auth/dice'
143
+ follow_redirect!
144
+ expect(last_response.location).to eq('/')
145
+ raw_info = last_request.env['rack.session']['omniauth.auth']['extra']['raw_info']
146
+ expect(raw_info).to eq(valid_user_json)
147
+ end
148
+
149
+ it 'should return an omniauth auth_hash' do
150
+ header 'Ssl-Client-Cert', user_cert
151
+ get '/auth/dice'
152
+ follow_redirect!
153
+ expect(last_response.location).to eq('/')
154
+ raw_info = last_request.env['rack.session']['omniauth.auth']['extra']['raw_info']
155
+ expect(last_request.env['rack.session']['omniauth.auth']).to be_kind_of(Hash)
156
+ ap '>'*40
157
+ ap last_request.env['rack.session']['omniauth.auth'].sort
158
+ ap '<'*40
159
+ ap auth_hash.sort
160
+ expect(last_request.env['rack.session']['omniauth.auth'].sort).to eq(auth_hash.sort)
161
+ end
162
+
163
+ it 'should return a 200 with an XML object of user information on success' do
164
+ set_app!({
165
+ cas_server: 'https://example.org:3000',
166
+ authentication_path: '/dn',
167
+ format_header: 'application/xml',
168
+ format: 'xml',
169
+ dnc_options: { transformation: 'downcase' },
170
+ ssl_config: {
171
+ ca_file: 'spec/certs/CA.pem',
172
+ client_cert: 'spec/certs/client.pem',
173
+ client_key: 'spec/certs/key.np.pem'
174
+ }
175
+ })
176
+ stub_request(:get, "https://example.org:3000/dn/cn=ruby%20certificate%20rbcert,dc=ruby-lang,dc=org/info.xml?issuerDN=cn=ruby%20ca,dc=ruby-lang,dc=org").
177
+ with(:headers => {'Accept'=>'application/xml', 'Content-Type'=>'application/xml', 'Host'=>'example.org:3000', 'User-Agent'=>/^Faraday via Ruby.*$/, 'X-Xsrf-Useprotection'=>'false'}).
178
+ to_return(status: 200, body: valid_user_xml, headers: {})
179
+
180
+ header 'Ssl-Client-Cert', user_cert
181
+ get '/auth/dice'
182
+ follow_redirect!
183
+ expect(last_response.location).to eq('/')
184
+ raw_info = last_request.env['rack.session']['omniauth.auth']['extra']['raw_info']
185
+ expect(raw_info).to eq(valid_user_xml)
186
+ end
187
+ end
188
+
189
+ context 'fail' do
190
+ it 'should raise a 404 with text for a non-existent user DN' do
191
+ stub_request(:get, "https://example.org:3000/dn/cn=ruby%20certificate%20rbcert,dc=ruby-lang,dc=org/info.json?issuerDN=cn=ruby%20ca,dc=ruby-lang,dc=org").
192
+ with(:headers => {'Accept'=>'application/json', 'Content-Type'=>'application/json', 'Host'=>'example.org:3000', 'User-Agent'=>/^Faraday via Ruby.*$/, 'X-Xsrf-Useprotection'=>'false'}).
193
+ to_return(status: 404, body: "User of dn:cn=ruby certificate rbcert,dc=ruby-lang,dc=org not found", headers: {})
194
+
195
+ header 'Ssl-Client-Cert', user_cert
196
+ get '/auth/dice'
197
+ expect { get '/auth/dice'; follow_redirect! }.to raise_error(OmniAuth::Error, 'invalid_credentials')
198
+ end
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,161 @@
1
+ require 'spec_helper'
2
+
3
+ describe OmniAuth::Strategies::Dice do
4
+ let!(:app) { TestRackApp.new }
5
+ let(:invalid_subject) { OmniAuth::Strategies::Dice.new(app) }
6
+ let(:dice_default_opts) { {
7
+ cas_server: 'https://dice.dev',
8
+ authentication_path: '/users'
9
+ } }
10
+ let(:valid_subject) {
11
+ OmniAuth::Strategies::Dice.new(app, dice_default_opts )
12
+ }
13
+ let!(:client_dn_from_cert) { '/DC=org/DC=ruby-lang/CN=Ruby certificate rbcert' }
14
+ let(:client_dn_reversed) { client_dn_from_cert.split('/').reverse.join('/') }
15
+ let(:formatted_client_dn) { 'CN=RUBY CERTIFICATE RBCERT,DC=RUBY-LANG,DC=ORG' }
16
+
17
+ # Travis-CI hack?
18
+ before(:all) do
19
+ @rack_env = ENV['RACK_ENV']
20
+ ENV['RACK_ENV'] = 'test'
21
+ end
22
+
23
+ context "invalid params" do
24
+ subject { invalid_subject }
25
+ let(:subject_without_authentication_path) { OmniAuth::Strategies::Dice.new(app, cas_server: 'https://dice.dev') }
26
+
27
+ it 'should require a cas server url' do
28
+ expect{ subject }.to raise_error(RequiredCustomParamError, "omniauth-dice error: cas_server is required")
29
+ end
30
+
31
+ it 'should require an authentication path' do
32
+ expect{ subject_without_authentication_path }.to raise_error(RequiredCustomParamError, "omniauth-dice error: authentication_path is required")
33
+ end
34
+ end
35
+
36
+ context "defaults" do
37
+ subject { valid_subject }
38
+ it 'should have the correct name' do
39
+ expect(subject.options.name).to eq('dice')
40
+ end
41
+
42
+ it "should return the default options" do
43
+ expect(subject.options.format).to eq('json')
44
+ expect(subject.options.format_header).to eq('application/json')
45
+ end
46
+ end
47
+
48
+ context "configured with options" do
49
+ subject { valid_subject }
50
+
51
+ it 'should have the configured CAS server URL' do
52
+ expect(subject.options.cas_server).to eq("https://dice.dev")
53
+ end
54
+
55
+ it 'should have the configured authorization path' do
56
+ expect(subject.options.authentication_path).to eq('/users')
57
+ end
58
+ end
59
+
60
+ context ".format_dn" do
61
+ subject { valid_subject }
62
+
63
+ it 'should ensure the client DN format is in the proper order' do
64
+ formatted_cert_dn = subject.format_dn(client_dn_from_cert)
65
+ expect(formatted_cert_dn).to eq(formatted_client_dn)
66
+
67
+ formatted_reverse_client_dn = subject.format_dn(client_dn_reversed)
68
+ expect(formatted_reverse_client_dn).to eq(formatted_client_dn)
69
+ end
70
+ end
71
+
72
+ context ".set_name" do
73
+ before do
74
+ @info_hash = {
75
+ 'common_name' => 'twilight.sparkle',
76
+ 'full_name' => 'Princess Twilight Sparkle',
77
+ 'first_name' => 'twilight',
78
+ 'last_name' => 'sparkle'
79
+ }
80
+ end
81
+
82
+ it 'should not set a name field if it is already defined' do
83
+ dice = OmniAuth::Strategies::Dice.new( app, dice_default_opts )
84
+ name = dice.send( :set_name, @info_hash.merge({'name' => 'nightmare moon'}) )
85
+ expect(name).to eq('nightmare moon')
86
+ end
87
+
88
+ it 'should default to :cn then :first_name and finally :first_last_name' do
89
+ dice = OmniAuth::Strategies::Dice.new( app, dice_default_opts )
90
+ # With only full_name available
91
+ name = dice.send(:set_name, { 'full_name' => @info_hash['full_name'] })
92
+ expect(name).to eq(@info_hash['full_name'])
93
+ # With only first_name and last_name available
94
+ name = dice.send(:set_name, { 'first_name' => @info_hash['first_name'], 'last_name' => @info_hash['last_name'] })
95
+ expect(name).to eq("#{@info_hash['first_name']} #{@info_hash['last_name']}")
96
+ # With only the dn available
97
+ name = dice.send(:set_name, { 'common_name' => @info_hash['common_name'] })
98
+ expect(name).to eq(@info_hash['common_name'])
99
+ end
100
+
101
+ it 'should support custom name formatting set as :cn' do
102
+ dice = OmniAuth::Strategies::Dice.new( app, dice_default_opts.merge({name_format: :cn}) )
103
+ name = dice.send(:set_name, @info_hash)
104
+ expect(name).to eq(@info_hash['common_name'])
105
+ end
106
+
107
+ it 'should support custom name formatting set as :full_name' do
108
+ dice = OmniAuth::Strategies::Dice.new( app, dice_default_opts.merge({name_format: :full_name}) )
109
+ name = dice.send(:set_name, @info_hash)
110
+ expect(name).to eq(@info_hash['full_name'])
111
+ end
112
+
113
+ it 'should support custom name formatting set as :first_last_name' do
114
+ dice = OmniAuth::Strategies::Dice.new( app, dice_default_opts.merge({name_format: :first_last_name}) )
115
+ name = dice.send(:set_name, @info_hash)
116
+ expect(name).to eq("#{@info_hash['first_name']} #{@info_hash['last_name']}")
117
+ end
118
+ end
119
+
120
+ context ".identify_npe" do
121
+ before do
122
+ @dice = OmniAuth::Strategies::Dice.new( app, dice_default_opts )
123
+ @all_info = {
124
+ 'dn' => 'cn=twilight.sparkle,ou=c001,ou=mlp,ou=pny,o=princesses of celestia,c=us',
125
+ 'full_name' => 'Princess Twilight Sparkle',
126
+ 'first_name' => 'twilight',
127
+ 'last_name' => 'sparkle',
128
+ 'email' => 'twilight@example.org'
129
+ }
130
+ end
131
+
132
+ it "should identify a client as a likely npe when the CN contains a *.tld" do
133
+ npe = @dice.send( :identify_npe, @all_info.merge({'common_name' => 'twilight.mlp.com'}) )
134
+ expect(npe).to eq(true)
135
+ end
136
+
137
+ it "should identify a client as a likely npe when there is a DN & no email" do
138
+ ['email'].each { |k| @all_info.delete(k) }
139
+ npe = @dice.send(:identify_npe, @all_info)
140
+ expect(npe).to eq(true)
141
+ end
142
+
143
+ it "should identify a client as a likely npe when there is a DN, email, and NO name fields" do
144
+ ['full_name', 'first_name', 'last_name'].each { |k| @all_info.delete(k) }
145
+ npe = @dice.send(:identify_npe, @all_info)
146
+ expect(npe).to eq(true)
147
+ end
148
+
149
+ it "should identify a client as not an npe when there is a DN, email, and ANY name field" do
150
+ name_keys = ['full_name', 'first_name', 'last_name']
151
+ names = ['Twilight Sparkle', 'Twilight', 'Sparkle']
152
+ name_keys.each{ |key| @all_info.delete(key) }
153
+ name_keys.each_with_index do |key, index|
154
+ name_hash = @all_info.dup
155
+ name_hash[key] = names[index]
156
+ npe = @dice.send(:identify_npe, name_hash)
157
+ expect(npe).to eq(false)
158
+ end
159
+ end
160
+ end
161
+ end