omniauth-dice 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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