Ejemplo n.º 1
0
def delete_api_key_v1(key_id: str) -> None:
    """Delete api key by key ID ONLY if it is not the last key on the chain
    Args:
        key_id: ID of api key to delete
    """
    # Don't allow removal of reserved keys
    if key_id.startswith("SC_") or key_id.startswith("INTERCHAIN"):
        raise exceptions.ActionForbidden("Cannot delete reserved API keys")
    # Don't allow removal of root keys
    if secrets.get_dc_secret("hmac-id") == key_id:
        raise exceptions.ActionForbidden("Cannot remove root API key")
    # Delete the actual key after previous checks pass
    api_key_dao.delete_api_key(key_id=key_id, interchain=False)
Ejemplo n.º 2
0
def submit_transaction_v1(
        transaction: Dict[str, Any], callback_url: Optional[str],
        api_key: "api_key_model.APIKeyModel") -> Dict[str, str]:
    """Formats and enqueues individual transaction (from user input) to the webserver queue
    Returns:
        String of the submitted transaction id
    """
    _log.info("[TRANSACTION] Parsing and loading user input")
    txn_model = _generate_transaction_model(transaction)

    # Check if allowed to create transaction of this type
    if not api_key.is_key_allowed(
            "transactions",
            "create",
            "create_transaction",
            False,
            extra_data={"requested_types": {txn_model.txn_type}}):
        raise exceptions.ActionForbidden(
            f"API Key is not allowed to create transaction of type {txn_model.txn_type}"
        )

    _log.info("[TRANSACTION] Txn valid. Queueing txn object")
    queue.enqueue_item(txn_model.export_as_queue_task())

    if callback_url:
        _log.info("[TRANSACTION] Registering callback for queued txn")
        callback.register_callback(txn_model.txn_id, callback_url)

    return {"transaction_id": txn_model.txn_id}
Ejemplo n.º 3
0
def delete_api_key_v1(key_id: str) -> None:
    """Delete api key by key ID ONLY if it is not the last key on the chain
    Args:
        key_id: ID of api key to delete
    """
    # Don't allow removal of root keys
    root_key_id = secrets.get_dc_secret("hmac-id")
    if root_key_id == key_id:
        raise exceptions.ActionForbidden("Cannot remove root API key")
    # Don't allow removal of reserved keys
    if key_id.startswith("SC_") or key_id.startswith(
            "INTERCHAIN") or key_id.startswith("WEB_"):
        raise exceptions.ActionForbidden("cannot delete reserved API keys")
    # Delete the actual key after previous checks pass
    if not authorization.remove_auth_key(auth_key_id=key_id, interchain=False):
        raise RuntimeError("Unkown error deleting key from storage")
Ejemplo n.º 4
0
def delete_transaction_type_v1(transaction_type: str) -> None:
    """
    Takes in a transaction type
    Checks if the transaction type is of smart contract
    Deletes type
    """
    try:
        existing_type_structure = transaction_type_dao.get_registered_transaction_type(transaction_type)
        if existing_type_structure is None:
            raise exceptions.NotFound(transaction_type)
        if existing_type_structure.contract_id:
            raise exceptions.ActionForbidden("Cannot delete smart contract transaction type")
        transaction_type_dao.remove_existing_transaction_type(transaction_type)
    except exceptions.NotFound:
        pass
Ejemplo n.º 5
0
def delete_transaction_type_v1(transaction_type: str) -> None:
    """
    Takes in a transaction type
    Checks if the transaction type is of smart contract
    Deletes type
    """
    try:
        existing_txn_type = transaction_type_dao.get_registered_transaction_type(
            transaction_type)
        # If txn type is a smart contract that exists, don't allow it to be deleted
        if existing_txn_type.contract_id and smart_contract_dao.contract_does_exist(
                existing_txn_type.contract_id):
            raise exceptions.ActionForbidden(
                "Cannot delete smart contract transaction type")
        transaction_type_dao.remove_existing_transaction_type(transaction_type)
    except exceptions.NotFound:
        return
