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

data[RW]
dn[RW]
raw_dn[RW]

Public Instance Methods

auth_hash() click to toggle source
# 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
callback_phase() click to toggle source
Calls superclass method
# 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
format_dn(dn_str) click to toggle source

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
redirect_for_callback() click to toggle source
# 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
request_phase() click to toggle source
# 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
required_params() click to toggle source

Specifies which attributes are required arguments to initialize

# File lib/omniauth/strategies/dice.rb, line 60
def required_params
  [:cas_server, :authentication_path]
end
validate_required_params() click to toggle source

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

auth_cn_with_tld?(common_name) click to toggle source

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
auth_has_email_with_any_name?(info) click to toggle source

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
auth_has_email_without_names?(info) click to toggle source

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
auth_info_custom(info) click to toggle source

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
auth_info_defaults(info) click to toggle source

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
auth_info_dynamic(info) click to toggle source

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
auth_info_has_any_name?(info) click to toggle source

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
auth_info_missing_email?(info) click to toggle source

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
authenticate_user() click to toggle source
# 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
connection() click to toggle source

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
format_data() click to toggle source

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
get_dn(dn_str) click to toggle source

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
get_dn_by_type(type = 'subject') click to toggle source

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
get_dn_from_certificate(type) click to toggle source

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
get_dn_from_header(type) click to toggle source

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
headers() click to toggle source
# 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
identify_npe(info) click to toggle source

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
includes_primary_visa?(info) click to toggle source

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
info_defaults() click to toggle source

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_dn_from_certificate(certificate, type = 'subject') click to toggle source

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
parse_response_data() click to toggle source

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
query_url() click to toggle source

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
set_name(info) click to toggle source

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
set_session_dn(dn_string, type = 'subject') click to toggle source
# 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
ssl_hash() click to toggle source

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
unhashie(hash) click to toggle source

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