ocm_types/discovery.rs
1// SPDX-FileCopyrightText: 2026 Matthias Kraus <info@opengeomesh.org>
2//
3// SPDX-License-Identifier: LGPL-3.0-or-later
4
5use std::collections::HashMap;
6
7use serde::{Deserialize, Serialize};
8
9use crate::common::ShareType;
10
11/// This is the response payload of the discovery endpoint, representing the
12/// properties and capabilities offered by this server.
13#[allow(rustdoc::bare_urls)]
14#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
15#[serde(rename_all = "camelCase")]
16pub struct Discovery {
17 /// Whether the OCM service is enabled at this endpoint
18 pub enabled: bool,
19 /// The OCM API version this endpoint supports
20 pub api_version: String,
21 /// The URI of the OCM API available at this endpoint
22 /// Example: https://cloud.example.org/ocm
23 pub end_point: String,
24 /// A friendly branding name of this endpoint
25 /// Example: MyCloudStorage
26 #[serde(skip_serializing_if = "Option::is_none", default)]
27 pub provider: Option<String>,
28 /// A list of all supported resource types with their access protocols.
29 /// Each resource type is identified by its `name`: the list MUST NOT
30 /// contain more than one resource type object per given `name`.
31 pub resource_types: Vec<Resource>,
32 /// The optional capabilities exposed at this endpoint according to the
33 /// present specifications.
34 /// ```
35 /// use ocm_types::discovery::Capability;
36 ///
37 /// let capabilities = vec![
38 /// Capability::WebdavUri,
39 /// Capability::ProtocolObject,
40 /// ];
41 /// let json: Vec<Capability> = serde_json::from_str(r#"[
42 /// "webdav-uri",
43 /// "protocol-object"
44 /// ]"#).unwrap();
45 /// assert_eq!(capabilities, json);
46 /// ```
47 #[serde(skip_serializing_if = "Option::is_none", default)]
48 pub capabilities: Option<Vec<Capability>>,
49 /// The criteria for accepting a Share Creation Notification.
50 /// As all Receiving Servers should require the use of TLS in API calls,
51 /// it is not necessary to expose that as a criterium.
52 ///
53 /// example:
54 /// - allowlist
55 /// - invite
56 /// ```
57 /// use ocm_types::discovery::Criterium;
58 ///
59 /// let criteria = vec![
60 /// Criterium::Allowlist,
61 /// Criterium::Invite,
62 /// ];
63 /// let json: Vec<Criterium> = serde_json::from_str(r#"[
64 /// "allowlist",
65 /// "invite"
66 /// ]"#).unwrap();
67 /// assert_eq!(criteria, json);
68 /// ```
69 #[serde(skip_serializing_if = "Option::is_none", default)]
70 pub criteria: Option<Vec<Criterium>>,
71 /// The signatory used to sign outgoing request to confirm its origin.
72 /// The signatory is optional but it MUST contain `id` and `publicKeyPem`.
73 #[deprecated(
74 note = "Use public keys at `/.well-known/jwks.json` instead for RFC 9421 support"
75 )]
76 #[serde(skip_serializing_if = "Option::is_none", default)]
77 pub public_key: Option<PublicKey>,
78 /// Optional URL path of a web page where a user can accept an invite, when
79 /// query parameters "token" and "providerDomain" are provided.
80 /// Implementations that offer the invites capability SHOULD provide this URL
81 /// as well in order to enhance the UX of the Invite Flow. If for example
82 /// "/index.php/apps/sciencemesh/accept" is specified here then a
83 /// Where-Are-You-From page could redirect the end-user to
84 /// /index.php/apps/sciencemesh/accept?token=zi5kooKu3ivohr9a&providerDomain=example.com.
85 #[serde(skip_serializing_if = "Option::is_none", default)]
86 pub invite_accept_dialog: Option<String>,
87 /// URL of the token endpoint where the Sending Server can exchange a refreshToken
88 /// for a short-lived bearer token.
89 /// Implementations that offer the [Capability::ExchangeToken] MUST provide this URL
90 /// as well.
91 // TODO enforce token_end_point to be available when ExchangeToken Capability is advertised?
92 pub token_end_point: Option<String>,
93}
94
95#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
96pub struct ApiVersion(pub String);
97
98#[allow(rustdoc::bare_urls)]
99#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize)]
100#[serde(rename_all = "camelCase")]
101pub struct PublicKey {
102 /// unique id of the key in URI format. The hostname set the origin
103 /// of the request and MUST be identical to the current discovery endpoint.
104 /// example: https://my-cloud-storage.org/ocm#signature
105 pub key_id: String,
106 /// PEM-encoded version of the public key.
107 /// example:
108 /// -----BEGIN PUBLIC KEY-----
109 /// MII...QDD
110 /// -----END PUBLIC KEY-----
111 pub public_key_pem: String,
112}
113
114#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
115#[serde(rename_all = "kebab-case")]
116pub enum Criterium {
117 /// to indicate that API requests
118 /// without http signatures will be rejected.
119 HttpRequestSignatures,
120 /// To indicate that API requests without token exchange will be rejected.
121 TokenExchange,
122 /// some servers MAY be blocked based on their address
123 Denylist,
124 /// unknown servers MAY be blocke
125 Allowlist,
126 /// an invite MUST have been exchanged between the
127 /// sender and the receiver before a [crate::share::NewShare] can be
128 /// sent
129 Invite,
130}
131
132#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
133#[serde(rename_all = "kebab-case")]
134pub enum Capability {
135 /// to indicate that this OCM Server can apply a Sending Server's MFA requirements for
136 /// a Share on their behalf.
137 EnforceMfa,
138 /// to indicate that this OCM Server exposes a [RFC6749](https://datatracker.ietf.org/doc/rfc6749/)-compliant
139 /// endpoint, which allows to exchange a secret received in the [crate::share::WebDavProperties] of a
140 /// [crate::share::NewShare] for a short-lived bearer token.
141 ExchangeToken,
142 /// to indicate that this OCM Server supports
143 /// [RFC9421] HTTP Message Signatures and advertises public keys in the
144 /// format specified by [RFC7517] at the `/.well-known/jwks.json`
145 /// endpoint for signature verification.
146 HttpSig,
147 /// to indicate the server would support acting as an Invite Sender or Invite Receiver
148 /// OCM Server. This might be useful for suggesting to a user that existing contacts
149 /// might be upgraded to the more secure (and possibly required) invite flow.
150 Invites,
151 /// to indicate that this OCM Server exposes a WAYF Page to facilitate the Invite flow.
152 InviteWayf,
153 /// to indicate that this OCM Server handles
154 /// notifications to exchange updates on shares and invites.
155 Notifications,
156 /// to indicate that this OCM Server can receive a Share Creation Notification whose
157 /// protocol object contains one property per supported protocol instead of containing
158 /// the standard name and options properties.
159 ProtocolObject,
160 /// to indicate that this OCM Server can append a relative URI to the path listed for
161 /// WebDAV in the appropriate resourceTypes entry
162 WebdavUri,
163}
164
165#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
166#[serde(rename_all = "camelCase")]
167pub struct Resource {
168 /// A supported resource type (file, folder, calendar, contact,
169 /// ...).
170 /// Implementations MUST support `file` at a minimum.
171 /// Example: file
172 pub name: String,
173 /// The supported recipient share types.
174 /// Implementations MUST support `user` at a minimum.
175 /// Example: user
176 pub share_types: Vec<ShareType>,
177 /// The supported protocols to access shared resources at this
178 /// endpoint.
179 /// Implementations MUST support at least `webdav` for `file` resources,
180 /// any other combination of resources and protocols is optional.
181 ///
182 // properties:
183 // webdav:
184 // type: string
185 // description: >
186 // The top-level WebDAV path at this endpoint. In order to
187 // access
188 // a remote shared resource, implementations SHOULD
189
190 // use this path
191 // as a prefix (see sharing examples).
192 // webapp:
193 // type: string
194 // description: >
195 // The top-level path for web apps at this endpoint. In order
196 // to
197 // access a remote web app, implementations SHOULD use this path
198 // as a prefix (see sharing examples).
199 // datatx:
200 // type: string
201 // description: >
202 // The top-level path to be used for data transfers. In order
203 // to
204 // access a remote shared resource, implementations SHOULD use
205 // this path as a prefix (see sharing examples). In addition,
206 // implementations are expected to execute the transfer using
207 // WebDAV as the wire protocol.
208 // patternProperties:
209 // ^.*$:
210 // type: string
211 // description: >
212 // Any additional protocol supported for this resource type
213 // MAY
214 // be advertised here, where the value MAY correspond to a top-level
215 // URI to be used for that protocol.
216 /// ```
217 /// use std::collections::HashMap;
218 ///
219 /// let resources = HashMap::from_iter(vec![
220 /// ("webdav".to_string(), "/remote/dav/ocm/".to_string()),
221 /// ("webapp".to_string(), "/apps/ocm/".to_string()),
222 /// ("talk".to_string(), "/apps/speed/api/".to_string()),
223 /// ("ssh".to_string(), "example.org:2222".to_string())
224 /// ].into_iter());
225 ///
226 /// let json: HashMap<String,String> = serde_json::from_str(r#"{
227 /// "webdav": "/remote/dav/ocm/",
228 /// "webapp": "/apps/ocm/",
229 /// "talk": "/apps/speed/api/",
230 /// "ssh": "example.org:2222"
231 /// }"#).unwrap();
232 /// assert_eq!(resources, json);
233 /// ```
234 pub protocols: HashMap<String, String>,
235}