commit 82c95245dbc11d42d3b82998e8f22d4f67ec189f
parent be04cca17ce24817c40ce7227231a859a31e87d7
Author: Florian Dold <dold@taler.net>
Date: Sun, 30 Aug 2026 00:20:48 +0200
exchange AML API: report officer identity and permissions
Diffstat:
13 files changed, 670 insertions(+), 11 deletions(-)
diff --git a/src/exchange/meson.build b/src/exchange/meson.build
@@ -163,6 +163,7 @@ executable(
taler_exchange_httpd_SOURCES = [
'taler-exchange-httpd.c',
+ 'taler-exchange-httpd_get-aml-OFFICER_PUB.c',
'taler-exchange-httpd_get-aml-OFFICER_PUB-accounts.c',
'taler-exchange-httpd_get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.c',
'taler-exchange-httpd_post-aml-OFFICER_PUB-decision.c',
diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c
@@ -82,6 +82,7 @@
#include "taler-exchange-httpd_get-transfers-WTID.h"
#include <gnunet/gnunet_mhd_compat.h>
#include "exchange-database/get_exists_aml_officer.h"
+#include "taler-exchange-httpd_get-aml-OFFICER_PUB.h"
#include "exchange-database/preflight.h"
/**
@@ -639,16 +640,6 @@ handle_get_aml (struct TEH_RequestContext *rc,
TALER_EC_EXCHANGE_GENERIC_AML_OFFICER_PUB_MALFORMED,
args[0]);
}
- if (NULL == args[1])
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_GENERIC_WRONG_NUMBER_OF_SEGMENTS,
- "AML GET operations must specify an operation identifier");
- }
-
{
const char *sig_hdr;
struct TALER_AmlOfficerSignatureP officer_sig;
@@ -710,6 +701,10 @@ handle_get_aml (struct TEH_RequestContext *rc,
break;
}
}
+ if (NULL == args[1])
+ return TEH_handler_aml_officer_get (rc,
+ &officer_pub,
+ &args[1]);
for (unsigned int i = 0; NULL != h[i].op; i++)
if (0 == strcmp (h[i].op,
args[1]))
diff --git a/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB.c b/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB.c
@@ -0,0 +1,89 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2026 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+*/
+/**
+ * @file taler-exchange-httpd_get-aml-OFFICER_PUB.c
+ * @brief Return information about an authenticated AML officer
+ */
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include "exchange-database/get_aml_officer.h"
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler-exchange-httpd.h"
+#include "taler-exchange-httpd_get-aml-OFFICER_PUB.h"
+
+
+enum MHD_Result
+TEH_handler_aml_officer_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *const args[])
+{
+ struct TALER_MasterSignatureP master_sig;
+ struct GNUNET_TIME_Absolute last_change;
+ enum GNUNET_DB_QueryStatus qs;
+ char *officer_name = NULL;
+ bool is_active;
+ bool read_only;
+ enum MHD_Result ret;
+
+ if (NULL != args[0])
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ rc->url);
+ qs = TALER_EXCHANGEDB_get_aml_officer (TEH_pg,
+ officer_pub,
+ &master_sig,
+ &officer_name,
+ &is_active,
+ &read_only,
+ &last_change);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_GENERIC_AML_OFFICER_ACCESS_DENIED,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ if (! is_active)
+ {
+ GNUNET_free (officer_name);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_GENERIC_AML_OFFICER_ACCESS_DENIED,
+ NULL);
+ }
+ ret = TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_string ("officer_name",
+ officer_name),
+ GNUNET_JSON_pack_bool ("read_only",
+ read_only));
+ GNUNET_free (officer_name);
+ return ret;
+}
+
+
+/* end of taler-exchange-httpd_get-aml-OFFICER_PUB.c */
diff --git a/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB.h b/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB.h
@@ -0,0 +1,35 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2026 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+*/
+/**
+ * @file taler-exchange-httpd_get-aml-OFFICER_PUB.h
+ * @brief Handle GET /aml/$OFFICER_PUB requests
+ */
+#ifndef TALER_EXCHANGE_HTTPD_GET_AML_OFFICER_PUB_H
+#define TALER_EXCHANGE_HTTPD_GET_AML_OFFICER_PUB_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Return information about the authenticated AML officer.
+ *
+ * @param rc request context
+ * @param officer_pub public key of the AML officer
+ * @param args remaining path arguments, must be empty
+ * @return MHD result code
+ */
+enum MHD_Result
+TEH_handler_aml_officer_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *const args[]);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_get-config.h b/src/exchange/taler-exchange-httpd_get-config.h
@@ -41,7 +41,7 @@
*
* Returned via both /config and /keys endpoints.
*/
-#define EXCHANGE_PROTOCOL_VERSION "39:0:5"
+#define EXCHANGE_PROTOCOL_VERSION "40:0:6"
/**
diff --git a/src/include/taler/exchange/get-aml-OFFICER_PUB.h b/src/include/taler/exchange/get-aml-OFFICER_PUB.h
@@ -0,0 +1,118 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2026 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+*/
+/**
+ * @file include/taler/exchange/get-aml-OFFICER_PUB.h
+ * @brief C interface for GET /aml/$OFFICER_PUB
+ */
+#ifndef _TALER_EXCHANGE__GET_AML_OFFICER_PUB_H
+#define _TALER_EXCHANGE__GET_AML_OFFICER_PUB_H
+
+#include <taler/exchange/common.h>
+
+
+/**
+ * Handle for a GET /aml/$OFFICER_PUB operation.
+ */
+struct TALER_EXCHANGE_GetAmlOfficerHandle;
+
+
+/**
+ * Response from GET /aml/$OFFICER_PUB.
+ */
+struct TALER_EXCHANGE_GetAmlOfficerResponse
+{
+ /**
+ * HTTP response data.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Response details depending on the HTTP status.
+ */
+ union
+ {
+ /**
+ * Details for an HTTP 200 response.
+ */
+ struct
+ {
+ /**
+ * Name configured for the AML officer.
+ */
+ const char *officer_name;
+
+ /**
+ * True if the officer may only read AML data.
+ */
+ bool read_only;
+ } ok;
+ } details;
+};
+
+
+#ifndef TALER_EXCHANGE_GET_AML_OFFICER_RESULT_CLOSURE
+/**
+ * Type of the closure used by #TALER_EXCHANGE_GetAmlOfficerCallback.
+ */
+#define TALER_EXCHANGE_GET_AML_OFFICER_RESULT_CLOSURE void
+#endif
+
+/**
+ * Callback for GET /aml/$OFFICER_PUB.
+ *
+ * @param cls closure
+ * @param response exchange response
+ */
+typedef void
+(*TALER_EXCHANGE_GetAmlOfficerCallback)(
+ TALER_EXCHANGE_GET_AML_OFFICER_RESULT_CLOSURE *cls,
+ const struct TALER_EXCHANGE_GetAmlOfficerResponse *response);
+
+
+/**
+ * Set up GET /aml/$OFFICER_PUB. The operation must be started explicitly.
+ *
+ * @param ctx CURL context
+ * @param url exchange base URL
+ * @param officer_priv private key of the officer
+ * @return operation handle
+ */
+struct TALER_EXCHANGE_GetAmlOfficerHandle *
+TALER_EXCHANGE_get_aml_officer_create (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_AmlOfficerPrivateKeyP *officer_priv);
+
+
+/**
+ * Start GET /aml/$OFFICER_PUB.
+ *
+ * @param[in,out] gaoh operation handle
+ * @param cb result callback
+ * @param cb_cls closure for @a cb
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_EXCHANGE_get_aml_officer_start (
+ struct TALER_EXCHANGE_GetAmlOfficerHandle *gaoh,
+ TALER_EXCHANGE_GetAmlOfficerCallback cb,
+ TALER_EXCHANGE_GET_AML_OFFICER_RESULT_CLOSURE *cb_cls);
+
+
+/**
+ * Cancel GET /aml/$OFFICER_PUB.
+ *
+ * @param[in] gaoh operation handle
+ */
+void
+TALER_EXCHANGE_get_aml_officer_cancel (
+ struct TALER_EXCHANGE_GetAmlOfficerHandle *gaoh);
+
+
+#endif
diff --git a/src/include/taler/taler_exchange_service.h b/src/include/taler/taler_exchange_service.h
@@ -61,6 +61,7 @@
<taler/exchange/post-management-denominations-H_DENOM_PUB-revoke.h>
#include <taler/exchange/post-management-signkeys-EXCHANGE_PUB-revoke.h>
#include <taler/exchange/post-management-aml-officers.h>
+#include <taler/exchange/get-aml-OFFICER_PUB.h>
#include <taler/exchange/get-aml-OFFICER_PUB-measures.h>
#include <taler/exchange/get-aml-OFFICER_PUB-kyc-statistics-NAMES.h>
#include <taler/exchange/post-aml-OFFICER_PUB-decision.h>
diff --git a/src/include/taler/taler_testing_lib.h b/src/include/taler/taler_testing_lib.h
@@ -2518,6 +2518,25 @@ TALER_TESTING_cmd_set_officer (
/**
+ * Check the authenticated AML officer resource.
+ *
+ * @param label command label
+ * @param ref_officer command that created the officer key
+ * @param expected_http_status expected HTTP status
+ * @param expected_name expected officer name for HTTP 200
+ * @param expected_read_only expected permission for HTTP 200
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_get_aml_officer (
+ const char *label,
+ const char *ref_officer,
+ unsigned int expected_http_status,
+ const char *expected_name,
+ bool expected_read_only);
+
+
+/**
* Make AML decision.
*
* @param label command label
diff --git a/src/lib/exchange_api_get-aml-OFFICER_PUB.c b/src/lib/exchange_api_get-aml-OFFICER_PUB.c
@@ -0,0 +1,212 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2026 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+*/
+/**
+ * @file lib/exchange_api_get-aml-OFFICER_PUB.c
+ * @brief Implementation of GET /aml/$OFFICER_PUB
+ */
+#include <microhttpd.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler/taler_json_lib.h"
+#include "taler/exchange/get-aml-OFFICER_PUB.h"
+#include "exchange_api_handle.h"
+#include "taler/taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * Handle for GET /aml/$OFFICER_PUB.
+ */
+struct TALER_EXCHANGE_GetAmlOfficerHandle
+{
+ char *base_url;
+ char *url;
+ struct GNUNET_CURL_Job *job;
+ TALER_EXCHANGE_GetAmlOfficerCallback cb;
+ TALER_EXCHANGE_GET_AML_OFFICER_RESULT_CLOSURE *cb_cls;
+ struct GNUNET_CURL_Context *ctx;
+ struct TALER_AmlOfficerPublicKeyP officer_pub;
+ struct TALER_AmlOfficerPrivateKeyP officer_priv;
+};
+
+
+/**
+ * Process a completed request.
+ *
+ * @param cls operation handle
+ * @param response_code HTTP status, 0 on transport failure
+ * @param response parsed JSON response
+ */
+static void
+handle_get_aml_officer_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_GetAmlOfficerHandle *gaoh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_GetAmlOfficerResponse result = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ gaoh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ result.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string (
+ "officer_name",
+ &result.details.ok.officer_name),
+ GNUNET_JSON_spec_bool (
+ "read_only",
+ &result.details.ok.read_only),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ result.hr.http_status = 0;
+ result.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ }
+ break;
+ }
+ case MHD_HTTP_BAD_REQUEST:
+ case MHD_HTTP_FORBIDDEN:
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ result.hr.ec = TALER_JSON_get_error_code (j);
+ result.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ default:
+ GNUNET_break_op (0);
+ result.hr.ec = TALER_JSON_get_error_code (j);
+ result.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for GET AML officer\n",
+ (unsigned int) response_code,
+ (int) result.hr.ec);
+ break;
+ }
+ gaoh->cb (gaoh->cb_cls,
+ &result);
+ gaoh->cb = NULL;
+ TALER_EXCHANGE_get_aml_officer_cancel (gaoh);
+}
+
+
+struct TALER_EXCHANGE_GetAmlOfficerHandle *
+TALER_EXCHANGE_get_aml_officer_create (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_AmlOfficerPrivateKeyP *officer_priv)
+{
+ struct TALER_EXCHANGE_GetAmlOfficerHandle *gaoh;
+
+ gaoh = GNUNET_new (struct TALER_EXCHANGE_GetAmlOfficerHandle);
+ gaoh->ctx = ctx;
+ gaoh->base_url = GNUNET_strdup (url);
+ gaoh->officer_priv = *officer_priv;
+ GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv,
+ &gaoh->officer_pub.eddsa_pub);
+ return gaoh;
+}
+
+
+enum TALER_ErrorCode
+TALER_EXCHANGE_get_aml_officer_start (
+ struct TALER_EXCHANGE_GetAmlOfficerHandle *gaoh,
+ TALER_EXCHANGE_GetAmlOfficerCallback cb,
+ TALER_EXCHANGE_GET_AML_OFFICER_RESULT_CLOSURE *cb_cls)
+{
+ CURL *eh;
+ struct TALER_AmlOfficerSignatureP officer_sig;
+ char arg_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2 + 16];
+ struct curl_slist *job_headers = NULL;
+ char pub_str[sizeof (gaoh->officer_pub) * 2];
+ char sig_str[sizeof (officer_sig) * 2];
+ char *end;
+ char *hdr;
+
+ gaoh->cb = cb;
+ gaoh->cb_cls = cb_cls;
+ TALER_officer_aml_query_sign (&gaoh->officer_priv,
+ &officer_sig);
+ end = GNUNET_STRINGS_data_to_string (&gaoh->officer_pub,
+ sizeof (gaoh->officer_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "aml/%s",
+ pub_str);
+ gaoh->url = TALER_url_join (gaoh->base_url,
+ arg_str,
+ NULL);
+ if (NULL == gaoh->url)
+ return TALER_EC_GENERIC_CONFIGURATION_INVALID;
+ eh = TALER_EXCHANGE_curl_easy_get_ (gaoh->url);
+ if (NULL == eh)
+ {
+ GNUNET_free (gaoh->url);
+ gaoh->url = NULL;
+ return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ }
+ end = GNUNET_STRINGS_data_to_string (&officer_sig,
+ sizeof (officer_sig),
+ sig_str,
+ sizeof (sig_str));
+ *end = '\0';
+ GNUNET_asprintf (&hdr,
+ "%s: %s",
+ TALER_AML_OFFICER_SIGNATURE_HEADER,
+ sig_str);
+ job_headers = curl_slist_append (NULL,
+ hdr);
+ GNUNET_free (hdr);
+ gaoh->job = GNUNET_CURL_job_add2 (gaoh->ctx,
+ eh,
+ job_headers,
+ &handle_get_aml_officer_finished,
+ gaoh);
+ curl_slist_free_all (job_headers);
+ if (NULL == gaoh->job)
+ {
+ GNUNET_free (gaoh->url);
+ gaoh->url = NULL;
+ return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ }
+ return TALER_EC_NONE;
+}
+
+
+void
+TALER_EXCHANGE_get_aml_officer_cancel (
+ struct TALER_EXCHANGE_GetAmlOfficerHandle *gaoh)
+{
+ if (NULL != gaoh->job)
+ {
+ GNUNET_CURL_job_cancel (gaoh->job);
+ gaoh->job = NULL;
+ }
+ GNUNET_free (gaoh->url);
+ GNUNET_free (gaoh->base_url);
+ GNUNET_free (gaoh);
+}
+
+
+/* end of exchange_api_get-aml-OFFICER_PUB.c */
diff --git a/src/lib/meson.build b/src/lib/meson.build
@@ -4,6 +4,7 @@
libtalerexchange_la_SOURCES = [
'exchange_api_delete-purses-PURSE_PUB.c',
+ 'exchange_api_get-aml-OFFICER_PUB.c',
'exchange_api_get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.c',
'exchange_api_get-aml-OFFICER_PUB-decisions.c',
'exchange_api_get-aml-OFFICER_PUB-kyc-statistics-NAMES.c',
diff --git a/src/testing/meson.build b/src/testing/meson.build
@@ -49,6 +49,7 @@ libtalertesting_la_SOURCES = [
'testing_api_cmd_exec_wget.c',
'testing_api_cmd_exec_wirewatch.c',
'testing_api_cmd_get_active_legitimization_measures.c',
+ 'testing_api_cmd_get_aml_officer.c',
'testing_api_cmd_get_auditor.c',
'testing_api_cmd_get_exchange.c',
'testing_api_cmd_get_kyc_info.c',
diff --git a/src/testing/test_kyc_api.c b/src/testing/test_kyc_api.c
@@ -590,6 +590,12 @@ run (void *cls,
"Peter Falk",
true,
true),
+ TALER_TESTING_cmd_get_aml_officer (
+ "get-read-only-aml-officer",
+ "create-aml-officer-1",
+ MHD_HTTP_OK,
+ "Peter Falk",
+ true),
TALER_TESTING_cmd_check_aml_decisions (
"check-decisions-none-normal",
"create-aml-officer-1",
@@ -642,6 +648,12 @@ run (void *cls,
"Peter Falk",
true,
false),
+ TALER_TESTING_cmd_get_aml_officer (
+ "get-read-write-aml-officer",
+ "create-aml-officer-1",
+ MHD_HTTP_OK,
+ "Peter Falk",
+ false),
TALER_TESTING_cmd_take_aml_decision (
"aml-decide",
"create-aml-officer-1",
@@ -683,6 +695,12 @@ run (void *cls,
"Peter Falk",
false,
true),
+ TALER_TESTING_cmd_get_aml_officer (
+ "get-disabled-aml-officer",
+ "create-aml-officer-1",
+ MHD_HTTP_FORBIDDEN,
+ NULL,
+ true),
/* Test that we are NOT allowed to read AML decisions now that
our AML staff account is disabled */
TALER_TESTING_cmd_check_aml_decisions (
diff --git a/src/testing/testing_api_cmd_get_aml_officer.c b/src/testing/testing_api_cmd_get_aml_officer.c
@@ -0,0 +1,169 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2026 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+*/
+/**
+ * @file testing/testing_api_cmd_get_aml_officer.c
+ * @brief command for testing GET /aml/$OFFICER_PUB
+ */
+#include <gnunet/gnunet_curl_lib.h>
+
+struct AmlOfficerState;
+
+#define TALER_EXCHANGE_GET_AML_OFFICER_RESULT_CLOSURE struct AmlOfficerState
+#include "taler/exchange/get-aml-OFFICER_PUB.h"
+#include "taler/taler_testing_lib.h"
+
+
+/**
+ * State for a GET AML officer command.
+ */
+struct AmlOfficerState
+{
+ struct TALER_EXCHANGE_GetAmlOfficerHandle *handle;
+ struct TALER_TESTING_Interpreter *is;
+ const char *ref_officer;
+ const char *expected_name;
+ unsigned int expected_http_status;
+ bool expected_read_only;
+};
+
+
+/**
+ * Process the AML officer response.
+ *
+ * @param state command state
+ * @param response exchange response
+ */
+static void
+get_aml_officer_cb (
+ TALER_EXCHANGE_GET_AML_OFFICER_RESULT_CLOSURE *state,
+ const struct TALER_EXCHANGE_GetAmlOfficerResponse *response)
+{
+ state->handle = NULL;
+ if (state->expected_http_status != response->hr.http_status)
+ {
+ TALER_TESTING_unexpected_status (state->is,
+ response->hr.http_status,
+ state->expected_http_status);
+ return;
+ }
+ if (MHD_HTTP_OK == response->hr.http_status)
+ {
+ if ( (0 != strcmp (state->expected_name,
+ response->details.ok.officer_name)) ||
+ (state->expected_read_only != response->details.ok.read_only) )
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (state->is);
+ return;
+ }
+ }
+ TALER_TESTING_interpreter_next (state->is);
+}
+
+
+/**
+ * Run the command.
+ */
+static void
+get_aml_officer_run (
+ void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct AmlOfficerState *state = cls;
+ const struct TALER_AmlOfficerPrivateKeyP *officer_priv;
+ const struct TALER_TESTING_Command *ref;
+ const struct TALER_TESTING_Command *exchange_cmd;
+ const char *exchange_url;
+ enum TALER_ErrorCode ec;
+
+ (void) cmd;
+ state->is = is;
+ exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+ "exchange");
+ GNUNET_assert (NULL != exchange_cmd);
+ GNUNET_assert (
+ GNUNET_OK ==
+ TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+ &exchange_url));
+ ref = TALER_TESTING_interpreter_lookup_command (is,
+ state->ref_officer);
+ if (NULL == ref)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_assert (
+ GNUNET_OK ==
+ TALER_TESTING_get_trait_officer_priv (ref,
+ &officer_priv));
+ state->handle = TALER_EXCHANGE_get_aml_officer_create (
+ TALER_TESTING_interpreter_get_context (is),
+ exchange_url,
+ officer_priv);
+ GNUNET_assert (NULL != state->handle);
+ ec = TALER_EXCHANGE_get_aml_officer_start (state->handle,
+ &get_aml_officer_cb,
+ state);
+ if (TALER_EC_NONE != ec)
+ {
+ state->handle = NULL;
+ TALER_TESTING_interpreter_fail (is);
+ }
+}
+
+
+/**
+ * Clean up the command.
+ */
+static void
+get_aml_officer_cleanup (
+ void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct AmlOfficerState *state = cls;
+
+ if (NULL != state->handle)
+ {
+ TALER_TESTING_command_incomplete (state->is,
+ cmd->label);
+ TALER_EXCHANGE_get_aml_officer_cancel (state->handle);
+ }
+ GNUNET_free (state);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_get_aml_officer (
+ const char *label,
+ const char *ref_officer,
+ unsigned int expected_http_status,
+ const char *expected_name,
+ bool expected_read_only)
+{
+ struct AmlOfficerState *state;
+ struct TALER_TESTING_Command cmd;
+
+ state = GNUNET_new (struct AmlOfficerState);
+ state->ref_officer = ref_officer;
+ state->expected_http_status = expected_http_status;
+ state->expected_name = expected_name;
+ state->expected_read_only = expected_read_only;
+ cmd = (struct TALER_TESTING_Command) {
+ .cls = state,
+ .label = label,
+ .run = &get_aml_officer_run,
+ .cleanup = &get_aml_officer_cleanup
+ };
+ return cmd;
+}
+
+
+/* end of testing_api_cmd_get_aml_officer.c */