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.
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