omniauth-dice 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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