Skip to content
This repository has been archived by the owner on Aug 1, 2022. It is now read-only.

Commit

Permalink
feat(proxy): integrate rad/self (#628)
Browse files Browse the repository at this point in the history
This integrates the use of rad/self in the monorepo. This means when we
create an identity, we set the default rad/self in the peer to that
User.

Fixes #576
Closes #548

***
* Introduce rad/self usage for getting and setting the default owner
* Set the shared owner
* Add check that set the default rad/self
* Introduce init_owner function and use that for creating the identity
user
* Get Draft peer back in main and verify it
* Filter projects by default rad/self
* Update librad SHA to get bug fix
* Update librad SHA
* Prevent new owner creation during nuke
* Fix specs
* Apply suggestions from code review
Co-authored-by: Rūdolfs Ošiņš <rudolfs@osins.org>

* Improve identit creation errors
Co-authored-by: Alexander Simmerl <a.simmerl@gmail.com>
Co-authored-by: Rūdolfs Ošiņš <rudolfs@osins.org>
  • Loading branch information
FintanH committed Jul 3, 2020
1 parent 8826b2d commit 69c0adf
Show file tree
Hide file tree
Showing 13 changed files with 152 additions and 59 deletions.
2 changes: 1 addition & 1 deletion cypress/integration/project_source_browsing.spec.js
@@ -1,7 +1,7 @@
before(() => {
cy.nukeAllState();
cy.createProjectWithFixture();
cy.createIdentity();
cy.createProjectWithFixture();
});

beforeEach(() => {
Expand Down
18 changes: 2 additions & 16 deletions proxy/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion proxy/Cargo.toml
Expand Up @@ -36,7 +36,7 @@ warp = { git = "https://github.com/radicle-dev/warp", branch = "openapi", featur

[dependencies.librad]
git = "https://github.com/radicle-dev/radicle-link.git"
rev = "79460f429e4d9ae9a49c9b389a2411fbc138ed0e"
rev = "455d4d54df019e2a353fef02227640642b61b2e4"

[dependencies.radicle-keystore]
git = "https://github.com/radicle-dev/radicle-keystore.git"
Expand Down
4 changes: 2 additions & 2 deletions proxy/src/coco.rs
Expand Up @@ -6,8 +6,8 @@ pub use radicle_surf::vcs::git::Stats;
/// Module that captures all the functions for working with `librad`'s [`PeerApi`].
mod peer;
pub use peer::{
create_peer_api, get_project, get_user, init_project, init_user, list_projects, list_users,
verify_user, with_browser, PeerApi, User,
create_peer_api, default_owner, get_project, get_user, init_owner, init_project, init_user,
list_projects, list_users, set_default_owner, verify_user, with_browser, PeerApi, User,
};

/// Module that captures all types and functions for source code.
Expand Down
95 changes: 83 additions & 12 deletions proxy/src/coco/peer.rs
@@ -1,6 +1,8 @@
use std::net::SocketAddr;
use std::sync::Arc;

use async_trait::async_trait;
use tokio::sync::Mutex;

use librad::keys;
use librad::meta::entity;
Expand Down Expand Up @@ -36,20 +38,67 @@ where
Ok(api)
}

/// Get the default owner for this `PeerApi`.
#[must_use]
pub fn default_owner(peer: &PeerApi) -> Option<user::User<entity::Draft>> {
match peer.storage().default_rad_self() {
Ok(user) => Some(user),
Err(err) => {
log::warn!("an error occurred while trying to get 'rad/self': {}", err);
None
},
}
}

/// Set the default owner for this `PeerApi`.
///
/// # Errors
///
/// * Fails to set the default `rad/self` for this `PeerApi`.
pub fn set_default_owner(peer: &PeerApi, user: User) -> Result<(), error::Error> {
Ok(peer.storage().set_default_rad_self(user)?)
}

/// Initialise a [`User`] and make them the default owner of this `PeerApi`.
///
/// # Errors
///
/// * Fails to initialise `User`.
/// * Fails to verify `User`.
/// * Fails to set the default `rad/self` for this `PeerApi`.
pub async fn init_owner(
peer: Arc<Mutex<PeerApi>>,
key: keys::SecretKey,
handle: &str,
) -> Result<User, error::Error> {
let user = init_user(&*peer.lock().await, key.clone(), handle)?;
let user = verify_user(user).await?;
set_default_owner(&*peer.lock().await, user.clone())?;
Ok(user)
}

/// Returns the list of [`project::Project`]s for your peer.
///
/// # Errors
///
/// The function will error if:
/// * The retrieving the project entities from the store fails.
#[allow(
clippy::match_wildcard_for_single_variants,
clippy::wildcard_enum_match_arm
)]
pub fn list_projects(peer: &PeerApi) -> Result<Vec<Project>, error::Error> {
let storage = peer.storage();
let storage = peer.storage().reopen()?;
let owner = storage.default_rad_self()?;
let project_meta = storage.all_metadata()?.flat_map(|entity| {
entity.ok()?.try_map(|info| match info {
let entity = entity.ok()?;
let rad_self = storage.get_rad_self(&entity.urn()).ok()?;

// We only list projects that are owned by the peer
if rad_self.urn() != owner.urn() {
return None;
}

entity.try_map(|info| match info {
entity::data::EntityInfo::Project(info) => Some(info),
_ => None,
})
Expand All @@ -68,7 +117,6 @@ pub fn list_projects(peer: &PeerApi) -> Result<Vec<Project>, error::Error> {
///
/// # Errors
///
/// The function will error if:
/// * The retrieving the project entities from the store fails.
#[allow(
clippy::match_wildcard_for_single_variants,
Expand All @@ -91,9 +139,8 @@ pub fn list_users(peer: &PeerApi) -> Result<Vec<user::User<entity::Draft>>, erro
///
/// # Errors
///
/// `get_project` fails if:
/// * Parsing the `project_urn` fails.
/// * Resolving the project fails.
/// * Parsing the `project_urn` fails.
/// * Resolving the project fails.
pub fn get_project(
peer: &PeerApi,
urn: &RadUrn,
Expand Down Expand Up @@ -182,7 +229,8 @@ pub fn init_project(
if storage.has_urn(&urn)? {
return Err(error::Error::EntityExists(urn));
} else {
let _repo = storage.create_repo(&meta)?;
let repo = storage.create_repo(&meta)?;
repo.set_rad_self(librad::git::storage::RadSelfSpec::Urn(owner.urn()))?;
}
Ok(meta)
};
Expand Down Expand Up @@ -332,6 +380,10 @@ impl entity::Resolver<user::User<entity::Draft>> for FakeUserResolver {
#[cfg(test)]
#[allow(clippy::panic)]
mod test {
use std::sync::Arc;

use tokio::sync::Mutex;

use librad::keys::SecretKey;

use crate::coco::config;
Expand Down Expand Up @@ -444,15 +496,32 @@ mod test {
#[tokio::test]
async fn test_list_projects() -> Result<(), Error> {
let tmp_dir = tempfile::tempdir().expect("failed to create temdir");
let repo_path = tmp_dir.path().join("radicle");

let key = SecretKey::new();
let config = config::default(key.clone(), tmp_dir.path())?;
let peer = super::create_peer_api(config).await?;
let peer = Arc::new(Mutex::new(peer));

let user = super::init_user(&peer, key.clone(), "cloudhead")?;
let user = super::verify_user(user).await?;
control::setup_fixtures(&peer, key, &user)?;
let user = super::init_owner(Arc::clone(&peer), key.clone(), "cloudhead").await?;

let peer = &*peer.lock().await;

control::setup_fixtures(peer, key.clone(), &user)?;

let kalt = super::init_user(peer, key.clone(), "kalt")?;
let kalt = super::verify_user(kalt).await?;
let fakie = super::init_project(
peer,
key,
&kalt,
&repo_path,
"fakie-nose-kickflip-backside-180-to-handplant",
"rad git tricks",
"dope",
)?;

let projects = super::list_projects(&peer)?;
let projects = super::list_projects(peer)?;
let mut project_names = projects
.into_iter()
.map(|project| project.metadata.name)
Expand All @@ -464,6 +533,8 @@ mod test {
vec!["Monadic", "monokel", "open source coin", "radicle"]
);

assert!(!project_names.contains(&fakie.name().to_string()));

Ok(())
}

Expand Down
5 changes: 3 additions & 2 deletions proxy/src/http.rs
Expand Up @@ -51,7 +51,7 @@ macro_rules! combine {
/// Main entry point for HTTP API.
pub fn api<R>(
peer: coco::PeerApi,
owner: coco::User,
owner: Option<coco::User>, // None if the default owner was not found
keystore: keystore::Keystorage,
registry: R,
store: kv::Store,
Expand All @@ -62,7 +62,7 @@ where
{
let peer = Arc::new(Mutex::new(peer));
// TODO(finto): The user should be read from rad/self
let owner = Arc::new(RwLock::new(Some(owner)));
let owner = Arc::new(RwLock::new(owner));
let keystore = Arc::new(RwLock::new(keystore));
let registry = Arc::new(RwLock::new(registry));
let store = Arc::new(RwLock::new(store));
Expand All @@ -78,6 +78,7 @@ where
);
let identity_filter = identity::filters(
Arc::clone(&peer),
Arc::clone(&owner),
Arc::clone(&keystore),
Arc::clone(&registry),
Arc::clone(&store),
Expand Down
11 changes: 5 additions & 6 deletions proxy/src/http/control.rs
Expand Up @@ -194,12 +194,7 @@ mod handler {
let mut keystore = keystore.write().await;

let mut owner = owner.write().await;
let new_owner = if let Some(old_owner) = &*owner {
let new_owner = coco::init_user(&new_peer, key, old_owner.name())?;
Some(coco::verify_user(new_owner).await?)
} else {
None
};
let new_owner = None;

*owner = new_owner;
*peer = new_peer;
Expand Down Expand Up @@ -313,6 +308,10 @@ mod test {
use crate::keystore;
use crate::registry;

// TODO(xla): This can't hold true anymore, given that we nuke the owner. Which is required in
// order to register a project. Should we rework the test? How do we make sure an owner is
// present?
#[ignore]
#[tokio::test]
async fn create_project_after_nuke() -> Result<(), error::Error> {
let tmp_dir = tempfile::tempdir()?;
Expand Down
18 changes: 15 additions & 3 deletions proxy/src/http/identity.rs
Expand Up @@ -16,23 +16,26 @@ use crate::registry;
/// Combination of all identity routes.
pub fn filters<R: registry::Client>(
peer: Arc<Mutex<coco::PeerApi>>,
owner: http::Shared<Option<coco::User>>,
keystore: http::Shared<keystore::Keystorage>,
registry: http::Shared<R>,
store: Arc<RwLock<kv::Store>>,
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
get_filter(Arc::clone(&peer)).or(create_filter(peer, keystore, registry, store))
get_filter(Arc::clone(&peer)).or(create_filter(peer, owner, keystore, registry, store))
}

/// `POST /identities`
fn create_filter<R: registry::Client>(
peer: Arc<Mutex<coco::PeerApi>>,
owner: http::Shared<Option<coco::User>>,
keystore: http::Shared<keystore::Keystorage>,
registry: http::Shared<R>,
store: Arc<RwLock<kv::Store>>,
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
path!("identities")
.and(warp::post())
.and(http::with_peer(peer))
.and(http::with_shared(owner))
.and(http::with_shared(keystore))
.and(http::with_shared(registry))
.and(http::with_store(store))
Expand Down Expand Up @@ -101,6 +104,7 @@ mod handler {
/// Create a new [`identity::Identity`].
pub async fn create<R: registry::Client>(
peer: Arc<Mutex<coco::PeerApi>>,
owner: http::Shared<Option<coco::User>>,
keystore: http::Shared<keystore::Keystorage>,
registry: http::Shared<R>,
store: Arc<RwLock<kv::Store>>,
Expand All @@ -118,7 +122,7 @@ mod handler {

let keystore = keystore.read().await;
let key = keystore.get_librad_key().map_err(error::Error::from)?;
let id = identity::create(peer, key, input.handle.parse()?).await?;
let id = identity::create(peer, owner, key, input.handle.parse()?).await?;

session::set_identity(&store, id.clone())?;

Expand Down Expand Up @@ -280,6 +284,7 @@ mod test {
));
let api = super::filters(
Arc::clone(&peer),
Arc::new(RwLock::new(None)),
Arc::new(RwLock::new(keystore)),
Arc::clone(&registry),
Arc::clone(&store),
Expand All @@ -296,9 +301,15 @@ mod test {

let store = &*store.read().await;
let registry = &*registry.read().await;
let session = session::current(peer, registry, store).await?;
let session = session::current(Arc::clone(&peer), registry, store).await?;
let urn = session.identity.expect("failed to set identity").id;

// Assert that we set the default owner and it's the same one as the session
{
let peer = &*peer.lock().await;
assert_eq!(coco::default_owner(peer), Some(coco::get_user(peer, &urn)?));
}

http::test::assert_response(&res, StatusCode::CREATED, |have| {
let avatar = avatar::Avatar::from(&urn.to_string(), avatar::Usage::Identity);
let shareable_entity_identifier = format!("cloudhead@{}", urn);
Expand Down Expand Up @@ -343,6 +354,7 @@ mod test {

let api = super::filters(
Arc::new(Mutex::new(peer)),
Arc::new(RwLock::new(None)),
Arc::new(RwLock::new(keystore)),
Arc::new(RwLock::new(registry)),
Arc::new(RwLock::new(store)),
Expand Down

0 comments on commit 69c0adf

Please sign in to comment.