LCOV - code coverage report
Current view: top level - src/test/modules/oauth_validator - oauth_hook_client.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 0 106 0.0 %
Date: 2025-02-22 15:15:04 Functions: 0 5 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * oauth_hook_client.c
       4             :  *      Test driver for t/002_client.pl, which verifies OAuth hook
       5             :  *      functionality in libpq.
       6             :  *
       7             :  * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
       8             :  * Portions Copyright (c) 1994, Regents of the University of California
       9             :  *
      10             :  *
      11             :  * IDENTIFICATION
      12             :  *      src/test/modules/oauth_validator/oauth_hook_client.c
      13             :  *
      14             :  *-------------------------------------------------------------------------
      15             :  */
      16             : 
      17             : #include "postgres_fe.h"
      18             : 
      19             : #include <sys/socket.h>
      20             : 
      21             : #include "getopt_long.h"
      22             : #include "libpq-fe.h"
      23             : 
      24             : static int  handle_auth_data(PGauthData type, PGconn *conn, void *data);
      25             : static PostgresPollingStatusType async_cb(PGconn *conn,
      26             :                                           PGoauthBearerRequest *req,
      27             :                                           pgsocket *altsock);
      28             : static PostgresPollingStatusType misbehave_cb(PGconn *conn,
      29             :                                               PGoauthBearerRequest *req,
      30             :                                               pgsocket *altsock);
      31             : 
      32             : static void
      33           0 : usage(char *argv[])
      34             : {
      35           0 :     printf("usage: %s [flags] CONNINFO\n\n", argv[0]);
      36             : 
      37           0 :     printf("recognized flags:\n");
      38           0 :     printf(" -h, --help                show this message\n");
      39           0 :     printf(" --expected-scope SCOPE    fail if received scopes do not match SCOPE\n");
      40           0 :     printf(" --expected-uri URI        fail if received configuration link does not match URI\n");
      41           0 :     printf(" --misbehave=MODE      have the hook fail required postconditions\n"
      42             :            "                       (MODEs: no-hook, fail-async, no-token, no-socket)\n");
      43           0 :     printf(" --no-hook             don't install OAuth hooks\n");
      44           0 :     printf(" --hang-forever            don't ever return a token (combine with connect_timeout)\n");
      45           0 :     printf(" --token TOKEN         use the provided TOKEN value\n");
      46           0 :     printf(" --stress-async            busy-loop on PQconnectPoll rather than polling\n");
      47           0 : }
      48             : 
      49             : /* --options */
      50             : static bool no_hook = false;
      51             : static bool hang_forever = false;
      52             : static bool stress_async = false;
      53             : static const char *expected_uri = NULL;
      54             : static const char *expected_scope = NULL;
      55             : static const char *misbehave_mode = NULL;
      56             : static char *token = NULL;
      57             : 
      58             : int
      59           0 : main(int argc, char *argv[])
      60             : {
      61             :     static const struct option long_options[] = {
      62             :         {"help", no_argument, NULL, 'h'},
      63             : 
      64             :         {"expected-scope", required_argument, NULL, 1000},
      65             :         {"expected-uri", required_argument, NULL, 1001},
      66             :         {"no-hook", no_argument, NULL, 1002},
      67             :         {"token", required_argument, NULL, 1003},
      68             :         {"hang-forever", no_argument, NULL, 1004},
      69             :         {"misbehave", required_argument, NULL, 1005},
      70             :         {"stress-async", no_argument, NULL, 1006},
      71             :         {0}
      72             :     };
      73             : 
      74             :     const char *conninfo;
      75             :     PGconn     *conn;
      76             :     int         c;
      77             : 
      78           0 :     while ((c = getopt_long(argc, argv, "h", long_options, NULL)) != -1)
      79             :     {
      80           0 :         switch (c)
      81             :         {
      82           0 :             case 'h':
      83           0 :                 usage(argv);
      84           0 :                 return 0;
      85             : 
      86           0 :             case 1000:          /* --expected-scope */
      87           0 :                 expected_scope = optarg;
      88           0 :                 break;
      89             : 
      90           0 :             case 1001:          /* --expected-uri */
      91           0 :                 expected_uri = optarg;
      92           0 :                 break;
      93             : 
      94           0 :             case 1002:          /* --no-hook */
      95           0 :                 no_hook = true;
      96           0 :                 break;
      97             : 
      98           0 :             case 1003:          /* --token */
      99           0 :                 token = optarg;
     100           0 :                 break;
     101             : 
     102           0 :             case 1004:          /* --hang-forever */
     103           0 :                 hang_forever = true;
     104           0 :                 break;
     105             : 
     106           0 :             case 1005:          /* --misbehave */
     107           0 :                 misbehave_mode = optarg;
     108           0 :                 break;
     109             : 
     110           0 :             case 1006:          /* --stress-async */
     111           0 :                 stress_async = true;
     112           0 :                 break;
     113             : 
     114           0 :             default:
     115           0 :                 usage(argv);
     116           0 :                 return 1;
     117             :         }
     118             :     }
     119             : 
     120           0 :     if (argc != optind + 1)
     121             :     {
     122           0 :         usage(argv);
     123           0 :         return 1;
     124             :     }
     125             : 
     126           0 :     conninfo = argv[optind];
     127             : 
     128             :     /* Set up our OAuth hooks. */
     129           0 :     PQsetAuthDataHook(handle_auth_data);
     130             : 
     131             :     /* Connect. (All the actual work is in the hook.) */
     132           0 :     if (stress_async)
     133             :     {
     134             :         /*
     135             :          * Perform an asynchronous connection, busy-looping on PQconnectPoll()
     136             :          * without actually waiting on socket events. This stresses code paths
     137             :          * that rely on asynchronous work to be done before continuing with
     138             :          * the next step in the flow.
     139             :          */
     140             :         PostgresPollingStatusType res;
     141             : 
     142           0 :         conn = PQconnectStart(conninfo);
     143             : 
     144             :         do
     145             :         {
     146           0 :             res = PQconnectPoll(conn);
     147           0 :         } while (res != PGRES_POLLING_FAILED && res != PGRES_POLLING_OK);
     148             :     }
     149             :     else
     150             :     {
     151             :         /* Perform a standard synchronous connection. */
     152           0 :         conn = PQconnectdb(conninfo);
     153             :     }
     154             : 
     155           0 :     if (PQstatus(conn) != CONNECTION_OK)
     156             :     {
     157           0 :         fprintf(stderr, "connection to database failed: %s\n",
     158             :                 PQerrorMessage(conn));
     159           0 :         PQfinish(conn);
     160           0 :         return 1;
     161             :     }
     162             : 
     163           0 :     printf("connection succeeded\n");
     164           0 :     PQfinish(conn);
     165           0 :     return 0;
     166             : }
     167             : 
     168             : /*
     169             :  * PQauthDataHook implementation. Replaces the default client flow by handling
     170             :  * PQAUTHDATA_OAUTH_BEARER_TOKEN.
     171             :  */
     172             : static int
     173           0 : handle_auth_data(PGauthData type, PGconn *conn, void *data)
     174             : {
     175           0 :     PGoauthBearerRequest *req = data;
     176             : 
     177           0 :     if (no_hook || (type != PQAUTHDATA_OAUTH_BEARER_TOKEN))
     178           0 :         return 0;
     179             : 
     180           0 :     if (hang_forever)
     181             :     {
     182             :         /* Start asynchronous processing. */
     183           0 :         req->async = async_cb;
     184           0 :         return 1;
     185             :     }
     186             : 
     187           0 :     if (misbehave_mode)
     188             :     {
     189           0 :         if (strcmp(misbehave_mode, "no-hook") != 0)
     190           0 :             req->async = misbehave_cb;
     191           0 :         return 1;
     192             :     }
     193             : 
     194           0 :     if (expected_uri)
     195             :     {
     196           0 :         if (!req->openid_configuration)
     197             :         {
     198           0 :             fprintf(stderr, "expected URI \"%s\", got NULL\n", expected_uri);
     199           0 :             return -1;
     200             :         }
     201             : 
     202           0 :         if (strcmp(expected_uri, req->openid_configuration) != 0)
     203             :         {
     204           0 :             fprintf(stderr, "expected URI \"%s\", got \"%s\"\n", expected_uri, req->openid_configuration);
     205           0 :             return -1;
     206             :         }
     207             :     }
     208             : 
     209           0 :     if (expected_scope)
     210             :     {
     211           0 :         if (!req->scope)
     212             :         {
     213           0 :             fprintf(stderr, "expected scope \"%s\", got NULL\n", expected_scope);
     214           0 :             return -1;
     215             :         }
     216             : 
     217           0 :         if (strcmp(expected_scope, req->scope) != 0)
     218             :         {
     219           0 :             fprintf(stderr, "expected scope \"%s\", got \"%s\"\n", expected_scope, req->scope);
     220           0 :             return -1;
     221             :         }
     222             :     }
     223             : 
     224           0 :     req->token = token;
     225           0 :     return 1;
     226             : }
     227             : 
     228             : static PostgresPollingStatusType
     229           0 : async_cb(PGconn *conn, PGoauthBearerRequest *req, pgsocket *altsock)
     230             : {
     231           0 :     if (hang_forever)
     232             :     {
     233             :         /*
     234             :          * This code tests that nothing is interfering with libpq's handling
     235             :          * of connect_timeout.
     236             :          */
     237             :         static pgsocket sock = PGINVALID_SOCKET;
     238             : 
     239           0 :         if (sock == PGINVALID_SOCKET)
     240             :         {
     241             :             /* First call. Create an unbound socket to wait on. */
     242             : #ifdef WIN32
     243             :             WSADATA     wsaData;
     244             :             int         err;
     245             : 
     246             :             err = WSAStartup(MAKEWORD(2, 2), &wsaData);
     247             :             if (err)
     248             :             {
     249             :                 perror("WSAStartup failed");
     250             :                 return PGRES_POLLING_FAILED;
     251             :             }
     252             : #endif
     253           0 :             sock = socket(AF_INET, SOCK_DGRAM, 0);
     254           0 :             if (sock == PGINVALID_SOCKET)
     255             :             {
     256           0 :                 perror("failed to create datagram socket");
     257           0 :                 return PGRES_POLLING_FAILED;
     258             :             }
     259             :         }
     260             : 
     261             :         /* Make libpq wait on the (unreadable) socket. */
     262           0 :         *altsock = sock;
     263           0 :         return PGRES_POLLING_READING;
     264             :     }
     265             : 
     266           0 :     req->token = token;
     267           0 :     return PGRES_POLLING_OK;
     268             : }
     269             : 
     270             : static PostgresPollingStatusType
     271           0 : misbehave_cb(PGconn *conn, PGoauthBearerRequest *req, pgsocket *altsock)
     272             : {
     273           0 :     if (strcmp(misbehave_mode, "fail-async") == 0)
     274             :     {
     275             :         /* Just fail "normally". */
     276           0 :         return PGRES_POLLING_FAILED;
     277             :     }
     278           0 :     else if (strcmp(misbehave_mode, "no-token") == 0)
     279             :     {
     280             :         /* Callbacks must assign req->token before returning OK. */
     281           0 :         return PGRES_POLLING_OK;
     282             :     }
     283           0 :     else if (strcmp(misbehave_mode, "no-socket") == 0)
     284             :     {
     285             :         /* Callbacks must assign *altsock before asking for polling. */
     286           0 :         return PGRES_POLLING_READING;
     287             :     }
     288             :     else
     289             :     {
     290           0 :         fprintf(stderr, "unrecognized --misbehave mode: %s\n", misbehave_mode);
     291           0 :         exit(1);
     292             :     }
     293             : }

Generated by: LCOV version 1.14