Skip to content

Commit

Permalink
Merge pull request #91 from PerimeterX/release/3.2.0
Browse files Browse the repository at this point in the history
Release version 3.2.0 to master
  • Loading branch information
chen-zimmer-px committed Mar 1, 2023
2 parents 379a8a9 + 0e0f6da commit ec96aaf
Show file tree
Hide file tree
Showing 36 changed files with 2,065 additions and 644 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)

## [3.2.0] - 2023-01-03
### Added
- Support for credentials intelligence protocols `v2` and `multistep_sso`
- Support for login successful reporting methods `header`, `status`, `body`, and `custom`
- Support for automatic sending of `additional_s2s` activity
- Support for manual sending of `additional_s2s` activity via header
- Support for sending raw username on `additional_s2s` activity
- Support for login credentials extraction via custom function
- New `request_id` field to all enforcer activities

## [3.1.4] - 2022-12-05
### Added
- Compatibility with .NET Framework 4.7 and higher
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Web;

namespace PerimeterX.CustomBehavior
{
public interface ICredentialsExtractionHandler
{
ExtractedCredentials Handle(HttpRequest httpRequest);
}
}
9 changes: 9 additions & 0 deletions PerimeterXModule/CustomBehavior/ILoginSuccessfulHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Web;

namespace PerimeterX.CustomBehavior
{
public interface ILoginSuccessfulHandler
{
bool Handle(HttpResponse httpResponse);
}
}
46 changes: 45 additions & 1 deletion PerimeterXModule/DataContracts/Activities/ActivityDetails.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,18 @@ public class ActivityDetails : IActivityDetails

[DataMember(Name = "simulated_block")]
public bool? SimulatedBlock;

[DataMember(Name = "ci_version")]
public string CiVersion { get; set; }

[DataMember(Name = "credentials_compromised")]
public bool CredentialsCompromised { get; set; }

[DataMember(Name = "sso_step")]
public string SsoStep { get; set; }

[DataMember(Name = "request_id")]
public string RequestId { get; set; }
}

[DataContract]
Expand All @@ -59,5 +71,37 @@ public class EnforcerTelemetryActivityDetails : IActivityDetails

[DataMember(Name = "enforcer_configs")]
public string EnforcerConfigs;
}
}

[DataContract]
public class AdditionalS2SActivityDetails : IActivityDetails
{

[DataMember(Name = "client_uuid")]
public string ClientUuid { get; set; }

[DataMember(Name = "request_id")]
public string RequestId { get; set; }

[DataMember(Name = "ci_version")]
public string CiVersion { get; set; }

[DataMember(Name = "credentials_compromised", EmitDefaultValue = false)]
public bool CredentialsCompromised { get; set; }

[DataMember(Name = "http_status_code")]
public int HttpStatusCode { get; set; }

[DataMember(Name = "login_successful")]
public bool LoginSuccessful { get; set; }

[DataMember(Name = "raw_username")]
public string RawUsername { get; set; }

[DataMember(Name = "sso_step")]
public string SsoStep { get; set; }

[DataMember(Name = "module_version")]
public string ModuleVersion { get; internal set; }
}
}
21 changes: 20 additions & 1 deletion PerimeterXModule/DataContracts/Requests/Additional.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,24 @@ public class Additional

[DataMember(Name = "enforcer_vid_source", EmitDefaultValue = false)]
public string VidSource;
}

[DataMember(Name = "user")]
public string Username;

[DataMember(Name = "pass")]
public string Password;

[DataMember(Name = "raw_username", IsRequired = false)]
public string RawUsername;

[DataMember(Name = "ci_version")]
public string CiVersion;

[DataMember(Name = "sso_step", IsRequired = false)]
public string SsoStep;

[DataMember(Name = "request_id")]
public string RequestId { get; set; }

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
using Jil;
using PerimeterX.CustomBehavior;

