pdk-contracts-lib 1.9.1-alpha.2

PDK Contracts Library
Documentation
// Copyright (c) 2026, Salesforce, Inc.,
// All rights reserved.
// For full license text, see the LICENSE.txt file

use pdk_core::logger;

use super::error::AuthorizationError;
use crate::api::credentials::ClientId;
use crate::api::ClientData;
use crate::implementation::model::contracts_storage::ContractsStorage;

pub fn authorize(
    contracts_storage: &dyn ContractsStorage,
    client_id: &ClientId,
) -> Result<ClientData, AuthorizationError> {
    if contracts_storage.last_update().is_none() {
        logger::debug!("Authorization: Contracts are unavailable.");
        return Err(AuthorizationError::UnavailableContracts);
    }

    let Some(contracts) = contracts_storage.get_contract_by_client(client_id) else {
        logger::debug!("Authorization: Client {client_id} does not exist for this API");
        return Err(AuthorizationError::InvalidClientId);
    };

    logger::debug!("Authorization: Credentials match with a Client of this API");
    Ok(contracts.cast_to_client_information())
}

#[cfg(test)]
mod tests {
    use std::rc::Rc;

    use super::authorize;
    use crate::implementation::hashing::hash;
    use crate::implementation::model::contracts_storage::{
        ContractsLocalStorage, ContractsStorage,
    };
    use crate::implementation::platform::shared::Contract;
    use crate::mocks::{ManualClock, MapSharedData};
    use crate::{ClientId, ClientSecret};

    const INVALID_CLIENT_ID: &str = "invalid_client_id";
    const VALID_CLIENT_ID: &str = "api";
    const VALID_CLIENT_SECRET: &str = "gateway";
    const API_ID: &str = "1234";
    const SALT: &str = "someSalt";

    fn invalid_client_id() -> ClientId {
        ClientId::new(INVALID_CLIENT_ID.to_string())
    }

    fn valid_client_id() -> ClientId {
        ClientId::new(VALID_CLIENT_ID.to_string())
    }

    fn valid_client_secret() -> ClientSecret {
        ClientSecret::new(VALID_CLIENT_SECRET.to_string())
    }

    fn contract(id: &str, client_id: &ClientId, client_secret: &ClientSecret) -> Contract {
        let hashed_secret = hash(SALT, client_secret);
        Contract {
            contract_id: id.to_string(),
            api_id: API_ID.to_string(),
            version_id: "".to_string(),
            sla_tier_id: None,
            client_id: client_id.as_str().to_string(),
            client_secret: String::from_utf8(hashed_secret)
                .expect("Could not map secret to String"),
            client_secret_salt: SALT.to_string(),
            client_name: client_id.to_string(),
            removed: false,
        }
    }

    #[test]
    fn nonexistent_client_is_rejected() {
        let clock = Rc::new(ManualClock::default());
        let shared_data = Rc::new(MapSharedData::default());
        let storage = ContractsLocalStorage::new(API_ID, clock, shared_data);

        let result = authorize(&storage, &invalid_client_id());

        assert!(result.is_err());
    }

    #[test]
    #[cfg(not(fips))]
    fn valid_client_is_accepted() {
        let clock = Rc::new(ManualClock::default());
        let shared_data = Rc::new(MapSharedData::default());
        let storage = ContractsLocalStorage::new(API_ID, clock, shared_data);

        let client_id = &valid_client_id();

        let contract = contract("1", client_id, &valid_client_secret());
        storage.save_contract(contract);
        storage.update_last();

        let validation = authorize(&storage, client_id);
        assert!(validation.is_ok());

        let client_data = validation.unwrap();
        assert_eq!(client_data.client_id, VALID_CLIENT_ID);
    }

    #[test]
    #[cfg(not(fips))]
    fn removed_client_is_rejected() {
        let clock = Rc::new(ManualClock::default());
        let shared_data = Rc::new(MapSharedData::default());
        let storage = ContractsLocalStorage::new(API_ID, clock, shared_data);

        let client_id = &valid_client_id();

        let contract = contract("1", client_id, &valid_client_secret());
        storage.save_contract(contract);
        storage.remove_contract(VALID_CLIENT_ID);

        let validation = authorize(&storage, client_id);

        assert!(validation.is_err());
    }
}