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
use reqwest::Client;

use crate::api::data::{Error as DataError, OwnedData};
use crate::api::nonce::{request_nonce, NonceError};
use crate::api::request::{ensure_success, ResponseError};
use crate::api::url::UrlBuilder;
use crate::crypto::key_set::KeySet;
use crate::file::remote_file::RemoteFile;

/// An action to change a password of an uploaded Send file.
///
/// This API specification for this action is compatible with both Firefox Send v2 and v3.
pub struct Password<'a> {
    /// The remote file to change the password for.
    file: &'a RemoteFile,

    /// The new password to use for the file.
    password: &'a str,

    /// The authentication nonce.
    /// May be an empty vector if the nonce is unknown.
    nonce: Vec<u8>,
}

impl<'a> Password<'a> {
    /// Construct a new password action for the given remote file.
    pub fn new(file: &'a RemoteFile, password: &'a str, nonce: Option<Vec<u8>>) -> Self {
        Self {
            file,
            password,
            nonce: nonce.unwrap_or_default(),
        }
    }

    /// Invoke the password action.
    pub fn invoke(mut self, client: &Client) -> Result<(), Error> {
        // Create a key set for the file
        let mut key = KeySet::from(self.file, None);

        // Fetch the authentication nonce if not set yet
        if self.nonce.is_empty() {
            self.nonce = self.fetch_auth_nonce(client)?;
        }

        // Derive a new authentication key
        key.derive_auth_password(self.password, &UrlBuilder::download(self.file, true));

        // Build the password data, wrap it as owned
        let data = OwnedData::from(PasswordData::from(&key), &self.file)
            .map_err(|err| -> PrepareError { err.into() })?;

        // Send the request to change the password
        self.change_password(client, &data)
    }

    /// Fetch the authentication nonce for the file from the Send server.
    fn fetch_auth_nonce(&self, client: &Client) -> Result<Vec<u8>, Error> {
        request_nonce(client, UrlBuilder::download(self.file, false)).map_err(|err| err.into())
    }

    /// Send the request for changing the file password.
    fn change_password(
        &self,
        client: &Client,
        data: &OwnedData<PasswordData>,
    ) -> Result<(), Error> {
        // Get the password URL, and send the change
        let url = UrlBuilder::api_password(self.file);
        let response = client
            .post(url)
            .json(&data)
            .send()
            .map_err(|_| ChangeError::Request)?;

        // Ensure the response is successful
        ensure_success(&response).map_err(|err| err.into())
    }
}

/// The data object to send to the password endpoint,
/// which sets the file password.
#[derive(Debug, Serialize)]
struct PasswordData {
    /// The authentication key
    auth: String,
}

impl PasswordData {
    /// Create the password data object from the given key set.
    pub fn from(key: &KeySet) -> PasswordData {
        PasswordData {
            auth: key.auth_key_encoded().unwrap(),
        }
    }
}

#[derive(Fail, Debug)]
pub enum Error {
    /// An error occurred while preparing the action.
    #[fail(display = "failed to prepare setting the password")]
    Prepare(#[cause] PrepareError),

    /// The given Send file has expired, or did never exist in the first place.
    /// Therefore the file could not be downloaded.
    #[fail(display = "the file has expired or did never exist")]
    Expired,

    /// An error has occurred while sending the password change request to
    /// the server.
    #[fail(display = "failed to send the password change request")]
    Change(#[cause] ChangeError),
}

impl From<NonceError> for Error {
    fn from(err: NonceError) -> Error {
        match err {
            NonceError::Expired => Error::Expired,
            err => Error::Prepare(PrepareError::Auth(err)),
        }
    }
}

impl From<PrepareError> for Error {
    fn from(err: PrepareError) -> Error {
        Error::Prepare(err)
    }
}

impl From<ChangeError> for Error {
    fn from(err: ChangeError) -> Error {
        Error::Change(err)
    }
}

impl From<ResponseError> for Error {
    fn from(err: ResponseError) -> Error {
        match err {
            ResponseError::Expired => Error::Expired,
            err => Error::Change(ChangeError::Response(err)),
        }
    }
}

#[derive(Fail, Debug)]
pub enum PrepareError {
    /// Failed authenticating, needed to set a new password.
    #[fail(display = "failed to authenticate")]
    Auth(#[cause] NonceError),

    /// Some error occurred while building the data that will be sent.
    /// The owner token might possibly be missing, the wrapped error will
    /// describe this further.
    #[fail(display = "")]
    Data(#[cause] DataError),
}

impl From<DataError> for PrepareError {
    fn from(err: DataError) -> PrepareError {
        PrepareError::Data(err)
    }
}

#[derive(Fail, Debug)]
pub enum ChangeError {
    /// Sending the request to change the password failed.
    #[fail(display = "failed to send password change request")]
    Request,

    /// The server responded with an error while changing the file password.
    #[fail(display = "bad response from server while changing password")]
    Response(#[cause] ResponseError),
}