Imported Upstream version 1.6.0
Nitesh A Jain
9 years ago
0 | before_install: | |
1 | - gem update bundler | |
2 | - bundle --version | |
3 | - gem update --system 2.1.11 | |
4 | - gem --version | |
0 | 5 | rvm: |
1 | 6 | - 1.8.7 |
2 | 7 | - 1.9.2 |
3 | 8 | - 1.9.3 |
9 | - 2.0.0 | |
4 | 10 | - jruby |
5 | branches: | |
6 | only: | |
7 | - master |
0 | ## 1.6.0 (2014-01-13) | |
1 | ||
2 | Features: | |
3 | ||
4 | - ability to specify `auth_type` per-request (#78, @sebastian-stylesaint) | |
5 | - image dimension can be set using `image_size` option (#91, @weilu) | |
6 | - update Facebook authorize URL to fix broken authorization (#103, @dlackty) | |
7 | - adds `info_fields` option (#109, @bloudermilk) | |
8 | - adds `locale` parameter (#133, @donbobka, @simi) | |
9 | - add automatically `appsecret_proof` (#140, @nlsrchtr, @simi) | |
10 | ||
11 | Changes: | |
12 | ||
13 | - `NoAuthorizationCodeError` and `UnknownSignatureAlgorithmError` will now `fail!` (#117, @nchelluri) | |
14 | - don't try to parse the signature if it's nil (#127, @oriolgual) | |
15 | ||
16 | ## 1.5.1 (2013-11-18) | |
17 | ||
18 | Changes: | |
19 | ||
20 | - don't use `access_token` in URL [CVE-2013-4593](https://github.com/mkdynamic/omniauth-facebook/wiki/Access-token-vulnerability:-CVE-2013-4593) (@homakov, @mkdynamic, @simi) | |
21 | ||
22 | ## 1.5.0 (2013-11-13) | |
23 | ||
24 | Changes: | |
25 | ||
26 | - remove `state` param to fix CSRF vulnerabilty [CVE-2013-4562](https://github.com/mkdynamic/omniauth-facebook/wiki/CSRF-vulnerability:-CVE-2013-4562) (@homakov, @mkdynamic, @simi) | |
27 | ||
28 | ## 1.4.1 (2012-07-07) | |
29 | ||
30 | Changes: | |
31 | ||
32 | - update to omniauth-oauth2 1.1.0 for csrf protection (@mkdynamic) | |
33 | ||
34 | ## 1.4.0 (2012-06-24) | |
35 | ||
36 | Features: | |
37 | ||
38 | - obey `skip_info?` config (@mkdynamic) | |
39 | - add support of the `:auth_type` option to `:authorize_options` (#58, @JHeidinga, @mkdynamic) | |
40 | - support `access_token` parameter as part of the callback request (#62, @steverandy) | |
41 | ||
42 | ## 1.3.0 (2012-05-05) | |
43 | ||
44 | Features: | |
45 | ||
46 | - dynamic permissions in the auth params (#30, @famoseagle) | |
47 | - add support for facebook canvas (@mkdynamic) | |
48 | - add verified key to the info hash (#34, @ryansobol) | |
49 | - add option to use secure url for image in auth hash (@mkdynamic) | |
50 | - add option to specify image size (@mkdynamic) | |
51 | ||
52 | Changes: | |
53 | ||
54 | - have `raw_info` return an empty hash if the Facebook response returns false (#44, @brianjlandau) | |
55 | - prevent oauth2 from interpreting Facebook's expires field as `expires_in`, when it's really `expires_at` (#39, @watsonbox) | |
56 | - remove deprecated `offline_access` permission (@mkdynamic) | |
57 | ||
58 | Changes: | |
59 | ||
60 | - tidy up the `callback_url` option (@mkdynamic) | |
61 | ||
62 | ## 1.2.0 (2012-01-06) | |
63 | ||
64 | Features: | |
65 | ||
66 | - add `state` to authorization params (#19, @GermanDZ) | |
67 | ||
68 | Changes: | |
69 | ||
70 | - lock to `rack ~> 1.3.6` (@mkdynamic) | |
71 | ||
72 | ## 1.1.0 (2011-12-10) | |
73 | ||
74 | Features: | |
75 | ||
76 | - add `callback_url` option (#13, @gumayunov) | |
77 | - support for parsing code from signed request cookie (client-side flow) (@mkdynamic) | |
78 | ||
79 | ## 1.0.0 (2011-11-19) | |
80 | ||
81 | Features: | |
82 | ||
83 | - allow passing of display via option (@mkdynamic) | |
84 | ||
85 | Bugfixes: | |
86 | ||
87 | - fix `ten_mins_from_now` calculation (#7, @olegkovalenko) | |
88 | ||
89 | ## 1.0.0.rc2 (2011-11-11) | |
90 | ||
91 | Features: | |
92 | ||
93 | - allow passing `display` parameter (@mkdynamic) | |
94 | - included default scope (@mkdynamic) | |
95 | ||
96 | ## 1.0.0.rc1 (2011-10-29) | |
97 | ||
98 | - first public gem release (@mkdynamic) |
0 | # OmniAuth Facebook [](http://travis-ci.org/mkdynamic/omniauth-facebook) | |
0 | **NOTE: If you're running < 1.5.1, please upgrade to address 2 security vulnerabilities. | |
1 | More details [here](https://github.com/mkdynamic/omniauth-facebook/wiki/CSRF-vulnerability:-CVE-2013-4562) and [here](https://github.com/mkdynamic/omniauth-facebook/wiki/Access-token-vulnerability:-CVE-2013-4593).** | |
1 | 2 | |
2 | Facebook OAuth2 Strategy for OmniAuth 1.0. | |
3 | --- | |
4 | ||
5 | # OmniAuth Facebook [](https://travis-ci.org/mkdynamic/omniauth-facebook) | |
6 | ||
7 | Facebook OAuth2 Strategy for OmniAuth. | |
3 | 8 | |
4 | 9 | Supports the OAuth 2.0 server-side and client-side flows. Read the Facebook docs for more details: http://developers.facebook.com/docs/authentication |
5 | 10 | |
15 | 20 | |
16 | 21 | ## Usage |
17 | 22 | |
18 | `OmniAuth::Strategies::Facebook` is simply a Rack middleware. Read the OmniAuth 1.0 docs for detailed instructions: https://github.com/intridea/omniauth. | |
23 | `OmniAuth::Strategies::Facebook` is simply a Rack middleware. Read the OmniAuth docs for detailed instructions: https://github.com/intridea/omniauth. | |
19 | 24 | |
20 | 25 | Here's a quick example, adding the middleware to a Rails app in `config/initializers/omniauth.rb`: |
21 | 26 | |
36 | 41 | * `auth_type`: Optionally specifies the requested authentication features as a comma-separated list, as per https://developers.facebook.com/docs/authentication/reauthentication/. |
37 | 42 | Valid values are `https` (checks for the presence of the secure cookie and asks for re-authentication if it is not present), and `reauthenticate` (asks the user to re-authenticate unconditionally). Default is `nil`. |
38 | 43 | * `secure_image_url`: Set to `true` to use https for the avatar image url returned in the auth hash. Default is `false`. |
39 | * `image_size`: Set the size for the returned image url in the auth hash. Valid options are `square` (50x50), `small` (50 pixels wide, variable height), `normal` (100 pixels wide, variable height), or `large` (about 200 pixels wide, variable height). Default is `square` (50x50). | |
44 | * `image_size`: Set the size for the returned image url in the auth hash. Valid options include `square` (50x50), `small` (50 pixels wide, variable height), `normal` (100 pixels wide, variable height), or `large` (about 200 pixels wide, variable height). Additionally, you can request a picture of a specific size by setting this option to a hash with `:width` and `:height` as keys. This will return an available profile picture closest to the requested size and requested aspect ratio. If only `:width` or `:height` is specified, we will return a picture whose width or height is closest to the requested size, respectively. | |
45 | * `info_fields`: Specify exactly which fields should be returned when getting the user's info. Value should be a comma-separated string as per https://developers.facebook.com/docs/reference/api/user/ (only /me endpoint). | |
46 | * `locale`: Specify locale which should be used when getting the user's info. Value should be locale string as per https://developers.facebook.com/docs/reference/api/locale/. | |
40 | 47 | |
41 | 48 | For example, to request `email`, `user_birthday` and `read_stream` permissions and display the authentication page in a popup window: |
42 | 49 | |
49 | 56 | |
50 | 57 | ### Per-Request Options |
51 | 58 | |
52 | If you want to set the `display` format or `scope` on a per-request basis, you can just pass it to the OmniAuth request phase URL, for example: `/auth/facebook?display=popup` or `/auth/facebook?scope=email`. | |
53 | ||
54 | You can also pass through a `state` param which will be passed along to the callback url. | |
59 | If you want to set the `display` format, `auth_type`, or `scope` on a per-request basis, you can just pass it to the OmniAuth request phase URL, for example: `/auth/facebook?display=popup` or `/auth/facebook?scope=email`. | |
55 | 60 | |
56 | 61 | ### Custom Callback URL/Path |
57 | 62 | |
135 | 140 | |
136 | 141 | Take a look at [the example Sinatra app for one option of how you can integrate with a canvas page](https://github.com/mkdynamic/omniauth-facebook/blob/master/example/config.ru). |
137 | 142 | |
138 | Bear in mind you have several options (including [authenticated referrals](https://developers.facebook.com/docs/opengraph/authentication/#referrals)). Read [the Facebook docs on canvas page authentication](https://developers.facebook.com/docs/authentication/canvas/) for more info. | |
143 | Bear in mind you have several [options](https://developers.facebook.com/docs/opengraph/authentication). Read [the Facebook docs on canvas page authentication](https://developers.facebook.com/docs/authentication/canvas/) for more info. | |
139 | 144 | |
140 | 145 | ## Token Expiry |
141 | 146 | |
149 | 154 | |
150 | 155 | ### Server-Side Flow |
151 | 156 | |
152 | If you use the server-side flow, Facebook will give you back a longer loved access token (~ 60 days). | |
157 | If you use the server-side flow, Facebook will give you back a longer lived access token (~ 60 days). | |
153 | 158 | |
154 | 159 | If you're having issue getting a long lived token with the server-side flow, make sure to enable the 'deprecate offline_access setting' in you Facebook app config. Read the [Facebook docs about the offline_access deprecation](https://developers.facebook.com/roadmap/offline-access-removal/) for more information. |
155 | 160 | |
157 | 162 | |
158 | 163 | Actively tested with the following Ruby versions: |
159 | 164 | |
165 | - MRI 2.0.0 | |
160 | 166 | - MRI 1.9.3 |
161 | 167 | - MRI 1.9.2 |
162 | 168 | - MRI 1.8.7 |
163 | - JRuby 1.6.5 | |
169 | - JRuby 1.7.4 | |
164 | 170 | |
165 | *NB.* For JRuby, you'll need to install the `jruby-openssl` gem. There's no way to automatically specify this in a Rubygem gemspec, so you need to manually add it your project's own Gemfile: | |
171 | *NB.* For JRuby < 1.7, you'll need to install the `jruby-openssl` gem. There's no way to automatically specify this in a Rubygem gemspec, so you need to manually add it your project's own Gemfile: | |
166 | 172 | |
167 | 173 | ```ruby |
168 | 174 | gem 'jruby-openssl', :platform => :jruby |
Binary diff not shown
0 | source :rubygems | |
0 | source 'https://rubygems.org' | |
1 | 1 | |
2 | 2 | gem 'sinatra' |
3 | 3 | gem 'omniauth-facebook', :path => '../' |
0 | 0 | PATH |
1 | 1 | remote: ../ |
2 | 2 | specs: |
3 | omniauth-facebook (1.4.0) | |
4 | omniauth-oauth2 (~> 1.1.0) | |
3 | omniauth-facebook (1.6.0.rc1) | |
4 | omniauth-oauth2 (~> 1.1) | |
5 | 5 | |
6 | 6 | GEM |
7 | remote: http://rubygems.org/ | |
7 | remote: https://rubygems.org/ | |
8 | 8 | specs: |
9 | faraday (0.8.1) | |
10 | multipart-post (~> 1.1) | |
11 | hashie (1.2.0) | |
12 | httpauth (0.1) | |
13 | json (1.7.3) | |
14 | jwt (0.1.4) | |
15 | json (>= 1.2.4) | |
16 | multi_json (1.3.6) | |
17 | multipart-post (1.1.5) | |
18 | oauth2 (0.8.0) | |
9 | faraday (0.8.8) | |
10 | multipart-post (~> 1.2.0) | |
11 | hashie (2.0.5) | |
12 | httpauth (0.2.0) | |
13 | jwt (0.1.8) | |
14 | multi_json (>= 1.5) | |
15 | multi_json (1.8.2) | |
16 | multipart-post (1.2.0) | |
17 | oauth2 (0.8.1) | |
19 | 18 | faraday (~> 0.8) |
20 | 19 | httpauth (~> 0.1) |
21 | 20 | jwt (~> 0.1.4) |
22 | 21 | multi_json (~> 1.0) |
23 | 22 | rack (~> 1.2) |
24 | omniauth (1.1.0) | |
25 | hashie (~> 1.2) | |
23 | omniauth (1.1.4) | |
24 | hashie (>= 1.2, < 3) | |
26 | 25 | rack |
27 | omniauth-oauth2 (1.1.0) | |
26 | omniauth-oauth2 (1.1.1) | |
28 | 27 | oauth2 (~> 0.8.0) |
29 | 28 | omniauth (~> 1.0) |
30 | rack (1.4.1) | |
31 | rack-protection (1.2.0) | |
29 | rack (1.5.2) | |
30 | rack-protection (1.5.1) | |
32 | 31 | rack |
33 | sinatra (1.3.2) | |
34 | rack (~> 1.3, >= 1.3.6) | |
35 | rack-protection (~> 1.2) | |
36 | tilt (~> 1.3, >= 1.3.3) | |
37 | tilt (1.3.3) | |
32 | sinatra (1.4.4) | |
33 | rack (~> 1.4) | |
34 | rack-protection (~> 1.4) | |
35 | tilt (~> 1.3, >= 1.3.4) | |
36 | tilt (1.4.1) | |
38 | 37 | |
39 | 38 | PLATFORMS |
40 | 39 | ruby |
86 | 86 | # signed_request FB sends us, asking for auth if the user has |
87 | 87 | # not already granted access, or simply moving straight to the |
88 | 88 | # callback where they have already granted access. |
89 | # | |
90 | # we pass the state parameter which we can detect in our callback | |
91 | # to do custom rendering/redirection for the canvas app page | |
92 | redirect "/auth/facebook?signed_request=#{request.params['signed_request']}&state=canvas" | |
89 | redirect "/auth/facebook?signed_request=#{request.params['signed_request']}" | |
93 | 90 | end |
94 | 91 | |
95 | 92 | get '/auth/:provider/callback' do |
96 | # we can do something special here is +state+ param is canvas | |
97 | # (see notes above in /canvas/ method for more details) | |
98 | 93 | content_type 'application/json' |
99 | 94 | MultiJson.encode(request.env) |
100 | 95 | end |
1 | 1 | require 'base64' |
2 | 2 | require 'openssl' |
3 | 3 | require 'rack/utils' |
4 | require 'uri' | |
4 | 5 | |
5 | 6 | module OmniAuth |
6 | 7 | module Strategies |
7 | 8 | class Facebook < OmniAuth::Strategies::OAuth2 |
8 | 9 | class NoAuthorizationCodeError < StandardError; end |
10 | class UnknownSignatureAlgorithmError < NotImplementedError; end | |
9 | 11 | |
10 | 12 | DEFAULT_SCOPE = 'email' |
11 | 13 | |
12 | 14 | option :client_options, { |
13 | 15 | :site => 'https://graph.facebook.com', |
16 | :authorize_url => "https://www.facebook.com/dialog/oauth", | |
14 | 17 | :token_url => '/oauth/access_token' |
15 | 18 | } |
16 | 19 | |
34 | 37 | 'name' => raw_info['name'], |
35 | 38 | 'first_name' => raw_info['first_name'], |
36 | 39 | 'last_name' => raw_info['last_name'], |
37 | 'image' => "#{options[:secure_image_url] ? 'https' : 'http'}://graph.facebook.com/#{uid}/picture?type=#{options[:image_size] || 'square'}", | |
40 | 'image' => image_url(uid, options), | |
38 | 41 | 'description' => raw_info['bio'], |
39 | 42 | 'urls' => { |
40 | 43 | 'Facebook' => raw_info['link'], |
52 | 55 | end |
53 | 56 | |
54 | 57 | def raw_info |
55 | @raw_info ||= access_token.get('/me').parsed || {} | |
56 | end | |
58 | @raw_info ||= access_token.get('/me', info_options).parsed || {} | |
59 | end | |
60 | ||
61 | def info_options | |
62 | params = {:appsecret_proof => appsecret_proof} | |
63 | params.merge!({:fields => options[:info_fields]}) if options[:info_fields] | |
64 | params.merge!({:locale => options[:locale]}) if options[:locale] | |
65 | ||
66 | { :params => params } | |
67 | end | |
68 | ||
69 | def callback_phase | |
70 | super | |
71 | rescue NoAuthorizationCodeError => e | |
72 | fail!(:no_authorization_code, e) | |
73 | rescue UnknownSignatureAlgorithmError => e | |
74 | fail!(:unknown_signature_algoruthm, e) | |
75 | end | |
76 | ||
77 | def request_phase | |
78 | if signed_request_contains_access_token? | |
79 | # If we already have an access token, we can just hit the callback URL directly and pass the signed request. | |
80 | params = { :signed_request => raw_signed_request } | |
81 | query = Rack::Utils.build_query(params) | |
82 | ||
83 | url = callback_url | |
84 | url << "?" unless url.match(/\?/) | |
85 | url << "&" unless url.match(/[\&\?]$/) | |
86 | url << query | |
87 | ||
88 | redirect url | |
89 | else | |
90 | super | |
91 | end | |
92 | end | |
93 | ||
94 | # NOTE If we're using code from the signed request then FB sets the redirect_uri to '' during the authorize | |
95 | # phase and it must match during the access_token phase: | |
96 | # https://github.com/facebook/php-sdk/blob/master/src/base_facebook.php#L348 | |
97 | def callback_url | |
98 | if @authorization_code_from_signed_request | |
99 | '' | |
100 | else | |
101 | options[:callback_url] || super | |
102 | end | |
103 | end | |
104 | ||
105 | def access_token_options | |
106 | options.access_token_options.inject({}) { |h,(k,v)| h[k.to_sym] = v; h } | |
107 | end | |
108 | ||
109 | # You can pass +display+, +scope+, or +auth_type+ params to the auth request, if you need to set them dynamically. | |
110 | # You can also set these options in the OmniAuth config :authorize_params option. | |
111 | # | |
112 | # /auth/facebook?display=popup | |
113 | def authorize_params | |
114 | super.tap do |params| | |
115 | %w[display scope auth_type].each do |v| | |
116 | if request.params[v] | |
117 | params[v.to_sym] = request.params[v] | |
118 | end | |
119 | end | |
120 | ||
121 | params[:scope] ||= DEFAULT_SCOPE | |
122 | end | |
123 | end | |
124 | ||
125 | # Parse signed request in order, from: | |
126 | # | |
127 | # 1. The request 'signed_request' param (server-side flow from canvas pages) or | |
128 | # 2. A cookie (client-side flow via JS SDK) | |
129 | def signed_request | |
130 | @signed_request ||= raw_signed_request && parse_signed_request(raw_signed_request) | |
131 | end | |
132 | ||
133 | protected | |
57 | 134 | |
58 | 135 | def build_access_token |
59 | if access_token = request.params["access_token"] | |
60 | ::OAuth2::AccessToken.from_hash( | |
61 | client, | |
62 | {"access_token" => access_token}.update(access_token_options) | |
63 | ) | |
64 | elsif signed_request_contains_access_token? | |
136 | if signed_request_contains_access_token? | |
65 | 137 | hash = signed_request.clone |
66 | 138 | ::OAuth2::AccessToken.new( |
67 | 139 | client, |
75 | 147 | end |
76 | 148 | end |
77 | 149 | |
78 | def request_phase | |
79 | if signed_request_contains_access_token? | |
80 | # if we already have an access token, we can just hit the | |
81 | # callback URL directly and pass the signed request along | |
82 | params = { :signed_request => raw_signed_request } | |
83 | params[:state] = request.params['state'] if request.params['state'] | |
84 | query = Rack::Utils.build_query(params) | |
85 | ||
86 | url = callback_url | |
87 | url << "?" unless url.match(/\?/) | |
88 | url << "&" unless url.match(/[\&\?]$/) | |
89 | url << query | |
90 | ||
91 | redirect url | |
92 | else | |
93 | super | |
94 | end | |
95 | end | |
96 | ||
97 | # NOTE if we're using code from the signed request | |
98 | # then FB sets the redirect_uri to '' during the authorize | |
99 | # phase + it must match during the access_token phase: | |
100 | # https://github.com/facebook/php-sdk/blob/master/src/base_facebook.php#L348 | |
101 | def callback_url | |
102 | if @authorization_code_from_signed_request | |
103 | '' | |
104 | else | |
105 | options[:callback_url] || super | |
106 | end | |
107 | end | |
108 | ||
109 | def access_token_options | |
110 | options.access_token_options.inject({}) { |h,(k,v)| h[k.to_sym] = v; h } | |
111 | end | |
112 | ||
113 | ## | |
114 | # You can pass +display+, +state+ or +scope+ params to the auth request, if | |
115 | # you need to set them dynamically. You can also set these options | |
116 | # in the OmniAuth config :authorize_params option. | |
117 | # | |
118 | # /auth/facebook?display=popup&state=ABC | |
119 | # | |
120 | def authorize_params | |
121 | super.tap do |params| | |
122 | %w[display state scope].each do |v| | |
123 | if request.params[v] | |
124 | params[v.to_sym] = request.params[v] | |
125 | ||
126 | # to support omniauth-oauth2's auto csrf protection | |
127 | session['omniauth.state'] = params[:state] if v == 'state' | |
128 | end | |
129 | end | |
130 | ||
131 | params[:scope] ||= DEFAULT_SCOPE | |
132 | end | |
133 | end | |
134 | ||
135 | ## | |
136 | # Parse signed request in order, from: | |
137 | # | |
138 | # 1. the request 'signed_request' param (server-side flow from canvas pages) or | |
139 | # 2. a cookie (client-side flow via JS SDK) | |
140 | # | |
141 | def signed_request | |
142 | @signed_request ||= raw_signed_request && | |
143 | parse_signed_request(raw_signed_request) | |
144 | end | |
145 | ||
146 | 150 | private |
147 | 151 | |
148 | 152 | def raw_signed_request |
149 | request.params['signed_request'] || | |
150 | request.cookies["fbsr_#{client.id}"] | |
151 | end | |
152 | ||
153 | ## | |
154 | # If the signed_request comes from a FB canvas page and the user | |
155 | # has already authorized your application, the JSON object will be | |
156 | # contain the access token. | |
153 | request.params['signed_request'] || request.cookies["fbsr_#{client.id}"] | |
154 | end | |
155 | ||
156 | # If the signed_request comes from a FB canvas page and the user has already authorized your application, the JSON | |
157 | # object will be contain the access token. | |
157 | 158 | # |
158 | 159 | # https://developers.facebook.com/docs/authentication/canvas/ |
159 | # | |
160 | 160 | def signed_request_contains_access_token? |
161 | signed_request && | |
162 | signed_request['oauth_token'] | |
163 | end | |
164 | ||
165 | ## | |
161 | signed_request && signed_request['oauth_token'] | |
162 | end | |
163 | ||
166 | 164 | # Picks the authorization code in order, from: |
167 | 165 | # |
168 | # 1. the request 'code' param (manual callback from standard server-side flow) | |
169 | # 2. a signed request (see #signed_request for more) | |
170 | # | |
166 | # 1. The request 'code' param (manual callback from standard server-side flow) | |
167 | # 2. A signed request (see #signed_request for more) | |
171 | 168 | def with_authorization_code! |
172 | 169 | if request.params.key?('code') |
173 | 170 | yield |
194 | 191 | |
195 | 192 | def parse_signed_request(value) |
196 | 193 | signature, encoded_payload = value.split('.') |
194 | return if signature.nil? | |
197 | 195 | |
198 | 196 | decoded_hex_signature = base64_decode_url(signature) |
199 | 197 | decoded_payload = MultiJson.decode(base64_decode_url(encoded_payload)) |
200 | 198 | |
201 | 199 | unless decoded_payload['algorithm'] == 'HMAC-SHA256' |
202 | raise NotImplementedError, "unkown algorithm: #{decoded_payload['algorithm']}" | |
200 | raise UnknownSignatureAlgorithmError, "unknown algorithm: #{decoded_payload['algorithm']}" | |
203 | 201 | end |
204 | 202 | |
205 | 203 | if valid_signature?(client.secret, decoded_hex_signature, encoded_payload) |
215 | 213 | value += '=' * (4 - value.size.modulo(4)) |
216 | 214 | Base64.decode64(value.tr('-_', '+/')) |
217 | 215 | end |
216 | ||
217 | def image_url(uid, options) | |
218 | uri_class = options[:secure_image_url] ? URI::HTTPS : URI::HTTP | |
219 | url = uri_class.build({:host => 'graph.facebook.com', :path => "/#{uid}/picture"}) | |
220 | ||
221 | query = if options[:image_size].is_a?(String) | |
222 | { :type => options[:image_size] } | |
223 | elsif options[:image_size].is_a?(Hash) | |
224 | options[:image_size] | |
225 | end | |
226 | url.query = Rack::Utils.build_query(query) if query | |
227 | ||
228 | url.to_s | |
229 | end | |
230 | ||
231 | def appsecret_proof | |
232 | @appsecret_proof ||= OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, client.secret, access_token.token) | |
233 | end | |
218 | 234 | end |
219 | 235 | end |
220 | 236 | end |
0 | 0 | --- !ruby/object:Gem::Specification |
1 | 1 | name: omniauth-facebook |
2 | 2 | version: !ruby/object:Gem::Version |
3 | version: 1.4.1 | |
4 | prerelease: | |
3 | version: 1.6.0 | |
5 | 4 | platform: ruby |
6 | 5 | authors: |
7 | 6 | - Mark Dodwell |
7 | - Josef Šimánek | |
8 | 8 | autorequire: |
9 | 9 | bindir: bin |
10 | 10 | cert_chain: [] |
11 | date: 2012-07-07 00:00:00.000000000 Z | |
11 | date: 2014-01-11 00:00:00.000000000 Z | |
12 | 12 | dependencies: |
13 | 13 | - !ruby/object:Gem::Dependency |
14 | 14 | name: omniauth-oauth2 |
15 | 15 | requirement: !ruby/object:Gem::Requirement |
16 | none: false | |
17 | 16 | requirements: |
18 | 17 | - - ~> |
19 | 18 | - !ruby/object:Gem::Version |
20 | version: 1.1.0 | |
19 | version: '1.1' | |
21 | 20 | type: :runtime |
22 | 21 | prerelease: false |
23 | 22 | version_requirements: !ruby/object:Gem::Requirement |
24 | none: false | |
25 | 23 | requirements: |
26 | 24 | - - ~> |
27 | 25 | - !ruby/object:Gem::Version |
28 | version: 1.1.0 | |
26 | version: '1.1' | |
29 | 27 | - !ruby/object:Gem::Dependency |
30 | 28 | name: minitest |
31 | 29 | requirement: !ruby/object:Gem::Requirement |
32 | none: false | |
33 | 30 | requirements: |
34 | - - ! '>=' | |
31 | - - '>=' | |
35 | 32 | - !ruby/object:Gem::Version |
36 | 33 | version: '0' |
37 | 34 | type: :development |
38 | 35 | prerelease: false |
39 | 36 | version_requirements: !ruby/object:Gem::Requirement |
40 | none: false | |
41 | 37 | requirements: |
42 | - - ! '>=' | |
38 | - - '>=' | |
43 | 39 | - !ruby/object:Gem::Version |
44 | 40 | version: '0' |
45 | 41 | - !ruby/object:Gem::Dependency |
46 | 42 | name: mocha |
47 | 43 | requirement: !ruby/object:Gem::Requirement |
48 | none: false | |
49 | 44 | requirements: |
50 | - - ! '>=' | |
45 | - - '>=' | |
51 | 46 | - !ruby/object:Gem::Version |
52 | 47 | version: '0' |
53 | 48 | type: :development |
54 | 49 | prerelease: false |
55 | 50 | version_requirements: !ruby/object:Gem::Requirement |
56 | none: false | |
57 | 51 | requirements: |
58 | - - ! '>=' | |
52 | - - '>=' | |
59 | 53 | - !ruby/object:Gem::Version |
60 | 54 | version: '0' |
61 | 55 | - !ruby/object:Gem::Dependency |
62 | 56 | name: rake |
63 | 57 | requirement: !ruby/object:Gem::Requirement |
64 | none: false | |
65 | 58 | requirements: |
66 | - - ! '>=' | |
59 | - - '>=' | |
67 | 60 | - !ruby/object:Gem::Version |
68 | 61 | version: '0' |
69 | 62 | type: :development |
70 | 63 | prerelease: false |
71 | 64 | version_requirements: !ruby/object:Gem::Requirement |
72 | none: false | |
73 | 65 | requirements: |
74 | - - ! '>=' | |
66 | - - '>=' | |
75 | 67 | - !ruby/object:Gem::Version |
76 | 68 | version: '0' |
77 | 69 | description: |
78 | 70 | email: |
79 | - mark@mkdynamic.co.uk | |
71 | - mark@madeofcode.com | |
72 | - retro@ballgag.cz | |
80 | 73 | executables: [] |
81 | 74 | extensions: [] |
82 | 75 | extra_rdoc_files: [] |
83 | 76 | files: |
84 | 77 | - .gitignore |
85 | 78 | - .travis.yml |
79 | - CHANGELOG.md | |
86 | 80 | - Gemfile |
87 | 81 | - README.md |
88 | 82 | - Rakefile |
98 | 92 | - test/support/shared_examples.rb |
99 | 93 | - test/test.rb |
100 | 94 | homepage: https://github.com/mkdynamic/omniauth-facebook |
101 | licenses: [] | |
95 | licenses: | |
96 | - MIT | |
97 | metadata: {} | |
102 | 98 | post_install_message: |
103 | 99 | rdoc_options: [] |
104 | 100 | require_paths: |
105 | 101 | - lib |
106 | 102 | required_ruby_version: !ruby/object:Gem::Requirement |
107 | none: false | |
108 | 103 | requirements: |
109 | - - ! '>=' | |
104 | - - '>=' | |
110 | 105 | - !ruby/object:Gem::Version |
111 | 106 | version: '0' |
112 | segments: | |
113 | - 0 | |
114 | hash: 1875274478054024285 | |
115 | 107 | required_rubygems_version: !ruby/object:Gem::Requirement |
116 | none: false | |
117 | 108 | requirements: |
118 | - - ! '>=' | |
109 | - - '>=' | |
119 | 110 | - !ruby/object:Gem::Version |
120 | 111 | version: '0' |
121 | segments: | |
122 | - 0 | |
123 | hash: 1875274478054024285 | |
124 | 112 | requirements: [] |
125 | 113 | rubyforge_project: |
126 | rubygems_version: 1.8.24 | |
114 | rubygems_version: 2.0.2 | |
127 | 115 | signing_key: |
128 | specification_version: 3 | |
129 | summary: Facebook strategy for OmniAuth | |
116 | specification_version: 4 | |
117 | summary: Facebook OAuth2 Strategy for OmniAuth | |
130 | 118 | test_files: |
131 | 119 | - test/helper.rb |
132 | 120 | - test/support/shared_examples.rb |
4 | 4 | Gem::Specification.new do |s| |
5 | 5 | s.name = 'omniauth-facebook' |
6 | 6 | s.version = OmniAuth::Facebook::VERSION |
7 | s.authors = ['Mark Dodwell'] | |
8 | s.email = ['mark@mkdynamic.co.uk'] | |
9 | s.summary = 'Facebook strategy for OmniAuth' | |
7 | s.authors = ['Mark Dodwell', 'Josef Šimánek'] | |
8 | s.email = ['mark@madeofcode.com', 'retro@ballgag.cz'] | |
9 | s.summary = 'Facebook OAuth2 Strategy for OmniAuth' | |
10 | 10 | s.homepage = 'https://github.com/mkdynamic/omniauth-facebook' |
11 | s.license = 'MIT' | |
11 | 12 | |
12 | 13 | s.files = `git ls-files`.split("\n") |
13 | 14 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") |
14 | 15 | s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) } |
15 | 16 | s.require_paths = ['lib'] |
16 | 17 | |
17 | s.add_runtime_dependency 'omniauth-oauth2', '~> 1.1.0' | |
18 | s.add_runtime_dependency 'omniauth-oauth2', '~> 1.1' | |
18 | 19 | |
19 | 20 | s.add_development_dependency 'minitest' |
20 | 21 | s.add_development_dependency 'mocha' |
0 | 0 | require 'bundler/setup' |
1 | 1 | require 'minitest/autorun' |
2 | require 'mocha' | |
2 | require 'mocha/setup' | |
3 | 3 | require 'omniauth/strategies/facebook' |
4 | 4 | |
5 | 5 | OmniAuth.config.test_mode = true |
24 | 24 | end |
25 | 25 | end |
26 | 26 | |
27 | class TestCase < MiniTest::Unit::TestCase | |
27 | class TestCase < Minitest::Test | |
28 | 28 | extend BlockTestHelper |
29 | 29 | include CustomAssertions |
30 | 30 | end |
35 | 35 | @request.stubs(:params).returns({}) |
36 | 36 | @request.stubs(:cookies).returns({}) |
37 | 37 | @request.stubs(:env).returns({}) |
38 | @request.stubs(:ssl?).returns(false) | |
38 | 39 | |
39 | 40 | @client_id = '123' |
40 | 41 | @client_secret = '53cr3tz' |
49 | 49 | assert_equal strategy.authorize_params['state'], strategy.session['omniauth.state'] |
50 | 50 | end |
51 | 51 | |
52 | test 'should store state in the session when present in authorize params vs. a random one' do | |
52 | test 'should not store state in the session when present in authorize params vs. a random one' do | |
53 | 53 | @options = { :authorize_params => { :state => 'bar' } } |
54 | 54 | refute_empty strategy.authorize_params['state'] |
55 | assert_equal 'bar', strategy.authorize_params[:state] | |
55 | refute_equal 'bar', strategy.authorize_params[:state] | |
56 | 56 | refute_empty strategy.session['omniauth.state'] |
57 | assert_equal 'bar', strategy.session['omniauth.state'] | |
57 | refute_equal 'bar', strategy.session['omniauth.state'] | |
58 | 58 | end |
59 | 59 | |
60 | test 'should store state in the session when present in request params vs. a random one' do | |
60 | test 'should not store state in the session when present in request params vs. a random one' do | |
61 | 61 | @request.stubs(:params).returns({ 'state' => 'foo' }) |
62 | 62 | refute_empty strategy.authorize_params['state'] |
63 | assert_equal 'foo', strategy.authorize_params[:state] | |
63 | refute_equal 'foo', strategy.authorize_params[:state] | |
64 | 64 | refute_empty strategy.session['omniauth.state'] |
65 | assert_equal 'foo', strategy.session['omniauth.state'] | |
65 | refute_equal 'foo', strategy.session['omniauth.state'] | |
66 | 66 | end |
67 | 67 | end |
68 | 68 |
12 | 12 | end |
13 | 13 | |
14 | 14 | test 'has correct authorize url' do |
15 | assert_equal '/oauth/authorize', strategy.client.options[:authorize_url] | |
15 | assert_equal 'https://www.facebook.com/dialog/oauth', strategy.client.options[:authorize_url] | |
16 | 16 | end |
17 | 17 | |
18 | 18 | test 'has correct token url' do |
55 | 55 | assert_equal 'touch', strategy.authorize_params[:display] |
56 | 56 | end |
57 | 57 | |
58 | test 'includes state parameter from request when present' do | |
59 | @request.stubs(:params).returns({ 'state' => 'some_state' }) | |
58 | test 'includes auth_type parameter from request when present' do | |
59 | @request.stubs(:params).returns({ 'auth_type' => 'reauthenticate' }) | |
60 | 60 | assert strategy.authorize_params.is_a?(Hash) |
61 | assert_equal 'some_state', strategy.authorize_params[:state] | |
61 | assert_equal 'reauthenticate', strategy.authorize_params[:auth_type] | |
62 | 62 | end |
63 | 63 | |
64 | 64 | test 'overrides default scope with parameter passed from request' do |
100 | 100 | @options = { :secure_image_url => true } |
101 | 101 | raw_info = { 'name' => 'Fred Smith', 'id' => '321' } |
102 | 102 | strategy.stubs(:raw_info).returns(raw_info) |
103 | assert_equal 'https://graph.facebook.com/321/picture?type=square', strategy.info['image'] | |
104 | end | |
105 | ||
106 | test 'returns the image size specified in the `image_size` option' do | |
103 | assert_equal 'https://graph.facebook.com/321/picture', strategy.info['image'] | |
104 | end | |
105 | ||
106 | test 'returns the image with size specified in the `image_size` option' do | |
107 | 107 | @options = { :image_size => 'normal' } |
108 | 108 | raw_info = { 'name' => 'Fred Smith', 'id' => '321' } |
109 | 109 | strategy.stubs(:raw_info).returns(raw_info) |
110 | 110 | assert_equal 'http://graph.facebook.com/321/picture?type=normal', strategy.info['image'] |
111 | 111 | end |
112 | ||
113 | test 'returns the image with width and height specified in the `image_size` option' do | |
114 | @options = { :image_size => { :width => 123, :height => 987 } } | |
115 | raw_info = { 'name' => 'Fred Smith', 'id' => '321' } | |
116 | strategy.stubs(:raw_info).returns(raw_info) | |
117 | image_url = strategy.info['image'] | |
118 | path, query = image_url.split("?") | |
119 | query_params = Hash[*query.split("&").map {|pair| pair.split("=") }.flatten] | |
120 | ||
121 | assert_equal 'http://graph.facebook.com/321/picture', path | |
122 | assert_equal '123', query_params['width'] | |
123 | assert_equal '987', query_params['height'] | |
124 | end | |
112 | 125 | end |
113 | 126 | |
114 | 127 | class InfoTestOptionalDataPresent < StrategyTestCase |
152 | 165 | assert_equal 'I am great', strategy.info['description'] |
153 | 166 | end |
154 | 167 | |
155 | test 'returns the square format facebook avatar url' do | |
168 | test 'returns the facebook avatar url' do | |
156 | 169 | @raw_info['id'] = '321' |
157 | assert_equal 'http://graph.facebook.com/321/picture?type=square', strategy.info['image'] | |
170 | assert_equal 'http://graph.facebook.com/321/picture', strategy.info['image'] | |
158 | 171 | end |
159 | 172 | |
160 | 173 | test 'returns the Facebook link as the Facebook url' do |
232 | 245 | def setup |
233 | 246 | super |
234 | 247 | @access_token = stub('OAuth2::AccessToken') |
248 | @appsecret_proof = 'appsecret_proof' | |
249 | @options = {:appsecret_proof => @appsecret_proof} | |
235 | 250 | end |
236 | 251 | |
237 | 252 | test 'performs a GET to https://graph.facebook.com/me' do |
253 | strategy.stubs(:appsecret_proof).returns(@appsecret_proof) | |
238 | 254 | strategy.stubs(:access_token).returns(@access_token) |
239 | @access_token.expects(:get).with('/me').returns(stub_everything('OAuth2::Response')) | |
255 | params = {:params => @options} | |
256 | @access_token.expects(:get).with('/me', params).returns(stub_everything('OAuth2::Response')) | |
257 | strategy.raw_info | |
258 | end | |
259 | ||
260 | test 'performs a GET to https://graph.facebook.com/me with locale' do | |
261 | @options.merge!({ :locale => 'cs_CZ' }) | |
262 | strategy.stubs(:access_token).returns(@access_token) | |
263 | strategy.stubs(:appsecret_proof).returns(@appsecret_proof) | |
264 | params = {:params => @options} | |
265 | @access_token.expects(:get).with('/me', params).returns(stub_everything('OAuth2::Response')) | |
266 | strategy.raw_info | |
267 | end | |
268 | ||
269 | test 'performs a GET to https://graph.facebook.com/me with info_fields' do | |
270 | @options.merge!({:info_fields => 'about'}) | |
271 | strategy.stubs(:access_token).returns(@access_token) | |
272 | strategy.stubs(:appsecret_proof).returns(@appsecret_proof) | |
273 | params = {:params => {:appsecret_proof => @appsecret_proof, :fields => 'about'}} | |
274 | @access_token.expects(:get).with('/me', params).returns(stub_everything('OAuth2::Response')) | |
240 | 275 | strategy.raw_info |
241 | 276 | end |
242 | 277 | |
243 | 278 | test 'returns a Hash' do |
244 | 279 | strategy.stubs(:access_token).returns(@access_token) |
280 | strategy.stubs(:appsecret_proof).returns(@appsecret_proof) | |
245 | 281 | raw_response = stub('Faraday::Response') |
246 | 282 | raw_response.stubs(:body).returns('{ "ohai": "thar" }') |
247 | 283 | raw_response.stubs(:status).returns(200) |
248 | 284 | raw_response.stubs(:headers).returns({'Content-Type' => 'application/json' }) |
249 | 285 | oauth2_response = OAuth2::Response.new(raw_response) |
250 | @access_token.stubs(:get).with('/me').returns(oauth2_response) | |
286 | params = {:params => @options} | |
287 | @access_token.stubs(:get).with('/me', params).returns(oauth2_response) | |
251 | 288 | assert_kind_of Hash, strategy.raw_info |
252 | 289 | assert_equal 'thar', strategy.raw_info['ohai'] |
253 | 290 | end |
254 | 291 | |
255 | 292 | test 'returns an empty hash when the response is false' do |
256 | 293 | strategy.stubs(:access_token).returns(@access_token) |
294 | strategy.stubs(:appsecret_proof).returns(@appsecret_proof) | |
257 | 295 | oauth2_response = stub('OAuth2::Response', :parsed => false) |
258 | @access_token.stubs(:get).with('/me').returns(oauth2_response) | |
296 | params = {:params => @options} | |
297 | @access_token.stubs(:get).with('/me', params).returns(oauth2_response) | |
259 | 298 | assert_kind_of Hash, strategy.raw_info |
299 | assert_equal({}, strategy.raw_info) | |
260 | 300 | end |
261 | 301 | |
262 | 302 | test 'should not include raw_info in extras hash when skip_info is specified' do |
359 | 399 | test 'is nil' do |
360 | 400 | assert_nil strategy.send(:signed_request) |
361 | 401 | end |
402 | ||
403 | test 'throws an error on calling build_access_token' do | |
404 | assert_equal 'must pass either a `code` parameter or a signed request (via `signed_request` parameter or a `fbsr_XXX` cookie)', | |
405 | assert_raises(OmniAuth::Strategies::Facebook::NoAuthorizationCodeError) { strategy.send(:build_access_token) }.message | |
406 | end | |
362 | 407 | end |
363 | 408 | |
364 | 409 | class CookiePresentTest < TestCase |
365 | def setup | |
366 | super | |
410 | def setup(algo = nil) | |
411 | super() | |
367 | 412 | @payload = { |
368 | 'algorithm' => 'HMAC-SHA256', | |
413 | 'algorithm' => algo || 'HMAC-SHA256', | |
369 | 414 | 'code' => 'm4c0d3z', |
370 | 415 | 'issued_at' => Time.now.to_i, |
371 | 416 | 'user_id' => '123456' |
377 | 422 | test 'parses the access code out from the cookie' do |
378 | 423 | assert_equal @payload, strategy.send(:signed_request) |
379 | 424 | end |
425 | ||
426 | test 'throws an error if the algorithm is unknown' do | |
427 | setup('UNKNOWN-ALGO') | |
428 | assert_equal "unknown algorithm: UNKNOWN-ALGO", assert_raises(OmniAuth::Strategies::Facebook::UnknownSignatureAlgorithmError) { strategy.send(:signed_request) }.message | |
429 | end | |
380 | 430 | end |
381 | 431 | |
382 | 432 | class ParamPresentTest < TestCase |
383 | def setup | |
384 | super | |
433 | def setup(algo = nil) | |
434 | super() | |
385 | 435 | @payload = { |
386 | 'algorithm' => 'HMAC-SHA256', | |
436 | 'algorithm' => algo || 'HMAC-SHA256', | |
387 | 437 | 'oauth_token' => 'XXX', |
388 | 438 | 'issued_at' => Time.now.to_i, |
389 | 439 | 'user_id' => '123456' |
394 | 444 | |
395 | 445 | test 'parses the access code out from the param' do |
396 | 446 | assert_equal @payload, strategy.send(:signed_request) |
447 | end | |
448 | ||
449 | test 'throws an error if the algorithm is unknown' do | |
450 | setup('UNKNOWN-ALGO') | |
451 | assert_equal "unknown algorithm: UNKNOWN-ALGO", assert_raises(OmniAuth::Strategies::Facebook::UnknownSignatureAlgorithmError) { strategy.send(:signed_request) }.message | |
397 | 452 | end |
398 | 453 | end |
399 | 454 | |
419 | 474 | assert_equal @payload_from_param, strategy.send(:signed_request) |
420 | 475 | end |
421 | 476 | end |
477 | ||
478 | class EmptySignedRequestTest < TestCase | |
479 | def setup | |
480 | super | |
481 | @request.stubs(:params).returns({'signed_request' => ''}) | |
482 | end | |
483 | ||
484 | test 'empty param' do | |
485 | assert_equal nil, strategy.send(:signed_request) | |
486 | end | |
487 | end | |
488 | ||
422 | 489 | end |
423 | 490 | |
424 | 491 | class RequestPhaseWithSignedRequestTest < StrategyTestCase |
464 | 531 | end |
465 | 532 | |
466 | 533 | test 'returns a new access token from the signed request' do |
467 | result = strategy.build_access_token | |
534 | result = strategy.send(:build_access_token) | |
468 | 535 | assert_kind_of ::OAuth2::AccessToken, result |
469 | 536 | assert_equal @payload['oauth_token'], result.token |
470 | 537 | end |
471 | 538 | |
472 | 539 | test 'returns an access token with the correct expiry time' do |
473 | result = strategy.build_access_token | |
540 | result = strategy.send(:build_access_token) | |
474 | 541 | assert_equal @payload['expires'], result.expires_at |
475 | 542 | end |
476 | 543 | end |
477 | ||
478 | class ParamsContainAccessTokenStringTest < TestCase | |
479 | def setup | |
480 | super | |
481 | ||
482 | @request.stubs(:params).returns({'access_token' => 'm4c0d3z'}) | |
483 | ||
484 | strategy.stubs(:callback_url).returns('/') | |
485 | end | |
486 | ||
487 | test 'returns a new access token' do | |
488 | result = strategy.build_access_token | |
489 | assert_kind_of ::OAuth2::AccessToken, result | |
490 | assert_equal 'm4c0d3z', result.token | |
491 | end | |
492 | end | |
493 | end | |
544 | end |