hipaapotamus 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ec4db2663583e74c68d65ef09d1735a0df311755
4
- data.tar.gz: 5755cee77600fe63a03ea05076db68552d48984a
3
+ metadata.gz: 27838a5cec369c4c46d64d4f8e9ac240d466302c
4
+ data.tar.gz: 5b04acd2ac7021c43bd3dc11aee49fe9db48ea24
5
5
  SHA512:
6
- metadata.gz: 17f4315d70ded832c4deed238dbf998d02200dffdfcf40dddadffc4b0a4da19c584afd9fad9b78cd4abfb5601e3775eddb62bc2f9829214aff73a93f41be21e0
7
- data.tar.gz: 9840c2108a4b73824db16793cfb5c2ef2db85cc189439b6e0701fb85bbe8b31451e8fbde9901a5eb6b7ab1f10830d9367c55a929201dcc3fb2ad1ee063d13892
6
+ metadata.gz: 28ae854376df17925f8caf2248aac680e2789680671c2eb7a9a35288a68427d4f27c0c587dad68f2447f7964ffac3c02383c9f5c84b5ff067a1d2da70165931e
7
+ data.tar.gz: a78f8f5289e87d662797e318a52e42d9d4c2db074231c1edac8884ed9987554f38ce575aa0aeb034a81ea9e2f3b28a521ab987affd1ff9b28c3cbe234c481180
data/.gitignore CHANGED
@@ -7,3 +7,4 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ /.idea/
@@ -1,4 +1,22 @@
1
1
  language: ruby
2
+
2
3
  rvm:
