ocm-types 0.2.1

Types required to implement the OpenCloudMesh filesharing protocol
Documentation
// SPDX-FileCopyrightText: 2026 Matthias Kraus <info@opengeomesh.org>
//
// SPDX-License-Identifier: LGPL-3.0-or-later

use std::collections::HashMap;

use serde::{Deserialize, Serialize};

use crate::common::ShareType;

/// This is the response payload of the discovery endpoint, representing the
/// properties and capabilities offered by this server.
#[allow(rustdoc::bare_urls)]
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Discovery {
    /// Whether the OCM service is enabled at this endpoint
    pub enabled: bool,
    /// The OCM API version this endpoint supports
    pub api_version: String,
    /// The URI of the OCM API available at this endpoint
    /// Example: https://cloud.example.org/ocm
    pub end_point: String,
    /// A friendly branding name of this endpoint
    /// Example: MyCloudStorage
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub provider: Option<String>,
    /// A list of all supported resource types with their access protocols.
    /// Each resource type is identified by its `name`: the list MUST NOT
    /// contain more than one resource type object per given `name`.
    pub resource_types: Vec<Resource>,
    /// The optional capabilities exposed at this endpoint according to the
    /// present specifications.
    /// ```
    /// use ocm_types::discovery::Capability;
    ///
    /// let capabilities = vec![
    ///     Capability::WebdavUri,
    ///     Capability::ProtocolObject,
    /// ];
    /// let json: Vec<Capability> = serde_json::from_str(r#"[
    ///     "webdav-uri",
    ///     "protocol-object"
    /// ]"#).unwrap();
    /// assert_eq!(capabilities, json);
    /// ```
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub capabilities: Option<Vec<Capability>>,
    /// The criteria for accepting a Share Creation Notification.
    /// As all Receiving Servers should require the use of TLS in API calls,
    /// it is not necessary to expose that as a criterium.
    ///
    /// example:
    /// - allowlist
    /// - invite
    /// ```
    /// use ocm_types::discovery::Criterium;
    ///
    /// let criteria = vec![
    ///     Criterium::Allowlist,
    ///     Criterium::Invite,
    /// ];
    /// let json: Vec<Criterium> = serde_json::from_str(r#"[
    ///     "allowlist",
    ///     "invite"
    /// ]"#).unwrap();
    /// assert_eq!(criteria, json);
    /// ```
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub criteria: Option<Vec<Criterium>>,
    /// The signatory used to sign outgoing request to confirm its origin.
    /// The signatory is optional but it MUST contain `id` and `publicKeyPem`.
    #[deprecated(
        note = "Use public keys at `/.well-known/jwks.json` instead for RFC 9421 support"
    )]
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub public_key: Option<PublicKey>,
    /// Optional URL path of a web page where a user can accept an invite, when
    /// query parameters "token" and "providerDomain" are provided.
    /// Implementations that offer the invites capability SHOULD provide this URL
    /// as well in order to enhance the UX of the Invite Flow. If for example
    /// "/index.php/apps/sciencemesh/accept" is specified here then a
    /// Where-Are-You-From page could redirect the end-user to
    /// /index.php/apps/sciencemesh/accept?token=zi5kooKu3ivohr9a&providerDomain=example.com.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub invite_accept_dialog: Option<String>,
    /// URL of the token endpoint where the Sending Server can exchange a refreshToken
    /// for a short-lived bearer token.
    /// Implementations that offer the [Capability::ExchangeToken] MUST provide this URL
    /// as well.
    // TODO enforce token_end_point to be available when ExchangeToken Capability is advertised?
    pub token_end_point: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct ApiVersion(pub String);

