Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 79 additions & 171 deletions audit-trail-move/sources/audit_trail.move
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,22 @@
module audit_trail::main;

use audit_trail::{
capability::{Self, Capability},
capability::Capability,
locking::{Self, LockingConfig, LockingWindow, set_delete_record_lock},
permission::{Self, Permission},
record::{Self, Record}
};
use iota::{
clock::{Self, Clock},
event,
linked_table::{Self, LinkedTable},
vec_map::{Self, VecMap},
vec_set::{Self, VecSet}
record::{Self, Record},
role_map::{Self, RoleMap}
};
use iota::{clock::{Self, Clock}, event, linked_table::{Self, LinkedTable}};
use std::string::String;

// ===== Errors =====
#[error]
const ERecordNotFound: vector<u8> = b"Record not found at the given sequence number";
#[error]
const ERoleDoesNotExist: vector<u8> = b"The specified role does not exist in the `roles` map";
#[error]
const EPermissionDenied: vector<u8> =
b"The role associated with the provided capability does not have the required permission";
#[error]
const ECapabilityHasBeenRevoked: vector<u8> =
b"The provided capability has been revoked and is no longer valid";
#[error]
const ETrailIdNotCorrect: vector<u8> =
b"The trail ID associated with the provided capability does not match the audit trail";
#[error]
const ERecordLocked: vector<u8> = b"The record is locked and cannot be deleted";

// ===== Constants =====
Expand Down Expand Up @@ -68,14 +55,12 @@ public struct AuditTrail<D: store + copy> has key, store {
records: LinkedTable<u64, Record<D>>,
/// Deletion locking rules
locking_config: LockingConfig,
/// Map of role names to permission sets.
roles: VecMap<String, VecSet<Permission>>,
/// A list of role definitions consisting of a unique role specifier and a list of associated permissions
roles: RoleMap<Permission>,
/// Set at creation, cannot be changed
immutable_metadata: Option<ImmutableMetadata>,
/// Can be updated by holders of MetadataUpdate permission
updatable_metadata: Option<String>,
/// Whitelist of valid capability IDs
issued_capabilities: VecSet<ID>,
}

// ===== Events =====
Expand Down Expand Up @@ -110,16 +95,6 @@ public struct RecordDeleted has copy, drop {
timestamp: u64,
}

/// Emitted when a capability is issued
public struct CapabilityIssued has copy, drop {
trail_id: ID,
capability_id: ID,
role: String,
issued_to: address,
issued_by: address,
timestamp: u64,
}

// ===== Constructors =====

/// Create immutable trail metadata
Expand Down Expand Up @@ -187,16 +162,25 @@ public fun create<D: store + copy>(
initial_data.destroy_none();
};

let mut roles = vec_map::empty<String, VecSet<Permission>>();
roles.insert(initial_admin_role_name(), permission::admin_permissions());
let role_admin_permissions = role_map::new_role_admin_permissions(
permission::add_roles(),
permission::delete_roles(),
permission::update_roles(),
);

let capability_admin_permissions = role_map::new_capability_admin_permissions(
permission::add_capabilities(),
permission::revoke_capabilities(),
);

