Skip to content

Commit a404cef

Browse files
authored
added nonce to virtual gateway (#412)
added nonce to virtual gateway (#412)
1 parent 03f2276 commit a404cef

File tree

7 files changed

+184
-156
lines changed

7 files changed

+184
-156
lines changed

src/Parbad/src/Gateway/ParbadVirtual/MiddlewareInvoker/ParbadVirtualGatewayMiddlewareInvoker.cs

Lines changed: 133 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -9,156 +9,177 @@
99
using Microsoft.AspNetCore.Http;
1010
using Microsoft.Extensions.Options;
1111
using Parbad.Internal;
12+
using Parbad.Options;
1213

13-
namespace Parbad.Gateway.ParbadVirtual.MiddlewareInvoker
14+
namespace Parbad.Gateway.ParbadVirtual.MiddlewareInvoker;
15+
16+
internal class ParbadVirtualGatewayMiddlewareInvoker : IParbadVirtualGatewayMiddlewareInvoker
1417
{
15-
internal class ParbadVirtualGatewayMiddlewareInvoker : IParbadVirtualGatewayMiddlewareInvoker
18+
private readonly HttpContext _httpContext;
19+
private readonly ParbadVirtualGatewayOptions _virtualGatewayOptions;
20+
private readonly ParbadOptions _options;
21+
22+
public ParbadVirtualGatewayMiddlewareInvoker(IHttpContextAccessor httpContextAccessor,
23+
IOptions<ParbadVirtualGatewayOptions> virtualGatewayOptions,
24+
IOptions<ParbadOptions> options)
1625
{
17-
private readonly HttpContext _httpContext;
18-
private readonly IOptions<ParbadVirtualGatewayOptions> _options;
26+
_httpContext = httpContextAccessor.HttpContext;
27+
_virtualGatewayOptions = virtualGatewayOptions.Value;
28+
_options = options.Value;
29+
}
1930

20-
public ParbadVirtualGatewayMiddlewareInvoker(IOptions<ParbadVirtualGatewayOptions> options,
21-
IHttpContextAccessor httpContextAccessor)
31+
public async Task InvokeAsync()
32+
{
33+
if (_httpContext == null)
2234
{
23-
_options = options;
24-
_httpContext = httpContextAccessor.HttpContext;
35+
throw new InvalidOperationException("HttpContext is null");
2536
}
2637

27-
public async Task InvokeAsync()
38+
if (_httpContext.Request.Query.ContainsKey("css"))
2839
{
29-
if (_httpContext == null)
30-
{
31-
throw new InvalidOperationException("HttpContext is null");
32-
}
40+
await HandleCss(_httpContext);
3341

34-
if (_httpContext.Request.Query.ContainsKey("css"))
35-
{
36-
await HandleCss(_httpContext);
42+
return;
43+
}
3744

38-
return;
39-
}
45+
HttpResponseUtilities.AddNecessaryContents(_httpContext, "text/html");
4046

41-
HttpResponseUtilities.AddNecessaryContents(_httpContext, "text/html");
47+
var commandDetails = await GetCommandDetails(_httpContext);
4248

43-
var commandDetails = await GetCommandDetails(_httpContext);
49+
if (commandDetails == null)
50+
{
51+
await _httpContext.Response.WriteAsync("Parbad Virtual Gateway message: Invalid data is received.");
4452

45-
if (commandDetails == null)
46-
{
47-
await _httpContext.Response.WriteAsync("Parbad Virtual Gateway message: Invalid data is received.");
53+
return;
54+
}
4855

49-
return;
50-
}
56+
var cssUrl =
57+
$"{_httpContext.Request.Scheme}://{_httpContext.Request.Host}{_httpContext.Request.PathBase}?css=true";
5158

52-
var cssUrl =
53-
$"{_httpContext.Request.Scheme}://{_httpContext.Request.Host}{_httpContext.Request.PathBase}?css=true";
59+
var nonceProp = GetNonceProp();
5460

55-
switch (commandDetails.CommandType)
56-
{
57-
case VirtualGatewayCommandType.Request:
58-
await HandleRequestPage(_httpContext, commandDetails, cssUrl);
61+
switch (commandDetails.CommandType)
62+
{
63+
case VirtualGatewayCommandType.Request:
64+
await HandleRequestPage(_httpContext, commandDetails, cssUrl, nonceProp);
5965

60-
break;
66+
break;
6167

62-
case VirtualGatewayCommandType.Pay:
63-
await HandleResultPage(_httpContext, commandDetails, cssUrl);
68+
case VirtualGatewayCommandType.Pay:
69+
await HandleResultPage(_httpContext, commandDetails, cssUrl, nonceProp);
6470

65-
break;
71+
break;
6672

67-
default:
68-
await _httpContext.Response.WriteAsync("CommandType is not valid.");
73+
default:
74+
await _httpContext.Response.WriteAsync("CommandType is not valid.");
6975

70-
break;
71-
}
76+
break;
7277
}
78+
}
7379

74-
private async Task HandleCss(HttpContext httpContext)
75-
{
76-
HttpResponseUtilities.AddNecessaryContents(_httpContext, "text/css");
80+
private async Task HandleCss(HttpContext httpContext)
81+
{
82+
HttpResponseUtilities.AddNecessaryContents(_httpContext, "text/css");
7783

78-
var cssContent = await GetTemplate("ParbadVirtualGatewayStyles.css");
84+
var cssContent = await GetTemplate("ParbadVirtualGatewayStyles.css");
7985

80-
await httpContext.Response.WriteAsync(cssContent);
81-
}
86+
await httpContext.Response.WriteAsync(cssContent);
87+
}
8288

83-
private async Task HandleRequestPage(HttpContext httpContext,
84-
VirtualGatewayCommandDetails commandDetails,
85-
string cssUrl)
89+
private async Task HandleRequestPage(HttpContext httpContext,
90+
VirtualGatewayCommandDetails commandDetails,
91+
string cssUrl,
92+
string nonceProp)
93+
{
94+
var template = await GetTemplate("VirtualGatewayRequestTemplate.html");
95+
96+
var html = template
97+
.Replace("#CssUrl#", cssUrl)
98+
.Replace("#nonce#", nonceProp)
99+
.Replace("#VirtualGatewayPath#", _virtualGatewayOptions.GatewayPath)
100+
.Replace("#TrackingNumber#", commandDetails.TrackingNumber.ToString())
101+
.Replace("#Amount#", commandDetails.Amount.ToString())
102+
.Replace("#DisplayAmount#", ((long)commandDetails.Amount).ToString("N0"))
103+
.Replace("#RedirectUrl#", commandDetails.RedirectUrl)
104+
.Replace("#YearNow#", DateTime.Now.Year.ToString());
105+
106+
await httpContext.Response.WriteAsync(html);
107+
}
108+
109+
private static async Task HandleResultPage(HttpContext httpContext,
110+
VirtualGatewayCommandDetails commandDetails,
111+
string cssUrl,
112+
string nonceProp)
113+
{
114+
if (!httpContext.Request.Form.TryGetValue("isPaid", out var isPaid) ||
115+
!bool.TryParse(isPaid, out var boolIsPaid))
86116
{
87-
var template = await GetTemplate("VirtualGatewayRequestTemplate.html");
88-
89-
var html = template
90-
.Replace("#CssUrl#", cssUrl)
91-
.Replace("#VirtualGatewayPath#", _options.Value.GatewayPath)
92-
.Replace("#TrackingNumber#", commandDetails.TrackingNumber.ToString())
93-
.Replace("#Amount#", commandDetails.Amount.ToString())
94-
.Replace("#DisplayAmount#", ((long)commandDetails.Amount).ToString("N0"))
95-
.Replace("#RedirectUrl#", commandDetails.RedirectUrl)
96-
.Replace("#YearNow#", DateTime.Now.Year.ToString());
97-
98-
await httpContext.Response.WriteAsync(html);
117+
await httpContext.Response.WriteAsync($"{nameof(isPaid)} field is not valid.");
118+
119+
return;
99120
}
100121

101-
private static async Task HandleResultPage(HttpContext httpContext,
102-
VirtualGatewayCommandDetails commandDetails,
103-
string cssUrl)
122+
var template = await GetTemplate("VirtualGatewayResultTemplate.html");
123+
124+
var html = template
125+
.Replace("#CssUrl#", cssUrl)
126+
.Replace("#nonce#", nonceProp)
127+
.Replace("#TrackingNumber#", commandDetails.TrackingNumber.ToString())
128+
.Replace("#DisplayAmount#", ((long)commandDetails.Amount).ToString("N0"))
129+
.Replace("#TransactionCode#", boolIsPaid ? Guid.NewGuid().ToString("N") : string.Empty)
130+
.Replace("#RedirectUrl#", commandDetails.RedirectUrl)
131+
.Replace("#IsPaid#", isPaid.ToString().ToLower())
132+
.Replace("#YearNow#", DateTime.Now.Year.ToString())
133+
.Replace("#StatusText#", boolIsPaid ? "Paid" : "Cancelled");
134+
135+
await httpContext.Response.WriteAsync(html);
136+
}
137+
138+
private static async Task<VirtualGatewayCommandDetails> GetCommandDetails(HttpContext httpContext)
139+
{
140+
if (!httpContext.Request.HasFormContentType)
104141
{
105-
if (!httpContext.Request.Form.TryGetValue("isPaid", out var isPaid) ||
106-
!bool.TryParse(isPaid, out var boolIsPaid))
107-
{
108-
await httpContext.Response.WriteAsync($"{nameof(isPaid)} field is not valid.");
109-
110-
return;
111-
}
112-
113-
var template = await GetTemplate("VirtualGatewayResultTemplate.html");
114-
115-
var html = template
116-
.Replace("#CssUrl#", cssUrl)
117-
.Replace("#TrackingNumber#", commandDetails.TrackingNumber.ToString())
118-
.Replace("#DisplayAmount#", ((long)commandDetails.Amount).ToString("N0"))
119-
.Replace("#TransactionCode#", boolIsPaid ? Guid.NewGuid().ToString("N") : string.Empty)
120-
.Replace("#RedirectUrl#", commandDetails.RedirectUrl)
121-
.Replace("#IsPaid#", isPaid.ToString().ToLower())
122-
.Replace("#YearNow#", DateTime.Now.Year.ToString())
123-
.Replace("#StatusText#", boolIsPaid ? "Paid" : "Cancelled");
124-
125-
await httpContext.Response.WriteAsync(html);
142+
return null;
126143
}
127144

128-
private static async Task<VirtualGatewayCommandDetails> GetCommandDetails(HttpContext httpContext)
145+
var form = await httpContext.Request.ReadFormAsync();
146+
147+
if (!Enum.TryParse(form["commandType"], out VirtualGatewayCommandType commandType))
129148
{
130-
if (!httpContext.Request.HasFormContentType)
131-
{
132-
return null;
133-
}
134-
135-
var form = await httpContext.Request.ReadFormAsync();
136-
137-
if (!Enum.TryParse(form["commandType"], out VirtualGatewayCommandType commandType))
138-
{
139-
commandType = VirtualGatewayCommandType.Request;
140-
}
141-
142-
if (!long.TryParse(form["trackingNumber"], out var trackingNumber) ||
143-
!form.TryGetValue("redirectUrl", out var redirectUrl) ||
144-
!Money.TryParse(form["amount"], out var amount))
145-
{
146-
return null;
147-
}
148-
149-
return new VirtualGatewayCommandDetails(commandType, trackingNumber, amount, redirectUrl);
149+
commandType = VirtualGatewayCommandType.Request;
150150
}
151151

152-
private static Task<string> GetTemplate(string templateName)
152+
if (!long.TryParse(form["trackingNumber"], out var trackingNumber) ||
153+
!form.TryGetValue("redirectUrl", out var redirectUrl) ||
154+
!Money.TryParse(form["amount"], out var amount))
153155
{
154-
using var stream = typeof(ParbadVirtualGatewayOptions)
155-
.GetTypeInfo()
156-
.Assembly
157-
.GetManifestResourceStream($"Parbad.Gateway.ParbadVirtual.{templateName}");
156+
return null;
157+
}
158158

159-
var streamReader = new StreamReader(stream, Encoding.UTF8);
159+
return new VirtualGatewayCommandDetails(commandType, trackingNumber, amount, redirectUrl);
160+
}
160161

161-
return streamReader.ReadToEndAsync();
162+
private static Task<string> GetTemplate(string templateName)
163+
{
164+
using var stream = typeof(ParbadVirtualGatewayOptions)
165+
.GetTypeInfo()
166+
.Assembly
167+
.GetManifestResourceStream($"Parbad.Gateway.ParbadVirtual.{templateName}");
168+
169+
var streamReader = new StreamReader(stream, Encoding.UTF8);
170+
171+
return streamReader.ReadToEndAsync();
172+
}
173+
174+
private string GetNonceProp()
175+
{
176+
string nonceValue = null;
177+
178+
if (_options.NonceFactory != null)
179+
{
180+
nonceValue = _options.NonceFactory(_httpContext);
162181
}
182+
183+
return string.IsNullOrWhiteSpace(nonceValue) ? null : $" nonce=\"{nonceValue}\"";
163184
}
164185
}

src/Parbad/src/Gateway/ParbadVirtual/VirtualGatewayRequestTemplate.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1" />
66
<title>Parbad Virtual Gateway</title>
7-
<link rel="stylesheet" href="#CssUrl#" />
7+
<link rel="stylesheet" href="#CssUrl#" #nonce#/>
88
</head>
99
<body>
1010
<div class="container">
@@ -37,7 +37,7 @@ <h1 class="heading">Parbad Virtual Gateway</h1>
3737
Copyright © Parbad 2016-#YearNow# <a href="https://github.com/Sina-Soltani/Parbad/blob/master/LICENSE.txt" target="_blank">GNU license</a>
3838
</div>
3939
</div>
40-
<script type="text/javascript">
40+
<script type="text/javascript" #nonce#>
4141
function pay() {
4242
document.getElementById('IsPaid').value = "true";
4343
return true;

src/Parbad/src/Gateway/ParbadVirtual/VirtualGatewayResultTemplate.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1" />
66
<title>Parbad Virtual Gateway</title>
7-
<link rel="stylesheet" href="#CssUrl#" />
7+
<link rel="stylesheet" href="#CssUrl#" #nonce#/>
88
</head>
99
<body>
1010
<div class="container">
@@ -43,7 +43,7 @@ <h1 class="heading">Parbad Virtual Gateway</h1>
4343
Copyright © Parbad 2016-#YearNow# <a href="https://github.com/Sina-Soltani/Parbad/blob/master/LICENSE.txt" target="_blank">GNU license</a>
4444
</div>
4545
</div>
46-
<script type="text/javascript">
46+
<script type="text/javascript" #nonce#>
4747
let timer;
4848
let seconds = 5;
4949

src/Parbad/src/Internal/HtmlFormBuilder.cs

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,31 @@
44
using System.Collections.Generic;
55
using System.Linq;
66

7-
namespace Parbad.Internal
7+
namespace Parbad.Internal;
8+
9+
internal static class HtmlFormBuilder
810
{
9-
internal static class HtmlFormBuilder
11+
public static string CreateForm(string url, IEnumerable<KeyValuePair<string, string>> data, string nonce = null)
1012
{
11-
public static string CreateForm(string url, IEnumerable<KeyValuePair<string, string>> data, string nonce = null)
12-
{
13-
var fields = string.Join("", data.Select(CreateHiddenInput));
13+
var fields = string.Join("", data.Select(CreateHiddenInput));
14+
15+
var nonceProp = string.IsNullOrWhiteSpace(nonce) ? null : $" nonce=\"{nonce}\"";
1416

15-
return
16-
"<html>" +
17-
"<body>" +
18-
$"<form id=\"paymentForm\" action=\"{url}\" method=\"post\" />" +
19-
fields +
20-
"</form>" +
21-
$"<script type=\"text/javascript\" {(string.IsNullOrWhiteSpace(nonce) ? null : $"nonce=\"{nonce}\"")}>" +
22-
"document.getElementById('paymentForm').submit();" +
23-
"</script>" +
24-
"</body>" +
25-
"</html>";
26-
}
17+
return
18+
"<html>" +
19+
"<body>" +
20+
$"<form id=\"paymentForm\" action=\"{url}\" method=\"post\" />" +
21+
fields +
22+
"</form>" +
23+
$"<script type=\"text/javascript\"{nonceProp}>" +
24+
"document.getElementById('paymentForm').submit();" +
25+
"</script>" +
26+
"</body>" +
27+
"</html>";
28+
}
2729

28-
public static string CreateHiddenInput(KeyValuePair<string, string> data)
29-
{
30-
return $"<input type=\"hidden\" name=\"{data.Key}\" value=\"{data.Value}\" />";
31-
}
30+
private static string CreateHiddenInput(KeyValuePair<string, string> data)
31+
{
32+
return $"<input type=\"hidden\" name=\"{data.Key}\" value=\"{data.Value}\" />";
3233
}
3334
}

0 commit comments

Comments
 (0)