Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  create.rs   Sprache: unbekannt

 
Spracherkennung für: .rs vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

//! Task structures to create a new identity.
use core::mem;
use std::rc::Rc;

use const_format::formatcp;
use libthreema_macros::{DebugVariantNames, Name, VariantNames};
use tracing::{debug, error, info};

use crate::{
    common::{ChatServerGroup, ClientInfo, ThreemaId, config::Config, keys::ClientKey, task::TaskLoop},
    csp_e2e::{CspE2eProtocolError, Flavor},
    https::{HttpsRequest, HttpsResult, directory},
    utils::debug::Name as _,
};

/// 1. Run the HTTPS request as defined by [`HttpsRequest`] and let `response` be the result.
/// 2. Provide `response` to the associated task as a [`CreateIdentityResponse`] and poll again.
pub struct CreateIdentityInstruction {
    /// The HTTPs request to be made.
    pub request: HttpsRequest,
}

/// Possible response to an [`CreateIdentityInstruction`].
#[derive(Name)]
pub struct CreateIdentityResponse {
    /// Result for the HTTPS request.
    pub result: HttpsResult,
}

/// Result of creating an identity.
pub struct CreateIdentityResult {
    /// Assigned identity.
    pub identity: ThreemaId,

    /// Client key associated to the identity.
    pub client_key: ClientKey,

    /// Assigned server group.
    pub server_group: ChatServerGroup,
}

/// Result of polling a [`CreateIdentityTask`].
pub type CreateIdentityLoop = TaskLoop<CreateIdentityInstruction, CreateIdentityResult>;

/// Context for creating an identity.
pub struct CreateIdentityContext {
    /// Client info.
    pub client_info: ClientInfo,

    /// Configuration used by the protocol.
    pub config: Rc<Config>,

    /// Application flavour.
    pub flavor: Flavor,
}

struct InitState {
    client_key: ClientKey,
}

struct DirectoryChallengeState {
    client_key: ClientKey,
    response: Option<CreateIdentityResponse>,
}

struct DirectoryCreateState {
    client_key: ClientKey,
    response: Option<CreateIdentityResponse>,
}

struct RegisterWorkChallengeState {
    client_key: ClientKey,
    identity_info: directory::CreateIdentityResponse,
    response: Option<CreateIdentityResponse>,
}

struct RegisterWorkState {
    client_key: ClientKey,
    identity_info: directory::CreateIdentityResponse,
    response: Option<CreateIdentityResponse>,
}

#[derive(DebugVariantNames, VariantNames)]
enum State {
    Error(CspE2eProtocolError),
    Init(InitState),
    DirectoryChallenge(DirectoryChallengeState),
    DirectoryCreate(DirectoryCreateState),
    RegisterWorkChallenge(RegisterWorkChallengeState),
    RegisterWork(RegisterWorkState),
    Done,
}
impl State {
    fn poll_init(context: &CreateIdentityContext, state: InitState) -> (Self, CreateIdentityLoop) {
        // Request an authentication challenge to create an identity
        let public_key = state.client_key.public_key();
        debug!("Requesting challenge to create identity");
        (
            Self::DirectoryChallenge(DirectoryChallengeState {
                client_key: state.client_key,
                response: None,
            }),
            CreateIdentityLoop::Instruction(CreateIdentityInstruction {
                request: directory::create_identity_authentication_request(
                    &context.client_info,
                    &context.config.directory_server_url,
                    &context.flavor,
                    public_key,
                ),
            }),
        )
    }

    fn poll_directory_challenge(
        context: &CreateIdentityContext,
        state: DirectoryChallengeState,
    ) -> Result<(Self, CreateIdentityLoop), CspE2eProtocolError> {
        // Ensure the caller provided the response
        let Some(response) = state.response else {
            return Err(CspE2eProtocolError::InvalidState(formatcp!(
                "{} result was not provided for '{}' state",
                CreateIdentityResponse::NAME,
                State::DIRECTORY_CHALLENGE,
            )));
        };

        // Handle the authentication challenge and provide the final request to create an identity
        let authentication = directory::handle_authentication_challenge(&state.client_key, response.result)?;
        let public_key = state.client_key.public_key();
        info!("Creating identity");
        Ok((
            Self::DirectoryCreate(DirectoryCreateState {
                client_key: state.client_key,
                response: None,
            }),
            CreateIdentityLoop::Instruction(CreateIdentityInstruction {
                request: directory::create_identity_request(
                    &context.client_info,
                    &context.config.directory_server_url,
                    &context.flavor,
                    authentication,
                    public_key,
                ),
            }),
        ))
    }

    fn poll_directory_create(
        context: &CreateIdentityContext,
        state: DirectoryCreateState,
    ) -> Result<(Self, CreateIdentityLoop), CspE2eProtocolError> {
        // Ensure the caller provided the response
        let Some(response) = state.response else {
            return Err(CspE2eProtocolError::InvalidState(formatcp!(
                "{} result was not provided for '{}' state",
                CreateIdentityResponse::NAME,
                State::DIRECTORY_CREATE,
            )));
        };

        // Handle the result
        let identity_info = directory::handle_create_identity_result(response.result)?;
        info!(identity = ?identity_info.identity, "Identity created");
        match &context.flavor {
            Flavor::Consumer => Ok((
                Self::Done,
                CreateIdentityLoop::Done(CreateIdentityResult {
                    identity: identity_info.identity,
                    client_key: state.client_key,
                    server_group: identity_info.server_group,
                }),
            )),

            // Request an authentication challenge to update the work properties.
            //
            // Note: This is necessary for work identities to appear in the work cockpit.
            Flavor::Work(work_context) => {
                debug!("Requesting challenge to update work properties");
                let instruction = CreateIdentityLoop::Instruction(CreateIdentityInstruction {
                    request: directory::update_work_properties_authentication_request(
                        &context.client_info,
                        &context.config.directory_server_url,
                        work_context,
                        identity_info.identity,
                    ),
                });
                Ok((
                    Self::RegisterWorkChallenge(RegisterWorkChallengeState {
                        client_key: state.client_key,
                        identity_info,
                        response: None,
                    }),
                    instruction,
                ))
            },
        }
    }

