GithubHelp home page GithubHelp logo

jamesstonehill / anonymous Goto Github PK

View Code? Open in Web Editor NEW
1.0 1.0 3.0 20 KB

Data anonymization for ActiveRecord

License: MIT License

Ruby 98.98% Shell 1.02%
gem data-anonymization rails rubygem ruby activerecord

anonymous's Introduction

Anonymous

Gem Version Build Status

Anonymous is a light-weight gem that makes anonymizing ActiveRecord models easy! Remember, friends don't let friends use production data in staging/development.

Installation

Add this line to your application's Gemfile:

gem 'anonymous'

And then execute:

$ bundle

Usage

Usage With ActiveRecord

To use this gem in your ActiveRecord models you need to do two things.

  1. Include the Anonymous::ActiveRecord module in your model
  2. Define a private method anonymization_definitions with the anonymization definitions inside it.
class User < ApplicationRecord
  include Anonymous::ActiveRecord

  private

  def anonymization_definitions
    {
      name: ["Bob Dylan", "Tony Blair"].sample,
      email: -> (user_email) { "fake_#{user_email}" },
      phone_number: -> (phone) { phone[0..-4] + 3.times.map{rand(10)}.join },
    }
  end
end

The return value of anonymization_definitions should be a Hash where the keys are the names of the attribute to be anonymized and the values are either a Proc object or the value to be filled in for the anonymized attribute.

If you use a proc or lambda as the argument then the attribute value will be provided to you in the proc's first argument. This is useful when you want your anonymized value to be a transformation of the original.

It is recommended that you use this gem in conjunction with a fake data generation library like faker.

  def anonymization_definitions
    {
      first_name: Faker::Name.first_name,
      email: Faker::Internet.unique.email,
    }
  end

Then when you have set up the gem correctly you can call anonymize and anonymize! on the model.

user = User.create(
  name: "John Smith",
  email: "[email protected]",
  phone_number: "+447875477389"
)
user.anonymize! # or user.anonymize
user.reload
user.email
=> "[email protected]"
user.name
=> "Bob Dylan"
user.phone_number
=> "+447875477412"

The only difference between anonymize! and anonymize is that the former calls update_attributes! and the latter calls update_attributes.

Configuration

You can configure the gem to alter the anonymisation behaviour.

# config/initializers/anonymous.rb

Anonymous.configure do |config|
  config.max_anonymize_retries = 0
end

Configuration Options

  1. max_anonymize_retries Under some situations (like if an RecordNotUnique error is raised when updating) the gem will retry the anonymization process. By default it will only do this once.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run bundle exec rake appraisal spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install.

To release a new version, update the version number in version.rb, then run bundle install and bundle exec appraisal install, and finally run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/jamesstonehill/anonymous.

Some ideas for feature contributions:

  • Support for ORMs other than ActiveRecord.
  • More comprehensive retry functionality in Anonymous::ActiveRecord. A the moment we only retry if we get an ActiveRecord::RecordNotUnique unique error. I didn't want to blindly rescue all errors, but it seems like that there are other times we would want to retry.

License

The gem is available as open source under the terms of the MIT License.

anonymous's People

Contributors

jamesstonehill avatar

Stargazers

 avatar

Watchers

 avatar

anonymous's Issues

display cookie bar

In an attempt to make this gem fully GDPR compliant, we should allow service providers with web apps to let their customers manage their cookies information.

make it easy to anonymize associations

We should be able to anonymize for model's associations with on one single method lie below/

class User < ApplicationRecord
  has_many :orders
  anonymize_association :orders
end

be able to anonymize readonly attributes

Sometimes, there are cases where attributes are marked as readonly attributes but because those data are related to personal information, they need to be anonymized. We have to think about way to handle that.

Features requests

Hey @jamesstonehill, hope you're well.

I've been using your gem since yesterday and it's great ! thanks for the good work. Have you thought about improving it and add new features ? like for example :

  • being able to anonymize associations on a model via a method like anonymize_association :association_name
  • have a default and customizable anonymization values based on the object

and eventually make it completely GDPR compliant ?

Let me know what you think. Totally opened to make things happen. ;)

have a delete account button (soft deletion)

This button will allow service providers' customers to delete their account softly (using discard gem for example). The customer should be informed that his account has been deactivated and that he'll have an amount of time before its data get lost (discarded and anonymized). The amount of time will be decided by the service provider so he can process things like collecting due money or things of that nature.

have default and customizable anonymization definitions

Since data anonymization is just a way to make data unrecognizable, we should introduce a way so the gem does it by default for a set of chosen attributes.

class User
  def anonymization_definitions
    [firstname, lastname, email] 
  end
end

user = User.new(firstname: 'John', lastname: 'Doe', email: '[email protected]')
user.save

John's has used the service for quite long time and was happy with it but for some reason, he no longer needs it. He decides to delete his account and therefore the service provider needs to anonymize its data.

user = User.find_by(email: '[email protected]')
user.anonymize
user.reload
=> #<User id:3, firstname: '3_anonymzed', lastname: '3_anonymized', email: '[email protected]'>

Customizable anonymzation values should still be allowed from the standpoint of the service providers. They may want to exposed those anonymized data to their customers in different way than <record.id>_anonymized

[BUG] push being rejected on Heroku

@jamesstonehill, I'm using rails 6.0.3, Ruby 2.7.2 and when trying to deploy on Heroku staging environment I'm getting the error below. Any idea on why this is happening please ? Any help would be much appreciated, thank you.

