~sircmpwn/aerc

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
4 2

[PATCH v2] imaps+xoauth2: support gmail

Details
Message ID
<20190623203402.46986-1-frode.aa@gmail.com>
Sender timestamp
1561322043
DKIM signature
pass
Download raw message
Patch: +89 -2
requires source query parameters to configure oauth2

source example:

  imaps+xoauth2://{email}:{refresh_token}@imap.gmail.com:993?\
  oauth2_client_id={google-client-id}\
  oauth2_client_secret={google-client-secret}&\
  oauth2_scope=https%3A%2F%2Fmail.google.com%2F&\
  oauth2_endpoint=google

see [1] for instruction on how to create an
oauth2 client and generating an refresh token

use smtps+plain with an app password [2] to send email

[1] https://hobo.house/2017/07/17/using-offlineimap-with-the-gmail-imap-api/
[2] https://support.google.com/mail/answer/185833?hl=en
[3] https://todo.sr.ht/~sircmpwn/aerc2/42
---
 doc/aerc-imap.5.scd   | 18 +++++++++++++++++-
 go.mod                |  1 +
 go.sum                | 10 ++++++++++
 lib/xoauth2.go        | 42 ++++++++++++++++++++++++++++++++++++++++++
 worker/imap/worker.go | 20 +++++++++++++++++++-
 5 files changed, 89 insertions(+), 2 deletions(-)
 create mode 100644 lib/xoauth2.go

diff --git a/doc/aerc-imap.5.scd b/doc/aerc-imap.5.scd
index 12dcfbe..aa3764c 100644
--- a/doc/aerc-imap.5.scd
+++ b/doc/aerc-imap.5.scd
@@ -19,7 +19,7 @@ In accounts.conf (see *aerc-config*(5)), the following IMAP-specific options are
 available:
 
 *source*
-	imap[s][+insecure]://username[:password]@hostname[:port]
+	imap[s][+insecure|+xoauth2]://username[:password]@hostname[:port][?parameters]
 
 	Remember that all fields must be URL encoded. The "@" symbol, when URL
 	encoded, is *%40*.
@@ -35,6 +35,22 @@ available:
 	*imaps*:
 		IMAP with TLS/SSL
 
+	*imaps+xoauth2://*:
+		IMAP with TLS/SSL using XOAUTH2
+
+		Required parameters are:
+
+		- oauth2_client_id=
+		- oauth2_client_secret=
+		- oauth2_scope=https://mail.google.com
+		- oauth2_endpoint=google
+
+		Example:
+
+		imaps+xoauth2://...?oauth_endpoint=google&oauth2_client_id=...
+
+		Set the password to be the refresh token.
+
 *source-cred-cmd*
 	Specifies the command to run to get the password for the IMAP
 	account. This command will be run using `sh -c [command]`. If a
diff --git a/go.mod b/go.mod
index fd26ed6..c144ac5 100644
--- a/go.mod
+++ b/go.mod
@@ -26,6 +26,7 @@ require (
 	github.com/smartystreets/assertions v1.0.0 // indirect
 	github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a // indirect
 	github.com/stretchr/testify v1.3.0
+	golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
 	golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed // indirect
 	gopkg.in/ini.v1 v1.42.0 // indirect
 )
diff --git a/go.sum b/go.sum
index 6c33d94..0af20cc 100644
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,5 @@
+cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 git.sr.ht/~sircmpwn/getopt v0.0.0-20190214165041-9a4f886f9fc7 h1:xTFH5S/3ltiRvAtETLLDFWm5nVIouT5GeCPHm8UaVEU=
 git.sr.ht/~sircmpwn/getopt v0.0.0-20190214165041-9a4f886f9fc7/go.mod h1:wMEGFFFNuPos7vHmWXfszqImLppbc0wEhh6JBfJIUgw=
 git.sr.ht/~sircmpwn/getopt v0.0.0-20190609193657-e7e23d1cd3a3 h1:2l17fmuVbiS2cSx1m8e8GbikDUjAT5lril3/+XQsZAs=
@@ -36,6 +38,7 @@ github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdk
 github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
 github.com/go-ini/ini v1.42.0 h1:TWr1wGj35+UiWHlBA8er89seFXxzwFn11spilrrj+38=
 github.com/go-ini/ini v1.42.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg=
 github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
@@ -77,7 +80,13 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed h1:uPxWBzB3+mlnjy9W58qY1j/cjyFjutgw/Vhan2zLy/A=
@@ -88,5 +97,6 @@ golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk=
 gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
