console1984 0.1.8 → 0.1.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/config/protections.yml +13 -12
- data/lib/console1984/command_executor.rb +15 -3
- data/lib/console1984/command_validator/.command_parser.rb +69 -0
- data/lib/console1984/command_validator/forbidden_constant_reference_validation.rb +2 -2
- data/lib/console1984/command_validator/forbidden_reopening_validation.rb +2 -2
- data/lib/console1984/command_validator/parsed_command.rb +3 -69
- data/lib/console1984/command_validator/suspicious_terms_validation.rb +1 -1
- data/lib/console1984/command_validator.rb +1 -1
- data/lib/console1984/errors.rb +6 -2
- data/lib/console1984/ext/active_record/protected_auditable_tables.rb +1 -1
- data/lib/console1984/ext/core/module.rb +18 -1
- data/lib/console1984/ext/core/object.rb +1 -1
- data/lib/console1984/freezeable.rb +1 -1
- data/lib/console1984/protections_config.rb +2 -2
- data/lib/console1984/refrigerator.rb +6 -7
- data/lib/console1984/shield/method_invocation_shell.rb +6 -12
- data/lib/console1984/supervisor.rb +9 -0
- data/lib/console1984/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a3bfdacf3954fbc30c23046c89f7951d061c419533685fb5ecc2db6a6cf41a1d
|
4
|
+
data.tar.gz: 0b26c9effb7ffdd1df5ca51c344c53c3d9260bef1b02c4c2d9e6c8949dceb032
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b24bf1fbfc353fdd8b5ac2194f4f30588281269748ea192def291cdedc19697b1bd75bff71baec0761e45ff08ec78aca78ff41d0b78c8ca6ae1cea4bdb43512e
|
7
|
+
data.tar.gz: 43628c0bd4f76662b65f1c343885618a4c54e2b8b40d536720f8c3eecd696ae51c03c78329810387598e68042622b6c6c15f6b015caf3b93d6fb3779bf349f95
|
data/config/protections.yml
CHANGED
@@ -1,12 +1,14 @@
|
|
1
|
-
|
1
|
+
validations:
|
2
2
|
forbidden_reopening:
|
3
3
|
- ActiveRecord
|
4
4
|
- Console1984
|
5
5
|
- PG
|
6
6
|
- Mysql2
|
7
|
+
- IRB
|
7
8
|
forbidden_constant_reference:
|
8
9
|
always:
|
9
10
|
- Console1984
|
11
|
+
- IRB
|
10
12
|
protected:
|
11
13
|
- PG
|
12
14
|
- Mysql2
|
@@ -16,15 +18,14 @@ static_validations:
|
|
16
18
|
- Console1984
|
17
19
|
- secret
|
18
20
|
- credentials
|
21
|
+
- irb
|
19
22
|
forbidden_methods:
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
Module:
|
30
|
-
- class_eval
|
23
|
+
Kernel:
|
24
|
+
- eval
|
25
|
+
Object:
|
26
|
+
- eval
|
27
|
+
BasicObject:
|
28
|
+
- eval
|
29
|
+
- instance_eval
|
30
|
+
Module:
|
31
|
+
- class_eval
|
@@ -19,11 +19,15 @@ class Console1984::CommandExecutor
|
|
19
19
|
run_as_system { session_logger.before_executing commands }
|
20
20
|
validate_command commands
|
21
21
|
execute_in_protected_mode(&block)
|
22
|
-
rescue Console1984::Errors::
|
22
|
+
rescue Console1984::Errors::ForbiddenCommandAttempted, FrozenError
|
23
23
|
flag_suspicious(commands)
|
24
|
-
rescue Console1984::Errors::
|
24
|
+
rescue Console1984::Errors::SuspiciousCommandAttempted
|
25
25
|
flag_suspicious(commands)
|
26
26
|
execute_in_protected_mode(&block)
|
27
|
+
rescue Console1984::Errors::ForbiddenCommandExecuted
|
28
|
+
# We detected that a forbidden command was executed. We exit IRB right away.
|
29
|
+
flag_suspicious(commands)
|
30
|
+
Console1984.supervisor.exit_irb
|
27
31
|
ensure
|
28
32
|
run_as_system { session_logger.after_executing commands }
|
29
33
|
end
|
@@ -65,13 +69,21 @@ class Console1984::CommandExecutor
|
|
65
69
|
command_validator.validate(command)
|
66
70
|
end
|
67
71
|
|
72
|
+
def from_irb?(backtrace)
|
73
|
+
executing_user_command? && backtrace.find do |line|
|
74
|
+
line_from_irb = line =~ /^[^\/]/
|
75
|
+
break if !(line =~ /console1984\/lib/ || line_from_irb)
|
76
|
+
line_from_irb
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
68
80
|
private
|
69
81
|
def command_validator
|
70
82
|
@command_validator ||= build_command_validator
|
71
83
|
end
|
72
84
|
|
73
85
|
def build_command_validator
|
74
|
-
Console1984::CommandValidator.from_config(Console1984.protections_config.
|
86
|
+
Console1984::CommandValidator.from_config(Console1984.protections_config.validations)
|
75
87
|
end
|
76
88
|
|
77
89
|
def flag_suspicious(commands)
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# Naming class with dot so that it doesn't get loaded eagerly by Zeitwork. We want to load
|
2
|
+
# only when a console session is started, when +parser+ is loaded.
|
3
|
+
#
|
4
|
+
# See +Console1984::Supervisor#require_dependencies+
|
5
|
+
class Console1984::CommandValidator::CommandParser < ::Parser::AST::Processor
|
6
|
+
include AST::Processor::Mixin
|
7
|
+
include Console1984::Freezeable
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@constants = []
|
11
|
+
@declared_classes_or_modules = []
|
12
|
+
@constant_assignments = []
|
13
|
+
end
|
14
|
+
|
15
|
+
# We define accessors to define lists without duplicates. We are not using a +SortedSet+ because we want
|
16
|
+
# to mutate strings in the list while the processing is happening. And we don't use metapgroamming to define the
|
17
|
+
# accessors to prevent having problems with freezable and its instance_variable* protection.
|
18
|
+
|
19
|
+
def constants
|
20
|
+
@constants.uniq
|
21
|
+
end
|
22
|
+
|
23
|
+
def declared_classes_or_modules
|
24
|
+
@declared_classes_or_modules.uniq
|
25
|
+
end
|
26
|
+
|
27
|
+
def constant_assignments
|
28
|
+
@constant_assignments.uniq
|
29
|
+
end
|
30
|
+
|
31
|
+
def on_class(node)
|
32
|
+
super
|
33
|
+
const_declaration, _, _ = *node
|
34
|
+
constant = extract_constants(const_declaration).first
|
35
|
+
@declared_classes_or_modules << constant if constant.present?
|
36
|
+
end
|
37
|
+
|
38
|
+
alias_method :on_module, :on_class
|
39
|
+
|
40
|
+
def on_const(node)
|
41
|
+
super
|
42
|
+
name, const_name = *node
|
43
|
+
const_name = const_name.to_s
|
44
|
+
last_constant = @constants.last
|
45
|
+
|
46
|
+
if name.nil? || (name && name.type == :cbase) # cbase = leading ::
|
47
|
+
if last_constant&.end_with?("::")
|
48
|
+
last_constant << const_name
|
49
|
+
else
|
50
|
+
@constants << const_name
|
51
|
+
end
|
52
|
+
elsif last_constant
|
53
|
+
last_constant << "::#{const_name}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def on_casgn(node)
|
58
|
+
super
|
59
|
+
scope_node, name, value_node = *node
|
60
|
+
@constant_assignments.push(*extract_constants(value_node))
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
def extract_constants(node)
|
65
|
+
self.class.new.tap do |processor|
|
66
|
+
processor.process(node)
|
67
|
+
end.constants
|
68
|
+
end
|
69
|
+
end
|
@@ -14,11 +14,11 @@ class Console1984::CommandValidator::ForbiddenConstantReferenceValidation
|
|
14
14
|
@constant_names_forbidden_in_protected_mode = config[:protected] || []
|
15
15
|
end
|
16
16
|
|
17
|
-
# Raises a Console1984::Errors::
|
17
|
+
# Raises a Console1984::Errors::ForbiddenCommandAttempted if a banned constant is referenced.
|
18
18
|
def validate(parsed_command)
|
19
19
|
if contains_invalid_const_reference?(parsed_command, @forbidden_constants_names) ||
|
20
20
|
(@shield.protected_mode? && contains_invalid_const_reference?(parsed_command, @constant_names_forbidden_in_protected_mode))
|
21
|
-
raise Console1984::Errors::
|
21
|
+
raise Console1984::Errors::ForbiddenCommandAttempted
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
@@ -8,11 +8,11 @@ class Console1984::CommandValidator::ForbiddenReopeningValidation
|
|
8
8
|
@banned_class_or_module_names = banned_classes_or_modules.collect(&:to_s)
|
9
9
|
end
|
10
10
|
|
11
|
-
# Raises a Console1984::Errors::
|
11
|
+
# Raises a Console1984::Errors::ForbiddenCommandAttempted if an banned class or module reopening
|
12
12
|
# is detected.
|
13
13
|
def validate(parsed_command)
|
14
14
|
if contains_invalid_class_or_module_declaration?(parsed_command)
|
15
|
-
raise Console1984::Errors::
|
15
|
+
raise Console1984::Errors::ForbiddenCommandAttempted
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
@@ -6,85 +6,19 @@ class Console1984::CommandValidator::ParsedCommand
|
|
6
6
|
|
7
7
|
attr_reader :raw_command
|
8
8
|
|
9
|
-
delegate :declared_classes_or_modules, :constants, :constant_assignments, to: :
|
9
|
+
delegate :declared_classes_or_modules, :constants, :constant_assignments, to: :command_parser
|
10
10
|
|
11
11
|
def initialize(raw_command)
|
12
12
|
@raw_command = Array(raw_command).join("\n")
|
13
13
|
end
|
14
14
|
|
15
15
|
private
|
16
|
-
def
|
17
|
-
@
|
16
|
+
def command_parser
|
17
|
+
@command_parser ||= Console1984::CommandValidator::CommandParser.new.tap do |processor|
|
18
18
|
ast = Parser::CurrentRuby.parse(raw_command)
|
19
19
|
processor.process(ast)
|
20
20
|
rescue Parser::SyntaxError
|
21
21
|
# Fail open with syntax errors
|
22
22
|
end
|
23
23
|
end
|
24
|
-
|
25
|
-
class CommandProcessor < ::Parser::AST::Processor
|
26
|
-
include AST::Processor::Mixin
|
27
|
-
include Console1984::Freezeable
|
28
|
-
|
29
|
-
def initialize
|
30
|
-
@constants = []
|
31
|
-
@declared_classes_or_modules = []
|
32
|
-
@constant_assignments = []
|
33
|
-
end
|
34
|
-
|
35
|
-
# We define accessors to define lists without duplicates. We are not using a +SortedSet+ because we want
|
36
|
-
# to mutate strings in the list while the processing is happening. And we don't use metapgroamming to define the
|
37
|
-
# accessors to prevent having problems with freezable and its instance_variable* protection.
|
38
|
-
|
39
|
-
def constants
|
40
|
-
@constants.uniq
|
41
|
-
end
|
42
|
-
|
43
|
-
def declared_classes_or_modules
|
44
|
-
@declared_classes_or_modules.uniq
|
45
|
-
end
|
46
|
-
|
47
|
-
def constant_assignments
|
48
|
-
@constant_assignments.uniq
|
49
|
-
end
|
50
|
-
|
51
|
-
def on_class(node)
|
52
|
-
super
|
53
|
-
const_declaration, _, _ = *node
|
54
|
-
constant = extract_constants(const_declaration).first
|
55
|
-
@declared_classes_or_modules << constant if constant.present?
|
56
|
-
end
|
57
|
-
|
58
|
-
alias_method :on_module, :on_class
|
59
|
-
|
60
|
-
def on_const(node)
|
61
|
-
super
|
62
|
-
name, const_name = *node
|
63
|
-
const_name = const_name.to_s
|
64
|
-
last_constant = @constants.last
|
65
|
-
|
66
|
-
if name.nil? || (name && name.type == :cbase) # cbase = leading ::
|
67
|
-
if last_constant&.end_with?("::")
|
68
|
-
last_constant << const_name
|
69
|
-
else
|
70
|
-
@constants << const_name
|
71
|
-
end
|
72
|
-
elsif last_constant
|
73
|
-
last_constant << "::#{const_name}"
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
def on_casgn(node)
|
78
|
-
super
|
79
|
-
scope_node, name, value_node = *node
|
80
|
-
@constant_assignments.push(*extract_constants(value_node))
|
81
|
-
end
|
82
|
-
|
83
|
-
private
|
84
|
-
def extract_constants(node)
|
85
|
-
self.class.new.tap do |processor|
|
86
|
-
processor.process(node)
|
87
|
-
end.constants
|
88
|
-
end
|
89
|
-
end
|
90
24
|
end
|
@@ -9,7 +9,7 @@ class Console1984::CommandValidator::SuspiciousTermsValidation
|
|
9
9
|
# Raises a Console1984::Errors::SuspiciousCommand if the term is referenced.
|
10
10
|
def validate(parsed_command)
|
11
11
|
if contains_suspicious_term?(parsed_command)
|
12
|
-
raise Console1984::Errors::
|
12
|
+
raise Console1984::Errors::SuspiciousCommandAttempted
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
@@ -5,7 +5,7 @@
|
|
5
5
|
#
|
6
6
|
# The validation itself happens as a chain of validation objects. The system will invoke
|
7
7
|
# each validation in order. Validations will raise an error if the validation fails (typically
|
8
|
-
# a Console1984::Errors::
|
8
|
+
# a Console1984::Errors::ForbiddenCommandAttempted or Console1984::Errors::SuspiciousCommands).
|
9
9
|
#
|
10
10
|
# Internally, validations will receive a Console1984::CommandValidator::ParsedCommand object. This
|
11
11
|
# exposes parsed constructs in addition to the raw strings so that validations can use those.
|
data/lib/console1984/errors.rb
CHANGED
@@ -10,11 +10,15 @@ module Console1984
|
|
10
10
|
|
11
11
|
# Attempt to execute a command that is not allowed. The system won't
|
12
12
|
# execute such commands and will flag them as sensitive.
|
13
|
-
class
|
13
|
+
class ForbiddenCommandAttempted < StandardError; end
|
14
14
|
|
15
15
|
# A suspicious command was executed. The command will be flagged but the system
|
16
16
|
# will let it run.
|
17
|
-
class
|
17
|
+
class SuspiciousCommandAttempted < StandardError; end
|
18
|
+
|
19
|
+
# A forbidden command was executed. The system will flag the command
|
20
|
+
# and exit.
|
21
|
+
class ForbiddenCommandExecuted < StandardError; end
|
18
22
|
|
19
23
|
# Attempt to incinerate a session ahead of time as determined by
|
20
24
|
# +config.console1984.incinerate_after+.
|
@@ -6,7 +6,7 @@ module Console1984::Ext::ActiveRecord::ProtectedAuditableTables
|
|
6
6
|
define_method method do |*args, **kwargs|
|
7
7
|
sql = args.first
|
8
8
|
if Console1984.command_executor.executing_user_command? && sql =~ auditable_tables_regexp
|
9
|
-
raise Console1984::Errors::
|
9
|
+
raise Console1984::Errors::ForbiddenCommandAttempted, "#{sql}"
|
10
10
|
else
|
11
11
|
super(*args, **kwargs)
|
12
12
|
end
|
@@ -7,9 +7,26 @@ module Console1984::Ext::Core::Module
|
|
7
7
|
|
8
8
|
def instance_eval(*)
|
9
9
|
if Console1984.command_executor.executing_user_command?
|
10
|
-
raise Console1984::Errors::
|
10
|
+
raise Console1984::Errors::ForbiddenCommandAttempted
|
11
11
|
else
|
12
12
|
super
|
13
13
|
end
|
14
14
|
end
|
15
|
+
|
16
|
+
def method_added(method)
|
17
|
+
if Console1984.command_executor.from_irb?(caller) && banned_for_reopening?
|
18
|
+
raise Console1984::Errors::ForbiddenCommandExecuted, "Trying to add method `#{method}` to #{self.name}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def banned_for_reopening?
|
24
|
+
classes_and_modules_banned_for_reopening.find do |banned_class_or_module_name|
|
25
|
+
"#{self.name}::".start_with?("#{banned_class_or_module_name}::")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def classes_and_modules_banned_for_reopening
|
30
|
+
@classes_and_modules_banned_for_reopening ||= Console1984.protections_config.validations[:forbidden_reopening]
|
31
|
+
end
|
15
32
|
end
|
@@ -25,7 +25,7 @@ module Console1984::Ext::Core::Object
|
|
25
25
|
# See the list +forbidden_reopening+ in +config/command_protections.yml+.
|
26
26
|
Console1984.command_executor.validate_command("class #{arguments.first}; end")
|
27
27
|
super
|
28
|
-
rescue Console1984::Errors::
|
28
|
+
rescue Console1984::Errors::ForbiddenCommandAttempted
|
29
29
|
raise
|
30
30
|
rescue StandardError
|
31
31
|
super
|
@@ -39,7 +39,7 @@ module Console1984::Freezeable
|
|
39
39
|
private
|
40
40
|
def prevent_sensitive_method(method_name)
|
41
41
|
define_method method_name do |*arguments|
|
42
|
-
raise Console1984::Errors::
|
42
|
+
raise Console1984::Errors::ForbiddenCommandAttempted, "You can't invoke #{method_name} on #{self}"
|
43
43
|
end
|
44
44
|
end
|
45
45
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
class Console1984::ProtectionsConfig
|
2
2
|
include Console1984::Freezeable
|
3
3
|
|
4
|
-
delegate :
|
4
|
+
delegate :validations, to: :instance
|
5
5
|
|
6
6
|
attr_reader :config
|
7
7
|
|
@@ -9,7 +9,7 @@ class Console1984::ProtectionsConfig
|
|
9
9
|
@config = config
|
10
10
|
end
|
11
11
|
|
12
|
-
%i[
|
12
|
+
%i[ validations forbidden_methods ].each do |method_name|
|
13
13
|
define_method method_name do
|
14
14
|
config[method_name].symbolize_keys
|
15
15
|
end
|
@@ -11,14 +11,17 @@ class Console1984::Refrigerator
|
|
11
11
|
end
|
12
12
|
|
13
13
|
private
|
14
|
-
EXTERNAL_MODULES_AND_CLASSES_TO_FREEZE = [Parser::CurrentRuby]
|
15
|
-
|
16
14
|
def freeze_internal_instances
|
17
15
|
Console1984.config.freeze unless Console1984.config.test_mode
|
18
16
|
end
|
19
17
|
|
20
18
|
def freeze_external_modules_and_classes
|
21
|
-
|
19
|
+
external_modules_and_classes_to_freeze.each { |klass| klass.include(Console1984::Freezeable) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def external_modules_and_classes_to_freeze
|
23
|
+
# Not using a constant because we want this to run lazily (console-dependant dependencies might not be loaded).
|
24
|
+
[Parser::CurrentRuby]
|
22
25
|
end
|
23
26
|
|
24
27
|
def eager_load_all_classes
|
@@ -26,7 +29,3 @@ class Console1984::Refrigerator
|
|
26
29
|
Console1984.class_loader.eager_load
|
27
30
|
end
|
28
31
|
end
|
29
|
-
|
30
|
-
class Parser::Ruby27
|
31
|
-
include Console1984::Freezeable
|
32
|
-
end
|
@@ -3,22 +3,20 @@ class Console1984::Shield::MethodInvocationShell
|
|
3
3
|
include Console1984::Freezeable
|
4
4
|
|
5
5
|
class << self
|
6
|
-
def install_for(
|
7
|
-
Array(
|
8
|
-
Array(config[:system]).each { |invocation| self.new(invocation, only_for_user_commands: false).prevent_methods_invocation }
|
6
|
+
def install_for(invocations)
|
7
|
+
Array(invocations).each { |invocation| self.new(invocation).prevent_methods_invocation }
|
9
8
|
end
|
10
9
|
end
|
11
10
|
|
12
11
|
attr_reader :class_name, :methods, :only_for_user_commands
|
13
12
|
|
14
|
-
def initialize(invocation
|
13
|
+
def initialize(invocation)
|
15
14
|
@class_name, methods = invocation.to_a
|
16
15
|
@methods = Array(methods)
|
17
|
-
@only_for_user_commands = only_for_user_commands
|
18
16
|
end
|
19
17
|
|
20
18
|
def prevent_methods_invocation
|
21
|
-
class_name.constantize.prepend build_protection_module
|
19
|
+
class_name.to_s.constantize.prepend build_protection_module
|
22
20
|
end
|
23
21
|
|
24
22
|
def build_protection_module
|
@@ -37,12 +35,8 @@ class Console1984::Shield::MethodInvocationShell
|
|
37
35
|
def protected_method_invocation_source_for(method)
|
38
36
|
<<~RUBY
|
39
37
|
def #{method}(*args)
|
40
|
-
if
|
41
|
-
|
42
|
-
break if !(line =~ /console1984\\/lib/ || line_from_irb)
|
43
|
-
line_from_irb
|
44
|
-
end
|
45
|
-
raise Console1984::Errors::ForbiddenCommand
|
38
|
+
if Console1984.command_executor.from_irb?(caller)
|
39
|
+
raise Console1984::Errors::ForbiddenCommandAttempted
|
46
40
|
else
|
47
41
|
super
|
48
42
|
end
|
@@ -30,12 +30,21 @@ class Console1984::Supervisor
|
|
30
30
|
stop_session
|
31
31
|
end
|
32
32
|
|
33
|
+
def exit_irb
|
34
|
+
stop
|
35
|
+
IRB.CurrentContext.exit
|
36
|
+
end
|
37
|
+
|
33
38
|
private
|
34
39
|
def require_dependencies
|
35
40
|
Kernel.silence_warnings do
|
36
41
|
require 'parser/current'
|
37
42
|
end
|
38
43
|
require 'colorized_string'
|
44
|
+
|
45
|
+
# Explicit lazy loading because it depends on +parser+, which we want to only load
|
46
|
+
# in console sessions.
|
47
|
+
require_relative "./command_validator/.command_parser"
|
39
48
|
end
|
40
49
|
|
41
50
|
def start_session
|
data/lib/console1984/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: console1984
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jorge Manrubia
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-09-
|
11
|
+
date: 2021-09-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: colorize
|
@@ -200,6 +200,7 @@ files:
|
|
200
200
|
- lib/console1984.rb
|
201
201
|
- lib/console1984/command_executor.rb
|
202
202
|
- lib/console1984/command_validator.rb
|
203
|
+
- lib/console1984/command_validator/.command_parser.rb
|
203
204
|
- lib/console1984/command_validator/forbidden_constant_reference_validation.rb
|
204
205
|
- lib/console1984/command_validator/forbidden_reopening_validation.rb
|
205
206
|
- lib/console1984/command_validator/parsed_command.rb
|