Exemple #1
0
class Signer:
    def __init__(self, net_id):
        self.net_id = net_id
        self.lambda_client = boto3.client("lambda", region_name=REGION_NAME)
        self.obj_utils = Utils()
        self.obj_blockchain_utils = BlockChainUtil(
            provider_type="HTTP_PROVIDER",
            provider=NETWORKS[self.net_id]["http_provider"],
        )
        self.mpe_address = self.obj_blockchain_utils.read_contract_address(
            net_id=self.net_id, path=MPE_ADDR_PATH, key="address")
        self.current_block_no = self.obj_blockchain_utils.get_current_block_no(
        )

    def _get_free_calls_allowed(self, org_id, service_id, group_id):
        lambda_payload = {
            "httpMethod": "GET",
            "pathParameters": {
                "orgId": org_id,
                "serviceId": service_id
            },
        }
        response = self.lambda_client.invoke(
            FunctionName=
            GET_SERVICE_DETAILS_FOR_GIVEN_ORG_ID_AND_SERVICE_ID_ARN,
            InvocationType="RequestResponse",
            Payload=json.dumps(lambda_payload),
        )
        response_body_raw = json.loads(response.get("Payload").read())["body"]
        get_service_response = json.loads(response_body_raw)
        if get_service_response["status"] == "success":
            groups_data = get_service_response["data"].get("groups", [])
            for group_data in groups_data:
                if group_data["group_id"] == group_id:
                    return group_data["free_calls"]
        raise Exception(
            "Unable to fetch free calls information for service %s under organization %s for %s group.",
            service_id, org_id, group_id)

    def _get_total_calls_made(self, username, org_id, service_id, group_id):
        lambda_payload = {
            "httpMethod": "GET",
            "queryStringParameters": {
                "organization_id": org_id,
                "service_id": service_id,
                "username": username,
                "group_id": group_id,
            },
        }
        response = self.lambda_client.invoke(
            FunctionName=METERING_ARN,
            InvocationType="RequestResponse",
            Payload=json.dumps(lambda_payload),
        )
        if response["StatusCode"] == 200:
            metering_data_raw = json.loads(
                response.get("Payload").read())["body"]
            total_calls_made = json.loads(metering_data_raw).get(
                "total_calls_made", None)
            if total_calls_made is not None:
                return total_calls_made
        raise Exception(
            "Unable to fetch total calls made for service %s under organization %s for %s group.",
            service_id, org_id, group_id)

    def _get_no_of_free_call_available(self, username, org_id, service_id,
                                       group_id):

        token_to_get_free_call, expiry_date_block, signature, current_block_number, daemon_endpoint, free_calls_allowed = self.token_to_get_free_call(
            username, org_id, service_id, group_id)
        total_free_call_available = 0
        try:

            total_free_call_available = self._get_no_of_free_calls_from_daemon(
                username, token_to_get_free_call, expiry_date_block, signature,
                current_block_number, daemon_endpoint)
        except Exception as e:
            logger.info(
                f"Free call from daemon not available switching to metering {org_id} {service_id} {group_id} {username}"
            )
            total_free_call_made = self._get_total_calls_made(
                username, org_id, service_id, group_id)

            total_free_call_available = free_calls_allowed - total_free_call_made

        return total_free_call_available

    def _free_calls_allowed(self, username, org_id, service_id, group_id):
        """
            Method to check free calls exists for given user or not.
            Call monitoring service to get the details
        """

        free_calls_available = self._get_no_of_free_call_available(
            username, org_id, service_id, group_id)
        if free_calls_available > 0:
            return True

        return False

    def signature_for_free_call(self, user_data, org_id, service_id, group_id):
        """
            Method to generate signature for free call.
        """
        try:
            username = user_data["authorizer"]["claims"]["email"]
            if self._free_calls_allowed(username=username,
                                        org_id=org_id,
                                        service_id=service_id,
                                        group_id=group_id):
                current_block_no = self.obj_utils.get_current_block_no(
                    ws_provider=NETWORKS[self.net_id]["ws_provider"])
                provider = Web3.HTTPProvider(
                    NETWORKS[self.net_id]["http_provider"])
                w3 = Web3(provider)
                message = web3.Web3.soliditySha3(
                    ["string", "string", "string", "string", "uint256"],
                    [
                        PREFIX_FREE_CALL, username, org_id, service_id,
                        current_block_no
                    ],
                )
                signer_key = SIGNER_KEY
                if not signer_key.startswith("0x"):
                    signer_key = "0x" + signer_key
                signature = bytes(
                    w3.eth.account.signHash(defunct_hash_message(message),
                                            signer_key).signature)
                signature = signature.hex()
                if not signature.startswith("0x"):
                    signature = "0x" + signature
                return {
                    "snet-free-call-user-id": username,
                    "snet-payment-channel-signature-bin": signature,
                    "snet-current-block-number": current_block_no,
                    "snet-payment-type": "free-call",
                    "snet-free-call-auth-token-bin": "",
                    "snet-free-call-token-expiry-block": 0
                }
            else:
                raise Exception("Free calls expired for username %s.",
                                username)
        except Exception as e:
            logger.error(repr(e))
            raise e

    def signature_for_regular_call(self, user_data, channel_id, nonce, amount):
        """
            Method to generate signature for regular call.
        """
        try:
            username = user_data["authorizer"]["claims"]["email"]
            data_types = ["string", "address", "uint256", "uint256", "uint256"]
            values = [
                "__MPE_claim_message",
                self.mpe_address,
                channel_id,
                nonce,
                amount,
            ]
            signature = self.obj_blockchain_utils.generate_signature(
                data_types=data_types, values=values, signer_key=SIGNER_KEY)
            return {
                "snet-payment-channel-signature-bin": signature,
                "snet-payment-type": "escrow",
                "snet-payment-channel-id": channel_id,
                "snet-payment-channel-nonce": nonce,
                "snet-payment-channel-amount": amount,
                "snet-current-block-number": self.current_block_no,
            }
        except Exception as e:
            logger.error(repr(e))
            raise Exception(
                "Unable to generate signature for daemon call for username %s",
                username)

    def signature_for_state_service(self, user_data, channel_id):
        """
            Method to generate signature for state service.
        """
        try:
            username = user_data["authorizer"]["claims"]["email"]
            data_types = ["string", "address", "uint256", "uint256"]
            values = [
                "__get_channel_state",
                self.mpe_address,
                channel_id,
                self.current_block_no,
            ]
            signature = self.obj_blockchain_utils.generate_signature(
                data_types=data_types, values=values, signer_key=SIGNER_KEY)
            return {
                "signature": signature,
                "snet-current-block-number": self.current_block_no,
            }
        except Exception as e:
            logger.error(repr(e))
            raise Exception(
                "Unable to generate signature for daemon call for username %s",
                username)

    def signature_for_open_channel_for_third_party(self, recipient, group_id,
                                                   amount_in_cogs, expiration,
                                                   message_nonce,
                                                   sender_private_key,
                                                   executor_wallet_address):
        data_types = [
            "string", "address", "address", "address", "address", "bytes32",
            "uint256", "uint256", "uint256"
        ]
        values = [
            "__openChannelByThirdParty", self.mpe_address,
            executor_wallet_address, SIGNER_ADDRESS, recipient, group_id,
            amount_in_cogs, expiration, message_nonce
        ]
        signature = self.obj_blockchain_utils.generate_signature(
            data_types=data_types,
            values=values,
            signer_key=sender_private_key)
        v, r, s = Web3.toInt(
            hexstr="0x" +
            signature[-2:]), signature[:66], "0x" + signature[66:130]
        return {"r": r, "s": s, "v": v, "signature": signature}

    def _get_no_of_free_calls_from_daemon(self, email, token_to_get_free_call,
                                          expiry_date_block, signature,
                                          current_block_number,
                                          daemon_endpoint):

        request = state_service_pb2.FreeCallStateRequest()
        request.user_id = email
        request.token_for_free_call = token_to_get_free_call
        request.token_expiry_date_block = expiry_date_block
        request.signature = signature
        request.current_block = current_block_number

        endpoint_object = urlparse(daemon_endpoint)
        if endpoint_object.port is not None:
            channel_endpoint = endpoint_object.hostname + ":" + str(
                endpoint_object.port)
        else:
            channel_endpoint = endpoint_object.hostname

        if endpoint_object.scheme == "http":
            channel = grpc.insecure_channel(channel_endpoint)
        elif endpoint_object.scheme == "https":
            channel = grpc.secure_channel(channel_endpoint,
                                          grpc.ssl_channel_credentials())
        else:
            raise ValueError(
                'Unsupported scheme in service metadata ("{}")'.format(
                    endpoint_object.scheme))

        stub = state_service_pb2_grpc.FreeCallStateServiceStub(channel)
        response = stub.GetFreeCallsAvailable(request)

        return response.free_calls_available

    def _is_free_call_available(self, email, token_for_free_call,
                                expiry_date_block, signature,
                                current_block_number, daemon_endpoint):
        if self._get_no_of_free_calls_from_daemon(
                email, token_for_free_call, expiry_date_block, signature,
                current_block_number, daemon_endpoint) > 0:
            return True
        return False

    def _get_daemon_endpoint_and_free_call_for_group(self, org_id, service_id,
                                                     group_id):
        lambda_payload = {
            "httpMethod": "GET",
            "pathParameters": {
                "orgId": org_id,
                "serviceId": service_id
            },
        }
        response = self.lambda_client.invoke(
            FunctionName=
            GET_SERVICE_DETAILS_FOR_GIVEN_ORG_ID_AND_SERVICE_ID_ARN,
            InvocationType="RequestResponse",
            Payload=json.dumps(lambda_payload),
        )
        response_body_raw = json.loads(response.get("Payload").read())["body"]
        get_service_response = json.loads(response_body_raw)
        if get_service_response["status"] == "success":
            groups_data = get_service_response["data"].get("groups", [])
            for group_data in groups_data:
                if group_data["group_id"] == group_id:
                    return group_data["endpoints"][0][
                        "endpoint"], group_data.get("free_calls", 0)
        raise Exception(
            "Unable to fetch daemon Endpoint information for service %s under organization %s for %s group.",
            service_id, org_id, group_id)

    def token_to_get_free_call(self, email, org_id, service_id, group_id):
        signer_public_key_checksum = Web3.toChecksumAddress(SIGNER_ADDRESS)
        current_block_number = self.obj_blockchain_utils.get_current_block_no()
        expiry_date_block = current_block_number + FREE_CALL_EXPIRY
        token_to_get_free_call = self.obj_blockchain_utils.generate_signature_bytes(
            ["string", "address", "uint256"],
            [email, signer_public_key_checksum, expiry_date_block], SIGNER_KEY)

        signature = self.obj_blockchain_utils.generate_signature_bytes([
            "string", "string", "string", "string", "string", "uint256",
            "bytes32"
        ], [
            "__prefix_free_trial", email, org_id, service_id, group_id,
            current_block_number, token_to_get_free_call
        ], SIGNER_KEY)

        daemon_endpoint, free_calls_allowed = self._get_daemon_endpoint_and_free_call_for_group(
            org_id, service_id, group_id)
        logger.info(
            f"Got daemon endpoint {daemon_endpoint} for org {org_id} service {service_id} group {group_id}"
        )

        return token_to_get_free_call, expiry_date_block, signature, current_block_number, daemon_endpoint, free_calls_allowed

    def token_to_make_free_call(self, email, org_id, service_id, group_id,
                                user_public_key):
        token_to_get_free_call, expiry_date_block, signature, current_block_number, daemon_endpoint, free_calls_allowed = self.token_to_get_free_call(
            email, org_id, service_id, group_id)

        token_with_expiry_to_make_free_call = ""
        if self._is_free_call_available(email, token_to_get_free_call,
                                        expiry_date_block, signature,
                                        current_block_number, daemon_endpoint):
            token_with_expiry_to_make_free_call = self.obj_blockchain_utils.generate_signature_bytes(
                ["string", "address", "uint256"], [
                    email,
                    Web3.toChecksumAddress(user_public_key), expiry_date_block
                ], SIGNER_KEY)

        return {
            "token_to_make_free_call":
            token_with_expiry_to_make_free_call.hex(),
            "token_expiration_block": expiry_date_block
        }
