Messaging Service

The PubSub+ Messaging API for Python provides the MessagingService class, which makes it easy to connect to an event broker. The MessagingService class handles all the functionality for interacting with a PubSub+ event broker. To create a MessagingService instance, you must first configure a properties dictionary with the information required to establish a connection to the event broker, including the host details and the authentication scheme.

Creating a Property Dictionary

To connect to an event broker, you need to configure service properties in a dictionary. A properties dictionary can have a number of properties, however it must contain the host and vpn-name keys. The broker properties dictionary is passed to the MessagingServiceClientBuilder to configure the connection to the event broker. There are five categories of properties that can be configured in a broker properties dictionary:

  • solace.messaging.config.solace_properties.service_properties (required for the vpn-name property)
  • solace.messaging.config.solace_properties.transport_layer_properties (required for host property )
  • solace.messaging.config.solace_properties.authentication_properties
  • solace.messaging.config.solace_properties.client_properties
  • solace.messaging.config.solace_properties.transport_layer_security_properties

For more information, see the PubSub+ Messaging API for Python reference.

The code below shows an example of a broker properties dictionary used for establishing a connection to an event broker using basic authentication:

from solace.messaging.config.solace_properties import transport_layer_properties, service_properties, authentication_properties
# ...
broker_props = {
    transport_layer_properties.HOST: "tcps://messaging.solace.cloud:55443",
    service_properties.VPN_NAME: "my_VPN",
    authentication_properties.SCHEME_BASIC_USER_NAME: "my_username",
    authentication_properties.SCHEME_BASIC_PASSWORD: "my_password",
    }

Alternatively if you're running samples, the configuration you want to use can be passed into the client application via a JSON file. The following sample code shows how you can use the json.loads() function to parse JSON data and use it to create a broker properties dictionary:

import json
# ...
# Read the JSON file
with open('/path/to/config.json') as file:
       json_data = file.read()

# Unmarshal JSON into a dictionary
configDict = json.loads(json_data)

# Use from_properties(configDict) to use JSON data when creating your MessagingService instance
messaging_service = MessagingService.builder().from_properties(configDict).build()

For a more detailed example, see the read_config() function in sampler_boot.py in the Solace GitHub repository.

Establishing a Connection to an Event Broker

A MessagingService object allows the API to establish a connection to the event broker. To create a MessagingService object, do the following:

  1. Call the MessagingService class' builder() function to return a MessagingServiceClientBuilder object.

  2. The MessagingServiceClientBuilder object gives you access to a number of functions that let you customize a MessagingService object. These include the following:

    • from_properties(configuration: dict)—Pass the necessary broker properties dictionary to the MessagingServiceClientBuilder.
    • with_authentication_strategy(authentication_strategy: AuthenticationStrategy)
    • with_transport_security_strategy(transport_layer_security_strategy: TransportSecurityStrategy)
    • with_reconnection_retry_strategy(strategy: RetryStrategy)
  3. Call the build() function on the MessagingServiceClientBuilder object to return a MessagingService object.

  4. Call the connect() function on your MessagingService object to connect to the event broker.

For more information, see the PubSub+ Messaging API for Python reference.

The following sample code shows how to create a simple MessagingService instance and connect it to an event broker:

# Build A messaging service with a reconnection strategy of 20 retries over an interval of 3 seconds
messaging_service = MessagingService.builder().from_properties(broker_props)\
    .with_reconnection_retry_strategy(RetryStrategy.parametrized_retry(20, 3)) \
    .with_authentication_strategy(BasicUserNamePassword.of("my_username", "my_password")) \
    .build()

# Blocking connect thread
messaging_service.connect()	

To sever a MessagingService object's connection to the event broker, call disconnect() on it. This also makes the MessagingService object eligible for garbage collection.

Using Transport Layer Security

Transport Layer Security (TLS) allows for encrypted authentication and data transmission between the PubSub+ Python API and a PubSub+ event broker. The Python API supports Transport Layer Security versions. The versions available are TLS 1.1, and TLS 1.2 . The recommended version to use is the most recent version of TLS.

You can use the with_transport_security_strategy() function with the config package to configure the TLS connection properties to use, or whether or not to disable certificate validation entirely. When you use TLS, you must always use the secure TCP  protocol (tcpsor https) in setting the host property for your connection, for example:

broker_props = {
    solace.messaging.config.solace_properties.transport_layer_properties.HOST: "tcps://messaging.solace.cloud:55443",
    solace.messaging.config.solace_properties.service_properties.VPN_NAME: "myVPN",
}