3
- - 2.2.1
4
- before_install: gem install bundler -v 1.10.5
4
+ - 2.0.0
5
+ - 2.1.7
6
+ - 2.2.3
7
+
8
+ cache: bundler
9
+
10
+ before_install:
11
+ - sudo apt-get autoremove sqlite3
12
+ - sudo apt-get install python-software-properties
13
+ - sudo apt-add-repository -y ppa:travis-ci/sqlite3
14
+ - sudo apt-get -y update
15
+ - sudo apt-cache show sqlite3
16
+ - sudo apt-get install sqlite3=3.7.15.1-1~travis1
17
+ - sudo sqlite3 -version
18
+ - gem install bundler -v 1.10.5
19
+
20
+
21
+ script:
22
+ - bundle exec rake test
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
- # Hipaapotamus
1
+ # ![Hipaa Hippo](http://imgh.us/hipaa-hippo.svg)Hipaapotamus
2
+ [![Build Status](https://travis-ci.org/anarchocurious/hipaapotamus.svg)](https://travis-ci.org/anarchocurious/hipaapotamus)
2
3
 
3
4
  Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/hipaapotamus`. To experiment with that code, run `bin/console` for an interactive prompt.
4
5
 
@@ -20,9 +21,65 @@ Or install it yourself as:
20
21
 
21
22
  $ gem install hipaapotamus
22
23
 
24
+ Once the gem is installed, run:
25
+
26
+ $ rails generate hipaapotamus:install
27
+
28
+ and run:
29
+
30
+ $ rake db:migrate
31
+
32
+ to create the hipaapotamus_actions table.
33
+
23
34
  ## Usage
24
35
 
25
- TODO: Write usage instructions here
36
+ ### Setting up Agents
37
+
38
+ Include Hipaapotamus::Agent on any models you want to act as an agent (for example, User) and override the hipaapotamus_display_name method to display whatever agent identifier you like:
39
+
40
+ ```ruby
41
+ class User < ActiveRecord::Base
42
+ include Hipaapotamus::Agent
43
+
44
+ def hipaapotamus_display_name
45
+ email
46
+ end
47
+ end
48
+ ```
49
+
50
+ ### Setting up Protected Models
51
+
52
+ Include Hipaapotamus::Protected on any models you want to be protected by a Hipaapotamus Policy:
53
+
54
+ ```ruby
55
+ class MedicalSecret < ActiveRecord::Base
56
+ include Hipaapotamus::Protected
57
+ end
58
+ ```
59
+
60
+ ### Setting up a Policy
61
+
62
+ Create a policies folder in your app directory and add your policy as follows (using MedicalSecret from above). Hipaapotamus will automatically against the policy when actions are attempted. When authorizing, the policy has access to the agent and the protected model.
63
+
64
+ ```ruby
65
+ class MedicalSecretPolicy < Hipaapotamus::Policy
66
+ def access?
67
+ agent.medical_secrets.include? protected
68
+ end
69
+
70
+ def creation?
71
+ agent.medical_secrets.include? protected
72
+ end
73
+
74
+ def modification?
75
+ agent.medical_secrets.include? protected
76
+ end
77
+
78
+ def destruction?
79
+ agent.medical_secrets.include? protected
80
+ end
81
+ end
82
+ ```
26
83
 
27
84
  ## Development
28
85
 
@@ -39,3 +96,4 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERN
39
96
 
40
97
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
41
98
 
99
+
data/Rakefile CHANGED
@@ -1,6 +1,18 @@
1
+ require "bundler/setup"
2
+ require "hipaapotamus"
1
3
  require "bundler/gem_tasks"
2
4
  require "rspec/core/rake_task"
3
5
 
4
- RSpec::Core::RakeTask.new(:spec)
6
+ RSpec::Core::RakeTask.new(:test)
7
+ task spec: :test
5
8
 
6
- task :default => :spec
9
+ task :console do
10
+ ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
11
+ ActiveRecord::Base.connection.execute 'CREATE TABLE hipaapotamus_actions (id integer PRIMARY KEY NOT NULL, agent_id integer, agent_type character varying NOT NULL, protected_id integer, protected_type character varying NOT NULL, serialized_protected_attributes text NOT NULL, action_type integer NOT NULL, performed_at timestamp without time zone NOT NULL, created_at timestamp without time zone NOT NULL);'
12
+
13
+ require 'pry'
14
+ Hipaapotamus.pry
15
+ end
16
+
17
+ # For travis
18
+ task default: :test
@@ -4,21 +4,28 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'hipaapotamus/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "hipaapotamus"
7
+ spec.name = 'hipaapotamus'
8
8
  spec.version = Hipaapotamus::VERSION
9
- spec.authors = ["Alec Larsen"]
10
- spec.email = ["aleclarsen42@gmail.com"]
9
+ spec.authors = ['Alec Larsen', 'Jacob Lee']
11
10
 
12
11
  spec.summary = %q{Hipaapotamus is an amazing gem for amazing people}
13
- spec.homepage = "https://github.com/anarchocurious/hipaapotamus"
14
- spec.license = "MIT"
12
+ spec.homepage = 'https://github.com/anarchocurious/hipaapotamus'
13
+ spec.license = 'MIT'
15
14
 
16
15
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
- spec.bindir = "exe"
16
+ spec.bindir = 'bin'
18
17
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
- spec.require_paths = ["lib"]
18
+ spec.require_paths = ['lib']
20
19
 
21
- spec.add_development_dependency "bundler", "~> 1.10"
22
- spec.add_development_dependency "rake", "~> 10.0"
23
- spec.add_development_dependency "rspec", "~> 3.0"
20
+ spec.required_ruby_version = '~> 2.0'
21
+
22
+ spec.add_runtime_dependency 'activerecord', '~> 4.1'
23
+ spec.add_runtime_dependency 'activesupport', '~> 4.1'
24
+
25
+ spec.add_development_dependency 'bundler', '~> 1.10'
26
+ spec.add_development_dependency 'rake', '~> 10.0'
27
+ spec.add_development_dependency 'rspec', '~> 3.3'
28
+ spec.add_development_dependency 'pry'
29
+ spec.add_development_dependency 'sqlite3', '1.3.11'
30
+ spec.add_development_dependency 'database_cleaner', '1.0.1'
24
31
  end
@@ -0,0 +1,18 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/active_record'
3
+
4
+ module Hipaapotamus
5
+ class InstallGenerator < ::Rails::Generators::Base
6
+ include ::Rails::Generators::Migration
7
+
8
+ def self.next_migration_number(dirname)
9
+ ::ActiveRecord::Generators::Base.next_migration_number(dirname)
10
+ end
11
+
12
+ source_root File.expand_path('../templates', __FILE__)
13
+
14
+ def create_migration_file
15
+ migration_template 'create_hipaapotamus_actions.rb', 'db/migrate/create_hipaapotamus_actions.rb'
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ class CreateHipaapotamusActions < ActiveRecord::Migration
2
+ def change
3
+ create_table :hipaapotamus_actions do |t|
4
+ t.integer :agent_id
5
+ t.string :agent_type, null: false
6
+
7
+ t.integer :protected_id
8
+ t.string :protected_type, null: false
9
+ t.text :serialized_protected_attributes, null: false
10
+
11
+ t.integer :action_type, null: false
12
+
13
+ t.boolean :is_transactional, null: false
14
+
15
+ t.datetime :performed_at, null: false
16
+ t.datetime :created_at, null: false
17
+ end
18
+ end
19
+ end
@@ -1,5 +1,62 @@
1
- require "hipaapotamus/version"
1
+ require 'active_record'
2
+ require 'hipaapotamus/accountability_context'
3
+ require 'hipaapotamus/accountability_error'
4
+ require 'hipaapotamus/accountable_controller'
5
+ require 'hipaapotamus/action'
6
+ require 'hipaapotamus/agent'
7
+ require 'hipaapotamus/anonymous_agent'
8
+ require 'hipaapotamus/execution'
9
+ require 'hipaapotamus/policy'
10
+ require 'hipaapotamus/protected'
11
+ require 'hipaapotamus/record_callback_proxy'
12
+ require 'hipaapotamus/system_agent'
13
+ require 'hipaapotamus/version'
2
14
 
3
15
  module Hipaapotamus
4
- # Your code goes here...
16
+ class << self
17
+ def transaction_manager
18
+ ActiveRecord::Base.connection.transaction_manager
19
+ end
20
+
21
+ def root_transaction
22
+ transaction_manager.instance_exec { @stack.try(:first) } || current_transaction
23
+ end
24
+
25
+ def current_transaction
26
+ transaction_manager.current_transaction
27
+ end
28
+
29
+ def current_transaction_state
30
+ current_transaction.try(:state)
31
+ end
32
+
33
+ def current_accountability_context
34
+ AccountabilityContext.current
35
+ end
36
+
37
+ def current_agent
38
+ current_accountability_context.try(:agent)
39
+ end
40
+
41
+ def with_accountability(agent, &block)
42
+ execution = nil
43
+ accountability_context = AccountabilityContext.new(agent)
44
+
45
+ is_using_callbacks = root_transaction.joinable?
46
+
47
+ root_transaction.add_record RecordCallbackProxy.new accountability_context if is_using_callbacks
48
+
49
+ accountability_context.within do
50
+ execution = Execution.new(&block)
51
+ end
52
+
53
+ accountability_context.finalize! unless is_using_callbacks
54
+
55
+ execution.try(:value)
56
+ end
57
+
58
+ def without_accountability(&block)
59
+ with_accountability(AnonymousAgent.instance, &block)
60
+ end
61
+ end
5
62
  end
@@ -0,0 +1,97 @@
1
+ require 'hipaapotamus/accountability_error'
2
+
3
+ module Hipaapotamus
4
+ class AccountabilityContext
5
+ THREAD_STORAGE_KEY = :hipaapotamus_active_accountability_context
6
+
7
+ attr_reader :agent, :parent_accountability_context, :progenitor_actions
8
+
9
+ def initialize(agent)
10
+ raise AccountabilityError, 'Cannot create AccountabilityContext without a valid Agent' unless agent.is_a? Agent
11
+
12
+ @agent = agent
13
+ @open = true
14
+ @finalized = false
15
+
16
+ within { yield(self) } if block_given?
17
+ end
18
+
19
+ def open?
20
+ @open
21
+ end
22
+
23
+ # noinspection RubyArgCount
24
+ def record_action(protected, action_type, transactional = false)
25
+ action = Action.new(
26
+ agent: agent,
27
+ protected: protected,
28
+ action_type: action_type,
29
+ source_transaction_state: Hipaapotamus.current_transaction_state,
30
+ is_transactional: transactional,
31
+ performed_at: DateTime.now
32
+ )
33
+
34
+ actions << action
35
+ end
36
+
37
+ def finalized?
38
+ @finalized
39
+ end
40
+
41
+ def finalize!
42
+ raise(AccountabilityError, 'AccountabilityContext is open') if open?
43
+ raise(AccountabilityError, 'AccountabilityContext is finalized') if finalized?
44
+
45
+ Action.bulk_insert(log_worthy_actions) if root?
46
+
47
+ @finalized = true
48
+ end
49
+
50
+ def root?
51
+ parent_accountability_context.nil?
52
+ end
53
+
54
+ def within
55
+ raise(AccountabilityError, 'AccountabilityContext is not open') unless open?
56
+ @open = false
57
+
58
+ @parent_accountability_context = Thread.current[THREAD_STORAGE_KEY]
59
+ Thread.current[THREAD_STORAGE_KEY] = self
60
+
61
+ begin
62
+
63
+ yield(self)
64
+ ensure
65
+ Thread.current[THREAD_STORAGE_KEY] = @parent_accountability_context
66
+ end
67
+ end
68
+
69
+ protected
70
+
71
+ def actions
72
+ raise(AccountabilityError, 'AccountabilityContext is open') if open?
73
+
74
+ @actions ||= if root?
75
+ []
76
+ else
77
+ parent_accountability_context.actions
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ def log_worthy_actions
84
+ actions.select(&:log_worthy?)
85
+ end
86
+
87
+ class << self
88
+ def current
89
+ Thread.current[THREAD_STORAGE_KEY]
90
+ end
91
+
92
+ def current!
93
+ current || raise(AccountabilityError, 'Not within an AccountabilityContext')
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,2 @@
1
+ class AccountabilityError < StandardError
2
+ end
@@ -0,0 +1,28 @@
1
+ require 'active_support/concern'
2
+
3
+ module Hipaapotamus
4
+ module AccountableController
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ around_action :wrap_in_accountability_context
9
+ rescue_from AccountabilityError, with: :agent_not_authorized
10
+ end
11
+
12
+ private
13
+
14
+ def current_agent
15
+ current_user
16
+ end
17
+
18
+ def wrap_in_accountability_context
19
+ Hipaapotamus.with_accountability(current_agent) do
20
+ yield
21
+ end
22
+ end
23
+
24
+ def agent_not_authorized(accountability_error)
25
+ render text: accountability_error.to_s, status: 401
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,123 @@
1
+ require 'active_record'
2
+
3
+ module Hipaapotamus
4
+ class Action < ActiveRecord::Base
5
+ self.table_name = 'hipaapotamus_actions'
6
+
7
+ attr_accessor :source_transaction_state
8
+
9
+ enum action_type: { access: 0, creation: 1, modification: 2, destruction: 3,
10
+ attempted_access: 4, attempted_creation: 5, attempted_modification: 6, attempted_destruction: 7 }
11
+
12
+ def transactional?
13
+ is_transactional
14
+ end
15
+
16
+ def log_worthy?
17
+ persisted? || !transactional? || source_transaction_state.committed?
18
+ end
19
+
20
+ def agent_class
21
+ agent_type.try(:constantize)
22
+ end
23
+
24
+ def agent
25
+ if agent_class < Singleton
26
+ agent_class.instance
27
+ else
28
+ agent_class.find(agent_id)
29
+ end
30
+ end
31
+
32
+ def agent=(agent)
33
+ if agent.is_a? Singleton
34
+ self.agent_id = nil
35
+ else
36
+ self.agent_id = agent.id
37
+ end
38
+
39
+ self.agent_type = agent.class.name
40
+ end
41
+
42
+ def protected_class
43
+ protected_type.try(:constantize)
44
+ end
45
+
46
+ def protected
47
+ @protected ||= protected_class.new.tap do |protected|
48
+ if protected_id.present?
49
+ protected.id = protected_id
50
+ end
51
+
52
+ if protected_attributes.present?
53
+ protected.assign_attributes protected_attributes
54
+ end
55
+
56
+ protected.authorize_access!
57
+ end
58
+ end
59
+
60
+ def protected=(protected)
61
+ self.protected_id = protected.try(:id)
62
+ self.protected_type = protected.try(:class).try(:name)
63
+ self.protected_attributes = protected.try(:attributes)
64
+
65
+ @protected = protected
66
+ end
67
+
68
+ def protected_attributes
69
+ JSON.parse(serialized_protected_attributes) if serialized_protected_attributes.present?
70
+ end
71
+
72
+ def protected_attributes=(protected_attributes)
73
+ self.serialized_protected_attributes = protected_attributes.try(:to_json)
74
+ end
75
+
76
+ validate :not_changed
77
+ validates :agent_type, :protected_type, :protected_attributes, :action_type, :performed_at, presence: true
78
+
79
+ scope :with_protected, -> (protected) { where(protected_type: protected.class.name, protected_id: protected.id) }
80
+
81
+ class << self
82
+ def bulk_insert(actions)
83
+ if actions.length > 0
84
+ actions.each do |action|
85
+ raise ActiveRecord::RecordInvalid, 'unable to modify existing actions' unless action.new_record?
86
+ raise ActiveRecord::RecordInvalid, action.errors.full_messages.to_sentence unless action.valid?
87
+ end
88
+
89
+ attributeses = actions.map(&:attributes)
90
+
91
+ now = DateTime.now
92
+ attributeses.each { |attributes| attributes['created_at'] = now } if self.column_names.include?('created_at')
93
+ attributeses.each { |attributes| attributes['updated_at'] = now } if self.column_names.include?('updated_at')
94
+
95
+ uniq_keys = attributeses.map { |attributes| attributes.keys }.flatten(1).uniq.reject { |key| key == primary_key || key == primary_key.to_sym }
96
+
97
+ column_names = uniq_keys.map(&:to_s)
98
+ rows = attributeses.map { |attributes| uniq_keys.map { |key| attributes[key] } }
99
+
100
+ value_template = "(#{column_names.map{'?'}.join(', ')})"
101
+
102
+ value_clauses = rows.map { |values| sanitize_sql_array([value_template, *values]) }
103
+ values_clause = value_clauses.join(', ')
104
+
105
+ column_clauses = column_names.map { |column_name| connection.quote_column_name(column_name) }
106
+ columns_clause = "#{connection.quote_column_name(table_name)} (#{column_clauses.join(', ')})"
107
+
108
+ insert_statement = "INSERT INTO #{columns_clause} VALUES #{values_clause};"
109
+
110
+ connection.execute(insert_statement)
111
+ end
112
+ end
113
+ end
114
+
115
+ private
116
+
117
+ def not_changed
118
+ unless new_record?
119
+ self.errors.add(:action, 'cannot be changed')
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,16 @@
1
+ require 'active_support/concern'
2
+ require 'hipaapotamus/accountability_context'
3
+
4
+ module Hipaapotamus
5
+ module Agent
6
+ extend ActiveSupport::Concern
7
+
8
+ def with_accountability(&block)
9
+ Hipaapotamus.with_accountability(self, &block)
10
+ end
11
+
12
+ def hipaapotamus_display_name
13
+ "#{self.class.name}(id=#{id})" rescue self.class.name
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,6 @@
1
+ require 'hipaapotamus/system_agent'
2
+
3
+ module Hipaapotamus
4
+ class AnonymousAgent < SystemAgent
5
+ end
6
+ end
@@ -0,0 +1,25 @@
1
+ module Hipaapotamus
2
+ class Execution
3
+ def initialize
4
+ begin
5
+ @value = yield
6
+ @raised = false
7
+ rescue StandardError => value
8
+ @value = value
9
+ @raised = true
10
+ end
11
+ end
12
+
13
+ def raised?
14
+ @raised
15
+ end
16
+
17
+ def value
18
+ if raised?
19
+ raise @value
20
+ else
21
+ @value
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,41 @@
1
+ require 'hipaapotamus/accountability_error'
2
+
3
+ module Hipaapotamus
4
+ class Policy
5
+ attr_reader :agent, :protected
6
+
7
+ def initialize(agent, protected)
8
+ @agent, @protected = agent, protected
9
+ end
10
+
11
+ def authorized?(action)
12
+ SystemAgent === agent || try(:"#{action}?")
13
+ end
14
+
15
+ def authorize!(action)
16
+ authorized?(action) || raise(AccountabilityError, "#{agent.hipaapotamus_display_name} does not have #{action} privileges to #{protected.hipaapotamus_display_name}")
17
+ end
18
+
19
+ def creation?
20
+ false
21
+ end
22
+
23
+ def access?
24
+ false
25
+ end
26
+
27
+ def modification?
28
+ false
29
+ end
30
+
31
+ def destruction?
32
+ false
33
+ end
34
+
35
+ class << self
36
+ def authorize!(agent, protected, action)
37
+ new(agent, protected).authorize!(action)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,97 @@
1
+ require 'hipaapotamus/accountability_error'
2
+
3
+ module Hipaapotamus
4
+ module Protected
5
+ extend ActiveSupport::Concern
6
+
7
+ class_methods do
8
+ def policy_class_name
9
+ @policy_class_name ||= "#{name}Policy"
10
+ end
11
+
12
+ def policy_class
13
+ if policy_class_name
14
+ @policy_class ||= policy_class_name.constantize
15
+ end
16
+ end
17
+
18
+ def policy_class!
19
+ policy_class || raise(AccountabilityError, "Could not find the policy class for #{name}")
20
+ end
21
+ end
22
+
23
+ included do
24
+ delegate :policy_class, :policy_class!, to: :class
25
+
26
+ after_initialize :authorize_access!, unless: :new_record?
27
+ after_create :authorize_creation!
28
+ after_update :authorize_modification!
29
+ after_destroy :authorize_destruction!
30
+ end
31
+
32
+ def hipaapotamus_display_name
33
+ if new_record?
34
+ "a new #{self.class.name}"
35
+ else
36
+
37
+ "#{self.class.name}(#{self.class.primary_key}=#{self[self.class.primary_key]})"
38
+ end
39
+ end
40
+
41
+ def authorize_access!
42
+ accountability_context = AccountabilityContext.current!
43
+
44
+ begin
45
+ policy_class!.authorize!(accountability_context.agent, self, :access)
46
+
47
+ accountability_context.record_action(self, :access)
48
+ rescue AccountabilityError => error
49
+ accountability_context.record_action(self, :attempted_access)
50
+
51
+ raise error
52
+ end
53
+ end
54
+
55
+ def authorize_creation!
56
+ accountability_context = AccountabilityContext.current!
57
+
58
+ begin
59
+ policy_class!.authorize!(accountability_context.agent, self, :creation)
60
+
61
+ accountability_context.record_action(self, :creation, true)
62
+ rescue AccountabilityError => error
63
+ accountability_context.record_action(self, :attempted_creation)
64
+
65
+ raise error
66
+ end
67
+ end
68
+
69
+ def authorize_modification!
70
+ accountability_context = AccountabilityContext.current!
71
+
72
+ begin
73
+ policy_class!.authorize!(accountability_context.agent, self, :modification)
74
+
75
+ accountability_context.record_action(self, :modification, true)
76
+ rescue AccountabilityError => error
77
+ accountability_context.record_action(self, :attempted_modification)
78
+
79
+ raise error
80
+ end
81
+ end
82
+
83
+ def authorize_destruction!
84
+ accountability_context = AccountabilityContext.current!
85
+
86
+ begin
87
+ policy_class!.authorize!(accountability_context.agent, self, :destruction)
88
+
89
+ accountability_context.record_action(self, :destruction, true)
90
+ rescue AccountabilityError => error
91
+ accountability_context.record_action(self, :attempted_destruction)
92
+
93
+ raise error
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,27 @@
1
+ require 'active_record'
2
+
3
+ module Hipaapotamus
4
+ class RecordCallbackProxy
5
+ attr_reader :accountability_context
6
+
7
+ def initialize(accountability_context)
8
+ @accountability_context = accountability_context
9
+ end
10
+
11
+ def rolledback!(*args)
12
+ accountability_context.finalize!
13
+ end
14
+
15
+ def before_committed!(*args)
16
+ # NOOP
17
+ end
18
+
19
+ def committed!(*args)
20
+ accountability_context.finalize!
21
+ end
22
+
23
+ def add_to_transaction
24
+ ActiveRecord::Base.connection.add_transaction_record(self)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,13 @@
1
+ require 'singleton'
2
+ require 'hipaapotamus/agent'
3
+
4
+ module Hipaapotamus
5
+ class SystemAgent
6
+ include Singleton
7
+ include Agent
8
+
9
+ def hipaapotamus_display_name
10
+ self.class.name
11
+ end
12
+ end
13
+ end
@@ -1,3 +1,3 @@
1
1
  module Hipaapotamus
2
- VERSION = "0.1.0"
2
+ VERSION = '0.2.0'
3
3
  end
metadata CHANGED
@@ -1,15 +1,44 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hipaapotamus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alec Larsen
8
+ - Jacob Lee
8
9
  autorequire:
9
- bindir: exe
10
+ bindir: bin
10
11
  cert_chain: []
11
- date: 2015-11-06 00:00:00.000000000 Z
12
+ date: 2015-12-11 00:00:00.000000000 Z
12
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '4.1'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '4.1'
28
+ - !ruby/object:Gem::Dependency
29
+ name: activesupport
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '4.1'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '4.1'
13
42
  - !ruby/object:Gem::Dependency
14
43
  name: bundler
15
44
  requirement: !ruby/object:Gem::Requirement
@@ -44,17 +73,58 @@ dependencies:
44
73
  requirements:
45
74
  - - "~>"
46
75
  - !ruby/object:Gem::Version
47
- version: '3.0'
76
+ version: '3.3'
48
77
  type: :development
49
78
  prerelease: false
50
79
  version_requirements: !ruby/object:Gem::Requirement
51
80
  requirements:
52
81
  - - "~>"
53
82
  - !ruby/object:Gem::Version
54
- version: '3.0'
83
+ version: '3.3'
84
+ - !ruby/object:Gem::Dependency
85
+ name: pry
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: sqlite3
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - '='
103
+ - !ruby/object:Gem::Version
104
+ version: 1.3.11
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - '='
110
+ - !ruby/object:Gem::Version
111
+ version: 1.3.11
112
+ - !ruby/object:Gem::Dependency
113
+ name: database_cleaner
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - '='
117
+ - !ruby/object:Gem::Version
118
+ version: 1.0.1
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - '='
124
+ - !ruby/object:Gem::Version
125
+ version: 1.0.1
55
126
  description:
56
- email:
57
- - aleclarsen42@gmail.com
127
+ email:
58
128
  executables: []
59
129
  extensions: []
60
130
  extra_rdoc_files: []
@@ -66,10 +136,21 @@ files:
66
136
  - LICENSE.txt
67
137
  - README.md
68
138
  - Rakefile
69
- - bin/console
70
- - bin/setup
71
139
  - hipaapotamus.gemspec
140
+ - lib/generators/hipaapotamus/install_generator.rb
141
+ - lib/generators/hipaapotamus/templates/create_hipaapotamus_actions.rb
72
142
  - lib/hipaapotamus.rb
143
+ - lib/hipaapotamus/accountability_context.rb
144
+ - lib/hipaapotamus/accountability_error.rb
145
+ - lib/hipaapotamus/accountable_controller.rb
146
+ - lib/hipaapotamus/action.rb
147
+ - lib/hipaapotamus/agent.rb
148
+ - lib/hipaapotamus/anonymous_agent.rb
149
+ - lib/hipaapotamus/execution.rb
150
+ - lib/hipaapotamus/policy.rb
151
+ - lib/hipaapotamus/protected.rb
152
+ - lib/hipaapotamus/record_callback_proxy.rb
153
+ - lib/hipaapotamus/system_agent.rb
73
154
  - lib/hipaapotamus/version.rb
74
155
  homepage: https://github.com/anarchocurious/hipaapotamus
75
156
  licenses:
@@ -81,9 +162,9 @@ require_paths:
81
162
  - lib
82
163
  required_ruby_version: !ruby/object:Gem::Requirement
83
164
  requirements:
84
- - - ">="
165
+ - - "~>"
85
166
  - !ruby/object:Gem::Version
86
- version: '0'
167
+ version: '2.0'
87
168
  required_rubygems_version: !ruby/object:Gem::Requirement
88
169
  requirements:
89
170
  - - ">="
@@ -96,4 +177,3 @@ signing_key:
96
177
  specification_version: 4
97
178
  summary: Hipaapotamus is an amazing gem for amazing people
98
179
  test_files: []
99
- has_rdoc:
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "bundler/setup"
4
- require "hipaapotamus"
5
-
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
8
-
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
12
-
13
- require "irb"
14
- IRB.start
data/bin/setup DELETED
@@ -1,7 +0,0 @@
1
- #!/bin/bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
-
5
- bundle install
6
-
7
- # Do any other automated setup that you need to do here