Temporal Permissions
Your platform hires a contractor for a three-month engagement. They need read access to the codebase, but that access must disappear automatically when the contract ends. An on-call engineer needs emergency admin access for 24 hours after an incident, not forever. A database migration requires production write access during a four-hour maintenance window.
Temporal permissions model all of these: access that activates at a specific time and expires at another.
Creating temporal permissions
A TemporalPermission wraps an AtomicPermission with optional start and end
timestamps. Timestamps are milliseconds since the Unix epoch.
#![allow(unused)]
fn main() {
use acls_rs::prelude::*;
use acls_rs::permission::current_timestamp_millis;
let now = current_timestamp_millis();
let three_months = 90 * 24 * 60 * 60 * 1000; // in milliseconds
let contractor_access = TemporalPermission::new(
AtomicPermission::new("code", "read"),
Some(now), // active immediately
Some(now + three_months), // expires in 3 months
);
// Valid right now
assert!(contractor_access.is_currently_valid());
// Valid at a specific time
assert!(contractor_access.is_valid_at(now + 1000));
// Expired after the window
assert!(!contractor_access.is_valid_at(now + three_months + 1));
}
Validity windows
The valid_from and valid_until fields are both optional, giving you four
patterns:
#![allow(unused)]
fn main() {
use acls_rs::prelude::*;
use acls_rs::permission::current_timestamp_millis;
let perm = AtomicPermission::new("system", "access");
let now = current_timestamp_millis();
// Active now, expires later
let expiring = TemporalPermission::new(perm.clone(), None, Some(now + 3600_000));
// Activates in the future, never expires
let deferred = TemporalPermission::new(perm.clone(), Some(now + 86400_000), None);
// Active during a specific window
let window = TemporalPermission::new(
perm.clone(),
Some(now + 3600_000),
Some(now + 7200_000),
);
// Always valid (equivalent to a regular permission)
let permanent = TemporalPermission::new(perm.clone(), None, None);
}
Attaching temporal permissions to subjects
Subjects carry a TemporalPermissionSet alongside their regular permissions.
Add temporal permissions with .add() and query what is effective at a given
time with .effective_at():
#![allow(unused)]
fn main() {
use acls_rs::prelude::*;
use acls_rs::permission::current_timestamp_millis;
let now = current_timestamp_millis();
let one_day = 24 * 60 * 60 * 1000;
let mut engineer = Subject::new("alice");
// Regular permission: always has code:read
engineer.grant(AtomicPermission::new("code", "read"));
// Temporal permission: admin access for 24 hours
engineer.temporal_permissions.add(TemporalPermission::new(
AtomicPermission::new("system", "admin"),
Some(now),
Some(now + one_day),
));
// Right now: both permissions are effective
let effective_now = engineer.effective_permissions_at(now + 1000);
assert!(effective_now.contains(&AtomicPermission::new("code", "read")));
assert!(effective_now.contains(&AtomicPermission::new("system", "admin")));
// After 24 hours: only the regular permission remains
let effective_later = engineer.effective_permissions_at(now + one_day + 1000);
assert!(effective_later.contains(&AtomicPermission::new("code", "read")));
assert!(!effective_later.contains(&AtomicPermission::new("system", "admin")));
}
Temporal permission sets
When you need to manage multiple temporal permissions as a collection,
TemporalPermissionSet provides batch operations:
#![allow(unused)]
fn main() {
use acls_rs::prelude::*;
use acls_rs::permission::{TemporalPermissionSet, current_timestamp_millis};
let now = current_timestamp_millis();
let mut temp_perms = TemporalPermissionSet::new();
temp_perms.add(TemporalPermission::new(
AtomicPermission::new("database", "write"),
Some(now),
Some(now + 3600_000),
));
temp_perms.add(TemporalPermission::new(
AtomicPermission::new("deploy", "staging"),
Some(now),
Some(now + 86400_000),
));
// What permissions are effective right now?
let active = temp_perms.currently_effective();
// What permissions will be effective at a specific time?
let at_time = temp_perms.effective_at(now + 7200_000);
}
Use cases
Contractor access
A contractor needs three months of read-only access:
#![allow(unused)]
fn main() {
use acls_rs::prelude::*;
use acls_rs::permission::current_timestamp_millis;
let now = current_timestamp_millis();
let three_months = 90 * 24 * 60 * 60 * 1000;
let mut contractor = Subject::new("contractor-jane");
contractor.temporal_permissions.add(TemporalPermission::new(
AtomicPermission::new("project", "read"),
Some(now),
Some(now + three_months),
));
}
Emergency access
An on-call engineer gets 24-hour admin access after an incident:
#![allow(unused)]
fn main() {
use acls_rs::prelude::*;
use acls_rs::permission::current_timestamp_millis;
let now = current_timestamp_millis();
let one_day = 24 * 60 * 60 * 1000;
let mut oncall = Subject::new("alice");
oncall.temporal_permissions.add(TemporalPermission::new(
AtomicPermission::new("system", "admin"),
Some(now),
Some(now + one_day),
));
}
Maintenance window
A DBA gets production write access during a scheduled window:
#![allow(unused)]
fn main() {
use acls_rs::prelude::*;
let window_start = 1737338400_000; // 2025-01-20 02:00 UTC
let window_end = 1737352800_000; // 2025-01-20 06:00 UTC
let mut dba = Subject::new("dba-bob");
dba.temporal_permissions.add(TemporalPermission::new(
AtomicPermission::new("production", "write"),
Some(window_start),
Some(window_end),
));
}
Best practices
- Always set an expiration. Open-ended temporary access defeats the purpose.
- Use UTC timestamps internally. Convert to local time only for display.
- Build in a small grace period to account for clock skew between systems.
- Log temporal grants and expirations for audit purposes.
- Periodically check for expired permissions and clean up stale entries.
Next steps
So far, acls-rs has been controlling what actions users can perform within your application. But as your organization adopts FreeIPA for centralized identity management, a different question arises: which users can access which services on which hosts? This is host-based access control, and it is the domain of hbac-rs.