diff --git a/lib/xoauth2.go b/lib/xoauth2.go
new file mode 100644
index 0000000..e28b132
--- /dev/null
+++ b/lib/xoauth2.go
@@ -0,0 +1,42 @@
+package lib
+
+import (
+	"context"
+	"github.com/emersion/go-sasl"
+	"golang.org/x/oauth2"
+	"golang.org/x/oauth2/google"
+	"net/url"
+)
+
+var endpoints = map[string]oauth2.Endpoint{
+	"google": google.Endpoint,
+}
+
+func refreshToken(refreshToken string) *oauth2.Token {
+	token := new(oauth2.Token)
+	token.RefreshToken = refreshToken
+	token.TokenType = "Bearer"
+	return token
+}
+
+func NewXoauth2Client(url *url.URL) (sasl.Client, error) {
+	q := url.Query()
+
+	conf := &oauth2.Config{
+		ClientID:     q.Get("oauth2_client_id"),
+		ClientSecret: q.Get("oauth2_client_secret"),
+		Scopes:       []string{q.Get("oauth2_scope")},
+		Endpoint:     endpoints[q.Get("oauth2_endpoint")],
+	}
+
+	username := url.User.Username()
+	password, _ := url.User.Password()
+	token := refreshToken(password)
+	token, err := conf.TokenSource(context.TODO(), token).Token()
+
+	if err != nil {
+		return nil, err
+	}
+
+	return sasl.NewXoauth2Client(username, token.AccessToken), nil
+}
diff --git a/worker/imap/worker.go b/worker/imap/worker.go
index aa59c27..dc3e0f6 100644
--- a/worker/imap/worker.go
+++ b/worker/imap/worker.go
@@ -10,6 +10,7 @@ import (
 	idle "github.com/emersion/go-imap-idle"
 	"github.com/emersion/go-imap/client"
 
+	"git.sr.ht/~sircmpwn/aerc/lib"
 	"git.sr.ht/~sircmpwn/aerc/worker/types"
 )
 
@@ -27,6 +28,7 @@ type IMAPWorker struct {
 		addr     string
 		user     *url.Userinfo
 		folders  []string
+		xoauth2  *url.URL
 	}
 
 	client   *imapClient
@@ -70,6 +72,11 @@ func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error {
 			w.config.insecure = true
 		}
 
+		if strings.HasSuffix(w.config.scheme, "+xoauth2") {
+			w.config.scheme = strings.TrimSuffix(w.config.scheme, "+xoauth2")
+			w.config.xoauth2 = u
+		}
+
 		w.config.addr = u.Host
 		if !strings.ContainsRune(w.config.addr, ':') {
 			w.config.addr += ":" + w.config.scheme
@@ -109,7 +116,18 @@ func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error {
 			if !hasPassword {
 				// TODO: ask password
 			}
-			if err := c.Login(username, password); err != nil {
+
+			if w.config.xoauth2 != nil {
+				saslClient, err := lib.NewXoauth2Client(w.config.xoauth2)
+
+				if err != nil {
+					return err
+				}
+
+				if err := c.Authenticate(saslClient); err != nil {
+					return err
+				}
+			} else if err := c.Login(username, password); err != nil {
 				return err
 			}
 		}
-- 
2.22.0
Details
Message ID
<BV1MXMMXMH33.TR8B5APA57F5@homura>
In-Reply-To
<20190623203402.46986-1-frode.aa@gmail.com> (view parent)
Sender timestamp
1561345913
DKIM signature
pass
Download raw message
Have you had a chance to evaluate OAUTHBEARER?
Details
Message ID
<20190624092012.wko5u67jadwdjwsa@belle.local>
In-Reply-To
<BV1MXMMXMH33.TR8B5APA57F5@homura> (view parent)
Sender timestamp
1561368012
DKIM signature
pass
Download raw message
On 2019-06-23 23:11 -0400, Drew DeVault wrote:
> Have you had a chance to evaluate OAUTHBEARER?

did some, and I think it is a better approach to
use it instead of XOAUTH as it may be better supported
by other email providers

the downside with it seems only to be its lib support [1]

1: https://github.com/emersion/go-sasl/issues/6
Details
Message ID
<BV25AE979FYD.16DDFWUTXGJTE@homura>
In-Reply-To
<20190624092012.wko5u67jadwdjwsa@belle.local> (view parent)
Sender timestamp
1561397694
DKIM signature
pass
Download raw message
On Mon Jun 24, 2019 at 11:20 AM Frode Aannevik wrote:
> did some, and I think it is a better approach to
> use it instead of XOAUTH as it may be better supported
> by other email providers
> 
> the downside with it seems only to be its lib support [1]
> 
> 1: https://github.com/emersion/go-sasl/issues/6

Gotcha. I would rather pursue it than merge XOAUTH support, even if it
means we have to start by implementing it in go-sasl first.
Details
Message ID
<20190624204211.kix4aywur5sub7by@belle.local>
In-Reply-To
<BV25AE979FYD.16DDFWUTXGJTE@homura> (view parent)
Sender timestamp
1561408931
DKIM signature
pass
Download raw message
On 2019-06-24 13:34 -0400, Drew DeVault wrote:
> Gotcha. I would rather pursue it than merge XOAUTH support, even if it
> means we have to start by implementing it in go-sasl first.

Agree, I have created a pull request for go-sasl [1]

I have also looked at how OAuthBearer is supported in
Mutt [2], with 'imap_oauth_refresh_command' for generating
accesses token

we could use the `source-cred-cmd` for the same purpose
and simplify the implementation

1: https://github.com/emersion/go-sasl/pull/10
2: http://mutt.org/doc/manual/#oauth