Class CloudKit::OAuthFilter

  1. lib/cloudkit/oauth_filter.rb
Parent: Object

An OAuthFilter provides both OAuth 1.0 support, plus OAuth Discovery.

Responds to the following URIs as part of the OAuth 1.0 “dance”:

/oauth/request_tokens
/oauth/authorization
/oauth/authorized_request_tokens/{id}
/oauth/access_tokens

Responds to the following URIs as part of OAuth Discovery:

/oauth
/oauth/meta

See also:

Included modules

  1. Util

Public class methods

new (app, options={})
[show source]
    # File lib/cloudkit/oauth_filter.rb, line 27
27:     def initialize(app, options={})
28:       @app     = app
29:       @options = options
30:     end

Public instance methods

call (env)
[show source]
    # File lib/cloudkit/oauth_filter.rb, line 32
32:     def call(env)
33:       @@lock.synchronize do
34:         @@store = OAuthStore.new
35:       end unless @@store
36: 
37:       request = Request.new(env)
38:       request.announce_auth(CLOUDKIT_OAUTH_FILTER_KEY)
39:       return xrds_location(request) if oauth_disco_draft2_xrds?(request)
40:       return @app.call(env) if request.path_info == '/'
41: 
42:       load_user_from_session(request)
43: 
44:       begin
45:         case request
46:         when r(:get, '/oauth/meta')
47:           get_meta(request)
48:         when r(:post, '/oauth/request_tokens', ['oauth_consumer_key'])
49:           create_request_token(request)
50:         when r(:get, '/oauth/authorization', ['oauth_token'])
51:           request_authorization(request)
52:         when r(:put, '/oauth/authorized_request_tokens/:id', ['submit' => 'Approve'])
53:           # Temporarily relying on a button value until pluggable templates are
54:           # introduced in 1.0.
55:           authorize_request_token(request)
56:         when r(:put, '/oauth/authorized_request_tokens/:id', ['submit' => 'Deny'])
57:           # See previous comment.
58:           deny_request_token(request)
59:         when r(:post, '/oauth/authorized_request_tokens/:id', [{'_method' => 'PUT'}])
60:           authorize_request_token(request)
61:         when r(:post, '/oauth/access_tokens')
62:           create_access_token(request)
63:         when r(:get, '/oauth')
64:           get_descriptor(request)
65:         else
66:           inject_user_or_challenge(request)
67:           @app.call(env)
68:         end
69:       rescue OAuth::Signature::UnknownSignatureMethod
70:         # The OAuth spec suggests a 400 status, but serving a 401 with the
71:         # meta/challenge info seems more appropriate as the OAuth metadata
72:         # specifies the supported signature methods, giving the user agent an
73:         # opportunity to fix the error.
74:         return challenge(request, 'unknown signature method')
75:       end
76:     end
store ()
[show source]
    # File lib/cloudkit/oauth_filter.rb, line 78
78:     def store; @@store; end

Protected instance methods

authorize_request_token (request)
[show source]
     # File lib/cloudkit/oauth_filter.rb, line 116
116:     def authorize_request_token(request)
117:       return login_redirect(request) unless request.current_user
118: 
119:       request_token_response = @@store.get("/cloudkit_oauth_request_tokens/#{request.last_path_element}")
120:       request_token = request_token_response.parsed_content
121:       if request_token['authorized_at']
122:         return challenge(request, 'invalid request token')
123:       end
124: 
125:       request_token['user_id']       = request.current_user
126:       request_token['authorized_at'] = Time.now.httpdate
127:       json                           = JSON.generate(request_token)
128:       @@store.put(
129:         "/cloudkit_oauth_request_tokens/#{request.last_path_element}",
130:         :etag => request_token_response.etag,
131:         :json => json)
132:       erb(request, :authorize_request_token)
133:     end
challenge (request, message='')
[show source]
     # File lib/cloudkit/oauth_filter.rb, line 220
220:     def challenge(request, message='')
221:       Rack::Response.new(message, 401, challenge_headers(request)).finish
222:     end
challenge_headers (request)
[show source]
     # File lib/cloudkit/oauth_filter.rb, line 224
