1: e2a0b48561 = 1: ce06c03e2b common/jsonapi: support FRONTEND clients 2: db625e1d01 = 2: 6989b75153 Refactor SASL exchange to return tri-state status 3: e4ad0260d5 = 3: 783bfe0b95 Explicitly require password for SCRAM exchange 4: 229f602d5c ! 4: 77550a47db libpq: add OAUTHBEARER SASL mechanism @@ Commit message are currently implemented (but clients may provide their own flows; see below). - The client implementation requires either libcurl or libiddawc and their - development headers. Pass `curl` or `iddawc` to --with-oauth/-Doauth - during configuration. + The client implementation requires libcurl and its development headers. + Pass `curl` to --with-oauth/-Doauth during configuration. Thomas Munro wrote the kqueue() implementation for oauth-curl; thanks! @@ configure: Optional Packages: --with-pam build with PAM support --with-bsd-auth build with BSD Authentication support --with-ldap build with LDAP support -+ --with-oauth=LIB use LIB for OAuth 2.0 support (curl, iddawc) ++ --with-oauth=LIB use LIB for OAuth 2.0 support (curl) --with-bonjour build with Bonjour support --with-selinux build with SELinux support --with-systemd build with systemd support @@ configure: $as_echo "$with_ldap" >&6; } + +$as_echo "#define USE_OAUTH_CURL 1" >>confdefs.h + -+elif test x"$with_oauth" = x"iddawc"; then -+ -+$as_echo "#define USE_OAUTH 1" >>confdefs.h -+ -+ -+$as_echo "#define USE_OAUTH_IDDAWC 1" >>confdefs.h -+ +elif test x"$with_oauth" != x"no"; then -+ as_fn_error $? "--with-oauth must specify curl or iddawc" "$LINENO" 5 ++ as_fn_error $? "--with-oauth must specify curl" "$LINENO" 5 +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_oauth" >&5 @@ configure: fi + as_fn_error $? "library 'curl' is required for --with-oauth=curl" "$LINENO" 5 +fi + -+elif test "$with_oauth" = iddawc ; then -+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for i_init_session in -liddawc" >&5 -+$as_echo_n "checking for i_init_session in -liddawc... " >&6; } -+if ${ac_cv_lib_iddawc_i_init_session+:} false; then : -+ $as_echo_n "(cached) " >&6 -+else -+ ac_check_lib_save_LIBS=$LIBS -+LIBS="-liddawc $LIBS" -+cat confdefs.h - <<_ACEOF >conftest.$ac_ext -+/* end confdefs.h. */ -+ -+/* Override any GCC internal prototype to avoid an error. -+ Use char because int might match the return type of a GCC -+ builtin and then its argument prototype would still apply. */ -+#ifdef __cplusplus -+extern "C" -+#endif -+char i_init_session (); -+int -+main () -+{ -+return i_init_session (); -+ ; -+ return 0; -+} -+_ACEOF -+if ac_fn_c_try_link "$LINENO"; then : -+ ac_cv_lib_iddawc_i_init_session=yes -+else -+ ac_cv_lib_iddawc_i_init_session=no -+fi -+rm -f core conftest.err conftest.$ac_objext \ -+ conftest$ac_exeext conftest.$ac_ext -+LIBS=$ac_check_lib_save_LIBS -+fi -+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_iddawc_i_init_session" >&5 -+$as_echo "$ac_cv_lib_iddawc_i_init_session" >&6; } -+if test "x$ac_cv_lib_iddawc_i_init_session" = xyes; then : -+ cat >>confdefs.h <<_ACEOF -+#define HAVE_LIBIDDAWC 1 -+_ACEOF -+ -+ LIBS="-liddawc $LIBS" -+ -+else -+ as_fn_error $? "library 'iddawc' is required for --with-oauth=iddawc" "$LINENO" 5 -+fi -+ -+ # Check for an older spelling of i_get_openid_config -+ for ac_func in i_load_openid_config -+do : -+ ac_fn_c_check_func "$LINENO" "i_load_openid_config" "ac_cv_func_i_load_openid_config" -+if test "x$ac_cv_func_i_load_openid_config" = xyes; then : -+ cat >>confdefs.h <<_ACEOF -+#define HAVE_I_LOAD_OPENID_CONFIG 1 -+_ACEOF -+ -+fi -+done -+ +fi + # for contrib/sepgsql @@ configure: fi + as_fn_error $? "header file is required for OAuth" "$LINENO" 5 +fi + -+ -+elif test "$with_oauth" = iddawc; then -+ ac_fn_c_check_header_mongrel "$LINENO" "iddawc.h" "ac_cv_header_iddawc_h" "$ac_includes_default" -+if test "x$ac_cv_header_iddawc_h" = xyes; then : -+ -+else -+ as_fn_error $? "header file is required for OAuth" "$LINENO" 5 -+fi -+ + fi @@ configure.ac: AC_MSG_RESULT([$with_ldap]) +# OAuth 2.0 +# +AC_MSG_CHECKING([whether to build with OAuth support]) -+PGAC_ARG_REQ(with, oauth, [LIB], [use LIB for OAuth 2.0 support (curl, iddawc)]) ++PGAC_ARG_REQ(with, oauth, [LIB], [use LIB for OAuth 2.0 support (curl)]) +if test x"$with_oauth" = x"" ; then + with_oauth=no +fi @@ configure.ac: AC_MSG_RESULT([$with_ldap]) +if test x"$with_oauth" = x"curl"; then + AC_DEFINE([USE_OAUTH], 1, [Define to 1 to build with OAuth 2.0 support. (--with-oauth)]) + AC_DEFINE([USE_OAUTH_CURL], 1, [Define to 1 to use libcurl for OAuth support.]) -+elif test x"$with_oauth" = x"iddawc"; then -+ AC_DEFINE([USE_OAUTH], 1, [Define to 1 to build with OAuth 2.0 support. (--with-oauth)]) -+ AC_DEFINE([USE_OAUTH_IDDAWC], 1, [Define to 1 to use libiddawc for OAuth support.]) +elif test x"$with_oauth" != x"no"; then -+ AC_MSG_ERROR([--with-oauth must specify curl or iddawc]) ++ AC_MSG_ERROR([--with-oauth must specify curl]) +fi + +AC_MSG_RESULT([$with_oauth]) @@ configure.ac: fi +if test "$with_oauth" = curl ; then + AC_CHECK_LIB(curl, curl_multi_init, [], [AC_MSG_ERROR([library 'curl' is required for --with-oauth=curl])]) -+elif test "$with_oauth" = iddawc ; then -+ AC_CHECK_LIB(iddawc, i_init_session, [], [AC_MSG_ERROR([library 'iddawc' is required for --with-oauth=iddawc])]) -+ # Check for an older spelling of i_get_openid_config -+ AC_CHECK_FUNCS([i_load_openid_config]) +fi + # for contrib/sepgsql @@ configure.ac: elif test "$with_uuid" = ossp ; then +if test "$with_oauth" = curl; then + AC_CHECK_HEADER(curl/curl.h, [], [AC_MSG_ERROR([header file is required for OAuth])]) -+elif test "$with_oauth" = iddawc; then -+ AC_CHECK_HEADER(iddawc.h, [], [AC_MSG_ERROR([header file is required for OAuth])]) +fi + if test "$PORTNAME" = "win32" ; then @@ meson.build: endif + endif +endif + -+if not oauth.found() and oauthopt in ['auto', 'iddawc'] -+ oauth = dependency('libiddawc', required: (oauthopt == 'iddawc')) -+ -+ if oauth.found() -+ oauth_library = 'iddawc' -+ cdata.set('USE_OAUTH', 1) -+ cdata.set('USE_OAUTH_IDDAWC', 1) -+ -+ # Check for an older spelling of i_get_openid_config -+ if cc.has_function('i_load_openid_config', -+ dependencies: oauth, args: test_c_args) -+ cdata.set('HAVE_I_LOAD_OPENID_CONFIG', 1) -+ endif -+ endif -+endif -+ +if oauthopt == 'auto' and auto_features.enabled() and not oauth.found() + error('no OAuth implementation library found') +endif @@ meson_options.txt: option('lz4', type: 'feature', value: 'auto', option('nls', type: 'feature', value: 'auto', description: 'Native language support') -+option('oauth', type : 'combo', choices : ['auto', 'none', 'curl', 'iddawc'], ++option('oauth', type : 'combo', choices : ['auto', 'none', 'curl'], + value: 'auto', -+ description: 'use LIB for OAuth 2.0 support (curl, iddawc)') ++ description: 'use LIB for OAuth 2.0 support (curl)') + option('pam', type: 'feature', value: 'auto', description: 'PAM support') @@ src/include/common/oauth-common.h (new) +#endif /* OAUTH_COMMON_H */ ## src/include/pg_config.h.in ## -@@ - /* Define to 1 if __builtin_constant_p(x) implies "i"(x) acceptance. */ - #undef HAVE_I_CONSTRAINT__BUILTIN_CONSTANT_P - -+/* Define to 1 if you have the `i_load_openid_config' function. */ -+#undef HAVE_I_LOAD_OPENID_CONFIG -+ - /* Define to 1 if you have the `kqueue' function. */ - #undef HAVE_KQUEUE - @@ /* Define to 1 if you have the `crypto' library (-lcrypto). */ #undef HAVE_LIBCRYPTO +/* Define to 1 if you have the `curl' library (-lcurl). */ +#undef HAVE_LIBCURL -+ -+/* Define to 1 if you have the `iddawc' library (-liddawc). */ -+#undef HAVE_LIBIDDAWC + /* Define to 1 if you have the `ldap' library (-lldap). */ #undef HAVE_LIBLDAP @@ src/include/pg_config.h.in + +/* Define to 1 to use libcurl for OAuth support. */ +#undef USE_OAUTH_CURL -+ -+/* Define to 1 to use libiddawc for OAuth support. */ -+#undef USE_OAUTH_IDDAWC + /* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */ #undef USE_OPENSSL @@ src/interfaces/libpq/Makefile: OBJS += \ +ifneq ($(with_oauth),no) +OBJS += fe-auth-oauth.o + -+ifeq ($(with_oauth),iddawc) -+OBJS += fe-auth-oauth-iddawc.o -+else ++ifeq ($(with_oauth),curl) +OBJS += fe-auth-oauth-curl.o +endif +endif @@ src/interfaces/libpq/Makefile: endif SHLIB_LINK_INTERNAL = -lpgcommon_shlib -lpgport_shlib ifneq ($(PORTNAME), win32) -SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lsocket -lnsl -lresolv -lintl -lm, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS) -+SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lcurl -liddawc -lsocket -lnsl -lresolv -lintl -lm, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS) ++SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lcurl -lsocket -lnsl -lresolv -lintl -lm, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS) else SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi32 -lssl -lsocket -lnsl -lresolv -lintl -lm $(PTHREAD_LIBS), $(LIBS)) $(LDAP_LIBS_FE) endif @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new) + + free_token(&tok); + return PGRES_POLLING_FAILED; -+} - - ## src/interfaces/libpq/fe-auth-oauth-iddawc.c (new) ## -@@ -+/*------------------------------------------------------------------------- -+ * -+ * fe-auth-oauth-iddawc.c -+ * The libiddawc implementation of OAuth/OIDC authentication. -+ * -+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group -+ * Portions Copyright (c) 1994, Regents of the University of California -+ * -+ * IDENTIFICATION -+ * src/interfaces/libpq/fe-auth-oauth-iddawc.c -+ * -+ *------------------------------------------------------------------------- -+ */ -+ -+#include "postgres_fe.h" -+ -+#include -+ -+#include "fe-auth.h" -+#include "fe-auth-oauth.h" -+#include "libpq-int.h" -+ -+#ifdef HAVE_I_LOAD_OPENID_CONFIG -+/* Older versions of iddawc used 'load' instead of 'get' for some APIs. */ -+#define i_get_openid_config i_load_openid_config -+#endif -+ -+static const char * -+iddawc_error_string(int errcode) -+{ -+ switch (errcode) -+ { -+ case I_OK: -+ return "I_OK"; -+ -+ case I_ERROR: -+ return "I_ERROR"; -+ -+ case I_ERROR_PARAM: -+ return "I_ERROR_PARAM"; -+ -+ case I_ERROR_MEMORY: -+ return "I_ERROR_MEMORY"; -+ -+ case I_ERROR_UNAUTHORIZED: -+ return "I_ERROR_UNAUTHORIZED"; -+ -+ case I_ERROR_SERVER: -+ return "I_ERROR_SERVER"; -+ } -+ -+ return ""; -+} -+ -+static void -+iddawc_error(PGconn *conn, int errcode, const char *msg) -+{ -+ appendPQExpBufferStr(&conn->errorMessage, libpq_gettext(msg)); -+ appendPQExpBuffer(&conn->errorMessage, -+ libpq_gettext(" (iddawc error %s)\n"), -+ iddawc_error_string(errcode)); -+} -+ -+static void -+iddawc_request_error(PGconn *conn, struct _i_session *i, int err, const char *msg) -+{ -+ const char *error_code; -+ const char *desc; -+ -+ appendPQExpBuffer(&conn->errorMessage, "%s: ", libpq_gettext(msg)); -+ -+ error_code = i_get_str_parameter(i, I_OPT_ERROR); -+ if (!error_code) -+ { -+ /* -+ * The server didn't give us any useful information, so just print the -+ * error code. -+ */ -+ appendPQExpBuffer(&conn->errorMessage, -+ libpq_gettext("(iddawc error %s)\n"), -+ iddawc_error_string(err)); -+ return; -+ } -+ -+ /* If the server gave a string description, print that too. */ -+ desc = i_get_str_parameter(i, I_OPT_ERROR_DESCRIPTION); -+ if (desc) -+ appendPQExpBuffer(&conn->errorMessage, "%s ", desc); -+ -+ appendPQExpBuffer(&conn->errorMessage, "(%s)\n", error_code); -+} -+ -+/* -+ * Runs the device authorization flow using libiddawc. If successful, a malloc'd -+ * token string in "Bearer xxxx..." format, suitable for sending to an -+ * OAUTHBEARER server, is returned. NULL is returned on error. -+ */ -+static char * -+run_iddawc_auth_flow(PGconn *conn, const char *discovery_uri) -+{ -+ struct _i_session session; -+ PQExpBuffer token_buf = NULL; -+ int err; -+ int auth_method; -+ bool user_prompted = false; -+ const char *verification_uri; -+ const char *user_code; -+ const char *access_token; -+ const char *token_type; -+ char *token = NULL; -+ -+ i_init_session(&session); -+ -+ token_buf = createPQExpBuffer(); -+ if (!token_buf) -+ goto cleanup; -+ -+ err = i_set_str_parameter(&session, I_OPT_OPENID_CONFIG_ENDPOINT, discovery_uri); -+ if (err) -+ { -+ iddawc_error(conn, err, "failed to set OpenID config endpoint"); -+ goto cleanup; -+ } -+ -+ err = i_get_openid_config(&session); -+ if (err) -+ { -+ iddawc_error(conn, err, "failed to fetch OpenID discovery document"); -+ goto cleanup; -+ } -+ -+ if (!i_get_str_parameter(&session, I_OPT_TOKEN_ENDPOINT)) -+ { -+ appendPQExpBufferStr(&conn->errorMessage, -+ libpq_gettext("issuer has no token endpoint")); -+ goto cleanup; -+ } -+ -+ if (!i_get_str_parameter(&session, I_OPT_DEVICE_AUTHORIZATION_ENDPOINT)) -+ { -+ appendPQExpBufferStr(&conn->errorMessage, -+ libpq_gettext("issuer does not support device authorization")); -+ goto cleanup; -+ } -+ -+ err = i_set_response_type(&session, I_RESPONSE_TYPE_DEVICE_CODE); -+ if (err) -+ { -+ iddawc_error(conn, err, "failed to set device code response type"); -+ goto cleanup; -+ } -+ -+ auth_method = I_TOKEN_AUTH_METHOD_NONE; -+ if (conn->oauth_client_secret && *conn->oauth_client_secret) -+ auth_method = I_TOKEN_AUTH_METHOD_SECRET_BASIC; -+ -+ err = i_set_parameter_list(&session, -+ I_OPT_CLIENT_ID, conn->oauth_client_id, -+ I_OPT_CLIENT_SECRET, conn->oauth_client_secret, -+ I_OPT_TOKEN_METHOD, auth_method, -+ I_OPT_SCOPE, conn->oauth_scope, -+ I_OPT_NONE -+ ); -+ if (err) -+ { -+ iddawc_error(conn, err, "failed to set client identifier"); -+ goto cleanup; -+ } -+ -+ err = i_run_device_auth_request(&session); -+ if (err) -+ { -+ iddawc_request_error(conn, &session, err, -+ "failed to obtain device authorization"); -+ goto cleanup; -+ } -+ -+ verification_uri = i_get_str_parameter(&session, I_OPT_DEVICE_AUTH_VERIFICATION_URI); -+ if (!verification_uri) -+ { -+ appendPQExpBufferStr(&conn->errorMessage, -+ libpq_gettext("issuer did not provide a verification URI")); -+ goto cleanup; -+ } -+ -+ user_code = i_get_str_parameter(&session, I_OPT_DEVICE_AUTH_USER_CODE); -+ if (!user_code) -+ { -+ appendPQExpBufferStr(&conn->errorMessage, -+ libpq_gettext("issuer did not provide a user code")); -+ goto cleanup; -+ } -+ -+ /* -+ * Poll the token endpoint until either the user logs in and authorizes -+ * the use of a token, or a hard failure occurs. We perform one ping -+ * _before_ prompting the user, so that we don't make them do the work of -+ * logging in only to find that the token endpoint is completely -+ * unreachable. -+ */ -+ err = i_run_token_request(&session); -+ while (err) -+ { -+ const char *error_code; -+ uint interval; -+ -+ error_code = i_get_str_parameter(&session, I_OPT_ERROR); -+ -+ /* -+ * authorization_pending and slow_down are the only acceptable errors; -+ * anything else and we bail. -+ */ -+ if (!error_code || (strcmp(error_code, "authorization_pending") -+ && strcmp(error_code, "slow_down"))) -+ { -+ iddawc_request_error(conn, &session, err, -+ "failed to obtain access token"); -+ goto cleanup; -+ } -+ -+ if (!user_prompted) -+ { -+ int res; -+ PQpromptOAuthDevice prompt = { -+ .verification_uri = verification_uri, -+ .user_code = user_code, -+ /* TODO: optional fields */ -+ }; -+ -+ /* -+ * Now that we know the token endpoint isn't broken, give the user -+ * the login instructions. -+ */ -+ res = PQauthDataHook(PQAUTHDATA_PROMPT_OAUTH_DEVICE, conn, -+ &prompt); -+ -+ if (!res) -+ { -+ fprintf(stderr, "Visit %s and enter the code: %s", -+ prompt.verification_uri, prompt.user_code); -+ } -+ else if (res < 0) -+ { -+ appendPQExpBufferStr(&conn->errorMessage, -+ libpq_gettext("device prompt failed\n")); -+ goto cleanup; -+ } -+ -+ user_prompted = true; -+ } -+ -+ /*--- -+ * We are required to wait between polls; the server tells us how -+ * long. -+ * TODO: if interval's not set, we need to default to five seconds -+ * TODO: sanity check the interval -+ */ -+ interval = i_get_int_parameter(&session, I_OPT_DEVICE_AUTH_INTERVAL); -+ -+ /* -+ * A slow_down error requires us to permanently increase our retry -+ * interval by five seconds. RFC 8628, Sec. 3.5. -+ */ -+ if (!strcmp(error_code, "slow_down")) -+ { -+ interval += 5; -+ i_set_int_parameter(&session, I_OPT_DEVICE_AUTH_INTERVAL, interval); -+ } -+ -+ sleep(interval); -+ -+ /* -+ * XXX Reset the error code before every call, because iddawc won't do -+ * that for us. This matters if the server first sends a "pending" -+ * error code, then later hard-fails without sending an error code to -+ * overwrite the first one. -+ * -+ * That we have to do this at all seems like a bug in iddawc. -+ */ -+ i_set_str_parameter(&session, I_OPT_ERROR, NULL); -+ -+ err = i_run_token_request(&session); -+ } -+ -+ access_token = i_get_str_parameter(&session, I_OPT_ACCESS_TOKEN); -+ token_type = i_get_str_parameter(&session, I_OPT_TOKEN_TYPE); -+ -+ if (!access_token || !token_type || strcasecmp(token_type, "Bearer")) -+ { -+ appendPQExpBufferStr(&conn->errorMessage, -+ libpq_gettext("issuer did not provide a bearer token")); -+ goto cleanup; -+ } -+ -+ appendPQExpBufferStr(token_buf, "Bearer "); -+ appendPQExpBufferStr(token_buf, access_token); -+ -+ if (PQExpBufferBroken(token_buf)) -+ goto cleanup; -+ -+ token = strdup(token_buf->data); -+ -+cleanup: -+ if (token_buf) -+ destroyPQExpBuffer(token_buf); -+ i_clean_session(&session); -+ -+ return token; -+} -+ -+PostgresPollingStatusType -+pg_fe_run_oauth_flow(PGconn *conn, pgsocket *altsock) -+{ -+ fe_oauth_state *state = conn->sasl_state; -+ -+ /* TODO: actually make this asynchronous */ -+ state->token = run_iddawc_auth_flow(conn, conn->oauth_discovery_uri); -+ return state->token ? PGRES_POLLING_OK : PGRES_POLLING_FAILED; +} ## src/interfaces/libpq/fe-auth-oauth.c (new) ## @@ src/interfaces/libpq/meson.build: if gssapi.found() +if oauth.found() + libpq_sources += files('fe-auth-oauth.c') -+ if oauth_library == 'iddawc' -+ libpq_sources += files('fe-auth-oauth-iddawc.c') -+ else ++ if oauth_library == 'curl' + libpq_sources += files('fe-auth-oauth-curl.c') + endif +endif 5: 5c5a83e44e = 5: 12ae7c4355 backend: add OAUTHBEARER SASL mechanism 6: 7a42365d62 = 6: 707edf9314 Introduce OAuth validator libraries 7: 9c46ea6cf9 ! 7: 47236c5644 Add pytest suite for OAuth @@ Commit message option of the same name, which allows an ephemeral server to be spun up during a test run. - For iddawc, asynchronous tests still hang, as expected. Bad-interval - tests fail because iddawc apparently doesn't care that the interval is - bad. - TODOs: - The --tap-stream option to pytest-tap is slightly broken during test failures (it suppresses error information), which impedes debugging. @@ src/test/python/client/test_oauth.py (new) + ), + pytest.param( + (400, {}), -+ alt_patterns( -+ r'failed to parse token error response: field "error" is missing', -+ r"failed to obtain device authorization: \(iddawc error I_ERROR_PARAM\)", -+ ), ++ r'failed to parse token error response: field "error" is missing', + id="broken error response", + ), + pytest.param( + (200, RawResponse(r'{ "interval": 3.5.8 }')), -+ alt_patterns( -+ r"failed to parse device authorization: Token .* is invalid", -+ r"failed to obtain device authorization: \(iddawc error I_ERROR\)", -+ ), ++ r"failed to parse device authorization: Token .* is invalid", + id="non-numeric interval", + ), + pytest.param( + (200, RawResponse(r'{ "interval": 08 }')), -+ alt_patterns( -+ r"failed to parse device authorization: Token .* is invalid", -+ r"failed to obtain device authorization: \(iddawc error I_ERROR\)", -+ ), ++ r"failed to parse device authorization: Token .* is invalid", + id="invalid numeric interval", + ), + ], @@ src/test/python/client/test_oauth.py (new) + else: + assert False, "update error_pattern for new failure mode" + -+ # XXX iddawc doesn't really check for problems in the device authorization -+ # response, leading to this patchwork: -+ if field_name == "verification_uri": -+ error_pattern = alt_patterns( -+ error_pattern, -+ "issuer did not provide a verification URI", -+ ) -+ elif field_name == "user_code": -+ error_pattern = alt_patterns( -+ error_pattern, -+ "issuer did not provide a user code", -+ ) -+ else: -+ error_pattern = alt_patterns( -+ error_pattern, -+ r"failed to obtain access token: \(iddawc error I_ERROR_PARAM\)", -+ ) -+ + with pytest.raises(psycopg2.OperationalError, match=error_pattern): + client.check_completed() + @@ src/test/python/client/test_oauth.py (new) + ), + pytest.param( + (400, {}), -+ alt_patterns( -+ r'failed to parse token error response: field "error" is missing', -+ r"failed to obtain access token: \(iddawc error I_ERROR_PARAM\)", -+ ), ++ r'failed to parse token error response: field "error" is missing', + id="empty error response", + ), + pytest.param( + (200, {}, {}), -+ alt_patterns( -+ r"failed to parse access token response: no content type was provided", -+ r"failed to obtain access token: \(iddawc error I_ERROR\)", -+ ), ++ r"failed to parse access token response: no content type was provided", + id="missing content type", + ), + pytest.param( + (200, {"Content-Type": "text/plain"}, {}), -+ alt_patterns( -+ r"failed to parse access token response: unexpected content type", -+ r"failed to obtain access token: \(iddawc error I_ERROR\)", -+ ), ++ r"failed to parse access token response: unexpected content type", + id="wrong content type", + ), + ], @@ src/test/python/client/test_oauth.py (new) + else: + assert False, "update error_pattern for new failure mode" + -+ # XXX iddawc is fairly silent on the topic. -+ error_pattern = alt_patterns( -+ error_pattern, -+ r"failed to obtain access token: \(iddawc error I_ERROR_PARAM\)", -+ ) -+ + with pytest.raises(psycopg2.OperationalError, match=error_pattern): + client.check_completed() + @@ src/test/python/client/test_oauth.py (new) + + expect_disconnected_handshake(sock) + -+ # XXX iddawc doesn't differentiate... -+ expected_error = alt_patterns( -+ expected_error, -+ r"failed to fetch OpenID discovery document \(iddawc error I_ERROR(_PARAM)?\)", -+ ) -+ + with pytest.raises(psycopg2.OperationalError, match=expected_error): + client.check_completed() + @@ src/test/python/test_pq3.py (new) +import contextlib +import getpass +import io ++import os +import platform +import struct +import sys 8: 8ad4ce3068 = 8: 116e17eeee XXX temporary patches to build and test 9: 5630465578 = 9: 28756eda1c WIP: Python OAuth provider implementation