Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 96 additions & 4 deletions lore-server/src/auth/jwt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ use tracing::warn;
use super::jwk::JWKServiceError;
use crate::auth::jwk::JWKService;

pub const PERMISSION_ADMIN: &str = "admin";
pub const PERMISSION_OWNER: &str = "owner";
pub const PERMISSION_READ: &str = "read";
pub const PERMISSION_WRITE: &str = "write";

#[serde_as]
#[derive(Debug, Deserialize, Clone, Serialize, PartialEq)]
pub struct JWTUserInfo {
Expand Down Expand Up @@ -53,6 +58,12 @@ impl ResourcePermission {
pub fn matches_repository(&self, repository_id: &String) -> bool {
self.resource_id == *repository_id || self.is_wildcard_resource()
}

pub fn has_any_permission(&self, permissions: &[&str]) -> bool {
permissions
.iter()
.any(|permission| self.permission.iter().any(|item| item == permission))
}
}

#[serde_as]
Expand Down Expand Up @@ -168,15 +179,67 @@ pub fn verify_authorization(
authorization: &AuthorizationToken,
repository: lore_revision::lore::RepositoryId,
) -> Result<(), JwtVerifierError> {
if has_repository_read_permission(authorization, repository) {
return Ok(());
}

Err(JwtVerifierError::NotAuthorized)
}

pub fn has_repository_read_permission(
authorization: &AuthorizationToken,
repository: lore_revision::lore::RepositoryId,
) -> bool {
has_any_repository_permission(
authorization,
repository,
&[
PERMISSION_READ,
PERMISSION_WRITE,
PERMISSION_ADMIN,
PERMISSION_OWNER,
],
)
}

pub fn has_repository_write_permission(
authorization: &AuthorizationToken,
repository: lore_revision::lore::RepositoryId,
) -> bool {
has_any_repository_permission(
authorization,
repository,
&[PERMISSION_WRITE, PERMISSION_ADMIN, PERMISSION_OWNER],
)
}

pub fn has_any_repository_permission(
authorization: &AuthorizationToken,
repository: lore_revision::lore::RepositoryId,
permissions: &[&str],
) -> bool {
if let Some(resources) = authorization.resources.as_ref() {
let checked_repository = format!("urc-{repository}");
for authorized_resource in resources.iter() {
if authorized_resource.matches_repository(&checked_repository) {
return Ok(());
if authorized_resource.matches_repository(&checked_repository)
&& authorized_resource.has_any_permission(permissions)
{
return true;
}
}
}

false
}

pub fn verify_repository_write_authorization(
authorization: &AuthorizationToken,
repository: lore_revision::lore::RepositoryId,
) -> Result<(), JwtVerifierError> {
if has_repository_write_permission(authorization, repository) {
return Ok(());
}

Err(JwtVerifierError::NotAuthorized)
}

Expand Down Expand Up @@ -227,7 +290,7 @@ mod tests {
fn verify_authorization_allows_repo_from_token() {
let allowed_repository_id = "urc-0194b726b34e72b0b45550b88a967076".to_string();
let resource_permission = ResourcePermission {
permission: vec![],
permission: vec![PERMISSION_READ.to_string()],
resource_id: allowed_repository_id.clone(),
};
let authorization_token = AuthorizationToken {
Expand Down Expand Up @@ -259,7 +322,7 @@ mod tests {
#[test]
fn verify_authorization_allows_all_repos_for_wildcard_token() {
let resource_permission = ResourcePermission {
permission: vec![],
permission: vec![PERMISSION_READ.to_string()],
resource_id: "urc-*".to_string(),
};
let wildcard_authorization_token = AuthorizationToken {
Expand Down Expand Up @@ -294,6 +357,35 @@ mod tests {
}
}

#[test]
fn verify_authorization_rejects_repo_without_read_permission() {
let repository_id = "urc-0194b726b34e72b0b45550b88a967076".to_string();
let resource_permission = ResourcePermission {
permission: vec![],
resource_id: repository_id.clone(),
};
let authorization_token = AuthorizationToken {
audience: vec!["test".to_string()],
env: "test".to_string(),
expires: 1234,
user_id: "test".to_string(),
idp: "test".to_string(),
issuer: "test".to_string(),
name: "test".to_string(),
preferred_username: "test".to_string(),
groups: None,
is_service_account: Some(false),
issued_at: 123,
resources: Some(vec![resource_permission]),
};
let repository: RepositoryId = Context::from_str("0194b726b34e72b0b45550b88a967076")
.unwrap()
.into();

verify_authorization(&authorization_token, repository)
.expect_err("resource without read-like permission must be rejected");
}

mod jwt_verifier {

use std::error::Error;
Expand Down
2 changes: 2 additions & 0 deletions lore-server/src/grpc/handlers/branch_create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use crate::grpc::get_repository;
use crate::grpc::get_user_id;
use crate::grpc::get_write_token;
use crate::grpc::hook_error_to_status;
use crate::grpc::require_write_permission;
use crate::hooks::HookContext;
use crate::hooks::HookDispatcher;
use crate::hooks::HookPoint;
Expand Down Expand Up @@ -66,6 +67,7 @@ pub async fn handler(
instrument_provider: &impl InstrumentProvider,
) -> Result<Response<BranchCreateResponse>, Status> {
let repository_id = get_repository(request.metadata())?;
require_write_permission(request.extensions(), repository_id)?;
let user_id = get_user_id(request.extensions());
let correlation_id = extract_correlation_id(&request).unwrap_or_default();
let req = request.into_inner();
Expand Down
2 changes: 2 additions & 0 deletions lore-server/src/grpc/handlers/branch_delete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use crate::grpc::extract_correlation_id;
use crate::grpc::get_repository;
use crate::grpc::get_user_id;
use crate::grpc::hook_error_to_status;
use crate::grpc::require_write_permission;
use crate::hooks::HookContext;
use crate::hooks::HookDispatcher;
use crate::hooks::HookPoint;
Expand All @@ -37,6 +38,7 @@ pub async fn handler(
instrument_provider: &impl InstrumentProvider,
) -> Result<Response<BranchDeleteResponse>, Status> {
let repository_id = get_repository(request.metadata())?;
require_write_permission(request.extensions(), repository_id)?;
let user_id = get_user_id(request.extensions());
let correlation_id = extract_correlation_id(&request).unwrap_or_default();
let req = request.into_inner();
Expand Down
2 changes: 2 additions & 0 deletions lore-server/src/grpc/handlers/branch_metadata_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use crate::grpc::extract_correlation_id;
use crate::grpc::get_repository;
use crate::grpc::get_user_id;
use crate::grpc::get_write_token;
use crate::grpc::require_write_permission;
use crate::grpc::warn_error_to_status;
use crate::util::setup_execution;

Expand Down Expand Up @@ -100,6 +101,7 @@ pub async fn handler(
mutable_store: Arc<dyn lore_storage::MutableStore>,
) -> Result<Response<BranchMetadataSetResponse>, Status> {
let repository_id = get_repository(request.metadata())?;
require_write_permission(request.extensions(), repository_id)?;
let user_id = get_user_id(request.extensions());
let correlation_id = extract_correlation_id(&request).unwrap_or_default();
let req = request.into_inner();
Expand Down
2 changes: 2 additions & 0 deletions lore-server/src/grpc/handlers/branch_protect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use tracing::warn;
use crate::grpc::extract_correlation_id;
use crate::grpc::get_repository;
use crate::grpc::get_user_id;
use crate::grpc::require_write_permission;
use crate::util::setup_execution;

#[tracing::instrument(name = "BranchProtect::handle", skip_all)]
Expand All @@ -26,6 +27,7 @@ pub async fn handler(
mutable_store: Arc<dyn lore_storage::MutableStore>,
) -> Result<Response<BranchProtectResponse>, Status> {
let repository_id = get_repository(request.metadata())?;
require_write_permission(request.extensions(), repository_id)?;
let user_id = get_user_id(request.extensions());
let correlation_id = extract_correlation_id(&request).unwrap_or_default();
let req = request.into_inner();
Expand Down
2 changes: 2 additions & 0 deletions lore-server/src/grpc/handlers/branch_push.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ use crate::grpc::get_repository;
use crate::grpc::get_user_id;
use crate::grpc::get_write_token;
use crate::grpc::hook_error_to_status;
use crate::grpc::require_write_permission;
use crate::grpc::warn_error_to_status;
use crate::hooks::HookContext;
use crate::hooks::HookDispatcher;
Expand Down Expand Up @@ -87,6 +88,7 @@ pub async fn handler(
let user_id = get_user_id(request.extensions());
let correlation_id = extract_correlation_id(&request).unwrap_or_default();
let repository = get_repository(request.metadata())?;
require_write_permission(request.extensions(), repository)?;

// TODO(mjansson): Once we have authz permission model with read/write/admin
// this should be upgraded to check for the correct permission rather than
Expand Down
2 changes: 2 additions & 0 deletions lore-server/src/grpc/handlers/branch_unprotect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use tracing::warn;
use crate::grpc::extract_correlation_id;
use crate::grpc::get_repository;
use crate::grpc::get_user_id;
use crate::grpc::require_write_permission;
use crate::util::setup_execution;

#[tracing::instrument(name = "BranchUnprotect::handle", skip_all)]
Expand All @@ -26,6 +27,7 @@ pub async fn handler(
mutable_store: Arc<dyn lore_storage::MutableStore>,
) -> Result<Response<BranchUnprotectResponse>, Status> {
let repository_id = get_repository(request.metadata())?;
require_write_permission(request.extensions(), repository_id)?;
let user_id = get_user_id(request.extensions());
let correlation_id = extract_correlation_id(&request).unwrap_or_default();
let req = request.into_inner();
Expand Down
7 changes: 4 additions & 3 deletions lore-server/src/grpc/handlers/repository_metadata_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use tonic::Status;
use crate::grpc::extract_correlation_id;
use crate::grpc::get_user_id;
use crate::grpc::get_write_token;
use crate::grpc::require_write_permission;
use crate::grpc::warn_error_to_status;
use crate::util::setup_execution;

Expand Down Expand Up @@ -97,12 +98,12 @@ pub async fn handler(
) -> Result<Response<RepositoryMetadataSetResponse>, Status> {
let user_id = get_user_id(request.extensions());
let correlation_id = extract_correlation_id(&request).unwrap_or_default();
let req = request.into_inner();

let repository_id: Context = req.repository_id.into();
let repository_id: Context = request.get_ref().repository_id.clone().into();
if repository_id == Context::default() {
return Err(Status::invalid_argument("Missing repository ID"));
}
require_write_permission(request.extensions(), repository_id.into())?;
let req = request.into_inner();

let expected_hash: Hash = req.expected_hash.into();
let new_hash: Hash = req.new_hash.into();
Expand Down
20 changes: 20 additions & 0 deletions lore-server/src/grpc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ use tracing::warn;

use crate::auth::jwt::AuthorizationToken;
use crate::auth::jwt::ResourcePermission;
use crate::auth::jwt::has_any_repository_permission;
use crate::auth::jwt::verify_authorization;
use crate::hooks::traits::HookError;
use crate::hooks::traits::StatusCode;
Expand Down Expand Up @@ -293,6 +294,25 @@ pub fn can_admin_lock(extensions: &Extensions, repository: RepositoryId) -> bool
has_required_permission(extensions, repository, "migrate")
}

pub fn can_write(extensions: &Extensions, repository: RepositoryId) -> bool {
get_authorization(extensions)
.ok()
.is_some_and(|authorization| {
has_any_repository_permission(&authorization, repository, &["write", "admin", "owner"])
})
}

pub fn require_write_permission(
extensions: &Extensions,
repository: RepositoryId,
) -> Result<(), Status> {
if get_authorization(extensions).is_err() || can_write(extensions, repository) {
Ok(())
} else {
Err(Status::permission_denied("Write permission required"))
}
}

pub fn get_matching_permissions(
extensions: &Extensions,
repository: RepositoryId,
Expand Down
7 changes: 4 additions & 3 deletions lore-server/src/grpc/repository/v1/repository_metadata_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use tonic::Status;
use crate::grpc::extract_correlation_id;
use crate::grpc::get_user_id;
use crate::grpc::get_write_token;
use crate::grpc::require_write_permission;
use crate::grpc::warn_error_to_status;
use crate::util::setup_execution;

Expand All @@ -44,12 +45,12 @@ pub async fn handler(
) -> Result<Response<RepositoryMetadataSetResponse>, Status> {
let user_id = get_user_id(request.extensions());
let correlation_id = extract_correlation_id(&request).unwrap_or_default();
let req = request.into_inner();

let repository_id: Context = req.id.into();
let repository_id: Context = request.get_ref().id.clone().into();
if repository_id == Context::default() {
return Err(Status::invalid_argument("Missing repository id"));
}
require_write_permission(request.extensions(), repository_id.into())?;
let req = request.into_inner();

let expected: Hash = req.expected.into();
let updated: Hash = req.updated.into();
Expand Down
Loading