-
Notifications
You must be signed in to change notification settings - Fork 0
/
google_login.rb
141 lines (115 loc) · 4.71 KB
/
google_login.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
require "net/https"
require "uri"
module GoogleLogin
# == ClientLogin
#
# Use this Class to get an auth-token
class ClientLogin
# Base Exception class
LoginError = Class.new Exception
# All the possible exceptions
[
"BadAuthentication",
"NotVerified",
"TermsNotAgreed",
"CaptchaRequired",
"Unknown",
"AccountDeleted",
"AccountDisabled",
"ServiceDisabled",
"ServiceUnavailable",
].each do |const|
const_set const, Class.new(LoginError)
end
DEFAULTS = {
:accountType => 'HOSTED_OR_GOOGLE' ,
:source => 'companyName-applicationName-versionID',
:service => 'service-identifier'
}
attr_reader :auth, :sid, :lsid, :captcha_url
# specify the :service, :source and optionally :accountType
#
# [:service] the service identifier, check the google api documentation.
#
# [:source] the name of your application. String should be in the form
# "companyName-applicationName-versionID".
#
# [:accountType] one of the following values:
# "GOOGLE", "HOSTED", "HOSTED_OR_GOOGLE" (default if none
# given)
def initialize(arghash = {})
@options = DEFAULTS.merge arghash
end
# authenticate a user, which sets the auth, sid and lsid instance_variables
# if you provide a block, it will be called with a captcha url if google
# forces you to answer the captcha. Make sure you return the anwer in the block.
#
# if no block is given, this will raise a CaptchaRequired error.
# you can rescue them and show the url via the captcha_url method.
#
# you can then call authenticate and as 3rd parameter you provide the
# captcha answer.
#
# all Exceptions this raises are subclasses of ClientLogin::LoginError.
# so make sure you handle them.
#
# This is a list of all the possible errors and their meaning
# Error code:: Description
# BadAuthentication:: The login request used a username or password that is not recognized.
# NotVerified:: The account email address has not been verified. The user will need to access their Google account directly to resolve the issue before logging in using a non-Google application.
# TermsNotAgreed:: The user has not agreed to terms. The user will need to access their Google account directly to resolve the issue before logging in using a non-Google application.
# CaptchaRequired:: A CAPTCHA is required. (A response with this error code will also contain an image URL and a CAPTCHA token.)
# Unknown:: The error is unknown or unspecified; the request contained invalid input or was malformed.
# AccountDeleted:: The user account has been deleted.
# AccountDisabled:: The user account has been disabled.
# ServiceDisabled:: The user's access to the specified service has been disabled. (The user account may still be valid.)
# ServiceUnavailable:: The service is not available; try again later.
def authenticate(username, password, captcha_response = nil)
@options[:Email], @options[:Passwd] = username, password
# set logincaptcha, captchatoken will already be set
@options[:logincaptcha] = captcha_response if captcha_response
parse_response perform_request
rescue CaptchaRequired
if block_given?
@options[:logincaptcha] = yield captcha_url
retry
else
raise CaptchaRequired
end
end
private
def perform_request
request = Net::HTTP::Post.new '/accounts/ClientLogin'
request.form_data = @options
https = Net::HTTP.new 'www.google.com', 443
https.use_ssl = true
https.request request
end
def parse_body(response_body)
response_body.scan(/(\w+)=(.+)\n/).each do |key, value|
instance_variable_set "@#{key.downcase}" , value
end
end
def parse_response(response)
if response.code_type == Net::HTTPOK
parse_body response.body
else
handle_error response.body
end
end
def handle_error(response_body)
error_message = response_body.match(/Error=(\w+)\n/)[1].strip
if error_message == "CaptchaRequired"
@options[:logintoken] = response_body.match(/CaptchaToken=(.+)\n/)[1]
self.captcha_url = response_body.match(/CaptchaUrl=(.+)\n/)[1]
end
raise_error_class error_message
end
def raise_error_class(error_message)
raise self.class.const_get error_message
end
def captcha_url=(url)
@captcha_url = "http://www.google.com/accounts/" << url
end
end
end