namespace PerimeterX
{
public class CredentialIntelligenceManager
{
private ICredentialsIntelligenceProtocol protocol;
private List<ExtractorObject> loginCredentialsExtractors;

public CredentialIntelligenceManager(string ciVersion, List<ExtractorObject> loginCredentialsExraction)
{
this.protocol = CredentialsIntelligenceProtocolFactory.Create(ciVersion);
this.loginCredentialsExtractors = loginCredentialsExraction;
}

public LoginCredentialsFields ExtractCredentialsFromRequest(PxContext context, HttpRequest request, ICredentialsExtractionHandler credentialsExtractionHandler)
{
try
{
ExtractorObject extractionDetails = FindMatchCredentialsDetails(request);

if (extractionDetails != null)
{
ExtractedCredentials extractedCredentials = ExtractLoginCredentials(context, request, credentialsExtractionHandler, extractionDetails);


if (extractedCredentials != null)
{
return protocol.ProcessCredentials(extractedCredentials);
}
}

} catch (Exception ex)
{
PxLoggingUtils.LogError(string.Format("Failed to extract credentials.", ex.Message));
}

return null;
}

private ExtractedCredentials ExtractLoginCredentials(PxContext context, HttpRequest request, ICredentialsExtractionHandler credentialsExtractionHandler, ExtractorObject extractionDetails)
{
if (credentialsExtractionHandler != null)
{
return credentialsExtractionHandler.Handle(request);
}
else
{
return HandleExtractCredentials(extractionDetails, context, request);
}
}

private ExtractorObject FindMatchCredentialsDetails(HttpRequest request)
{
foreach (ExtractorObject loginObject in this.loginCredentialsExtractors)
{
if (IsRequestMatchLoginRequestConfiguration(loginObject, request))
{
return loginObject;
}
}

return null;
}

private static bool IsRequestMatchLoginRequestConfiguration(ExtractorObject extractorObject, HttpRequest request)
{
if (request.HttpMethod.ToLower() == extractorObject.Method)
{
if (extractorObject.PathType == "exact" && request.Path == extractorObject.Path)
{
return true;
}

if (extractorObject.PathType == "regex" && Regex.IsMatch(request.Path, extractorObject.Path))
{
return true;
}
}

return false;
}

private ExtractedCredentials HandleExtractCredentials(ExtractorObject extractionDetails, PxContext pxContext, HttpRequest request)
{
string userFieldName = extractionDetails.UserFieldName;
string passwordFieldName = extractionDetails.PassFieldName;

if (userFieldName == null || passwordFieldName == null)
{
return null;
}

Dictionary<string, string> headers = pxContext.GetLowercaseHeadersAsDictionary();

if (extractionDetails.SentThrough == "header")
{
return ExtractFromHeader(userFieldName, passwordFieldName, headers);
} else if (extractionDetails.SentThrough == "query-param")
{
return new ExtractedCredentials(
request.QueryString[userFieldName].Replace(" ", "+"),
request.QueryString[passwordFieldName].Replace(" ", "+")
);
} else if (extractionDetails.SentThrough == "body")
{
return ExtractFromBody(userFieldName, passwordFieldName, headers, request);
}

return null;
}

public static ExtractedCredentials ExtractFromHeader(string userFieldName, string passwordFieldName, Dictionary<string, string> headers)
{
bool isUsernameHeaderExist = headers.TryGetValue(userFieldName.ToLower(), out string userName);
bool isPasswordHeaderExist = headers.TryGetValue(passwordFieldName.ToLower(), out string password);

if (!isUsernameHeaderExist && !isPasswordHeaderExist) { return null; }

return new ExtractedCredentials(userName, password);
}

private ExtractedCredentials ExtractFromBody(string userFieldName, string passwordFieldName, Dictionary<string, string> headers, HttpRequest request)
{
bool isContentTypeHeaderExist = headers.TryGetValue("content-type", out string contentType);

string body = BodyReader.ReadRequestBody(request);

if (!isContentTypeHeaderExist)
{
return null;
} else if (contentType.Contains("application/json"))
{
return ExtractCredentialsFromJson(body, userFieldName, passwordFieldName);
} else if (contentType.Contains("x-www-form-urlencoded"))
{
return ReadValueFromUrlEncoded(body, userFieldName, passwordFieldName);
} else if (contentType.Contains("form-data"))
{
return ExtractValueFromMultipart(body, contentType, userFieldName, passwordFieldName);
}

return null;
}

private ExtractedCredentials ExtractCredentialsFromJson(string body, string userFieldName, string passwordFieldName) {

dynamic jsonBody = JSON.DeserializeDynamic(body, PxConstants.JSON_OPTIONS);

string userValue = PxCommonUtils.ExtractValueFromNestedJson(userFieldName, jsonBody);
string passValue = PxCommonUtils.ExtractValueFromNestedJson(passwordFieldName, jsonBody);

return new ExtractedCredentials(userValue, passValue);
}

private ExtractedCredentials ReadValueFromUrlEncoded(string body, string userFieldName, string passwordFieldName)
{
var parametersQueryString = HttpUtility.ParseQueryString(body);
var parametersDictionary = new Dictionary<string, string>();
foreach (var key in parametersQueryString.AllKeys)
{
parametersDictionary.Add(key, parametersQueryString[key]);
}

return ExtractCredentialsFromDictionary(parametersDictionary, userFieldName, passwordFieldName);
}

private ExtractedCredentials ExtractValueFromMultipart(string body, string contentType, string userFieldName, string passwordFieldName)
{
Dictionary<string, string> formData = BodyReader.GetFormDataContentAsDictionary(body, contentType);

return ExtractCredentialsFromDictionary(formData, userFieldName, passwordFieldName);
}

private ExtractedCredentials ExtractCredentialsFromDictionary(Dictionary<string, string> parametersDictionary, string userFieldName, string passwordFieldName)
{
bool isUsernameExist = parametersDictionary.TryGetValue(userFieldName, out string userField);
bool isPasswordExist = parametersDictionary.TryGetValue(passwordFieldName, out string passwordField);

if (!isUsernameExist && !isPasswordExist)
{
return null;
}

return new ExtractedCredentials(userField, passwordField);
}
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using PerimeterX;

namespace PerimeterX
{
public class CredentialsIntelligenceProtocolFactory
{
public static ICredentialsIntelligenceProtocol Create(string protocolVersion)
{
switch(protocolVersion)
{
case CIVersion.V2:
return new V2CredentialsIntelligenceProtocol();
case CIVersion.MULTISTEP_SSO:
return new MultistepSSoCredentialsIntelligenceProtocol();
default:
throw new Exception("Unknown CI protocol version: " + protocolVersion);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Runtime.Serialization;

namespace PerimeterX
{
public class ExtractedCredentials
{
[DataMember(Name = "username")]
public string Username { get; set; }

[DataMember(Name = "password")]
public string Password { get; set; }

public ExtractedCredentials(string username, string password)
{
this.Username = username;
this.Password = password;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Runtime.Serialization;

namespace PerimeterX
{
[DataContract]
public class ExtractorObject
{
[DataMember(Name = "path")]
public string Path;

[DataMember(Name = "path_type")]
public string PathType;

[DataMember(Name = "method")]
public string Method;

[DataMember(Name = "sent_through")]
public string SentThrough;

[DataMember(Name = "pass_field")]
public string PassFieldName;

[DataMember(Name = "user_field")]
public string UserFieldName;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

namespace PerimeterX
{
public interface ICredentialsIntelligenceProtocol
{
LoginCredentialsFields ProcessCredentials(ExtractedCredentials extractedCredentials);
}
}
Loading

0 comments on commit ec96aaf

Please sign in to comment.