From 0d2093230985de68686bfbd04f96e78cc517b8ab Mon Sep 17 00:00:00 2001 From: Robert Thew Date: Wed, 23 May 2018 15:40:35 -0400 Subject: [PATCH] Added filtering capability, forms, and tests --- Gemfile | 2 + app/assets/stylesheets/application.css | 15 + .../concerns/dashboard_controller.rb | 2 +- app/controllers/evaluations_controller.rb | 68 ++- app/controllers/filter_groups_controller.rb | 81 ++++ app/controllers/filters_controller.rb | 73 ++++ app/controllers/profiles_controller.rb | 16 +- app/helpers/application_helper.rb | 5 + app/models/ability.rb | 2 + app/models/control.rb | 25 ++ app/models/download.rb | 2 +- app/models/evaluation.rb | 24 +- app/models/filter.rb | 158 +++++++ app/models/filter_group.rb | 8 + app/models/profile.rb | 23 +- app/models/repo.rb | 16 +- app/views/evaluations/show.html.erb | 390 ++++++++++++++++-- app/views/evaluations/ssp.html.erb | 4 +- app/views/evaluations/ssp_pdf.html.erb | 4 +- .../filter_groups/_filter_group.json.jbuilder | 2 + app/views/filter_groups/_form.html.erb | 24 ++ app/views/filter_groups/edit.html.erb | 28 ++ app/views/filter_groups/index.html.erb | 45 ++ app/views/filter_groups/index.json.jbuilder | 1 + app/views/filter_groups/new.html.erb | 27 ++ app/views/filter_groups/show.html.erb | 90 ++++ app/views/filter_groups/show.json.jbuilder | 1 + app/views/filters/_filter.json.jbuilder | 2 + app/views/filters/_form.html.erb | 286 +++++++++++++ app/views/filters/edit.html.erb | 27 ++ app/views/filters/index.html.erb | 55 +++ app/views/filters/index.json.jbuilder | 1 + app/views/filters/new.html.erb | 27 ++ app/views/filters/show.html.erb | 64 +++ app/views/filters/show.json.jbuilder | 1 + app/views/layouts/_header.html.erb | 5 +- app/views/profiles/show.html.erb | 2 +- config/initializers/constants.rb | 8 + config/routes.rb | 8 +- lib/inspec2ckl.rb | 8 - lib/inspec2ckl/StigChecklist.rb | 93 ----- lib/inspec2ckl/cli.rb | 76 ---- lib/inspec2ckl/inspec2ckl.rb | 160 ------- lib/inspec2ckl/version.rb | 5 - .../evaluations_controller_spec.rb | 71 +++- .../filter_groups_controller_spec.rb | 158 +++++++ spec/controllers/filters_controller_spec.rb | 144 +++++++ spec/factories/filter_groups.rb | 5 + spec/factories/filters.rb | 31 ++ spec/helpers/application_helper_spec.rb | 6 + spec/models/filter_group_spec.rb | 6 + spec/models/filter_spec.rb | 102 +++++ spec/models/repo_spec.rb | 19 + spec/requests/filter_groups_spec.rb | 12 + spec/requests/filters_spec.rb | 12 + spec/views/evaluations/show.html.erb_spec.rb | 2 +- .../views/filter_groups/edit.html.erb_spec.rb | 18 + spec/views/filter_groups/new.html.erb_spec.rb | 18 + .../views/filter_groups/show.html.erb_spec.rb | 14 + 59 files changed, 2157 insertions(+), 425 deletions(-) create mode 100644 app/controllers/filter_groups_controller.rb create mode 100644 app/controllers/filters_controller.rb create mode 100644 app/models/filter.rb create mode 100644 app/models/filter_group.rb create mode 100644 app/views/filter_groups/_filter_group.json.jbuilder create mode 100644 app/views/filter_groups/_form.html.erb create mode 100644 app/views/filter_groups/edit.html.erb create mode 100644 app/views/filter_groups/index.html.erb create mode 100644 app/views/filter_groups/index.json.jbuilder create mode 100644 app/views/filter_groups/new.html.erb create mode 100644 app/views/filter_groups/show.html.erb create mode 100644 app/views/filter_groups/show.json.jbuilder create mode 100644 app/views/filters/_filter.json.jbuilder create mode 100644 app/views/filters/_form.html.erb create mode 100644 app/views/filters/edit.html.erb create mode 100644 app/views/filters/index.html.erb create mode 100644 app/views/filters/index.json.jbuilder create mode 100644 app/views/filters/new.html.erb create mode 100644 app/views/filters/show.html.erb create mode 100644 app/views/filters/show.json.jbuilder create mode 100644 config/initializers/constants.rb delete mode 100644 lib/inspec2ckl.rb delete mode 100644 lib/inspec2ckl/StigChecklist.rb delete mode 100755 lib/inspec2ckl/cli.rb delete mode 100755 lib/inspec2ckl/inspec2ckl.rb delete mode 100644 lib/inspec2ckl/version.rb create mode 100644 spec/controllers/filter_groups_controller_spec.rb create mode 100644 spec/controllers/filters_controller_spec.rb create mode 100644 spec/factories/filter_groups.rb create mode 100644 spec/factories/filters.rb create mode 100644 spec/models/filter_group_spec.rb create mode 100644 spec/models/filter_spec.rb create mode 100644 spec/requests/filter_groups_spec.rb create mode 100644 spec/requests/filters_spec.rb create mode 100644 spec/views/filter_groups/edit.html.erb_spec.rb create mode 100644 spec/views/filter_groups/new.html.erb_spec.rb create mode 100644 spec/views/filter_groups/show.html.erb_spec.rb diff --git a/Gemfile b/Gemfile index 4ea16a8..bb2ffba 100644 --- a/Gemfile +++ b/Gemfile @@ -52,6 +52,8 @@ gem 'thor' gem 'json' gem 'pry' +gem 'inspec_to', :git => "git@gitlab.mitre.org:inspec/inspec_to.git" + group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index ab40f6a..903442f 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -64,6 +64,21 @@ padding: 5px; font-size: 70px; } +.padded-right { + padding-right: 35px; +} +.no-top-padding { + padding-top: 0px; + padding-bottom: 5px; +} +.little_wider { + min-width: 200px; +} +.span-title { + font-size: 18px; + margin: 0; + line-height: 1; +} .column-2 { -webkit-columns: 100px 2; /* Chrome, Safari, Opera */ -moz-columns: 100px 2; /* Firefox */ diff --git a/app/controllers/concerns/dashboard_controller.rb b/app/controllers/concerns/dashboard_controller.rb index 3fbbf45..1ec4023 100644 --- a/app/controllers/concerns/dashboard_controller.rb +++ b/app/controllers/concerns/dashboard_controller.rb @@ -1,5 +1,5 @@ class DashboardController < ApplicationController def index - # nop + session[:filter] = nil end end diff --git a/app/controllers/evaluations_controller.rb b/app/controllers/evaluations_controller.rb index ee84092..8d4a39a 100644 --- a/app/controllers/evaluations_controller.rb +++ b/app/controllers/evaluations_controller.rb @@ -1,6 +1,6 @@ class EvaluationsController < ApplicationController load_resource - authorize_resource only: [:show, :destroy, :filter] + authorize_resource only: [:show, :destroy, :filter, :clear_filter] protect_from_forgery # GET /evaluations @@ -12,10 +12,10 @@ def index # GET /evaluations/1 # GET /evaluations/1.json def show - logger.debug "params: #{params.inspect}" @profiles = @evaluation.profiles - @counts, @controls = @evaluation.status_counts - @nist_hash = ProfilesController.nist_800_53 + filters, @filter_label = session_filters + @counts, @controls = @evaluation.status_counts(filters) + @nist_hash = Constants::NIST_800_53 respond_to do |format| format.html { render :show } format.json { render :show } @@ -37,9 +37,8 @@ def destroy # GET /profiles/1.json def ssp authorize! :read, Evaluation - @nist_hash = ProfilesController.nist_800_53 + @nist_hash = Constants::NIST_800_53 @symbols = @evaluation.symbols - # @counts, @controls = @evaluation.status_counts @evaluation.profiles.each do |profile| families, nist = profile.control_families next if families.empty? @@ -61,14 +60,46 @@ def ssp def nist authorize! :read, Evaluation + filters, = session_filters category = nil category = params[:category].downcase if params.key?(:category) status_sym = nil status_sym = params[:status_symbol].downcase.tr(' ', '_').to_sym if params.key?(:status_symbol) - @control_hash = @evaluation.nist_hash category, status_sym - nist_hash = ProfilesController.nist_800_53 - @name = nist_hash['name'] - @families = nist_hash['children'] + @control_hash = @evaluation.nist_hash category, status_sym, filters + @name = Constants::NIST_800_53['name'] + @families = Constants::NIST_800_53['children'] + end + + def clear_filter + session[:filter] = nil + session[:filter_group] = nil + redirect_to @evaluation + end + + def filter + logger.debug "params: #{params.inspect}" + f_params = params[:filter] + @filter = Filter.valid_filter f_params + if f_params[:save_filter] + @filter.save + end + session[:filter] = @filter + session[:filter_group] = nil + redirect_to @evaluation + end + + def filter_select + filter_params = params[:filter_group] + if filter_params[:id].present? + @filter_group = FilterGroup.find(filter_params[:id]) + session[:filter] = nil + session[:filter_group] = @filter_group + elsif filter_params[:filter_ids].present? + @filter = Filter.find(filter_params[:filter_ids]) + session[:filter_group] = nil + session[:filter] = @filter + end + redirect_to @evaluation end def upload @@ -81,4 +112,21 @@ def upload redirect_to evaluations_url, notice: 'File does not contain an evaluation.' end end + + private + + def session_filters + filters = nil + filter_label = nil + if session[:filter] + filter = session[:filter].is_a?(Filter) ? session[:filter] : Filter.new(session[:filter]) + filters = [filter] + filter_label = filter.to_s + elsif session[:filter_group] + filter_group = session[:filter_group].is_a?(FilterGroup) ? session[:filter_group] : FilterGroup.new(session[:filter_group]) + filters = filter_group.filters + filter_label = "#{filter_group.name}: #{filter_group.filters.map(&:to_s).join(', ')}" + end + [filters, filter_label] + end end diff --git a/app/controllers/filter_groups_controller.rb b/app/controllers/filter_groups_controller.rb new file mode 100644 index 0000000..3c61d58 --- /dev/null +++ b/app/controllers/filter_groups_controller.rb @@ -0,0 +1,81 @@ +class FilterGroupsController < ApplicationController + load_and_authorize_resource + protect_from_forgery + # authorize_resource only: [:show, :destroy, :filter, :clear_filter] + # before_action :set_filter_group, only: [:show, :edit, :update, :destroy, :remove_filter] + + # GET /filter_groups + # GET /filter_groups.json + def index + @filter_groups = FilterGroup.all + end + + # GET /filter_groups/1 + # GET /filter_groups/1.json + def show + end + + # GET /filter_groups/new + def new + @filter_group = FilterGroup.new + end + + # GET /filter_groups/1/edit + def edit + end + + # POST /filter_groups + # POST /filter_groups.json + def create + @filter_group = FilterGroup.new(filter_group_params) + + respond_to do |format| + if @filter_group.save + format.html { redirect_to @filter_group, notice: 'Filter group was successfully created.' } + format.json { render :show, status: :created, location: @filter_group } + else + format.html { render :new } + format.json { render json: @filter_group.errors, status: :unprocessable_entity } + end + end + end + + # PATCH/PUT /filter_groups/1 + # PATCH/PUT /filter_groups/1.json + def update + logger.debug "filter_group_params: #{filter_group_params.inspect}" + if (filter_ids = filter_group_params[:filter_ids]) + if (filter = Filter.find(filter_ids[:id])) + @filter_group.filters << filter + redirect_to @filter_group, notice: 'Filter was added.' + end + else + respond_to do |format| + if @filter_group.update(filter_group_params) + format.html { redirect_to @filter_group, notice: 'Filter group was successfully updated.' } + format.json { render :show, status: :ok, location: @filter_group } + else + format.html { render :edit } + format.json { render json: @filter_group.errors, status: :unprocessable_entity } + end + end + end + end + + # DELETE /filter_groups/1 + # DELETE /filter_groups/1.json + def destroy + @filter_group.destroy + respond_to do |format| + format.html { redirect_to filter_groups_url, notice: 'Filter group was successfully destroyed.' } + format.json { head :no_content } + end + end + + private + + # Never trust parameters from the scary internet, only allow the white list through. + def filter_group_params + params.require(:filter_group).permit(:name, { filter_ids: [:id] }) + end +end diff --git a/app/controllers/filters_controller.rb b/app/controllers/filters_controller.rb new file mode 100644 index 0000000..d218f4b --- /dev/null +++ b/app/controllers/filters_controller.rb @@ -0,0 +1,73 @@ +class FiltersController < ApplicationController + load_and_authorize_resource + protect_from_forgery + + # GET /filters + # GET /filters.json + def index + @filters = Filter.all + end + + # GET /filters/1 + # GET /filters/1.json + def show + end + + # GET /filters/new + def new + @filter = Filter.new + @nist_hash = Constants::NIST_800_53 + end + + # GET /filters/1/edit + def edit + @nist_hash = Constants::NIST_800_53 + end + + # POST /filters + # POST /filters.json + def create + @filter = Filter.valid_filter filter_params + @filter.save + respond_to do |format| + format.html { redirect_to Filter.find(@filter.id), notice: 'Filter was successfully created.' } + format.json { render :show, status: :created, location: @filter } + end + end + + # PATCH/PUT /filters/1 + # PATCH/PUT /filters/1.json + def update + respond_to do |format| + @filter.update(filter_params) + format.html { redirect_to @filter, notice: 'Filter was successfully updated.' } + format.json { render :show, status: :ok, location: @filter } + end + end + + # DELETE /filters/1 + # DELETE /filters/1.json + def destroy + if params.key?(:filter_group_id) + @filter_group = FilterGroup.find(params[:filter_group_id]) + @filter_group.filters.delete(Filter.find(params[:id])) + respond_to do |format| + format.html { redirect_to @filter_group, notice: 'Filter was successfully removed.' } + format.json { head :no_content } + end + else + @filter.destroy + respond_to do |format| + format.html { redirect_to filters_url, notice: 'Filter was successfully destroyed.' } + format.json { head :no_content } + end + end + end + + private + + # Never trust parameters from the scary internet, only allow the white list through. + def filter_params + params.require(:filter).permit(family: [], number: [], sub_fam: [], sub_num: [], enhancement: [], enh_sub_fam: [], enh_sub_num: []) + end +end diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index cabe7b1..003ce14 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -2,12 +2,6 @@ class ProfilesController < ApplicationController load_resource authorize_resource only: [:show, :edit, :destroy, :upload] - @nist_800_53_json = nil - - class << self - attr_accessor :nist_800_53_json - end - # GET /profiles # GET /profiles.json def index @@ -71,7 +65,7 @@ def nist category = nil category = params[:category].downcase if params.key?(:category) @control_hash = @profile.nist_hash category - nist_hash = ProfilesController.nist_800_53 + nist_hash = Constants::NIST_800_53 @name = nist_hash['name'] @families = nist_hash['children'] end @@ -96,14 +90,6 @@ def upload end end - def self.nist_800_53 - unless ProfilesController.nist_800_53_json - file = File.read("#{Rails.root}/data/nist_800_53.json") - ProfilesController.nist_800_53_json = JSON.parse(file) - end - ProfilesController.nist_800_53_json - end - private # Never trust parameters from the scary internet, only allow the white list through. diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 80d1a33..b3a3a73 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -37,4 +37,9 @@ def result_message(symbol) 'No test available for this control' end end + + def ary_to_s(val) + return '' if val.nil? || val.empty? + val.size == 1 ? val.first.to_s.delete('"') : val.to_s.delete('"') + end end diff --git a/app/models/ability.rb b/app/models/ability.rb index 502f1a4..6e13c38 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -20,6 +20,8 @@ def initialize(user) Role, Support, Tag, + Filter, + FilterGroup, ], created_by_id: user.id end if user.has_role?(:admin) diff --git a/app/models/control.rb b/app/models/control.rb index 40f3a17..c51d562 100644 --- a/app/models/control.rb +++ b/app/models/control.rb @@ -70,6 +70,31 @@ def severity end end + def parse_nist_tag(nist_tag) + if (m_fields = nist_tag.match(/([A-Z]{2})\-(\d+)((\s*([a-z])|\.([a-z])){1}((\.|\s+)(\d+))?)?\s*(\(\d+\))?((\s*([a-z])|\.([a-z])|\s*\(([a-z])\)){1}((\.|\s+)(\d+))?)?/)) + # + value = "#{m_fields[1]}-#{m_fields[2]}" + value += ".#{m_fields[5] || m_fields[6]}" if m_fields[4].present? + value += ".#{m_fields[9]}" if m_fields[9].present? + value += m_fields[10] if m_fields[10].present? + value += ".#{m_fields[13] || m_fields[14] || m_fields[15]}" if m_fields[12].present? + value += ".#{m_fields[18]}" if m_fields[18].present? + value + else + nist_tag + end + end + + def nist_tags + values = [] + if (nist_tags = tag('nist')) + nist_tags.split(',').map(&:strip).each do |nist_tag| + values << parse_nist_tag(nist_tag) + end + end + values + end + def short_title title.nil? ? '' : "#{title[0..50]}..." end diff --git a/app/models/download.rb b/app/models/download.rb index 953a787..28f4d28 100644 --- a/app/models/download.rb +++ b/app/models/download.rb @@ -41,7 +41,7 @@ def to_pdf 'margin-bottom': '0.10in', 'margin-left': '0.10in' } - kit = PDFKit.new(as_html, options=options) + kit = PDFKit.new(as_html, options) kit.to_file('tmp/ssp.pdf') end diff --git a/app/models/evaluation.rb b/app/models/evaluation.rb index 03dccad..df40e32 100644 --- a/app/models/evaluation.rb +++ b/app/models/evaluation.rb @@ -1,5 +1,3 @@ -require 'inspec2ckl' - class Evaluation include Mongoid::Document include Mongoid::Timestamps @@ -36,20 +34,22 @@ def to_json end def to_ckl - inspec2ckl = Inspec2ckl.new(to_json, nil, nil, nil) - inspec2ckl.to_ckl + InspecTo.ckl(to_json) end - def status_counts + def status_counts(filters = nil) counts = { open: 0, not_a_finding: 0, not_reviewed: 0, not_tested: 0, not_applicable: 0 } controls = {} profiles.each do |profile| - profile.controls.each do |control| + p_controls = filters.nil? ? profile.controls : profile.filtered_controls(filters) + p_controls.each do |control| controls[control.id] = { control: control, results: [] } end end results.each do |result| - controls[result.control_id][:results] << result + if controls[result.control_id] + controls[result.control_id][:results] << result + end end controls.each do |_, ct| sym = status_symbol(ct[:control], ct[:results]) @@ -113,10 +113,11 @@ def tag_values(tag, control, params, nist) end end - def profile_values(cat, params) + def profile_values(cat, params, filters = nil) nist = {} profiles.each do |profile| - profile.controls.each do |control| + p_controls = filters.nil? ? profile.controls : profile.filtered_controls(filters) + p_controls.each do |control| params[:ct_results] = params[:cts][control.id] severity = control.severity next unless severity && (cat.nil? || cat == severity) @@ -129,7 +130,7 @@ def profile_values(cat, params) nist end - def nist_hash(cat, status_symbol) + def nist_hash(cat, status_symbol, filters = nil) cts = {} results.each do |result| unless cts.key?(result.control_id) @@ -138,7 +139,7 @@ def nist_hash(cat, status_symbol) cts[result.control_id] << result end params = { status_symbol: status_symbol, cts: cts } - profile_values cat, params + profile_values cat, params, filters end def self.transform(hash) @@ -155,7 +156,6 @@ def self.transform(hash) all_profiles, results = Profile.parse hash.delete('profiles') hash['results'] = results hash['profiles'] = all_profiles - logger.debug("hash: #{hash.inspect}") hash end diff --git a/app/models/filter.rb b/app/models/filter.rb new file mode 100644 index 0000000..0e3bf78 --- /dev/null +++ b/app/models/filter.rb @@ -0,0 +1,158 @@ +class Filter + include Mongoid::Document + include Mongoid::Timestamps + include Mongoid::Userstamps::Model + field :family, type: Array, default: [] + field :number, type: Array, default: [] + field :sub_fam, type: Array, default: [] + field :sub_num, type: Array, default: [] + field :enhancement, type: Array, default: [] + field :enh_sub_fam, type: Array, default: [] + field :enh_sub_num, type: Array, default: [] + has_and_belongs_to_many :filter_groups + + def self.valid_filter(params) + %w{family number sub_fam sub_num enhancement enh_sub_fam enh_sub_num}.each do |field| + if params.key?(field) + params[field] = params[field].split(',') if params[field].is_a?(String) + else + params[field] = [] + end + end + if params['enhancement'].size != 1 || params['enhancement'].first == 'none' + params['enh_sub_fam'] = [] + params['enh_sub_num'] = [] + end + Filter.new( + family: params['family'], + number: params['number'], + sub_fam: params['sub_fam'], + sub_num: params['sub_num'], + enhancement: params['enhancement'], + enh_sub_fam: params['enh_sub_fam'], + enh_sub_num: params['enh_sub_num'], + ) + end + + def reg_sub_num + if sub_num.nil? || sub_num.empty? + '(\.\d+)?' + elsif sub_num.size == 1 + '\.' + sub_num.first + else + '\.(' + sub_num.join('|') + ')' + end + end + + def reg_sub_fam + if sub_fam.nil? || sub_fam.empty? + '(\.[a-z]{1})?(\.\d+)?' + elsif sub_fam.size == 1 + '\.' + sub_fam.first + reg_sub_num + else + '\.(' + sub_fam.join('|') + ')(\.\d+)?' + end + end + + def reg_number + if number.nil? || number.empty? + '\d+' + reg_sub_fam + elsif number.size == 1 + number.first + reg_sub_fam + else + '(' + number.join('|') + ')(\.[a-z]{1})?(\.\d+)?' + end + end + + def reg_enh_sub_num + if enh_sub_num.nil? || enh_sub_num.empty? + '(\.\d+)?\Z' + elsif enh_sub_num.size == 1 + '\.' + enh_sub_num.first + '\Z' + else + '\.(' + enh_sub_num.join('|') + ')\Z' + end + end + + def reg_enh_sub_fam + if enh_sub_fam.nil? || enh_sub_fam.empty? + '(\.[a-z]{1}(\.\d+)?)?\Z' + elsif enh_sub_fam.size == 1 + '\.' + enh_sub_fam.first + reg_enh_sub_num + else + '\.(' + enh_sub_fam.join('|') + ')(\.\d+)?\Z' + end + end + + def reg_enhancement + if enhancement.nil? || enhancement.empty? + '' + elsif enhancement.size == 1 + if enhancement.first == 'none' + '\Z' + else + '\(' + enhancement.first + '\)' + reg_enh_sub_fam + end + else + '\((' + enhancement.join('|') + ')\)' + + reg_enh_sub_fam + end + end + + # AC-15.a.3(2).b.2 + def reg_family + if family.nil? || family.empty? + '\b[A-Z]{2}.*' + elsif family.size == 1 + '\b' + family.first + '\-' + reg_number + reg_enhancement + else + '\b(' + family.join('|') + ').*' + end + end + + def regex + reg_str = reg_family + Object::Regexp.new reg_str + end + + def ary_to_s(val) + val.size == 1 ? val.first.to_s : val.to_s + end + + def family_s + val = "#{ary_to_s(family)}-" + if number.nil? || number.empty? + val += '*' + else + val += ary_to_s(number) + if sub_fam&.present? + val += ".#{ary_to_s(sub_fam)}" + if sub_num&.present? + val += ".#{ary_to_s(sub_num)}" + end + end + end + val + end + + def to_s + if family&.present? + val = family_s + else + val = '*' + end + if enhancement&.present? + if enhancement.first == 'none' + val += "\Z" + else + val += "(#{enhancement})" + end + if enh_sub_fam&.present? + val += ".#{ary_to_s(enh_sub_fam)}" + if enh_sub_num&.present? + val += ".#{ary_to_s(enh_sub_num)}" + end + end + end + val.delete '"' + end +end diff --git a/app/models/filter_group.rb b/app/models/filter_group.rb new file mode 100644 index 0000000..79098d6 --- /dev/null +++ b/app/models/filter_group.rb @@ -0,0 +1,8 @@ +class FilterGroup + include Mongoid::Document + include Mongoid::Timestamps + include Mongoid::Userstamps::Model + field :name, type: String + has_and_belongs_to_many :filters + validates_presence_of :name +end diff --git a/app/models/profile.rb b/app/models/profile.rb index 45e2900..24dec59 100644 --- a/app/models/profile.rb +++ b/app/models/profile.rb @@ -25,6 +25,24 @@ class Profile accepts_nested_attributes_for :profile_attributes validates_presence_of :name, :title, :sha256 + def filtered_controls(filters = nil) + return controls if filters.nil? + filtered_list = controls.select do |control| + keep = false + filters.each do |filter| + control.nist_tags.each do |nist_tag| + if nist_tag.match(filter.regex) + keep = true + break + end + end + break if keep + end + keep + end + filtered_list + end + def to_jbuilder Jbuilder.new do |json| json.extract! self, :name, :title, :maintainer, :copyright, @@ -86,7 +104,6 @@ def self.find_or_new(profile_hash) new_profile_hash, controls = Profile.transform(profile_hash.deep_dup) profile = Profile.create(new_profile_hash) controls.each do |control| - logger.debug "Add Control: #{control.keys}" profile.controls.create(control) end end @@ -99,16 +116,12 @@ def self.parse(profiles) profiles.try(:each) do |profile_hash| profile = Profile.find_or_new profile_hash profile_hash['controls'].try(:each) do |control_hash| - logger.debug "For #{control_hash['control_id']}" if (control = profile.controls.where(control_id: control_hash['control_id']).try(:first)) - logger.debug 'Found Control' control_hash['results'].try(:each) do |result| - logger.debug "For result #{result.inspect}" control.results.create(result) end results.concat control.results end - logger.debug "Results: #{results.size}" end all_profiles << profile end diff --git a/app/models/repo.rb b/app/models/repo.rb index 20bfd97..3692f05 100644 --- a/app/models/repo.rb +++ b/app/models/repo.rb @@ -15,11 +15,21 @@ def self.types end def projects(repo_cred) + return [] unless repo_cred if repo_type == 'GitLab' - api = Git::GitLabProxy.new(api_url, repo_cred.token) + begin + api = Git::GitLabProxy.new(api_url, repo_cred.token) + api.projects + rescue Gitlab::Error::Unauthorized, UncaughtThrowError + [] + end elsif repo_type == 'GitHub' - api = Git::GitHubProxy.new(repo_cred.token) + begin + api = Git::GitHubProxy.new(repo_cred.token) + api.projects + rescue Octokit::Unauthorized, UncaughtThrowError + [] + end end - api.projects end end diff --git a/app/views/evaluations/show.html.erb b/app/views/evaluations/show.html.erb index 813158d..65cbb41 100644 --- a/app/views/evaluations/show.html.erb +++ b/app/views/evaluations/show.html.erb @@ -165,7 +165,7 @@ -
+
@@ -189,40 +189,284 @@
- +
+ <%= form_for :filter, url: evaluation_filter_path(@evaluation), remote: true do |form| %> +
+
+
+

Families

+
+
+
+ <% @nist_hash["children"].first((@nist_hash["children"].size/2).to_i).each do |family| %> +
+ +
+ <% end %> +
+
+ <% @nist_hash["children"].last((@nist_hash["children"].size/2).to_i).each do |family| %> +
+ +
+ <% end %> +
+
+
+
+
+

Numbers

+
+
+
+ <% (1..11).each do |num| %> +
+ +
+ <% end %> +
+
+ <% (12..22).each do |num| %> +
+ +
+ <% end %> +
+
+ <% (23..33).each do |num| %> +
+ +
+ <% end %> +
+
+ <% (34..44).each do |num| %> +
+ +
+ <% end %> +
+
+
+
+
+

Part

+
+
+
+ <% ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k'].each do |fam| %> +
+ +
+ <% end %> +
+
+
+
+
+

Sub-Part

+
+
+
+ <% (1..10).each do |num| %> +
+ +
+ <% end %> +
+
+
+
+
+

Enhancement

+
+
+
+ <% (1..12).each do |enhancement| %> +
+ +
+ <% end %> +
+
+ <% (13..24).each do |enhancement| %> +
+ +
+ <% end %> +
+ +
+
+
+
+
+
+

Part

+
+
+
+ <% ['a', 'b', 'c', 'd', 'e', 'f'].each do |fam| %> +
+ +
+ <% end %> +
+
-
- <% @nist_hash["children"].last((@nist_hash["children"].size/2).to_i).each do |family| %> -
- +
+
+

Sub-Part

+
+
+
+ <% (1..5).each do |num| %> +
+ +
+ <% end %>
+
+
+
+
+
+
+ +
+
+
+ <%= button_tag(type: "submit", class: "btn btn-primary pull-right margin-right") do %> + Filter by Selected <% end %>
+
+ +
<% end %>
- +
@@ -265,7 +509,7 @@ <%= control.title %> <%= link_to control.control_id, profile_control_path(control.profile_id, control.id) %> <%= control.severity %> - <%= control.tag 'nist' %> + <%= control.nist_tags.join(', ') %> <%= control.code %> <% end %> @@ -296,6 +540,91 @@ var shown_id = null; var loaded = null; var compliance_count = 0.0; var total_count = 0; +var nist_counts = {"AC": 25, "AU": 16, "AT": 5, "CM": 11, "CP": 13, "IA": 11, "IR": 10, "MA": 6, "MP": 8, + "PS": 8, "PE": 20, "PL": 9, "PM": 16, "RA": 6, "CA": 9, "SC": 44, "SI": 17, "SA": 22}; + +function filterNumbers() { + count = 0; + checked = null; + $('input:checkbox[id^="fam_cb_"]:checked').each(function(){ + count += 1; + checked = $(this).val(); + }); + if(count == 1) { + $('input:checkbox[id^="num_cb_"]').each(function(){ + if($(this).val() <= nist_counts[checked]) { + $(this).prop("disabled", false); + } else { + $(this).prop('checked', false); + $(this).prop("disabled", true); + } + $('input:checkbox[id^="sub_fam_cb_"]').each(function(){ + $(this).prop("disabled", false); + }); + $('input:checkbox[id^="sub_num_cb_"]').each(function(){ + $(this).prop("disabled", false); + }); + }); + } else if (count > 1) { + $('input:checkbox[id^="num_cb_"]').each(function(){ + $(this).prop('checked', false); + $(this).prop("disabled", true); + }); + $('input:checkbox[id^="sub_fam_cb_"]').each(function(){ + $(this).prop('checked', false); + $(this).prop("disabled", true); + }); + $('input:checkbox[id^="sub_num_cb_"]').each(function(){ + $(this).prop('checked', false); + $(this).prop("disabled", true); + }); + } else { + $('input:checkbox[id^="num_cb_"]').each(function(){ + $(this).prop("disabled", false); + }); + } +} + +function noEnhancements() { + $('input:checkbox[id^="enhancement_cb_"]').each(function(){ + $(this).prop('checked', false); + }); + $('input:checkbox[id^="enh_sub_fam_cb_"]').each(function(){ + $(this).prop('checked', false); + $(this).prop("disabled", true); + }); + $('input:checkbox[id^="enh_sub_num_cb_"]').each(function(){ + $(this).prop('checked', false); + $(this).prop("disabled", true); + }); +} + +function yesEnhancements() { + count = 0; + $('input:checkbox[id^="enhancement_cb_"]:checked').each(function(){ + count += 1; + }); + $('input:checkbox[id^="none_enhance_cb"]').each(function(){ + $(this).prop('checked', false); + }); + if(count > 1){ + $('input:checkbox[id^="enh_sub_fam_cb_"]').each(function(){ + $(this).prop('checked', false); + $(this).prop("disabled", true); + }); + $('input:checkbox[id^="enh_sub_num_cb_"]').each(function(){ + $(this).prop('checked', false); + $(this).prop("disabled", true); + }); + } else { + $('input:checkbox[id^="enh_sub_fam_cb_"]').each(function(){ + $(this).prop("disabled", false); + }); + $('input:checkbox[id^="enh_sub_num_cb_"]').each(function(){ + $(this).prop("disabled", false); + }); + } +} function show_details(profile_id, control_id, evaluation_id) { hsh = "#" + control_id; @@ -382,6 +711,16 @@ document.addEventListener("turbolinks:load", function() { }); } + $("#build_btn").click(function(){ + console.log("show builder"); + document.getElementById("filter_row").style.display = 'block'; + }); + + $("#close_build_btn").click(function(){ + console.log("Close builder"); + document.getElementById("filter_row").style.display = 'none'; + }); + $("#clear_filters_button").click(function() { if (currentDepth == 0) { document.getElementById("clear_filters_button").style.visibility = "hidden"; @@ -425,7 +764,6 @@ document.addEventListener("turbolinks:load", function() { if (!(ary.includes(child3.name))) { ary.push(child3.name); status_value = child3.status_value; - console.log("Child3: " + child3.name + ", status_value: " + status_value); if (status_value == 0.2) { not_app += 1; } else if (status_value == 0.4) { @@ -446,8 +784,8 @@ document.addEventListener("turbolinks:load", function() { //var not_rev = accumulate_status(d, 0.4); //var not_find = accumulate_status(d, 0.6); //var open = accumulate_status(d, 0.8); - total_count = not_app + not_find + not_rev + open; - compliance_count = ((not_find + not_app) / total_count) * 100; + total_count = not_find + not_rev + open; + compliance_count = (not_find / total_count) * 100; status_data = [ ['Not A Finding', not_find], diff --git a/app/views/evaluations/ssp.html.erb b/app/views/evaluations/ssp.html.erb index 974e1c3..ee4cb7c 100644 --- a/app/views/evaluations/ssp.html.erb +++ b/app/views/evaluations/ssp.html.erb @@ -134,8 +134,8 @@
<% counts, _ = @evaluation.status_counts %> - <% total_count = counts[:open] + counts[:not_a_finding] + counts[:not_reviewed] + counts[:not_tested] + counts[:not_applicable] %> - <% compliance_level = ((counts[:not_a_finding] + counts[:not_applicable]).to_f / total_count) * 100 %> + <% total_count = counts[:open] + counts[:not_a_finding] + counts[:not_reviewed] + counts[:not_tested] %> + <% compliance_level = ((counts[:not_a_finding]).to_f / total_count) * 100 %>

Compliance Level: <%= compliance_level.round(1) %>%

diff --git a/app/views/evaluations/ssp_pdf.html.erb b/app/views/evaluations/ssp_pdf.html.erb index bc879e0..28aef1f 100644 --- a/app/views/evaluations/ssp_pdf.html.erb +++ b/app/views/evaluations/ssp_pdf.html.erb @@ -693,8 +693,8 @@ fieldset[disabled] .btn-link:focus {

<%= evaluation.profiles.map(&:name).join(', ') %>

<% counts, _ = evaluation.status_counts %> - <% total_count = counts[:open] + counts[:not_a_finding] + counts[:not_reviewed] + counts[:not_tested] + counts[:not_applicable] %> - <% compliance_level = ((counts[:not_a_finding] + counts[:not_applicable]).to_f / total_count) * 100 %> + <% total_count = counts[:open] + counts[:not_a_finding] + counts[:not_reviewed] + counts[:not_tested] %> + <% compliance_level = ((counts[:not_a_finding]).to_f / total_count) * 100 %>

Compliance Level: <%= compliance_level.round(1) %>%

diff --git a/app/views/filter_groups/_filter_group.json.jbuilder b/app/views/filter_groups/_filter_group.json.jbuilder new file mode 100644 index 0000000..98a0c52 --- /dev/null +++ b/app/views/filter_groups/_filter_group.json.jbuilder @@ -0,0 +1,2 @@ +json.extract! filter_group, :id, :name, :created_at, :updated_at +json.url filter_group_url(filter_group, format: :json) diff --git a/app/views/filter_groups/_form.html.erb b/app/views/filter_groups/_form.html.erb new file mode 100644 index 0000000..bf743b6 --- /dev/null +++ b/app/views/filter_groups/_form.html.erb @@ -0,0 +1,24 @@ +<%= form_with(model: filter_group, local: true) do |form| %> + <% if filter_group.errors.any? %> +
+

<%= pluralize(filter_group.errors.count, "error") %> prohibited this filter_group from being saved:

+ +
    + <% filter_group.errors.full_messages.each do |message| %> +
  • <%= message %>
  • + <% end %> +
+
+ <% end %> + +
+ <%= form.label :name %> + <%= form.text_field :name, id: :filter_group_name %> +
+
+
+ <%= button_tag(type: "submit", class: "btn btn-primary") do %> + Save + <% end %> +
+<% end %> diff --git a/app/views/filter_groups/edit.html.erb b/app/views/filter_groups/edit.html.erb new file mode 100644 index 0000000..10ab8b1 --- /dev/null +++ b/app/views/filter_groups/edit.html.erb @@ -0,0 +1,28 @@ +
+

+ Edit Filter Group +

+ +
+ + +
+
+
+
+
+

Edit Filter Group

+
+ +
+ <%= render 'form', filter_group: @filter_group %> +
+
+
+
+
diff --git a/app/views/filter_groups/index.html.erb b/app/views/filter_groups/index.html.erb new file mode 100644 index 0000000..9368f85 --- /dev/null +++ b/app/views/filter_groups/index.html.erb @@ -0,0 +1,45 @@ +
+

+ Filter Groups +

+ +
+ +
+
+
+
+
+

Filter Groups

+
+ + <% if can? :read, FilterGroup %> +
+
+ + + + + + <% @filter_groups.each do |filter_group| %> + + + + + + <% end %> +
NameFilters
<%= link_to filter_group.name, filter_group %> + <%= filter_group.filters.map{|filter| filter.to_s}.join(', ') %> + <%= link_to 'Destroy', filter_group, method: :delete, data: { confirm: 'Are you sure?' } %>
+
+ <% end %> +
+
+
+
+ <%= link_to 'New Filter Group', new_filter_group_path, class: "btn btn-info" %> +
+ diff --git a/app/views/filter_groups/index.json.jbuilder b/app/views/filter_groups/index.json.jbuilder new file mode 100644 index 0000000..d0e47d1 --- /dev/null +++ b/app/views/filter_groups/index.json.jbuilder @@ -0,0 +1 @@ +json.array! @filter_groups, partial: 'filter_groups/filter_group', as: :filter_group diff --git a/app/views/filter_groups/new.html.erb b/app/views/filter_groups/new.html.erb new file mode 100644 index 0000000..119fb63 --- /dev/null +++ b/app/views/filter_groups/new.html.erb @@ -0,0 +1,27 @@ +
+

+ New Filter Group +

+ +
+ + +
+
+
+
+
+

New Filter Group

+
+ +
+ <%= render 'form', filter_group: @filter_group %> +
+
+
+
+
diff --git a/app/views/filter_groups/show.html.erb b/app/views/filter_groups/show.html.erb new file mode 100644 index 0000000..49f18f1 --- /dev/null +++ b/app/views/filter_groups/show.html.erb @@ -0,0 +1,90 @@ +
+

+ Filter Group +

+ +
+ + +
+
+
+
+
+

Filter Group

+ <% if can? :read, FilterGroup %> + <%= link_to 'Destroy', @filter_group, :method => :delete, class: "btn btn-danger pull-right", data: { confirm: 'Are you sure?' } %> + <%= link_to (' Edit').html_safe, edit_filter_group_path(@filter_group), class: "btn btn-primary pull-right margin-right" %> + <% end %> +
+ +
+

+ Name: + <%= @filter_group.name %> +

+
+
+
+
+
+
+
+
+

Filters

+
+
+ Add Filter: + <% #form_with(model: @filter_group, url: filter_group_add_filter_path(@filter_group), local: true) do |form| %> + <%= form_with(model: @filter_group, local: true) do |form| %> +

+ <%= form.fields_for :filter_ids do |ff| %> + <%= ff.select :id, Filter.all.collect {|u| [u.to_s, u.id]}, { :prompt => 'Select Filter' } %> + <% end %> + <%= button_tag(type: "submit", class: "btn btn-primary margin-right") do %> + Add + <% end %> +

+ <% end %> +
+
+ + + + + + + + + + + + + <% @filter_group.filters.each do |filter| %> + + + + + + + + + + + + <% end %> +
FilterFamilyNumberPartSub-PartEnhancementPartSub-Part
<%= link_to filter.to_s, filter %><%= ary_to_s(filter.family) %><%= ary_to_s(filter.number) %><%= ary_to_s(filter.sub_fam) %><%= ary_to_s(filter.sub_num) %><%= ary_to_s(filter.enhancement) %><%= ary_to_s(filter.enh_sub_fam) %><%= ary_to_s(filter.enh_sub_num) %> + <%= link_to 'Remove', filter_group_filter_path(@filter_group, filter), :method => :delete, data: { confirm: 'Are you sure?' } %> +
+
+
+ <%= link_to 'Create New Filter', new_filter_path, class: "btn btn-info" %> +
+
+
+
+
diff --git a/app/views/filter_groups/show.json.jbuilder b/app/views/filter_groups/show.json.jbuilder new file mode 100644 index 0000000..01ffaf9 --- /dev/null +++ b/app/views/filter_groups/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'filter_groups/filter_group', filter_group: @filter_group diff --git a/app/views/filters/_filter.json.jbuilder b/app/views/filters/_filter.json.jbuilder new file mode 100644 index 0000000..6cc3e2a --- /dev/null +++ b/app/views/filters/_filter.json.jbuilder @@ -0,0 +1,2 @@ +json.extract! filter, :id, :family, :number, :sub_fam, :sub_num, :enhancement, :enh_sub_fam, :enh_sub_num, :created_at, :updated_at +json.url filter_url(filter, format: :json) diff --git a/app/views/filters/_form.html.erb b/app/views/filters/_form.html.erb new file mode 100644 index 0000000..d1a4e59 --- /dev/null +++ b/app/views/filters/_form.html.erb @@ -0,0 +1,286 @@ +<%= form_with(model: filter, local: true) do |form| %> + <% if filter.errors.any? %> +
+

<%= pluralize(filter.errors.count, "error") %> prohibited this filter from being saved:

+ +
    + <% filter.errors.full_messages.each do |message| %> +
  • <%= message %>
  • + <% end %> +
+
+ <% end %> + +
+
+
+

Families

+
+
+
+ <% nist_hash["children"].first((nist_hash["children"].size/2).to_i).each do |family| %> +
+ +
+ <% end %> +
+
+ <% nist_hash["children"].last((nist_hash["children"].size/2).to_i).each do |family| %> +
+ +
+ <% end %> +
+
+
+
+
+

Numbers

+
+
+
+ <% (1..11).each do |num| %> +
+ +
+ <% end %> +
+
+ <% (12..22).each do |num| %> +
+ +
+ <% end %> +
+
+ <% (23..33).each do |num| %> +
+ +
+ <% end %> +
+
+ <% (34..44).each do |num| %> +
+ +
+ <% end %> +
+
+
+
+
+

Part

+
+
+
+ <% ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k'].each do |fam| %> +
+ +
+ <% end %> +
+
+
+
+
+

Sub-Part

+
+
+
+ <% (1..10).each do |num| %> +
+ +
+ <% end %> +
+
+
+
+
+

Enhancement

+
+
+
+ <% (1..12).each do |enhancement| %> +
+ +
+ <% end %> +
+
+ <% (13..24).each do |enhancement| %> +
+ +
+ <% end %> +
+ +
+
+
+
+
+
+

Part

+
+
+
+ <% ['a', 'b', 'c', 'd', 'e', 'f'].each do |fam| %> +
+ +
+ <% end %> +
+
+
+
+
+

Sub-Part

+
+
+
+ <% (1..5).each do |num| %> +
+ +
+ <% end %> +
+
+
+
+
+
+ <%= button_tag(type: "submit", class: "btn btn-primary") do %> + Save + <% end %> +
+<% end %> + + diff --git a/app/views/filters/edit.html.erb b/app/views/filters/edit.html.erb new file mode 100644 index 0000000..903ab44 --- /dev/null +++ b/app/views/filters/edit.html.erb @@ -0,0 +1,27 @@ +
+

+ Editing Filter +

+ +
+ + +
+
+
+
+
+

Editing Filter

+
+ +
+ <%= render 'form', filter: @filter, nist_hash: @nist_hash %> +
+
+
+
+
diff --git a/app/views/filters/index.html.erb b/app/views/filters/index.html.erb new file mode 100644 index 0000000..0e561b2 --- /dev/null +++ b/app/views/filters/index.html.erb @@ -0,0 +1,55 @@ +
+

+ Filters +

+ +
+ +
+
+
+
+
+

Filters

+
+ + <% if can? :read, FilterGroup %> +
+ + + + + + + + + + + + + <% @filters.each do |filter| %> + + + + + + + + + + + + <% end %> +
FilterFamilyNumberPartSub-PartEnhancementPartSub-Part
<%= link_to filter.to_s, filter %><%= ary_to_s(filter.family) %><%= ary_to_s(filter.number) %><%= ary_to_s(filter.sub_fam) %><%= ary_to_s(filter.sub_num) %><%= ary_to_s(filter.enhancement) %><%= ary_to_s(filter.enh_sub_fam) %><%= ary_to_s(filter.enh_sub_num) %><%= link_to 'Destroy', filter, method: :delete, data: { confirm: 'Are you sure?' } %>
+
+ <% end %> +
+
+
+
+ <%= link_to 'New Filter', new_filter_path, class: "btn btn-info" %> +
+
diff --git a/app/views/filters/index.json.jbuilder b/app/views/filters/index.json.jbuilder new file mode 100644 index 0000000..a934aca --- /dev/null +++ b/app/views/filters/index.json.jbuilder @@ -0,0 +1 @@ +json.array! @filters, partial: 'filters/filter', as: :filter diff --git a/app/views/filters/new.html.erb b/app/views/filters/new.html.erb new file mode 100644 index 0000000..af7ac67 --- /dev/null +++ b/app/views/filters/new.html.erb @@ -0,0 +1,27 @@ +
+

+ New Filter +

+ +
+ + +
+
+
+
+
+

New Filter

+
+ +
+ <%= render 'form', filter: @filter, nist_hash: @nist_hash %> +
+
+
+
+
diff --git a/app/views/filters/show.html.erb b/app/views/filters/show.html.erb new file mode 100644 index 0000000..74f7073 --- /dev/null +++ b/app/views/filters/show.html.erb @@ -0,0 +1,64 @@ +
+

+ Filter +

+ +
+ +
+
+
+
+
+

Filter

+
+ + <% if can? :read, Filter %> +
+

+ Family: + <%= @filter.family %> +

+ +

+ Number: + <%= @filter.number %> +

+ +

+ Part: + <%= @filter.sub_fam %> +

+ +

+ Sub-Part: + <%= @filter.sub_num %> +

+ +

+ Enhancement: + <%= @filter.enhancement %> +

+ +

+ Part: + <%= @filter.enh_sub_fam %> +

+ +

+ Sub-Part: + <%= @filter.enh_sub_num %> +

+
+ <% end %> +
+
+
+ <%= link_to 'Edit', edit_filter_path(@filter), class: "btn btn-info" %> +
+
+
diff --git a/app/views/filters/show.json.jbuilder b/app/views/filters/show.json.jbuilder new file mode 100644 index 0000000..30a8a25 --- /dev/null +++ b/app/views/filters/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'filters/filter', filter: @filter diff --git a/app/views/layouts/_header.html.erb b/app/views/layouts/_header.html.erb index a29724c..3497517 100644 --- a/app/views/layouts/_header.html.erb +++ b/app/views/layouts/_header.html.erb @@ -16,12 +16,13 @@
  • <%= link_to "Evaluations", evaluations_path %>
  • <%= link_to "Profiles", profiles_path %>
  • <%= link_to "Repos", repos_path %>
  • +
  • <%= link_to "Filters", filter_groups_path %>
  • -