#[allow(rustdoc::bare_urls)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PublicKey {
    /// unique id of the key in URI format. The hostname set the origin
    /// of the request and MUST be identical to the current discovery endpoint.
    /// example: https://my-cloud-storage.org/ocm#signature
    pub key_id: String,
    /// PEM-encoded version of the public key.
    /// example:
    ///  -----BEGIN PUBLIC KEY-----
    ///     MII...QDD
    ///  -----END PUBLIC KEY-----
    pub public_key_pem: String,
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum Criterium {
    /// to indicate that API requests
    /// without http signatures will be rejected.
    HttpRequestSignatures,
    /// To indicate that API requests without token exchange will be rejected.
    TokenExchange,
    /// some servers MAY be blocked based on their address
    Denylist,
    /// unknown servers MAY be blocke
    Allowlist,
    /// an invite MUST have been exchanged between the
    /// sender and the receiver before a [crate::share::NewShare] can be
    /// sent
    Invite,
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum Capability {
    /// to indicate that this OCM Server can apply a Sending Server's MFA requirements for
    /// a Share on their behalf.
    EnforceMfa,
    /// to indicate that this OCM Server exposes a [RFC6749](https://datatracker.ietf.org/doc/rfc6749/)-compliant
    /// endpoint, which allows to exchange a secret received in the [crate::share::WebDavProperties] of a
    /// [crate::share::NewShare] for a short-lived bearer token.
    ExchangeToken,
    /// to indicate that this OCM Server supports
    /// [RFC9421] HTTP Message Signatures and advertises public keys in the
    /// format specified by [RFC7517] at the `/.well-known/jwks.json`
    /// endpoint for signature verification.
    HttpSig,
    /// to indicate the server would support acting as an Invite Sender or Invite Receiver
    /// OCM Server. This might be useful for suggesting to a user that existing contacts
    /// might be upgraded to the more secure (and possibly required) invite flow.
    Invites,
    /// to indicate that this OCM Server exposes a WAYF Page to facilitate the Invite flow.
    InviteWayf,
    /// to indicate that this OCM Server handles
    /// notifications to exchange updates on shares and invites.
    Notifications,
    /// to indicate that this OCM Server can receive a Share Creation Notification whose
    /// protocol object contains one property per supported protocol instead of containing
    /// the standard name and options properties.
    ProtocolObject,
    /// to indicate that this OCM Server can append a relative URI to the path listed for
    /// WebDAV in the appropriate resourceTypes entry
    WebdavUri,
}

#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Resource {
    /// A supported resource type (file, folder, calendar, contact,
    /// ...).
    /// Implementations MUST support `file` at a minimum.
    /// Example: file
    pub name: String,
    /// The supported recipient share types.
    /// Implementations MUST support `user` at a minimum.
    /// Example: user
    pub share_types: Vec<ShareType>,
    /// The supported protocols to access shared resources at this
    /// endpoint.
    /// Implementations MUST support at least `webdav` for `file` resources,
    /// any other combination of resources and protocols is optional.
    ///
    // properties:
    //   webdav:
    //     type: string
    //     description: >
    //       The top-level WebDAV path at this endpoint. In order to
    //       access
    //       a remote shared resource, implementations SHOULD

    //     use this path
    //       as a prefix (see sharing examples).
    //   webapp:
    //     type: string
    //     description: >
    //       The top-level path for web apps at this endpoint. In order
    //       to
    //       access a remote web app, implementations SHOULD use this path
    //       as a prefix (see sharing examples).
    //   datatx:
    //     type: string
    //     description: >
    //       The top-level path to be used for data transfers. In order
    //       to
    //       access a remote shared resource, implementations SHOULD use
    //       this path as a prefix (see sharing examples). In addition,
    //       implementations are expected to execute the transfer using
    //       WebDAV as the wire protocol.
    // patternProperties:
    //   ^.*$:
    //     type: string
    //     description: >
    //       Any additional protocol supported for this resource type
    //       MAY
    //       be advertised here, where the value MAY correspond to a top-level
    //       URI to be used for that protocol.
    /// ```
    /// use std::collections::HashMap;
    ///
    /// let resources = HashMap::from_iter(vec![
    ///     ("webdav".to_string(), "/remote/dav/ocm/".to_string()),
    ///     ("webapp".to_string(), "/apps/ocm/".to_string()),
    ///     ("talk".to_string(), "/apps/speed/api/".to_string()),
    ///     ("ssh".to_string(), "example.org:2222".to_string())
    /// ].into_iter());
    ///
    /// let json: HashMap<String,String> = serde_json::from_str(r#"{
    ///     "webdav": "/remote/dav/ocm/",
    ///     "webapp": "/apps/ocm/",
    ///     "talk": "/apps/speed/api/",
    ///     "ssh": "example.org:2222"
    /// }"#).unwrap();
    /// assert_eq!(resources, json);
    /// ```
    pub protocols: HashMap<String, String>,
}