Eine aufbereitete Darstellung der Quelle

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

Benutzer

Quelle  directory.rs

  Sprache: Rust
 

//! Directory endpoint.
use serde::{Deserialize, Serialize};
use serde_repr::Deserialize_repr;

use super::endpoint::{HttpsEndpointError, TIMEOUT, https_headers_with_authentication};
use crate::{
    common::{
        ChatServerGroup, ClientInfo, FeatureMask, ThreemaId,
        config::{DirectoryServerBaseUrl, Flavor, WorkContext},
        keys::{ClientKey, PublicKey},
    },
    crypto::digest::Mac as _,
    https::{HttpsHeadersBuilder, HttpsMethod, HttpsRequest, HttpsResponse, HttpsResult},
    model::contact::ContactInit,
    protobuf::{self, d2d_sync::contact as protobuf_contact},
    utils::{serde::base64, time::utc_now_ms},
};

#[derive(Debug, Serialize)]
struct WorkCredentials<'creds> {
    #[serde(rename = "licenseUsername")]
    username: &'creds str,

    #[serde(rename = "licensePassword")]
    password: &'creds str,
}
impl<'creds> From<&'creds crate::common::config::WorkCredentials> for WorkCredentials<'creds> {
    fn from(credentials: &'creds crate::common::config::WorkCredentials) -> Self {
        Self {
            username: &credentials.username,
            password: &credentials.password,
        }
    }
}

#[inline]
fn https_headers(mode: &Flavor) -> HttpsHeadersBuilder {
    match mode {
        Flavor::Consumer => HttpsHeadersBuilder::default(),
        Flavor::Work(context) => https_headers_with_authentication(context),
    }
}

fn handle_status<TStatusFn: FnOnce(u16) -> Option<HttpsEndpointError>>(
    result: HttpsResult,
    unexpected_status_map_fn: TStatusFn,
) -> Result<HttpsResponse, HttpsEndpointError> {
    let response = result?;
    match response.status {
        200 | 204 => Ok(response),
        401 => Err(HttpsEndpointError::InvalidCredentials),
        429 => Err(HttpsEndpointError::RateLimitExceeded),
        status => {
            Err(unexpected_status_map_fn(status).unwrap_or(HttpsEndpointError::UnexpectedStatus(status)))
        },
    }
}

#[derive(Deserialize)]
#[serde(untagged)]
enum AwkwardResponse {
    Error {
        #[serde(rename = "success")]
        success: bool,
        #[serde(rename = "error")]
        error: String,
    },
    Success {
        #[serde(rename = "success")]
        success: bool,
    },
}

fn handle_status_and_awkward_response<TStatusFn: FnOnce(u16) -> Option<HttpsEndpointError>>(
    result: HttpsResult,
    unexpected_status_map_fn: TStatusFn,
) -> Result<HttpsResponse, HttpsEndpointError> {
    let response = handle_status(result, unexpected_status_map_fn)?;

    // Handle the super-awkward response with the `success` field
    if response.status != 200 {
        return Err(HttpsEndpointError::UnexpectedStatus(response.status));
    }
    match serde_json::from_slice::<AwkwardResponse>(&response.body)? {
        AwkwardResponse::Error {
            success: _success,
            error,
        } => Err(HttpsEndpointError::CustomPossiblyLocalizedError(error)),
        AwkwardResponse::Success { success } => {
            if success {
                Ok(response)
            } else {
                Err(HttpsEndpointError::CustomPossiblyLocalizedError(
                    "Server is totally stoned".to_owned(),
                ))
            }
        },
    }
}

#[derive(Deserialize)]
struct AuthenticationChallenge {
    #[serde(rename = "tokenRespKeyPub", with = "base64::fixed_length")]
    public_key: [u8; PublicKey::LENGTH],

    #[serde(rename = "token", with = "base64::variable_length")]
    challenge: Vec<u8>,
}

