Skip to main content

dittolive_ditto/identity/
mod.rs

1//! # Ditto needs an `Identity` to start syncing with other peers.
2//!
3//! The various identity configurations that you can use when initializing a `Ditto` instance.
4//!
5//! - [`OfflinePlayground`]: Develop peer-to-peer apps with no cloud connection. This mode offers no
6//!   security and must only be used for development.
7//! - [`OnlineWithAuthentication`]: Run Ditto in secure production mode, logging on to Ditto Cloud
8//!   or on on-premises authentication server. User permissions are centrally managed.
9//! - [`OnlinePlayground`]: Test a Ditto Cloud app with weak shared token authentication
10//!   ("Playground mode"). This mode is not secure and must only be used for development.
11//! - [`SharedKey`]: A mode where any device is trusted provided they  know the secret key. This is
12//!   a simplistic authentication model normally only suitable for private apps where users and
13//!   devices are both trusted.
14//! - [`Manual`]: A Ditto peer identity that was created manually rather than generated by an
15//!   identity service. This is a text bundle beginning with the line `-----BEGIN DITTO
16//!   IDENTITY-----`. To learn how to create and use Manual Identities please refer to Ditto's
17//!   online documentation.
18
19use std::sync::{Arc, Mutex};
20
21use ffi_sdk::BoxedIdentityConfig;
22
23use crate::{
24    ditto::AppId,
25    error::{DittoError, ErrorKind},
26};
27use_prelude!();
28
29// TODO(v5): Remove pub
30#[doc(hidden)]
31pub mod auth;
32
33use self::auth::LoginProvider;
34pub use self::auth::{
35    AuthenticationClientFeedback, DittoAuthenticationEventHandler, DittoAuthenticator,
36};
37
38// Identity is pub because it is part of the Builder's public interface (and technically
39// this very path: `::dittolive_ditto::identity::Identity`).
40// It is therefore Sealed to prevent downstream implementations
41#[doc(hidden)]
42pub trait Identity: private::Sealed + Send + Sync {
43    /// Consumes the identity's built IdentityConfig
44    fn identity_config(&self) -> Option<BoxedIdentityConfig>;
45
46    /// Returns whether a `DittoAuthenticator` is to be involved.
47    fn involves_authenticator(&self) -> bool {
48        false
49    }
50
51    /// REFACTOR(Ham): This is necessary for a couple of reasons:
52    ///
53    /// we only want to pass down our login provider down into the Rust core once we've had a
54    /// chance to make the FFI `Ditto` object and store a reference to it in the
55    /// `DittoAuthenticator`
56    fn set_login_provider_with_ditto_pointer(
57        &self,
58        _ditto_fields: &Arc<crate::ditto::DittoFields>,
59    ) {
60    }
61
62    /// Indicates if cloud sync should be enabled by default
63    fn is_cloud_sync_enabled(&self) -> bool;
64
65    /// Returns the configured URL for Auth
66    fn auth_url(&self) -> Result<String, DittoError>;
67
68    // TODO(v5): This shouldn't be necessary now that we're using the "transports behind core"
69    // implementation to manage transports
70    /// Returns the configured URL for WebSocket sync
71    fn sync_url(&self) -> Result<String, DittoError>;
72
73    /// Indicates whether the specific Identity type requires an offline only license token to be
74    /// set.
75    fn requires_offline_only_license_token(&self) -> bool;
76}
77
78/// Run Ditto in secure production mode, logging on to Ditto Cloud or an
79/// on-premises authentication server. User permissions are centrally managed.
80/// Sync will not work until a successful login has occurred.
81///
82/// The only required configuration is the application's UUID, which can be
83/// found on the Ditto portal where the app is registered.
84///
85/// By default cloud sync is enabled. This means the SDK will sync to a Big Peer
86/// in Ditto's cloud when an internet connection is available. This is
87/// controlled by the `enable_cloud_sync` parameter. If `true` (default), a
88/// suitable wss:// URL will be added to the [`TransportConfig`].
89///
90/// To prevent cloud sync, or to specify your own URL later, pass `false`.
91///
92/// Authentication requests are handled by Ditto's cloud by default, configured
93/// in the portal at `<https://portal.ditto.live>`.
94///
95/// To use a different or on-premises authentication service, pass a custom
96/// HTTPS base URL as the * `auth_url` parameter.
97pub struct OnlineWithAuthentication {
98    app_id: AppId,
99    enable_cloud_sync: bool,
100    auth_url: String,
101    identity_config: Mutex<Option<BoxedIdentityConfig>>,
102    auth_event_handler: Arc<dyn 'static + DittoAuthenticationEventHandler>,
103}
104
105#[doc(hidden)]
106#[deprecated(note = "use OnlinePlayground instead")]
107pub type OnlinePlaygroundV2 = OnlinePlayground;
108
109/// Test a Ditto Cloud app with a simple shared token ("Playground mode"). This
110/// mode offers no security and must only be used for development. Other
111/// behavior mirrors the [`OnlineWithAuthentication`] identity.
112pub struct OnlinePlayground {
113    app_id: AppId,
114    enable_cloud_sync: bool,
115    auth_url: String,
116    identity_config: Mutex<Option<BoxedIdentityConfig>>,
117}
118
119/// Develop peer-to-peer apps with no cloud connection. This mode offers no
120/// security and must only be used for development. In this mode, any string can
121/// be used as the name of the app.
122pub struct OfflinePlayground {
123    identity_config: Mutex<Option<BoxedIdentityConfig>>,
124}
125
126/// An identity where any device is trusted provided they know the secret key.
127/// This is a simplistic authentication model normally only suitable for private
128/// apps where users and devices are both trusted. In this mode, any string may
129/// be used as the app id.
130pub struct SharedKey {
131    identity_config: Mutex<Option<BoxedIdentityConfig>>,
132}
133
134/// An identity where devices are manually configured with a x509 certificate
135/// bundle
136pub struct Manual {
137    identity_config: Mutex<Option<BoxedIdentityConfig>>,
138}
139
140impl OnlineWithAuthentication {
141    /// Construct an OnlineWithAuthentication Identity.
142    ///
143    /// - `ditto_root`: The directory containing ditto local storage
144    /// - `app_id`: The unique identifier of the application, usually a UUIDv4 string
145    /// - `auth_event_handler`: A user created type which implements the
146    ///   `DittoAuthenticationEventHandler` trait
147    /// - `enable_cloud_sync`: whether cloud sync should be started by default
148    /// - `custom_auth_url`: an optional custom authentication URL; default is is `cloud.ditto.live`
149    pub fn new(
150        _ditto_root: Arc<dyn DittoRoot>, // TODO: Remove this in a future major release (v5)
151        app_id: AppId,
152        auth_event_handler: impl DittoAuthenticationEventHandler + 'static,
153        enable_cloud_sync: bool,
154        custom_auth_url: Option<&str>,
155    ) -> Result<Self, DittoError> {
156        let auth_url = match custom_auth_url {
157            Some(url) => url.to_owned(),
158            None => app_id.default_auth_url(),
159        };
160        let c_app_id = char_p::new(app_id.to_string());
161        let c_auth_url = char_p::new(auth_url.as_str());
162
163        let identity_config = Mutex::new(Some(
164            ffi_sdk::ditto_identity_config_make_online_with_authentication(
165                c_app_id.as_ref(),
166                c_auth_url.as_ref(),
167            )
168            .ok_or(ErrorKind::Config)?,
169        ));
170
171        Ok(OnlineWithAuthentication {
172            app_id,
173            enable_cloud_sync,
174            auth_url,
175            identity_config,
176            auth_event_handler: Arc::new(auth_event_handler),
177        })
178    }
179}
180
181impl Identity for OnlineWithAuthentication {
182    fn identity_config(&self) -> Option<BoxedIdentityConfig> {
183        let mut config = self.identity_config.lock().unwrap();
184        config.take()
185    }
186
187    fn involves_authenticator(&self) -> bool {
188        true
189    }
190
191    fn set_login_provider_with_ditto_pointer(&self, ditto_fields: &Arc<crate::ditto::DittoFields>) {
192        let authenticator = DittoAuthenticator {
193            ditto_fields: Arc::downgrade(ditto_fields),
194        };
195        let auth_event_handler = self.auth_event_handler.retain();
196        let LoginProvider(c_provider) = LoginProvider::new(auth_event_handler, authenticator);
197        ffi_sdk::ditto_auth_set_login_provider(&ditto_fields.ditto, Some(c_provider));
198    }
199
200    fn is_cloud_sync_enabled(&self) -> bool {
201        self.enable_cloud_sync
202    }
203
204    fn auth_url(&self) -> Result<String, DittoError> {
205        Ok(self.auth_url.to_owned())
206    }
207
208    fn sync_url(&self) -> Result<String, DittoError> {
209        Ok(self.app_id.default_sync_url())
210    }
211
212    fn requires_offline_only_license_token(&self) -> bool {
213        false
214    }
215}
216
217impl OnlinePlayground {
218    /// Construct a new `OnlinePlayground` identity.
219    ///
220    /// - `ditto_root`: Instance of DittoRoot indicating local storage directory
221    /// - `app_id`: A unique AppId which must be a valid UUIDv4
222    /// - `shared_token`: A shared token used to set up the `OnlinePlayground` session. This token
223    ///   is provided by the portal when setting up the application.
224    /// - `enable_cloud_sync`: Should WebSocket sync with `wss://<app_id>.cloud.ditto.live` be
225    ///   enabled by default. Do not enable this if you want to provide a custom sync URL later
226    /// - `custom_auth_url`: An optional alternative URL for authentication requests. Defaults to `https://<app_id>.cloud.ditto.live/`
227    pub fn new(
228        _ditto_root: Arc<dyn DittoRoot>, // TODO: Remove this in a future major release (v5)
229        app_id: AppId,
230        shared_token: String,
231        enable_cloud_sync: bool,
232        custom_auth_url: Option<&str>,
233    ) -> Result<Self, DittoError> {
234        let c_app_id = app_id.to_c_string();
235        let c_shared_token = char_p::new(shared_token.as_str());
236        let auth_url = match custom_auth_url {
237            Some(url) => url.to_owned(),
238            None => app_id.default_auth_url(),
239        };
240        let c_auth_url = char_p::new(auth_url.as_str());
241        let identity_config = Mutex::new(Some(
242            ffi_sdk::ditto_identity_config_make_online_playground(
243                c_app_id.as_ref(),
244                c_shared_token.as_ref(),
245                c_auth_url.as_ref(),
246            )
247            .ok_or(ErrorKind::Config)?,
248        ));
249
250        Ok(Self {
251            app_id,
252            enable_cloud_sync,
253            auth_url,
254            identity_config,
255        })
256    }
257}
258
259impl Identity for OnlinePlayground {
260    fn identity_config(&self) -> Option<BoxedIdentityConfig> {
261        let mut config = self.identity_config.lock().unwrap();
262        config.take()
263    }
264
265    fn involves_authenticator(&self) -> bool {
266        true
267    }
268
269    fn auth_url(&self) -> Result<String, DittoError> {
270        Ok(self.auth_url.to_owned())
271    }
272
273    fn sync_url(&self) -> Result<String, DittoError> {
274        Ok(self.app_id.default_sync_url())
275    }
276
277    fn is_cloud_sync_enabled(&self) -> bool {
278        self.enable_cloud_sync
279    }
280
281    fn requires_offline_only_license_token(&self) -> bool {
282        false
283    }
284}
285
286impl OfflinePlayground {
287    /// Construct an `OfflinePlayground` identity.
288    ///
289    /// - `ditto_root`: Location for the local database
290    /// - `app_id`: A unique string identifying the App. For an `OfflinePlayground` identity this
291    ///   does not need to be a UUIDv4.
292    // TODO: Remove `ditto_root` in a future major release (v5)
293    pub fn new(_ditto_root: Arc<dyn DittoRoot>, app_id: AppId) -> Result<Self, DittoError> {
294        let c_app_id = app_id.to_c_string();
295        let identity_config = Mutex::new(Some(
296            ffi_sdk::ditto_identity_config_make_offline_playground(c_app_id.as_ref(), 0)
297                .ok_or(ErrorKind::Config)?,
298        ));
299        Ok(Self { identity_config })
300    }
301
302    /// Generate an `OfflinePlayground` identity with a random AppId for testing
303    pub fn random(ditto_root: Arc<dyn DittoRoot>) -> Result<Self, DittoError> {
304        let app_id = AppId::generate();
305        Self::new(ditto_root, app_id)
306    }
307}
308
309impl Identity for OfflinePlayground {
310    fn identity_config(&self) -> Option<BoxedIdentityConfig> {
311        let mut config = self.identity_config.lock().unwrap();
312        config.take()
313    }
314
315    fn is_cloud_sync_enabled(&self) -> bool {
316        false
317    }
318
319    fn auth_url(&self) -> Result<String, DittoError> {
320        Err(DittoError::new(
321            ErrorKind::InvalidInput,
322            "Cloud Auth is not enabled for OfflinePlayground Identities".to_string(),
323        ))
324    }
325    fn sync_url(&self) -> Result<String, DittoError> {
326        Err(DittoError::new(
327            ErrorKind::InvalidInput,
328            "Cloud sync is not enabled for OfflinePlayground Identities".to_string(),
329        ))
330    }
331
332    fn requires_offline_only_license_token(&self) -> bool {
333        true
334    }
335}
336
337impl SharedKey {
338    /// Construct a `SharedKey` identity.
339    ///
340    /// - `ditto_root`: An instance of `DittoRoot` for local storage
341    /// - `app_id`: The unique UUIDv4 identifying this app; must be shared across all instances
342    /// - `key_der_b64`: A pre-shared key for securing peer-to-peer communication encoded as a
343    ///   Base64 string
344    pub fn new(
345        _ditto_root: Arc<dyn DittoRoot>, // TODO: Remove this in a future major release (v5)
346        app_id: AppId,
347        key_der_b64: &str,
348    ) -> Result<Self, DittoError> {
349        let c_app_id = app_id.to_c_string();
350        let c_key_der = char_p::new(key_der_b64);
351        let identity_config = Mutex::new(Some(
352            ffi_sdk::ditto_identity_config_make_shared_key(
353                c_app_id.as_ref(),
354                c_key_der.as_ref(),
355                0,
356            )
357            .ok_or(ErrorKind::Config)?,
358        ));
359        Ok(Self { identity_config })
360    }
361}
362
363impl Identity for SharedKey {
364    fn identity_config(&self) -> Option<BoxedIdentityConfig> {
365        let mut config = self.identity_config.lock().unwrap();
366        config.take()
367    }
368
369    fn auth_url(&self) -> Result<String, DittoError> {
370        let msg = "SharedKey Identities don't support auth urls".to_owned();
371        Err(DittoError::new(ErrorKind::Config, msg))
372    }
373
374    fn sync_url(&self) -> Result<String, DittoError> {
375        let msg = "SharedKey Identities to support cloud sync".to_owned();
376        Err(DittoError::new(ErrorKind::Config, msg))
377    }
378
379    fn is_cloud_sync_enabled(&self) -> bool {
380        false
381    }
382
383    fn requires_offline_only_license_token(&self) -> bool {
384        true
385    }
386}
387
388impl Manual {
389    /// Construct a `Manual` identity.
390    ///
391    /// A Ditto peer identity that was created manually rather than generated by an identity
392    /// service. This is a text bundle beginning with the line "-----BEGIN DITTO IDENTITY-----".
393    /// To learn how to create and use Manual Identities please refer to Ditto's online
394    /// documentation.
395    ///
396    /// - `manual_identity`: a multiline string containing a Manual Identity bundle
397    pub fn new(manual_identity: impl Into<String>) -> Result<Self, DittoError> {
398        let manual_identity_str = char_p::new(manual_identity.into());
399        let identity_config = Mutex::new(Some(
400            ffi_sdk::ditto_identity_config_make_manual(manual_identity_str.as_ref())
401                .ok_or(ErrorKind::Config)?,
402        ));
403        Ok(Self { identity_config })
404    }
405}
406
407impl Identity for Manual {
408    fn identity_config(&self) -> Option<BoxedIdentityConfig> {
409        let mut config = self.identity_config.lock().unwrap();
410        config.take()
411    }
412
413    fn auth_url(&self) -> Result<String, DittoError> {
414        let msg = "Manual Identities don't support auth urls".to_owned();
415        Err(DittoError::new(ErrorKind::Config, msg))
416    }
417
418    fn sync_url(&self) -> Result<String, DittoError> {
419        let msg = "Manual Identities to support cloud sync".to_owned();
420        Err(DittoError::new(ErrorKind::Config, msg))
421    }
422
423    fn is_cloud_sync_enabled(&self) -> bool {
424        false
425    }
426
427    fn requires_offline_only_license_token(&self) -> bool {
428        true
429    }
430}
431
432mod private {
433    use super::*;
434    pub trait Sealed {}
435    impl Sealed for OnlineWithAuthentication {}
436    impl Sealed for OnlinePlayground {}
437    impl Sealed for OfflinePlayground {}
438    impl Sealed for SharedKey {}
439    impl Sealed for Manual {}
440}