let admin_cap = capability::new_capability(
initial_admin_role_name(),
let (roles, admin_cap) = role_map::new(
trail_id,
initial_admin_role_name(),
permission::admin_permissions(),
role_admin_permissions,
capability_admin_permissions,
ctx,
);
let mut issued_capabilities = vec_set::empty<ID>();
issued_capabilities.insert(admin_cap.id());

let trail = AuditTrail {
id: trail_uid,
Expand All @@ -208,7 +192,6 @@ public fun create<D: store + copy>(
roles,
immutable_metadata: trail_metadata,
updatable_metadata,
issued_capabilities,
};

transfer::share_object(trail);
Expand Down Expand Up @@ -240,7 +223,17 @@ public fun add_record<D: store + copy>(
clock: &Clock,
ctx: &mut TxContext,
) {
assert!(trail.has_capability_permission(cap, &permission::add_record()), EPermissionDenied);
assert!(
trail
.roles
.is_capability_valid(
cap,
&permission::add_record(),
clock,
ctx,
),
EPermissionDenied,
);

let caller = ctx.sender();
let timestamp = clock::timestamp_ms(clock);
Expand Down Expand Up @@ -277,7 +270,17 @@ public fun delete_record<D: store + copy + drop>(
clock: &Clock,
ctx: &mut TxContext,
) {
assert!(trail.has_capability_permission(cap, &permission::delete_record()), EPermissionDenied);
assert!(
trail
.roles
.is_capability_valid(
cap,
&permission::delete_record(),
clock,
ctx,
),
EPermissionDenied,
);
assert!(linked_table::contains(&trail.records, sequence_number), ERecordNotFound);
assert!(!trail.is_record_locked(sequence_number, clock), ERecordLocked);

Expand Down Expand Up @@ -324,10 +327,18 @@ public fun update_locking_config<D: store + copy>(
trail: &mut AuditTrail<D>,
cap: &Capability,
new_config: LockingConfig,
_: &mut TxContext,
clock: &Clock,
ctx: &TxContext,
) {
assert!(
trail.has_capability_permission(cap, &permission::update_locking_config()),
trail
.roles
.is_capability_valid(
cap,
&permission::update_locking_config(),
clock,
ctx,
),
EPermissionDenied,
);
trail.locking_config = new_config;
Expand All @@ -338,13 +349,18 @@ public fun update_locking_config_for_delete_record<D: store + copy>(
trail: &mut AuditTrail<D>,
cap: &Capability,
new_delete_record_lock: LockingWindow,
_: &mut TxContext,
clock: &Clock,
ctx: &TxContext,
) {
assert!(
trail.has_capability_permission(
cap,
&permission::update_locking_config_for_delete_record(),
),
trail
.roles
.is_capability_valid(
cap,
&permission::update_locking_config_for_delete_record(),
clock,
ctx,
),
EPermissionDenied,
);
set_delete_record_lock(&mut trail.locking_config, new_delete_record_lock);
Expand All @@ -355,10 +371,18 @@ public fun update_metadata<D: store + copy>(
trail: &mut AuditTrail<D>,
cap: &Capability,
new_metadata: Option<String>,
_: &mut TxContext,
clock: &Clock,
ctx: &TxContext,
) {
assert!(
trail.has_capability_permission(cap, &permission::update_metadata()),
trail
.roles
.is_capability_valid(
cap,
&permission::update_metadata(),
clock,
ctx,
),
EPermissionDenied,
);
trail.updatable_metadata = new_metadata;
Expand Down Expand Up @@ -442,130 +466,14 @@ public fun has_record<D: store + copy>(trail: &AuditTrail<D>, sequence_number: u
public fun records<D: store + copy>(trail: &AuditTrail<D>): &LinkedTable<u64, Record<D>> {
&trail.records
}
// ===== Role and Capability Functions =====

// ===== Role related Functions =====

/// Get the permissions associated with a specific role.
/// Aborts with ERoleDoesNotExist if the role does not exist.
public fun get_role_permissions<D: store + copy>(
trail: &AuditTrail<D>,
role: &String,
): &VecSet<Permission> {
assert!(vec_map::contains(&trail.roles, role), ERoleDoesNotExist);
vec_map::get(&trail.roles, role)
}

/// Create a new role consisting of a role name and associated permissions
public fun create_role<D: store + copy>(
trail: &mut AuditTrail<D>,
cap: &Capability,
role: String,
permissions: VecSet<Permission>,
_: &mut TxContext,
) {
assert!(trail.has_capability_permission(cap, &permission::add_roles()), EPermissionDenied);
vec_map::insert(&mut trail.roles, role, permissions);
}

/// Delete an existing role
public fun delete_role<D: store + copy>(
trail: &mut AuditTrail<D>,
cap: &Capability,
role: &String,
_: &mut TxContext,
) {
assert!(trail.has_capability_permission(cap, &permission::delete_roles()), EPermissionDenied);
vec_map::remove(&mut trail.roles, role);
}

/// Update permissions associated with an existing role
public fun update_role_permissions<D: store + copy>(
trail: &mut AuditTrail<D>,
cap: &Capability,
role: &String,
new_permissions: VecSet<Permission>,
_: &mut TxContext,
) {
assert!(trail.has_capability_permission(cap, &permission::update_roles()), EPermissionDenied);
assert!(vec_map::contains(&trail.roles, role), ERoleDoesNotExist);
vec_map::remove(&mut trail.roles, role);
vec_map::insert(&mut trail.roles, *role, new_permissions);
}

/// Returns the roles defined in the audit trail
public fun roles<D: store + copy>(trail: &AuditTrail<D>): &VecMap<String, VecSet<Permission>> {
/// Returns a reference the RoleMap managing the roles and capabilities used in the audit trail
public fun roles<D: store + copy>(trail: &AuditTrail<D>): &RoleMap<Permission> {
&trail.roles
}

/// Indicates if the specified role exists in the audit trail
public fun has_role<D: store + copy>(trail: &AuditTrail<D>, role: &String): bool {
vec_map::contains(&trail.roles, role)
}

// ===== Capability related Functions =====

/// Indicates if a provided capability has a specific permission.
public fun has_capability_permission<D: store + copy>(
trail: &AuditTrail<D>,
cap: &Capability,
permission: &Permission,
): bool {
assert!(trail.id() == cap.trail_id(), ETrailIdNotCorrect);
assert!(trail.issued_capabilities.contains(&cap.id()), ECapabilityHasBeenRevoked);
let permissions = trail.get_role_permissions(cap.role());
vec_set::contains(permissions, permission)
}

/// Create a new capability with a specific role
/// Aborts with ERoleDoesNotExist if the role does not exist.
public fun new_capability<D: store + copy>(
trail: &mut AuditTrail<D>,
cap: &Capability,
role: &String,
ctx: &mut TxContext,
): Capability {
assert!(
trail.has_capability_permission(cap, &permission::add_capabilities()),
EPermissionDenied,
);
assert!(trail.roles.contains(role), ERoleDoesNotExist);
let new_cap = capability::new_capability(
*role,
trail.id(),
ctx,
);
trail.issued_capabilities.insert(new_cap.id());
new_cap
}

/// Destroy an existing capability
/// Every owner of a capability is allowed to destroy it when no longer needed.
/// TODO: Clarify if we need to restrict access with the `CapabilitiesRevoke` permission here.
/// If yes, we also need a destroy function for Admin capabilities (without the need of another Admin capability).
/// Otherwise the last Admin capability holder will block the trail forever by not being able to destroy it.
public fun destroy_capability<D: store + copy>(
trail: &mut AuditTrail<D>,
cap_to_destroy: Capability,
) {
assert!(trail.id() == cap_to_destroy.trail_id(), ETrailIdNotCorrect);
trail.issued_capabilities.remove(&cap_to_destroy.id());
cap_to_destroy.destroy();
}

/// Revoke a capability. Requires `CapabilitiesRevoke` permission.
public fun revoke_capability<D: store + copy>(
trail: &mut AuditTrail<D>,
cap: &Capability,
cap_to_revoke: ID,
) {
assert!(
trail.has_capability_permission(cap, &permission::revoke_capabilities()),
EPermissionDenied,
);
trail.issued_capabilities.remove(&cap_to_revoke);
}

/// Get the capabilities issued for this trail
public fun issued_capabilities<D: store + copy>(trail: &AuditTrail<D>): &VecSet<ID> {
&trail.issued_capabilities
/// Returns a mutable reference to the RoleMap managing the roles and capabilities used in the audit trail
public fun roles_mut<D: store + copy>(trail: &mut AuditTrail<D>): &mut RoleMap<Permission> {
&mut trail.roles
}
Loading
Loading