Ejemplo n.º 6
0
def submit_bulk_transaction_v1(
        bulk_transaction: Sequence[Dict[str, Any]],
        api_key: "api_key_model.APIKeyModel") -> Dict[str, List[Any]]:
    """
    Formats, validates and enqueues the transactions in
    the payload of bulk_transaction
    Returns dictionary of 2 lists, key "201" are successfully created txn ids and key "400" are transactions that failed to post (entire dto passed in from user)
    """
    _log.info(
        "[TRANSACTION_BULK] Checking if key is allowed to create all given bulk transactions"
    )
    requested_types = set()
    for transaction in bulk_transaction:
        requested_types.add(transaction["txn_type"])
    # Check if allowed to create all these transactions of (potentially) various types
    if not api_key.is_key_allowed(
            "transactions",
            "create",
            "create_transaction",
            False,
            extra_data={"requested_types": requested_types}):
        raise exceptions.ActionForbidden(
            "API Key is not allowed to create all of the provided transaction types"
        )

    _log.info(
        f"[TRANSACTION_BULK] Auth successful. Attempting to enqueue {len(bulk_transaction)} transactions"
    )
    success = []
    fail = []
    pipeline = dc_redis.pipeline_sync()
    for transaction in bulk_transaction:
        try:
            txn_model = _generate_transaction_model(transaction)
            queue.enqueue_l1_pipeline(pipeline,
                                      txn_model.export_as_queue_task())
            success.append(txn_model.txn_id)
        except Exception:
            fail.append(transaction)
    # Queue the actual transactions in redis now
    for result in pipeline.execute():
        if not result:
            raise RuntimeError("Failed to enqueue")

    return {"201": success, "400": fail}
Ejemplo n.º 7
0
def update_api_key_v1(
    key_id: str,
    nickname: Optional[str] = None,
    permissions_document: Optional["permissions_doc"] = None
) -> Dict[str, Any]:
    """Updates the nickname for an existing key
    Args:
        key_id: ID of api key to update
        nickname: new nickname for the given key
    """
    if key_id.startswith("INTERCHAIN"):
        raise exceptions.ActionForbidden("Cannot modify interchain keys")
    key = api_key_dao.get_api_key(key_id, interchain=False)
    if nickname:
        key.nickname = nickname
    if permissions_document:
        # This permissions doc that was passed in should have had the schema validated by the webserver route
        key.permissions_document = permissions_document
    api_key_dao.save_api_key(key)
    return _api_key_model_to_user_dto(key)
