Auditing and model lifecycle management for instances and their associations?
I am trying to write an application to track legal case requests. The main model is Case, which has_many
Subjects, Keywords, Notes, and Evidences (which, in turn, has_many
CustodyLogs). Since the application is legal-related, there are some requirements that are out of the ordinary:
- CRUD operations must be logged, including what the operation was, who the actor was, and when the operation occurred
- There needs to be some way to validate the data (i.e. recording MD5 checksums of records)
- 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)
- 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.
I've got bits of this working, but I'm running into a problem. Authentication is being handled by an external web single-sign-on service, so the only visibility to the ID of the logged in user is in a request variable. If I put audit logging in the model, through a callback, for example, I can be fairly sure that all data modifications are logged, but the model has no visibility to the request variables, so I can't log the user ID. This also ensures that changes to the state machine (currently using state_machine plugin) get logged.
If, on the other hand, I put the audit logging in the application controller, I lose the ability to be sure that all CRUD operations are logged (code in the Case model calling Subject.create, for example, wouldn't be logged). I also think that I'd lose state changes.
Is there a way to be sure that all CRUD operations are logged throughout the association tree such that the user ID of the logged in 开发者_运维百科user is recorded?
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.
精彩评论