use std::collections::{BTreeMap, BTreeSet};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex, MutexGuard, OnceLock};
use lys_pass::membership::{
CONTRACT_UNSUPPORTED, CONTRACT_VERSION, GrantLog, LOG_MISMATCH, MembershipDecision,
MembershipRequest, Subject, Verdict,
};
use lys_pass::membership_admission::{AdmissionDecision, AdmissionRequest};
use lys_pass::membership_pages::{
CURSOR_FOREIGN, CURSOR_RESET, MembershipPage, PageBounds, PageOutcome, PageRow,
RecipientPageRequest, ResourcePageRequest,
};
use lys_pass::rights::{Mode, Resource};
use crate::ServerError;
use crate::auth_lys::HotAction;
use crate::auth_lys::session::LysSession;
use crate::config::types::MembershipConfig;
pub use crate::agents::binding::check as check_agent_binding;
pub const DECISION_ROUTE: &str = "grants/membership";
pub const ADMISSION_ROUTE: &str = "grants/membership/admission";
pub const RESOURCES_ROUTE: &str = "grants/membership/resources";
pub const RECIPIENTS_ROUTE: &str = "grants/membership/recipients";
mod refusal;
pub use refusal::{MembershipAction, MembershipRefusal};
use refusal::{bump, bump_by, invalid, malformed};
mod mapping;
use mapping::{ChannelMap, channel_map, validated_bounds};
pub use mapping::{MappedChannel, MembershipSubject};
mod provider;
pub use provider::{HttpMembershipProvider, MembershipProvider, ProviderError};
mod authority;
use authority::Counters;
pub use authority::{
AppliedChange, AuthorityState, Decided, MembershipAuthority, MembershipChange,
MembershipCounts, MembershipListener, ResumeReport,
};
mod index;
pub use index::{ChannelPage, PageCursor, RecipientPage};
use index::{DecisionIndex, DecisionKey, Indexed, PageParts, PageQuestion, resource_key};
mod gate;
pub use gate::MembershipGate;
mod pages;
mod listener;
use listener::{admitted, continuation, encoded_bytes, judged, page_refusal};
mod connection;
pub use connection::{ConnectionMembership, ReleaseCheck};
#[path = "auth_lys/grant_stream_owner.rs"]
pub mod grant_stream;
pub(crate) fn validate_configuration(config: &MembershipConfig) -> Result<(), ServerError> {
validated_bounds(config)?;
let map = channel_map(config)?;
if config
.connect_workspace
.as_ref()
.is_some_and(|workspace| !map.workspaces.contains(workspace))
{
return Err(invalid(
"auth.lys.membership.connect_workspace is not a mapped workspace",
));
}
if config.credential_path.as_os_str().is_empty() {
return Err(invalid(
"auth.lys.membership.credential_path must name a file",
));
}
Ok(())
}
pub fn configured_gate(
config: &MembershipConfig,
issuer: &str,
) -> Result<Arc<MembershipGate>, ServerError> {
let handle = tokio::runtime::Handle::try_current()
.map_err(|_| invalid("auth.lys.membership needs the server's tokio runtime at start"))?;
configured_gate_on(config, issuer, handle)
}
pub fn configured_gate_on(
config: &MembershipConfig,
issuer: &str,
handle: tokio::runtime::Handle,
) -> Result<Arc<MembershipGate>, ServerError> {
let response_bytes = usize::try_from(config.page_bytes)
.map_err(|_| invalid("membership_page_bytes_overflow"))?;
let credential = grant_stream::read_credential(&config.credential_path, response_bytes)?;
let gate_state = grant_stream::StreamState::for_log(GrantLog {
identity: config.log_identity.clone(),
epoch: config.log_epoch,
});
let provider = HttpMembershipProvider::new(
issuer,
credential,
handle,
response_bytes,
gate_state.cancellation(),
)
.map_err(|error| {
invalid(format!(
"auth.lys.membership provider refused: {}",
error.cause
))
})?;
let gate = Arc::new(MembershipGate::new(config, Arc::new(provider))?);
gate.install_authority(gate_state.clone())
.map_err(|_| invalid("membership_authority_already_installed"))?;
gate.stream
.set(gate_state)
.map_err(|_| invalid("membership_stream_already_installed"))?;
Ok(gate)
}
#[cfg(any(test, feature = "test-support"))]
pub mod fixture;
#[cfg(test)]
#[path = "auth_membership_tests.rs"]
mod tests;