#[derive(Serialize)]
pub(cratestruct AuthenticationChallengeResponse {
    #[serde(rename = "token", with = "base64::variable_length")]
    challenge: Vec<u8>,

    #[serde(rename = "response", with = "base64::fixed_length")]
    response: [u8; PublicKey::LENGTH],
}

/// Process the result and solve the authentication challenge.
pub(cratefn handle_authentication_challenge(
    client_key: &ClientKey,
    result: HttpsResult,
) -> Result<AuthenticationChallengeResponse, HttpsEndpointError> {
    let response = handle_status(result, |_| None)?;
    let challenge: AuthenticationChallenge = serde_json::from_slice(&response.body)?;
    let response = AuthenticationChallengeResponse {
        challenge: challenge.challenge.clone(),
        response: client_key
            .derive_directory_authentication_key(&PublicKey::from(challenge.public_key))
            .0
            .chain_update(&challenge.challenge)
            .finalize()
            .into_bytes()
            .into(),
    };
    Ok(response)
}

#[derive(Serialize)]
struct IdentitiesRequest<'request> {
    identities: &'request [ThreemaId],
}

/// Request identity properties tied to a set of identities.
pub(cratefn request_identities(
    client_info: &ClientInfo,
    directory_server_url: &DirectoryServerBaseUrl,
    mode: &Flavor,
    identities: &[ThreemaId],
) -> HttpsRequest {
    HttpsRequest {
        timeout: TIMEOUT,
        url: directory_server_url.request_identities_path(),
        method: HttpsMethod::Post,
        headers: https_headers(mode).accept("application/json").build(client_info),
        body: serde_json::to_vec(&IdentitiesRequest { identities })
            .expect("Failed to create directory identities request body"),
    }
}

#[derive(Deserialize_repr)]
#[repr(u8)]
enum IdentityType {
    Regular = 0,
    Work = 1,
}
impl From<IdentityType> for protobuf_contact::IdentityType {
    fn from(r#type: IdentityType) -> Self {
        match r#type {
            IdentityType::Regular => protobuf_contact::IdentityType::Regular,
            IdentityType::Work => protobuf_contact::IdentityType::Work,
        }
    }
}

#[derive(Default, Deserialize_repr)]
#[repr(u8)]
enum ActivityState {
    #[default]
    Active = 0,
    Inactive = 1,
}
impl From<ActivityState> for protobuf_contact::ActivityState {
    fn from(state: ActivityState) -> Self {
        match state {
            ActivityState::Active => protobuf_contact::ActivityState::Active,
            ActivityState::Inactive => protobuf_contact::ActivityState::Inactive,
        }
    }
}

#[derive(Deserialize)]
struct ValidIdentity {
    #[serde(rename = "identity")]
    identity: ThreemaId,

    #[serde(rename = "type")]
    identity_type: IdentityType,

    #[serde(rename = "publicKey", with = "base64::fixed_length")]
    public_key: [u8; PublicKey::LENGTH],

    // Note: OnPrem directory omits this parameter for some reason, so we need a default
    #[serde(default, rename = "state")]
    activity_state: ActivityState,

    #[serde(rename = "featureMask")]
    feature_mask: u64,
}
impl From<ValidIdentity> for ContactInit {
    fn from(entry: ValidIdentity) -> Self {
        ContactInit {
            identity: entry.identity,
            public_key: PublicKey::from(entry.public_key),
            created_at: utc_now_ms(),
            first_name: None,
            last_name: None,
            nickname: None,
            verification_level: protobuf_contact::VerificationLevel::Unverified,
            work_verification_level: protobuf_contact::WorkVerificationLevel::None,
            identity_type: entry.identity_type.into(),
            acquaintance_level: protobuf_contact::AcquaintanceLevel::GroupOrDeleted,
            activity_state: entry.activity_state.into(),
            feature_mask: FeatureMask(entry.feature_mask),
            sync_state: protobuf_contact::SyncState::Initial,
            read_receipt_policy_override: None,
            typing_indicator_policy_override: None,
            notification_trigger_policy_override: None,
            notification_sound_policy_override: None,
            conversation_category: protobuf::d2d_sync::ConversationCategory::Default,
            conversation_visibility: protobuf::d2d_sync::ConversationVisibility::Normal,
        }
    }
}

#[derive(Deserialize)]
struct IdentitiesResponse {
    #[serde(rename = "identities")]
    identities: Vec<ValidIdentity>,
}

/// Process the identities result and map it to all valid identities.
///
/// IMPORTANT: Identities that do not exist or have already been revoked will not be included!
pub(cratefn handle_identities_result(result: HttpsResult) -> Result<Vec<ContactInit>, HttpsEndpointError> {
    let response = handle_status(result, |_| None)?;
    let IdentitiesResponse { identities } = serde_json::from_slice(&response.body)?;
    Ok(identities.into_iter().map(ContactInit::from).collect())
}

#[derive(Serialize)]
struct CreateIdentityRequest {
    #[serde(rename = "publicKey", with = "base64::fixed_length")]
    public_key: [u8; PublicKey::LENGTH],
}

/// Request an authentication challenge to create an identity.
pub(cratefn create_identity_authentication_request(
    client_info: &ClientInfo,
    directory_server_url: &DirectoryServerBaseUrl,
    mode: &Flavor,
    public_key: PublicKey,
) -> HttpsRequest {
    HttpsRequest {
        timeout: TIMEOUT,
        url: directory_server_url.create_identity_path(),
        method: HttpsMethod::Post,
        headers: https_headers(mode).accept("application/json").build(client_info),
        body: serde_json::to_vec(&CreateIdentityRequest {
            public_key: public_key.0.to_bytes(),
        })
        .expect("Failed to create directory identity creation challenge request body"),
    }
}

#[derive(Serialize)]
struct CreateIdentityAuthenticatedRequest<'creds> {
    #[serde(flatten)]
    request: CreateIdentityRequest,

    #[serde(flatten)]
    authentication: AuthenticationChallengeResponse,

    #[serde(flatten)]
    credentials: Option<WorkCredentials<'creds>>,
}

/// Create an identity.
pub(cratefn create_identity_request(
    client_info: &ClientInfo,
    directory_server_url: &DirectoryServerBaseUrl,
    mode: &Flavor,
    authentication: AuthenticationChallengeResponse,
    public_key: PublicKey,
) -> HttpsRequest {
    let credentials = match mode {
        Flavor::Work(work_context) => Some(WorkCredentials::from(&work_context.credentials)),
        Flavor::Consumer => None,
    };
    HttpsRequest {
        timeout: TIMEOUT,
        url: directory_server_url.create_identity_path(),
        method: HttpsMethod::Post,
        headers: https_headers(mode).accept("application/json").build(client_info),
        body: serde_json::to_vec(&CreateIdentityAuthenticatedRequest {
            request: CreateIdentityRequest {
                public_key: public_key.0.to_bytes(),
            },
            authentication,
            credentials,
        })
        .expect("Failed to create directory identity creation request body"),
    }
}

/// Response for a newly created identity.
#[derive(Deserialize)]
pub(cratestruct CreateIdentityResponse {
    #[serde(rename = "identity")]
    pub(crate) identity: ThreemaId,

    #[serde(rename = "serverGroup")]
    pub(crate) server_group: ChatServerGroup,
}

/// Process the result after attempting to create an identity.
pub(cratefn handle_create_identity_result(
    result: HttpsResult,
) -> Result<CreateIdentityResponse, HttpsEndpointError> {
    let response = handle_status_and_awkward_response(result, |_| None)?;
    Ok(serde_json::from_slice(&response.body)?)
}

#[derive(Serialize)]
struct UpdateWorkPropertiesRequest<'body> {
    #[serde(flatten)]
    credentials: WorkCredentials<'body>,

    #[serde(rename = "identity")]
    identity: ThreemaId,

    #[serde(rename = "version")]
    version: &'body str,
}

/// Request an authentication challenge to update work properties.
///
/// Note: Technically, this is a part that the work directory should do but historically it is part of the
/// directory. So, we're reflecting that... for now.
pub(cratefn update_work_properties_authentication_request(
    client_info: &ClientInfo,
    directory_server_url: &DirectoryServerBaseUrl,
    work_context: &WorkContext,
    identity: ThreemaId,
) -> HttpsRequest {
    HttpsRequest {
        timeout: TIMEOUT,
        url: directory_server_url.update_work_properties_path(),
        method: HttpsMethod::Post,
        headers: https_headers_with_authentication(work_context)
            .accept("application/json")
            .build(client_info),
        body: serde_json::to_vec(&UpdateWorkPropertiesRequest {
            credentials: (&work_context.credentials).into(),
            identity,
            version: &client_info.to_semicolon_separated(),
        })
        .expect("Failed to create update work properties challenge request body"),
    }
}

#[derive(Serialize)]
struct UpdateWorkPropertiesAuthenticatedRequest<'body> {
    #[serde(flatten)]
    request: UpdateWorkPropertiesRequest<'body>,

    #[serde(flatten)]
    authentication: AuthenticationChallengeResponse,
}

/// Update work properties.
///
/// Note: Technically, this is a part that the work directory should do but historically it is part of the
/// directory. So, we're reflecting that... for now.
pub(cratefn update_work_properties_request(
    client_info: &ClientInfo,
    directory_server_url: &DirectoryServerBaseUrl,
    work_context: &WorkContext,
    identity: ThreemaId,
    authentication: AuthenticationChallengeResponse,
) -> HttpsRequest {
    HttpsRequest {
        timeout: TIMEOUT,
        url: directory_server_url.update_work_properties_path(),
        method: HttpsMethod::Post,
        headers: https_headers_with_authentication(work_context)
            .accept("application/json")
            .build(client_info),
        body: serde_json::to_vec(&UpdateWorkPropertiesAuthenticatedRequest {
            request: UpdateWorkPropertiesRequest {
                credentials: (&work_context.credentials).into(),
                identity,
                version: &client_info.to_semicolon_separated(),
            },
            authentication,
        })
        .expect("Failed to create update work properties request body"),
    }
}

/// Process the result after attempting to update work properties.
pub(cratefn handle_update_work_properties_result(result: HttpsResult) -> Result<(), HttpsEndpointError> {
    let _ = handle_status_and_awkward_response(result, |_| None)?;
    Ok(())
}

Messung V0.5 in Prozent
C=95 H=100 G=97

¤ Dauer der Verarbeitung: 0.15 Sekunden  (vorverarbeitet am  2026-06-29) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik