Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement CMMC scorecards #4276

Merged
merged 1 commit into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using CSETWebCore.Model.Aggregation;
using System;
using CSETWebCore.Business.Maturity.Configuration;
using CSETWebCore.Model.Maturity;

namespace CSETWebCore.Business.Aggregation
{
Expand Down Expand Up @@ -62,7 +63,8 @@ public List<BarChartX> GetMaturityModelComplianceChart(int aggId)

_context.FillEmptyMaturityQuestionsForAnalysis(assessmentId);

var ms = new Helpers.MaturityStructureAsXml(assessmentId, _context, false);
var options = new StructureOptions() { IncludeQuestionText = false, IncludeSupplemental = false };
var ms = new Helpers.MaturityStructureAsXml(assessmentId, _context, options);
var mx = ms.ToXDocument();

// ignore assessment if it doesn't have a maturity model
Expand Down
160 changes: 139 additions & 21 deletions CSETWebApi/CSETWeb_Api/CSETWebCore.Business/Maturity/CmmcBusiness.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
using CSETWebCore.Interfaces.Helpers;
using CSETWebCore.Model.Maturity;
using System.Collections.Generic;
using System.Xml.Linq;
using System.Linq;


namespace CSETWebCore.Business.Maturity
{
/// <summary>
Expand All @@ -23,7 +25,7 @@
private readonly IAssessmentUtil _assessmentUtil;
private readonly IAdminTabBusiness _adminTabBusiness;

private int _maturityModelId;

Check warning on line 28 in CSETWebApi/CSETWeb_Api/CSETWebCore.Business/Maturity/CmmcBusiness.cs

View workflow job for this annotation

GitHub Actions / .NET Analysis

The field 'CmmcBusiness._maturityModelId' is never used

Check warning on line 28 in CSETWebApi/CSETWeb_Api/CSETWebCore.Business/Maturity/CmmcBusiness.cs

View workflow job for this annotation

GitHub Actions / .NET Analysis

The field 'CmmcBusiness._maturityModelId' is never used

private static object myLock = new object();

Expand All @@ -31,6 +33,10 @@

private AdditionalSupplemental _addlSuppl;

private List<MATURITY_EXTRA> _maturityExtra;

private List<string> _goodAnswerOptions = new List<string>() {"Y", "NA"};


private readonly int modelIdCmmc2 = 19;

Expand All @@ -50,6 +56,14 @@
_addlSuppl = new AdditionalSupplemental(context);

_overlay = new TranslationOverlay();

var query = from me in _context.MATURITY_EXTRA
join mq in _context.MATURITY_QUESTIONS on me.Maturity_Question_Id equals mq.Mat_Question_Id
where mq.Maturity_Model_Id == modelIdCmmc2
select me;


_maturityExtra = query.ToList();
}


Expand Down Expand Up @@ -85,11 +99,11 @@
/// <summary>
/// Sums up the number of "Y" and "NA" answers for CMMC2F questions
/// at a given maturity level.
///
/// This method assumes 1 point rewarded per MET question.
/// </summary>
public int GetScoreForLevel(int assessmentId, int level)
{
var goodAnswers = new List<string>() { "Y", "NA" };

var levelId = _context.MATURITY_LEVELS
.Where(x => x.Level == level && x.Maturity_Model_Id == modelIdCmmc2)
.Select(x => x.Maturity_Level_Id)
Expand All @@ -102,13 +116,108 @@

var answerList = query.ToList().Select(x => x.Answer_Text).ToList();

var score = answerList.Where(x => goodAnswers.Contains(x)).Count();
var score = answerList.Where(x => _goodAnswerOptions.Contains(x)).Count();

return score;
}


/// <summary>
/// Returns a list of scorecards, one for each active level.
/// </summary>
/// <returns></returns>
public List<SprsScoreModel> GetLevelScorecards(int assessmentId)
{
var response = new List<SprsScoreModel>();


IList<SPRSScore> scores = _context.usp_GetSPRSScore(assessmentId);


var biz = new MaturityBusiness(_context, _assessmentUtil, _adminTabBusiness);

var options = new StructureOptions() {
IncludeQuestionText = true,
IncludeSupplemental = false
};

var x = biz.GetMaturityStructureAsXml(assessmentId, options);


var l1 = FilterByLevel(x, 1);
if (l1.Domains.Count > 0)
{
response.Add(l1);
}

var l2 = FilterByLevel(x, 2);
if (l2.Domains.Count > 0)
{
response.Add(l2);
}

var l3 = FilterByLevel(x, 3);
if (l3.Domains.Count > 0)
{
response.Add(l3);
}

return response;
}


/// <summary>
/// Returns an object with all domains and questions at
/// the specified maturity level.
///
/// Questions are also assigned their score/deduction.
/// </summary>
private SprsScoreModel FilterByLevel(XDocument x, int level)
{
var response = new SprsScoreModel();
response.Level = level;


foreach (var goal in x.Descendants("Goal"))
{
var d = new SprsDomain();
d.DomainName = goal.Attribute("title").Value;


foreach (var question in goal.Descendants("Question"))
{
if (question.Attribute("level").Value != level.ToString())
{
continue;
}

var q = new SprsQuestion();
q.QuestionId = int.Parse(question.Attribute("questionid").Value);
q.Title = question.Attribute("displaynumber").Value;
q.QuestionText = question.Attribute("questiontext")?.Value;
q.AnswerText = question.Attribute("answer").Value;

// default the question score to 1
q.Score = DeductionForAnswer(q);

d.Questions.Add(q);
}

if (d.Questions.Count() > 0)
{
response.Domains.Add(d);
}
}

return response;
}


/// <summary>
///
/// DEPRECATE OR REFACTOR THIS
///
///
/// Calculates a SPRS score based on the question scoring values in MATURITY_EXTRA.
/// </summary>
/// <param name="assessmentId"></param>
Expand All @@ -120,10 +229,11 @@
IList<SPRSScore> scores = _context.usp_GetSPRSScore(assessmentId);


var maturityExtra = _context.MATURITY_EXTRA.ToList();
//var maturityExtra = _context.MATURITY_EXTRA.ToList();

var biz = new MaturityBusiness(_context, _assessmentUtil, _adminTabBusiness);
var x = biz.GetMaturityStructureAsXml(assessmentId, true);
var options = new StructureOptions() { IncludeQuestionText = true, IncludeSupplemental = false };
var x = biz.GetMaturityStructureAsXml(assessmentId, options);


int calculatedScore = 110;
Expand All @@ -137,28 +247,14 @@
foreach (var question in goal.Descendants("Question"))
{
var q = new SprsQuestion();
q.Id = question.Attribute("displaynumber").Value;
q.Title = question.Attribute("displaynumber").Value;
q.QuestionText = question.Attribute("questiontext").Value;
q.AnswerText = question.Attribute("answer").Value;

int questionID = int.Parse(question.Attribute("questionid").Value);
var mx = maturityExtra.Where(x => x.Maturity_Question_Id == questionID).FirstOrDefault();

switch (q.AnswerText)
{
case "Y":
case "NA":
break;
case "N":
case "U":
if (mx != null)
{
q.Score = mx.SPRSValue ?? 0;
}
break;
}

calculatedScore -= q.Score;
calculatedScore -= DeductionForAnswer(q);

d.Questions.Add(q);
}
Expand All @@ -173,5 +269,27 @@

return response;
}