We recommend using certificate validation when configuring your messaging service. The Python API's config module provides the following functions for configuring certificate validation:

  • with_certificate_validation(ignore_expiration: bool, validate_server_name: bool, trust_store_file_path: str, trusted_common_name_list: str)

    • ignore_expiration— When set to true, expired certificates are accepted. This is not recommended because it poses a security risk.
    • validate_server_name— When set to true, the default setting, certificates without the matching host are not accepted.
    • trust_store_file_path— The location of the trust store files. If an empty string is passed, no file path will be set.
    • trusted_common_name_list— A comma-separated list of acceptable common names for matching with server certificates. An empty string will match no names. Modern TLS implementations confirm the subject alternative name in the server certificate, so it is usually unnecessary to set this parameter. If your server certificate cannot be upgraded appropriately, you may use trusted_common_name_list for additional validation of the server certificate.

     

    The following sample code shows the recommended security setup for client applications when you use TLS:

    # Creates a transport security strategy with validation on certificates that excludes dated TLS/SSL protocols			
    transport_security = TLS.create().with_certificate_validation(True, False, "./trusted-store", "") \
                    .with_excluded_protocols(TLS.SecureProtocols.TLSv1,
                                             TLS.SecureProtocols.TLSv1_1,
                                             TLS.SecureProtocols.SSLv3) 
    			
    # Build A messaging service
    messaging_service = MessagingService.builder().from_properties(broker_props)\
                   .with_authentication_strategy(BasicUserNamePassword.of("username", "password")) \
                   .with_transport_security_strategy(transport_security) \
                   .build()	
    
  • without_certificate_validation()—This function configures your TLS connection not to validate server certificates.

    Only use without_certificate_validation() in development environments. We recommend that you never use this function in production environments because it creates a security vulnerability.

    The following sample code shows how to use the without_certificate_validation() function:

    # Creates a transport security strategy without validation on certificates			
    transport_security = TLS.create().without_certificate_validation()
    			
    # Build a messaging service
    messaging_service = MessagingService.builder().from_properties(broker_props)\
                   .with_authentication_strategy(BasicUserNamePassword.of("username", "password")) \
                   .with_transport_security_strategy(transport_security) \
                   .build()						

You can also configure the aspects of the TLS connection using transport_layer_security_properties in a properties dictionary. The TLS connection can be configured using various fields found in solace.messaging.config.solace_properties.transport_layer_security_properties. We recommend that you use the default settings (set to true and enabled) to ensure secure connections for the following properties:

  • solace.messaging.config.solace_properties.transport_layer_security_properties.CERT_REJECT_EXPIRED
  • solace.messaging.config.solace_properties.transport_layer_security_properties.CERT_VALIDATE_SERVERNAME
  • solace.messaging.config.solace_properties.transport_layer_security_properties.CERT_VALIDATED

For more information, see the PubSub+ Messaging API for Python reference.

Authentication

The PubSub+ Messing API for Python supports a number of authentication schemes (or strategies). that you can choose from. The scheme that you choose may depend on the credentials that the connecting client is required to provide. You can use one of the following authentication schemes:

Basic Authentication

Basic authentication is the default client authentication scheme which allows a client to authenticate with an event broker using a client username and password. To specify basic authentication, create an instance of a MessagingService and specify the following as the parameter for the with_authentication_strategy() function:

  • BasicUserNamePassword.of(username: str, password: str)
    • username— The user name to use to create a BasicUserNamePassword object.
    • password—The password to use to create a BasicUserNamePassword object.

For more information, see the PubSub+ Messaging API for Python reference.

The following sample code shows how to use basic authentication:

# Build a messaging service
messaging_service = MessagingService.builder().from_properties(broker_props)\
               .with_authentication_strategy(BasicUserNamePassword.of("username", "password")) \
               .build()	

Kerberos Authentication

The Python API provides support for Kerberos Authentication. Connecting using this function requires you to load a Kerberos Keytab on the broker (see Managing Event Broker Files) and Kerberos authentication must be configured and enabled for any Message VPNs that Kerberos-authenticated clients connect to.

. Call the with_authentication_strategy() function and pass the following as the parameters:

  • Kerberos.of(service_name: str)
    • service_name—A valid Kerberos service name.

For more information, see the PubSub+ Messaging API for Python reference.

The following sample code shows how to use Kerberos for authentication:

# Build a messaging service
messaging_service = MessagingService.builder().from_properties(broker_props)\
               .with_authentication_strategy(Kerberos.of("client_kerberos_service_name")) \
               .build()	

Client Certificate Authentication