NameError: uninitialized constant User (call 'User.connection' to establish a connection)::Anonymous
       /tmp/build_21dd28c1_/app/models/user.rb:7:in `<class:User>'
       /tmp/build_21dd28c1_/app/models/user.rb:4:in `<main>'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/bootsnap-1.5.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/bootsnap-1.5.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/bootsnap-1.5.1/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/bootsnap-1.5.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require_with_bootsnap_lfi'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/bootsnap-1.5.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:31:in `require'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/zeitwerk-2.4.2/lib/zeitwerk/kernel.rb:26:in `require'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/activesupport-6.0.3.4/lib/active_support/inflector/methods.rb:282:in `const_get'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/activesupport-6.0.3.4/lib/active_support/inflector/methods.rb:282:in `block in constantize'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/activesupport-6.0.3.4/lib/active_support/inflector/methods.rb:280:in `each'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/activesupport-6.0.3.4/lib/active_support/inflector/methods.rb:280:in `inject'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/activesupport-6.0.3.4/lib/active_support/inflector/methods.rb:280:in `constantize'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/activesupport-6.0.3.4/lib/active_support/dependencies/zeitwerk_integration.rb:19:in `constantize'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/bundler/gems/devise-92facc7f2b4a/lib/devise.rb:320:in `get'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/bundler/gems/devise-92facc7f2b4a/lib/devise/mapping.rb:83:in `to'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/bundler/gems/devise-92facc7f2b4a/lib/devise/mapping.rb:78:in `modules'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/bundler/gems/devise-92facc7f2b4a/lib/devise/mapping.rb:95:in `routes'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/bundler/gems/devise-92facc7f2b4a/lib/devise/mapping.rb:158:in `default_used_route'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/bundler/gems/devise-92facc7f2b4a/lib/devise/mapping.rb:72:in `initialize'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/bundler/gems/devise-92facc7f2b4a/lib/devise.rb:350:in `new'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/bundler/gems/devise-92facc7f2b4a/lib/devise.rb:350:in `add_mapping'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/bundler/gems/devise-92facc7f2b4a/lib/devise/rails/routes.rb:243:in `block in devise_for'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/bundler/gems/devise-92facc7f2b4a/lib/devise/rails/routes.rb:242:in `each'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/bundler/gems/devise-92facc7f2b4a/lib/devise/rails/routes.rb:242:in `devise_for'
       /tmp/build_21dd28c1_/config/routes.rb:5:in `block in <main>'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/actionpack-6.0.3.4/lib/action_dispatch/routing/route_set.rb:426:in `instance_exec'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/actionpack-6.0.3.4/lib/action_dispatch/routing/route_set.rb:426:in `eval_block'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/actionpack-6.0.3.4/lib/action_dispatch/routing/route_set.rb:408:in `draw'
       /tmp/build_21dd28c1_/config/routes.rb:4:in `<main>'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/bootsnap-1.5.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:59:in `load'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/bootsnap-1.5.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:59:in `load'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/railties-6.0.3.4/lib/rails/application/routes_reloader.rb:40:in `block in load_paths'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/railties-6.0.3.4/lib/rails/application/routes_reloader.rb:40:in `each'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/railties-6.0.3.4/lib/rails/application/routes_reloader.rb:40:in `load_paths'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/railties-6.0.3.4/lib/rails/application/routes_reloader.rb:20:in `reload!'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/railties-6.0.3.4/lib/rails/application/routes_reloader.rb:29:in `block in updater'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/activesupport-6.0.3.4/lib/active_support/file_update_checker.rb:83:in `execute'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/railties-6.0.3.4/lib/rails/application/routes_reloader.rb:10:in `execute'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/railties-6.0.3.4/lib/rails/application/finisher.rb:184:in `block in <module:Finisher>'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/railties-6.0.3.4/lib/rails/initializable.rb:32:in `instance_exec'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/railties-6.0.3.4/lib/rails/initializable.rb:32:in `run'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/railties-6.0.3.4/lib/rails/initializable.rb:61:in `block in run_initializers'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/railties-6.0.3.4/lib/rails/initializable.rb:60:in `run_initializers'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/railties-6.0.3.4/lib/rails/application.rb:363:in `initialize!'
       /tmp/build_21dd28c1_/config/environment.rb:7:in `<main>'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/bootsnap-1.5.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/bootsnap-1.5.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/bootsnap-1.5.1/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/bootsnap-1.5.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require_with_bootsnap_lfi'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/bootsnap-1.5.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:31:in `require'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/zeitwerk-2.4.2/lib/zeitwerk/kernel.rb:34:in `require'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/activesupport-6.0.3.4/lib/active_support/dependencies.rb:324:in `block in require'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/activesupport-6.0.3.4/lib/active_support/dependencies.rb:291:in `load_dependency'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/activesupport-6.0.3.4/lib/active_support/dependencies.rb:324:in `require'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/railties-6.0.3.4/lib/rails/application.rb:339:in `require_environment!'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/railties-6.0.3.4/lib/rails/application.rb:523:in `block in run_tasks_blocks'
       /tmp/build_21dd28c1_/vendor/bundle/ruby/2.7.0/gems/sprockets-rails-3.2.2/lib/sprockets/rails/task.rb:61:in `block (2 levels) in define'
       Tasks: TOP => environment
       (See full trace by running task with --trace)
 !
 !     Precompiling assets failed.
 !
 !     Push rejected, failed to compile Ruby app.
 !     Push failed

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.