-
-
Notifications
You must be signed in to change notification settings - Fork 305
/
Copy pathoauth2.rb
131 lines (112 loc) · 4.58 KB
/
oauth2.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
require 'oauth2'
require 'omniauth'
require 'securerandom'
require 'socket' # for SocketError
require 'timeout' # for Timeout::Error
require 'faraday' # for Faraday::Error::TimeoutError and Faraday::Error::ConnectionFailed
require 'multi_json' # for MultiJson::DecodeError
module OmniAuth
module Strategies
# Authentication strategy for connecting with APIs constructed using
# the [OAuth 2.0 Specification](http://tools.ietf.org/html/draft-ietf-oauth-v2-10).
# You must generally register your application with the provider and
# utilize an application id and secret in order to authenticate using
# OAuth 2.0.
class OAuth2
include OmniAuth::Strategy
args [:client_id, :client_secret]
option :client_id, nil
option :client_secret, nil
option :client_options, {}
option :authorize_params, {}
option :authorize_options, [:scope]
option :token_params, {}
option :token_options, []
option :auth_token_params, {}
option :provider_ignores_state, false
attr_accessor :access_token
def client
::OAuth2::Client.new(options.client_id, options.client_secret, deep_symbolize(options.client_options))
end
def callback_url
full_host + script_name + callback_path
end
credentials do
hash = {'token' => access_token.token}
hash.merge!('refresh_token' => access_token.refresh_token) if access_token.expires? && access_token.refresh_token
hash.merge!('expires_at' => access_token.expires_at) if access_token.expires?
hash.merge!('expires' => access_token.expires?)
hash
end
def request_phase
redirect client.auth_code.authorize_url({:redirect_uri => callback_url}.merge(authorize_params))
end
def authorize_params
options.authorize_params[:state] = SecureRandom.hex(24)
params = options.authorize_params.merge(options_for('authorize'))
if OmniAuth.config.test_mode
@env ||= {}
@env['rack.session'] ||= {}
end
session['omniauth.state'] = params[:state]
params
end
def token_params
options.token_params.merge(options_for('token'))
end
def callback_phase # rubocop:disable AbcSize, CyclomaticComplexity, MethodLength, PerceivedComplexity
error = request.params['error_reason'] || request.params['error']
if error
fail!(error, CallbackError.new(request.params['error'], request.params['error_description'] || request.params['error_reason'], request.params['error_uri']))
elsif !options.provider_ignores_state && (request.params['state'].to_s.empty? || request.params['state'] != session.delete('omniauth.state'))
fail!(:csrf_detected, CallbackError.new(:csrf_detected, 'CSRF detected'))
else
self.access_token = build_access_token
self.access_token = access_token.refresh! if access_token.expired?
super
end
rescue ::OAuth2::Error, CallbackError => e
fail!(:invalid_credentials, e)
rescue ::MultiJson::DecodeError => e
fail!(:invalid_response, e)
rescue ::Timeout::Error, ::Errno::ETIMEDOUT, Faraday::Error::TimeoutError => e
fail!(:timeout, e)
rescue ::SocketError, Faraday::Error::ConnectionFailed => e
fail!(:failed_to_connect, e)
end
protected
def build_access_token
verifier = request.params['code']
client.auth_code.get_token(verifier, {:redirect_uri => callback_url}.merge(token_params.to_hash(:symbolize_keys => true)), deep_symbolize(options.auth_token_params))
end
def deep_symbolize(options)
hash = {}
options.each do |key, value|
hash[key.to_sym] = value.is_a?(Hash) ? deep_symbolize(value) : value
end
hash
end
def options_for(option)
hash = {}
options.send(:"#{option}_options").select { |key| options[key] }.each do |key|
hash[key.to_sym] = options[key]
end
hash
end
# An error that is indicated in the OAuth 2.0 callback.
# This could be a `redirect_uri_mismatch` or other
class CallbackError < StandardError
attr_accessor :error, :error_reason, :error_uri
def initialize(error, error_reason = nil, error_uri = nil)
self.error = error
self.error_reason = error_reason
self.error_uri = error_uri
end
def message
[error, error_reason, error_uri].compact.join(' | ')
end
end
end
end
end
OmniAuth.config.add_camelization 'oauth2', 'OAuth2'