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

Commit

Permalink
feat: apply project registration permission (#504)
Browse files Browse the repository at this point in the history
* Check for local projects
* Apply project registration permissions
* Remove obsolete check
  • Loading branch information
Merle Breitkreuz committed Jun 18, 2020
1 parent 762d361 commit 9469148
Show file tree
Hide file tree
Showing 11 changed files with 206 additions and 58 deletions.
99 changes: 74 additions & 25 deletions cypress/integration/project_registration.spec.js
Expand Up @@ -14,34 +14,83 @@ const project2 = {
const org1 = "monadic";
const org2 = "github";

beforeEach(() => {
cy.nukeAllState();

cy.createIdentity(user);
cy.registerUser(user);

cy.registerOrg(org1);
cy.registerOrg(org2);

cy.createProjectWithFixture(
project1.name,
project1.description,
project1.defaultBranch
);
cy.createProjectWithFixture(
project2.name,
project2.description,
project2.defaultBranch
);

// The transaction center is populated with transactions that come from this
// before block and it covers the dropdown menu that is needed in the tests.
cy.nukeCache();

cy.visit("public/index.html");
context("project registration permission", () => {
it("disables project registration without a registered user", () => {
cy.nukeAllState();
cy.createIdentity(user);
cy.createProjectWithFixture(
project1.name,
project1.description,
project1.defaultBranch
);
cy.visit("public/index.html");

// via the project list
cy.pick(`project-list-entry-${project1.name}`, "context-menu").click();
cy.pick(
`project-list-entry-${project1.name}`,
"dropdown-menu",
"register-project"
).should("have.class", "disabled");

// via the project page
cy.pick(`project-list-entry-${project1.name}`).click();

cy.pick("project-screen", "context-menu").click();
cy.pick("dropdown-menu", "register-project").should(
"have.class",
"disabled"
);
});

it("disables project registration under an org without a local project", () => {
cy.nukeAllState();
cy.createIdentity(user);
cy.registerUser(user);
cy.registerOrg(org1);
cy.visit("public/index.html");

cy.pick("sidebar", `org-${org1}`).click();
// via org onboarding page register button
cy.pick("add-project").should("have.class", "disabled");

// via org onboarding page context menu
cy.pick("context-menu").click();
cy.pick("dropdown-menu", "add-project").should("have.class", "disabled");

// via org onboarding page menu button
cy.pick("projects-menu-button").should("have.class", "disabled");
});
});

context("project registration", () => {
beforeEach(() => {
cy.nukeAllState();

cy.createIdentity(user);
cy.registerUser(user);

cy.registerOrg(org1);
cy.registerOrg(org2);

cy.createProjectWithFixture(
project1.name,
project1.description,
project1.defaultBranch
);
cy.createProjectWithFixture(
project2.name,
project2.description,
project2.defaultBranch
);

// The transaction center is populated with transactions that come from this
// before block and it covers the dropdown menu that is needed in the tests.
cy.nukeCache();

cy.visit("public/index.html");
});

context("navigation", () => {
it("can be accessed via profile project list project context menu", () => {
cy.pick(`project-list-entry-${project2.name}`, "context-menu").click();
Expand Down
12 changes: 10 additions & 2 deletions proxy/src/http.rs
Expand Up @@ -46,7 +46,11 @@ where
Arc::clone(&owner),
Arc::clone(&registry),
))
.or(identity::filters(Arc::clone(&registry), Arc::clone(&store)))
.or(identity::filters(
Arc::clone(&peer),
Arc::clone(&registry),
Arc::clone(&store),
))
.or(notification::filters(subscriptions.clone()))
.or(org::routes(
Arc::clone(&peer),
Expand All @@ -59,7 +63,11 @@ where
Arc::clone(&registry),
subscriptions.clone(),
))
.or(session::routes(Arc::clone(&registry), Arc::clone(&store)))
.or(session::routes(
Arc::clone(&peer),
Arc::clone(&registry),
Arc::clone(&store),
))
.or(source::routes(peer))
.or(transaction::filters(Arc::clone(&registry)))
.or(user::routes(registry, store, subscriptions)),
Expand Down
39 changes: 32 additions & 7 deletions proxy/src/http/identity.rs
Expand Up @@ -2,30 +2,34 @@

use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::RwLock;
use tokio::sync::{Mutex, RwLock};
use warp::document::{self, ToDocumentedType};
use warp::{path, Filter, Rejection, Reply};

use crate::avatar;
use crate::coco;
use crate::http;
use crate::identity;
use crate::registry;

/// Combination of all identity routes.
pub fn filters<R: registry::Client>(
peer: Arc<Mutex<coco::Peer>>,
registry: http::Shared<R>,
store: Arc<RwLock<kv::Store>>,
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
get_filter().or(create_filter(registry, store))
get_filter().or(create_filter(peer, registry, store))
}

/// `POST /identities`
fn create_filter<R: registry::Client>(
peer: Arc<Mutex<coco::Peer>>,
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(registry))
.and(http::with_store(store))
.and(warp::body::json())
Expand Down Expand Up @@ -75,11 +79,12 @@ fn get_filter() -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone
/// Identity handlers for conversion between core domain and http request fullfilment.
mod handler {
use std::sync::Arc;
use tokio::sync::RwLock;
use tokio::sync::{Mutex, RwLock};
use warp::http::StatusCode;
use warp::{reply, Rejection, Reply};

use crate::avatar;
use crate::coco;
use crate::error;
use crate::http;
use crate::identity;
Expand All @@ -88,14 +93,18 @@ mod handler {

/// Create a new [`identity::Identity`].
pub async fn create<R: registry::Client>(
peer: Arc<Mutex<coco::Peer>>,
registry: http::Shared<R>,
store: Arc<RwLock<kv::Store>>,
input: super::CreateInput,
) -> Result<impl Reply, Rejection> {
let reg = registry.read().await;
let store = store.read().await;

if let Some(identity) = session::current(&store, (*reg).clone()).await?.identity {
if let Some(identity) = session::current(peer, &store, (*reg).clone())
.await?
.identity
{
return Err(Rejection::from(error::Error::IdentityExists(identity.id)));
}

Expand Down Expand Up @@ -234,23 +243,31 @@ mod test {
use pretty_assertions::assert_eq;
use serde_json::{json, Value};
use std::sync::Arc;
use tokio::sync::RwLock;
use tokio::sync::{Mutex, RwLock};
use warp::http::StatusCode;
use warp::test::request;

use librad::keys::SecretKey;

use crate::avatar;
use crate::coco;
use crate::error;
use crate::identity;
use crate::registry;

#[tokio::test]
async fn create() {
async fn create() -> Result<(), error::Error> {
let tmp_dir = tempfile::tempdir().unwrap();
let key = SecretKey::new();
let config = coco::default_config(key, tmp_dir.path())?;
let peer = Arc::new(Mutex::new(coco::Peer::new(config).await?));
let registry = {
let (client, _) = radicle_registry_client::Client::new_emulator();
registry::Registry::new(client)
};
let store = kv::Store::new(kv::Config::new(tmp_dir.path().join("store"))).unwrap();
let api = super::filters(
Arc::clone(&peer),
Arc::new(RwLock::new(registry)),
Arc::new(RwLock::new(store)),
);
Expand Down Expand Up @@ -284,17 +301,23 @@ mod test {

assert_eq!(res.status(), StatusCode::CREATED);
assert_eq!(have, want);

Ok(())
}

#[tokio::test]
async fn get() {
async fn get() -> Result<(), error::Error> {
let tmp_dir = tempfile::tempdir().unwrap();
let key = SecretKey::new();
let config = coco::default_config(key, tmp_dir.path())?;
let peer = Arc::new(Mutex::new(coco::Peer::new(config).await?));
let registry = {
let (client, _) = radicle_registry_client::Client::new_emulator();
registry::Registry::new(client)
};
let store = kv::Store::new(kv::Config::new(tmp_dir.path().join("store"))).unwrap();
let api = super::filters(
Arc::clone(&peer),
Arc::new(RwLock::new(registry)),
Arc::new(RwLock::new(store)),
);
Expand Down Expand Up @@ -322,5 +345,7 @@ mod test {
avatar_fallback: avatar::Avatar::from(id, avatar::Usage::Identity),
})
);

Ok(())
}
}

0 comments on commit 9469148

Please sign in to comment.