224:     def challenge_headers(request)
225:       {
226:         'WWW-Authenticate' => "OAuth realm=\"http://#{request.env['HTTP_HOST']}\"",
227:         'Link' => discovery_link(request),
228:         'Content-Type' => 'application/json'
229:       }
230:     end
create_access_token (request)
[show source]
     # File lib/cloudkit/oauth_filter.rb, line 145
145:     def create_access_token(request)
146:       return challenge(request, 'invalid nonce') unless valid_nonce?(request)
147: 
148:       consumer_response = @@store.get("/cloudkit_oauth_consumers/#{request[:oauth_consumer_key]}")
149:       unless consumer_response.status == 200
150:         return challenge(request, 'invalid consumer')
151:       end
152: 
153:       consumer = consumer_response.parsed_content
154:       request_token_response = @@store.get("/cloudkit_oauth_request_tokens/#{request[:oauth_token]}")
155:       unless request_token_response.status == 200
156:         return challenge(request, 'invalid request token')
157:       end
158: 
159:       request_token = request_token_response.parsed_content
160:       unless request_token['consumer_key'] == request[:oauth_consumer_key]
161:         return challenge(request, 'invalid consumer')
162:       end
163: 
164:       signature = OAuth::Signature.build(request) do
165:         [request_token['secret'], consumer['secret']]
166:       end
167:       unless signature.verify
168:         return challenge(request, 'invalid signature')
169:       end
170: 
171:       token_id, secret = OAuth::Server.new(request.host).generate_credentials
172:       token_data = JSON.generate(
173:         :secret          => secret,
174:         :consumer_key    => request[:oauth_consumer_key],
175:         :consumer_secret => consumer['secret'],
176:         :user_id         => request_token['user_id'])
177:       @@store.put("/cloudkit_oauth_tokens/#{token_id}", :json => token_data)
178:       @@store.delete(
179:         "/cloudkit_oauth_request_tokens/#{request[:oauth_token]}",
180:         :etag => request_token_response.etag)
181:       Rack::Response.new("oauth_token=#{token_id}&oauth_token_secret=#{secret}", 201).finish
182:     end
create_request_token (request)
[show source]
     # File lib/cloudkit/oauth_filter.rb, line 82
 82:     def create_request_token(request)
 83:       return challenge(request, 'invalid nonce') unless valid_nonce?(request)
 84: 
 85:       consumer_result = @@store.get("/cloudkit_oauth_consumers/#{request[:oauth_consumer_key]}")
 86:       unless consumer_result.status == 200
 87:         return challenge(request, 'invalid consumer')
 88:       end
 89: 
 90:       consumer  = consumer_result.parsed_content
 91:       signature = OAuth::Signature.build(request) { [nil, consumer['secret']] }
 92:       return challenge(request, 'invalid signature') unless signature.verify
 93: 
 94:       token_id, secret = OAuth::Server.new(request.host).generate_credentials
 95:       request_token = JSON.generate(
 96:         :secret       => secret,
 97:         :consumer_key => request[:oauth_consumer_key])
 98:       @@store.put(
 99:         "/cloudkit_oauth_request_tokens/#{token_id}",
100:         :json => request_token)
101:       Rack::Response.new("oauth_token=#{token_id}&oauth_token_secret=#{secret}", 201).finish
102:     end
deny_request_token (request)
[show source]
     # File lib/cloudkit/oauth_filter.rb, line 135
135:     def deny_request_token(request)
136:       return login_redirect(request) unless request.current_user
137: 
138:       request_token_response = @@store.get("/cloudkit_oauth_request_tokens/#{request.last_path_element}")
139:       @@store.delete(
140:         "/cloudkit_oauth_request_tokens/#{request.last_path_element}",
141:         :etag => request_token_response.etag)
142:       erb(request, :request_token_denied)
143:     end
discovery_link (request)
[show source]
     # File lib/cloudkit/oauth_filter.rb, line 232
232:     def discovery_link(request)
233:       "<#{request.scheme}://#{request.env['HTTP_HOST']}/oauth/meta>; rel=\"http://oauth.net/discovery/1.0/rel/provider\""
234:     end
get_descriptor (request)
[show source]
     # File lib/cloudkit/oauth_filter.rb, line 262
262:     def get_descriptor(request)
263:       erb(request, :oauth_descriptor, {'Content-Type' => 'application/xrds+xml'})
264:     end
get_meta (request)
[show source]
     # File lib/cloudkit/oauth_filter.rb, line 245
245:     def get_meta(request)
246:       # Expected in next OAuth Discovery Draft
247:       erb(request, :oauth_meta)
248:     end
inject_challenge (request)
[show source]
     # File lib/cloudkit/oauth_filter.rb, line 216
216:     def inject_challenge(request)
217:       request.env[CLOUDKIT_AUTH_CHALLENGE] = challenge_headers(request)
218:     end
inject_user_or_challenge (request)
[show source]
     # File lib/cloudkit/oauth_filter.rb, line 184
184:     def inject_user_or_challenge(request)
185:       unless valid_nonce?(request)
186:         request.current_user = ''
187:         inject_challenge(request)
188:         return
189:       end
190: 
191:       result       = @@store.get("/cloudkit_oauth_tokens/#{request[:oauth_token]}")
192:       access_token = result.parsed_content
193:       signature    = OAuth::Signature.build(request) do
194:         [access_token['secret'], access_token['consumer_secret']]
195:       end
196:       if signature.verify
197:         request.current_user = access_token['user_id']
198:       else
199:         request.current_user = ''
200:         inject_challenge(request)
201:       end
202:     end
load_user_from_session (request)
[show source]
     # File lib/cloudkit/oauth_filter.rb, line 241
241:     def load_user_from_session(request)
242:       request.current_user = request.session['user_uri'] if request.session
243:     end
login_redirect (request)
[show source]
     # File lib/cloudkit/oauth_filter.rb, line 236
236:     def login_redirect(request)
237:       request.session['return_to'] = request.url if request.session
238:       Rack::Response.new([], 302, {'Location' => request.login_url}).finish
239:     end
oauth_disco_draft2_xrds? (request)
[show source]
     # File lib/cloudkit/oauth_filter.rb, line 250
250:     def oauth_disco_draft2_xrds?(request)
251:       # Current OAuth Discovery Draft 2 / XRDS-Simple 1.0, Section 5.1.2
252:       request.get? &&
253:         request.env['HTTP_ACCEPT'] &&
254:         request.env['HTTP_ACCEPT'].match(/application\/xrds\+xml/)
255:     end
request_authorization (request)
[show source]
     # File lib/cloudkit/oauth_filter.rb, line 104
104:     def request_authorization(request)
105:       return login_redirect(request) unless request.current_user
106: 
107:       request_token_result = @@store.get("/cloudkit_oauth_request_tokens/#{request[:oauth_token]}")
108:       unless request_token_result.status == 200
109:         return challenge(request, 'invalid request token')
110:       end
111: 
112:       request_token = request_token_result.parsed_content
113:       erb(request, :request_authorization)
114:     end
valid_nonce? (request)
[show source]
     # File lib/cloudkit/oauth_filter.rb, line 204
204:     def valid_nonce?(request)
205:       timestamp = request[:oauth_timestamp]
206:       nonce     = request[:oauth_nonce]
207:       return false unless (timestamp && nonce)
208: 
209:       uri    = "/cloudkit_oauth_nonces/#{timestamp},#{nonce}"
210:       result = @@store.put(uri, :json => '{}')
211:       return false unless result.status == 201
212: 
213:       true
214:     end
xrds_location (request)
[show source]
     # File lib/cloudkit/oauth_filter.rb, line 257
257:     def xrds_location(request)
258:       # Current OAuth Discovery Draft 2 / XRDS-Simple 1.0, Section 5.1.2
259:       Rack::Response.new([], 200, {'X-XRDS-Location' => "#{request.scheme}://#{request.env['HTTP_HOST']}/oauth"}).finish
260:     end