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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 136390a70c4e465698843d744e9328e4a4b63131
4
+ data.tar.gz: f9822f9f4742f6a7c6c918fbf3a89b4ff9512c7c
5
+ SHA512:
6
+ metadata.gz: 6d2dd8b1326fc262540a5491854bf5536947964872a111a75db27f3b297979b2880f20150bf8e888b1ed89d7cb21ac5809a0bbc25d003439b76799f4369d5ad7
7
+ data.tar.gz: 6cc4801fe4420043826cfc27169b1e524460971cb826132e75a11ce6b734f036da268b9e80a25d6431b5fe49cf66596f7865c91a33011a5f99c3f0707a972d4c
checksums.yaml.gz.sig ADDED
Binary file
data/.coveralls.yml ADDED
@@ -0,0 +1,2 @@
1
+ service_name: travis-ci
2
+ repo_token:
data/.gitignore ADDED
@@ -0,0 +1,41 @@
1
+ *.gem
2
+ *.rbc
3
+ *.yml.a*
4
+ /.config
5
+ /coverage/
6
+ /InstalledFiles
7
+ /pkg/
8
+ /spec/certs/*.pem
9
+ /spec/reports/
10
+ /test/tmp/
11
+ /test/version_tmp/
12
+ /tmp/
13
+
14
+ ## Specific to RubyMotion:
15
+ .dat*
16
+ .repl_history
17
+ build/
18
+
19
+ ## Documentation cache and generated files:
20
+ /.yardoc/
21
+ /_yardoc/
22
+ /doc/
23
+ /rdoc/
24
+
25
+ ## Environment normalisation:
26
+ /.bundle/
27
+ /lib/bundler/man/
28
+ .rvmrc
29
+
30
+ ## Gem ignores
31
+ /Gemfile.lock
32
+ /.ruby-version
33
+ /.ruby-gemset
34
+
35
+ *.so
36
+ *.o
37
+ *.a
38
+ mkmf.log
39
+
40
+ # Custom
41
+ config/gem_sources.yml
data/.rubocop.yml ADDED
@@ -0,0 +1,8 @@
1
+ AllCops:
2
+ Exclude:
3
+ - 'omniauth-casport.gemspec'
4
+ - 'lib/omniauth/casport/version.rb'
5
+ - 'spec/**/*'
6
+ - 'vendor/cache/**/*'
7
+ - 'vendor/bundle/**/*'
8
+ - '**/gems/**/*'
data/.travis.yml ADDED
@@ -0,0 +1,15 @@
1
+ language: ruby
2
+ cache: bundler
3
+
4
+ rvm:
5
+ - ruby-head
6
+ - jruby-head
7
+ - 2.2.0
8
+ - 2.1.5
9
+ - 2.0.0-p590
10
+ - 1.9.3-p551
11
+
12
+ env:
13
+ - RACK_ENV=test
14
+
15
+ script: 'bundle exec rake'
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --markup markdown
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ require 'psych' # Fix double-load bug when requiring yaml
2
+ require 'yaml'
3
+ if File.exist?('config/gem_sources.yml')
4
+ YAML.load_file('config/gem_sources.yml').each do |gem_source|
5
+ puts "Loading gem source: #{gem_source}"
6
+ source gem_source
7
+ end
8
+ else
9
+ source 'https://rubygems.org'
10
+ end
11
+
12
+ # Specify your gem's dependencies in omniauth-dice.gemspec
13
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Steven Haddox
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # Omniauth::Dice [![Gem Version](https://badge.fury.io/rb/omniauth-dice.png)](http://badge.fury.io/rb/omniauth-dice)
2
+
3
+ [![Travis CI](https://travis-ci.org/stevenhaddox/omniauth-dice.svg?branch=master)](https://travis-ci.org/stevenhaddox/omniauth-dice) [![Dependency Status](https://gemnasium.com/stevenhaddox/omniauth-dice.png)](https://gemnasium.com/stevenhaddox/omniauth-dice) [![Coverage Status](https://coveralls.io/repos/stevenhaddox/omniauth-dice/badge.png)](https://coveralls.io/r/stevenhaddox/omniauth-dice) [![Code Climate](https://codeclimate.com/github/stevenhaddox/omniauth-dice/badges/gpa.svg)](https://codeclimate.com/github/stevenhaddox/omniauth-dice) [![Inline docs](http://inch-ci.org/github/stevenhaddox/omniauth-dice.svg?branch=master)](http://inch-ci.org/github/stevenhaddox/omniauth-dice)
4
+
5
+ # **D**N **I**nteroperable **C**onversion **E**xpert
6
+
7
+ omniauth-dice is an internal authentication strategy that authenticates via
8
+ a user's X509 certificate DN string to an Enterprise CAS server via REST.
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+ ```ruby
14
+ gem 'omniauth-dice'
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself with:
21
+
22
+ $ gem install omniauth-dice
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ Setup your OmniAuth::Dice builder like so:
28
+
29
+ ```ruby
30
+ {
31
+ cas_server: 'https://example.org:3000',
32
+ authentication_path: '/dn',
33
+ format_header: 'application/xml', # default is 'application/json'
34
+ format: 'xml', # default is 'json'
35
+ dnc_options: { transformation: 'downcase' }, # see `dnc` gem for all options
36
+ ssl_config: {
37
+ ca_file: 'spec/certs/CA.pem',
38
+ client_cert: 'spec/certs/client.pem',
39
+ client_key: 'spec/certs/key.np.pem'
40
+ } # See OmniAuth::Strategies::Dice.ssl_hash for all options
41
+ }
42
+ ```
43
+
44
+ Full configuration options are as follows:
45
+
46
+ ```
47
+ cas_server [String] Required base URL for CAS server
48
+ authentication_path [String] URL path for endpoint, e.g. '/users'
49
+ return_field [String] Optional path to append after DN string
50
+ ssl_config [Hash] Configuration hash for `Faraday` SSL options
51
+ format_header [String] 'application/json', 'application/xml', etc
52
+ Defaults to 'application/json'
53
+ format [String] 'json', 'xml', etc.
54
+ Defaults to 'json'
55
+ client_cert_header [String] ENV string to access user's X509 cert
56
+ Defaults to 'HTTP_SSL_CLIENT_CERT'
57
+ subject_dn_header [String] ENV string to access user's subject_dn
58
+ Defaults to 'HTTP_SSLC_LIENT_S_DN'
59
+ issuer_dn_header [String] ENV string to access user's issuer_dn
60
+ Defaults to 'HTTP_SSL_CLIENT_I_DN'
61
+ name_format [Symbol] Format for auth_hash['info']['name']
62
+ Defaults to attempting DN common name -> full name -> first & last name
63
+ Valid options are: :cn, :full_name, :first_last_name to override
64
+ ```
65
+
66
+ ### SSL Client Certificate Notes
67
+
68
+ `Faraday` (the HTTP library used by OmniAuth) can accept certificate paths:
69
+
70
+ ```
71
+ client_cert: 'spec/certs/client.pem',
72
+ client_key: 'spec/certs/key.np.pem'
73
+ ```
74
+
75
+ Or it also works with actual certificates (such as to pass a passphrase in):
76
+ ```
77
+ client_cert: File.read('spec/certs/client.pem').to_cert,
78
+ client_key: OpenSSL::PKey::RSA.new(File.read('spec/certs/key.pem'), 'PASSW0RD')
79
+ ```
80
+
81
+ ## Contributing
82
+
83
+ 1. Fork it ( https://github.com/[my-github-username]/omniauth-dice/fork )
84
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
85
+ 3. **Add specs!**
86
+ 4. Commit your changes (`git commit -am 'Add some feature'`)
87
+ 5. Push to the branch (`git push origin my-new-feature`)
88
+ 6. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'rubocop/rake_task'
4
+ require 'coveralls/rake/task'
5
+ Coveralls::RakeTask.new
6
+
7
+ #task default: [:spec, :rubocop, 'coveralls:push']
8
+ task default: [:spec, 'coveralls:push']
9
+
10
+ desc 'Run specs'
11
+ RSpec::Core::RakeTask.new(:spec)
12
+
13
+ desc 'Run rubocop'
14
+ task :rubocop do
15
+ RuboCop::RakeTask.new
16
+ end
17
+
18
+ desc 'Display TODOs, FIXMEs, and OPTIMIZEs'
19
+ task :notes do
20
+ system("grep -r 'OPTIMIZE:\\|FIXME:\\|TODO:' #{Dir.pwd}")
21
+ end
@@ -0,0 +1,21 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDhTCCAm2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMRYwFAYDVQQDDA1zdGV2
3
+ ZW4uaGFkZG94MRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJk/IsZAEZ
4
+ FgNjb20wHhcNMTQxMTE4MDIxMzIwWhcNMTUxMTE4MDIxMzIwWjBEMRYwFAYDVQQD
5
+ DA1zdGV2ZW4uaGFkZG94MRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJ
6
+ k/IsZAEZFgNjb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDanmKr
7
+ vJDcVGMeDbDouLfKvU5ugOcHTXP04QDYSshaMTeuWSm4OXakxk2rxnR7Laq86R+8
8
+ h1NbHMdiZdwlHcpZm9/YD6qjbQhnLNGsezMrNpfZwfy9VnUQY4e0OCAca9vQXKTL
9
+ qC4fiuRD6sQQpyXkiIno0KlJOA4sKtH8vFucPGmhO0FUdlQY5FarDvCvZrtteO6L
10
+ 6/GQFjupFBd9X6zt1XBs28IC+YUw33SN0UJ5JHB45ig0BmeWMXdd4SKWe4ve/2UY
11
+ asgs2miI3HP0wCPs0EF64/8LbuEUyMjHDr3a7+7KIRxYn2H/yUH5Ndqz6yL5G0sf
12
+ jUsC32JuE7VlJwFNAgMBAAGjgYEwfzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAd
13
+ BgNVHQ4EFgQUC8HywyOMPJFCsH7uGW+CeKcZ8+8wIgYDVR0RBBswGYEXc3RldmVu
14
+ LmhhZGRveEBnbWFpbC5jb20wIgYDVR0SBBswGYEXc3RldmVuLmhhZGRveEBnbWFp
15
+ bC5jb20wDQYJKoZIhvcNAQEFBQADggEBAFoac9ZKc20ZXw2R2mWUz7FaJJdUvb7o
16
+ 4rKVzFQkJwvAX+NEdP32yCDViGoEqlA13el5fllllmG3E7Qrw+0JA5B3wrZbVfQA
17
+ v4eX0ZohhW3CXLSz65pd3zfrwPAw0pXs1QKP+IioTuLQoBsGUiIqCPulZvzn/xN2
18
+ KG7SexyfUEXyJRMMigA/mE8h6bYfgKKUmLQVs1uRaXmOI7dKUF6HZJpda51zJH3v
19
+ 42qdwEXvvkODZAD6KAIXPdmbMfBgPbcd+B/4eUA0PyKo+4dgL1NuqX4MPWToevIZ
20
+ O8EKLF2X7NmC6FY1bOsSj/J8r1SOkx0rxgF+geRvY1P+hfNjDfxTsjU=
21
+ -----END CERTIFICATE-----
@@ -0,0 +1 @@
1
+ - https://geminabox.example.com
@@ -0,0 +1,5 @@
1
+ module Omniauth
2
+ module Dice
3
+ VERSION = '0.1.1'
4
+ end
5
+ end
@@ -0,0 +1,422 @@
1
+ require 'faraday'
2
+ require 'faraday_middleware'
3
+ require 'open-uri'
4
+ require 'omniauth'
5
+ require 'cert_munger'
6
+ require 'dnc'
7
+
8
+ class RequiredCustomParamError < StandardError; end
9
+
10
+ module OmniAuth
11
+ module Strategies
12
+
13
+ #
14
+ # Provides omniauth authentication integration with a CAS server
15
+ #
16
+ # @option cas_server [String] Required base URL for CAS server
17
+ # @option authentication_path [String] URL path for endpoint, e.g. '/users'
18
+ # @option return_field [String] Optional path to append after DN string
19
+ # @option ssl_config [Hash] Configuration hash for `Faraday` SSL options
20
+ # @option format_header [String] 'application/json', 'application/xml', etc
21
+ # Defaults to 'application/json'
22
+ # @option format [String] 'json', 'xml', etc.
23
+ # Defaults to 'json'
24
+ # @option client_cert_header [String] ENV string to access user's X509 cert
25
+ # Defaults to 'HTTP_SSL_CLIENT_CERT'
26
+ # @option subject_dn_header [String] ENV string to access user's subject_dn
27
+ # Defaults to 'HTTP_SSLC_LIENT_S_DN'
28
+ # @option issuer_dn_header [String] ENV string to access user's issuer_dn
29
+ # Defaults to 'HTTP_SSL_CLIENT_I_DN'
30
+ # @option name_format [Symbol] Format for auth_hash['info']['name']
31
+ # Defaults to attempting DN common name -> full name -> first & last name
32
+ # Valid options are: :cn, :full_name, :first_last_name to override
33
+ # @option primary_visa_str [String] String to trigger primary visa boolean
34
+ class Dice
35
+ include OmniAuth::Strategy
36
+ attr_accessor :dn, :raw_dn, :data
37
+ args [:cas_server, :authentication_path]
38
+
39
+ def initialize(*args, &block)
40
+ validate_required_params(args)
41
+
42
+ super
43
+ end
44
+
45
+ option :dnc_options, {}
46
+ option :cas_server, nil
47
+ option :authentication_path, nil
48
+ option :return_field, 'info'
49
+ option :ssl_config, {}
50
+ option :format_header, 'application/json'
51
+ option :format, 'json'
52
+ option :client_cert_header, 'HTTP_SSL_CLIENT_CERT'
53
+ option :subject_dn_header, 'HTTP_SSL_CLIENT_S_DN'
54
+ option :issuer_dn_header, 'HTTP_SSL_CLIENT_I_DN'
55
+ option :name_format
56
+ option :primary_visa_str
57
+
58
+ # Reformat DN to expected element order for CAS DN server (via dnc gem).
59
+ def format_dn(dn_str)
60
+ get_dn(dn_str).to_s
61
+ end
62
+
63
+ protected
64
+
65
+ # Change Hashie indifferent access keys back to symbols
66
+ def unhashie(hash)
67
+ tmp_hash = {}
68
+ hash.each do |key, value|
69
+ tmp_hash[key.to_sym] = value
70
+ end
71
+
72
+ tmp_hash
73
+ end
74
+
75
+ def setup_phase(*args)
76
+ log :debug, 'setup_phase'
77
+ super
78
+ end
79
+
80
+ def request_phase
81
+ subject_dn = get_dn_by_type('subject')
82
+ return fail!('You need a valid DN to authenticate.') unless subject_dn
83
+ user_dn = format_dn(subject_dn)
84
+ log :debug, "Formatted user_dn: #{user_dn}"
85
+ return fail!('You need a valid DN to authenticate.') unless user_dn
86
+ set_session_dn(user_dn, 'subject')
87
+ issuer_dn = get_dn_by_type('issuer')
88
+ issuer_dn = format_dn(issuer_dn) if issuer_dn
89
+ log :debug, "Formatted issuer_dn: #{issuer_dn}"
90
+ set_session_dn(issuer_dn, 'issuer') if issuer_dn
91
+
92
+ redirect callback_url
93
+ end
94
+
95
+ def callback_phase
96
+ issuer_dn = env['omniauth.params']['issuer_dn']
97
+ if issuer_dn
98
+ response = connection.get query_url, { issuerDN: issuer_dn }
99
+ else
100
+ response = connection.get query_url
101
+ end
102
+ if !response || response.status.to_i >= 400
103
+ log :error, response.inspect
104
+ return fail!(:invalid_credentials)
105
+ end
106
+ @data = response.body
107
+ create_auth_hash
108
+
109
+ redirect request.env['omniauth.origin'] || '/'
110
+ end
111
+
112
+ private
113
+
114
+ # Coordinate building out the auth_hash
115
+ def create_auth_hash
116
+ log :debug, '.create_auth_hash'
117
+ init_auth_hash
118
+ set_auth_uid
119
+ parse_response_data
120
+ create_auth_info
121
+ end
122
+
123
+ # Initialize the auth_hash expected fields
124
+ def init_auth_hash
125
+ log :debug, '.init_auth_hash'
126
+ session['omniauth.auth'] ||= {
127
+ 'provider' => 'Dice',
128
+ 'uid' => nil,
129
+ 'info' => nil,
130
+ 'extra' => {
131
+ 'raw_info' => nil
132
+ }
133
+ }
134
+ end
135
+
136
+ # Set the user's uid field for the auth_hash
137
+ def set_auth_uid
138
+ log :debug, '.set_auth_uid'
139
+ session['omniauth.auth']['uid'] = env['omniauth.params']['user_dn']
140
+ end
141
+
142
+ # Detect data format, parse with appropriate library
143
+ def parse_response_data
144
+ log :debug, '.parse_response_data'
145
+ session['omniauth.auth']['extra']['raw_info'] = @data
146
+ log :debug, "cas_server response.body:\r\n#{@data}"
147
+ unless @data.class == Hash # Webmock hack
148
+ case options.format.to_sym
149
+ when :json
150
+ @data = JSON.parse(@data, symbolize_names: true)
151
+ when :xml
152
+ @data = MultiXml.parse(@data)['userinfo']
153
+ end
154
+ log :debug, "Formatted response.body data: #{@data}"
155
+ end
156
+
157
+ @data
158
+ end
159
+
160
+
161
+ # Parse CAS server response and assign values as appropriate
162
+ def create_auth_info
163
+ log :debug, '.create_auth_info'
164
+ info = {}
165
+ info = auth_info_defaults(info)
166
+ info = auth_info_dynamic(info)
167
+ info = auth_info_custom(info)
168
+
169
+ session['omniauth.auth']['info'] = info
170
+ end
171
+
172
+ def info_defaults
173
+ [:dn, :email, :firstName, :lastName, :fullName, :citizenshipStatus,
174
+ :country, :grantBy, :organizations, :uid, :dutyorg, :visas,
175
+ :affiliations]
176
+ end
177
+
178
+ # Defualt auth_info fields
179
+ def auth_info_defaults(info)
180
+ info_defaults.each do |key_name|
181
+ info[key_name.to_s.to_snake] = @data[key_name]
182
+ end
183
+
184
+ info
185
+ end
186
+
187
+ # Dynamic auth_info fields
188
+ def auth_info_dynamic(info)
189
+ @data.each do |key, value|
190
+ info[key.to_s.to_snake] = value unless info_defaults.include?(key)
191
+ end
192
+
193
+ info
194
+ end
195
+
196
+ # Custom auth_info fields
197
+ def auth_info_custom(info)
198
+ info['common_name'] = get_dn(info['dn']).cn
199
+ set_name(info)
200
+ has_primary_visa?(info)
201
+ info['likely_npe?'] = identify_npe(info)
202
+
203
+ info
204
+ end
205
+
206
+ # Allow for a custom field for the name, or use a best guess default
207
+ def set_name(info)
208
+ # Do NOT override the value if it's returned from the CAS server
209
+ return info['name'] if info['name']
210
+ info['name'] = case options.name_format
211
+ when :cn
212
+ info['common_name']
213
+ when :full_name
214
+ info['full_name']
215
+ when :first_last_name
216
+ "#{info['first_name']} #{info['last_name']}"
217
+ end
218
+ info['name'] ||= info['common_name'] || info['full_name'] ||
219
+ "#{info['first_name']} #{info['last_name']}"
220
+ end
221
+
222
+ # Determine if client has the primary visa
223
+ def has_primary_visa?(info)
224
+ return info['primary_visa?'] = nil unless info['visas']
225
+ return info['primary_visa?'] = nil unless options.primary_visa
226
+ info['primary_visa?'] = info['visas'].include?(options.primary_visa)
227
+ end
228
+
229
+ # Determine if a client is likely a non-person entity
230
+ def identify_npe(info)
231
+ info['likely_npe?'] = nil
232
+ return true if auth_cn_with_tld?(info['common_name']) == true
233
+ return true if auth_info_missing_email?(info) == true
234
+ return true if auth_has_email_without_names?(info) == true
235
+ return false if auth_has_email_with_any_name?(info) == true
236
+ end
237
+
238
+ # Identify if there's a domain w/ TLD in the common_name
239
+ def auth_cn_with_tld?(common_name)
240
+ !!( common_name =~ /\w{3}\.\w+(\.\w{3,}+)?/ )
241
+ end
242
+
243
+ # Determine if the auth_hash does not have an email address
244
+ def auth_info_missing_email?(info)
245
+ !( info['email'] ) # !! returns false if no email, ! returns true
246
+ end
247
+
248
+ # Determine if the auth_hash has an email but no name fields
249
+ def auth_has_email_without_names?(info)
250
+ return false unless info['email']
251
+ return true if auth_info_has_any_name?(info) == false
252
+ end
253
+
254
+ # Determine if the auth_hash has an email with ANY name field
255
+ def auth_has_email_with_any_name?(info)
256
+ return false unless info['email']
257
+ return true if auth_info_has_any_name?(info) == true
258
+ end
259
+
260
+ # Determine if any name fields are present in the auth_hash['info']
261
+ def auth_info_has_any_name?(info)
262
+ name = info['full_name']
263
+ name ||= info['first_name']
264
+ name ||= info['last_name']
265
+ !!(name)
266
+ end
267
+
268
+ # Coordinate getting DN from cert, fallback to header
269
+ def get_dn_by_type(type='subject')
270
+ raw_dn = get_dn_from_certificate(type)
271
+ raw_dn ||= get_dn_from_header(type)
272
+ end
273
+
274
+ # Reads the DN from headers
275
+ def get_dn_from_header(type)
276
+ headers = request.env
277
+ if type == 'issuer'
278
+ raw_dn = headers["#{options.issuer_dn_header}"]
279
+ else
280
+ raw_dn = headers["#{options.subject_dn_header}"]
281
+ end
282
+ log :debug, "raw_dn (#{type}) from headers: #{raw_dn}"
283
+
284
+ raw_dn
285
+ end
286
+
287
+ # Gets the DN from X509 certificate
288
+ def get_dn_from_certificate(type)
289
+ cert_str = request.env["#{options.client_cert_header}"]
290
+ if cert_str
291
+ client_cert = cert_str.to_cert
292
+ log :debug, "Client certificate:\r\n#{client_cert}"
293
+ raw_dn ||= parse_dn_from_certificate(client_cert, type)
294
+ log :debug, "raw_dn (#{type}) from cert: #{raw_dn}"
295
+ end
296
+
297
+ raw_dn
298
+ end
299
+
300
+ # Parse the DN out of an SSL X509 Client Certificate
301
+ def parse_dn_from_certificate(certificate, type='subject')
302
+ certificate.send(type.to_sym).to_s
303
+ end
304
+
305
+ # Create a Faraday instance with the cas_server & appropriate SSL config
306
+ def connection
307
+ log :debug, '.connection'
308
+
309
+ @conn ||= Faraday.new(url: options.cas_server, ssl: ssl_hash) do |conn|
310
+ conn.headers = headers
311
+ conn.response :logger # log requests to STDOUT
312
+ conn.response :xml, :content_type => /\bxml$/
313
+ conn.response :json, :content_type => /\bjson$/
314
+ conn.adapter :excon
315
+ end
316
+ end
317
+
318
+ def headers
319
+ {
320
+ 'Accept' => options.format_header,
321
+ 'Content-Type' => options.format_header,
322
+ 'X-XSRF-UseProtection' => ('false' if options.format_header),
323
+ 'user-agent' => "Faraday via Ruby #{RUBY_VERSION}"
324
+ }
325
+ end
326
+
327
+ # Build out the query URL for CAS server with DN params
328
+ def query_url
329
+ user_dn = env['omniauth.params']['user_dn']
330
+ build_query = "#{options.cas_server}#{options.authentication_path}"
331
+ build_query += "/#{user_dn}"
332
+ build_query += "/#{options.return_field}.#{options.format}"
333
+ URI::encode(build_query)
334
+ end
335
+
336
+ # Specifies which attributes are required arguments to initialize
337
+ def required_params
338
+ [:cas_server, :authentication_path]
339
+ end
340
+
341
+ # Verify that arguments required to properly run are present or fail hard
342
+ # NOTE: CANNOT call "log" method from initialize block hooks
343
+ def validate_required_params(args)
344
+ required_params.each do |param|
345
+ param_present = nil
346
+ args.each do |arg|
347
+ param_present = true if param_in_arg?(param, arg) == true
348
+ end
349
+
350
+ if param_present.nil?
351
+ error_msg = "omniauth-dice error: #{param} is required"
352
+ fail RequiredCustomParamError, error_msg
353
+ end
354
+ end
355
+ end
356
+
357
+ # Determine if a specified param symbol exists in the passed argument
358
+ # NOTE: CANNOT call "log" method from initialize block hooks
359
+ def param_in_arg?(param, arg)
360
+ if arg.class == Hash
361
+ if arg.key?(param.to_sym)
362
+ true
363
+ else
364
+ false
365
+ end
366
+ else
367
+ false
368
+ end
369
+ end
370
+
371
+ def set_session_dn(dn_string, type='subject')
372
+ dn_type = case type
373
+ when 'subject'
374
+ 'user_dn'
375
+ when 'issuer'
376
+ 'issuer_dn'
377
+ else
378
+ fail "Invalid DN string type"
379
+ end
380
+ session['omniauth.params'] ||= {}
381
+ session['omniauth.params'][dn_type] = dn_string
382
+ end
383
+
384
+ # Dynamically builds out Faraday's SSL config hash by merging passed
385
+ # options hash with the default options.
386
+ #
387
+ # Available Faraday config options include:
388
+ # ca_file (e.g., /usr/lib/ssl/certs/ca-certificates.crt)
389
+ # ca_path (e.g., /usr/lib/ssl/certs)
390
+ # cert_store
391
+ # client_cert
392
+ # client_key
393
+ # certificate
394
+ # private_key
395
+ # verify
396
+ # verify_mode
397
+ # verify_depth
398
+ # version
399
+ def ssl_hash
400
+ ssl_defaults = {
401
+ verify: true,
402
+ verify_depth: 3,
403
+ version: 'TLSv1'
404
+ }
405
+
406
+ custom_config = unhashie(options.ssl_config)
407
+ ssl_defaults.merge(custom_config)
408
+ end
409
+
410
+ # Retrieve DNC default & custom configs
411
+ #
412
+ # @param dn_str [String] The string of text you wish to parse into a DN
413
+ # @return [DN]
414
+ def get_dn(dn_str)
415
+ custom_order = %w(cn l st ou o c street dc uid)
416
+ default_opts = { dn_string: dn_str, string_order: custom_order }
417
+ dnc_config = unhashie(options.dnc_options)
418
+ DN.new( default_opts.merge(dnc_config) )
419
+ end
420
+ end
421
+ end
422
+ end