Skip to content

Commit

Permalink
Add Square Credentials Dynamic Validator (#515)
Browse files Browse the repository at this point in the history
* Add Square Credentials Dynamic Validator

* Rebaselined tests

* Added additional moq tests for Square API

* Add messaging to test results.

Co-authored-by: Eddy Nakamura <ednakamu@microsoft.com>
  • Loading branch information
Bpendragon and eddynaka committed Jul 30, 2021
1 parent 6e9a22f commit 06ff25f
Show file tree
Hide file tree
Showing 6 changed files with 219 additions and 7 deletions.
80 changes: 80 additions & 0 deletions Src/Plugins/Security/SEC101_011.SquareCredentialsValidator.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
// 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.Json;

using Microsoft.CodeAnalysis.Sarif.PatternMatcher.Sdk;
using Microsoft.RE2.Managed;
Expand All @@ -22,6 +27,18 @@ public static IEnumerable<ValidationResult> IsValidStatic(Dictionary<string, Fle
return IsValidStatic(Instance, groups);
}

public static ValidationState IsValidDynamic(ref Fingerprint fingerprint,
ref string message,
Dictionary<string, string> options,
ref ResultLevelKind resultLevelKind)
{
return IsValidDynamic(Instance,
ref fingerprint,
ref message,
options,
ref resultLevelKind);
}

protected override IEnumerable<ValidationResult> IsValidStaticHelper(Dictionary<string, FlexMatch> groups)
{
if (!groups.TryGetNonEmptyValue("id", out FlexMatch id) ||
Expand All @@ -43,5 +60,68 @@ protected override IEnumerable<ValidationResult> IsValidStaticHelper(Dictionary<

return new[] { validationResult };
}

protected override ValidationState IsValidDynamicHelper(ref Fingerprint fingerprint,
ref string message,
Dictionary<string, string> options,
ref ResultLevelKind resultLevelKind)
{
string id = fingerprint.Id;
string secret = fingerprint.Secret;

const string codeForRequest = "123";
const string uri = "https://connect.squareup.com/oauth2/token";
const string codeNotFoundMessage = "Authorization code not found";

try
{
HttpClient client = CreateHttpClient();

var dict = new Dictionary<string, string>()
{
{ "client_id", id },
{ "code", codeForRequest },
{ "client_secret", secret },
{ "grant_type", "authorization_code" },
};

using var request = new HttpRequestMessage(HttpMethod.Post, uri);
request.Content = new FormUrlEncodedContent(dict);

using HttpResponseMessage response = client
.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
.GetAwaiter()
.GetResult();

switch (response.StatusCode)
{
case HttpStatusCode.Unauthorized:
{
string content = response.Content
.ReadAsStringAsync()
.GetAwaiter()
.GetResult();

if (content.Contains(codeNotFoundMessage))
{
// Credential was valid, code was not.
return ReturnAuthorizedAccess(ref message, id);
}

// Credential not valid.
return ReturnUnauthorizedAccess(ref message, id);
}

default:
{
return ReturnUnexpectedResponseCode(ref message, response.StatusCode);
}
}
}
catch (Exception e)
{
return ReturnUnhandledException(ref message, e);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"",
"Square access key and secret",
"",
""
" (no validation occurred as it was not enabled. Pass '--dynamic-validation' on the command-line to validate this match)"
]
},
"locations": [
Expand Down Expand Up @@ -94,7 +94,7 @@
"",
"Square access key and secret",
"",
""
" (no validation occurred as it was not enabled. Pass '--dynamic-validation' on the command-line to validate this match)"
]
},
"locations": [
Expand Down Expand Up @@ -139,7 +139,7 @@
"",
"Square access key and secret",
"",
""
" (no validation occurred as it was not enabled. Pass '--dynamic-validation' on the command-line to validate this match)"
]
},
"locations": [
Expand Down
6 changes: 4 additions & 2 deletions Src/Plugins/Tests.Security/Tests.Security.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
</EmbeddedResource>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Moq" Version="4.15.2" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\Sarif.PatternMatcher\Sarif.PatternMatcher.csproj" />
<ProjectReference Include="..\Security\Security.csproj" />
<ProjectReference Include="..\..\sarif-sdk\src\Sarif\Sarif.csproj" />
<ProjectReference Include="..\..\sarif-sdk\src\Sarif.Driver\Sarif.Driver.csproj" />
<ProjectReference Include="..\..\sarif-sdk\src\Test.Utilities.Sarif\Test.Utilities.Sarif.csproj" />
</ItemGroup>

<ProjectExtensions><VisualStudio><UserProperties /></VisualStudio></ProjectExtensions>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

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

using FluentAssertions;

using Microsoft.CodeAnalysis.Sarif.PatternMatcher.Sdk;

using Moq;
using Moq.Protected;

using Xunit;

namespace Microsoft.CodeAnalysis.Sarif.PatternMatcher.Plugins.Security.Validators
{
public class SquareCredentialsValidatorTests
{
[Fact]
public void SquareCredentialsValidator_Test()
{
string fingerprintText = "";
if (string.IsNullOrEmpty(fingerprintText))
{
return;
}

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

SquareCredentialsValidator.IsValidDynamic(ref fingerprint,
ref message,
keyValuePairs,
ref resultLevelKind);
}

[Fact]
public void SquareCredentialsValidator_MockHttpTests()
{
var testCases = new[]
{
new
{
Title = "Testing unexpected OK StatusCode",
HttpStatusCode = HttpStatusCode.OK,
HttpContent = (HttpContent)null,
ExpectedValidationState = ValidationState.Unknown,
ExpectedMessage = "An unexpected HTTP response code was received: 'OK'."
},
new
{
Title = "Testing Valid credentials",
HttpStatusCode = HttpStatusCode.Unauthorized,
HttpContent = new StringContent("{\"message\": \"Authorization code not found for app [a]\",\"type\": \"service.not_authorized\"}",
Encoding.UTF8,
"application/json").As<HttpContent>(),
ExpectedValidationState = ValidationState.Authorized,
ExpectedMessage = "The compromised asset is 'a'."
},
new
{
Title = "Testing Invalid credentials",
HttpStatusCode = HttpStatusCode.Unauthorized,
HttpContent = new StringContent("{\n\"message\": \"Not Authorized\",\n\"type\": \"service.not_authorized\"\n}",
Encoding.UTF8,
"application/json").As<HttpContent>(),
ExpectedValidationState = ValidationState.Unauthorized,
ExpectedMessage = "The provided secret is not authorized to access 'a'."
},
};

const string fingerprintText = "[id=a][secret=b]";

var sb = new StringBuilder();
foreach (var testCase in testCases)
{
string message = string.Empty;
ResultLevelKind resultLevelKind = default;
var fingerprint = new Fingerprint(fingerprintText);
var keyValuePairs = new Dictionary<string, string>();

ValidatorBase.SetHttpClient(new HttpClient(MockHttpMessageHandler(testCase.HttpStatusCode, testCase.HttpContent)));

ValidationState currentState = SquareCredentialsValidator.IsValidDynamic(ref fingerprint,
ref message,
keyValuePairs,
ref resultLevelKind);
if (currentState != testCase.ExpectedValidationState)
{
sb.AppendLine($"The test case '{testCase.Title}' was expecting '{testCase.ExpectedValidationState}' but found '{currentState}'.");
}

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

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



private 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;
}
}
}
5 changes: 5 additions & 0 deletions Src/Sarif.PatternMatcher.Sdk/ValidatorBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,11 @@ internal static string ParseValue(string value)
return value.Substring(indexOfFirstEqualSign + 1).Trim();
}

internal static void SetHttpClient(HttpClient client)
{
httpClient = client;
}

protected HttpClient CreateHttpClient()
{
if (httpClient == null)
Expand Down
4 changes: 2 additions & 2 deletions docs/Rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Security.dll (1)

### SEC101.DoNotExposePlaintextSecrets (43)

ID | Name | Validation
---| --- | ---
SEC101/001 | DoNotExposePlaintextSecrets/HttpAuthorizationRequestHeader | Y
Expand All @@ -15,7 +16,7 @@ SEC101/007 | DoNotExposePlaintextSecrets/GitHubAppCredentials | -
SEC101/008 | DoNotExposePlaintextSecrets/AwsCredentials | Y
SEC101/009 | DoNotExposePlaintextSecrets/LinkedInCredentials | -
SEC101/010 | DoNotExposePlaintextSecrets/SquarePat | Y
SEC101/011 | DoNotExposePlaintextSecrets/SquareCredentials | -
SEC101/011 | DoNotExposePlaintextSecrets/SquareCredentials | Y
SEC101/012 | DoNotExposePlaintextSecrets/SlackWebhook | Y
SEC101/013 | DoNotExposePlaintextSecrets/CryptographicPrivateKey | -
SEC101/014 | DoNotExposePlaintextSecrets/FacebookAccessToken | -
Expand Down Expand Up @@ -48,4 +49,3 @@ SEC101/051 | DoNotExposePlaintextSecrets/GpgCredential | -
SEC101/053 | DoNotExposePlaintextSecrets/GoogleServiceAccountKey | -
SEC101/054 | DoNotExposePlaintextSecrets/AlibabaAccessKey | -
SEC101/102 | DoNotExposePlaintextSecrets/AdoPat | -

0 comments on commit 06ff25f

Please sign in to comment.