diff options
Diffstat (limited to 'src/exchange/taler-exchange-httpd_kyc-proof.c')
| -rw-r--r-- | src/exchange/taler-exchange-httpd_kyc-proof.c | 481 | 
1 files changed, 443 insertions, 38 deletions
diff --git a/src/exchange/taler-exchange-httpd_kyc-proof.c b/src/exchange/taler-exchange-httpd_kyc-proof.c index be7fc50f..842e5dfd 100644 --- a/src/exchange/taler-exchange-httpd_kyc-proof.c +++ b/src/exchange/taler-exchange-httpd_kyc-proof.c @@ -36,10 +36,100 @@  struct KycProofContext  { +  /** +   * Kept in a DLL while suspended. +   */ +  struct KycProofContext *next; + +  /** +   * Kept in a DLL while suspended. +   */ +  struct KycProofContext *prev; + +  /** +   * Details about the connection we are processing. +   */ +  struct TEH_RequestContext *rc; + +  /** +   * Handle for the OAuth 2.0 CURL request. +   */ +  struct GNUNET_CURL_Job *job; + +  /** +   * OAuth 2.0 authorization code. +   */ +  const char *authorization_code; + +  /** +   * OAuth 2.0 token URL we are using for the +   * request. +   */ +  char *token_url; + +  /** +   * Body of the POST request. +   */ +  char *post_body; + +  /** +   * Payment target this is about. +   */ +  unsigned long long payment_target_uuid; + +  /** +   * HTTP response to return. +   */ +  struct MHD_Response *response; + +  /** +   * HTTP response code to return. +   */ +  unsigned int response_code; + +  /** +   * #GNUNET_YES if we are suspended, +   * #GNUNET_NO if not. +   * #GNUNET_SYSERR if we had some error. +   */ +  enum GNUNET_GenericReturnValue suspended; +  };  /** + * Contexts are kept in a DLL while suspended. + */ +static struct KycProofContext *kpc_head; + +/** + * Contexts are kept in a DLL while suspended. + */ +static struct KycProofContext *kpc_tail; + + +void +TEH_kyc_proof_cleanup (void) +{ +  struct KycProofContext *kpc; + +  while (NULL != (kpc = kpc_head)) +  { +    if (NULL != kpc->job) +    { +      GNUNET_CURL_job_cancel (kpc->job); +      kpc->job = NULL; +    } +    GNUNET_CONTAINER_DLL_remove (kpc_head, +                                 kpc_tail, +                                 kpc); +    kpc->suspended = GNUNET_NO; +    MHD_resume_connection (kpc->rc->connection); +  } +} + + +/**   * Function implementing database transaction to check proof's KYC status.   * Runs the transaction logic; IF it returns a non-error code, the transaction   * logic MUST NOT queue a MHD response.  IF it returns an hard error, the @@ -54,14 +144,185 @@ struct KycProofContext   * @return transaction status   */  static enum GNUNET_DB_QueryStatus -proof_kyc_check (void *cls, -                 struct MHD_Connection *connection, -                 MHD_RESULT *mhd_ret) +persist_kyc_ok (void *cls, +                struct MHD_Connection *connection, +                MHD_RESULT *mhd_ret)  {    struct KycProofContext *kpc = cls; -  (void) kpc; // FIXME: do work here! -  return -2; +  return TEH_plugin->set_kyc_ok (TEH_plugin->cls, +                                 kpc->payment_target_uuid); +} + + +/** + * After we are done with the CURL interaction we + * need to update our database state. + * + * @param cls our `struct KycProofContext` + * @param response_code HTTP response code from server, 0 on hard error + * @param response in JSON, NULL if response was not in JSON format + */ +static void +handle_curl_login_finished (void *cls, +                            long response_code, +                            const void *response) +{ +  struct KycProofContext *kpc = cls; +  const json_t *j = response; + +  kpc->job = NULL; +  switch (response_code) +  { +  case MHD_HTTP_OK: +    { +      const char *access_token; +      const char *token_type; +      uint64_t expires_in_s; +      const char *refresh_token; +      struct GNUNET_JSON_Specification spec[] = { +        GNUNET_JSON_spec_string ("access_token", +                                 &access_token), +        GNUNET_JSON_spec_string ("token_type", +                                 &token_type), +        GNUNET_JSON_spec_uint64 ("expires_in", +                                 &expires_in_s), +        GNUNET_JSON_spec_string ("refresh_token", +                                 &refresh_token), +        GNUNET_JSON_spec_end () +      }; + +      { +        enum GNUNET_GenericReturnValue res; +        const char *emsg; +        unsigned int line; + +        res = GNUNET_JSON_parse (j, +                                 spec, +                                 &emsg, +                                 &line); +        if (GNUNET_OK != res) +        { +          GNUNET_break_op (0); +          kpc->response +            = TALER_MHD_make_error ( +                TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE, +                "Unexpected response from KYC gateway"); +          kpc->response_code +            = MHD_HTTP_BAD_GATEWAY; +          MHD_resume_connection (kpc->rc->connection); +          TALER_MHD_daemon_trigger (); +          return; +        } +      } +      if (0 != strcasecmp (token_type, +                           "bearer")) +      { +        GNUNET_break_op (0); +        kpc->response +          = TALER_MHD_make_error ( +              TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE, +              "Unexpected token type in response from KYC gateway"); +        kpc->response_code +          = MHD_HTTP_BAD_GATEWAY; +        MHD_resume_connection (kpc->rc->connection); +        TALER_MHD_daemon_trigger (); +        return; +      } + +      /* TODO: Here we might want to keep something to persist in the DB, and +         possibly use the access_token to download information we should +         persist; then continue! */ + +      MHD_resume_connection (kpc->rc->connection); +      TALER_MHD_daemon_trigger (); +      return; +    } +  default: +    { +      const char *msg; +      const char *desc; +      struct GNUNET_JSON_Specification spec[] = { +        GNUNET_JSON_spec_string ("error", +                                 &msg), +        GNUNET_JSON_spec_string ("error_description", +                                 &desc), +        GNUNET_JSON_spec_end () +      }; + +      { +        enum GNUNET_GenericReturnValue res; +        const char *emsg; +        unsigned int line; + +        res = GNUNET_JSON_parse (j, +                                 spec, +                                 &emsg, +                                 &line); +        if (GNUNET_OK != res) +        { +          GNUNET_break_op (0); +          kpc->response +            = TALER_MHD_make_error ( +                TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE, +                "Unexpected response from KYC gateway"); +          kpc->response_code +            = MHD_HTTP_BAD_GATEWAY; +          MHD_resume_connection (kpc->rc->connection); +          TALER_MHD_daemon_trigger (); +          return; +        } +      } +      /* case TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_AUTHORZATION_FAILED, +         we MAY want to in the future look at the requested content type +         and possibly respond in JSON if indicated. */ +      { +        char *reply; + +        GNUNET_asprintf (&reply, +                         "<html><head><title>%s</title></head><body><h1>%s</h1><p>%s</p></body></html>", +                         msg, +                         msg, +                         desc); +        kpc->response +          = MHD_create_response_from_buffer (strlen (reply), +                                             reply, +                                             MHD_RESPMEM_MUST_COPY); +        GNUNET_assert (NULL != kpc->response); +        GNUNET_free (reply); +      } +      kpc->response_code = MHD_HTTP_FORBIDDEN; +      MHD_resume_connection (kpc->rc->connection); +      TALER_MHD_daemon_trigger (); +    } +    break; +  } +} + + +/** + * Function called to clean up a context. + * + * @param rc request context + */ +static void +clean_kpc (struct TEH_RequestContext *rc) +{ +  struct KycProofContext *kpc = rc->rh_ctx; + +  if (NULL != kpc->job) +  { +    GNUNET_CURL_job_cancel (kpc->job); +    kpc->job = NULL; +  } +  if (NULL != kpc->response) +  { +    MHD_destroy_response (kpc->response); +    kpc->response = NULL; +  } +  GNUNET_free (kpc->post_body); +  GNUNET_free (kpc->token_url); +  GNUNET_free (kpc);  } @@ -70,44 +331,188 @@ TEH_handler_kyc_proof (    struct TEH_RequestContext *rc,    const char *const args[])  { -  struct KycProofContext kpc; -  MHD_RESULT res; -  enum GNUNET_GenericReturnValue ret; -  unsigned long long payment_target_uuid; -  char dummy; +  struct KycProofContext *kpc = rc->rh_ctx; + +  if (NULL == kpc) +  { /* first time */ +    char dummy; + +    kpc = GNUNET_new (struct KycProofContext); +    kpc->rc = rc; +    rc->rh_ctx = kpc; +    rc->rh_cleaner = &clean_kpc; + +    if (1 != +        sscanf (args[0], +                "%llu%c", +                &kpc->payment_target_uuid, +                &dummy)) +    { +      GNUNET_break_op (0); +      return TALER_MHD_reply_with_error (rc->connection, +                                         MHD_HTTP_BAD_REQUEST, +                                         TALER_EC_GENERIC_PARAMETER_MALFORMED, +                                         "payment_target_uuid"); +    } +    kpc->authorization_code +      = MHD_lookup_connection_value (rc->connection, +                                     MHD_GET_ARGUMENT_KIND, +                                     "code"); +    if (NULL == kpc->authorization_code) +    { +      GNUNET_break_op (0); +      return TALER_MHD_reply_with_error (rc->connection, +                                         MHD_HTTP_BAD_REQUEST, +                                         TALER_EC_GENERIC_PARAMETER_MALFORMED, +                                         "code"); +    } +    if (TEH_KYC_NONE == TEH_kyc_config.mode) +      return TALER_MHD_reply_static ( +        rc->connection, +        MHD_HTTP_NO_CONTENT, +        NULL, +        NULL, +        0); + +    { +      CURL *eh; + +      eh = curl_easy_init (); +      if (NULL == eh) +      { +        GNUNET_break (0); +        return TALER_MHD_reply_with_error (rc->connection, +                                           MHD_HTTP_INTERNAL_SERVER_ERROR, +                                           TALER_EC_GENERIC_ALLOCATION_FAILURE, +                                           "curl_easy_init"); +      } +      GNUNET_asprintf (&kpc->token_url, +                       "%s/token", +                       TEH_kyc_config.details.oauth2.url); +      GNUNET_assert (CURLE_OK == +                     curl_easy_setopt (eh, +                                       CURLOPT_URL, +                                       kpc->token_url)); +      GNUNET_assert (CURLE_OK == +                     curl_easy_setopt (eh, +                                       CURLOPT_POST, +                                       1)); +      { +        char *client_id; +        char *redirect_uri; +        char *client_secret; +        char *authorization_code; + +        client_id = curl_easy_escape (eh, +                                      TEH_kyc_config.details.oauth2.client_id, +                                      0); +        GNUNET_assert (NULL != client_id); +        { +          char *request_uri; + +          GNUNET_asprintf (&request_uri, +                           "%s/login?client_id=%s", +                           TEH_kyc_config.details.oauth2.url, +                           TEH_kyc_config.details.oauth2.client_id); +          redirect_uri = curl_easy_escape (eh, +                                           request_uri, +                                           0); +          GNUNET_free (request_uri); +        } +        GNUNET_assert (NULL != redirect_uri); +        client_secret = curl_easy_escape (eh, +                                          TEH_kyc_config.details.oauth2. +                                          client_secret, +                                          0); +        GNUNET_assert (NULL != client_secret); +        authorization_code = curl_easy_escape (eh, +                                               kpc->authorization_code, +                                               0); +        GNUNET_assert (NULL != authorization_code); +        GNUNET_asprintf (&kpc->post_body, +                         "client_id=%s&redirect_uri=%s&client_secret=%s&code=%s&grant_type=authorization_code", +                         client_id, +                         redirect_uri, +                         client_secret, +                         authorization_code); +        curl_free (authorization_code); +        curl_free (client_secret); +        curl_free (redirect_uri); +        curl_free (client_id); +      } +      GNUNET_assert (CURLE_OK == +                     curl_easy_setopt (eh, +                                       CURLOPT_POSTFIELDS, +                                       kpc->post_body)); +      GNUNET_assert (CURLE_OK == +                     curl_easy_setopt (eh, +                                       CURLOPT_FOLLOWLOCATION, +                                       1L)); +      /* limit MAXREDIRS to 5 as a simple security measure against +         a potential infinite loop caused by a malicious target */ +      GNUNET_assert (CURLE_OK == +                     curl_easy_setopt (eh, +                                       CURLOPT_MAXREDIRS, +                                       5L)); -  if (1 != -      sscanf (args[0], -              "%llu%c", -              &payment_target_uuid, -              &dummy)) +      kpc->job = GNUNET_CURL_job_add (TEH_curl_ctx, +                                      eh, +                                      &handle_curl_login_finished, +                                      kpc); +      kpc->suspended = GNUNET_YES; +      GNUNET_CONTAINER_DLL_insert (kpc_head, +                                   kpc_tail, +                                   kpc); +      MHD_suspend_connection (rc->connection); +      return MHD_YES; +    } +  } + +  if (NULL != kpc->response)    { -    GNUNET_break_op (0); -    return TALER_MHD_reply_with_error (rc->connection, -                                       MHD_HTTP_BAD_REQUEST, -                                       TALER_EC_GENERIC_PARAMETER_MALFORMED, -                                       "payment_target_uuid"); +    /* handle _failed_ resumed cases */ +    return MHD_queue_response (rc->connection, +                               kpc->response_code, +                               kpc->response);    } -  if (1 || (TEH_KYC_NONE == TEH_kyc_config.mode)) -    return TALER_MHD_reply_static ( -      rc->connection, -      MHD_HTTP_NO_CONTENT, -      NULL, -      NULL, -      0); -  ret = TEH_DB_run_transaction (rc->connection, -                                "check proof kyc", -                                &res, -                                &proof_kyc_check, -                                &kpc); -  if (GNUNET_SYSERR == ret) +  /* _successfully_ resumed case */ +  { +    MHD_RESULT res; +    enum GNUNET_GenericReturnValue ret; + +    ret = TEH_DB_run_transaction (kpc->rc->connection, +                                  "check proof kyc", +                                  &res, +                                  &persist_kyc_ok, +                                  kpc); +    if (GNUNET_SYSERR == ret) +      return res; +  } + +  { +    struct MHD_Response *response; +    MHD_RESULT res; + +    response = MHD_create_response_from_buffer (0, +                                                "", +                                                MHD_RESPMEM_PERSISTENT); +    if (NULL == response) +    { +      GNUNET_break (0); +      return MHD_NO; +    } +    GNUNET_break (MHD_YES == +                  MHD_add_response_header ( +                    response, +                    MHD_HTTP_HEADER_LOCATION, +                    TEH_kyc_config.details.oauth2.post_kyc_redirect_url)); +    res = MHD_queue_response (rc->connection, +                              MHD_HTTP_SEE_OTHER, +                              response); +    MHD_destroy_response (response);      return res; -  return TALER_MHD_REPLY_JSON_PACK ( -    rc->connection, -    MHD_HTTP_OK, -    GNUNET_JSON_pack_uint64 ("42", -                             42)); +  }  }  | 
