|
|
# An implementation of the OpenID Provider Authentication Policy
|
|
|
# Extension 1.0
|
|
|
# see: http://openid.net/specs/
|
|
|
|
|
|
require 'openid/extension'
|
|
|
|
|
|
module OpenID
|
|
|
|
|
|
module PAPE
|
|
|
NS_URI = "http://specs.openid.net/extensions/pape/1.0"
|
|
|
AUTH_MULTI_FACTOR_PHYSICAL =
|
|
|
'http://schemas.openid.net/pape/policies/2007/06/multi-factor-physical'
|
|
|
AUTH_MULTI_FACTOR =
|
|
|
'http://schemas.openid.net/pape/policies/2007/06/multi-factor'
|
|
|
AUTH_PHISHING_RESISTANT =
|
|
|
'http://schemas.openid.net/pape/policies/2007/06/phishing-resistant'
|
|
|
TIME_VALIDATOR = /\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ/
|
|
|
# A Provider Authentication Policy request, sent from a relying
|
|
|
# party to a provider
|
|
|
class Request < Extension
|
|
|
attr_accessor :preferred_auth_policies, :max_auth_age, :ns_alias, :ns_uri
|
|
|
def initialize(preferred_auth_policies=[], max_auth_age=nil)
|
|
|
@ns_alias = 'pape'
|
|
|
@ns_uri = NS_URI
|
|
|
@preferred_auth_policies = preferred_auth_policies
|
|
|
@max_auth_age = max_auth_age
|
|
|
end
|
|
|
|
|
|
# Add an acceptable authentication policy URI to this request
|
|
|
# This method is intended to be used by the relying party to add
|
|
|
# acceptable authentication types to the request.
|
|
|
def add_policy_uri(policy_uri)
|
|
|
unless @preferred_auth_policies.member? policy_uri
|
|
|
@preferred_auth_policies << policy_uri
|
|
|
end
|
|
|
end
|
|
|
|
|
|
def get_extension_args
|
|
|
ns_args = {
|
|
|
'preferred_auth_policies' => @preferred_auth_policies.join(' ')
|
|
|
}
|
|
|
ns_args['max_auth_age'] = @max_auth_age.to_s if @max_auth_age
|
|
|
return ns_args
|
|
|
end
|
|
|
|
|
|
# Instantiate a Request object from the arguments in a
|
|
|
# checkid_* OpenID message
|
|
|
# return nil if the extension was not requested.
|
|
|
def self.from_openid_request(oid_req)
|
|
|
pape_req = new
|
|
|
args = oid_req.message.get_args(NS_URI)
|
|
|
if args == {}
|
|
|
return nil
|
|
|
end
|
|
|
pape_req.parse_extension_args(args)
|
|
|
return pape_req
|
|
|
end
|
|
|
|
|
|
# Set the state of this request to be that expressed in these
|
|
|
# PAPE arguments
|
|
|
def parse_extension_args(args)
|
|
|
@preferred_auth_policies = []
|
|
|
policies_str = args['preferred_auth_policies']
|
|
|
if policies_str
|
|
|
policies_str.split(' ').each{|uri|
|
|
|
add_policy_uri(uri)
|
|
|
}
|
|
|
end
|
|
|
|
|
|
max_auth_age_str = args['max_auth_age']
|
|
|
if max_auth_age_str
|
|
|
@max_auth_age = max_auth_age_str.to_i
|
|
|
else
|
|
|
@max_auth_age = nil
|
|
|
end
|
|
|
end
|
|
|
|
|
|
# Given a list of authentication policy URIs that a provider
|
|
|
# supports, this method returns the subset of those types
|
|
|
# that are preferred by the relying party.
|
|
|
def preferred_types(supported_types)
|
|
|
@preferred_auth_policies.select{|uri| supported_types.member? uri}
|
|
|
end
|
|
|
end
|
|
|
|
|
|
# A Provider Authentication Policy response, sent from a provider
|
|
|
# to a relying party
|
|
|
class Response < Extension
|
|
|
attr_accessor :ns_alias, :auth_policies, :auth_time, :nist_auth_level
|
|
|
def initialize(auth_policies=[], auth_time=nil, nist_auth_level=nil)
|
|
|
@ns_alias = 'pape'
|
|
|
@ns_uri = NS_URI
|
|
|
@auth_policies = auth_policies
|
|
|
@auth_time = auth_time
|
|
|
@nist_auth_level = nist_auth_level
|
|
|
end
|
|
|
|
|
|
# Add a policy URI to the response
|
|
|
# see http://openid.net/specs/openid-provider-authentication-policy-extension-1_0-01.html#auth_policies
|
|
|
def add_policy_uri(policy_uri)
|
|
|
@auth_policies << policy_uri unless @auth_policies.member?(policy_uri)
|
|
|
end
|
|
|
|
|
|
# Create a Response object from an OpenID::Consumer::SuccessResponse
|
|
|
def self.from_success_response(success_response)
|
|
|
args = success_response.get_signed_ns(NS_URI)
|
|
|
return nil if args.nil?
|
|
|
pape_resp = new
|
|
|
pape_resp.parse_extension_args(args)
|
|
|
return pape_resp
|
|
|
end
|
|
|
|
|
|
# parse the provider authentication policy arguments into the
|
|
|
# internal state of this object
|
|
|
# if strict is specified, raise an exception when bad data is
|
|
|
# encountered
|
|
|
def parse_extension_args(args, strict=false)
|
|
|
policies_str = args['auth_policies']
|
|
|
if policies_str and policies_str != 'none'
|
|
|
@auth_policies = policies_str.split(' ')
|
|
|
end
|
|
|
|
|
|
nist_level_str = args['nist_auth_level']
|
|
|
if nist_level_str
|
|
|
# special handling of zero to handle to_i behavior
|
|
|
if nist_level_str.strip == '0'
|
|
|
nist_level = 0
|
|
|
else
|
|
|
nist_level = nist_level_str.to_i
|
|
|
# if it's zero here we have a bad value
|
|
|
if nist_level == 0
|
|
|
nist_level = nil
|
|
|
end
|
|
|
end
|
|
|
if nist_level and nist_level >= 0 and nist_level < 5
|
|
|
@nist_auth_level = nist_level
|
|
|
elsif strict
|
|
|
raise ArgumentError, "nist_auth_level must be an integer 0 through 4, not #{nist_level_str.inspect}"
|
|
|
end
|
|
|
end
|
|
|
|
|
|
auth_time_str = args['auth_time']
|
|
|
if auth_time_str
|
|
|
# validate time string
|
|
|
if auth_time_str =~ TIME_VALIDATOR
|
|
|
@auth_time = auth_time_str
|
|
|
elsif strict
|
|
|
raise ArgumentError, "auth_time must be in RFC3339 format"
|
|
|
end
|
|
|
end
|
|
|
end
|
|
|
|
|
|
def get_extension_args
|
|
|
ns_args = {}
|
|
|
if @auth_policies.empty?
|
|
|
ns_args['auth_policies'] = 'none'
|
|
|
else
|
|
|
ns_args['auth_policies'] = @auth_policies.join(' ')
|
|
|
end
|
|
|
if @nist_auth_level
|
|
|
unless (0..4).member? @nist_auth_level
|
|
|
raise ArgumentError, "nist_auth_level must be an integer 0 through 4, not #{@nist_auth_level.inspect}"
|
|
|
end
|
|
|
ns_args['nist_auth_level'] = @nist_auth_level.to_s
|
|
|
end
|
|
|
|
|
|
if @auth_time
|
|
|
unless @auth_time =~ TIME_VALIDATOR
|
|
|
raise ArgumentError, "auth_time must be in RFC3339 format"
|
|
|
end
|
|
|
ns_args['auth_time'] = @auth_time
|
|
|
end
|
|
|
return ns_args
|
|
|
end
|
|
|
|
|
|
end
|
|
|
end
|
|
|
|
|
|
end
|
|
|
|