Skip to content

Commit

Permalink
Improving HttpMock capabilities (#527)
Browse files Browse the repository at this point in the history
* Improving HttpMock capabilities

* Updating validatorbase and test file name
  • Loading branch information
eddynaka committed Aug 6, 2021
1 parent 6ee5829 commit ab164b7
Show file tree
Hide file tree
Showing 12 changed files with 225 additions and 44 deletions.
3 changes: 2 additions & 1 deletion Src/Plugins/Security/SEC101_044.NpmCredentialsValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,9 @@ protected override IEnumerable<ValidationResult> IsValidStaticHelper(Dictionary<

try
{
using var requestWithNoCredentials = new HttpRequestMessage(HttpMethod.Get, uri);
using HttpResponseMessage responseWithNoCredentials = client
.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead)
.SendAsync(requestWithNoCredentials, HttpCompletionOption.ResponseHeadersRead)
.GetAwaiter()
.GetResult();

Expand Down
66 changes: 66 additions & 0 deletions Src/Plugins/Tests.Security/Helpers/HttpMockHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

using Moq;
using Moq.Protected;

namespace Microsoft.CodeAnalysis.Sarif.PatternMatcher.Plugins.Security.Helpers
{
public class HttpMockHelper : DelegatingHandler
{
private readonly List<Tuple<HttpRequestMessage, HttpStatusCode, HttpContent>> _fakeResponses =
new List<Tuple<HttpRequestMessage, HttpStatusCode, HttpContent>>();

public void Mock(HttpRequestMessage httpRequestMessage, HttpStatusCode httpStatusCode, HttpContent httpContent)
{
_fakeResponses.Add(new Tuple<HttpRequestMessage, HttpStatusCode, HttpContent>(httpRequestMessage, httpStatusCode, httpContent));
}

public void Clear()
{
_fakeResponses.Clear();
}

public static HttpMessageHandler Mock(HttpStatusCode httpStatusCode, HttpContent httpContent)
{
var mockMessageHandler = new Mock<HttpMessageHandler>();
mockMessageHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = httpStatusCode,
Content = httpContent
});

return mockMessageHandler.Object;
}

protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
Tuple<HttpRequestMessage, HttpStatusCode, HttpContent> fakeResponse = _fakeResponses.Find(fr =>
fr.Item1.RequestUri == request.RequestUri
&& fr.Item1.Headers.IsEmptyEnumerable() == request.Headers.IsEmptyEnumerable());

if (fakeResponse == null)
{
fakeResponse = _fakeResponses.Find(fr =>
fr.Item1.RequestUri == request.RequestUri
&& fr.Item1.Headers.Authorization == request.Headers.Authorization);
}

return Task.FromResult(
new HttpResponseMessage(fakeResponse.Item2)
{
RequestMessage = request,
Content = fakeResponse.Item3
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,11 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

using Moq;
using Moq.Protected;

namespace Microsoft.CodeAnalysis.Sarif.PatternMatcher.Plugins.Security
namespace Microsoft.CodeAnalysis.Sarif.PatternMatcher.Plugins.Security.Helpers
{
public static class MockHelper
public static class ValidatorHelper
{
/// <summary>
/// ResetStaticInstance is a method that will invoke the static instance creation.
Expand All @@ -25,19 +18,5 @@ public static void ResetStaticInstance<T>()
ConstructorInfo constructor = typeof(T).GetConstructor(BindingFlags.Static | BindingFlags.NonPublic, null, new Type[0], null);
constructor.Invoke(null, null);
}

public static HttpMessageHandler MockHttpMessageHandler(HttpStatusCode httpStatusCode, HttpContent httpContent)
{
var mockMessageHandler = new Mock<HttpMessageHandler>();
mockMessageHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = httpStatusCode,
Content = httpContent
});

return mockMessageHandler.Object;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

using FluentAssertions;

using Microsoft.CodeAnalysis.Sarif.PatternMatcher.Plugins.Security.Helpers;
using Microsoft.CodeAnalysis.Sarif.PatternMatcher.Sdk;

using Xunit;
Expand Down Expand Up @@ -123,14 +124,14 @@ public void SlackTokenValidatorMockHttp_Test()
var fingerprint = new Fingerprint(fingerprintText);
var keyValuePairs = new Dictionary<string, string>();

MockHelper.ResetStaticInstance<SlackTokenValidator>();
using var httpClient = new HttpClient(MockHelper.MockHttpMessageHandler(testCase.HttpStatusCode, testCase.HttpContent));
ValidatorHelper.ResetStaticInstance<SlackTokenValidator>();
using var httpClient = new HttpClient(HttpMockHelper.Mock(testCase.HttpStatusCode, testCase.HttpContent));
SlackTokenValidator.Instance.SetHttpClient(httpClient);

ValidationState currentState = SlackTokenValidator.IsValidDynamic(ref fingerprint,
ref message,
keyValuePairs,
ref resultLevelKind);
ref message,
keyValuePairs,
ref resultLevelKind);
if (currentState != testCase.ExpectedValidationState)
{
sb.AppendLine($"The test case '{testCase.Title}' was expecting '{testCase.ExpectedValidationState}' but found '{currentState}'.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

using FluentAssertions;

using Microsoft.CodeAnalysis.Sarif.PatternMatcher.Plugins.Security.Helpers;
using Microsoft.CodeAnalysis.Sarif.PatternMatcher.Sdk;

using Xunit;
Expand Down Expand Up @@ -82,8 +83,8 @@ public void GitHubAppCredentialsValidator_MockHttpTests()
var fingerprint = new Fingerprint(fingerprintText);
var keyValuePairs = new Dictionary<string, string>();

MockHelper.ResetStaticInstance<GitHubAppCredentialsValidator>();
using var httpClient = new HttpClient(MockHelper.MockHttpMessageHandler(testCase.HttpStatusCode, testCase.HttpContent));
ValidatorHelper.ResetStaticInstance<GitHubAppCredentialsValidator>();
using var httpClient = new HttpClient(HttpMockHelper.Mock(testCase.HttpStatusCode, testCase.HttpContent));
GitHubAppCredentialsValidator.Instance.SetHttpClient(httpClient);

ValidationState currentState = GitHubAppCredentialsValidator.IsValidDynamic(ref fingerprint,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

using FluentAssertions;

using Microsoft.CodeAnalysis.Sarif.PatternMatcher.Plugins.Security.Helpers;
using Microsoft.CodeAnalysis.Sarif.PatternMatcher.Sdk;

using Xunit;
Expand Down Expand Up @@ -57,8 +58,8 @@ public void SquarePatValidator_MockHttpTests()
var fingerprint = new Fingerprint(fingerprintText);
var keyValuePairs = new Dictionary<string, string>();

MockHelper.ResetStaticInstance<SquarePatValidator>();
using var httpClient = new HttpClient(MockHelper.MockHttpMessageHandler(testCase.HttpStatusCode, testCase.HttpContent));
ValidatorHelper.ResetStaticInstance<SquarePatValidator>();
using var httpClient = new HttpClient(HttpMockHelper.Mock(testCase.HttpStatusCode, testCase.HttpContent));
SquarePatValidator.Instance.SetHttpClient(httpClient);

ValidationState currentState = SquarePatValidator.IsValidDynamic(ref fingerprint,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

using FluentAssertions;

using Microsoft.CodeAnalysis.Sarif.PatternMatcher.Plugins.Security.Helpers;
using Microsoft.CodeAnalysis.Sarif.PatternMatcher.Sdk;

using Xunit;
Expand Down Expand Up @@ -81,8 +82,8 @@ public void SquareCredentialsValidator_MockHttpTests()
var fingerprint = new Fingerprint(fingerprintText);
var keyValuePairs = new Dictionary<string, string>();

MockHelper.ResetStaticInstance<SquareCredentialsValidator>();
using var httpClient = new HttpClient(MockHelper.MockHttpMessageHandler(testCase.HttpStatusCode, testCase.HttpContent));
ValidatorHelper.ResetStaticInstance<SquareCredentialsValidator>();
using var httpClient = new HttpClient(HttpMockHelper.Mock(testCase.HttpStatusCode, testCase.HttpContent));
SquareCredentialsValidator.Instance.SetHttpClient(httpClient);

ValidationState currentState = SquareCredentialsValidator.IsValidDynamic(ref fingerprint,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

using FluentAssertions;

using Microsoft.CodeAnalysis.Sarif.PatternMatcher.Plugins.Security.Helpers;
using Microsoft.CodeAnalysis.Sarif.PatternMatcher.Sdk;

using Xunit;
Expand Down Expand Up @@ -129,8 +130,8 @@ public void NpmAuthorTokenValidator_MockHttpTests()
var fingerprint = new Fingerprint(fingerprintText);
var keyValuePairs = new Dictionary<string, string>();

MockHelper.ResetStaticInstance<NpmAuthorTokenValidator>();
using var httpClient = new HttpClient(MockHelper.MockHttpMessageHandler(testCase.HttpStatusCode, testCase.HttpContent));
ValidatorHelper.ResetStaticInstance<NpmAuthorTokenValidator>();
using var httpClient = new HttpClient(HttpMockHelper.Mock(testCase.HttpStatusCode, testCase.HttpContent));
NpmAuthorTokenValidator.Instance.SetHttpClient(httpClient);

ValidationState currentState = NpmAuthorTokenValidator.IsValidDynamic(ref fingerprint,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

namespace Microsoft.CodeAnalysis.Sarif.PatternMatcher.Plugins.Security.Validators
{
public class SqlCredenntialsValidatorTests
public class SqlCredentialsValidatorTests
{
[Fact]
public void SqlCredentialsValidatorTests_Test()
Expand All @@ -21,9 +21,9 @@ public void SqlCredentialsValidatorTests_Test()
var keyValuePairs = new Dictionary<string, string>();

ValidationState actualValidationState = SqlCredentialsValidator.IsValidDynamic(ref fingerprint,
ref message,
keyValuePairs,
ref resultLevelKind);
ref message,
keyValuePairs,
ref resultLevelKind);
Assert.True(actualValidationState == ValidationState.Unknown || actualValidationState == ValidationState.UnknownHost);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;

using FluentAssertions;

using Microsoft.CodeAnalysis.Sarif.PatternMatcher.Plugins.Security.Helpers;
using Microsoft.CodeAnalysis.Sarif.PatternMatcher.Sdk;

using Xunit;

namespace Microsoft.CodeAnalysis.Sarif.PatternMatcher.Plugins.Security.Validators
{
public class NpmCredentialsValidatorTests
{
[Fact]
public void NpmCredentialsValidator_MockHttp()
{
const string id = "id";
const string host = "host";
const string secret = "secret";
string uri = $"https://{host}";
using var requestWithNoCredentials = new HttpRequestMessage(HttpMethod.Get, uri);

string credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes(string.Format("{0}:{1}", id, secret)));

using var requestWithCredentials = new HttpRequestMessage(HttpMethod.Get, uri);
requestWithCredentials.Headers.Authorization = new AuthenticationHeaderValue("Basic", credentials);

var mockHandler = new HttpMockHelper();

string fingerprintText = $"[host={host}][id={id}][secret={secret}]";

var testCases = new TestCase[]
{
new TestCase
{
HttpContents = new List<HttpContent>{ null },
HttpStatusCodes = new List<HttpStatusCode>{ HttpStatusCode.OK },
HttpRequestMessages = new List<HttpRequestMessage>{ requestWithNoCredentials },
ExpectedMessage = string.Empty,
Title = "Endpoint does not require credential",
ExpectedValidationState = ValidationState.NoMatch,
},
new TestCase
{
HttpContents = new List<HttpContent>{ null, null },
HttpStatusCodes = new List<HttpStatusCode>{ HttpStatusCode.Unauthorized, HttpStatusCode.OK },
HttpRequestMessages = new List<HttpRequestMessage>{ requestWithNoCredentials, requestWithCredentials },
ExpectedMessage = $"The '{id}' account is compromised for '{host}'.",
Title = "Credential is valid",
ExpectedValidationState = ValidationState.Authorized,
},
new TestCase
{
HttpContents = new List<HttpContent>{ null, null },
HttpStatusCodes = new List<HttpStatusCode>{ HttpStatusCode.Unauthorized, HttpStatusCode.Unauthorized },
HttpRequestMessages = new List<HttpRequestMessage>{ requestWithNoCredentials, requestWithCredentials },
ExpectedMessage = $"The provided '{id}' account secret is not authorized to access '{host}'.",
Title = "Credential is invalid",
ExpectedValidationState = ValidationState.Unauthorized,
},
new TestCase
{
HttpContents = new List<HttpContent>{ null, null },
HttpStatusCodes = new List<HttpStatusCode>{ HttpStatusCode.Unauthorized, HttpStatusCode.NotFound },
HttpRequestMessages = new List<HttpRequestMessage>{ requestWithNoCredentials, requestWithCredentials },
ExpectedMessage = $"An unexpected HTTP response code was received from '{id}' account on '{host}': NotFound.",
Title = "Unexpected NotFound StatusCode",
ExpectedValidationState = ValidationState.Unknown,
},
};

var sb = new StringBuilder();
foreach (TestCase testCase in testCases)
{
for (int i = 0; i < testCase.HttpStatusCodes.Count; i++)
{
mockHandler.Mock(testCase.HttpRequestMessages[i], testCase.HttpStatusCodes[i], testCase.HttpContents[i]);
}

string message = string.Empty;
ResultLevelKind resultLevelKind = default;
var fingerprint = new Fingerprint(fingerprintText);
var keyValuePairs = new Dictionary<string, string>();

ValidatorHelper.ResetStaticInstance<NpmCredentialsValidator>();
using var httpClient = new HttpClient(mockHandler);
NpmCredentialsValidator.Instance.SetHttpClient(httpClient);

ValidationState currentState = NpmCredentialsValidator.IsValidDynamic(ref fingerprint,
ref message,
keyValuePairs,
ref resultLevelKind);

if (currentState != testCase.ExpectedValidationState)
{
sb.AppendLine($"The test '{testCase.Title}' was expecting '{testCase.ExpectedValidationState}' but found '{currentState}'.");
}

if (message != testCase.ExpectedMessage)
{
sb.AppendLine($"The test '{testCase.Title}' was expecting '{testCase.ExpectedMessage}' but found '{message}'.");
}

mockHandler.Clear();
}

sb.Length.Should().Be(0, sb.ToString());
}

private struct TestCase
{
public List<HttpContent> HttpContents { get; set; }
public List<HttpStatusCode> HttpStatusCodes { get; set; }
public List<HttpRequestMessage> HttpRequestMessages { get; set; }

public string Title { get; set; }
public string ExpectedMessage { get; set; }
public ValidationState ExpectedValidationState { get; set; }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

using FluentAssertions;

using Microsoft.CodeAnalysis.Sarif.PatternMatcher.Plugins.Security.Helpers;
using Microsoft.CodeAnalysis.Sarif.PatternMatcher.Sdk;

using Xunit;
Expand Down Expand Up @@ -76,8 +77,8 @@ public void DiscordCredentialsValidator_MockHttpTests()
var fingerprint = new Fingerprint(fingerprintText);
var keyValuePairs = new Dictionary<string, string>();

MockHelper.ResetStaticInstance<DiscordApiCredentialsValidator>();
using var httpClient = new HttpClient(MockHelper.MockHttpMessageHandler(testCase.HttpStatusCode, testCase.HttpContent));
ValidatorHelper.ResetStaticInstance<DiscordApiCredentialsValidator>();
using var httpClient = new HttpClient(HttpMockHelper.Mock(testCase.HttpStatusCode, testCase.HttpContent));
DiscordApiCredentialsValidator.Instance.SetHttpClient(httpClient);

ValidationState currentState = DiscordApiCredentialsValidator.IsValidDynamic(ref fingerprint,
Expand Down

0 comments on commit ab164b7

Please sign in to comment.