elm_history_tools 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: 9a30716f0cdb540ad8228ec16e82e2e42d1a000f
4
- data.tar.gz: 3163afcb272e617cce11a7c4afdfea92db1d801d
3
+ metadata.gz: 280b6456a5473cc4af85ebe96dd303466bf1341d
4
+ data.tar.gz: d18f4ff0a50be7ddd539b0198b51d05415812151
5
5
  SHA512:
6
- metadata.gz: d34ce51ff0aa23cbc2dae2d6dbd5ee5a7cd568e02cd222a43cdf93e049998c5015d1d2be3f3af68ba12701d4b0c7bbc3f0e4a4c6225e60613b36dc83680bed7e
7
- data.tar.gz: b1d356d39c1434c69b5520006bd1be06c39efde94ee8ff0096d008f2334371ef683d6cb9d18817a84eef0aa63af69b32dc10cce668768c62da06f92564a42679
6
+ metadata.gz: b9e621128a84df9e1ee1c84369cff2bb46c6fe7800a664b40ac3f64dc58efa028b140229f2b20693ab7390e3e3e02c319d980416316c0acc0f832c52283a460e
7
+ data.tar.gz: 9e7c57afa830d958f2fcce36d9894227605d4afb810a32f6ff679211068327e4c29cb37dc99e60651e8139bacaeadd8912bf1655f316fc7e5c37bc5a8fc3bf75
data/Gemfile.lock CHANGED
@@ -1,12 +1,11 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- elm_history_tools (0.1.0)
4
+ elm_history_tools (0.2.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
- awesome_print (1.8.0)
10
9
  byebug (10.0.2)
11
10
  diff-lcs (1.3)
12
11
  rake (10.5.0)
@@ -28,7 +27,6 @@ PLATFORMS
28
27
  ruby
29
28
 
30
29
  DEPENDENCIES
31
- awesome_print
32
30
  bundler (~> 1.16)
33
31
  byebug
34
32
  elm_history_tools!
data/README.md CHANGED
@@ -38,17 +38,46 @@ _Sanitizing History_
38
38
 
39
39
  How do you actually do this?
40
40
 
41
- COMING SOON
41
+ `ElmHistoryTools::HistorySanitizer` allows you to sanitize Elm records and objects. Because these
42
+ objects are so individual to your application, there's no universal code that can sanitize the data
43
+ without breaking the structure. (This is why we can't, for instance, just use something like Rails'
44
+ ParameterFilters.)
45
+
46
+ Instead, you tell the HistorySanitizer which terms to look for and provide a block that can do the
47
+ sanitizing:
48
+
49
+ ```ruby
50
+ # let's say you have an Elm object like
51
+ # MySecretData "access_token" {password = "myP@ssw0rd", expiration = "tomorrow"}
52
+ ElmHistoryTools::HistorySanitizer.sanitize_history(history_data, ["MySecretData", "password"]) do |elm_object_or_record|
53
+ if elm_object_or_record["ctor"] == "MySecretData"
54
+ # replace the access token
55
+ # make sure to return the updated object!
56
+ elm_object_or_record.merge("_0" =>"[FILTERED]")
57
+ else
58
+ # we have the record
59
+ elm_object_or_record.merge("password" => "[FILTERED]")
60
+ end
61
+ end
62
+
63
+ # before: {"ctor" => "MySecretData", "_0" => "access_token", "_1" => {"password" => "myP@ssw0rd", "expiration" => "tomorrow"}}
64
+ # after: {"ctor" => "MySecretData", "_0" => "[FILTERED]", "_1" => {"password" => "[FILTERED]", "expiration" => "tomorrow"}}
65
+ ```
66
+
67
+ As you can see, this is neither flexible nor elegant -- unfortunately, that's the nature of the
68
+ game when you're manipulating the state generated by an Elm program. Elm types may store sensitive data
69
+ as basic data in the object, which leaves no automated way to detect and replace that information.
42
70
 
43
71
  _Format a History into a Hash_
44
72
 
45
- One approach we've found useful for user issues at eSpark Learning has been to skim the
46
- history and see if anything looks amiss. Eyeballing the user's actions shows if a student get an unhappy response back from a
47
- server, if they were able to they answer any quiz questions, etc., providing a useful context for
48
- further debugging.
73
+ One approach we've found useful for user issues at eSpark Learning has been to skim the history and
74
+ see if anything looks amiss. Eyeballing the user's actions shows if a student get an unhappy
75
+ response back from a server, if they were able to they answer any quiz questions, etc., providing a
76
+ useful context for further debugging.
49
77
 
50
- In order to make sense of data, it has to be presented in a comprehensible format. That's what the
51
- `ElmHistoryTools::HistoryFormatter` object can do:
78
+ In order to make sense of data, it has to be presented in a compact and (relatively) human-readable
79
+ format, which the raw history export is not. (See below for more information on that.) Such cleanup
80
+ is what the `ElmHistoryTools::HistoryFormatter` object can do:
52
81
 
53
82
  ```ruby
54
83
  # given a JSON history file like
@@ -1,5 +1,7 @@
1
1
  require "elm_history_tools/version"
2
+ require "elm_history_tools/utils"
2
3
  require "elm_history_tools/history_formatter"
4
+ require "elm_history_tools/history_sanitizer"
3
5
 
4
6
  module ElmHistoryTools
5
7
  # Your code goes here...
@@ -20,23 +20,23 @@ module ElmHistoryTools::HistoryFormatter
20
20
  # alternative approach would be to use nil. While that would clearly distinguish between those
21
21
  # cases, it would make working with the results more complicated.
22
22
  def self.simplify_history_entry(entry)
23
- if !entry.is_a?(Hash)
24
- entry
25
- elsif entry["ctor"] == "::"
26
- # Elm lists are represented as nested entries with the contructor ::. (See the readme for
27
- # more detail.)
28
- # We collapse those into a proper Ruby array via flatten.
29
- # The last entry of the list will have no nested entry, so we use compact to remove the nil.
30
- [simplify_history_entry(entry["_0"]), simplify_history_entry(entry["_1"])].compact.flatten
31
- elsif entry["ctor"]
32
- # we have an Elm type
33
- {
34
- entry["ctor"] => entry.reject {|k, _v| k == "ctor"}.values.map {|val| simplify_history_entry(val) }
35
- }
36
- else
37
- entry.each_with_object({}) do |(key, value), hash|
38
- hash[key] = simplify_history_entry(value)
23
+ ElmHistoryTools::Utils.transform_object(entry) do |object_hash|
24
+ if object_hash["ctor"] == "::"
25
+ # Elm lists are represented as nested entries with the contructor ::. (See the readme for
26
+ # more detail.)
27
+ # We collapse those into a proper Ruby array via flatten.
28
+ # The last entry of the list will have no nested entry, so we use compact to remove the nil.
29
+ [simplify_history_entry(object_hash["_0"]), simplify_history_entry(object_hash["_1"])].compact.flatten
30
+ elsif object_hash["ctor"]
31
+ # we have an Elm object type (we know this because non-objects aren't passed to the block)
32
+ {
33
+ object_hash["ctor"] => object_hash.reject {|k, _v| k == "ctor"}.values.map {|val| simplify_history_entry(val) }
34
+ }
39
35
  end
40
36
  end
41
37
  end
38
+
39
+ class << self
40
+ private :simplify_history_entry
41
+ end
42
42
  end
@@ -0,0 +1,68 @@
1
+ module ElmHistoryTools::HistorySanitizer
2
+ # We don't include the object received because it might have sensitive information
3
+ # This might be reconsidered in the future.
4
+ class ElmObjectExpected < StandardError; end
5
+
6
+ # Given a raw Elm history file parsed to JSON, a set of key terms to watch for (password, token,
7
+ # etc.) , sanitize a
8
+ # history so it doesn't contain sensitive information.
9
+ #
10
+ # The message sanitizers should be in the format of
11
+ # "HistoryConstructor" => block_taking_history_entry_and_returning_sanitized_version
12
+ #
13
+ # You can optionally specify a set of watch words to ensure you don't accidentally
14
+ # introduce unsafe data. If specified and any unsanitized entry contains a constructor or record
15
+ # property matching one of those words, an error will be raised. (So for instance, if you add a
16
+ # User object with an Password property to a message, a watch word for "password" will catch
17
+ # that -- though it would also catch password_reset_requested.)
18
+ #
19
+ # Each app will have such different entry structures that there's no easy to generalize this
20
+ # further.
21
+ def self.sanitize_history(history_data, watch_words, &sanitizing_block)
22
+ matchers = watch_words.map {|word| word.is_a?(Regexp) ? word : Regexp.new(word, true)}
23
+ history_data.dup.merge(
24
+ "history" => history_data["history"].map {|entry| sanitize(entry, matchers, &sanitizing_block)}
25
+ )
26
+ end
27
+
28
+ # For each entry, we sanitize it if there's a matching handler. If there is no sanitizer, see if
29
+ # any data in the entry merit flagging against a watch word.
30
+ def self.sanitize(entry, matchers, &sanitizing_block)
31
+ ElmHistoryTools::Utils.transform_object(entry) do |elm_object|
32
+ constructor = elm_object["ctor"]
33
+ raise ElmObjectExpected unless constructor
34
+
35
+ # replace any records that need it
36
+ cleaned_object = elm_object.each_with_object({}) do |(key, value), hash|
37
+ if value.is_a?(Hash) && value["ctor"]
38
+ # we have an elm object -- sanitize that
39
+ hash[key] = sanitize(value, matchers, &sanitizing_block)
40
+ elsif value.is_a?(Hash)
41
+ # We have an Elm record, represented as a raw hash
42
+ # This is inefficient, but should hopefully be acceptable for the relatively low number of records
43
+ # being processed. If not, we can rewrite it.
44
+ if value.keys.any? {|field| matchers.any? {|matcher| field.match(matcher)}}
45
+ hash[key] = yield value
46
+ else
47
+ hash[key] = value
48
+ end
49
+ else
50
+ # raw values
51
+ hash[key] = value
52
+ end
53
+ end
54
+
55
+ # now that we've sanitized any stored records or objects, sanitize the overall record
56
+ if matchers.any? {|matcher| constructor.match(matcher)}
57
+ yield cleaned_object
58
+ else
59
+ cleaned_object
60
+ end
61
+ end
62
+ end
63
+
64
+ class << self
65
+ private :sanitize
66
+ end
67
+ end
68
+
@@ -0,0 +1,22 @@
1
+ module ElmHistoryTools::Utils
2
+ # This method gives us a tool to walk down an Elm history entry, processing all the Elm objects contained within per the provided block. Whatever the block returns will become the value at that point.
3
+ #
4
+ # To simplify matters, this doesn't process primitive values. Additionally, while each value an
5
+ # Elm record (which are stored as plain JSON objects) is examined, the overall record itself is
6
+ # never passed to the block -- to examine/alter an Elm record, do so in the callback for
7
+ # whichever Elm object contains it. (All history entries are Message objects, so there's always
8
+ # an entry point.)
9
+ def self.transform_object(entry, &block)
10
+ if !entry.is_a?(Hash)
11
+ entry
12
+ elsif entry["ctor"]
13
+ # If you want to walk down sub-entries, call transform_object within your block.
14
+ yield entry
15
+ else
16
+ # Each Elm record should be
17
+ entry.each_with_object({}) do |(key, value), hash|
18
+ hash[key] = transform_object(value, &block)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,3 +1,3 @@
1
1
  module ElmHistoryTools
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elm_history_tools
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
  - Alex Koppel
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-04-21 00:00:00.000000000 Z
11
+ date: 2018-06-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -57,6 +57,8 @@ files:
57
57
  - elm_history_tools.gemspec
58
58
  - lib/elm_history_tools.rb
59
59
  - lib/elm_history_tools/history_formatter.rb
60
+ - lib/elm_history_tools/history_sanitizer.rb
61
+ - lib/elm_history_tools/utils.rb
60
62
  - lib/elm_history_tools/version.rb
61
63
  - readme-images/elm-history-dashboard.png
62
64
  homepage: http://github.com/arsduo/elm-history-tools