Skip to main content

Crate openstack_sdk_auth_core

Crate openstack_sdk_auth_core 

Source
Expand description

§Core trait for implementing OpenStack authentication plugins to [openstack_sdk]

This crate provides the foundational types and traits required to authenticate against the OpenStack Identity service (Keystone). It defines:

  • OpenStackAuthType — the primary trait that all authentication plugins must implement to enable login flows (password, token, JWT, WebSSO, etc.).
  • OpenStackMultifactorAuthMethod — the trait for multifactor-capable methods that can be composed into multipass/multifactor authentication requests.
  • AuthToken — the structure that represents a successful Keystone authentication result, including the bearer token and parsed server response.
  • AuthTokenScope — represents the authorization scope attached to a token (project, domain, system, or unscoped).
  • AuthError — the unified error type covering all authentication-related failures, including receipts for multipass flows.
  • Auth — the enum that wraps the current authentication state (token or none).
  • AuthState — describes token validity (valid, expired, about-to-expire, or unset).

§Plugin Registration

Authentication plugins are registered at compile time using Rust’s inventory crate. Implement OpenStackAuthType and submit an AuthPluginRegistration via inventory::submit!{}. For multipass support, additionally implement OpenStackMultifactorAuthMethod and submit an AuthMethodPluginRegistration.

The execute_auth_request function provides a common, instrumented pathway for sending authentication HTTP requests with timing and request-id logging.

§Examples

§Basic Authentication

Authenticate by creating an AuthToken from an existing token string (e.g., obtained from an environment variable or a previous login):

use secrecy::{ExposeSecret, SecretString};
use openstack_sdk_auth_core::{AuthToken, AuthTokenScope};

let token = AuthToken::new(SecretString::from("my-token"), None);
let scope = AuthTokenScope::Unscoped;

println!("Scope: {:?}", token.get_scope());
println!("State: {:?}", token.get_state(None));

§Checking Token State

Tokens can be checked for validity with an optional expiration threshold:

use chrono::TimeDelta;
use openstack_sdk_auth_core::AuthState;
let offset = TimeDelta::minutes(5);
match auth.get_state(Some(offset)) {
    AuthState::Valid => println!("Token is valid"),
    AuthState::AboutToExpire => println!("Token will expire within 5 minutes"),
    AuthState::Expired => println!("Token has expired"),
    AuthState::Unset => println!("No token data available"),
}

§Setting Request Headers

The Auth type can inject the X-Auth-Token header into outgoing HTTP requests:

use http::HeaderMap;
let mut headers = HeaderMap::new();
auth.set_header(&mut headers)?;
// headers now contains the `X-Auth-Token` header if the auth type is a token.

§Implementing a Custom Authenticator

Creating a new authentication method requires implementing OpenStackAuthType:

use std::collections::HashMap;
use async_trait::async_trait;
use secrecy::SecretString;
use serde_json::{Value, json};
use openstack_sdk_auth_core::{Auth, AuthError, AuthToken, AuthTokenScope, OpenStackAuthType};

pub struct MyAuthenticator;

static PLUGIN: MyAuthenticator = MyAuthenticator;
inventory::submit! {
    openstack_sdk_auth_core::AuthPluginRegistration { method: &PLUGIN }
}

#[async_trait]
impl OpenStackAuthType for MyAuthenticator {
    fn get_supported_auth_methods(&self) -> Vec<&'static str> {
        vec!["v3myauth", "myauth"]
    }

    fn requirements(&self, _hints: Option<&Value>) -> Result<Value, AuthError> {
        Ok(json!({
            "type": "object",
            "required": ["token_id"],
            "properties": {
                "token_id": {
                    "type": "string",
                    "description": "The token identifier"
                }
            }
        }))
    }

    fn api_version(&self) -> (u8, u8) {
        (3, 0)
    }

    async fn auth(
        &self,
        _http_client: &reqwest::Client,
        _identity_url: &url::Url,
        _values: &HashMap<String, SecretString>,
        _scope: Option<&AuthTokenScope>,
        _hints: Option<&Value>,
    ) -> Result<Auth, AuthError> {
        // Perform your authentication logic here and return an AuthToken
        let auth_token = AuthToken::new("example-token", None);
        Ok(Auth::AuthToken(Box::new(auth_token)))
    }
}

