core: add community token issuer seam
This commit is contained in:
Generated
+1
@@ -404,6 +404,7 @@ dependencies = [
|
|||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"time",
|
"time",
|
||||||
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ Progress:
|
|||||||
- done:
|
- done:
|
||||||
- Phase 0 / task `0.1`: создан `crank-core::ext` module skeleton
|
- Phase 0 / task `0.1`: создан `crank-core::ext` module skeleton
|
||||||
- Phase 0 / task `0.2`: `MachineCredentialVerifier` и связанные типы вынесены из `apps/mcp-server` в public seam `crank_core::ext::auth`
|
- Phase 0 / task `0.2`: `MachineCredentialVerifier` и связанные типы вынесены из `apps/mcp-server` в public seam `crank_core::ext::auth`
|
||||||
|
- Phase 0 / task `0.3`: введены `MachineTokenIssuer`, `NoMachineTokenIssuer`, `TokenIssuerActor` и `TokenIssuerError` в public seam `crank_core::ext::auth`
|
||||||
- Phase 0 / task `0.5`: введены `PolicyEngine`, `SessionActor`, `PolicyAction`, `PolicyScope`, `PolicyDecision` и default `OwnerOnlyPolicyEngine` в public seam `crank_core::ext::access`
|
- Phase 0 / task `0.5`: введены `PolicyEngine`, `SessionActor`, `PolicyAction`, `PolicyScope`, `PolicyDecision` и default `OwnerOnlyPolicyEngine` в public seam `crank_core::ext::access`
|
||||||
- Phase 0 / task `0.6`: введены `AuditSink`, `NoopAuditSink`, `AuditEventId` и базовые audit-типы в public seam `crank_core::ext::audit`
|
- Phase 0 / task `0.6`: введены `AuditSink`, `NoopAuditSink`, `AuditEventId` и базовые audit-типы в public seam `crank_core::ext::audit`
|
||||||
- Phase 0 / task `0.7`: введены `CapabilityProfile` и `CommunityCapabilityProfile`, а `admin-api` capability payload теперь собирается через public seam вместо локального literal
|
- Phase 0 / task `0.7`: введены `CapabilityProfile` и `CommunityCapabilityProfile`, а `admin-api` capability payload теперь собирается через public seam вместо локального literal
|
||||||
|
|||||||
@@ -14,3 +14,4 @@ time.workspace = true
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serde_yaml.workspace = true
|
serde_yaml.workspace = true
|
||||||
|
tokio.workspace = true
|
||||||
|
|||||||
@@ -2,7 +2,11 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
|
||||||
use crate::{MachineAccessMode, OperationSecurityLevel, PlatformApiKeyScope};
|
use crate::{
|
||||||
|
IssueAgentTokenRequest, IssueOneTimeAgentTokenRequest, IssuedAgentTokenResponse,
|
||||||
|
MachineAccessMode, MembershipRole, OperationSecurityLevel, PlatformApiKeyScope, UserId,
|
||||||
|
WorkspaceId,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct VerifiedMachineCredential {
|
pub struct VerifiedMachineCredential {
|
||||||
@@ -26,3 +30,117 @@ pub trait MachineCredentialVerifier: Send + Sync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub type SharedMachineCredentialVerifier = Arc<dyn MachineCredentialVerifier>;
|
pub type SharedMachineCredentialVerifier = Arc<dyn MachineCredentialVerifier>;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct TokenIssuerActor {
|
||||||
|
pub user_id: UserId,
|
||||||
|
pub workspace_id: WorkspaceId,
|
||||||
|
pub role: MembershipRole,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
|
||||||
|
pub enum TokenIssuerError {
|
||||||
|
#[error("machine tokens are not available in this edition")]
|
||||||
|
NotSupportedInEdition,
|
||||||
|
#[error("invalid grant: {0}")]
|
||||||
|
InvalidGrant(String),
|
||||||
|
#[error("agent key is unknown")]
|
||||||
|
AgentKeyUnknown,
|
||||||
|
#[error("operation is not strict")]
|
||||||
|
OperationNotStrict,
|
||||||
|
#[error("operation is not published for agent")]
|
||||||
|
OperationNotPublishedForAgent,
|
||||||
|
#[error("registry failure: {0}")]
|
||||||
|
RegistryFailure(String),
|
||||||
|
#[error("replay guard failure: {0}")]
|
||||||
|
ReplayGuardFailure(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait MachineTokenIssuer: Send + Sync {
|
||||||
|
async fn issue_short_lived(
|
||||||
|
&self,
|
||||||
|
request: IssueAgentTokenRequest,
|
||||||
|
actor: &TokenIssuerActor,
|
||||||
|
) -> Result<IssuedAgentTokenResponse, TokenIssuerError>;
|
||||||
|
|
||||||
|
async fn issue_one_time(
|
||||||
|
&self,
|
||||||
|
request: IssueOneTimeAgentTokenRequest,
|
||||||
|
actor: &TokenIssuerActor,
|
||||||
|
) -> Result<IssuedAgentTokenResponse, TokenIssuerError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type SharedMachineTokenIssuer = Arc<dyn MachineTokenIssuer>;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
pub struct NoMachineTokenIssuer;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl MachineTokenIssuer for NoMachineTokenIssuer {
|
||||||
|
async fn issue_short_lived(
|
||||||
|
&self,
|
||||||
|
_request: IssueAgentTokenRequest,
|
||||||
|
_actor: &TokenIssuerActor,
|
||||||
|
) -> Result<IssuedAgentTokenResponse, TokenIssuerError> {
|
||||||
|
Err(TokenIssuerError::NotSupportedInEdition)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn issue_one_time(
|
||||||
|
&self,
|
||||||
|
_request: IssueOneTimeAgentTokenRequest,
|
||||||
|
_actor: &TokenIssuerActor,
|
||||||
|
) -> Result<IssuedAgentTokenResponse, TokenIssuerError> {
|
||||||
|
Err(TokenIssuerError::NotSupportedInEdition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{MachineTokenIssuer, NoMachineTokenIssuer, TokenIssuerActor, TokenIssuerError};
|
||||||
|
use crate::{
|
||||||
|
AgentTokenGrantType, IssueAgentTokenRequest, IssueOneTimeAgentTokenRequest, MembershipRole,
|
||||||
|
UserId, WorkspaceId,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn actor() -> TokenIssuerActor {
|
||||||
|
TokenIssuerActor {
|
||||||
|
user_id: UserId::new("user_01"),
|
||||||
|
workspace_id: WorkspaceId::new("ws_01"),
|
||||||
|
role: MembershipRole::Owner,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn no_machine_token_issuer_rejects_short_lived_issue() {
|
||||||
|
let result = NoMachineTokenIssuer
|
||||||
|
.issue_short_lived(
|
||||||
|
IssueAgentTokenRequest {
|
||||||
|
grant_type: AgentTokenGrantType::AgentKey,
|
||||||
|
agent_key: Some("crk_agent_secret".to_owned()),
|
||||||
|
refresh_token: None,
|
||||||
|
scope: vec![],
|
||||||
|
},
|
||||||
|
&actor(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(result, Err(TokenIssuerError::NotSupportedInEdition));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn no_machine_token_issuer_rejects_one_time_issue() {
|
||||||
|
let result = NoMachineTokenIssuer
|
||||||
|
.issue_one_time(
|
||||||
|
IssueOneTimeAgentTokenRequest {
|
||||||
|
agent_key: "crk_agent_secret".to_owned(),
|
||||||
|
operation_id: "op_01".into(),
|
||||||
|
scope: vec![],
|
||||||
|
},
|
||||||
|
&actor(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(result, Err(TokenIssuerError::NotSupportedInEdition));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -40,8 +40,9 @@ pub use ext::audit::{
|
|||||||
AuditTargetKind, NoopAuditSink, SharedAuditSink,
|
AuditTargetKind, NoopAuditSink, SharedAuditSink,
|
||||||
};
|
};
|
||||||
pub use ext::auth::{
|
pub use ext::auth::{
|
||||||
MachineCredentialVerifier, MachineCredentialVerifierError, SharedMachineCredentialVerifier,
|
MachineCredentialVerifier, MachineCredentialVerifierError, MachineTokenIssuer,
|
||||||
VerifiedMachineCredential,
|
NoMachineTokenIssuer, SharedMachineCredentialVerifier, SharedMachineTokenIssuer,
|
||||||
|
TokenIssuerActor, TokenIssuerError, VerifiedMachineCredential,
|
||||||
};
|
};
|
||||||
pub use ext::capability::{CapabilityProfile, CommunityCapabilityProfile};
|
pub use ext::capability::{CapabilityProfile, CommunityCapabilityProfile};
|
||||||
pub use ids::{
|
pub use ids::{
|
||||||
|
|||||||
Reference in New Issue
Block a user