class Signer:
    def __init__(self, obj_repo, net_id):
        self.repo = obj_repo
        self.net_id = net_id
        self.lambda_client = boto3.client('lambda')
        self.obj_utils = Utils()

    def _free_calls_allowed(self, username, org_id, service_id):
        """
            Method to check free calls exists for given user or not.
            Call monitoring service to get the details
        """
        try:
            lambda_payload = {"httpMethod": "GET",
                              "queryStringParameters": {"organization_id": org_id, "service_id": service_id,
                                                            "username": username}}


            response = self.lambda_client.invoke(FunctionName=GET_FREE_CALLS_METERING_ARN, InvocationType='RequestResponse',
                                                 Payload=json.dumps(lambda_payload))
            response_body_raw = json.loads(
                response.get('Payload').read())['body']
            response_body = json.loads(response_body_raw)
            free_calls_allowed = response_body["free_calls_allowed"]
            total_calls_made = response_body["total_calls_made"]
            is_free_calls_allowed = True if (
                (free_calls_allowed - total_calls_made) > 0) else False
            return is_free_calls_allowed
        except Exception as e:
            print(repr(e))
            raise e

    def signature_for_free_call(self, user_data, org_id, service_id):
        """
            Method to generate signature for free call.
        """
        try:
            username = user_data['authorizer']['claims']['email']
            if self._free_calls_allowed(username=username, org_id=org_id, service_id=service_id):
                current_block_no = self.obj_utils.get_current_block_no(
                    ws_provider=NETWORKS[self.net_id]['ws_provider'])
                provider = Web3.HTTPProvider(
                    NETWORKS[self.net_id]['http_provider'])
                w3 = Web3(provider)
                message = web3.Web3.soliditySha3(["string", "string", "string", "string", "uint256"],
                                                 [PREFIX_FREE_CALL, username, org_id, service_id, current_block_no])
                if not config['private_key'].startswith("0x"):
                    config['private_key'] = "0x" + config['private_key']
                signature = bytes(w3.eth.account.signHash(
                    defunct_hash_message(message), config['private_key']).signature)
                signature = signature.hex()
                if not signature.startswith("0x"):
                    signature = "0x" + signature
                return {"snet-free-call-user-id": username, "snet-payment-channel-signature-bin": signature,
                        "snet-current-block-number": current_block_no, "snet-payment-type": "free-call"}
            else:
                raise Exception(
                    "Free calls expired for username %s.", username)
        except Exception as e:
            print(repr(e))
            raise e

    def _get_user_address(self, username):
        try:
            wallet_address_data = self.repo.execute("SELECT address FROM wallet WHERE username = %s AND status = 1 "
                                                    "LIMIT 1", [username])
            if len(wallet_address_data) == 1:
                return wallet_address_data[0]['address']
            raise Exception(
                "Unable to find wallet address for username %s", username)
        except Exception as e:
            print(repr(e))
            raise e

    def _get_service_metadata(self, org_id, service_id):
        """
            Method to get group details for given org_id and service_id.
        """
        try:
            result = self.repo.execute(
                "SELECT O.*, G.* FROM org_group O, service_group G WHERE O.org_id = G.org_id AND G.group_id = "
                "O.group_id AND G.service_id = %s AND G.org_id = %s AND G.service_row_id IN (SELECT row_id FROM service "
                "WHERE is_curated = 1 ) LIMIT 1", [org_id, service_id])
            if len(result) == 1:
                metadata = result[0]
                payment = json.loads(metadata["payment"])
                pricing = json.loads(metadata["pricing"])
                metadata.update(payment)
                metadata["pricing"] = pricing
                return metadata
            else:
                raise Exception(
                    "Unable to find service for service_id %s", service_id)
        except Exception as e:
            print(repr(e))
            raise e

    def _get_channel_id(self, sender_address, recipient_address, group_id, signer_address):
        """
            Method to fetch channel id from mpe_channel(RDS).
        """
        try:
            channel_data = self.repo.execute(
                "SELECT channel_id FROM mpe_channel WHERE sender = %s AND recipient = %s AND "
                " groupId = %s AND signer = %s LIMIT 1", [sender_address, recipient_address,
                                                          group_id, signer_address])
            if len(channel_data) == 1:
                return channel_data[0]["channel_id"]
            raise Exception("Unable to find channels.")
        except Exception as e:
            print(repr(e))
            raise e

    def signature_for_regular_call(self, user_data, org_id, service_id):
        """
            Method to generate signature for regular call.
        """
        try:
            username = user_data['authorizer']['claims']['email']
            user_address = self._get_user_address(username=username)
            metadata = self._get_service_metadata(
                org_id=org_id, service_id=service_id)
            recipient_address = metadata['payment']['payment_address']
            channel_id = self._get_channel_id(sender_address=user_address, recipient_address=recipient_address,
                                              group_id=metadata['group_id'], signer_address=config["signer_address"])
            object_service_client = ServiceClient(
                config=config, metadata=metadata, options=dict())
            return object_service_client.get_service_call_metadata(channel_id=channel_id)
        except Exception as e:
            print(repr(e))
            raise Exception(
                "Unable to sign regular call for username %s", username)