tests: OAuth2 - Replace Python `/userinfo` endpoint with Caddy
Better documented, easier flow and separation of concerns via Caddy. The python code had additional noise related to setting up a basic API which is abstracted away via `Caddyfile` config that's dedicated to this task.
This commit is contained in:
parent
deb0d2d09a
commit
7eb3fc6de0
|
@ -0,0 +1,67 @@
|
||||||
|
# Mocked OAuth2 /userinfo endpoint normally provided via an Authorization Server (AS) / Identity Provider (IdP)
|
||||||
|
#
|
||||||
|
# Dovecot will query the mocked `/userinfo` endpoint with the OAuth2 bearer token it was provided during login.
|
||||||
|
# If the session for the token is valid, a response returns an attribute to perform a UserDB lookup on (default: email).
|
||||||
|
|
||||||
|
:80 {
|
||||||
|
# This is the `/userinfo` endpoint that Dovecot connects to with the OAuth2 setting (default: `introspection_mode = auth`).
|
||||||
|
# Example: curl http://auth.example.test/userinfo -H 'Authorization: Bearer <token here>'
|
||||||
|
handle_path /userinfo {
|
||||||
|
reverse_proxy localhost:2000
|
||||||
|
}
|
||||||
|
|
||||||
|
# An additional endpoint for maintainers to generate `test/files/auth/imap-oauth2-auth.txt`
|
||||||
|
handle_path /imap/xoauth2 {
|
||||||
|
reverse_proxy localhost:3000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Internal blocks below provide actual endpoint logic
|
||||||
|
#
|
||||||
|
|
||||||
|
# /userinfo
|
||||||
|
:2000 {
|
||||||
|
# OAuth2.0 Bearer token (paste into https://jwt.io/ to check it's contents).
|
||||||
|
# You should never need to edit this unless you REALLY need to change the issuer.
|
||||||
|
vars token "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vcHJvdmlkZXIuZXhhbXBsZS50ZXN0OjgwMDAvIiwic3ViIjoiODJjMWMzMzRkY2M2ZTMxMWFlNGFhZWJmZTk0NmM1ZTg1OGYwNTVhZmYxY2U1YTM3YWE3Y2M5MWFhYjE3ZTM1YyIsImF1ZCI6Im1haWxzZXJ2ZXIiLCJ1aWQiOiI4OU4zR0NuN1M1Y090WkZNRTVBeVhNbmxURFdVcnEzRmd4YWlyWWhFIn0.zuCytArbphhJn9XT_y9cBdGqDCNo68tBrtOwPIsuKNyF340SaOuZa0xarZofygytdDpLtYr56QlPTKImi-n1ZWrHkRZkwrQi5jQ-j_n2hEAL0vUToLbDnXYfc5q2w7z7X0aoCmiK8-fV7Kx4CVTM7riBgpElf6F3wNAIcX6R1ijUh6ISCL0XYsdogf8WUNZipXY-O4R7YHXdOENuOp3G48hWhxuUh9PsUqE5yxDwLsOVzCTqg9S5gxPQzF2eCN9J0I2XiIlLKvLQPIZ2Y_K7iYvVwjpNdgb4xhm9wuKoIVinYkF_6CwIzAawBWIDJAbix1IslkUPQMGbupTDtOgTiQ"
|
||||||
|
|
||||||
|
# Expects to match an authorization header with a specific bearer token:
|
||||||
|
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#authentication_schemes
|
||||||
|
@auth header Authorization "Bearer {vars.token}"
|
||||||
|
|
||||||
|
# If the provided authorization header has the expected value (bearer token), respond with this JSON payload:
|
||||||
|
handle @auth {
|
||||||
|
# JSON inlined via HereDoc string feature:
|
||||||
|
# Dovecot OAuth2 defaults to `username_attribute = email`, which must be returned in the response to match
|
||||||
|
# with the `user` credentials field that Dovecot received via base64 encoded IMAP `AUTHENTICATE` value.
|
||||||
|
respond <<EOF
|
||||||
|
{
|
||||||
|
"email": "user1@localhost.localdomain",
|
||||||
|
"email_verified": true,
|
||||||
|
"sub": "82c1c334dcc6e311ae4aaebfe946c5e858f055aff1ce5a37aa7cc91aab17e35c"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Failed to authorize, close connection and send a 401 status (unauthorized):
|
||||||
|
respond 401 {
|
||||||
|
close
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# /imap/xoauth2
|
||||||
|
# Responds with the auth string (base64 encoded) for use with the IMAP `AUTHENTICATE` command:
|
||||||
|
# curl http://auth.example.test/imap/xoauth2
|
||||||
|
# When Dovecot queries /userinfo endpoint, it will be after base64 decoding the IMAP `AUTHENTICATE` value,
|
||||||
|
# and sending the `auth` value from the `credentials` variable as an HTTP Authorization header.
|
||||||
|
:3000 {
|
||||||
|
route {
|
||||||
|
vars token "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vcHJvdmlkZXIuZXhhbXBsZS50ZXN0OjgwMDAvIiwic3ViIjoiODJjMWMzMzRkY2M2ZTMxMWFlNGFhZWJmZTk0NmM1ZTg1OGYwNTVhZmYxY2U1YTM3YWE3Y2M5MWFhYjE3ZTM1YyIsImF1ZCI6Im1haWxzZXJ2ZXIiLCJ1aWQiOiI4OU4zR0NuN1M1Y090WkZNRTVBeVhNbmxURFdVcnEzRmd4YWlyWWhFIn0.zuCytArbphhJn9XT_y9cBdGqDCNo68tBrtOwPIsuKNyF340SaOuZa0xarZofygytdDpLtYr56QlPTKImi-n1ZWrHkRZkwrQi5jQ-j_n2hEAL0vUToLbDnXYfc5q2w7z7X0aoCmiK8-fV7Kx4CVTM7riBgpElf6F3wNAIcX6R1ijUh6ISCL0XYsdogf8WUNZipXY-O4R7YHXdOENuOp3G48hWhxuUh9PsUqE5yxDwLsOVzCTqg9S5gxPQzF2eCN9J0I2XiIlLKvLQPIZ2Y_K7iYvVwjpNdgb4xhm9wuKoIVinYkF_6CwIzAawBWIDJAbix1IslkUPQMGbupTDtOgTiQ"
|
||||||
|
vars user "user1@localhost.localdomain"
|
||||||
|
vars credentials "user={vars.user}\001auth=Bearer {vars.token}\001\001"
|
||||||
|
}
|
||||||
|
|
||||||
|
templates
|
||||||
|
respond "{{b64enc \"{vars.credentials}\"}}"
|
||||||
|
}
|
|
@ -1,56 +0,0 @@
|
||||||
# OAuth2 mock service
|
|
||||||
#
|
|
||||||
# Dovecot will query this service with the token it was provided.
|
|
||||||
# If the session for the token is valid, a response provides an attribute to perform a UserDB lookup on (default: email).
|
|
||||||
|
|
||||||
import json
|
|
||||||
import base64
|
|
||||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
||||||
|
|
||||||
# OAuth2.0 Bearer token (paste into https://jwt.io/ to check it's contents).
|
|
||||||
# You should never need to edit this unless you REALLY need to change the issuer.
|
|
||||||
token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vcHJvdmlkZXIuZXhhbXBsZS50ZXN0OjgwMDAvIiwic3ViIjoiODJjMWMzMzRkY2M2ZTMxMWFlNGFhZWJmZTk0NmM1ZTg1OGYwNTVhZmYxY2U1YTM3YWE3Y2M5MWFhYjE3ZTM1YyIsImF1ZCI6Im1haWxzZXJ2ZXIiLCJ1aWQiOiI4OU4zR0NuN1M1Y090WkZNRTVBeVhNbmxURFdVcnEzRmd4YWlyWWhFIn0.zuCytArbphhJn9XT_y9cBdGqDCNo68tBrtOwPIsuKNyF340SaOuZa0xarZofygytdDpLtYr56QlPTKImi-n1ZWrHkRZkwrQi5jQ-j_n2hEAL0vUToLbDnXYfc5q2w7z7X0aoCmiK8-fV7Kx4CVTM7riBgpElf6F3wNAIcX6R1ijUh6ISCL0XYsdogf8WUNZipXY-O4R7YHXdOENuOp3G48hWhxuUh9PsUqE5yxDwLsOVzCTqg9S5gxPQzF2eCN9J0I2XiIlLKvLQPIZ2Y_K7iYvVwjpNdgb4xhm9wuKoIVinYkF_6CwIzAawBWIDJAbix1IslkUPQMGbupTDtOgTiQ"
|
|
||||||
|
|
||||||
# This is the string the user-facing client (e.g. Roundcube) should send via IMAP to Dovecot.
|
|
||||||
# We include the user and the above token separated by '\1' chars as per the XOAUTH2 spec.
|
|
||||||
xoauth2 = base64.b64encode(f"user=user1@localhost.localdomain\1auth=Bearer {token}\1\1".encode("utf-8"))
|
|
||||||
# If changing the user above, use the new output from the below line with the contents of the AUTHENTICATE command in test/test-files/auth/imap-oauth2-auth.txt
|
|
||||||
print("XOAUTH2 string: " + str(xoauth2))
|
|
||||||
|
|
||||||
|
|
||||||
class HTTPRequestHandler(BaseHTTPRequestHandler):
|
|
||||||
def do_GET(self):
|
|
||||||
auth = self.headers.get("Authorization")
|
|
||||||
if auth is None:
|
|
||||||
self.send_response(401)
|
|
||||||
self.end_headers()
|
|
||||||
return
|
|
||||||
if len(auth.split()) != 2:
|
|
||||||
self.send_response(401)
|
|
||||||
self.end_headers()
|
|
||||||
return
|
|
||||||
auth = auth.split()[1]
|
|
||||||
# Valid session, respond with JSON containing the expected `email` claim to match as Dovecot username:
|
|
||||||
if auth == token:
|
|
||||||
self.send_response(200)
|
|
||||||
self.send_header('Content-Type', 'application/json')
|
|
||||||
self.end_headers()
|
|
||||||
self.wfile.write(json.dumps({
|
|
||||||
"email": "user1@localhost.localdomain",
|
|
||||||
"email_verified": True,
|
|
||||||
"sub": "82c1c334dcc6e311ae4aaebfe946c5e858f055aff1ce5a37aa7cc91aab17e35c"
|
|
||||||
}).encode("utf-8"))
|
|
||||||
else:
|
|
||||||
self.send_response(401)
|
|
||||||
self.end_headers()
|
|
||||||
|
|
||||||
server = HTTPServer(('', 80), HTTPRequestHandler)
|
|
||||||
print("Starting server", flush=True)
|
|
||||||
|
|
||||||
try:
|
|
||||||
server.serve_forever()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print()
|
|
||||||
print("Received keyboard interrupt")
|
|
||||||
finally:
|
|
||||||
print("Exiting")
|
|
|
@ -19,11 +19,10 @@ function setup_file() {
|
||||||
docker run --rm -d --name "${CONTAINER2_NAME}" \
|
docker run --rm -d --name "${CONTAINER2_NAME}" \
|
||||||
--hostname "${FQDN_OAUTH2}" \
|
--hostname "${FQDN_OAUTH2}" \
|
||||||
--network "${DMS_TEST_NETWORK}" \
|
--network "${DMS_TEST_NETWORK}" \
|
||||||
--volume "${REPOSITORY_ROOT}/test/config/oauth2/:/app/" \
|
--volume "${REPOSITORY_ROOT}/test/config/oauth2/Caddyfile:/etc/caddy/Caddyfile:ro" \
|
||||||
docker.io/library/python:latest \
|
caddy:2.7
|
||||||
python /app/provider.py
|
|
||||||
|
|
||||||
_run_until_success_or_timeout 20 sh -c "docker logs ${CONTAINER2_NAME} 2>&1 | grep 'Starting server'"
|
_run_until_success_or_timeout 20 sh -c "docker logs ${CONTAINER2_NAME} 2>&1 | grep 'serving initial configuration'"
|
||||||
|
|
||||||
#
|
#
|
||||||
# Setup DMS container
|
# Setup DMS container
|
||||||
|
|
Loading…
Reference in New Issue