imap-backup 4.0.0.rc1 → 4.0.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/README.md +22 -8
  4. data/imap-backup.gemspec +0 -2
  5. data/lib/imap/backup/account/connection.rb +2 -21
  6. data/lib/imap/backup/cli/local.rb +4 -4
  7. data/lib/imap/backup/cli.rb +1 -1
  8. data/lib/imap/backup/configuration/account.rb +1 -11
  9. data/lib/imap/backup/version.rb +1 -1
  10. data/lib/imap/backup.rb +0 -1
  11. data/spec/unit/imap/backup/account/connection_spec.rb +0 -45
  12. data/spec/unit/imap/backup/configuration/account_spec.rb +0 -43
  13. metadata +2 -65
  14. data/docs/01-credentials-screen.png +0 -0
  15. data/docs/02-new-project.png +0 -0
  16. data/docs/03-initial-credentials-for-project.png +0 -0
  17. data/docs/04-credential-type-selection.png +0 -0
  18. data/docs/05-cant-create-without-consent-setup.png +0 -0
  19. data/docs/06-user-type-selection.png +0 -0
  20. data/docs/07-consent-screen-form.png +0 -0
  21. data/docs/08-app-scopes.png +0 -0
  22. data/docs/09-scope-selection.png +0 -0
  23. data/docs/10-updated-app-scopes.png +0 -0
  24. data/docs/11-test-users.png +0 -0
  25. data/docs/12-add-users.png +0 -0
  26. data/docs/13-create-oauth-client.png +0 -0
  27. data/docs/14-application-details.png +0 -0
  28. data/docs/16-initial-menu.png +0 -0
  29. data/docs/17-inputting-the-email-address.png +0 -0
  30. data/docs/18-choose-password.png +0 -0
  31. data/docs/19-supply-client-info.png +0 -0
  32. data/docs/20-choose-gmail-account.png +0 -0
  33. data/docs/21-accept-warnings.png +0 -0
  34. data/docs/22-grant-access.png +0 -0
  35. data/docs/24-confirm-choices.png +0 -0
  36. data/docs/25-success-code.png +0 -0
  37. data/docs/26-type-code-into-imap-backup.png +0 -0
  38. data/docs/27-success.png +0 -0
  39. data/docs/setting-up-gmail-with-oauth2.md +0 -166
  40. data/lib/gmail/authenticator.rb +0 -160
  41. data/lib/google/auth/stores/in_memory_token_store.rb +0 -9
  42. data/lib/imap/backup/configuration/gmail_oauth2.rb +0 -102
  43. data/spec/unit/gmail/authenticator_spec.rb +0 -138
  44. data/spec/unit/google/auth/stores/in_memory_token_store_spec.rb +0 -15
  45. data/spec/unit/imap/backup/configuration/gmail_oauth2_spec.rb +0 -121
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ba3f202308a586f7ec54b28d66ea2f9422cf30f706c118ab02f362346351f268
4
- data.tar.gz: 585bd29f2d2f836aad27c6ac58855649b7621eff770d5e2b40bfdd3f6aa774f0
3
+ metadata.gz: 307d6f8c3448e730c14e9550fbfcbfee0c946f28953a9cb6a7070f75feda3033
4
+ data.tar.gz: 553f15b7bf8dee1ad126111915d398b25181cd4f9aa50b252a980ecc5002ef63
5
5
  SHA512:
6
- metadata.gz: 6270660f55ab52e3a3d8242bdfbb2d044ce7428ccc75daabf25b6753a7c91f9c3f4d8b2be4db517f44fb887663a40e946f128c2bf06f4d1006cd02182d9f477e
7
- data.tar.gz: f94af41fee60be97e15e0d2aa3da5a0696b9d8bab5cb03cb42c805881b13e8a38fc5be42ee30efd26576d5cadb10d2f53b0c46c00fc94ca72cf23f94e05d602b
6
+ metadata.gz: 11a50621ff9ef4eacdfa5622ab0d2a9e2fa4dd2adf5cc162a1709686e5187db89ed0aa5d91a374d968e38e1caba62c87d0c384f3c1964a8b001338bbb3b3eb13
7
+ data.tar.gz: 35902dcb5520b7ca17a6adab6833b2d2296e3655e2ff5cffb7843c018d5b7eb25a2779a3955833088730df95eff5bb02fecff2c8911653637cceafb8897ae7f0
data/CHANGELOG.md CHANGED
@@ -4,8 +4,16 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](http://keepachangelog.com/)
5
5
  and this project adheres to [Semantic Versioning](http://semver.org/).
6
6
 
7
+ ## [4.0.0.rc2] - 2021-11-18
8
+
9
+ ### Removed
10
+
11
+ * GMail OAuth2 support. Tokens only last a few days, so this authentication
12
+ method is not usable for automated backups.
13
+
7
14
  ## [4.0.0.rc1] - 2021-11-17
8
15
 
9
16
  ### Added
17
+
10
18
  * `local` commands to list accounts, folders and emails and to view single
11
19
  emails.
data/README.md CHANGED
@@ -20,20 +20,24 @@
20
20
 
21
21
  To use imap-backup with GMail, you will need to enable 'App passwords' on your account.
22
22
 
23
- ## GMail OAuth2
23
+ # Installation
24
+
25
+ ```shell
26
+ $ gem install 'imap-backup'
27
+ ```
24
28
 
25
- GMail OAuth2 authentication is supported, but as GMail's policy requires
26
- users to set up an application specific to their account, the feature
27
- is disabled by default.
29
+ # Commands
28
30
 
29
- You will need to set the environment variable IMAP_BACKUP_ENABLE_GMAIL_OAUTH2.
31
+ For a full list, run
30
32
 
31
- To set it up, [follow the HOWTO](docs/setting-up-gmail-with-oauth2.md).
33
+ ```
34
+ $ imap-backup help
35
+ ```
32
36
 
33
- # Installation
37
+ For more information about a command, run
34
38
 
35
39
  ```shell
36
- $ gem install 'imap-backup'
40
+ $ imap-backup help COMMAND
37
41
  ```
38
42
 
39
43
  # Setup
@@ -148,6 +152,16 @@ Each folder is saved to an mbox file.
148
152
  Alongside each mbox is a file with extension '.imap', which lists the source IMAP
149
153
  UIDs to allow a full restore.
150
154
 
155
+ # Local commands
156
+
157
+ There a various commands for viewing local backup status.
158
+
159
+ To view the list, use
160
+
161
+ ```shell
162
+ $ imap_backup help local
163
+ ```
164
+
151
165
  # Troubleshooting
152
166
 
153
167
  If you have problems:
data/imap-backup.gemspec CHANGED
@@ -16,8 +16,6 @@ Gem::Specification.new do |gem|
16
16
  gem.required_ruby_version = ">= 2.5"
17
17
  gem.version = Imap::Backup::VERSION
18
18
 
19
- gem.add_runtime_dependency "gmail_xoauth"
20
- gem.add_runtime_dependency "googleauth"
21
19
  gem.add_runtime_dependency "highline"
22
20
  gem.add_runtime_dependency "mail"
23
21
  gem.add_runtime_dependency "rake"
@@ -1,15 +1,11 @@
1
1
  require "net/imap"
2
- require "gmail_xoauth"
3
2
 
4
- require "gmail/authenticator"
5
3
  require "retry_on_error"
6
4
 
7
5
  module Imap::Backup
8
6
  module Account; end
9
7
 
10
8
  class Account::Connection
11
- class InvalidGmailOauth2RefreshToken < StandardError; end
12
-
13
9
  include RetryOnError
14
10
 
15
11
  LOGIN_RETRY_CLASSES = [EOFError, Errno::ECONNRESET, SocketError].freeze
@@ -109,17 +105,8 @@ module Imap::Backup
109
105
  "Creating IMAP instance: #{server}, options: #{options.inspect}"
110
106
  )
111
107
  imap = Net::IMAP.new(server, options)
112
- if use_gmail_oauth2? && Gmail::Authenticator.refresh_token?(password)
113
- authenticator = Gmail::Authenticator.new(email: username, token: password)
114
- credentials = authenticator.credentials
115
- raise InvalidGmailOauth2RefreshToken if !credentials
116
-
117
- Imap::Backup.logger.debug "Logging in with OAuth2 token: #{username}"
118
- imap.authenticate("XOAUTH2", username, credentials.access_token)
119
- else
120
- Imap::Backup.logger.debug "Logging in: #{username}/#{masked_password}"
121
- imap.login(username, password)
122
- end
108
+ Imap::Backup.logger.debug "Logging in: #{username}/#{masked_password}"
109
+ imap.login(username, password)
123
110
  Imap::Backup.logger.debug "Login complete"
124
111
  imap
125
112
  end
@@ -181,12 +168,6 @@ module Imap::Backup
181
168
  password.gsub(/./, "x")
182
169
  end
183
170
 
184
- def use_gmail_oauth2?
185
- # TODO: test use of ENV
186
- server == Email::Provider::GMAIL_IMAP_SERVER &&
187
- ENV["IMAP_BACKUP_ENABLE_GMAIL_OAUTH2"]
188
- end
189
-
190
171
  def backup_folders
191
172
  @backup_folders ||=
192
173
  begin
@@ -3,13 +3,13 @@ module Imap::Backup
3
3
  include Thor::Actions
4
4
  include CLI::Helpers
5
5
 
6
- desc "accounts", "list locally backed-up accounts"
6
+ desc "accounts", "List locally backed-up accounts"
7
7
  def accounts
8
8
  connections = Imap::Backup::Configuration::List.new
9
9
  connections.accounts.each { |a| puts a[:username] }
10
10
  end
11
11
 
12
- desc "folders EMAIL", "list account folders"
12
+ desc "folders EMAIL", "List account folders"
13
13
  def folders(email)
14
14
  connections = Imap::Backup::Configuration::List.new
15
15
  account = connections.accounts.find { |a| a[:username] == email }
@@ -21,7 +21,7 @@ module Imap::Backup
21
21
  end
22
22
  end
23
23
 
24
- desc "emails EMAIL FOLDER", "list emails in a folder"
24
+ desc "emails EMAIL FOLDER", "List emails in a folder"
25
25
  def emails(email, folder_name)
26
26
  connections = Imap::Backup::Configuration::List.new
27
27
  account = connections.accounts.find { |a| a[:username] == email }
@@ -49,7 +49,7 @@ module Imap::Backup
49
49
  end
50
50
  end
51
51
 
52
- desc "email EMAIL FOLDER UID", "show an email"
52
+ desc "email EMAIL FOLDER UID", "Show an email"
53
53
  def email(email, folder_name, uid)
54
54
  connections = Imap::Backup::Configuration::List.new
55
55
  account = connections.accounts.find { |a| a[:username] == email }
@@ -82,6 +82,6 @@ class Imap::Backup::CLI < Thor
82
82
  Status.new(symbolized(options)).run
83
83
  end
84
84
 
85
- desc "local subcommand ...ARGS", "View local info"
85
+ desc "local SUBCOMMAND [OPTIONS]", "View local info"
86
86
  subcommand "local", Local
87
87
  end
@@ -68,12 +68,7 @@ module Imap::Backup
68
68
 
69
69
  def modify_password(menu)
70
70
  menu.choice("modify password") do
71
- password =
72
- if use_gmail_oauth2?(account)
73
- Configuration::GmailOauth2.new(account).run
74
- else
75
- Configuration::Asker.password
76
- end
71
+ password = Configuration::Asker.password
77
72
 
78
73
  if !password.nil?
79
74
  account[:password] = password
@@ -82,11 +77,6 @@ module Imap::Backup
82
77
  end
83
78
  end
84
79
 
85
- def use_gmail_oauth2?(account)
86
- account[:server] == Email::Provider::GMAIL_IMAP_SERVER &&
87
- ENV["IMAP_BACKUP_ENABLE_GMAIL_OAUTH2"]
88
- end
89
-
90
80
  def modify_server(menu)
91
81
  menu.choice("modify server") do
92
82
  server = highline.ask("server: ")
@@ -4,6 +4,6 @@ module Imap::Backup
4
4
  MAJOR = 4
5
5
  MINOR = 0
6
6
  REVISION = 0
7
- PRE = "rc1"
7
+ PRE = "rc2"
8
8
  VERSION = [MAJOR, MINOR, REVISION, PRE].compact.map(&:to_s).join(".")
9
9
  end
data/lib/imap/backup.rb CHANGED
@@ -7,7 +7,6 @@ require "imap/backup/configuration/account"
7
7
  require "imap/backup/configuration/asker"
8
8
  require "imap/backup/configuration/connection_tester"
9
9
  require "imap/backup/configuration/folder_chooser"
10
- require "imap/backup/configuration/gmail_oauth2"
11
10
  require "imap/backup/configuration/list"
12
11
  require "imap/backup/configuration/setup"
13
12
  require "imap/backup/configuration/store"
@@ -90,56 +90,11 @@ describe Imap::Backup::Account::Connection do
90
90
  end
91
91
 
92
92
  context "with the GMail IMAP server" do
93
- ACCESS_TOKEN = "access_token".freeze
94
-
95
93
  let(:server) { GMAIL_IMAP_SERVER }
96
94
  let(:refresh_token) { true }
97
95
  let(:result) { nil }
98
- let(:authenticator) do
99
- instance_double(
100
- Gmail::Authenticator,
101
- credentials: credentials
102
- )
103
- end
104
- let(:credentials) { OpenStruct.new(access_token: ACCESS_TOKEN) }
105
-
106
- before do
107
- allow(Gmail::Authenticator).
108
- to receive(:refresh_token?) { refresh_token }
109
- allow(Gmail::Authenticator).
110
- to receive(:new).
111
- with(email: USERNAME, token: PASSWORD) { authenticator }
112
- end
113
-
114
- context "when the password is our copy of a GMail refresh token and the environment IMAP_BACKUP_ENABLE_GMAIL_OAUTH2 is set" do
115
- before do
116
- ENV["IMAP_BACKUP_ENABLE_GMAIL_OAUTH2"] = "1"
117
- end
118
-
119
- after do
120
- ENV.delete("IMAP_BACKUP_ENABLE_GMAIL_OAUTH2")
121
- end
122
-
123
- it "uses the OAuth2 access_token to authenticate" do
124
- subject.imap
125
-
126
- expect(imap).to have_received(:authenticate).with(
127
- "XOAUTH2", USERNAME, ACCESS_TOKEN
128
- )
129
- end
130
-
131
- context "when the refresh token is invalid" do
132
- let(:credentials) { nil }
133
-
134
- it "raises" do
135
- expect { subject.imap }.to raise_error(String)
136
- end
137
- end
138
- end
139
96
 
140
97
  context "when the password is not our copy of a GMail refresh token" do
141
- let(:refresh_token) { false }
142
-
143
98
  it "uses the password" do
144
99
  subject.imap
145
100
 
@@ -241,49 +241,6 @@ describe Imap::Backup::Configuration::Account do
241
241
  end
242
242
  end
243
243
 
244
- describe "choosing 'modify password' when the server is for GMail" do
245
- let(:new_password) { "new_password" }
246
- let(:current_server) { GMAIL_IMAP_SERVER }
247
- let(:gmail_oauth2) do
248
- instance_double(Imap::Backup::Configuration::GmailOauth2, run: nil)
249
- end
250
-
251
- before do
252
- allow(Imap::Backup::Configuration::Asker).
253
- to receive(:password) { new_password }
254
- allow(Imap::Backup::Configuration::GmailOauth2).
255
- to receive(:new).
256
- with(account) { gmail_oauth2 }
257
- end
258
-
259
- context "when the environment IMAP_BACKUP_ENABLE_GMAIL_OAUTH2 is set" do
260
- before do
261
- ENV["IMAP_BACKUP_ENABLE_GMAIL_OAUTH2"] = "1"
262
- subject.run
263
- menu.choices["modify password"].call
264
- end
265
-
266
- after do
267
- ENV.delete("IMAP_BACKUP_ENABLE_GMAIL_OAUTH2")
268
- end
269
-
270
- it "sets up GMail OAuth2" do
271
- expect(gmail_oauth2).to have_received(:run)
272
- end
273
- end
274
-
275
- context "when the environment IMAP_BACKUP_ENABLE_GMAIL_OAUTH2 is not set" do
276
- before do
277
- subject.run
278
- menu.choices["modify password"].call
279
- end
280
-
281
- it "sets up GMail OAuth2" do
282
- expect(gmail_oauth2).to_not have_received(:run)
283
- end
284
- end
285
- end
286
-
287
244
  describe "choosing 'modify server'" do
288
245
  let(:server) { "server" }
289
246
 
metadata CHANGED
@@ -1,43 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: imap-backup
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0.rc1
4
+ version: 4.0.0.rc2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joe Yates
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-11-17 00:00:00.000000000 Z
11
+ date: 2021-11-18 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: gmail_xoauth
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
- - !ruby/object:Gem::Dependency
28
- name: googleauth
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
13
  - !ruby/object:Gem::Dependency
42
14
  name: highline
43
15
  requirement: !ruby/object:Gem::Requirement
@@ -199,39 +171,11 @@ files:
199
171
  - Rakefile
200
172
  - bin/imap-backup
201
173
  - docker-compose.yml
202
- - docs/01-credentials-screen.png
203
- - docs/02-new-project.png
204
- - docs/03-initial-credentials-for-project.png
205
- - docs/04-credential-type-selection.png
206
- - docs/05-cant-create-without-consent-setup.png
207
- - docs/06-user-type-selection.png
208
- - docs/07-consent-screen-form.png
209
- - docs/08-app-scopes.png
210
- - docs/09-scope-selection.png
211
- - docs/10-updated-app-scopes.png
212
- - docs/11-test-users.png
213
- - docs/12-add-users.png
214
- - docs/13-create-oauth-client.png
215
- - docs/14-application-details.png
216
- - docs/16-initial-menu.png
217
- - docs/17-inputting-the-email-address.png
218
- - docs/18-choose-password.png
219
- - docs/19-supply-client-info.png
220
- - docs/20-choose-gmail-account.png
221
- - docs/21-accept-warnings.png
222
- - docs/22-grant-access.png
223
- - docs/24-confirm-choices.png
224
- - docs/25-success-code.png
225
- - docs/26-type-code-into-imap-backup.png
226
- - docs/27-success.png
227
174
  - docs/docker-imap.md
228
- - docs/setting-up-gmail-with-oauth2.md
229
175
  - imap-backup
230
176
  - imap-backup.gemspec
231
177
  - lib/email/mboxrd/message.rb
232
178
  - lib/email/provider.rb
233
- - lib/gmail/authenticator.rb
234
- - lib/google/auth/stores/in_memory_token_store.rb
235
179
  - lib/imap/backup.rb
236
180
  - lib/imap/backup/account/connection.rb
237
181
  - lib/imap/backup/account/folder.rb
@@ -247,7 +191,6 @@ files:
247
191
  - lib/imap/backup/configuration/asker.rb
248
192
  - lib/imap/backup/configuration/connection_tester.rb
249
193
  - lib/imap/backup/configuration/folder_chooser.rb
250
- - lib/imap/backup/configuration/gmail_oauth2.rb
251
194
  - lib/imap/backup/configuration/list.rb
252
195
  - lib/imap/backup/configuration/setup.rb
253
196
  - lib/imap/backup/configuration/store.rb
@@ -276,15 +219,12 @@ files:
276
219
  - spec/support/silence_logging.rb
277
220
  - spec/unit/email/mboxrd/message_spec.rb
278
221
  - spec/unit/email/provider_spec.rb
279
- - spec/unit/gmail/authenticator_spec.rb
280
- - spec/unit/google/auth/stores/in_memory_token_store_spec.rb
281
222
  - spec/unit/imap/backup/account/connection_spec.rb
282
223
  - spec/unit/imap/backup/account/folder_spec.rb
283
224
  - spec/unit/imap/backup/configuration/account_spec.rb
284
225
  - spec/unit/imap/backup/configuration/asker_spec.rb
285
226
  - spec/unit/imap/backup/configuration/connection_tester_spec.rb
286
227
  - spec/unit/imap/backup/configuration/folder_chooser_spec.rb
287
- - spec/unit/imap/backup/configuration/gmail_oauth2_spec.rb
288
228
  - spec/unit/imap/backup/configuration/list_spec.rb
289
229
  - spec/unit/imap/backup/configuration/setup_spec.rb
290
230
  - spec/unit/imap/backup/configuration/store_spec.rb
@@ -335,15 +275,12 @@ test_files:
335
275
  - spec/support/silence_logging.rb
336
276
  - spec/unit/email/mboxrd/message_spec.rb
337
277
  - spec/unit/email/provider_spec.rb
338
- - spec/unit/gmail/authenticator_spec.rb
339
- - spec/unit/google/auth/stores/in_memory_token_store_spec.rb
340
278
  - spec/unit/imap/backup/account/connection_spec.rb
341
279
  - spec/unit/imap/backup/account/folder_spec.rb
342
280
  - spec/unit/imap/backup/configuration/account_spec.rb
343
281
  - spec/unit/imap/backup/configuration/asker_spec.rb
344
282
  - spec/unit/imap/backup/configuration/connection_tester_spec.rb
345
283
  - spec/unit/imap/backup/configuration/folder_chooser_spec.rb
346
- - spec/unit/imap/backup/configuration/gmail_oauth2_spec.rb
347
284
  - spec/unit/imap/backup/configuration/list_spec.rb
348
285
  - spec/unit/imap/backup/configuration/setup_spec.rb
349
286
  - spec/unit/imap/backup/configuration/store_spec.rb
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 DELETED
Binary file
@@ -1,166 +0,0 @@
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.
@@ -1,160 +0,0 @@
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
@@ -1,9 +0,0 @@
1
- module Google; end
2
- module Google::Auth; end
3
- module Google::Auth::Stores; end
4
-
5
- class Google::Auth::Stores::InMemoryTokenStore < Hash
6
- def load(id)
7
- self[id]
8
- end
9
- end
@@ -1,102 +0,0 @@
1
- module Imap::Backup
2
- module Configuration; end
3
-
4
- class Configuration::GmailOauth2
5
- BANNER = <<~BANNER.freeze
6
- GMail OAuth2 Setup
7
-
8
- You need to authorize imap_backup to get access to your email.
9
- To do so, please follow the instructions here:
10
-
11
- https://github.com/joeyates/imap-backup/blob/main/docs/setting-up-gmail-with-oauth2.md
12
-
13
- BANNER
14
-
15
- GMAIL_READ_SCOPE = "https://mail.google.com/".freeze
16
- OOB_URI = "urn:ietf:wg:oauth:2.0:oob".freeze
17
-
18
- attr_reader :account
19
- attr_reader :client_id
20
- attr_reader :client_secret
21
-
22
- def initialize(account)
23
- @account = account
24
- end
25
-
26
- def run
27
- Kernel.system("clear")
28
- Kernel.puts BANNER
29
-
30
- keep = if token.valid?
31
- highline.agree("Use existing client info?")
32
- else
33
- false
34
- end
35
-
36
- if keep
37
- @client_id = token.client_id
38
- @client_secret = token.client_secret
39
- else
40
- @client_id = highline.ask("client_id: ")
41
- @client_secret = highline.ask("client_secret: ")
42
- end
43
-
44
- Kernel.puts <<~MESSAGE
45
-
46
- Open the following URL in your browser
47
-
48
- #{authorization_url}
49
-
50
- Then copy the success code
51
-
52
- MESSAGE
53
-
54
- @code = highline.ask("success code: ")
55
- @credentials = authorizer.get_and_store_credentials_from_code(
56
- user_id: email, code: @code, base_url: OOB_URI
57
- )
58
-
59
- raise "Failed" if !@credentials
60
-
61
- new_token = JSON.parse(token_store.load(email))
62
- new_token["client_secret"] = client_secret
63
- new_token.to_json
64
- end
65
-
66
- private
67
-
68
- def email
69
- account[:username]
70
- end
71
-
72
- def password
73
- account[:password]
74
- end
75
-
76
- def token
77
- @token ||= Gmail::Authenticator::ImapBackupToken.new(password)
78
- end
79
-
80
- def highline
81
- Configuration::Setup.highline
82
- end
83
-
84
- def auth_client_id
85
- @auth_client_id = Google::Auth::ClientId.new(client_id, client_secret)
86
- end
87
-
88
- def authorizer
89
- @authorizer ||= Google::Auth::UserAuthorizer.new(
90
- auth_client_id, GMAIL_READ_SCOPE, token_store
91
- )
92
- end
93
-
94
- def token_store
95
- @token_store ||= Google::Auth::Stores::InMemoryTokenStore.new
96
- end
97
-
98
- def authorization_url
99
- authorizer.get_authorization_url(base_url: OOB_URI)
100
- end
101
- end
102
- end
@@ -1,138 +0,0 @@
1
- require "gmail/authenticator"
2
- require "googleauth"
3
-
4
- describe Gmail::Authenticator do
5
- ACCESS_TOKEN = "access_token".freeze
6
- AUTHORIZATION_URL = "authorization_url".freeze
7
- CLIENT_ID = "client_id".freeze
8
- CLIENT_SECRET = "client_secret".freeze
9
- CODE = "code".freeze
10
- CREDENTIALS = "credentials".freeze
11
- EMAIL = "email".freeze
12
- EXPIRATION_TIME_MILLIS = "expiration_time_millis".freeze
13
- GMAIL_READ_SCOPE = "https://mail.google.com/".freeze
14
- IMAP_BACKUP_TOKEN = "imap_backup_token".freeze
15
- OOB_URI = "urn:ietf:wg:oauth:2.0:oob".freeze
16
- REFRESH_TOKEN = "refresh_token".freeze
17
-
18
- subject { described_class.new(**params) }
19
-
20
- let(:params) do
21
- {
22
- email: EMAIL,
23
- token: IMAP_BACKUP_TOKEN
24
- }
25
- end
26
-
27
- let(:authorizer) do
28
- instance_double(Google::Auth::UserAuthorizer)
29
- end
30
-
31
- let(:imap_backup_token) do
32
- instance_double(
33
- Gmail::Authenticator::ImapBackupToken,
34
- access_token: ACCESS_TOKEN,
35
- client_id: CLIENT_ID,
36
- client_secret: CLIENT_SECRET,
37
- expiration_time_millis: EXPIRATION_TIME_MILLIS,
38
- refresh_token: REFRESH_TOKEN,
39
- valid?: true
40
- )
41
- end
42
-
43
- let(:token_store) do
44
- instance_double(Google::Auth::Stores::InMemoryTokenStore)
45
- end
46
-
47
- let(:credentials) do
48
- instance_double(Google::Auth::UserRefreshCredentials, refresh!: true)
49
- end
50
-
51
- let(:expired) { false }
52
-
53
- before do
54
- allow(Google::Auth::UserAuthorizer).
55
- to receive(:new).
56
- with(
57
- instance_of(Google::Auth::ClientId),
58
- GMAIL_READ_SCOPE,
59
- token_store
60
- ) { authorizer }
61
- allow(authorizer).to receive(:get_authorization_url).
62
- with(base_url: OOB_URI) { AUTHORIZATION_URL }
63
- allow(authorizer).to receive(:get_credentials).
64
- with(EMAIL) { credentials }
65
- allow(authorizer).to receive(:get_credentials_from_code).
66
- with(user_id: EMAIL, code: CODE, base_url: OOB_URI) { CREDENTIALS }
67
-
68
- allow(Google::Auth::UserRefreshCredentials).
69
- to receive(:new) { credentials }
70
- allow(credentials).to receive(:expired?) { expired }
71
-
72
- allow(Google::Auth::Stores::InMemoryTokenStore).
73
- to receive(:new) { token_store }
74
- allow(token_store).to receive(:store).
75
- with(EMAIL, anything) # TODO: use a JSON matcher
76
- allow(Gmail::Authenticator::ImapBackupToken).
77
- to receive(:new).
78
- with(IMAP_BACKUP_TOKEN) { imap_backup_token }
79
- end
80
-
81
- describe "#initialize" do
82
- [:email, :token].each do |param|
83
- context "parameter #{param}" do
84
- let(:params) { super().dup.reject { |k| k == param } }
85
-
86
- it "is expected" do
87
- expect { subject }.to raise_error(
88
- ArgumentError, /missing keyword: :?#{param}/
89
- )
90
- end
91
- end
92
- end
93
- end
94
-
95
- describe "#credentials" do
96
- let!(:result) { subject.credentials }
97
-
98
- it "attempts to get credentials" do
99
- expect(authorizer).to have_received(:get_credentials)
100
- end
101
-
102
- it "returns the result" do
103
- expect(result).to eq(credentials)
104
- end
105
-
106
- context "when the access_token has expired" do
107
- let(:expired) { true }
108
-
109
- it "refreshes it" do
110
- expect(credentials).to have_received(:refresh!)
111
- end
112
- end
113
- end
114
-
115
- describe "#authorization_url" do
116
- let!(:result) { subject.authorization_url }
117
-
118
- it "requests an authorization URL" do
119
- expect(authorizer).to have_received(:get_authorization_url)
120
- end
121
-
122
- it "returns the result" do
123
- expect(result).to eq(AUTHORIZATION_URL)
124
- end
125
- end
126
-
127
- describe "#credentials_from_code" do
128
- let!(:result) { subject.credentials_from_code(CODE) }
129
-
130
- it "requests credentials" do
131
- expect(authorizer).to have_received(:get_credentials_from_code)
132
- end
133
-
134
- it "returns credentials" do
135
- expect(result).to eq(CREDENTIALS)
136
- end
137
- end
138
- end
@@ -1,15 +0,0 @@
1
- require "google/auth/stores/in_memory_token_store"
2
-
3
- describe Google::Auth::Stores::InMemoryTokenStore do
4
- KEY = "key".freeze
5
- VALUE = "value".freeze
6
-
7
- subject { described_class.new }
8
-
9
- describe "#load" do
10
- it "returns an item's value" do
11
- subject[KEY] = VALUE
12
- expect(subject.load(KEY)).to eq(VALUE)
13
- end
14
- end
15
- end
@@ -1,121 +0,0 @@
1
- describe Imap::Backup::Configuration::GmailOauth2 do
2
- include HighLineTestHelpers
3
-
4
- CLIENT_ID = "my_client_id".freeze
5
- CLIENT_SECRET = "my_client_secret".freeze
6
-
7
- subject { described_class.new(account) }
8
-
9
- let(:authorization_url) { "some long authorization_url" }
10
- let(:credentials) { "credentials" }
11
- let(:json_token) { '{"sentinel":"foo"}' }
12
- let!(:highline_streams) { prepare_highline }
13
- let(:highline) { Imap::Backup::Configuration::Setup.highline }
14
- let(:input) { highline_streams[0] }
15
- let(:output) { highline_streams[1] }
16
- let(:account) { {} }
17
- let(:user_input) { %W(my_client_id\n my_secret\n my_code\n) }
18
-
19
- let(:authorizer) do
20
- instance_double(
21
- Google::Auth::UserAuthorizer,
22
- get_authorization_url: authorization_url,
23
- get_and_store_credentials_from_code: credentials
24
- )
25
- end
26
- let(:token_store) do
27
- instance_double(
28
- Google::Auth::Stores::InMemoryTokenStore,
29
- load: json_token
30
- )
31
- end
32
- let(:token) do
33
- instance_double(
34
- Gmail::Authenticator::ImapBackupToken,
35
- valid?: valid,
36
- client_id: CLIENT_ID,
37
- client_secret: CLIENT_SECRET
38
- )
39
- end
40
- let(:valid) { false }
41
-
42
- before do
43
- allow(Google::Auth::UserAuthorizer).
44
- to receive(:new) { authorizer }
45
- allow(Google::Auth::Stores::InMemoryTokenStore).
46
- to receive(:new) { token_store }
47
- allow(Gmail::Authenticator::ImapBackupToken).
48
- to receive(:new) { token }
49
-
50
- allow(highline).to receive(:ask).and_call_original
51
- allow(highline).to receive(:agree).and_call_original
52
-
53
- allow(Kernel).to receive(:system)
54
- allow(Kernel).to receive(:puts)
55
-
56
- allow(input).to receive(:gets).and_return(*user_input)
57
- end
58
-
59
- describe "#run" do
60
- let!(:result) { subject.run }
61
-
62
- it "clears the screen" do
63
- expect(Kernel).to have_received(:system).with("clear")
64
- end
65
-
66
- it "requests client_id" do
67
- expect(highline).to have_received(:ask).with("client_id: ")
68
- end
69
-
70
- it "requests client_secret" do
71
- expect(highline).to have_received(:ask).with("client_secret: ")
72
- end
73
-
74
- it "displays the authorization URL" do
75
- expect(Kernel).
76
- to have_received(:puts).
77
- with(/#{authorization_url}/)
78
- end
79
-
80
- it "requests the success code" do
81
- expect(highline).to have_received(:ask).with("success code: ")
82
- end
83
-
84
- it "requests an access_token via the code" do
85
- expect(authorizer).to have_received(:get_and_store_credentials_from_code)
86
- end
87
-
88
- it "returns the credentials" do
89
- expect(result).to match('"sentinel":"foo"')
90
- end
91
-
92
- it "includes the client_secret in the credentials" do
93
- expect(result).to match('"client_secret":"my_secret"')
94
- end
95
-
96
- context "when the account already has client info" do
97
- let(:valid) { true }
98
- let(:user_input) { %W(yes\n) }
99
-
100
- it "requests confirmation of client info" do
101
- expect(highline).to have_received(:agree).with("Use existing client info?")
102
- end
103
-
104
- context "when yhe user says 'no'" do
105
- let(:user_input) { %W(no\n) }
106
-
107
- it "requests client_id" do
108
- expect(highline).to have_received(:ask).with("client_id: ")
109
- end
110
-
111
- it "requests client_secret" do
112
- expect(highline).to have_received(:ask).with("client_secret: ")
113
- end
114
-
115
- it "requests the success code" do
116
- expect(highline).to have_received(:ask).with("success code: ")
117
- end
118
- end
119
- end
120
- end
121
- end