Skip to content

Commit 43192ab

Browse files
committed
feat(guardian, exchange): enhance request handling and add unique requests
- Refined error responses in guardian service and process capability apps - Added support for request uniqueness checks in guardian operations - Introduced `request_identifier` field to ensure unique operation requests - Implemented a method to retrieve token identity in exchange token object - Extended `create_operation_package` to include `request_identifier` handling This is a very simple implementation of at-most-once semantics for guardian capability processing. To use the feature, add a property to the guardian capability handler class: "unique_request = True". This implementation uses an in memory set on a per-token basis. The implemmentation uses the token identity property that is part of the token-guardian protocol. Added a method to expose the token identity value from the base token_object method class. Signed-off-by: Mic Bowman <mic.bowman@intel.com>
1 parent 3c28ce8 commit 43192ab

File tree

5 files changed

+98
-47
lines changed

5 files changed

+98
-47
lines changed

common-contract/pdo/contracts/guardian/common/guardian_service.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,10 @@ def __post_request__(self, path, request) :
9191
response.raise_for_status()
9292
return response.json()
9393

94-
except (requests.HTTPError, requests.ConnectionError, requests.Timeout) as e :
94+
except requests.HTTPError as he :
95+
logger.warn('HTTP error [%s]; %s, %s', path, he.response.status_code, he.response.text.strip())
96+
raise MessageException(f'HTTP error [{he.response.status_code}]: {he.response.text.strip()}') from he
97+
except (requests.ConnectionError, requests.Timeout) as e :
9598
logger.warn('network error connecting to service (%s); %s', path, str(e))
9699
raise MessageException(str(e)) from e
97100

@@ -111,7 +114,10 @@ def __get_request__(self, path) :
111114
response.raise_for_status()
112115
return response.json()
113116

114-
except (requests.HTTPError, requests.ConnectionError, requests.Timeout) as e :
117+
except requests.HTTPError as he :
118+
logger.warn('HTTP error [%s]; %s, %s', path, he.response.status_code, he.response.text.strip())
119+
raise MessageException(f'HTTP error [{he.response.status_code}]: {he.response.text.strip()}') from he
120+
except (requests.ConnectionError, requests.Timeout) as e :
115121
logger.warn('network error connecting to service (%s); %s', path, str(e))
116122
raise MessageException(str(e)) from e
117123

common-contract/pdo/contracts/guardian/scripts/guardianCLI.py

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -47,29 +47,6 @@
4747
from twisted.internet.endpoints import TCP4ServerEndpoint
4848
from twisted.web.wsgi import WSGIResource
4949

50-
## ----------------------------------------------------------------
51-
def ErrorResponse(request, error_code, msg) :
52-
"""Generate a common error response for broken requests
53-
"""
54-
55-
result = ""
56-
if request.method != 'HEAD' :
57-
result = msg + '\n'
58-
result = result.encode('utf8')
59-
60-
request.setResponseCode(error_code)
61-
request.setHeader(b'Content-Type', b'text/plain')
62-
request.setHeader(b'Content-Length', len(result))
63-
request.write(result)
64-
65-
try :
66-
request.finish()
67-
except :
68-
logger.exception("exception during request finish")
69-
raise
70-
71-
return request
72-
7350
# -----------------------------------------------------------------
7451
# -----------------------------------------------------------------
7552
def __shutdown__(*args) :

common-contract/pdo/contracts/guardian/wsgi/process_capability.py

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -44,24 +44,29 @@ class ProcessCapabilityApp(object) :
4444
"session_key_iv" : { "type" : "string" },
4545
"encrypted_message" : { "type" : "string" },
4646
},
47+
"required" : [ "encrypted_session_key", "session_key_iv", "encrypted_message" ]
4748
},
48-
}
49+
},
50+
"required" : [ "minted_identity", "operation" ],
4951
}
5052

5153
__operation_schema__ = {
5254
"type" : "object",
5355
"properties" : {
5456
"nonce" : { "type" : "string" },
57+
"request_identifier" : { "type" : "string" },
5558
"method_name" : { "type" : "string" },
5659
"parameters" : { "type" : "object" },
57-
}
60+
},
61+
"required" : [ "nonce", "method_name", "parameters" ],
5862
}
5963

