To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.

Statistics Download as Zip
| Branch: | Tag: | Revision:

root / .svn / pristine / a4 / a494e5a07ce50a0c4f746379917023ec9e432959.svn-base @ 1297:0a574315af3e

History | View | Annotate | Download (7.93 KB)

1
require 'uri'
2
require 'openid/extensions/sreg'
3
require 'openid/extensions/ax'
4
require 'openid/store/filesystem'
5

    
6
require File.dirname(__FILE__) + '/open_id_authentication/db_store'
7
require File.dirname(__FILE__) + '/open_id_authentication/mem_cache_store'
8
require File.dirname(__FILE__) + '/open_id_authentication/request'
9
require File.dirname(__FILE__) + '/open_id_authentication/timeout_fixes' if OpenID::VERSION == "2.0.4"
10

    
11
module OpenIdAuthentication
12
  OPEN_ID_AUTHENTICATION_DIR = RAILS_ROOT + "/tmp/openids"
13

    
14
  def self.store
15
    @@store
16
  end
17

    
18
  def self.store=(*store_option)
19
    store, *parameters = *([ store_option ].flatten)
20

    
21
    @@store = case store
22
    when :db
23
      OpenIdAuthentication::DbStore.new
24
    when :mem_cache
25
      OpenIdAuthentication::MemCacheStore.new(*parameters)
26
    when :file
27
      OpenID::Store::Filesystem.new(OPEN_ID_AUTHENTICATION_DIR)
28
    else
29
      raise "Unknown store: #{store}"
30
    end
31
  end
32

    
33
  self.store = :db
34

    
35
  class InvalidOpenId < StandardError
36
  end
37

    
38
  class Result
39
    ERROR_MESSAGES = {
40
      :missing      => "Sorry, the OpenID server couldn't be found",
41
      :invalid      => "Sorry, but this does not appear to be a valid OpenID",
42
      :canceled     => "OpenID verification was canceled",
43
      :failed       => "OpenID verification failed",
44
      :setup_needed => "OpenID verification needs setup"
45
    }
46

    
47
    def self.[](code)
48
      new(code)
49
    end
50

    
51
    def initialize(code)
52
      @code = code
53
    end
54

    
55
    def status
56
      @code
57
    end
58

    
59
    ERROR_MESSAGES.keys.each { |state| define_method("#{state}?") { @code == state } }
60

    
61
    def successful?
62
      @code == :successful
63
    end
64

    
65
    def unsuccessful?
66
      ERROR_MESSAGES.keys.include?(@code)
67
    end
68

    
69
    def message
70
      ERROR_MESSAGES[@code]
71
    end
72
  end
73

    
74
  # normalizes an OpenID according to http://openid.net/specs/openid-authentication-2_0.html#normalization
75
  def self.normalize_identifier(identifier)
76
    # clean up whitespace
77
    identifier = identifier.to_s.strip
78

    
79
    # if an XRI has a prefix, strip it.
