-
Notifications
You must be signed in to change notification settings - Fork 73
/
local_provider.go
160 lines (136 loc) · 4.45 KB
/
local_provider.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package localcred
import (
"context"
"fmt"
"os"
"path"
"google.golang.org/api/gmail/v1"
"github.com/mbrt/gmailctl/cmd/gmailctl/cmd"
"github.com/mbrt/gmailctl/internal/engine/api"
)
const (
credentialsMissingMsg = `The credentials are not initialized.
To do so, head to https://console.developers.google.com
1. Create a new project if you don't have one.
1. Go to 'Enable API and services' and select Gmail.
2. Go to credentials and create a new one, by selecting 'Help me choose'.
2a. Select the Gmail API.
2b. Select 'Other UI'.
2c. Access 'User data'.
3. Go to 'OAuth consent screen'.
3a. If your account is managed by an organization, you have to
select 'Internal' as 'User Type' and Create (otherwise ignore).
3b. Set an application name (e.g. 'gmailctl').
3c. Update 'Scopes for Google API', by adding:
* https://www.googleapis.com/auth/gmail.labels
* https://www.googleapis.com/auth/gmail.settings.basic
5. IMPORTANT: you don't need to submit your changes for verification, as
you're only going to access your own data. Save and 'Go back to
Dashboard'.
5a. Make sure that the 'Publishig status' is set to 'In production'.
If it's set to 'Testing', Publish the app and ignore the
verification. Using the testing mode will make your tokens
expire every 7 days and require re-authentication.
6. Go back to Credentials.
6a. Click 'Create credentials'.
6b. Select 'OAuth client ID'.
6c. Select 'Desktop app' as 'Application type' and give it a name.
6d. Create.
7. Download the credentials file into '%s' and execute the 'init'
command again.
Documentation about Gmail API authorization can be found
at: https://developers.google.com/gmail/api/auth/about-auth
`
)
// Provider is a GMail credential provider that uses the local filesystem.
type Provider struct{}
func (Provider) Service(ctx context.Context, cfgDir string) (*gmail.Service, error) {
auth, err := openCredentials(credentialsPath(cfgDir))
if err != nil {
return nil, err
}
return openToken(ctx, auth, tokenPath(cfgDir))
}
func (Provider) InitConfig(cfgDir string) error {
cpath := credentialsPath(cfgDir)
tpath := tokenPath(cfgDir)
auth, err := openCredentials(cpath)
if err != nil {
fmt.Printf(credentialsMissingMsg, cpath)
return err
}
_, err = openToken(context.Background(), auth, tpath)
if err != nil {
stderrPrintf("%v\n\n", err)
err = setupToken(auth, tpath)
}
return err
}
func (Provider) ResetConfig(cfgDir string) error {
if err := deleteFile(credentialsPath(cfgDir)); err != nil {
return err
}
if err := deleteFile(tokenPath(cfgDir)); err != nil {
return err
}
return nil
}
func openCredentials(path string) (*api.Authenticator, error) {
cred, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("opening credentials: %w", err)
}
return api.NewAuthenticator(cred)
}
func openToken(ctx context.Context, auth *api.Authenticator, path string) (*gmail.Service, error) {
token, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("missing or invalid cached token: %w", err)
}
return auth.Service(ctx, token)
}
func setupToken(auth *api.Authenticator, path string) error {
fmt.Printf("Go to the following link in your browser then type the "+
"authorization code: \n%v\nAuthorization code: ", auth.AuthURL())
var authCode string
if _, err := fmt.Scan(&authCode); err != nil {
return fmt.Errorf("unable to retrieve token from web: %w", err)
}
if err := saveToken(path, authCode, auth); err != nil {
return fmt.Errorf("caching token: %w", err)
}
return nil
}
func saveToken(path, authCode string, auth *api.Authenticator) (err error) {
fmt.Printf("Saving credential file to %s\n", path)
f, e := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if e != nil {
return fmt.Errorf("creating token file: %w", e)
}
defer func() {
e = f.Close()
// do not hide more important error
if err == nil {
err = e
}
}()
return auth.CacheToken(context.Background(), authCode, f)
}
func credentialsPath(cfgDir string) string {
return path.Join(cfgDir, "credentials.json")
}
func tokenPath(cfgDir string) string {
return path.Join(cfgDir, "token.json")
}
func deleteFile(path string) error {
if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
return nil
}
return os.Remove(path)
}
func stderrPrintf(format string, a ...interface{}) {
/* #nosec */
_, _ = fmt.Fprintf(os.Stderr, format, a...)
}
// Verify that the interface is implemented.
var _ cmd.GmailAPIProvider = Provider{}