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 +4 -4
- data/Gemfile.lock +1 -3
- data/README.md +36 -7
- data/lib/elm_history_tools.rb +2 -0
- data/lib/elm_history_tools/history_formatter.rb +16 -16
- data/lib/elm_history_tools/history_sanitizer.rb +68 -0
- data/lib/elm_history_tools/utils.rb +22 -0
- data/lib/elm_history_tools/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 280b6456a5473cc4af85ebe96dd303466bf1341d
|
4
|
+
data.tar.gz: d18f4ff0a50be7ddd539b0198b51d05415812151
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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
|
-
|
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
|
-
|
47
|
-
server, if they were able to they answer any quiz questions, etc., providing a
|
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
|
51
|
-
|
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
|
data/lib/elm_history_tools.rb
CHANGED
@@ -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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
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.
|
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-
|
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
|