6064
# -----------------------------------------------------------------
6165
def __init__(self, config, capability_store, endpoint_registry) :
6266
self.config = config
6367
self.capability_store = capability_store
6468
self.endpoint_registry = endpoint_registry
69+
self.request_registry = {} # map of sets, used to check for duplicate requests
6570

6671
try :
6772
operation_module_name = config['GuardianService']['Operations']
@@ -81,39 +86,62 @@ def __call__(self, environ, start_response) :
8186
try :
8287
request = UnpackJSONRequest(environ)
8388
if not ValidateJSON(request, self.__input_schema__) :
84-
return ErrorResponse(start_response, "invalid JSON")
89+
return ErrorResponse(start_response, "invalid JSON, malformed request")
8590

86-
capability_key = self.capability_store.get_capability_key(request['minted_identity'])
91+
minted_identity = request['minted_identity']
92+
capability_key = self.capability_store.get_capability_key(minted_identity)
8793

8894
operation_message = recv_secret(capability_key, request['operation'])
8995
if not ValidateJSON(operation_message, self.__operation_schema__) :
90-
return ErrorResponse(start_response, "invalid JSON")
96+
return ErrorResponse(start_response, "invalid JSON, malformed operation")
9197

9298
except KeyError as ke :
93-
logger.error(f'missing field in request: {ke}')
99+
logger.info(f'missing field in request: {ke}')
94100
return ErrorResponse(start_response, f'missing field in request: {ke}')
95101
except Exception as e :
96102
logger.error(f'unknown exception unpacking request (ProcessCapability); {e}')
97103
return ErrorResponse(start_response, "unknown exception while unpacking request")
98104

99-
# dispatch the operation
100-
try :
101-
method_name = operation_message['method_name']
102-
parameters = operation_message['parameters']
103-
except KeyError as ke :
104-
logger.error(f'missing field {ke}')
105-
return ErrorResponse(start_response, f'missing field {ke}')
105+
# find the operation, we've already validated the JSON so no errors here
106+
method_name = operation_message['method_name']
107+
parameters = operation_message['parameters']
106108

107109
logger.info("process capability operation %s with parameters %s", method_name, parameters)
108110

109111
try :
110112
operation = self.capability_handler_map[method_name]
113+
except KeyError as ke :
114+
logger.info(f'unknown operation {ke}')
115+
return ErrorResponse(start_response, f'unknown operation {ke}', HTTPStatus.NOT_FOUND)
116+
117+
# check for request replays
118+
try :
119+
if hasattr(operation, 'unique_requests') and operation.unique_requests is True :
120+
request_identifier = operation_message.get('request_identifier')
121+
if request_identifier is None :
122+
logger.info('missing request identifier for unique operation')
123+
return ErrorResponse(start_response, "missing request identifier for unique operation")
124+
125+
# add the minted identity to the registry if it does not exist
126+
if self.request_registry.get(minted_identity) is None :
127+
self.request_registry[minted_identity] = set()
128+
129+
# check if the request identifier is already in the registry for this minted identity
130+
if request_identifier in self.request_registry[minted_identity] :
131+
logger.info('duplicate request for unique operation')
132+
return ErrorResponse(start_response, 'duplicate request for unique operation', HTTPStatus.UNAUTHORIZED)
133+
134+
# add the request identifier to the registry for this minted identity
135+
self.request_registry[minted_identity].add(request_identifier)
136+
except Exception as e :
137+
logger.error(f'unexpected error checking for duplicate request; {e}')
138+
return ErrorResponse(start_response, "unexpected error checking for duplicate request")
139+
140+
# dispatch the operation
141+
try :
111142
operation_result = operation(parameters)
112143
if operation_result is None :
113-
return ErrorResponse(start_response, "operation failed")
114-
except KeyError as ke :
115-
logger.error(f'unknown operation {ke}')
116-
return ErrorResponse(start_response, f'unknown operation {ke}')
144+
return ErrorResponse(start_response, "operation failed", HTTPStatus.UNPROCESSABLE_ENTITY)
117145
except Exception as e :
118146
logger.error(f'unknown exception performing operation (ProcessCapability); {e}')
119147
return ErrorResponse(start_response, "unknown exception while performing operation")

