support for parsing code from signed request cookie
Mark Dodwell
12 years ago
65 | 65 | end |
66 | 66 | |
67 | 67 | def build_access_token |
68 | super.tap do |token| | |
69 | token.options.merge!(access_token_options) | |
68 | with_code(request.params['code'] || signed_request && signed_request['code']) do | |
69 | super.tap do |token| | |
70 | token.options.merge!(access_token_options) | |
71 | end | |
70 | 72 | end |
71 | 73 | end |
72 | 74 | |
80 | 82 | params[:scope] ||= DEFAULT_SCOPE |
81 | 83 | end |
82 | 84 | end |
85 | ||
86 | def signed_request | |
87 | @signed_request ||= begin | |
88 | cookie = request.cookies["fbsr_#{client.id}"] and | |
89 | parse_signed_request(cookie) | |
90 | end | |
91 | end | |
83 | 92 | |
84 | 93 | private |
94 | ||
95 | def with_code(code) | |
96 | original_code = request.params['code'] | |
97 | begin | |
98 | request.params['code'] = code | |
99 | yield | |
100 | ensure | |
101 | request.params['code'] = original_code | |
102 | end | |
103 | end | |
85 | 104 | |
86 | 105 | def prune!(hash) |
87 | 106 | hash.delete_if do |_, value| |
89 | 108 | value.nil? || (value.respond_to?(:empty?) && value.empty?) |
90 | 109 | end |
91 | 110 | end |
111 | ||
112 | def parse_signed_request(value) | |
113 | signature, encoded_payload = value.split('.') | |
114 | ||
115 | decoded_hex_signature = base64_decode_url(signature)#.unpack('H*') | |
116 | decoded_payload = MultiJson.decode(base64_decode_url(encoded_payload)) | |
117 | ||
118 | unless decoded_payload['algorithm'] == 'HMAC-SHA256' | |
119 | raise NotImplementedError, "unkown algorithm: #{decoded_payload['algorithm']}" | |
120 | end | |
121 | ||
122 | if valid_signature?(client.secret, decoded_hex_signature, encoded_payload) | |
123 | decoded_payload | |
124 | end | |
125 | end | |
126 | ||
127 | def valid_signature?(secret, signature, payload, algorithm = OpenSSL::Digest::SHA256.new) | |
128 | OpenSSL::HMAC.digest(algorithm, secret, payload) == signature | |
129 | end | |
130 | ||
131 | def base64_decode_url(value) | |
132 | value += '=' * (4 - value.size.modulo(4)) | |
133 | Base64.decode64(value.tr('-_', '+/')) | |
134 | end | |
92 | 135 | end |
93 | 136 | end |
94 | 137 | end |
0 | 0 | require 'spec_helper' |
1 | 1 | require 'omniauth-facebook' |
2 | require 'openssl' | |
3 | require 'base64' | |
2 | 4 | |
3 | 5 | describe OmniAuth::Strategies::Facebook do |
4 | 6 | before :each do |
5 | 7 | @request = double('Request') |
6 | 8 | @request.stub(:params) { {} } |
9 | @request.stub(:cookies) { {} } | |
10 | ||
11 | @client_id = '123' | |
12 | @client_secret = '53cr3tz' | |
7 | 13 | end |
8 | 14 | |
9 | 15 | subject do |
10 | OmniAuth::Strategies::Facebook.new(nil, @options || {}).tap do |strategy| | |
16 | args = [@client_id, @client_secret, @options].compact | |
17 | OmniAuth::Strategies::Facebook.new(nil, *args).tap do |strategy| | |
11 | 18 | strategy.stub(:request) { @request } |
12 | 19 | end |
13 | 20 | end |
270 | 277 | subject.extra.should eq({ 'raw_info' => @raw_info }) |
271 | 278 | end |
272 | 279 | end |
280 | ||
281 | describe '#signed_request' do | |
282 | context 'cookie not present' do | |
283 | it 'is nil' do | |
284 | subject.send(:signed_request).should be_nil | |
285 | end | |
286 | end | |
287 | ||
288 | context 'cookie present' do | |
289 | before :each do | |
290 | @payload = { | |
291 | 'algorithm' => 'HMAC-SHA256', | |
292 | 'code' => 'm4c0d3z', | |
293 | 'issued_at' => Time.now.to_i, | |
294 | 'user_id' => '123456' | |
295 | } | |
296 | ||
297 | @request.stub(:cookies) do | |
298 | { "fbsr_#{@client_id}" => signed_request(@payload, @client_secret) } | |
299 | end | |
300 | end | |
301 | ||
302 | it 'parses the access code out from the cookie' do | |
303 | subject.send(:signed_request).should eq(@payload) | |
304 | end | |
305 | end | |
306 | end | |
307 | ||
308 | private | |
309 | ||
310 | def signed_request(payload, secret) | |
311 | encoded_payload = base64_encode_url(MultiJson.encode(payload)) | |
312 | encoded_signature = base64_encode_url(signature(encoded_payload, secret)) | |
313 | [encoded_signature, encoded_payload].join('.') | |
314 | end | |
315 | ||
316 | def base64_encode_url(value) | |
317 | Base64.encode64(value).tr('+/', '-_').gsub(/\n/, '') | |
318 | end | |
319 | ||
320 | def signature(payload, secret, algorithm = OpenSSL::Digest::SHA256.new) | |
321 | OpenSSL::HMAC.digest(algorithm, secret, payload) | |
322 | end | |
273 | 323 | end |