§Error Handling Patterns

The AuthError enum provides different error variants that you can match on to handle specific failure scenarios:

use openstack_sdk_auth_core::{AuthError, Auth, AuthToken, AuthTokenScope};

match simulate_auth_result() {
    Ok(Auth::AuthToken(token)) => {
        println!("Authentication successful");
        println!("Token expires at: {:?}", token.get_state(None));
    }
    Ok(Auth::None) => {
        println!("No authentication available");
    }
    Ok(auth) => {
        println!("Unknown auth type: {:?}", auth);
    }
    Err(AuthError::AuthReceipt(receipt)) => {
        println!("Multifactor authentication required");
        let methods: Vec<_> = receipt.required_auth_methods.iter()
            .flatten()
            .cloned()
            .collect();
        println!("Required methods: {:?}", methods);
    }
    Err(AuthError::Serde { .. }) => {
        println!("Failed to parse response (malformed JSON)");
    }
    Err(AuthError::UnknownAuth { code, message }) => {
        println!("Unknown authentication error (code: {})", code);
        if let Some(msg) = &message {
            println!("Message: {}", msg);
        }
    }
    Err(e) => {
        println!("Authentication failed: {}", e);
    }
}

§Implementing a Multifactor-Authenticator

To support multifactor authentication (e.g., TOTP or password + token), implement both OpenStackAuthType and OpenStackMultifactorAuthMethod:

use std::collections::HashMap;
use async_trait::async_trait;
use secrecy::{SecretString, ExposeSecret};
use serde_json::{Value, json};
use openstack_sdk_auth_core::{
    Auth, AuthError, AuthToken, AuthTokenScope,
    OpenStackAuthType, OpenStackMultifactorAuthMethod,
};

pub struct MyMultifactorAuthenticator;

static PLUGIN: MyMultifactorAuthenticator = MyMultifactorAuthenticator;
inventory::submit! {
    openstack_sdk_auth_core::AuthPluginRegistration { method: &PLUGIN }
}
inventory::submit! {
    openstack_sdk_auth_core::AuthMethodPluginRegistration { method: &PLUGIN }
}

#[async_trait]
impl OpenStackMultifactorAuthMethod for MyMultifactorAuthenticator {
    fn get_supported_auth_methods(&self) -> Vec<&'static str> {
        vec!["v3myauth", "myauth"]
    }

    fn requirements(&self, _hints: Option<&Value>) -> Result<Value, AuthError> {
        Ok(json!({
            "type": "object",
            "required": ["auth_code"],  // Additional auth method requirement
            "properties": {
                "auth_code": {
                    "type": "string",
                    "format": "password",
                    "description": "One-time authentication code"
                }
            }
        }))
    }

    /// Extracts authentication data from the values map.
    fn get_auth_data(
        &self,
        values: &HashMap<String, SecretString>,
    ) -> Result<(&'static str, Value), AuthError> {
        let auth_code = values
            .get("auth_code")
            .ok_or_else(|| AuthError::AuthValueNotSupplied("auth_code".to_string()))?;
        Ok(("myauth", json!({
            "auth_code": auth_code.expose_secret()
        })))
    }
}

#[async_trait]
impl OpenStackAuthType for MyMultifactorAuthenticator {
    fn get_supported_auth_methods(&self) -> Vec<&'static str> {
        vec!["v3myauth", "myauth"]
    }

    fn requirements(&self, _hints: Option<&Value>) -> Result<Value, AuthError> {
        Ok(json!({
            "type": "object",
            "required": ["auth_code"],  
            "properties": {
                "auth_code": {
                    "type": "string",
                    "format": "password",
                    "description": "One-time authentication code"
                }
            }
        }))
    }

    fn api_version(&self) -> (u8, u8) {
        (3, 0)
    }

    async fn auth(
        &self,
        _http_client: &reqwest::Client,
        _identity_url: &url::Url,
        _values: &HashMap<String, SecretString>,
        _scope: Option<&AuthTokenScope>,
        _hints: Option<&Value>,
    ) -> Result<Auth, AuthError> {
        // Perform multifactor authentication and return an AuthToken:
        let auth_token = AuthToken::new("example-token", None);
        Ok(Auth::AuthToken(Box::new(auth_token)))
    }
}

