server: Record uploader JWT subject in objects

This commit is contained in:
Zhaofeng Li 2023-01-04 21:05:07 -07:00
parent f4e52f9ad0
commit 3fd587315f
6 changed files with 56 additions and 2 deletions

View file

@ -34,6 +34,13 @@ impl AuthState {
}
}
/// Returns the username if it exists.
///
/// Currently it's the `sub` claim of the JWT.
pub fn username(&self) -> Option<&str> {
self.token.get().map(|token| token.sub())
}
/// Finds and performs authorization for a cache.
pub async fn auth_cache<F, T>(
&self,

View file

@ -232,6 +232,11 @@ impl Token {
.map_err(|e| Error::TokenError(e).into())
}
/// Returns the subject of the token.
pub fn sub(&self) -> &str {
self.0.claims.sub.as_str()
}
/// Returns the claims as a serializable value.
pub fn opaque_claims(&self) -> &impl Serialize {
&self.0.claims

View file

@ -107,22 +107,26 @@ pub(crate) async fn upload_path(
stream.map(|r| r.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))),
);
let username = req_state.auth.username()
.map(str::to_string);
// Try to acquire a lock on an existing NAR
let existing_nar = database.find_and_lock_nar(&upload_info.nar_hash).await?;
match existing_nar {
Some(existing_nar) => {
// Deduplicate
upload_path_dedup(cache, upload_info, stream, existing_nar, database).await
upload_path_dedup(username, cache, upload_info, stream, existing_nar, database).await
}
None => {
// New NAR
upload_path_new(cache, upload_info, stream, database, &state).await
upload_path_new(username, cache, upload_info, stream, database, &state).await
}
}
}
/// Uploads a path when there is already a matching NAR in the global cache.
async fn upload_path_dedup(
username: Option<String>,
cache: cache::Model,
upload_info: UploadPathNarInfo,
stream: impl AsyncRead + Unpin,
@ -164,6 +168,7 @@ async fn upload_path_dedup(
new_object.cache_id = Set(cache.id);
new_object.nar_id = Set(existing_nar.id);
new_object.created_at = Set(Utc::now());
new_object.created_by = Set(username);
new_object
})
.exec(&txn)
@ -185,6 +190,7 @@ async fn upload_path_dedup(
/// us. The `nar` table can hold duplicate NARs which can be deduplicated
/// in a background process.
async fn upload_path_new(
username: Option<String>,
cache: cache::Model,
upload_info: UploadPathNarInfo,
stream: impl AsyncRead + Send + Unpin + 'static,
@ -308,6 +314,7 @@ async fn upload_path_new(
new_object.cache_id = Set(cache.id);
new_object.nar_id = Set(nar_id);
new_object.created_at = Set(Utc::now());
new_object.created_by = Set(username);
new_object
})
.exec(&txn)

View file

@ -62,6 +62,12 @@ pub struct Model {
/// Timestamp when the object is last accessed.
pub last_accessed_at: Option<ChronoDateTimeUtc>,
/// The uploader of the object.
///
/// This is a "username." Currently, it's set to the `sub` claim in
/// the client's JWT.
pub created_by: Option<String>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]

View file

@ -0,0 +1,27 @@
use sea_orm_migration::prelude::*;
use crate::database::entity::object::*;
pub struct Migration;
impl MigrationName for Migration {
fn name(&self) -> &str {
"m20230103_000001_add_object_created_by"
}
}
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.alter_table(
Table::alter()
.table(Entity)
.add_column(ColumnDef::new(Column::CreatedBy).string().null())
.to_owned(),
)
.await?;
Ok(())
}
}

View file

@ -7,6 +7,7 @@ mod m20221227_000002_create_nar_table;
mod m20221227_000003_create_object_table;
mod m20221227_000004_add_object_last_accessed;
mod m20221227_000005_add_cache_retention_period;
mod m20230103_000001_add_object_created_by;
pub struct Migrator;
@ -19,6 +20,7 @@ impl MigratorTrait for Migrator {
Box::new(m20221227_000003_create_object_table::Migration),
Box::new(m20221227_000004_add_object_last_accessed::Migration),
Box::new(m20221227_000005_add_cache_retention_period::Migration),
Box::new(m20230103_000001_add_object_created_by::Migration),
]
}
}