imap-backup 4.0.0.rc1 → 4.0.0.rc5

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 (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 +9 -5
  7. data/lib/imap/backup/cli.rb +77 -73
  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
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