commit 1346ff888defd446dd1cffefa1ddf3890a373591
parent 007ce3bcb9f0406a6b833bff2ae9463bc390bc33
Author: Christian Grothoff <christian@grothoff.org>
Date: Thu, 25 Jul 2024 22:23:51 +0200
-fix age withdraw test
Diffstat:
9 files changed, 244 insertions(+), 133 deletions(-)
diff --git a/src/exchange/taler-exchange-httpd_withdraw.c b/src/exchange/taler-exchange-httpd_withdraw.c
@@ -238,10 +238,11 @@ TEH_withdraw_kyc_check (
if (qs < 0)
{
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "reserves_get_origin");
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "reserves_get_origin");
return qs;
}
/* If _no_ results, reserve was created by merge,
@@ -254,7 +255,7 @@ TEH_withdraw_kyc_check (
kyc,
connection,
mhd_ret,
- TALER_KYCLOGIC_KYC_TRIGGER_AGE_WITHDRAW,
+ TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW,
payto_uri,
&wc.h_payto,
&withdraw_amount_cb,
diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h
@@ -3000,6 +3000,12 @@ struct TALER_EXCHANGE_AgeWithdrawResponse
*/
struct TALER_ExchangePublicKeyP exchange_pub;
} ok;
+
+ /**
+ * Details if the status is #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS.
+ */
+ struct TALER_EXCHANGE_KycNeededRedirect unavailable_for_legal_reasons;
+
} details;
};
@@ -3112,6 +3118,13 @@ struct TALER_EXCHANGE_AgeWithdrawBlindedResponse
struct TALER_ExchangePublicKeyP exchange_pub;
} ok;
+
+
+ /**
+ * Details if the status is #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS.
+ */
+ struct TALER_EXCHANGE_KycNeededRedirect unavailable_for_legal_reasons;
+
} details;
};
@@ -3232,6 +3245,12 @@ struct TALER_EXCHANGE_AgeWithdrawRevealResponse
const struct TALER_BlindedDenominationSignature *blinded_denom_sigs;
} ok;
+
+ /**
+ * Details if the status is #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS.
+ */
+ struct TALER_EXCHANGE_KycNeededRedirect unavailable_for_legal_reasons;
+
} details;
};
diff --git a/src/include/taler_kyclogic_lib.h b/src/include/taler_kyclogic_lib.h
@@ -61,12 +61,8 @@ enum TALER_KYCLOGIC_KycTriggerEvent
/**
* Reserve is being closed by force.
*/
- TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE = 5,
+ TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE = 5
- /**
- * Customer withdraws coins via age-withdraw.
- */
- TALER_KYCLOGIC_KYC_TRIGGER_AGE_WITHDRAW = 6,
};
diff --git a/src/json/json_helper.c b/src/json/json_helper.c
@@ -531,6 +531,7 @@ TALER_JSON_spec_age_commitment (const char *name,
return ret;
}
+
struct GNUNET_JSON_Specification
TALER_JSON_spec_token_issue_sig (const char *field,
struct TALER_TokenIssueSignatureP *sig)
@@ -540,6 +541,7 @@ TALER_JSON_spec_token_issue_sig (const char *field,
&sig->signature);
}
+
struct GNUNET_JSON_Specification
TALER_JSON_spec_blinded_token_issue_sig (
const char *field,
@@ -1471,8 +1473,6 @@ parse_kycte (void *cls,
.val = TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE },
{ .name = "RESERVE-CLOSE",
.val = TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE },
- { .name = "AGE-WITHDRAW",
- .val = TALER_KYCLOGIC_KYC_TRIGGER_AGE_WITHDRAW },
{ .name = NULL,
.val = TALER_KYCLOGIC_KYC_TRIGGER_NONE },
};
diff --git a/src/kyclogic/kyclogic_api.c b/src/kyclogic/kyclogic_api.c
@@ -974,7 +974,6 @@ TALER_KYCLOGIC_kyc_trigger_from_string (
enum TALER_KYCLOGIC_KycTriggerEvent out;
} map [] = {
{ "withdraw", TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW },
- { "age-withdraw", TALER_KYCLOGIC_KYC_TRIGGER_AGE_WITHDRAW },
{ "deposit", TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT },
{ "merge", TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE },
{ "balance", TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE },
@@ -1007,8 +1006,6 @@ TALER_KYCLOGIC_kyc_trigger2s (
return NULL;
case TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW:
return "withdraw";
- case TALER_KYCLOGIC_KYC_TRIGGER_AGE_WITHDRAW:
- return "age-withdraw";
case TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT:
return "deposit";
case TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE:
diff --git a/src/lib/exchange_api_age_withdraw.c b/src/lib/exchange_api_age_withdraw.c
@@ -449,10 +449,13 @@ handle_reserve_age_withdraw_blinded_finished (
case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
/* only validate reply is well-formed */
{
- uint64_t ptu;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_uint64 ("requirement_row",
- &ptu),
+ GNUNET_JSON_spec_fixed_auto (
+ "h_payto",
+ &awbr.details.unavailable_for_legal_reasons.h_payto),
+ GNUNET_JSON_spec_uint64 (
+ "requirement_row",
+ &awbr.details.unavailable_for_legal_reasons.requirement_row),
GNUNET_JSON_spec_end ()
};
@@ -500,13 +503,13 @@ perform_protocol (
struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh)
{
#define FAIL_IF(cond) \
- do { \
- if ((cond)) \
- { \
- GNUNET_break (! (cond)); \
- goto ERROR; \
- } \
- } while (0)
+ do { \
+ if ((cond)) \
+ { \
+ GNUNET_break (! (cond)); \
+ goto ERROR; \
+ } \
+ } while (0)
struct GNUNET_HashContext *coins_hctx = NULL;
json_t *j_denoms = NULL;
@@ -744,8 +747,7 @@ call_age_withdraw_blinded (
* @param awbh The handler
* @param exchange_url The base-URL to the exchange
*/
-static
-enum GNUNET_GenericReturnValue
+static enum GNUNET_GenericReturnValue
prepare_url (
struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh,
const char *exchange_url)
@@ -827,14 +829,15 @@ csr_withdraw_done (
can->planchet_detail.blinded_planchet! */
do {
if (GNUNET_OK !=
- TALER_planchet_prepare (&csr->denom_pub->key,
- &can->details.alg_values,
- &can->details.blinding_key,
- &csr->nonce,
- &can->details.coin_priv,
- &can->details.h_age_commitment,
- &can->details.h_coin_pub,
- planchet))
+ TALER_planchet_prepare (
+ &csr->denom_pub->key,
+ &can->details.alg_values,
+ &can->details.blinding_key,
+ &csr->nonce,
+ &can->details.coin_priv,
+ &can->details.h_age_commitment,
+ &can->details.h_coin_pub,
+ planchet))
{
GNUNET_break (0);
break;
@@ -871,10 +874,9 @@ csr_withdraw_done (
* @param awh The handler to the age-withdraw
* @param num_coins The number of coins in @e coin_inputs
* @param coin_inputs The input for the individual coin(-candidates)
- * @return GNUNET_OK on success, GNUNET_SYSERR on failure
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
*/
-static
-enum GNUNET_GenericReturnValue
+static enum GNUNET_GenericReturnValue
prepare_coins (
struct TALER_EXCHANGE_AgeWithdrawHandle *awh,
size_t num_coins,
@@ -882,13 +884,13 @@ prepare_coins (
static num_coins])
{
#define FAIL_IF(cond) \
- do { \
- if ((cond)) \
- { \
- GNUNET_break (! (cond)); \
- goto ERROR; \
- } \
- } while (0)
+ do { \
+ if ((cond)) \
+ { \
+ GNUNET_break (! (cond)); \
+ goto ERROR; \
+ } \
+ } while (0)
GNUNET_assert (0 < num_coins);
awh->age_mask = coin_inputs[0].denom_pub->key.age_mask;
@@ -932,18 +934,20 @@ prepare_coins (
TALER_planchet_setup_coin_priv (&can->secret,
&can->details.alg_values,
&can->details.coin_priv);
- TALER_planchet_blinding_secret_create (&can->secret,
- &can->details.alg_values,
- &can->details.blinding_key);
+ TALER_planchet_blinding_secret_create (
+ &can->secret,
+ &can->details.alg_values,
+ &can->details.blinding_key);
FAIL_IF (GNUNET_OK !=
- TALER_planchet_prepare (&cd->denom_pub.key,
- &can->details.alg_values,
- &can->details.blinding_key,
- NULL,
- &can->details.coin_priv,
- &can->details.h_age_commitment,
- &can->details.h_coin_pub,
- planchet));
+ TALER_planchet_prepare (
+ &cd->denom_pub.key,
+ &can->details.alg_values,
+ &can->details.blinding_key,
+ NULL,
+ &can->details.coin_priv,
+ &can->details.h_age_commitment,
+ &can->details.h_coin_pub,
+ planchet));
TALER_coin_ev_hash (&planchet->blinded_planchet,
&planchet->denom_pub_hash,
&can->blinded_coin_h);
@@ -985,7 +989,8 @@ ERROR:
TALER_EXCHANGE_age_withdraw_cancel (awh);
return GNUNET_SYSERR;
#undef FAIL_IF
-};
+}
+
struct TALER_EXCHANGE_AgeWithdrawHandle *
TALER_EXCHANGE_age_withdraw (
@@ -1011,11 +1016,10 @@ TALER_EXCHANGE_age_withdraw (
awh->callback_cls = res_cb_cls;
awh->num_coins = num_coins;
awh->max_age = max_age;
-
-
- if (GNUNET_OK != prepare_coins (awh,
- num_coins,
- coin_inputs))
+ if (GNUNET_OK !=
+ prepare_coins (awh,
+ num_coins,
+ coin_inputs))
{
GNUNET_free (awh);
return NULL;
@@ -1090,12 +1094,12 @@ TALER_EXCHANGE_age_withdraw_blinded (
awbh->callback = res_cb;
awbh->callback_cls = res_cb_cls;
awbh->max_age = max_age;
-
- GNUNET_CRYPTO_eddsa_key_get_public (&awbh->reserve_priv->eddsa_priv,
- &awbh->reserve_pub.eddsa_pub);
-
- if (GNUNET_OK != prepare_url (awbh,
- exchange_url))
+ GNUNET_CRYPTO_eddsa_key_get_public (
+ &awbh->reserve_priv->eddsa_priv,
+ &awbh->reserve_pub.eddsa_pub);
+ if (GNUNET_OK !=
+ prepare_url (awbh,
+ exchange_url))
return NULL;
perform_protocol (awbh);
@@ -1109,7 +1113,6 @@ TALER_EXCHANGE_age_withdraw_blinded_cancel (
{
if (NULL == awbh)
return;
-
if (NULL != awbh->job)
{
GNUNET_CURL_job_cancel (awbh->job);
diff --git a/src/lib/exchange_api_age_withdraw_reveal.c b/src/lib/exchange_api_age_withdraw_reveal.c
@@ -41,22 +41,34 @@
struct TALER_EXCHANGE_AgeWithdrawRevealHandle
{
- /* The index not to be disclosed */
+ /**
+ * The index not to be disclosed
+ */
uint8_t noreveal_index;
- /* The age-withdraw commitment */
+ /**
+ * The age-withdraw commitment
+ */
struct TALER_AgeWithdrawCommitmentHashP h_commitment;
- /* The reserve's public key */
+ /**
+ * The reserve's public key
+ */
const struct TALER_ReservePublicKeyP *reserve_pub;
- /* Number of coins */
+ /**
+ * Number of coins
+ */
size_t num_coins;
- /* The @e num_coins * kappa coin secrets from the age-withdraw commitment */
+ /**
+ * The @e num_coins * kappa coin secrets from the age-withdraw commitment
+ */
const struct TALER_EXCHANGE_AgeWithdrawCoinInput *coins_input;
- /* The url for the reveal request */
+ /**
+ * The url for the reveal request
+ */
char *request_url;
/**
@@ -69,10 +81,14 @@ struct TALER_EXCHANGE_AgeWithdrawRevealHandle
*/
struct TALER_CURL_PostContext post_ctx;
- /* Callback */
+ /**
+ * Callback
+ */
TALER_EXCHANGE_AgeWithdrawRevealCallback callback;
- /* Reveal */
+ /**
+ * Reveal
+ */
void *callback_cls;
};
@@ -101,9 +117,10 @@ age_withdraw_reveal_ok (
GNUNET_JSON_spec_end ()
};
- if (GNUNET_OK != GNUNET_JSON_parse (j_response,
- spec,
- NULL, NULL))
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j_response,
+ spec,
+ NULL, NULL))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
@@ -130,9 +147,10 @@ age_withdraw_reveal_ok (
GNUNET_JSON_spec_end ()
};
- if (GNUNET_OK != GNUNET_JSON_parse (j_sig,
- spec,
- NULL, NULL))
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j_sig,
+ spec,
+ NULL, NULL))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
@@ -203,10 +221,13 @@ handle_age_withdraw_reveal_finished (
case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
/* only validate reply is well-formed */
{
- uint64_t ptu;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_uint64 ("legitimization_uuid",
- &ptu),
+ GNUNET_JSON_spec_fixed_auto (
+ "h_payto",
+ &awr.details.unavailable_for_legal_reasons.h_payto),
+ GNUNET_JSON_spec_uint64 (
+ "requirement_row",
+ &awr.details.unavailable_for_legal_reasons.requirement_row),
GNUNET_JSON_spec_end ()
};
@@ -300,10 +321,11 @@ prepare_url (
char pub_str[sizeof (struct TALER_AgeWithdrawCommitmentHashP) * 2];
char *end;
- end = GNUNET_STRINGS_data_to_string (&awrh->h_commitment,
- sizeof (awrh->h_commitment),
- pub_str,
- sizeof (pub_str));
+ end = GNUNET_STRINGS_data_to_string (
+ &awrh->h_commitment,
+ sizeof (awrh->h_commitment),
+ pub_str,
+ sizeof (pub_str));
*end = '\0';
GNUNET_snprintf (arg_str,
sizeof (arg_str),
@@ -330,8 +352,7 @@ prepare_url (
* @param curl_ctx The context for CURL
* @param awrh The handler
*/
-static
-void
+static void
perform_protocol (
struct GNUNET_CURL_Context *curl_ctx,
struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh)
@@ -356,8 +377,8 @@ perform_protocol (
for (size_t n = 0; n < awrh->num_coins; n++)
{
- const struct TALER_PlanchetMasterSecretP *secrets =
- awrh->coins_input[n].secrets;
+ const struct TALER_PlanchetMasterSecretP *secrets
+ = awrh->coins_input[n].secrets;
j_secrets = json_array ();
FAIL_IF (NULL == j_secrets);
@@ -395,11 +416,12 @@ perform_protocol (
json_decref (j_request_body);
j_request_body = NULL;
- awrh->job = GNUNET_CURL_job_add2 (curl_ctx,
- curlh,
- awrh->post_ctx.headers,
- &handle_age_withdraw_reveal_finished,
- awrh);
+ awrh->job = GNUNET_CURL_job_add2 (
+ curl_ctx,
+ curlh,
+ awrh->post_ctx.headers,
+ &handle_age_withdraw_reveal_finished,
+ awrh);
FAIL_IF (NULL == awrh->job);
/* No error, return */
diff --git a/src/testing/test_exchange_api_age_restriction.c b/src/testing/test_exchange_api_age_restriction.c
@@ -288,7 +288,7 @@ run (void *cls,
"withdraw-coin-1-lacking-kyc",
"create-reserve-kyc-1",
"EUR:10",
- 0, /* age restriction off */
+ 0, /* age restriction off */
MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS),
TALER_TESTING_cmd_admin_add_kycauth (
"setup-account-key",
@@ -313,21 +313,21 @@ run (void *cls,
0,
MHD_HTTP_OK),
TALER_TESTING_cmd_proof_kyc_oauth2 (
- "proof-kyc",
+ "proof-withdraw-kyc",
"withdraw-coin-1-lacking-kyc",
- "kyc-provider-test-oauth2",
+ "test-oauth2",
"pass",
MHD_HTTP_SEE_OTHER),
TALER_TESTING_cmd_withdraw_amount (
"withdraw-coin-1-with-kyc",
"create-reserve-kyc-1",
"EUR:10",
- 0, /* age restriction off */
+ 0, /* age restriction off */
MHD_HTTP_CONFLICT),
TALER_TESTING_cmd_age_withdraw (
"age-withdraw-coin-1-too-low",
"create-reserve-kyc-1",
- 18, /* Too high */
+ 18, /* Too high */
MHD_HTTP_CONFLICT,
"EUR:10",
NULL),
diff --git a/src/testing/test_exchange_api_age_restriction.conf b/src/testing/test_exchange_api_age_restriction.conf
@@ -23,7 +23,6 @@ HTTP_PORT = 8082
[exchange]
TERMS_ETAG = tos
PRIVACY_ETAG = 0
-AML_THRESHOLD = EUR:10
PORT = 8081
MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
DB = postgres
@@ -75,13 +74,13 @@ USERNAME = Exchange
PASSWORD = x
WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/"
+[exchange-extension-age_restriction]
+ENABLED = YES
+#AGE_GROUPS = "8:10:12:14:16:18:21"
[kyc-provider-test-oauth2]
-COST = 0
LOGIC = oauth2
-USER_TYPE = INDIVIDUAL
-PROVIDED_CHECKS = DUMMY
-CONVERTER = cat
+CONVERTER = taler-exchange-helper-converter-oauth2-address
KYC_OAUTH2_VALIDITY = forever
KYC_OAUTH2_TOKEN_URL = http://localhost:6666/oauth/v2/token
KYC_OAUTH2_AUTHORIZE_URL = http://localhost:6666/oauth/v2/login
@@ -91,30 +90,104 @@ KYC_OAUTH2_CLIENT_SECRET = exchange-secret
KYC_OAUTH2_POST_URL = http://example.com/
KYC_OAUTH2_CONVERTER_HELPER = taler-exchange-kyc-oauth2-test-converter.sh
-[kyc-legitimization-balance-high]
-OPERATION_TYPE = BALANCE
-REQUIRED_CHECKS = DUMMY
-THRESHOLD = EUR:20
-
-[kyc-legitimization-deposit-any]
-OPERATION_TYPE = DEPOSIT
-REQUIRED_CHECKS = DUMMY
-THRESHOLD = EUR:10
-TIMEFRAME = 1d
-
-[kyc-legitimization-withdraw]
+[kyc-check-oauth-test-id]
+VOLUNTARY = NO
+# We use an external provider
+TYPE = LINK
+DESCRIPTION = "Oauth2 dummy authentication"
+DESCRIPTION_I18N = {}
+# No context requirements
+REQUIRES =
+# Measure to execute if check failed.
+FALLBACK = manual-freeze
+# This check runs on oauth2
+PROVIDER_ID = test-oauth2
+
+# This is the "default" setting for an account if
+# it has not yet triggered anything.
+[kyc-check-default]
+VOLUNTARY = NO
+TYPE = INFO
+DESCRIPTION = "Your account is operating normally"
+DESCRIPTION_I18N = {}
+# No context requirements
+REQUIRES =
+# Measure to execute if check failed. Well,
+# this check cannot really fail, but the
+# conservative answer is to freeze.
+FALLBACK = manual-freeze
+
+# If this "check" is triggered, we merely inform
+# the user that their account has been frozen. The
+# user cannot proceed manually.
+[kyc-check-info-frozen]
+VOLUNTARY = NO
+TYPE = INFO
+DESCRIPTION = "Your account is frozen pending investigation"
+DESCRIPTION_I18N = {}
+# No context requirements
+REQUIRES =
+# Measure to execute if check failed. Well,
+# this check cannot really fail, but we stay
+# where we are: frozen.
+FALLBACK = manual-freeze
+
+# If this "check" is triggered, we merely inform
+# the user that we got their oauth-test data on file.
+[kyc-check-info-oauth-test-passed]
+VOLUNTARY = NO
+TYPE = INFO
+DESCRIPTION = "You passed the OAuth2 check. Thank you."
+DESCRIPTION_I18N = {}
+# No context requirements
+REQUIRES =
+# Measure to execute if check failed. Well,
+# this check cannot really fail, but we stay
+# where we are: frozen.
+FALLBACK = manual-freeze
+
+[aml-program-oauth-output-check]
+DESCRIPTION = "Validates the output from OAauth2 and then permits the reserve closing to proceed"
+# Command that runs on the output of the OAuth provider
+# to decide what rules should apply next.
+COMMAND = taler-exchange-helper-measure-test-oauth
+# What measure to take if the COMMAND failed.
+FALLBACK = manual-freeze
+
+[kyc-measure-run-oauth]
+# Get client ID via the OAuth test provider
+CHECK_NAME = oauth-test-id
+# AML program to run on the output of the OAuth provider
+# to decide what rules should apply next.
+PROGRAM = oauth-output-check
+# Context to provide for check and program; empty.
+CONTEXT = {}
+
+# This is a base-measure that is being triggered
+# whenever something goes wrong. We freeze the
+# account and ask AML staff to investigate.
+[kyc-measure-manual-freeze]
+CHECK_NAME = skip
+# AML program that freezes the account and flags
+# it for investigation.
+PROGRAM = taler-exchange-helper-measure-freeze
+# Context to provide for check and program; empty.
+CONTEXT = {}
+
+# This rule requests that the users passes KYC
+# when closing the reserve.
+[kyc-rule-withdraw]
+ENABLED = YES
+# This is a public rule.
+EXPOSED = YES
+# All checks listed must be done (well, there is only one...)
+IS_AND_COMBINATOR = YES
+# This happens if the reserve is closed.
OPERATION_TYPE = WITHDRAW
-REQUIRED_CHECKS = DUMMY
+# Threshold is 0, so any amount.
THRESHOLD = EUR:15
+# Timeframe doesn't exactly matter with a threshold of EUR:0.
TIMEFRAME = 1d
-
-[kyc-legitimization-merge]
-OPERATION_TYPE = MERGE
-REQUIRED_CHECKS = DUMMY
-THRESHOLD = EUR:15
-TIMEFRAME = 1d
-
-
-[exchange-extension-age_restriction]
-ENABLED = YES
-#AGE_GROUPS = "8:10:12:14:16:18:21"
+# If the rule is triggered, ask the user to provide
+# personal data via OAuth2
+NEXT_MEASURES = run-oauth