Permalink
Browse files

Migrated code from the older big_brother project.

  • Loading branch information...
0 parents commit 425c68d8edaaa289324690cc9e5e5199d09e58a2 @kunklejr committed Jan 6, 2011
@@ -0,0 +1,3 @@
+coverage
+rdoc
+auditor-*.gem
@@ -0,0 +1,20 @@
+Copyright (c) 2009 Near Infinity Corporation
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,69 @@
+= Auditor
+
+Auditor is a Rails 3 plugin for auditing access to your ActiveRecord model objects. It allows you to declaratively specify what CRUD operations should be audited and store that audit data in the database. You can also specify what attributes of model objects should automatically be audited and which ones should be ignored.
+
+To audit your model objects you must specify which operations should be audited and which model attributes should be tracked. This "specify what you want to collect" approach avoids being overwhelmed with data and makes you carefully consider what is most important to audit.
+
+= Installation
+
+To use it with your Rails 3 project, add the following line to your Gemfile
+
+ gem 'auditor'
+
+Auditor can also be installed as a Rails plugin
+
+ rails plugin install git://github.com/nearinfinity/auditor.git
+
+Generate the migration and create the audits table
+
+ rails generate auditor:migration
+ rake db:migrate
+
+= Setup
+
+Auditor needs to know who the current user is, but with no standard for doing so you'll have to do a little work to set things up. You simply need to set your current user model object as the Auditor current user before any CRUD operations are performed. For example, in a Rails application you could add the following to your application_controller.rb
+
+ class ApplicationController < ActionController::Base
+ before_filter :set_current_user
+
+ private
+
+ def set_current_user
+ Auditor::User.current_user = @current_user
+ end
+ end
+
+= Examples
+
+Auditor works very similarly to Joshua Clayton's acts_as_auditable plugin. There are two audit calls in the example below. The first declares that create and update actions should be audited for the EditablePage model and the string returned by the passed block should be included as a custom message. The second audit call simply changes the custom message when auditing destroy (aka delete) actions.
+
+ class EditablePage < ActiveRecord::Base
+ include Auditor::ModelAudit
+
+ has_many :tags
+
+ audit(:create, :update) { |model, user| "Editable page modified by #{user.display_name}" }
+ audit(:destroy) { |model, user| "#{user.display_name} deleted editable page #{model.id}" }
+ end
+
+All audit data is stored in a table named Audits, which is automatically created for you when you run the migration included with the plugin. However, there's a lot more recorded than just the custom message, including:
+
+* auditable_id - the primary key of the table belonging to the audited model object
+* auditable_type - the class type of the audited model object
+* auditable_version - the version number of the audited model object (if versioning is tracked)
+* user_id - the primary key of the table belonging to the user being audited
+* user_type - the class type of the model object representing users in your application
+* action - a string indicating the action that was audited (create, update, destroy, or find)
+* message - the custom message returned by any block passed to the audit call
+* edits - a YAML string containing the before and after state of any model attributes that changed
+* created_at - the date and time the audit record was recorded
+
+The edits column automatically serializes the before and after state of any model attributes that change during the action. If there are only a few attributes you want to audit or a couple that you want to prevent from being audited, you can specify that in the audit call. For example
+
+ # Prevent SSN and passwords from being saved in the audit table
+ audit(:create, :destroy, :except => [:ssn, :password])
+
+ # Only audit edits to the title column when destroying/deleting
+ audit(:destroy, :only => :title)
+
+Copyright (c) 2010 Near Infinity Corporation, released under the MIT license
@@ -0,0 +1,37 @@
+$:.unshift File.expand_path("../lib", __FILE__)
+
+require 'rake'
+require 'rake/rdoctask'
+require 'rspec/core/rake_task'
+
+desc 'Default: run specs'
+task :default => :spec
+
+desc "Run specs"
+RSpec::Core::RakeTask.new do |t|
+ t.rspec_opts = %w(-fs --color)
+end
+
+desc "Run specs with RCov"
+RSpec::Core::RakeTask.new(:rcov) do |t|
+ t.rspec_opts = %w(-fs --color)
+ t.rcov = true
+ t.rcov_opts = %w(--exclude "spec/*,gems/*")
+end
+
+desc 'Generate documentation for the gem.'
+Rake::RDocTask.new(:rdoc) do |rdoc|
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = 'Auditor'
+ rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.rdoc_files.include('README.rdoc')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
+
+task :build do
+ system "gem build auditor.gemspec"
+end
+
+task :release => :build do
+ system "gem push auditor-#{Auditor::VERSION}"
+end
@@ -0,0 +1,24 @@
+# -*- encoding: utf-8 -*-
+lib = File.expand_path('../lib/', __FILE__)
+$:.unshift lib unless $:.include?(lib)
+
+require 'auditor/version'
+
+Gem::Specification.new do |s|
+ s.name = "auditor"
+ s.version = Auditor::VERSION
+ s.platform = Gem::Platform::RUBY
+ s.authors = ["Jeff Kunkle", "Matt Wizeman"]
+ s.homepage = "http://github.com/nearinfinity/auditor"
+ s.summary = "Rails 3 plugin for auditing access to your ActiveRecord model objects"
+ s.description = "Auditor allows you to declaratively specify what CRUD operations should be audited and save the audit data to the database."
+ s.license = "MIT"
+
+ s.required_rubygems_version = ">= 1.3.6"
+
+ s.add_development_dependency "rspec"
+
+ s.files = Dir.glob("{lib}/**/*") + %w(LICENSE README.rdoc)
+ s.test_files = Dir.glob("{spec}/**/*")
+ s.require_path = 'lib'
+end
@@ -0,0 +1 @@
+require 'auditor'
@@ -0,0 +1,9 @@
+require 'auditor/audit'
+require 'auditor/integration'
+require 'auditor/model_audit'
+require 'auditor/user'
+require 'auditor/version'
+
+module Auditor
+ class Error < StandardError; end
+end
@@ -0,0 +1,6 @@
+require 'active_record'
+
+class Audit < ActiveRecord::Base
+ validates_presence_of :auditable_id, :auditable_type, :user_id, :user_type, :action
+ serialize :edits
+end
@@ -0,0 +1,36 @@
+module Auditor
+ class ConfigParser
+
+ def self.extract_config(args)
+ options = (args.delete_at(args.size - 1) if args.last.kind_of?(Hash)) || {}
+ normalize_config args, options
+ validate_config args, options
+ options = normalize_options(options)
+
+ [args, options]
+ end
+
+ private
+
+ def self.normalize_config(actions, options)
+ actions.each_with_index { |item, index| actions[index] = item.to_sym }
+ options.each_pair { |k, v| options[k.to_sym] = options.delete(k) unless k.kind_of? Symbol }
+ end
+
+ def self.normalize_options(options)
+ return { :except => [], :only => [] } if options.nil? || options.empty?
+ options[:except] = options[:except] || []
+ options[:only] = options[:only] || []
+ options[:except] = Array(options[:except]).map(&:to_s)
+ options[:only] = Array(options[:only]).map(&:to_s)
+ options
+ end
+
+ def self.validate_config(actions, options)
+ raise Auditor::Error.new "at least one :create, :find, :update, or :destroy action must be specified" if actions.empty?
+ raise Auditor::Error.new ":create, :find, :update, and :destroy are the only valid actions" unless actions.all? { |a| [:create, :find, :update, :destroy].include? a }
+ raise Auditor::Error.new "only one of :except and :only can be specified" if options.size > 1
+ end
+
+ end
+end
@@ -0,0 +1,49 @@
+require 'auditor/thread_status'
+
+module Auditor
+ module Integration
+
+ def without_auditor
+ previously_disabled = auditor_disabled?
+ disable_auditor
+
+ begin
+ result = yield if block_given?
+ ensure
+ enable_auditor unless previously_disabled
+ end
+
+ result
+ end
+
+ def with_auditor
+ previously_disabled = auditor_disabled?
+ enable_auditor
+
+ begin
+ result = yield if block_given?
+ ensure
+ disable_auditor if previously_disabled
+ end
+
+ result
+ end
+
+ def disable_auditor
+ Auditor::ThreadStatus.disable
+ end
+
+ def enable_auditor
+ Auditor::ThreadStatus.enable
+ end
+
+ def auditor_disabled?
+ Auditor::ThreadStatus.disabled?
+ end
+
+ def auditor_enabled?
+ Auditor::ThreadStatus.enabled?
+ end
+
+ end
+end
@@ -0,0 +1,47 @@
+require 'auditor/thread_status'
+require 'auditor/config_parser'
+require 'auditor/recorder'
+
+module Auditor
+ module ModelAudit
+
+ def self.included(base)
+ base.extend ClassMethods
+ end
+
+ # ActiveRecord won't call the after_find handler unless it see's a specific after_find method defined
+ def after_find; end
+
+ def auditor_disabled?
+ Auditor::ThreadStatus.disabled? || @auditor_disabled
+ end
+
+ module ClassMethods
+ def audit(*args, &blk)
+ actions, options = Auditor::ConfigParser.extract_config(args)
+
+ actions.each do |action|
+ unless action.to_sym == :find
+ callback = "auditor_before_#{action}"
+ define_method(callback) do
+ @auditor_auditor = Auditor::Recorder.new(action, self, options, &blk)
+ @auditor_auditor.audit_before unless auditor_disabled?
+ true
+ end
+ send "before_#{action}".to_sym, callback
+ end
+
+ callback = "auditor_after_#{action}"
+ define_method(callback) do
+ @auditor_auditor = Auditor::Recorder.new(action, self, options, &blk) if action.to_sym == :find
+ @auditor_auditor.audit_after unless auditor_disabled?
+ true
+ end
+ send "after_#{action}".to_sym, callback
+
+ end
+ end
+ end
+
+ end
+end
@@ -0,0 +1,44 @@
+require 'auditor/user'
+
+module Auditor
+ class Recorder
+
+ def initialize(action, model, options, &blk)
+ @action, @model, @options, @blk = action.to_sym, model, options, blk
+ end
+
+ def audit_before
+ @audit = Audit.new(:edits => prepare_edits(@model.changes, @options))
+ end
+
+ def audit_after
+ @audit ||= Audit.new
+
+ @audit.attributes = {
+ :auditable_id => @model.id,
+ :auditable_type => @model.class.to_s,
+ :user_id => user.id,
+ :user_type => user.class.to_s,
+ :action => @action.to_s
+ }
+
+ @audit.auditable_version = @model.version if @model.respond_to? :version
+ @audit.message = @blk.call(@model, user) if @blk
+
+ @audit.save
+ end
+
+ private
+ def user
+ Auditor::User.current_user
+ end
+
+ def prepare_edits(changes, options)
+ chg = changes.dup
+ chg = chg.delete_if { |key, value| options[:except].include? key } unless options[:except].empty?
+ chg = chg.delete_if { |key, value| !options[:only].include? key } unless options[:only].empty?
+ chg.empty? ? nil : chg
+ end
+
+ end
+end
@@ -0,0 +1,20 @@
+module Auditor
+ module SpecHelpers
+ include Auditor::Integration
+
+ def self.included(base)
+ base.class_eval do
+ before(:each) do
+ disable_auditor
+ end
+
+ after(:each) do
+ enable_auditor
+ end
+ end
+ end
+
+ end
+end
+
+
Oops, something went wrong.

0 comments on commit 425c68d

Please sign in to comment.