mirror of
https://github.com/TracksApp/tracks.git
synced 2025-12-23 18:50:12 +01:00
Upgraded to open_id_authentication plugin at 00d8bc7f97 and unpacked ruby-openid gem version 2.1.2.
This commit is contained in:
parent
6149900e0c
commit
e92dae2ffc
227 changed files with 30857 additions and 669 deletions
112
vendor/gems/ruby-openid-2.1.2/lib/hmac/hmac.rb
vendored
Normal file
112
vendor/gems/ruby-openid-2.1.2/lib/hmac/hmac.rb
vendored
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
# Copyright (C) 2001 Daiki Ueno <ueno@unixuser.org>
|
||||
# This library is distributed under the terms of the Ruby license.
|
||||
|
||||
# This module provides common interface to HMAC engines.
|
||||
# HMAC standard is documented in RFC 2104:
|
||||
#
|
||||
# H. Krawczyk et al., "HMAC: Keyed-Hashing for Message Authentication",
|
||||
# RFC 2104, February 1997
|
||||
#
|
||||
# These APIs are inspired by JCE 1.2's javax.crypto.Mac interface.
|
||||
#
|
||||
# <URL:http://java.sun.com/security/JCE1.2/spec/apidoc/javax/crypto/Mac.html>
|
||||
|
||||
module HMAC
|
||||
class Base
|
||||
def initialize(algorithm, block_size, output_length, key)
|
||||
@algorithm = algorithm
|
||||
@block_size = block_size
|
||||
@output_length = output_length
|
||||
@status = STATUS_UNDEFINED
|
||||
@key_xor_ipad = ''
|
||||
@key_xor_opad = ''
|
||||
set_key(key) unless key.nil?
|
||||
end
|
||||
|
||||
private
|
||||
def check_status
|
||||
unless @status == STATUS_INITIALIZED
|
||||
raise RuntimeError,
|
||||
"The underlying hash algorithm has not yet been initialized."
|
||||
end
|
||||
end
|
||||
|
||||
public
|
||||
def set_key(key)
|
||||
# If key is longer than the block size, apply hash function
|
||||
# to key and use the result as a real key.
|
||||
key = @algorithm.digest(key) if key.size > @block_size
|
||||
key_xor_ipad = "\x36" * @block_size
|
||||
key_xor_opad = "\x5C" * @block_size
|
||||
for i in 0 .. key.size - 1
|
||||
key_xor_ipad[i] ^= key[i]
|
||||
key_xor_opad[i] ^= key[i]
|
||||
end
|
||||
@key_xor_ipad = key_xor_ipad
|
||||
@key_xor_opad = key_xor_opad
|
||||
@md = @algorithm.new
|
||||
@status = STATUS_INITIALIZED
|
||||
end
|
||||
|
||||
def reset_key
|
||||
@key_xor_ipad.gsub!(/./, '?')
|
||||
@key_xor_opad.gsub!(/./, '?')
|
||||
@key_xor_ipad[0..-1] = ''
|
||||
@key_xor_opad[0..-1] = ''
|
||||
@status = STATUS_UNDEFINED
|
||||
end
|
||||
|
||||
def update(text)
|
||||
check_status
|
||||
# perform inner H
|
||||
md = @algorithm.new
|
||||
md.update(@key_xor_ipad)
|
||||
md.update(text)
|
||||
str = md.digest
|
||||
# perform outer H
|
||||
md = @algorithm.new
|
||||
md.update(@key_xor_opad)
|
||||
md.update(str)
|
||||
@md = md
|
||||
end
|
||||
alias << update
|
||||
|
||||
def digest
|
||||
check_status
|
||||
@md.digest
|
||||
end
|
||||
|
||||
def hexdigest
|
||||
check_status
|
||||
@md.hexdigest
|
||||
end
|
||||
alias to_s hexdigest
|
||||
|
||||
# These two class methods below are safer than using above
|
||||
# instance methods combinatorially because an instance will have
|
||||
# held a key even if it's no longer in use.
|
||||
def Base.digest(key, text)
|
||||
begin
|
||||
hmac = self.new(key)
|
||||
hmac.update(text)
|
||||
hmac.digest
|
||||
ensure
|
||||
hmac.reset_key
|
||||
end
|
||||
end
|
||||
|
||||
def Base.hexdigest(key, text)
|
||||
begin
|
||||
hmac = self.new(key)
|
||||
hmac.update(text)
|
||||
hmac.hexdigest
|
||||
ensure
|
||||
hmac.reset_key
|
||||
end
|
||||
end
|
||||
|
||||
private_class_method :new, :digest, :hexdigest
|
||||
end
|
||||
|
||||
STATUS_UNDEFINED, STATUS_INITIALIZED = 0, 1
|
||||
end
|
||||
11
vendor/gems/ruby-openid-2.1.2/lib/hmac/sha1.rb
vendored
Normal file
11
vendor/gems/ruby-openid-2.1.2/lib/hmac/sha1.rb
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
require 'hmac/hmac'
|
||||
require 'digest/sha1'
|
||||
|
||||
module HMAC
|
||||
class SHA1 < Base
|
||||
def initialize(key = nil)
|
||||
super(Digest::SHA1, 64, 20, key)
|
||||
end
|
||||
public_class_method :new, :digest, :hexdigest
|
||||
end
|
||||
end
|
||||
25
vendor/gems/ruby-openid-2.1.2/lib/hmac/sha2.rb
vendored
Normal file
25
vendor/gems/ruby-openid-2.1.2/lib/hmac/sha2.rb
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
require 'hmac/hmac'
|
||||
require 'digest/sha2'
|
||||
|
||||
module HMAC
|
||||
class SHA256 < Base
|
||||
def initialize(key = nil)
|
||||
super(Digest::SHA256, 64, 32, key)
|
||||
end
|
||||
public_class_method :new, :digest, :hexdigest
|
||||
end
|
||||
|
||||
class SHA384 < Base
|
||||
def initialize(key = nil)
|
||||
super(Digest::SHA384, 128, 48, key)
|
||||
end
|
||||
public_class_method :new, :digest, :hexdigest
|
||||
end
|
||||
|
||||
class SHA512 < Base
|
||||
def initialize(key = nil)
|
||||
super(Digest::SHA512, 128, 64, key)
|
||||
end
|
||||
public_class_method :new, :digest, :hexdigest
|
||||
end
|
||||
end
|
||||
20
vendor/gems/ruby-openid-2.1.2/lib/openid.rb
vendored
Normal file
20
vendor/gems/ruby-openid-2.1.2/lib/openid.rb
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# Copyright 2006-2007 JanRain, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you
|
||||
# may not use this file except in compliance with the License. You may
|
||||
# obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# permissions and limitations under the License.
|
||||
|
||||
module OpenID
|
||||
VERSION = "2.1.2"
|
||||
end
|
||||
|
||||
require "openid/consumer"
|
||||
require 'openid/server'
|
||||
249
vendor/gems/ruby-openid-2.1.2/lib/openid/association.rb
vendored
Normal file
249
vendor/gems/ruby-openid-2.1.2/lib/openid/association.rb
vendored
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
require "openid/kvform"
|
||||
require "openid/util"
|
||||
require "openid/cryptutil"
|
||||
require "openid/message"
|
||||
|
||||
module OpenID
|
||||
|
||||
def self.get_secret_size(assoc_type)
|
||||
if assoc_type == 'HMAC-SHA1'
|
||||
return 20
|
||||
elsif assoc_type == 'HMAC-SHA256'
|
||||
return 32
|
||||
else
|
||||
raise ArgumentError("Unsupported association type: #{assoc_type}")
|
||||
end
|
||||
end
|
||||
|
||||
# An Association holds the shared secret between a relying party and
|
||||
# an OpenID provider.
|
||||
class Association
|
||||
attr_reader :handle, :secret, :issued, :lifetime, :assoc_type
|
||||
|
||||
FIELD_ORDER =
|
||||
[:version, :handle, :secret, :issued, :lifetime, :assoc_type,]
|
||||
|
||||
# Load a serialized Association
|
||||
def self.deserialize(serialized)
|
||||
parsed = Util.kv_to_seq(serialized)
|
||||
parsed_fields = parsed.map{|k, v| k.to_sym}
|
||||
if parsed_fields != FIELD_ORDER
|
||||
raise ProtocolError, 'Unexpected fields in serialized association'\
|
||||
" (Expected #{FIELD_ORDER.inspect}, got #{parsed_fields.inspect})"
|
||||
end
|
||||
version, handle, secret64, issued_s, lifetime_s, assoc_type =
|
||||
parsed.map {|field, value| value}
|
||||
if version != '2'
|
||||
raise ProtocolError, "Attempted to deserialize unsupported version "\
|
||||
"(#{parsed[0][1].inspect})"
|
||||
end
|
||||
|
||||
self.new(handle,
|
||||
Util.from_base64(secret64),
|
||||
Time.at(issued_s.to_i),
|
||||
lifetime_s.to_i,
|
||||
assoc_type)
|
||||
end
|
||||
|
||||
# Create an Association with an issued time of now
|
||||
def self.from_expires_in(expires_in, handle, secret, assoc_type)
|
||||
issued = Time.now
|
||||
self.new(handle, secret, issued, expires_in, assoc_type)
|
||||
end
|
||||
|
||||
def initialize(handle, secret, issued, lifetime, assoc_type)
|
||||
@handle = handle
|
||||
@secret = secret
|
||||
@issued = issued
|
||||
@lifetime = lifetime
|
||||
@assoc_type = assoc_type
|
||||
end
|
||||
|
||||
# Serialize the association to a form that's consistent across
|
||||
# JanRain OpenID libraries.
|
||||
def serialize
|
||||
data = {
|
||||
:version => '2',
|
||||
:handle => handle,
|
||||
:secret => Util.to_base64(secret),
|
||||
:issued => issued.to_i.to_s,
|
||||
:lifetime => lifetime.to_i.to_s,
|
||||
:assoc_type => assoc_type,
|
||||
}
|
||||
|
||||
Util.assert(data.length == FIELD_ORDER.length)
|
||||
|
||||
pairs = FIELD_ORDER.map{|field| [field.to_s, data[field]]}
|
||||
return Util.seq_to_kv(pairs, strict=true)
|
||||
end
|
||||
|
||||
# The number of seconds until this association expires
|
||||
def expires_in(now=nil)
|
||||
if now.nil?
|
||||
now = Time.now.to_i
|
||||
else
|
||||
now = now.to_i
|
||||
end
|
||||
time_diff = (issued.to_i + lifetime) - now
|
||||
if time_diff < 0
|
||||
return 0
|
||||
else
|
||||
return time_diff
|
||||
end
|
||||
end
|
||||
|
||||
# Generate a signature for a sequence of [key, value] pairs
|
||||
def sign(pairs)
|
||||
kv = Util.seq_to_kv(pairs)
|
||||
case assoc_type
|
||||
when 'HMAC-SHA1'
|
||||
CryptUtil.hmac_sha1(@secret, kv)
|
||||
when 'HMAC-SHA256'
|
||||
CryptUtil.hmac_sha256(@secret, kv)
|
||||
else
|
||||
raise ProtocolError, "Association has unknown type: "\
|
||||
"#{assoc_type.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
# Generate the list of pairs that form the signed elements of the
|
||||
# given message
|
||||
def make_pairs(message)
|
||||
signed = message.get_arg(OPENID_NS, 'signed')
|
||||
if signed.nil?
|
||||
raise ProtocolError, 'Missing signed list'
|
||||
end
|
||||
signed_fields = signed.split(',', -1)
|
||||
data = message.to_post_args
|
||||
signed_fields.map {|field| [field, data.fetch('openid.'+field,'')] }
|
||||
end
|
||||
|
||||
# Return whether the message's signature passes
|
||||
def check_message_signature(message)
|
||||
message_sig = message.get_arg(OPENID_NS, 'sig')
|
||||
if message_sig.nil?
|
||||
raise ProtocolError, "#{message} has no sig."
|
||||
end
|
||||
calculated_sig = get_message_signature(message)
|
||||
return calculated_sig == message_sig
|
||||
end
|
||||
|
||||
# Get the signature for this message
|
||||
def get_message_signature(message)
|
||||
Util.to_base64(sign(make_pairs(message)))
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
(other.class == self.class and
|
||||
other.handle == self.handle and
|
||||
other.secret == self.secret and
|
||||
|
||||
# The internals of the time objects seemed to differ
|
||||
# in an opaque way when serializing/unserializing.
|
||||
# I don't think this will be a problem.
|
||||
other.issued.to_i == self.issued.to_i and
|
||||
|
||||
other.lifetime == self.lifetime and
|
||||
other.assoc_type == self.assoc_type)
|
||||
end
|
||||
|
||||
# Add a signature (and a signed list) to a message.
|
||||
def sign_message(message)
|
||||
if (message.has_key?(OPENID_NS, 'sig') or
|
||||
message.has_key?(OPENID_NS, 'signed'))
|
||||
raise ArgumentError, 'Message already has signed list or signature'
|
||||
end
|
||||
|
||||
extant_handle = message.get_arg(OPENID_NS, 'assoc_handle')
|
||||
if extant_handle and extant_handle != self.handle
|
||||
raise ArgumentError, "Message has a different association handle"
|
||||
end
|
||||
|
||||
signed_message = message.copy()
|
||||
signed_message.set_arg(OPENID_NS, 'assoc_handle', self.handle)
|
||||
message_keys = signed_message.to_post_args.keys()
|
||||
|
||||
signed_list = []
|
||||
message_keys.each { |k|
|
||||
if k.starts_with?('openid.')
|
||||
signed_list << k[7..-1]
|
||||
end
|
||||
}
|
||||
|
||||
signed_list << 'signed'
|
||||
signed_list.sort!
|
||||
|
||||
signed_message.set_arg(OPENID_NS, 'signed', signed_list.join(','))
|
||||
sig = get_message_signature(signed_message)
|
||||
signed_message.set_arg(OPENID_NS, 'sig', sig)
|
||||
return signed_message
|
||||
end
|
||||
end
|
||||
|
||||
class AssociationNegotiator
|
||||
attr_reader :allowed_types
|
||||
|
||||
def self.get_session_types(assoc_type)
|
||||
case assoc_type
|
||||
when 'HMAC-SHA1'
|
||||
['DH-SHA1', 'no-encryption']
|
||||
when 'HMAC-SHA256'
|
||||
['DH-SHA256', 'no-encryption']
|
||||
else
|
||||
raise ProtocolError, "Unknown association type #{assoc_type.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
def self.check_session_type(assoc_type, session_type)
|
||||
if !get_session_types(assoc_type).include?(session_type)
|
||||
raise ProtocolError, "Session type #{session_type.inspect} not "\
|
||||
"valid for association type #{assoc_type.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(allowed_types)
|
||||
self.allowed_types=(allowed_types)
|
||||
end
|
||||
|
||||
def copy
|
||||
Marshal.load(Marshal.dump(self))
|
||||
end
|
||||
|
||||
def allowed_types=(allowed_types)
|
||||
allowed_types.each do |assoc_type, session_type|
|
||||
self.class.check_session_type(assoc_type, session_type)
|
||||
end
|
||||
@allowed_types = allowed_types
|
||||
end
|
||||
|
||||
def add_allowed_type(assoc_type, session_type=nil)
|
||||
if session_type.nil?
|
||||
session_types = self.class.get_session_types(assoc_type)
|
||||
else
|
||||
self.class.check_session_type(assoc_type, session_type)
|
||||
session_types = [session_type]
|
||||
end
|
||||
for session_type in session_types do
|
||||
@allowed_types << [assoc_type, session_type]
|
||||
end
|
||||
end
|
||||
|
||||
def allowed?(assoc_type, session_type)
|
||||
@allowed_types.include?([assoc_type, session_type])
|
||||
end
|
||||
|
||||
def get_allowed_type
|
||||
@allowed_types.empty? ? nil : @allowed_types[0]
|
||||
end
|
||||
end
|
||||
|
||||
DefaultNegotiator =
|
||||
AssociationNegotiator.new([['HMAC-SHA1', 'DH-SHA1'],
|
||||
['HMAC-SHA1', 'no-encryption'],
|
||||
['HMAC-SHA256', 'DH-SHA256'],
|
||||
['HMAC-SHA256', 'no-encryption']])
|
||||
|
||||
EncryptedNegotiator =
|
||||
AssociationNegotiator.new([['HMAC-SHA1', 'DH-SHA1'],
|
||||
['HMAC-SHA256', 'DH-SHA256']])
|
||||
end
|
||||
394
vendor/gems/ruby-openid-2.1.2/lib/openid/consumer.rb
vendored
Normal file
394
vendor/gems/ruby-openid-2.1.2/lib/openid/consumer.rb
vendored
Normal file
|
|
@ -0,0 +1,394 @@
|
|||
require "openid/consumer/idres.rb"
|
||||
require "openid/consumer/checkid_request.rb"
|
||||
require "openid/consumer/associationmanager.rb"
|
||||
require "openid/consumer/responses.rb"
|
||||
require "openid/consumer/discovery_manager"
|
||||
require "openid/consumer/discovery"
|
||||
require "openid/message"
|
||||
require "openid/yadis/discovery"
|
||||
require "openid/store/nonce"
|
||||
|
||||
module OpenID
|
||||
# OpenID support for Relying Parties (aka Consumers).
|
||||
#
|
||||
# This module documents the main interface with the OpenID consumer
|
||||
# library. The only part of the library which has to be used and
|
||||
# isn't documented in full here is the store required to create an
|
||||
# Consumer instance.
|
||||
#
|
||||
# = OVERVIEW
|
||||
#
|
||||
# The OpenID identity verification process most commonly uses the
|
||||
# following steps, as visible to the user of this library:
|
||||
#
|
||||
# 1. The user enters their OpenID into a field on the consumer's
|
||||
# site, and hits a login button.
|
||||
#
|
||||
# 2. The consumer site discovers the user's OpenID provider using
|
||||
# the Yadis protocol.
|
||||
#
|
||||
# 3. The consumer site sends the browser a redirect to the OpenID
|
||||
# provider. This is the authentication request as described in
|
||||
# the OpenID specification.
|
||||
#
|
||||
# 4. The OpenID provider's site sends the browser a redirect back to
|
||||
# the consumer site. This redirect contains the provider's
|
||||
# response to the authentication request.
|
||||
#
|
||||
# The most important part of the flow to note is the consumer's site
|
||||
# must handle two separate HTTP requests in order to perform the
|
||||
# full identity check.
|
||||
#
|
||||
# = LIBRARY DESIGN
|
||||
#
|
||||
# This consumer library is designed with that flow in mind. The
|
||||
# goal is to make it as easy as possible to perform the above steps
|
||||
# securely.
|
||||
#
|
||||
# At a high level, there are two important parts in the consumer
|
||||
# library. The first important part is this module, which contains
|
||||
# the interface to actually use this library. The second is
|
||||
# openid/store/interface.rb, which describes the interface to use if
|
||||
# you need to create a custom method for storing the state this
|
||||
# library needs to maintain between requests.
|
||||
#
|
||||
# In general, the second part is less important for users of the
|
||||
# library to know about, as several implementations are provided
|
||||
# which cover a wide variety of situations in which consumers may
|
||||
# use the library.
|
||||
#
|
||||
# The Consumer class has methods corresponding to the actions
|
||||
# necessary in each of steps 2, 3, and 4 described in the overview.
|
||||
# Use of this library should be as easy as creating an Consumer
|
||||
# instance and calling the methods appropriate for the action the
|
||||
# site wants to take.
|
||||
#
|
||||
# This library automatically detects which version of the OpenID
|
||||
# protocol should be used for a transaction and constructs the
|
||||
# proper requests and responses. Users of this library do not need
|
||||
# to worry about supporting multiple protocol versions; the library
|
||||
# supports them implicitly. Depending on the version of the
|
||||
# protocol in use, the OpenID transaction may be more secure. See
|
||||
# the OpenID specifications for more information.
|
||||
#
|
||||
# = SESSIONS, STORES, AND STATELESS MODE
|
||||
#
|
||||
# The Consumer object keeps track of two types of state:
|
||||
#
|
||||
# 1. State of the user's current authentication attempt. Things
|
||||
# like the identity URL, the list of endpoints discovered for
|
||||
# that URL, and in case where some endpoints are unreachable, the
|
||||
# list of endpoints already tried. This state needs to be held
|
||||
# from Consumer.begin() to Consumer.complete(), but it is only
|
||||
# applicable to a single session with a single user agent, and at
|
||||
# the end of the authentication process (i.e. when an OP replies
|
||||
# with either <tt>id_res</tt>. or <tt>cancel</tt> it may be
|
||||
# discarded.
|
||||
#
|
||||
# 2. State of relationships with servers, i.e. shared secrets
|
||||
# (associations) with servers and nonces seen on signed messages.
|
||||
# This information should persist from one session to the next
|
||||
# and should not be bound to a particular user-agent.
|
||||
#
|
||||
# These two types of storage are reflected in the first two
|
||||
# arguments of Consumer's constructor, <tt>session</tt> and
|
||||
# <tt>store</tt>. <tt>session</tt> is a dict-like object and we
|
||||
# hope your web framework provides you with one of these bound to
|
||||
# the user agent. <tt>store</tt> is an instance of Store.
|
||||
#
|
||||
# Since the store does hold secrets shared between your application
|
||||
# and the OpenID provider, you should be careful about how you use
|
||||
# it in a shared hosting environment. If the filesystem or database
|
||||
# permissions of your web host allow strangers to read from them, do
|
||||
# not store your data there! If you have no safe place to store
|
||||
# your data, construct your consumer with nil for the store, and it
|
||||
# will operate only in stateless mode. Stateless mode may be
|
||||
# slower, put more load on the OpenID provider, and trusts the
|
||||
# provider to keep you safe from replay attacks.
|
||||
#
|
||||
# Several store implementation are provided, and the interface is
|
||||
# fully documented so that custom stores can be used as well. See
|
||||
# the documentation for the Consumer class for more information on
|
||||
# the interface for stores. The implementations that are provided
|
||||
# allow the consumer site to store the necessary data in several
|
||||
# different ways, including several SQL databases and normal files
|
||||
# on disk.
|
||||
#
|
||||
# = IMMEDIATE MODE
|
||||
#
|
||||
# In the flow described above, the user may need to confirm to the
|
||||
# OpenID provider that it's ok to disclose his or her identity. The
|
||||
# provider may draw pages asking for information from the user
|
||||
# before it redirects the browser back to the consumer's site. This
|
||||
# is generally transparent to the consumer site, so it is typically
|
||||
# ignored as an implementation detail.
|
||||
#
|
||||
# There can be times, however, where the consumer site wants to get
|
||||
# a response immediately. When this is the case, the consumer can
|
||||
# put the library in immediate mode. In immediate mode, there is an
|
||||
# extra response possible from the server, which is essentially the
|
||||
# server reporting that it doesn't have enough information to answer
|
||||
# the question yet.
|
||||
#
|
||||
# = USING THIS LIBRARY
|
||||
#
|
||||
# Integrating this library into an application is usually a
|
||||
# relatively straightforward process. The process should basically
|
||||
# follow this plan:
|
||||
#
|
||||
# Add an OpenID login field somewhere on your site. When an OpenID
|
||||
# is entered in that field and the form is submitted, it should make
|
||||
# a request to the your site which includes that OpenID URL.
|
||||
#
|
||||
# First, the application should instantiate a Consumer with a
|
||||
# session for per-user state and store for shared state using the
|
||||
# store of choice.
|
||||
#
|
||||
# Next, the application should call the <tt>begin</tt> method of
|
||||
# Consumer instance. This method takes the OpenID URL as entered by
|
||||
# the user. The <tt>begin</tt> method returns a CheckIDRequest
|
||||
# object.
|
||||
#
|
||||
# Next, the application should call the redirect_url method on the
|
||||
# CheckIDRequest object. The parameter <tt>return_to</tt> is the
|
||||
# URL that the OpenID server will send the user back to after
|
||||
# attempting to verify his or her identity. The <tt>realm</tt>
|
||||
# parameter is the URL (or URL pattern) that identifies your web
|
||||
# site to the user when he or she is authorizing it. Send a
|
||||
# redirect to the resulting URL to the user's browser.
|
||||
#
|
||||
# That's the first half of the authentication process. The second
|
||||
# half of the process is done after the user's OpenID Provider sends
|
||||
# the user's browser a redirect back to your site to complete their
|
||||
# login.
|
||||
#
|
||||
# When that happens, the user will contact your site at the URL
|
||||
# given as the <tt>return_to</tt> URL to the redirect_url call made
|
||||
# above. The request will have several query parameters added to
|
||||
# the URL by the OpenID provider as the information necessary to
|
||||
# finish the request.
|
||||
#
|
||||
# Get a Consumer instance with the same session and store as before
|
||||
# and call its complete() method, passing in all the received query
|
||||
# arguments and URL currently being handled.
|
||||
#
|
||||
# There are multiple possible return types possible from that
|
||||
# method. These indicate the whether or not the login was
|
||||
# successful, and include any additional information appropriate for
|
||||
# their type.
|
||||
class Consumer
|
||||
attr_accessor :session_key_prefix
|
||||
|
||||
# Initialize a Consumer instance.
|
||||
#
|
||||
# You should create a new instance of the Consumer object with
|
||||
# every HTTP request that handles OpenID transactions.
|
||||
#
|
||||
# session: the session object to use to store request information.
|
||||
# The session should behave like a hash.
|
||||
#
|
||||
# store: an object that implements the interface in Store.
|
||||
def initialize(session, store)
|
||||
@session = session
|
||||
@store = store
|
||||
@session_key_prefix = 'OpenID::Consumer::'
|
||||
end
|
||||
|
||||
# Start the OpenID authentication process. See steps 1-2 in the
|
||||
# overview for the Consumer class.
|
||||
#
|
||||
# user_url: Identity URL given by the user. This method performs a
|
||||
# textual transformation of the URL to try and make sure it is
|
||||
# normalized. For example, a user_url of example.com will be
|
||||
# normalized to http://example.com/ normalizing and resolving any
|
||||
# redirects the server might issue.
|
||||
#
|
||||
# anonymous: A boolean value. Whether to make an anonymous
|
||||
# request of the OpenID provider. Such a request does not ask for
|
||||
# an authorization assertion for an OpenID identifier, but may be
|
||||
# used with extensions to pass other data. e.g. "I don't care who
|
||||
# you are, but I'd like to know your time zone."
|
||||
#
|
||||
# Returns a CheckIDRequest object containing the discovered
|
||||
# information, with a method for building a redirect URL to the
|
||||
# server, as described in step 3 of the overview. This object may
|
||||
# also be used to add extension arguments to the request, using
|
||||
# its add_extension_arg method.
|
||||
#
|
||||
# Raises DiscoveryFailure when no OpenID server can be found for
|
||||
# this URL.
|
||||
def begin(openid_identifier, anonymous=false)
|
||||
manager = discovery_manager(openid_identifier)
|
||||
service = manager.get_next_service(&method(:discover))
|
||||
|
||||
if service.nil?
|
||||
raise DiscoveryFailure.new("No usable OpenID services were found "\
|
||||
"for #{openid_identifier.inspect}", nil)
|
||||
else
|
||||
begin_without_discovery(service, anonymous)
|
||||
end
|
||||
end
|
||||
|
||||
# Start OpenID verification without doing OpenID server
|
||||
# discovery. This method is used internally by Consumer.begin()
|
||||
# after discovery is performed, and exists to provide an interface
|
||||
# for library users needing to perform their own discovery.
|
||||
#
|
||||
# service: an OpenID service endpoint descriptor. This object and
|
||||
# factories for it are found in the openid/consumer/discovery.rb
|
||||
# module.
|
||||
#
|
||||
# Returns an OpenID authentication request object.
|
||||
def begin_without_discovery(service, anonymous)
|
||||
assoc = association_manager(service).get_association
|
||||
checkid_request = CheckIDRequest.new(assoc, service)
|
||||
checkid_request.anonymous = anonymous
|
||||
|
||||
if service.compatibility_mode
|
||||
rt_args = checkid_request.return_to_args
|
||||
rt_args[Consumer.openid1_return_to_nonce_name] = Nonce.mk_nonce
|
||||
rt_args[Consumer.openid1_return_to_claimed_id_name] =
|
||||
service.claimed_id
|
||||
end
|
||||
|
||||
self.last_requested_endpoint = service
|
||||
return checkid_request
|
||||
end
|
||||
|
||||
# Called to interpret the server's response to an OpenID
|
||||
# request. It is called in step 4 of the flow described in the
|
||||
# Consumer overview.
|
||||
#
|
||||
# query: A hash of the query parameters for this HTTP request.
|
||||
# Note that in rails, this is <b>not</b> <tt>params</tt> but
|
||||
# <tt>params.reject{|k,v|request.path_parameters[k]}</tt>
|
||||
# because <tt>controller</tt> and <tt>action</tt> and other
|
||||
# "path parameters" are included in params.
|
||||
#
|
||||
# current_url: Extract the URL of the current request from your
|
||||
# application's web request framework and specify it here to have it
|
||||
# checked against the openid.return_to value in the response. Do not
|
||||
# just pass <tt>args['openid.return_to']</tt> here; that will defeat the
|
||||
# purpose of this check. (See OpenID Authentication 2.0 section 11.1.)
|
||||
#
|
||||
# If the return_to URL check fails, the status of the completion will be
|
||||
# FAILURE.
|
||||
|
||||
#
|
||||
# Returns a subclass of Response. The type of response is
|
||||
# indicated by the status attribute, which will be one of
|
||||
# SUCCESS, CANCEL, FAILURE, or SETUP_NEEDED.
|
||||
def complete(query, current_url)
|
||||
message = Message.from_post_args(query)
|
||||
mode = message.get_arg(OPENID_NS, 'mode', 'invalid')
|
||||
begin
|
||||
meth = method('complete_' + mode)
|
||||
rescue NameError
|
||||
meth = method(:complete_invalid)
|
||||
end
|
||||
response = meth.call(message, current_url)
|
||||
cleanup_last_requested_endpoint
|
||||
if [SUCCESS, CANCEL].member?(response.status)
|
||||
cleanup_session
|
||||
end
|
||||
return response
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def session_get(name)
|
||||
@session[session_key(name)]
|
||||
end
|
||||
|
||||
def session_set(name, val)
|
||||
@session[session_key(name)] = val
|
||||
end
|
||||
|
||||
def session_key(suffix)
|
||||
@session_key_prefix + suffix
|
||||
end
|
||||
|
||||
def last_requested_endpoint
|
||||
session_get('last_requested_endpoint')
|
||||
end
|
||||
|
||||
def last_requested_endpoint=(endpoint)
|
||||
session_set('last_requested_endpoint', endpoint)
|
||||
end
|
||||
|
||||
def cleanup_last_requested_endpoint
|
||||
@session[session_key('last_requested_endpoint')] = nil
|
||||
end
|
||||
|
||||
def discovery_manager(openid_identifier)
|
||||
DiscoveryManager.new(@session, openid_identifier, @session_key_prefix)
|
||||
end
|
||||
|
||||
def cleanup_session
|
||||
discovery_manager(nil).cleanup(true)
|
||||
end
|
||||
|
||||
|
||||
def discover(identifier)
|
||||
OpenID.discover(identifier)
|
||||
end
|
||||
|
||||
def negotiator
|
||||
DefaultNegotiator
|
||||
end
|
||||
|
||||
def association_manager(service)
|
||||
AssociationManager.new(@store, service.server_url,
|
||||
service.compatibility_mode, negotiator)
|
||||
end
|
||||
|
||||
def handle_idres(message, current_url)
|
||||
IdResHandler.new(message, current_url, @store, last_requested_endpoint)
|
||||
end
|
||||
|
||||
def complete_invalid(message, unused_return_to)
|
||||
mode = message.get_arg(OPENID_NS, 'mode', '<No mode set>')
|
||||
return FailureResponse.new(last_requested_endpoint,
|
||||
"Invalid openid.mode: #{mode}")
|
||||
end
|
||||
|
||||
def complete_cancel(unused_message, unused_return_to)
|
||||
return CancelResponse.new(last_requested_endpoint)
|
||||
end
|
||||
|
||||
def complete_error(message, unused_return_to)
|
||||
error = message.get_arg(OPENID_NS, 'error')
|
||||
contact = message.get_arg(OPENID_NS, 'contact')
|
||||
reference = message.get_arg(OPENID_NS, 'reference')
|
||||
|
||||
return FailureResponse.new(last_requested_endpoint,
|
||||
error, contact, reference)
|
||||
end
|
||||
|
||||
def complete_setup_needed(message, unused_return_to)
|
||||
if message.is_openid1
|
||||
return complete_invalid(message, nil)
|
||||
else
|
||||
return SetupNeededResponse.new(last_requested_endpoint, nil)
|
||||
end
|
||||
end
|
||||
|
||||
def complete_id_res(message, current_url)
|
||||
if message.is_openid1
|
||||
setup_url = message.get_arg(OPENID1_NS, 'user_setup_url')
|
||||
if !setup_url.nil?
|
||||
return SetupNeededResponse.new(last_requested_endpoint, setup_url)
|
||||
end
|
||||
end
|
||||
|
||||
begin
|
||||
idres = handle_idres(message, current_url)
|
||||
rescue OpenIDError => why
|
||||
return FailureResponse.new(last_requested_endpoint, why.message)
|
||||
else
|
||||
return SuccessResponse.new(idres.endpoint, message,
|
||||
idres.signed_fields)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
340
vendor/gems/ruby-openid-2.1.2/lib/openid/consumer/associationmanager.rb
vendored
Normal file
340
vendor/gems/ruby-openid-2.1.2/lib/openid/consumer/associationmanager.rb
vendored
Normal file
|
|
@ -0,0 +1,340 @@
|
|||
require "openid/dh"
|
||||
require "openid/util"
|
||||
require "openid/kvpost"
|
||||
require "openid/cryptutil"
|
||||
require "openid/protocolerror"
|
||||
require "openid/association"
|
||||
|
||||
module OpenID
|
||||
class Consumer
|
||||
|
||||
# A superclass for implementing Diffie-Hellman association sessions.
|
||||
class DiffieHellmanSession
|
||||
class << self
|
||||
attr_reader :session_type, :secret_size, :allowed_assoc_types,
|
||||
:hashfunc
|
||||
end
|
||||
|
||||
def initialize(dh=nil)
|
||||
if dh.nil?
|
||||
dh = DiffieHellman.from_defaults
|
||||
end
|
||||
@dh = dh
|
||||
end
|
||||
|
||||
# Return the query parameters for requesting an association
|
||||
# using this Diffie-Hellman association session
|
||||
def get_request
|
||||
args = {'dh_consumer_public' => CryptUtil.num_to_base64(@dh.public)}
|
||||
if (!@dh.using_default_values?)
|
||||
args['dh_modulus'] = CryptUtil.num_to_base64(@dh.modulus)
|
||||
args['dh_gen'] = CryptUtil.num_to_base64(@dh.generator)
|
||||
end
|
||||
|
||||
return args
|
||||
end
|
||||
|
||||
# Process the response from a successful association request and
|
||||
# return the shared secret for this association
|
||||
def extract_secret(response)
|
||||
dh_server_public64 = response.get_arg(OPENID_NS, 'dh_server_public',
|
||||
NO_DEFAULT)
|
||||
enc_mac_key64 = response.get_arg(OPENID_NS, 'enc_mac_key', NO_DEFAULT)
|
||||
dh_server_public = CryptUtil.base64_to_num(dh_server_public64)
|
||||
enc_mac_key = Util.from_base64(enc_mac_key64)
|
||||
return @dh.xor_secret(self.class.hashfunc,
|
||||
dh_server_public, enc_mac_key)
|
||||
end
|
||||
end
|
||||
|
||||
# A Diffie-Hellman association session that uses SHA1 as its hash
|
||||
# function
|
||||
class DiffieHellmanSHA1Session < DiffieHellmanSession
|
||||
@session_type = 'DH-SHA1'
|
||||
@secret_size = 20
|
||||
@allowed_assoc_types = ['HMAC-SHA1']
|
||||
@hashfunc = CryptUtil.method(:sha1)
|
||||
end
|
||||
|
||||
# A Diffie-Hellman association session that uses SHA256 as its hash
|
||||
# function
|
||||
class DiffieHellmanSHA256Session < DiffieHellmanSession
|
||||
@session_type = 'DH-SHA256'
|
||||
@secret_size = 32
|
||||
@allowed_assoc_types = ['HMAC-SHA256']
|
||||
@hashfunc = CryptUtil.method(:sha256)
|
||||
end
|
||||
|
||||
# An association session that does not use encryption
|
||||
class NoEncryptionSession
|
||||
class << self
|
||||
attr_reader :session_type, :allowed_assoc_types
|
||||
end
|
||||
@session_type = 'no-encryption'
|
||||
@allowed_assoc_types = ['HMAC-SHA1', 'HMAC-SHA256']
|
||||
|
||||
def get_request
|
||||
return {}
|
||||
end
|
||||
|
||||
def extract_secret(response)
|
||||
mac_key64 = response.get_arg(OPENID_NS, 'mac_key', NO_DEFAULT)
|
||||
return Util.from_base64(mac_key64)
|
||||
end
|
||||
end
|
||||
|
||||
# An object that manages creating and storing associations for an
|
||||
# OpenID provider endpoint
|
||||
class AssociationManager
|
||||
def self.create_session(session_type)
|
||||
case session_type
|
||||
when 'no-encryption'
|
||||
NoEncryptionSession.new
|
||||
when 'DH-SHA1'
|
||||
DiffieHellmanSHA1Session.new
|
||||
when 'DH-SHA256'
|
||||
DiffieHellmanSHA256Session.new
|
||||
else
|
||||
raise ArgumentError, "Unknown association session type: "\
|
||||
"#{session_type.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(store, server_url, compatibility_mode=false,
|
||||
negotiator=nil)
|
||||
@store = store
|
||||
@server_url = server_url
|
||||
@compatibility_mode = compatibility_mode
|
||||
@negotiator = negotiator || DefaultNegotiator
|
||||
end
|
||||
|
||||
def get_association
|
||||
if @store.nil?
|
||||
return nil
|
||||
end
|
||||
|
||||
assoc = @store.get_association(@server_url)
|
||||
if assoc.nil? || assoc.expires_in <= 0
|
||||
assoc = negotiate_association
|
||||
if !assoc.nil?
|
||||
@store.store_association(@server_url, assoc)
|
||||
end
|
||||
end
|
||||
|
||||
return assoc
|
||||
end
|
||||
|
||||
def negotiate_association
|
||||
assoc_type, session_type = @negotiator.get_allowed_type
|
||||
begin
|
||||
return request_association(assoc_type, session_type)
|
||||
rescue ServerError => why
|
||||
supported_types = extract_supported_association_type(why, assoc_type)
|
||||
if !supported_types.nil?
|
||||
# Attempt to create an association from the assoc_type and
|
||||
# session_type that the server told us it supported.
|
||||
assoc_type, session_type = supported_types
|
||||
begin
|
||||
return request_association(assoc_type, session_type)
|
||||
rescue ServerError => why
|
||||
Util.log("Server #{@server_url} refused its suggested " \
|
||||
"association type: session_type=#{session_type}, " \
|
||||
"assoc_type=#{assoc_type}")
|
||||
return nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def extract_supported_association_type(server_error, assoc_type)
|
||||
# Any error message whose code is not 'unsupported-type' should
|
||||
# be considered a total failure.
|
||||
if (server_error.error_code != 'unsupported-type' or
|
||||
server_error.message.is_openid1)
|
||||
Util.log("Server error when requesting an association from "\
|
||||
"#{@server_url}: #{server_error.error_text}")
|
||||
return nil
|
||||
end
|
||||
|
||||
# The server didn't like the association/session type that we
|
||||
# sent, and it sent us back a message that might tell us how to
|
||||
# handle it.
|
||||
Util.log("Unsupported association type #{assoc_type}: "\
|
||||
"#{server_error.error_text}")
|
||||
|
||||
# Extract the session_type and assoc_type from the error message
|
||||
assoc_type = server_error.message.get_arg(OPENID_NS, 'assoc_type')
|
||||
session_type = server_error.message.get_arg(OPENID_NS, 'session_type')
|
||||
|
||||
if assoc_type.nil? or session_type.nil?
|
||||
Util.log("Server #{@server_url} responded with unsupported "\
|
||||
"association session but did not supply a fallback.")
|
||||
return nil
|
||||
elsif !@negotiator.allowed?(assoc_type, session_type)
|
||||
Util.log("Server sent unsupported session/association type: "\
|
||||
"session_type=#{session_type}, assoc_type=#{assoc_type}")
|
||||
return nil
|
||||
else
|
||||
return [assoc_type, session_type]
|
||||
end
|
||||
end
|
||||
|
||||
# Make and process one association request to this endpoint's OP
|
||||
# endpoint URL. Returns an association object or nil if the
|
||||
# association processing failed. Raises ServerError when the
|
||||
# remote OpenID server returns an error.
|
||||
def request_association(assoc_type, session_type)
|
||||
assoc_session, args = create_associate_request(assoc_type, session_type)
|
||||
|
||||
begin
|
||||
response = OpenID.make_kv_post(args, @server_url)
|
||||
return extract_association(response, assoc_session)
|
||||
rescue HTTPStatusError => why
|
||||
Util.log("Got HTTP status error when requesting association: #{why}")
|
||||
return nil
|
||||
rescue Message::KeyNotFound => why
|
||||
Util.log("Missing required parameter in response from "\
|
||||
"#{@server_url}: #{why}")
|
||||
return nil
|
||||
|
||||
rescue ProtocolError => why
|
||||
Util.log("Protocol error processing response from #{@server_url}: "\
|
||||
"#{why}")
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
# Create an association request for the given assoc_type and
|
||||
# session_type. Returns a pair of the association session object
|
||||
# and the request message that will be sent to the server.
|
||||
def create_associate_request(assoc_type, session_type)
|
||||
assoc_session = self.class.create_session(session_type)
|
||||
args = {
|
||||
'mode' => 'associate',
|
||||
'assoc_type' => assoc_type,
|
||||
}
|
||||
|
||||
if !@compatibility_mode
|
||||
args['ns'] = OPENID2_NS
|
||||
end
|
||||
|
||||
# Leave out the session type if we're in compatibility mode
|
||||
# *and* it's no-encryption.
|
||||
if !@compatibility_mode ||
|
||||
assoc_session.class.session_type != 'no-encryption'
|
||||
args['session_type'] = assoc_session.class.session_type
|
||||
end
|
||||
|
||||
args.merge!(assoc_session.get_request)
|
||||
message = Message.from_openid_args(args)
|
||||
return assoc_session, message
|
||||
end
|
||||
|
||||
# Given an association response message, extract the OpenID 1.X
|
||||
# session type. Returns the association type for this message
|
||||
#
|
||||
# This function mostly takes care of the 'no-encryption' default
|
||||
# behavior in OpenID 1.
|
||||
#
|
||||
# If the association type is plain-text, this function will
|
||||
# return 'no-encryption'
|
||||
def get_openid1_session_type(assoc_response)
|
||||
# If it's an OpenID 1 message, allow session_type to default
|
||||
# to nil (which signifies "no-encryption")
|
||||
session_type = assoc_response.get_arg(OPENID1_NS, 'session_type')
|
||||
|
||||
# Handle the differences between no-encryption association
|
||||
# respones in OpenID 1 and 2:
|
||||
|
||||
# no-encryption is not really a valid session type for
|
||||
# OpenID 1, but we'll accept it anyway, while issuing a
|
||||
# warning.
|
||||
if session_type == 'no-encryption'
|
||||
Util.log("WARNING: #{@server_url} sent 'no-encryption'"\
|
||||
"for OpenID 1.X")
|
||||
|
||||
# Missing or empty session type is the way to flag a
|
||||
# 'no-encryption' response. Change the session type to
|
||||
# 'no-encryption' so that it can be handled in the same
|
||||
# way as OpenID 2 'no-encryption' respones.
|
||||
elsif session_type == '' || session_type.nil?
|
||||
session_type = 'no-encryption'
|
||||
end
|
||||
|
||||
return session_type
|
||||
end
|
||||
|
||||
def self.extract_expires_in(message)
|
||||
# expires_in should be a base-10 string.
|
||||
expires_in_str = message.get_arg(OPENID_NS, 'expires_in', NO_DEFAULT)
|
||||
if !(/\A\d+\Z/ =~ expires_in_str)
|
||||
raise ProtocolError, "Invalid expires_in field: #{expires_in_str}"
|
||||
end
|
||||
expires_in_str.to_i
|
||||
end
|
||||
|
||||
# Attempt to extract an association from the response, given the
|
||||
# association response message and the established association
|
||||
# session.
|
||||
def extract_association(assoc_response, assoc_session)
|
||||
# Extract the common fields from the response, raising an
|
||||
# exception if they are not found
|
||||
assoc_type = assoc_response.get_arg(OPENID_NS, 'assoc_type',
|
||||
NO_DEFAULT)
|
||||
assoc_handle = assoc_response.get_arg(OPENID_NS, 'assoc_handle',
|
||||
NO_DEFAULT)
|
||||
expires_in = self.class.extract_expires_in(assoc_response)
|
||||
|
||||
# OpenID 1 has funny association session behaviour.
|
||||
if assoc_response.is_openid1
|
||||
session_type = get_openid1_session_type(assoc_response)
|
||||
else
|
||||
session_type = assoc_response.get_arg(OPENID2_NS, 'session_type',
|
||||
NO_DEFAULT)
|
||||
end
|
||||
|
||||
# Session type mismatch
|
||||
if assoc_session.class.session_type != session_type
|
||||
if (assoc_response.is_openid1 and session_type == 'no-encryption')
|
||||
# In OpenID 1, any association request can result in a
|
||||
# 'no-encryption' association response. Setting
|
||||
# assoc_session to a new no-encryption session should
|
||||
# make the rest of this function work properly for
|
||||
# that case.
|
||||
assoc_session = NoEncryptionSession.new
|
||||
else
|
||||
# Any other mismatch, regardless of protocol version
|
||||
# results in the failure of the association session
|
||||
# altogether.
|
||||
raise ProtocolError, "Session type mismatch. Expected "\
|
||||
"#{assoc_session.class.session_type}, got "\
|
||||
"#{session_type}"
|
||||
end
|
||||
end
|
||||
|
||||
# Make sure assoc_type is valid for session_type
|
||||
if !assoc_session.class.allowed_assoc_types.member?(assoc_type)
|
||||
raise ProtocolError, "Unsupported assoc_type for session "\
|
||||
"#{assoc_session.class.session_type} "\
|
||||
"returned: #{assoc_type}"
|
||||
end
|
||||
|
||||
# Delegate to the association session to extract the secret
|
||||
# from the response, however is appropriate for that session
|
||||
# type.
|
||||
begin
|
||||
secret = assoc_session.extract_secret(assoc_response)
|
||||
rescue Message::KeyNotFound, ArgumentError => why
|
||||
raise ProtocolError, "Malformed response for "\
|
||||
"#{assoc_session.class.session_type} "\
|
||||
"session: #{why.message}"
|
||||
end
|
||||
|
||||
|
||||
return Association.from_expires_in(expires_in, assoc_handle, secret,
|
||||
assoc_type)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
186
vendor/gems/ruby-openid-2.1.2/lib/openid/consumer/checkid_request.rb
vendored
Normal file
186
vendor/gems/ruby-openid-2.1.2/lib/openid/consumer/checkid_request.rb
vendored
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
require "openid/message"
|
||||
require "openid/util"
|
||||
|
||||
module OpenID
|
||||
class Consumer
|
||||
# An object that holds the state necessary for generating an
|
||||
# OpenID authentication request. This object holds the association
|
||||
# with the server and the discovered information with which the
|
||||
# request will be made.
|
||||
#
|
||||
# It is separate from the consumer because you may wish to add
|
||||
# things to the request before sending it on its way to the
|
||||
# server. It also has serialization options that let you encode
|
||||
# the authentication request as a URL or as a form POST.
|
||||
class CheckIDRequest
|
||||
attr_accessor :return_to_args, :message
|
||||
attr_reader :endpoint
|
||||
|
||||
# Users of this library should not create instances of this
|
||||
# class. Instances of this class are created by the library
|
||||
# when needed.
|
||||
def initialize(assoc, endpoint)
|
||||
@assoc = assoc
|
||||
@endpoint = endpoint
|
||||
@return_to_args = {}
|
||||
@message = Message.new(endpoint.preferred_namespace)
|
||||
@anonymous = false
|
||||
end
|
||||
|
||||
attr_reader :anonymous
|
||||
|
||||
# Set whether this request should be made anonymously. If a
|
||||
# request is anonymous, the identifier will not be sent in the
|
||||
# request. This is only useful if you are making another kind of
|
||||
# request with an extension in this request.
|
||||
#
|
||||
# Anonymous requests are not allowed when the request is made
|
||||
# with OpenID 1.
|
||||
def anonymous=(is_anonymous)
|
||||
if is_anonymous && @message.is_openid1
|
||||
raise ArgumentError, ("OpenID1 requests MUST include the "\
|
||||
"identifier in the request")
|
||||
end
|
||||
@anonymous = is_anonymous
|
||||
end
|
||||
|
||||
# Add an object that implements the extension interface for
|
||||
# adding arguments to an OpenID message to this checkid request.
|
||||
#
|
||||
# extension_request: an OpenID::Extension object.
|
||||
def add_extension(extension_request)
|
||||
extension_request.to_message(@message)
|
||||
end
|
||||
|
||||
# Add an extension argument to this OpenID authentication
|
||||
# request. You probably want to use add_extension and the
|
||||
# OpenID::Extension interface.
|
||||
#
|
||||
# Use caution when adding arguments, because they will be
|
||||
# URL-escaped and appended to the redirect URL, which can easily
|
||||
# get quite long.
|
||||
def add_extension_arg(namespace, key, value)
|
||||
@message.set_arg(namespace, key, value)
|
||||
end
|
||||
|
||||
# Produce a OpenID::Message representing this request.
|
||||
#
|
||||
# Not specifying a return_to URL means that the user will not be
|
||||
# returned to the site issuing the request upon its completion.
|
||||
#
|
||||
# If immediate mode is requested, the OpenID provider is to send
|
||||
# back a response immediately, useful for behind-the-scenes
|
||||
# authentication attempts. Otherwise the OpenID provider may
|
||||
# engage the user before providing a response. This is the
|
||||
# default case, as the user may need to provide credentials or
|
||||
# approve the request before a positive response can be sent.
|
||||
def get_message(realm, return_to=nil, immediate=false)
|
||||
if !return_to.nil?
|
||||
return_to = Util.append_args(return_to, @return_to_args)
|
||||
elsif immediate
|
||||
raise ArgumentError, ('"return_to" is mandatory when using '\
|
||||
'"checkid_immediate"')
|
||||
elsif @message.is_openid1
|
||||
raise ArgumentError, ('"return_to" is mandatory for OpenID 1 '\
|
||||
'requests')
|
||||
elsif @return_to_args.empty?
|
||||
raise ArgumentError, ('extra "return_to" arguments were specified, '\
|
||||
'but no return_to was specified')
|
||||
end
|
||||
|
||||
|
||||
message = @message.copy
|
||||
|
||||
mode = immediate ? 'checkid_immediate' : 'checkid_setup'
|
||||
message.set_arg(OPENID_NS, 'mode', mode)
|
||||
|
||||
realm_key = message.is_openid1 ? 'trust_root' : 'realm'
|
||||
message.set_arg(OPENID_NS, realm_key, realm)
|
||||
|
||||
if !return_to.nil?
|
||||
message.set_arg(OPENID_NS, 'return_to', return_to)
|
||||
end
|
||||
|
||||
if not @anonymous
|
||||
if @endpoint.is_op_identifier
|
||||
# This will never happen when we're in OpenID 1
|
||||
# compatibility mode, as long as is_op_identifier()
|
||||
# returns false whenever preferred_namespace returns
|
||||
# OPENID1_NS.
|
||||
claimed_id = request_identity = IDENTIFIER_SELECT
|
||||
else
|
||||
request_identity = @endpoint.get_local_id
|
||||
claimed_id = @endpoint.claimed_id
|
||||
end
|
||||
|
||||
# This is true for both OpenID 1 and 2
|
||||
message.set_arg(OPENID_NS, 'identity', request_identity)
|
||||
|
||||
if message.is_openid2
|
||||
message.set_arg(OPENID2_NS, 'claimed_id', claimed_id)
|
||||
end
|
||||
end
|
||||
|
||||
if @assoc
|
||||
message.set_arg(OPENID_NS, 'assoc_handle', @assoc.handle)
|
||||
assoc_log_msg = "with assocication #{@assoc.handle}"
|
||||
else
|
||||
assoc_log_msg = 'using stateless mode.'
|
||||
end
|
||||
|
||||
Util.log("Generated #{mode} request to #{@endpoint.server_url} "\
|
||||
"#{assoc_log_msg}")
|
||||
return message
|
||||
end
|
||||
|
||||
# Returns a URL with an encoded OpenID request.
|
||||
#
|
||||
# The resulting URL is the OpenID provider's endpoint URL with
|
||||
# parameters appended as query arguments. You should redirect
|
||||
# the user agent to this URL.
|
||||
#
|
||||
# OpenID 2.0 endpoints also accept POST requests, see
|
||||
# 'send_redirect?' and 'form_markup'.
|
||||
def redirect_url(realm, return_to=nil, immediate=false)
|
||||
message = get_message(realm, return_to, immediate)
|
||||
return message.to_url(@endpoint.server_url)
|
||||
end
|
||||
|
||||
# Get html for a form to submit this request to the IDP.
|
||||
#
|
||||
# form_tag_attrs is a hash of attributes to be added to the form
|
||||
# tag. 'accept-charset' and 'enctype' have defaults that can be
|
||||
# overridden. If a value is supplied for 'action' or 'method',
|
||||
# it will be replaced.
|
||||
def form_markup(realm, return_to=nil, immediate=false,
|
||||
form_tag_attrs=nil)
|
||||
message = get_message(realm, return_to, immediate)
|
||||
return message.to_form_markup(@endpoint.server_url, form_tag_attrs)
|
||||
end
|
||||
|
||||
# Get a complete HTML document that autosubmits the request to the IDP
|
||||
# with javascript. This method wraps form_markup - see that method's
|
||||
# documentation for help with the parameters.
|
||||
def html_markup(realm, return_to=nil, immediate=false,
|
||||
form_tag_attrs=nil)
|
||||
Util.auto_submit_html(form_markup(realm,
|
||||
return_to,
|
||||
immediate,
|
||||
form_tag_attrs))
|
||||
end
|
||||
|
||||
# Should this OpenID authentication request be sent as a HTTP
|
||||
# redirect or as a POST (form submission)?
|
||||
#
|
||||
# This takes the same parameters as redirect_url or form_markup
|
||||
def send_redirect?(realm, return_to=nil, immediate=false)
|
||||
if @endpoint.compatibility_mode
|
||||
return true
|
||||
else
|
||||
url = redirect_url(realm, return_to, immediate)
|
||||
return url.length <= OPENID1_URL_LIMIT
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
490
vendor/gems/ruby-openid-2.1.2/lib/openid/consumer/discovery.rb
vendored
Normal file
490
vendor/gems/ruby-openid-2.1.2/lib/openid/consumer/discovery.rb
vendored
Normal file
|
|
@ -0,0 +1,490 @@
|
|||
# Functions to discover OpenID endpoints from identifiers.
|
||||
|
||||
require 'uri'
|
||||
require 'openid/util'
|
||||
require 'openid/fetchers'
|
||||
require 'openid/urinorm'
|
||||
require 'openid/message'
|
||||
require 'openid/yadis/discovery'
|
||||
require 'openid/yadis/xrds'
|
||||
require 'openid/yadis/xri'
|
||||
require 'openid/yadis/services'
|
||||
require 'openid/yadis/filters'
|
||||
require 'openid/consumer/html_parse'
|
||||
require 'openid/yadis/xrires'
|
||||
|
||||
module OpenID
|
||||
|
||||
OPENID_1_0_NS = 'http://openid.net/xmlns/1.0'
|
||||
OPENID_IDP_2_0_TYPE = 'http://specs.openid.net/auth/2.0/server'
|
||||
OPENID_2_0_TYPE = 'http://specs.openid.net/auth/2.0/signon'
|
||||
OPENID_1_1_TYPE = 'http://openid.net/signon/1.1'
|
||||
OPENID_1_0_TYPE = 'http://openid.net/signon/1.0'
|
||||
|
||||
OPENID_1_0_MESSAGE_NS = OPENID1_NS
|
||||
OPENID_2_0_MESSAGE_NS = OPENID2_NS
|
||||
|
||||
# Object representing an OpenID service endpoint.
|
||||
class OpenIDServiceEndpoint
|
||||
|
||||
# OpenID service type URIs, listed in order of preference. The
|
||||
# ordering of this list affects yadis and XRI service discovery.
|
||||
OPENID_TYPE_URIS = [
|
||||
OPENID_IDP_2_0_TYPE,
|
||||
|
||||
OPENID_2_0_TYPE,
|
||||
OPENID_1_1_TYPE,
|
||||
OPENID_1_0_TYPE,
|
||||
]
|
||||
|
||||
# the verified identifier.
|
||||
attr_accessor :claimed_id
|
||||
|
||||
# For XRI, the persistent identifier.
|
||||
attr_accessor :canonical_id
|
||||
|
||||
attr_accessor :server_url, :type_uris, :local_id, :used_yadis
|
||||
|
||||
def initialize
|
||||
@claimed_id = nil
|
||||
@server_url = nil
|
||||
@type_uris = []
|
||||
@local_id = nil
|
||||
@canonical_id = nil
|
||||
@used_yadis = false # whether this came from an XRDS
|
||||
@display_identifier = nil
|
||||
end
|
||||
|
||||
def display_identifier
|
||||
return @display_identifier if @display_identifier
|
||||
|
||||
return @claimed_id if @claimed_id.nil?
|
||||
|
||||
begin
|
||||
parsed_identifier = URI.parse(@claimed_id)
|
||||
rescue URI::InvalidURIError
|
||||
raise ProtocolError, "Claimed identifier #{claimed_id} is not a valid URI"
|
||||
end
|
||||
|
||||
return @claimed_id if not parsed_identifier.fragment
|
||||
|
||||
disp = parsed_identifier
|
||||
disp.fragment = nil
|
||||
|
||||
return disp.to_s
|
||||
end
|
||||
|
||||
def display_identifier=(display_identifier)
|
||||
@display_identifier = display_identifier
|
||||
end
|
||||
|
||||
def uses_extension(extension_uri)
|
||||
return @type_uris.member?(extension_uri)
|
||||
end
|
||||
|
||||
def preferred_namespace
|
||||
if (@type_uris.member?(OPENID_IDP_2_0_TYPE) or
|
||||
@type_uris.member?(OPENID_2_0_TYPE))
|
||||
return OPENID_2_0_MESSAGE_NS
|
||||
else
|
||||
return OPENID_1_0_MESSAGE_NS
|
||||
end
|
||||
end
|
||||
|
||||
def supports_type(type_uri)
|
||||
# Does this endpoint support this type?
|
||||
#
|
||||
# I consider C{/server} endpoints to implicitly support C{/signon}.
|
||||
(
|
||||
@type_uris.member?(type_uri) or
|
||||
(type_uri == OPENID_2_0_TYPE and is_op_identifier())
|
||||
)
|
||||
end
|
||||
|
||||
def compatibility_mode
|
||||
return preferred_namespace() != OPENID_2_0_MESSAGE_NS
|
||||
end
|
||||
|
||||
def is_op_identifier
|
||||
return @type_uris.member?(OPENID_IDP_2_0_TYPE)
|
||||
end
|
||||
|
||||
def parse_service(yadis_url, uri, type_uris, service_element)
|
||||
# Set the state of this object based on the contents of the
|
||||
# service element.
|
||||
@type_uris = type_uris
|
||||
@server_url = uri
|
||||
@used_yadis = true
|
||||
|
||||
if !is_op_identifier()
|
||||
# XXX: This has crappy implications for Service elements that
|
||||
# contain both 'server' and 'signon' Types. But that's a
|
||||
# pathological configuration anyway, so I don't think I care.
|
||||
@local_id = OpenID.find_op_local_identifier(service_element,
|
||||
@type_uris)
|
||||
@claimed_id = yadis_url
|
||||
end
|
||||
end
|
||||
|
||||
def get_local_id
|
||||
# Return the identifier that should be sent as the
|
||||
# openid.identity parameter to the server.
|
||||
if @local_id.nil? and @canonical_id.nil?
|
||||
return @claimed_id
|
||||
else
|
||||
return (@local_id or @canonical_id)
|
||||
end
|
||||
end
|
||||
|
||||
def self.from_basic_service_endpoint(endpoint)
|
||||
# Create a new instance of this class from the endpoint object
|
||||
# passed in.
|
||||
#
|
||||
# @return: nil or OpenIDServiceEndpoint for this endpoint object"""
|
||||
|
||||
type_uris = endpoint.match_types(OPENID_TYPE_URIS)
|
||||
|
||||
# If any Type URIs match and there is an endpoint URI specified,
|
||||
# then this is an OpenID endpoint
|
||||
if (!type_uris.nil? and !type_uris.empty?) and !endpoint.uri.nil?
|
||||
openid_endpoint = self.new
|
||||
openid_endpoint.parse_service(
|
||||
endpoint.yadis_url,
|
||||
endpoint.uri,
|
||||
endpoint.type_uris,
|
||||
endpoint.service_element)
|
||||
else
|
||||
openid_endpoint = nil
|
||||
end
|
||||
|
||||
return openid_endpoint
|
||||
end
|
||||
|
||||
def self.from_html(uri, html)
|
||||
# Parse the given document as HTML looking for an OpenID <link
|
||||
# rel=...>
|
||||
#
|
||||
# @rtype: [OpenIDServiceEndpoint]
|
||||
|
||||
discovery_types = [
|
||||
[OPENID_2_0_TYPE, 'openid2.provider', 'openid2.local_id'],
|
||||
[OPENID_1_1_TYPE, 'openid.server', 'openid.delegate'],
|
||||
]
|
||||
|
||||
link_attrs = OpenID.parse_link_attrs(html)
|
||||
services = []
|
||||
discovery_types.each { |type_uri, op_endpoint_rel, local_id_rel|
|
||||
|
||||
op_endpoint_url = OpenID.find_first_href(link_attrs, op_endpoint_rel)
|
||||
|
||||
if !op_endpoint_url
|
||||
next
|
||||
end
|
||||
|
||||
service = self.new
|
||||
service.claimed_id = uri
|
||||
service.local_id = OpenID.find_first_href(link_attrs, local_id_rel)
|
||||
service.server_url = op_endpoint_url
|
||||
service.type_uris = [type_uri]
|
||||
|
||||
services << service
|
||||
}
|
||||
|
||||
return services
|
||||
end
|
||||
|
||||
def self.from_xrds(uri, xrds)
|
||||
# Parse the given document as XRDS looking for OpenID services.
|
||||
#
|
||||
# @rtype: [OpenIDServiceEndpoint]
|
||||
#
|
||||
# @raises L{XRDSError}: When the XRDS does not parse.
|
||||
return Yadis::apply_filter(uri, xrds, self)
|
||||
end
|
||||
|
||||
def self.from_discovery_result(discoveryResult)
|
||||
# Create endpoints from a DiscoveryResult.
|
||||
#
|
||||
# @type discoveryResult: L{DiscoveryResult}
|
||||
#
|
||||
# @rtype: list of L{OpenIDServiceEndpoint}
|
||||
#
|
||||
# @raises L{XRDSError}: When the XRDS does not parse.
|
||||
if discoveryResult.is_xrds()
|
||||
meth = self.method('from_xrds')
|
||||
else
|
||||
meth = self.method('from_html')
|
||||
end
|
||||
|
||||
return meth.call(discoveryResult.normalized_uri,
|
||||
discoveryResult.response_text)
|
||||
end
|
||||
|
||||
def self.from_op_endpoint_url(op_endpoint_url)
|
||||
# Construct an OP-Identifier OpenIDServiceEndpoint object for
|
||||
# a given OP Endpoint URL
|
||||
#
|
||||
# @param op_endpoint_url: The URL of the endpoint
|
||||
# @rtype: OpenIDServiceEndpoint
|
||||
service = self.new
|
||||
service.server_url = op_endpoint_url
|
||||
service.type_uris = [OPENID_IDP_2_0_TYPE]
|
||||
return service
|
||||
end
|
||||
|
||||
def to_s
|
||||
return sprintf("<%s server_url=%s claimed_id=%s " +
|
||||
"local_id=%s canonical_id=%s used_yadis=%s>",
|
||||
self.class, @server_url, @claimed_id,
|
||||
@local_id, @canonical_id, @used_yadis)
|
||||
end
|
||||
end
|
||||
|
||||
def self.find_op_local_identifier(service_element, type_uris)
|
||||
# Find the OP-Local Identifier for this xrd:Service element.
|
||||
#
|
||||
# This considers openid:Delegate to be a synonym for xrd:LocalID
|
||||
# if both OpenID 1.X and OpenID 2.0 types are present. If only
|
||||
# OpenID 1.X is present, it returns the value of
|
||||
# openid:Delegate. If only OpenID 2.0 is present, it returns the
|
||||
# value of xrd:LocalID. If there is more than one LocalID tag and
|
||||
# the values are different, it raises a DiscoveryFailure. This is
|
||||
# also triggered when the xrd:LocalID and openid:Delegate tags are
|
||||
# different.
|
||||
|
||||
# XXX: Test this function on its own!
|
||||
|
||||
# Build the list of tags that could contain the OP-Local
|
||||
# Identifier
|
||||
local_id_tags = []
|
||||
if type_uris.member?(OPENID_1_1_TYPE) or
|
||||
type_uris.member?(OPENID_1_0_TYPE)
|
||||
# local_id_tags << Yadis::nsTag(OPENID_1_0_NS, 'openid', 'Delegate')
|
||||
service_element.add_namespace('openid', OPENID_1_0_NS)
|
||||
local_id_tags << "openid:Delegate"
|
||||
end
|
||||
|
||||
if type_uris.member?(OPENID_2_0_TYPE)
|
||||
# local_id_tags.append(Yadis::nsTag(XRD_NS_2_0, 'xrd', 'LocalID'))
|
||||
service_element.add_namespace('xrd', Yadis::XRD_NS_2_0)
|
||||
local_id_tags << "xrd:LocalID"
|
||||
end
|
||||
|
||||
# Walk through all the matching tags and make sure that they all
|
||||
# have the same value
|
||||
local_id = nil
|
||||
local_id_tags.each { |local_id_tag|
|
||||
service_element.each_element(local_id_tag) { |local_id_element|
|
||||
if local_id.nil?
|
||||
local_id = local_id_element.text
|
||||
elsif local_id != local_id_element.text
|
||||
format = 'More than one %s tag found in one service element'
|
||||
message = sprintf(format, local_id_tag)
|
||||
raise DiscoveryFailure.new(message, nil)
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
return local_id
|
||||
end
|
||||
|
||||
def self.normalize_url(url)
|
||||
# Normalize a URL, converting normalization failures to
|
||||
# DiscoveryFailure
|
||||
begin
|
||||
normalized = URINorm.urinorm(url)
|
||||
rescue URI::Error => why
|
||||
raise DiscoveryFailure.new("Error normalizing #{url}: #{why.message}", nil)
|
||||
else
|
||||
defragged = URI::parse(normalized)
|
||||
defragged.fragment = nil
|
||||
return defragged.normalize.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def self.best_matching_service(service, preferred_types)
|
||||
# Return the index of the first matching type, or something higher
|
||||
# if no type matches.
|
||||
#
|
||||
# This provides an ordering in which service elements that contain
|
||||
# a type that comes earlier in the preferred types list come
|
||||
# before service elements that come later. If a service element
|
||||
# has more than one type, the most preferred one wins.
|
||||
preferred_types.each_with_index { |value, index|
|
||||
if service.type_uris.member?(value)
|
||||
return index
|
||||
end
|
||||
}
|
||||
|
||||
return preferred_types.length
|
||||
end
|
||||
|
||||
def self.arrange_by_type(service_list, preferred_types)
|
||||
# Rearrange service_list in a new list so services are ordered by
|
||||
# types listed in preferred_types. Return the new list.
|
||||
|
||||
# Build a list with the service elements in tuples whose
|
||||
# comparison will prefer the one with the best matching service
|
||||
prio_services = []
|
||||
|
||||
service_list.each_with_index { |s, index|
|
||||
prio_services << [best_matching_service(s, preferred_types), index, s]
|
||||
}
|
||||
|
||||
prio_services.sort!
|
||||
|
||||
# Now that the services are sorted by priority, remove the sort
|
||||
# keys from the list.
|
||||
(0...prio_services.length).each { |i|
|
||||
prio_services[i] = prio_services[i][2]
|
||||
}
|
||||
|
||||
return prio_services
|
||||
end
|
||||
|
||||
def self.get_op_or_user_services(openid_services)
|
||||
# Extract OP Identifier services. If none found, return the rest,
|
||||
# sorted with most preferred first according to
|
||||
# OpenIDServiceEndpoint.openid_type_uris.
|
||||
#
|
||||
# openid_services is a list of OpenIDServiceEndpoint objects.
|
||||
#
|
||||
# Returns a list of OpenIDServiceEndpoint objects.
|
||||
|
||||
op_services = arrange_by_type(openid_services, [OPENID_IDP_2_0_TYPE])
|
||||
|
||||
openid_services = arrange_by_type(openid_services,
|
||||
OpenIDServiceEndpoint::OPENID_TYPE_URIS)
|
||||
|
||||
if !op_services.empty?
|
||||
return op_services
|
||||
else
|
||||
return openid_services
|
||||
end
|
||||
end
|
||||
|
||||
def self.discover_yadis(uri)
|
||||
# Discover OpenID services for a URI. Tries Yadis and falls back
|
||||
# on old-style <link rel='...'> discovery if Yadis fails.
|
||||
#
|
||||
# @param uri: normalized identity URL
|
||||
# @type uri: str
|
||||
#
|
||||
# @return: (claimed_id, services)
|
||||
# @rtype: (str, list(OpenIDServiceEndpoint))
|
||||
#
|
||||
# @raises DiscoveryFailure: when discovery fails.
|
||||
|
||||
# Might raise a yadis.discover.DiscoveryFailure if no document
|
||||
# came back for that URI at all. I don't think falling back to
|
||||
# OpenID 1.0 discovery on the same URL will help, so don't bother
|
||||
# to catch it.
|
||||
response = Yadis.discover(uri)
|
||||
|
||||
yadis_url = response.normalized_uri
|
||||
body = response.response_text
|
||||
|
||||
begin
|
||||
openid_services = OpenIDServiceEndpoint.from_xrds(yadis_url, body)
|
||||
rescue Yadis::XRDSError
|
||||
# Does not parse as a Yadis XRDS file
|
||||
openid_services = []
|
||||
end
|
||||
|
||||
if openid_services.empty?
|
||||
# Either not an XRDS or there are no OpenID services.
|
||||
|
||||
if response.is_xrds
|
||||
# if we got the Yadis content-type or followed the Yadis
|
||||
# header, re-fetch the document without following the Yadis
|
||||
# header, with no Accept header.
|
||||
return self.discover_no_yadis(uri)
|
||||
end
|
||||
|
||||
# Try to parse the response as HTML.
|
||||
# <link rel="...">
|
||||
openid_services = OpenIDServiceEndpoint.from_html(yadis_url, body)
|
||||
end
|
||||
|
||||
return [yadis_url, self.get_op_or_user_services(openid_services)]
|
||||
end
|
||||
|
||||
def self.discover_xri(iname)
|
||||
endpoints = []
|
||||
|
||||
begin
|
||||
canonical_id, services = Yadis::XRI::ProxyResolver.new().query(
|
||||
iname, OpenIDServiceEndpoint::OPENID_TYPE_URIS)
|
||||
|
||||
if canonical_id.nil?
|
||||
raise Yadis::XRDSError.new(sprintf('No CanonicalID found for XRI %s', iname))
|
||||
end
|
||||
|
||||
flt = Yadis.make_filter(OpenIDServiceEndpoint)
|
||||
|
||||
services.each { |service_element|
|
||||
endpoints += flt.get_service_endpoints(iname, service_element)
|
||||
}
|
||||
rescue Yadis::XRDSError => why
|
||||
Util.log('xrds error on ' + iname + ': ' + why.to_s)
|
||||
end
|
||||
|
||||
endpoints.each { |endpoint|
|
||||
# Is there a way to pass this through the filter to the endpoint
|
||||
# constructor instead of tacking it on after?
|
||||
endpoint.canonical_id = canonical_id
|
||||
endpoint.claimed_id = canonical_id
|
||||
endpoint.display_identifier = iname
|
||||
}
|
||||
|
||||
# FIXME: returned xri should probably be in some normal form
|
||||
return [iname, self.get_op_or_user_services(endpoints)]
|
||||
end
|
||||
|
||||
def self.discover_no_yadis(uri)
|
||||
http_resp = OpenID.fetch(uri)
|
||||
if http_resp.code != "200" and http_resp.code != "206"
|
||||
raise DiscoveryFailure.new(
|
||||
"HTTP Response status from identity URL host is not \"200\". "\
|
||||
"Got status #{http_resp.code.inspect}", http_resp)
|
||||
end
|
||||
|
||||
claimed_id = http_resp.final_url
|
||||
openid_services = OpenIDServiceEndpoint.from_html(
|
||||
claimed_id, http_resp.body)
|
||||
return [claimed_id, openid_services]
|
||||
end
|
||||
|
||||
def self.discover_uri(uri)
|
||||
# Hack to work around URI parsing for URls with *no* scheme.
|
||||
if uri.index("://").nil?
|
||||
uri = 'http://' + uri
|
||||
end
|
||||
|
||||
begin
|
||||
parsed = URI::parse(uri)
|
||||
rescue URI::InvalidURIError => why
|
||||
raise DiscoveryFailure.new("URI is not valid: #{why.message}", nil)
|
||||
end
|
||||
|
||||
if !parsed.scheme.nil? and !parsed.scheme.empty?
|
||||
if !['http', 'https'].member?(parsed.scheme)
|
||||
raise DiscoveryFailure.new(
|
||||
"URI scheme #{parsed.scheme} is not HTTP or HTTPS", nil)
|
||||
end
|
||||
end
|
||||
|
||||
uri = self.normalize_url(uri)
|
||||
claimed_id, openid_services = self.discover_yadis(uri)
|
||||
claimed_id = self.normalize_url(claimed_id)
|
||||
return [claimed_id, openid_services]
|
||||
end
|
||||
|
||||
def self.discover(identifier)
|
||||
if Yadis::XRI::identifier_scheme(identifier) == :xri
|
||||
normalized_identifier, services = discover_xri(identifier)
|
||||
else
|
||||
return discover_uri(identifier)
|
||||
end
|
||||
end
|
||||
end
|
||||
123
vendor/gems/ruby-openid-2.1.2/lib/openid/consumer/discovery_manager.rb
vendored
Normal file
123
vendor/gems/ruby-openid-2.1.2/lib/openid/consumer/discovery_manager.rb
vendored
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
module OpenID
|
||||
class Consumer
|
||||
|
||||
# A set of discovered services, for tracking which providers have
|
||||
# been attempted for an OpenID identifier
|
||||
class DiscoveredServices
|
||||
attr_reader :current
|
||||
|
||||
def initialize(starting_url, yadis_url, services)
|
||||
@starting_url = starting_url
|
||||
@yadis_url = yadis_url
|
||||
@services = services.dup
|
||||
@current = nil
|
||||
end
|
||||
|
||||
def next
|
||||
@current = @services.shift
|
||||
end
|
||||
|
||||
def for_url?(url)
|
||||
[@starting_url, @yadis_url].member?(url)
|
||||
end
|
||||
|
||||
def started?
|
||||
!@current.nil?
|
||||
end
|
||||
|
||||
def empty?
|
||||
@services.empty?
|
||||
end
|
||||
end
|
||||
|
||||
# Manages calling discovery and tracking which endpoints have
|
||||
# already been attempted.
|
||||
class DiscoveryManager
|
||||
def initialize(session, url, session_key_suffix=nil)
|
||||
@url = url
|
||||
|
||||
@session = session
|
||||
@session_key_suffix = session_key_suffix || 'auth'
|
||||
end
|
||||
|
||||
def get_next_service
|
||||
manager = get_manager
|
||||
if !manager.nil? && manager.empty?
|
||||
destroy_manager
|
||||
manager = nil
|
||||
end
|
||||
|
||||
if manager.nil?
|
||||
yadis_url, services = yield @url
|
||||
manager = create_manager(yadis_url, services)
|
||||
end
|
||||
|
||||
if !manager.nil?
|
||||
service = manager.next
|
||||
store(manager)
|
||||
else
|
||||
service = nil
|
||||
end
|
||||
|
||||
return service
|
||||
end
|
||||
|
||||
def cleanup(force=false)
|
||||
manager = get_manager(force)
|
||||
if !manager.nil?
|
||||
service = manager.current
|
||||
destroy_manager(force)
|
||||
else
|
||||
service = nil
|
||||
end
|
||||
return service
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def get_manager(force=false)
|
||||
manager = load
|
||||
if force || manager.nil? || manager.for_url?(@url)
|
||||
return manager
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
def create_manager(yadis_url, services)
|
||||
manager = get_manager
|
||||
if !manager.nil?
|
||||
raise StandardError, "There is already a manager for #{yadis_url}"
|
||||
end
|
||||
if services.empty?
|
||||
return nil
|
||||
end
|
||||
manager = DiscoveredServices.new(@url, yadis_url, services)
|
||||
store(manager)
|
||||
return manager
|
||||
end
|
||||
|
||||
def destroy_manager(force=false)
|
||||
if !get_manager(force).nil?
|
||||
destroy!
|
||||
end
|
||||
end
|
||||
|
||||
def session_key
|
||||
'OpenID::Consumer::DiscoveredServices::' + @session_key_suffix
|
||||
end
|
||||
|
||||
def store(manager)
|
||||
@session[session_key] = manager
|
||||
end
|
||||
|
||||
def load
|
||||
@session[session_key]
|
||||
end
|
||||
|
||||
def destroy!
|
||||
@session[session_key] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
134
vendor/gems/ruby-openid-2.1.2/lib/openid/consumer/html_parse.rb
vendored
Normal file
134
vendor/gems/ruby-openid-2.1.2/lib/openid/consumer/html_parse.rb
vendored
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
require "openid/yadis/htmltokenizer"
|
||||
|
||||
module OpenID
|
||||
|
||||
# Stuff to remove before we start looking for tags
|
||||
REMOVED_RE = /
|
||||
# Comments
|
||||
<!--.*?-->
|
||||
|
||||
# CDATA blocks
|
||||
| <!\[CDATA\[.*?\]\]>
|
||||
|
||||
# script blocks
|
||||
| <script\b
|
||||
|
||||
# make sure script is not an XML namespace
|
||||
(?!:)
|
||||
|
||||
[^>]*>.*?<\/script>
|
||||
|
||||
/mixu
|
||||
|
||||
def OpenID.openid_unescape(s)
|
||||
s.gsub('&','&').gsub('<','<').gsub('>','>').gsub('"','"')
|
||||
end
|
||||
|
||||
def OpenID.unescape_hash(h)
|
||||
newh = {}
|
||||
h.map{|k,v|
|
||||
newh[k]=openid_unescape(v)
|
||||
}
|
||||
newh
|
||||
end
|
||||
|
||||
|
||||
def OpenID.parse_link_attrs(html)
|
||||
stripped = html.gsub(REMOVED_RE,'')
|
||||
parser = HTMLTokenizer.new(stripped)
|
||||
|
||||
links = []
|
||||
# to keep track of whether or not we are in the head element
|
||||
in_head = false
|
||||
in_html = false
|
||||
saw_head = false
|
||||
|
||||
begin
|
||||
while el = parser.getTag('head', '/head', 'link', 'body', '/body',
|
||||
'html', '/html')
|
||||
|
||||
# we are leaving head or have reached body, so we bail
|
||||
return links if ['/head', 'body', '/body', '/html'].member?(el.tag_name)
|
||||
|
||||
# enforce html > head > link
|
||||
if el.tag_name == 'html'
|
||||
in_html = true
|
||||
end
|
||||
next unless in_html
|
||||
if el.tag_name == 'head'
|
||||
if saw_head
|
||||
return links #only allow one head
|
||||
end
|
||||
saw_head = true
|
||||
unless el.to_s[-2] == 47 # tag ends with a /: a short tag
|
||||
in_head = true
|
||||
end
|
||||
end
|
||||
next unless in_head
|
||||
|
||||
return links if el.tag_name == 'html'
|
||||
|
||||
if el.tag_name == 'link'
|
||||
links << unescape_hash(el.attr_hash)
|
||||
end
|
||||
|
||||
end
|
||||
rescue Exception # just stop parsing if there's an error
|
||||
end
|
||||
return links
|
||||
end
|
||||
|
||||
def OpenID.rel_matches(rel_attr, target_rel)
|
||||
# Does this target_rel appear in the rel_str?
|
||||
# XXX: TESTME
|
||||
rels = rel_attr.strip().split()
|
||||
rels.each { |rel|
|
||||
rel = rel.downcase
|
||||
if rel == target_rel
|
||||
return true
|
||||
end
|
||||
}
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
def OpenID.link_has_rel(link_attrs, target_rel)
|
||||
# Does this link have target_rel as a relationship?
|
||||
|
||||
# XXX: TESTME
|
||||
rel_attr = link_attrs['rel']
|
||||
return (rel_attr and rel_matches(rel_attr, target_rel))
|
||||
end
|
||||
|
||||
def OpenID.find_links_rel(link_attrs_list, target_rel)
|
||||
# Filter the list of link attributes on whether it has target_rel
|
||||
# as a relationship.
|
||||
|
||||
# XXX: TESTME
|
||||
matchesTarget = lambda { |attrs| link_has_rel(attrs, target_rel) }
|
||||
result = []
|
||||
|
||||
link_attrs_list.each { |item|
|
||||
if matchesTarget.call(item)
|
||||
result << item
|
||||
end
|
||||
}
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
def OpenID.find_first_href(link_attrs_list, target_rel)
|
||||
# Return the value of the href attribute for the first link tag in
|
||||
# the list that has target_rel as a relationship.
|
||||
|
||||
# XXX: TESTME
|
||||
matches = find_links_rel(link_attrs_list, target_rel)
|
||||
if !matches or matches.empty?
|
||||
return nil
|
||||
end
|
||||
|
||||
first = matches[0]
|
||||
return first['href']
|
||||
end
|
||||
end
|
||||
|
||||
523
vendor/gems/ruby-openid-2.1.2/lib/openid/consumer/idres.rb
vendored
Normal file
523
vendor/gems/ruby-openid-2.1.2/lib/openid/consumer/idres.rb
vendored
Normal file
|
|
@ -0,0 +1,523 @@
|
|||
require "openid/message"
|
||||
require "openid/protocolerror"
|
||||
require "openid/kvpost"
|
||||
require "openid/consumer/discovery"
|
||||
require "openid/urinorm"
|
||||
|
||||
module OpenID
|
||||
class TypeURIMismatch < ProtocolError
|
||||
attr_reader :type_uri, :endpoint
|
||||
|
||||
def initialize(type_uri, endpoint)
|
||||
@type_uri = type_uri
|
||||
@endpoint = endpoint
|
||||
end
|
||||
end
|
||||
|
||||
class Consumer
|
||||
@openid1_return_to_nonce_name = 'rp_nonce'
|
||||
@openid1_return_to_claimed_id_name = 'openid1_claimed_id'
|
||||
|
||||
# Set the name of the query parameter that this library will use
|
||||
# to thread a nonce through an OpenID 1 transaction. It will be
|
||||
# appended to the return_to URL.
|
||||
def self.openid1_return_to_nonce_name=(query_arg_name)
|
||||
@openid1_return_to_nonce_name = query_arg_name
|
||||
end
|
||||
|
||||
# See openid1_return_to_nonce_name= documentation
|
||||
def self.openid1_return_to_nonce_name
|
||||
@openid1_return_to_nonce_name
|
||||
end
|
||||
|
||||
# Set the name of the query parameter that this library will use
|
||||
# to thread the requested URL through an OpenID 1 transaction (for
|
||||
# use when verifying discovered information). It will be appended
|
||||
# to the return_to URL.
|
||||
def self.openid1_return_to_claimed_id_name=(query_arg_name)
|
||||
@openid1_return_to_claimed_id_name = query_arg_name
|
||||
end
|
||||
|
||||
# See openid1_return_to_claimed_id_name=
|
||||
def self.openid1_return_to_claimed_id_name
|
||||
@openid1_return_to_claimed_id_name
|
||||
end
|
||||
|
||||
# Handles an openid.mode=id_res response. This object is
|
||||
# instantiated and used by the Consumer.
|
||||
class IdResHandler
|
||||
attr_reader :endpoint, :message
|
||||
|
||||
def initialize(message, current_url, store=nil, endpoint=nil)
|
||||
@store = store # Fer the nonce and invalidate_handle
|
||||
@message = message
|
||||
@endpoint = endpoint
|
||||
@current_url = current_url
|
||||
@signed_list = nil
|
||||
|
||||
# Start the verification process
|
||||
id_res
|
||||
end
|
||||
|
||||
def signed_fields
|
||||
signed_list.map {|x| 'openid.' + x}
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# This method will raise ProtocolError unless the request is a
|
||||
# valid id_res response. Once it has been verified, the methods
|
||||
# 'endpoint', 'message', and 'signed_fields' contain the
|
||||
# verified information.
|
||||
def id_res
|
||||
check_for_fields
|
||||
verify_return_to
|
||||
verify_discovery_results
|
||||
check_signature
|
||||
check_nonce
|
||||
end
|
||||
|
||||
def server_url
|
||||
@endpoint.nil? ? nil : @endpoint.server_url
|
||||
end
|
||||
|
||||
def openid_namespace
|
||||
@message.get_openid_namespace
|
||||
end
|
||||
|
||||
def fetch(field, default=NO_DEFAULT)
|
||||
@message.get_arg(OPENID_NS, field, default)
|
||||
end
|
||||
|
||||
def signed_list
|
||||
if @signed_list.nil?
|
||||
signed_list_str = fetch('signed', nil)
|
||||
if signed_list_str.nil?
|
||||
raise ProtocolError, 'Response missing signed list'
|
||||
end
|
||||
|
||||
@signed_list = signed_list_str.split(',', -1)
|
||||
end
|
||||
@signed_list
|
||||
end
|
||||
|
||||
def check_for_fields
|
||||
# XXX: if a field is missing, we should not have to explicitly
|
||||
# check that it's present, just make sure that the fields are
|
||||
# actually being used by the rest of the code in
|
||||
# tests. Although, which fields are signed does need to be
|
||||
# checked somewhere.
|
||||
basic_fields = ['return_to', 'assoc_handle', 'sig', 'signed']
|
||||
basic_sig_fields = ['return_to', 'identity']
|
||||
|
||||
case openid_namespace
|
||||
when OPENID2_NS
|
||||
require_fields = basic_fields + ['op_endpoint']
|
||||
require_sigs = basic_sig_fields +
|
||||
['response_nonce', 'claimed_id', 'assoc_handle',]
|
||||
when OPENID1_NS
|
||||
require_fields = basic_fields + ['identity']
|
||||
require_sigs = basic_sig_fields
|
||||
else
|
||||
raise RuntimeError, "check_for_fields doesn't know about "\
|
||||
"namespace #{openid_namespace.inspect}"
|
||||
end
|
||||
|
||||
require_fields.each do |field|
|
||||
if !@message.has_key?(OPENID_NS, field)
|
||||
raise ProtocolError, "Missing required field #{field}"
|
||||
end
|
||||
end
|
||||
|
||||
require_sigs.each do |field|
|
||||
# Field is present and not in signed list
|
||||
if @message.has_key?(OPENID_NS, field) && !signed_list.member?(field)
|
||||
raise ProtocolError, "#{field.inspect} not signed"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def verify_return_to
|
||||
begin
|
||||
msg_return_to = URI.parse(URINorm::urinorm(fetch('return_to')))
|
||||
rescue URI::InvalidURIError
|
||||
raise ProtocolError, ("return_to is not a valid URI")
|
||||
end
|
||||
|
||||
verify_return_to_args(msg_return_to)
|
||||
if !@current_url.nil?
|
||||
verify_return_to_base(msg_return_to)
|
||||
end
|
||||
end
|
||||
|
||||
def verify_return_to_args(msg_return_to)
|
||||
return_to_parsed_query = {}
|
||||
if !msg_return_to.query.nil?
|
||||
CGI.parse(msg_return_to.query).each_pair do |k, vs|
|
||||
return_to_parsed_query[k] = vs[0]
|
||||
end
|
||||
end
|
||||
query = @message.to_post_args
|
||||
return_to_parsed_query.each_pair do |rt_key, rt_val|
|
||||
msg_val = query[rt_key]
|
||||
if msg_val.nil?
|
||||
raise ProtocolError, "Message missing return_to argument '#{rt_key}'"
|
||||
elsif msg_val != rt_val
|
||||
raise ProtocolError, ("Parameter '#{rt_key}' value "\
|
||||
"#{msg_val.inspect} does not match "\
|
||||
"return_to's value #{rt_val.inspect}")
|
||||
end
|
||||
end
|
||||
@message.get_args(BARE_NS).each_pair do |bare_key, bare_val|
|
||||
rt_val = return_to_parsed_query[bare_key]
|
||||
if not return_to_parsed_query.has_key? bare_key
|
||||
# This may be caused by your web framework throwing extra
|
||||
# entries in to your parameters hash that were not GET or
|
||||
# POST parameters. For example, Rails has been known to
|
||||
# add "controller" and "action" keys; another server adds
|
||||
# at least a "format" key.
|
||||
raise ProtocolError, ("Unexpected parameter (not on return_to): "\
|
||||
"'#{bare_key}'=#{rt_val.inspect})")
|
||||
end
|
||||
if rt_val != bare_val
|
||||
raise ProtocolError, ("Parameter '#{bare_key}' value "\
|
||||
"#{bare_val.inspect} does not match "\
|
||||
"return_to's value #{rt_val.inspect}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def verify_return_to_base(msg_return_to)
|
||||
begin
|
||||
app_parsed = URI.parse(URINorm::urinorm(@current_url))
|
||||
rescue URI::InvalidURIError
|
||||
raise ProtocolError, "current_url is not a valid URI: #{@current_url}"
|
||||
end
|
||||
|
||||
[:scheme, :host, :port, :path].each do |meth|
|
||||
if msg_return_to.send(meth) != app_parsed.send(meth)
|
||||
raise ProtocolError, "return_to #{meth.to_s} does not match"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Raises ProtocolError if the signature is bad
|
||||
def check_signature
|
||||
if @store.nil?
|
||||
assoc = nil
|
||||
else
|
||||
assoc = @store.get_association(server_url, fetch('assoc_handle'))
|
||||
end
|
||||
|
||||
if assoc.nil?
|
||||
check_auth
|
||||
else
|
||||
if assoc.expires_in <= 0
|
||||
# XXX: It might be a good idea sometimes to re-start the
|
||||
# authentication with a new association. Doing it
|
||||
# automatically opens the possibility for
|
||||
# denial-of-service by a server that just returns expired
|
||||
# associations (or really short-lived associations)
|
||||
raise ProtocolError, "Association with #{server_url} expired"
|
||||
elsif !assoc.check_message_signature(@message)
|
||||
raise ProtocolError, "Bad signature in response from #{server_url}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def check_auth
|
||||
Util.log("Using 'check_authentication' with #{server_url}")
|
||||
begin
|
||||
request = create_check_auth_request
|
||||
rescue Message::KeyNotFound => why
|
||||
raise ProtocolError, "Could not generate 'check_authentication' "\
|
||||
"request: #{why.message}"
|
||||
end
|
||||
|
||||
response = OpenID.make_kv_post(request, server_url)
|
||||
|
||||
process_check_auth_response(response)
|
||||
end
|
||||
|
||||
def create_check_auth_request
|
||||
signed_list = @message.get_arg(OPENID_NS, 'signed', NO_DEFAULT).split(',')
|
||||
|
||||
# check that we got all the signed arguments
|
||||
signed_list.each {|k|
|
||||
@message.get_aliased_arg(k, NO_DEFAULT)
|
||||
}
|
||||
|
||||
ca_message = @message.copy
|
||||
ca_message.set_arg(OPENID_NS, 'mode', 'check_authentication')
|
||||
|
||||
return ca_message
|
||||
end
|
||||
|
||||
# Process the response message from a check_authentication
|
||||
# request, invalidating associations if requested.
|
||||
def process_check_auth_response(response)
|
||||
is_valid = response.get_arg(OPENID_NS, 'is_valid', 'false')
|
||||
|
||||
invalidate_handle = response.get_arg(OPENID_NS, 'invalidate_handle')
|
||||
if !invalidate_handle.nil?
|
||||
Util.log("Received 'invalidate_handle' from server #{server_url}")
|
||||
if @store.nil?
|
||||
Util.log('Unexpectedly got "invalidate_handle" without a store!')
|
||||
else
|
||||
@store.remove_association(server_url, invalidate_handle)
|
||||
end
|
||||
end
|
||||
|
||||
if is_valid != 'true'
|
||||
raise ProtocolError, ("Server #{server_url} responds that the "\
|
||||
"'check_authentication' call is not valid")
|
||||
end
|
||||
end
|
||||
|
||||
def check_nonce
|
||||
case openid_namespace
|
||||
when OPENID1_NS
|
||||
nonce =
|
||||
@message.get_arg(BARE_NS, Consumer.openid1_return_to_nonce_name)
|
||||
|
||||
# We generated the nonce, so it uses the empty string as the
|
||||
# server URL
|
||||
server_url = ''
|
||||
when OPENID2_NS
|
||||
nonce = @message.get_arg(OPENID2_NS, 'response_nonce')
|
||||
server_url = self.server_url
|
||||
else
|
||||
raise StandardError, 'Not reached'
|
||||
end
|
||||
|
||||
if nonce.nil?
|
||||
raise ProtocolError, 'Nonce missing from response'
|
||||
end
|
||||
|
||||
begin
|
||||
time, extra = Nonce.split_nonce(nonce)
|
||||
rescue ArgumentError => why
|
||||
raise ProtocolError, "Malformed nonce: #{nonce.inspect}"
|
||||
end
|
||||
|
||||
if !@store.nil? && !@store.use_nonce(server_url, time, extra)
|
||||
raise ProtocolError, ("Nonce already used or out of range: "\
|
||||
"#{nonce.inspect}")
|
||||
end
|
||||
end
|
||||
|
||||
def verify_discovery_results
|
||||
begin
|
||||
case openid_namespace
|
||||
when OPENID1_NS
|
||||
verify_discovery_results_openid1
|
||||
when OPENID2_NS
|
||||
verify_discovery_results_openid2
|
||||
else
|
||||
raise StandardError, "Not reached: #{openid_namespace}"
|
||||
end
|
||||
rescue Message::KeyNotFound => why
|
||||
raise ProtocolError, "Missing required field: #{why.message}"
|
||||
end
|
||||
end
|
||||
|
||||
def verify_discovery_results_openid2
|
||||
to_match = OpenIDServiceEndpoint.new
|
||||
to_match.type_uris = [OPENID_2_0_TYPE]
|
||||
to_match.claimed_id = fetch('claimed_id', nil)
|
||||
to_match.local_id = fetch('identity', nil)
|
||||
to_match.server_url = fetch('op_endpoint')
|
||||
|
||||
if to_match.claimed_id.nil? && !to_match.local_id.nil?
|
||||
raise ProtocolError, ('openid.identity is present without '\
|
||||
'openid.claimed_id')
|
||||
elsif !to_match.claimed_id.nil? && to_match.local_id.nil?
|
||||
raise ProtocolError, ('openid.claimed_id is present without '\
|
||||
'openid.identity')
|
||||
|
||||
# This is a response without identifiers, so there's really no
|
||||
# checking that we can do, so return an endpoint that's for
|
||||
# the specified `openid.op_endpoint'
|
||||
elsif to_match.claimed_id.nil?
|
||||
@endpoint =
|
||||
OpenIDServiceEndpoint.from_op_endpoint_url(to_match.server_url)
|
||||
return
|
||||
end
|
||||
|
||||
if @endpoint.nil?
|
||||
Util.log('No pre-discovered information supplied')
|
||||
discover_and_verify(to_match.claimed_id, [to_match])
|
||||
else
|
||||
begin
|
||||
verify_discovery_single(@endpoint, to_match)
|
||||
rescue ProtocolError => why
|
||||
Util.log("Error attempting to use stored discovery "\
|
||||
"information: #{why.message}")
|
||||
Util.log("Attempting discovery to verify endpoint")
|
||||
discover_and_verify(to_match.claimed_id, [to_match])
|
||||
end
|
||||
end
|
||||
|
||||
if @endpoint.claimed_id != to_match.claimed_id
|
||||
@endpoint = @endpoint.dup
|
||||
@endpoint.claimed_id = to_match.claimed_id
|
||||
end
|
||||
end
|
||||
|
||||
def verify_discovery_results_openid1
|
||||
claimed_id =
|
||||
@message.get_arg(BARE_NS, Consumer.openid1_return_to_claimed_id_name)
|
||||
|
||||
if claimed_id.nil?
|
||||
if @endpoint.nil?
|
||||
raise ProtocolError, ("When using OpenID 1, the claimed ID must "\
|
||||
"be supplied, either by passing it through "\
|
||||
"as a return_to parameter or by using a "\
|
||||
"session, and supplied to the IdResHandler "\
|
||||
"when it is constructed.")
|
||||
else
|
||||
claimed_id = @endpoint.claimed_id
|
||||
end
|
||||
end
|
||||
|
||||
to_match = OpenIDServiceEndpoint.new
|
||||
to_match.type_uris = [OPENID_1_1_TYPE]
|
||||
to_match.local_id = fetch('identity')
|
||||
# Restore delegate information from the initiation phase
|
||||
to_match.claimed_id = claimed_id
|
||||
|
||||
to_match_1_0 = to_match.dup
|
||||
to_match_1_0.type_uris = [OPENID_1_0_TYPE]
|
||||
|
||||
if !@endpoint.nil?
|
||||
begin
|
||||
begin
|
||||
verify_discovery_single(@endpoint, to_match)
|
||||
rescue TypeURIMismatch
|
||||
verify_discovery_single(@endpoint, to_match_1_0)
|
||||
end
|
||||
rescue ProtocolError => why
|
||||
Util.log('Error attempting to use stored discovery information: ' +
|
||||
why.message)
|
||||
Util.log('Attempting discovery to verify endpoint')
|
||||
else
|
||||
return @endpoint
|
||||
end
|
||||
end
|
||||
|
||||
# Either no endpoint was supplied or OpenID 1.x verification
|
||||
# of the information that's in the message failed on that
|
||||
# endpoint.
|
||||
discover_and_verify(to_match.claimed_id, [to_match, to_match_1_0])
|
||||
end
|
||||
|
||||
# Given an endpoint object created from the information in an
|
||||
# OpenID response, perform discovery and verify the discovery
|
||||
# results, returning the matching endpoint that is the result of
|
||||
# doing that discovery.
|
||||
def discover_and_verify(claimed_id, to_match_endpoints)
|
||||
Util.log("Performing discovery on #{claimed_id}")
|
||||
_, services = OpenID.discover(claimed_id)
|
||||
if services.length == 0
|
||||
# XXX: this might want to be something other than
|
||||
# ProtocolError. In Python, it's DiscoveryFailure
|
||||
raise ProtocolError, ("No OpenID information found at "\
|
||||
"#{claimed_id}")
|
||||
end
|
||||
verify_discovered_services(claimed_id, services, to_match_endpoints)
|
||||
end
|
||||
|
||||
|
||||
def verify_discovered_services(claimed_id, services, to_match_endpoints)
|
||||
# Search the services resulting from discovery to find one
|
||||
# that matches the information from the assertion
|
||||
failure_messages = []
|
||||
for endpoint in services
|
||||
for to_match_endpoint in to_match_endpoints
|
||||
begin
|
||||
verify_discovery_single(endpoint, to_match_endpoint)
|
||||
rescue ProtocolError => why
|
||||
failure_messages << why.message
|
||||
else
|
||||
# It matches, so discover verification has
|
||||
# succeeded. Return this endpoint.
|
||||
@endpoint = endpoint
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Util.log("Discovery verification failure for #{claimed_id}")
|
||||
failure_messages.each do |failure_message|
|
||||
Util.log(" * Endpoint mismatch: " + failure_message)
|
||||
end
|
||||
|
||||
# XXX: is DiscoveryFailure in Python OpenID
|
||||
raise ProtocolError, ("No matching endpoint found after "\
|
||||
"discovering #{claimed_id}")
|
||||
end
|
||||
|
||||
def verify_discovery_single(endpoint, to_match)
|
||||
# Every type URI that's in the to_match endpoint has to be
|
||||
# present in the discovered endpoint.
|
||||
for type_uri in to_match.type_uris
|
||||
if !endpoint.uses_extension(type_uri)
|
||||
raise TypeURIMismatch.new(type_uri, endpoint)
|
||||
end
|
||||
end
|
||||
|
||||
# Fragments do not influence discovery, so we can't compare a
|
||||
# claimed identifier with a fragment to discovered information.
|
||||
defragged_claimed_id =
|
||||
case Yadis::XRI.identifier_scheme(endpoint.claimed_id)
|
||||
when :xri
|
||||
endpoint.claimed_id
|
||||
when :uri
|
||||
begin
|
||||
parsed = URI.parse(endpoint.claimed_id)
|
||||
rescue URI::InvalidURIError
|
||||
endpoint.claimed_id
|
||||
else
|
||||
parsed.fragment = nil
|
||||
parsed.to_s
|
||||
end
|
||||
else
|
||||
raise StandardError, 'Not reached'
|
||||
end
|
||||
|
||||
if defragged_claimed_id != endpoint.claimed_id
|
||||
raise ProtocolError, ("Claimed ID does not match (different "\
|
||||
"subjects!), Expected "\
|
||||
"#{defragged_claimed_id}, got "\
|
||||
"#{endpoint.claimed_id}")
|
||||
end
|
||||
|
||||
if to_match.get_local_id != endpoint.get_local_id
|
||||
raise ProtocolError, ("local_id mismatch. Expected "\
|
||||
"#{to_match.get_local_id}, got "\
|
||||
"#{endpoint.get_local_id}")
|
||||
end
|
||||
|
||||
# If the server URL is nil, this must be an OpenID 1
|
||||
# response, because op_endpoint is a required parameter in
|
||||
# OpenID 2. In that case, we don't actually care what the
|
||||
# discovered server_url is, because signature checking or
|
||||
# check_auth should take care of that check for us.
|
||||
if to_match.server_url.nil?
|
||||
if to_match.preferred_namespace != OPENID1_NS
|
||||
raise StandardError,
|
||||
"The code calling this must ensure that OpenID 2 "\
|
||||
"responses have a non-none `openid.op_endpoint' and "\
|
||||
"that it is set as the `server_url' attribute of the "\
|
||||
"`to_match' endpoint."
|
||||
end
|
||||
elsif to_match.server_url != endpoint.server_url
|
||||
raise ProtocolError, ("OP Endpoint mismatch. Expected"\
|
||||
"#{to_match.server_url}, got "\
|
||||
"#{endpoint.server_url}")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
148
vendor/gems/ruby-openid-2.1.2/lib/openid/consumer/responses.rb
vendored
Normal file
148
vendor/gems/ruby-openid-2.1.2/lib/openid/consumer/responses.rb
vendored
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
module OpenID
|
||||
class Consumer
|
||||
# Code returned when either the of the
|
||||
# OpenID::OpenIDConsumer.begin_auth or OpenID::OpenIDConsumer.complete_auth
|
||||
# methods return successfully.
|
||||
SUCCESS = :success
|
||||
|
||||
# Code OpenID::OpenIDConsumer.complete_auth
|
||||
# returns when the value it received indicated an invalid login.
|
||||
FAILURE = :failure
|
||||
|
||||
# Code returned by OpenIDConsumer.complete_auth when the user
|
||||
# cancels the operation from the server.
|
||||
CANCEL = :cancel
|
||||
|
||||
# Code returned by OpenID::OpenIDConsumer.complete_auth when the
|
||||
# OpenIDConsumer instance is in immediate mode and ther server sends back a
|
||||
# URL for the user to login with.
|
||||
SETUP_NEEDED = :setup_needed
|
||||
|
||||
|
||||
module Response
|
||||
attr_reader :endpoint
|
||||
|
||||
def status
|
||||
self.class::STATUS
|
||||
end
|
||||
|
||||
# The identity URL that has been authenticated; the Claimed Identifier.
|
||||
# See also display_identifier.
|
||||
def identity_url
|
||||
@endpoint ? @endpoint.claimed_id : nil
|
||||
end
|
||||
|
||||
# The display identifier is related to the Claimed Identifier, but the
|
||||
# two are not always identical. The display identifier is something the
|
||||
# user should recognize as what they entered, whereas the response's
|
||||
# claimed identifier (in the identity_url attribute) may have extra
|
||||
# information for better persistence.
|
||||
#
|
||||
# URLs will be stripped of their fragments for display. XRIs will
|
||||
# display the human-readable identifier (i-name) instead of the
|
||||
# persistent identifier (i-number).
|
||||
#
|
||||
# Use the display identifier in your user interface. Use identity_url
|
||||
# for querying your database or authorization server, or other
|
||||
# identifier equality comparisons.
|
||||
def display_identifier
|
||||
@endpoint ? @endpoint.display_identifier : nil
|
||||
end
|
||||
end
|
||||
|
||||
# A successful acknowledgement from the OpenID server that the
|
||||
# supplied URL is, indeed controlled by the requesting agent.
|
||||
class SuccessResponse
|
||||
include Response
|
||||
|
||||
STATUS = SUCCESS
|
||||
|
||||
attr_reader :message, :signed_fields
|
||||
|
||||
def initialize(endpoint, message, signed_fields)
|
||||
# Don't use :endpoint=, because endpoint should never be nil
|
||||
# for a successfull transaction.
|
||||
@endpoint = endpoint
|
||||
@identity_url = endpoint.claimed_id
|
||||
@message = message
|
||||
@signed_fields = signed_fields
|
||||
end
|
||||
|
||||
# Was this authentication response an OpenID 1 authentication
|
||||
# response?
|
||||
def is_openid1
|
||||
@message.is_openid1
|
||||
end
|
||||
|
||||
# Return whether a particular key is signed, regardless of its
|
||||
# namespace alias
|
||||
def signed?(ns_uri, ns_key)
|
||||
@signed_fields.member?(@message.get_key(ns_uri, ns_key))
|
||||
end
|
||||
|
||||
# Return the specified signed field if available, otherwise
|
||||
# return default
|
||||
def get_signed(ns_uri, ns_key, default=nil)
|
||||
if singed?(ns_uri, ns_key)
|
||||
return @message.get_arg(ns_uri, ns_key, default)
|
||||
else
|
||||
return default
|
||||
end
|
||||
end
|
||||
|
||||
# Get signed arguments from the response message. Return a dict
|
||||
# of all arguments in the specified namespace. If any of the
|
||||
# arguments are not signed, return nil.
|
||||
def get_signed_ns(ns_uri)
|
||||
msg_args = @message.get_args(ns_uri)
|
||||
msg_args.each_key do |key|
|
||||
if !signed?(ns_uri, key)
|
||||
return nil
|
||||
end
|
||||
end
|
||||
return msg_args
|
||||
end
|
||||
|
||||
# Return response arguments in the specified namespace.
|
||||
# If require_signed is true and the arguments are not signed,
|
||||
# return nil.
|
||||
def extension_response(namespace_uri, require_signed)
|
||||
if require_signed
|
||||
get_signed_ns(namespace_uri)
|
||||
else
|
||||
@message.get_args(namespace_uri)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class FailureResponse
|
||||
include Response
|
||||
STATUS = FAILURE
|
||||
|
||||
attr_reader :message, :contact, :reference
|
||||
def initialize(endpoint, message, contact=nil, reference=nil)
|
||||
@endpoint = endpoint
|
||||
@message = message
|
||||
@contact = contact
|
||||
@reference = reference
|
||||
end
|
||||
end
|
||||
|
||||
class CancelResponse
|
||||
include Response
|
||||
STATUS = CANCEL
|
||||
def initialize(endpoint)
|
||||
@endpoint = endpoint
|
||||
end
|
||||
end
|
||||
|
||||
class SetupNeededResponse
|
||||
include Response
|
||||
STATUS = SETUP_NEEDED
|
||||
def initialize(endpoint, setup_url)
|
||||
@endpoint = endpoint
|
||||
@setup_url = setup_url
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
97
vendor/gems/ruby-openid-2.1.2/lib/openid/cryptutil.rb
vendored
Normal file
97
vendor/gems/ruby-openid-2.1.2/lib/openid/cryptutil.rb
vendored
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
require "openid/util"
|
||||
require "digest/sha1"
|
||||
require "digest/sha2"
|
||||
begin
|
||||
require "digest/hmac"
|
||||
rescue LoadError
|
||||
require "hmac/sha1"
|
||||
require "hmac/sha2"
|
||||
end
|
||||
|
||||
module OpenID
|
||||
# This module contains everything needed to perform low-level
|
||||
# cryptograph and data manipulation tasks.
|
||||
module CryptUtil
|
||||
|
||||
# Generate a random number, doing a little extra work to make it
|
||||
# more likely that it's suitable for cryptography. If your system
|
||||
# doesn't have /dev/urandom then this number is not
|
||||
# cryptographically safe. See
|
||||
# <http://www.cosine.org/2007/08/07/security-ruby-kernel-rand/>
|
||||
# for more information. max is the largest possible value of such
|
||||
# a random number, where the result will be less than max.
|
||||
def CryptUtil.rand(max)
|
||||
Kernel.srand()
|
||||
return Kernel.rand(max)
|
||||
end
|
||||
|
||||
def CryptUtil.sha1(text)
|
||||
return Digest::SHA1.digest(text)
|
||||
end
|
||||
|
||||
def CryptUtil.hmac_sha1(key, text)
|
||||
if Digest.const_defined? :HMAC
|
||||
Digest::HMAC.new(key,Digest::SHA1).update(text).digest
|
||||
else
|
||||
return HMAC::SHA1.digest(key, text)
|
||||
end
|
||||
end
|
||||
|
||||
def CryptUtil.sha256(text)
|
||||
return Digest::SHA256.digest(text)
|
||||
end
|
||||
|
||||
def CryptUtil.hmac_sha256(key, text)
|
||||
if Digest.const_defined? :HMAC
|
||||
Digest::HMAC.new(key,Digest::SHA256).update(text).digest
|
||||
else
|
||||
return HMAC::SHA256.digest(key, text)
|
||||
end
|
||||
end
|
||||
|
||||
# Generate a random string of the given length, composed of the
|
||||
# specified characters. If chars is nil, generate a string
|
||||
# composed of characters in the range 0..255.
|
||||
def CryptUtil.random_string(length, chars=nil)
|
||||
s = ""
|
||||
|
||||
unless chars.nil?
|
||||
length.times { s << chars[rand(chars.length)] }
|
||||
else
|
||||
length.times { s << rand(256).chr }
|
||||
end
|
||||
return s
|
||||
end
|
||||
|
||||
# Convert a number to its binary representation; return a string
|
||||
# of bytes.
|
||||
def CryptUtil.num_to_binary(n)
|
||||
bits = n.to_s(2)
|
||||
prepend = (8 - bits.length % 8)
|
||||
bits = ('0' * prepend) + bits
|
||||
return [bits].pack('B*')
|
||||
end
|
||||
|
||||
# Convert a string of bytes into a number.
|
||||
def CryptUtil.binary_to_num(s)
|
||||
# taken from openid-ruby 0.0.1
|
||||
s = "\000" * (4 - (s.length % 4)) + s
|
||||
num = 0
|
||||
s.unpack('N*').each do |x|
|
||||
num <<= 32
|
||||
num |= x
|
||||
end
|
||||
return num
|
||||
end
|
||||
|
||||
# Encode a number as a base64-encoded byte string.
|
||||
def CryptUtil.num_to_base64(l)
|
||||
return OpenID::Util.to_base64(num_to_binary(l))
|
||||
end
|
||||
|
||||
# Decode a base64 byte string to a number.
|
||||
def CryptUtil.base64_to_num(s)
|
||||
return binary_to_num(OpenID::Util.from_base64(s))
|
||||
end
|
||||
end
|
||||
end
|
||||
89
vendor/gems/ruby-openid-2.1.2/lib/openid/dh.rb
vendored
Normal file
89
vendor/gems/ruby-openid-2.1.2/lib/openid/dh.rb
vendored
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
require "openid/util"
|
||||
require "openid/cryptutil"
|
||||
|
||||
module OpenID
|
||||
|
||||
# Encapsulates a Diffie-Hellman key exchange. This class is used
|
||||
# internally by both the consumer and server objects.
|
||||
#
|
||||
# Read more about Diffie-Hellman on wikipedia:
|
||||
# http://en.wikipedia.org/wiki/Diffie-Hellman
|
||||
|
||||
class DiffieHellman
|
||||
|
||||
# From the OpenID specification
|
||||
@@default_mod = 155172898181473697471232257763715539915724801966915404479707795314057629378541917580651227423698188993727816152646631438561595825688188889951272158842675419950341258706556549803580104870537681476726513255747040765857479291291572334510643245094715007229621094194349783925984760375594985848253359305585439638443
|
||||
@@default_gen = 2
|
||||
|
||||
attr_reader :modulus, :generator, :public
|
||||
|
||||
# A new DiffieHellman object, using the modulus and generator from
|
||||
# the OpenID specification
|
||||
def DiffieHellman.from_defaults
|
||||
DiffieHellman.new(@@default_mod, @@default_gen)
|
||||
end
|
||||
|
||||
def initialize(modulus=nil, generator=nil, priv=nil)
|
||||
@modulus = modulus.nil? ? @@default_mod : modulus
|
||||
@generator = generator.nil? ? @@default_gen : generator
|
||||
set_private(priv.nil? ? OpenID::CryptUtil.rand(@modulus-2) + 1 : priv)
|
||||
end
|
||||
|
||||
def get_shared_secret(composite)
|
||||
DiffieHellman.powermod(composite, @private, @modulus)
|
||||
end
|
||||
|
||||
def xor_secret(algorithm, composite, secret)
|
||||
dh_shared = get_shared_secret(composite)
|
||||
packed_dh_shared = OpenID::CryptUtil.num_to_binary(dh_shared)
|
||||
hashed_dh_shared = algorithm.call(packed_dh_shared)
|
||||
return DiffieHellman.strxor(secret, hashed_dh_shared)
|
||||
end
|
||||
|
||||
def using_default_values?
|
||||
@generator == @@default_gen && @modulus == @@default_mod
|
||||
end
|
||||
|
||||
private
|
||||
def set_private(priv)
|
||||
@private = priv
|
||||
@public = DiffieHellman.powermod(@generator, @private, @modulus)
|
||||
end
|
||||
|
||||
def DiffieHellman.strxor(s, t)
|
||||
if s.length != t.length
|
||||
raise ArgumentError, "strxor: lengths don't match. " +
|
||||
"Inputs were #{s.inspect} and #{t.inspect}"
|
||||
end
|
||||
|
||||
if String.method_defined? :bytes
|
||||
s.bytes.zip(t.bytes).map{|sb,tb| sb^tb}.pack('C*')
|
||||
else
|
||||
indices = 0...(s.length)
|
||||
chrs = indices.collect {|i| (s[i]^t[i]).chr}
|
||||
chrs.join("")
|
||||
end
|
||||
end
|
||||
|
||||
# This code is taken from this post:
|
||||
# <http://blade.nagaokaut.ac.jp/cgi-bin/scat.\rb/ruby/ruby-talk/19098>
|
||||
# by Eric Lee Green.
|
||||
def DiffieHellman.powermod(x, n, q)
|
||||
counter=0
|
||||
n_p=n
|
||||
y_p=1
|
||||
z_p=x
|
||||
while n_p != 0
|
||||
if n_p[0]==1
|
||||
y_p=(y_p*z_p) % q
|
||||
end
|
||||
n_p = n_p >> 1
|
||||
z_p = (z_p * z_p) % q
|
||||
counter += 1
|
||||
end
|
||||
return y_p
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
39
vendor/gems/ruby-openid-2.1.2/lib/openid/extension.rb
vendored
Normal file
39
vendor/gems/ruby-openid-2.1.2/lib/openid/extension.rb
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
require 'openid/message'
|
||||
|
||||
module OpenID
|
||||
# An interface for OpenID extensions.
|
||||
class Extension < Object
|
||||
|
||||
def initialize
|
||||
@ns_uri = nil
|
||||
@ns_alias = nil
|
||||
end
|
||||
|
||||
# Get the string arguments that should be added to an OpenID
|
||||
# message for this extension.
|
||||
def get_extension_args
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
# Add the arguments from this extension to the provided
|
||||
# message, or create a new message containing only those
|
||||
# arguments. Returns the message with added extension args.
|
||||
def to_message(message = nil)
|
||||
if message.nil?
|
||||
# warnings.warn('Passing None to Extension.toMessage is deprecated. '
|
||||
# 'Creating a message assuming you want OpenID 2.',
|
||||
# DeprecationWarning, stacklevel=2)
|
||||
Message.new(OPENID2_NS)
|
||||
end
|
||||
message = Message.new if message.nil?
|
||||
|
||||
implicit = message.is_openid1()
|
||||
|
||||
message.namespaces.add_alias(@ns_uri, @ns_alias, implicit)
|
||||
# XXX python ignores keyerror if m.ns.getAlias(uri) == alias
|
||||
|
||||
message.update_args(@ns_uri, get_extension_args)
|
||||
return message
|
||||
end
|
||||
end
|
||||
end
|
||||
516
vendor/gems/ruby-openid-2.1.2/lib/openid/extensions/ax.rb
vendored
Normal file
516
vendor/gems/ruby-openid-2.1.2/lib/openid/extensions/ax.rb
vendored
Normal file
|
|
@ -0,0 +1,516 @@
|
|||
# Implements the OpenID attribute exchange specification, version 1.0
|
||||
|
||||
require 'openid/extension'
|
||||
require 'openid/trustroot'
|
||||
require 'openid/message'
|
||||
|
||||
module OpenID
|
||||
module AX
|
||||
|
||||
UNLIMITED_VALUES = "unlimited"
|
||||
MINIMUM_SUPPORTED_ALIAS_LENGTH = 32
|
||||
|
||||
# check alias for invalid characters, raise AXError if found
|
||||
def self.check_alias(name)
|
||||
if name.match(/(,|\.)/)
|
||||
raise Error, ("Alias #{name.inspect} must not contain a "\
|
||||
"comma or period.")
|
||||
end
|
||||
end
|
||||
|
||||
# Raised when data does not comply with AX 1.0 specification
|
||||
class Error < ArgumentError
|
||||
end
|
||||
|
||||
# Abstract class containing common code for attribute exchange messages
|
||||
class AXMessage < Extension
|
||||
attr_accessor :ns_alias, :mode, :ns_uri
|
||||
|
||||
NS_URI = 'http://openid.net/srv/ax/1.0'
|
||||
def initialize
|
||||
@ns_alias = 'ax'
|
||||
@ns_uri = NS_URI
|
||||
@mode = nil
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Raise an exception if the mode in the attribute exchange
|
||||
# arguments does not match what is expected for this class.
|
||||
def check_mode(ax_args)
|
||||
actual_mode = ax_args['mode']
|
||||
if actual_mode != @mode
|
||||
raise Error, "Expected mode #{mode.inspect}, got #{actual_mode.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
def new_args
|
||||
{'mode' => @mode}
|
||||
end
|
||||
end
|
||||
|
||||
# Represents a single attribute in an attribute exchange
|
||||
# request. This should be added to an Request object in order to
|
||||
# request the attribute.
|
||||
#
|
||||
# @ivar required: Whether the attribute will be marked as required
|
||||
# when presented to the subject of the attribute exchange
|
||||
# request.
|
||||
# @type required: bool
|
||||
#
|
||||
# @ivar count: How many values of this type to request from the
|
||||
# subject. Defaults to one.
|
||||
# @type count: int
|
||||
#
|
||||
# @ivar type_uri: The identifier that determines what the attribute
|
||||
# represents and how it is serialized. For example, one type URI
|
||||
# representing dates could represent a Unix timestamp in base 10
|
||||
# and another could represent a human-readable string.
|
||||
# @type type_uri: str
|
||||
#
|
||||
# @ivar ns_alias: The name that should be given to this alias in the
|
||||
# request. If it is not supplied, a generic name will be
|
||||
# assigned. For example, if you want to call a Unix timestamp
|
||||
# value 'tstamp', set its alias to that value. If two attributes
|
||||
# in the same message request to use the same alias, the request
|
||||
# will fail to be generated.
|
||||
# @type alias: str or NoneType
|
||||
class AttrInfo < Object
|
||||
attr_reader :type_uri, :count, :ns_alias
|
||||
attr_accessor :required
|
||||
def initialize(type_uri, ns_alias=nil, required=false, count=1)
|
||||
@type_uri = type_uri
|
||||
@count = count
|
||||
@required = required
|
||||
@ns_alias = ns_alias
|
||||
end
|
||||
|
||||
def wants_unlimited_values?
|
||||
@count == UNLIMITED_VALUES
|
||||
end
|
||||
end
|
||||
|
||||
# Given a namespace mapping and a string containing a
|
||||
# comma-separated list of namespace aliases, return a list of type
|
||||
# URIs that correspond to those aliases.
|
||||
# namespace_map: OpenID::NamespaceMap
|
||||
def self.to_type_uris(namespace_map, alias_list_s)
|
||||
return [] if alias_list_s.nil?
|
||||
alias_list_s.split(',').inject([]) {|uris, name|
|
||||
type_uri = namespace_map.get_namespace_uri(name)
|
||||
raise IndexError, "No type defined for attribute name #{name.inspect}" if type_uri.nil?
|
||||
uris << type_uri
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
# An attribute exchange 'fetch_request' message. This message is
|
||||
# sent by a relying party when it wishes to obtain attributes about
|
||||
# the subject of an OpenID authentication request.
|
||||
class FetchRequest < AXMessage
|
||||
attr_reader :requested_attributes
|
||||
attr_accessor :update_url
|
||||
|
||||
def initialize(update_url = nil)
|
||||
super()
|
||||
@mode = 'fetch_request'
|
||||
@requested_attributes = {}
|
||||
@update_url = update_url
|
||||
end
|
||||
|
||||
# Add an attribute to this attribute exchange request.
|
||||
# attribute: AttrInfo, the attribute being requested
|
||||
# Raises IndexError if the requested attribute is already present
|
||||
# in this request.
|
||||
def add(attribute)
|
||||
if @requested_attributes[attribute.type_uri]
|
||||
raise IndexError, "The attribute #{attribute.type_uri} has already been requested"
|
||||
end
|
||||
@requested_attributes[attribute.type_uri] = attribute
|
||||
end
|
||||
|
||||
# Get the serialized form of this attribute fetch request.
|
||||
# returns a hash of the arguments
|
||||
def get_extension_args
|
||||
aliases = NamespaceMap.new
|
||||
required = []
|
||||
if_available = []
|
||||
ax_args = new_args
|
||||
@requested_attributes.each{|type_uri, attribute|
|
||||
if attribute.ns_alias
|
||||
name = aliases.add_alias(type_uri, attribute.ns_alias)
|
||||
else
|
||||
name = aliases.add(type_uri)
|
||||
end
|
||||
if attribute.required
|
||||
required << name
|
||||
else
|
||||
if_available << name
|
||||
end
|
||||
if attribute.count != 1
|
||||
ax_args["count.#{name}"] = attribute.count.to_s
|
||||
end
|
||||
ax_args["type.#{name}"] = type_uri
|
||||
}
|
||||
|
||||
unless required.empty?
|
||||
ax_args['required'] = required.join(',')
|
||||
end
|
||||
unless if_available.empty?
|
||||
ax_args['if_available'] = if_available.join(',')
|
||||
end
|
||||
return ax_args
|
||||
end
|
||||
|
||||
# Get the type URIs for all attributes that have been marked
|
||||
# as required.
|
||||
def get_required_attrs
|
||||
@requested_attributes.inject([]) {|required, (type_uri, attribute)|
|
||||
if attribute.required
|
||||
required << type_uri
|
||||
else
|
||||
required
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
# Extract a FetchRequest from an OpenID message
|
||||
# message: OpenID::Message
|
||||
# return a FetchRequest or nil if AX arguments are not present
|
||||
def self.from_openid_request(oidreq)
|
||||
message = oidreq.message
|
||||
ax_args = message.get_args(NS_URI)
|
||||
return nil if ax_args == {}
|
||||
req = new
|
||||
req.parse_extension_args(ax_args)
|
||||
|
||||
if req.update_url
|
||||
realm = message.get_arg(OPENID_NS, 'realm',
|
||||
message.get_arg(OPENID_NS, 'return_to'))
|
||||
if realm.nil? or realm.empty?
|
||||
raise Error, "Cannot validate update_url #{req.update_url.inspect} against absent realm"
|
||||
end
|
||||
tr = TrustRoot::TrustRoot.parse(realm)
|
||||
unless tr.validate_url(req.update_url)
|
||||
raise Error, "Update URL #{req.update_url.inspect} failed validation against realm #{realm.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
return req
|
||||
end
|
||||
|
||||
def parse_extension_args(ax_args)
|
||||
check_mode(ax_args)
|
||||
|
||||
aliases = NamespaceMap.new
|
||||
|
||||
ax_args.each{|k,v|
|
||||
if k.index('type.') == 0
|
||||
name = k[5..-1]
|
||||
type_uri = v
|
||||
aliases.add_alias(type_uri, name)
|
||||
|
||||
count_key = 'count.'+name
|
||||
count_s = ax_args[count_key]
|
||||
count = 1
|
||||
if count_s
|
||||
if count_s == UNLIMITED_VALUES
|
||||
count = count_s
|
||||
else
|
||||
count = count_s.to_i
|
||||
if count <= 0
|
||||
raise Error, "Invalid value for count #{count_key.inspect}: #{count_s.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
add(AttrInfo.new(type_uri, name, false, count))
|
||||
end
|
||||
}
|
||||
|
||||
required = AX.to_type_uris(aliases, ax_args['required'])
|
||||
required.each{|type_uri|
|
||||
@requested_attributes[type_uri].required = true
|
||||
}
|
||||
if_available = AX.to_type_uris(aliases, ax_args['if_available'])
|
||||
all_type_uris = required + if_available
|
||||
|
||||
aliases.namespace_uris.each{|type_uri|
|
||||
unless all_type_uris.member? type_uri
|
||||
raise Error, "Type URI #{type_uri.inspect} was in the request but not present in 'required' or 'if_available'"
|
||||
end
|
||||
}
|
||||
@update_url = ax_args['update_url']
|
||||
end
|
||||
|
||||
# return the list of AttrInfo objects contained in the FetchRequest
|
||||
def attributes
|
||||
@requested_attributes.values
|
||||
end
|
||||
|
||||
# return the list of requested attribute type URIs
|
||||
def requested_types
|
||||
@requested_attributes.keys
|
||||
end
|
||||
|
||||
def member?(type_uri)
|
||||
! @requested_attributes[type_uri].nil?
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Abstract class that implements a message that has attribute
|
||||
# keys and values. It contains the common code between
|
||||
# fetch_response and store_request.
|
||||
class KeyValueMessage < AXMessage
|
||||
attr_reader :data
|
||||
def initialize
|
||||
super()
|
||||
@mode = nil
|
||||
@data = {}
|
||||
@data.default = []
|
||||
end
|
||||
|
||||
# Add a single value for the given attribute type to the
|
||||
# message. If there are already values specified for this type,
|
||||
# this value will be sent in addition to the values already
|
||||
# specified.
|
||||
def add_value(type_uri, value)
|
||||
@data[type_uri] = @data[type_uri] << value
|
||||
end
|
||||
|
||||
# Set the values for the given attribute type. This replaces
|
||||
# any values that have already been set for this attribute.
|
||||
def set_values(type_uri, values)
|
||||
@data[type_uri] = values
|
||||
end
|
||||
|
||||
# Get the extension arguments for the key/value pairs
|
||||
# contained in this message.
|
||||
def _get_extension_kv_args(aliases = nil)
|
||||
aliases = NamespaceMap.new if aliases.nil?
|
||||
|
||||
ax_args = new_args
|
||||
|
||||
@data.each{|type_uri, values|
|
||||
name = aliases.add(type_uri)
|
||||
ax_args['type.'+name] = type_uri
|
||||
ax_args['count.'+name] = values.size.to_s
|
||||
|
||||
values.each_with_index{|value, i|
|
||||
key = "value.#{name}.#{i+1}"
|
||||
ax_args[key] = value
|
||||
}
|
||||
}
|
||||
return ax_args
|
||||
end
|
||||
|
||||
# Parse attribute exchange key/value arguments into this object.
|
||||
|
||||
def parse_extension_args(ax_args)
|
||||
check_mode(ax_args)
|
||||
aliases = NamespaceMap.new
|
||||
|
||||
ax_args.each{|k, v|
|
||||
if k.index('type.') == 0
|
||||
type_uri = v
|
||||
name = k[5..-1]
|
||||
|
||||
AX.check_alias(name)
|
||||
aliases.add_alias(type_uri,name)
|
||||
end
|
||||
}
|
||||
|
||||
aliases.each{|type_uri, name|
|
||||
count_s = ax_args['count.'+name]
|
||||
count = count_s.to_i
|
||||
if count_s.nil?
|
||||
value = ax_args['value.'+name]
|
||||
if value.nil?
|
||||
raise IndexError, "Missing #{'value.'+name} in FetchResponse"
|
||||
elsif value.empty?
|
||||
values = []
|
||||
else
|
||||
values = [value]
|
||||
end
|
||||
elsif count_s.to_i == 0
|
||||
values = []
|
||||
else
|
||||
values = (1..count).inject([]){|l,i|
|
||||
key = "value.#{name}.#{i}"
|
||||
v = ax_args[key]
|
||||
raise IndexError, "Missing #{key} in FetchResponse" if v.nil?
|
||||
l << v
|
||||
}
|
||||
end
|
||||
@data[type_uri] = values
|
||||
}
|
||||
end
|
||||
|
||||
# Get a single value for an attribute. If no value was sent
|
||||
# for this attribute, use the supplied default. If there is more
|
||||
# than one value for this attribute, this method will fail.
|
||||
def get_single(type_uri, default = nil)
|
||||
values = @data[type_uri]
|
||||
return default if values.empty?
|
||||
if values.size != 1
|
||||
raise Error, "More than one value present for #{type_uri.inspect}"
|
||||
else
|
||||
return values[0]
|
||||
end
|
||||
end
|
||||
|
||||
# retrieve the list of values for this attribute
|
||||
def get(type_uri)
|
||||
@data[type_uri]
|
||||
end
|
||||
|
||||
# retrieve the list of values for this attribute
|
||||
def [](type_uri)
|
||||
@data[type_uri]
|
||||
end
|
||||
|
||||
# get the number of responses for this attribute
|
||||
def count(type_uri)
|
||||
@data[type_uri].size
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# A fetch_response attribute exchange message
|
||||
class FetchResponse < KeyValueMessage
|
||||
attr_reader :update_url
|
||||
|
||||
def initialize(update_url = nil)
|
||||
super()
|
||||
@mode = 'fetch_response'
|
||||
@update_url = update_url
|
||||
end
|
||||
|
||||
# Serialize this object into arguments in the attribute
|
||||
# exchange namespace
|
||||
# Takes an optional FetchRequest. If specified, the response will be
|
||||
# validated against this request, and empty responses for requested
|
||||
# fields with no data will be sent.
|
||||
def get_extension_args(request = nil)
|
||||
aliases = NamespaceMap.new
|
||||
zero_value_types = []
|
||||
|
||||
if request
|
||||
# Validate the data in the context of the request (the
|
||||
# same attributes should be present in each, and the
|
||||
# counts in the response must be no more than the counts
|
||||
# in the request)
|
||||
@data.keys.each{|type_uri|
|
||||
unless request.member? type_uri
|
||||
raise IndexError, "Response attribute not present in request: #{type_uri.inspect}"
|
||||
end
|
||||
}
|
||||
|
||||
request.attributes.each{|attr_info|
|
||||
# Copy the aliases from the request so that reading
|
||||
# the response in light of the request is easier
|
||||
if attr_info.ns_alias.nil?
|
||||
aliases.add(attr_info.type_uri)
|
||||
else
|
||||
aliases.add_alias(attr_info.type_uri, attr_info.ns_alias)
|
||||
end
|
||||
values = @data[attr_info.type_uri]
|
||||
if values.empty? # @data defaults to []
|
||||
zero_value_types << attr_info
|
||||
end
|
||||
if attr_info.count != UNLIMITED_VALUES and attr_info.count < values.size
|
||||
raise Error, "More than the number of requested values were specified for #{attr_info.type_uri.inspect}"
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
kv_args = _get_extension_kv_args(aliases)
|
||||
|
||||
# Add the KV args into the response with the args that are
|
||||
# unique to the fetch_response
|
||||
ax_args = new_args
|
||||
|
||||
zero_value_types.each{|attr_info|
|
||||
name = aliases.get_alias(attr_info.type_uri)
|
||||
kv_args['type.' + name] = attr_info.type_uri
|
||||
kv_args['count.' + name] = '0'
|
||||
}
|
||||
update_url = (request and request.update_url or @update_url)
|
||||
ax_args['update_url'] = update_url unless update_url.nil?
|
||||
ax_args.update(kv_args)
|
||||
return ax_args
|
||||
end
|
||||
|
||||
def parse_extension_args(ax_args)
|
||||
super
|
||||
@update_url = ax_args['update_url']
|
||||
end
|
||||
|
||||
# Construct a FetchResponse object from an OpenID library
|
||||
# SuccessResponse object.
|
||||
def self.from_success_response(success_response, signed=true)
|
||||
obj = self.new
|
||||
if signed
|
||||
ax_args = success_response.get_signed_ns(obj.ns_uri)
|
||||
else
|
||||
ax_args = success_response.message.get_args(obj.ns_uri)
|
||||
end
|
||||
|
||||
begin
|
||||
obj.parse_extension_args(ax_args)
|
||||
return obj
|
||||
rescue Error => e
|
||||
return nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# A store request attribute exchange message representation
|
||||
class StoreRequest < KeyValueMessage
|
||||
def initialize
|
||||
super
|
||||
@mode = 'store_request'
|
||||
end
|
||||
|
||||
def get_extension_args(aliases=nil)
|
||||
ax_args = new_args
|
||||
kv_args = _get_extension_kv_args(aliases)
|
||||
ax_args.update(kv_args)
|
||||
return ax_args
|
||||
end
|
||||
end
|
||||
|
||||
# An indication that the store request was processed along with
|
||||
# this OpenID transaction.
|
||||
class StoreResponse < AXMessage
|
||||
SUCCESS_MODE = 'store_response_success'
|
||||
FAILURE_MODE = 'store_response_failure'
|
||||
attr_reader :error_message
|
||||
|
||||
def initialize(succeeded = true, error_message = nil)
|
||||
super()
|
||||
if succeeded and error_message
|
||||
raise Error, "Error message included in a success response"
|
||||
end
|
||||
if succeeded
|
||||
@mode = SUCCESS_MODE
|
||||
else
|
||||
@mode = FAILURE_MODE
|
||||
end
|
||||
@error_message = error_message
|
||||
end
|
||||
|
||||
def succeeded?
|
||||
@mode == SUCCESS_MODE
|
||||
end
|
||||
|
||||
def get_extension_args
|
||||
ax_args = new_args
|
||||
if !succeeded? and error_message
|
||||
ax_args['error'] = @error_message
|
||||
end
|
||||
return ax_args
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
179
vendor/gems/ruby-openid-2.1.2/lib/openid/extensions/pape.rb
vendored
Normal file
179
vendor/gems/ruby-openid-2.1.2/lib/openid/extensions/pape.rb
vendored
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
# 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
|
||||
277
vendor/gems/ruby-openid-2.1.2/lib/openid/extensions/sreg.rb
vendored
Normal file
277
vendor/gems/ruby-openid-2.1.2/lib/openid/extensions/sreg.rb
vendored
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
require 'openid/extension'
|
||||
require 'openid/util'
|
||||
require 'openid/message'
|
||||
|
||||
module OpenID
|
||||
module SReg
|
||||
DATA_FIELDS = {
|
||||
'fullname'=>'Full Name',
|
||||
'nickname'=>'Nickname',
|
||||
'dob'=>'Date of Birth',
|
||||
'email'=>'E-mail Address',
|
||||
'gender'=>'Gender',
|
||||
'postcode'=>'Postal Code',
|
||||
'country'=>'Country',
|
||||
'language'=>'Language',
|
||||
'timezone'=>'Time Zone',
|
||||
}
|
||||
|
||||
NS_URI_1_0 = 'http://openid.net/sreg/1.0'
|
||||
NS_URI_1_1 = 'http://openid.net/extensions/sreg/1.1'
|
||||
NS_URI = NS_URI_1_1
|
||||
|
||||
begin
|
||||
Message.register_namespace_alias(NS_URI_1_1, 'sreg')
|
||||
rescue NamespaceAliasRegistrationError => e
|
||||
Util.log(e)
|
||||
end
|
||||
|
||||
# raise ArgumentError if fieldname is not in the defined sreg fields
|
||||
def OpenID.check_sreg_field_name(fieldname)
|
||||
unless DATA_FIELDS.member? fieldname
|
||||
raise ArgumentError, "#{fieldname} is not a defined simple registration field"
|
||||
end
|
||||
end
|
||||
|
||||
# Does the given endpoint advertise support for simple registration?
|
||||
def OpenID.supports_sreg?(endpoint)
|
||||
endpoint.uses_extension(NS_URI_1_1) || endpoint.uses_extension(NS_URI_1_0)
|
||||
end
|
||||
|
||||
# Extract the simple registration namespace URI from the given
|
||||
# OpenID message. Handles OpenID 1 and 2, as well as both sreg
|
||||
# namespace URIs found in the wild, as well as missing namespace
|
||||
# definitions (for OpenID 1)
|
||||
def OpenID.get_sreg_ns(message)
|
||||
[NS_URI_1_1, NS_URI_1_0].each{|ns|
|
||||
if message.namespaces.get_alias(ns)
|
||||
return ns
|
||||
end
|
||||
}
|
||||
# try to add an alias, since we didn't find one
|
||||
ns = NS_URI_1_1
|
||||
begin
|
||||
message.namespaces.add_alias(ns, 'sreg')
|
||||
rescue IndexError
|
||||
raise NamespaceError
|
||||
end
|
||||
return ns
|
||||
end
|
||||
|
||||
# The simple registration namespace was not found and could not
|
||||
# be created using the expected name (there's another extension
|
||||
# using the name 'sreg')
|
||||
#
|
||||
# This is not <em>illegal</em>, for OpenID 2, although it probably
|
||||
# indicates a problem, since it's not expected that other extensions
|
||||
# will re-use the alias that is in use for OpenID 1.
|
||||
#
|
||||
# If this is an OpenID 1 request, then there is no recourse. This
|
||||
# should not happen unless some code has modified the namespaces for
|
||||
# the message that is being processed.
|
||||
class NamespaceError < ArgumentError
|
||||
end
|
||||
|
||||
# An object to hold the state of a simple registration request.
|
||||
class Request < Extension
|
||||
attr_reader :optional, :required, :ns_uri
|
||||
attr_accessor :policy_url
|
||||
def initialize(required = nil, optional = nil, policy_url = nil, ns_uri = NS_URI)
|
||||
super()
|
||||
|
||||
@policy_url = policy_url
|
||||
@ns_uri = ns_uri
|
||||
@ns_alias = 'sreg'
|
||||
@required = []
|
||||
@optional = []
|
||||
|
||||
if required
|
||||
request_fields(required, true, true)
|
||||
end
|
||||
if optional
|
||||
request_fields(optional, false, true)
|
||||
end
|
||||
end
|
||||
|
||||
# Create a simple registration request that contains the
|
||||
# fields that were requested in the OpenID request with the
|
||||
# given arguments
|
||||
# Takes an OpenID::CheckIDRequest, returns an OpenID::Sreg::Request
|
||||
# return nil if the extension was not requested.
|
||||
def self.from_openid_request(request)
|
||||
# Since we're going to mess with namespace URI mapping, don't
|
||||
# mutate the object that was passed in.
|
||||
message = request.message.copy
|
||||
ns_uri = OpenID::get_sreg_ns(message)
|
||||
args = message.get_args(ns_uri)
|
||||
return nil if args == {}
|
||||
req = new(nil,nil,nil,ns_uri)
|
||||
req.parse_extension_args(args)
|
||||
return req
|
||||
end
|
||||
|
||||
# Parse the unqualified simple registration request
|
||||
# parameters and add them to this object.
|
||||
#
|
||||
# This method is essentially the inverse of
|
||||
# getExtensionArgs. This method restores the serialized simple
|
||||
# registration request fields.
|
||||
#
|
||||
# If you are extracting arguments from a standard OpenID
|
||||
# checkid_* request, you probably want to use fromOpenIDRequest,
|
||||
# which will extract the sreg namespace and arguments from the
|
||||
# OpenID request. This method is intended for cases where the
|
||||
# OpenID server needs more control over how the arguments are
|
||||
# parsed than that method provides.
|
||||
def parse_extension_args(args, strict = false)
|
||||
required_items = args['required']
|
||||
unless required_items.nil? or required_items.empty?
|
||||
required_items.split(',').each{|field_name|
|
||||
begin
|
||||
request_field(field_name, true, strict)
|
||||
rescue ArgumentError
|
||||
raise if strict
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
optional_items = args['optional']
|
||||
unless optional_items.nil? or optional_items.empty?
|
||||
optional_items.split(',').each{|field_name|
|
||||
begin
|
||||
request_field(field_name, false, strict)
|
||||
rescue ArgumentError
|
||||
raise if strict
|
||||
end
|
||||
}
|
||||
end
|
||||
@policy_url = args['policy_url']
|
||||
end
|
||||
|
||||
# A list of all of the simple registration fields that were
|
||||
# requested, whether they were required or optional.
|
||||
def all_requested_fields
|
||||
@required + @optional
|
||||
end
|
||||
|
||||
# Have any simple registration fields been requested?
|
||||
def were_fields_requested?
|
||||
!all_requested_fields.empty?
|
||||
end
|
||||
|
||||
# Request the specified field from the OpenID user
|
||||
# field_name: the unqualified simple registration field name
|
||||
# required: whether the given field should be presented
|
||||
# to the user as being a required to successfully complete
|
||||
# the request
|
||||
# strict: whether to raise an exception when a field is
|
||||
# added to a request more than once
|
||||
# Raises ArgumentError if the field_name is not a simple registration
|
||||
# field, or if strict is set and a field is added more than once
|
||||
def request_field(field_name, required=false, strict=false)
|
||||
OpenID::check_sreg_field_name(field_name)
|
||||
|
||||
if strict
|
||||
if (@required + @optional).member? field_name
|
||||
raise ArgumentError, 'That field has already been requested'
|
||||
end
|
||||
else
|
||||
return if @required.member? field_name
|
||||
if @optional.member? field_name
|
||||
if required
|
||||
@optional.delete field_name
|
||||
else
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
if required
|
||||
@required << field_name
|
||||
else
|
||||
@optional << field_name
|
||||
end
|
||||
end
|
||||
|
||||
# Add the given list of fields to the request.
|
||||
def request_fields(field_names, required = false, strict = false)
|
||||
raise ArgumentError unless field_names.respond_to?(:each) and
|
||||
field_names[0].is_a?(String)
|
||||
field_names.each{|fn|request_field(fn, required, strict)}
|
||||
end
|
||||
|
||||
# Get a hash of unqualified simple registration arguments
|
||||
# representing this request.
|
||||
# This method is essentially the inverse of parse_extension_args.
|
||||
# This method serializes the simple registration request fields.
|
||||
def get_extension_args
|
||||
args = {}
|
||||
args['required'] = @required.join(',') unless @required.empty?
|
||||
args['optional'] = @optional.join(',') unless @optional.empty?
|
||||
args['policy_url'] = @policy_url unless @policy_url.nil?
|
||||
return args
|
||||
end
|
||||
|
||||
def member?(field_name)
|
||||
all_requested_fields.member?(field_name)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Represents the data returned in a simple registration response
|
||||
# inside of an OpenID id_res response. This object will be
|
||||
# created by the OpenID server, added to the id_res response
|
||||
# object, and then extracted from the id_res message by the Consumer.
|
||||
class Response < Extension
|
||||
attr_reader :ns_uri, :data
|
||||
|
||||
def initialize(data = {}, ns_uri=NS_URI)
|
||||
@ns_alias = 'sreg'
|
||||
@data = data
|
||||
@ns_uri = ns_uri
|
||||
end
|
||||
|
||||
# Take a Request and a hash of simple registration
|
||||
# values and create a Response object containing that data.
|
||||
def self.extract_response(request, data)
|
||||
arf = request.all_requested_fields
|
||||
resp_data = data.reject{|k,v| !arf.member?(k) || v.nil? }
|
||||
new(resp_data, request.ns_uri)
|
||||
end
|
||||
|
||||
# Create an Response object from an
|
||||
# OpenID::Consumer::SuccessResponse from consumer.complete
|
||||
# If you set the signed_only parameter to false, unsigned data from
|
||||
# the id_res message from the server will be processed.
|
||||
def self.from_success_response(success_response, signed_only = true)
|
||||
ns_uri = OpenID::get_sreg_ns(success_response.message)
|
||||
if signed_only
|
||||
args = success_response.get_signed_ns(ns_uri)
|
||||
return nil if args.nil? # No signed args, so fail
|
||||
else
|
||||
args = success_response.message.get_args(ns_uri)
|
||||
end
|
||||
args.reject!{|k,v| !DATA_FIELDS.member?(k) }
|
||||
new(args, ns_uri)
|
||||
end
|
||||
|
||||
# Get the fields to put in the simple registration namespace
|
||||
# when adding them to an id_res message.
|
||||
def get_extension_args
|
||||
return @data
|
||||
end
|
||||
|
||||
# Read-only hashlike interface.
|
||||
# Raises an exception if the field name is bad
|
||||
def [](field_name)
|
||||
OpenID::check_sreg_field_name(field_name)
|
||||
data[field_name]
|
||||
end
|
||||
|
||||
def empty?
|
||||
@data.empty?
|
||||
end
|
||||
# XXX is there more to a hashlike interface I should add?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
11
vendor/gems/ruby-openid-2.1.2/lib/openid/extras.rb
vendored
Normal file
11
vendor/gems/ruby-openid-2.1.2/lib/openid/extras.rb
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
class String
|
||||
def starts_with?(other)
|
||||
head = self[0, other.length]
|
||||
head == other
|
||||
end
|
||||
|
||||
def ends_with?(other)
|
||||
tail = self[-1 * other.length, other.length]
|
||||
tail == other
|
||||
end
|
||||
end
|
||||
239
vendor/gems/ruby-openid-2.1.2/lib/openid/fetchers.rb
vendored
Normal file
239
vendor/gems/ruby-openid-2.1.2/lib/openid/fetchers.rb
vendored
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
require 'net/http'
|
||||
require 'openid'
|
||||
require 'openid/util'
|
||||
|
||||
begin
|
||||
require 'net/https'
|
||||
rescue LoadError
|
||||
OpenID::Util.log('WARNING: no SSL support found. Will not be able ' +
|
||||
'to fetch HTTPS URLs!')
|
||||
require 'net/http'
|
||||
end
|
||||
|
||||
MAX_RESPONSE_KB = 1024
|
||||
|
||||
module Net
|
||||
class HTTP
|
||||
def post_connection_check(hostname)
|
||||
check_common_name = true
|
||||
cert = @socket.io.peer_cert
|
||||
cert.extensions.each { |ext|
|
||||
next if ext.oid != "subjectAltName"
|
||||
ext.value.split(/,\s+/).each{ |general_name|
|
||||
if /\ADNS:(.*)/ =~ general_name
|
||||
check_common_name = false
|
||||
reg = Regexp.escape($1).gsub(/\\\*/, "[^.]+")
|
||||
return true if /\A#{reg}\z/i =~ hostname
|
||||
elsif /\AIP Address:(.*)/ =~ general_name
|
||||
check_common_name = false
|
||||
return true if $1 == hostname
|
||||
end
|
||||
}
|
||||
}
|
||||
if check_common_name
|
||||
cert.subject.to_a.each{ |oid, value|
|
||||
if oid == "CN"
|
||||
reg = Regexp.escape(value).gsub(/\\\*/, "[^.]+")
|
||||
return true if /\A#{reg}\z/i =~ hostname
|
||||
end
|
||||
}
|
||||
end
|
||||
raise OpenSSL::SSL::SSLError, "hostname does not match"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module OpenID
|
||||
# Our HTTPResponse class extends Net::HTTPResponse with an additional
|
||||
# method, final_url.
|
||||
class HTTPResponse
|
||||
attr_accessor :final_url
|
||||
|
||||
attr_accessor :_response
|
||||
|
||||
def self._from_net_response(response, final_url, headers=nil)
|
||||
me = self.new
|
||||
me._response = response
|
||||
me.final_url = final_url
|
||||
return me
|
||||
end
|
||||
|
||||
def method_missing(method, *args)
|
||||
@_response.send(method, *args)
|
||||
end
|
||||
|
||||
def body=(s)
|
||||
@_response.instance_variable_set('@body', s)
|
||||
# XXX Hack to work around ruby's HTTP library behavior. @body
|
||||
# is only returned if it has been read from the response
|
||||
# object's socket, but since we're not using a socket in this
|
||||
# case, we need to set the @read flag to true to avoid a bug in
|
||||
# Net::HTTPResponse.stream_check when @socket is nil.
|
||||
@_response.instance_variable_set('@read', true)
|
||||
end
|
||||
end
|
||||
|
||||
class FetchingError < OpenIDError
|
||||
end
|
||||
|
||||
class HTTPRedirectLimitReached < FetchingError
|
||||
end
|
||||
|
||||
class SSLFetchingError < FetchingError
|
||||
end
|
||||
|
||||
@fetcher = nil
|
||||
|
||||
def self.fetch(url, body=nil, headers=nil,
|
||||
redirect_limit=StandardFetcher::REDIRECT_LIMIT)
|
||||
return fetcher.fetch(url, body, headers, redirect_limit)
|
||||
end
|
||||
|
||||
def self.fetcher
|
||||
if @fetcher.nil?
|
||||
@fetcher = StandardFetcher.new
|
||||
end
|
||||
|
||||
return @fetcher
|
||||
end
|
||||
|
||||
def self.fetcher=(fetcher)
|
||||
@fetcher = fetcher
|
||||
end
|
||||
|
||||
# Set the default fetcher to use the HTTP proxy defined in the environment
|
||||
# variable 'http_proxy'.
|
||||
def self.fetcher_use_env_http_proxy
|
||||
proxy_string = ENV['http_proxy']
|
||||
return unless proxy_string
|
||||
|
||||
proxy_uri = URI.parse(proxy_string)
|
||||
@fetcher = StandardFetcher.new(proxy_uri.host, proxy_uri.port,
|
||||
proxy_uri.user, proxy_uri.password)
|
||||
end
|
||||
|
||||
class StandardFetcher
|
||||
|
||||
USER_AGENT = "ruby-openid/#{OpenID::VERSION} (#{RUBY_PLATFORM})"
|
||||
|
||||
REDIRECT_LIMIT = 5
|
||||
TIMEOUT = 60
|
||||
|
||||
attr_accessor :ca_file
|
||||
attr_accessor :timeout
|
||||
|
||||
# I can fetch through a HTTP proxy; arguments are as for Net::HTTP::Proxy.
|
||||
def initialize(proxy_addr=nil, proxy_port=nil,
|
||||
proxy_user=nil, proxy_pass=nil)
|
||||
@ca_file = nil
|
||||
@proxy = Net::HTTP::Proxy(proxy_addr, proxy_port, proxy_user, proxy_pass)
|
||||
@timeout = TIMEOUT
|
||||
end
|
||||
|
||||
def supports_ssl?(conn)
|
||||
return conn.respond_to?(:use_ssl=)
|
||||
end
|
||||
|
||||
def make_http(uri)
|
||||
http = @proxy.new(uri.host, uri.port)
|
||||
http.read_timeout = @timeout
|
||||
http.open_timeout = @timeout
|
||||
return http
|
||||
end
|
||||
|
||||
def set_verified(conn, verify)
|
||||
if verify
|
||||
conn.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
||||
else
|
||||
conn.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
||||
end
|
||||
end
|
||||
|
||||
def make_connection(uri)
|
||||
conn = make_http(uri)
|
||||
|
||||
if !conn.is_a?(Net::HTTP)
|
||||
raise RuntimeError, sprintf("Expected Net::HTTP object from make_http; got %s",
|
||||
conn.class)
|
||||
end
|
||||
|
||||
if uri.scheme == 'https'
|
||||
if supports_ssl?(conn)
|
||||
|
||||
conn.use_ssl = true
|
||||
|
||||
if @ca_file
|
||||
set_verified(conn, true)
|
||||
conn.ca_file = @ca_file
|
||||
else
|
||||
Util.log("WARNING: making https request to #{uri} without verifying " +
|
||||
"server certificate; no CA path was specified.")
|
||||
set_verified(conn, false)
|
||||
end
|
||||
else
|
||||
raise RuntimeError, "SSL support not found; cannot fetch #{uri}"
|
||||
end
|
||||
end
|
||||
|
||||
return conn
|
||||
end
|
||||
|
||||
def fetch(url, body=nil, headers=nil, redirect_limit=REDIRECT_LIMIT)
|
||||
unparsed_url = url.dup
|
||||
url = URI::parse(url)
|
||||
if url.nil?
|
||||
raise FetchingError, "Invalid URL: #{unparsed_url}"
|
||||
end
|
||||
|
||||
headers ||= {}
|
||||
headers['User-agent'] ||= USER_AGENT
|
||||
headers['Range'] ||= "0-#{MAX_RESPONSE_KB*1024}"
|
||||
|
||||
begin
|
||||
conn = make_connection(url)
|
||||
response = nil
|
||||
|
||||
response = conn.start {
|
||||
# Check the certificate against the URL's hostname
|
||||
if supports_ssl?(conn) and conn.use_ssl?
|
||||
conn.post_connection_check(url.host)
|
||||
end
|
||||
|
||||
if body.nil?
|
||||
conn.request_get(url.request_uri, headers)
|
||||
else
|
||||
headers["Content-type"] ||= "application/x-www-form-urlencoded"
|
||||
conn.request_post(url.request_uri, body, headers)
|
||||
end
|
||||
}
|
||||
rescue RuntimeError => why
|
||||
raise why
|
||||
rescue OpenSSL::SSL::SSLError => why
|
||||
raise SSLFetchingError, "Error connecting to SSL URL #{url}: #{why}"
|
||||
rescue FetchingError => why
|
||||
raise why
|
||||
rescue Exception => why
|
||||
# Things we've caught here include a Timeout::Error, which descends
|
||||
# from SignalException.
|
||||
raise FetchingError, "Error fetching #{url}: #{why}"
|
||||
end
|
||||
|
||||
case response
|
||||
when Net::HTTPRedirection
|
||||
if redirect_limit <= 0
|
||||
raise HTTPRedirectLimitReached.new(
|
||||
"Too many redirects, not fetching #{response['location']}")
|
||||
end
|
||||
begin
|
||||
return fetch(response['location'], body, headers, redirect_limit - 1)
|
||||
rescue HTTPRedirectLimitReached => e
|
||||
raise e
|
||||
rescue FetchingError => why
|
||||
raise FetchingError, "Error encountered in redirect from #{url}: #{why}"
|
||||
end
|
||||
else
|
||||
return HTTPResponse._from_net_response(response, unparsed_url)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
136
vendor/gems/ruby-openid-2.1.2/lib/openid/kvform.rb
vendored
Normal file
136
vendor/gems/ruby-openid-2.1.2/lib/openid/kvform.rb
vendored
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
|
||||
module OpenID
|
||||
|
||||
class KVFormError < Exception
|
||||
end
|
||||
|
||||
module Util
|
||||
|
||||
def Util.seq_to_kv(seq, strict=false)
|
||||
# Represent a sequence of pairs of strings as newline-terminated
|
||||
# key:value pairs. The pairs are generated in the order given.
|
||||
#
|
||||
# @param seq: The pairs
|
||||
#
|
||||
# returns a string representation of the sequence
|
||||
err = lambda { |msg|
|
||||
msg = "seq_to_kv warning: #{msg}: #{seq.inspect}"
|
||||
if strict
|
||||
raise KVFormError, msg
|
||||
else
|
||||
Util.log(msg)
|
||||
end
|
||||
}
|
||||
|
||||
lines = []
|
||||
seq.each { |k, v|
|
||||
if !k.is_a?(String)
|
||||
err.call("Converting key to string: #{k.inspect}")
|
||||
k = k.to_s
|
||||
end
|
||||
|
||||
if !k.index("\n").nil?
|
||||
raise KVFormError, "Invalid input for seq_to_kv: key contains newline: #{k.inspect}"
|
||||
end
|
||||
|
||||
if !k.index(":").nil?
|
||||
raise KVFormError, "Invalid input for seq_to_kv: key contains colon: #{k.inspect}"
|
||||
end
|
||||
|
||||
if k.strip() != k
|
||||
err.call("Key has whitespace at beginning or end: #{k.inspect}")
|
||||
end
|
||||
|
||||
if !v.is_a?(String)
|
||||
err.call("Converting value to string: #{v.inspect}")
|
||||
v = v.to_s
|
||||
end
|
||||
|
||||
if !v.index("\n").nil?
|
||||
raise KVFormError, "Invalid input for seq_to_kv: value contains newline: #{v.inspect}"
|
||||
end
|
||||
|
||||
if v.strip() != v
|
||||
err.call("Value has whitespace at beginning or end: #{v.inspect}")
|
||||
end
|
||||
|
||||
lines << k + ":" + v + "\n"
|
||||
}
|
||||
|
||||
return lines.join("")
|
||||
end
|
||||
|
||||
def Util.kv_to_seq(data, strict=false)
|
||||
# After one parse, seq_to_kv and kv_to_seq are inverses, with no
|
||||
# warnings:
|
||||
#
|
||||
# seq = kv_to_seq(s)
|
||||
# seq_to_kv(kv_to_seq(seq)) == seq
|
||||
err = lambda { |msg|
|
||||
msg = "kv_to_seq warning: #{msg}: #{data.inspect}"
|
||||
if strict
|
||||
raise KVFormError, msg
|
||||
else
|
||||
Util.log(msg)
|
||||
end
|
||||
}
|
||||
|
||||
lines = data.split("\n")
|
||||
if data.length == 0
|
||||
return []
|
||||
end
|
||||
|
||||
if data[-1].chr != "\n"
|
||||
err.call("Does not end in a newline")
|
||||
# We don't expect the last element of lines to be an empty
|
||||
# string because split() doesn't behave that way.
|
||||
end
|
||||
|
||||
pairs = []
|
||||
line_num = 0
|
||||
lines.each { |line|
|
||||
line_num += 1
|
||||
|
||||
# Ignore blank lines
|
||||
if line.strip() == ""
|
||||
next
|
||||
end
|
||||
|
||||
pair = line.split(':', 2)
|
||||
if pair.length == 2
|
||||
k, v = pair
|
||||
k_s = k.strip()
|
||||
if k_s != k
|
||||
msg = "In line #{line_num}, ignoring leading or trailing whitespace in key #{k.inspect}"
|
||||
err.call(msg)
|
||||
end
|
||||
|
||||
if k_s.length == 0
|
||||
err.call("In line #{line_num}, got empty key")
|
||||
end
|
||||
|
||||
v_s = v.strip()
|
||||
if v_s != v
|
||||
msg = "In line #{line_num}, ignoring leading or trailing whitespace in value #{v.inspect}"
|
||||
err.call(msg)
|
||||
end
|
||||
|
||||
pairs << [k_s, v_s]
|
||||
else
|
||||
err.call("Line #{line_num} does not contain a colon")
|
||||
end
|
||||
}
|
||||
|
||||
return pairs
|
||||
end
|
||||
|
||||
def Util.dict_to_kv(d)
|
||||
return seq_to_kv(d.entries.sort)
|
||||
end
|
||||
|
||||
def Util.kv_to_dict(s)
|
||||
seq = kv_to_seq(s)
|
||||
return Hash[*seq.flatten]
|
||||
end
|
||||
end
|
||||
end
|
||||
58
vendor/gems/ruby-openid-2.1.2/lib/openid/kvpost.rb
vendored
Normal file
58
vendor/gems/ruby-openid-2.1.2/lib/openid/kvpost.rb
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
require "openid/message"
|
||||
require "openid/fetchers"
|
||||
|
||||
module OpenID
|
||||
# Exception that is raised when the server returns a 400 response
|
||||
# code to a direct request.
|
||||
class ServerError < OpenIDError
|
||||
attr_reader :error_text, :error_code, :message
|
||||
|
||||
def initialize(error_text, error_code, message)
|
||||
super(error_text)
|
||||
@error_text = error_text
|
||||
@error_code = error_code
|
||||
@message = message
|
||||
end
|
||||
|
||||
def self.from_message(msg)
|
||||
error_text = msg.get_arg(OPENID_NS, 'error',
|
||||
'<no error message supplied>')
|
||||
error_code = msg.get_arg(OPENID_NS, 'error_code')
|
||||
return self.new(error_text, error_code, msg)
|
||||
end
|
||||
end
|
||||
|
||||
class KVPostNetworkError < OpenIDError
|
||||
end
|
||||
class HTTPStatusError < OpenIDError
|
||||
end
|
||||
|
||||
class Message
|
||||
def self.from_http_response(response, server_url)
|
||||
msg = self.from_kvform(response.body)
|
||||
case response.code.to_i
|
||||
when 200
|
||||
return msg
|
||||
when 206
|
||||
return msg
|
||||
when 400
|
||||
raise ServerError.from_message(msg)
|
||||
else
|
||||
error_message = "bad status code from server #{server_url}: "\
|
||||
"#{response.code}"
|
||||
raise HTTPStatusError.new(error_message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Send the message to the server via HTTP POST and receive and parse
|
||||
# a response in KV Form
|
||||
def self.make_kv_post(request_message, server_url)
|
||||
begin
|
||||
http_response = self.fetch(server_url, request_message.to_url_encoded)
|
||||
rescue Exception
|
||||
raise KVPostNetworkError.new("Unable to contact OpenID server: #{$!.to_s}")
|
||||
end
|
||||
return Message.from_http_response(http_response, server_url)
|
||||
end
|
||||
end
|
||||
553
vendor/gems/ruby-openid-2.1.2/lib/openid/message.rb
vendored
Normal file
553
vendor/gems/ruby-openid-2.1.2/lib/openid/message.rb
vendored
Normal file
|
|
@ -0,0 +1,553 @@
|
|||
require 'openid/util'
|
||||
require 'openid/kvform'
|
||||
|
||||
module OpenID
|
||||
|
||||
IDENTIFIER_SELECT = 'http://specs.openid.net/auth/2.0/identifier_select'
|
||||
|
||||
# URI for Simple Registration extension, the only commonly deployed
|
||||
# OpenID 1.x extension, and so a special case.
|
||||
SREG_URI = 'http://openid.net/sreg/1.0'
|
||||
|
||||
# The OpenID 1.x namespace URIs
|
||||
OPENID1_NS = 'http://openid.net/signon/1.0'
|
||||
OPENID11_NS = 'http://openid.net/signon/1.1'
|
||||
OPENID1_NAMESPACES = [OPENID1_NS, OPENID11_NS]
|
||||
|
||||
# The OpenID 2.0 namespace URI
|
||||
OPENID2_NS = 'http://specs.openid.net/auth/2.0'
|
||||
|
||||
# The namespace consisting of pairs with keys that are prefixed with
|
||||
# "openid." but not in another namespace.
|
||||
NULL_NAMESPACE = :null_namespace
|
||||
|
||||
# The null namespace, when it is an allowed OpenID namespace
|
||||
OPENID_NS = :openid_namespace
|
||||
|
||||
# The top-level namespace, excluding all pairs with keys that start
|
||||
# with "openid."
|
||||
BARE_NS = :bare_namespace
|
||||
|
||||
# Limit, in bytes, of identity provider and return_to URLs,
|
||||
# including response payload. See OpenID 1.1 specification,
|
||||
# Appendix D.
|
||||
OPENID1_URL_LIMIT = 2047
|
||||
|
||||
# All OpenID protocol fields. Used to check namespace aliases.
|
||||
OPENID_PROTOCOL_FIELDS = [
|
||||
'ns', 'mode', 'error', 'return_to',
|
||||
'contact', 'reference', 'signed',
|
||||
'assoc_type', 'session_type',
|
||||
'dh_modulus', 'dh_gen',
|
||||
'dh_consumer_public', 'claimed_id',
|
||||
'identity', 'realm', 'invalidate_handle',
|
||||
'op_endpoint', 'response_nonce', 'sig',
|
||||
'assoc_handle', 'trust_root', 'openid',
|
||||
]
|
||||
|
||||
# Sentinel used for Message implementation to indicate that getArg
|
||||
# should raise an exception instead of returning a default.
|
||||
NO_DEFAULT = :no_default
|
||||
|
||||
# Raised if the generic OpenID namespace is accessed when there
|
||||
# is no OpenID namespace set for this message.
|
||||
class UndefinedOpenIDNamespace < Exception; end
|
||||
|
||||
# Raised when an alias or namespace URI has already been registered.
|
||||
class NamespaceAliasRegistrationError < Exception; end
|
||||
|
||||
# Raised if openid.ns is not a recognized value.
|
||||
# See Message class variable @@allowed_openid_namespaces
|
||||
class InvalidOpenIDNamespace < Exception; end
|
||||
|
||||
class Message
|
||||
attr_reader :namespaces
|
||||
|
||||
# Raised when key lookup fails
|
||||
class KeyNotFound < IndexError ; end
|
||||
|
||||
# Namespace / alias registration map. See
|
||||
# register_namespace_alias.
|
||||
@@registered_aliases = {}
|
||||
|
||||
# Registers a (namespace URI, alias) mapping in a global namespace
|
||||
# alias map. Raises NamespaceAliasRegistrationError if either the
|
||||
# namespace URI or alias has already been registered with a
|
||||
# different value. This function is required if you want to use a
|
||||
# namespace with an OpenID 1 message.
|
||||
def Message.register_namespace_alias(namespace_uri, alias_)
|
||||
if @@registered_aliases[alias_] == namespace_uri
|
||||
return
|
||||
end
|
||||
|
||||
if @@registered_aliases.values.include?(namespace_uri)
|
||||
raise NamespaceAliasRegistrationError,
|
||||
'Namespace uri #{namespace_uri} already registered'
|
||||
end
|
||||
|
||||
if @@registered_aliases.member?(alias_)
|
||||
raise NamespaceAliasRegistrationError,
|
||||
'Alias #{alias_} already registered'
|
||||
end
|
||||
|
||||
@@registered_aliases[alias_] = namespace_uri
|
||||
end
|
||||
|
||||
@@allowed_openid_namespaces = [OPENID1_NS, OPENID2_NS, OPENID11_NS]
|
||||
|
||||
# Raises InvalidNamespaceError if you try to instantiate a Message
|
||||
# with a namespace not in the above allowed list
|
||||
def initialize(openid_namespace=nil)
|
||||
@args = {}
|
||||
@namespaces = NamespaceMap.new
|
||||
if openid_namespace
|
||||
implicit = OPENID1_NAMESPACES.member? openid_namespace
|
||||
self.set_openid_namespace(openid_namespace, implicit)
|
||||
else
|
||||
@openid_ns_uri = nil
|
||||
end
|
||||
end
|
||||
|
||||
# Construct a Message containing a set of POST arguments.
|
||||
# Raises InvalidNamespaceError if you try to instantiate a Message
|
||||
# with a namespace not in the above allowed list
|
||||
def Message.from_post_args(args)
|
||||
m = Message.new
|
||||
openid_args = {}
|
||||
args.each do |key,value|
|
||||
if value.is_a?(Array)
|
||||
raise ArgumentError, "Query dict must have one value for each key, " +
|
||||
"not lists of values. Query is #{args.inspect}"
|
||||
end
|
||||
|
||||
prefix, rest = key.split('.', 2)
|
||||
|
||||
if prefix != 'openid' or rest.nil?
|
||||
m.set_arg(BARE_NS, key, value)
|
||||
else
|
||||
openid_args[rest] = value
|
||||
end
|
||||
end
|
||||
|
||||
m._from_openid_args(openid_args)
|
||||
return m
|
||||
end
|
||||
|
||||
# Construct a Message from a parsed KVForm message.
|
||||
# Raises InvalidNamespaceError if you try to instantiate a Message
|
||||
# with a namespace not in the above allowed list
|
||||
def Message.from_openid_args(openid_args)
|
||||
m = Message.new
|
||||
m._from_openid_args(openid_args)
|
||||
return m
|
||||
end
|
||||
|
||||
# Raises InvalidNamespaceError if you try to instantiate a Message
|
||||
# with a namespace not in the above allowed list
|
||||
def _from_openid_args(openid_args)
|
||||
ns_args = []
|
||||
|
||||
# resolve namespaces
|
||||
openid_args.each { |rest, value|
|
||||
ns_alias, ns_key = rest.split('.', 2)
|
||||
if ns_key.nil?
|
||||
ns_alias = NULL_NAMESPACE
|
||||
ns_key = rest
|
||||
end
|
||||
|
||||
if ns_alias == 'ns'
|
||||
@namespaces.add_alias(value, ns_key)
|
||||
elsif ns_alias == NULL_NAMESPACE and ns_key == 'ns'
|
||||
set_openid_namespace(value, false)
|
||||
else
|
||||
ns_args << [ns_alias, ns_key, value]
|
||||
end
|
||||
}
|
||||
|
||||
# implicitly set an OpenID 1 namespace
|
||||
unless get_openid_namespace
|
||||
set_openid_namespace(OPENID1_NS, true)
|
||||
end
|
||||
|
||||
# put the pairs into the appropriate namespaces
|
||||
ns_args.each { |ns_alias, ns_key, value|
|
||||
ns_uri = @namespaces.get_namespace_uri(ns_alias)
|
||||
unless ns_uri
|
||||
ns_uri = _get_default_namespace(ns_alias)
|
||||
unless ns_uri
|
||||
ns_uri = get_openid_namespace
|
||||
ns_key = "#{ns_alias}.#{ns_key}"
|
||||
else
|
||||
@namespaces.add_alias(ns_uri, ns_alias, true)
|
||||
end
|
||||
end
|
||||
self.set_arg(ns_uri, ns_key, value)
|
||||
}
|
||||
end
|
||||
|
||||
def _get_default_namespace(mystery_alias)
|
||||
# only try to map an alias to a default if it's an
|
||||
# OpenID 1.x namespace
|
||||
if is_openid1
|
||||
@@registered_aliases[mystery_alias]
|
||||
end
|
||||
end
|
||||
|
||||
def set_openid_namespace(openid_ns_uri, implicit)
|
||||
if !@@allowed_openid_namespaces.include?(openid_ns_uri)
|
||||
raise InvalidOpenIDNamespace, "Invalid null namespace: #{openid_ns_uri}"
|
||||
end
|
||||
@namespaces.add_alias(openid_ns_uri, NULL_NAMESPACE, implicit)
|
||||
@openid_ns_uri = openid_ns_uri
|
||||
end
|
||||
|
||||
def get_openid_namespace
|
||||
return @openid_ns_uri
|
||||
end
|
||||
|
||||
def is_openid1
|
||||
return OPENID1_NAMESPACES.member?(@openid_ns_uri)
|
||||
end
|
||||
|
||||
def is_openid2
|
||||
return @openid_ns_uri == OPENID2_NS
|
||||
end
|
||||
|
||||
# Create a message from a KVForm string
|
||||
def Message.from_kvform(kvform_string)
|
||||
return Message.from_openid_args(Util.kv_to_dict(kvform_string))
|
||||
end
|
||||
|
||||
def copy
|
||||
return Marshal.load(Marshal.dump(self))
|
||||
end
|
||||
|
||||
# Return all arguments with "openid." in from of namespaced arguments.
|
||||
def to_post_args
|
||||
args = {}
|
||||
|
||||
# add namespace defs to the output
|
||||
@namespaces.each { |ns_uri, ns_alias|
|
||||
if @namespaces.implicit?(ns_uri)
|
||||
next
|
||||
end
|
||||
if ns_alias == NULL_NAMESPACE
|
||||
ns_key = 'openid.ns'
|
||||
else
|
||||
ns_key = 'openid.ns.' + ns_alias
|
||||
end
|
||||
args[ns_key] = ns_uri
|
||||
}
|
||||
|
||||
@args.each { |k, value|
|
||||
ns_uri, ns_key = k
|
||||
key = get_key(ns_uri, ns_key)
|
||||
args[key] = value
|
||||
}
|
||||
|
||||
return args
|
||||
end
|
||||
|
||||
# Return all namespaced arguments, failing if any non-namespaced arguments
|
||||
# exist.
|
||||
def to_args
|
||||
post_args = self.to_post_args
|
||||
kvargs = {}
|
||||
post_args.each { |k,v|
|
||||
if !k.starts_with?('openid.')
|
||||
raise ArgumentError, "This message can only be encoded as a POST, because it contains arguments that are not prefixed with 'openid.'"
|
||||
else
|
||||
kvargs[k[7..-1]] = v
|
||||
end
|
||||
}
|
||||
return kvargs
|
||||
end
|
||||
|
||||
# Generate HTML form markup that contains the values in this
|
||||
# message, to be HTTP POSTed as x-www-form-urlencoded UTF-8.
|
||||
def to_form_markup(action_url, form_tag_attrs=nil, submit_text='Continue')
|
||||
form_tag_attr_map = {}
|
||||
|
||||
if form_tag_attrs
|
||||
form_tag_attrs.each { |name, attr|
|
||||
form_tag_attr_map[name] = attr
|
||||
}
|
||||
end
|
||||
|
||||
form_tag_attr_map['action'] = action_url
|
||||
form_tag_attr_map['method'] = 'post'
|
||||
form_tag_attr_map['accept-charset'] = 'UTF-8'
|
||||
form_tag_attr_map['enctype'] = 'application/x-www-form-urlencoded'
|
||||
|
||||
markup = "<form "
|
||||
|
||||
form_tag_attr_map.each { |k, v|
|
||||
markup += " #{k}=\"#{v}\""
|
||||
}
|
||||
|
||||
markup += ">\n"
|
||||
|
||||
to_post_args.each { |k,v|
|
||||
markup += "<input type='hidden' name='#{k}' value='#{v}' />\n"
|
||||
}
|
||||
markup += "<input type='submit' value='#{submit_text}' />\n"
|
||||
markup += "\n</form>"
|
||||
return markup
|
||||
end
|
||||
|
||||
# Generate a GET URL with the paramters in this message attacked as
|
||||
# query parameters.
|
||||
def to_url(base_url)
|
||||
return Util.append_args(base_url, self.to_post_args)
|
||||
end
|
||||
|
||||
# Generate a KVForm string that contains the parameters in this message.
|
||||
# This will fail is the message contains arguments outside of the
|
||||
# "openid." prefix.
|
||||
def to_kvform
|
||||
return Util.dict_to_kv(to_args)
|
||||
end
|
||||
|
||||
# Generate an x-www-urlencoded string.
|
||||
def to_url_encoded
|
||||
args = to_post_args.map.sort
|
||||
return Util.urlencode(args)
|
||||
end
|
||||
|
||||
# Convert an input value into the internally used values of this obejct.
|
||||
def _fix_ns(namespace)
|
||||
if namespace == OPENID_NS
|
||||
unless @openid_ns_uri
|
||||
raise UndefinedOpenIDNamespace, 'OpenID namespace not set'
|
||||
else
|
||||
namespace = @openid_ns_uri
|
||||
end
|
||||
end
|
||||
|
||||
if namespace == BARE_NS
|
||||
return namespace
|
||||
end
|
||||
|
||||
if !namespace.is_a?(String)
|
||||
raise ArgumentError, ("Namespace must be BARE_NS, OPENID_NS or "\
|
||||
"a string. Got #{namespace.inspect}")
|
||||
end
|
||||
|
||||
if namespace.index(':').nil?
|
||||
msg = ("OpenID 2.0 namespace identifiers SHOULD be URIs. "\
|
||||
"Got #{namespace.inspect}")
|
||||
Util.log(msg)
|
||||
|
||||
if namespace == 'sreg'
|
||||
msg = "Using #{SREG_URI} instead of \"sreg\" as namespace"
|
||||
Util.log(msg)
|
||||
return SREG_URI
|
||||
end
|
||||
end
|
||||
|
||||
return namespace
|
||||
end
|
||||
|
||||
def has_key?(namespace, ns_key)
|
||||
namespace = _fix_ns(namespace)
|
||||
return @args.member?([namespace, ns_key])
|
||||
end
|
||||
|
||||
# Get the key for a particular namespaced argument
|
||||
def get_key(namespace, ns_key)
|
||||
namespace = _fix_ns(namespace)
|
||||
return ns_key if namespace == BARE_NS
|
||||
|
||||
ns_alias = @namespaces.get_alias(namespace)
|
||||
|
||||
# no alias is defined, so no key can exist
|
||||
return nil if ns_alias.nil?
|
||||
|
||||
if ns_alias == NULL_NAMESPACE
|
||||
tail = ns_key
|
||||
else
|
||||
tail = "#{ns_alias}.#{ns_key}"
|
||||
end
|
||||
|
||||
return 'openid.' + tail
|
||||
end
|
||||
|
||||
# Get a value for a namespaced key.
|
||||
def get_arg(namespace, key, default=nil)
|
||||
namespace = _fix_ns(namespace)
|
||||
@args.fetch([namespace, key]) {
|
||||
if default == NO_DEFAULT
|
||||
raise KeyNotFound, "<#{namespace}>#{key} not in this message"
|
||||
else
|
||||
default
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
# Get the arguments that are defined for this namespace URI.
|
||||
def get_args(namespace)
|
||||
namespace = _fix_ns(namespace)
|
||||
args = {}
|
||||
@args.each { |k,v|
|
||||
pair_ns, ns_key = k
|
||||
args[ns_key] = v if pair_ns == namespace
|
||||
}
|
||||
return args
|
||||
end
|
||||
|
||||
# Set multiple key/value pairs in one call.
|
||||
def update_args(namespace, updates)
|
||||
namespace = _fix_ns(namespace)
|
||||
updates.each {|k,v| set_arg(namespace, k, v)}
|
||||
end
|
||||
|
||||
# Set a single argument in this namespace
|
||||
def set_arg(namespace, key, value)
|
||||
namespace = _fix_ns(namespace)
|
||||
@args[[namespace, key].freeze] = value
|
||||
if namespace != BARE_NS
|
||||
@namespaces.add(namespace)
|
||||
end
|
||||
end
|
||||
|
||||
# Remove a single argument from this namespace.
|
||||
def del_arg(namespace, key)
|
||||
namespace = _fix_ns(namespace)
|
||||
_key = [namespace, key]
|
||||
@args.delete(_key)
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
other.is_a?(self.class) && @args == other.instance_eval { @args }
|
||||
end
|
||||
|
||||
def get_aliased_arg(aliased_key, default=nil)
|
||||
if aliased_key == 'ns'
|
||||
return get_openid_namespace()
|
||||
end
|
||||
|
||||
ns_alias, key = aliased_key.split('.', 2)
|
||||
if ns_alias == 'ns'
|
||||
uri = @namespaces.get_namespace_uri(key)
|
||||
if uri.nil? and default == NO_DEFAULT
|
||||
raise KeyNotFound, "Namespace #{key} not defined when looking "\
|
||||
"for #{aliased_key}"
|
||||
else
|
||||
return (uri.nil? ? default : uri)
|
||||
end
|
||||
end
|
||||
|
||||
if key.nil?
|
||||
key = aliased_key
|
||||
ns = nil
|
||||
else
|
||||
ns = @namespaces.get_namespace_uri(ns_alias)
|
||||
end
|
||||
|
||||
if ns.nil?
|
||||
key = aliased_key
|
||||
ns = get_openid_namespace
|
||||
end
|
||||
|
||||
return get_arg(ns, key, default)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Maintains a bidirectional map between namespace URIs and aliases.
|
||||
class NamespaceMap
|
||||
|
||||
def initialize
|
||||
@alias_to_namespace = {}
|
||||
@namespace_to_alias = {}
|
||||
@implicit_namespaces = []
|
||||
end
|
||||
|
||||
def get_alias(namespace_uri)
|
||||
@namespace_to_alias[namespace_uri]
|
||||
end
|
||||
|
||||
def get_namespace_uri(namespace_alias)
|
||||
@alias_to_namespace[namespace_alias]
|
||||
end
|
||||
|
||||
# Add an alias from this namespace URI to the alias.
|
||||
def add_alias(namespace_uri, desired_alias, implicit=false)
|
||||
# Check that desired_alias is not an openid protocol field as
|
||||
# per the spec.
|
||||
Util.assert(!OPENID_PROTOCOL_FIELDS.include?(desired_alias),
|
||||
"#{desired_alias} is not an allowed namespace alias")
|
||||
|
||||
# check that there is not a namespace already defined for the
|
||||
# desired alias
|
||||
current_namespace_uri = @alias_to_namespace.fetch(desired_alias, nil)
|
||||
if current_namespace_uri and current_namespace_uri != namespace_uri
|
||||
raise IndexError, "Cannot map #{namespace_uri} to alias #{desired_alias}. #{current_namespace_uri} is already mapped to alias #{desired_alias}"
|
||||
end
|
||||
|
||||
# Check that desired_alias does not contain a period as per the
|
||||
# spec.
|
||||
if desired_alias.is_a?(String)
|
||||
Util.assert(desired_alias.index('.').nil?,
|
||||
"#{desired_alias} must not contain a dot")
|
||||
end
|
||||
|
||||
# check that there is not already a (different) alias for this
|
||||
# namespace URI.
|
||||
_alias = @namespace_to_alias[namespace_uri]
|
||||
if _alias and _alias != desired_alias
|
||||
raise IndexError, "Cannot map #{namespace_uri} to alias #{desired_alias}. It is already mapped to alias #{_alias}"
|
||||
end
|
||||
|
||||
@alias_to_namespace[desired_alias] = namespace_uri
|
||||
@namespace_to_alias[namespace_uri] = desired_alias
|
||||
@implicit_namespaces << namespace_uri if implicit
|
||||
return desired_alias
|
||||
end
|
||||
|
||||
# Add this namespace URI to the mapping, without caring what alias
|
||||
# it ends up with.
|
||||
def add(namespace_uri)
|
||||
# see if this namepace is already mapped to an alias
|
||||
_alias = @namespace_to_alias[namespace_uri]
|
||||
return _alias if _alias
|
||||
|
||||
# Fall back to generating a numberical alias
|
||||
i = 0
|
||||
while true
|
||||
_alias = 'ext' + i.to_s
|
||||
begin
|
||||
add_alias(namespace_uri, _alias)
|
||||
rescue IndexError
|
||||
i += 1
|
||||
else
|
||||
return _alias
|
||||
end
|
||||
end
|
||||
|
||||
raise StandardError, 'Unreachable'
|
||||
end
|
||||
|
||||
def member?(namespace_uri)
|
||||
@namespace_to_alias.has_key?(namespace_uri)
|
||||
end
|
||||
|
||||
def each
|
||||
@namespace_to_alias.each {|k,v| yield k,v}
|
||||
end
|
||||
|
||||
def namespace_uris
|
||||
# Return an iterator over the namespace URIs
|
||||
return @namespace_to_alias.keys()
|
||||
end
|
||||
|
||||
def implicit?(namespace_uri)
|
||||
return @implicit_namespaces.member?(namespace_uri)
|
||||
end
|
||||
|
||||
def aliases
|
||||
# Return an iterator over the aliases
|
||||
return @alias_to_namespace.keys()
|
||||
end
|
||||
end
|
||||
end
|
||||
8
vendor/gems/ruby-openid-2.1.2/lib/openid/protocolerror.rb
vendored
Normal file
8
vendor/gems/ruby-openid-2.1.2/lib/openid/protocolerror.rb
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
require 'openid/util'
|
||||
|
||||
module OpenID
|
||||
|
||||
# An error in the OpenID protocol
|
||||
class ProtocolError < OpenIDError
|
||||
end
|
||||
end
|
||||
1544
vendor/gems/ruby-openid-2.1.2/lib/openid/server.rb
vendored
Normal file
1544
vendor/gems/ruby-openid-2.1.2/lib/openid/server.rb
vendored
Normal file
File diff suppressed because it is too large
Load diff
271
vendor/gems/ruby-openid-2.1.2/lib/openid/store/filesystem.rb
vendored
Normal file
271
vendor/gems/ruby-openid-2.1.2/lib/openid/store/filesystem.rb
vendored
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
require 'fileutils'
|
||||
require 'pathname'
|
||||
require 'tempfile'
|
||||
|
||||
require 'openid/util'
|
||||
require 'openid/store/interface'
|
||||
require 'openid/association'
|
||||
|
||||
module OpenID
|
||||
module Store
|
||||
class Filesystem < Interface
|
||||
@@FILENAME_ALLOWED = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-".split("")
|
||||
|
||||
# Create a Filesystem store instance, putting all data in +directory+.
|
||||
def initialize(directory)
|
||||
p_dir = Pathname.new(directory)
|
||||
@nonce_dir = p_dir.join('nonces')
|
||||
@association_dir = p_dir.join('associations')
|
||||
@temp_dir = p_dir.join('temp')
|
||||
|
||||
self.ensure_dir(@nonce_dir)
|
||||
self.ensure_dir(@association_dir)
|
||||
self.ensure_dir(@temp_dir)
|
||||
end
|
||||
|
||||
# Create a unique filename for a given server url and handle. The
|
||||
# filename that is returned will contain the domain name from the
|
||||
# server URL for ease of human inspection of the data dir.
|
||||
def get_association_filename(server_url, handle)
|
||||
unless server_url.index('://')
|
||||
raise ArgumentError, "Bad server URL: #{server_url}"
|
||||
end
|
||||
|
||||
proto, rest = server_url.split('://', 2)
|
||||
domain = filename_escape(rest.split('/',2)[0])
|
||||
url_hash = safe64(server_url)
|
||||
if handle
|
||||
handle_hash = safe64(handle)
|
||||
else
|
||||
handle_hash = ''
|
||||
end
|
||||
filename = [proto,domain,url_hash,handle_hash].join('-')
|
||||
@association_dir.join(filename)
|
||||
end
|
||||
|
||||
# Store an association in the assoc directory
|
||||
def store_association(server_url, association)
|
||||
assoc_s = association.serialize
|
||||
filename = get_association_filename(server_url, association.handle)
|
||||
f, tmp = mktemp
|
||||
|
||||
begin
|
||||
begin
|
||||
f.write(assoc_s)
|
||||
f.fsync
|
||||
ensure
|
||||
f.close
|
||||
end
|
||||
|
||||
begin
|
||||
File.rename(tmp, filename)
|
||||
rescue Errno::EEXIST
|
||||
|
||||
begin
|
||||
File.unlink(filename)
|
||||
rescue Errno::ENOENT
|
||||
# do nothing
|
||||
end
|
||||
|
||||
File.rename(tmp, filename)
|
||||
end
|
||||
|
||||
rescue
|
||||
self.remove_if_present(tmp)
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
# Retrieve an association
|
||||
def get_association(server_url, handle=nil)
|
||||
# the filename with empty handle is the prefix for the associations
|
||||
# for a given server url
|
||||
filename = get_association_filename(server_url, handle)
|
||||
if handle
|
||||
return _get_association(filename)
|
||||
end
|
||||
assoc_filenames = Dir.glob(filename.to_s + '*')
|
||||
|
||||
assocs = assoc_filenames.collect do |f|
|
||||
_get_association(f)
|
||||
end
|
||||
|
||||
assocs = assocs.find_all { |a| not a.nil? }
|
||||
assocs = assocs.sort_by { |a| a.issued }
|
||||
|
||||
return nil if assocs.empty?
|
||||
return assocs[-1]
|
||||
end
|
||||
|
||||
def _get_association(filename)
|
||||
begin
|
||||
assoc_file = File.open(filename, "r")
|
||||
rescue Errno::ENOENT
|
||||
return nil
|
||||
else
|
||||
begin
|
||||
assoc_s = assoc_file.read
|
||||
ensure
|
||||
assoc_file.close
|
||||
end
|
||||
|
||||
begin
|
||||
association = Association.deserialize(assoc_s)
|
||||
rescue
|
||||
self.remove_if_present(filename)
|
||||
return nil
|
||||
end
|
||||
|
||||
# clean up expired associations
|
||||
if association.expires_in == 0
|
||||
self.remove_if_present(filename)
|
||||
return nil
|
||||
else
|
||||
return association
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Remove an association if it exists, otherwise do nothing.
|
||||
def remove_association(server_url, handle)
|
||||
assoc = get_association(server_url, handle)
|
||||
|
||||
if assoc.nil?
|
||||
return false
|
||||
else
|
||||
filename = get_association_filename(server_url, handle)
|
||||
return self.remove_if_present(filename)
|
||||
end
|
||||
end
|
||||
|
||||
# Return whether the nonce is valid
|
||||
def use_nonce(server_url, timestamp, salt)
|
||||
return false if (timestamp - Time.now.to_i).abs > Nonce.skew
|
||||
|
||||
if server_url and !server_url.empty?
|
||||
proto, rest = server_url.split('://',2)
|
||||
else
|
||||
proto, rest = '',''
|
||||
end
|
||||
raise "Bad server URL" unless proto && rest
|
||||
|
||||
domain = filename_escape(rest.split('/',2)[0])
|
||||
url_hash = safe64(server_url)
|
||||
salt_hash = safe64(salt)
|
||||
|
||||
nonce_fn = '%08x-%s-%s-%s-%s'%[timestamp, proto, domain, url_hash, salt_hash]
|
||||
|
||||
filename = @nonce_dir.join(nonce_fn)
|
||||
|
||||
begin
|
||||
fd = File.new(filename, File::CREAT | File::EXCL | File::WRONLY, 0200)
|
||||
fd.close
|
||||
return true
|
||||
rescue Errno::EEXIST
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
# Remove expired entries from the database. This is potentially expensive,
|
||||
# so only run when it is acceptable to take time.
|
||||
def cleanup
|
||||
cleanup_associations
|
||||
cleanup_nonces
|
||||
end
|
||||
|
||||
def cleanup_associations
|
||||
association_filenames = Dir[@association_dir.join("*").to_s]
|
||||
count = 0
|
||||
association_filenames.each do |af|
|
||||
begin
|
||||
f = File.open(af, 'r')
|
||||
rescue Errno::ENOENT
|
||||
next
|
||||
else
|
||||
begin
|
||||
assoc_s = f.read
|
||||
ensure
|
||||
f.close
|
||||
end
|
||||
begin
|
||||
association = OpenID::Association.deserialize(assoc_s)
|
||||
rescue StandardError
|
||||
self.remove_if_present(af)
|
||||
next
|
||||
else
|
||||
if association.expires_in == 0
|
||||
self.remove_if_present(af)
|
||||
count += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
def cleanup_nonces
|
||||
nonces = Dir[@nonce_dir.join("*").to_s]
|
||||
now = Time.now.to_i
|
||||
|
||||
count = 0
|
||||
nonces.each do |filename|
|
||||
nonce = filename.split('/')[-1]
|
||||
timestamp = nonce.split('-', 2)[0].to_i(16)
|
||||
nonce_age = (timestamp - now).abs
|
||||
if nonce_age > Nonce.skew
|
||||
self.remove_if_present(filename)
|
||||
count += 1
|
||||
end
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Create a temporary file and return the File object and filename.
|
||||
def mktemp
|
||||
f = Tempfile.new('tmp', @temp_dir)
|
||||
[f, f.path]
|
||||
end
|
||||
|
||||
# create a safe filename from a url
|
||||
def filename_escape(s)
|
||||
s = '' if s.nil?
|
||||
filename_chunks = []
|
||||
s.split('').each do |c|
|
||||
if @@FILENAME_ALLOWED.index(c)
|
||||
filename_chunks << c
|
||||
else
|
||||
filename_chunks << sprintf("_%02X", c[0])
|
||||
end
|
||||
end
|
||||
filename_chunks.join("")
|
||||
end
|
||||
|
||||
def safe64(s)
|
||||
s = OpenID::CryptUtil.sha1(s)
|
||||
s = OpenID::Util.to_base64(s)
|
||||
s.gsub!('+', '_')
|
||||
s.gsub!('/', '.')
|
||||
s.gsub!('=', '')
|
||||
return s
|
||||
end
|
||||
|
||||
# remove file if present in filesystem
|
||||
def remove_if_present(filename)
|
||||
begin
|
||||
File.unlink(filename)
|
||||
rescue Errno::ENOENT
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
# ensure that a path exists
|
||||
def ensure_dir(dir_name)
|
||||
FileUtils::mkdir_p(dir_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
75
vendor/gems/ruby-openid-2.1.2/lib/openid/store/interface.rb
vendored
Normal file
75
vendor/gems/ruby-openid-2.1.2/lib/openid/store/interface.rb
vendored
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
require 'openid/util'
|
||||
|
||||
module OpenID
|
||||
|
||||
# Stores for Associations and nonces. Used by both the Consumer and
|
||||
# the Server. If you have a database abstraction layer or other
|
||||
# state storage in your application or framework already, you can
|
||||
# implement the store interface.
|
||||
module Store
|
||||
# Abstract Store
|
||||
# Changes in 2.0:
|
||||
# * removed store_nonce, get_auth_key, is_dumb
|
||||
# * changed use_nonce to support one-way nonces
|
||||
# * added cleanup_nonces, cleanup_associations, cleanup
|
||||
class Interface < Object
|
||||
|
||||
# Put a Association object into storage.
|
||||
# When implementing a store, don't assume that there are any limitations
|
||||
# on the character set of the server_url. In particular, expect to see
|
||||
# unescaped non-url-safe characters in the server_url field.
|
||||
def store_association(server_url, association)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
# Returns a Association object from storage that matches
|
||||
# the server_url. Returns nil if no such association is found or if
|
||||
# the one matching association is expired. (Is allowed to GC expired
|
||||
# associations when found.)
|
||||
def get_association(server_url, handle=nil)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
# If there is a matching association, remove it from the store and
|
||||
# return true, otherwise return false.
|
||||
def remove_association(server_url, handle)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
# Return true if the nonce has not been used before, and store it
|
||||
# for a while to make sure someone doesn't try to use the same value
|
||||
# again. Return false if the nonce has already been used or if the
|
||||
# timestamp is not current.
|
||||
# You can use OpenID::Store::Nonce::SKEW for your timestamp window.
|
||||
# server_url: URL of the server from which the nonce originated
|
||||
# timestamp: time the nonce was created in seconds since unix epoch
|
||||
# salt: A random string that makes two nonces issued by a server in
|
||||
# the same second unique
|
||||
def use_nonce(server_url, timestamp, salt)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
# Remove expired nonces from the store
|
||||
# Discards any nonce that is old enough that it wouldn't pass use_nonce
|
||||
# Not called during normal library operation, this method is for store
|
||||
# admins to keep their storage from filling up with expired data
|
||||
def cleanup_nonces
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
# Remove expired associations from the store
|
||||
# Not called during normal library operation, this method is for store
|
||||
# admins to keep their storage from filling up with expired data
|
||||
def cleanup_associations
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
# Remove expired nonces and associations from the store
|
||||
# Not called during normal library operation, this method is for store
|
||||
# admins to keep their storage from filling up with expired data
|
||||
def cleanup
|
||||
return cleanup_nonces, cleanup_associations
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
84
vendor/gems/ruby-openid-2.1.2/lib/openid/store/memory.rb
vendored
Normal file
84
vendor/gems/ruby-openid-2.1.2/lib/openid/store/memory.rb
vendored
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
require 'openid/store/interface'
|
||||
module OpenID
|
||||
module Store
|
||||
# An in-memory implementation of Store. This class is mainly used
|
||||
# for testing, though it may be useful for long-running single
|
||||
# process apps. Note that this store is NOT thread-safe.
|
||||
#
|
||||
# You should probably be looking at OpenID::Store::Filesystem
|
||||
class Memory < Interface
|
||||
|
||||
def initialize
|
||||
@associations = {}
|
||||
@associations.default = {}
|
||||
@nonces = {}
|
||||
end
|
||||
|
||||
def store_association(server_url, assoc)
|
||||
assocs = @associations[server_url]
|
||||
@associations[server_url] = assocs.merge({assoc.handle => deepcopy(assoc)})
|
||||
end
|
||||
|
||||
def get_association(server_url, handle=nil)
|
||||
assocs = @associations[server_url]
|
||||
assoc = nil
|
||||
if handle
|
||||
assoc = assocs[handle]
|
||||
else
|
||||
assoc = assocs.values.sort{|a,b| a.issued <=> b.issued}[-1]
|
||||
end
|
||||
|
||||
return assoc
|
||||
end
|
||||
|
||||
def remove_association(server_url, handle)
|
||||
assocs = @associations[server_url]
|
||||
if assocs.delete(handle)
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def use_nonce(server_url, timestamp, salt)
|
||||
return false if (timestamp - Time.now.to_i).abs > Nonce.skew
|
||||
nonce = [server_url, timestamp, salt].join('')
|
||||
return false if @nonces[nonce]
|
||||
@nonces[nonce] = timestamp
|
||||
return true
|
||||
end
|
||||
|
||||
def cleanup_associations
|
||||
count = 0
|
||||
@associations.each{|server_url, assocs|
|
||||
assocs.each{|handle, assoc|
|
||||
if assoc.expires_in == 0
|
||||
assocs.delete(handle)
|
||||
count += 1
|
||||
end
|
||||
}
|
||||
}
|
||||
return count
|
||||
end
|
||||
|
||||
def cleanup_nonces
|
||||
count = 0
|
||||
now = Time.now.to_i
|
||||
@nonces.each{|nonce, timestamp|
|
||||
if (timestamp - now).abs > Nonce.skew
|
||||
@nonces.delete(nonce)
|
||||
count += 1
|
||||
end
|
||||
}
|
||||
return count
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def deepcopy(o)
|
||||
Marshal.load(Marshal.dump(o))
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
68
vendor/gems/ruby-openid-2.1.2/lib/openid/store/nonce.rb
vendored
Normal file
68
vendor/gems/ruby-openid-2.1.2/lib/openid/store/nonce.rb
vendored
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
require 'openid/cryptutil'
|
||||
require 'date'
|
||||
require 'time'
|
||||
|
||||
module OpenID
|
||||
module Nonce
|
||||
DEFAULT_SKEW = 60*60*5
|
||||
TIME_FMT = '%Y-%m-%dT%H:%M:%SZ'
|
||||
TIME_STR_LEN = '0000-00-00T00:00:00Z'.size
|
||||
@@NONCE_CHRS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
TIME_VALIDATOR = /\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ/
|
||||
|
||||
@skew = DEFAULT_SKEW
|
||||
|
||||
# The allowed nonce time skew in seconds. Defaults to 5 hours.
|
||||
# Used for checking nonce validity, and by stores' cleanup methods.
|
||||
def Nonce.skew
|
||||
@skew
|
||||
end
|
||||
|
||||
def Nonce.skew=(new_skew)
|
||||
@skew = new_skew
|
||||
end
|
||||
|
||||
# Extract timestamp from a nonce string
|
||||
def Nonce.split_nonce(nonce_str)
|
||||
timestamp_str = nonce_str[0...TIME_STR_LEN]
|
||||
raise ArgumentError if timestamp_str.size < TIME_STR_LEN
|
||||
raise ArgumentError unless timestamp_str.match(TIME_VALIDATOR)
|
||||
ts = Time.parse(timestamp_str).to_i
|
||||
raise ArgumentError if ts < 0
|
||||
return ts, nonce_str[TIME_STR_LEN..-1]
|
||||
end
|
||||
|
||||
# Is the timestamp that is part of the specified nonce string
|
||||
# within the allowed clock-skew of the current time?
|
||||
def Nonce.check_timestamp(nonce_str, allowed_skew=nil, now=nil)
|
||||
allowed_skew = skew if allowed_skew.nil?
|
||||
begin
|
||||
stamp, foo = split_nonce(nonce_str)
|
||||
rescue ArgumentError # bad timestamp
|
||||
return false
|
||||
end
|
||||
now = Time.now.to_i unless now
|
||||
|
||||
# times before this are too old
|
||||
past = now - allowed_skew
|
||||
|
||||
# times newer than this are too far in the future
|
||||
future = now + allowed_skew
|
||||
|
||||
return (past <= stamp and stamp <= future)
|
||||
end
|
||||
|
||||
# generate a nonce with the specified timestamp (defaults to now)
|
||||
def Nonce.mk_nonce(time = nil)
|
||||
salt = CryptUtil::random_string(6, @@NONCE_CHRS)
|
||||
if time.nil?
|
||||
t = Time.now.getutc
|
||||
else
|
||||
t = Time.at(time).getutc
|
||||
end
|
||||
time_str = t.strftime(TIME_FMT)
|
||||
return time_str + salt
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
349
vendor/gems/ruby-openid-2.1.2/lib/openid/trustroot.rb
vendored
Normal file
349
vendor/gems/ruby-openid-2.1.2/lib/openid/trustroot.rb
vendored
Normal file
|
|
@ -0,0 +1,349 @@
|
|||
require 'uri'
|
||||
require 'openid/urinorm'
|
||||
|
||||
module OpenID
|
||||
|
||||
class RealmVerificationRedirected < Exception
|
||||
# Attempting to verify this realm resulted in a redirect.
|
||||
def initialize(relying_party_url, rp_url_after_redirects)
|
||||
@relying_party_url = relying_party_url
|
||||
@rp_url_after_redirects = rp_url_after_redirects
|
||||
end
|
||||
|
||||
def to_s
|
||||
return "Attempting to verify #{@relying_party_url} resulted in " +
|
||||
"redirect to #{@rp_url_after_redirects}"
|
||||
end
|
||||
end
|
||||
|
||||
module TrustRoot
|
||||
TOP_LEVEL_DOMAINS = %w'
|
||||
ac ad ae aero af ag ai al am an ao aq ar arpa as asia at
|
||||
au aw ax az ba bb bd be bf bg bh bi biz bj bm bn bo br bs bt
|
||||
bv bw by bz ca cat cc cd cf cg ch ci ck cl cm cn co com coop
|
||||
cr cu cv cx cy cz de dj dk dm do dz ec edu ee eg er es et eu
|
||||
fi fj fk fm fo fr ga gb gd ge gf gg gh gi gl gm gn gov gp gq
|
||||
gr gs gt gu gw gy hk hm hn hr ht hu id ie il im in info int
|
||||
io iq ir is it je jm jo jobs jp ke kg kh ki km kn kp kr kw
|
||||
ky kz la lb lc li lk lr ls lt lu lv ly ma mc md me mg mh mil
|
||||
mk ml mm mn mo mobi mp mq mr ms mt mu museum mv mw mx my mz
|
||||
na name nc ne net nf ng ni nl no np nr nu nz om org pa pe pf
|
||||
pg ph pk pl pm pn pr pro ps pt pw py qa re ro rs ru rw sa sb
|
||||
sc sd se sg sh si sj sk sl sm sn so sr st su sv sy sz tc td
|
||||
tel tf tg th tj tk tl tm tn to tp tr travel tt tv tw tz ua
|
||||
ug uk us uy uz va vc ve vg vi vn vu wf ws xn--0zwm56d
|
||||
xn--11b5bs3a9aj6g xn--80akhbyknj4f xn--9t4b11yi5a
|
||||
xn--deba0ad xn--g6w251d xn--hgbk6aj7f53bba
|
||||
xn--hlcj6aya9esc7a xn--jxalpdlp xn--kgbechtv xn--zckzah ye
|
||||
yt yu za zm zw'
|
||||
|
||||
ALLOWED_PROTOCOLS = ['http', 'https']
|
||||
|
||||
# The URI for relying party discovery, used in realm verification.
|
||||
#
|
||||
# XXX: This should probably live somewhere else (like in
|
||||
# OpenID or OpenID::Yadis somewhere)
|
||||
RP_RETURN_TO_URL_TYPE = 'http://specs.openid.net/auth/2.0/return_to'
|
||||
|
||||
# If the endpoint is a relying party OpenID return_to endpoint,
|
||||
# return the endpoint URL. Otherwise, return None.
|
||||
#
|
||||
# This function is intended to be used as a filter for the Yadis
|
||||
# filtering interface.
|
||||
#
|
||||
# endpoint: An XRDS BasicServiceEndpoint, as returned by
|
||||
# performing Yadis dicovery.
|
||||
#
|
||||
# returns the endpoint URL or None if the endpoint is not a
|
||||
# relying party endpoint.
|
||||
def TrustRoot._extract_return_url(endpoint)
|
||||
if endpoint.matchTypes([RP_RETURN_TO_URL_TYPE])
|
||||
return endpoint.uri
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
# Is the return_to URL under one of the supplied allowed
|
||||
# return_to URLs?
|
||||
def TrustRoot.return_to_matches(allowed_return_to_urls, return_to)
|
||||
allowed_return_to_urls.each { |allowed_return_to|
|
||||
# A return_to pattern works the same as a realm, except that
|
||||
# it's not allowed to use a wildcard. We'll model this by
|
||||
# parsing it as a realm, and not trying to match it if it has
|
||||
# a wildcard.
|
||||
|
||||
return_realm = TrustRoot.parse(allowed_return_to)
|
||||
if (# Parses as a trust root
|
||||
!return_realm.nil? and
|
||||
|
||||
# Does not have a wildcard
|
||||
!return_realm.wildcard and
|
||||
|
||||
# Matches the return_to that we passed in with it
|
||||
return_realm.validate_url(return_to)
|
||||
)
|
||||
return true
|
||||
end
|
||||
}
|
||||
|
||||
# No URL in the list matched
|
||||
return false
|
||||
end
|
||||
|
||||
# Given a relying party discovery URL return a list of return_to
|
||||
# URLs.
|
||||
def TrustRoot.get_allowed_return_urls(relying_party_url)
|
||||
rp_url_after_redirects, return_to_urls = services.get_service_endpoints(
|
||||
relying_party_url, _extract_return_url)
|
||||
|
||||
if rp_url_after_redirects != relying_party_url
|
||||
# Verification caused a redirect
|
||||
raise RealmVerificationRedirected.new(
|
||||
relying_party_url, rp_url_after_redirects)
|
||||
end
|
||||
|
||||
return return_to_urls
|
||||
end
|
||||
|
||||
# Verify that a return_to URL is valid for the given realm.
|
||||
#
|
||||
# This function builds a discovery URL, performs Yadis discovery
|
||||
# on it, makes sure that the URL does not redirect, parses out
|
||||
# the return_to URLs, and finally checks to see if the current
|
||||
# return_to URL matches the return_to.
|
||||
#
|
||||
# raises DiscoveryFailure when Yadis discovery fails returns
|
||||
# true if the return_to URL is valid for the realm
|
||||
def TrustRoot.verify_return_to(realm_str, return_to, _vrfy=nil)
|
||||
# _vrfy parameter is there to make testing easier
|
||||
if _vrfy.nil?
|
||||
_vrfy = self.method('get_allowed_return_urls')
|
||||
end
|
||||
|
||||
if !(_vrfy.is_a?(Proc) or _vrfy.is_a?(Method))
|
||||
raise ArgumentError, "_vrfy must be a Proc or Method"
|
||||
end
|
||||
|
||||
realm = TrustRoot.parse(realm_str)
|
||||
if realm.nil?
|
||||
# The realm does not parse as a URL pattern
|
||||
return false
|
||||
end
|
||||
|
||||
begin
|
||||
allowable_urls = _vrfy.call(realm.build_discovery_url())
|
||||
rescue RealmVerificationRedirected => err
|
||||
Util.log(err.to_s)
|
||||
return false
|
||||
end
|
||||
|
||||
if return_to_matches(allowable_urls, return_to)
|
||||
return true
|
||||
else
|
||||
Util.log("Failed to validate return_to #{return_to} for " +
|
||||
"realm #{realm_str}, was not in #{allowable_urls}")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
class TrustRoot
|
||||
|
||||
attr_reader :unparsed, :proto, :wildcard, :host, :port, :path
|
||||
|
||||
@@empty_re = Regexp.new('^http[s]*:\/\/\*\/$')
|
||||
|
||||
def TrustRoot._build_path(path, query=nil, frag=nil)
|
||||
s = path.dup
|
||||
|
||||
frag = nil if frag == ''
|
||||
query = nil if query == ''
|
||||
|
||||
if query
|
||||
s << "?" << query
|
||||
end
|
||||
|
||||
if frag
|
||||
s << "#" << frag
|
||||
end
|
||||
|
||||
return s
|
||||
end
|
||||
|
||||
def TrustRoot._parse_url(url)
|
||||
begin
|
||||
url = URINorm.urinorm(url)
|
||||
rescue URI::InvalidURIError => err
|
||||
nil
|
||||
end
|
||||
|
||||
begin
|
||||
parsed = URI::parse(url)
|
||||
rescue URI::InvalidURIError
|
||||
return nil
|
||||
end
|
||||
|
||||
path = TrustRoot._build_path(parsed.path,
|
||||
parsed.query,
|
||||
parsed.fragment)
|
||||
|
||||
return [parsed.scheme || '', parsed.host || '',
|
||||
parsed.port || '', path || '']
|
||||
end
|
||||
|
||||
def TrustRoot.parse(trust_root)
|
||||
trust_root = trust_root.dup
|
||||
unparsed = trust_root.dup
|
||||
|
||||
# look for wildcard
|
||||
wildcard = (not trust_root.index('://*.').nil?)
|
||||
trust_root.sub!('*.', '') if wildcard
|
||||
|
||||
# handle http://*/ case
|
||||
if not wildcard and @@empty_re.match(trust_root)
|
||||
proto = trust_root.split(':')[0]
|
||||
port = proto == 'http' ? 80 : 443
|
||||
return new(unparsed, proto, true, '', port, '/')
|
||||
end
|
||||
|
||||
parts = TrustRoot._parse_url(trust_root)
|
||||
return nil if parts.nil?
|
||||
|
||||
proto, host, port, path = parts
|
||||
|
||||
# check for URI fragment
|
||||
if path and !path.index('#').nil?
|
||||
return nil
|
||||
end
|
||||
|
||||
return nil unless ['http', 'https'].member?(proto)
|
||||
return new(unparsed, proto, wildcard, host, port, path)
|
||||
end
|
||||
|
||||
def TrustRoot.check_sanity(trust_root_string)
|
||||
trust_root = TrustRoot.parse(trust_root_string)
|
||||
if trust_root.nil?
|
||||
return false
|
||||
else
|
||||
return trust_root.sane?
|
||||
end
|
||||
end
|
||||
|
||||
# quick func for validating a url against a trust root. See the
|
||||
# TrustRoot class if you need more control.
|
||||
def self.check_url(trust_root, url)
|
||||
tr = self.parse(trust_root)
|
||||
return (!tr.nil? and tr.validate_url(url))
|
||||
end
|
||||
|
||||
# Return a discovery URL for this realm.
|
||||
#
|
||||
# This function does not check to make sure that the realm is
|
||||
# valid. Its behaviour on invalid inputs is undefined.
|
||||
#
|
||||
# return_to:: The relying party return URL of the OpenID
|
||||
# authentication request
|
||||
#
|
||||
# Returns the URL upon which relying party discovery should be
|
||||
# run in order to verify the return_to URL
|
||||
def build_discovery_url
|
||||
if self.wildcard
|
||||
# Use "www." in place of the star
|
||||
www_domain = 'www.' + @host
|
||||
port = (!@port.nil? and ![80, 443].member?(@port)) ? (":" + @port.to_s) : ''
|
||||
return "#{@proto}://#{www_domain}#{port}#{@path}"
|
||||
else
|
||||
return @unparsed
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(unparsed, proto, wildcard, host, port, path)
|
||||
@unparsed = unparsed
|
||||
@proto = proto
|
||||
@wildcard = wildcard
|
||||
@host = host
|
||||
@port = port
|
||||
@path = path
|
||||
end
|
||||
|
||||
def sane?
|
||||
return true if @host == 'localhost'
|
||||
|
||||
host_parts = @host.split('.')
|
||||
|
||||
# a note: ruby string split does not put an empty string at
|
||||
# the end of the list if the split element is last. for
|
||||
# example, 'foo.com.'.split('.') => ['foo','com']. Mentioned
|
||||
# because the python code differs here.
|
||||
|
||||
return false if host_parts.length == 0
|
||||
|
||||
# no adjacent dots
|
||||
return false if host_parts.member?('')
|
||||
|
||||
# last part must be a tld
|
||||
tld = host_parts[-1]
|
||||
return false unless TOP_LEVEL_DOMAINS.member?(tld)
|
||||
|
||||
return false if host_parts.length == 1
|
||||
|
||||
if @wildcard
|
||||
if tld.length == 2 and host_parts[-2].length <= 3
|
||||
# It's a 2-letter tld with a short second to last segment
|
||||
# so there needs to be more than two segments specified
|
||||
# (e.g. *.co.uk is insane)
|
||||
return host_parts.length > 2
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
def validate_url(url)
|
||||
parts = TrustRoot._parse_url(url)
|
||||
return false if parts.nil?
|
||||
|
||||
proto, host, port, path = parts
|
||||
|
||||
return false unless proto == @proto
|
||||
return false unless port == @port
|
||||
return false unless host.index('*').nil?
|
||||
|
||||
if !@wildcard
|
||||
if host != @host
|
||||
return false
|
||||
end
|
||||
elsif ((@host != '') and
|
||||
(!host.ends_with?('.' + @host)) and
|
||||
(host != @host))
|
||||
return false
|
||||
end
|
||||
|
||||
if path != @path
|
||||
path_len = @path.length
|
||||
trust_prefix = @path[0...path_len]
|
||||
url_prefix = path[0...path_len]
|
||||
|
||||
# must be equal up to the length of the path, at least
|
||||
if trust_prefix != url_prefix
|
||||
return false
|
||||
end
|
||||
|
||||
# These characters must be on the boundary between the end
|
||||
# of the trust root's path and the start of the URL's path.
|
||||
if !@path.index('?').nil?
|
||||
allowed = '&'
|
||||
else
|
||||
allowed = '?/'
|
||||
end
|
||||
|
||||
return (!allowed.index(@path[-1]).nil? or
|
||||
!allowed.index(path[path_len]).nil?)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
75
vendor/gems/ruby-openid-2.1.2/lib/openid/urinorm.rb
vendored
Normal file
75
vendor/gems/ruby-openid-2.1.2/lib/openid/urinorm.rb
vendored
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
require 'uri'
|
||||
|
||||
require "openid/extras"
|
||||
|
||||
module OpenID
|
||||
|
||||
module URINorm
|
||||
public
|
||||
def URINorm.urinorm(uri)
|
||||
uri = URI.parse(uri)
|
||||
|
||||
raise URI::InvalidURIError.new('no scheme') unless uri.scheme
|
||||
uri.scheme = uri.scheme.downcase
|
||||
unless ['http','https'].member?(uri.scheme)
|
||||
raise URI::InvalidURIError.new('Not an HTTP or HTTPS URI')
|
||||
end
|
||||
|
||||
raise URI::InvalidURIError.new('no host') unless uri.host
|
||||
uri.host = uri.host.downcase
|
||||
|
||||
uri.path = remove_dot_segments(uri.path)
|
||||
uri.path = '/' if uri.path.length == 0
|
||||
|
||||
uri = uri.normalize.to_s
|
||||
uri = uri.gsub(PERCENT_ESCAPE_RE) {
|
||||
sub = $&[1..2].to_i(16).chr
|
||||
reserved(sub) ? $&.upcase : sub
|
||||
}
|
||||
|
||||
return uri
|
||||
end
|
||||
|
||||
private
|
||||
RESERVED_RE = /[A-Za-z0-9._~-]/
|
||||
PERCENT_ESCAPE_RE = /%[0-9a-zA-Z]{2}/
|
||||
|
||||
def URINorm.reserved(chr)
|
||||
not RESERVED_RE =~ chr
|
||||
end
|
||||
|
||||
def URINorm.remove_dot_segments(path)
|
||||
result_segments = []
|
||||
|
||||
while path.length > 0
|
||||
if path.starts_with?('../')
|
||||
path = path[3..-1]
|
||||
elsif path.starts_with?('./')
|
||||
path = path[2..-1]
|
||||
elsif path.starts_with?('/./')
|
||||
path = path[2..-1]
|
||||
elsif path == '/.'
|
||||
path = '/'
|
||||
elsif path.starts_with?('/../')
|
||||
path = path[3..-1]
|
||||
result_segments.pop if result_segments.length > 0
|
||||
elsif path == '/..'
|
||||
path = '/'
|
||||
result_segments.pop if result_segments.length > 0
|
||||
elsif path == '..' or path == '.'
|
||||
path = ''
|
||||
else
|
||||
i = 0
|
||||
i = 1 if path[0].chr == '/'
|
||||
i = path.index('/', i)
|
||||
i = path.length if i.nil?
|
||||
result_segments << path[0...i]
|
||||
path = path[i..-1]
|
||||
end
|
||||
end
|
||||
|
||||
return result_segments.join('')
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
110
vendor/gems/ruby-openid-2.1.2/lib/openid/util.rb
vendored
Normal file
110
vendor/gems/ruby-openid-2.1.2/lib/openid/util.rb
vendored
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
require "cgi"
|
||||
require "uri"
|
||||
require "logger"
|
||||
|
||||
require "openid/extras"
|
||||
|
||||
# See OpenID::Consumer or OpenID::Server modules, as well as the store classes
|
||||
module OpenID
|
||||
class AssertionError < Exception
|
||||
end
|
||||
|
||||
# Exceptions that are raised by the library are subclasses of this
|
||||
# exception type, so if you want to catch all exceptions raised by
|
||||
# the library, you can catch OpenIDError
|
||||
class OpenIDError < StandardError
|
||||
end
|
||||
|
||||
module Util
|
||||
|
||||
BASE64_CHARS = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' \
|
||||
'abcdefghijklmnopqrstuvwxyz0123456789+/')
|
||||
BASE64_RE = Regexp.compile("
|
||||
\\A
|
||||
([#{BASE64_CHARS}]{4})*
|
||||
([#{BASE64_CHARS}]{2}==|
|
||||
[#{BASE64_CHARS}]{3}=)?
|
||||
\\Z", Regexp::EXTENDED)
|
||||
|
||||
def Util.assert(value, message=nil)
|
||||
if not value
|
||||
raise AssertionError, message or value
|
||||
end
|
||||
end
|
||||
|
||||
def Util.to_base64(s)
|
||||
[s].pack('m').gsub("\n", "")
|
||||
end
|
||||
|
||||
def Util.from_base64(s)
|
||||
without_newlines = s.gsub(/[\r\n]+/, '')
|
||||
if !BASE64_RE.match(without_newlines)
|
||||
raise ArgumentError, "Malformed input: #{s.inspect}"
|
||||
end
|
||||
without_newlines.unpack('m').first
|
||||
end
|
||||
|
||||
def Util.urlencode(args)
|
||||
a = []
|
||||
args.each do |key, val|
|
||||
val = '' unless val
|
||||
a << (CGI::escape(key) + "=" + CGI::escape(val))
|
||||
end
|
||||
a.join("&")
|
||||
end
|
||||
|
||||
def Util.parse_query(qs)
|
||||
query = {}
|
||||
CGI::parse(qs).each {|k,v| query[k] = v[0]}
|
||||
return query
|
||||
end
|
||||
|
||||
def Util.append_args(url, args)
|
||||
url = url.dup
|
||||
return url if args.length == 0
|
||||
|
||||
if args.respond_to?('each_pair')
|
||||
args = args.sort
|
||||
end
|
||||
|
||||
url << (url.include?("?") ? "&" : "?")
|
||||
url << Util.urlencode(args)
|
||||
end
|
||||
|
||||
@@logger = Logger.new(STDERR)
|
||||
@@logger.progname = "OpenID"
|
||||
|
||||
def Util.logger=(logger)
|
||||
@@logger = logger
|
||||
end
|
||||
|
||||
def Util.logger
|
||||
@@logger
|
||||
end
|
||||
|
||||
# change the message below to do whatever you like for logging
|
||||
def Util.log(message)
|
||||
logger.info(message)
|
||||
end
|
||||
|
||||
def Util.auto_submit_html(form, title='OpenID transaction in progress')
|
||||
return "
|
||||
<html>
|
||||
<head>
|
||||
<title>#{title}</title>
|
||||
</head>
|
||||
<body onload='document.forms[0].submit();'>
|
||||
#{form}
|
||||
<script>
|
||||
var elements = document.forms[0].elements;
|
||||
for (var i = 0; i < elements.length; i++) {
|
||||
elements[i].style.display = \"none\";
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
148
vendor/gems/ruby-openid-2.1.2/lib/openid/yadis/accept.rb
vendored
Normal file
148
vendor/gems/ruby-openid-2.1.2/lib/openid/yadis/accept.rb
vendored
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
module OpenID
|
||||
|
||||
module Yadis
|
||||
|
||||
# Generate an accept header value
|
||||
#
|
||||
# [str or (str, float)] -> str
|
||||
def self.generate_accept_header(*elements)
|
||||
parts = []
|
||||
elements.each { |element|
|
||||
if element.is_a?(String)
|
||||
qs = "1.0"
|
||||
mtype = element
|
||||
else
|
||||
mtype, q = element
|
||||
q = q.to_f
|
||||
if q > 1 or q <= 0
|
||||
raise ArgumentError.new("Invalid preference factor: #{q}")
|
||||
end
|
||||
qs = sprintf("%0.1f", q)
|
||||
end
|
||||
|
||||
parts << [qs, mtype]
|
||||
}
|
||||
|
||||
parts.sort!
|
||||
chunks = []
|
||||
parts.each { |q, mtype|
|
||||
if q == '1.0'
|
||||
chunks << mtype
|
||||
else
|
||||
chunks << sprintf("%s; q=%s", mtype, q)
|
||||
end
|
||||
}
|
||||
|
||||
return chunks.join(', ')
|
||||
end
|
||||
|
||||
def self.parse_accept_header(value)
|
||||
# Parse an accept header, ignoring any accept-extensions
|
||||
#
|
||||
# returns a list of tuples containing main MIME type, MIME
|
||||
# subtype, and quality markdown.
|
||||
#
|
||||
# str -> [(str, str, float)]
|
||||
chunks = value.split(',', -1).collect { |v| v.strip }
|
||||
accept = []
|
||||
chunks.each { |chunk|
|
||||
parts = chunk.split(";", -1).collect { |s| s.strip }
|
||||
|
||||
mtype = parts.shift
|
||||
if mtype.index('/').nil?
|
||||
# This is not a MIME type, so ignore the bad data
|
||||
next
|
||||
end
|
||||
|
||||
main, sub = mtype.split('/', 2)
|
||||
|
||||
q = nil
|
||||
parts.each { |ext|
|
||||
if !ext.index('=').nil?
|
||||
k, v = ext.split('=', 2)
|
||||
if k == 'q'
|
||||
q = v.to_f
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
q = 1.0 if q.nil?
|
||||
|
||||
accept << [q, main, sub]
|
||||
}
|
||||
|
||||
accept.sort!
|
||||
accept.reverse!
|
||||
|
||||
return accept.collect { |q, main, sub| [main, sub, q] }
|
||||
end
|
||||
|
||||
def self.match_types(accept_types, have_types)
|
||||
# Given the result of parsing an Accept: header, and the
|
||||
# available MIME types, return the acceptable types with their
|
||||
# quality markdowns.
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
# >>> acceptable = parse_accept_header('text/html, text/plain; q=0.5')
|
||||
# >>> matchTypes(acceptable, ['text/plain', 'text/html', 'image/jpeg'])
|
||||
# [('text/html', 1.0), ('text/plain', 0.5)]
|
||||
#
|
||||
# Type signature: ([(str, str, float)], [str]) -> [(str, float)]
|
||||
if accept_types.nil? or accept_types == []
|
||||
# Accept all of them
|
||||
default = 1
|
||||
else
|
||||
default = 0
|
||||
end
|
||||
|
||||
match_main = {}
|
||||
match_sub = {}
|
||||
accept_types.each { |main, sub, q|
|
||||
if main == '*'
|
||||
default = [default, q].max
|
||||
next
|
||||
elsif sub == '*'
|
||||
match_main[main] = [match_main.fetch(main, 0), q].max
|
||||
else
|
||||
match_sub[[main, sub]] = [match_sub.fetch([main, sub], 0), q].max
|
||||
end
|
||||
}
|
||||
|
||||
accepted_list = []
|
||||
order_maintainer = 0
|
||||
have_types.each { |mtype|
|
||||
main, sub = mtype.split('/', 2)
|
||||
if match_sub.member?([main, sub])
|
||||
q = match_sub[[main, sub]]
|
||||
else
|
||||
q = match_main.fetch(main, default)
|
||||
end
|
||||
|
||||
if q != 0
|
||||
accepted_list << [1 - q, order_maintainer, q, mtype]
|
||||
order_maintainer += 1
|
||||
end
|
||||
}
|
||||
|
||||
accepted_list.sort!
|
||||
return accepted_list.collect { |_, _, q, mtype| [mtype, q] }
|
||||
end
|
||||
|
||||
def self.get_acceptable(accept_header, have_types)
|
||||
# Parse the accept header and return a list of available types
|
||||
# in preferred order. If a type is unacceptable, it will not be
|
||||
# in the resulting list.
|
||||
#
|
||||
# This is a convenience wrapper around matchTypes and
|
||||
# parse_accept_header
|
||||
#
|
||||
# (str, [str]) -> [str]
|
||||
accepted = self.parse_accept_header(accept_header)
|
||||
preferred = self.match_types(accepted, have_types)
|
||||
return preferred.collect { |mtype, _| mtype }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
21
vendor/gems/ruby-openid-2.1.2/lib/openid/yadis/constants.rb
vendored
Normal file
21
vendor/gems/ruby-openid-2.1.2/lib/openid/yadis/constants.rb
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
|
||||
require 'openid/yadis/accept'
|
||||
|
||||
module OpenID
|
||||
|
||||
module Yadis
|
||||
|
||||
YADIS_HEADER_NAME = 'X-XRDS-Location'
|
||||
YADIS_CONTENT_TYPE = 'application/xrds+xml'
|
||||
|
||||
# A value suitable for using as an accept header when performing
|
||||
# YADIS discovery, unless the application has special requirements
|
||||
YADIS_ACCEPT_HEADER = generate_accept_header(
|
||||
['text/html', 0.3],
|
||||
['application/xhtml+xml', 0.5],
|
||||
[YADIS_CONTENT_TYPE, 1.0]
|
||||
)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
153
vendor/gems/ruby-openid-2.1.2/lib/openid/yadis/discovery.rb
vendored
Normal file
153
vendor/gems/ruby-openid-2.1.2/lib/openid/yadis/discovery.rb
vendored
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
|
||||
require 'openid/util'
|
||||
require 'openid/fetchers'
|
||||
require 'openid/yadis/constants'
|
||||
require 'openid/yadis/parsehtml'
|
||||
|
||||
module OpenID
|
||||
|
||||
# Raised when a error occurs in the discovery process
|
||||
class DiscoveryFailure < OpenIDError
|
||||
attr_accessor :identity_url, :http_response
|
||||
|
||||
def initialize(message, http_response)
|
||||
super(message)
|
||||
@identity_url = nil
|
||||
@http_response = http_response
|
||||
end
|
||||
end
|
||||
|
||||
module Yadis
|
||||
|
||||
# Contains the result of performing Yadis discovery on a URI
|
||||
class DiscoveryResult
|
||||
|
||||
# The result of following redirects from the request_uri
|
||||
attr_accessor :normalize_uri
|
||||
|
||||
# The URI from which the response text was returned (set to
|
||||
# nil if there was no XRDS document found)
|
||||
attr_accessor :xrds_uri
|
||||
|
||||
# The content-type returned with the response_text
|
||||
attr_accessor :content_type
|
||||
|
||||
# The document returned from the xrds_uri
|
||||
attr_accessor :response_text
|
||||
|
||||
attr_accessor :request_uri, :normalized_uri
|
||||
|
||||
def initialize(request_uri)
|
||||
# Initialize the state of the object
|
||||
#
|
||||
# sets all attributes to None except the request_uri
|
||||
@request_uri = request_uri
|
||||
@normalized_uri = nil
|
||||
@xrds_uri = nil
|
||||
@content_type = nil
|
||||
@response_text = nil
|
||||
end
|
||||
|
||||
# Was the Yadis protocol's indirection used?
|
||||
def used_yadis_location?
|
||||
return @normalized_uri != @xrds_uri
|
||||
end
|
||||
|
||||
# Is the response text supposed to be an XRDS document?
|
||||
def is_xrds
|
||||
return (used_yadis_location?() or
|
||||
@content_type == YADIS_CONTENT_TYPE)
|
||||
end
|
||||
end
|
||||
|
||||
# Discover services for a given URI.
|
||||
#
|
||||
# uri: The identity URI as a well-formed http or https URI. The
|
||||
# well-formedness and the protocol are not checked, but the
|
||||
# results of this function are undefined if those properties do
|
||||
# not hold.
|
||||
#
|
||||
# returns a DiscoveryResult object
|
||||
#
|
||||
# Raises DiscoveryFailure when the HTTP response does not have
|
||||
# a 200 code.
|
||||
def self.discover(uri)
|
||||
result = DiscoveryResult.new(uri)
|
||||
begin
|
||||
resp = OpenID.fetch(uri, nil, {'Accept' => YADIS_ACCEPT_HEADER})
|
||||
rescue Exception
|
||||
raise DiscoveryFailure.new("Failed to fetch identity URL #{uri} : #{$!}", $!)
|
||||
end
|
||||
if resp.code != "200" and resp.code != "206"
|
||||
raise DiscoveryFailure.new(
|
||||
"HTTP Response status from identity URL host is not \"200\"."\
|
||||
"Got status #{resp.code.inspect} for #{resp.final_url}", resp)
|
||||
end
|
||||
|
||||
# Note the URL after following redirects
|
||||
result.normalized_uri = resp.final_url
|
||||
|
||||
# Attempt to find out where to go to discover the document or if
|
||||
# we already have it
|
||||
result.content_type = resp['content-type']
|
||||
|
||||
result.xrds_uri = self.where_is_yadis?(resp)
|
||||
|
||||
if result.xrds_uri and result.used_yadis_location?
|
||||
begin
|
||||
resp = OpenID.fetch(result.xrds_uri)
|
||||
rescue
|
||||
raise DiscoveryFailure.new("Failed to fetch Yadis URL #{result.xrds_uri} : #{$!}", $!)
|
||||
end
|
||||
if resp.code != "200" and resp.code != "206"
|
||||
exc = DiscoveryFailure.new(
|
||||
"HTTP Response status from Yadis host is not \"200\". " +
|
||||
"Got status #{resp.code.inspect} for #{resp.final_url}", resp)
|
||||
exc.identity_url = result.normalized_uri
|
||||
raise exc
|
||||
end
|
||||
|
||||
result.content_type = resp['content-type']
|
||||
end
|
||||
|
||||
result.response_text = resp.body
|
||||
return result
|
||||
end
|
||||
|
||||
# Given a HTTPResponse, return the location of the Yadis
|
||||
# document.
|
||||
#
|
||||
# May be the URL just retrieved, another URL, or None, if I
|
||||
# can't find any.
|
||||
#
|
||||
# [non-blocking]
|
||||
def self.where_is_yadis?(resp)
|
||||
# Attempt to find out where to go to discover the document or if
|
||||
# we already have it
|
||||
content_type = resp['content-type']
|
||||
|
||||
# According to the spec, the content-type header must be an
|
||||
# exact match, or else we have to look for an indirection.
|
||||
if (!content_type.nil? and !content_type.to_s.empty? and
|
||||
content_type.split(';', 2)[0].downcase == YADIS_CONTENT_TYPE)
|
||||
return resp.final_url
|
||||
else
|
||||
# Try the header
|
||||
yadis_loc = resp[YADIS_HEADER_NAME.downcase]
|
||||
|
||||
if yadis_loc.nil?
|
||||
# Parse as HTML if the header is missing.
|
||||
#
|
||||
# XXX: do we want to do something with content-type, like
|
||||
# have a whitelist or a blacklist (for detecting that it's
|
||||
# HTML)?
|
||||
yadis_loc = Yadis.html_yadis_location(resp.body)
|
||||
end
|
||||
end
|
||||
|
||||
return yadis_loc
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
205
vendor/gems/ruby-openid-2.1.2/lib/openid/yadis/filters.rb
vendored
Normal file
205
vendor/gems/ruby-openid-2.1.2/lib/openid/yadis/filters.rb
vendored
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
# This file contains functions and classes used for extracting
|
||||
# endpoint information out of a Yadis XRD file using the REXML
|
||||
# XML parser.
|
||||
|
||||
#
|
||||
module OpenID
|
||||
module Yadis
|
||||
class BasicServiceEndpoint
|
||||
attr_reader :type_uris, :yadis_url, :uri, :service_element
|
||||
|
||||
# Generic endpoint object that contains parsed service
|
||||
# information, as well as a reference to the service element
|
||||
# from which it was generated. If there is more than one
|
||||
# xrd:Type or xrd:URI in the xrd:Service, this object represents
|
||||
# just one of those pairs.
|
||||
#
|
||||
# This object can be used as a filter, because it implements
|
||||
# fromBasicServiceEndpoint.
|
||||
#
|
||||
# The simplest kind of filter you can write implements
|
||||
# fromBasicServiceEndpoint, which takes one of these objects.
|
||||
def initialize(yadis_url, type_uris, uri, service_element)
|
||||
@type_uris = type_uris
|
||||
@yadis_url = yadis_url
|
||||
@uri = uri
|
||||
@service_element = service_element
|
||||
end
|
||||
|
||||
# Query this endpoint to see if it has any of the given type
|
||||
# URIs. This is useful for implementing other endpoint classes
|
||||
# that e.g. need to check for the presence of multiple
|
||||
# versions of a single protocol.
|
||||
def match_types(type_uris)
|
||||
return @type_uris & type_uris
|
||||
end
|
||||
|
||||
# Trivial transform from a basic endpoint to itself. This
|
||||
# method exists to allow BasicServiceEndpoint to be used as a
|
||||
# filter.
|
||||
#
|
||||
# If you are subclassing this object, re-implement this function.
|
||||
def self.from_basic_service_endpoint(endpoint)
|
||||
return endpoint
|
||||
end
|
||||
|
||||
# A hack to make both this class and its instances respond to
|
||||
# this message since Ruby doesn't support static methods.
|
||||
def from_basic_service_endpoint(endpoint)
|
||||
return self.class.from_basic_service_endpoint(endpoint)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Take a list of basic filters and makes a filter that
|
||||
# transforms the basic filter into a top-level filter. This is
|
||||
# mostly useful for the implementation of make_filter, which
|
||||
# should only be needed for special cases or internal use by
|
||||
# this library.
|
||||
#
|
||||
# This object is useful for creating simple filters for services
|
||||
# that use one URI and are specified by one Type (we expect most
|
||||
# Types will fit this paradigm).
|
||||
#
|
||||
# Creates a BasicServiceEndpoint object and apply the filter
|
||||
# functions to it until one of them returns a value.
|
||||
class TransformFilterMaker
|
||||
attr_reader :filter_procs
|
||||
|
||||
# Initialize the filter maker's state
|
||||
#
|
||||
# filter_functions are the endpoint transformer
|
||||
# Procs to apply to the basic endpoint. These are called in
|
||||
# turn until one of them does not return nil, and the result
|
||||
# of that transformer is returned.
|
||||
def initialize(filter_procs)
|
||||
@filter_procs = filter_procs
|
||||
end
|
||||
|
||||
# Returns an array of endpoint objects produced by the
|
||||
# filter procs.
|
||||
def get_service_endpoints(yadis_url, service_element)
|
||||
endpoints = []
|
||||
|
||||
# Do an expansion of the service element by xrd:Type and
|
||||
# xrd:URI
|
||||
Yadis::expand_service(service_element).each { |type_uris, uri, _|
|
||||
# Create a basic endpoint object to represent this
|
||||
# yadis_url, Service, Type, URI combination
|
||||
endpoint = BasicServiceEndpoint.new(
|
||||
yadis_url, type_uris, uri, service_element)
|
||||
|
||||
e = apply_filters(endpoint)
|
||||
if !e.nil?
|
||||
endpoints << e
|
||||
end
|
||||
}
|
||||
return endpoints
|
||||
end
|
||||
|
||||
def apply_filters(endpoint)
|
||||
# Apply filter procs to an endpoint until one of them returns
|
||||
# non-nil.
|
||||
@filter_procs.each { |filter_proc|
|
||||
e = filter_proc.call(endpoint)
|
||||
if !e.nil?
|
||||
# Once one of the filters has returned an endpoint, do not
|
||||
# apply any more.
|
||||
return e
|
||||
end
|
||||
}
|
||||
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
class CompoundFilter
|
||||
attr_reader :subfilters
|
||||
|
||||
# Create a new filter that applies a set of filters to an
|
||||
# endpoint and collects their results.
|
||||
def initialize(subfilters)
|
||||
@subfilters = subfilters
|
||||
end
|
||||
|
||||
# Generate all endpoint objects for all of the subfilters of
|
||||
# this filter and return their concatenation.
|
||||
def get_service_endpoints(yadis_url, service_element)
|
||||
endpoints = []
|
||||
@subfilters.each { |subfilter|
|
||||
endpoints += subfilter.get_service_endpoints(yadis_url, service_element)
|
||||
}
|
||||
return endpoints
|
||||
end
|
||||
end
|
||||
|
||||
# Exception raised when something is not able to be turned into a
|
||||
# filter
|
||||
@@filter_type_error = TypeError.new(
|
||||
'Expected a filter, an endpoint, a callable or a list of any of these.')
|
||||
|
||||
# Convert a filter-convertable thing into a filter
|
||||
#
|
||||
# parts should be a filter, an endpoint, a callable, or a list of
|
||||
# any of these.
|
||||
def self.make_filter(parts)
|
||||
# Convert the parts into a list, and pass to mk_compound_filter
|
||||
if parts.nil?
|
||||
parts = [BasicServiceEndpoint]
|
||||
end
|
||||
|
||||
if parts.is_a?(Array)
|
||||
return mk_compound_filter(parts)
|
||||
else
|
||||
return mk_compound_filter([parts])
|
||||
end
|
||||
end
|
||||
|
||||
# Create a filter out of a list of filter-like things
|
||||
#
|
||||
# Used by make_filter
|
||||
#
|
||||
# parts should be a list of things that can be passed to make_filter
|
||||
def self.mk_compound_filter(parts)
|
||||
|
||||
if !parts.respond_to?('each')
|
||||
raise TypeError, "#{parts.inspect} is not iterable"
|
||||
end
|
||||
|
||||
# Separate into a list of callables and a list of filter objects
|
||||
transformers = []
|
||||
filters = []
|
||||
parts.each { |subfilter|
|
||||
if !subfilter.is_a?(Array)
|
||||
# If it's not an iterable
|
||||
if subfilter.respond_to?('get_service_endpoints')
|
||||
# It's a full filter
|
||||
filters << subfilter
|
||||
elsif subfilter.respond_to?('from_basic_service_endpoint')
|
||||
# It's an endpoint object, so put its endpoint conversion
|
||||
# attribute into the list of endpoint transformers
|
||||
transformers << subfilter.method('from_basic_service_endpoint')
|
||||
elsif subfilter.respond_to?('call')
|
||||
# It's a proc, so add it to the list of endpoint
|
||||
# transformers
|
||||
transformers << subfilter
|
||||
else
|
||||
raise @@filter_type_error
|
||||
end
|
||||
else
|
||||
filters << mk_compound_filter(subfilter)
|
||||
end
|
||||
}
|
||||
|
||||
if transformers.length > 0
|
||||
filters << TransformFilterMaker.new(transformers)
|
||||
end
|
||||
|
||||
if filters.length == 1
|
||||
return filters[0]
|
||||
else
|
||||
return CompoundFilter.new(filters)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
305
vendor/gems/ruby-openid-2.1.2/lib/openid/yadis/htmltokenizer.rb
vendored
Normal file
305
vendor/gems/ruby-openid-2.1.2/lib/openid/yadis/htmltokenizer.rb
vendored
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
# = HTMLTokenizer
|
||||
#
|
||||
# Author:: Ben Giddings (mailto:bg-rubyforge@infofiend.com)
|
||||
# Copyright:: Copyright (c) 2004 Ben Giddings
|
||||
# License:: Distributes under the same terms as Ruby
|
||||
#
|
||||
#
|
||||
# This is a partial port of the functionality behind Perl's TokeParser
|
||||
# Provided a page it progressively returns tokens from that page
|
||||
#
|
||||
# $Id: htmltokenizer.rb,v 1.7 2005/06/07 21:05:53 merc Exp $
|
||||
|
||||
#
|
||||
# A class to tokenize HTML.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# page = "<HTML>
|
||||
# <HEAD>
|
||||
# <TITLE>This is the title</TITLE>
|
||||
# </HEAD>
|
||||
# <!-- Here comes the <a href=\"missing.link\">blah</a>
|
||||
# comment body
|
||||
# -->
|
||||
# <BODY>
|
||||
# <H1>This is the header</H1>
|
||||
# <P>
|
||||
# This is the paragraph, it contains
|
||||
# <a href=\"link.html\">links</a>,
|
||||
# <img src=\"blah.gif\" optional alt='images
|
||||
# are
|
||||
# really cool'>. Ok, here is some more text and
|
||||
# <A href=\"http://another.link.com/\" target=\"_blank\">another link</A>.
|
||||
# </P>
|
||||
# </body>
|
||||
# </HTML>
|
||||
# "
|
||||
# toke = HTMLTokenizer.new(page)
|
||||
#
|
||||
# assert("<h1>" == toke.getTag("h1", "h2", "h3").to_s.downcase)
|
||||
# assert(HTMLTag.new("<a href=\"link.html\">") == toke.getTag("IMG", "A"))
|
||||
# assert("links" == toke.getTrimmedText)
|
||||
# assert(toke.getTag("IMG", "A").attr_hash['optional'])
|
||||
# assert("_blank" == toke.getTag("IMG", "A").attr_hash['target'])
|
||||
#
|
||||
class HTMLTokenizer
|
||||
@@version = 1.0
|
||||
|
||||
# Get version of HTMLTokenizer lib
|
||||
def self.version
|
||||
@@version
|
||||
end
|
||||
|
||||
attr_reader :page
|
||||
|
||||
# Create a new tokenizer, based on the content, used as a string.
|
||||
def initialize(content)
|
||||
@page = content.to_s
|
||||
@cur_pos = 0
|
||||
end
|
||||
|
||||
# Reset the parser, setting the current position back at the stop
|
||||
def reset
|
||||
@cur_pos = 0
|
||||
end
|
||||
|
||||
# Look at the next token, but don't actually grab it
|
||||
def peekNextToken
|
||||
if @cur_pos == @page.length then return nil end
|
||||
|
||||
if ?< == @page[@cur_pos]
|
||||
# Next token is a tag of some kind
|
||||
if '!--' == @page[(@cur_pos + 1), 3]
|
||||
# Token is a comment
|
||||
tag_end = @page.index('-->', (@cur_pos + 1))
|
||||
if tag_end.nil?
|
||||
raise HTMLTokenizerError, "No end found to started comment:\n#{@page[@cur_pos,80]}"
|
||||
end
|
||||
# p @page[@cur_pos .. (tag_end+2)]
|
||||
HTMLComment.new(@page[@cur_pos .. (tag_end + 2)])
|
||||
else
|
||||
# Token is a html tag
|
||||
tag_end = @page.index('>', (@cur_pos + 1))
|
||||
if tag_end.nil?
|
||||
raise HTMLTokenizerError, "No end found to started tag:\n#{@page[@cur_pos,80]}"
|
||||
end
|
||||
# p @page[@cur_pos .. tag_end]
|
||||
HTMLTag.new(@page[@cur_pos .. tag_end])
|
||||
end
|
||||
else
|
||||
# Next token is text
|
||||
text_end = @page.index('<', @cur_pos)
|
||||
text_end = text_end.nil? ? -1 : (text_end - 1)
|
||||
# p @page[@cur_pos .. text_end]
|
||||
HTMLText.new(@page[@cur_pos .. text_end])
|
||||
end
|
||||
end
|
||||
|
||||
# Get the next token, returns an instance of
|
||||
# * HTMLText
|
||||
# * HTMLToken
|
||||
# * HTMLTag
|
||||
def getNextToken
|
||||
token = peekNextToken
|
||||
if token
|
||||
# @page = @page[token.raw.length .. -1]
|
||||
# @page.slice!(0, token.raw.length)
|
||||
@cur_pos += token.raw.length
|
||||
end
|
||||
#p token
|
||||
#print token.raw
|
||||
return token
|
||||
end
|
||||
|
||||
# Get a tag from the specified set of desired tags.
|
||||
# For example:
|
||||
# <tt>foo = toke.getTag("h1", "h2", "h3")</tt>
|
||||
# Will return the next header tag encountered.
|
||||
def getTag(*sought_tags)
|
||||
sought_tags.collect! {|elm| elm.downcase}
|
||||
|
||||
while (tag = getNextToken)
|
||||
if tag.kind_of?(HTMLTag) and
|
||||
(0 == sought_tags.length or sought_tags.include?(tag.tag_name))
|
||||
break
|
||||
end
|
||||
end
|
||||
tag
|
||||
end
|
||||
|
||||
# Get all the text between the current position and the next tag
|
||||
# (if specified) or a specific later tag
|
||||
def getText(until_tag = nil)
|
||||
if until_tag.nil?
|
||||
if ?< == @page[@cur_pos]
|
||||
# Next token is a tag, not text
|
||||
""
|
||||
else
|
||||
# Next token is text
|
||||
getNextToken.text
|
||||
end
|
||||
else
|
||||
ret_str = ""
|
||||
|
||||
while (tag = peekNextToken)
|
||||
if tag.kind_of?(HTMLTag) and tag.tag_name == until_tag
|
||||
break
|
||||
end
|
||||
|
||||
if ("" != tag.text)
|
||||
ret_str << (tag.text + " ")
|
||||
end
|
||||
getNextToken
|
||||
end
|
||||
|
||||
ret_str
|
||||
end
|
||||
end
|
||||
|
||||
# Like getText, but squeeze all whitespace, getting rid of
|
||||
# leading and trailing whitespace, and squeezing multiple
|
||||
# spaces into a single space.
|
||||
def getTrimmedText(until_tag = nil)
|
||||
getText(until_tag).strip.gsub(/\s+/m, " ")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class HTMLTokenizerError < Exception
|
||||
end
|
||||
|
||||
# The parent class for all three types of HTML tokens
|
||||
class HTMLToken
|
||||
attr_accessor :raw
|
||||
|
||||
# Initialize the token based on the raw text
|
||||
def initialize(text)
|
||||
@raw = text
|
||||
end
|
||||
|
||||
# By default, return exactly the string used to create the text
|
||||
def to_s
|
||||
raw
|
||||
end
|
||||
|
||||
# By default tokens have no text representation
|
||||
def text
|
||||
""
|
||||
end
|
||||
|
||||
def trimmed_text
|
||||
text.strip.gsub(/\s+/m, " ")
|
||||
end
|
||||
|
||||
# Compare to another based on the raw source
|
||||
def ==(other)
|
||||
raw == other.to_s
|
||||
end
|
||||
end
|
||||
|
||||
# Class representing text that isn't inside a tag
|
||||
class HTMLText < HTMLToken
|
||||
def text
|
||||
raw
|
||||
end
|
||||
end
|
||||
|
||||
# Class representing an HTML comment
|
||||
class HTMLComment < HTMLToken
|
||||
attr_accessor :contents
|
||||
def initialize(text)
|
||||
super(text)
|
||||
temp_arr = text.scan(/^<!--\s*(.*?)\s*-->$/m)
|
||||
if temp_arr[0].nil?
|
||||
raise HTMLTokenizerError, "Text passed to HTMLComment.initialize is not a comment"
|
||||
end
|
||||
|
||||
@contents = temp_arr[0][0]
|
||||
end
|
||||
end
|
||||
|
||||
# Class representing an HTML tag
|
||||
class HTMLTag < HTMLToken
|
||||
attr_reader :end_tag, :tag_name
|
||||
def initialize(text)
|
||||
super(text)
|
||||
if ?< != text[0] or ?> != text[-1]
|
||||
raise HTMLTokenizerError, "Text passed to HTMLComment.initialize is not a comment"
|
||||
end
|
||||
|
||||
@attr_hash = Hash.new
|
||||
@raw = text
|
||||
|
||||
tag_name = text.scan(/[\w:-]+/)[0]
|
||||
if tag_name.nil?
|
||||
raise HTMLTokenizerError, "Error, tag is nil: #{tag_name}"
|
||||
end
|
||||
|
||||
if ?/ == text[1]
|
||||
# It's an end tag
|
||||
@end_tag = true
|
||||
@tag_name = '/' + tag_name.downcase
|
||||
else
|
||||
@end_tag = false
|
||||
@tag_name = tag_name.downcase
|
||||
end
|
||||
|
||||
@hashed = false
|
||||
end
|
||||
|
||||
# Retrieve a hash of all the tag's attributes.
|
||||
# Lazily done, so that if you don't look at a tag's attributes
|
||||
# things go quicker
|
||||
def attr_hash
|
||||
# Lazy initialize == don't build the hash until it's needed
|
||||
if !@hashed
|
||||
if !@end_tag
|
||||
# Get the attributes
|
||||
attr_arr = @raw.scan(/<[\w:-]+\s+(.*?)\/?>/m)[0]
|
||||
if attr_arr.kind_of?(Array)
|
||||
# Attributes found, parse them
|
||||
attrs = attr_arr[0]
|
||||
attr_arr = attrs.scan(/\s*([\w:-]+)(?:\s*=\s*("[^"]*"|'[^']*'|([^"'>][^\s>]*)))?/m)
|
||||
# clean up the array by:
|
||||
# * setting all nil elements to true
|
||||
# * removing enclosing quotes
|
||||
attr_arr.each {
|
||||
|item|
|
||||
val = if item[1].nil?
|
||||
item[0]
|
||||
elsif '"'[0] == item[1][0] or '\''[0] == item[1][0]
|
||||
item[1][1 .. -2]
|
||||
else
|
||||
item[1]
|
||||
end
|
||||
@attr_hash[item[0].downcase] = val
|
||||
}
|
||||
end
|
||||
end
|
||||
@hashed = true
|
||||
end
|
||||
|
||||
#p self
|
||||
|
||||
@attr_hash
|
||||
end
|
||||
|
||||
# Get the 'alt' text for a tag, if it exists, or an empty string otherwise
|
||||
def text
|
||||
if !end_tag
|
||||
case tag_name
|
||||
when 'img'
|
||||
if !attr_hash['alt'].nil?
|
||||
return attr_hash['alt']
|
||||
end
|
||||
when 'applet'
|
||||
if !attr_hash['alt'].nil?
|
||||
return attr_hash['alt']
|
||||
end
|
||||
end
|
||||
end
|
||||
return ''
|
||||
end
|
||||
end
|
||||
|
||||
44
vendor/gems/ruby-openid-2.1.2/lib/openid/yadis/parsehtml.rb
vendored
Normal file
44
vendor/gems/ruby-openid-2.1.2/lib/openid/yadis/parsehtml.rb
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
require "openid/yadis/htmltokenizer"
|
||||
require 'cgi'
|
||||
|
||||
module OpenID
|
||||
module Yadis
|
||||
def Yadis.html_yadis_location(html)
|
||||
parser = HTMLTokenizer.new(html)
|
||||
|
||||
# to keep track of whether or not we are in the head element
|
||||
in_head = false
|
||||
|
||||
begin
|
||||
while el = parser.getTag('head', '/head', 'meta', 'body', '/body',
|
||||
'html', 'script')
|
||||
|
||||
# we are leaving head or have reached body, so we bail
|
||||
return nil if ['/head', 'body', '/body'].member?(el.tag_name)
|
||||
|
||||
if el.tag_name == 'head'
|
||||
unless el.to_s[-2] == ?/ # tag ends with a /: a short tag
|
||||
in_head = true
|
||||
end
|
||||
end
|
||||
next unless in_head
|
||||
|
||||
if el.tag_name == 'script'
|
||||
unless el.to_s[-2] == ?/ # tag ends with a /: a short tag
|
||||
parser.getTag('/script')
|
||||
end
|
||||
end
|
||||
|
||||
return nil if el.tag_name == 'html'
|
||||
|
||||
if el.tag_name == 'meta' and (equiv = el.attr_hash['http-equiv'])
|
||||
if ['x-xrds-location','x-yadis-location'].member?(equiv.downcase)
|
||||
return CGI::unescapeHTML(el.attr_hash['content'])
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue HTMLTokenizerError # just stop parsing if there's an error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
42
vendor/gems/ruby-openid-2.1.2/lib/openid/yadis/services.rb
vendored
Normal file
42
vendor/gems/ruby-openid-2.1.2/lib/openid/yadis/services.rb
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
|
||||
require 'openid/yadis/filters'
|
||||
require 'openid/yadis/discovery'
|
||||
require 'openid/yadis/xrds'
|
||||
|
||||
module OpenID
|
||||
module Yadis
|
||||
def Yadis.get_service_endpoints(input_url, flt=nil)
|
||||
# Perform the Yadis protocol on the input URL and return an
|
||||
# iterable of resulting endpoint objects.
|
||||
#
|
||||
# @param flt: A filter object or something that is convertable
|
||||
# to a filter object (using mkFilter) that will be used to
|
||||
# generate endpoint objects. This defaults to generating
|
||||
# BasicEndpoint objects.
|
||||
result = Yadis.discover(input_url)
|
||||
begin
|
||||
endpoints = Yadis.apply_filter(result.normalized_uri,
|
||||
result.response_text, flt)
|
||||
rescue XRDSError => err
|
||||
raise DiscoveryFailure.new(err.to_s, nil)
|
||||
end
|
||||
|
||||
return [result.normalized_uri, endpoints]
|
||||
end
|
||||
|
||||
def Yadis.apply_filter(normalized_uri, xrd_data, flt=nil)
|
||||
# Generate an iterable of endpoint objects given this input data,
|
||||
# presumably from the result of performing the Yadis protocol.
|
||||
|
||||
flt = Yadis.make_filter(flt)
|
||||
et = Yadis.parseXRDS(xrd_data)
|
||||
|
||||
endpoints = []
|
||||
each_service(et) { |service_element|
|
||||
endpoints += flt.get_service_endpoints(normalized_uri, service_element)
|
||||
}
|
||||
|
||||
return endpoints
|
||||
end
|
||||
end
|
||||
end
|
||||
155
vendor/gems/ruby-openid-2.1.2/lib/openid/yadis/xrds.rb
vendored
Normal file
155
vendor/gems/ruby-openid-2.1.2/lib/openid/yadis/xrds.rb
vendored
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
require 'rexml/document'
|
||||
require 'rexml/element'
|
||||
require 'rexml/xpath'
|
||||
|
||||
require 'openid/yadis/xri'
|
||||
|
||||
module OpenID
|
||||
module Yadis
|
||||
|
||||
XRD_NS_2_0 = 'xri://$xrd*($v*2.0)'
|
||||
XRDS_NS = 'xri://$xrds'
|
||||
|
||||
XRDS_NAMESPACES = {
|
||||
'xrds' => XRDS_NS,
|
||||
'xrd' => XRD_NS_2_0,
|
||||
}
|
||||
|
||||
class XRDSError < StandardError; end
|
||||
|
||||
# Raised when there's an assertion in the XRDS that it does not
|
||||
# have the authority to make.
|
||||
class XRDSFraud < XRDSError
|
||||
end
|
||||
|
||||
def Yadis::get_canonical_id(iname, xrd_tree)
|
||||
# Return the CanonicalID from this XRDS document.
|
||||
#
|
||||
# @param iname: the XRI being resolved.
|
||||
# @type iname: unicode
|
||||
#
|
||||
# @param xrd_tree: The XRDS output from the resolver.
|
||||
#
|
||||
# @returns: The XRI CanonicalID or None.
|
||||
# @returntype: unicode or None
|
||||
|
||||
xrd_list = []
|
||||
REXML::XPath::match(xrd_tree.root, '/xrds:XRDS/xrd:XRD', XRDS_NAMESPACES).each { |el|
|
||||
xrd_list << el
|
||||
}
|
||||
|
||||
xrd_list.reverse!
|
||||
|
||||
cid_elements = []
|
||||
|
||||
if !xrd_list.empty?
|
||||
xrd_list[0].elements.each { |e|
|
||||
if !e.respond_to?('name')
|
||||
next
|
||||
end
|
||||
if e.name == 'CanonicalID'
|
||||
cid_elements << e
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
cid_element = cid_elements[0]
|
||||
|
||||
if !cid_element
|
||||
return nil
|
||||
end
|
||||
|
||||
canonicalID = XRI.make_xri(cid_element.text)
|
||||
|
||||
childID = canonicalID.downcase
|
||||
|
||||
xrd_list[1..-1].each { |xrd|
|
||||
parent_sought = childID[0...childID.rindex('!')]
|
||||
|
||||
parent = XRI.make_xri(xrd.elements["CanonicalID"].text)
|
||||
|
||||
if parent_sought != parent.downcase
|
||||
raise XRDSFraud.new(sprintf("%s can not come from %s", parent_sought,
|
||||
parent))
|
||||
end
|
||||
|
||||
childID = parent_sought
|
||||
}
|
||||
|
||||
root = XRI.root_authority(iname)
|
||||
if not XRI.provider_is_authoritative(root, childID)
|
||||
raise XRDSFraud.new(sprintf("%s can not come from root %s", childID, root))
|
||||
end
|
||||
|
||||
return canonicalID
|
||||
end
|
||||
|
||||
class XRDSError < StandardError
|
||||
end
|
||||
|
||||
def Yadis::parseXRDS(text)
|
||||
if text.nil?
|
||||
raise XRDSError.new("Not an XRDS document.")
|
||||
end
|
||||
|
||||
begin
|
||||
d = REXML::Document.new(text)
|
||||
rescue RuntimeError => why
|
||||
raise XRDSError.new("Not an XRDS document. Failed to parse XML.")
|
||||
end
|
||||
|
||||
if is_xrds?(d)
|
||||
return d
|
||||
else
|
||||
raise XRDSError.new("Not an XRDS document.")
|
||||
end
|
||||
end
|
||||
|
||||
def Yadis::is_xrds?(xrds_tree)
|
||||
xrds_root = xrds_tree.root
|
||||
return (!xrds_root.nil? and
|
||||
xrds_root.name == 'XRDS' and
|
||||
xrds_root.namespace == XRDS_NS)
|
||||
end
|
||||
|
||||
def Yadis::get_yadis_xrd(xrds_tree)
|
||||
REXML::XPath.each(xrds_tree.root,
|
||||
'/xrds:XRDS/xrd:XRD[last()]',
|
||||
XRDS_NAMESPACES) { |el|
|
||||
return el
|
||||
}
|
||||
raise XRDSError.new("No XRD element found.")
|
||||
end
|
||||
|
||||
# aka iterServices in Python
|
||||
def Yadis::each_service(xrds_tree, &block)
|
||||
xrd = get_yadis_xrd(xrds_tree)
|
||||
xrd.each_element('Service', &block)
|
||||
end
|
||||
|
||||
def Yadis::services(xrds_tree)
|
||||
s = []
|
||||
each_service(xrds_tree) { |service|
|
||||
s << service
|
||||
}
|
||||
return s
|
||||
end
|
||||
|
||||
def Yadis::expand_service(service_element)
|
||||
es = service_element.elements
|
||||
uris = es.each('URI') { |u| }
|
||||
uris = prio_sort(uris)
|
||||
types = es.each('Type/text()')
|
||||
# REXML::Text objects are not strings.
|
||||
types = types.collect { |t| t.to_s }
|
||||
uris.collect { |uri| [types, uri.text, service_element] }
|
||||
end
|
||||
|
||||
# Sort a list of elements that have priority attributes.
|
||||
def Yadis::prio_sort(elements)
|
||||
elements.sort { |a,b|
|
||||
a.attribute('priority').to_s.to_i <=> b.attribute('priority').to_s.to_i
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
90
vendor/gems/ruby-openid-2.1.2/lib/openid/yadis/xri.rb
vendored
Normal file
90
vendor/gems/ruby-openid-2.1.2/lib/openid/yadis/xri.rb
vendored
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
require 'openid/yadis/xrds'
|
||||
require 'openid/fetchers'
|
||||
|
||||
module OpenID
|
||||
module Yadis
|
||||
module XRI
|
||||
|
||||
# The '(' is for cross-reference authorities, and hopefully has a
|
||||
# matching ')' somewhere.
|
||||
XRI_AUTHORITIES = ["!", "=", "@", "+", "$", "("]
|
||||
|
||||
def self.identifier_scheme(identifier)
|
||||
if (!identifier.nil? and
|
||||
identifier.length > 0 and
|
||||
(identifier.match('^xri://') or
|
||||
XRI_AUTHORITIES.member?(identifier[0].chr)))
|
||||
return :xri
|
||||
else
|
||||
return :uri
|
||||
end
|
||||
end
|
||||
|
||||
# Transform an XRI reference to an IRI reference. Note this is
|
||||
# not not idempotent, so do not apply this to an identifier more
|
||||
# than once. XRI Syntax section 2.3.1
|
||||
def self.to_iri_normal(xri)
|
||||
iri = xri.dup
|
||||
iri.insert(0, 'xri://') if not iri.match('^xri://')
|
||||
return escape_for_iri(iri)
|
||||
end
|
||||
|
||||
# Note this is not not idempotent, so do not apply this more than
|
||||
# once. XRI Syntax section 2.3.2
|
||||
def self.escape_for_iri(xri)
|
||||
esc = xri.dup
|
||||
# encode all %
|
||||
esc.gsub!(/%/, '%25')
|
||||
esc.gsub!(/\((.*?)\)/) { |xref_match|
|
||||
xref_match.gsub(/[\/\?\#]/) { |char_match|
|
||||
CGI::escape(char_match)
|
||||
}
|
||||
}
|
||||
return esc
|
||||
end
|
||||
|
||||
# Transform an XRI reference to a URI reference. Note this is not
|
||||
# not idempotent, so do not apply this to an identifier more than
|
||||
# once. XRI Syntax section 2.3.1
|
||||
def self.to_uri_normal(xri)
|
||||
return iri_to_uri(to_iri_normal(xri))
|
||||
end
|
||||
|
||||
# RFC 3987 section 3.1
|
||||
def self.iri_to_uri(iri)
|
||||
uri = iri.dup
|
||||
# for char in ucschar or iprivate
|
||||
# convert each char to %HH%HH%HH (as many %HH as octets)
|
||||
return uri
|
||||
end
|
||||
|
||||
def self.provider_is_authoritative(provider_id, canonical_id)
|
||||
lastbang = canonical_id.rindex('!')
|
||||
return false unless lastbang
|
||||
parent = canonical_id[0...lastbang]
|
||||
return parent == provider_id
|
||||
end
|
||||
|
||||
def self.root_authority(xri)
|
||||
xri = xri[6..-1] if xri.index('xri://') == 0
|
||||
authority = xri.split('/', 2)[0]
|
||||
if authority[0].chr == '('
|
||||
root = authority[0...authority.index(')')+1]
|
||||
elsif XRI_AUTHORITIES.member?(authority[0].chr)
|
||||
root = authority[0].chr
|
||||
else
|
||||
root = authority.split(/[!*]/)[0]
|
||||
end
|
||||
|
||||
self.make_xri(root)
|
||||
end
|
||||
|
||||
def self.make_xri(xri)
|
||||
if xri.index('xri://') != 0
|
||||
xri = 'xri://' + xri
|
||||
end
|
||||
return xri
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
106
vendor/gems/ruby-openid-2.1.2/lib/openid/yadis/xrires.rb
vendored
Normal file
106
vendor/gems/ruby-openid-2.1.2/lib/openid/yadis/xrires.rb
vendored
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
require "cgi"
|
||||
require "openid/yadis/xri"
|
||||
require "openid/yadis/xrds"
|
||||
require "openid/fetchers"
|
||||
|
||||
module OpenID
|
||||
|
||||
module Yadis
|
||||
|
||||
module XRI
|
||||
|
||||
class XRIHTTPError < StandardError; end
|
||||
|
||||
class ProxyResolver
|
||||
|
||||
DEFAULT_PROXY = 'http://proxy.xri.net/'
|
||||
|
||||
def initialize(proxy_url=nil)
|
||||
if proxy_url
|
||||
@proxy_url = proxy_url
|
||||
else
|
||||
@proxy_url = DEFAULT_PROXY
|
||||
end
|
||||
|
||||
@proxy_url += '/' unless @proxy_url.match('/$')
|
||||
end
|
||||
|
||||
def query_url(xri, service_type=nil)
|
||||
# URI normal form has a leading xri://, but we need to strip
|
||||
# that off again for the QXRI. This is under discussion for
|
||||
# XRI Resolution WD 11.
|
||||
qxri = XRI.to_uri_normal(xri)[6..-1]
|
||||
hxri = @proxy_url + qxri
|
||||
args = {'_xrd_r' => 'application/xrds+xml'}
|
||||
if service_type
|
||||
args['_xrd_t'] = service_type
|
||||
else
|
||||
# don't perform service endpoint selection
|
||||
args['_xrd_r'] += ';sep=false'
|
||||
end
|
||||
|
||||
return XRI.append_args(hxri, args)
|
||||
end
|
||||
|
||||
def query(xri, service_types)
|
||||
# these can be query args or http headers, needn't be both.
|
||||
# headers = {'Accept' => 'application/xrds+xml;sep=true'}
|
||||
canonicalID = nil
|
||||
|
||||
services = service_types.collect { |service_type|
|
||||
url = self.query_url(xri, service_type)
|
||||
begin
|
||||
response = OpenID.fetch(url)
|
||||
rescue
|
||||
raise XRIHTTPError, ["Could not fetch #{xri}", $!]
|
||||
end
|
||||
raise XRIHTTPError, "Could not fetch #{xri}" if response.nil?
|
||||
|
||||
xrds = Yadis::parseXRDS(response.body)
|
||||
canonicalID = Yadis::get_canonical_id(xri, xrds)
|
||||
|
||||
Yadis::services(xrds) unless xrds.nil?
|
||||
}
|
||||
# TODO:
|
||||
# * If we do get hits for multiple service_types, we're almost
|
||||
# certainly going to have duplicated service entries and
|
||||
# broken priority ordering.
|
||||
services = services.inject([]) { |flatter, some_services|
|
||||
flatter += some_services unless some_services.nil?
|
||||
}
|
||||
|
||||
return canonicalID, services
|
||||
end
|
||||
end
|
||||
|
||||
def self.urlencode(args)
|
||||
a = []
|
||||
args.each do |key, val|
|
||||
a << (CGI::escape(key) + "=" + CGI::escape(val))
|
||||
end
|
||||
a.join("&")
|
||||
end
|
||||
|
||||
def self.append_args(url, args)
|
||||
return url if args.length == 0
|
||||
|
||||
# rstrip question marks
|
||||
rstripped = url.dup
|
||||
while rstripped[-1].chr == '?'
|
||||
rstripped = rstripped[0...rstripped.length-1]
|
||||
end
|
||||
|
||||
if rstripped.index('?')
|
||||
sep = '&'
|
||||
else
|
||||
sep = '?'
|
||||
end
|
||||
|
||||
return url + sep + XRI.urlencode(args)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue