Skip to content

Commit

Permalink
Support new returnUrl, returnRisk, and risk parameters (#454)
Browse files Browse the repository at this point in the history
* Support new returnUrl, returnRisk, and risk parameters

* Improvements

* Fixes

* Fixes

* Use enum for risk level

* Fix
  • Loading branch information
PeterOrneholm authored May 10, 2024
1 parent 583d03e commit 2c14d46
Show file tree
Hide file tree
Showing 15 changed files with 148 additions and 23 deletions.
13 changes: 13 additions & 0 deletions src/ActiveLogin.Authentication.BankId.Api/BankIdApiConverters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,19 @@ public static CollectHintCode ParseCollectHintCode(string hintCode)
return Enum.TryParse<CollectHintCode>(hintCode, true, out var parsedStatus) ? parsedStatus : CollectHintCode.Unknown;
}

/// <summary>
/// Parse collect hint code from string.
/// </summary>
public static RiskLevel ParseCollectRiskLevel(string? riskLevel)
{
if (string.IsNullOrEmpty(riskLevel))
{
return RiskLevel.Unknown;
}

return Enum.TryParse<RiskLevel>(riskLevel, true, out var parsedRiskLevel) ? parsedRiskLevel : RiskLevel.Unknown;
}

/// <summary>
/// Parse error code from string.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public class BankIdSimulatedAppApiClient : IBankIdAppApiClient
private const string DefaultPersonalIdentityNumber = "199908072391";
private const string DefaultUniqueHardwareId = "OZvYM9VvyiAmG7NA5jU5zqGcVpo=";
private const string DefaultBankIdIssueDate = "2023-01-01";
private const string DefaultRiskLevel = "low";

private static readonly List<CollectState> DefaultCollectStates = new()
{
Expand Down Expand Up @@ -90,7 +91,7 @@ public async Task<AuthResponse> AuthAsync(AuthRequest request)
throw new ArgumentNullException(nameof(request));
}

var response = await GetOrderResponseAsync(request.EndUserIp, request.Requirement.Mrtd ?? false).ConfigureAwait(false);
var response = await GetOrderResponseAsync(request.EndUserIp, request.Requirement.Mrtd ?? false, request.ReturnRisk ?? false).ConfigureAwait(false);
return new AuthResponse(response.OrderRef, response.AutoStartToken, response.QrStartToken, response.QrStartSecret);
}

Expand All @@ -101,11 +102,11 @@ public async Task<SignResponse> SignAsync(SignRequest request)
throw new ArgumentNullException(nameof(request));
}

var response = await GetOrderResponseAsync(request.EndUserIp, request.Requirement.Mrtd ?? false).ConfigureAwait(false);
var response = await GetOrderResponseAsync(request.EndUserIp, request.Requirement.Mrtd ?? false, request.ReturnRisk ?? false).ConfigureAwait(false);
return new SignResponse(response.OrderRef, response.AutoStartToken, response.QrStartToken, response.QrStartSecret);
}

private async Task<OrderResponse> GetOrderResponseAsync(string endUserIp, bool mrtd)
private async Task<OrderResponse> GetOrderResponseAsync(string endUserIp, bool mrtd, bool returnRisk)
{
await SimulateResponseDelay().ConfigureAwait(false);

Expand All @@ -116,7 +117,7 @@ private async Task<OrderResponse> GetOrderResponseAsync(string endUserIp, bool m
var qrStartToken = GetRandomToken();
var qrStartSecret = GetRandomToken();

var session = new Session(endUserIp, orderRef, _personalIdentityNumber, mrtd);
var session = new Session(endUserIp, orderRef, _personalIdentityNumber, mrtd, returnRisk);
_sessions.Add(orderRef, session);
return new OrderResponse(orderRef, autoStartToken, qrStartToken, qrStartSecret);
}
Expand Down Expand Up @@ -169,7 +170,7 @@ private async Task<PhoneOrderResponse> GetPhoneOrderResponseAsync(string persona
var orderRef = GetRandomToken();
var ip = GetRandomIp();

var session = new Session(ip, orderRef, personalIdentityNumber, false, callInitiator);
var session = new Session(ip, orderRef, personalIdentityNumber, false, false, callInitiator);
_sessions.Add(orderRef, session);
return new PhoneOrderResponse(orderRef);
}
Expand Down Expand Up @@ -205,12 +206,12 @@ public async Task<CollectResponse> CollectAsync(CollectRequest request)
_sessions.Remove(request.OrderRef);
}

var completionData = GetCompletionData(session.EndUserIp, status, session.PersonalIdentityNumber, session.Mrtd);
var completionData = GetCompletionData(session.EndUserIp, status, session.PersonalIdentityNumber, session.Mrtd, session.ReturnRisk);

return new CollectResponse(session.OrderRef, status.ToString(), hintCode.ToString(), completionData);
}

private CompletionData? GetCompletionData(string endUserIp, CollectStatus status, string personalIdentityNumber, bool mrtd)
private CompletionData? GetCompletionData(string endUserIp, CollectStatus status, string personalIdentityNumber, bool mrtd, bool returnRisk)
{
if (status != CollectStatus.Complete)
{
Expand All @@ -222,8 +223,9 @@ public async Task<CollectResponse> CollectAsync(CollectRequest request)
var stepUp = mrtd ? new StepUp(true) : null;
var signature = string.Empty; // Not implemented in the simulated client
var ocspResponse = string.Empty; // Not implemented in the simulated client
var risk = returnRisk ? DefaultRiskLevel : string.Empty;

return new CompletionData(user, device, _bankIdIssueDate, stepUp, signature, ocspResponse);
return new CompletionData(user, device, _bankIdIssueDate, stepUp, signature, ocspResponse, risk);
}

private CollectStatus GetStatus(int collectCalls)
Expand Down Expand Up @@ -262,12 +264,13 @@ private async Task SimulateResponseDelay()

private class Session
{
public Session(string endUserIp, string orderRef, string personalIdentityNumber, bool mrtd, string? callInitiator = null)
public Session(string endUserIp, string orderRef, string personalIdentityNumber, bool mrtd, bool returnRisk, string? callInitiator = null)
{
EndUserIp = endUserIp;
OrderRef = orderRef;
PersonalIdentityNumber = personalIdentityNumber;
Mrtd = mrtd;
ReturnRisk = returnRisk;
CallInitiator = callInitiator;
}

Expand All @@ -278,6 +281,7 @@ public Session(string endUserIp, string orderRef, string personalIdentityNumber,
public string PersonalIdentityNumber { get; }

public bool Mrtd { get; }
public bool ReturnRisk { get; }

public int CollectCalls { get; set; }

Expand Down
11 changes: 9 additions & 2 deletions src/ActiveLogin.Authentication.BankId.Api/Models/AuthRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,25 @@ public class AuthRequest : Request
/// If present, and set to "simpleMarkdownV1", this parameter indicates that userVisibleData holds formatting characters which, if used correctly, will make the text displayed with the user nicer to look at.
/// For further information of formatting options, please study the document Guidelines for Formatted Text.
/// </param>
/// <param name="returnUrl">The URL to return to when the authentication order is completed.</param>
/// <param name="returnRisk">If set to true, a risk indication will be included in the collect response.</param>
public AuthRequest(
string endUserIp,
Requirement? requirement = null,
string? userVisibleData = null,
byte[]? userNonVisibleData = null,
string? userVisibleDataFormat = null)
string? userVisibleDataFormat = null,
string? returnUrl = null,
bool? returnRisk = null)
: base(
endUserIp,
userVisibleData: userVisibleData,
userNonVisibleData: userNonVisibleData,
requirement: requirement,
userVisibleDataFormat: userVisibleDataFormat)
userVisibleDataFormat: userVisibleDataFormat,
returnUrl: returnUrl,
returnRisk: returnRisk
)
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ namespace ActiveLogin.Authentication.BankId.Api.Models;

public class CompletionData
{
public CompletionData(User user, Device device, string bankIdIssueDate, StepUp? stepUp, string signature, string ocspResponse)
public CompletionData(User user, Device device, string bankIdIssueDate, StepUp? stepUp, string signature, string ocspResponse, string? risk)
{
User = user;
Device = device;
BankIdIssueDate = bankIdIssueDate;
StepUp = stepUp;
Signature = signature;
OcspResponse = ocspResponse;
Risk = risk;
}

/// <summary>
Expand Down Expand Up @@ -52,4 +53,10 @@ public CompletionData(User user, Device device, string bankIdIssueDate, StepUp?
/// </summary>
[JsonPropertyName("ocspResponse")]
public string OcspResponse { get; }

/// <summary>
/// Indicates the risk level of the order based on data available in the order. Only returned if requested in the order.
/// </summary>
[JsonPropertyName("risk")]
public string? Risk { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,12 @@ public static string GetSignatureXml(this CompletionData completionData)
{
return BankIdApiConverters.GetXml(completionData.Signature);
}

/// <summary>
/// Collect risk level.
/// </summary>
public static RiskLevel GetCollectRiskLevel(this CompletionData completionData)
{
return BankIdApiConverters.ParseCollectRiskLevel(completionData.Risk);
}
}
18 changes: 17 additions & 1 deletion src/ActiveLogin.Authentication.BankId.Api/Models/Request.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,9 @@ public Request(string endUserIp, string userVisibleData, byte[]? userNonVisibleD
/// If present, and set to "simpleMarkdownV1", this parameter indicates that userVisibleData holds formatting characters which, if used correctly, will make the text displayed with the user nicer to look at.
/// For further information of formatting options, please study the document Guidelines for Formatted Text.
/// </param>
public Request(string endUserIp, string? userVisibleData, byte[]? userNonVisibleData, Requirement? requirement, string? userVisibleDataFormat)
/// <param name="returnUrl">The URL to return to when the authentication order is completed.</param>
/// <param name="returnRisk">If set to true, a risk indication will be included in the collect response.</param>
public Request(string endUserIp, string? userVisibleData, byte[]? userNonVisibleData, Requirement? requirement, string? userVisibleDataFormat, string? returnUrl = null, bool? returnRisk = null)
{
if(this is SignRequest && userVisibleData == null)
{
Expand All @@ -120,6 +122,8 @@ public Request(string endUserIp, string? userVisibleData, byte[]? userNonVisible
UserNonVisibleData = ToBase64EncodedString(userNonVisibleData);
Requirement = requirement ?? new Requirement();
UserVisibleDataFormat = userVisibleDataFormat;
ReturnUrl = returnUrl;
ReturnRisk = returnRisk;
}

/// <summary>
Expand Down Expand Up @@ -166,6 +170,18 @@ public Request(string endUserIp, string? userVisibleData, byte[]? userNonVisible
[JsonPropertyName("userNonVisibleData"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public string? UserNonVisibleData { get; }

/// <summary>
/// Orders started on the same device (started with autostart token) will call this URL when the order is completed, ignoring any return URL provided in the start URL when the BankID app was launched.
/// </summary>
[JsonPropertyName("returnUrl"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public string? ReturnUrl { get; set; }

/// <summary>
/// If this is set to true, a risk indication will be included in the collect response when the order completes.
/// </summary>
[JsonPropertyName("returnRisk"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public bool? ReturnRisk { get; set; }

private static string? ToBase64EncodedString(string? value)
{
if (value == null)
Expand Down
27 changes: 26 additions & 1 deletion src/ActiveLogin.Authentication.BankId.Api/Models/Requirement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,15 @@ public class Requirement
/// <param name="personalNumber">
/// A personal number to be used to complete the transaction. If a BankID with another personal number attempts to sign the transaction, it fails.
/// </param>
public Requirement(List<string>? certificatePolicies = null, bool? pinCode = null, bool? mrtd = null, string? personalNumber = null)
/// <param name="risk">Set the acceptable risk level for the transaction.</param>
public Requirement(List<string>? certificatePolicies = null, bool? pinCode = null, bool? mrtd = null, string? personalNumber = null, RiskLevel? risk = null)
{
CertificatePolicies = certificatePolicies;
PinCode = pinCode;
Mrtd = mrtd;
PersonalNumber = personalNumber;

Risk = ParseRiskLevel(risk);
}

/// <summary>
Expand Down Expand Up @@ -67,4 +70,26 @@ public Requirement(List<string>? certificatePolicies = null, bool? pinCode = nul
/// </summary>
[JsonPropertyName("personalNumber"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public string? PersonalNumber { get; }

/// <summary>
/// Set the acceptable risk level for the transaction.
/// </summary>
[JsonPropertyName("risk"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public string? Risk { get; }

private static string? ParseRiskLevel(RiskLevel? risk)
{
return risk switch
{
null => null,

RiskLevel.Low => RiskLevel.Low.ToString().ToLower(),
RiskLevel.Moderate => RiskLevel.Moderate.ToString().ToLower(),
RiskLevel.High => RiskLevel.High.ToString().ToLower(),

_ => throw new ArgumentException(
$"Risk must be {RiskLevel.Low}, {RiskLevel.Moderate} or {RiskLevel.High}",
nameof(risk))
};
}
}
27 changes: 27 additions & 0 deletions src/ActiveLogin.Authentication.BankId.Api/Models/RiskLevel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace ActiveLogin.Authentication.BankId.Api.Models;

/// <summary>
/// Represents the risk levels returned in the collect response.
/// </summary>
public enum RiskLevel
{
/// <summary>
/// If no value was returned the risk couldn’t be calculated.
/// </summary>
Unknown,

/// <summary>
/// Low risk orders. No or low risk identified in the available order data.
/// </summary>
Low,

/// <summary>
/// Moderate risk orders. Might need further action, investigation or follow-up by the Relying Party based on order data.
/// </summary>
Moderate,

/// <summary>
/// High risk transaction. The order should be blocked/cancelled by the Relying Party and needs further action, investigation or follow-up.
/// </summary>
High
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,17 @@ public class SignRequest : Request
/// If present, and set to "simpleMarkdownV1", this parameter indicates that userVisibleData holds formatting characters which, if used correctly, will make the text displayed with the user nicer to look at.
/// For further information of formatting options, please study the document Guidelines for Formatted Text.
/// </param>
/// <param name="returnUrl">The URL to return to when the authentication order is completed.</param>
/// <param name="returnRisk">If set to true, a risk indication will be included in the collect response.</param>
public SignRequest(
string endUserIp,
string userVisibleData,
byte[]? userNonVisibleData = null,
Requirement? requirement = null,
string? userVisibleDataFormat = null)
: base(endUserIp, userVisibleData, userNonVisibleData, requirement, userVisibleDataFormat)
string? userVisibleDataFormat = null,
string? returnUrl = null,
bool? returnRisk = null)
: base(endUserIp, userVisibleData, userNonVisibleData, requirement, userVisibleDataFormat, returnUrl, returnRisk)
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,9 @@ protected string ConstructProtectedUiResult(string orderRef, CompletionData comp
completionData.Signature,
completionData.OcspResponse,
completionData.Device.IpAddress,
completionData.Device.Uhi);
completionData.Device.Uhi,
completionData.Risk
);
return _uiAuthResultProtector.Protect(uiResult);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace ActiveLogin.Authentication.BankId.AspNetCore.DataProtection.Serializat

internal abstract class BankIdDataSerializer<TModel> : IDataSerializer<TModel>
{
private const int FormatVersion = 10;
private const int FormatVersion = 11;

public byte[] Serialize(TModel model)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ protected override void Write(BinaryWriter writer, BankIdUiResult model)

writer.Write(model.DetectedIpAddress);
writer.Write(model.DetectedUniqueHardwareId);

writer.Write(model.Risk ?? string.Empty);
}

protected override BankIdUiResult Read(BinaryReader reader)
Expand All @@ -48,6 +50,8 @@ protected override BankIdUiResult Read(BinaryReader reader)
reader.ReadString(),

reader.ReadString(),
reader.ReadString(),

reader.ReadString()
);
}
Expand Down
Loading

0 comments on commit 2c14d46

Please sign in to comment.