gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated: harness: test for FORM-based


From: gnunet
Subject: [taler-wallet-core] branch master updated: harness: test for FORM-based KYC
Date: Tue, 20 Aug 2024 16:29:43 +0200

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository wallet-core.

The following commit(s) were added to refs/heads/master by this push:
     new dcde71193 harness: test for FORM-based KYC
dcde71193 is described below

commit dcde71193724e34f0f9c99491ffc4f6fade0a704
Author: Florian Dold <florian@dold.me>
AuthorDate: Tue Aug 20 16:29:45 2024 +0200

    harness: test for FORM-based KYC
---
 packages/taler-harness/src/harness/helpers.ts      |  15 +
 .../integrationtests/test-kyc-form-withdrawal.ts   | 311 +++++++++++++++++++++
 .../src/integrationtests/testrunner.ts             |   2 +
 packages/taler-util/src/taler-signatures.ts        |   6 +
 .../src/types-taler-wallet-transactions.ts         |   8 +
 packages/taler-wallet-core/src/db.ts               |   2 +
 packages/taler-wallet-core/src/withdraw.ts         |  14 +-
 7 files changed, 354 insertions(+), 4 deletions(-)

diff --git a/packages/taler-harness/src/harness/helpers.ts 
b/packages/taler-harness/src/harness/helpers.ts
index fb8d85fc4..183664638 100644
--- a/packages/taler-harness/src/harness/helpers.ts
+++ b/packages/taler-harness/src/harness/helpers.ts
@@ -759,8 +759,21 @@ export async function 
createFaultInjectedMerchantTestkudosEnvironment(
 }
 
 export interface WithdrawViaBankResult {
+  /**
+   * Payto URI of the account that the withdrawal
+   * originated from. Typically a new account used for testing.
+   */
   accountPaytoUri: string;
+
+  /**
+   * Helper promise that resolves when withdrawal has finished successfully.
+   */
   withdrawalFinishedCond: Promise<true>;
+
+  /**
+   * The wallet-core withdrawal transaction ID.
+   */
+  transactionId: string;
 }
 
 /**
@@ -819,6 +832,7 @@ export async function withdrawViaBankV2(
   return {
     accountPaytoUri: user.accountPaytoUri,
     withdrawalFinishedCond,
+    transactionId: acceptRes.transactionId,
   };
 }
 
@@ -884,6 +898,7 @@ export async function withdrawViaBankV3(
   return {
     accountPaytoUri: user.accountPaytoUri,
     withdrawalFinishedCond,
+    transactionId: acceptRes.transactionId,
   };
 }
 
diff --git 
a/packages/taler-harness/src/integrationtests/test-kyc-form-withdrawal.ts 
b/packages/taler-harness/src/integrationtests/test-kyc-form-withdrawal.ts
new file mode 100644
index 000000000..1ad4b5bb4
--- /dev/null
+++ b/packages/taler-harness/src/integrationtests/test-kyc-form-withdrawal.ts
@@ -0,0 +1,311 @@
+/*
+ This file is part of GNU Taler
+ (C) 2020 Taler Systems S.A.
+
+ GNU 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.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * Imports.
+ */
+import {
+  codecForAny,
+  codecForKycProcessClientInformation,
+  decodeCrock,
+  encodeCrock,
+  j2s,
+  signAmlQuery,
+  TalerCorebankApiClient,
+  TransactionIdStr,
+  TransactionMajorState,
+  TransactionMinorState,
+} from "@gnu-taler/taler-util";
+import { readResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
+import {
+  createSyncCryptoApi,
+  EddsaKeypair,
+  WalletApiOperation,
+} from "@gnu-taler/taler-wallet-core";
+import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
+import {
+  BankService,
+  DbInfo,
+  ExchangeService,
+  generateRandomPayto,
+  GlobalTestState,
+  HarnessExchangeBankAccount,
+  harnessHttpLib,
+  setupDb,
+  WalletClient,
+  WalletService,
+} from "../harness/harness.js";
+import { EnvOptions, withdrawViaBankV3 } from "../harness/helpers.js";
+
+interface KycTestEnv {
+  commonDb: DbInfo;
+  bankClient: TalerCorebankApiClient;
+  exchange: ExchangeService;
+  exchangeBankAccount: HarnessExchangeBankAccount;
+  walletClient: WalletClient;
+  walletService: WalletService;
+  amlKeypair: EddsaKeypair;
+}
+
+async function createKycTestkudosEnvironment(
+  t: GlobalTestState,
+  coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS")),
+  opts: EnvOptions = {},
+): Promise<KycTestEnv> {
+  const db = await setupDb(t);
+
+  const bank = await BankService.create(t, {
+    allowRegistrations: true,
+    currency: "TESTKUDOS",
+    database: db.connStr,
+    httpPort: 8082,
+  });
+
+  const exchange = ExchangeService.create(t, {
+    name: "testexchange-1",
+    currency: "TESTKUDOS",
+    httpPort: 8081,
+    database: db.connStr,
+  });
+
+  let receiverName = "Exchange";
+  let exchangeBankUsername = "exchange";
+  let exchangeBankPassword = "mypw";
+  let exchangePaytoUri = generateRandomPayto(exchangeBankUsername);
+
+  await exchange.addBankAccount("1", {
+    accountName: exchangeBankUsername,
+    accountPassword: exchangeBankPassword,
+    wireGatewayApiBaseUrl: new URL(
+      "accounts/exchange/taler-wire-gateway/",
+      bank.baseUrl,
+    ).href,
+    accountPaytoUri: exchangePaytoUri,
+  });
+
+  bank.setSuggestedExchange(exchange, exchangePaytoUri);
+
+  await bank.start();
+
+  await bank.pingUntilAvailable();
+
+  const bankClient = new TalerCorebankApiClient(bank.corebankApiBaseUrl, {
+    auth: {
+      username: "admin",
+      password: "adminpw",
+    },
+  });
+
+  await bankClient.registerAccountExtended({
+    name: receiverName,
+    password: exchangeBankPassword,
+    username: exchangeBankUsername,
+    is_taler_exchange: true,
+    payto_uri: exchangePaytoUri,
+  });
+
+  exchange.addCoinConfigList(coinConfig);
+
+  await exchange.modifyConfig(async (config) => {
+    config.setString("exchange", "enable_kyc", "yes");
+
+    config.setString("KYC-RULE-R1", "operation_type", "withdraw");
+    config.setString("KYC-RULE-R1", "enabled", "yes");
+    config.setString("KYC-RULE-R1", "exposed", "yes");
+    config.setString("KYC-RULE-R1", "is_and_combinator", "yes");
+    config.setString("KYC-RULE-R1", "threshold", "TESTKUDOS:5");
+    config.setString("KYC-RULE-R1", "timeframe", "1d");
+    config.setString("KYC-RULE-R1", "next_measures", "M1 M2");
+
+    config.setString("KYC-MEASURE-M1", "check_name", "C1");
+    config.setString("KYC-MEASURE-M1", "context", "{}");
+    config.setString("KYC-MEASURE-M1", "program", "P1");
+
+    config.setString("KYC-MEASURE-M2", "check_name", "C2");
+    config.setString("KYC-MEASURE-M2", "context", "{}");
+    config.setString("KYC-MEASURE-M2", "program", "P2");
+
+    config.setString(
+      "AML-PROGRAM-P1",
+      "command",
+      "taler-exchange-helper-measure-test-form",
+    );
+    config.setString("AML-PROGRAM-P1", "enabled", "true");
+    config.setString(
+      "AML-PROGRAM-P1",
+      "description",
+      "test for full_name and birthdate",
+    );
+    config.setString("AML-PROGRAM-P1", "description_i18n", "{}");
+    config.setString("AML-PROGRAM-P1", "fallback", "M1");
+
+    config.setString("AML-PROGRAM-P2", "command", "/bin/true");
+    config.setString("AML-PROGRAM-P2", "enabled", "true");
+    config.setString("AML-PROGRAM-P2", "description", "does nothing");
+    config.setString("AML-PROGRAM-P2", "description_i18n", "{}");
+    config.setString("AML-PROGRAM-P2", "fallback", "M1");
+
+    config.setString("KYC-CHECK-C1", "type", "FORM");
+    config.setString("KYC-CHECK-C1", "form_name", "myform");
+    config.setString("KYC-CHECK-C1", "description", "my check!");
+    config.setString("KYC-CHECK-C1", "description_i18n", "{}");
+    config.setString("KYC-CHECK-C1", "outputs", "full_name birthdate");
+    config.setString("KYC-CHECK-C1", "fallback", "M1");
+
+    config.setString("KYC-CHECK-C2", "type", "INFO");
+    config.setString("KYC-CHECK-C2", "description", "my check info!");
+    config.setString("KYC-CHECK-C2", "description_i18n", "{}");
+    config.setString("KYC-CHECK-C2", "fallback", "M2");
+  });
+
+  await exchange.start();
+
+  const cryptoApi = createSyncCryptoApi();
+  const amlKeypair = await cryptoApi.createEddsaKeypair({});
+
+  await exchange.enableAmlAccount(amlKeypair.pub, "Alice");
+
+  const walletService = new WalletService(t, {
+    name: "wallet",
+    useInMemoryDb: true,
+  });
+  await walletService.start();
+  await walletService.pingUntilAvailable();
+
+  const walletClient = new WalletClient({
+    name: "wallet",
+    unixPath: walletService.socketPath,
+    onNotification(n) {
+      console.log("got notification", n);
+    },
+  });
+  await walletClient.connect();
+  await walletClient.client.call(WalletApiOperation.InitWallet, {
+    config: {
+      testing: {
+        skipDefaults: true,
+      },
+    },
+  });
+
+  console.log("setup done!");
+
+  return {
+    commonDb: db,
+    exchange,
+    amlKeypair,
+    walletClient,
+    walletService,
+    bankClient,
+    exchangeBankAccount: {
+      accountName: "",
+      accountPassword: "",
+      accountPaytoUri: "",
+      wireGatewayApiBaseUrl: "",
+    },
+  };
+}
+
+export async function runKycFormWithdrawalTest(t: GlobalTestState) {
+  // Set up test environment
+
+  const { walletClient, bankClient, exchange, amlKeypair } =
+    await createKycTestkudosEnvironment(t);
+
+  // Withdraw digital cash into the wallet.
+
+  const wres = await withdrawViaBankV3(t, {
+    amount: "TESTKUDOS:20",
+    bankClient,
+    exchange,
+    walletClient,
+  });
+
+  await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+    transactionId: wres.transactionId as TransactionIdStr,
+    txState: {
+      major: TransactionMajorState.Pending,
+      minor: TransactionMinorState.KycRequired,
+    },
+  });
+
+  const txDetails = await walletClient.call(
+    WalletApiOperation.GetTransactionById,
+    {
+      transactionId: wres.transactionId,
+    },
+  );
+
+  console.log(j2s(txDetails));
+  const accessToken = txDetails.kycAccessToken;
+  t.assertTrue(!!accessToken);
+
+  const infoResp = await harnessHttpLib.fetch(
+    new URL(`kyc-info/${txDetails.kycAccessToken}`, exchange.baseUrl).href,
+  );
+
+  const clientInfo = await readResponseJsonOrThrow(
+    infoResp,
+    codecForKycProcessClientInformation(),
+  );
+
+  console.log(j2s(clientInfo));
+
+  const kycId = clientInfo.requirements.find((x) => x.id != null)?.id;
+  t.assertTrue(!!kycId);
+
+  const uploadResp = await harnessHttpLib.fetch(
+    new URL(`kyc-upload/${kycId}`, exchange.baseUrl).href,
+    {
+      method: "POST",
+      headers: {
+        "Content-Type": "application/x-www-form-urlencoded",
+      },
+      body: "full_name=Alice+Abc&birthdate=2000-01-01",
+    },
+  );
+
+  console.log("resp status", uploadResp.status);
+
+  t.assertDeepEqual(uploadResp.status, 204);
+
+  const sig = signAmlQuery(decodeCrock(amlKeypair.priv));
+
+  const decisionsResp = await harnessHttpLib.fetch(
+    new URL(`aml/${amlKeypair.pub}/decisions`, exchange.baseUrl).href,
+    {
+      headers: {
+        "Taler-AML-Officer-Signature": encodeCrock(sig),
+      },
+    },
+  );
+
+  const decisions = await readResponseJsonOrThrow(decisionsResp, 
codecForAny());
+  console.log(j2s(decisions));
+
+  t.assertDeepEqual(decisionsResp.status, 200);
+
+  // KYC should pass now
+
+  await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+    transactionId: wres.transactionId as TransactionIdStr,
+    txState: {
+      major: TransactionMajorState.Done,
+    },
+  });
+}
+
+runKycFormWithdrawalTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts 
b/packages/taler-harness/src/integrationtests/testrunner.ts
index 9aea886f7..5c4ba41d9 100644
--- a/packages/taler-harness/src/integrationtests/testrunner.ts
+++ b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -49,6 +49,7 @@ import { runFeeRegressionTest } from 
"./test-fee-regression.js";
 import { runForcedSelectionTest } from "./test-forced-selection.js";
 import { runKycDepositAggregateTest } from "./test-kyc-deposit-aggregate.js";
 import { runKycExchangeWalletTest } from "./test-kyc-exchange-wallet.js";