§Token Scoping

Tokens can be scoped to specific projects or domains using AuthTokenScope:

use openstack_sdk_auth_core::{AuthTokenScope};
use openstack_sdk_auth_core::types::{Project, Domain};

// Project-scope by ID
let project_scope = AuthTokenScope::Project(Project {
    id: Some("project-id-123".to_string()),
    name: None,
    domain: None,
});

// Domain-scope with domain name
let domain_scope = AuthTokenScope::Domain(Domain {
    id: None,
    name: Some("Default".to_string()),
});

// Unscoped (default)
let unscoped: AuthTokenScope = AuthTokenScope::default();

§Handling Authentication Receipts

When multifactor authentication is enabled, Keystone returns an authentication receipt instead of a new token. The receipt contains information about what additional authentication methods are required:

use openstack_sdk_auth_core::{AuthTokenScope, AuthError, Auth, AuthReceiptResponse, AuthReceipt};
use chrono::Local;


let scope = Some(&AuthTokenScope::Unscoped);
match simulate_auth_result() {
    Ok(auth) => {
        match auth {
            Auth::AuthToken(token) => {
                println!("Authenticated with token");
                println!("Token: {:?}", token.get_scope());
            }
            Auth::None => {
                println!("Not authenticated");
            }
            _ => {
                println!("Unknown auth type");
            }
        }
    }
    Err(AuthError::AuthReceipt(receipt)) => {
        println!("Additional authentication methods required");
        println!("Required methods: {:?}", receipt.required_auth_methods);
        let methods: Vec<_> = receipt.receipt.methods.iter().cloned().collect();
        println!("Already completed methods: {:?}", methods);

        // Use the receipt token for subsequent authentication requests
        if let Some(receipt_token) = &receipt.token {
            println!("Receipt token: {}...", &receipt_token.chars().take(8).collect::<String>());
        }
    }
    Err(e) => {
        println!("Authentication error: {}", e);
    }
}

§Token State Management

The AuthToken::get_state method can be used to determine if a token is valid and needs to be refreshed. Using an expiration offset allows you to proactively refresh tokens before they expire:

use chrono::TimeDelta;
use openstack_sdk_auth_core::{AuthToken, AuthState, types::{AuthResponse, TokenInfo, User, Project}};
use secrecy::SecretString;

// Create a token with expiration info
let auth_info = AuthResponse {
    token: TokenInfo {
        expires_at: chrono::Utc::now() + TimeDelta::hours(24),
        user: User::default(),
        ..Default::default()
    },
};
let token = AuthToken::new("my-token", Some(auth_info));

// Check with 5-minute buffer for proactive refresh
match token.get_state(Some(TimeDelta::minutes(5))) {
    AuthState::Valid => {
        println!("Token is valid");
    }
    AuthState::AboutToExpire => {
        println!("Token will expire soon, refreshing...");
        // Refresh the token before it expires
    }
    AuthState::Expired => {
        println!("Token has expired, re-authenticating...");
    }
    AuthState::Unset => {
        println!("No token data available");
    }
}

Re-exports§

pub use authtoken::AuthToken;
pub use authtoken::AuthTokenError;
pub use authtoken_scope::AuthTokenScope;
pub use types::*;

Modules§

auth_helper
Authentication helper.
authtoken
OpenStack AuthToken based authorization (X-Auth-Token)
authtoken_scope
OpenStack AuthToken Scope handling
types
Types of the SDK authentication methods

Structs§

AuthMethodPluginRegistration
Registry entry for multifactor-capable authentication methods.
AuthPluginRegistration
Registry entry for authentication plugins.

Enums§

Auth
An OpenStack Authentication type
AuthError
Authentication error.
AuthState
Authentication state enum
BuilderError
Builder error.

Traits§

OpenStackAuthType
The trait for authentication “plugins” for enabling OpenStack authentication.
OpenStackMultifactorAuthMethod
The trait for multifactor-capable authentication methods.

Functions§

anchor_plugins
Force the linker to include all auth plugin crates by iterating over the inventory registries. Must be called during client initialization since plugin crates are otherwise stripped by the linker (no other code references them), causing AuthType lookups to fail at runtime.
execute_auth_request