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

Issue 243 migrate transformation analyzer #296

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
2 changes: 2 additions & 0 deletions KenticoInspector.Core/Constants/ReportTags.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ public class ReportTags
public const string Performance = "Performance";
public const string PortalEngine = "Portal Engine";
public const string SEO = "SEO";
public const string Security = "Security";
public const string Transformations = "Transformations";
public const string WebParts = "Web Parts";
}
}
5 changes: 4 additions & 1 deletion KenticoInspector.Core/Models/InstanceDetails.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ namespace KenticoInspector.Core.Models
public class InstanceDetails
{
public Guid Guid { get; set; }

public Version AdministrationVersion { get; set; }

public Version DatabaseVersion { get; set; }
public List<Site> Sites { get; set; }

public IEnumerable<Site> Sites { get; set; }
}
}
17 changes: 17 additions & 0 deletions KenticoInspector.Reports.Tests/Helpers/MockInstanceDetails.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using KenticoInspector.Core.Models;
using System;
using System.Collections.Generic;

namespace KenticoInspector.Reports.Tests.Helpers
{
Expand All @@ -9,24 +10,40 @@ public static class MockInstanceDetails
{
AdministrationVersion = new Version("9.0"),
DatabaseVersion = new Version("9.0"),
Sites = new List<Site>
{
new Site { DomainName = "kentico9.com" }
}
};

public static InstanceDetails Kentico10 = new InstanceDetails
{
AdministrationVersion = new Version("10.0"),
DatabaseVersion = new Version("10.0"),
Sites = new List<Site>
{
new Site { DomainName = "kentico10.com" }
}
};

public static InstanceDetails Kentico11 = new InstanceDetails
{
AdministrationVersion = new Version("11.0"),
DatabaseVersion = new Version("11.0"),
Sites = new List<Site>
{
new Site { DomainName = "kentico11.com" }
}
};

public static InstanceDetails Kentico12 = new InstanceDetails
{
AdministrationVersion = new Version("12.0"),
DatabaseVersion = new Version("12.0"),
Sites = new List<Site>
{
new Site { DomainName = "kentico12.com" }
}
};

public static InstanceDetails Get(int majorVersion, Instance instance)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,21 @@
<None Update="TestData\classFormDefinitionXml_Clean_5512.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="TestData\CMS_PageTemplate\PageTemplateWebParts\CleanAscx.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="TestData\CMS_PageTemplate\PageTemplateWebParts\CleanText.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="TestData\CMS_Transformation\TransformationCode\CleanASCX.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="TestData\CMS_Transformation\TransformationCode\CleanText.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="TestData\CMS_Transformation\TransformationCode\WithXssQueryHelperIssueASCX.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="TestData\VersionHistoryItem_Clean_518.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<page>
<webpartzone id="webPartZone" v="1">
<properties>
<property name="containerhideonsubpages">False</property>
<property name="disableviewstate">False</property>
<property name="hideonsubpages">False</property>
<property name="timezonetype">inherit</property>
<property name="useupdatepanel">False</property>
<property name="visible">True</property>
<property name="zonetitle">Web Part Zone</property>
</properties>
<webpart controlid="webPart" guid="d2c96a8b-2b04-4236-ad57-fc534c530418" type="WebPart1" v="1">
<property name="transformationname">PageType1.ASCXTransformation</property>
</webpart>
</webpartzone>
</page>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<page>
<webpartzone id="webPartZone" v="1">
<properties>
<property name="containerhideonsubpages">False</property>
<property name="disableviewstate">False</property>
<property name="hideonsubpages">False</property>
<property name="timezonetype">inherit</property>
<property name="useupdatepanel">False</property>
<property name="visible">True</property>
<property name="zonetitle">Web Part Zone</property>
</properties>
<webpart controlid="webPart" guid="d2c96a8b-2b04-4236-ad57-fc534c530417" type="WebPart2" v="1">
<property name="transformationname">PageType2.TextTransformation</property>
</webpart>
<webpart controlid="webPart" guid="d2c96a8b-2b04-4236-ad57-fc534c530416" type="WebPart3" v="1">
<property name="transformationname">PageType1.JQueryTransformation</property>
</webpart>
</webpartzone>
</page>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div><%# Eval("FieldName") %></div>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div>{%FieldName%}</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div><%# QueryHelper.GetString("xssstring") %></div>
166 changes: 166 additions & 0 deletions KenticoInspector.Reports.Tests/TransformationSecurityAnalysisTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

using KenticoInspector.Core.Constants;
using KenticoInspector.Core.Models;
using KenticoInspector.Reports.Tests.Helpers;
using KenticoInspector.Reports.TransformationSecurityAnalysis;
using KenticoInspector.Reports.TransformationSecurityAnalysis.Models;
using KenticoInspector.Reports.TransformationSecurityAnalysis.Models.Data;
using KenticoInspector.Reports.TransformationSecurityAnalysis.Models.Results;

using NUnit.Framework;

namespace KenticoInspector.Reports.Tests
{
[TestFixture(10)]
[TestFixture(11)]
[TestFixture(12)]
public class TransformationSecurityAnalysisTests : AbstractReportTest<Report, Terms>
{
private readonly Report mockReport;

private IEnumerable<PageDto> CleanPageDtoTable => new List<PageDto>
{
new PageDto()
{
DocumentName = "Page Using ASCX Page Template",
DocumentCulture = "en-US",
NodeAliasPath = "/path/to/page-using-template",
DocumentPageTemplateID = 1
},
new PageDto()
{
DocumentName = "Another Page Using ASCX Page Template",
DocumentCulture = "en-US",
NodeAliasPath = "/path/to/another/page-using-template",
DocumentPageTemplateID = 1
},
new PageDto()
{
DocumentName = "Page Using Text Page Template",
DocumentCulture = "es-ES",
NodeAliasPath = "/path/to/page-using-text-template",
DocumentPageTemplateID = 2
}
};

private IEnumerable<PageTemplateDto> CleanPageTemplateDtoTable => new List<PageTemplateDto>
{
new PageTemplateDto()
{
PageTemplateID = 1,
PageTemplateWebParts = FromFile(@"TestData\CMS_PageTemplate\PageTemplateWebParts\CleanAscx.xml"),
PageTemplateCodeName = "PageTemplateASCX"
},
new PageTemplateDto()
{
PageTemplateID = 2,
PageTemplateWebParts = FromFile(@"TestData\CMS_PageTemplate\PageTemplateWebParts\CleanText.xml"),
PageTemplateCodeName = "PageTemplateText"
}
};

private IEnumerable<TransformationDto> CleanTransformationDtoTable => new List<TransformationDto>
{
new TransformationDto()
{
TransformationName = "ASCXTransformation",
TransformationCode = FromFile(@"TestData\CMS_Transformation\TransformationCode\CleanASCX.txt"),
ClassName = "PageType1",
Type = TransformationType.ASCX
},
new TransformationDto()
{
TransformationName = "JQueryTransformation",
TransformationCode = FromFile(@"TestData\CMS_Transformation\TransformationCode\CleanText.txt"),
ClassName = "PageType1",
Type = TransformationType.JQuery
},
new TransformationDto()
{
TransformationName = "TextTransformation",
TransformationCode = FromFile(@"TestData\CMS_Transformation\TransformationCode\CleanText.txt"),
ClassName = "PageType2",
Type = TransformationType.Text
}
};

public TransformationSecurityAnalysisTests(int majorVersion) : base(majorVersion)
{
mockReport = new Report(_mockDatabaseService.Object, _mockReportMetadataService.Object, _mockInstanceService.Object);
}

private static string FromFile(string path)
{
return File.ReadAllText(path);
}

[Test]
public void Should_ReturnGoodStatusAndGoodSummary_WhenTransformationsHaveNoIssues()
{
// Arrange
ArrangeDatabaseService(CleanTransformationDtoTable);

// Act
var results = mockReport.GetResults();

// Assert
Assert.That(results.Status, Is.EqualTo(ReportResultsStatus.Good));

Assert.That(results.Summary, Is.EqualTo(mockReport.Metadata.Terms.GoodSummary.ToString()));
}

[Test]
public void Should_ReturnWarningStatus_WhenTransformationsHaveSingleXssQueryHelperIssue() => TestSingleIssue(@"TestData\CMS_Transformation\TransformationCode\WithXssQueryHelperIssueASCX.txt", (r, d) => d.XssQueryHelper != string.Empty && r.Uses == 2);

public void TestSingleIssue(string transformationCodeFilePath, Func<TransformationResult, dynamic, bool> transformationResultEvaluator)
{
var transformationDtoTableWithIssue = new List<TransformationDto>
{
new TransformationDto()
{
TransformationName = "ASCXTransformation",
TransformationCode = FromFile(transformationCodeFilePath),
ClassName = "PageType1",
Type = TransformationType.ASCX
}
};

// Arrange
ArrangeDatabaseService(transformationDtoTableWithIssue);

// Act
var results = mockReport.GetResults();

// Assert
Assert.That(results.Status, Is.EqualTo(ReportResultsStatus.Warning));

Assert.That(GetAnonymousTableResult<TableResult<IssueTypeResult>>(results, "issueTypesResult").Rows.Count(), Is.EqualTo(1));
Assert.That(GetAnonymousTableResult<TableResult<TransformationResult>>(results, "transformationsResult").Rows.Count(), Is.EqualTo(1));
Assert.That(GetAnonymousTableResult<TableResult<TransformationResult>>(results, "transformationsResult").Rows, Has.One.Matches<TransformationResult>(row => transformationResultEvaluator(row, row as dynamic)));

Assert.That(GetAnonymousTableResult<TableResult<TransformationUsageResult>>(results, "transformationUsageResult").Rows.Count(), Is.EqualTo(1));

Assert.That(GetAnonymousTableResult<TableResult<TemplateUsageResult>>(results, "templateUsageResult").Rows.Count(), Is.EqualTo(2));
}

private void ArrangeDatabaseService(IEnumerable<TransformationDto> transformationDtoTable)
{
_mockDatabaseService.SetupExecuteSqlFromFile(Scripts.GetTransformations, transformationDtoTable);
_mockDatabaseService.SetupExecuteSqlFromFile(Scripts.GetPages, CleanPageDtoTable);
_mockDatabaseService.SetupExecuteSqlFromFileWithListParameter(Scripts.GetPageTemplates, "DocumentPageTemplateIDs", CleanPageDtoTable.Select(pageDto => pageDto.DocumentPageTemplateID), CleanPageTemplateDtoTable);
}

private static TResult GetAnonymousTableResult<TResult>(ReportResults results, string resultName)
{
return results
.Data
.GetType()
.GetProperty(resultName)
.GetValue(results.Data);
}
}
}
12 changes: 12 additions & 0 deletions KenticoInspector.Reports/KenticoInspector.Reports.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,18 @@
<None Update="TemplateLayoutAnalysis\Metadata\en-US.yaml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="TransformationSecurityAnalysis\Metadata\en-US.yaml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="TransformationSecurityAnalysis\Scripts\GetPages.sql">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="TransformationSecurityAnalysis\Scripts\GetPageTemplates.sql">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="TransformationSecurityAnalysis\Scripts\GetTransformations.sql">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="UnusedPageTypeSummary\Scripts\GetUnusedPageTypes.sql">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;

using KenticoInspector.Core.Models;
using KenticoInspector.Reports.TransformationSecurityAnalysis.Models;
using KenticoInspector.Reports.TransformationSecurityAnalysis.Models.Data;

namespace KenticoInspector.Reports.TransformationSecurityAnalysis
{
/// <summary>
/// Contains instance methods returning <see cref="void"/> that are called by the report to analyze a single <see cref="Transformation"/>.
/// Each method adds issues using <see cref="Transformation.AddIssue(int, int, string)"/>. If there are any issues found, it also adds itself to <see cref="DetectedIssueTypes"/>.
/// </summary>
public class IssueAnalyzers
{
private Terms ReportTerms { get; }

public static IDictionary<string, Term> DetectedIssueTypes { get; set; } = new Dictionary<string, Term>();

public IssueAnalyzers(Terms reportTerms)
{
ReportTerms = reportTerms;
}

public void XssQueryHelper(Transformation transformation) => UseRegexAnalysis(transformation, "queryhelper\\.", ReportTerms.IssueDescriptions.XssQueryHelper);

public void XssQueryString(Transformation transformation) => UseRegexAnalysis(transformation, "[ (.]querystring", ReportTerms.IssueDescriptions.XssQueryString);

public void XssHttpContext(Transformation transformation) => UseRegexAnalysis(transformation, "[ (.]httpcontext\\.", ReportTerms.IssueDescriptions.XssHttpContext);

public void XssServer(Transformation transformation) => UseRegexAnalysis(transformation, "[ (.]server\\.", ReportTerms.IssueDescriptions.XssServer);

public void XssRequest(Transformation transformation) => UseRegexAnalysis(transformation, "[ (.]request\\.", ReportTerms.IssueDescriptions.XssRequest);

public void XssDocument(Transformation transformation) => UseRegexAnalysis(transformation, "<script .*?document\\.", ReportTerms.IssueDescriptions.XssDocument);

public void XssWindow(Transformation transformation) => UseRegexAnalysis(transformation, "window\\.", ReportTerms.IssueDescriptions.XssWindow);

public void ServerSideScript(Transformation transformation) => UseRegexAnalysis(transformation, "<script runat=\"?server\"?", ReportTerms.IssueDescriptions.ServerSideScript);

public void DocumentsMacro(Transformation transformation) => UseRegexAnalysis(transformation, "{%.*?documents[[.]", ReportTerms.IssueDescriptions.DocumentsMacro);

public void QueryMacro(Transformation transformation) => UseRegexAnalysis(transformation, "{\\?.*|{%.*querystring", ReportTerms.IssueDescriptions.QueryMacro);

private void UseRegexAnalysis(Transformation transformation, string pattern, Term issueDescription, [CallerMemberName]string issueType = null)
{
var regex = new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);

var regexMatches = regex.Matches(transformation.Code);

if (regexMatches.Count == 0) return;

DetectedIssueTypes.TryAdd(issueType, issueDescription);

foreach (Match match in regex.Matches(transformation.Code))
{
transformation.AddIssue(match.Index, match.Length, issueType);
}
}
}
}
Loading