omniauth-granicus 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +7 -0
- data/Gemfile +5 -0
- data/README.md +5 -0
- data/Rakefile +6 -0
- data/example/Gemfile +7 -0
- data/example/Gemfile.lock +42 -0
- data/example/config.ru +118 -0
- data/lib/omniauth/granicus/version.rb +5 -0
- data/lib/omniauth/granicus-admin.rb +2 -0
- data/lib/omniauth/strategies/granicus-admin.rb +136 -0
- data/lib/omniauth-granicus-admin.rb +1 -0
- data/omniauth-granicus.gemspec +22 -0
- data/spec/omniauth/strategies/granicus_spec.rb +339 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/support/shared_examples.rb +37 -0
- metadata +96 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
data/Rakefile
ADDED
data/example/Gemfile
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ../
|
3
|
+
specs:
|
4
|
+
omniauth-facebook (1.2.0)
|
5
|
+
omniauth-oauth2 (~> 1.0.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: http://rubygems.org/
|
9
|
+
specs:
|
10
|
+
addressable (2.2.7)
|
11
|
+
faraday (0.7.6)
|
12
|
+
addressable (~> 2.2)
|
13
|
+
multipart-post (~> 1.1)
|
14
|
+
rack (~> 1.1)
|
15
|
+
hashie (1.2.0)
|
16
|
+
multi_json (1.1.0)
|
17
|
+
multipart-post (1.1.5)
|
18
|
+
oauth2 (0.5.2)
|
19
|
+
faraday (~> 0.7)
|
20
|
+
multi_json (~> 1.0)
|
21
|
+
omniauth (1.0.2)
|
22
|
+
hashie (~> 1.2)
|
23
|
+
rack
|
24
|
+
omniauth-oauth2 (1.0.0)
|
25
|
+
oauth2 (~> 0.5.0)
|
26
|
+
omniauth (~> 1.0)
|
27
|
+
rack (1.3.6)
|
28
|
+
rack-protection (1.2.0)
|
29
|
+
rack
|
30
|
+
sinatra (1.3.2)
|
31
|
+
rack (~> 1.3, >= 1.3.6)
|
32
|
+
rack-protection (~> 1.2)
|
33
|
+
tilt (~> 1.3, >= 1.3.3)
|
34
|
+
tilt (1.3.3)
|
35
|
+
|
36
|
+
PLATFORMS
|
37
|
+
ruby
|
38
|
+
|
39
|
+
DEPENDENCIES
|
40
|
+
omniauth-facebook!
|
41
|
+
rack (~> 1.3.6)
|
42
|
+
sinatra
|
data/example/config.ru
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'sinatra/base'
|
3
|
+
require 'omniauth-facebook'
|
4
|
+
|
5
|
+
# https://github.com/intridea/omniauth-oauth2/pull/9
|
6
|
+
require 'timeout'
|
7
|
+
|
8
|
+
SCOPE = 'email,read_stream'
|
9
|
+
|
10
|
+
class App < Sinatra::Base
|
11
|
+
# turn off sinatra default X-Frame-Options for FB canvas
|
12
|
+
set :protection, :except => :frame_options
|
13
|
+
|
14
|
+
# server-side flow
|
15
|
+
get '/' do
|
16
|
+
# NOTE: you would just hit this endpoint directly from the browser
|
17
|
+
# in a real app. the redirect is just here to setup the root
|
18
|
+
# path in this example sinatra app.
|
19
|
+
redirect '/auth/facebook'
|
20
|
+
end
|
21
|
+
|
22
|
+
# client-side flow
|
23
|
+
get '/client-side' do
|
24
|
+
content_type 'text/html'
|
25
|
+
# NOTE: when you enable cookie below in the FB.init call
|
26
|
+
# the GET request in the FB.login callback will send
|
27
|
+
# a signed request in a cookie back the OmniAuth callback
|
28
|
+
# which will parse out the authorization code and obtain
|
29
|
+
# the access_token. This will be the exact same access_token
|
30
|
+
# returned to the client in response.authResponse.accessToken.
|
31
|
+
<<-END
|
32
|
+
<html>
|
33
|
+
<head>
|
34
|
+
<title>Client-side Flow Example</title>
|
35
|
+
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js" type="text/javascript"></script>
|
36
|
+
</head>
|
37
|
+
<body>
|
38
|
+
<div id="fb-root"></div>
|
39
|
+
|
40
|
+
<script type="text/javascript">
|
41
|
+
window.fbAsyncInit = function() {
|
42
|
+
FB.init({
|
43
|
+
appId : '#{ENV['APP_ID']}',
|
44
|
+
status : true, // check login status
|
45
|
+
cookie : true, // enable cookies to allow the server to access the session
|
46
|
+
xfbml : true // parse XFBML
|
47
|
+
});
|
48
|
+
};
|
49
|
+
|
50
|
+
(function(d) {
|
51
|
+
var js, id = 'facebook-jssdk'; if (d.getElementById(id)) {return;}
|
52
|
+
js = d.createElement('script'); js.id = id; js.async = true;
|
53
|
+
js.src = "//connect.facebook.net/en_US/all.js";
|
54
|
+
d.getElementsByTagName('head')[0].appendChild(js);
|
55
|
+
}(document));
|
56
|
+
|
57
|
+
$(function() {
|
58
|
+
$('a').click(function(e) {
|
59
|
+
e.preventDefault();
|
60
|
+
|
61
|
+
FB.login(function(response) {
|
62
|
+
if (response.authResponse) {
|
63
|
+
$('#connect').html('Connected! Hitting OmniAuth callback (GET /auth/facebook/callback)...');
|
64
|
+
|
65
|
+
// since we have cookies enabled, this request will allow omniauth to parse
|
66
|
+
// out the auth code from the signed request in the fbsr_XXX cookie
|
67
|
+
$.getJSON('/auth/facebook/callback', function(json) {
|
68
|
+
$('#connect').html('Connected! Callback complete.');
|
69
|
+
$('#results').html(JSON.stringify(json));
|
70
|
+
});
|
71
|
+
}
|
72
|
+
}, { scope: '#{SCOPE}' });
|
73
|
+
});
|
74
|
+
});
|
75
|
+
</script>
|
76
|
+
|
77
|
+
<p id="connect">
|
78
|
+
<a href="#">Connect to FB</a>
|
79
|
+
</p>
|
80
|
+
|
81
|
+
<p id="results" />
|
82
|
+
</body>
|
83
|
+
</html>
|
84
|
+
END
|
85
|
+
end
|
86
|
+
|
87
|
+
# auth via FB canvas and signed request param
|
88
|
+
post '/canvas/' do
|
89
|
+
# we just redirect to /auth/facebook here which will parse the
|
90
|
+
# signed_request FB sends us, asking for auth if the user has
|
91
|
+
# not already granted access, or simply moving straight to the
|
92
|
+
# callback where they have already granted access.
|
93
|
+
#
|
94
|
+
# we pass the state parameter which we detect in our callback
|
95
|
+
# to do custom rendering/redirection for the canvas app page
|
96
|
+
redirect "/auth/facebook?signed_request=#{request.params['signed_request']}&state=canvas"
|
97
|
+
end
|
98
|
+
|
99
|
+
get '/auth/:provider/callback' do
|
100
|
+
# we can do something special here is +state+ param is canvas
|
101
|
+
# (see notes abovein /canvas/ method for more details)
|
102
|
+
content_type 'application/json'
|
103
|
+
MultiJson.encode(request.env)
|
104
|
+
end
|
105
|
+
|
106
|
+
get '/auth/failure' do
|
107
|
+
content_type 'application/json'
|
108
|
+
MultiJson.encode(request.env)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
use Rack::Session::Cookie
|
113
|
+
|
114
|
+
use OmniAuth::Builder do
|
115
|
+
provider :facebook, ENV['APP_ID'], ENV['APP_SECRET'], :scope => SCOPE
|
116
|
+
end
|
117
|
+
|
118
|
+
run App.new
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'omniauth/strategies/oauth2'
|
2
|
+
require 'base64'
|
3
|
+
require 'openssl'
|
4
|
+
require 'rack/utils'
|
5
|
+
|
6
|
+
module OmniAuth
|
7
|
+
module Strategies
|
8
|
+
class GranicusAdmin < OmniAuth::Strategies::OAuth2
|
9
|
+
class NoAuthorizationCodeError < StandardError; end
|
10
|
+
|
11
|
+
DEFAULT_SCOPE = ''
|
12
|
+
|
13
|
+
option :name, 'granicus_admin'
|
14
|
+
|
15
|
+
option :client_options, {
|
16
|
+
:site => 'https://citizen.dev.granicus.com',
|
17
|
+
:token_url => '/auth/oauth/token',
|
18
|
+
:authorize_url => '/auth/oauth/authorize'
|
19
|
+
}
|
20
|
+
|
21
|
+
option :token_params, {
|
22
|
+
:parse => :query
|
23
|
+
}
|
24
|
+
|
25
|
+
option :access_token_options, {
|
26
|
+
:header_format => 'OAuth %s',
|
27
|
+
:param_name => 'access_token'
|
28
|
+
}
|
29
|
+
|
30
|
+
option :authorize_options, [:scope, :display]
|
31
|
+
|
32
|
+
uid { raw_info['id'] }
|
33
|
+
|
34
|
+
info do
|
35
|
+
prune!({
|
36
|
+
'nickname' => raw_info['username'],
|
37
|
+
'email' => raw_info['email'],
|
38
|
+
'name' => raw_info['name'],
|
39
|
+
'first_name' => raw_info['first_name'],
|
40
|
+
'last_name' => raw_info['last_name'],
|
41
|
+
'image' => "#{options[:secure_image_url] ? 'https' : 'http'}://graph.facebook.com/#{uid}/picture?type=square",
|
42
|
+
'description' => raw_info['bio'],
|
43
|
+
'urls' => {
|
44
|
+
'Facebook' => raw_info['link'],
|
45
|
+
'Website' => raw_info['website']
|
46
|
+
},
|
47
|
+
'location' => (raw_info['location'] || {})['name'],
|
48
|
+
'verified' => raw_info['verified']
|
49
|
+
})
|
50
|
+
end
|
51
|
+
|
52
|
+
credentials do
|
53
|
+
prune!({
|
54
|
+
'expires' => access_token.expires?,
|
55
|
+
'expires_at' => access_token.expires_at
|
56
|
+
})
|
57
|
+
end
|
58
|
+
|
59
|
+
extra do
|
60
|
+
prune!({
|
61
|
+
'raw_info' => raw_info
|
62
|
+
})
|
63
|
+
end
|
64
|
+
|
65
|
+
def raw_info
|
66
|
+
@raw_info ||= access_token.get('/me').parsed
|
67
|
+
end
|
68
|
+
|
69
|
+
def build_access_token
|
70
|
+
with_authorization_code! { super }.tap do |token|
|
71
|
+
token.options.merge!(access_token_options)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# NOTE if we're using code from the signed request
|
76
|
+
# then FB sets the redirect_uri to '' during the authorize
|
77
|
+
# phase + it must match during the access_token phase:
|
78
|
+
# https://github.com/facebook/php-sdk/blob/master/src/base_facebook.php#L348
|
79
|
+
def callback_url
|
80
|
+
options[:callback_url] || super
|
81
|
+
end
|
82
|
+
|
83
|
+
def access_token_options
|
84
|
+
options.access_token_options.inject({}) { |h,(k,v)| h[k.to_sym] = v; h }
|
85
|
+
end
|
86
|
+
|
87
|
+
##
|
88
|
+
# You can pass +display+, +state+ or +scope+ params to the auth request, if
|
89
|
+
# you need to set them dynamically. You can also set these options
|
90
|
+
# in the OmniAuth config :authorize_params option.
|
91
|
+
#
|
92
|
+
# /auth/granicus_admin?host=sacramento.granicus.com
|
93
|
+
#
|
94
|
+
def authorize_params
|
95
|
+
super.tap do |params|
|
96
|
+
%w[host scope].each { |v| params[v.to_sym] = request.params[v] if request.params[v] }
|
97
|
+
params[:scope] ||= DEFAULT_SCOPE
|
98
|
+
if !params[:host].nil?
|
99
|
+
options.client_options[:site] = "https://#{params[:host]}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
##
|
107
|
+
# Picks the authorization code in order, from:
|
108
|
+
#
|
109
|
+
# the request 'code' param (manual callback from standard server-side flow)
|
110
|
+
#
|
111
|
+
def with_authorization_code!
|
112
|
+
if request.params.key?('code')
|
113
|
+
yield
|
114
|
+
else
|
115
|
+
raise NoAuthorizationCodeError, 'must pass a `code` parameter'
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def prune!(hash)
|
120
|
+
hash.delete_if do |_, value|
|
121
|
+
prune!(value) if value.is_a?(Hash)
|
122
|
+
value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# def valid_signature?(secret, signature, payload, algorithm = OpenSSL::Digest::SHA256.new)
|
127
|
+
# OpenSSL::HMAC.digest(algorithm, secret, payload) == signature
|
128
|
+
# end
|
129
|
+
|
130
|
+
def base64_decode_url(value)
|
131
|
+
value += '=' * (4 - value.size.modulo(4))
|
132
|
+
Base64.decode64(value.tr('-_', '+/'))
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'omniauth/granicus-admin'
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path('../lib', __FILE__)
|
3
|
+
require 'omniauth/granicus/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'omniauth-granicus'
|
7
|
+
s.version = OmniAuth::GranicusAdmin::VERSION
|
8
|
+
s.authors = ['Javier Muniz']
|
9
|
+
s.email = ['javier@granicus.com']
|
10
|
+
s.summary = 'Granicus Admin strategy for OmniAuth'
|
11
|
+
s.homepage = 'https://github.com/granicus/omniauth-granicus-admin'
|
12
|
+
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
16
|
+
s.require_paths = ['lib']
|
17
|
+
|
18
|
+
s.add_runtime_dependency 'omniauth-oauth2', '~> 1.0.0'
|
19
|
+
|
20
|
+
s.add_development_dependency 'rspec', '~> 2.7.0'
|
21
|
+
s.add_development_dependency 'rake'
|
22
|
+
end
|
@@ -0,0 +1,339 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'omniauth-granicus-admin'
|
3
|
+
require 'openssl'
|
4
|
+
require 'base64'
|
5
|
+
|
6
|
+
describe OmniAuth::Strategies::GranicusAdmin do
|
7
|
+
before :each do
|
8
|
+
@request = double('Request')
|
9
|
+
@request.stub(:params) { {} }
|
10
|
+
@request.stub(:cookies) { {} }
|
11
|
+
@request.stub(:env) { {} }
|
12
|
+
|
13
|
+
@client_id = '123'
|
14
|
+
@client_secret = '53cr3tz'
|
15
|
+
end
|
16
|
+
|
17
|
+
subject do
|
18
|
+
args = [@client_id, @client_secret, @options].compact
|
19
|
+
OmniAuth::Strategies::GranicusAdmin.new(nil, *args).tap do |strategy|
|
20
|
+
strategy.stub(:request) { @request }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it_should_behave_like 'an oauth2 strategy'
|
25
|
+
|
26
|
+
describe '#client' do
|
27
|
+
it 'has correct Granicus site' do
|
28
|
+
subject.client.site.should eq('https://citizen.dev.granicus.com')
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'has correct authorize url' do
|
32
|
+
subject.client.options[:authorize_url].should eq('/auth/oauth/authorize')
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'has correct token url' do
|
36
|
+
subject.client.options[:token_url].should eq('/auth/oauth/token')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#callback_url' do
|
41
|
+
it "returns the default callback url" do
|
42
|
+
url_base = 'http://auth.request.com'
|
43
|
+
@request.stub(:url) { "#{url_base}/some/page" }
|
44
|
+
subject.stub(:script_name) { '' } # as not to depend on Rack env
|
45
|
+
subject.callback_url.should eq("#{url_base}/auth/granicus_admin/callback")
|
46
|
+
end
|
47
|
+
|
48
|
+
it "returns path from callback_path option" do
|
49
|
+
@options = { :callback_path => "/auth/FB/done"}
|
50
|
+
url_base = 'http://auth.request.com'
|
51
|
+
@request.stub(:url) { "#{url_base}/page/path" }
|
52
|
+
subject.stub(:script_name) { '' } # as not to depend on Rack env
|
53
|
+
subject.callback_url.should eq("#{url_base}/auth/FB/done")
|
54
|
+
end
|
55
|
+
|
56
|
+
it "returns url from callback_url option" do
|
57
|
+
url = 'https://auth.myapp.com/auth/fb/callback'
|
58
|
+
@options = { :callback_url => url }
|
59
|
+
subject.callback_url.should eq(url)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe '#authorize_params' do
|
64
|
+
it 'includes default scope for session access' do
|
65
|
+
subject.authorize_params.should be_a(Hash)
|
66
|
+
subject.authorize_params[:scope].should eq('')
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'changes site to site defined by host param in request when present' do
|
70
|
+
@request.stub(:params) { { 'host' => 'dev.dev.granicus.com' } }
|
71
|
+
subject.authorize_params.should be_a(Hash)
|
72
|
+
subject.authorize_params[:host].should eq('dev.dev.granicus.com')
|
73
|
+
subject.client.site.should eq('https://dev.dev.granicus.com')
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'overrides default scope with parameter passed from request' do
|
77
|
+
@request.stub(:params) { { 'scope' => 'email' } }
|
78
|
+
subject.authorize_params.should be_a(Hash)
|
79
|
+
subject.authorize_params[:scope].should eq('email')
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe '#token_params' do
|
84
|
+
it 'has correct parse strategy' do
|
85
|
+
subject.token_params[:parse].should eq(:query)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe '#access_token_options' do
|
90
|
+
it 'has correct param name by default' do
|
91
|
+
subject.access_token_options[:param_name].should eq('access_token')
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'has correct header format by default' do
|
95
|
+
subject.access_token_options[:header_format].should eq('OAuth %s')
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe '#uid' do
|
100
|
+
before :each do
|
101
|
+
subject.stub(:raw_info) { { 'id' => '123' } }
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'returns the id from raw_info' do
|
105
|
+
subject.uid.should eq('123')
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe '#info' do
|
110
|
+
context 'when optional data is not present in raw info' do
|
111
|
+
before :each do
|
112
|
+
@raw_info ||= { 'name' => 'Fred Smith' }
|
113
|
+
subject.stub(:raw_info) { @raw_info }
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'has no email key' do
|
117
|
+
subject.info.should_not have_key('email')
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'has no nickname key' do
|
121
|
+
subject.info.should_not have_key('nickname')
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'has no first name key' do
|
125
|
+
subject.info.should_not have_key('first_name')
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'has no last name key' do
|
129
|
+
subject.info.should_not have_key('last_name')
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'has no location key' do
|
133
|
+
subject.info.should_not have_key('location')
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'has no description key' do
|
137
|
+
subject.info.should_not have_key('description')
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'has no urls' do
|
141
|
+
subject.info.should_not have_key('urls')
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'has no verified key' do
|
145
|
+
subject.info.should_not have_key('verified')
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
context 'when optional data is present in raw info' do
|
150
|
+
before :each do
|
151
|
+
@raw_info ||= { 'name' => 'Fred Smith' }
|
152
|
+
subject.stub(:raw_info) { @raw_info }
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'returns the name' do
|
156
|
+
subject.info['name'].should eq('Fred Smith')
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'returns the email' do
|
160
|
+
@raw_info['email'] = 'fred@smith.com'
|
161
|
+
subject.info['email'].should eq('fred@smith.com')
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'returns the username as nickname' do
|
165
|
+
@raw_info['username'] = 'fredsmith'
|
166
|
+
subject.info['nickname'].should eq('fredsmith')
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'returns the first name' do
|
170
|
+
@raw_info['first_name'] = 'Fred'
|
171
|
+
subject.info['first_name'].should eq('Fred')
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'returns the last name' do
|
175
|
+
@raw_info['last_name'] = 'Smith'
|
176
|
+
subject.info['last_name'].should eq('Smith')
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'returns the location name as location' do
|
180
|
+
@raw_info['location'] = { 'id' => '104022926303756', 'name' => 'Palo Alto, California' }
|
181
|
+
subject.info['location'].should eq('Palo Alto, California')
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'returns bio as description' do
|
185
|
+
@raw_info['bio'] = 'I am great'
|
186
|
+
subject.info['description'].should eq('I am great')
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'returns the square format granicus avatar url' do
|
190
|
+
@raw_info['id'] = '321'
|
191
|
+
subject.info['image'].should eq('http://graph.facebook.com/321/picture?type=square')
|
192
|
+
end
|
193
|
+
|
194
|
+
it 'returns the Facebook link as the Facebook url' do
|
195
|
+
@raw_info['link'] = 'http://www.facebook.com/fredsmith'
|
196
|
+
subject.info['urls'].should be_a(Hash)
|
197
|
+
subject.info['urls']['Facebook'].should eq('http://www.facebook.com/fredsmith')
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'returns website url' do
|
201
|
+
@raw_info['website'] = 'https://my-wonderful-site.com'
|
202
|
+
subject.info['urls'].should be_a(Hash)
|
203
|
+
subject.info['urls']['Website'].should eq('https://my-wonderful-site.com')
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'return both Facebook link and website urls' do
|
207
|
+
@raw_info['link'] = 'http://www.facebook.com/fredsmith'
|
208
|
+
@raw_info['website'] = 'https://my-wonderful-site.com'
|
209
|
+
subject.info['urls'].should be_a(Hash)
|
210
|
+
subject.info['urls']['Facebook'].should eq('http://www.facebook.com/fredsmith')
|
211
|
+
subject.info['urls']['Website'].should eq('https://my-wonderful-site.com')
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'returns the positive verified status' do
|
215
|
+
@raw_info['verified'] = true
|
216
|
+
subject.info['verified'].should be_true
|
217
|
+
end
|
218
|
+
|
219
|
+
it 'returns the negative verified status' do
|
220
|
+
@raw_info['verified'] = false
|
221
|
+
subject.info['verified'].should be_false
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
it 'returns the secure facebook avatar url when `secure_image_url` option is specified' do
|
226
|
+
@options = { :secure_image_url => true }
|
227
|
+
raw_info = { 'name' => 'Fred Smith', 'id' => '321' }
|
228
|
+
subject.stub(:raw_info) { raw_info }
|
229
|
+
subject.info['image'].should eq('https://graph.facebook.com/321/picture?type=square')
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
describe '#raw_info' do
|
234
|
+
before :each do
|
235
|
+
@access_token = double('OAuth2::AccessToken')
|
236
|
+
subject.stub(:access_token) { @access_token }
|
237
|
+
end
|
238
|
+
|
239
|
+
it 'performs a GET to https://graph.facebook.com/me' do
|
240
|
+
@access_token.stub(:get) { double('OAuth2::Response').as_null_object }
|
241
|
+
@access_token.should_receive(:get).with('/me')
|
242
|
+
subject.raw_info
|
243
|
+
end
|
244
|
+
|
245
|
+
it 'returns a Hash' do
|
246
|
+
@access_token.stub(:get).with('/me') do
|
247
|
+
raw_response = double('Faraday::Response')
|
248
|
+
raw_response.stub(:body) { '{ "ohai": "thar" }' }
|
249
|
+
raw_response.stub(:status) { 200 }
|
250
|
+
raw_response.stub(:headers) { { 'Content-Type' => 'application/json' } }
|
251
|
+
OAuth2::Response.new(raw_response)
|
252
|
+
end
|
253
|
+
subject.raw_info.should be_a(Hash)
|
254
|
+
subject.raw_info['ohai'].should eq('thar')
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
describe '#credentials' do
|
259
|
+
before :each do
|
260
|
+
@access_token = double('OAuth2::AccessToken')
|
261
|
+
@access_token.stub(:token)
|
262
|
+
@access_token.stub(:expires?)
|
263
|
+
@access_token.stub(:expires_at)
|
264
|
+
@access_token.stub(:refresh_token)
|
265
|
+
subject.stub(:access_token) { @access_token }
|
266
|
+
end
|
267
|
+
|
268
|
+
it 'returns a Hash' do
|
269
|
+
subject.credentials.should be_a(Hash)
|
270
|
+
end
|
271
|
+
|
272
|
+
it 'returns the token' do
|
273
|
+
@access_token.stub(:token) { '123' }
|
274
|
+
subject.credentials['token'].should eq('123')
|
275
|
+
end
|
276
|
+
|
277
|
+
it 'returns the expiry status' do
|
278
|
+
@access_token.stub(:expires?) { true }
|
279
|
+
subject.credentials['expires'].should eq(true)
|
280
|
+
|
281
|
+
@access_token.stub(:expires?) { false }
|
282
|
+
subject.credentials['expires'].should eq(false)
|
283
|
+
end
|
284
|
+
|
285
|
+
it 'returns the refresh token and expiry time when expiring' do
|
286
|
+
ten_mins_from_now = (Time.now + 600).to_i
|
287
|
+
@access_token.stub(:expires?) { true }
|
288
|
+
@access_token.stub(:refresh_token) { '321' }
|
289
|
+
@access_token.stub(:expires_at) { ten_mins_from_now }
|
290
|
+
subject.credentials['refresh_token'].should eq('321')
|
291
|
+
subject.credentials['expires_at'].should eq(ten_mins_from_now)
|
292
|
+
end
|
293
|
+
|
294
|
+
it 'does not return the refresh token when it is nil and expiring' do
|
295
|
+
@access_token.stub(:expires?) { true }
|
296
|
+
@access_token.stub(:refresh_token) { nil }
|
297
|
+
subject.credentials['refresh_token'].should be_nil
|
298
|
+
subject.credentials.should_not have_key('refresh_token')
|
299
|
+
end
|
300
|
+
|
301
|
+
it 'does not return the refresh token when not expiring' do
|
302
|
+
@access_token.stub(:expires?) { false }
|
303
|
+
@access_token.stub(:refresh_token) { 'XXX' }
|
304
|
+
subject.credentials['refresh_token'].should be_nil
|
305
|
+
subject.credentials.should_not have_key('refresh_token')
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
describe '#extra' do
|
310
|
+
before :each do
|
311
|
+
@raw_info = { 'name' => 'Fred Smith' }
|
312
|
+
subject.stub(:raw_info) { @raw_info }
|
313
|
+
end
|
314
|
+
|
315
|
+
it 'returns a Hash' do
|
316
|
+
subject.extra.should be_a(Hash)
|
317
|
+
end
|
318
|
+
|
319
|
+
it 'contains raw info' do
|
320
|
+
subject.extra.should eq({ 'raw_info' => @raw_info })
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
private
|
325
|
+
|
326
|
+
def signed_request(payload, secret)
|
327
|
+
encoded_payload = base64_encode_url(MultiJson.dump(payload))
|
328
|
+
encoded_signature = base64_encode_url(signature(encoded_payload, secret))
|
329
|
+
[encoded_signature, encoded_payload].join('.')
|
330
|
+
end
|
331
|
+
|
332
|
+
def base64_encode_url(value)
|
333
|
+
Base64.encode64(value).tr('+/', '-_').gsub(/\n/, '')
|
334
|
+
end
|
335
|
+
|
336
|
+
def signature(payload, secret, algorithm = OpenSSL::Digest::SHA256.new)
|
337
|
+
OpenSSL::HMAC.digest(algorithm, secret, payload)
|
338
|
+
end
|
339
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# NOTE it would be useful if this lived in omniauth-oauth2 eventually
|
2
|
+
shared_examples 'an oauth2 strategy' do
|
3
|
+
describe '#client' do
|
4
|
+
it 'should be initialized with symbolized client_options' do
|
5
|
+
@options = { :client_options => { 'authorize_url' => 'https://example.com' } }
|
6
|
+
subject.client.options[:authorize_url].should == 'https://example.com'
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '#authorize_params' do
|
11
|
+
it 'should include any authorize params passed in the :authorize_params option' do
|
12
|
+
@options = { :authorize_params => { :foo => 'bar', :baz => 'zip' } }
|
13
|
+
subject.authorize_params['foo'].should eq('bar')
|
14
|
+
subject.authorize_params['baz'].should eq('zip')
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should include top-level options that are marked as :authorize_options' do
|
18
|
+
@options = { :authorize_options => [:scope, :foo], :scope => 'bar', :foo => 'baz' }
|
19
|
+
subject.authorize_params['scope'].should eq('bar')
|
20
|
+
subject.authorize_params['foo'].should eq('baz')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#token_params' do
|
25
|
+
it 'should include any authorize params passed in the :authorize_params option' do
|
26
|
+
@options = { :token_params => { :foo => 'bar', :baz => 'zip' } }
|
27
|
+
subject.token_params['foo'].should eq('bar')
|
28
|
+
subject.token_params['baz'].should eq('zip')
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should include top-level options that are marked as :authorize_options' do
|
32
|
+
@options = { :token_options => [:scope, :foo], :scope => 'bar', :foo => 'baz' }
|
33
|
+
subject.token_params['scope'].should eq('bar')
|
34
|
+
subject.token_params['foo'].should eq('baz')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: omniauth-granicus
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Javier Muniz
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-24 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: omniauth-oauth2
|
16
|
+
requirement: &2152710860 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.0.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2152710860
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &2152703180 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 2.7.0
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *2152703180
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rake
|
38
|
+
requirement: &2152702700 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *2152702700
|
47
|
+
description:
|
48
|
+
email:
|
49
|
+
- javier@granicus.com
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- .gitignore
|
55
|
+
- Gemfile
|
56
|
+
- README.md
|
57
|
+
- Rakefile
|
58
|
+
- example/Gemfile
|
59
|
+
- example/Gemfile.lock
|
60
|
+
- example/config.ru
|
61
|
+
- lib/omniauth-granicus-admin.rb
|
62
|
+
- lib/omniauth/granicus-admin.rb
|
63
|
+
- lib/omniauth/granicus/version.rb
|
64
|
+
- lib/omniauth/strategies/granicus-admin.rb
|
65
|
+
- omniauth-granicus.gemspec
|
66
|
+
- spec/omniauth/strategies/granicus_spec.rb
|
67
|
+
- spec/spec_helper.rb
|
68
|
+
- spec/support/shared_examples.rb
|
69
|
+
homepage: https://github.com/granicus/omniauth-granicus-admin
|
70
|
+
licenses: []
|
71
|
+
post_install_message:
|
72
|
+
rdoc_options: []
|
73
|
+
require_paths:
|
74
|
+
- lib
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ! '>='
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ! '>='
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
requirements: []
|
88
|
+
rubyforge_project:
|
89
|
+
rubygems_version: 1.8.5
|
90
|
+
signing_key:
|
91
|
+
specification_version: 3
|
92
|
+
summary: Granicus Admin strategy for OmniAuth
|
93
|
+
test_files:
|
94
|
+
- spec/omniauth/strategies/granicus_spec.rb
|
95
|
+
- spec/spec_helper.rb
|
96
|
+
- spec/support/shared_examples.rb
|