safe_authenticator 0.10.2

SAFE Authenticator
Documentation
// Copyright 2018 MaidSafe.net limited.
//
// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. Please review the Licences for the specific language governing
// permissions and limitations relating to use of the SAFE Network Software.

//! Routines to handle an app's dedicated containers.

use crate::access_container;
use crate::client::AuthClient;
use crate::{AuthError, AuthFuture};
use futures::Future;
use safe_core::{app_container_name, nfs, Client, FutureExt, MDataInfo, DIR_TAG};
use safe_nd::{MDataAction, MDataKind, MDataPermissionSet, MDataSeqEntryActions, PublicKey};

/// Returns an app's dedicated container if available and stored in the access container,
/// `None` otherwise.
#[cfg(test)]
pub fn fetch(client: &AuthClient, app_id: &str) -> Box<AuthFuture<Option<MDataInfo>>> {
    let app_cont_name = app_container_name(app_id);

    access_container::fetch_authenticator_entry(client)
        .and_then(move |(_, mut ac_entries)| Ok(ac_entries.remove(&app_cont_name)))
        .into_box()
}

/// Checks if an app's dedicated container is available and stored in the access container.
/// If no previously created container has been found, then it will be created.
pub fn fetch_or_create(
    client: &AuthClient,
    app_id: &str,
    app_pk: PublicKey,
) -> Box<AuthFuture<MDataInfo>> {
    let c2 = client.clone();
    let c3 = client.clone();
    let app_cont_name = app_container_name(app_id);

    access_container::fetch_authenticator_entry(client)
        .and_then(move |(ac_entry_version, mut ac_entries)| {
            match ac_entries.remove(&app_cont_name) {
                Some(mdata_info) => {
                    // Reuse the already existing app container and update
                    // permissions for it
                    let ps = MDataPermissionSet::new()
                        .allow(MDataAction::Read)
                        .allow(MDataAction::Insert)
                        .allow(MDataAction::Update)
                        .allow(MDataAction::Delete)
                        .allow(MDataAction::ManagePermissions);

                    c2.get_mdata_version(*mdata_info.address())
                        .and_then(move |version| {
                            c2.set_mdata_user_permissions(
                                *mdata_info.address(),
                                app_pk,
                                ps,
                                version + 1,
                            )
                            .map(move |_| mdata_info)
                        })
                        .map_err(From::from)
                        .into_box()
                }
                None => {
                    // If the container is not found, create it
                    create(&c2, app_pk)
                        .and_then(move |md_info| {
                            let _ = ac_entries.insert(app_cont_name, md_info.clone());

                            access_container::put_authenticator_entry(
                                &c3,
                                &ac_entries,
                                ac_entry_version + 1,
                            )
                            .map(move |()| md_info)
                        })
                        .into_box()
                }
            }
        })
        .into_box()
}

/// Removes an app's dedicated container if it's available and stored in the user's root dir.
/// Returns `true` if it was removed successfully and `false` if it wasn't found in the parent dir.
pub fn remove(client: AuthClient, app_id: &str) -> Box<AuthFuture<bool>> {
    let c2 = client.clone();
    let app_cont_name = app_container_name(app_id);

    access_container::fetch_authenticator_entry(&client)
        .and_then(move |(ac_entry_version, mut ac_entries)| {
            match ac_entries.remove(&app_cont_name) {
                None => {
                    // App container doesn't exist
                    ok!(false)
                }
                Some(mdata_info) => {
                    let c3 = c2.clone();

                    c2.list_seq_mdata_entries(mdata_info.name(), mdata_info.type_tag())
                        .and_then(move |entries| {
                            // Remove all entries in MData
                            let actions = entries.iter().fold(
                                MDataSeqEntryActions::new(),
                                |actions, (entry_name, val)| {
                                    actions.del(entry_name.clone(), val.version + 1)
                                },
                            );

                            c3.mutate_seq_mdata_entries(
                                mdata_info.name(),
                                mdata_info.type_tag(),
                                actions,
                            )
                        })
                        .map_err(From::from)
                        .and_then(move |_| {
                            // Remove MDataInfo from the access container
                            access_container::put_authenticator_entry(
                                &client,
                                &ac_entries,
                                ac_entry_version + 1,
                            )

                            // TODO(nbaksalyar): when MData deletion is implemented properly,
                            // also delete the actual MutableData related to app
                        })
                        .map_err(From::from)
                        .map(move |_| true)
                        .into_box()
                }
            }
        })
        .into_box()
}

// Creates a new app's dedicated container
fn create(client: &AuthClient, app_pk: PublicKey) -> Box<AuthFuture<MDataInfo>> {
    let dir = fry!(MDataInfo::random_private(MDataKind::Seq, DIR_TAG).map_err(AuthError::from));
    nfs::create_dir(
        client,
        &dir,
        btree_map![],
        btree_map![app_pk => MDataPermissionSet::new()
                .allow(MDataAction::Read)
                .allow(MDataAction::Insert)
                .allow(MDataAction::Update)
                .allow(MDataAction::Delete)
                .allow(MDataAction::ManagePermissions)],
    )
    .map(move |()| dir)
    .map_err(From::from)
    .into_box()
}