Learn how to put your digital team to work with MuleSoft for Agentforce.
Contact Us 1-800-596-4880

Using Contracts Validation Library Functions

Contracts between API instances and applications give the application access to the instance based on SLA tiers. Client applications can validate their contracts by providing credentials that consist of the client ID and client secret. The PDK Contracts Validation library provides functions to validate client credentials and implement a custom client ID enforcement policy.

Only one contract at a time can exist between an API instance and an application.

To view an example policy project that uses Flex Gateway Policy Development Kit (PDK) Contracts Validation library, see Client ID Enforcement Policy ExampleLeaving the Site.

Extract Client Credentials

The Contracts Validation library is available from the pdk::contracts module.

The module provides the ContractValidator object to validate client credentials represented by the ClientId and ClientSecret objects. Validate authentication and authorization credentials with the ContractValidator::authenticate() and ContractValidator::authorize() methods:

impl ContractValidator {
   pub fn authenticate(client_id: &ClientId, client_secret: &ClientSecret) -> Result<ClientData, AuthenticationError>;
   pub fn authorize(client_id: &ClientId, client_secret: &ClientSecret) -> Result<ClientData, AuthorizationError>;
}
Rust

Both methods return a ClientData struct that holds the client ID, client name, and SLA ID fields:

pub struct ClientData {
    pub client_id: String,
    pub client_name: String,
    pub sla_id: Option<String>,
}
Rust

Validate Request Authentication and Authorization Headers

Authenticate a request by extracting the client credentials from the request and then invoke the ClientValidation::authenticate() method. The pdk::contracts module provides the basic_auth_credentials() helper function to extract Basic Authentication credentials from request headers:

pub fn basic_auth_credentials(request_headers_state: &RequestHeadersState)
    -> Result<(ClientId, ClientSecret), BasicAuthError>;
Rust

This code snippet provides a simple authentication-request filter:

This code snippet provides a simple authorization-request filter:

async fn my_authorization_filter(
  state: RequestHeadersState,
  validator: &ContractValidator,
) -> Flow<()> {

    // Extract client id with a user defined helper `extract_client_id()` function,
    let client_id = match extract_client_id(&state) {
        Ok(credentials) => credentials,
        Err(e) => {
            logger::info!("Invalid credentials: {e}");

            // For simplicity, we are using a user-defined `unathorized_response()`
            // helper function for building responses.
            return Flow::Break(unauthorized_response("Invalid credentials", 401));
        }
    };

    // Validate authorization
    let validation = validator.authorize(&client_id);

    if let  Err(e) = validation {
        logger::info!("Invalid authentication: {e}");
        return Flow::Break(unauthorized_response("Invalid authentication", 403));
    }

    Flow::Continue(())
}
Rust

Implement Custom Extraction for Client Credentials

Invoke the ClientId::new() and ClientSecret::new() methods to extract credentials:

impl ClientId {
   pub fn new(client_id: String) -> Self;
}

impl ClientSecret {
   pub fn new(client_secret: String) -> Self;
}
Rust

After extracting the credentials, initialize a set of the credentials:

fn initialize_credentials(raw_client_id: String, raw_client_secret) -> (ClientId, ClientSecret) {
   let client_id = ClientId::new(raw_client_id);
   let client_secret = ClientSecret::new(raw_client_secret);

   (client_id, client_secret)
}
Rust
For security, the content of ClientSecret is deleted after use, and the Debug trait doesn’t reveal the ClientSecret content.

Inject the ContractValidator Object

Inject the ContractValidator object into the #[entrypoint] function and share its reference to the request filter:

#[entrypoint]
async fn configure(launcher: Launcher, validator: ContractValidator) -> Result<(), LaunchError> {

    let filter = on_request(|state, authentication| my_authentication_filter(state, authentication, &validator));
    launcher.launch(filter).await?;

    OK(())
}
Rust

Poll the Local Contracts Database

The ContractValidator object maintains a local copy of the contracts database. You must poll the local copy periodically to maintain a valid copy of the contracts. Invoke the ContractValidator::update_contracts() method to poll the local copy:

impl ContractValidator {
    pub async fn update_contracts(&self) -> Result<(), UpdateError>;
}
Rust

If the ContractValidator::update_contracts() method returns an error that specifies a connectivity problem occurred during the contracts database polling, you must implement the retry and error handling heuristics. To do this, invoke ContractValidator::update_contracts() in a period specified by the ContractValidator::UPDATE_PERIOD constant:

async fn update_my_contracts(validator: &ContractValidator, clock: Clock) {

    // Configure a new timer
    let timer = clock.period(ContractValidator::UPDATE_PERIOD);

    loop {
       // Update result handling should be customized by the programmer
       let update_result = validator.update_contracts().await;

       // Wait for the next tick
       if !timer.next_tick().await {
           // If no more ticks are available, finish the task.
           return;
       }
    }
}
Rust

The contract database initialization period specifies when to poll the contracts database at a higher frequency than update period. The ContractValidator::INITIALIZATION_PERIOD specifies the interval required by the initial polling:

async fn initialize_my_contracts(validator: &ContractValidator, clock: Clock) {

    // Configure a new timer
    let timer = clock.period(ContractValidator::UPDATE_PERIOD);

    loop {
       // Update result handling should be customized by the programmer
       let update_result = validator.update_contracts().await;

       // Wait for the next tick
       if !timer.next_tick().await {
           // If no more ticks are available, finish the task.
           return;
       }
    }
}
Rust

This snippet shows a complete contract polling task with initialization and update polling periods:

async fn update_my_contracts(validator: &ContractValidator, clock: Clock) {
    let initialization_timer = clock.period(ContractValidator::INITIALIZATION_PERIOD);

    loop {
        if validator.update_contracts().await.is_ok() {
            logger::info!("Contracts storage initialized.");
            break;
        }

        if !initialization_timer.next_tick().await {
            logger::info!("Tick event suspended.");
            break;
        }
 }

    let update_timer = initialization_timer
        .release()
        .period(ContractValidator::UPDATE_PERIOD);

   loop {
        let _ = validator.update_contracts().await;

        if !update_timer.next_tick().await {
            logger::info!("Tick event suspended.");
            break;
        }
        logger::info!("Retrying contracts storage initialization.");
    }
}
Rust

Because the contracts database polling task must run concurrently with the Launcher::launch() task, use the join!() macro from the futuresLeaving the Site crate to join both tasks. This snippet implements a complete #[entrypoint] function with polling and launching:

#[entrypoint]
async fn configure(launcher: Launcher, clock: Clock,validator: ContractValidator) -> Result<(), LauncherError> {

    let filter = on_request(|state, authentication| my_authentication_filter(state, authentication, &validator));

    let (_, launcher_result) = join! {
        update_my_contracts(&validator, clock),
        launcher.launch(filter),
    };

    launcher_result
}
Rust