コントラクト検証ライブラリ関数の使用

API インスタンスとアプリケーション間の​コントラクト​により、アプリケーションは SLA 層に基づいてインスタンスにアクセスできます。クライアントアプリケーションは、クライアント ID とクライアントシークレットで構成されるログイン情報を提供してコントラクトを検証できます。PDK コントラクト検証ライブラリでは、クライアントログイン情報を検証してカスタムクライアント ID 適用ポリシーを実装する関数が提供されます。

API インスタンスとアプリケーション間に同時に存在できるコントラクトはコントラクトは 1 つのみです。

Flex Gateway ポリシー開発キット (PDK) コントラクト検証ライブラリを使用するポリシープロジェクトの例については、 「Client ID Enforcement Policy Example (クライアント ID 適用ポリシーの例)」​を参照してください。

クライアントログイン情報を抽出する

コントラクト検証ライブラリは、​pdk::cors​ モジュールから使用できます。

このモジュールで提供される ​ContractValidator​ は、​ClientId​ オブジェクトと ​ClientSecret​ オブジェクトで表されるクライアントログイン情報を検証します。​ContractValidator::authenticate()​ メソッドと ​ContractValidator::authorize()​ メソッドを使用して、認証および承認ログイン情報を検証します。

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>;
}

どちらのメソッドでも、クライアント ID、クライアント名、SLA ID 項目が含まれる ​ClientData​ 構造体が返されます。

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

要求の認証および承認ヘッダーを検証する

要求からクライアントログイン情報を抽出して要求を認証し、​ClientValidation::authenticate()​ メソッドを呼び出します。​pdk::contracts​ モジュールで提供される ​basic_auth_credentials()​ ヘルパー関数は、リクエストヘッダーから基本認証ログイン情報を抽出します。

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

次のコードスニペットでは、認証要求のシンプルな検索条件が提供されます。

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

    // Extract credentials
    let (client_id, client_secret) = match basic_auth_credentials(&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 authentication
    let validation = validator.authenticate(&client_id, &client_secret);

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

    // Update the current authentication
    if let Some(mut auth) = authentication.authentication() {
        auth.client_id = Some(client_data.client_id);
        auth.client_name = Some(client_data.client_name);

        authentication.set_authentication(Some(&auth));
    }

    Flow::Continue(())
}

次のコードスニペットでは、承認要求のシンプルな検索条件が提供されます。

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(())
}

クライアントログイン情報のカスタム抽出を実装する

ClientId::new()​ および ​ClientSecret::new()​ メソッドを呼び出して、ログイン情報を抽出します。

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

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

ログイン情報を抽出したら、一連のログイン情報を初期化します。

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)
}
セキュリティ上の理由により、​ClientSecret​ のコンテンツは使用後に削除され、​Debug​ trait に ​ClientSecret​ のコンテンツは表示されません。

ContractValidator オブジェクトを挿入する

ContractValidator​ オブジェクトを ​#[entrypoint]​ 関数に挿入し、要求検索条件への参照を共有します。

#[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(())
}

ローカルコントラクトデータベースをポーリングする

ContractValidator​ オブジェクトには、コントラクトデータベースのローカルコピーが保持されます。ローカルコピーをポーリングして、コントラクトの有効なコピーを保持する必要があります。​ContractValidator::update_contracts()​ メソッドを呼び出して、ローカルコピーをポーリングします。

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

ContractValidator::update_contracts()​ メソッドでコントラクトデータベースのポーリング中に接続の問題が発生したことを示すエラーが返された場合、再試行およびエラー処理ヒューリスティックを実装する必要があります。これを行うには、​ContractValidator::UPDATE_PERIOD​ 定数で指定された期間に ​ContractValidator::update_contracts()​ を呼び出します。

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;
       }
    }
}

コントラクトデータベース初期化期間では、更新期間よりも高い頻度でコントラクトデータベースをポーリングする期間を指定します。​ContractValidator::INITIALIZATION_PERIOD​ では、最初のポーリングに必要な間隔を指定します。

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;
       }
    }
}

次のスニペットは、初期化と更新のポーリング期間が含まれる完全なコントラクトポーリングタスクを示しています。

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.");
    }
}

コントラクトデータベースポーリングタスクは ​Launcher::launch()​ タスクと同時に実行する必要があるため、 futures​ クレートの ​join!()​ マクロを使用して両方のタスクを結合します。次のスニペットでは、ポーリングと起動が含まれる完全な ​#[entrypoint]​ 関数を実装します。

#[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
}