oscal 0.1.1 → 0.2.2
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/.docker/Dockerfile +19 -0
- data/.docker/Makefile +43 -0
- data/.docker/docker-compose.yml +14 -0
- data/.docker/readme.md +61 -0
- data/.gitignore +2 -0
- data/.rspec +0 -1
- data/.rubocop.yml +1 -1
- data/.ruby-version +1 -0
- data/Gemfile +2 -0
- data/LICENSE +25 -0
- data/Makefile +1 -0
- data/README.adoc +3 -0
- data/Rakefile +13 -6
- data/bin/console +2 -2
- data/bin/rspec +27 -0
- data/docker-compose.yml +1 -0
- data/lib/oscal/add.rb +5 -4
- data/lib/oscal/address.rb +3 -2
- data/lib/oscal/address_line.rb +1 -0
- data/lib/oscal/alter.rb +3 -2
- data/lib/oscal/assembly.rb +119 -0
- data/lib/oscal/assessment_plan.rb +28 -0
- data/lib/oscal/assessment_result.rb +230 -0
- data/lib/oscal/attribute_type_hash.rb +81 -0
- data/lib/oscal/back_matter.rb +2 -1
- data/lib/oscal/base64_object.rb +1 -0
- data/lib/oscal/base_class.rb +5 -4
- data/lib/oscal/catalog.rb +8 -7
- data/lib/oscal/choice.rb +1 -0
- data/lib/oscal/citation.rb +3 -2
- data/lib/oscal/combine.rb +1 -0
- data/lib/oscal/common_utils.rb +1 -1
- data/lib/oscal/constraint.rb +2 -1
- data/lib/oscal/control.rb +6 -5
- data/lib/oscal/custom.rb +3 -2
- data/lib/oscal/datatypes.rb +50 -0
- data/lib/oscal/document_id.rb +1 -0
- data/lib/oscal/email_address.rb +1 -0
- data/lib/oscal/exclude_control.rb +3 -2
- data/lib/oscal/external_id.rb +1 -0
- data/lib/oscal/group.rb +9 -8
- data/lib/oscal/guideline.rb +1 -0
- data/lib/oscal/hash_object.rb +1 -0
- data/lib/oscal/import_object.rb +3 -2
- data/lib/oscal/include_control.rb +3 -2
- data/lib/oscal/insert_control.rb +3 -2
- data/lib/oscal/link.rb +1 -0
- data/lib/oscal/list.rb +160 -0
- data/lib/oscal/location.rb +8 -7
- data/lib/oscal/location_uuid.rb +1 -0
- data/lib/oscal/logger.rb +12 -0
- data/lib/oscal/matching.rb +1 -0
- data/lib/oscal/member_of_organization.rb +1 -0
- data/lib/oscal/merge.rb +2 -1
- data/lib/oscal/metadata_block.rb +11 -10
- data/lib/oscal/modify.rb +3 -2
- data/lib/oscal/parameter.rb +8 -7
- data/lib/oscal/parsing_functions.rb +19 -0
- data/lib/oscal/part.rb +4 -3
- data/lib/oscal/party.rb +11 -10
- data/lib/oscal/party_uuid.rb +1 -0
- data/lib/oscal/profile.rb +7 -6
- data/lib/oscal/property.rb +1 -0
- data/lib/oscal/remove.rb +1 -0
- data/lib/oscal/resource.rb +7 -6
- data/lib/oscal/responsible_party.rb +11 -10
- data/lib/oscal/revision.rb +4 -3
- data/lib/oscal/rlink.rb +2 -1
- data/lib/oscal/role.rb +3 -2
- data/lib/oscal/select.rb +2 -1
- data/lib/oscal/set_parameter.rb +8 -7
- data/lib/oscal/telephone_number.rb +1 -0
- data/lib/oscal/test.rb +1 -0
- data/lib/oscal/url.rb +1 -0
- data/lib/oscal/value.rb +5 -4
- data/lib/oscal/version.rb +1 -1
- data/lib/oscal/with_id.rb +2 -1
- data/lib/oscal.rb +1 -1
- data/spec/oscal/catalog_spec.rb +5 -4
- data/spec/oscal_spec.rb +11 -0
- data/spec/sample_inputs/import-ap.json +4 -0
- metadata +24 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b2897a40fdb6b5d42344c89a7834fa5d1ebfb899bd0a73e855023274fcff0cbe
|
4
|
+
data.tar.gz: 99d05b3aa2f7e5bee0b4340f64a660670d403d48289af8818e09e556131186bf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 141e14988d56c8942450ba94739396c8f5f41804d5df0065e0e698f5f0d9201bd8552fc6d4ac3ff7efac785dab13e80d4b7a0fec6c36708d87f8a02f74d222ca
|
7
|
+
data.tar.gz: 3aacc11681265d9db40625aa3b4a552503bc20d3eeeb423a8dd5330819f60b83b80232737d0efcc02e15a6411cc96a5c694ce2a05a0f7c74efa59cc6198f2567
|
data/.docker/Dockerfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
ARG RUBY_IMAGE=ruby:3.1.2-slim
|
2
|
+
|
3
|
+
FROM ${RUBY_IMAGE}
|
4
|
+
|
5
|
+
RUN apt-get update \
|
6
|
+
&& apt-get install -y build-essential git \
|
7
|
+
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
8
|
+
|
9
|
+
# install latest bundler
|
10
|
+
RUN gem install bundler
|
11
|
+
|
12
|
+
# Create app directory
|
13
|
+
WORKDIR /workspace
|
14
|
+
|
15
|
+
# Set bundle path
|
16
|
+
ENV BUNDLE_PATH /bundle
|
17
|
+
|
18
|
+
# Default to console
|
19
|
+
CMD ["bin/console"]
|
data/.docker/Makefile
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
export SPEC ?= spec
|
2
|
+
SPEC_FILE = $(subst ../,, $(SPEC))
|
3
|
+
export RUBY_IMAGE ?= ruby:3.1.2-slim
|
4
|
+
|
5
|
+
.PHONY: up
|
6
|
+
up:
|
7
|
+
docker-compose up
|
8
|
+
|
9
|
+
.PHONY: down
|
10
|
+
down:
|
11
|
+
docker-compose down
|
12
|
+
|
13
|
+
.PHONY: test
|
14
|
+
test: rspec
|
15
|
+
|
16
|
+
.PHONY: ssh
|
17
|
+
ssh:
|
18
|
+
docker-compose run lib bash
|
19
|
+
|
20
|
+
.PHONY: install
|
21
|
+
install:
|
22
|
+
docker-compose run lib bin/setup
|
23
|
+
|
24
|
+
.PHONY: console
|
25
|
+
console:
|
26
|
+
docker-compose run lib bin/console
|
27
|
+
|
28
|
+
.PHONY: rspec
|
29
|
+
rspec:
|
30
|
+
docker-compose run lib bin/rspec ${SPEC_FILE}
|
31
|
+
|
32
|
+
.PHONY: rake
|
33
|
+
rake:
|
34
|
+
docker-compose run lib bundle exec rake
|
35
|
+
|
36
|
+
.PHONY: lint
|
37
|
+
lint:
|
38
|
+
docker-compose run lib bundle exec rubocop
|
39
|
+
|
40
|
+
.PHONY: setup
|
41
|
+
setup:
|
42
|
+
docker-compose build --build-arg RUBY_IMAGE=${RUBY_IMAGE}
|
43
|
+
docker-compose run lib bin/setup
|
data/.docker/readme.md
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
## Docker
|
2
|
+
|
3
|
+
This directory is only meant to be used for development, and contains some
|
4
|
+
necessary setup to spin up docker containers with multiple ruby environment.
|
5
|
+
|
6
|
+
### Setup
|
7
|
+
|
8
|
+
Before doing anything, you might want to create a symlink to the docker file and
|
9
|
+
Makefile. This would allow you to avoid some of the unnecessary work related to
|
10
|
+
the file paths To do that run the following from the root of the project.
|
11
|
+
|
12
|
+
```
|
13
|
+
ln -sf .docker/Makefile .
|
14
|
+
ln -sf .docker/docker-compose.yml .
|
15
|
+
```
|
16
|
+
|
17
|
+
By default it usages the most recent ruby version for docker environment, but if
|
18
|
+
you want to run it in any specific version then you can set it up by exporting
|
19
|
+
`RUBY_IMAGE` environment variable in your shell:
|
20
|
+
|
21
|
+
```sh
|
22
|
+
export RUBY_IMAGE=ruby:3.0-buster
|
23
|
+
```
|
24
|
+
|
25
|
+
Once everything is set then you would need to build the development images for
|
26
|
+
the first time and you can do that using:
|
27
|
+
|
28
|
+
```sh
|
29
|
+
make setup
|
30
|
+
```
|
31
|
+
|
32
|
+
The setup process will install all dependencies and it will also setup a volume
|
33
|
+
to speed up the repeated gem installation.
|
34
|
+
|
35
|
+
### Playground
|
36
|
+
|
37
|
+
The `Makefile` contains two target for tests, and you can run the tests using
|
38
|
+
any of the following commands:
|
39
|
+
|
40
|
+
```sh
|
41
|
+
make test
|
42
|
+
|
43
|
+
# or
|
44
|
+
make rspec
|
45
|
+
```
|
46
|
+
|
47
|
+
If you need more control, and you want to do some development on the go then you
|
48
|
+
can get into the container using:
|
49
|
+
|
50
|
+
```sh
|
51
|
+
make ssh
|
52
|
+
```
|
53
|
+
|
54
|
+
### Cleanup
|
55
|
+
|
56
|
+
Once you are done with your experiment then you can cleanup the docker
|
57
|
+
environment using the following command.
|
58
|
+
|
59
|
+
```sh
|
60
|
+
make down
|
61
|
+
```
|
data/.gitignore
CHANGED
data/.rspec
CHANGED
data/.rubocop.yml
CHANGED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.3.1
|
data/Gemfile
CHANGED
data/LICENSE
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
BSD 2-Clause License
|
2
|
+
|
3
|
+
Copyright (c) 2023, Ribose
|
4
|
+
All rights reserved.
|
5
|
+
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
8
|
+
|
9
|
+
* Redistributions of source code must retain the above copyright notice, this
|
10
|
+
list of conditions and the following disclaimer.
|
11
|
+
|
12
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
14
|
+
and/or other materials provided with the distribution.
|
15
|
+
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
17
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
18
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
19
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
20
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
21
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
22
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
23
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
24
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
25
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/Makefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
.docker/Makefile
|
data/README.adoc
CHANGED
data/Rakefile
CHANGED
@@ -3,10 +3,17 @@
|
|
3
3
|
require "bundler/gem_tasks"
|
4
4
|
require "rspec/core/rake_task"
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
# Note:
|
7
|
+
#
|
8
|
+
# There seems to be lots of issue with the current rubocop rules
|
9
|
+
# We are commenting this out for the moment, so instead of fixing
|
10
|
+
# those right away, we can focus on the new codes and then later
|
11
|
+
# come back to this and fix the issues.
|
12
|
+
#
|
13
|
+
# require "rubocop/rake_task"
|
14
|
+
# RuboCop::RakeTask.new
|
15
|
+
#
|
16
|
+
# task default: %i[spec rubocop]
|
9
17
|
|
10
|
-
|
11
|
-
|
12
|
-
task default: %i[spec rubocop]
|
18
|
+
RSpec::Core::RakeTask.new(:spec)
|
19
|
+
task default: :spec
|
data/bin/console
CHANGED
@@ -14,9 +14,9 @@ require "oscal"
|
|
14
14
|
require "irb"
|
15
15
|
|
16
16
|
def reload!(print = true)
|
17
|
-
puts
|
17
|
+
puts "Reloading ..." if print
|
18
18
|
# Main project directory.
|
19
|
-
root_dir = File.expand_path(
|
19
|
+
root_dir = File.expand_path("..", __dir__)
|
20
20
|
# Directories within the project that should be reloaded.
|
21
21
|
reload_dirs = %w{lib}
|
22
22
|
# Loop through and reload every file in all relevant project directories.
|
data/bin/rspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rspec' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
12
|
+
|
13
|
+
bundle_binstub = File.expand_path("bundle", __dir__)
|
14
|
+
|
15
|
+
if File.file?(bundle_binstub)
|
16
|
+
if /This file was generated by Bundler/.match?(File.read(bundle_binstub, 300))
|
17
|
+
load(bundle_binstub)
|
18
|
+
else
|
19
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
20
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
require "rubygems"
|
25
|
+
require "bundler/setup"
|
26
|
+
|
27
|
+
load Gem.bin_path("rspec-core", "rspec")
|
data/docker-compose.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
.docker/docker-compose.yml
|
data/lib/oscal/add.rb
CHANGED
@@ -5,17 +5,18 @@ module Oscal
|
|
5
5
|
KEY = %i(position by_id title params props links parts)
|
6
6
|
|
7
7
|
attr_accessor *KEY
|
8
|
+
|
8
9
|
attr_serializable *KEY
|
9
10
|
|
10
11
|
def set_value(key_name, val)
|
11
12
|
case key_name
|
12
|
-
when
|
13
|
+
when "params"
|
13
14
|
Parameter.wrap(val)
|
14
|
-
when
|
15
|
+
when "props"
|
15
16
|
Property.wrap(val)
|
16
|
-
when
|
17
|
+
when "links"
|
17
18
|
Link.wrap(val)
|
18
|
-
when
|
19
|
+
when "part"
|
19
20
|
Part.wrap(val)
|
20
21
|
else
|
21
22
|
val
|
data/lib/oscal/address.rb
CHANGED
@@ -5,13 +5,14 @@ module Oscal
|
|
5
5
|
KEY = %i(type addr_lines city state postal_code country)
|
6
6
|
|
7
7
|
attr_accessor *KEY
|
8
|
+
|
8
9
|
attr_serializable *KEY
|
9
10
|
|
10
11
|
def set_value(key_name, val)
|
11
12
|
case key_name
|
12
|
-
when
|
13
|
+
when "addr_lines"
|
13
14
|
AddressLine.wrap(val)
|
14
|
-
when
|
15
|
+
when "links"
|
15
16
|
Link.wrap(val)
|
16
17
|
else
|
17
18
|
val
|
data/lib/oscal/address_line.rb
CHANGED
data/lib/oscal/alter.rb
CHANGED
@@ -5,13 +5,14 @@ module Oscal
|
|
5
5
|
KEY = %i(control_id klass removes adds)
|
6
6
|
|
7
7
|
attr_accessor *KEY
|
8
|
+
|
8
9
|
attr_serializable *KEY
|
9
10
|
|
10
11
|
def set_value(key_name, val)
|
11
12
|
case key_name
|
12
|
-
when
|
13
|
+
when "removes"
|
13
14
|
Remove.wrap(val)
|
14
|
-
when
|
15
|
+
when "adds"
|
15
16
|
Add.wrap(val)
|
16
17
|
else
|
17
18
|
val
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require_relative "parsing_functions"
|
2
|
+
require_relative "logger"
|
3
|
+
require_relative "metadata_block"
|
4
|
+
|
5
|
+
module Oscal
|
6
|
+
class MetadataBlockWrapper < Oscal::MetadataBlock
|
7
|
+
include ParsingFunctions
|
8
|
+
def initialize(metadata_hash)
|
9
|
+
# MetadataBlock likes to get strings, but may sometimes get symbols
|
10
|
+
# this little function makes sure it gets strings everytime
|
11
|
+
super(metadata_hash.transform_keys { |key| sym2str(key) })
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Assembly
|
16
|
+
include Oscal::ParsingFunctions
|
17
|
+
include Oscal::ParsingLogger
|
18
|
+
|
19
|
+
def mandatory_attributes
|
20
|
+
if self.class.constants.include?(:MANDATORY)
|
21
|
+
self.class::MANDATORY
|
22
|
+
else
|
23
|
+
[]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def allowed_attributes
|
28
|
+
if self.class.constants.include?(:OPTIONAL)
|
29
|
+
mandatory_attributes + self.class::OPTIONAL
|
30
|
+
else
|
31
|
+
mandatory_attributes
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_json
|
36
|
+
to_h.to_json
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_h
|
40
|
+
allowed_attributes.each_with_object({}) do |var, hash|
|
41
|
+
attr_value = method(var).call
|
42
|
+
hash[sym2str(var)] = if attr_value == nil
|
43
|
+
next
|
44
|
+
elsif attr_value.class <= OscalArray
|
45
|
+
attr_value.each(&:to_h)
|
46
|
+
elsif attr_value.class <= OscalDatatype
|
47
|
+
attr_value
|
48
|
+
else
|
49
|
+
attr_value.to_h
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def check_and_normalize_input(input)
|
55
|
+
@logger.debug("Checking to see if input is a Hash")
|
56
|
+
unless input.is_a? Hash
|
57
|
+
raise Oscal::InvalidTypeError,
|
58
|
+
"Assemblies can only be created from Hash types"
|
59
|
+
end
|
60
|
+
@logger.debug("Assembly is hash with keys #{input.keys}")
|
61
|
+
|
62
|
+
@logger.debug("Attempting to transform strings to symbols.")
|
63
|
+
# Transform the keys from Strings to Symbols
|
64
|
+
input.transform_keys { |key| str2sym(key) }
|
65
|
+
end
|
66
|
+
|
67
|
+
def validate_input(input)
|
68
|
+
@logger.debug("Checking mandatory and optional values.")
|
69
|
+
missing_values?(mandatory_attributes, input)
|
70
|
+
extra_values?(allowed_attributes, input)
|
71
|
+
end
|
72
|
+
|
73
|
+
def missing_values?(mandatory, provided)
|
74
|
+
@logger.debug("Checking mandatory values: #{mandatory}")
|
75
|
+
missing_values = mandatory - provided.keys.intersection(mandatory)
|
76
|
+
if missing_values.length.positive?
|
77
|
+
raise Oscal::InvalidTypeError,
|
78
|
+
"Missing mandatory values: #{missing_values}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def extra_values?(allowed, provided)
|
83
|
+
@logger.debug("Checking allowed values: #{allowed}")
|
84
|
+
extra_values = provided.keys - provided.keys.intersection(allowed)
|
85
|
+
if extra_values.length.positive?
|
86
|
+
raise Oscal::InvalidTypeError,
|
87
|
+
"Extra attributes provided #{extra_values}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def validate_content(key, value)
|
92
|
+
@logger.info("Validating #{value}")
|
93
|
+
expected_class = Oscal::get_type_of_attribute(key)
|
94
|
+
@logger.debug("Attempting to instiate #{key} as #{expected_class}")
|
95
|
+
instantiated = expected_class.new(value)
|
96
|
+
rescue Oscal::InvalidTypeError
|
97
|
+
raise Oscal::InvalidTypeError,
|
98
|
+
"Value #{value.to_s[0, 25]} not a valid #{key}"
|
99
|
+
else
|
100
|
+
instantiated # Return the valid class
|
101
|
+
end
|
102
|
+
|
103
|
+
def initialize(input)
|
104
|
+
@logger = get_logger
|
105
|
+
@logger.debug("#{self.class}.new called with #{input.to_s[0, 25]}")
|
106
|
+
|
107
|
+
# covert String:String to Symbol:String
|
108
|
+
sym_hash = check_and_normalize_input(input)
|
109
|
+
|
110
|
+
# Make sure all required and no extra keys are provided
|
111
|
+
validate_input(sym_hash)
|
112
|
+
|
113
|
+
# Attempt to convert each value to it's registered type
|
114
|
+
sym_hash.each do |key, value|
|
115
|
+
method("#{key}=".to_sym).call(validate_content(key, value))
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative "assembly"
|
2
|
+
require_relative "metadata_block"
|
3
|
+
require_relative "datatypes"
|
4
|
+
|
5
|
+
module Oscal
|
6
|
+
module AssessmentPlan
|
7
|
+
class ImportSSP < Assembly
|
8
|
+
attr_accessor(*(MANDATORY = %i(href).freeze),
|
9
|
+
*(OPTIONAL = %i(remarks).freeze))
|
10
|
+
end
|
11
|
+
|
12
|
+
class ReviewedControls < Assembly
|
13
|
+
attr_accessor(*(MANDATORY = %i(control_selections).freeze),
|
14
|
+
*(OPTIONAL = %i(description props links
|
15
|
+
control_objective_selections
|
16
|
+
remarks).freeze))
|
17
|
+
end
|
18
|
+
|
19
|
+
class AssessmentPlan < Assembly
|
20
|
+
attr_accessor(*(MANDATORY = %i(uuid metadata import_ssp
|
21
|
+
reviewed_controls).freeze),
|
22
|
+
*(OPTIONAL = %i(local_definitions terms_and_conditions
|
23
|
+
reviewed_controls assessment_subjects
|
24
|
+
assessment_assets tasks
|
25
|
+
back_matter).freeze))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|