exchange-contract/exchange/contracts/token_object.cpp

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,25 @@ bool ww::exchange::token_object::get_token_metadata(
6767
ERROR_IF_NOT(deserialized_token_metadata.deserialize(serialized_token_metadata.c_str()),
6868
"unexpected error: failed to deserialize token metadata");
6969

70-
ww::value::Object token_metadata_schema;
71-
ERROR_IF_NOT(token_metadata_schema.deserialize(schema.c_str()),
72-
"unexpected error: failed to deserialize token metadata schema");
73-
74-
ERROR_IF_NOT(deserialized_token_metadata.validate_schema(token_metadata_schema),
70+
ERROR_IF_NOT(deserialized_token_metadata.validate_schema(schema.c_str()),
7571
"unexpected error: token metadata does not match schema");
7672

7773
token_metadata.set(deserialized_token_metadata);
7874
return true;
7975
}
8076

77+
// -----------------------------------------------------------------
78+
// get_token_identity
79+
//
80+
// Return the minted identity for this token object.
81+
// -----------------------------------------------------------------
82+
bool ww::exchange::token_object::get_token_identity(
83+
std::string& token_identity)
84+
{
85+
ERROR_IF_NOT(token_object_store.get(minted_identity_key, token_identity),
86+
"unexpected error: failed to get minted identity");
87+
return true;
88+
}
8189

8290
// -----------------------------------------------------------------
8391
// METHOD: initialize_contract
@@ -463,6 +471,24 @@ bool ww::exchange::token_object::create_operation_package(
463471
const std::string& method_name,
464472
const ww::value::Object& parameters,
465473
ww::value::Object& capability_result)
474+
{
475+
// No request identifier is specified so we'll generate a new one
476+
ww::types::ByteArray identifier_raw;
477+
if (! ww::crypto::random_identifier(identifier_raw))
478+
return false;
479+
480+
std::string identifier;
481+
if (! ww::crypto::b64_encode(identifier_raw, identifier))
482+
return false;
483+
484+
return create_operation_package(identifier, method_name, parameters, capability_result);
485+
}
486+
487+
bool ww::exchange::token_object::create_operation_package(
488+
const std::string& request_identifier,
489+
const std::string& method_name,
490+
const ww::value::Object& parameters,
491+
ww::value::Object& capability_result)
466492
{
467493
// Create the operation package, this will be the message in the
468494
// secret that we are going to create for the capability
@@ -479,6 +505,9 @@ bool ww::exchange::token_object::create_operation_package(
479505
if (! operation.set_string("nonce", nonce.c_str()))
480506
return false;
481507

508+
if (! operation.set_string("request_identifier", request_identifier.c_str()))
509+
return false;
510+
482511
if (! operation.set_string("method_name", method_name.c_str()))
483512
return false;
484513

exchange-contract/exchange/token_object.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
#define TO_OPERATION_SCHEMA \
4949
"{" \
5050
SCHEMA_KW(nonce,"") "," \
51+
SCHEMA_KW(request_identifier,"") "," \
5152
SCHEMA_KW(method_name, "") "," \
5253
SCHEMA_KWS(parameters, "{}") \
5354
"}"
@@ -71,6 +72,13 @@ namespace token_object
7172
7273
// utility functions
7374
bool initialize_contract(const Environment& env);
75+
76+
bool create_operation_package(
77+
const std::string& request_identifier,
78+
const std::string& method_name,
79+
const ww::value::Object& parameters,
80+
ww::value::Object& capability_result);
81+
7482
bool create_operation_package(
7583
const std::string& method_name,
7684
const ww::value::Object& parameters,
@@ -80,6 +88,9 @@ namespace token_object
8088
const std::string& schema,
8189
ww::value::Object& token_metadata);
8290
91+
bool get_token_identity(
92+
std::string& token_identity);
93+
8394
}; // token_object
8495
}; // exchange
8596
}; // ww

0 commit comments

Comments
 (0)