imap-backup 2.2.2 → 3.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -2
  3. data/.rubocop_todo.yml +10 -26
  4. data/README.md +4 -11
  5. data/bin/imap-backup +3 -0
  6. data/docs/01-credentials-screen.png +0 -0
  7. data/docs/02-new-project.png +0 -0
  8. data/docs/03-initial-credentials-for-project.png +0 -0
  9. data/docs/04-credential-type-selection.png +0 -0
  10. data/docs/05-cant-create-without-consent-setup.png +0 -0
  11. data/docs/06-user-type-selection.png +0 -0
  12. data/docs/07-consent-screen-form.png +0 -0
  13. data/docs/08-app-scopes.png +0 -0
  14. data/docs/09-scope-selection.png +0 -0
  15. data/docs/10-updated-app-scopes.png +0 -0
  16. data/docs/11-test-users.png +0 -0
  17. data/docs/12-add-users.png +0 -0
  18. data/docs/13-create-oauth-client.png +0 -0
  19. data/docs/14-application-details.png +0 -0
  20. data/docs/16-initial-menu.png +0 -0
  21. data/docs/17-inputting-the-email-address.png +0 -0
  22. data/docs/18-choose-password.png +0 -0
  23. data/docs/19-supply-client-info.png +0 -0
  24. data/docs/20-choose-gmail-account.png +0 -0
  25. data/docs/21-accept-warnings.png +0 -0
  26. data/docs/22-grant-access.png +0 -0
  27. data/docs/24-confirm-choices.png +0 -0
  28. data/docs/25-success-code.png +0 -0
  29. data/docs/26-type-code-into-imap-backup.png +0 -0
  30. data/docs/27-success.png +0 -0
  31. data/docs/setting-up-gmail.md +166 -0
  32. data/imap-backup.gemspec +2 -8
  33. data/lib/email/mboxrd/message.rb +2 -0
  34. data/lib/email/provider.rb +3 -1
  35. data/lib/gmail/authenticator.rb +160 -0
  36. data/lib/google/auth/stores/in_memory_token_store.rb +9 -0
  37. data/lib/imap/backup.rb +2 -1
  38. data/lib/imap/backup/account/connection.rb +31 -13
  39. data/lib/imap/backup/configuration/account.rb +9 -1
  40. data/lib/imap/backup/configuration/gmail_oauth2.rb +82 -0
  41. data/lib/imap/backup/configuration/setup.rb +4 -1
  42. data/lib/imap/backup/version.rb +5 -4
  43. data/spec/features/backup_spec.rb +1 -1
  44. data/spec/unit/gmail/authenticator_spec.rb +138 -0
  45. data/spec/unit/google/auth/stores/in_memory_token_store_spec.rb +15 -0
  46. data/spec/unit/imap/backup/account/connection_spec.rb +103 -40
  47. data/spec/unit/imap/backup/configuration/account_spec.rb +37 -24
  48. data/spec/unit/imap/backup/configuration/gmail_oauth2_spec.rb +84 -0
  49. data/spec/unit/imap/backup/configuration/setup_spec.rb +51 -31
  50. metadata +68 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 726ef170419a3bcdc1a39517cdd81640ca0674f5d5f5452f8bc97ef819d74031
4
- data.tar.gz: 8fc24ed36537a68899a45e22afa3fcf1dee90c27e8432853c2d88e5b4d5c5325
3
+ metadata.gz: bc793ba25439fdcaa725850344f0d95ba1dfc753847f0ac99ca6b6c54ab4517b
4
+ data.tar.gz: 4ca72bb525a0f155840f92c698f96c27df1f53898c691c016dd5bc39f65cda83
5
5
  SHA512:
6
- metadata.gz: e2b59fa22614c634169104d75ad6214a0d8c788da712a8c3545281e98666b4d23b17e1a10cc1744fcf615966bd63616daa3241f7e932d2af4d10fd4508391583
7
- data.tar.gz: 9e73290e3684aadc443f449f870c7ed8d23506408439bb69dc41b57613e8e439e32a4017f8dbfaf462ea5dac5fd029a0d4691c861600d4aaeeaff290e94e9215
6
+ metadata.gz: 19547948dba23faf57765db36cb9316e515a4fd92545e906e4ccf433c02d5d1a43c6aeeff25ebf1a32557f10a0956d7f6ae21a9cb99bd7f515bd741f3782a65e
7
+ data.tar.gz: a26d27d9ba32115d133df5da4660f4473d9defe7578bf77d735463dbad887ffe555414f71eeaf3f9ec16e229bea97072f85dfd72eaa41f30643da275c2f58a19
@@ -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:
@@ -1,28 +1,28 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2020-09-24 16:30:54 UTC using RuboCop version 0.89.1.
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: 10
9
+ # Offense count: 11
10
10
  # Configuration parameters: IgnoredMethods.
11
11
  Metrics/AbcSize:
12
- Max: 29
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: 133
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: 14
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: 136
33
+ Max: 141
34
34
 
35
- # Offense count: 181
35
+ # Offense count: 200
36
36
  # Configuration parameters: AllowSubject.
37
37
  RSpec/MultipleMemoizedHelpers:
38
- Max: 19
38
+ Max: 16
39
39
 
40
40
  # Offense count: 1
41
- # Cop supports --auto-correct.
42
- Style/GlobalStdStream:
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
- ## Version 2
19
+ ## GMail
20
+
21
+ GMail OAuth2 authentication is supported.
20
22
 
21
- With versions above 2.x, this gems stores IMAP metadata in a
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
@@ -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
@@ -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.
@@ -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.post_install_message = <<-MESSAGE.gsub(/^\s{4}/m, "")
20
- Note that, when upgrading #{gem.name} from version 1.x to 2.x,
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"
@@ -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
 
@@ -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
- "imap.gmail.com"
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