To use the Client certificate authentication scheme, the following steps are required:

  1. Configure the host event broker to use TLS connections (see Using Transport Layer Security).

  2. Your application must connect to the broker using TLS.

  3. Enable Client certificate verification on the Message VPN that the application uses to connect.

  4. The client-side certificate must be present in a keystore file and configured using the following:

    • ClientCertificateAuthentication.of(certificate_file: str, key_file: str, key_password: str)

      • certificate_file—The file that contains the client certificate or the client-certificate chain.
      • key_file—The file contains the client private key.
      • key_password—The password if the private key (key_file) is password protected.

For more information, see the PubSub+ Messaging API for Python reference.

The following sample code shows how to configure client certificate authentication:

# Build a messaging service
messaging_service = MessagingService.builder().from_properties(broker_props)\
               .with_authentication_strategy(ClientCertificateAuthentication.of(certificate_file,
                                                                                key_file, 
                                                                                key_password)) \
               .build()	

OAuth 2.0 Authentication

OAuth 2.0 is an open standard for access delegation and authorization. It is commonly used as a mechanism to grant websites or applications access to users' information on other websites without giving them access to sensitive credentials. The OAuth authentication scheme allows access through the use of tokens issued to third-party clients by an authorization server that provides access to Message VPNs on PubSub+ event brokers. To use OAuth 2.0 authentication, configure the host event broker to use TLS connections (see Using Transport Layer Security) and make sure your application connects to the event broker using TLS. For more information, see OAuth Authentication.

The Python API supports different fields that can be sent to the event broker:

  • access_token—A String for applications to make requests for data access.

    and/or

    oidc_id_token—A String for Open ID Connect (OIDC) connections.

  • issuer_identifier—(Optional) A String to identify the appropriate OAuth provider configuration.

OAuth authentication requires an access_token, an oidc_id_token, or both to be enabled using this function:

  • OAuth2.of(access_token: str, oidc_id_token: str, issuer_identifier: str)
    • At least one of access_token or oidc_id_token must be provided. Optionally, issuer_identifier can be provided. If any of the parameters is not required, None can be passed.

For more information, see the PubSub+ Messaging API for Python reference.

The following sample code shows how to use OAuth authentication with OpenID Connect (OIDC):

# Configure service access to use a Open ID connect authentication with an ID token and an optional access token.
messaging_service = MessagingService.builder().from_properties(broker_props)\
               .with_authentication_strategy(OAuth2.of("my_access_token", 
                                                       "my_oidc_token",
                                                       None)) \
               .with_transport_security_strategy(transport_security) \
               .build()	
# Configure service access to use OAuth 2 authentication with an access token and an optional issuer identifier.
messaging_service = MessagingService.builder().from_properties(broker_props)\
               .with_authentication_strategy(OAuth2.of("my_access_token",
                                                       None, 
                                                       "my_issuer_identifier")) \
               .with_transport_security_strategy(transport_security) \
               .build()	

Required Event Broker Configurations

For a client application to use an OAuth authentication scheme, a Message VPN OAuth profile must be configured for the host event broker and OAuth authentication must be configured and enabled for any Message VPNs to which a client connects. For more information, see OAuth Authentication.

Refreshing Expired OAuth Tokens

By default, event brokers disconnect clients when their tokens expire (see Disconnect on Token Expiration). When a client session is disconnected, the client application tries to reconnect a number of times using the same OAuth token based on the RECONNECTION_ATTEMPTS property. If the reconnection attempts are exceeded, then the connection cannot be re-established and the client application must recreate the session with all its subscriptions.

To avoid needing to destroy and recreate the session, the client should update the OAuth token before it expires, or while reconnecting. To update the OAuth token, use the update_property(name: str, value: Any) function, which allows you to set a modifiable service property after the creation of the MessagingService object. The first parameter is one of the following strings and the second parameter is the token:

  • messaging_service.update_property(authentication_properties.SCHEME_OAUTH2_ACCESS_TOKEN, new_access_token) to update an expired access token
  • messaging_service.update_property(authentication_properties.SCHEME_OAUTH2_OIDC_ID_TOKEN, new_id_token) to update an expired ID token

Modifiable service properties may not update immediately and may require the next reconnection attempt to update.

Refreshing the expired token can happen while:

  • the client application is connected. In this case, the client contacts the authentication server to refresh the token and modifies the session to use the updated token the next time the API connects to the event broker.

  • the client application is reconnecting. The reconnecting event includes a diagnostic subCode. If this subCode is Login Failure, this may indicate that your token has expired. In this case, the API tries to reconnect (using the expired token). The client then contacts the authentication server to refresh the token and modifies the session to use the updated token the next time the API attempts to reconnect to the event broker.

In general, it is better if the client application is aware of potential token expiry and refreshes the token before it expires.

When the client application's session is reconnected, the Python API re-applies the client application's direct subscriptions. If there is a change in the ACLs as a result of the refreshed token, the subscriptions may be rejected by the event broker.