class OmniAuth::Strategies::Dice
Provides omniauth authentication integration with a CAS server
@option cas_server [String] Required base URL for CAS server @option authentication_path [String] URL path for endpoint, e.g. ‘/users’ @option return_field [String] Optional path to append after DN string @option ssl_config [Hash] Configuration hash for ‘Faraday` SSL options @option format_header [String] ’application/json’, ‘application/xml’, etc
Defaults to 'application/json'
@option format [String] ‘json’, ‘xml’, etc.
Defaults to 'json'
@option client_cert_header [String] ENV string to access user’s X509 cert
Defaults to 'HTTP_SSL_CLIENT_CERT'
@option subject_dn_header [String] ENV string to access user’s subject_dn
Defaults to 'HTTP_SSLC_LIENT_S_DN'
@option issuer_dn_header [String] ENV string to access user’s issuer_dn
Defaults to 'HTTP_SSL_CLIENT_I_DN'
@option name_format [Symbol] Format for auth_hash[‘name’]
Defaults to attempting DN common name -> full name -> first & last name Valid options are: :cn, :full_name, :first_last_name to override
@option primary_visa [String] String
to trigger primary visa boolean
Attributes
Public Instance Methods
# File lib/omniauth/strategies/dice.rb, line 100 def auth_hash log :debug, '.auth_hash' Hashie::Mash.new( 'provider' => name, 'uid' => uid, 'info' => info, 'extra' => extra ) end
# File lib/omniauth/strategies/dice.rb, line 90 def callback_phase response = authenticate_user return fail!(:invalid_credentials) if response.nil? @raw_data = response.body @data = parse_response_data session['omniauth.auth'] ||= auth_hash super end
Reformat DN to expected element order for CAS DN server (via dnc gem).
# File lib/omniauth/strategies/dice.rb, line 55 def format_dn(dn_str) get_dn(dn_str).to_s end
# File lib/omniauth/strategies/dice.rb, line 133 def redirect_for_callback if options.custom_callback_url redirect options.custom_callback_url else if options.use_callback_url == true redirect callback_url else redirect callback_path end end end
# File lib/omniauth/strategies/dice.rb, line 74 def request_phase validate_required_params subject_dn = get_dn_by_type('subject') return fail!('You need a valid DN to authenticate.') unless subject_dn user_dn = format_dn(subject_dn) log :debug, "Formatted user_dn: #{user_dn}" return fail!('You need a valid DN to authenticate.') unless user_dn set_session_dn(user_dn, 'subject') issuer_dn = get_dn_by_type('issuer') issuer_dn = format_dn(issuer_dn) if issuer_dn log :debug, "Formatted issuer_dn: #{issuer_dn}" set_session_dn(issuer_dn, 'issuer') if issuer_dn redirect_for_callback end
Specifies which attributes are required arguments to initialize
# File lib/omniauth/strategies/dice.rb, line 60 def required_params [:cas_server, :authentication_path] end
Determine if required arguments are present or fail hard
# File lib/omniauth/strategies/dice.rb, line 65 def validate_required_params required_params.each do |param| unless options.send(param) error_msg = "omniauth-dice error: #{param} is required" fail RequiredCustomParamError, error_msg end end end
Private Instance Methods
Identify if there’s a domain w/ TLD in the common_name
# File lib/omniauth/strategies/dice.rb, line 240 def auth_cn_with_tld?(common_name) /\w{2}\.\w+(\.\w{3,}+)?/.match(common_name).nil? ? false : true end
Determine if the auth_hash
has an email with ANY name field
# File lib/omniauth/strategies/dice.rb, line 256 def auth_has_email_with_any_name?(info) return false unless info['email'] return true if auth_info_has_any_name?(info) == true end
Determine if the auth_hash
has an email but no name fields
# File lib/omniauth/strategies/dice.rb, line 250 def auth_has_email_without_names?(info) return false unless info['email'] return true if auth_info_has_any_name?(info) == false end
Custom auth_info fields
# File lib/omniauth/strategies/dice.rb, line 198 def auth_info_custom(info) info['common_name'] = get_dn(info['dn']).cn set_name(info) includes_primary_visa?(info) info['likely_npe?'] = identify_npe(info) info end
Defualt auth_info fields
# File lib/omniauth/strategies/dice.rb, line 180 def auth_info_defaults(info) info_defaults.each do |key_name| info[key_name.to_s.to_snake] = @data[key_name] end info end
Dynamic auth_info fields
# File lib/omniauth/strategies/dice.rb, line 189 def auth_info_dynamic(info) @data.each do |key, value| info[key.to_s.to_snake] = value unless info_defaults.include?(key) end info end
Determine if any name fields are present in the auth_hash
# File lib/omniauth/strategies/dice.rb, line 262 def auth_info_has_any_name?(info) [info['full_name'], info['first_name'], info['last_name']].any? end
Determine if the auth_hash
does not have an email address
# File lib/omniauth/strategies/dice.rb, line 245 def auth_info_missing_email?(info) !(info['email']) # !! returns false if no email, ! returns true end
# File lib/omniauth/strategies/dice.rb, line 157 def authenticate_user issuer_dn = env['omniauth.params']['issuer_dn'] if issuer_dn response = connection.get query_url, issuerDn: issuer_dn else response = connection.get query_url end if !response || response.status.to_i >= 400 log :error, response.inspect return nil end response end
Create a Faraday instance with the cas_server & appropriate SSL config
# File lib/omniauth/strategies/dice.rb, line 303 def connection log :debug, '.connection' @conn ||= Faraday.new(url: options.cas_server, ssl: ssl_hash) do |conn| conn.headers = headers conn.response :logger # log requests to STDOUT format = options.format conn.response(:xml, content_type: /\bxml$/) if format == 'xml' conn.response(:json, content_type: /\bjson$/) if format == 'json' conn.adapter :excon end end
Parse data by specified format
# File lib/omniauth/strategies/dice.rb, line 345 def format_data formatted_data = nil unless @raw_data.class == Hash # Webmock hack case options.format.to_sym when :json formatted_data = JSON.parse(@raw_data, symbolize_names: true) when :xml formatted_data = MultiXml.parse(@raw_data)['userinfo'] end end formatted_data end
Retrieve DNC default & custom configs
@param dn_str [String] The string of text you wish to parse into a DN @return [DN]
# File lib/omniauth/strategies/dice.rb, line 402 def get_dn(dn_str) custom_order = %w(cn l st ou o c street dc uid) default_opts = { dn_string: dn_str, string_order: custom_order } dnc_config = unhashie(options.dnc_options) DN.new(default_opts.merge(dnc_config)) end
Coordinate getting DN from cert, fallback to header
# File lib/omniauth/strategies/dice.rb, line 267 def get_dn_by_type(type = 'subject') get_dn_from_certificate(type) || get_dn_from_header(type) end
Gets the DN from X509 certificate
# File lib/omniauth/strategies/dice.rb, line 285 def get_dn_from_certificate(type) cert_str = request.env["#{options.client_cert_header}"] if cert_str client_cert = cert_str.to_cert log :debug, "Client certificate:\r\n#{client_cert}" raw_dn ||= parse_dn_from_certificate(client_cert, type) log :debug, "raw_dn (#{type}) from cert: #{raw_dn}" end raw_dn end
Reads the DN from headers
# File lib/omniauth/strategies/dice.rb, line 272 def get_dn_from_header(type) headers = request.env if type == 'issuer' raw_dn = headers["#{options.issuer_dn_header}"] else raw_dn = headers["#{options.subject_dn_header}"] end log :debug, "raw_dn (#{type}) from headers: #{raw_dn}" raw_dn end
# File lib/omniauth/strategies/dice.rb, line 316 def headers { 'Accept' => options.format_header, 'Content-Type' => options.format_header, 'X-XSRF-UseProtection' => ('false' if options.format_header), 'user-agent' => "Faraday via Ruby #{RUBY_VERSION}" } end
Determine if a client is likely a non-person entity
# File lib/omniauth/strategies/dice.rb, line 231 def identify_npe(info) info['likely_npe?'] = nil return true if auth_cn_with_tld?(info['common_name']) == true return true if auth_info_missing_email?(info) == true return true if auth_has_email_without_names?(info) == true return false if auth_has_email_with_any_name?(info) == true end
Determine if client has the primary visa
# File lib/omniauth/strategies/dice.rb, line 224 def includes_primary_visa?(info) return info['primary_visa?'] = false unless info['visas'] return info['primary_visa?'] = false unless options.primary_visa info['primary_visa?'] = info['visas'].include?(options.primary_visa) end
Default [‘omniauth.auth’] field names
# File lib/omniauth/strategies/dice.rb, line 173 def info_defaults [:dn, :email, :firstName, :lastName, :fullName, :citizenshipStatus, :country, :grantBy, :organizations, :uid, :dutyorg, :visas, :affiliations] end
Parse the DN out of an SSL X509 Client Certificate
# File lib/omniauth/strategies/dice.rb, line 298 def parse_dn_from_certificate(certificate, type = 'subject') certificate.send(type.to_sym).to_s end
Detect data format, parse with appropriate library
# File lib/omniauth/strategies/dice.rb, line 335 def parse_response_data log :debug, "cas_server response.body:\r\n#{@raw_data}" formatted_data = format_data formatted_data = formatted_data.nil? ? @raw_data : formatted_data log :debug, "Formatted response.body: #{formatted_data}" formatted_data end
Build out the query URL for CAS server with DN params
# File lib/omniauth/strategies/dice.rb, line 326 def query_url user_dn = env['omniauth.params']['user_dn'] build_query = "#{options.cas_server}#{options.authentication_path}" build_query += "/#{user_dn}" build_query += "/#{options.return_field}.#{options.format}" URI.encode(build_query) end
Allow for a custom field for the name, or use a best guess default
# File lib/omniauth/strategies/dice.rb, line 208 def set_name(info) # Do NOT override the value if it's returned from the CAS server return info['name'] if info['name'] info['name'] = case options.name_format when :cn info['common_name'] when :full_name info['full_name'] when :first_last_name "#{info['first_name']} #{info['last_name']}" end info['name'] ||= info['common_name'] || info['full_name'] || "#{info['first_name']} #{info['last_name']}" end
# File lib/omniauth/strategies/dice.rb, line 359 def set_session_dn(dn_string, type = 'subject') dn_type = case type when 'subject' 'user_dn' when 'issuer' 'issuer_dn' else fail 'Invalid DN string type' end session['omniauth.params'] ||= {} session['omniauth.params'][dn_type] = dn_string end
Dynamically builds out Faraday’s SSL config hash by merging passed options hash with the default options.
Available Faraday config options include: ca_file (e.g., /usr/lib/ssl/certs/ca-certificates.crt) ca_path (e.g., /usr/lib/ssl/certs) cert_store client_cert client_key certificate private_key verify verify_mode verify_depth version
# File lib/omniauth/strategies/dice.rb, line 387 def ssl_hash ssl_defaults = { verify: true, verify_depth: 3, version: 'TLSv1' } custom_config = unhashie(options.ssl_config) ssl_defaults.merge(custom_config) end
Change Hashie indifferent access keys back to symbols
# File lib/omniauth/strategies/dice.rb, line 148 def unhashie(hash) tmp_hash = {} hash.each do |key, value| tmp_hash[key.to_sym] = value end tmp_hash end