Python Node: How to handle a credential output port

Hi there :wave:

I’m trying to create a Python node with an output port of type knext.PortType.CREDENTIAL - similar to the existing Google Authenticator. This node authenticates with an OAuth service and passes the credentials to other nodes down the line. In order to avoid sending those in clear text, I tried to use a credential port.

However, I’m stuck at specifying the schema within the configure method. I think the knext.CredentialPortObjectSpec object [source] could be what I’m looking for. But the constructor requires a java_callback parameter which I do not know how to handle.

How do I specify the schema within the configure method and the returned data within the execute method for a credential port?

Best
Lo

P.S. Maybe this is not possible with Python, but only with a Java node. It looks like nodes written in Java can use some features which are not (yet) accessible for Python nodes.

Hey @Loo :wave:t2:

First, thanks for posting in the KNIME forum; I hope it will be the first of multiple. :writing_hand:t2:

It might be worth mentioning that a KNIME Authentication extension is already available, which allows you to authenticate via client credentials or interactive authentication.

For example, you can use the OAuth2 Authenticator node:knime:, which is very flexible. Indeed, you can use custom endpoints or specific prefilled services.

The output port of the OAuth2 Authenticator node is of type Credential. So, If it is not strictly necessary, I recommend you use this node to pass the access token (credentials) to the downstream nodes you are developing.

You can use a port port_type=knext.PortType.CREDENTIAL"as an input port, and you can access the token in both methods, the configure() and the execute() ones.

Below, I’ve included a dummy code snippet of a node that fetches my GitHub profile information. You need a GitHub account, a Client ID, and a Secret.

@knext.input_port(
    name="Input",
    description="Input port for testing",
    port_type=knext.PortType.CREDENTIAL,
)
@knext.output_port(
    name="Output",
    description="Output port for testing",
    port_type=knext.PortType.TABLE,
)
class CredentialsTest:
    """
    This node provides credentials for testing purposes.
    """

    api_endpoint = knext.StringParameter(
        label="API Endpoint",
        description="Endpoint to interact with the API.",
        default_value="https://api.github.com/user",
    )

    def configure(
        self,
        configuration_context: knext.ConfigurationContext,
        credential_port: knext.CredentialPortObjectSpec,
    ):
        """
        This node provides credentials for testing purposes.
        """
        LOGGER.warning("Configuring credentials test node.")
        LOGGER.warning(f"Port spec: {credential_port}")
        LOGGER.warning(f"Port spec type: {type(credential_port)}")
        LOGGER.warning(f"Port auth: {credential_port.auth_parameters}")
        LOGGER.warning(f"Port auth type: {type(credential_port.auth_parameters)}")
        LOGGER.warning(f"Port schema: {credential_port.auth_schema}")
        LOGGER.warning(f"Port schema type: {type(credential_port.auth_schema)}")
        pass

    def execute(
        self, exec_context: knext.ExecutionContext, credential: knext.PortObject
    ):
        """
        This node provides credentials for testing purposes.
        """
        api_url = self.api_endpoint
        access_token = credential.spec.auth_parameters
        LOGGER.warning(f"Access token: {access_token}")
        LOGGER.warning(f"inside the specs {dir(credential.spec)}")

        headers = {
            "Authorization": f"Bearer {access_token}",
            "Accept": "application/vnd.github.v3+json",
        }

        response = requests.get(api_url, headers=headers)

        profile_data = response.json()

        # Create a pandas DataFrame to store the profile data
        data = {
            "Username": [profile_data["login"]],
            "Name": [profile_data.get("name", "No name available")],
            "Public Repos": [profile_data["public_repos"]],
            "Followers": [profile_data["followers"]],
            "Following": [profile_data["following"]],
            "Bio": [profile_data.get("bio", "No bio available")],
        }

        # Convert the dictionary to a DataFrame
        df = pd.DataFrame(data)

        # Display the DataFrame
        return knext.Table.from_pandas(df)

Below is the screenshot of the workflow that is using the node:

I don’t know which kind of OAuth service are you trying to authenticate and if you need to refresh the token, in case we can check how to do it.

BR,

5 Likes

Hi @diego_rod_lop

Thank you for your detailed reply and for explaining how to use an input port of type Credential; I appreciate your effort.

However, is creating an output port of type Credential in a Python node possible?

While experimenting, I already managed to forward an input port to an output port (both of type Credential). But I’m clueless about how to create my own custom Credential output port.

Best
Lo

Hi @Loo

Can you elaborate on why you want to create a custom credential output port in Python?

I think currently this port type only works in the direction Java → Python and cannot be created in Python, but I’ll double check.

Best,
Carsten

Hi @carstenhaubold

Thank you for your answer and for clarifying that this feature is available for Java but not for Python nodes.

I’m sorry for the delay it getting back to you!

I asked because I’m working on a set of nodes that need to pass custom OAuth credentials between them. From what I understand, port contents are typically saved as “clear text” in a workflow file. However, it seems that ports of type knext.PortType.CREDENTIAL might be an exception, as I remember reading that these ports are handled differently in terms of security.

Can you share a bit more about how security is enhanced for ports of type knext.PortType.CREDENTIAL? I’m curious to understand how they’re handled differently from other port types.

Best
Lo

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.