Skip to content

Commit

Permalink
feat: Check revocations
Browse files Browse the repository at this point in the history
  • Loading branch information
matheus23 committed Dec 12, 2023
1 parent 4933c45 commit ee89e57
Show file tree
Hide file tree
Showing 10 changed files with 89 additions and 36 deletions.
4 changes: 3 additions & 1 deletion fission-core/src/revocation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@ impl Revocation {
}
}

fn canonical_cid<F, C>(ucan: &Ucan<F, C>) -> Result<String>
/// Returns the "canonical CID" of a UCAN.
/// That is the CID of a UCAN with a raw codec, sha-256 hash and base32-encoded.
pub fn canonical_cid<F, C>(ucan: &Ucan<F, C>) -> Result<String>
where
F: Clone + DeserializeOwned,
C: CapabilityParser,
Expand Down
34 changes: 31 additions & 3 deletions fission-server/src/authority.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
//! Authority struct and functions
use crate::{db::Conn, models::revocation::find_revoked_subset};
use anyhow::{anyhow, bail, Result};
use fission_core::{capabilities::did::Did, revocation::Revocation};
use fission_core::{
capabilities::did::Did,
revocation::{canonical_cid, Revocation},
};
use libipld::{raw::RawCodec, Ipld};
use rs_ucan::{
did_verifier::DidVerifierMap,
Expand All @@ -11,6 +15,7 @@ use rs_ucan::{
DefaultFact,
};
use serde::de::DeserializeOwned;
use std::collections::BTreeSet;

//-------//
// TYPES //
Expand Down Expand Up @@ -62,22 +67,45 @@ impl<F: Clone + DeserializeOwned> Authority<F> {
let mut store = InMemoryStore::<RawCodec>::default();

for proof in &self.proofs {
// TODO(matheus23): we assume SHA2-256 atm. The spec says to hash with all CID formats used in proofs >.<
store.write(Ipld::Bytes(proof.encode()?.as_bytes().to_vec()), None)?;
}

revocation.verify_valid(&self.ucan, &DidVerifierMap::default(), &store)
}

/// find the set of UCAN canonical CIDs that are revoked and relevant to this request
pub async fn get_relevant_revocations(&self, conn: &mut Conn<'_>) -> Result<BTreeSet<String>> {
let mut canonical_cids = BTreeSet::from([canonical_cid(&self.ucan)?]);

for proof in &self.proofs {
// This is duplicating work in the usual case, but also it's not *too bad*.
canonical_cids.insert(canonical_cid(proof)?);
}

find_revoked_subset(canonical_cids, conn).await
}

/// Validates whether or not the UCAN and proofs have the capability to
/// perform the given action, with the given issuer as the root of that
/// authority.
pub fn get_capability(&self, ability: impl Ability) -> Result<Did> {
pub fn get_capability(
&self,
ability: impl Ability,
revocations: &BTreeSet<String>,
) -> Result<Did> {
if revocations.contains(&canonical_cid(&self.ucan)?) {
bail!("Invocation UCAN was revoked");
}

let current_time = rs_ucan::time::now();

let mut store = InMemoryStore::<RawCodec>::default();

for proof in &self.proofs {
// TODO(matheus23): rs-ucan should probably have support for revoked CIDs
if revocations.contains(&canonical_cid(proof)?) {
continue; // This CID was revoked.
}
// TODO(matheus23): we assume SHA2-256 atm. The spec says to hash with all CID formats used in proofs >.<
store.write(Ipld::Bytes(proof.encode()?.as_bytes().to_vec()), None)?;
}
Expand Down
4 changes: 2 additions & 2 deletions fission-server/src/docs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{
error::AppError,
extract::authority_addon::UcanAddon,
models::account::{Account, RootAccount},
routes::{account, auth, health, ping, revocation},
routes::{account, auth, health, ping, revocations},
};
use fission_core::{
common::{AccountCreationRequest, AccountResponse, EmailVerifyRequest, SuccessResponse},
Expand All @@ -23,7 +23,7 @@ use utoipa::OpenApi;
account::create_account,
account::get_account,
account::get_did,
revocation::post_revocation,
revocations::post_revocation,
),
components(
schemas(
Expand Down
3 changes: 1 addition & 2 deletions fission-server/src/models/capability_indexing.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
//! Models related to capability indexing, specifically the `ucans` and `capabilities` table.
use std::{collections::BTreeSet, str::FromStr};

use crate::db::{
schema::{capabilities, ucans},
Conn,
Expand All @@ -20,6 +18,7 @@ use diesel_async::RunQueryDsl;
use rs_ucan::{capability::Capability, ucan::Ucan};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::{collections::BTreeSet, str::FromStr};
use utoipa::ToSchema;

/// Represents an indexed UCAN in the database
Expand Down
19 changes: 18 additions & 1 deletion fission-server/src/models/revocation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
use crate::db::{schema::revocations, Conn};
use anyhow::Result;
use diesel::{
associations::Identifiable, deserialize::Queryable, pg::Pg, prelude::Insertable, Selectable,
associations::Identifiable, deserialize::Queryable, pg::Pg, prelude::Insertable,
ExpressionMethods, QueryDsl, Selectable,
};
use diesel_async::RunQueryDsl;
use fission_core::revocation::Revocation;
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
use utoipa::ToSchema;

/// Represents a revocation record in the database
Expand Down Expand Up @@ -79,3 +81,18 @@ impl NewRevocationRecord {
})
}
}

/// From a list of canonical CIDs, find the subset that is revoked
pub async fn find_revoked_subset(
canonical_cids: BTreeSet<String>,
conn: &mut Conn<'_>,
) -> Result<BTreeSet<String>> {
let revoked_cids = revocations::table
.filter(revocations::cid.eq_any(canonical_cids))
.select(revocations::cid)
.get_results(conn)
.await?;

// Convert into set
Ok(revoked_cids.into_iter().collect())
}
4 changes: 2 additions & 2 deletions fission-server/src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{
middleware::logging::{log_request_response, DebugOnlyLogger, Logger},
routes::{
account, auth, capability_indexing, doh, fallback::notfound_404, health, ipfs, ping,
revocation, ws,
revocations, ws,
},
setups::ServerSetup,
};
Expand Down Expand Up @@ -48,7 +48,7 @@ pub fn setup_app_router<S: ServerSetup + 'static>(app_state: AppState<S>) -> Rou
)
.route("/account/:username/did", get(account::get_did))
.route("/capabilities", get(capability_indexing::get_capabilities))
.route("/revocations", post(revocation::post_revocation))
.route("/revocations", post(revocations::post_revocation))
.with_state(app_state.clone())
.fallback(notfound_404);

Expand Down
30 changes: 18 additions & 12 deletions fission-server/src/routes/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,15 @@ pub async fn create_account<S: ServerSetup>(
.validate()
.map_err(|e| AppError::new(StatusCode::BAD_REQUEST, Some(e)))?;

let Did(did) = authority
.get_capability(FissionAbility::AccountCreate)
.map_err(|e| AppError::new(StatusCode::FORBIDDEN, Some(e)))?;

let conn = &mut db::connect(&state.db_pool).await?;
conn.transaction(|conn| {
async move {
let revocation_set = authority.get_relevant_revocations(conn).await?;

let Did(did) = authority
.get_capability(FissionAbility::AccountCreate, &revocation_set)
.map_err(|e| AppError::new(StatusCode::FORBIDDEN, Some(e)))?;

let verification = EmailVerification::find_token(conn, &request.email, &request.code)
.await
.map_err(|err| AppError::new(StatusCode::FORBIDDEN, Some(err.to_string())))?;
Expand Down Expand Up @@ -163,12 +165,14 @@ pub async fn patch_username<S: ServerSetup>(
authority: Authority,
Path(username): Path<String>,
) -> AppResult<(StatusCode, Json<SuccessResponse>)> {
let conn = &mut db::connect(&state.db_pool).await?;

let revocation_set = authority.get_relevant_revocations(conn).await?;

let Did(did) = authority
.get_capability(FissionAbility::AccountManage)
.get_capability(FissionAbility::AccountManage, &revocation_set)
.map_err(|e| AppError::new(StatusCode::FORBIDDEN, Some(e)))?;

let conn = &mut db::connect(&state.db_pool).await?;

use crate::db::schema::*;
diesel::update(accounts::table)
.filter(accounts::did.eq(&did))
Expand Down Expand Up @@ -197,14 +201,16 @@ pub async fn delete_account<S: ServerSetup>(
State(state): State<AppState<S>>,
authority: Authority,
) -> AppResult<(StatusCode, Json<Account>)> {
let Did(did) = authority
.get_capability(FissionAbility::AccountDelete)
.map_err(|e| AppError::new(StatusCode::FORBIDDEN, Some(e)))?;

let conn = &mut db::connect(&state.db_pool).await?;
conn.transaction(|conn| {
async move {
use crate::db::schema::*;
let revocation_set = authority.get_relevant_revocations(conn).await?;

let Did(did) = authority
.get_capability(FissionAbility::AccountDelete, &revocation_set)
.map_err(|e| AppError::new(StatusCode::FORBIDDEN, Some(e)))?;

use crate::db::schema::{accounts, capabilities, ucans};
let account = diesel::delete(accounts::table)
.filter(accounts::did.eq(&did))
.get_result::<Account>(conn)
Expand Down
25 changes: 13 additions & 12 deletions fission-server/src/routes/capability_indexing.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
//! Routes for the capability indexing endpoints
use axum::extract::State;
use diesel_async::{scoped_futures::ScopedFutureExt, AsyncConnection};
use fission_core::{
capabilities::{did::Did, indexing::IndexingAbility},
common::UcansResponse,
};
use http::StatusCode;

use crate::{
app_state::AppState,
authority::Authority,
Expand All @@ -17,6 +9,13 @@ use crate::{
models::capability_indexing::find_ucans_for_audience,
setups::ServerSetup,
};
use axum::extract::State;
use diesel_async::{scoped_futures::ScopedFutureExt, AsyncConnection};
use fission_core::{
capabilities::{did::Did, indexing::IndexingAbility},
common::UcansResponse,
};
use http::StatusCode;

/// Return capabilities for a given DID
#[utoipa::path(
Expand All @@ -36,13 +35,15 @@ pub async fn get_capabilities<S: ServerSetup>(
State(state): State<AppState<S>>,
authority: Authority,
) -> AppResult<(StatusCode, Json<UcansResponse>)> {
let Did(audience_needle) = authority
.get_capability(IndexingAbility::Fetch)
.map_err(|e| AppError::new(StatusCode::FORBIDDEN, Some(e)))?;

let conn = &mut db::connect(&state.db_pool).await?;
conn.transaction(|conn| {
async move {
let revocation_set = authority.get_relevant_revocations(conn).await?;

let Did(audience_needle) = authority
.get_capability(IndexingAbility::Fetch, &revocation_set)
.map_err(|e| AppError::new(StatusCode::FORBIDDEN, Some(e)))?;

let ucans = find_ucans_for_audience(audience_needle, conn)
.await
.map_err(|e| AppError::new(StatusCode::INTERNAL_SERVER_ERROR, Some(e)))?;
Expand Down
2 changes: 1 addition & 1 deletion fission-server/src/routes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ pub mod fallback;
pub mod health;
pub mod ipfs;
pub mod ping;
pub mod revocation;
pub mod revocations;
pub mod ws;
File renamed without changes.

0 comments on commit ee89e57

Please sign in to comment.