80
    identifier.gsub!(/xri:\/\//i, '')
81

    
82
    # dodge XRIs -- TODO: validate, don't just skip.
83
    unless ['=', '@', '+', '$', '!', '('].include?(identifier.at(0))
84
      # does it begin with http?  if not, add it.
85
      identifier = "http://#{identifier}" unless identifier =~ /^http/i
86

    
87
      # strip any fragments
88
      identifier.gsub!(/\#(.*)$/, '')
89

    
90
      begin
91
        uri = URI.parse(identifier)
92
        uri.scheme = uri.scheme.downcase if uri.scheme # URI should do this
93
        identifier = uri.normalize.to_s
94
      rescue URI::InvalidURIError
95
        raise InvalidOpenId.new("#{identifier} is not an OpenID identifier")
96
      end
97
    end
98

    
99
    return identifier
100
  end
101

    
102
  # deprecated for OpenID 2.0, where not all OpenIDs are URLs
103
  def self.normalize_url(url)
104
    ActiveSupport::Deprecation.warn "normalize_url has been deprecated, use normalize_identifier instead"
105
    self.normalize_identifier(url)
106
  end
107

    
108
  protected
109
    def normalize_url(url)
110
      OpenIdAuthentication.normalize_url(url)
111
    end
112

    
113
    def normalize_identifier(url)
114
      OpenIdAuthentication.normalize_identifier(url)
115
    end
116

    
117
    # The parameter name of "openid_identifier" is used rather than the Rails convention "open_id_identifier"
118
    # because that's what the specification dictates in order to get browser auto-complete working across sites
119
    def using_open_id?(identity_url = nil) #:doc:
120
      identity_url ||= params[:openid_identifier] || params[:openid_url]
121
      !identity_url.blank? || params[:open_id_complete]
122
    end
123

    
124
    def authenticate_with_open_id(identity_url = nil, options = {}, &block) #:doc:
125
      identity_url ||= params[:openid_identifier] || params[:openid_url]
126

    
127
      if params[:open_id_complete].nil?
128
        begin_open_id_authentication(identity_url, options, &block)
129
      else
130
        complete_open_id_authentication(&block)
131
      end
132
    end
133

    
134
  private
135
    def begin_open_id_authentication(identity_url, options = {})
136
      identity_url = normalize_identifier(identity_url)
137
      return_to    = options.delete(:return_to)
138
      method       = options.delete(:method)
139
      
140
      options[:required] ||= []  # reduces validation later
141
      options[:optional] ||= []
142

    
143
      open_id_request = open_id_consumer.begin(identity_url)
144
      add_simple_registration_fields(open_id_request, options)
145
      add_ax_fields(open_id_request, options)
146
      redirect_to(open_id_redirect_url(open_id_request, return_to, method))
147
    rescue OpenIdAuthentication::InvalidOpenId => e
148
      yield Result[:invalid], identity_url, nil
149
    rescue OpenID::OpenIDError, Timeout::Error => e
150
      logger.error("[OPENID] #{e}")
151
      yield Result[:missing], identity_url, nil
152
    end
153

    
154
    def complete_open_id_authentication
155
      params_with_path = params.reject { |key, value| request.path_parameters[key] }
156
      params_with_path.delete(:format)
157
      open_id_response = timeout_protection_from_identity_server { open_id_consumer.complete(params_with_path, requested_url) }
158
      identity_url     = normalize_identifier(open_id_response.display_identifier) if open_id_response.display_identifier
159

    
160
      case open_id_response.status
161
      when OpenID::Consumer::SUCCESS
162
        profile_data = {}
163

    
164
        # merge the SReg data and the AX data into a single hash of profile data
165
        [ OpenID::SReg::Response, OpenID::AX::FetchResponse ].each do |data_response|
166
          if data_response.from_success_response( open_id_response )
167
            profile_data.merge! data_response.from_success_response( open_id_response ).data
168
          end
169
        end
170
        
171
        yield Result[:successful], identity_url, profile_data
172
      when OpenID::Consumer::CANCEL
173
        yield Result[:canceled], identity_url, nil
174
      when OpenID::Consumer::FAILURE
175
        yield Result[:failed], identity_url, nil
176
      when OpenID::Consumer::SETUP_NEEDED
177
        yield Result[:setup_needed], open_id_response.setup_url, nil
178
      end
179
    end
180

    
181
    def open_id_consumer
182
      OpenID::Consumer.new(session, OpenIdAuthentication.store)
183
    end
184

    
185
    def add_simple_registration_fields(open_id_request, fields)
186
      sreg_request = OpenID::SReg::Request.new
187
      
188
      # filter out AX identifiers (URIs)
189
      required_fields = fields[:required].collect { |f| f.to_s unless f =~ /^https?:\/\// }.compact
190
      optional_fields = fields[:optional].collect { |f| f.to_s unless f =~ /^https?:\/\// }.compact
191
      
192
      sreg_request.request_fields(required_fields, true) unless required_fields.blank?
193
      sreg_request.request_fields(optional_fields, false) unless optional_fields.blank?
194
      sreg_request.policy_url = fields[:policy_url] if fields[:policy_url]
195
      open_id_request.add_extension(sreg_request)
196
    end
197
    
198
    def add_ax_fields( open_id_request, fields )
199
      ax_request = OpenID::AX::FetchRequest.new
200
      
201
      # look through the :required and :optional fields for URIs (AX identifiers)
202
      fields[:required].each do |f|
203
        next unless f =~ /^https?:\/\//
204
        ax_request.add( OpenID::AX::AttrInfo.new( f, nil, true ) )
205
      end
206

    
207
      fields[:optional].each do |f|
208
        next unless f =~ /^https?:\/\//
209
        ax_request.add( OpenID::AX::AttrInfo.new( f, nil, false ) )
210
      end
211
      
212
      open_id_request.add_extension( ax_request )
213
    end
214
        
215
    def open_id_redirect_url(open_id_request, return_to = nil, method = nil)
216
      open_id_request.return_to_args['_method'] = (method || request.method).to_s
217
      open_id_request.return_to_args['open_id_complete'] = '1'
218
      open_id_request.redirect_url(root_url, return_to || requested_url)
219
    end
220

    
221
    def requested_url
222
      relative_url_root = self.class.respond_to?(:relative_url_root) ?
223
        self.class.relative_url_root.to_s :
224
        request.relative_url_root
225
      "#{request.protocol}#{request.host_with_port}#{relative_url_root}#{request.path}"
226
    end
227

    
228
    def timeout_protection_from_identity_server
229
      yield
230
    rescue Timeout::Error
231
      Class.new do
232
        def status
233
          OpenID::FAILURE
234
        end
235

    
236
        def msg
237
          "Identity server timed out"
238
        end
239
      end.new
240
    end
241
end