imap-backup 2.2.2 → 3.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -2
- data/.rubocop_todo.yml +10 -26
- data/README.md +4 -11
- data/bin/imap-backup +3 -0
- data/docs/01-credentials-screen.png +0 -0
- data/docs/02-new-project.png +0 -0
- data/docs/03-initial-credentials-for-project.png +0 -0
- data/docs/04-credential-type-selection.png +0 -0
- data/docs/05-cant-create-without-consent-setup.png +0 -0
- data/docs/06-user-type-selection.png +0 -0
- data/docs/07-consent-screen-form.png +0 -0
- data/docs/08-app-scopes.png +0 -0
- data/docs/09-scope-selection.png +0 -0
- data/docs/10-updated-app-scopes.png +0 -0
- data/docs/11-test-users.png +0 -0
- data/docs/12-add-users.png +0 -0
- data/docs/13-create-oauth-client.png +0 -0
- data/docs/14-application-details.png +0 -0
- data/docs/16-initial-menu.png +0 -0
- data/docs/17-inputting-the-email-address.png +0 -0
- data/docs/18-choose-password.png +0 -0
- data/docs/19-supply-client-info.png +0 -0
- data/docs/20-choose-gmail-account.png +0 -0
- data/docs/21-accept-warnings.png +0 -0
- data/docs/22-grant-access.png +0 -0
- data/docs/24-confirm-choices.png +0 -0
- data/docs/25-success-code.png +0 -0
- data/docs/26-type-code-into-imap-backup.png +0 -0
- data/docs/27-success.png +0 -0
- data/docs/setting-up-gmail.md +166 -0
- data/imap-backup.gemspec +2 -8
- data/lib/email/mboxrd/message.rb +2 -0
- data/lib/email/provider.rb +3 -1
- data/lib/gmail/authenticator.rb +160 -0
- data/lib/google/auth/stores/in_memory_token_store.rb +9 -0
- data/lib/imap/backup.rb +2 -1
- data/lib/imap/backup/account/connection.rb +31 -13
- data/lib/imap/backup/configuration/account.rb +9 -1
- data/lib/imap/backup/configuration/gmail_oauth2.rb +82 -0
- data/lib/imap/backup/configuration/setup.rb +4 -1
- data/lib/imap/backup/version.rb +5 -4
- data/spec/features/backup_spec.rb +1 -1
- data/spec/unit/gmail/authenticator_spec.rb +138 -0
- data/spec/unit/google/auth/stores/in_memory_token_store_spec.rb +15 -0
- data/spec/unit/imap/backup/account/connection_spec.rb +103 -40
- data/spec/unit/imap/backup/configuration/account_spec.rb +37 -24
- data/spec/unit/imap/backup/configuration/gmail_oauth2_spec.rb +84 -0
- data/spec/unit/imap/backup/configuration/setup_spec.rb +51 -31
- metadata +68 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bc793ba25439fdcaa725850344f0d95ba1dfc753847f0ac99ca6b6c54ab4517b
|
4
|
+
data.tar.gz: 4ca72bb525a0f155840f92c698f96c27df1f53898c691c016dd5bc39f65cda83
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 19547948dba23faf57765db36cb9316e515a4fd92545e906e4ccf433c02d5d1a43c6aeeff25ebf1a32557f10a0956d7f6ae21a9cb99bd7f515bd741f3782a65e
|
7
|
+
data.tar.gz: a26d27d9ba32115d133df5da4660f4473d9defe7578bf77d735463dbad887ffe555414f71eeaf3f9ec16e229bea97072f85dfd72eaa41f30643da275c2f58a19
|
data/.rubocop.yml
CHANGED
@@ -11,10 +11,10 @@ AllCops:
|
|
11
11
|
RSpec/ContextWording:
|
12
12
|
Exclude:
|
13
13
|
- "spec/features/**/*"
|
14
|
+
RSpec/LeakyConstantDeclaration:
|
15
|
+
Enabled: false
|
14
16
|
RSpec/MessageSpies:
|
15
17
|
Enabled: false
|
16
|
-
RSpec/NestedGroups:
|
17
|
-
Max: 4
|
18
18
|
RSpec/ReturnFromStub:
|
19
19
|
Enabled: false
|
20
20
|
Style/EmptyCaseCondition:
|
data/.rubocop_todo.yml
CHANGED
@@ -1,28 +1,28 @@
|
|
1
1
|
# This configuration was generated by
|
2
2
|
# `rubocop --auto-gen-config`
|
3
|
-
# on
|
3
|
+
# on 2021-01-09 09:21:34 UTC using RuboCop version 0.89.1.
|
4
4
|
# The point is for the user to remove these configuration records
|
5
5
|
# one by one as the offenses are removed from the code base.
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
7
7
|
# versions of RuboCop, may require this file to be generated again.
|
8
8
|
|
9
|
-
# Offense count:
|
9
|
+
# Offense count: 11
|
10
10
|
# Configuration parameters: IgnoredMethods.
|
11
11
|
Metrics/AbcSize:
|
12
|
-
Max:
|
12
|
+
Max: 33
|
13
13
|
|
14
14
|
# Offense count: 2
|
15
15
|
# Configuration parameters: CountComments, CountAsOne, ExcludedMethods.
|
16
16
|
# ExcludedMethods: refine
|
17
17
|
Metrics/BlockLength:
|
18
|
-
Max:
|
18
|
+
Max: 138
|
19
19
|
|
20
20
|
# Offense count: 2
|
21
21
|
# Configuration parameters: CountComments, CountAsOne.
|
22
22
|
Metrics/ClassLength:
|
23
23
|
Max: 167
|
24
24
|
|
25
|
-
# Offense count:
|
25
|
+
# Offense count: 17
|
26
26
|
# Configuration parameters: CountComments, CountAsOne, ExcludedMethods.
|
27
27
|
Metrics/MethodLength:
|
28
28
|
Max: 25
|
@@ -30,29 +30,13 @@ Metrics/MethodLength:
|
|
30
30
|
# Offense count: 2
|
31
31
|
# Configuration parameters: CountComments, CountAsOne.
|
32
32
|
Metrics/ModuleLength:
|
33
|
-
Max:
|
33
|
+
Max: 141
|
34
34
|
|
35
|
-
# Offense count:
|
35
|
+
# Offense count: 200
|
36
36
|
# Configuration parameters: AllowSubject.
|
37
37
|
RSpec/MultipleMemoizedHelpers:
|
38
|
-
Max:
|
38
|
+
Max: 16
|
39
39
|
|
40
40
|
# Offense count: 1
|
41
|
-
|
42
|
-
|
43
|
-
Exclude:
|
44
|
-
- 'lib/imap/backup.rb'
|
45
|
-
|
46
|
-
# Offense count: 3
|
47
|
-
# Cop supports --auto-correct.
|
48
|
-
Style/IfUnlessModifier:
|
49
|
-
Exclude:
|
50
|
-
- 'bin/imap-backup'
|
51
|
-
- 'lib/email/mboxrd/message.rb'
|
52
|
-
- 'lib/imap/backup/configuration/account.rb'
|
53
|
-
|
54
|
-
# Offense count: 1
|
55
|
-
# Cop supports --auto-correct.
|
56
|
-
Style/RedundantRegexpEscape:
|
57
|
-
Exclude:
|
58
|
-
- 'spec/features/backup_spec.rb'
|
41
|
+
RSpec/NestedGroups:
|
42
|
+
Max: 6
|
data/README.md
CHANGED
@@ -16,11 +16,11 @@
|
|
16
16
|
[Rubygem]: http://rubygems.org/gems/imap-backup "Ruby gem at rubygems.org"
|
17
17
|
[Continuous Integration]: http://travis-ci.org/joeyates/imap-backup "Build status by Travis-CI"
|
18
18
|
|
19
|
-
##
|
19
|
+
## GMail
|
20
|
+
|
21
|
+
GMail OAuth2 authentication is supported.
|
20
22
|
|
21
|
-
|
22
|
-
backwardly-incompatible way. When upgrading, all old backups will be gradually
|
23
|
-
deleted to allow for the new file format to be introduced.
|
23
|
+
To set it up, [follow the HOWTO](docs/setting-up-gmail.md).
|
24
24
|
|
25
25
|
# Installation
|
26
26
|
|
@@ -115,13 +115,6 @@ Specifically, if you are using a self-signed certificate and get SSL errors, e.g
|
|
115
115
|
}
|
116
116
|
```
|
117
117
|
|
118
|
-
## GMail
|
119
|
-
|
120
|
-
* Enable IMAP access to your account via the GMail interface (Settings/Forwarding and POP/IMAP),
|
121
|
-
* Under 'Sign-in & security', 'Signing in to Google', 'App passwords', generate a password
|
122
|
-
for imap-backup,
|
123
|
-
* In imap-backup setup, set the server to imap.gmail.com
|
124
|
-
|
125
118
|
# Security
|
126
119
|
|
127
120
|
Note that email usernames and passwords are held in plain text
|
data/bin/imap-backup
CHANGED
@@ -48,10 +48,13 @@ parser.parse!
|
|
48
48
|
|
49
49
|
options[:command] = ARGV.shift if !ARGV.empty?
|
50
50
|
|
51
|
+
# rubocop:disable Style/IfUnlessModifier
|
51
52
|
if KNOWN_COMMANDS.find { |c| c[:name] == options[:command] }.nil?
|
52
53
|
raise "Unknown command '#{options[:command]}'"
|
53
54
|
end
|
54
55
|
|
56
|
+
# rubocop:enable Style/IfUnlessModifier
|
57
|
+
|
55
58
|
if options[:command] == "help"
|
56
59
|
puts parser
|
57
60
|
exit
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/docs/27-success.png
ADDED
Binary file
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# Setting up GMail Authentication for imap-backup
|
2
|
+
|
3
|
+
# Create a Google project
|
4
|
+
|
5
|
+
Go to https://console.developers.google.com
|
6
|
+
|
7
|
+
Select "Credentials".
|
8
|
+
|
9
|
+
![Credential screen](01-credentials-screen.png)
|
10
|
+
|
11
|
+
Select "CREATE PROJECT".
|
12
|
+
|
13
|
+
![New project](02-new-project.png)
|
14
|
+
|
15
|
+
Set or accept the "Project name",
|
16
|
+
|
17
|
+
And optionally do the same with the "Project ID",
|
18
|
+
|
19
|
+
Leave "Location" on "No organization",
|
20
|
+
|
21
|
+
Click "CREATE".
|
22
|
+
|
23
|
+
![Initial project credentials](03-initial-credentials-for-project.png)
|
24
|
+
|
25
|
+
Click "+ CREATE CREDENTIALS".
|
26
|
+
|
27
|
+
![Credential type selection](04-credential-type-selection.png)
|
28
|
+
|
29
|
+
Select "OAuth client ID".
|
30
|
+
|
31
|
+
![Can't create credentials before setting up the consent screen](05-cant-create-without-consent-setup.png)
|
32
|
+
|
33
|
+
Click "CONFIGURE CONSENT SCREEN".
|
34
|
+
|
35
|
+
![User type selection](06-user-type-selection.png)
|
36
|
+
|
37
|
+
Select "External",
|
38
|
+
|
39
|
+
Click "CREATE".
|
40
|
+
|
41
|
+
![Consent screen form](07-consent-screen-form.png)
|
42
|
+
|
43
|
+
Fill in "App name",
|
44
|
+
|
45
|
+
Select your email as "User support email",
|
46
|
+
|
47
|
+
Type in your email at the bottom under "Developer contact information",
|
48
|
+
|
49
|
+
Click "SAVE AND CONTINUE".
|
50
|
+
|
51
|
+
![App scopes](08-app-scopes.png)
|
52
|
+
|
53
|
+
Click "ADD OR REMOVE SCOPES".
|
54
|
+
|
55
|
+
![Scope selection](09-scope-selection.png)
|
56
|
+
|
57
|
+
Under "Manually add scopes", type "https://mail.google.com/",
|
58
|
+
|
59
|
+
Click "ADD TO TABLE",
|
60
|
+
|
61
|
+
Click "UPDATE".
|
62
|
+
|
63
|
+
![Updated app scopes](10-updated-app-scopes.png)
|
64
|
+
|
65
|
+
Click "SAVE AND CONTINUE".
|
66
|
+
|
67
|
+
![Test users](11-test-users.png)
|
68
|
+
|
69
|
+
Click "+ ADD USERS".
|
70
|
+
|
71
|
+
![Add users](12-add-users.png)
|
72
|
+
|
73
|
+
Type in your email,
|
74
|
+
|
75
|
+
Click "SAVE AND CONTINUE",
|
76
|
+
|
77
|
+
Click "BACK TO DASHBOARD",
|
78
|
+
|
79
|
+
Click "Credentials" in the menu
|
80
|
+
|
81
|
+
And then click "+ CREATE CREDENTIALS" again,
|
82
|
+
|
83
|
+
And select "OAuth client ID" again.
|
84
|
+
|
85
|
+
![Create OAuth client](13-create-oauth-client.png)
|
86
|
+
|
87
|
+
This time you will be able to proceed.
|
88
|
+
|
89
|
+
![Application details](14-application-details.png)
|
90
|
+
|
91
|
+
Select "TVs and limited input devices",
|
92
|
+
|
93
|
+
Click "CREATE",
|
94
|
+
|
95
|
+
Copy both "Your Client ID"
|
96
|
+
|
97
|
+
And "Your Client Secret".
|
98
|
+
|
99
|
+
# Set up imap-backup
|
100
|
+
|
101
|
+
Run `imap-backup setup`.
|
102
|
+
|
103
|
+
![Initial imap-backup menu](16-initial-menu.png)
|
104
|
+
|
105
|
+
Choose 'add account'.
|
106
|
+
|
107
|
+
![Type in your email address](17-inputting-the-email-address.png)
|
108
|
+
|
109
|
+
Type in your GMail address.
|
110
|
+
|
111
|
+
Note: if you have a custom domain (GSuite) address,
|
112
|
+
e.g. "me@mycompany.com", you now need to
|
113
|
+
choose 'server' and
|
114
|
+
type in 'imap.gmail.com'.
|
115
|
+
|
116
|
+
![Choose password](18-choose-password.png)
|
117
|
+
|
118
|
+
Choose `password`.
|
119
|
+
|
120
|
+
![Supply client info](19-supply-client-info.png)
|
121
|
+
|
122
|
+
Type your "Client ID" and "Client Secret",
|
123
|
+
|
124
|
+
Next you will be shown a URL to open in your browser.
|
125
|
+
|
126
|
+
![Choose GMail account](20-choose-gmail-account.png)
|
127
|
+
|
128
|
+
If you have more than one GMail account you will need to choose which
|
129
|
+
you are configuring.
|
130
|
+
|
131
|
+
![Accept warnings](21-accept-warnings.png)
|
132
|
+
|
133
|
+
As the project "app" is in test mode,
|
134
|
+
you'll need to accept to ignore this warning.
|
135
|
+
|
136
|
+
Click "Advanced",
|
137
|
+
|
138
|
+
Then click "Go to XXXYYYZZZ (unsafe)"
|
139
|
+
|
140
|
+
![Grant access](22-grant-access.png)
|
141
|
+
|
142
|
+
Choose "Allow".
|
143
|
+
|
144
|
+
![Confirm choices](24-confirm-choices.png)
|
145
|
+
|
146
|
+
Choose "Allow".
|
147
|
+
|
148
|
+
![Success code screen](25-success-code.png)
|
149
|
+
|
150
|
+
Click on the copy logo to copy the success code.
|
151
|
+
|
152
|
+
![Paste the code](26-type-code-into-imap_backup.png)
|
153
|
+
|
154
|
+
Paste the success code into imap-backup.
|
155
|
+
|
156
|
+
Finally, choose 'test connection'.
|
157
|
+
|
158
|
+
If all has gone well you should see this:
|
159
|
+
|
160
|
+
![Connection successful](27-success.png)
|
161
|
+
|
162
|
+
Now choose 'return to main menu',
|
163
|
+
|
164
|
+
Then 'save and exit'.
|
165
|
+
|
166
|
+
Your imap-backup is now configured to back up your GMail.
|
data/imap-backup.gemspec
CHANGED
@@ -16,14 +16,8 @@ Gem::Specification.new do |gem|
|
|
16
16
|
gem.required_ruby_version = [">= 2.4.0"]
|
17
17
|
gem.version = Imap::Backup::VERSION
|
18
18
|
|
19
|
-
gem.
|
20
|
-
|
21
|
-
the metadata storage method has changed (from flat file to JSON).
|
22
|
-
|
23
|
-
As a result, on the first run after an upgrade, old backup folders will be
|
24
|
-
**deleted** and a full new backup created.
|
25
|
-
MESSAGE
|
26
|
-
|
19
|
+
gem.add_runtime_dependency "gmail_xoauth"
|
20
|
+
gem.add_runtime_dependency "googleauth"
|
27
21
|
gem.add_runtime_dependency "highline"
|
28
22
|
gem.add_runtime_dependency "mail"
|
29
23
|
gem.add_runtime_dependency "rake"
|
data/lib/email/mboxrd/message.rb
CHANGED
@@ -10,9 +10,11 @@ module Email::Mboxrd
|
|
10
10
|
cleaned = serialized.gsub(/^>(>*From)/, "\\1")
|
11
11
|
# Serialized messages in this format *should* start with a line
|
12
12
|
# From xxx yy zz
|
13
|
+
# rubocop:disable Style/IfUnlessModifier
|
13
14
|
if cleaned.start_with?("From ")
|
14
15
|
cleaned = cleaned.sub(/^From .*[\r\n]*/, "")
|
15
16
|
end
|
17
|
+
# rubocop:enable Style/IfUnlessModifier
|
16
18
|
new(cleaned)
|
17
19
|
end
|
18
20
|
|
data/lib/email/provider.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
module Email; end
|
2
2
|
|
3
3
|
class Email::Provider
|
4
|
+
GMAIL_IMAP_SERVER = "imap.gmail.com".freeze
|
5
|
+
|
4
6
|
def self.for_address(address)
|
5
7
|
case
|
6
8
|
when address.end_with?("@fastmail.com")
|
@@ -27,7 +29,7 @@ class Email::Provider
|
|
27
29
|
def host
|
28
30
|
case provider
|
29
31
|
when :gmail
|
30
|
-
|
32
|
+
GMAIL_IMAP_SERVER
|
31
33
|
when :fastmail
|
32
34
|
"imap.fastmail.com"
|
33
35
|
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
require "googleauth"
|
2
|
+
require "google/auth/stores/in_memory_token_store"
|
3
|
+
|
4
|
+
module Gmail; end
|
5
|
+
|
6
|
+
class Gmail::Authenticator
|
7
|
+
class MalformedImapBackupToken < StandardError; end
|
8
|
+
|
9
|
+
class ImapBackupToken
|
10
|
+
attr_reader :token
|
11
|
+
|
12
|
+
def self.from(
|
13
|
+
access_token:,
|
14
|
+
client_id:,
|
15
|
+
client_secret:,
|
16
|
+
expiration_time_millis:,
|
17
|
+
refresh_token:
|
18
|
+
)
|
19
|
+
{
|
20
|
+
access_token: access_token,
|
21
|
+
client_id: client_id,
|
22
|
+
client_secret: client_secret,
|
23
|
+
expiration_time_millis: expiration_time_millis,
|
24
|
+
refresh_token: refresh_token
|
25
|
+
}.to_json
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(token)
|
29
|
+
@token = token
|
30
|
+
end
|
31
|
+
|
32
|
+
def valid?
|
33
|
+
return false if !body
|
34
|
+
return false if !access_token
|
35
|
+
return false if !client_id
|
36
|
+
return false if !client_secret
|
37
|
+
return false if !expiration_time_millis
|
38
|
+
return false if !refresh_token
|
39
|
+
|
40
|
+
true
|
41
|
+
end
|
42
|
+
|
43
|
+
def access_token
|
44
|
+
body["access_token"]
|
45
|
+
end
|
46
|
+
|
47
|
+
def client_id
|
48
|
+
body["client_id"]
|
49
|
+
end
|
50
|
+
|
51
|
+
def client_secret
|
52
|
+
body["client_secret"]
|
53
|
+
end
|
54
|
+
|
55
|
+
def expiration_time_millis
|
56
|
+
body["expiration_time_millis"]
|
57
|
+
end
|
58
|
+
|
59
|
+
def refresh_token
|
60
|
+
body["refresh_token"]
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def body
|
66
|
+
@body ||= JSON.parse(token)
|
67
|
+
rescue JSON::ParserError
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
GMAIL_READ_SCOPE = "https://mail.google.com/".freeze
|
73
|
+
OOB_URI = "urn:ietf:wg:oauth:2.0:oob".freeze
|
74
|
+
|
75
|
+
attr_reader :email
|
76
|
+
attr_reader :token
|
77
|
+
|
78
|
+
def self.refresh_token?(text)
|
79
|
+
ImapBackupToken.new(text).valid?
|
80
|
+
end
|
81
|
+
|
82
|
+
def initialize(email:, token:)
|
83
|
+
@email = email
|
84
|
+
@token = token
|
85
|
+
end
|
86
|
+
|
87
|
+
def authorization_url
|
88
|
+
authorizer.get_authorization_url(base_url: OOB_URI)
|
89
|
+
end
|
90
|
+
|
91
|
+
def credentials
|
92
|
+
authorizer.get_credentials(email).tap do |c|
|
93
|
+
c.refresh! if c.expired?
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def credentials_from_code(code)
|
98
|
+
authorizer.get_credentials_from_code(
|
99
|
+
user_id: email,
|
100
|
+
code: code,
|
101
|
+
base_url: OOB_URI
|
102
|
+
)
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
def auth_client_id
|
108
|
+
@auth_client_id = Google::Auth::ClientId.new(client_id, client_secret)
|
109
|
+
end
|
110
|
+
|
111
|
+
def authorizer
|
112
|
+
@authorizer ||= Google::Auth::UserAuthorizer.new(
|
113
|
+
auth_client_id, GMAIL_READ_SCOPE, token_store
|
114
|
+
)
|
115
|
+
end
|
116
|
+
|
117
|
+
def access_token
|
118
|
+
imap_backup_token.access_token
|
119
|
+
end
|
120
|
+
|
121
|
+
def client_id
|
122
|
+
imap_backup_token.client_id
|
123
|
+
end
|
124
|
+
|
125
|
+
def client_secret
|
126
|
+
imap_backup_token.client_secret
|
127
|
+
end
|
128
|
+
|
129
|
+
def expiration_time_millis
|
130
|
+
imap_backup_token.expiration_time_millis
|
131
|
+
end
|
132
|
+
|
133
|
+
def refresh_token
|
134
|
+
imap_backup_token.refresh_token
|
135
|
+
end
|
136
|
+
|
137
|
+
def imap_backup_token
|
138
|
+
@imap_backup_token ||=
|
139
|
+
ImapBackupToken.new(token).tap do |t|
|
140
|
+
raise MalformedImapBackupToken if !t.valid?
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def store_token
|
145
|
+
{
|
146
|
+
"client_id" => client_id,
|
147
|
+
"access_token" => access_token,
|
148
|
+
"refresh_token" => refresh_token,
|
149
|
+
"scope": [GMAIL_READ_SCOPE],
|
150
|
+
"expiration_time_millis": expiration_time_millis
|
151
|
+
}.to_json
|
152
|
+
end
|
153
|
+
|
154
|
+
def token_store
|
155
|
+
@token_store ||=
|
156
|
+
Google::Auth::Stores::InMemoryTokenStore.new.tap do |t|
|
157
|
+
t.store(email, store_token)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|