Sending mails using Gmail with Gnome Online Accounts
Who is this aimed at
Gnome users that have a email address at gmail and want to collaborate using git-send-email.
Note: Although I'm using this to send emails using the Gmail OAuth2 flow there is nothing gmail specific about the helper. It works for any OAuth token stored in Gnome Online Accounts.
Yeah, I've been meaning to setup my own mail server using stalwart. But in the meaning time we shouldn't let perfect be the enemy of the good.
If you don't care about the details, jump to the TL;DR section at the end.
What we are going to do
The first step is connecting our Google Account in Gnome's Online Account settings dialog. Then we need to find the account ID the Online Accounts. We can use use busctl or another way to call the org.gnome.OnlineAccounts D-Bus service. With the account ID, will write a git-credential-helper to retrieve an OAuth Access Token over dbus when called. Finall configure git to use the credential helper.
Finding out the Account ID
To find the account ID you can use an d-bus explorer or busctl. Running
busctl --user tree org.gnome.OnlineAccounts
should print something like
ββ /org
ββ /org/gnome
ββ /org/gnome/OnlineAccounts
ββ /org/gnome/OnlineAccounts/Accounts
β ββ /org/gnome/OnlineAccounts/Accounts/account_1778792312_0
β ββ /org/gnome/OnlineAccounts/Accounts/account_1779120274_0
ββ /org/gnome/OnlineAccounts/Manager
The account ID is the It last segment under the /org/gnome/OnlineAccounts/Accounts path. If we only have a single account it is easy to know which account ID to use. In case you have multiple you can use jq to print the account id and the email it is linked to.
busctl --user --json=short call org.gnome.OnlineAccounts
/org/gnome/OnlineAccounts org.freedesktop.DBus.ObjectManager GetManagedObjects |
jq -r '.data[0] | to_entries[] | .value["org.gnome.OnlineAccounts.Account"] as
$a | select($a) | [.key, $a.Id.data, $a.PresentationIdentity.data] | @tsv'
/org/gnome/OnlineAccounts/Accounts/account_1779120274_0 account_1779120274_0 janedoe@gmail.com
/org/gnome/OnlineAccounts/Accounts/account_1778792312_0 account_1778792312_0 jonhdoe@gmail.com
Our first credential helper
#!/usr/bin/env sh
set -eu
# git-credential-goa-oauth is credential helper that pulls an OAuth2 access
# token from GNOME Online Accounts via D-Bus.
#
# git helpers support three operations: get, store and cache. git tells us which
# operation to perform by passing its name as the first argument. We only
# respond to get. For the operations we don't support we should exit
# silently. To tell git the list of attributes to use we should write them to
# STDOUT. One attribute per line. With the format name=value.
#
# = References
#
# To learn more about how to implement a custom helper read:
#
# - https://git-scm.com/docs/gitcredentials#_custom_helpers
# - man 7 gitcredentials
#
# To learn more of the how git communicates with the credential helper and
# vice-versa read:
#
# - https://git-scm.com/docs/git-credential#IOFMT
# - man 1 git-credential for the output format
# The first argument is the operation to perform. It can be get, store or
# erase. We only handle the get operation in our script.
[ "$1" = "get" ] || exit 0
ACCOUNT_ID="account_1778792312_0"
ACCOUNT_PATH="/org/gnome/OnlineAccounts/Accounts/${ACCOUNT_ID}"
REPLY=$(busctl --user --json=short call org.gnome.OnlineAccounts "${ACCOUNT_PATH}" org.gnome.OnlineAccounts.OAuth2Based GetAccessToken)
TOKEN=$(echo "${REPLY}" | jq -er '.data[0]')
printf 'password=%s\n' "$TOKEN"
Improving the credential helper
There is still one more improvement we can make to the helper at the cost of making it a little bit more complex. We are currently hard-coding the account ID. This is not necessary as Git tells us the email of the account it is looking for. We can use that information to find the account id ourselves.
We can use the use "/org/gnome/OnlineAccounts/Accounts" method to obtain a list of the accounts registered in the system.
Analyzing the response we see that we need the data key. The data key contains
an array of accounts. We need to iterate over them, searching for the one with
the email (org.gnome.OnlineAccounts.Mail.EmailAddress.data or
SmtpUserName.data) that matches username attribute git sent us and returning
the account ID (org.gnome.OnlineAccounts.Account.Id.data) attribute in the
top-level array.
We need to iterate data, selecting all the keys that begin with "/org/gnome/OnlineAccounts/Accounts/".
We need to check that the Account object:
- has an attribute named "org.gnome.OnlineAccounts.OAuth2Based".
- has an attribute named "org.gnome.OnlineAccounts.Mail".
- which contains an attribute "ImapUserName"."data" that matches the username.
When we find a match, the account ID will be found under "org.gnome.OnlineAccounts.Account"."Id"."data".
Putting it all together in using jq:
busctl --user --json=pretty call org.gnome.OnlineAccounts /org/gnome/OnlineAccounts org.freedesktop.DBus.ObjectManager GetManagedObjects > /tmp/goa.json
jq '.data.[] | to_entries.[] | select(.key | test("/org/gnome/OnlineAccounts/Accounts/")) | select(.value["org.gnome.OnlineAccounts.Mail"].ImapUserName.data | test("janedoe@gmail")) | .key' /tmp/goa.json
Taking all this into account the credential helper now looks like:
#!/usr/bin/env sh
set -eu
# git-credential-goa-oauth is credential helper that pulls an OAuth2 access token from
# GNOME Online Accounts via D-Bus.
#
# git helpers support three operations: get, store and cache. git tells us which
# operation to perform by passing its name as the first argument. We only
# respond to get. For the operations we don't support we should exit
# silently. To tell git the list of attributes to use we should write them to
# STDOUT. One attribute per line. With the format name=value.
#
# = References
#
# To learn more about how to implement a custom helper read:
#
# - https://git-scm.com/docs/gitcredentials#_custom_helpers
# - man 7 gitcredentials
#
# To learn more of the how git communicates with the credential helper and
# vice-versa read:
#
# - https://git-scm.com/docs/git-credential#IOFMT
# - man 1 git-credential for the output format
[ "$1" = "get" ] || exit 0
while IFS='=' read -r key value; do
[ -z "$key" ] && break
# For an exhaustive list of attributes see: https://git-scm.com/docs/git-credential#IOFMT
case "$key" in
username) USERNAME="$value" ;;
esac
done
# Resolve the GOA account whose Mail.ImapUserName matches the
# username git is asking for, then read the object path it was found under.
ACCOUNT_PATH=$(busctl --user --json=short call org.gnome.OnlineAccounts \
/org/gnome/OnlineAccounts \
org.freedesktop.DBus.ObjectManager GetManagedObjects \
| jq -er --arg email "${USERNAME}" '.data.[] | to_entries.[] |
select(.key | test("/org/gnome/OnlineAccounts/Accounts/")) |
select(.value["org.gnome.OnlineAccounts.Mail"].ImapUserName.data | test($email)) |
.key')
REPLY=$(busctl --user --json=short call org.gnome.OnlineAccounts "${ACCOUNT_PATH}" org.gnome.OnlineAccounts.OAuth2Based GetAccessToken)
TOKEN=$(echo "${REPLY}" | jq -er '.data[0]')
printf 'password=%s\n' "$TOKEN"
Configuring git
Finally we need to configure git.sendemail to use the helper. Add the following
to entries to your ~/.gitconfig
[sendemail]
smtpserver = smtp.gmail.com
smtpuser = janedoe@gmail.com
smtpserverport = 587
smtpencryption = tls
smtpAuth = XOAUTH2
[credential "smtp://smtp.gmail.com:587"]
helper = goa-oauth
Then place the following script in your path under the same
git-credential-goa-oauth. The name is important as git expects credentials not
using the full path to start with git-credential-.
Conclusion
TL;DR; The configuration
Assuming your gmail is janedoe@gmail.com
Connect your Google Account to Gnome Online Accounts.
Add the following to entries to your ~/.gitconfig
[sendemail]
smtpserver = smtp.gmail.com
smtpuser = janedoe@gmail.com
smtpserverport = 587
smtpencryption = tls
smtpAuth = XOAUTH2
[credential "smtp://smtp.gmail.com:587"]
helper = goa-oauth
Then place the following script in your path under the same
git-credential-goa-oauth.
#!/usr/bin/env sh
set -eu
# git-credential-goa-oauth is credential helper that pulls an OAuth2 access token from
# GNOME Online Accounts via D-Bus.
#
# git helpers support three operations: get, store and cache. git tells us which
# operation to perform by passing its name as the first argument. We only
# respond to get. For the operations we don't support we should exit
# silently. To tell git the list of attributes to use we should write them to
# STDOUT. One attribute per line. With the format name=value.
#
# = References
#
# To learn more about how to implement a custom helper read:
#
# - https://git-scm.com/docs/gitcredentials#_custom_helpers
# - man 7 gitcredentials
#
# To learn more of the how git communicates with the credential helper and
# vice-versa read:
#
# - https://git-scm.com/docs/git-credential#IOFMT
# - man 1 git-credential for the output format
[ "$1" = "get" ] || exit 0
while IFS='=' read -r key value; do
[ -z "$key" ] && break
# For an exhaustive list of attributes see: https://git-scm.com/docs/git-credential#IOFMT
case "$key" in
username) USERNAME="$value" ;;
esac
done
# Resolve the GOA account whose Mail.ImapUserName matches the
# username git is asking for, then read the object path it was found under.
ACCOUNT_PATH=$(busctl --user --json=short call org.gnome.OnlineAccounts \
/org/gnome/OnlineAccounts \
org.freedesktop.DBus.ObjectManager GetManagedObjects \
| jq -er --arg email "${USERNAME}" '.data.[] | to_entries.[] |
select(.key | test("/org/gnome/OnlineAccounts/Accounts/")) |
select(.value["org.gnome.OnlineAccounts.Mail"].ImapUserName.data | test($email)) |
.key')
REPLY=$(busctl --user --json=short call org.gnome.OnlineAccounts "${ACCOUNT_PATH}" org.gnome.OnlineAccounts.OAuth2Based GetAccessToken)
TOKEN=$(echo "${REPLY}" | jq -er '.data[0]')
printf 'password=%s\n' "$TOKEN"