feat: resolve repository coordinates #33
+12
-4
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,244 +1,249 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use syncode_identity_application::IdentityBridgeUseCases;
|
||||
use tonic::{Request, Response, Status};
|
||||
|
||||
use self::conversion::{
|
||||
application_error, application_principal, parse_resource, parse_resource_kind,
|
||||
wire_principal_kind, wire_resource,
|
||||
};
|
||||
|
||||
use crate::wire::identity_server::Identity;
|
||||
use crate::wire::{
|
||||
ArchiveRepositoryProjectionRequest, ArchiveRepositoryProjectionResponse,
|
||||
ArchiveRepositoryRequest, ArchiveRepositoryResponse, CheckBranchProtectionBypassRequest,
|
||||
CheckBranchProtectionBypassResponse, CheckCapabilitiesRequest, CheckCapabilitiesResponse,
|
||||
CheckCapabilityRequest, CheckCapabilityResponse, DeleteRepositoryRequest,
|
||||
DeleteRepositoryResponse, ListBranchProtectionRulesRequest, ListBranchProtectionRulesResponse,
|
||||
ListPermittedResourcesRequest, ListPermittedResourcesResponse, ListUserSigningKeysRequest,
|
||||
ListUserSigningKeysResponse, PrincipalKind, ProvisionRepositoryRequest,
|
||||
ProvisionRepositoryResponse, RegisterRepositoryRequest, RegisterRepositoryResponse,
|
||||
ResolveRepositoryRequest, ResolveRepositoryResponse, ResolveSshKeyRequest,
|
||||
ResolveSshKeyResponse, UnarchiveRepositoryRequest, UnarchiveRepositoryResponse,
|
||||
UpdateRepositoryMetadataRequest, UpdateRepositoryMetadataResponse,
|
||||
UpdateRepositoryVisibilityRequest, UpdateRepositoryVisibilityResponse, ValidateSessionRequest,
|
||||
ValidateSessionResponse,
|
||||
};
|
||||
|
||||
mod access;
|
||||
mod conversion;
|
||||
mod repository;
|
||||
mod server;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct IdentityServer {
|
||||
application: Arc<dyn IdentityBridgeUseCases>,
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl Identity for IdentityServer {
|
||||
async fn resolve_repository(
|
||||
&self,
|
||||
request: Request<ResolveRepositoryRequest>,
|
||||
) -> Result<Response<ResolveRepositoryResponse>, Status> {
|
||||
repository::resolve(self, request).await
|
||||
}
|
||||
|
||||
async fn register_repository(
|
||||
&self,
|
||||
request: Request<RegisterRepositoryRequest>,
|
||||
) -> Result<Response<RegisterRepositoryResponse>, Status> {
|
||||
repository::register(self, request).await
|
||||
}
|
||||
|
||||
async fn validate_session(
|
||||
&self,
|
||||
request: Request<ValidateSessionRequest>,
|
||||
) -> Result<Response<ValidateSessionResponse>, Status> {
|
||||
let token = request.into_inner().session_token;
|
||||
let credential = self
|
||||
.application
|
||||
.validate_credential(&token)
|
||||
.await
|
||||
.map_err(application_error)?;
|
||||
let (resource_kind, resource_id) = wire_resource(credential.resource);
|
||||
Ok(Response::new(ValidateSessionResponse {
|
||||
principal_id: credential.principal.get().to_string(),
|
||||
principal_kind: wire_principal_kind(credential.principal.kind()) as i32,
|
||||
expires_at_unix: credential.expires_at.timestamp(),
|
||||
owner_user_id: credential.owner_user_id.to_string(),
|
||||
capabilities: credential
|
||||
.capabilities
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect(),
|
||||
audience: credential.audience,
|
||||
resource_kind: resource_kind as i32,
|
||||
resource_id,
|
||||
}))
|
||||
}
|
||||
|
||||
async fn check_capability(
|
||||
&self,
|
||||
request: Request<CheckCapabilityRequest>,
|
||||
) -> Result<Response<CheckCapabilityResponse>, Status> {
|
||||
let request = request.into_inner();
|
||||
let resource = parse_resource(request.resource_kind(), &request.resource_id)?;
|
||||
|
||||
let principal = application_principal(request.principal_kind(), &request.principal_id)?;
|
||||
let capability = request
|
||||
.capability
|
||||
.parse()
|
||||
.map_err(|_| Status::invalid_argument("unsupported capability"))?;
|
||||
let allowed = self
|
||||
.application
|
||||
.check_capability(principal, resource, capability)
|
||||
.await
|
||||
.map_err(application_error)?;
|
||||
|
||||
Ok(Response::new(CheckCapabilityResponse { allowed }))
|
||||
}
|
||||
|
||||
async fn check_capabilities(
|
||||
&self,
|
||||
request: Request<CheckCapabilitiesRequest>,
|
||||
) -> Result<Response<CheckCapabilitiesResponse>, Status> {
|
||||
let request = request.into_inner();
|
||||
let principal = application_principal(request.principal_kind(), &request.principal_id)?;
|
||||
let mut pairs = Vec::with_capacity(request.queries.len());
|
||||
for query in &request.queries {
|
||||
let resource = parse_resource(query.resource_kind(), &query.resource_id)?;
|
||||
let capability = query
|
||||
.capability
|
||||
.parse()
|
||||
.map_err(|_| Status::invalid_argument("unsupported capability"))?;
|
||||
pairs.push((resource, capability));
|
||||
}
|
||||
let allowed = self
|
||||
.application
|
||||
.check_capabilities(principal, &pairs)
|
||||
.await
|
||||
.map_err(application_error)?;
|
||||
Ok(Response::new(CheckCapabilitiesResponse { allowed }))
|
||||
}
|
||||
|
||||
async fn list_permitted_resources(
|
||||
&self,
|
||||
request: Request<ListPermittedResourcesRequest>,
|
||||
) -> Result<Response<ListPermittedResourcesResponse>, Status> {
|
||||
let request = request.into_inner();
|
||||
let principal = application_principal(request.principal_kind(), &request.principal_id)?;
|
||||
let resource_kind = parse_resource_kind(request.resource_kind())?;
|
||||
let capability = request
|
||||
.capability
|
||||
.parse()
|
||||
.map_err(|_| Status::invalid_argument("unsupported capability"))?;
|
||||
let permitted = self
|
||||
.application
|
||||
.list_permitted_resources(principal, resource_kind, capability)
|
||||
.await
|
||||
.map_err(application_error)?;
|
||||
Ok(Response::new(ListPermittedResourcesResponse {
|
||||
resource_ids: permitted
|
||||
.resource_ids
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect(),
|
||||
includes_public: permitted.includes_public,
|
||||
}))
|
||||
}
|
||||
|
||||
async fn resolve_ssh_key(
|
||||
&self,
|
||||
request: Request<ResolveSshKeyRequest>,
|
||||
) -> Result<Response<ResolveSshKeyResponse>, Status> {
|
||||
let fingerprint = request.into_inner().fingerprint;
|
||||
if fingerprint.is_empty() {
|
||||
return Err(Status::invalid_argument("fingerprint is required"));
|
||||
}
|
||||
let owner = self
|
||||
.application
|
||||
.resolve_ssh_key(&fingerprint)
|
||||
.await
|
||||
.map_err(application_error)?;
|
||||
let principal_kind = match owner.owner {
|
||||
syncode_identity_application::SshKeyOwnerId::User(_) => PrincipalKind::User,
|
||||
syncode_identity_application::SshKeyOwnerId::LocalAgent(_) => PrincipalKind::LocalAgent,
|
||||
};
|
||||
Ok(Response::new(ResolveSshKeyResponse {
|
||||
principal_id: owner.owner.get().to_string(),
|
||||
principal_kind: principal_kind as i32,
|
||||
owner_user_id: owner.owner_user_id.to_string(),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn check_branch_protection_bypass(
|
||||
&self,
|
||||
request: Request<CheckBranchProtectionBypassRequest>,
|
||||
) -> Result<Response<CheckBranchProtectionBypassResponse>, Status> {
|
||||
access::check_branch_protection_bypass(self, request).await
|
||||
}
|
||||
|
||||
async fn list_branch_protection_rules(
|
||||
&self,
|
||||
request: Request<ListBranchProtectionRulesRequest>,
|
||||
) -> Result<Response<ListBranchProtectionRulesResponse>, Status> {
|
||||
access::list_branch_protection_rules(self, request).await
|
||||
}
|
||||
|
||||
async fn list_user_signing_keys(
|
||||
&self,
|
||||
request: Request<ListUserSigningKeysRequest>,
|
||||
) -> Result<Response<ListUserSigningKeysResponse>, Status> {
|
||||
access::list_user_signing_keys(self, request).await
|
||||
}
|
||||
|
||||
async fn provision_repository(
|
||||
&self,
|
||||
request: Request<ProvisionRepositoryRequest>,
|
||||
) -> Result<Response<ProvisionRepositoryResponse>, Status> {
|
||||
repository::provision(self, request).await
|
||||
}
|
||||
|
||||
async fn update_repository_visibility(
|
||||
&self,
|
||||
request: Request<UpdateRepositoryVisibilityRequest>,
|
||||
) -> Result<Response<UpdateRepositoryVisibilityResponse>, Status> {
|
||||
repository::update_visibility(self, request).await
|
||||
}
|
||||
|
||||
async fn update_repository_metadata(
|
||||
&self,
|
||||
request: Request<UpdateRepositoryMetadataRequest>,
|
||||
) -> Result<Response<UpdateRepositoryMetadataResponse>, Status> {
|
||||
repository::update_metadata(self, request).await
|
||||
}
|
||||
|
||||
async fn archive_repository(
|
||||
&self,
|
||||
request: Request<ArchiveRepositoryRequest>,
|
||||
) -> Result<Response<ArchiveRepositoryResponse>, Status> {
|
||||
repository::archive_repository(self, request).await
|
||||
}
|
||||
|
||||
async fn archive_repository_projection(
|
||||
&self,
|
||||
request: Request<ArchiveRepositoryProjectionRequest>,
|
||||
) -> Result<Response<ArchiveRepositoryProjectionResponse>, Status> {
|
||||
repository::archive(self, request).await
|
||||
}
|
||||
|
||||
async fn unarchive_repository(
|
||||
&self,
|
||||
request: Request<UnarchiveRepositoryRequest>,
|
||||
) -> Result<Response<UnarchiveRepositoryResponse>, Status> {
|
||||
repository::unarchive(self, request).await
|
||||
}
|
||||
|
||||
async fn delete_repository(
|
||||
&self,
|
||||
request: Request<DeleteRepositoryRequest>,
|
||||
) -> Result<Response<DeleteRepositoryResponse>, Status> {
|
||||
repository::delete(self, request).await
|
||||
}
|
||||
}
|
||||
use std::sync::Arc;
|
||||
|
||||
use syncode_identity_application::IdentityBridgeUseCases;
|
||||
use tonic::{Request, Response, Status};
|
||||
|
||||
use self::conversion::{
|
||||
application_error, application_principal, parse_resource, parse_resource_kind,
|
||||
wire_principal_kind, wire_resource,
|
||||
};
|
||||
|
||||
use crate::wire::identity_server::Identity;
|
||||
use crate::wire::{
|
||||
ArchiveRepositoryProjectionRequest, ArchiveRepositoryProjectionResponse,
|
||||
ArchiveRepositoryRequest, ArchiveRepositoryResponse, CheckBranchProtectionBypassRequest,
|
||||
CheckBranchProtectionBypassResponse, CheckCapabilitiesRequest, CheckCapabilitiesResponse,
|
||||
CheckCapabilityRequest, CheckCapabilityResponse, DeleteRepositoryRequest,
|
||||
DeleteRepositoryResponse, GetRepositoryCoordinatesRequest, GetRepositoryCoordinatesResponse,
|
||||
ListBranchProtectionRulesRequest, ListBranchProtectionRulesResponse,
|
||||
ListPermittedResourcesRequest, ListPermittedResourcesResponse, ListUserSigningKeysRequest,
|
||||
ListUserSigningKeysResponse, PrincipalKind, ProvisionRepositoryRequest,
|
||||
ProvisionRepositoryResponse, RegisterRepositoryRequest, RegisterRepositoryResponse,
|
||||
ResolveRepositoryRequest, ResolveRepositoryResponse, ResolveSshKeyRequest,
|
||||
ResolveSshKeyResponse, UnarchiveRepositoryRequest, UnarchiveRepositoryResponse,
|
||||
UpdateRepositoryMetadataRequest, UpdateRepositoryMetadataResponse,
|
||||
UpdateRepositoryVisibilityRequest, UpdateRepositoryVisibilityResponse, ValidateSessionRequest,
|
||||
ValidateSessionResponse,
|
||||
};
|
||||
|
||||
mod access;
|
||||
mod conversion;
|
||||
mod repository;
|
||||
mod server;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct IdentityServer {
|
||||
application: Arc<dyn IdentityBridgeUseCases>,
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl Identity for IdentityServer {
|
||||
async fn resolve_repository(
|
||||
&self,
|
||||
request: Request<ResolveRepositoryRequest>,
|
||||
) -> Result<Response<ResolveRepositoryResponse>, Status> {
|
||||
repository::resolve(self, request).await
|
||||
}
|
||||
async fn get_repository_coordinates(
|
||||
&self,
|
||||
request: Request<GetRepositoryCoordinatesRequest>,
|
||||
) -> Result<Response<GetRepositoryCoordinatesResponse>, Status> {
|
||||
repository::coordinates(self, request).await
|
||||
}
|
||||
async fn register_repository(
|
||||
&self,
|
||||
request: Request<RegisterRepositoryRequest>,
|
||||
) -> Result<Response<RegisterRepositoryResponse>, Status> {
|
||||
repository::register(self, request).await
|
||||
}
|
||||
async fn validate_session(
|
||||
&self,
|
||||
request: Request<ValidateSessionRequest>,
|
||||
) -> Result<Response<ValidateSessionResponse>, Status> {
|
||||
let token = request.into_inner().session_token;
|
||||
let credential = self
|
||||
.application
|
||||
.validate_credential(&token)
|
||||
.await
|
||||
.map_err(application_error)?;
|
||||
let (resource_kind, resource_id) = wire_resource(credential.resource);
|
||||
Ok(Response::new(ValidateSessionResponse {
|
||||
principal_id: credential.principal.get().to_string(),
|
||||
principal_kind: wire_principal_kind(credential.principal.kind()) as i32,
|
||||
expires_at_unix: credential.expires_at.timestamp(),
|
||||
owner_user_id: credential.owner_user_id.to_string(),
|
||||
capabilities: credential
|
||||
.capabilities
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect(),
|
||||
audience: credential.audience,
|
||||
resource_kind: resource_kind as i32,
|
||||
resource_id,
|
||||
}))
|
||||
}
|
||||
|
||||
async fn check_capability(
|
||||
&self,
|
||||
request: Request<CheckCapabilityRequest>,
|
||||
) -> Result<Response<CheckCapabilityResponse>, Status> {
|
||||
let request = request.into_inner();
|
||||
let resource = parse_resource(request.resource_kind(), &request.resource_id)?;
|
||||
|
||||
let principal = application_principal(request.principal_kind(), &request.principal_id)?;
|
||||
let capability = request
|
||||
.capability
|
||||
.parse()
|
||||
.map_err(|_| Status::invalid_argument("unsupported capability"))?;
|
||||
let allowed = self
|
||||
.application
|
||||
.check_capability(principal, resource, capability)
|
||||
.await
|
||||
.map_err(application_error)?;
|
||||
|
||||
Ok(Response::new(CheckCapabilityResponse { allowed }))
|
||||
}
|
||||
|
||||
async fn check_capabilities(
|
||||
&self,
|
||||
request: Request<CheckCapabilitiesRequest>,
|
||||
) -> Result<Response<CheckCapabilitiesResponse>, Status> {
|
||||
let request = request.into_inner();
|
||||
let principal = application_principal(request.principal_kind(), &request.principal_id)?;
|
||||
let mut pairs = Vec::with_capacity(request.queries.len());
|
||||
for query in &request.queries {
|
||||
let resource = parse_resource(query.resource_kind(), &query.resource_id)?;
|
||||
let capability = query
|
||||
.capability
|
||||
.parse()
|
||||
.map_err(|_| Status::invalid_argument("unsupported capability"))?;
|
||||
pairs.push((resource, capability));
|
||||
}
|
||||
let allowed = self
|
||||
.application
|
||||
.check_capabilities(principal, &pairs)
|
||||
.await
|
||||
.map_err(application_error)?;
|
||||
Ok(Response::new(CheckCapabilitiesResponse { allowed }))
|
||||
}
|
||||
|
||||
async fn list_permitted_resources(
|
||||
&self,
|
||||
request: Request<ListPermittedResourcesRequest>,
|
||||
) -> Result<Response<ListPermittedResourcesResponse>, Status> {
|
||||
let request = request.into_inner();
|
||||
let principal = application_principal(request.principal_kind(), &request.principal_id)?;
|
||||
let resource_kind = parse_resource_kind(request.resource_kind())?;
|
||||
let capability = request
|
||||
.capability
|
||||
.parse()
|
||||
.map_err(|_| Status::invalid_argument("unsupported capability"))?;
|
||||
let permitted = self
|
||||
.application
|
||||
.list_permitted_resources(principal, resource_kind, capability)
|
||||
.await
|
||||
.map_err(application_error)?;
|
||||
Ok(Response::new(ListPermittedResourcesResponse {
|
||||
resource_ids: permitted
|
||||
.resource_ids
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect(),
|
||||
includes_public: permitted.includes_public,
|
||||
}))
|
||||
}
|
||||
|
||||
async fn resolve_ssh_key(
|
||||
&self,
|
||||
request: Request<ResolveSshKeyRequest>,
|
||||
) -> Result<Response<ResolveSshKeyResponse>, Status> {
|
||||
let fingerprint = request.into_inner().fingerprint;
|
||||
if fingerprint.is_empty() {
|
||||
return Err(Status::invalid_argument("fingerprint is required"));
|
||||
}
|
||||
let owner = self
|
||||
.application
|
||||
.resolve_ssh_key(&fingerprint)
|
||||
.await
|
||||
.map_err(application_error)?;
|
||||
let principal_kind = match owner.owner {
|
||||
syncode_identity_application::SshKeyOwnerId::User(_) => PrincipalKind::User,
|
||||
syncode_identity_application::SshKeyOwnerId::LocalAgent(_) => PrincipalKind::LocalAgent,
|
||||
};
|
||||
Ok(Response::new(ResolveSshKeyResponse {
|
||||
principal_id: owner.owner.get().to_string(),
|
||||
principal_kind: principal_kind as i32,
|
||||
owner_user_id: owner.owner_user_id.to_string(),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn check_branch_protection_bypass(
|
||||
&self,
|
||||
request: Request<CheckBranchProtectionBypassRequest>,
|
||||
) -> Result<Response<CheckBranchProtectionBypassResponse>, Status> {
|
||||
access::check_branch_protection_bypass(self, request).await
|
||||
}
|
||||
|
||||
async fn list_branch_protection_rules(
|
||||
&self,
|
||||
request: Request<ListBranchProtectionRulesRequest>,
|
||||
) -> Result<Response<ListBranchProtectionRulesResponse>, Status> {
|
||||
access::list_branch_protection_rules(self, request).await
|
||||
}
|
||||
|
||||
async fn list_user_signing_keys(
|
||||
&self,
|
||||
request: Request<ListUserSigningKeysRequest>,
|
||||
) -> Result<Response<ListUserSigningKeysResponse>, Status> {
|
||||
access::list_user_signing_keys(self, request).await
|
||||
}
|
||||
|
||||
async fn provision_repository(
|
||||
&self,
|
||||
request: Request<ProvisionRepositoryRequest>,
|
||||
) -> Result<Response<ProvisionRepositoryResponse>, Status> {
|
||||
repository::provision(self, request).await
|
||||
}
|
||||
|
||||
async fn update_repository_visibility(
|
||||
&self,
|
||||
request: Request<UpdateRepositoryVisibilityRequest>,
|
||||
) -> Result<Response<UpdateRepositoryVisibilityResponse>, Status> {
|
||||
repository::update_visibility(self, request).await
|
||||
}
|
||||
|
||||
async fn update_repository_metadata(
|
||||
&self,
|
||||
request: Request<UpdateRepositoryMetadataRequest>,
|
||||
) -> Result<Response<UpdateRepositoryMetadataResponse>, Status> {
|
||||
repository::update_metadata(self, request).await
|
||||
}
|
||||
|
||||
async fn archive_repository(
|
||||
&self,
|
||||
request: Request<ArchiveRepositoryRequest>,
|
||||
) -> Result<Response<ArchiveRepositoryResponse>, Status> {
|
||||
repository::archive_repository(self, request).await
|
||||
}
|
||||
|
||||
async fn archive_repository_projection(
|
||||
&self,
|
||||
request: Request<ArchiveRepositoryProjectionRequest>,
|
||||
) -> Result<Response<ArchiveRepositoryProjectionResponse>, Status> {
|
||||
repository::archive(self, request).await
|
||||
}
|
||||
|
||||
async fn unarchive_repository(
|
||||
&self,
|
||||
request: Request<UnarchiveRepositoryRequest>,
|
||||
) -> Result<Response<UnarchiveRepositoryResponse>, Status> {
|
||||
repository::unarchive(self, request).await
|
||||
}
|
||||
|
||||
async fn delete_repository(
|
||||
&self,
|
||||
request: Request<DeleteRepositoryRequest>,
|
||||
) -> Result<Response<DeleteRepositoryResponse>, Status> {
|
||||
repository::delete(self, request).await
|
||||
}
|
||||
}
|
||||
@@ -1,234 +1,242 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use chrono::{DateTime, Utc};
|
||||
use syncode_identity_model::{
|
||||
AccessTokenId, Capability, Grant, GrantPrincipal, LocalAgentId, PlatformAgentId, RepositoryId,
|
||||
RepositoryName, RepositoryOwner, RepositoryOwnerId, RepositoryVisibility, Resource, UserId,
|
||||
UserPrincipal,
|
||||
};
|
||||
|
||||
use crate::ApplicationError;
|
||||
|
||||
pub use access::PermittedResources;
|
||||
mod access;
|
||||
mod repository;
|
||||
mod types;
|
||||
|
||||
pub use types::*;
|
||||
#[async_trait]
|
||||
pub trait IdentityBridgeRepository: Send + Sync {
|
||||
async fn validate_web_session(
|
||||
&self,
|
||||
token: &str,
|
||||
) -> Result<Option<(UserId, DateTime<Utc>)>, ApplicationError>;
|
||||
async fn validate_access_token(
|
||||
&self,
|
||||
token: &str,
|
||||
) -> Result<Option<AccessTokenIdentity>, ApplicationError>;
|
||||
async fn load_access_token(
|
||||
&self,
|
||||
token_id: AccessTokenId,
|
||||
) -> Result<Option<AccessTokenIdentity>, ApplicationError>;
|
||||
async fn load_user_principal(&self, user_id: UserId)
|
||||
-> Result<UserPrincipal, ApplicationError>;
|
||||
async fn load_active_grants(&self, resource: Resource) -> Result<Vec<Grant>, ApplicationError>;
|
||||
/// `load_active_grants` inverted: what a principal can reach, so listing
|
||||
/// many resources costs one query instead of one per resource.
|
||||
async fn load_granted_resources(
|
||||
&self,
|
||||
principals: &[(syncode_identity_model::GrantPrincipalKind, uuid::Uuid)],
|
||||
resource_kind: syncode_identity_model::ResourceKind,
|
||||
) -> Result<
|
||||
Vec<(
|
||||
uuid::Uuid,
|
||||
std::collections::BTreeSet<syncode_identity_model::Capability>,
|
||||
)>,
|
||||
ApplicationError,
|
||||
>;
|
||||
async fn load_active_repository_visibility(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
) -> Result<Option<RepositoryVisibility>, ApplicationError>;
|
||||
async fn load_active_repository_owner(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
) -> Result<Option<RepositoryOwnerId>, ApplicationError>;
|
||||
async fn platform_agent_is_active(
|
||||
&self,
|
||||
agent_id: PlatformAgentId,
|
||||
) -> Result<bool, ApplicationError>;
|
||||
async fn local_agent(
|
||||
&self,
|
||||
agent_id: LocalAgentId,
|
||||
) -> Result<Option<LocalAgentIdentity>, ApplicationError>;
|
||||
async fn resolve_ssh_key(
|
||||
&self,
|
||||
fingerprint: &str,
|
||||
) -> Result<Option<SshKeyIdentity>, ApplicationError>;
|
||||
async fn branch_protection_bypass_allowed(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
pattern: &str,
|
||||
principal: GrantPrincipal,
|
||||
) -> Result<bool, ApplicationError>;
|
||||
async fn active_branch_protection_rules(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
) -> Result<Vec<ActiveBranchProtectionRule>, ApplicationError>;
|
||||
async fn user_signing_keys(
|
||||
&self,
|
||||
user_id: UserId,
|
||||
) -> Result<Vec<SigningKeyIdentity>, ApplicationError>;
|
||||
async fn resolve_repository(
|
||||
&self,
|
||||
owner: &RepositoryOwner,
|
||||
name: &RepositoryName,
|
||||
) -> Result<Option<RepositoryId>, ApplicationError>;
|
||||
async fn provision_repository(
|
||||
&self,
|
||||
owner: RepositoryOwnerId,
|
||||
name: &RepositoryName,
|
||||
visibility: RepositoryVisibility,
|
||||
creator_id: UserId,
|
||||
) -> Result<RepositoryId, ApplicationError>;
|
||||
async fn register_repository(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
owner: RepositoryOwnerId,
|
||||
name: &RepositoryName,
|
||||
visibility: RepositoryVisibility,
|
||||
creator_id: UserId,
|
||||
) -> Result<(), ApplicationError>;
|
||||
async fn update_repository_visibility(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
visibility: RepositoryVisibility,
|
||||
) -> Result<bool, ApplicationError>;
|
||||
async fn update_repository_metadata(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
owner: RepositoryOwnerId,
|
||||
name: &RepositoryName,
|
||||
visibility: RepositoryVisibility,
|
||||
actor_id: UserId,
|
||||
) -> Result<bool, ApplicationError>;
|
||||
async fn archive_repository(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
) -> Result<bool, ApplicationError>;
|
||||
async fn set_repository_archived(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
archived: bool,
|
||||
) -> Result<bool, ApplicationError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait IdentityBridgeAccessUseCases: Send + Sync {
|
||||
async fn validate_credential(
|
||||
&self,
|
||||
token: &str,
|
||||
) -> Result<ValidatedCredential, ApplicationError>;
|
||||
async fn check_capability(
|
||||
&self,
|
||||
principal: BridgePrincipalId,
|
||||
resource: Resource,
|
||||
capability: Capability,
|
||||
) -> Result<bool, ApplicationError>;
|
||||
/// One round trip for many resources. Same decision as `check_capability`,
|
||||
/// answered per pair, in order.
|
||||
async fn check_capabilities(
|
||||
&self,
|
||||
principal: BridgePrincipalId,
|
||||
pairs: &[(Resource, Capability)],
|
||||
) -> Result<Vec<bool>, ApplicationError>;
|
||||
async fn list_permitted_resources(
|
||||
&self,
|
||||
principal: BridgePrincipalId,
|
||||
resource_kind: syncode_identity_model::ResourceKind,
|
||||
capability: Capability,
|
||||
) -> Result<PermittedResources, ApplicationError>;
|
||||
async fn resolve_ssh_key(&self, fingerprint: &str) -> Result<SshKeyIdentity, ApplicationError>;
|
||||
async fn check_branch_protection_bypass(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
pattern: &str,
|
||||
principal: Option<BridgePrincipalId>,
|
||||
) -> Result<BranchProtectionDecision, ApplicationError>;
|
||||
async fn list_branch_protection_rules(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
) -> Result<Vec<ActiveBranchProtectionRule>, ApplicationError>;
|
||||
async fn list_user_signing_keys(
|
||||
&self,
|
||||
user_id: UserId,
|
||||
) -> Result<Vec<SigningKeyIdentity>, ApplicationError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait IdentityBridgeRepositoryUseCases: Send + Sync {
|
||||
async fn resolve_repository(
|
||||
&self,
|
||||
owner: RepositoryOwner,
|
||||
name: RepositoryName,
|
||||
) -> Result<RepositoryId, ApplicationError>;
|
||||
async fn register_repository(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
owner: RepositoryOwnerId,
|
||||
name: RepositoryName,
|
||||
visibility: RepositoryVisibility,
|
||||
creator_id: UserId,
|
||||
) -> Result<(), ApplicationError>;
|
||||
async fn provision_repository(
|
||||
&self,
|
||||
owner: RepositoryOwnerId,
|
||||
name: RepositoryName,
|
||||
visibility: RepositoryVisibility,
|
||||
creator_id: UserId,
|
||||
) -> Result<RepositoryId, ApplicationError>;
|
||||
async fn update_repository_visibility(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
visibility: RepositoryVisibility,
|
||||
) -> Result<(), ApplicationError>;
|
||||
async fn update_repository_metadata(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
owner: RepositoryOwnerId,
|
||||
name: RepositoryName,
|
||||
visibility: RepositoryVisibility,
|
||||
actor_id: UserId,
|
||||
) -> Result<(), ApplicationError>;
|
||||
async fn archive_repository(&self, repository_id: RepositoryId)
|
||||
-> Result<(), ApplicationError>;
|
||||
async fn set_repository_archived(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
archived: bool,
|
||||
) -> Result<(), ApplicationError>;
|
||||
async fn delete_repository(&self, repository_id: RepositoryId) -> Result<(), ApplicationError>;
|
||||
}
|
||||
|
||||
pub trait IdentityBridgeUseCases:
|
||||
IdentityBridgeAccessUseCases + IdentityBridgeRepositoryUseCases
|
||||
{
|
||||
}
|
||||
|
||||
impl<T> IdentityBridgeUseCases for T where
|
||||
T: IdentityBridgeAccessUseCases + IdentityBridgeRepositoryUseCases + ?Sized
|
||||
{
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct IdentityBridgeApplication {
|
||||
repository: Arc<dyn IdentityBridgeRepository>,
|
||||
}
|
||||
|
||||
impl IdentityBridgeApplication {
|
||||
#[must_use]
|
||||
pub fn new(repository: Arc<dyn IdentityBridgeRepository>) -> Self {
|
||||
Self { repository }
|
||||
}
|
||||
}
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use chrono::{DateTime, Utc};
|
||||
use syncode_identity_model::{
|
||||
AccessTokenId, Capability, Grant, GrantPrincipal, LocalAgentId, PlatformAgentId,
|
||||
RepositoryCoordinates, RepositoryId, RepositoryName, RepositoryOwner, RepositoryOwnerId,
|
||||
RepositoryVisibility, Resource, UserId, UserPrincipal,
|
||||
};
|
||||
|
||||
use crate::ApplicationError;
|
||||
|
||||
pub use access::PermittedResources;
|
||||
mod access;
|
||||
mod repository;
|
||||
mod types;
|
||||
|
||||
pub use types::*;
|
||||
#[async_trait]
|
||||
pub trait IdentityBridgeRepository: Send + Sync {
|
||||
async fn validate_web_session(
|
||||
&self,
|
||||
token: &str,
|
||||
) -> Result<Option<(UserId, DateTime<Utc>)>, ApplicationError>;
|
||||
async fn validate_access_token(
|
||||
&self,
|
||||
token: &str,
|
||||
) -> Result<Option<AccessTokenIdentity>, ApplicationError>;
|
||||
async fn load_access_token(
|
||||
&self,
|
||||
token_id: AccessTokenId,
|
||||
) -> Result<Option<AccessTokenIdentity>, ApplicationError>;
|
||||
async fn load_user_principal(&self, user_id: UserId)
|
||||
-> Result<UserPrincipal, ApplicationError>;
|
||||
async fn load_active_grants(&self, resource: Resource) -> Result<Vec<Grant>, ApplicationError>;
|
||||
/// `load_active_grants` inverted: what a principal can reach, so listing
|
||||
/// many resources costs one query instead of one per resource.
|
||||
async fn load_granted_resources(
|
||||
&self,
|
||||
principals: &[(syncode_identity_model::GrantPrincipalKind, uuid::Uuid)],
|
||||
resource_kind: syncode_identity_model::ResourceKind,
|
||||
) -> Result<
|
||||
Vec<(
|
||||
uuid::Uuid,
|
||||
std::collections::BTreeSet<syncode_identity_model::Capability>,
|
||||
)>,
|
||||
ApplicationError,
|
||||
>;
|
||||
async fn load_active_repository_visibility(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
) -> Result<Option<RepositoryVisibility>, ApplicationError>;
|
||||
async fn load_active_repository_owner(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
) -> Result<Option<RepositoryOwnerId>, ApplicationError>;
|
||||
async fn platform_agent_is_active(
|
||||
&self,
|
||||
agent_id: PlatformAgentId,
|
||||
) -> Result<bool, ApplicationError>;
|
||||
async fn local_agent(
|
||||
&self,
|
||||
agent_id: LocalAgentId,
|
||||
) -> Result<Option<LocalAgentIdentity>, ApplicationError>;
|
||||
async fn resolve_ssh_key(
|
||||
&self,
|
||||
fingerprint: &str,
|
||||
) -> Result<Option<SshKeyIdentity>, ApplicationError>;
|
||||
async fn branch_protection_bypass_allowed(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
pattern: &str,
|
||||
principal: GrantPrincipal,
|
||||
) -> Result<bool, ApplicationError>;
|
||||
async fn active_branch_protection_rules(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
) -> Result<Vec<ActiveBranchProtectionRule>, ApplicationError>;
|
||||
async fn user_signing_keys(
|
||||
&self,
|
||||
user_id: UserId,
|
||||
) -> Result<Vec<SigningKeyIdentity>, ApplicationError>;
|
||||
async fn resolve_repository(
|
||||
&self,
|
||||
owner: &RepositoryOwner,
|
||||
name: &RepositoryName,
|
||||
) -> Result<Option<RepositoryId>, ApplicationError>;
|
||||
async fn repository_coordinates(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
) -> Result<Option<RepositoryCoordinates>, ApplicationError>;
|
||||
async fn provision_repository(
|
||||
&self,
|
||||
owner: RepositoryOwnerId,
|
||||
name: &RepositoryName,
|
||||
visibility: RepositoryVisibility,
|
||||
creator_id: UserId,
|
||||
) -> Result<RepositoryId, ApplicationError>;
|
||||
async fn register_repository(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
owner: RepositoryOwnerId,
|
||||
name: &RepositoryName,
|
||||
visibility: RepositoryVisibility,
|
||||
creator_id: UserId,
|
||||
) -> Result<(), ApplicationError>;
|
||||
async fn update_repository_visibility(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
visibility: RepositoryVisibility,
|
||||
) -> Result<bool, ApplicationError>;
|
||||
async fn update_repository_metadata(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
owner: RepositoryOwnerId,
|
||||
name: &RepositoryName,
|
||||
visibility: RepositoryVisibility,
|
||||
actor_id: UserId,
|
||||
) -> Result<bool, ApplicationError>;
|
||||
async fn archive_repository(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
) -> Result<bool, ApplicationError>;
|
||||
async fn set_repository_archived(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
archived: bool,
|
||||
) -> Result<bool, ApplicationError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait IdentityBridgeAccessUseCases: Send + Sync {
|
||||
async fn validate_credential(
|
||||
&self,
|
||||
token: &str,
|
||||
) -> Result<ValidatedCredential, ApplicationError>;
|
||||
async fn check_capability(
|
||||
&self,
|
||||
principal: BridgePrincipalId,
|
||||
resource: Resource,
|
||||
capability: Capability,
|
||||
) -> Result<bool, ApplicationError>;
|
||||
/// One round trip for many resources. Same decision as `check_capability`,
|
||||
/// answered per pair, in order.
|
||||
async fn check_capabilities(
|
||||
&self,
|
||||
principal: BridgePrincipalId,
|
||||
pairs: &[(Resource, Capability)],
|
||||
) -> Result<Vec<bool>, ApplicationError>;
|
||||
async fn list_permitted_resources(
|
||||
&self,
|
||||
principal: BridgePrincipalId,
|
||||
resource_kind: syncode_identity_model::ResourceKind,
|
||||
capability: Capability,
|
||||
) -> Result<PermittedResources, ApplicationError>;
|
||||
async fn resolve_ssh_key(&self, fingerprint: &str) -> Result<SshKeyIdentity, ApplicationError>;
|
||||
async fn check_branch_protection_bypass(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
pattern: &str,
|
||||
principal: Option<BridgePrincipalId>,
|
||||
) -> Result<BranchProtectionDecision, ApplicationError>;
|
||||
async fn list_branch_protection_rules(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
) -> Result<Vec<ActiveBranchProtectionRule>, ApplicationError>;
|
||||
async fn list_user_signing_keys(
|
||||
&self,
|
||||
user_id: UserId,
|
||||
) -> Result<Vec<SigningKeyIdentity>, ApplicationError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait IdentityBridgeRepositoryUseCases: Send + Sync {
|
||||
async fn resolve_repository(
|
||||
&self,
|
||||
owner: RepositoryOwner,
|
||||
name: RepositoryName,
|
||||
) -> Result<RepositoryId, ApplicationError>;
|
||||
async fn repository_coordinates(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
) -> Result<RepositoryCoordinates, ApplicationError>;
|
||||
async fn register_repository(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
owner: RepositoryOwnerId,
|
||||
name: RepositoryName,
|
||||
visibility: RepositoryVisibility,
|
||||
creator_id: UserId,
|
||||
) -> Result<(), ApplicationError>;
|
||||
async fn provision_repository(
|
||||
&self,
|
||||
owner: RepositoryOwnerId,
|
||||
name: RepositoryName,
|
||||
visibility: RepositoryVisibility,
|
||||
creator_id: UserId,
|
||||
) -> Result<RepositoryId, ApplicationError>;
|
||||
async fn update_repository_visibility(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
visibility: RepositoryVisibility,
|
||||
) -> Result<(), ApplicationError>;
|
||||
async fn update_repository_metadata(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
owner: RepositoryOwnerId,
|
||||
name: RepositoryName,
|
||||
visibility: RepositoryVisibility,
|
||||
actor_id: UserId,
|
||||
) -> Result<(), ApplicationError>;
|
||||
async fn archive_repository(&self, repository_id: RepositoryId)
|
||||
-> Result<(), ApplicationError>;
|
||||
async fn set_repository_archived(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
archived: bool,
|
||||
) -> Result<(), ApplicationError>;
|
||||
async fn delete_repository(&self, repository_id: RepositoryId) -> Result<(), ApplicationError>;
|
||||
}
|
||||
|
||||
pub trait IdentityBridgeUseCases:
|
||||
IdentityBridgeAccessUseCases + IdentityBridgeRepositoryUseCases
|
||||
{
|
||||
}
|
||||
|
||||
impl<T> IdentityBridgeUseCases for T where
|
||||
T: IdentityBridgeAccessUseCases + IdentityBridgeRepositoryUseCases + ?Sized
|
||||
{
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct IdentityBridgeApplication {
|
||||
repository: Arc<dyn IdentityBridgeRepository>,
|
||||
}
|
||||
|
||||
impl IdentityBridgeApplication {
|
||||
#[must_use]
|
||||
pub fn new(repository: Arc<dyn IdentityBridgeRepository>) -> Self {
|
||||
Self { repository }
|
||||
}
|
||||
}
|
||||
@@ -1,247 +1,250 @@
|
||||
use async_trait::async_trait;
|
||||
use syncode_identity_application::{
|
||||
AccessTokenIdentity, ActiveBranchProtectionRule, ApplicationError, IdentityBridgeRepository,
|
||||
LocalAgentIdentity, SigningKeyIdentity, SshKeyIdentity,
|
||||
};
|
||||
use syncode_identity_model::{
|
||||
AccessTokenId, Grant, GrantPrincipal, LocalAgentId, PlatformAgentId, RepositoryId,
|
||||
RepositoryName, RepositoryOwner, RepositoryOwnerId, RepositoryVisibility, Resource, UserId,
|
||||
UserPrincipal,
|
||||
};
|
||||
|
||||
use crate::{Postgres, application_persistence as persistence};
|
||||
|
||||
mod identities;
|
||||
mod repository;
|
||||
mod repository_grants;
|
||||
mod repository_metadata;
|
||||
mod repository_registration;
|
||||
mod token;
|
||||
|
||||
use token::{access_token, ssh_key};
|
||||
|
||||
#[async_trait]
|
||||
impl IdentityBridgeRepository for Postgres {
|
||||
async fn validate_web_session(
|
||||
&self,
|
||||
token: &str,
|
||||
) -> Result<Option<(UserId, chrono::DateTime<chrono::Utc>)>, ApplicationError> {
|
||||
self.validate_session(token)
|
||||
.await
|
||||
.map(|session| session.map(|(id, expires_at)| (id.into(), expires_at)))
|
||||
.map_err(persistence)
|
||||
}
|
||||
|
||||
async fn validate_access_token(
|
||||
&self,
|
||||
token: &str,
|
||||
) -> Result<Option<AccessTokenIdentity>, ApplicationError> {
|
||||
Postgres::validate_access_token(self, token)
|
||||
.await
|
||||
.map(|token| token.map(access_token))
|
||||
.map_err(persistence)
|
||||
}
|
||||
|
||||
async fn load_access_token(
|
||||
&self,
|
||||
token_id: AccessTokenId,
|
||||
) -> Result<Option<AccessTokenIdentity>, ApplicationError> {
|
||||
Postgres::load_access_token(self, token_id.get())
|
||||
.await
|
||||
.map(|token| token.map(access_token))
|
||||
.map_err(persistence)
|
||||
}
|
||||
|
||||
async fn load_user_principal(
|
||||
&self,
|
||||
user_id: UserId,
|
||||
) -> Result<UserPrincipal, ApplicationError> {
|
||||
Postgres::load_user_principal(self, user_id.get())
|
||||
.await
|
||||
.map_err(persistence)
|
||||
}
|
||||
|
||||
async fn load_granted_resources(
|
||||
&self,
|
||||
principals: &[(syncode_identity_model::GrantPrincipalKind, uuid::Uuid)],
|
||||
resource_kind: syncode_identity_model::ResourceKind,
|
||||
) -> Result<
|
||||
Vec<(
|
||||
uuid::Uuid,
|
||||
std::collections::BTreeSet<syncode_identity_model::Capability>,
|
||||
)>,
|
||||
ApplicationError,
|
||||
> {
|
||||
Postgres::load_granted_resources(self, principals, resource_kind)
|
||||
.await
|
||||
.map_err(|error| ApplicationError::Persistence(error.to_string()))
|
||||
}
|
||||
|
||||
async fn load_active_grants(&self, resource: Resource) -> Result<Vec<Grant>, ApplicationError> {
|
||||
Postgres::load_active_grants(self, resource)
|
||||
.await
|
||||
.map_err(persistence)
|
||||
}
|
||||
|
||||
async fn load_active_repository_visibility(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
) -> Result<Option<RepositoryVisibility>, ApplicationError> {
|
||||
self.bridge_repository_visibility(repository_id.get()).await
|
||||
}
|
||||
|
||||
async fn load_active_repository_owner(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
) -> Result<Option<RepositoryOwnerId>, ApplicationError> {
|
||||
self.bridge_repository_owner(repository_id.get()).await
|
||||
}
|
||||
|
||||
async fn platform_agent_is_active(
|
||||
&self,
|
||||
agent_id: PlatformAgentId,
|
||||
) -> Result<bool, ApplicationError> {
|
||||
Postgres::platform_agent_is_active(self, agent_id.get())
|
||||
.await
|
||||
.map_err(persistence)
|
||||
}
|
||||
|
||||
async fn local_agent(
|
||||
&self,
|
||||
agent_id: LocalAgentId,
|
||||
) -> Result<Option<LocalAgentIdentity>, ApplicationError> {
|
||||
self.bridge_local_agent(agent_id.get()).await
|
||||
}
|
||||
|
||||
async fn resolve_ssh_key(
|
||||
&self,
|
||||
fingerprint: &str,
|
||||
) -> Result<Option<SshKeyIdentity>, ApplicationError> {
|
||||
Postgres::resolve_ssh_key(self, fingerprint)
|
||||
.await
|
||||
.map_err(persistence)?
|
||||
.map(ssh_key)
|
||||
.transpose()
|
||||
}
|
||||
|
||||
async fn branch_protection_bypass_allowed(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
pattern: &str,
|
||||
principal: GrantPrincipal,
|
||||
) -> Result<bool, ApplicationError> {
|
||||
Postgres::branch_protection_bypass_allowed(
|
||||
self,
|
||||
repository_id.get(),
|
||||
pattern,
|
||||
principal.kind(),
|
||||
principal.get(),
|
||||
)
|
||||
.await
|
||||
.map_err(persistence)
|
||||
}
|
||||
|
||||
async fn active_branch_protection_rules(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
) -> Result<Vec<ActiveBranchProtectionRule>, ApplicationError> {
|
||||
self.bridge_active_branch_protection_rules(repository_id.get())
|
||||
.await
|
||||
}
|
||||
|
||||
async fn user_signing_keys(
|
||||
&self,
|
||||
user_id: UserId,
|
||||
) -> Result<Vec<SigningKeyIdentity>, ApplicationError> {
|
||||
self.bridge_user_signing_keys(user_id.get()).await
|
||||
}
|
||||
|
||||
async fn resolve_repository(
|
||||
&self,
|
||||
owner: &RepositoryOwner,
|
||||
name: &RepositoryName,
|
||||
) -> Result<Option<RepositoryId>, ApplicationError> {
|
||||
self.bridge_resolve_repository(owner, name).await
|
||||
}
|
||||
|
||||
async fn provision_repository(
|
||||
&self,
|
||||
owner: RepositoryOwnerId,
|
||||
name: &RepositoryName,
|
||||
visibility: RepositoryVisibility,
|
||||
creator_id: UserId,
|
||||
) -> Result<RepositoryId, ApplicationError> {
|
||||
self.bridge_provision_repository(
|
||||
owner.kind(),
|
||||
owner.get(),
|
||||
name,
|
||||
visibility,
|
||||
creator_id.get(),
|
||||
)
|
||||
.await
|
||||
.map(RepositoryId::from)
|
||||
}
|
||||
|
||||
async fn register_repository(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
owner: RepositoryOwnerId,
|
||||
name: &RepositoryName,
|
||||
visibility: RepositoryVisibility,
|
||||
creator_id: UserId,
|
||||
) -> Result<(), ApplicationError> {
|
||||
self.bridge_register_repository(
|
||||
repository_id.get(),
|
||||
owner.kind(),
|
||||
owner.get(),
|
||||
name,
|
||||
visibility,
|
||||
creator_id.get(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn update_repository_visibility(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
visibility: RepositoryVisibility,
|
||||
) -> Result<bool, ApplicationError> {
|
||||
self.bridge_update_repository_visibility(repository_id.get(), visibility)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn update_repository_metadata(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
owner: RepositoryOwnerId,
|
||||
name: &RepositoryName,
|
||||
visibility: RepositoryVisibility,
|
||||
actor_id: UserId,
|
||||
) -> Result<bool, ApplicationError> {
|
||||
self.bridge_update_repository_metadata(
|
||||
repository_id.get(),
|
||||
owner.kind(),
|
||||
owner.get(),
|
||||
name,
|
||||
visibility,
|
||||
actor_id.get(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn archive_repository(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
) -> Result<bool, ApplicationError> {
|
||||
self.bridge_archive_repository(repository_id.get()).await
|
||||
}
|
||||
|
||||
async fn set_repository_archived(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
archived: bool,
|
||||
) -> Result<bool, ApplicationError> {
|
||||
self.bridge_set_repository_archived(repository_id.get(), archived)
|
||||
.await
|
||||
}
|
||||
}
|
||||
use async_trait::async_trait;
|
||||
use syncode_identity_application::{
|
||||
AccessTokenIdentity, ActiveBranchProtectionRule, ApplicationError, IdentityBridgeRepository,
|
||||
LocalAgentIdentity, SigningKeyIdentity, SshKeyIdentity,
|
||||
};
|
||||
use syncode_identity_model::{
|
||||
AccessTokenId, Grant, GrantPrincipal, LocalAgentId, PlatformAgentId, RepositoryCoordinates,
|
||||
RepositoryId, RepositoryName, RepositoryOwner, RepositoryOwnerId, RepositoryVisibility,
|
||||
Resource, UserId, UserPrincipal,
|
||||
};
|
||||
|
||||
use crate::{Postgres, application_persistence as persistence};
|
||||
|
||||
mod identities;
|
||||
mod repository;
|
||||
mod repository_grants;
|
||||
mod repository_metadata;
|
||||
mod repository_registration;
|
||||
mod token;
|
||||
|
||||
use token::{access_token, ssh_key};
|
||||
|
||||
#[async_trait]
|
||||
impl IdentityBridgeRepository for Postgres {
|
||||
async fn validate_web_session(
|
||||
&self,
|
||||
token: &str,
|
||||
) -> Result<Option<(UserId, chrono::DateTime<chrono::Utc>)>, ApplicationError> {
|
||||
self.validate_session(token)
|
||||
.await
|
||||
.map(|session| session.map(|(id, expires_at)| (id.into(), expires_at)))
|
||||
.map_err(persistence)
|
||||
}
|
||||
async fn validate_access_token(
|
||||
&self,
|
||||
token: &str,
|
||||
) -> Result<Option<AccessTokenIdentity>, ApplicationError> {
|
||||
Postgres::validate_access_token(self, token)
|
||||
.await
|
||||
.map(|token| token.map(access_token))
|
||||
.map_err(persistence)
|
||||
}
|
||||
|
||||
async fn load_access_token(
|
||||
&self,
|
||||
token_id: AccessTokenId,
|
||||
) -> Result<Option<AccessTokenIdentity>, ApplicationError> {
|
||||
Postgres::load_access_token(self, token_id.get())
|
||||
.await
|
||||
.map(|token| token.map(access_token))
|
||||
.map_err(persistence)
|
||||
}
|
||||
|
||||
async fn load_user_principal(
|
||||
&self,
|
||||
user_id: UserId,
|
||||
) -> Result<UserPrincipal, ApplicationError> {
|
||||
Postgres::load_user_principal(self, user_id.get())
|
||||
.await
|
||||
.map_err(persistence)
|
||||
}
|
||||
|
||||
async fn load_granted_resources(
|
||||
&self,
|
||||
principals: &[(syncode_identity_model::GrantPrincipalKind, uuid::Uuid)],
|
||||
resource_kind: syncode_identity_model::ResourceKind,
|
||||
) -> Result<
|
||||
Vec<(
|
||||
uuid::Uuid,
|
||||
std::collections::BTreeSet<syncode_identity_model::Capability>,
|
||||
)>,
|
||||
ApplicationError,
|
||||
> {
|
||||
Postgres::load_granted_resources(self, principals, resource_kind)
|
||||
.await
|
||||
.map_err(|error| ApplicationError::Persistence(error.to_string()))
|
||||
}
|
||||
|
||||
async fn load_active_grants(&self, resource: Resource) -> Result<Vec<Grant>, ApplicationError> {
|
||||
Postgres::load_active_grants(self, resource)
|
||||
.await
|
||||
.map_err(persistence)
|
||||
}
|
||||
|
||||
async fn load_active_repository_visibility(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
) -> Result<Option<RepositoryVisibility>, ApplicationError> {
|
||||
self.bridge_repository_visibility(repository_id.get()).await
|
||||
}
|
||||
|
||||
async fn load_active_repository_owner(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
) -> Result<Option<RepositoryOwnerId>, ApplicationError> {
|
||||
self.bridge_repository_owner(repository_id.get()).await
|
||||
}
|
||||
|
||||
async fn platform_agent_is_active(
|
||||
&self,
|
||||
agent_id: PlatformAgentId,
|
||||
) -> Result<bool, ApplicationError> {
|
||||
Postgres::platform_agent_is_active(self, agent_id.get())
|
||||
.await
|
||||
.map_err(persistence)
|
||||
}
|
||||
|
||||
async fn local_agent(
|
||||
&self,
|
||||
agent_id: LocalAgentId,
|
||||
) -> Result<Option<LocalAgentIdentity>, ApplicationError> {
|
||||
self.bridge_local_agent(agent_id.get()).await
|
||||
}
|
||||
|
||||
async fn resolve_ssh_key(
|
||||
&self,
|
||||
fingerprint: &str,
|
||||
) -> Result<Option<SshKeyIdentity>, ApplicationError> {
|
||||
Postgres::resolve_ssh_key(self, fingerprint)
|
||||
.await
|
||||
.map_err(persistence)?
|
||||
.map(ssh_key)
|
||||
.transpose()
|
||||
}
|
||||
|
||||
async fn branch_protection_bypass_allowed(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
pattern: &str,
|
||||
principal: GrantPrincipal,
|
||||
) -> Result<bool, ApplicationError> {
|
||||
Postgres::branch_protection_bypass_allowed(
|
||||
self,
|
||||
repository_id.get(),
|
||||
pattern,
|
||||
principal.kind(),
|
||||
principal.get(),
|
||||
)
|
||||
.await
|
||||
.map_err(persistence)
|
||||
}
|
||||
|
||||
async fn active_branch_protection_rules(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
) -> Result<Vec<ActiveBranchProtectionRule>, ApplicationError> {
|
||||
self.bridge_active_branch_protection_rules(repository_id.get())
|
||||
.await
|
||||
}
|
||||
async fn user_signing_keys(
|
||||
&self,
|
||||
user_id: UserId,
|
||||
) -> Result<Vec<SigningKeyIdentity>, ApplicationError> {
|
||||
self.bridge_user_signing_keys(user_id.get()).await
|
||||
}
|
||||
async fn resolve_repository(
|
||||
&self,
|
||||
owner: &RepositoryOwner,
|
||||
name: &RepositoryName,
|
||||
) -> Result<Option<RepositoryId>, ApplicationError> {
|
||||
self.bridge_resolve_repository(owner, name).await
|
||||
}
|
||||
async fn repository_coordinates(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
) -> Result<Option<RepositoryCoordinates>, ApplicationError> {
|
||||
self.bridge_repository_coordinates(repository_id.get())
|
||||
.await
|
||||
}
|
||||
async fn provision_repository(
|
||||
&self,
|
||||
owner: RepositoryOwnerId,
|
||||
name: &RepositoryName,
|
||||
visibility: RepositoryVisibility,
|
||||
creator_id: UserId,
|
||||
) -> Result<RepositoryId, ApplicationError> {
|
||||
self.bridge_provision_repository(
|
||||
owner.kind(),
|
||||
owner.get(),
|
||||
name,
|
||||
visibility,
|
||||
creator_id.get(),
|
||||
)
|
||||
.await
|
||||
.map(RepositoryId::from)
|
||||
}
|
||||
|
||||
async fn register_repository(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
owner: RepositoryOwnerId,
|
||||
name: &RepositoryName,
|
||||
visibility: RepositoryVisibility,
|
||||
creator_id: UserId,
|
||||
) -> Result<(), ApplicationError> {
|
||||
self.bridge_register_repository(
|
||||
repository_id.get(),
|
||||
owner.kind(),
|
||||
owner.get(),
|
||||
name,
|
||||
visibility,
|
||||
creator_id.get(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn update_repository_visibility(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
visibility: RepositoryVisibility,
|
||||
) -> Result<bool, ApplicationError> {
|
||||
self.bridge_update_repository_visibility(repository_id.get(), visibility)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn update_repository_metadata(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
owner: RepositoryOwnerId,
|
||||
name: &RepositoryName,
|
||||
visibility: RepositoryVisibility,
|
||||
actor_id: UserId,
|
||||
) -> Result<bool, ApplicationError> {
|
||||
self.bridge_update_repository_metadata(
|
||||
repository_id.get(),
|
||||
owner.kind(),
|
||||
owner.get(),
|
||||
name,
|
||||
visibility,
|
||||
actor_id.get(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn archive_repository(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
) -> Result<bool, ApplicationError> {
|
||||
self.bridge_archive_repository(repository_id.get()).await
|
||||
}
|
||||
|
||||
async fn set_repository_archived(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
archived: bool,
|
||||
) -> Result<bool, ApplicationError> {
|
||||
self.bridge_set_repository_archived(repository_id.get(), archived)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -1,179 +1,195 @@
|
||||
use syncode_identity_model::{RepositoryName, RepositoryOwner};
|
||||
use tonic::{Request, Response, Status};
|
||||
|
||||
use super::IdentityServer;
|
||||
use super::conversion::{
|
||||
application_error, model_repository_owner, model_repository_visibility, parse_uuid,
|
||||
};
|
||||
use crate::wire::{
|
||||
ArchiveRepositoryProjectionRequest, ArchiveRepositoryProjectionResponse,
|
||||
ArchiveRepositoryRequest, ArchiveRepositoryResponse, DeleteRepositoryRequest,
|
||||
DeleteRepositoryResponse, ProvisionRepositoryRequest, ProvisionRepositoryResponse,
|
||||
RegisterRepositoryRequest, RegisterRepositoryResponse, ResolveRepositoryRequest,
|
||||
ResolveRepositoryResponse, UnarchiveRepositoryRequest, UnarchiveRepositoryResponse,
|
||||
UpdateRepositoryMetadataRequest, UpdateRepositoryMetadataResponse,
|
||||
UpdateRepositoryVisibilityRequest, UpdateRepositoryVisibilityResponse,
|
||||
};
|
||||
|
||||
pub(super) async fn resolve(
|
||||
server: &IdentityServer,
|
||||
request: Request<ResolveRepositoryRequest>,
|
||||
) -> Result<Response<ResolveRepositoryResponse>, Status> {
|
||||
let request = request.into_inner();
|
||||
let owner = request
|
||||
.owner
|
||||
.parse::<RepositoryOwner>()
|
||||
.map_err(|error| Status::invalid_argument(error.to_string()))?;
|
||||
let name = request
|
||||
.name
|
||||
.parse::<RepositoryName>()
|
||||
.map_err(|error| Status::invalid_argument(error.to_string()))?;
|
||||
let repository_id = server
|
||||
.application
|
||||
.resolve_repository(owner, name)
|
||||
.await
|
||||
.map_err(application_error)?;
|
||||
Ok(Response::new(ResolveRepositoryResponse {
|
||||
repository_id: repository_id.to_string(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub(super) async fn provision(
|
||||
server: &IdentityServer,
|
||||
request: Request<ProvisionRepositoryRequest>,
|
||||
) -> Result<Response<ProvisionRepositoryResponse>, Status> {
|
||||
let request = request.into_inner();
|
||||
let owner = model_repository_owner(request.owner_kind(), &request.owner_id)?;
|
||||
let visibility = model_repository_visibility(request.visibility())?;
|
||||
let name = request
|
||||
.name
|
||||
.parse::<RepositoryName>()
|
||||
.map_err(|error| Status::invalid_argument(error.to_string()))?;
|
||||
let repository_id = server
|
||||
.application
|
||||
.provision_repository(
|
||||
owner,
|
||||
name,
|
||||
visibility,
|
||||
parse_uuid(&request.creator_id)?.into(),
|
||||
)
|
||||
.await
|
||||
.map_err(application_error)?;
|
||||
Ok(Response::new(ProvisionRepositoryResponse {
|
||||
repository_id: repository_id.to_string(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub(super) async fn register(
|
||||
server: &IdentityServer,
|
||||
request: Request<RegisterRepositoryRequest>,
|
||||
) -> Result<Response<RegisterRepositoryResponse>, Status> {
|
||||
let request = request.into_inner();
|
||||
server
|
||||
.application
|
||||
.register_repository(
|
||||
parse_uuid(&request.repository_id)?.into(),
|
||||
model_repository_owner(request.owner_kind(), &request.owner_id)?,
|
||||
request
|
||||
.name
|
||||
.parse::<RepositoryName>()
|
||||
.map_err(|error| Status::invalid_argument(error.to_string()))?,
|
||||
model_repository_visibility(request.visibility())?,
|
||||
parse_uuid(&request.creator_id)?.into(),
|
||||
)
|
||||
.await
|
||||
.map_err(application_error)?;
|
||||
Ok(Response::new(RegisterRepositoryResponse {}))
|
||||
}
|
||||
|
||||
pub(super) async fn update_visibility(
|
||||
server: &IdentityServer,
|
||||
request: Request<UpdateRepositoryVisibilityRequest>,
|
||||
) -> Result<Response<UpdateRepositoryVisibilityResponse>, Status> {
|
||||
let request = request.into_inner();
|
||||
server
|
||||
.application
|
||||
.update_repository_visibility(
|
||||
parse_uuid(&request.repository_id)?.into(),
|
||||
model_repository_visibility(request.visibility())?,
|
||||
)
|
||||
.await
|
||||
.map_err(application_error)?;
|
||||
Ok(Response::new(UpdateRepositoryVisibilityResponse {}))
|
||||
}
|
||||
|
||||
pub(super) async fn update_metadata(
|
||||
server: &IdentityServer,
|
||||
request: Request<UpdateRepositoryMetadataRequest>,
|
||||
) -> Result<Response<UpdateRepositoryMetadataResponse>, Status> {
|
||||
let request = request.into_inner();
|
||||
server
|
||||
.application
|
||||
.update_repository_metadata(
|
||||
parse_uuid(&request.repository_id)?.into(),
|
||||
model_repository_owner(request.owner_kind(), &request.owner_id)?,
|
||||
request
|
||||
.name
|
||||
.parse::<RepositoryName>()
|
||||
.map_err(|error| Status::invalid_argument(error.to_string()))?,
|
||||
model_repository_visibility(request.visibility())?,
|
||||
parse_uuid(&request.actor_id)?.into(),
|
||||
)
|
||||
.await
|
||||
.map_err(application_error)?;
|
||||
Ok(Response::new(UpdateRepositoryMetadataResponse {}))
|
||||
}
|
||||
|
||||
pub(super) async fn archive_repository(
|
||||
server: &IdentityServer,
|
||||
request: Request<ArchiveRepositoryRequest>,
|
||||
) -> Result<Response<ArchiveRepositoryResponse>, Status> {
|
||||
server
|
||||
.application
|
||||
.archive_repository(parse_uuid(&request.into_inner().repository_id)?.into())
|
||||
.await
|
||||
.map_err(application_error)?;
|
||||
Ok(Response::new(ArchiveRepositoryResponse {}))
|
||||
}
|
||||
|
||||
pub(super) async fn archive(
|
||||
server: &IdentityServer,
|
||||
request: Request<ArchiveRepositoryProjectionRequest>,
|
||||
) -> Result<Response<ArchiveRepositoryProjectionResponse>, Status> {
|
||||
server
|
||||
.application
|
||||
.set_repository_archived(
|
||||
parse_uuid(&request.into_inner().repository_id)?.into(),
|
||||
true,
|
||||
)
|
||||
.await
|
||||
.map_err(application_error)?;
|
||||
Ok(Response::new(ArchiveRepositoryProjectionResponse {}))
|
||||
}
|
||||
|
||||
pub(super) async fn unarchive(
|
||||
server: &IdentityServer,
|
||||
request: Request<UnarchiveRepositoryRequest>,
|
||||
) -> Result<Response<UnarchiveRepositoryResponse>, Status> {
|
||||
server
|
||||
.application
|
||||
.set_repository_archived(
|
||||
parse_uuid(&request.into_inner().repository_id)?.into(),
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.map_err(application_error)?;
|
||||
Ok(Response::new(UnarchiveRepositoryResponse {}))
|
||||
}
|
||||
|
||||
pub(super) async fn delete(
|
||||
server: &IdentityServer,
|
||||
request: Request<DeleteRepositoryRequest>,
|
||||
) -> Result<Response<DeleteRepositoryResponse>, Status> {
|
||||
server
|
||||
.application
|
||||
.delete_repository(parse_uuid(&request.into_inner().repository_id)?.into())
|
||||
.await
|
||||
.map_err(application_error)?;
|
||||
Ok(Response::new(DeleteRepositoryResponse {}))
|
||||
}
|
||||
use syncode_identity_model::{RepositoryName, RepositoryOwner};
|
||||
use tonic::{Request, Response, Status};
|
||||
|
||||
use super::IdentityServer;
|
||||
use super::conversion::{
|
||||
application_error, model_repository_owner, model_repository_visibility, parse_uuid,
|
||||
};
|
||||
use crate::wire::{
|
||||
ArchiveRepositoryProjectionRequest, ArchiveRepositoryProjectionResponse,
|
||||
ArchiveRepositoryRequest, ArchiveRepositoryResponse, DeleteRepositoryRequest,
|
||||
DeleteRepositoryResponse, GetRepositoryCoordinatesRequest, GetRepositoryCoordinatesResponse,
|
||||
ProvisionRepositoryRequest, ProvisionRepositoryResponse, RegisterRepositoryRequest,
|
||||
RegisterRepositoryResponse, ResolveRepositoryRequest, ResolveRepositoryResponse,
|
||||
UnarchiveRepositoryRequest, UnarchiveRepositoryResponse, UpdateRepositoryMetadataRequest,
|
||||
UpdateRepositoryMetadataResponse, UpdateRepositoryVisibilityRequest,
|
||||
UpdateRepositoryVisibilityResponse,
|
||||
};
|
||||
|
||||
pub(super) async fn resolve(
|
||||
server: &IdentityServer,
|
||||
request: Request<ResolveRepositoryRequest>,
|
||||
) -> Result<Response<ResolveRepositoryResponse>, Status> {
|
||||
let request = request.into_inner();
|
||||
let owner = request
|
||||
.owner
|
||||
.parse::<RepositoryOwner>()
|
||||
.map_err(|error| Status::invalid_argument(error.to_string()))?;
|
||||
let name = request
|
||||
.name
|
||||
.parse::<RepositoryName>()
|
||||
.map_err(|error| Status::invalid_argument(error.to_string()))?;
|
||||
let repository_id = server
|
||||
.application
|
||||
.resolve_repository(owner, name)
|
||||
.await
|
||||
.map_err(application_error)?;
|
||||
Ok(Response::new(ResolveRepositoryResponse {
|
||||
repository_id: repository_id.to_string(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub(super) async fn coordinates(
|
||||
server: &IdentityServer,
|
||||
request: Request<GetRepositoryCoordinatesRequest>,
|
||||
) -> Result<Response<GetRepositoryCoordinatesResponse>, Status> {
|
||||
let coordinates = server
|
||||
.application
|
||||
.repository_coordinates(parse_uuid(&request.into_inner().repository_id)?.into())
|
||||
.await
|
||||
.map_err(application_error)?;
|
||||
Ok(Response::new(GetRepositoryCoordinatesResponse {
|
||||
owner: coordinates.owner.as_str().to_owned(),
|
||||
name: coordinates.name.as_str().to_owned(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub(super) async fn provision(
|
||||
server: &IdentityServer,
|
||||
request: Request<ProvisionRepositoryRequest>,
|
||||
) -> Result<Response<ProvisionRepositoryResponse>, Status> {
|
||||
let request = request.into_inner();
|
||||
let owner = model_repository_owner(request.owner_kind(), &request.owner_id)?;
|
||||
let visibility = model_repository_visibility(request.visibility())?;
|
||||
let name = request
|
||||
.name
|
||||
.parse::<RepositoryName>()
|
||||
.map_err(|error| Status::invalid_argument(error.to_string()))?;
|
||||
let repository_id = server
|
||||
.application
|
||||
.provision_repository(
|
||||
owner,
|
||||
name,
|
||||
visibility,
|
||||
parse_uuid(&request.creator_id)?.into(),
|
||||
)
|
||||
.await
|
||||
.map_err(application_error)?;
|
||||
Ok(Response::new(ProvisionRepositoryResponse {
|
||||
repository_id: repository_id.to_string(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub(super) async fn register(
|
||||
server: &IdentityServer,
|
||||
request: Request<RegisterRepositoryRequest>,
|
||||
) -> Result<Response<RegisterRepositoryResponse>, Status> {
|
||||
let request = request.into_inner();
|
||||
server
|
||||
.application
|
||||
.register_repository(
|
||||
parse_uuid(&request.repository_id)?.into(),
|
||||
model_repository_owner(request.owner_kind(), &request.owner_id)?,
|
||||
request
|
||||
.name
|
||||
.parse::<RepositoryName>()
|
||||
.map_err(|error| Status::invalid_argument(error.to_string()))?,
|
||||
model_repository_visibility(request.visibility())?,
|
||||
parse_uuid(&request.creator_id)?.into(),
|
||||
)
|
||||
.await
|
||||
.map_err(application_error)?;
|
||||
Ok(Response::new(RegisterRepositoryResponse {}))
|
||||
}
|
||||
|
||||
pub(super) async fn update_visibility(
|
||||
server: &IdentityServer,
|
||||
request: Request<UpdateRepositoryVisibilityRequest>,
|
||||
) -> Result<Response<UpdateRepositoryVisibilityResponse>, Status> {
|
||||
let request = request.into_inner();
|
||||
server
|
||||
.application
|
||||
.update_repository_visibility(
|
||||
parse_uuid(&request.repository_id)?.into(),
|
||||
model_repository_visibility(request.visibility())?,
|
||||
)
|
||||
.await
|
||||
.map_err(application_error)?;
|
||||
Ok(Response::new(UpdateRepositoryVisibilityResponse {}))
|
||||
}
|
||||
|
||||
pub(super) async fn update_metadata(
|
||||
server: &IdentityServer,
|
||||
request: Request<UpdateRepositoryMetadataRequest>,
|
||||
) -> Result<Response<UpdateRepositoryMetadataResponse>, Status> {
|
||||
let request = request.into_inner();
|
||||
server
|
||||
.application
|
||||
.update_repository_metadata(
|
||||
parse_uuid(&request.repository_id)?.into(),
|
||||
model_repository_owner(request.owner_kind(), &request.owner_id)?,
|
||||
request
|
||||
.name
|
||||
.parse::<RepositoryName>()
|
||||
.map_err(|error| Status::invalid_argument(error.to_string()))?,
|
||||
model_repository_visibility(request.visibility())?,
|
||||
parse_uuid(&request.actor_id)?.into(),
|
||||
)
|
||||
.await
|
||||
.map_err(application_error)?;
|
||||
Ok(Response::new(UpdateRepositoryMetadataResponse {}))
|
||||
}
|
||||
|
||||
pub(super) async fn archive_repository(
|
||||
server: &IdentityServer,
|
||||
request: Request<ArchiveRepositoryRequest>,
|
||||
) -> Result<Response<ArchiveRepositoryResponse>, Status> {
|
||||
server
|
||||
.application
|
||||
.archive_repository(parse_uuid(&request.into_inner().repository_id)?.into())
|
||||
.await
|
||||
.map_err(application_error)?;
|
||||
Ok(Response::new(ArchiveRepositoryResponse {}))
|
||||
}
|
||||
|
||||
pub(super) async fn archive(
|
||||
server: &IdentityServer,
|
||||
request: Request<ArchiveRepositoryProjectionRequest>,
|
||||
) -> Result<Response<ArchiveRepositoryProjectionResponse>, Status> {
|
||||
server
|
||||
.application
|
||||
.set_repository_archived(
|
||||
parse_uuid(&request.into_inner().repository_id)?.into(),
|
||||
true,
|
||||
)
|
||||
.await
|
||||
.map_err(application_error)?;
|
||||
Ok(Response::new(ArchiveRepositoryProjectionResponse {}))
|
||||
}
|
||||
|
||||
pub(super) async fn unarchive(
|
||||
server: &IdentityServer,
|
||||
request: Request<UnarchiveRepositoryRequest>,
|
||||
) -> Result<Response<UnarchiveRepositoryResponse>, Status> {
|
||||
server
|
||||
.application
|
||||
.set_repository_archived(
|
||||
parse_uuid(&request.into_inner().repository_id)?.into(),
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.map_err(application_error)?;
|
||||
Ok(Response::new(UnarchiveRepositoryResponse {}))
|
||||
}
|
||||
|
||||
pub(super) async fn delete(
|
||||
server: &IdentityServer,
|
||||
request: Request<DeleteRepositoryRequest>,
|
||||
) -> Result<Response<DeleteRepositoryResponse>, Status> {
|
||||
server
|
||||
.application
|
||||
.delete_repository(parse_uuid(&request.into_inner().repository_id)?.into())
|
||||
.await
|
||||
.map_err(application_error)?;
|
||||
Ok(Response::new(DeleteRepositoryResponse {}))
|
||||
}
|
||||
@@ -1,115 +1,126 @@
|
||||
use async_trait::async_trait;
|
||||
use syncode_identity_model::{
|
||||
RepositoryId, RepositoryName, RepositoryOwner, RepositoryOwnerId, RepositoryVisibility, UserId,
|
||||
};
|
||||
|
||||
use super::{IdentityBridgeApplication, IdentityBridgeRepositoryUseCases};
|
||||
use crate::ApplicationError;
|
||||
|
||||
#[async_trait]
|
||||
impl IdentityBridgeRepositoryUseCases for IdentityBridgeApplication {
|
||||
async fn resolve_repository(
|
||||
&self,
|
||||
owner: RepositoryOwner,
|
||||
name: RepositoryName,
|
||||
) -> Result<RepositoryId, ApplicationError> {
|
||||
self.repository
|
||||
.resolve_repository(&owner, &name)
|
||||
.await?
|
||||
.ok_or(ApplicationError::NotFound("repository"))
|
||||
}
|
||||
|
||||
async fn register_repository(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
owner: RepositoryOwnerId,
|
||||
name: RepositoryName,
|
||||
visibility: RepositoryVisibility,
|
||||
creator_id: UserId,
|
||||
) -> Result<(), ApplicationError> {
|
||||
self.repository.load_user_principal(creator_id).await?;
|
||||
self.repository
|
||||
.register_repository(repository_id, owner, &name, visibility, creator_id)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn provision_repository(
|
||||
&self,
|
||||
owner: RepositoryOwnerId,
|
||||
name: RepositoryName,
|
||||
visibility: RepositoryVisibility,
|
||||
creator_id: UserId,
|
||||
) -> Result<RepositoryId, ApplicationError> {
|
||||
self.repository.load_user_principal(creator_id).await?;
|
||||
self.repository
|
||||
.provision_repository(owner, &name, visibility, creator_id)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn update_repository_visibility(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
visibility: RepositoryVisibility,
|
||||
) -> Result<(), ApplicationError> {
|
||||
if self
|
||||
.repository
|
||||
.update_repository_visibility(repository_id, visibility)
|
||||
.await?
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ApplicationError::NotFound("repository"))
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_repository_metadata(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
owner: RepositoryOwnerId,
|
||||
name: RepositoryName,
|
||||
visibility: RepositoryVisibility,
|
||||
actor_id: UserId,
|
||||
) -> Result<(), ApplicationError> {
|
||||
self.repository.load_user_principal(actor_id).await?;
|
||||
if self
|
||||
.repository
|
||||
.update_repository_metadata(repository_id, owner, &name, visibility, actor_id)
|
||||
.await?
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ApplicationError::NotFound("repository"))
|
||||
}
|
||||
}
|
||||
|
||||
async fn archive_repository(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
) -> Result<(), ApplicationError> {
|
||||
if self.repository.archive_repository(repository_id).await? {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ApplicationError::NotFound("repository"))
|
||||
}
|
||||
}
|
||||
|
||||
async fn set_repository_archived(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
archived: bool,
|
||||
) -> Result<(), ApplicationError> {
|
||||
if self
|
||||
.repository
|
||||
.set_repository_archived(repository_id, archived)
|
||||
.await?
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ApplicationError::NotFound("repository"))
|
||||
}
|
||||
}
|
||||
|
||||
async fn delete_repository(&self, repository_id: RepositoryId) -> Result<(), ApplicationError> {
|
||||
self.archive_repository(repository_id).await
|
||||
}
|
||||
}
|
||||
use async_trait::async_trait;
|
||||
use syncode_identity_model::{
|
||||
RepositoryCoordinates, RepositoryId, RepositoryName, RepositoryOwner, RepositoryOwnerId,
|
||||
RepositoryVisibility, UserId,
|
||||
};
|
||||
|
||||
use super::{IdentityBridgeApplication, IdentityBridgeRepositoryUseCases};
|
||||
use crate::ApplicationError;
|
||||
|
||||
#[async_trait]
|
||||
impl IdentityBridgeRepositoryUseCases for IdentityBridgeApplication {
|
||||
async fn resolve_repository(
|
||||
&self,
|
||||
owner: RepositoryOwner,
|
||||
name: RepositoryName,
|
||||
) -> Result<RepositoryId, ApplicationError> {
|
||||
self.repository
|
||||
.resolve_repository(&owner, &name)
|
||||
.await?
|
||||
.ok_or(ApplicationError::NotFound("repository"))
|
||||
}
|
||||
|
||||
async fn repository_coordinates(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
) -> Result<RepositoryCoordinates, ApplicationError> {
|
||||
self.repository
|
||||
.repository_coordinates(repository_id)
|
||||
.await?
|
||||
.ok_or(ApplicationError::NotFound("repository"))
|
||||
}
|
||||
|
||||
async fn register_repository(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
owner: RepositoryOwnerId,
|
||||
name: RepositoryName,
|
||||
visibility: RepositoryVisibility,
|
||||
creator_id: UserId,
|
||||
) -> Result<(), ApplicationError> {
|
||||
self.repository.load_user_principal(creator_id).await?;
|
||||
self.repository
|
||||
.register_repository(repository_id, owner, &name, visibility, creator_id)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn provision_repository(
|
||||
&self,
|
||||
owner: RepositoryOwnerId,
|
||||
name: RepositoryName,
|
||||
visibility: RepositoryVisibility,
|
||||
creator_id: UserId,
|
||||
) -> Result<RepositoryId, ApplicationError> {
|
||||
self.repository.load_user_principal(creator_id).await?;
|
||||
self.repository
|
||||
.provision_repository(owner, &name, visibility, creator_id)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn update_repository_visibility(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
visibility: RepositoryVisibility,
|
||||
) -> Result<(), ApplicationError> {
|
||||
if self
|
||||
.repository
|
||||
.update_repository_visibility(repository_id, visibility)
|
||||
.await?
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ApplicationError::NotFound("repository"))
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_repository_metadata(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
owner: RepositoryOwnerId,
|
||||
name: RepositoryName,
|
||||
visibility: RepositoryVisibility,
|
||||
actor_id: UserId,
|
||||
) -> Result<(), ApplicationError> {
|
||||
self.repository.load_user_principal(actor_id).await?;
|
||||
if self
|
||||
.repository
|
||||
.update_repository_metadata(repository_id, owner, &name, visibility, actor_id)
|
||||
.await?
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ApplicationError::NotFound("repository"))
|
||||
}
|
||||
}
|
||||
|
||||
async fn archive_repository(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
) -> Result<(), ApplicationError> {
|
||||
if self.repository.archive_repository(repository_id).await? {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ApplicationError::NotFound("repository"))
|
||||
}
|
||||
}
|
||||
|
||||
async fn set_repository_archived(
|
||||
&self,
|
||||
repository_id: RepositoryId,
|
||||
archived: bool,
|
||||
) -> Result<(), ApplicationError> {
|
||||
if self
|
||||
.repository
|
||||
.set_repository_archived(repository_id, archived)
|
||||
.await?
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ApplicationError::NotFound("repository"))
|
||||
}
|
||||
}
|
||||
|
||||
async fn delete_repository(&self, repository_id: RepositoryId) -> Result<(), ApplicationError> {
|
||||
self.archive_repository(repository_id).await
|
||||
}
|
||||
}
|
||||
@@ -1,177 +1,202 @@
|
||||
use syncode_identity_application::ApplicationError;
|
||||
use syncode_identity_model::{
|
||||
RepositoryId, RepositoryName, RepositoryOwner, RepositoryOwnerId, RepositoryOwnerKind,
|
||||
RepositoryVisibility,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::repository_grants::{ensure_repository_admin_grants, owner_admin_principals};
|
||||
use crate::{Postgres, application_persistence as persistence};
|
||||
|
||||
impl Postgres {
|
||||
pub(super) async fn bridge_resolve_repository(
|
||||
&self,
|
||||
owner: &RepositoryOwner,
|
||||
name: &RepositoryName,
|
||||
) -> Result<Option<RepositoryId>, ApplicationError> {
|
||||
Postgres::resolve_repository(self, owner.as_str(), name.as_str())
|
||||
.await
|
||||
.map(|repository| repository.map(RepositoryId::from))
|
||||
.map_err(persistence)
|
||||
}
|
||||
|
||||
pub(super) async fn bridge_repository_visibility(
|
||||
&self,
|
||||
repository_id: Uuid,
|
||||
) -> Result<Option<RepositoryVisibility>, ApplicationError> {
|
||||
sqlx::query_scalar!(
|
||||
r#"SELECT visibility::text AS "visibility!" FROM repository
|
||||
WHERE id = $1 AND status = 'active' AND deleted_at IS NULL"#,
|
||||
repository_id
|
||||
)
|
||||
.fetch_optional(&self.pool)
|
||||
.await
|
||||
.map_err(persistence)?
|
||||
.map(|visibility| match visibility.as_str() {
|
||||
"public" => Ok(RepositoryVisibility::Public),
|
||||
"private" => Ok(RepositoryVisibility::Private),
|
||||
"limited" => Ok(RepositoryVisibility::Limited),
|
||||
other => Err(ApplicationError::Persistence(format!(
|
||||
"unknown repository visibility {other}"
|
||||
))),
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
pub(super) async fn bridge_repository_owner(
|
||||
&self,
|
||||
repository_id: Uuid,
|
||||
) -> Result<Option<RepositoryOwnerId>, ApplicationError> {
|
||||
sqlx::query!(
|
||||
r#"SELECT owner_kind::text AS "owner_kind!", owner_id FROM repository
|
||||
WHERE id = $1 AND status = 'active' AND deleted_at IS NULL"#,
|
||||
repository_id
|
||||
)
|
||||
.fetch_optional(&self.pool)
|
||||
.await
|
||||
.map_err(persistence)?
|
||||
.map(|owner| {
|
||||
let owner_id = match owner.owner_kind.as_str() {
|
||||
"user" => RepositoryOwnerId::User(owner.owner_id.into()),
|
||||
"organization" => RepositoryOwnerId::Organization(owner.owner_id.into()),
|
||||
other => {
|
||||
return Err(ApplicationError::Persistence(format!(
|
||||
"unknown repository owner kind {other}"
|
||||
)));
|
||||
}
|
||||
};
|
||||
Ok(owner_id)
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
pub(super) async fn bridge_provision_repository(
|
||||
&self,
|
||||
owner_kind: RepositoryOwnerKind,
|
||||
owner_id: Uuid,
|
||||
name: &RepositoryName,
|
||||
visibility: RepositoryVisibility,
|
||||
creator_id: Uuid,
|
||||
) -> Result<Uuid, ApplicationError> {
|
||||
let mut transaction = self.pool.begin().await.map_err(persistence)?;
|
||||
let repository_id = Uuid::new_v4();
|
||||
let repository_id = sqlx::query_scalar!(
|
||||
"INSERT INTO repository (id, owner_kind, owner_id, name, visibility)
|
||||
VALUES ($1, $2::text::owner_kind, $3, $4, $5::text::repository_visibility)
|
||||
ON CONFLICT (owner_kind, owner_id, name) WHERE deleted_at IS NULL DO UPDATE
|
||||
SET visibility = EXCLUDED.visibility
|
||||
RETURNING id",
|
||||
repository_id,
|
||||
owner_kind.as_str(),
|
||||
owner_id,
|
||||
name.as_str(),
|
||||
visibility.as_str()
|
||||
)
|
||||
.fetch_one(&mut *transaction)
|
||||
.await
|
||||
.map_err(persistence)?;
|
||||
let principals = owner_admin_principals(&mut transaction, owner_kind, owner_id).await?;
|
||||
ensure_repository_admin_grants(&mut transaction, repository_id, &principals, creator_id)
|
||||
.await?;
|
||||
transaction.commit().await.map_err(persistence)?;
|
||||
Ok(repository_id)
|
||||
}
|
||||
|
||||
pub(super) async fn bridge_update_repository_visibility(
|
||||
&self,
|
||||
repository_id: Uuid,
|
||||
visibility: RepositoryVisibility,
|
||||
) -> Result<bool, ApplicationError> {
|
||||
sqlx::query_scalar!(
|
||||
"UPDATE repository SET visibility = $2::text::repository_visibility
|
||||
WHERE id = $1 AND status = 'active' AND deleted_at IS NULL
|
||||
RETURNING id",
|
||||
repository_id,
|
||||
visibility.as_str()
|
||||
)
|
||||
.fetch_optional(&self.pool)
|
||||
.await
|
||||
.map(|updated| updated.is_some())
|
||||
.map_err(persistence)
|
||||
}
|
||||
|
||||
pub(super) async fn bridge_archive_repository(
|
||||
&self,
|
||||
repository_id: Uuid,
|
||||
) -> Result<bool, ApplicationError> {
|
||||
let mut transaction = self.pool.begin().await.map_err(persistence)?;
|
||||
let exists = sqlx::query_scalar!(
|
||||
"SELECT id FROM repository WHERE id = $1 FOR UPDATE",
|
||||
repository_id
|
||||
)
|
||||
.fetch_optional(&mut *transaction)
|
||||
.await
|
||||
.map_err(persistence)?
|
||||
.is_some();
|
||||
if !exists {
|
||||
return Ok(false);
|
||||
}
|
||||
sqlx::query!(
|
||||
"UPDATE repository SET status = 'deleted', deleted_at = COALESCE(deleted_at, now())
|
||||
WHERE id = $1",
|
||||
repository_id
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await
|
||||
.map_err(persistence)?;
|
||||
sqlx::query!(
|
||||
r#"UPDATE "grant" SET revoked_at = now()
|
||||
WHERE resource_kind = 'repository' AND resource_id = $1 AND revoked_at IS NULL"#,
|
||||
repository_id
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await
|
||||
.map_err(persistence)?;
|
||||
transaction.commit().await.map_err(persistence)?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub(super) async fn bridge_set_repository_archived(
|
||||
&self,
|
||||
repository_id: Uuid,
|
||||
archived: bool,
|
||||
) -> Result<bool, ApplicationError> {
|
||||
let status = if archived { "archived" } else { "active" };
|
||||
sqlx::query_scalar!(
|
||||
"UPDATE repository SET status = $2::text::repository_status
|
||||
WHERE id = $1 AND status IN ('active', 'archived') AND deleted_at IS NULL
|
||||
RETURNING id",
|
||||
repository_id,
|
||||
status
|
||||
)
|
||||
.fetch_optional(&self.pool)
|
||||
.await
|
||||
.map(|updated| updated.is_some())
|
||||
.map_err(persistence)
|
||||
}
|
||||
}
|
||||
use syncode_identity_application::ApplicationError;
|
||||
use syncode_identity_model::{
|
||||
RepositoryCoordinates, RepositoryId, RepositoryName, RepositoryOwner, RepositoryOwnerId,
|
||||
RepositoryOwnerKind, RepositoryVisibility,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::repository_grants::{ensure_repository_admin_grants, owner_admin_principals};
|
||||
use crate::{Postgres, application_persistence as persistence};
|
||||
|
||||
impl Postgres {
|
||||
pub(super) async fn bridge_resolve_repository(
|
||||
&self,
|
||||
owner: &RepositoryOwner,
|
||||
name: &RepositoryName,
|
||||
) -> Result<Option<RepositoryId>, ApplicationError> {
|
||||
Postgres::resolve_repository(self, owner.as_str(), name.as_str())
|
||||
.await
|
||||
.map(|repository| repository.map(RepositoryId::from))
|
||||
.map_err(persistence)
|
||||
}
|
||||
|
||||
pub(super) async fn bridge_repository_coordinates(
|
||||
&self,
|
||||
repository_id: Uuid,
|
||||
) -> Result<Option<RepositoryCoordinates>, ApplicationError> {
|
||||
let Some(repository) = self
|
||||
.find_repository_by_id(repository_id)
|
||||
.await
|
||||
.map_err(persistence)?
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
Ok(Some(RepositoryCoordinates {
|
||||
owner: repository.owner_name.parse().map_err(
|
||||
|error: syncode_identity_model::InvalidForgeValue| {
|
||||
ApplicationError::Persistence(error.to_string())
|
||||
},
|
||||
)?,
|
||||
name: repository.name.parse().map_err(
|
||||
|error: syncode_identity_model::InvalidForgeValue| {
|
||||
ApplicationError::Persistence(error.to_string())
|
||||
},
|
||||
)?,
|
||||
}))
|
||||
}
|
||||
|
||||
pub(super) async fn bridge_repository_visibility(
|
||||
&self,
|
||||
repository_id: Uuid,
|
||||
) -> Result<Option<RepositoryVisibility>, ApplicationError> {
|
||||
sqlx::query_scalar!(
|
||||
r#"SELECT visibility::text AS "visibility!" FROM repository
|
||||
WHERE id = $1 AND status = 'active' AND deleted_at IS NULL"#,
|
||||
repository_id
|
||||
)
|
||||
.fetch_optional(&self.pool)
|
||||
.await
|
||||
.map_err(persistence)?
|
||||
.map(|visibility| match visibility.as_str() {
|
||||
"public" => Ok(RepositoryVisibility::Public),
|
||||
"private" => Ok(RepositoryVisibility::Private),
|
||||
"limited" => Ok(RepositoryVisibility::Limited),
|
||||
other => Err(ApplicationError::Persistence(format!(
|
||||
"unknown repository visibility {other}"
|
||||
))),
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
pub(super) async fn bridge_repository_owner(
|
||||
&self,
|
||||
repository_id: Uuid,
|
||||
) -> Result<Option<RepositoryOwnerId>, ApplicationError> {
|
||||
sqlx::query!(
|
||||
r#"SELECT owner_kind::text AS "owner_kind!", owner_id FROM repository
|
||||
WHERE id = $1 AND status = 'active' AND deleted_at IS NULL"#,
|
||||
repository_id
|
||||
)
|
||||
.fetch_optional(&self.pool)
|
||||
.await
|
||||
.map_err(persistence)?
|
||||
.map(|owner| {
|
||||
let owner_id = match owner.owner_kind.as_str() {
|
||||
"user" => RepositoryOwnerId::User(owner.owner_id.into()),
|
||||
"organization" => RepositoryOwnerId::Organization(owner.owner_id.into()),
|
||||
other => {
|
||||
return Err(ApplicationError::Persistence(format!(
|
||||
"unknown repository owner kind {other}"
|
||||
)));
|
||||
}
|
||||
};
|
||||
Ok(owner_id)
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
pub(super) async fn bridge_provision_repository(
|
||||
&self,
|
||||
owner_kind: RepositoryOwnerKind,
|
||||
owner_id: Uuid,
|
||||
name: &RepositoryName,
|
||||
visibility: RepositoryVisibility,
|
||||
creator_id: Uuid,
|
||||
) -> Result<Uuid, ApplicationError> {
|
||||
let mut transaction = self.pool.begin().await.map_err(persistence)?;
|
||||
let repository_id = Uuid::new_v4();
|
||||
let repository_id = sqlx::query_scalar!(
|
||||
"INSERT INTO repository (id, owner_kind, owner_id, name, visibility)
|
||||
VALUES ($1, $2::text::owner_kind, $3, $4, $5::text::repository_visibility)
|
||||
ON CONFLICT (owner_kind, owner_id, name) WHERE deleted_at IS NULL DO UPDATE
|
||||
SET visibility = EXCLUDED.visibility
|
||||
RETURNING id",
|
||||
repository_id,
|
||||
owner_kind.as_str(),
|
||||
owner_id,
|
||||
name.as_str(),
|
||||
visibility.as_str()
|
||||
)
|
||||
.fetch_one(&mut *transaction)
|
||||
.await
|
||||
.map_err(persistence)?;
|
||||
let principals = owner_admin_principals(&mut transaction, owner_kind, owner_id).await?;
|
||||
ensure_repository_admin_grants(&mut transaction, repository_id, &principals, creator_id)
|
||||
.await?;
|
||||
transaction.commit().await.map_err(persistence)?;
|
||||
Ok(repository_id)
|
||||
}
|
||||
|
||||
pub(super) async fn bridge_update_repository_visibility(
|
||||
&self,
|
||||
repository_id: Uuid,
|
||||
visibility: RepositoryVisibility,
|
||||
) -> Result<bool, ApplicationError> {
|
||||
sqlx::query_scalar!(
|
||||
"UPDATE repository SET visibility = $2::text::repository_visibility
|
||||
WHERE id = $1 AND status = 'active' AND deleted_at IS NULL
|
||||
RETURNING id",
|
||||
repository_id,
|
||||
visibility.as_str()
|
||||
)
|
||||
.fetch_optional(&self.pool)
|
||||
.await
|
||||
.map(|updated| updated.is_some())
|
||||
.map_err(persistence)
|
||||
}
|
||||
|
||||
pub(super) async fn bridge_archive_repository(
|
||||
&self,
|
||||
repository_id: Uuid,
|
||||
) -> Result<bool, ApplicationError> {
|
||||
let mut transaction = self.pool.begin().await.map_err(persistence)?;
|
||||
let exists = sqlx::query_scalar!(
|
||||
"SELECT id FROM repository WHERE id = $1 FOR UPDATE",
|
||||
repository_id
|
||||
)
|
||||
.fetch_optional(&mut *transaction)
|
||||
.await
|
||||
.map_err(persistence)?
|
||||
.is_some();
|
||||
if !exists {
|
||||
return Ok(false);
|
||||
}
|
||||
sqlx::query!(
|
||||
"UPDATE repository SET status = 'deleted', deleted_at = COALESCE(deleted_at, now())
|
||||
WHERE id = $1",
|
||||
repository_id
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await
|
||||
.map_err(persistence)?;
|
||||
sqlx::query!(
|
||||
r#"UPDATE "grant" SET revoked_at = now()
|
||||
WHERE resource_kind = 'repository' AND resource_id = $1 AND revoked_at IS NULL"#,
|
||||
repository_id
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await
|
||||
.map_err(persistence)?;
|
||||
transaction.commit().await.map_err(persistence)?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub(super) async fn bridge_set_repository_archived(
|
||||
&self,
|
||||
repository_id: Uuid,
|
||||
archived: bool,
|
||||
) -> Result<bool, ApplicationError> {
|
||||
let status = if archived { "archived" } else { "active" };
|
||||
sqlx::query_scalar!(
|
||||
"UPDATE repository SET status = $2::text::repository_status
|
||||
WHERE id = $1 AND status IN ('active', 'archived') AND deleted_at IS NULL
|
||||
RETURNING id",
|
||||
repository_id,
|
||||
status
|
||||
)
|
||||
.fetch_optional(&self.pool)
|
||||
.await
|
||||
.map(|updated| updated.is_some())
|
||||
.map_err(persistence)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user