From af569a29a6265cd7e8c8c6af32adeb75acdf1d0f Mon Sep 17 00:00:00 2001 From: opcfoundation-org Date: Wed, 29 Nov 2023 17:27:31 -0800 Subject: [PATCH 1/3] Set up CI with Azure Pipelines [skip ci] --- azure-pipelines.yml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 azure-pipelines.yml diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 00000000..de7d5a0f --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,33 @@ +# .NET Desktop +# Build and run tests for .NET Desktop or Windows classic desktop solutions. +# Add steps that publish symbols, save build artifacts, and more: +# https://docs.microsoft.com/azure/devops/pipelines/apps/windows/dot-net + +trigger: +- main + +pool: + vmImage: 'windows-latest' + +variables: + solution: '**/*.sln' + buildPlatform: 'Any CPU' + buildConfiguration: 'Release' + +steps: +- task: NuGetToolInstaller@1 + +- task: NuGetCommand@2 + inputs: + restoreSolution: '$(solution)' + +- task: VSBuild@1 + inputs: + solution: '$(solution)' + platform: '$(buildPlatform)' + configuration: '$(buildConfiguration)' + +- task: VSTest@2 + inputs: + platform: '$(buildPlatform)' + configuration: '$(buildConfiguration)' From 3a78ea6048d87b019440b66748f873186dfdc6cb Mon Sep 17 00:00:00 2001 From: Randy Armstrong Date: Wed, 29 Nov 2023 18:57:23 -0800 Subject: [PATCH 2/3] Trigger build. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 827ab71b..d038f485 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # UA Cloud Library -The reference implementation of the UA Cloud Library. The UA Cloud Library enables the storage in and querying of OPC UA Information Models from anywhere in the world. +The reference implementation of the UA Cloud Library. The UA Cloud Library enables the storage in and querying of OPC UA Information Models from anywhere in the world. ## Features From ad23bb1c694425d36f4a3289d49aa469475dfc64 Mon Sep 17 00:00:00 2001 From: MarkusHorstmann Date: Wed, 29 Nov 2023 22:49:11 -0800 Subject: [PATCH 3/3] Recaptcha V3 (#200) * API Key: copy to clipboard option * Recaptcha for Registration * Recaptcha for password reset and email verification/change * Remove commented out code --- .../Pages/Account/ForgotPassword.cshtml | 34 +++- .../Pages/Account/ForgotPassword.cshtml.cs | 28 ++- .../Pages/Account/Manage/Email.cshtml | 43 +++- .../Pages/Account/Manage/Email.cshtml.cs | 29 ++- .../Pages/Account/Manage/ManageApiKeys.cshtml | 40 +++- .../Account/Manage/ManageApiKeys.cshtml.cs | 3 - .../Identity/Pages/Account/Register.cshtml | 89 +++++--- .../Identity/Pages/Account/Register.cshtml.cs | 37 +++- UACloudLibraryServer/CaptchaValidation.cs | 191 ++++++++++++++++++ .../Interfaces/ICaptchaValidation.cs | 12 ++ UACloudLibraryServer/Startup.cs | 7 + UACloudLibraryServer/appsettings.json | 12 ++ .../lib/clipboard-copy-element/LICENSE | 19 ++ .../lib/clipboard-copy-element/README.md | 98 +++++++++ .../lib/clipboard-copy-element/dist/bundle.js | 154 ++++++++++++++ .../dist/clipboard-copy-element-define.d.ts | 19 ++ .../dist/clipboard-copy-element-define.js | 13 ++ .../dist/clipboard-copy-element.d.ts | 7 + .../dist/clipboard-copy-element.js | 85 ++++++++ .../dist/clipboard.d.ts | 2 + .../clipboard-copy-element/dist/clipboard.js | 39 ++++ .../clipboard-copy-element/dist/index.d.ts | 4 + .../lib/clipboard-copy-element/dist/index.js | 4 + .../lib/clipboard-copy-element/package.json | 63 ++++++ 24 files changed, 992 insertions(+), 40 deletions(-) create mode 100644 UACloudLibraryServer/CaptchaValidation.cs create mode 100644 UACloudLibraryServer/Interfaces/ICaptchaValidation.cs create mode 100644 UACloudLibraryServer/wwwroot/lib/clipboard-copy-element/LICENSE create mode 100644 UACloudLibraryServer/wwwroot/lib/clipboard-copy-element/README.md create mode 100644 UACloudLibraryServer/wwwroot/lib/clipboard-copy-element/dist/bundle.js create mode 100644 UACloudLibraryServer/wwwroot/lib/clipboard-copy-element/dist/clipboard-copy-element-define.d.ts create mode 100644 UACloudLibraryServer/wwwroot/lib/clipboard-copy-element/dist/clipboard-copy-element-define.js create mode 100644 UACloudLibraryServer/wwwroot/lib/clipboard-copy-element/dist/clipboard-copy-element.d.ts create mode 100644 UACloudLibraryServer/wwwroot/lib/clipboard-copy-element/dist/clipboard-copy-element.js create mode 100644 UACloudLibraryServer/wwwroot/lib/clipboard-copy-element/dist/clipboard.d.ts create mode 100644 UACloudLibraryServer/wwwroot/lib/clipboard-copy-element/dist/clipboard.js create mode 100644 UACloudLibraryServer/wwwroot/lib/clipboard-copy-element/dist/index.d.ts create mode 100644 UACloudLibraryServer/wwwroot/lib/clipboard-copy-element/dist/index.js create mode 100644 UACloudLibraryServer/wwwroot/lib/clipboard-copy-element/package.json diff --git a/UACloudLibraryServer/Areas/Identity/Pages/Account/ForgotPassword.cshtml b/UACloudLibraryServer/Areas/Identity/Pages/Account/ForgotPassword.cshtml index 613aa5b5..a7dab917 100644 --- a/UACloudLibraryServer/Areas/Identity/Pages/Account/ForgotPassword.cshtml +++ b/UACloudLibraryServer/Areas/Identity/Pages/Account/ForgotPassword.cshtml @@ -2,6 +2,7 @@ @model ForgotPasswordModel @{ ViewData["Title"] = "Forgot your password?"; + var _reCaptchaUrl = $"{@Model.CaptchaSettings.ClientApiUrl}{@Model.CaptchaSettings.SiteKey}"; }

