1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
pub use crate::internal::user_api::{
    EncryptedPrivateKey, UserCreateResult, UserDevice, UserDeviceListResult, UserId, UserResult,
    UserUpdatePrivateKeyResult,
};
use crate::{
    internal::{
        user_api::{self, DeviceId, DeviceName},
        PublicKey, OUR_REQUEST,
    },
    DeviceContext, IronOxide, Result,
};
use recrypt::api::Recrypt;
use std::{collections::HashMap, convert::TryInto};
use tokio::runtime::current_thread::Runtime;

/// Optional parameters for creating a new device instance.
#[derive(Debug, PartialEq, Clone)]
pub struct DeviceCreateOpts {
    device_name: Option<DeviceName>,
}
impl DeviceCreateOpts {
    /// Create a new device with an optional readable name for the device.
    pub fn new(device_name: Option<DeviceName>) -> DeviceCreateOpts {
        DeviceCreateOpts { device_name }
    }
}
impl Default for DeviceCreateOpts {
    fn default() -> Self {
        DeviceCreateOpts::new(None)
    }
}

/// Options that can be specified at when calling [`user_create`](trait.UserOps.html#tymethod.user_create).
pub struct UserCreateOpts {
    // see docs on `new`
    needs_rotation: bool,
}

impl UserCreateOpts {
    /// Constructor. Also see [`default`](#method.default).
    ///
    /// # Arguments
    /// - `needs_rotation` - Set to true if the private key for this user should be rotated when the user "takes control" of their keys.
    ///
    /// The main use case for this is a workflow that requires that users and groups to be generated
    /// prior to the user logging in for the first time. In this situation, a user's cryptographic identity
    /// can be generated by a third party, like a server process, and then the user can take control of their
    /// keys by rotating the private key using [`rotate_private_key`](https://github.com/IronCoreLabs/ironoxide/issues/44).
    pub fn new(needs_rotation: bool) -> UserCreateOpts {
        UserCreateOpts { needs_rotation }
    }
}

impl Default for UserCreateOpts {
    fn default() -> Self {
        UserCreateOpts::new(false)
    }
}

pub trait UserOps {
    /// Sync a new user within the IronCore system.
    ///
    /// # Arguments
    /// - `jwt` - Valid IronCore or Auth0 JWT
    /// - `password` - Password used to encrypt and escrow the user's private master key
    /// - `user_create_opts` - see [`UserCreateOpts`](struct.UserCreateOpts.html)
    /// # Returns
    /// Newly generated `UserCreateResult` or Err. For most use cases, this public key can
    /// be discarded as IronCore escrows your user's keys. The escrowed keys are unlocked
    /// by the provided password.
    fn user_create(
        jwt: &str,
        password: &str,
        user_create_opts: &UserCreateOpts,
    ) -> Result<UserCreateResult>;

    /// Get all the devices for the current user
    ///
    /// # Returns
    /// All devices for the current user, sorted by the device id.
    fn user_list_devices(&self) -> Result<UserDeviceListResult>;

    /// Generates a new device for the user specified in the signed JWT.
    ///
    /// This will result in a new transform key (from the user's master private key to the new device's public key)
    /// being generated and stored with the IronCore Service.
    ///
    /// # Arguments
    /// - `jwt`                   - Valid IronCore JWT
    /// - `password`              - Password used to encrypt and escrow the user's private key
    /// - `device_create_options` - Optional device create arguments, like device name
    ///
    /// # Returns
    /// Details about the newly created device.
    fn generate_new_device(
        jwt: &str,
        password: &str,
        device_create_options: &DeviceCreateOpts,
    ) -> Result<DeviceContext>;

    /// Delete a user device.
    ///
    /// If deleting the currently signed in device (None for `device_id`), the sdk will need to be
    /// reinitialized with `IronOxide.initialize()` before further use.
    ///
    /// # Arguments
    /// - `device_id` - ID of the device to delete. Get from `user_list_devices`. If None, deletes the currently SDK contexts device which
    ///                 once deleted will cause this SDK instance to no longer function.
    ///
    /// # Returns
    /// Id of deleted device or IronOxideErr
    fn user_delete_device(&self, device_id: Option<&DeviceId>) -> Result<DeviceId>;

