/
has_authentication.rb
210 lines (182 loc) · 6.66 KB
/
has_authentication.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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
module Yt
module Associations
# Provides authentication methods to YouTube resources, which allows to
# access to content detail set-specific methods like `access_token`.
#
# YouTube resources with authentication are: {Yt::Models::Account accounts}.
module HasAuthentication
def has_authentication
require 'yt/collections/authentications'
require 'yt/collections/device_flows'
require 'yt/errors/missing_auth'
require 'yt/errors/no_items'
require 'yt/errors/unauthorized'
include Associations::Authenticable
end
end
module Authenticable
delegate :access_token, :refresh_token, :expires_at, to: :authentication
def initialize(options = {})
@access_token = options[:access_token]
@refresh_token = options[:refresh_token]
@device_code = options[:device_code]
@expires_at = options[:expires_at]
@authorization_code = options[:authorization_code]
@redirect_uri = options[:redirect_uri]
@scopes = options[:scopes]
@authentication = options[:authentication]
end
def auth
self
end
def authentication
@authentication = current_authentication
@authentication ||= use_refresh_token! if @refresh_token
@authentication ||= use_authorization_code! if @authorization_code
@authentication ||= use_device_code! if @device_code
@authentication ||= raise_missing_authentication!
end
def authentication_url
host = 'accounts.google.com'
path = '/o/oauth2/auth'
query = authentication_url_params.to_param
URI::HTTPS.build(host: host, path: path, query: query).to_s
end
# Obtains a new access token.
# Returns true if the new access token is different from the previous one
def refresh
old_access_token = authentication.access_token
@authentication = @access_token = @refreshed_authentications = nil
old_access_token != authentication.access_token
end
private
def current_authentication
@authentication ||= Yt::Authentication.new current_data if @access_token
@authentication unless @authentication.nil? || @authentication.expired?
end
def current_data
{}.tap do |data|
data['access_token'] = @access_token
data['expires_at'] = @expires_at
data['refresh_token'] = @refresh_token
end
end
# Tries to obtain an access token using the authorization code (which
# can only be used once). On failure, raise an error.
def use_authorization_code!
new_authentications.first!
rescue Errors::NoItems => error
raise Errors::Unauthorized, error.to_param
end
# Tries to obtain an access token using the refresh token (which can
# be used multiple times). On failure, raise an error.
def use_refresh_token!
refreshed_authentications.first!
rescue Errors::NoItems => error
raise Errors::Unauthorized, error.to_param
end
# Tries to obtain an access token using the device code (which must be
# confirmed by the user with the user_code). On failure, raise an error.
def use_device_code!
device_code_authentications.first!.tap do |auth|
raise Errors::MissingAuth, pending_device_code_message if auth.pending?
end
end
def raise_missing_authentication!
error_message = case
when @redirect_uri && @scopes then missing_authorization_code_message
when @scopes then pending_device_code_message
end
raise Errors::MissingAuth, error_message
end
def pending_device_code_message
@device_flow ||= device_flows.first!
@device_code ||= @device_flow.device_code
{}.tap do |params|
params[:scopes] = @scopes
params[:user_code] = @device_flow.user_code
params[:verification_url] = @device_flow.verification_url
end
end
def missing_authorization_code_message
{}.tap do |params|
params[:scopes] = @scopes
params[:authentication_url] = authentication_url
params[:redirect_uri] = @redirect_uri
end
end
def new_authentications
@new_authentications ||= Collections::Authentications.of(self).tap do |auth|
auth.auth_params = new_authentication_params
end
end
def refreshed_authentications
@refreshed_authentications ||= Collections::Authentications.of(self).tap do |auth|
auth.auth_params = refreshed_authentication_params
end
end
def device_code_authentications
Collections::Authentications.of(self).tap do |auth|
auth.auth_params = device_code_authentication_params
end
end
def device_flows
@device_flows ||= Collections::DeviceFlows.of(self).tap do |auth|
auth.auth_params = device_flow_params
end
end
def authentication_url_params
{}.tap do |params|
params[:client_id] = client_id
params[:scope] = authentication_scope
params[:redirect_uri] = @redirect_uri
params[:response_type] = :code
params[:access_type] = :offline
# params[:include_granted_scopes] = true
end
end
def authentication_scope
@scopes.map do |scope|
"https://www.googleapis.com/auth/#{scope}"
end.join(' ') if @scopes.is_a?(Array)
end
def new_authentication_params
{}.tap do |params|
params[:client_id] = client_id
params[:client_secret] = client_secret
params[:code] = @authorization_code
params[:redirect_uri] = @redirect_uri
params[:grant_type] = :authorization_code
end
end
def refreshed_authentication_params
{}.tap do |params|
params[:client_id] = client_id
params[:client_secret] = client_secret
params[:refresh_token] = @refresh_token
params[:grant_type] = :refresh_token
end
end
def device_code_authentication_params
{}.tap do |params|
params[:client_id] = client_id
params[:client_secret] = client_secret
params[:code] = @device_code
params[:grant_type] = 'http://oauth.net/grant_type/device/1.0'
end
end
def device_flow_params
{}.tap do |params|
params[:client_id] = client_id
params[:scope] = authentication_scope
end
end
def client_id
Yt.configuration.client_id
end
def client_secret
Yt.configuration.client_secret
end
end
end
end