/// <summary>
///
/// </summary>
private int DeductionForAnswer(SprsQuestion q)
{
if (_goodAnswerOptions.Contains(q.AnswerText))
{
return 0;
}

var mx = _maturityExtra.Where(x => x.Maturity_Question_Id == q.QuestionId).FirstOrDefault();

// default to 1 if no score is defined
if (mx == null || mx.SPRSValue == null)
{
return 1;
}

return (int)mx.SPRSValue;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,8 @@ public List<DomainAnswers> GetAnswerDistributionByDomain(int assessmentId)
var response = new List<DomainAnswers>();


var structure = new MaturityStructureAsXml(assessmentId, _context, false);
var options = new StructureOptions() { IncludeQuestionText = false, IncludeSupplemental = false };
var structure = new MaturityStructureAsXml(assessmentId, _context, options);


// In this model sructure, the Goal element represents domains
Expand Down Expand Up @@ -1300,9 +1301,9 @@ public Model.Maturity.CPG.ContentModel GetMaturityStructure(int assessmentId, bo
/// </summary>
/// <param name="assessmentId"></param>
/// <returns></returns>
public XDocument GetMaturityStructureAsXml(int assessmentId, bool includeSupplemental)
public XDocument GetMaturityStructureAsXml(int assessmentId, StructureOptions options)
{
var x = new MaturityStructureAsXml(assessmentId, _context, includeSupplemental);
var x = new MaturityStructureAsXml(assessmentId, _context, options);
return x.ToXDocument();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,24 +35,33 @@ public class MaturityStructureAsXml

private AdditionalSupplemental _addlSuppl { get; set; }


private bool _includeQuestionText = true;

private bool _includeSupplemental = true;

/// <summary>
/// The consumer can optionally suppress
/// grouping descriptions, question text and supplemental info
/// if they want a smaller response object.
/// </summary>
private bool _includeText = true;
private bool _includeOtherText = true;



/// <summary>
/// Returns a populated instance of the maturity grouping
/// and question structure for an assessment.
/// </summary>
/// <param name="assessmentId"></param>
public MaturityStructureAsXml(int assessmentId, CSETContext context, bool includeText)
public MaturityStructureAsXml(int assessmentId, CSETContext context, StructureOptions options)
{
this.AssessmentId = assessmentId;
this._context = context;
this._includeText = includeText;

this._includeQuestionText = options.IncludeQuestionText;
this._includeSupplemental = options.IncludeSupplemental;
this._includeOtherText = options.IncludeOtherText;

this._addlSuppl = new AdditionalSupplemental(context);

Expand Down Expand Up @@ -167,7 +176,7 @@ private void GetSubgroupsAsXml(XElement xE, int? parentID,
xE.Add(xGrouping);
xGrouping.SetAttributeValue("abbreviation", sg.Abbreviation);

if (_includeText)
if (_includeOtherText)
{
xGrouping.SetAttributeValue("description", sg.Description);
}
Expand All @@ -193,19 +202,26 @@ private void GetSubgroupsAsXml(XElement xE, int? parentID,
xQuestion.SetAttributeValue("questionid", myQ.Mat_Question_Id.ToString());
xQuestion.SetAttributeValue("parentquestionid", myQ.Parent_Question_Id.ToString());
xQuestion.SetAttributeValue("sequence", myQ.Sequence.ToString());
xQuestion.SetAttributeValue("level", myQ.Maturity_Level.Level.ToString());
xQuestion.SetAttributeValue("displaynumber", myQ.Question_Title);
xQuestion.SetAttributeValue("answer", answer?.a.Answer_Text ?? "");
xQuestion.SetAttributeValue("comment", answer?.a.Comment ?? "");
xQuestion.SetAttributeValue("isparentquestion", B2S(parentQuestionIDs.Contains(myQ.Mat_Question_Id)));

if (_includeText)
if (_includeQuestionText)
{
xQuestion.SetAttributeValue("questiontext", myQ.Question_Text.Replace("\r\n", "<br/>").Replace("\n", "<br/>").Replace("\r", "<br/> "));
xQuestion.SetAttributeValue("securitypractice", myQ.Security_Practice);
}

if (_includeSupplemental)
{
xQuestion.SetAttributeValue("supplemental", myQ.Supplemental_Info);
}

if (_includeOtherText)
{
// CPG question elements
xQuestion.SetAttributeValue("securitypractice", myQ.Security_Practice);
xQuestion.SetAttributeValue("outcome", myQ.Outcome);
xQuestion.SetAttributeValue("scope", myQ.Scope);
xQuestion.SetAttributeValue("recommendedaction", myQ.Recommend_Action);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@
//
//
////////////////////////////////
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


namespace CSETWebCore.Model.Maturity
{
public class SprsScoreModel
{
public int Level { get; set; }

public int SprsScore { get; set; }

public List<SprsDomain> Domains { get; set; }
Expand All @@ -40,7 +39,9 @@ public SprsDomain()

public class SprsQuestion
{
public string Id { get; set; }
public int QuestionId { get; set; }

public string Title { get; set; }
public string QuestionText { get; set; }

public string AnswerText { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSETWebCore.Model.Maturity
{
public class StructureOptions
{
public bool IncludeQuestionText { get; set; } = true;

public bool IncludeSupplemental { get; set; } = false;

public bool IncludeOtherText { get; set; } = false;
}
}
Loading
Loading