omniauth-stackoverflow 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 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: []