cloak_id 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +24 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/LICENSE.txt +22 -0
- data/README.md +122 -0
- data/Rakefile +2 -0
- data/cloak_id.gemspec +30 -0
- data/lib/cloak_id.rb +127 -0
- data/lib/cloak_id/cloak_id_encoder.rb +59 -0
- data/lib/cloak_id/errors.rb +6 -0
- data/lib/cloak_id/version.rb +3 -0
- data/lib/generators/cloak_id/install/install_generator.rb +11 -0
- data/lib/generators/cloak_id/install/templates/cloak_id.rb.erb +3 -0
- data/spec/cloak_id/cloak_id_encoder_spec.rb +50 -0
- data/spec/cloak_id/cloak_id_spec.rb +92 -0
- data/spec/cloak_id/install_generator_spec.rb +20 -0
- data/spec/schema.rb +15 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/test_model.rb +20 -0
- metadata +182 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4702a69506e1c0ccc4888a32e69b04161161e20e
|
4
|
+
data.tar.gz: 4701111cd4ec15d847ca64421f54d2862bfb9fef
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: daf77844a7c971e5920a567f9dc4d2b1d698e56e16289f8fcf0806d8fe86d9c0cc77e78d06c14018b8ca60f8980c87966cddfb31a62c2aea9897bf287b9f727c
|
7
|
+
data.tar.gz: 5aad2b8a0b8fef298dd5eb6bc79d8acb418e19c9b2c35a053a65006b626b351cbca31bcb9ca857e842e998906b83fab2b3c8c03985bc6abc4b03832fb31e35a0
|
data/.gitignore
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
23
|
+
.idea/
|
24
|
+
coverage/
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 elleleb
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Beth LeBeau
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
# CloakId
|
2
|
+
|
3
|
+
### Hide your database ids from users of your webapp.
|
4
|
+
|
5
|
+
Using sequential ids makes predicting the ids of other resources in your application simple, and can provide details
|
6
|
+
about how your app is growing (how many users, etc.) to current and potential competitors. Obfuscating identifiers isn't
|
7
|
+
meant to be a primary means of providing security, but it can help to keep confidential information from being completely
|
8
|
+
public.
|
9
|
+
|
10
|
+
Making use of the gem will replace the identifier in URLs:
|
11
|
+
e.g. http://my.railsapp.com/users/U3OFSP23
|
12
|
+
|
13
|
+
as well as in JSON representations of resources
|
14
|
+
|
15
|
+
e.g.
|
16
|
+
{"id":"U3OFSP23", "name": "Joe User", "active":true}
|
17
|
+
|
18
|
+
## Installation
|
19
|
+
|
20
|
+
Add this line to your application's Gemfile:
|
21
|
+
|
22
|
+
gem 'cloak_id'
|
23
|
+
|
24
|
+
And then execute:
|
25
|
+
|
26
|
+
$ bundle
|
27
|
+
|
28
|
+
Or install it yourself as:
|
29
|
+
|
30
|
+
$ gem install cloak_id
|
31
|
+
|
32
|
+
To seed the random key used during cloaking, execute the following command
|
33
|
+
|
34
|
+
rails generate cloak_id::install
|
35
|
+
|
36
|
+
This command is optional, but if it not done then your application will make use of a global default key, leading to identical
|
37
|
+
cloaked ids being generated for identically named models. This value will be used to seed the transformation, and help ensure
|
38
|
+
the quality of the obfuscation.
|
39
|
+
|
40
|
+
## Usage
|
41
|
+
|
42
|
+
In your model files (that inherit from Active Record) add the 'cloak_id' call to enable the obfuscating of the ids.
|
43
|
+
|
44
|
+
class User < ActiveRecord::Base
|
45
|
+
cloak_id
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
## What the gem does
|
50
|
+
|
51
|
+
The cloak_id gem provides a way to "cloak" your identifiers. This doesn't provide any real security, but instead makes it
|
52
|
+
less obvious to what the underlying database id of a resource really is. This is desirable in cases where you want to make
|
53
|
+
it more difficult for users to guess identifiers, or for observers to judge how fast your application is growing.
|
54
|
+
|
55
|
+
This technique does not provide any real security - it only makes it more difficult for casual observers to guess resource
|
56
|
+
identifiers.
|
57
|
+
|
58
|
+
The logic behind the transformation is to use a prime number to generate a simple, yet reversible hash of the database id.
|
59
|
+
That newly obscured hash is then encoded as a string, to further hide its meaning. This will happen when generating forms
|
60
|
+
from ActiveRecords (when using the entire object or the to_param call), as well as when encoding the object in JSON format.
|
61
|
+
|
62
|
+
|
63
|
+
## Customizing your cloaking
|
64
|
+
|
65
|
+
The cloak_id method call takes an option hash that controls how the cloaking occurs:
|
66
|
+
<p>
|
67
|
+
<table>
|
68
|
+
<tr>
|
69
|
+
<th>option name</th>
|
70
|
+
<th>description</th>
|
71
|
+
</tr>
|
72
|
+
<tr>
|
73
|
+
<td>key</td>
|
74
|
+
<td>The key to use for obfuscating identifier. This can be either an integer or a string. This is an optional
|
75
|
+
parameter. If the user does not provide one, then this will be based on the name of the model itself. This
|
76
|
+
means that "common" models (e.g. User) may have the same obfuscated values across different web apps. This
|
77
|
+
can be prevented by passing in a value to this parameter or by setting a seed (by running rails generate cloak_id install)
|
78
|
+
</td>
|
79
|
+
</tr>
|
80
|
+
<tr>
|
81
|
+
<td> prefix </td>
|
82
|
+
<td> Obfuscating the identifier results in a string. To better help developers understand what type of resource
|
83
|
+
if being identified, an optional prefix may be specified. If none is provided, then a prefix will be created
|
84
|
+
based on the name of the model. This prefix must start with a alphabetic character.
|
85
|
+
</td>
|
86
|
+
</tr>
|
87
|
+
</table>
|
88
|
+
|
89
|
+
###Examples
|
90
|
+
This line will result in a cloaked identifier encoded with a specifically defined key, and the resulting cloaked identifier
|
91
|
+
will begin with UU.
|
92
|
+
|
93
|
+
cloak_id key:'SampleKey', prefix:'UU'
|
94
|
+
|
95
|
+
After this call, most instances of the id will be replaced with the cloaked id. This includes calls to serialize the
|
96
|
+
resoruce as XML or JSON, as well as when it is used as a parameter in forms, or in RESTful API calls. The "id" call will
|
97
|
+
not change, as this gem does not actually change the underlying id of the object. Just how it is displayed.
|
98
|
+
|
99
|
+
To access the cloaked id directly, you may call
|
100
|
+
self.cloaked_id
|
101
|
+
|
102
|
+
Retrieving objects from the database from cloaked ids can happen in two ways
|
103
|
+
|
104
|
+
ClassName.find(cloaked_id)
|
105
|
+
or
|
106
|
+
|
107
|
+
ClassName.find_by_cloaked_id(cloaked_id)
|
108
|
+
|
109
|
+
Objects that have had their id cloaked will use a "smart" find method. This method will inspect the id to see if it
|
110
|
+
appears to be a cloaked id (all cloaked ids must start with an alphabetic character), and if it does, then it will use
|
111
|
+
the find_by_cloaked_id method. If it is numeric, or if it does not begin with the expected prefix, then it will fall
|
112
|
+
back to use the out-of-the-box find functionality.
|
113
|
+
|
114
|
+
Because of this logic, most cases will work without any changes to the code that makes use of the cloaked resource.
|
115
|
+
|
116
|
+
## Contributing
|
117
|
+
|
118
|
+
1. Fork it ( https://github.com/elleleb/cloak_id/fork )
|
119
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
120
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
121
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
122
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/cloak_id.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'cloak_id/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "cloak_id"
|
8
|
+
spec.version = CloakId::VERSION
|
9
|
+
spec.authors = ["Elle L."]
|
10
|
+
spec.email = ["elle@pandromos.com"]
|
11
|
+
spec.summary = %q{Gem to help obfuscate Active Record ids.}
|
12
|
+
spec.description = %q{The cloak id gem allows developers to easily hide the actual (database) id of resources through obfuscation.}
|
13
|
+
spec.homepage = "https://github.com/elleleb/cloak_id"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_runtime_dependency "rails","> 4.0"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "rspec"
|
26
|
+
spec.add_development_dependency "sqlite3"
|
27
|
+
spec.add_development_dependency "pry"
|
28
|
+
spec.add_development_dependency "simplecov"
|
29
|
+
spec.add_development_dependency "generator_spec"
|
30
|
+
end
|
data/lib/cloak_id.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
require "cloak_id/version"
|
2
|
+
require "active_record"
|
3
|
+
require "zlib"
|
4
|
+
require "cloak_id/cloak_id_encoder"
|
5
|
+
require "cloak_id/errors"
|
6
|
+
|
7
|
+
module CloakId
|
8
|
+
|
9
|
+
# The main entry point for cloak_id This will cause the Active Record that this is called in to be able to
|
10
|
+
# cloak it's ids.
|
11
|
+
#
|
12
|
+
# options:
|
13
|
+
# :prefix : All cloaked ids will begin with this prefix. If none is provided, then the letter 'X' will be used
|
14
|
+
# :key : The key that will be used to do the obfuscation. If none is provided, then the obfuscation will use
|
15
|
+
# a key based on the model name. This could result in multiple applications cloaking ids in the same way.
|
16
|
+
def cloak_id(options = {})
|
17
|
+
cattr_accessor :cloak_id_prefix, :cloak_id_key
|
18
|
+
self.cloak_id_prefix = (options[:prefix] || model_name.singular.split('_').slice(0,2).inject('') {|prefix,word| prefix + word[0,1].upcase})
|
19
|
+
|
20
|
+
raise CloakingError, 'Prefix values must start with a letter.' unless (/^[A-Za-z]/ =~ self.cloak_id_prefix)
|
21
|
+
|
22
|
+
key = options[:key]
|
23
|
+
|
24
|
+
if (!key.nil? and key.is_a? String)
|
25
|
+
key = Zlib::crc32(key)
|
26
|
+
end
|
27
|
+
|
28
|
+
self.cloak_id_key = key
|
29
|
+
|
30
|
+
alias_method :old_serializable_hash, :serializable_hash
|
31
|
+
extend ClassMethods
|
32
|
+
include InstanceMethods
|
33
|
+
end
|
34
|
+
|
35
|
+
module InstanceMethods
|
36
|
+
|
37
|
+
# Return the id for the object in cloaked form. If the id is nil, then this method will also return nil.
|
38
|
+
def cloaked_id
|
39
|
+
if self.id.nil?
|
40
|
+
nil
|
41
|
+
else
|
42
|
+
self.class.cloaked_id_for(self.id)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_param
|
47
|
+
self.cloaked_id
|
48
|
+
end
|
49
|
+
|
50
|
+
def serializable_hash (options = nil)
|
51
|
+
attribute_hash = self.old_serializable_hash (options)
|
52
|
+
attribute_hash['id'] = self.cloaked_id
|
53
|
+
|
54
|
+
#now we want to cloak any fk ids that have been cloaked
|
55
|
+
self.class.reflections.values.each do |association|
|
56
|
+
if association.klass.respond_to? :cloaked_id_for
|
57
|
+
# this is a related item that has been cloaked
|
58
|
+
if attribute_hash.has_key? association.foreign_key
|
59
|
+
attribute_hash[association.foreign_key] = association.klass.cloaked_id_for(attribute_hash[association.foreign_key])
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
attribute_hash
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
module ClassMethods
|
68
|
+
|
69
|
+
# class method to create the cloaked id. This will be used when generation a new cloaked id either on demand by the
|
70
|
+
# object itself, or when an association wants to hide the id.
|
71
|
+
|
72
|
+
def cloaked_id_for(id)
|
73
|
+
"#{self.cloak_id_prefix}#{CloakIdEncoder.cloak_mod_35(id, self.cloaking_key)}"
|
74
|
+
end
|
75
|
+
|
76
|
+
# Return the key that we're going to use in the twiddle function during reversible hashing
|
77
|
+
# this value will be based on the CRC check sum of the model name unless one is provided by
|
78
|
+
# the user.
|
79
|
+
def cloaking_key
|
80
|
+
self.cloak_id_key = Zlib::crc32(self.model_name)^CloakIdEncoder.cloak_id_default_key unless self.cloak_id_key.is_a? Integer
|
81
|
+
self.cloak_id_key
|
82
|
+
end
|
83
|
+
|
84
|
+
# Perform a find request based on the cloaked id. This command will verify that the cloaked id is in the
|
85
|
+
# correct format to allow it to be found. If it is not then the requested record cannot be found, and a
|
86
|
+
# RecordNotFound error will be raised.
|
87
|
+
def find_by_cloaked_id(cloaked_id,options={})
|
88
|
+
# make sure this is a valid id
|
89
|
+
raise new ActiveRecord::RecordNotFound("Cloaked Id does not have a valid format.") unless cloaked_id.start_with? self.cloak_id_prefix
|
90
|
+
|
91
|
+
decloaked_id = decloak_id_for_class(cloaked_id)
|
92
|
+
self.find(decloaked_id)
|
93
|
+
end
|
94
|
+
|
95
|
+
def decloak_id_for_class(cloaked_id)
|
96
|
+
CloakIdEncoder.decloak_mod_35(cloaked_id[self.cloak_id_prefix.length..-1], self.cloaking_key)
|
97
|
+
end
|
98
|
+
|
99
|
+
## method to check whether an id might be a valid cloaked id.
|
100
|
+
def is_cloaked_id?(id)
|
101
|
+
id.is_a? String and id.starts_with? self.cloak_id_prefix
|
102
|
+
end
|
103
|
+
|
104
|
+
# This is a "Smart" version of the find method. It takes a look at the id, and figures out if it might be a cloaked
|
105
|
+
# id. If it is, then it will perform the search with the decloaked value. Otherwise it will treat it as a "normal"
|
106
|
+
# identifier.
|
107
|
+
def find(arg)
|
108
|
+
arg_is_cloaked_id = is_cloaked_id? arg
|
109
|
+
if arg_is_cloaked_id
|
110
|
+
find_by_cloaked_id arg
|
111
|
+
elsif arg.is_a? Array
|
112
|
+
arg_list = arg.map do |entry|
|
113
|
+
if is_cloaked_id? entry
|
114
|
+
decloak_id_for_class entry
|
115
|
+
else
|
116
|
+
entry
|
117
|
+
end
|
118
|
+
end
|
119
|
+
super arg_list
|
120
|
+
else
|
121
|
+
super arg
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
ActiveRecord::Base.extend CloakId
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module CloakId
|
2
|
+
# The CloakIdEncoder is a support class that helps with the cloaking process. It provides the base functionality
|
3
|
+
# need to do the hashing back and forth from the numeric identifier, as well as the mechanism to turn this into
|
4
|
+
# a cloaked id.
|
5
|
+
class CloakIdEncoder
|
6
|
+
TWIDDLE_PRIME = 0x20C8592B
|
7
|
+
|
8
|
+
cattr_accessor :cloak_id_default_key
|
9
|
+
|
10
|
+
# Basic hashing function to go back and forth between a hashed value.
|
11
|
+
def self.twiddle(val,key)
|
12
|
+
hashed = (key ^ val) * TWIDDLE_PRIME
|
13
|
+
hashed >> (hashed & 0x0f) & 0xffff
|
14
|
+
end
|
15
|
+
|
16
|
+
# Take the integer and cloak it as another integer. This is a reversible function, and cloak(cloak(X)) = X
|
17
|
+
def self.cloak(id,key=CloakIdEncoder.cloak_id_default_key)
|
18
|
+
raise "Id must be an integer to cloak properly" unless id.is_a? Integer
|
19
|
+
|
20
|
+
low = id & 0xffff
|
21
|
+
high = ((id >> 16) & 0xffff) ^ twiddle(low,key)
|
22
|
+
((low ^ twiddle(high,key||0)) << 16) + high
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
# This calls the cloak method, but instead of returning the integer value, it will return it as a base 36 string.
|
27
|
+
# Unlike the main cloak method, this is not reversible in the same way. To reverse the action of cloaking, the
|
28
|
+
# decloak_base36 method must be used instead.
|
29
|
+
def self.cloak_base36(id,key=CloakIdEncoder.cloak_id_default_key)
|
30
|
+
self.cloak(id,key||0).to_s(36).upcase
|
31
|
+
end
|
32
|
+
|
33
|
+
# This method reverses the cloaking procedure for when the ID has been cloaked using the base36 technique. It is
|
34
|
+
# important to know that this method does not handle the stripping any prefix that might have been added. It acts
|
35
|
+
# only as an inverse function to the cloak_base36 function
|
36
|
+
def self.decloak_base36(cloaked_id, key=CloakIdEncoder.cloak_id_default_key)
|
37
|
+
id = cloaked_id.downcase.to_i(36)
|
38
|
+
cloak(id,key||0)
|
39
|
+
end
|
40
|
+
|
41
|
+
# The modified base 35 encoding eliminates Z's from appearing normallty. This was we can make cloaked id's at least
|
42
|
+
# a given length. This can help make the representation of the cloaked id, a little bit more homogenized.
|
43
|
+
def self.cloak_mod_35(id, key=CloakIdEncoder.cloak_id_default_key, min_len=7)
|
44
|
+
intermediate_id = self.cloak(id,key||0).to_s(35).upcase
|
45
|
+
if intermediate_id.length < min_len
|
46
|
+
"#{"Z"*(min_len-intermediate_id.length)}#{intermediate_id}"
|
47
|
+
else
|
48
|
+
intermediate_id
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Preform the decloaking operation for the modified base 35 encoding scheme. This will remove the "Z" characters that
|
53
|
+
# only serve to be place holders, then turn it back into a number and decloak it.
|
54
|
+
def self.decloak_mod_35(id,key=CloakIdEncoder.cloak_id_default_key)
|
55
|
+
intermediate_id = id.slice(/[0-9A-Y]+/).downcase.to_i(35)
|
56
|
+
self.cloak(intermediate_id,key)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require File.dirname(__FILE__)+'/../../lib/cloak_id/cloak_id_encoder'
|
3
|
+
|
4
|
+
describe CloakId::CloakIdEncoder do
|
5
|
+
|
6
|
+
it 'should raise exception when the id to be cloaked is not numeric' do
|
7
|
+
expect {CloakId::CloakIdEncoder.cloak(id)}.to raise_error
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should return the base id after cloaking twice' do
|
11
|
+
cloaked_v = CloakId::CloakIdEncoder.cloak(10000,0)
|
12
|
+
expect(CloakId::CloakIdEncoder.cloak(cloaked_v,0)).to eql 10000
|
13
|
+
|
14
|
+
cloaked_v = CloakId::CloakIdEncoder.cloak(0xffff25,0x1234)
|
15
|
+
expect(CloakId::CloakIdEncoder.cloak(cloaked_v,0x1234)).to eql 0xffff25
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should be able to perform a base 36 encoding and decoding successfully' do
|
19
|
+
cloaked_v = CloakId::CloakIdEncoder.cloak_base36(10000,0)
|
20
|
+
decloaked_v = CloakId::CloakIdEncoder.decloak_base36(cloaked_v,0)
|
21
|
+
expect(decloaked_v).to eql 10000
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should enforce a minimum length when using modified base 35 encoding' do
|
25
|
+
cloaked_v = CloakId::CloakIdEncoder.cloak_mod_35(10000,0,40)
|
26
|
+
|
27
|
+
expect(cloaked_v).to include 'ZZZZZZZZZ'
|
28
|
+
expect(cloaked_v).to have(40).characters
|
29
|
+
|
30
|
+
decloaked_id = CloakId::CloakIdEncoder.decloak_mod_35(cloaked_v,0)
|
31
|
+
expect(decloaked_id).to eql 10000
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
it 'should not modify the cloaked id if the cloaked value is already long enough' do
|
36
|
+
cloaked_v = CloakId::CloakIdEncoder.cloak_mod_35(10000,0,5)
|
37
|
+
expect(cloaked_v).to_not include 'Z'
|
38
|
+
expect(cloaked_v).to have_at_least(5).characters
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should make use of the configured default key when none is provided' do
|
42
|
+
key_0_cloaked = CloakId::CloakIdEncoder.cloak_mod_35(1234)
|
43
|
+
CloakId::CloakIdEncoder.cloak_id_default_key = 4321
|
44
|
+
key_4321_cloaked = CloakId::CloakIdEncoder.cloak_mod_35(1234)
|
45
|
+
key_4321_decloaked = CloakId::CloakIdEncoder.decloak_mod_35(key_4321_cloaked)
|
46
|
+
|
47
|
+
expect(key_4321_decloaked).to eql 1234
|
48
|
+
expect(key_0_cloaked).to_not eql key_4321_cloaked
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
|
4
|
+
## helper to make a simple model
|
5
|
+
def make_model
|
6
|
+
test_model = TestModel.new
|
7
|
+
test_model.id = 1234
|
8
|
+
test_model.test_field='This is a test field'
|
9
|
+
|
10
|
+
test_model
|
11
|
+
end
|
12
|
+
|
13
|
+
describe CloakId do
|
14
|
+
|
15
|
+
it 'should provide the obfuscated id on an active record' do
|
16
|
+
test_model = make_model()
|
17
|
+
cloaked_id = test_model.cloaked_id
|
18
|
+
|
19
|
+
expect(cloaked_id).to start_with 'TM'
|
20
|
+
decloaked_id = CloakId::CloakIdEncoder.decloak_mod_35(cloaked_id[2..-1],test_model.cloak_id_key)
|
21
|
+
expect(decloaked_id).to eql test_model.id
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should use the specified prefix on the front of the cloaked id' do
|
25
|
+
test_model = TestModel.create
|
26
|
+
test_association = TestAssociation.create
|
27
|
+
|
28
|
+
expect(test_model.cloaked_id).to start_with 'TM' #default
|
29
|
+
expect(test_association.cloaked_id).to start_with 'L'
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should be able to convert a string key into a numeric key for cloaking' do
|
33
|
+
expect(TestModel2.cloaking_key).to eql Zlib::crc32('my_key')
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should raise an error when using a prefix that does not start with a letter' do
|
37
|
+
sample_model = UncloakedModel.new
|
38
|
+
expect do
|
39
|
+
class << sample_model
|
40
|
+
cloak_id prefix:'123'
|
41
|
+
end
|
42
|
+
end.to raise_error(CloakId::CloakingError)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should return null for the cloaked id when there is no id in place' do
|
46
|
+
model = TestModel.new
|
47
|
+
|
48
|
+
expect(model.cloaked_id).to be_nil
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should allow the user to used the find_by_cloaked_id method to use the cloaked id to find the resource' do
|
52
|
+
model = TestModel.create
|
53
|
+
cloaked_id = model.cloaked_id
|
54
|
+
|
55
|
+
found_model = TestModel.find_by_cloaked_id(cloaked_id)
|
56
|
+
expect(found_model).to eql model
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should provide the cloaked id when converting the resource into a parameter' do
|
60
|
+
model = make_model()
|
61
|
+
expect(model.to_param).to eql model.cloaked_id
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should include the cloaked id in the json that is produced for the resource' do
|
65
|
+
test_model = make_model()
|
66
|
+
model_json = test_model.as_json
|
67
|
+
expect(model_json['id']).to eql test_model.cloaked_id
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
it 'should handle cloaking ids with the JSON as it bubbles up' do
|
72
|
+
model = TestModel.create
|
73
|
+
association = TestAssociation.create
|
74
|
+
|
75
|
+
model.test_associations << association
|
76
|
+
model.test_associations << TestAssociation.create
|
77
|
+
model_json = model.as_json(include: :test_associations)
|
78
|
+
|
79
|
+
# This is ugly, but since this is a single association on the model, we want the first item in the "has many"
|
80
|
+
expect(model_json['test_associations'][0]['id']).to eql association.cloaked_id
|
81
|
+
expect(model_json['test_associations'][0]['test_model_id']).to eql model.cloaked_id
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'should use the right find technique when presented with the cloaked vs. the decloaked id.' do
|
85
|
+
model = TestModel.create
|
86
|
+
model_2 = TestModel.create
|
87
|
+
expect(TestModel.find(model.id)).to eql model
|
88
|
+
expect(TestModel.find(model.cloaked_id)).to eql model
|
89
|
+
expect(TestModel.find([model.id, model_2.id])).to eql [model,model_2]
|
90
|
+
expect(TestModel.find([model.cloaked_id, model_2.cloaked_id])).to eql [model,model_2]
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# Spec file for the installation generator.
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'generator_spec/test_case'
|
4
|
+
require 'generators/cloak_id/install/install_generator'
|
5
|
+
|
6
|
+
describe CloakId::Generators::InstallGenerator do
|
7
|
+
include GeneratorSpec::TestCase
|
8
|
+
destination File.expand_path('../../tmp',__FILE__)
|
9
|
+
|
10
|
+
before (:all) do
|
11
|
+
SecureRandom.stub(:random_number).and_return(12345)
|
12
|
+
prepare_destination
|
13
|
+
run_generator
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should create an initializer' do
|
17
|
+
# We're going to check to make sure that the file has the expected key in it.
|
18
|
+
assert_file 'config/initializers/cloak_id.rb',/CloakId::CloakIdEncoder.cloak_id_default_key = 12345/
|
19
|
+
end
|
20
|
+
end
|
data/spec/schema.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
ActiveRecord::Schema.define do
|
2
|
+
self.verbose = false
|
3
|
+
|
4
|
+
create_table :test_models, :force => true do |t|
|
5
|
+
t.string :test_field
|
6
|
+
end
|
7
|
+
|
8
|
+
create_table :test_associations, :force => true do |t|
|
9
|
+
t.references :test_model
|
10
|
+
end
|
11
|
+
|
12
|
+
create_table :uncloaked_models, :force => true do |t|
|
13
|
+
t.string :extra_field_for_sample
|
14
|
+
end
|
15
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'active_record'
|
6
|
+
require 'pry'
|
7
|
+
|
8
|
+
Bundler.setup
|
9
|
+
require 'cloak_id' # and any other gems you need
|
10
|
+
require 'support/test_model'
|
11
|
+
require 'zlib'
|
12
|
+
|
13
|
+
RSpec.configure do |config|
|
14
|
+
config.debug = false
|
15
|
+
config.color_enabled = true
|
16
|
+
config.formatter = 'documentation'
|
17
|
+
end
|
18
|
+
|
19
|
+
ActiveRecord::Base.establish_connection adapter:'sqlite3', database:':memory:'
|
20
|
+
|
21
|
+
load File.dirname(__FILE__) + '/schema.rb'
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# Here lie model classes that help with the unit tests. We'll use these to create certain sub-categories of
|
2
|
+
# models that have cloaked ids.
|
3
|
+
class TestModel < ActiveRecord::Base
|
4
|
+
cloak_id
|
5
|
+
has_many :test_associations
|
6
|
+
end
|
7
|
+
|
8
|
+
class TestAssociation < ActiveRecord::Base
|
9
|
+
cloak_id prefix:'L'
|
10
|
+
belongs_to :test_model
|
11
|
+
end
|
12
|
+
|
13
|
+
class UncloakedModel < ActiveRecord::Base
|
14
|
+
end
|
15
|
+
|
16
|
+
class TestModel2 < ActiveRecord::Base
|
17
|
+
self.table_name = 'test_models'
|
18
|
+
|
19
|
+
cloak_id key:'my_key'
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,182 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cloak_id
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Elle L.
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-05-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.6'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.6'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: sqlite3
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pry
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: simplecov
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: generator_spec
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description: The cloak id gem allows developers to easily hide the actual (database)
|
126
|
+
id of resources through obfuscation.
|
127
|
+
email:
|
128
|
+
- elle@pandromos.com
|
129
|
+
executables: []
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- ".gitignore"
|
134
|
+
- Gemfile
|
135
|
+
- LICENSE
|
136
|
+
- LICENSE.txt
|
137
|
+
- README.md
|
138
|
+
- Rakefile
|
139
|
+
- cloak_id.gemspec
|
140
|
+
- lib/cloak_id.rb
|
141
|
+
- lib/cloak_id/cloak_id_encoder.rb
|
142
|
+
- lib/cloak_id/errors.rb
|
143
|
+
- lib/cloak_id/version.rb
|
144
|
+
- lib/generators/cloak_id/install/install_generator.rb
|
145
|
+
- lib/generators/cloak_id/install/templates/cloak_id.rb.erb
|
146
|
+
- spec/cloak_id/cloak_id_encoder_spec.rb
|
147
|
+
- spec/cloak_id/cloak_id_spec.rb
|
148
|
+
- spec/cloak_id/install_generator_spec.rb
|
149
|
+
- spec/schema.rb
|
150
|
+
- spec/spec_helper.rb
|
151
|
+
- spec/support/test_model.rb
|
152
|
+
homepage: https://github.com/elleleb/cloak_id
|
153
|
+
licenses:
|
154
|
+
- MIT
|
155
|
+
metadata: {}
|
156
|
+
post_install_message:
|
157
|
+
rdoc_options: []
|
158
|
+
require_paths:
|
159
|
+
- lib
|
160
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
161
|
+
requirements:
|
162
|
+
- - ">="
|
163
|
+
- !ruby/object:Gem::Version
|
164
|
+
version: '0'
|
165
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
166
|
+
requirements:
|
167
|
+
- - ">="
|
168
|
+
- !ruby/object:Gem::Version
|
169
|
+
version: '0'
|
170
|
+
requirements: []
|
171
|
+
rubyforge_project:
|
172
|
+
rubygems_version: 2.2.2
|
173
|
+
signing_key:
|
174
|
+
specification_version: 4
|
175
|
+
summary: Gem to help obfuscate Active Record ids.
|
176
|
+
test_files:
|
177
|
+
- spec/cloak_id/cloak_id_encoder_spec.rb
|
178
|
+
- spec/cloak_id/cloak_id_spec.rb
|
179
|
+
- spec/cloak_id/install_generator_spec.rb
|
180
|
+
- spec/schema.rb
|
181
|
+
- spec/spec_helper.rb
|
182
|
+
- spec/support/test_model.rb
|