    /// Verify a user given a JWT for their user record.
    ///
    /// # Arguments
    /// - `jwt` - Valid IronCore JWT
    ///
    /// # Returns
    /// Option of whether the user's account record exists in the IronCore system or not. Err if the request couldn't be made.
    fn user_verify(jwt: &str) -> Result<Option<UserResult>>;

    /// Get a list of user public keys given their IDs. Allows discovery of which user IDs have keys in the
    /// IronCore system to determine of they can be added to groups or have documents shared with them.
    ///
    /// # Arguments
    /// - users - List of user IDs to check
    ///
    /// # Returns
    /// Map from user ID to users public key. Only users who have public keys will be returned in the map.
    fn user_get_public_key(&self, users: &[UserId]) -> Result<HashMap<UserId, PublicKey>>;

    /// Rotate the current user's private key, but leave the public key the same.
    /// There's no black magic here! This is accomplished via multi-party computation with the
    /// IronCore webservice.
    ///
    /// # Arguments
    /// `password` - Password to unlock the current user's user master key
    ///
    /// # Returns
    /// The (encrypted) updated private key and associated metadata
    fn user_rotate_private_key(&self, password: &str) -> Result<UserUpdatePrivateKeyResult>;
}
impl UserOps for IronOxide {
    fn user_create(
        jwt: &str,
        password: &str,
        user_create_opts: &UserCreateOpts,
    ) -> Result<UserCreateResult> {
        let recrypt = Recrypt::new();
        let mut rt = Runtime::new().unwrap();
        rt.block_on(user_api::user_create(
            &recrypt,
            jwt.try_into()?,
            password.try_into()?,
            user_create_opts.needs_rotation,
            *OUR_REQUEST,
        ))
    }

    fn user_list_devices(&self) -> Result<UserDeviceListResult> {
        let mut rt = Runtime::new().unwrap();
        rt.block_on(user_api::device_list(self.device.auth()))
    }

    fn generate_new_device(
        jwt: &str,
        password: &str,
        device_create_options: &DeviceCreateOpts,
    ) -> Result<DeviceContext> {
        let recrypt = Recrypt::new();
        let mut rt = Runtime::new().unwrap();
        let device_create_options = device_create_options.clone();

        rt.block_on(user_api::generate_device_key(
            &recrypt,
            &jwt.try_into()?,
            password.try_into()?,
            device_create_options.device_name,
            &std::time::SystemTime::now().into(),
            &OUR_REQUEST,
        ))
    }

    fn user_delete_device(&self, device_id: Option<&DeviceId>) -> Result<DeviceId> {
        self.runtime
            .block_on(user_api::device_delete(self.device.auth(), device_id))
    }

    fn user_verify(jwt: &str) -> Result<Option<UserResult>> {
        let mut rt = Runtime::new().unwrap();
        rt.block_on(user_api::user_verify(jwt.try_into()?, *OUR_REQUEST))
    }

    fn user_get_public_key(&self, users: &[UserId]) -> Result<HashMap<UserId, PublicKey>> {
        self.runtime
            .block_on(user_api::user_key_list(self.device.auth(), &users.to_vec()))
    }

    fn user_rotate_private_key(&self, password: &str) -> Result<UserUpdatePrivateKeyResult> {
        self.runtime.block_on(user_api::user_rotate_private_key(
            &self.recrypt,
            password.try_into()?,
            self.device().auth(),
        ))
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use galvanic_assert::matchers::*;

    #[test]
    fn user_create_opts_defaults() {
        let opts = UserCreateOpts::default();
        assert_that!(
            &opts,
            has_structure!(UserCreateOpts {
                needs_rotation: eq(false)
            })
        )
    }
    #[test]
    fn user_create_opts_new() {
        let opts = UserCreateOpts::new(true);
        assert_that!(
            &opts,
            has_structure!(UserCreateOpts {
                needs_rotation: eq(true)
            })
        )
    }
}