Ejemplo n.º 8
0
def verify_request_authorization(  # noqa: C901
    authorization: str,
    http_verb: str,
    full_path: str,
    dcid: str,
    timestamp: str,
    content_type: str,
    content: bytes,
    interchain: bool,
    api_resource: str,
    api_operation: str,
    api_name: str,
) -> api_key_model.APIKeyModel:
    """Verify an http request to the webserver
    Args:
        authorization: Authorization header of the request
        http_verb: HTTP Verb of the request (i.e. GET, POST, etc)
        full_path: full path of the request after the FQDN (including any query parameters) (i.e. /chains/transaction)
        dcid: dragonchain header of the request
        timestamp: timestamp header of the request
        content-type: content-type header of the request (if it exists)
        content: byte object of the body of the request (if it exists)
        interchain: boolean whether to use interchain keys to check or not
        api_resource: the api resource name of this endpoint
        api_operation: the CRUD api operation of this endpoint ("create", "read", "update", "delete")
        api_name: the api name of this particular endpoint
    Raises:
        exceptions.UnauthorizedException (with message) when the authorization is not valid
        exceptions.ActionForbidden (with message) when the authorization is valid, but the action is not allowed
        exceptions.APIRateLimitException (with message) when rate limit is currently exceeded for the provided api key id
    Returns:
        The api key model used for this request (if successfully authenticated)
    """
    if dcid != keys.get_public_id():
        raise exceptions.UnauthorizedException("Incorrect Dragonchain ID")
    try:
        # Note, noqa for typing on re.searches are because we explicitly catch the exceptions and handle below
        version = re.search("^DC(.*)-HMAC",
                            authorization).group(1)  # noqa: T484
        if version == "1":
            hash_type = re.search("HMAC-(.*) ",
                                  authorization).group(1)  # noqa: T484
            try:
                supported_hash = get_supported_hmac_hash(hash_type)
            except ValueError:
                raise exceptions.UnauthorizedException(
                    "Unsupported HMAC Hash Type")
            # Make sure clock drift isn't too far to prevent replays
            now = get_now_datetime()
            request_time = None
            # Tolerate given timestamps both with/without decimals of a second
            if "." in timestamp:
                request_time = datetime.datetime.strptime(
                    timestamp, "%Y-%m-%dT%H:%M:%S.%fZ")
            else:
                request_time = datetime.datetime.strptime(
                    timestamp, "%Y-%m-%dT%H:%M:%SZ")
            delta = datetime.timedelta(seconds=TIMEOUT_SEC)
            # Allow all requests within +/- TIMEOUT_SEC seconds of the chain's curent time
            if now + delta < request_time or now - delta > request_time:
                raise exceptions.UnauthorizedException(
                    "Timestamp of request too skewed")
            hmac_index = authorization.rfind(":")
            if hmac_index == -1:
                raise exceptions.UnauthorizedException(
                    "Malformed Authorization Header")
            hmac = base64.b64decode(authorization[hmac_index + 1:])
            message_string = get_hmac_message_string(http_verb, full_path,
                                                     dcid, timestamp,
                                                     content_type, content,
                                                     supported_hash)
            try:
                auth_key_id = re.search(" (.*):",
                                        authorization).group(1)  # noqa: T484
                try:
                    auth_key = api_key_dao.get_api_key(auth_key_id, interchain)
                except exceptions.NotFound:
                    _log.info(
                        f"Authorization failure from key that does not exist {auth_key_id}"
                    )
                    raise exceptions.UnauthorizedException(
                        "Invalid HMAC Authentication")
                # Check if this key should be rate limited (does not apply to interchain keys)
                if not interchain and should_rate_limit(auth_key_id):
                    raise exceptions.APIRateLimitException(
                        f"API Rate Limit Exceeded. {RATE_LIMIT} requests allowed per minute."
                    )
                if crypto.compare_hmac(supported_hash, hmac, auth_key.key,
                                       message_string):
                    # Check if this signature has already been used for replay protection
                    if signature_is_replay(
                            f"{auth_key_id}:{base64.b64encode(hmac).decode('ascii')}"
                    ):
                        raise exceptions.UnauthorizedException(
                            "Previous matching request found (no replays allowed)"
                        )
                    # Check that this key is allowed to perform this action
                    try:
                        if auth_key.is_key_allowed(api_resource, api_operation,
                                                   api_name, interchain):
                            # Signature is valid and key is allowed; Return the api key used on success
                            return auth_key
                    except Exception:
                        _log.exception(
                            "Uncaught exception checking if api key is allowed"
                        )
                    raise exceptions.ActionForbidden(
                        f"This key is not allowed to perform {api_name}")
                else:
                    # HMAC doesn't match
                    raise exceptions.UnauthorizedException(
                        "Invalid HMAC Authentication")
            except exceptions.DragonchainException:
                raise
            except Exception:
                raise exceptions.UnauthorizedException("Invalid HMAC Format")
        else:
            raise exceptions.UnauthorizedException(
                "Unsupported DC Authorization Version")
    except exceptions.DragonchainException:
        raise
    except Exception:
        raise exceptions.UnauthorizedException(
            "Malformed Authorization Header")
Ejemplo n.º 9
0
 def test_webserver_error_handler_action_forbidden(self, mock_http_response, mock_report_exception):
     exception = exceptions.ActionForbidden()
     helpers.webserver_error_handler(exception)
     mock_report_exception.assert_not_called()
     mock_http_response.assert_called_once_with(403, ANY)