summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2021-08-19 14:43:39 +0200
committerChristian Grothoff <christian@grothoff.org>2021-08-19 14:43:39 +0200
commit677c7e87a5da584e68194c9cca19a91191c3140c (patch)
tree33425fd28b6580c29d2f5fd80ab7241a5a643f13
parent4067891ed9f66eb5e47a709d3ea21c2ed36a1e86 (diff)
downloadanastasis-677c7e87a5da584e68194c9cca19a91191c3140c.tar.gz
anastasis-677c7e87a5da584e68194c9cca19a91191c3140c.tar.bz2
anastasis-677c7e87a5da584e68194c9cca19a91191c3140c.zip
-implement 'poll' transition in state machine
-rw-r--r--doc/sphinx/reducer.rst228
-rw-r--r--src/include/anastasis.h8
-rw-r--r--src/lib/anastasis_recovery.c8
-rw-r--r--src/reducer/anastasis_api_recovery_redux.c165
4 files changed, 307 insertions, 102 deletions
diff --git a/doc/sphinx/reducer.rst b/doc/sphinx/reducer.rst
index e5f1699..68df5b1 100644
--- a/doc/sphinx/reducer.rst
+++ b/doc/sphinx/reducer.rst
@@ -1459,121 +1459,139 @@ that applications must all handle. States other than ``solved`` are:
}
}
- - **body**: Here, the server provided an HTTP reply for
- how to solve the challenge, but the reducer could not parse
- them into a known format. A mime-type may be provided and may
- help parse the details.
+ - **body**: Here, the server provided an HTTP reply for
+ how to solve the challenge, but the reducer could not parse
+ them into a known format. A mime-type may be provided and may
+ help parse the details.
- .. code-block:: json
+ .. code-block:: json
- {
- "recovery_state": "CHALLENGE_SOLVING",
- "recovery_information": {
- "...": "..."
- }
- "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
- "challenge_feedback": {
- "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
- "state": "body",
- "body": "CROCKFORDBASE32ENCODEDBODY",
- "http_status": 403,
- "mime_type" : "anything/possible"
- }
- }
- }
+ {
+ "recovery_state": "CHALLENGE_SOLVING",
+ "recovery_information": {
+ "...": "..."
+ }
+ "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
+ "challenge_feedback": {
+ "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
+ "state": "body",
+ "body": "CROCKFORDBASE32ENCODEDBODY",
+ "http_status": 403,
+ "mime_type" : "anything/possible"
+ }
+ }
+ }
- - **hint**: Here, the server provided human-readable hint for
- how to solve the challenge. Note that the ``hint`` provided this
- time is from the Anastasis provider and may differ from the ``instructions``
- for the challenge under ``recovery_information``:
+ - **hint**: Here, the server provided human-readable hint for
+ how to solve the challenge. Note that the ``hint`` provided this
+ time is from the Anastasis provider and may differ from the ``instructions``
+ for the challenge under ``recovery_information``:
- .. code-block:: json
+ .. code-block:: json
- {
- "recovery_state": "CHALLENGE_SOLVING",
- "recovery_information": {
- "...": "..."
- }
- "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
- "challenge_feedback": {
- "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
- "state": "hint",
- "hint": "Recovery TAN send to email mail@DOMAIN",
- "http_status": 403
- }
+ {
+ "recovery_state": "CHALLENGE_SOLVING",
+ "recovery_information": {
+ "...": "..."
+ }
+ "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
+ "challenge_feedback": {
+ "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
+ "state": "hint",
+ "hint": "Recovery TAN send to email mail@DOMAIN",
+ "http_status": 403
+ }
+ }
+ }
+
+ - **details**: Here, the server provided a detailed JSON status response
+ related to solving the challenge:
+
+ .. code-block:: json
+
+ {
+ "recovery_state": "CHALLENGE_SOLVING",
+ "recovery_information": {
+ "...": "..."
+ }
+ "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
+ "challenge_feedback": {
+ "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
+ "state": "details",
+ "details": {
+ "code": 8111,
+ "hint": "The client's response to the challenge was invalid.",
+ "detail" : null
+ },
+ "http_status": 403
}
}
+ }
- - **details**: Here, the server provided a detailed JSON status response
- related to solving the challenge:
+ - **redirect**: To solve the challenge, the user must visit the indicated
+ Web site at ``redirect_url``, for example to perform video authentication:
- .. code-block:: json
+ .. code-block:: json
- {
- "recovery_state": "CHALLENGE_SOLVING",
- "recovery_information": {
- "...": "..."
- }
- "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
- "challenge_feedback": {
- "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
- "state": "details",
- "details": {
- "code": 8111,
- "hint": "The client's response to the challenge was invalid.",
- "detail" : null
- },
- "http_status": 403
- }
+ {
+ "recovery_state": "CHALLENGE_SOLVING",
+ "recovery_information": {
+ "...": "..."
+ }
+ "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
+ "challenge_feedback": {
+ "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
+ "state": "redirect",
+ "redirect_url": "https://videoconf.example.com/",
+ "http_status": 303
}
}
+ }
- - **redirect**: To solve the challenge, the user must visit the indicated
- Web site at ``redirect_url``, for example to perform video authentication:
+ - **server-failure**: This indicates that the Anastasis provider encountered
+ a failure and recovery using this challenge cannot proceed at this time.
+ Examples for failures might be that the provider is unable to send SMS
+ messages at this time due to an outage. The body includes details about
+ the failure. The user may try again later or continue with other challenges.
- .. code-block:: json
+ .. code-block:: json
- {
- "recovery_state": "CHALLENGE_SOLVING",
- "recovery_information": {
- "...": "..."
- }
- "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
- "challenge_feedback": {
- "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
- "state": "redirect",
- "redirect_url": "https://videoconf.example.com/",
- "http_status": 303
- }
- }
+ {
+ "recovery_state": "CHALLENGE_SELECTING",
+ "recovery_information": {
+ "...": "..."
}
+ "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
+ "challenge_feedback": {
+ "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
+ "state": "server-failure",
+ "http_status": "500",
+ "error_code": 52
+ }
+ }
+ }
- - **server-failure**: This indicates that the Anastasis provider encountered
- a failure and recovery using this challenge cannot proceed at this time.
- Examples for failures might be that the provider is unable to send SMS
- messages at this time due to an outage. The body includes details about
- the failure. The user may try again later or continue with other challenges.
+ - **truth-unknown**: This indicates that the Anastasis provider is unaware of
+ the specified challenge. This is typically a permanent failure, and user
+ interfaces should not allow users to re-try this challenge.
.. code-block:: json
- {
- "recovery_state": "CHALLENGE_SELECTING",
- "recovery_information": {
- "...": "..."
- }
- "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
- "challenge_feedback": {
- "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
- "state": "server-failure",
- "http_status": "500",
- "error_code": 52
- }
- }
- }
+ {
+ "recovery_state": "CHALLENGE_SELECTING",
+ "recovery_information": {
+ "...": "..."
+ }
+ "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
+ "challenge_feedback": {
+ "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
+ "state": "truth-unknown",
+ "error_code": 8108
+ }
+ }
+ }
- - **truth-unknown**: This indicates that the Anastasis provider is unaware of
- the specified challenge. This is typically a permanent failure, and user
- interfaces should not allow users to re-try this challenge.
+ - **rate-limit-exceeded**:
.. code-block:: json
@@ -1585,13 +1603,13 @@ that applications must all handle. States other than ``solved`` are:
"selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
"challenge_feedback": {
"TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
- "state": "truth-unknown",
- "error_code": 8108
+ "state": "rate-limit-exceeded",
+ "error_code": 8121
}
}
}
- - **rate-limit-exceeded**:
+ - **authentication-timeout**:
.. code-block:: json
@@ -1603,12 +1621,24 @@ that applications must all handle. States other than ``solved`` are:
"selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
"challenge_feedback": {
"TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
- "state": "rate-limit-exceeded",
- "error_code": 8121
+ "state": "authentication-timeout",
+ "error_code": 8122
}
}
}
+
+**poll:**
+
+With a ``poll`` transition, the application indicates that it wants to wait longer for one or more of the challenges that are in state ``authentication-timeout`` to possibly complete. While technically optional, the ``timeout`` argument should really be provided to enable long-polling, for example:
+
+.. code-block:: json
+
+ {
+ "timeout" : { "d_ms" : 5000 },
+ }
+
+
**pay:**
With a ``pay`` transition, the application indicates to the reducer that
diff --git a/src/include/anastasis.h b/src/include/anastasis.h
index 8443eb6..3027e2a 100644
--- a/src/include/anastasis.h
+++ b/src/include/anastasis.h
@@ -71,10 +71,16 @@ struct ANASTASIS_ChallengeDetails
const char *instructions;
/**
- * true if challenged was already solved, else false.
+ * true if challenge was already solved, else false.
*/
bool solved;
+ /**
+ * true if challenge is awaiting asynchronous
+ * resolution by the user.
+ */
+ bool async;
+
};
diff --git a/src/lib/anastasis_recovery.c b/src/lib/anastasis_recovery.c
index 4e23db0..623e882 100644
--- a/src/lib/anastasis_recovery.c
+++ b/src/lib/anastasis_recovery.c
@@ -348,6 +348,7 @@ keyshare_lookup_cb (void *cls,
= dd->details.server_failure.http_status
};
+ c->ci.async = true;
c->af (c->af_cls,
&csr);
return;
@@ -1043,7 +1044,9 @@ ANASTASIS_recovery_serialize (const struct ANASTASIS_Recovery *r)
GNUNET_JSON_pack_string ("instructions",
c->instructions),
GNUNET_JSON_pack_bool ("solved",
- c->ci.solved));
+ c->ci.solved),
+ GNUNET_JSON_pack_bool ("async",
+ c->ci.async));
GNUNET_assert (0 ==
json_array_append_new (cs_arr,
cs));
@@ -1119,6 +1122,9 @@ parse_cs_array (struct ANASTASIS_Recovery *r,
GNUNET_JSON_spec_string ("type",
&escrow_type),
GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_bool ("async",
+ &c->ci.async)),
+ GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_fixed_auto ("key_share",
&c->key_share)),
GNUNET_JSON_spec_end ()
diff --git a/src/reducer/anastasis_api_recovery_redux.c b/src/reducer/anastasis_api_recovery_redux.c
index fab3c24..95632cc 100644
--- a/src/reducer/anastasis_api_recovery_redux.c
+++ b/src/reducer/anastasis_api_recovery_redux.c
@@ -151,6 +151,13 @@ struct SelectChallengeContext
* Payment secret, if we are in the "pay" state.
*/
struct ANASTASIS_PaymentSecretP ps;
+
+ /**
+ * Application asked us to only poll for existing
+ * asynchronous challenges, and not to being a
+ * new one.
+ */
+ bool poll_only;
};
@@ -741,7 +748,6 @@ solve_challenge_cb (void *cls,
&ps),
GNUNET_JSON_spec_end ()
};
-
json_t *challenge;
if (NULL == ri)
@@ -769,6 +775,80 @@ solve_challenge_cb (void *cls,
return;
}
+ /* resume all async, unsolved challenges */
+ {
+ bool poll_started = false;
+
+ for (unsigned int i = 0; i<ri->cs_len; i++)
+ {
+ struct ANASTASIS_Challenge *ci = ri->cs[i];
+ const struct ANASTASIS_ChallengeDetails *cd;
+ json_t *challenge;
+ json_t *pin;
+
+ cd = ANASTASIS_challenge_get_details (ci);
+ if (cd->solved ||
+ (! cd->async) )
+ continue;
+
+ challenge = find_challenge_in_ri (sctx->state,
+ &cd->uuid);
+ if (NULL == challenge)
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "challenge not found");
+ sctx_free (sctx);
+ return;
+ }
+ pin = json_object_get (challenge,
+ "answer-pin");
+ if (! json_is_integer (pin))
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "async challenge 'answer-pin' not found");
+ sctx_free (sctx);
+ return;
+ }
+ if (GNUNET_OK !=
+ ANASTASIS_challenge_answer2 (ci,
+ psp,
+ timeout,
+ json_integer_value (pin),
+ &answer_feedback_cb,
+ sctx))
+ {
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "Failed to begin answering asynchronous challenge");
+ sctx_free (sctx);
+ return;
+ }
+ poll_started = true;
+ }
+
+ if (sctx->poll_only)
+ {
+ if (! poll_started)
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_ACTION_INVALID,
+ "no challenge available for polling");
+ return;
+ }
+ /* only polling, do not start new challenges */
+ return;
+ }
+ } /* end resuming async challenges */
+
/* Check if we got a payment_secret */
challenge = find_challenge_in_ri (sctx->state,
&sctx->uuid);
@@ -823,6 +903,7 @@ solve_challenge_cb (void *cls,
psp = &ps;
}
+ /* start or solve selected challenge */
for (unsigned int i = 0; i<ri->cs_len; i++)
{
struct ANASTASIS_Challenge *ci = ri->cs[i];
@@ -830,6 +911,8 @@ solve_challenge_cb (void *cls,
int ret;
cd = ANASTASIS_challenge_get_details (ci);
+ if (cd->async)
+ continue; /* handled above */
if (0 !=
GNUNET_memcmp (&sctx->uuid,
&cd->uuid))
@@ -883,6 +966,12 @@ solve_challenge_cb (void *cls,
{
uint64_t ianswer = json_integer_value (pin);
+ /* persist answer, in case async processing
+ happens via poll */
+ GNUNET_assert (0 ==
+ json_object_set (challenge,
+ "answer-pin",
+ pin));
ret = ANASTASIS_challenge_answer2 (ci,
psp,
timeout,
@@ -1153,6 +1242,75 @@ solve_challenge (json_t *state,
/**
+ * The user asked for us to poll on pending
+ * asynchronous challenges to see if they have
+ * now completed / been satisfied.
+ *
+ * @param[in] state we are in
+ * @param arguments our arguments with the solution
+ * @param cb functiont o call with the new state
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel challenge selection step
+ */
+static struct ANASTASIS_ReduxAction *
+poll_challenges (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ struct SelectChallengeContext *sctx
+ = GNUNET_new (struct SelectChallengeContext);
+ json_t *rd;
+
+ if (NULL == arguments)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ rd = json_object_get (state,
+ "recovery_document");
+ if (NULL == rd)
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "poll_challenges");
+ return NULL;
+ }
+ sctx->poll_only = true;
+ sctx->cb = cb;
+ sctx->cb_cls = cb_cls;
+ sctx->state = json_incref (state);
+ sctx->args = json_incref ((json_t*) arguments);
+ sctx->r = ANASTASIS_recovery_deserialize (ANASTASIS_REDUX_ctx_,
+ rd,
+ &solve_challenge_cb,
+ sctx,
+ &core_secret_cb,
+ sctx);
+ if (NULL == sctx->r)
+ {
+ json_decref (sctx->state);
+ json_decref (sctx->args);
+ GNUNET_free (sctx);
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'recovery_document' invalid");
+ return NULL;
+ }
+ sctx->ra.cleanup = &sctx_free;
+ sctx->ra.cleanup_cls = sctx;
+ return &sctx->ra;
+}
+
+
+/**
* The user selected a challenge to be solved. Handle the payment
* process.
*
@@ -1712,6 +1870,11 @@ ANASTASIS_recovery_action_ (json_t *state,
},
{
ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING,
+ "poll",
+ &poll_challenges
+ },
+ {
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING,
"back",
&ANASTASIS_back_generic_decrement_
},