@ViewData["Title"]

@@ -9,18 +10,47 @@
-
+
+
- + + @if (!Model.CaptchaSettings.Enabled) + { + + } + else + { + + }
@section Scripts { + @if (Model.CaptchaSettings.Enabled) + { + + + } } diff --git a/UACloudLibraryServer/Areas/Identity/Pages/Account/ForgotPassword.cshtml.cs b/UACloudLibraryServer/Areas/Identity/Pages/Account/ForgotPassword.cshtml.cs index e4dc2200..32106182 100644 --- a/UACloudLibraryServer/Areas/Identity/Pages/Account/ForgotPassword.cshtml.cs +++ b/UACloudLibraryServer/Areas/Identity/Pages/Account/ForgotPassword.cshtml.cs @@ -11,7 +11,9 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.WebUtilities; +using Microsoft.Extensions.Configuration; using Opc.Ua.Cloud.Library.Authentication; +using Opc.Ua.Cloud.Library.Interfaces; namespace Opc.Ua.Cloud.Library.Areas.Identity.Pages.Account { @@ -19,13 +21,33 @@ public class ForgotPasswordModel : PageModel { private readonly UserManager _userManager; private readonly IEmailSender _emailSender; + private readonly Interfaces.ICaptchaValidation _captchaValidation; + private readonly CaptchaSettings _captchaSettings; - public ForgotPasswordModel(UserManager userManager, IEmailSender emailSender) + public ForgotPasswordModel( + UserManager userManager, + IEmailSender emailSender, + IConfiguration configuration, + Interfaces.ICaptchaValidation captchaValidation) { _userManager = userManager; _emailSender = emailSender; + _captchaValidation = captchaValidation; + + _captchaSettings = new CaptchaSettings(); + configuration.GetSection("CaptchaSettings").Bind(_captchaSettings); } + /// Populate values for cshtml to use + /// + public CaptchaSettings CaptchaSettings { get { return _captchaSettings; } } + + /// + /// Populate a token returned from client side call to Google Captcha + /// + [BindProperty] + public string CaptchaResponseToken { get; set; } + /// /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. @@ -50,6 +72,10 @@ public class InputModel public async Task OnPostAsync() { + //Captcha validate + var captchaResult = await _captchaValidation.ValidateCaptcha(CaptchaResponseToken); + if (!string.IsNullOrEmpty(captchaResult)) ModelState.AddModelError("CaptchaResponseToken", captchaResult); + if (ModelState.IsValid) { var user = await _userManager.FindByEmailAsync(Input.Email).ConfigureAwait(false); diff --git a/UACloudLibraryServer/Areas/Identity/Pages/Account/Manage/Email.cshtml b/UACloudLibraryServer/Areas/Identity/Pages/Account/Manage/Email.cshtml index 9e8418b7..85c2ae8a 100644 --- a/UACloudLibraryServer/Areas/Identity/Pages/Account/Manage/Email.cshtml +++ b/UACloudLibraryServer/Areas/Identity/Pages/Account/Manage/Email.cshtml @@ -3,14 +3,17 @@ @{ ViewData["Title"] = "Manage Email"; ViewData["ActivePage"] = ManageNavPages.Email; + var _reCaptchaUrl = $"{@Model.CaptchaSettings.ClientApiUrl}{@Model.CaptchaSettings.SiteKey}"; }

@ViewData["Title"]

-
+
+ + @if (Model.IsEmailConfirmed) {
@@ -26,7 +29,7 @@
- +
}
@@ -34,11 +37,45 @@
- +
@section Scripts { + @if (Model.CaptchaSettings.Enabled) + { + + + } + } diff --git a/UACloudLibraryServer/Areas/Identity/Pages/Account/Manage/Email.cshtml.cs b/UACloudLibraryServer/Areas/Identity/Pages/Account/Manage/Email.cshtml.cs index 5551865c..ced44711 100644 --- a/UACloudLibraryServer/Areas/Identity/Pages/Account/Manage/Email.cshtml.cs +++ b/UACloudLibraryServer/Areas/Identity/Pages/Account/Manage/Email.cshtml.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.WebUtilities; +using Microsoft.Extensions.Configuration; using Opc.Ua.Cloud.Library.Authentication; namespace Opc.Ua.Cloud.Library.Areas.Identity.Pages.Account.Manage @@ -20,15 +21,23 @@ public class EmailModel : PageModel private readonly UserManager _userManager; private readonly SignInManager _signInManager; private readonly IEmailSender _emailSender; + private readonly Interfaces.ICaptchaValidation _captchaValidation; + private readonly CaptchaSettings _captchaSettings; public EmailModel( UserManager userManager, SignInManager signInManager, - IEmailSender emailSender) + IEmailSender emailSender, + IConfiguration configuration, + Interfaces.ICaptchaValidation captchaValidation) { _userManager = userManager; _signInManager = signInManager; _emailSender = emailSender; + _captchaValidation = captchaValidation; + + _captchaSettings = new CaptchaSettings(); + configuration.GetSection("CaptchaSettings").Bind(_captchaSettings); } /// @@ -43,6 +52,17 @@ public EmailModel( /// public bool IsEmailConfirmed { get; set; } + /// + /// Populate values for cshtml to use + /// + public CaptchaSettings CaptchaSettings { get { return _captchaSettings; } } + + /// + /// Populate a token returned from client side call to Google Captcha + /// + [BindProperty] + public string CaptchaResponseToken { get; set; } + /// /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. @@ -105,6 +125,9 @@ public async Task OnPostChangeEmailAsync() return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); } + var captchaResult = await _captchaValidation.ValidateCaptcha(CaptchaResponseToken); + if (!string.IsNullOrEmpty(captchaResult)) ModelState.AddModelError("CaptchaResponseToken", captchaResult); + if (!ModelState.IsValid) { await LoadAsync(user); @@ -147,6 +170,10 @@ public async Task OnPostSendVerificationEmailAsync() return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); } + //Captcha validate + var captchaResult = await _captchaValidation.ValidateCaptcha(CaptchaResponseToken); + if (!string.IsNullOrEmpty(captchaResult)) ModelState.AddModelError("CaptchaResponseToken", captchaResult); + if (!ModelState.IsValid) { await LoadAsync(user).ConfigureAwait(false); diff --git a/UACloudLibraryServer/Areas/Identity/Pages/Account/Manage/ManageApiKeys.cshtml b/UACloudLibraryServer/Areas/Identity/Pages/Account/Manage/ManageApiKeys.cshtml index 6d54eec3..4411f225 100644 --- a/UACloudLibraryServer/Areas/Identity/Pages/Account/Manage/ManageApiKeys.cshtml +++ b/UACloudLibraryServer/Areas/Identity/Pages/Account/Manage/ManageApiKeys.cshtml @@ -7,10 +7,23 @@

@ViewData["Title"]

+ +
@if (!string.IsNullOrEmpty(Model.GeneratedApiKey)) { -
+

Generated Key

@@ -18,18 +31,33 @@ - +
Name:
- @Model.GeneratedApiKeyName +
@Model.GeneratedApiKeyName
- +
Key:
+
@Model.GeneratedApiKey + + + + + + + + + +
@@ -40,7 +68,7 @@
} -
+

Generate a new API key

@@ -59,7 +87,7 @@
@if (Model.ApiKeysAndNames?.Any() == true) { -
+

Existing API keys

diff --git a/UACloudLibraryServer/Areas/Identity/Pages/Account/Manage/ManageApiKeys.cshtml.cs b/UACloudLibraryServer/Areas/Identity/Pages/Account/Manage/ManageApiKeys.cshtml.cs index ec475d7e..43f69407 100644 --- a/UACloudLibraryServer/Areas/Identity/Pages/Account/Manage/ManageApiKeys.cshtml.cs +++ b/UACloudLibraryServer/Areas/Identity/Pages/Account/Manage/ManageApiKeys.cshtml.cs @@ -4,13 +4,10 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; using Opc.Ua.Cloud.Library.Authentication; namespace Opc.Ua.Cloud.Library.Areas.Identity.Pages.Account.Manage diff --git a/UACloudLibraryServer/Areas/Identity/Pages/Account/Register.cshtml b/UACloudLibraryServer/Areas/Identity/Pages/Account/Register.cshtml index 99b730f4..e280a262 100644 --- a/UACloudLibraryServer/Areas/Identity/Pages/Account/Register.cshtml +++ b/UACloudLibraryServer/Areas/Identity/Pages/Account/Register.cshtml @@ -2,34 +2,57 @@ @model RegisterModel @{ ViewData["Title"] = "Register"; + var _reCaptchaUrl = $"{@Model.CaptchaSettings.ClientApiUrl}{@Model.CaptchaSettings.SiteKey}"; }

@ViewData["Title"]

-
-
-

Create a new account.

-
-
-
- - - -
-
- - - -
-
- - - -
- -
-
+ @if (Model.AllowSelfRegistration) + { +
+
+

Create a new account.

+
+
+ +
+ + + +
+
+ + + +
+
+ + + +
+ + @if (!Model.CaptchaSettings.Enabled) + { + + } + else + { + + } +
+
+ } + else + { +
+
+

Create a new account.

+
+ Creation of local accounts is currently unavailable. +
+
+ }

Use another service to register.

@@ -64,4 +87,24 @@ @section Scripts { + + @if (Model.CaptchaSettings.Enabled) { + + + } } diff --git a/UACloudLibraryServer/Areas/Identity/Pages/Account/Register.cshtml.cs b/UACloudLibraryServer/Areas/Identity/Pages/Account/Register.cshtml.cs index 62cc750a..6489ca50 100644 --- a/UACloudLibraryServer/Areas/Identity/Pages/Account/Register.cshtml.cs +++ b/UACloudLibraryServer/Areas/Identity/Pages/Account/Register.cshtml.cs @@ -17,6 +17,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.WebUtilities; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Opc.Ua.Cloud.Library.Authentication; @@ -30,13 +31,20 @@ public class RegisterModel : PageModel private readonly IUserEmailStore _emailStore; private readonly ILogger _logger; private readonly IEmailSender _emailSender; + private readonly IConfiguration _configuration; + private readonly Interfaces.ICaptchaValidation _captchaValidation; + private readonly CaptchaSettings _captchaSettings; + + public bool AllowSelfRegistration { get; set; } = true; public RegisterModel( UserManager userManager, IUserStore userStore, SignInManager signInManager, ILogger logger, - IEmailSender emailSender) + IEmailSender emailSender, + IConfiguration configuration, + Interfaces.ICaptchaValidation captchaValidation) { _userManager = userManager; _userStore = userStore; @@ -44,6 +52,13 @@ public RegisterModel( _signInManager = signInManager; _logger = logger; _emailSender = emailSender; + _configuration = configuration; + _captchaValidation = captchaValidation; + + _captchaSettings = new CaptchaSettings(); + configuration.GetSection("CaptchaSettings").Bind(_captchaSettings); + + AllowSelfRegistration = configuration.GetValue(nameof(AllowSelfRegistration)) != false; } /// @@ -65,6 +80,17 @@ public RegisterModel( /// public IList ExternalLogins { get; set; } + /// + /// Populate values for cshtml to use + /// + public CaptchaSettings CaptchaSettings { get { return _captchaSettings; } } + + /// + /// Populate a token returned from client side call to Google Captcha + /// + [BindProperty] + public string CaptchaResponseToken { get; set; } + /// /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. @@ -109,8 +135,17 @@ public async Task OnGetAsync(string returnUrl = null) public async Task OnPostAsync(string returnUrl = null) { + if (!AllowSelfRegistration) + { + return Page(); + } returnUrl ??= Url.Content("~/"); ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync().ConfigureAwait(false)).ToList(); + + //Captcha validate + var captchaResult = await _captchaValidation.ValidateCaptcha(CaptchaResponseToken); + if (!string.IsNullOrEmpty(captchaResult)) ModelState.AddModelError("CaptchaResponseToken", captchaResult); + if (ModelState.IsValid) { var user = CreateUser(); diff --git a/UACloudLibraryServer/CaptchaValidation.cs b/UACloudLibraryServer/CaptchaValidation.cs new file mode 100644 index 00000000..b5818ec1 --- /dev/null +++ b/UACloudLibraryServer/CaptchaValidation.cs @@ -0,0 +1,191 @@ +/* ======================================================================== + * Copyright (c) 2005-2021 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +namespace Opc.Ua.Cloud.Library +{ + using System; + using System.Collections.Generic; + using System.Net.Http; + using System.Threading.Tasks; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.Logging; + using Newtonsoft.Json; + using Opc.Ua.Cloud.Library.Interfaces; + + + public class CaptchaSettings + { + public string SiteVerifyUrl { get; set; } + public string ClientApiUrl { get; set; } + public string SecretKey { get; set; } + public string SiteKey { get; set; } + public float BotThreshold { get; set; } + public bool Enabled { get; set; } = false; + } + + /// + /// Structure matches up with Google's response JSON + /// + public class ReCaptchaResponse + { + public bool success { get; set; } + public double score { get; set; } + public string action { get; set; } + public DateTime challenge_ts { get; set; } + public string hostname { get; set; } + [JsonProperty("error-codes")] + public List error_codes { get; set; } + } + + public class CaptchaValidation : Interfaces.ICaptchaValidation + { + private readonly ILogger _logger; + private readonly IHttpClientFactory _httpClientFactory; + private readonly CaptchaSettings _captchaSettings; + + public CaptchaValidation( + ILogger logger, + IConfiguration configuration, + IHttpClientFactory httpClientFactory) + { + _logger = logger; + _httpClientFactory = httpClientFactory; + + _captchaSettings = new CaptchaSettings(); + configuration.GetSection("CaptchaSettings").Bind(_captchaSettings); + } + + public async Task ValidateCaptcha(string responseToken) + { + if (!_captchaSettings.Enabled) return null; + + bool configError = false; + //check for valid values + if (_captchaSettings == null) _logger.LogCritical($"ValidateCaptcha|Captcha settings are missing or invalid"); + if (string.IsNullOrEmpty(_captchaSettings.SiteVerifyUrl)) + { + configError = true; + _logger.LogCritical($"ValidateCaptcha|Captcha:BaseAddress is missing or invalid"); + } + if (string.IsNullOrEmpty(_captchaSettings.SecretKey)) + { + configError = true; + _logger.LogCritical($"ValidateCaptcha|Captcha:Secret Key is missing or invalid"); + } + if (string.IsNullOrEmpty(_captchaSettings.SiteKey)) + { + configError = true; + _logger.LogCritical($"ValidateCaptcha|Captcha:Site Key is missing or invalid"); + } + + //non-user caused issue... + if (configError) + { + return "The automated Captcha system is not configured. Please contact the system administrator."; + } + + //var responseToken = Request.Form["reCaptchaResponseToken"]; + if (string.IsNullOrEmpty(responseToken)) + { + _logger.LogCritical($"ValidateCaptcha|Captcha:responseToken is missing or invalid"); + return "The Captcha client response was incorrect or not supplied. Please contact the system administrator."; + } + + //make the API call + HttpClient client = _httpClientFactory.CreateClient(); + try + { + client.BaseAddress = new Uri(_captchaSettings.SiteVerifyUrl); + + //prepare the request + using (var requestMessage = new HttpRequestMessage(HttpMethod.Post, "")) + { + //add the body + var parameters = new Dictionary{ + {"secret", _captchaSettings.SecretKey}, + {"response", responseToken} + //{"remoteip", "ip" } <= this is optional + }; + + requestMessage.Content = new FormUrlEncodedContent(parameters); + + //call the api + HttpResponseMessage response = await client.SendAsync(requestMessage); + + //basic error with call + if (!response.IsSuccessStatusCode) + { + var msg = $"{(int)response.StatusCode}-{response.ReasonPhrase}"; + _logger.LogCritical($"ValidateCaptcha|Error occurred in the API call: {msg}"); + return "An error occurred validating the Captcha response. Please contact your system administrator."; + } + + //check the reCaptcha response + var data = response.Content.ReadAsStringAsync().Result; //Make sure to add a reference to System.Net.Http.Formatting.dll + var recaptchaResponse = Newtonsoft.Json.JsonConvert.DeserializeObject(data); + if (recaptchaResponse == null) + { + _logger.LogCritical($"ValidateCaptcha|Expected Google reCaptcha response was null"); + return "An error occurred retrieving the Captcha response. Please contact your system administrator."; + } + + if (!recaptchaResponse.success) + { + var errors = string.Join(",", recaptchaResponse.error_codes); + _logger.LogCritical($"ValidateCaptcha| Google reCaptcha returned error(s): {errors}"); + return "Error(s) occurred validating the Captcha response. Please contact your system administrator."; + } + + // anything less than 0.5 is a bot + if (recaptchaResponse.score < _captchaSettings.BotThreshold) + { + _logger.LogCritical($"ValidateCaptcha|Bot score: {recaptchaResponse.score} < Threshold: {_captchaSettings.BotThreshold}"); + return "You are not a human. If you believe this is not correct, please contact your system administrator."; + } + else + { + _logger.LogInformation($"ValidateCaptcha|Goggle Bot score: {recaptchaResponse.score} (0 bad, {_captchaSettings.BotThreshold} threshold, 1 good)"); + } + //if we get here, all good. + return null; + } + } + catch (Exception ex) + { + var msg = $"ValidateCaptcha|Unexpected error occurred in the API call: {ex.Message}"; + _logger.LogCritical(ex, msg); + return "An unexpected error occurred validating the Captcha response. Please contact your system administrator."; + } + finally + { + client.Dispose(); + } + } + } +} diff --git a/UACloudLibraryServer/Interfaces/ICaptchaValidation.cs b/UACloudLibraryServer/Interfaces/ICaptchaValidation.cs new file mode 100644 index 00000000..04bc9268 --- /dev/null +++ b/UACloudLibraryServer/Interfaces/ICaptchaValidation.cs @@ -0,0 +1,12 @@ +namespace Opc.Ua.Cloud.Library.Interfaces +{ + using System.Collections.Generic; + using System.Security.Claims; + using System.Threading.Tasks; + + public interface ICaptchaValidation + { + Task ValidateCaptcha(string responseToken); + } +} + diff --git a/UACloudLibraryServer/Startup.cs b/UACloudLibraryServer/Startup.cs index 9528c186..0f85ac1c 100644 --- a/UACloudLibraryServer/Startup.cs +++ b/UACloudLibraryServer/Startup.cs @@ -101,6 +101,8 @@ public void ConfigureServices(IServiceCollection services) services.AddTransient(); + services.AddScoped(); + if (!string.IsNullOrEmpty(Configuration["UseSendGridEmailSender"])) { services.AddTransient(); @@ -120,6 +122,11 @@ public void ConfigureServices(IServiceCollection services) #endif ; + //for captcha validation call + //add httpclient service for dependency injection + //https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-6.0 + services.AddHttpClient(); + if (Configuration["OAuth2ClientId"] != null) { services.AddAuthentication() diff --git a/UACloudLibraryServer/appsettings.json b/UACloudLibraryServer/appsettings.json index 81ff8777..63569715 100644 --- a/UACloudLibraryServer/appsettings.json +++ b/UACloudLibraryServer/appsettings.json @@ -1,4 +1,16 @@ { + "CloudLibrary": { + "ApprovalRequired": false + }, + "AllowSelfRegistration": true, + "CaptchaSettings": { + "SiteVerifyUrl": "https://www.google.com/recaptcha/api/siteverify", + "ClientApiUrl": "https://www.google.com/recaptcha/api.js?render=", + "SecretKey": "", + "SiteKey": "", + "BotThreshold": 0.5, + "Enabled": true + }, "Logging": { "LogLevel": { "Default": "Information", diff --git a/UACloudLibraryServer/wwwroot/lib/clipboard-copy-element/LICENSE b/UACloudLibraryServer/wwwroot/lib/clipboard-copy-element/LICENSE new file mode 100644 index 00000000..6dce93c4 --- /dev/null +++ b/UACloudLibraryServer/wwwroot/lib/clipboard-copy-element/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018 GitHub, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/UACloudLibraryServer/wwwroot/lib/clipboard-copy-element/README.md b/UACloudLibraryServer/wwwroot/lib/clipboard-copy-element/README.md new file mode 100644 index 00000000..1259d79b --- /dev/null +++ b/UACloudLibraryServer/wwwroot/lib/clipboard-copy-element/README.md @@ -0,0 +1,98 @@ +# <clipboard-copy> element + +Copy element text content or input values to the clipboard. + +## Installation + +``` +$ npm install --save @github/clipboard-copy-element +``` + +## Usage + +### Script + +Import as ES modules: + +```js +import '@github/clipboard-copy-element' +``` + +With a script tag: + +```html +