+import { runKycFormWithdrawalTest } from "./test-kyc-form-withdrawal.js";
 import { runKycPeerPullTest } from "./test-kyc-peer-pull.js";
 import { runKycPeerPushTest } from "./test-kyc-peer-push.js";
 import { runKycThresholdWithdrawalTest } from 
"./test-kyc-threshold-withdrawal.js";
@@ -254,6 +255,7 @@ const allTests: TestMainFunction[] = [
   runKycPeerPushTest,
   runKycPeerPullTest,
   runKycDepositAggregateTest,
+  runKycFormWithdrawalTest,
 ];
 
 export interface TestRunSpec {
diff --git a/packages/taler-util/src/taler-signatures.ts 
b/packages/taler-util/src/taler-signatures.ts
index 81b7c242e..5c9690528 100644
--- a/packages/taler-util/src/taler-signatures.ts
+++ b/packages/taler-util/src/taler-signatures.ts
@@ -61,3 +61,9 @@ export function signAmlDecision(
 
   return eddsaSign(sigBlob, priv);
 }
+
+export function signAmlQuery(key: Uint8Array): Uint8Array {
+  const sigBlob = buildSigPS(TalerSignaturePurpose.AML_QUERY).build();
+
+  return eddsaSign(sigBlob, key);
+}
diff --git a/packages/taler-util/src/types-taler-wallet-transactions.ts 
b/packages/taler-util/src/types-taler-wallet-transactions.ts
index 4064052a5..000afb86a 100644
--- a/packages/taler-util/src/types-taler-wallet-transactions.ts
+++ b/packages/taler-util/src/types-taler-wallet-transactions.ts
@@ -222,7 +222,15 @@ export interface TransactionCommon {
    */
   kycUrl?: string;
 
+  /**
+   * KYC payto hash. Useful for testing, not so useful for UIs.
+   */
   kycPaytoHash?: string;
+
+  /**
+   * KYC access token. Useful for testing, not so useful for UIs.
+   */
+  kycAccessToken?: string;
 }
 
 export type Transaction =
diff --git a/packages/taler-wallet-core/src/db.ts 
b/packages/taler-wallet-core/src/db.ts
index 0dd67d2fa..531e19b63 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -1472,6 +1472,8 @@ export interface WithdrawalGroupRecord {
 
   kycUrl?: string;
 
+  kycAccessToken?: string;
+
   /**
    * Secret seed used to derive planchets.
    * Stored since planchets are created lazily.
diff --git a/packages/taler-wallet-core/src/withdraw.ts 
b/packages/taler-wallet-core/src/withdraw.ts
index ef04f8737..fa1066f0f 100644
--- a/packages/taler-wallet-core/src/withdraw.ts
+++ b/packages/taler-wallet-core/src/withdraw.ts
@@ -26,6 +26,7 @@ import {
   AbsoluteTime,
   AcceptManualWithdrawalResult,
   AcceptWithdrawalResponse,
+  AccountKycStatus,
   AgeRestriction,
   Amount,
   AmountJson,
@@ -229,6 +230,7 @@ function buildTransactionForBankIntegratedWithdraw(
         wg.status === WithdrawalGroupStatus.PendingReady,
     },
     kycUrl: wg.kycUrl,
+    kycAccessToken: wg.kycAccessToken,
     kycPaytoHash: wg.kycPending?.paytoHash,
     timestamp: timestampPreciseFromDb(wg.timestampStart),
     transactionId: constructTransactionIdentifier({
@@ -1210,7 +1212,7 @@ async function handleKycRequired(
       ["Account-Owner-Signature"]: sigResp.sig,
     },
   });
-  let kycUrl: string;
+  let kycStatus: AccountKycStatus;
   if (
     kycStatusRes.status === HttpStatusCode.Ok ||
     // FIXME: NoContent is not expected 
https://docs.taler.net/core/api-exchange.html#post--purses-$PURSE_PUB-merge
@@ -1220,12 +1222,11 @@ async function handleKycRequired(
     logger.warn("kyc requested, but already fulfilled");
     return;
   } else if (kycStatusRes.status === HttpStatusCode.Accepted) {
-    const kycStatus = await readSuccessResponseJsonOrThrow(
+    kycStatus = await readSuccessResponseJsonOrThrow(
       kycStatusRes,
       codecForAccountKycStatus(),
     );
     logger.info(`kyc status: ${j2s(kycStatus)}`);
-    kycUrl = new URL(`kyc-spa/${kycStatus.access_token}`, exchangeUrl).href;
   } else {
     throwUnexpectedRequestError(
       kycStatusRes,
@@ -1255,11 +1256,16 @@ async function handleKycRequired(
       if (wg2.status !== WithdrawalGroupStatus.PendingReady) {
         return TransitionResult.stay();
       }
+      // FIXME: Why not just store the whole kycState?!
       wg2.kycPending = {
         paytoHash: uuidResp.h_payto,
         requirementRow: uuidResp.requirement_row,
       };
-      wg2.kycUrl = kycUrl;
+      wg2.kycUrl = new URL(
+        `kyc-spa/${kycStatus.access_token}`,
+        exchangeUrl,
+      ).href;
+      wg2.kycAccessToken = kycStatus.access_token;
       wg2.status = WithdrawalGroupStatus.PendingKyc;
       return TransitionResult.transition(wg2);
     },

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]