elm_history_tools 0.1.0 → 0.2.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 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