    fn poll_register_work_challenge(
        context: &CreateIdentityContext,
        state: RegisterWorkChallengeState,
    ) -> Result<(Self, CreateIdentityLoop), CspE2eProtocolError> {
        // Ensure the caller provided the response
        let Some(response) = state.response else {
            return Err(CspE2eProtocolError::InvalidState(formatcp!(
                "{} result was not provided for '{}' state",
                CreateIdentityResponse::NAME,
                State::REGISTER_WORK_CHALLENGE,
            )));
        };

        // Ensure we're in a work context
        let Flavor::Work(work_context) = &context.flavor else {
            let message = "Work context missing";
            error!(message);
            return Err(CspE2eProtocolError::InternalError(message.into()));
        };

        // Handle the result
        let authentication = directory::handle_authentication_challenge(&state.client_key, response.result)?;
        info!("Registering as work identity");
        let instruction = CreateIdentityLoop::Instruction(CreateIdentityInstruction {
            request: directory::update_work_properties_request(
                &context.client_info,
                &context.config.directory_server_url,
                work_context,
                state.identity_info.identity,
                authentication,
            ),
        });
        Ok((
            State::RegisterWork(RegisterWorkState {
                client_key: state.client_key,
                identity_info: state.identity_info,
                response: None,
            }),
            instruction,
        ))
    }

    fn poll_register_work(
        state: RegisterWorkState,
    ) -> Result<(Self, CreateIdentityLoop), CspE2eProtocolError> {
        // Ensure the caller provided the response
        let Some(response) = state.response else {
            return Err(CspE2eProtocolError::InvalidState(formatcp!(
                "{} result was not provided for '{}' state",
                CreateIdentityResponse::NAME,
                State::REGISTER_WORK,
            )));
        };

        // Handle the result
        directory::handle_update_work_properties_result(response.result)?;
        info!("Registered as work identity");
        Ok((
            Self::Done,
            CreateIdentityLoop::Done(CreateIdentityResult {
                identity: state.identity_info.identity,
                client_key: state.client_key,
                server_group: state.identity_info.server_group,
            }),
        ))
    }
}

/// Task for creating a new identity.
#[derive(Debug, Name)]
pub struct CreateIdentityTask {
    state: State,
}
impl CreateIdentityTask {
    /// Create a new task for creating a new identity.
    #[must_use]
    #[expect(clippy::new_without_default, reason = "Task pattern")]
    pub fn new() -> Self {
        Self {
            state: State::Init(InitState {
                client_key: ClientKey::random(),
            }),
        }
    }

    /// Poll to advance the state.
    ///
    /// # Errors
    ///
    /// Returns [`CspE2eProtocolError`] for all possible reasons.
    #[tracing::instrument(skip_all, fields(?self))]
    pub fn poll(
        &mut self,
        context: &CreateIdentityContext,
    ) -> Result<CreateIdentityLoop, CspE2eProtocolError> {
        let result = match mem::replace(
            &mut self.state,
            State::Error(CspE2eProtocolError::InvalidState(formatcp!(
                "{} in a transitional state",
                CreateIdentityTask::NAME
            ))),
        ) {
            State::Error(error) => Err(error),
            State::Init(state) => Ok(State::poll_init(context, state)),
            State::DirectoryChallenge(state) => State::poll_directory_challenge(context, state),
            State::DirectoryCreate(state) => State::poll_directory_create(context, state),
            State::RegisterWorkChallenge(state) => State::poll_register_work_challenge(context, state),
            State::RegisterWork(state) => State::poll_register_work(state),
            State::Done => Err(CspE2eProtocolError::InvalidState(formatcp!(
                "{} already done",
                CreateIdentityTask::NAME
            ))),
        };
        match result {
            Ok((state, instruction)) => {
                self.state = state;
                debug!(state = ?self.state, "Changed state");
                Ok(instruction)
            },
            Err(error) => {
                self.state = State::Error(error.clone());
                debug!(state = ?self.state, "Changed state to error");
                Err(error)
            },
        }
    }

    /// Possible results after handling a [`CreateIdentityInstruction`].
    ///
    /// # Errors
    ///
    /// Returns [`CspE2eProtocolError`] for all possible reasons.
    #[tracing::instrument(skip_all, fields(?self))]
    pub fn response(&mut self, response: CreateIdentityResponse) -> Result<(), CspE2eProtocolError> {
        match &mut self.state {
            State::DirectoryChallenge(state) => {
                let _ = state.response.insert(response);
                Ok(())
            },
            State::DirectoryCreate(state) => {
                let _ = state.response.insert(response);
                Ok(())
            },
            State::RegisterWorkChallenge(state) => {
                let _ = state.response.insert(response);
                Ok(())
            },
            State::RegisterWork(state) => {
                let _ = state.response.insert(response);
                Ok(())
            },
            _ => Err(CspE2eProtocolError::InvalidState(formatcp!(
                "Must be in any of the following states: {}, {}, {}, {}",
                State::DIRECTORY_CHALLENGE,
                State::DIRECTORY_CREATE,
                State::REGISTER_WORK,
                State::REGISTER_WORK_CHALLENGE,
            ))),
        }
    }
}

[Dauer der Verarbeitung: 0.23 Sekunden, vorverarbeitet 2026-04-27]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge