rocketmq-auth 1.0.0

Unofficial Rust implementation of Apache RocketMQ
Documentation
// Copyright 2023 The RocketMQ Rust Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::any::Any;
use std::fmt;

use cheetah_string::CheetahString;
use rocketmq_error::REDACTED;

use crate::authentication::context::base_authentication_context::BaseAuthenticationContext;
use crate::authentication::AsAny;
use crate::authorization::context::authentication_context::AuthenticationContext;

#[derive(Default)]
pub struct DefaultAuthenticationContext {
    pub base: BaseAuthenticationContext,

    username: Option<CheetahString>,
    content: Option<Vec<u8>>,
    signature: Option<CheetahString>,
    request_timestamp: Option<CheetahString>,
    request_timestamp_millis: Option<i64>,
    verified_transport_identity: Option<Vec<u8>>,
}

impl fmt::Debug for DefaultAuthenticationContext {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("DefaultAuthenticationContext")
            .field("username", &self.username)
            .field("content_len", &self.content.as_ref().map(Vec::len))
            .field("signature", &self.signature.as_ref().map(|_| REDACTED))
            .field("request_timestamp", &self.request_timestamp)
            .field("request_timestamp_millis", &self.request_timestamp_millis)
            .field(
                "verified_transport_identity_len",
                &self.verified_transport_identity.as_ref().map(Vec::len),
            )
            .finish_non_exhaustive()
    }
}

impl DefaultAuthenticationContext {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn username(&self) -> Option<&CheetahString> {
        self.username.as_ref()
    }

    pub fn set_username(&mut self, username: CheetahString) {
        self.username = Some(username);
    }

    pub fn content(&self) -> Option<&[u8]> {
        self.content.as_deref()
    }

    pub fn set_content(&mut self, content: Vec<u8>) {
        self.content = Some(content);
    }

    pub fn signature(&self) -> Option<&CheetahString> {
        self.signature.as_ref()
    }

    pub fn set_signature(&mut self, signature: CheetahString) {
        self.signature = Some(signature);
    }

    pub fn request_timestamp(&self) -> Option<&CheetahString> {
        self.request_timestamp.as_ref()
    }

    pub fn set_request_timestamp(&mut self, request_timestamp: CheetahString) {
        self.request_timestamp = Some(request_timestamp);
    }

    pub fn request_timestamp_millis(&self) -> Option<i64> {
        self.request_timestamp_millis
    }

    pub fn set_request_timestamp_millis(&mut self, request_timestamp_millis: i64) {
        self.request_timestamp_millis = Some(request_timestamp_millis);
    }

    /// Returns the transport-verified peer identity, when one was supplied by a trusted ingress.
    pub fn verified_transport_identity(&self) -> Option<&[u8]> {
        self.verified_transport_identity.as_deref()
    }

    /// Attaches a transport-verified identity proof to this authentication context.
    pub fn set_verified_transport_identity(&mut self, identity: Vec<u8>) {
        self.verified_transport_identity = Some(identity);
    }
}

impl AsAny for DefaultAuthenticationContext {
    fn as_any_mut(&mut self) -> &mut dyn Any {
        self
    }

    fn as_any(&self) -> &dyn Any {
        self
    }
}

impl AuthenticationContext for DefaultAuthenticationContext {}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_default_authentication_context_default_and_new() {
        let context = DefaultAuthenticationContext::default();
        assert!(context.username().is_none());
        assert!(context.content().is_none());
        assert!(context.signature().is_none());
        assert!(context.request_timestamp().is_none());
        assert!(context.request_timestamp_millis().is_none());
        assert!(context.verified_transport_identity().is_none());

        let context = DefaultAuthenticationContext::new();
        assert!(context.username().is_none());
        assert!(context.content().is_none());
        assert!(context.signature().is_none());
        assert!(context.request_timestamp().is_none());
        assert!(context.request_timestamp_millis().is_none());
        assert!(context.verified_transport_identity().is_none());
    }

    #[test]
    fn test_default_authentication_context_setters_and_getters() {
        let mut context = DefaultAuthenticationContext::new();
        let username = CheetahString::from("test_user");
        let content = vec![1, 2, 3];
        let signature = CheetahString::from("test_signature");
        let request_timestamp = CheetahString::from("20231227T194619Z");

        context.set_username(username.clone());
        context.set_content(content.clone());
        context.set_signature(signature.clone());
        context.set_request_timestamp(request_timestamp.clone());
        context.set_request_timestamp_millis(1_703_706_379_000);
        context.set_verified_transport_identity(vec![7, 8, 9]);

        assert_eq!(context.username(), Some(&username));
        assert_eq!(context.content(), Some(content.as_slice()));
        assert_eq!(context.signature(), Some(&signature));
        assert_eq!(context.request_timestamp(), Some(&request_timestamp));
        assert_eq!(context.request_timestamp_millis(), Some(1_703_706_379_000));
        assert_eq!(context.verified_transport_identity(), Some([7, 8, 9].as_slice()));
    }

    #[test]
    fn test_default_authentication_context_as_any() {
        let context = DefaultAuthenticationContext::new();
        let any = context.as_any();
        assert!(any.is::<DefaultAuthenticationContext>());

        let mut context_mut = DefaultAuthenticationContext::new();
        let any_mut = context_mut.as_any_mut();
        assert!(any_mut.is::<DefaultAuthenticationContext>());
    }

    #[test]
    fn default_authentication_context_debug_redacts_signature() {
        let mut context = DefaultAuthenticationContext::new();
        context.set_username(CheetahString::from("alice"));
        context.set_content(vec![1, 2, 3, 4]);
        context.set_signature(CheetahString::from("signature-secret"));

        let debug = format!("{context:?}");

        assert!(debug.contains("alice"));
        assert!(debug.contains("content_len"));
        assert!(debug.contains(REDACTED));
        assert!(!debug.contains("signature-secret"));
    }
}