omniauth-stackoverflow 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ *.gem
2
+ .bundle
3
+ .rspec
4
+ /Gemfile.lock
5
+ pkg/*
6
+ .powenv
7
+ tmp
8
+ bin
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ rvm:
2
+ - 1.8.7
3
+ - 1.9.2
4
+ - 1.9.3
5
+ - jruby
6
+ branches:
7
+ only:
8
+ - master
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+
5
+ gem 'jruby-openssl', :platform => :jruby
data/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # OmniAuth StackOverflow
2
+
3
+ StackOverflow OAuth2 Strategy for OmniAuth 1.0.
4
+
5
+ All credit due to [Mark Dodwell](https://github.com/mkdynamic), this repository is a hacked up copy of his [Facebook OAuth2 Strategy](https://github.com/mkdynamic/omniauth-facebook). When I get a chance I will put all the specs back in and everything, promise!
6
+
7
+ Configuration is a little different to other Omniauth strategies, Stack Exchange use an app id as well, configure it like this (in the Devise initialiser);
8
+
9
+ config.omniauth :stackoverflow, '<client_id>', '<oauth secret>', :scope => 'no_expiry', :oauth_key => '<oauth key>'
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,5 @@
1
+ module OmniAuth
2
+ module Stackoverflow
3
+ VERSION = "1.0.0"
4
+ end
5
+ end
@@ -0,0 +1,2 @@
1
+ require 'omniauth/stackoverflow/version'
2
+ require 'omniauth/strategies/stackoverflow'
@@ -0,0 +1,211 @@
1
+ require 'omniauth/strategies/oauth2'
2
+ require 'base64'
3
+ require 'openssl'
4
+ require 'rack/utils'
5
+
6
+ module OmniAuth
7
+ module Strategies
8
+ class Stackoverflow < OmniAuth::Strategies::OAuth2
9
+ class NoAuthorizationCodeError < StandardError; end
10
+
11
+ attr_accessor :oauth_key
12
+
13
+ DEFAULT_SCOPE = 'email'
14
+
15
+ option :client_options, {
16
+ :site => 'https://stackexchange.com',
17
+ :token_url => '/oauth/access_token'
18
+ }
19
+
20
+ option :token_params, {
21
+ :parse => :query
22
+ }
23
+
24
+ option :access_token_options, {
25
+ :header_format => 'OAuth %s',
26
+ :param_name => 'access_token'
27
+ }
28
+
29
+ option :authorize_options, [:scope, :display, :oauth_key]
30
+
31
+ uid { raw_info['user_id'] }
32
+
33
+ info do
34
+ prune!({
35
+ 'id' => raw_info['user_id'],
36
+ 'display_name' => raw_info['display_name'],
37
+ 'email' => "#{raw_info['user_id']}@stackoverflow.com",
38
+ 'image' => raw_info['profile_image']
39
+ })
40
+ end
41
+
42
+ extra do
43
+ hash = {}
44
+ hash['raw_info'] = raw_info unless skip_info?
45
+ prune! hash
46
+ end
47
+
48
+ def raw_info
49
+ access_token.client.site = "https://api.stackexchange.com"
50
+ @raw_info ||= access_token.get('/2.0/me', :params => { 'site' => 'stackoverflow', 'access_token' => access_token.token, 'key' => @oauth_key }).parsed["items"].first || {}
51
+ end
52
+
53
+ def build_access_token
54
+
55
+ if signed_request_contains_access_token?
56
+
57
+ hash = signed_request.clone
58
+ ::OAuth2::AccessToken.new(
59
+ client,
60
+ hash.delete('oauth_token'),
61
+ hash.merge!(access_token_options.merge(:expires_at => hash.delete('expires')))
62
+ )
63
+ else
64
+ with_authorization_code! { super }.tap do |token|
65
+ token.options.merge!(access_token_options)
66
+ end
67
+ end
68
+ end
69
+
70
+ def request_phase
71
+ if signed_request_contains_access_token?
72
+
73
+ # if we already have an access token, we can just hit the
74
+ # callback URL directly and pass the signed request along
75
+ params = { :signed_request => raw_signed_request }
76
+ params[:state] = request.params['state'] if request.params['state']
77
+
78
+ query = Rack::Utils.build_query(params)
79
+
80
+ url = callback_url
81
+ url << "?" unless url.match(/\?/)
82
+ url << "&" unless url.match(/[\&\?]$/)
83
+ url << query
84
+
85
+ redirect url
86
+ else
87
+ super
88
+ end
89
+ end
90
+
91
+ def callback_phase
92
+ @oauth_key = authorize_params[:oauth_key]
93
+
94
+ super
95
+ end
96
+ # NOTE if we're using code from the signed request
97
+ # then FB sets the redirect_uri to '' during the authorize
98
+ # phase + it must match during the access_token phase:
99
+ # https://github.com/facebook/php-sdk/blob/master/src/base_facebook.php#L348
100
+ def callback_url
101
+ if @authorization_code_from_signed_request
102
+ ''
103
+ else
104
+ options[:callback_url] || super
105
+ end
106
+ end
107
+
108
+ def access_token_options
109
+ options.access_token_options.inject({}) { |h,(k,v)| h[k.to_sym] = v; h }
110
+ end
111
+
112
+ ##
113
+ # You can pass +display+, +state+ or +scope+ params to the auth request, if
114
+ # you need to set them dynamically. You can also set these options
115
+ # in the OmniAuth config :authorize_params option.
116
+ #
117
+ # /auth/facebook?display=popup&state=ABC
118
+ #
119
+ def authorize_params
120
+ super.tap do |params|
121
+ %w[display state scope].each { |v| params[v.to_sym] = request.params[v] if request.params[v] }
122
+ params[:scope] ||= DEFAULT_SCOPE
123
+ end
124
+ end
125
+
126
+ ##
127
+ # Parse signed request in order, from:
128
+ #
129
+ # 1. the request 'signed_request' param (server-side flow from canvas pages) or
130
+ # 2. a cookie (client-side flow via JS SDK)
131
+ #
132
+ def signed_request
133
+ @signed_request ||= raw_signed_request &&
134
+ parse_signed_request(raw_signed_request)
135
+ end
136
+
137
+ private
138
+
139
+ def raw_signed_request
140
+ request.params['signed_request'] ||
141
+ request.cookies["fbsr_#{client.id}"]
142
+ end
143
+
144
+ ##
145
+ # If the signed_request comes from a FB canvas page and the user
146
+ # has already authorized your application, the JSON object will be
147
+ # contain the access token.
148
+ #
149
+ # https://developers.facebook.com/docs/authentication/canvas/
150
+ #
151
+ def signed_request_contains_access_token?
152
+ signed_request &&
153
+ signed_request['oauth_token']
154
+ end
155
+
156
+ ##
157
+ # Picks the authorization code in order, from:
158
+ #
159
+ # 1. the request 'code' param (manual callback from standard server-side flow)
160
+ # 2. a signed request (see #signed_request for more)
161
+ #
162
+ def with_authorization_code!
163
+ if request.params.key?('code')
164
+ yield
165
+ elsif code_from_signed_request = signed_request && signed_request['code']
166
+ request.params['code'] = code_from_signed_request
167
+ @authorization_code_from_signed_request = true
168
+ begin
169
+ yield
170
+ ensure
171
+ request.params.delete('code')
172
+ @authorization_code_from_signed_request = false
173
+ end
174
+ else
175
+ raise NoAuthorizationCodeError, 'must pass either a `code` parameter or a signed request (via `signed_request` parameter or a `fbsr_XXX` cookie)'
176
+ end
177
+ end
178
+
179
+ def prune!(hash)
180
+ hash.delete_if do |_, value|
181
+ prune!(value) if value.is_a?(Hash)
182
+ value.nil? || (value.respond_to?(:empty?) && value.empty?)
183
+ end
184
+ end
185
+
186
+ def parse_signed_request(value)
187
+ signature, encoded_payload = value.split('.')
188
+
189
+ decoded_hex_signature = base64_decode_url(signature)
190
+ decoded_payload = MultiJson.decode(base64_decode_url(encoded_payload))
191
+
192
+ unless decoded_payload['algorithm'] == 'HMAC-SHA256'
193
+ raise NotImplementedError, "unkown algorithm: #{decoded_payload['algorithm']}"
194
+ end
195
+
196
+ if valid_signature?(client.secret, decoded_hex_signature, encoded_payload)
197
+ decoded_payload
198
+ end
199
+ end
200
+
201
+ def valid_signature?(secret, signature, payload, algorithm = OpenSSL::Digest::SHA256.new)
202
+ OpenSSL::HMAC.digest(algorithm, secret, payload) == signature
203
+ end
204
+
205
+ def base64_decode_url(value)
206
+ value += '=' * (4 - value.size.modulo(4))
207
+ Base64.decode64(value.tr('-_', '+/'))
208
+ end
209
+ end
210
+ end
211
+ end
@@ -0,0 +1 @@
1
+ require 'omniauth/stackoverflow'
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+ require 'omniauth/stackoverflow/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'omniauth-stackoverflow'
7
+ s.version = OmniAuth::Stackoverflow::VERSION
8
+ s.authors = ['Mark Dodwell', 'Dan Higham']
9
+ s.email = ['mark@mkdynamic.co.uk', 'dhigham@vmware.com']
10
+ s.summary = 'StackOverflow strategy for OmniAuth'
11
+ s.homepage = 'https://github.com/danhigham/omniauth-stackoverflow'
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.2'
19
+
20
+ s.add_development_dependency 'rspec', '~> 2'
21
+ s.add_development_dependency 'rake'
22
+
23
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: omniauth-stackoverflow
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Mark Dodwell
9
+ - Dan Higham
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2012-06-25 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: omniauth-oauth2
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: 1.0.2
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ~>
29
+ - !ruby/object:Gem::Version
30
+ version: 1.0.2
31
+ - !ruby/object:Gem::Dependency
32
+ name: rspec
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ~>
37
+ - !ruby/object:Gem::Version
38
+ version: '2'
39
+ type: :development
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ version: '2'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ description:
64
+ email:
65
+ - mark@mkdynamic.co.uk
66
+ - dhigham@vmware.com
67
+ executables: []
68
+ extensions: []
69
+ extra_rdoc_files: []
70
+ files:
71
+ - .gitignore
72
+ - .travis.yml
73
+ - Gemfile
74
+ - README.md
75
+ - Rakefile
76
+ - lib/omniauth-stackoverflow.rb
77
+ - lib/omniauth/stackoverflow.rb
78
+ - lib/omniauth/stackoverflow/version.rb
79
+ - lib/omniauth/strategies/stackoverflow.rb
80
+ - omniauth-stackoverflow.gemspec
81
+ homepage: https://github.com/danhigham/omniauth-stackoverflow
82
+ licenses: []
83
+ post_install_message:
84
+ rdoc_options: []
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ! '>='
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubyforge_project:
101
+ rubygems_version: 1.8.24
102
+ signing_key:
103
+ specification_version: 3
104
+ summary: StackOverflow strategy for OmniAuth
105
+ test_files: []