tests: OAuth2 - Implement coverage for `OAUTHBEARER`

Caddyfile route for `/imap/` now accepts any subpath to support handling both `xoauth2` and `oauthbearer` subpaths.

Both SASL mechanisms represent the same information, with `XOAUTH2` being a common mechanism to encounter defined by Google, whilst `OAUTHBEARER` is the newer variant standardized by RFC 7628 but not yet as widely adopted.

The request to `/userinfo` endpoint will be the same, only the `credentials` value to be encoded differs.

Instead of repeating the block for a similar route, this difference is handled via the Caddyfile `map` directive.

We match the path context (_`/xoauth2` or `/oauthbearer`, the `/imap` prefix was stripped by `handle_path` earlier_), when there is a valid match, `sasl_mechanism` and `credentials` map vars are created and assigned to be referenced by the later `respond` directive.

---

Repeat the same test-case logic, DRY with log asserts extracted to a common function call. This should be fine as the auth method will be sufficient to match against or a common failure caught.
This commit is contained in:
polarathene 2024-01-19 18:08:44 +13:00
parent 74ead6a83a
commit f770609a66
4 changed files with 22 additions and 6 deletions

View File

@ -15,7 +15,7 @@
} }
# An additional endpoint for maintainers to generate `test/files/auth/imap-oauth2-auth.txt` # An additional endpoint for maintainers to generate `test/files/auth/imap-oauth2-auth.txt`
handle_path /imap/xoauth2 { handle_path /imap/* {
reverse_proxy localhost:3000 reverse_proxy localhost:3000
} }
} }
@ -55,6 +55,7 @@
# Generate IMAP commands for authentication testing # Generate IMAP commands for authentication testing
# Provide `user` and `access_token` values via query string parameters: # Provide `user` and `access_token` values via query string parameters:
# curl 'http://auth.example.test/imap/xoauth2?user=user1@localhost.localdomain&access_token=DMS_YWNjZXNzX3Rva2Vu' # curl 'http://auth.example.test/imap/xoauth2?user=user1@localhost.localdomain&access_token=DMS_YWNjZXNzX3Rva2Vu'
# curl 'http://auth.example.test/imap/oauthbearer?user=user1@localhost.localdomain&access_token=DMS_YWNjZXNzX3Rva2Vu'
# #
# Example Response: # Example Response:
# a0 AUTHENTICATE XOAUTH2 dXNlcj11c2VyMUBsb2NhbGhvc3QubG9jYWxkb21haW4BYXV0aD1CZWFyZXIgRE1TX1lXTmpaWE56WDNSdmEyVnUBAQ== # a0 AUTHENTICATE XOAUTH2 dXNlcj11c2VyMUBsb2NhbGhvc3QubG9jYWxkb21haW4BYXV0aD1CZWFyZXIgRE1TX1lXTmpaWE56WDNSdmEyVnUBAQ==
@ -66,13 +67,17 @@
:3000 { :3000 {
# The login username + OAuth2 access token prior to Base64 encoding, as per the XOAUTH2 spec: # The login username + OAuth2 access token prior to Base64 encoding, as per the XOAUTH2 spec:
# https://developers.google.com/gmail/imap/xoauth2-protocol#the_sasl_xoauth2_mechanism # https://developers.google.com/gmail/imap/xoauth2-protocol#the_sasl_xoauth2_mechanism
vars credentials "user={query.user}\001auth=Bearer {query.access_token}\001\001" # For OAUTHBEARER `host` and `port` do not appear to affect authentication with Dovecot
map {path} {sasl_mechanism} {credentials} {
/xoauth2 XOAUTH2 "user={query.user}\001auth=Bearer {query.access_token}\001\001"
/oauthbearer OAUTHBEARER "n,a={query.user},\001host=localhost\001port=143\001auth=Bearer {query.access_token}\001\001"
}
# Responds with the raw IMAP commands for testing XOAUTH2 authentication. # Responds with the raw IMAP commands for testing XOAUTH2 authentication.
# Uses the `b64enc` template function to encode credentials as required for `IMAP AUTHENTICATE`: # Uses the `b64enc` template function to encode credentials as required for `IMAP AUTHENTICATE`:
templates templates
respond <<EOF respond <<EOF
a0 AUTHENTICATE XOAUTH2 {{b64enc "{vars.credentials}"}} a0 AUTHENTICATE {sasl_mechanism} {{b64enc "{credentials}"}}
a1 EXAMINE INBOX a1 EXAMINE INBOX
a2 LOGOUT a2 LOGOUT
EOF EOF

View File

@ -0,0 +1,4 @@
a0 NOOP See test/config/oauth2/Caddyfile to generate the below OAUTHBEARER string
a1 AUTHENTICATE OAUTHBEARER bixhPXVzZXIxQGxvY2FsaG9zdC5sb2NhbGRvbWFpbiwBaG9zdD1sb2NhbGhvc3QBcG9ydD0xNDMBYXV0aD1CZWFyZXIgRE1TX1lXTmpaWE56WDNSdmEyVnUBAQ==
a2 EXAMINE INBOX
a3 LOGOUT

View File

@ -55,15 +55,22 @@ function teardown_file() {
docker network rm "${DMS_TEST_NETWORK}" docker network rm "${DMS_TEST_NETWORK}"
} }
@test "oauth2: imap connect and authentication works" { @test "oauth2: imap connect and authentication works" {
# An initial connection needs to be made first, otherwise the auth attempt fails # An initial connection needs to be made first, otherwise the auth attempt fails
_run_in_container_bash 'nc -vz 0.0.0.0 143' _run_in_container_bash 'nc -vz 0.0.0.0 143'
_nc_wrapper 'auth/imap-oauth2-auth.txt' '-w 1 0.0.0.0 143' _nc_wrapper 'auth/imap-oauth2-xoauth2.txt' '-w 1 0.0.0.0 143'
__verify_successful_login 'XOAUTH2'
_nc_wrapper 'auth/imap-oauth2-oauthbearer.txt' '-w 1 0.0.0.0 143'
__verify_successful_login 'OAUTHBEARER'
}
function __verify_successful_login() {
local AUTH_METHOD=${1}
# Inspect the relevant Dovecot logs to catch failure / success: # Inspect the relevant Dovecot logs to catch failure / success:
_run_in_container grep 'dovecot:' /var/log/mail.log _run_in_container grep 'dovecot:' /var/log/mail.log
refute_output --partial 'oauth2 failed: Introspection failed' refute_output --partial 'oauth2 failed: Introspection failed'
assert_output --partial 'dovecot: imap-login: Login: user=<user1@localhost.localdomain>, method=XOAUTH2' assert_output --partial "dovecot: imap-login: Login: user=<user1@localhost.localdomain>, method=${AUTH_METHOD}"
} }