CRUD operations must be logged, including what the operation was, who the actor was, and when the operation occurred
This can be addressed with an ActiveRecord::Callbacks and an attr_accessor field.
In any of the models that need to be logged add the following.
attr_accessor :modifier_id, :modifier
valiadate :valid_user
before_validate :populate_modifier
before_save :write_save_attempted_to_audit_log
after_save :write_save_completed_to_audit_log
def populate_modifier
self.modifier = User.find_by_id(modifier_id) unless modifier
end
def valid_user
unless modifier
errors.add(:modifiers_user_id, "Unknown user attempted to modify this record")
write_unauthorized_modification_to_audit_log
end
end
def write_save_attempted_to_audit_log
# announce that user is attempting to save a record with timestamp to audit log
# use ActiveRecord::Dirty.changes to show the change the might be made
end
def write_save_competed_to_audit_log
# announce that user has successfully changed the record with timestamp to audit log
end
def write_unauthorized_modification
# announce that a change was attempted without a user
end
Because you're likely to use this in a few models you can abstract it into a plugin, and add it only with needed with a method call like audit_changes
. See any of the acts_as plugins for inspiration on how to accomplish this.
In the controllers you will need to remember to add @thing.modifier = @current_user
before attempting to save.
There needs to be some way to validate the data (i.e. recording MD5 checksums of records)
As for a checksum... of an operation? You could override inspect to print a string containing all the information in the record in a consistent fashion and then generate a checksum for that. While you're at it, might as well add it to the to the audit log as part of the writing to log methods.
Some data should be write-once (i.e. the app can create an audit log entry, but that log cannot be edited or deleted from within the application thereafter)
Write each access log as a separate file with a deterministic name ("/logs/audits/#{class}/#{id}/#{timestamp}"
) and remove the write permission once it's saved. File.chmod(0555, access_log_file)
Changes to associated objects probably should be logged throughout the nesting. For example, adding a CustodyLog to a piece of Evidence should have a log for itself, a log for it's Evidence, and a log for the parent Case. This is to ensure that the last update timestamp for the Case accurately reflects the real last update, and not just the last time that the Case model data itself changed.
As for the 4th requirement. That will automatically get rolled into my solution for the first if you use accepts_nested_attributes_for on any of your nested relationships. And :autosave => true for belongs_to relationships.
If you're saving checksums into audit logs, you can roll a check into the before_save method to ensure the object you're working on, was has not been tampered with. Just by checking the latest audit log for the object and matching up the checksums.