since-v43.diff.txt
text/plain
Filename: since-v43.diff.txt
Type: text/plain
Part: 0
1: 5d474397364 = 1: 258b8dbb770 Move PG_MAX_AUTH_TOKEN_LENGTH to libpq/auth.h
2: 20452d21e0b ! 2: ec960cf363d require_auth: prepare for multiple SASL mechanisms
@@ src/interfaces/libpq/fe-auth.c: pg_SASL_init(PGconn *conn, int payloadlen)
{
## src/interfaces/libpq/fe-connect.c ##
+@@ src/interfaces/libpq/fe-connect.c: static const PQEnvironmentOption EnvironmentOptions[] =
+ }
+ };
+
++static const pg_fe_sasl_mech *supported_sasl_mechs[] =
++{
++ &pg_scram_mech,
++};
++#define SASL_MECHANISM_COUNT lengthof(supported_sasl_mechs)
++
+ /* The connection URI must start with either of the following designators: */
+ static const char uri_designator[] = "postgresql://";
+ static const char short_uri_designator[] = "postgres://";
@@ src/interfaces/libpq/fe-connect.c: libpq_prng_init(PGconn *conn)
pg_prng_seed(&conn->prng_state, rseed);
}
@@ src/interfaces/libpq/fe-connect.c: libpq_prng_init(PGconn *conn)
+ * rely on the compile-time assertion here to keep us honest.
+ *
+ * To add a new mechanism to require_auth,
++ * - add it to supported_sasl_mechs,
+ * - update the length of conn->allowed_sasl_mechs,
-+ * - add the new pg_fe_sasl_mech pointer to this function, and
+ * - handle the new mechanism name in the require_auth portion of
+ * pqConnectOptions2(), below.
+ */
-+ StaticAssertDecl(lengthof(conn->allowed_sasl_mechs) == 1,
-+ "fill_allowed_sasl_mechs() must be updated when resizing conn->allowed_sasl_mechs[]");
++ StaticAssertDecl(lengthof(conn->allowed_sasl_mechs) == SASL_MECHANISM_COUNT,
++ "conn->allowed_sasl_mechs[] is not sufficiently large for holding all supported SASL mechanisms");
+
-+ conn->allowed_sasl_mechs[0] = &pg_scram_mech;
++ for (int i = 0; i < SASL_MECHANISM_COUNT; i++)
++ conn->allowed_sasl_mechs[i] = supported_sasl_mechs[i];
+}
+
+/*
@@ src/interfaces/libpq/fe-connect.c: pqConnectOptions2(PGconn *conn)
+ * Next group: SASL mechanisms. All of these use the same request
+ * codes, so the list of allowed mechanisms is tracked separately.
+ *
-+ * fill_allowed_sasl_mechs() must be updated when adding a new
-+ * mechanism here!
++ * supported_sasl_mechs must contain all mechanisms handled here.
+ */
else if (strcmp(method, "scram-sha-256") == 0)
{
@@ src/interfaces/libpq/fe-connect.c: pqConnectOptions2(PGconn *conn)
+ i = index_of_allowed_sasl_mech(conn, mech);
+ if (i < 0)
+ goto duplicate;
-
-- conn->allowed_auth_methods &= ~bits;
++
+ conn->allowed_sasl_mechs[i] = NULL;
+ }
+ else
@@ src/interfaces/libpq/fe-connect.c: pqConnectOptions2(PGconn *conn)
+ i = index_of_allowed_sasl_mech(conn, mech);
+ if (i >= 0)
+ goto duplicate;
-+
+
+- conn->allowed_auth_methods &= ~bits;
+ i = index_of_allowed_sasl_mech(conn, NULL);
+ if (i < 0)
+ {
3: f0afefb80d6 ! 3: 9725788086c libpq: handle asynchronous actions during SASL
@@ src/interfaces/libpq/fe-connect.c: pqDropConnection(PGconn *conn, bool flushInpu
+ conn->cleanup_async_auth = NULL;
+ }
+ conn->async_auth = NULL;
-+ conn->altsock = PGINVALID_SOCKET; /* cleanup_async_auth() should have
-+ * done this, but make sure. */
++ /* cleanup_async_auth() should have done this, but make sure */
++ conn->altsock = PGINVALID_SOCKET;
#ifdef ENABLE_GSS
{
OM_uint32 min_s;
4: 711ca3f1efc ! 4: a260d9436f0 Add OAUTHBEARER SASL mechanism
@@ .cirrus.tasks.yml: task:
###
# Test that code can be built with gcc/clang without warnings
+ ## config/programs.m4 ##
+@@ config/programs.m4: AC_DEFUN([PGAC_CHECK_STRIP],
+ AC_SUBST(STRIP_STATIC_LIB)
+ AC_SUBST(STRIP_SHARED_LIB)
+ ])# PGAC_CHECK_STRIP
++
++
++
++# PGAC_CHECK_LIBCURL
++# ------------------
++# Check for required libraries and headers, and test to see whether the current
++# installation of libcurl is threadsafe.
++
++AC_DEFUN([PGAC_CHECK_LIBCURL],
++[
++ AC_CHECK_HEADER(curl/curl.h, [],
++ [AC_MSG_ERROR([header file <curl/curl.h> is required for --with-libcurl])])
++ AC_CHECK_LIB(curl, curl_multi_init, [],
++ [AC_MSG_ERROR([library 'curl' does not provide curl_multi_init])])
++
++ # Check to see whether the current platform supports threadsafe Curl
++ # initialization.
++ AC_CACHE_CHECK([for curl_global_init thread safety], [pgac_cv__libcurl_threadsafe_init],
++ [AC_RUN_IFELSE([AC_LANG_PROGRAM([
++#include <curl/curl.h>
++],[
++ curl_version_info_data *info;
++
++ if (curl_global_init(CURL_GLOBAL_ALL))
++ return -1;
++
++ info = curl_version_info(CURLVERSION_NOW);
++#ifdef CURL_VERSION_THREADSAFE
++ if (info->features & CURL_VERSION_THREADSAFE)
++ return 0;
++#endif
++
++ return 1;
++])],
++ [pgac_cv__libcurl_threadsafe_init=yes],
++ [pgac_cv__libcurl_threadsafe_init=no],
++ [pgac_cv__libcurl_threadsafe_init=unknown])])
++ if test x"$pgac_cv__libcurl_threadsafe_init" = xyes ; then
++ AC_DEFINE(HAVE_THREADSAFE_CURL_GLOBAL_INIT, 1,
++ [Define to 1 if curl_global_init() is guaranteed to be threadsafe.])
++ fi
++])# PGAC_CHECK_LIBCURL
+
## configure ##
@@ configure: XML2_LIBS
XML2_CFLAGS
@@ configure: fi
+
+fi
+
++ # We only care about -I, -D, and -L switches;
++ # note that -lcurl will be added by PGAC_CHECK_LIBCURL below.
++ for pgac_option in $LIBCURL_CFLAGS; do
++ case $pgac_option in
++ -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";;
++ esac
++ done
++ for pgac_option in $LIBCURL_LIBS; do
++ case $pgac_option in
++ -L*) LDFLAGS="$LDFLAGS $pgac_option";;
++ esac
++ done
++
+ # OAuth requires python for testing
+ if test "$with_python" != yes; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: *** OAuth support tests require --with-python to run" >&5
@@ configure: fi
+# during gss_acquire_cred(). This is possibly related to Curl's Heimdal
+# dependency on that platform?
+if test "$with_libcurl" = yes ; then
++
++ ac_fn_c_check_header_mongrel "$LINENO" "curl/curl.h" "ac_cv_header_curl_curl_h" "$ac_includes_default"
++if test "x$ac_cv_header_curl_curl_h" = xyes; then :
++
++else
++ as_fn_error $? "header file <curl/curl.h> is required for --with-libcurl" "$LINENO" 5
++fi
++
++
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for curl_multi_init in -lcurl" >&5
+$as_echo_n "checking for curl_multi_init in -lcurl... " >&6; }
+if ${ac_cv_lib_curl_curl_multi_init+:} false; then :
@@ configure: fi
+ LIBS="-lcurl $LIBS"
+
+else
-+ as_fn_error $? "library 'curl' is required for --with-libcurl" "$LINENO" 5
++ as_fn_error $? "library 'curl' does not provide curl_multi_init" "$LINENO" 5
+fi
+
++
++ # Check to see whether the current platform supports threadsafe Curl
++ # initialization.
++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for curl_global_init thread safety" >&5
++$as_echo_n "checking for curl_global_init thread safety... " >&6; }
++if ${pgac_cv__libcurl_threadsafe_init+:} false; then :
++ $as_echo_n "(cached) " >&6
++else
++ if test "$cross_compiling" = yes; then :
++ pgac_cv__libcurl_threadsafe_init=unknown
++else
++ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
++/* end confdefs.h. */
++
++#include <curl/curl.h>
++
++int
++main ()
++{
++
++ curl_version_info_data *info;
++
++ if (curl_global_init(CURL_GLOBAL_ALL))
++ return -1;
++
++ info = curl_version_info(CURLVERSION_NOW);
++#ifdef CURL_VERSION_THREADSAFE
++ if (info->features & CURL_VERSION_THREADSAFE)
++ return 0;
++#endif
++
++ return 1;
++
++ ;
++ return 0;
++}
++_ACEOF
++if ac_fn_c_try_run "$LINENO"; then :
++ pgac_cv__libcurl_threadsafe_init=yes
++else
++ pgac_cv__libcurl_threadsafe_init=no
++fi
++rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
++ conftest.$ac_objext conftest.beam conftest.$ac_ext
++fi
++
++fi
++{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv__libcurl_threadsafe_init" >&5
++$as_echo "$pgac_cv__libcurl_threadsafe_init" >&6; }
++ if test x"$pgac_cv__libcurl_threadsafe_init" = xyes ; then
++
++$as_echo "#define HAVE_THREADSAFE_CURL_GLOBAL_INIT 1" >>confdefs.h
++
++ fi
++
+fi
+
if test "$with_gssapi" = yes ; then
if test "$PORTNAME" != "win32"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing gss_store_cred_into" >&5
-@@ configure: fi
-
- done
-
-+fi
-+
-+if test "$with_libcurl" = yes; then
-+ ac_fn_c_check_header_mongrel "$LINENO" "curl/curl.h" "ac_cv_header_curl_curl_h" "$ac_includes_default"
-+if test "x$ac_cv_header_curl_curl_h" = xyes; then :
-+
-+else
-+ as_fn_error $? "header file <curl/curl.h> is required for --with-libcurl" "$LINENO" 5
-+fi
-+
-+
- fi
-
- if test "$PORTNAME" = "win32" ; then
## configure.ac ##
@@ configure.ac: fi
@@ configure.ac: fi
+ # to explicitly set TLS 1.3 ciphersuites).
+ PKG_CHECK_MODULES(LIBCURL, [libcurl >= 7.61.0])
+
++ # We only care about -I, -D, and -L switches;
++ # note that -lcurl will be added by PGAC_CHECK_LIBCURL below.
++ for pgac_option in $LIBCURL_CFLAGS; do
++ case $pgac_option in
++ -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";;
++ esac
++ done
++ for pgac_option in $LIBCURL_LIBS; do
++ case $pgac_option in
++ -L*) LDFLAGS="$LDFLAGS $pgac_option";;
++ esac
++ done
++
+ # OAuth requires python for testing
+ if test "$with_python" != yes; then
+ AC_MSG_WARN([*** OAuth support tests require --with-python to run])
@@ configure.ac: failure. It is possible the compiler isn't looking in the proper
+# during gss_acquire_cred(). This is possibly related to Curl's Heimdal
+# dependency on that platform?
+if test "$with_libcurl" = yes ; then
-+ AC_CHECK_LIB(curl, curl_multi_init, [], [AC_MSG_ERROR([library 'curl' is required for --with-libcurl])])
++ PGAC_CHECK_LIBCURL
+fi
+
if test "$with_gssapi" = yes ; then
if test "$PORTNAME" != "win32"; then
AC_SEARCH_LIBS(gss_store_cred_into, [gssapi_krb5 gss 'gssapi -lkrb5 -lcrypto'], [],
-@@ configure.ac: elif test "$with_uuid" = ossp ; then
- [AC_MSG_ERROR([header file <ossp/uuid.h> or <uuid.h> is required for OSSP UUID])])])
- fi
-
-+if test "$with_libcurl" = yes; then
-+ AC_CHECK_HEADER(curl/curl.h, [], [AC_MSG_ERROR([header file <curl/curl.h> is required for --with-libcurl])])
-+fi
-+
- if test "$PORTNAME" = "win32" ; then
- AC_CHECK_HEADERS(crtdefs.h)
- fi
## doc/src/sgml/client-auth.sgml ##
@@ doc/src/sgml/client-auth.sgml: include_dir <replaceable>directory</replaceable>
@@ doc/src/sgml/libpq.sgml: void PQinitSSL(int do_ssl);
<sect1 id="libpq-threading">
<title>Behavior in Threaded Programs</title>
+@@ doc/src/sgml/libpq.sgml: int PQisthreadsafe();
+ <application>libpq</application> source code for a way to do cooperative
+ locking between <application>libpq</application> and your application.
+ </para>
++
++ <para>
++ Similarly, if you are using Curl inside your application,
++ <emphasis>and</emphasis> you do not already
++ <ulink url="https://curl.se/libcurl/c/curl_global_init.html">initialize
++ libcurl globally</ulink> before starting new threads, you will need to
++ cooperatively lock (again via <function>PQregisterThreadLock</function>)
++ around any code that may initialize libcurl. This restriction is lifted for
++ more recent versions of Curl that are built to support threadsafe
++ initialization; those builds can be identified by the advertisement of a
++ <literal>threadsafe</literal> feature in their version metadata.
++ </para>
+ </sect1>
+
+
## doc/src/sgml/oauth-validators.sgml (new) ##
@@
@@ doc/src/sgml/postgres.sgml: break is not needed in a wider output rendering.
</part>
+ ## doc/src/sgml/protocol.sgml ##
+@@ doc/src/sgml/protocol.sgml: SELCT 1/0;<!-- this typo is intentional -->
+
+ <para>
+ <firstterm>SASL</firstterm> is a framework for authentication in connection-oriented
+- protocols. At the moment, <productname>PostgreSQL</productname> implements two SASL
+- authentication mechanisms, SCRAM-SHA-256 and SCRAM-SHA-256-PLUS. More
+- might be added in the future. The below steps illustrate how SASL
+- authentication is performed in general, while the next subsection gives
+- more details on SCRAM-SHA-256 and SCRAM-SHA-256-PLUS.
++ protocols. At the moment, <productname>PostgreSQL</productname> implements three
++ SASL authentication mechanisms: SCRAM-SHA-256, SCRAM-SHA-256-PLUS, and
++ OAUTHBEARER. More might be added in the future. The below steps illustrate how SASL
++ authentication is performed in general, while the next subsections give
++ more details on particular mechanisms.
+ </para>
+
+ <procedure>
+@@ doc/src/sgml/protocol.sgml: SELCT 1/0;<!-- this typo is intentional -->
+ <step id="sasl-auth-end">
+ <para>
+ Finally, when the authentication exchange is completed successfully, the
+- server sends an AuthenticationSASLFinal message, followed
++ server sends an optional AuthenticationSASLFinal message, followed
+ immediately by an AuthenticationOk message. The AuthenticationSASLFinal
+ contains additional server-to-client data, whose content is particular to the
+ selected authentication mechanism. If the authentication mechanism doesn't
+@@ doc/src/sgml/protocol.sgml: SELCT 1/0;<!-- this typo is intentional -->
+ <title>SCRAM-SHA-256 Authentication</title>
+
+ <para>
+- The implemented SASL mechanisms at the moment
+- are <literal>SCRAM-SHA-256</literal> and its variant with channel
+- binding <literal>SCRAM-SHA-256-PLUS</literal>. They are described in
++ <literal>SCRAM-SHA-256</literal>, and its variant with channel
++ binding <literal>SCRAM-SHA-256-PLUS</literal>, are password-based
++ authentication mechanisms. They are described in
+ detail in <ulink url="https://datatracker.ietf.org/doc/html/rfc7677">RFC 7677</ulink>
+ and <ulink url="https://datatracker.ietf.org/doc/html/rfc5802">RFC 5802</ulink>.
+ </para>
+@@ doc/src/sgml/protocol.sgml: SELCT 1/0;<!-- this typo is intentional -->
+ </step>
+ </procedure>
+ </sect2>
++
++ <sect2 id="sasl-oauthbearer">
++ <title>OAUTHBEARER Authentication</title>
++
++ <para>
++ <literal>OAUTHBEARER</literal> is a token-based mechanism for federated
++ authentication. It is described in detail in
++ <ulink url="https://datatracker.ietf.org/doc/html/rfc7628">RFC 7628</ulink>.
++ </para>
++
++ <para>
++ A typical exchange differs depending on whether or not the client already
++ has a bearer token cached for the current user. If it does not, the exchange
++ will take place over two connections: the first "discovery" connection to
++ obtain OAuth metadata from the server, and the second connection to send
++ the token after the client has obtained it. (libpq does not currently
++ implement a caching method as part of its builtin flow, so it uses the
++ two-connection exchange.)
++ </para>
++
++ <para>
++ This mechanism is client-initiated, like SCRAM. The client initial response
++ consists of the standard "GS2" header used by SCRAM, followed by a list of
++ <literal>key=value</literal> pairs. The only key currently supported by
++ the server is <literal>auth</literal>, which contains the bearer token.
++ <literal>OAUTHBEARER</literal> additionally specifies three optional
++ components of the client initial response (the <literal>authzid</literal> of
++ the GS2 header, and the <structfield>host</structfield> and
++ <structfield>port</structfield> keys) which are currently ignored by the
++ server.
++ </para>
++
++ <para>
++ <literal>OAUTHBEARER</literal> does not support channel binding, and there
++ is no "OAUTHBEARER-PLUS" mechanism. This mechanism does not make use of
++ server data during a successful authentication, so the
++ AuthenticationSASLFinal message is not used in the exchange.
++ </para>
++
++ <procedure>
++ <title>Example</title>
++ <step>
++ <para>
++ During the first exchange, the server sends an AuthenticationSASL message
++ with the <literal>OAUTHBEARER</literal> mechanism advertised.
++ </para>
++ </step>
++
++ <step>
++ <para>
++ The client responds by sending a SASLInitialResponse message which
++ indicates the <literal>OAUTHBEARER</literal> mechanism. Assuming the
++ client does not already have a valid bearer token for the current user,
++ the <structfield>auth</structfield> field is empty, indicating a discovery
++ connection.
++ </para>
++ </step>
++
++ <step>
++ <para>
++ Server sends an AuthenticationSASLContinue message containing an error
++ <literal>status</literal> alongside a well-known URI and scopes that the
++ client should use to conduct an OAuth flow.
++ </para>
++ </step>
++
++ <step>
++ <para>
++ Client sends a SASLResponse message containing the empty set (a single
++ <literal>0x01</literal> byte) to finish its half of the discovery
++ exchange.
++ </para>
++ </step>
++
++ <step>
++ <para>
++ Server sends an ErrorMessage to fail the first exchange.
++ </para>
++ <para>
++ At this point, the client conducts one of many possible OAuth flows to
++ obtain a bearer token, using any metadata that it has been configured with
++ in addition to that provided by the server. (This description is left
++ deliberately vague; <literal>OAUTHBEARER</literal> does not specify or
++ mandate any particular method for obtaining a token.)
++ </para>
++ <para>
++ Once it has a token, the client reconnects to the server for the final
++ exchange:
++ </para>
++ </step>
++
++ <step>
++ <para>
++ The server once again sends an AuthenticationSASL message with the
++ <literal>OAUTHBEARER</literal> mechanism advertised.
++ </para>
++ </step>
++
++ <step>
++ <para>
++ The client responds by sending a SASLInitialResponse message, but this
++ time the <structfield>auth</structfield> field in the message contains the
++ bearer token that was obtained during the client flow.
++ </para>
++ </step>
++
++ <step>
++ <para>
++ The server validates the token according to the instructions of the
++ token provider. If the client is authorized to connect, it sends an
++ AuthenticationOk message to end the SASL exchange.
++ </para>
++ </step>
++ </procedure>
++ </sect2>
+ </sect1>
+
+ <sect1 id="protocol-replication">
+
## doc/src/sgml/regress.sgml ##
@@ doc/src/sgml/regress.sgml: make check-world PG_TEST_EXTRA='kerberos ldap ssl load_balance libpq_encryption'
</para>
@@ meson.build: endif
+ libcurl = dependency('libcurl', version: '>= 7.61.0', required: libcurlopt)
+ if libcurl.found()
+ cdata.set('USE_LIBCURL', 1)
++
++ # Check to see whether the current platform supports threadsafe Curl
++ # initialization.
++ libcurl_threadsafe_init = false
++
++ if not meson.is_cross_build()
++ r = cc.run('''
++ #include <curl/curl.h>
++
++ int main(void)
++ {
++ curl_version_info_data *info;
++
++ if (curl_global_init(CURL_GLOBAL_ALL))
++ return -1;
++
++ info = curl_version_info(CURLVERSION_NOW);
++ #ifdef CURL_VERSION_THREADSAFE
++ if (info->features & CURL_VERSION_THREADSAFE)
++ return 0;
++ #endif
++
++ return 1;
++ }''',
++ name: 'test for curl_global_init thread safety',
++ dependencies: libcurl,
++ )
++
++ assert(r.compiled())
++ if r.returncode() == 0
++ libcurl_threadsafe_init = true
++ message('curl_global_init is threadsafe')
++ elif r.returncode() == 1
++ message('curl_global_init is not threadsafe')
++ else
++ message('curl_global_init failed; assuming not threadsafe')
++ endif
++ endif
++
++ if libcurl_threadsafe_init
++ cdata.set('HAVE_THREADSAFE_CURL_GLOBAL_INIT', 1)
++ endif
+ endif
++
+else
+ libcurl = not_found_dep
+endif
@@ src/backend/utils/misc/postgresql.conf.sample
#ssl_passphrase_command_supports_reload = off
+# OAuth
-+#oauth_validator_libraries = ''
++#oauth_validator_libraries = '' # comma-separated list of trusted validator modules
+
#------------------------------------------------------------------------------
@@ src/include/pg_config.h.in
/* Define to 1 if you have the `ldap' library (-lldap). */
#undef HAVE_LIBLDAP
+@@
+ /* Define to 1 if you have the <termios.h> header file. */
+ #undef HAVE_TERMIOS_H
+
++/* Define to 1 if curl_global_init() is guaranteed to be threadsafe. */
++#undef HAVE_THREADSAFE_CURL_GLOBAL_INIT
++
+ /* Define to 1 if your compiler understands `typeof' or something similar. */
+ #undef HAVE_TYPEOF
+
@@
/* Define to 1 to build with LDAP support. (--with-ldap) */
#undef USE_LDAP
@@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
+ return true;
+}
+
++#define HTTPS_SCHEME "https://"
+#define OAUTH_GRANT_TYPE_DEVICE_CODE "urn:ietf:params:oauth:grant-type:device_code"
+
+/*
+ * Ensure that the provider supports the Device Authorization flow (i.e. it
-+ * accepts the device_code grant type and provides an authorization endpoint).
++ * provides an authorization endpoint, and both the token and authorization
++ * endpoint URLs seem reasonable).
+ */
+static bool
+check_for_device_flow(struct async_ctx *actx)
+{
+ const struct provider *provider = &actx->provider;
-+ const struct curl_slist *grant;
-+ bool device_grant_found = false;
+
+ Assert(provider->issuer); /* ensured by parse_provider() */
-+
-+ /*------
-+ * First, sanity checks for discovery contents that are OPTIONAL in the
-+ * spec but required for our flow:
-+ * - the issuer must support the device_code grant
-+ * - the issuer must have actually given us a
-+ * device_authorization_endpoint
-+ */
-+
-+ grant = provider->grant_types_supported;
-+ while (grant)
-+ {
-+ if (strcmp(grant->data, OAUTH_GRANT_TYPE_DEVICE_CODE) == 0)
-+ {
-+ device_grant_found = true;
-+ break;
-+ }
-+
-+ grant = grant->next;
-+ }
-+
-+ if (!device_grant_found)
-+ {
-+ actx_error(actx, "issuer \"%s\" does not support device code grants",
-+ provider->issuer);
-+ return false;
-+ }
++ Assert(provider->token_endpoint); /* ensured by parse_provider() */
+
+ if (!provider->device_authorization_endpoint)
+ {
@@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
+ return false;
+ }
+
-+ /* TODO: check that the endpoint uses HTTPS */
++ /*
++ * The original implementation checked that OAUTH_GRANT_TYPE_DEVICE_CODE
++ * was present in the discovery document's grant_types_supported list. MS
++ * Entra does not advertise this grant type, though, and since it doesn't
++ * make sense to stand up a device_authorization_endpoint without also
++ * accepting device codes at the token_endpoint, that's the only thing we
++ * currently require.
++ */
++
++ /*
++ * Although libcurl will fail later if the URL contains an unsupported
++ * scheme, that error message is going to be a bit opaque. This is a
++ * decent time to bail out if we're not using HTTPS for the endpoints
++ * we'll use for the flow.
++ */
++ if (!actx->debugging)
++ {
++ if (pg_strncasecmp(provider->device_authorization_endpoint,
++ HTTPS_SCHEME, strlen(HTTPS_SCHEME)) != 0)
++ {
++ actx_error(actx,
++ "device authorization endpoint \"%s\" must use HTTPS",
++ provider->device_authorization_endpoint);
++ return false;
++ }
++
++ if (pg_strncasecmp(provider->token_endpoint,
++ HTTPS_SCHEME, strlen(HTTPS_SCHEME)) != 0)
++ {
++ actx_error(actx,
++ "token endpoint \"%s\" must use HTTPS",
++ provider->token_endpoint);
++ return false;
++ }
++ }
+
+ return true;
+}
@@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
+ return true;
+}
+
++/*
++ * Calls curl_global_init() in a thread-safe way.
++ *
++ * libcurl has stringent requirements for the thread context in which you call
++ * curl_global_init(), because it's going to try initializing a bunch of other
++ * libraries (OpenSSL, Winsock, etc). Recent versions of libcurl have improved
++ * the thread-safety situation, but there's a chicken-and-egg problem at
++ * runtime: you can't check the thread safety until you've initialized libcurl,
++ * which you can't do from within a thread unless you know it's thread-safe...
++ *
++ * Returns true if initialization was successful. Successful or not, this
++ * function will not try to reinitialize Curl on successive calls.
++ */
++static bool
++initialize_curl(PGconn *conn)
++{
++ /*
++ * Don't let the compiler play tricks with this variable. In the
++ * HAVE_THREADSAFE_CURL_GLOBAL_INIT case, we don't care if two threads
++ * enter simultaneously, but we do care if this gets set transiently to
++ * PG_BOOL_YES/NO in cases where that's not the final answer.
++ */
++ static volatile PGTernaryBool init_successful = PG_BOOL_UNKNOWN;
++#if HAVE_THREADSAFE_CURL_GLOBAL_INIT
++ curl_version_info_data *info;
++#endif
++
++#if !HAVE_THREADSAFE_CURL_GLOBAL_INIT
++
++ /*
++ * Lock around the whole function. If a libpq client performs its own work
++ * with libcurl, it must either ensure that Curl is initialized safely
++ * before calling us (in which case our call will be a no-op), or else it
++ * must guard its own calls to curl_global_init() with a registered
++ * threadlock handler. See PQregisterThreadLock().
++ */
++ pglock_thread();
++#endif
++
++ /*
++ * Skip initialization if we've already done it. (Curl tracks the number
++ * of calls; there's no point in incrementing the counter every time we
++ * connect.)
++ */
++ if (init_successful == PG_BOOL_YES)
++ goto done;
++ else if (init_successful == PG_BOOL_NO)
++ {
++ libpq_append_conn_error(conn,
++ "curl_global_init previously failed during OAuth setup");
++ goto done;
++ }
++
++ /*
++ * We know we've already initialized Winsock by this point (see
++ * pqMakeEmptyPGconn()), so we should be able to safely skip that bit. But
++ * we have to tell libcurl to initialize everything else, because other
++ * pieces of our client executable may already be using libcurl for their
++ * own purposes. If we initialize libcurl with only a subset of its
++ * features, we could break those other clients nondeterministically, and
++ * that would probably be a nightmare to debug.
++ *
++ * If some other part of the program has already called this, it's a
++ * no-op.
++ */
++ if (curl_global_init(CURL_GLOBAL_ALL & ~CURL_GLOBAL_WIN32) != CURLE_OK)
++ {
++ libpq_append_conn_error(conn,
++ "curl_global_init failed during OAuth setup");
++ init_successful = PG_BOOL_NO;
++ goto done;
++ }
++
++#if HAVE_THREADSAFE_CURL_GLOBAL_INIT
++
++ /*
++ * If we determined at configure time that the Curl installation is
++ * threadsafe, our job here is much easier. We simply initialize above
++ * without any locking (concurrent or duplicated calls are fine in that
++ * situation), then double-check to make sure the runtime setting agrees,
++ * to try to catch silent downgrades.
++ */
++ info = curl_version_info(CURLVERSION_NOW);
++ if (!(info->features & CURL_VERSION_THREADSAFE))
++ {
++ /*
++ * In a downgrade situation, the damage is already done. Curl global
++ * state may be corrupted. Be noisy.
++ */
++ libpq_append_conn_error(conn, "libcurl is no longer threadsafe\n"
++ "\tCurl initialization was reported threadsafe when libpq\n"
++ "\twas compiled, but the currently installed version of\n"
++ "\tlibcurl reports that it is not. Recompile libpq against\n"
++ "\tthe installed version of libcurl.");
++ init_successful = PG_BOOL_NO;
++ goto done;
++ }
++#endif
++
++ init_successful = PG_BOOL_YES;
++
++done:
++#if !HAVE_THREADSAFE_CURL_GLOBAL_INIT
++ pgunlock_thread();
++#endif
++ return (init_successful == PG_BOOL_YES);
++}
+
+/*
+ * The core nonblocking libcurl implementation. This will be called several
@@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
+ fe_oauth_state *state = conn->sasl_state;
+ struct async_ctx *actx;
+
-+ /*
-+ * XXX This is not safe. libcurl has stringent requirements for the thread
-+ * context in which you call curl_global_init(), because it's going to try
-+ * initializing a bunch of other libraries (OpenSSL, Winsock...). And we
-+ * probably need to consider both the TLS backend libcurl is compiled
-+ * against and what the user has asked us to do via PQinit[Open]SSL.
-+ *
-+ * Recent versions of libcurl have improved the thread-safety situation,
-+ * but you apparently can't check at compile time whether the
-+ * implementation is thread-safe, and there's a chicken-and-egg problem
-+ * where you can't check the thread safety until you've initialized
-+ * libcurl, which you can't do before you've made sure it's thread-safe...
-+ *
-+ * We know we've already initialized Winsock by this point, so we should
-+ * be able to safely skip that bit. But we have to tell libcurl to
-+ * initialize everything else, because other pieces of our client
-+ * executable may already be using libcurl for their own purposes. If we
-+ * initialize libcurl first, with only a subset of its features, we could
-+ * break those other clients nondeterministically, and that would probably
-+ * be a nightmare to debug.
-+ */
-+ curl_global_init(CURL_GLOBAL_ALL
-+ & ~CURL_GLOBAL_WIN32); /* we already initialized Winsock */
++ if (!initialize_curl(conn))
++ return PGRES_POLLING_FAILED;
+
+ if (!state->async_ctx)
+ {
@@ src/interfaces/libpq/fe-connect.c: static const internalPQconninfoOption PQconni
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
+@@ src/interfaces/libpq/fe-connect.c: static const PQEnvironmentOption EnvironmentOptions[] =
+ static const pg_fe_sasl_mech *supported_sasl_mechs[] =
+ {
+ &pg_scram_mech,
++ &pg_oauth_mech,
+ };
+ #define SASL_MECHANISM_COUNT lengthof(supported_sasl_mechs)
+
@@ src/interfaces/libpq/fe-connect.c: pqDropServerData(PGconn *conn)
conn->write_failed = false;
free(conn->write_err_msg);
@@ src/interfaces/libpq/fe-connect.c: static inline void
* linked list, conn->allowed_sasl_mechs is an array of static length. We
* rely on the compile-time assertion here to keep us honest.
*
-@@ src/interfaces/libpq/fe-connect.c: fill_allowed_sasl_mechs(PGconn *conn)
- * - handle the new mechanism name in the require_auth portion of
- * pqConnectOptions2(), below.
- */
-- StaticAssertDecl(lengthof(conn->allowed_sasl_mechs) == 1,
-+ StaticAssertDecl(lengthof(conn->allowed_sasl_mechs) == 2,
- "fill_allowed_sasl_mechs() must be updated when resizing conn->allowed_sasl_mechs[]");
-
- conn->allowed_sasl_mechs[0] = &pg_scram_mech;
-+ conn->allowed_sasl_mechs[1] = &pg_oauth_mech;
- }
-
- /*
@@ src/interfaces/libpq/fe-connect.c: pqConnectOptions2(PGconn *conn)
{
mech = &pg_scram_mech;
5: 66ef3b4b687 = 5: 035a3832b40 XXX fix libcurl link error
6: 4df1bc59638 ! 6: 5e360725bf9 DO NOT MERGE: Add pytest suite for OAuth
@@ src/test/python/client/test_oauth.py (new)
+ {
+ "issuer": "{issuer}",
+ "token_endpoint": "https://256.256.256.256/token",
-+ "device_authorization_endpoint": "https://256.256.256.256/dev",
-+ },
-+ ),
-+ r'cannot run OAuth device authorization: issuer "https://.*" does not support device code grants',
-+ id="missing device code grants",
-+ ),
-+ pytest.param(
-+ (
-+ 200,
-+ {
-+ "issuer": "{issuer}",
-+ "token_endpoint": "https://256.256.256.256/token",
+ "grant_types_supported": [
+ "urn:ietf:params:oauth:grant-type:device_code"
+ ],