diff --git a/EasyGCaptchaMVC.sln b/EasyGCaptchaMVC.sln index e46be60..f55fcd2 100644 --- a/EasyGCaptchaMVC.sln +++ b/EasyGCaptchaMVC.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 +# Visual Studio 15 +VisualStudioVersion = 15.0.26403.3 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyGCaptchaMVC", "EasyGCaptchaMVC\EasyGCaptchaMVC.csproj", "{82F9AA64-B9F3-4672-AF82-FB39C56686CB}" EndProject @@ -13,6 +13,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".Net 4.5", ".Net 4.5", "{769EFAAE-EBE9-4477-9223-BA4EF8C99647}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".Net Core", ".Net Core", "{2359F7B1-061D-4A62-A195-A687EE67290F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyGCaptchaMVCCore", "EasyGCaptchaMVCCore\EasyGCaptchaMVCCore.csproj", "{5E8A79EE-EF91-4DE4-8975-00B36DC5DB71}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyGCaptchaMVCCore.Demo", "EasyGCaptchaMVCCore.Demo\EasyGCaptchaMVCCore.Demo.csproj", "{0DB7189E-8520-4B25-A8F6-BD334DEA8D02}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,8 +35,22 @@ Global {E3ED254A-43E2-4BB8-8E7C-2F20C6E0AF4F}.Debug|Any CPU.Build.0 = Debug|Any CPU {E3ED254A-43E2-4BB8-8E7C-2F20C6E0AF4F}.Release|Any CPU.ActiveCfg = Release|Any CPU {E3ED254A-43E2-4BB8-8E7C-2F20C6E0AF4F}.Release|Any CPU.Build.0 = Release|Any CPU + {5E8A79EE-EF91-4DE4-8975-00B36DC5DB71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5E8A79EE-EF91-4DE4-8975-00B36DC5DB71}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5E8A79EE-EF91-4DE4-8975-00B36DC5DB71}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5E8A79EE-EF91-4DE4-8975-00B36DC5DB71}.Release|Any CPU.Build.0 = Release|Any CPU + {0DB7189E-8520-4B25-A8F6-BD334DEA8D02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0DB7189E-8520-4B25-A8F6-BD334DEA8D02}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0DB7189E-8520-4B25-A8F6-BD334DEA8D02}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0DB7189E-8520-4B25-A8F6-BD334DEA8D02}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {82F9AA64-B9F3-4672-AF82-FB39C56686CB} = {769EFAAE-EBE9-4477-9223-BA4EF8C99647} + {E3ED254A-43E2-4BB8-8E7C-2F20C6E0AF4F} = {769EFAAE-EBE9-4477-9223-BA4EF8C99647} + {5E8A79EE-EF91-4DE4-8975-00B36DC5DB71} = {2359F7B1-061D-4A62-A195-A687EE67290F} + {0DB7189E-8520-4B25-A8F6-BD334DEA8D02} = {2359F7B1-061D-4A62-A195-A687EE67290F} + EndGlobalSection EndGlobal diff --git a/EasyGCaptchaMVCCore.Demo/.bowerrc b/EasyGCaptchaMVCCore.Demo/.bowerrc new file mode 100644 index 0000000..6406626 --- /dev/null +++ b/EasyGCaptchaMVCCore.Demo/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "wwwroot/lib" +} diff --git a/EasyGCaptchaMVCCore.Demo/Controllers/HomeController.cs b/EasyGCaptchaMVCCore.Demo/Controllers/HomeController.cs new file mode 100644 index 0000000..fe81b35 --- /dev/null +++ b/EasyGCaptchaMVCCore.Demo/Controllers/HomeController.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using EasyGCaptchaMVCCore.Configuration; +using EasyGCaptchaMVCCore.Demo.Models; +using EasyGCaptchaMVCCore.Model; +using EasyGCaptchaMVCCore.Worker; +using Microsoft.AspNetCore.Mvc; + +namespace EasyGCaptchaMVCCore.Demo.Controllers +{ + public class HomeController : Controller + { + [HttpGet] + public ActionResult Index() + { + GCaptchaSettingsProvider.Instance.Theme = Theme.Light; + GCaptchaSettingsProvider.Instance.Size = Size.Normal; + + return View(new IndexViewModel()); + } + + [HttpPost] + [EasyGCaptcha] + public ActionResult Index(IndexViewModel model, EasyGCaptchaResult easyGCaptchaResult) + { + if (easyGCaptchaResult.Success) + { + // Do your work here + } + model.EasyGCaptchaResult = easyGCaptchaResult; + return View(model); + } + } +} diff --git a/EasyGCaptchaMVCCore.Demo/EasyGCaptchaMVCCore.Demo.csproj b/EasyGCaptchaMVCCore.Demo/EasyGCaptchaMVCCore.Demo.csproj new file mode 100644 index 0000000..183693a --- /dev/null +++ b/EasyGCaptchaMVCCore.Demo/EasyGCaptchaMVCCore.Demo.csproj @@ -0,0 +1,25 @@ + + + + netcoreapp1.1 + + + + $(PackageTargetFallback);portable-net45+win8+wp8+wpa81; + + + + + + + + + + + + + + + + + diff --git a/EasyGCaptchaMVCCore.Demo/Models/ContactViewModel.cs b/EasyGCaptchaMVCCore.Demo/Models/ContactViewModel.cs new file mode 100644 index 0000000..124b7e6 --- /dev/null +++ b/EasyGCaptchaMVCCore.Demo/Models/ContactViewModel.cs @@ -0,0 +1,8 @@ +namespace EasyGCaptchaMVCCore.Demo.Models +{ + public class ContactViewModel + { + public string Name { get; set; } + public string Message { get; set; } + } +} \ No newline at end of file diff --git a/EasyGCaptchaMVCCore.Demo/Models/IndexViewModel.cs b/EasyGCaptchaMVCCore.Demo/Models/IndexViewModel.cs new file mode 100644 index 0000000..eeef2f0 --- /dev/null +++ b/EasyGCaptchaMVCCore.Demo/Models/IndexViewModel.cs @@ -0,0 +1,17 @@ +using EasyGCaptchaMVCCore.Model; + +namespace EasyGCaptchaMVCCore.Demo.Models +{ + public class IndexViewModel + { + public ContactViewModel ContactViewModel { get; set; } + public EasyGCaptchaResult EasyGCaptchaResult { get; set; } + + + public IndexViewModel() + { + ContactViewModel = new ContactViewModel(); + EasyGCaptchaResult = new EasyGCaptchaResult(); + } + } +} \ No newline at end of file diff --git a/EasyGCaptchaMVCCore.Demo/Program.cs b/EasyGCaptchaMVCCore.Demo/Program.cs new file mode 100644 index 0000000..19780d3 --- /dev/null +++ b/EasyGCaptchaMVCCore.Demo/Program.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; + +namespace EasyGCaptchaMVCCore.Demo +{ + public class Program + { + public static void Main(string[] args) + { + var host = new WebHostBuilder() + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseStartup() + .UseApplicationInsights() + .Build(); + + host.Run(); + } + } +} diff --git a/EasyGCaptchaMVCCore.Demo/Properties/launchSettings.json b/EasyGCaptchaMVCCore.Demo/Properties/launchSettings.json new file mode 100644 index 0000000..dfbe161 --- /dev/null +++ b/EasyGCaptchaMVCCore.Demo/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:23455/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "EasyGCaptchaMVCCore.Demo": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:23456" + } + } +} diff --git a/EasyGCaptchaMVCCore.Demo/Startup.cs b/EasyGCaptchaMVCCore.Demo/Startup.cs new file mode 100644 index 0000000..36530e7 --- /dev/null +++ b/EasyGCaptchaMVCCore.Demo/Startup.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace EasyGCaptchaMVCCore.Demo +{ + public class Startup + { + public Startup(IHostingEnvironment env) + { + var builder = new ConfigurationBuilder() + .SetBasePath(env.ContentRootPath) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) + .AddEnvironmentVariables(); + Configuration = builder.Build(); + } + + public IConfigurationRoot Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + // Add framework services. + services.AddMvc(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + { + loggerFactory.AddConsole(Configuration.GetSection("Logging")); + loggerFactory.AddDebug(); + + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + app.UseBrowserLink(); + } + else + { + app.UseExceptionHandler("/Home/Error"); + } + + app.UseStaticFiles(); + + app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); + } + } +} diff --git a/EasyGCaptchaMVCCore.Demo/Views/Home/Index.cshtml b/EasyGCaptchaMVCCore.Demo/Views/Home/Index.cshtml new file mode 100644 index 0000000..76e8c56 --- /dev/null +++ b/EasyGCaptchaMVCCore.Demo/Views/Home/Index.cshtml @@ -0,0 +1,39 @@ +@using EasyGCaptchaMVCCore +@using EasyGCaptchaMVCCore.Worker +@using Microsoft.AspNetCore.Mvc.ViewFeatures +@using Type = EasyGCaptchaMVCCore.Model.Type +@model EasyGCaptchaMVCCore.Demo.Models.IndexViewModel +

EasyGCaptchaMVC Demo

+ + +@using (Html.BeginForm("Index", "Home", FormMethod.Post, new { @id = "contact-form", role = "form" })) +{ +

Protected form

+ @Html.LabelFor(x => x.ContactViewModel.Name, "Name")
+ @Html.TextBoxFor(x => x.ContactViewModel.Name) + +

+ + @Html.LabelFor(x => x.ContactViewModel.Message, "Message")
+ @Html.TextBoxFor(x => x.ContactViewModel.Message) + +

+ + @Html.EasyGCaptchaGenerateCaptcha() + +

+ + +} + +
+ +@if (Model.EasyGCaptchaResult != null) +{ +

Result

+ + @:State: @Model.EasyGCaptchaResult.Success
+ @:Time: @Model.EasyGCaptchaResult.Date
+ @:Hostname: @Model.EasyGCaptchaResult.Hostname
+ @:Errors: @(Model.EasyGCaptchaResult.GErrorCodes == null ? "" : string.Join(", ", Model.EasyGCaptchaResult.GErrorCodes)) +} \ No newline at end of file diff --git a/EasyGCaptchaMVCCore.Demo/Views/Shared/Error.cshtml b/EasyGCaptchaMVCCore.Demo/Views/Shared/Error.cshtml new file mode 100644 index 0000000..e514139 --- /dev/null +++ b/EasyGCaptchaMVCCore.Demo/Views/Shared/Error.cshtml @@ -0,0 +1,14 @@ +@{ + ViewData["Title"] = "Error"; +} + +

Error.

+

An error occurred while processing your request.

+ +

Development Mode

+

+ Swapping to Development environment will display more detailed information about the error that occurred. +

+

+ Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. +

diff --git a/EasyGCaptchaMVCCore.Demo/Views/Shared/_Layout.cshtml b/EasyGCaptchaMVCCore.Demo/Views/Shared/_Layout.cshtml new file mode 100644 index 0000000..20b7b35 --- /dev/null +++ b/EasyGCaptchaMVCCore.Demo/Views/Shared/_Layout.cshtml @@ -0,0 +1,24 @@ +@inject Microsoft.ApplicationInsights.AspNetCore.JavaScriptSnippet JavaScriptSnippet + + + + + + @ViewBag.Title - My ASP.NET Application + + + + + + + + + + +@RenderBody() + +@RenderSection("scripts", required: false) + + diff --git a/EasyGCaptchaMVCCore.Demo/Views/Shared/_ValidationScriptsPartial.cshtml b/EasyGCaptchaMVCCore.Demo/Views/Shared/_ValidationScriptsPartial.cshtml new file mode 100644 index 0000000..27e0ea7 --- /dev/null +++ b/EasyGCaptchaMVCCore.Demo/Views/Shared/_ValidationScriptsPartial.cshtml @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/EasyGCaptchaMVCCore.Demo/Views/_ViewImports.cshtml b/EasyGCaptchaMVCCore.Demo/Views/_ViewImports.cshtml new file mode 100644 index 0000000..d6417f6 --- /dev/null +++ b/EasyGCaptchaMVCCore.Demo/Views/_ViewImports.cshtml @@ -0,0 +1,2 @@ +@using EasyGCaptchaMVCCore.Demo +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/EasyGCaptchaMVCCore.Demo/Views/_ViewStart.cshtml b/EasyGCaptchaMVCCore.Demo/Views/_ViewStart.cshtml new file mode 100644 index 0000000..a5f1004 --- /dev/null +++ b/EasyGCaptchaMVCCore.Demo/Views/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_Layout"; +} diff --git a/EasyGCaptchaMVCCore.Demo/appsettings.Development.json b/EasyGCaptchaMVCCore.Demo/appsettings.Development.json new file mode 100644 index 0000000..fa8ce71 --- /dev/null +++ b/EasyGCaptchaMVCCore.Demo/appsettings.Development.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/EasyGCaptchaMVCCore.Demo/appsettings.json b/EasyGCaptchaMVCCore.Demo/appsettings.json new file mode 100644 index 0000000..5fff67b --- /dev/null +++ b/EasyGCaptchaMVCCore.Demo/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Warning" + } + } +} diff --git a/EasyGCaptchaMVCCore.Demo/bower.json b/EasyGCaptchaMVCCore.Demo/bower.json new file mode 100644 index 0000000..b07e3cc --- /dev/null +++ b/EasyGCaptchaMVCCore.Demo/bower.json @@ -0,0 +1,10 @@ +{ + "name": "asp.net", + "private": true, + "dependencies": { + "bootstrap": "3.3.7", + "jquery": "2.2.0", + "jquery-validation": "1.14.0", + "jquery-validation-unobtrusive": "3.2.6" + } +} diff --git a/EasyGCaptchaMVCCore.Demo/bundleconfig.json b/EasyGCaptchaMVCCore.Demo/bundleconfig.json new file mode 100644 index 0000000..6d3f9a5 --- /dev/null +++ b/EasyGCaptchaMVCCore.Demo/bundleconfig.json @@ -0,0 +1,24 @@ +// Configure bundling and minification for the project. +// More info at https://go.microsoft.com/fwlink/?LinkId=808241 +[ + { + "outputFileName": "wwwroot/css/site.min.css", + // An array of relative input file paths. Globbing patterns supported + "inputFiles": [ + "wwwroot/css/site.css" + ] + }, + { + "outputFileName": "wwwroot/js/site.min.js", + "inputFiles": [ + "wwwroot/js/site.js" + ], + // Optionally specify minification options + "minify": { + "enabled": true, + "renameLocals": true + }, + // Optionally generate .map file + "sourceMap": false + } +] diff --git a/EasyGCaptchaMVCCore.Demo/wwwroot/css/site.css b/EasyGCaptchaMVCCore.Demo/wwwroot/css/site.css new file mode 100644 index 0000000..e31abde --- /dev/null +++ b/EasyGCaptchaMVCCore.Demo/wwwroot/css/site.css @@ -0,0 +1,37 @@ +body { + padding-top: 50px; + padding-bottom: 20px; +} + +/* Wrapping element */ +/* Set some basic padding to keep content from hitting the edges */ +.body-content { + padding-left: 15px; + padding-right: 15px; +} + +/* Set widths on the form inputs since otherwise they're 100% wide */ +input, +select, +textarea { + max-width: 280px; +} + +/* Carousel */ +.carousel-caption p { + font-size: 20px; + line-height: 1.4; +} + +/* Make .svg files in the carousel display properly in older browsers */ +.carousel-inner .item img[src$=".svg"] { + width: 100%; +} + +/* Hide/rearrange for smaller screens */ +@media screen and (max-width: 767px) { + /* Hide captions */ + .carousel-caption { + display: none; + } +} diff --git a/EasyGCaptchaMVCCore.Demo/wwwroot/css/site.min.css b/EasyGCaptchaMVCCore.Demo/wwwroot/css/site.min.css new file mode 100644 index 0000000..3beb45f --- /dev/null +++ b/EasyGCaptchaMVCCore.Demo/wwwroot/css/site.min.css @@ -0,0 +1 @@ +body{padding-top:50px;padding-bottom:20px}.body-content{padding-left:15px;padding-right:15px}input,select,textarea{max-width:280px}.carousel-caption p{font-size:20px;line-height:1.4}.carousel-inner .item img[src$=".svg"]{width:100%}@media screen and (max-width:767px){.carousel-caption{display:none}} \ No newline at end of file diff --git a/EasyGCaptchaMVCCore.Demo/wwwroot/favicon.ico b/EasyGCaptchaMVCCore.Demo/wwwroot/favicon.ico new file mode 100644 index 0000000..a3a7999 Binary files /dev/null and b/EasyGCaptchaMVCCore.Demo/wwwroot/favicon.ico differ diff --git a/EasyGCaptchaMVCCore.Demo/wwwroot/js/site.js b/EasyGCaptchaMVCCore.Demo/wwwroot/js/site.js new file mode 100644 index 0000000..82ecce7 --- /dev/null +++ b/EasyGCaptchaMVCCore.Demo/wwwroot/js/site.js @@ -0,0 +1 @@ +// Write your Javascript code. diff --git a/EasyGCaptchaMVCCore.Demo/wwwroot/js/site.min.js b/EasyGCaptchaMVCCore.Demo/wwwroot/js/site.min.js new file mode 100644 index 0000000..e69de29 diff --git a/EasyGCaptchaMVCCore/Configuration/GCaptchaSettingsProvider.cs b/EasyGCaptchaMVCCore/Configuration/GCaptchaSettingsProvider.cs new file mode 100644 index 0000000..c51300c --- /dev/null +++ b/EasyGCaptchaMVCCore/Configuration/GCaptchaSettingsProvider.cs @@ -0,0 +1,21 @@ +namespace EasyGCaptchaMVCCore.Configuration +{ + public class GCaptchaSettingsProvider + { + private static Settings _instance; + + private GCaptchaSettingsProvider() { } + + public static Settings Instance + { + get + { + if (_instance == null) + { + _instance = new Settings(); + } + return _instance; + } + } + } +} diff --git a/EasyGCaptchaMVCCore/Configuration/Settings.cs b/EasyGCaptchaMVCCore/Configuration/Settings.cs new file mode 100644 index 0000000..ecb52e4 --- /dev/null +++ b/EasyGCaptchaMVCCore/Configuration/Settings.cs @@ -0,0 +1,117 @@ +using System; +using EasyGCaptchaMVCCore.Model; +using EasyGCaptchaMVCCore.Worker; +using Type = EasyGCaptchaMVCCore.Model.Type; + +namespace EasyGCaptchaMVCCore.Configuration +{ + public class Settings + { + private string _publicKey = String.Empty; + /// + /// Contains the public key, provides by Google. Default: testing key + /// + public string PublicKey + { + get + { + if (string.IsNullOrEmpty(_publicKey)) + { + _publicKey = Helper.GetGPublicKey(); + } + + return _publicKey; + } + set => _publicKey = value; + } + + private string _privateKey = String.Empty; + /// + /// Contains the private key, provides by Google. Default: testing key + /// + public string PrivateKey { + get + { + if (string.IsNullOrEmpty(_privateKey)) + { + _privateKey = Helper.GetGPrivateKey(); + } + + return _privateKey; + } + set => _privateKey = value; + } + + private string _divId = "EasyGCaptchaMVC"; + + /// + /// Contains the id for the div which contains the module. Used for styling and things like that. Can't be set to null or empty. Default: "EasyGCaptchaMVC" + /// + public string DivId + { + get { return _divId; } + set + { + if (!string.IsNullOrEmpty(value)) + { + _divId = value; + } + } + } + + /// + /// Contains the theme setting, provided by Google. Default: Light + /// + public Theme Theme { get; set; } = Theme.Light; + + /// + /// Contains the size setting, provided by Google + /// + public Size Size { get; set; } = Size.Normal; + + /// + /// Contains the validation type setting, provided by Google. Default: Image + /// + public Type Type { get; set; } = Type.Image; + + /// + /// Contains the tabindex for the form, provided by Google. Default: 0 + /// + public int Tabindex { get; set; } = 0; + + /// + /// Provides the ability to call a js function if the module is fully loaded, provided by Google. Default: None + /// + public string CallBack { get; set; } = String.Empty; + + ///// + ///// Can be enabled to show error messages from the extension on the website if the site is in debug mode. Default: true + ///// + //public bool ShowErrorMessagesOnDebug { get; set; } = true; + + /// + /// Prevents the module from throwing exceptions. Default: false + /// + public bool DisableExceptions { get; set; } = false; + + /// + /// Forces the debug/release mode. Should be None on prduction. Default: None + /// + public ForcedConfigurationMode ForcedConfigurationMode { get; set; } = ForcedConfigurationMode.None; + + /// + /// Uses special keys, provided by Google, to show the module but always passthru it. Only for testing. Default: true + /// + public bool UsePassthruInDebugMode { get; set; } = true; + + /// + /// Passes the ip of the client ip to Google. Don't know for what. Default: false + /// + public bool PassRemoteIpToGoogle { get; set; } = false; + + /// + /// Shows the configuration mode in which the extension is running + /// + public EnvironmentSetting EnvironmentSetting => Helper.GetEnvironmentSetting(ForcedConfigurationMode); + } +} diff --git a/EasyGCaptchaMVCCore/EasyGCaptchaMVCCore.csproj b/EasyGCaptchaMVCCore/EasyGCaptchaMVCCore.csproj new file mode 100644 index 0000000..f202ef8 --- /dev/null +++ b/EasyGCaptchaMVCCore/EasyGCaptchaMVCCore.csproj @@ -0,0 +1,13 @@ + + + + netcoreapp1.1 + + + + + + + + + \ No newline at end of file diff --git a/EasyGCaptchaMVCCore/Exceptions/InvalidEnvironmentSetting.cs b/EasyGCaptchaMVCCore/Exceptions/InvalidEnvironmentSetting.cs new file mode 100644 index 0000000..80e3a41 --- /dev/null +++ b/EasyGCaptchaMVCCore/Exceptions/InvalidEnvironmentSetting.cs @@ -0,0 +1,11 @@ +using System; + +namespace EasyGCaptchaMVCCore.Exceptions +{ + class InvalidEnvironmentSetting : Exception + { + public InvalidEnvironmentSetting() { } + public InvalidEnvironmentSetting(string message) : base(message) { } + public InvalidEnvironmentSetting(string message, Exception inner) : base(message, inner) { } + } +} diff --git a/EasyGCaptchaMVCCore/Exceptions/InvalidKeyException.cs b/EasyGCaptchaMVCCore/Exceptions/InvalidKeyException.cs new file mode 100644 index 0000000..6355e8c --- /dev/null +++ b/EasyGCaptchaMVCCore/Exceptions/InvalidKeyException.cs @@ -0,0 +1,11 @@ +using System; + +namespace EasyGCaptchaMVCCore.Exceptions +{ + public class InvalidKeyException : Exception + { + public InvalidKeyException() { } + public InvalidKeyException(string message) : base(message) { } + public InvalidKeyException(string message, Exception inner) : base(message, inner) { } + } +} diff --git a/EasyGCaptchaMVCCore/Exceptions/MissingActionParameterException.cs b/EasyGCaptchaMVCCore/Exceptions/MissingActionParameterException.cs new file mode 100644 index 0000000..41ee1ce --- /dev/null +++ b/EasyGCaptchaMVCCore/Exceptions/MissingActionParameterException.cs @@ -0,0 +1,11 @@ +using System; + +namespace EasyGCaptchaMVCCore.Exceptions +{ + public class MissingActionParameterException : Exception + { + public MissingActionParameterException() { } + public MissingActionParameterException(string message) : base(message) { } + public MissingActionParameterException(string message, Exception inner) : base(message, inner) { } + } +} diff --git a/EasyGCaptchaMVCCore/Model/EasyGCaptchaResult.cs b/EasyGCaptchaMVCCore/Model/EasyGCaptchaResult.cs new file mode 100644 index 0000000..84166b4 --- /dev/null +++ b/EasyGCaptchaMVCCore/Model/EasyGCaptchaResult.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace EasyGCaptchaMVCCore.Model +{ + public class EasyGCaptchaResult + { + /// + /// The master success flag + /// + [JsonProperty(PropertyName = "success")] + public bool Success { get; internal set; } + + /// + /// Date of the challenge load + /// + [JsonProperty(PropertyName = "challenge_ts")] + public DateTime Date { get; internal set; } + + /// + /// The hostname of the site where the reCAPTCHA was solved + /// + [JsonProperty(PropertyName = "hostname")] + public string Hostname { get; internal set; } + + /// + /// Optional error codes. Not tested. + /// + [JsonProperty(PropertyName = "error-codes")] + //[JsonConverter(typeof(StringEnumConverter))] + public List GErrorCodes { get; internal set; } + } +} diff --git a/EasyGCaptchaMVCCore/Model/Enums.cs b/EasyGCaptchaMVCCore/Model/Enums.cs new file mode 100644 index 0000000..c72b037 --- /dev/null +++ b/EasyGCaptchaMVCCore/Model/Enums.cs @@ -0,0 +1,88 @@ +using System.Runtime.Serialization; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace EasyGCaptchaMVCCore.Model +{ + /// + /// The color theme of the widget. Default: Light + /// + public enum Theme + { + Light, + Dark + } + + /// + /// The type of CAPTCHA to serve. Seams unsupported by google. Default: Image + /// + public enum Type + { + Image, + Audio + } + + /// + /// The size of the widget. Default: Normal + /// + public enum Size + { + Normal, + Compact, + Invisible + } + + public enum ForcedConfigurationMode + { + None, + Debug, + Release + } + + /// + /// Error codes from google returned by webrequest + /// + [JsonConverter(typeof(StringEnumConverter))] + public enum GErrorCodes + { + /// + /// The secret parameter is missing. + /// + [EnumMember(Value = "missing-input-secret")] + MissingInputSecret, + + /// + /// The secret parameter is invalid or malformed. + /// + [EnumMember(Value = "invalid-input-secret")] + InvalidInputSecret, + + /// + /// The response parameter is missing. + /// + [EnumMember(Value = "missing-input-response")] + MissingInputResponse, + + /// + /// The response parameter is invalid or malformed. + /// + [EnumMember(Value = "invalid-input-response")] + InvalidInputResponse, + + /// + /// The request is invalid or malformed. + /// + [EnumMember(Value = "bad-request")] + BadRequest + } + + /// + /// Internaly used to get the environment type + /// + public enum EnvironmentSetting + { + Unknown, + Debug, + Release + } +} diff --git a/EasyGCaptchaMVCCore/Worker/EasyGCaptcha.cs b/EasyGCaptchaMVCCore/Worker/EasyGCaptcha.cs new file mode 100644 index 0000000..770957f --- /dev/null +++ b/EasyGCaptchaMVCCore/Worker/EasyGCaptcha.cs @@ -0,0 +1,109 @@ +using System; +using System.IO; +using System.Net; +using System.Text; +using EasyGCaptchaMVCCore.Configuration; +using EasyGCaptchaMVCCore.Exceptions; +using EasyGCaptchaMVCCore.Model; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Newtonsoft.Json; + +namespace EasyGCaptchaMVCCore.Worker +{ + public class EasyGCaptcha : ActionFilterAttribute + { + ///// + ///// The EasyGCaptchaSettings-Object + ///// + //public EasyGCaptchaSettings EasyGCaptchaSettings { get; set; } = null; + public override void OnActionExecuting(ActionExecutingContext filterContext) + { + string userIP = string.Empty; + + if (GCaptchaSettingsProvider.Instance.PassRemoteIpToGoogle) + { + userIP = filterContext.HttpContext.Request.Host.Host; + } + + string postData = string.Format("&secret={0}&response={1}&remoteip={2}", + GCaptchaSettingsProvider.Instance.PrivateKey, + filterContext.HttpContext.Request.Form["g-recaptcha-response"], + userIP); + + byte[] postDataAsBytes = Encoding.UTF8.GetBytes(postData); + + // Create web request + WebRequest request; + Stream dataStream; + try + { + request = WebRequest.Create("https://www.google.com/recaptcha/api/siteverify"); + request.Method = "POST"; + request.ContentType = "application/x-www-form-urlencoded"; + //request.ContentLength = postDataAsBytes.Length; + + dataStream = request.GetRequestStreamAsync().Result; + dataStream.Write(postDataAsBytes, 0, postDataAsBytes.Length); + dataStream.Dispose(); + } + catch (Exception) + { + if (GCaptchaSettingsProvider.Instance.DisableExceptions) + { + ((Controller)filterContext.Controller).ModelState.AddModelError("EasyGCaptchaMVC", "Error on webrequest"); + filterContext.ActionArguments["easyGCaptchaResult"] = new EasyGCaptchaResult(); + return; + } + else + { + throw; + } + } + + // Get the response + string responseFromServer; + try + { + WebResponse response = request.GetResponseAsync().Result; + + using (dataStream = response.GetResponseStream()) + { + using (StreamReader reader = new StreamReader(dataStream)) + { + responseFromServer = reader.ReadToEnd(); + } + } + } + catch (Exception) + { + if (GCaptchaSettingsProvider.Instance.DisableExceptions) + { + ((Controller)filterContext.Controller).ModelState.AddModelError("EasyGCaptchaMVC", "Error on parsing webrequest"); + filterContext.ActionArguments["easyGCaptchaResult"] = new EasyGCaptchaResult(); + return; + } + else + { + throw; + } + } + + EasyGCaptchaResult result = JsonConvert.DeserializeObject(responseFromServer); + + if (filterContext.ActionArguments.ContainsKey("easyGCaptchaResult")) + { + filterContext.ActionArguments["easyGCaptchaResult"] = result; + } + else + { + throw new MissingActionParameterException("Action have to contan a parameter of type EasyGCaptchaResult with name easyGCaptchaResult"); + } + + if (!result.Success) + { + ((Controller)filterContext.Controller).ModelState.AddModelError("EasyGCaptchaMVC", "Captcha incorrect"); + } + } + } +} diff --git a/EasyGCaptchaMVCCore/Worker/Extensions.cs b/EasyGCaptchaMVCCore/Worker/Extensions.cs new file mode 100644 index 0000000..db9082b --- /dev/null +++ b/EasyGCaptchaMVCCore/Worker/Extensions.cs @@ -0,0 +1,113 @@ +using System; +using System.Text; +using EasyGCaptchaMVCCore.Configuration; +using EasyGCaptchaMVCCore.Model; +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewFeatures; + +namespace EasyGCaptchaMVCCore.Worker +{ + public static class Extensions + { + + /// + /// Renders the Google ReCaptcha with the single EasyGCaptcha settings + /// + /// MVC Extension + /// Outputs raw html to the cshtml-Page + public static IHtmlContent EasyGCaptchaGenerateCaptcha(this IHtmlHelper helper) + { + string tab = string.Empty; + string call = string.Empty; + + if (GCaptchaSettingsProvider.Instance.Tabindex > 0) + { + tab = ", 'tabindex' : '" + GCaptchaSettingsProvider.Instance.Tabindex + "'"; + } + + if (!string.IsNullOrEmpty(GCaptchaSettingsProvider.Instance.CallBack)) + { + call = ", 'callback' : '" + GCaptchaSettingsProvider.Instance.CallBack + "'"; + } + + StringBuilder templateBuilder = new StringBuilder(); + templateBuilder.Append(Environment.NewLine); + templateBuilder.Append(""); + templateBuilder.Append(Environment.NewLine); + + if (GCaptchaSettingsProvider.Instance.Size == Size.Invisible) + { + // Render Invisible with auto exec + // ################################################## + templateBuilder.Append("
"); + templateBuilder.Append("
"); + /* + 0 = sitekey + 1 = theme + 2 = size + 3 = type + 4 = tabindex + 5 = callback + */ + templateBuilder.Append(""); + templateBuilder.Append(Environment.NewLine); + } + else + { + // Render default visible + // ################################################## + templateBuilder.Append("
"); + templateBuilder.Append(Environment.NewLine); + templateBuilder.Append(""); + templateBuilder.Append(Environment.NewLine); + } + + templateBuilder.Append(""); + templateBuilder.Append(Environment.NewLine); + templateBuilder.Append(""); + templateBuilder.Append(Environment.NewLine); + + string html = string.Format(templateBuilder.ToString(), + GCaptchaSettingsProvider.Instance.PublicKey, + GCaptchaSettingsProvider.Instance.Theme.ToString().ToLower(), + GCaptchaSettingsProvider.Instance.Size.ToString().ToLower(), + GCaptchaSettingsProvider.Instance.Type.ToString().ToLower(), + tab, call); + + return new HtmlString(html); + } + } +} diff --git a/EasyGCaptchaMVCCore/Worker/Helper.cs b/EasyGCaptchaMVCCore/Worker/Helper.cs new file mode 100644 index 0000000..e3ddc9e --- /dev/null +++ b/EasyGCaptchaMVCCore/Worker/Helper.cs @@ -0,0 +1,62 @@ +using System; +using System.Diagnostics; +using EasyGCaptchaMVCCore.Configuration; +using EasyGCaptchaMVCCore.Model; + +namespace EasyGCaptchaMVCCore.Worker +{ + internal static class Helper + { + + internal static EnvironmentSetting GetEnvironmentSetting(ForcedConfigurationMode forcedConfigurationMode) + { + EnvironmentSetting environmentSetting = EnvironmentSetting.Release; + + if (forcedConfigurationMode == ForcedConfigurationMode.Debug) + { + environmentSetting = EnvironmentSetting.Debug; + } + else if (forcedConfigurationMode == ForcedConfigurationMode.Release) + { + environmentSetting = EnvironmentSetting.Release; + } + else if (Debugger.IsAttached) + { + environmentSetting = EnvironmentSetting.Debug; + } + + return environmentSetting; + } + + internal static string GetGPublicKey() + { + string key = String.Empty; + + if (GCaptchaSettingsProvider.Instance.EnvironmentSetting == EnvironmentSetting.Debug && GCaptchaSettingsProvider.Instance.UsePassthruInDebugMode) + { + key = "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI"; + } + //else if (ConfigurationManager.AppSettings.AllKeys.Contains("EasyGCaptchaMVC.PublicKey") && !string.IsNullOrEmpty(ConfigurationManager.AppSettings["EasyGCaptchaMVC.PublicKey"])) + //{ + // key = ConfigurationManager.AppSettings["EasyGCaptchaMVC.PublicKey"]; + //} + + return key; + } + internal static string GetGPrivateKey() + { + string key = String.Empty; + + if (GCaptchaSettingsProvider.Instance.EnvironmentSetting == EnvironmentSetting.Debug && GCaptchaSettingsProvider.Instance.UsePassthruInDebugMode) + { + key = "6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe"; + } + //else if (ConfigurationManager.AppSettings.AllKeys.Contains("EasyGCaptchaMVC.PrivateKey") && !string.IsNullOrEmpty(ConfigurationManager.AppSettings["EasyGCaptchaMVC.PrivateKey"])) + //{ + // key = ConfigurationManager.AppSettings["EasyGCaptchaMVC.PrivateKey"]; + //} + + return key; + } + } +} diff --git a/README.md b/README.md index f172ee4..76840f8 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,109 @@ My try to make it easy to implement the google ReCaptcha service for a MVC Websi --------------- -##Documentation +##Steps to implement -The full documatation can be found here: https://confluence.stefmde.com/display/RCAPTCHA + 1. Get the api key pair from google: https://www.google.com/recaptcha + 2. Check the sample/demo project on [GitHub](https://github.com/stefmde/EasyGCaptchaMVC "GitHub - EasyGCaptchaMVC") + 3. Download and install the Package from [nuget](https://www.nuget.org/packages/EasyGCaptchaMVC/ "nuget - EasyGCaptchaMVC") + 4. Implementing the HTML-Extension `EasyGCaptchaGenerateCaptcha` in the view + 5. Implementing the `ActionFilterAttribute` above the Action + 6. Check the given `EasyGCaptchaResult` in the Action + +--------------- + +##Quickstart +###Html-Extension +Calling the `Html`-Extension `EasyGCaptchaGenerateCaptcha` and pass your publickey/websitekey to it is all you have to do in the view. You kann pass a few more settings like the `Theme` mode to it. + + @Html.EasyGCaptchaGenerateCaptcha(publicKey: "xyzxyzxyz...xyzxyzxyz", theme: Theme.Dark) + +###ActionFilterAttribute and the result +Place the `EasyGCaptcha`-Attribute above your `Post`-Action and pass the privatekey to it. +If you have done this you have to add the `EasyGCaptchaResult easyGCaptchaResult`-Parameter to your Action. In this parameter you can check the results from Google. + + [HttpPost] + [EasyGCaptcha(PrivateKey ="xyzxyzxyz...xyzxyzxyz")] + public ActionResult Index(IndexViewModel model, EasyGCaptchaResult easyGCaptchaResult) + { + if (easyGCaptchaResult.Success) + { + // Do your work here + } + model.EasyGCaptchaResult = easyGCaptchaResult; + return View(model); + } + +--------------- + +##Some notes + + - This extension automatically uses some development/testing keys from google if the app is in developement or no keys are provided + - The Extension and the Attribute provides some additional parameters to customize it + +##Supported parameters +###Extension + - string `publicKey` + - Default: Dev-Key from Google + - Description: Contains the public or website key, provides by Google + - string `divID` + - Default: `EasyGCaptchaMVC_div` + - Description: Contains the id for the div which contains the module. Used for styling and things like that + - Theme `theme` + - Default: `Theme.Light` + - Description: Contains the theme setting, provided by Google + - Size `size` + - Default: `Size.Normal` + - Description: Contains the size setting, provided by Google + - Type `type` + - Default: `Type.Image` + - Description: Contains the validation type setting, provided by Google + - int `tabindex` + - Default: `-1` + - Description: Contains the tabindex for the form, provided by Google + - string `callBack` + - Default: empty + - Description: Provides the ability to call a js function if the module is fully loaded, provided by Google + - bool `forceDebugMode` + - Default: `false` + - Description: Forces the debug mode. Should be false on prduction + - bool `forceReleaseMode` + - Default: `false` + - Description: Forces release mode. For testing only + - bool `usePassthruInDebugMode` + - Default: `true` + - Description: Uses special keys, provided by Google, to show the module but always passthru it. Only for testing. + - bool `showErrorMessagesOnDebug` + - Default: `true` + - Description: Can be enabled to show error messages from the extension on the website if the site is in debug mode + - bool `disableExceptions` + - Default: `false` + - Description: Prevents the module from throwing exceptions. But can hide errors if 'ShowErrorMessageOnDebug' is false + + +###Attribute +- string `PrivateKey` + - Default: Dev-Key from Google + - Description: Contains the private key, provides by Google +- bool `DisableExceptions` + - Default: `false` + - Description: Prevents the module from throwing exceptions. But can hide errors if 'ShowErrorMessageOnDebug' is false +- bool `ForceDebugMode` + - Default: `false` + - Description: Forces the debug mode. Should be false on prduction. +- bool `ForceReleaseMode` + - Default: `false` + - Description: Forces release mode. For testing only. +- bool `UsePassthruInDebugMode` + - Default: `true` + - Description: Uses special keys, provided by Google, to show the module but always passthru it. Only for testing. +- bool `PassRemoteIPToGoogle` + - Default: `false` + - Description: Passes the ip of the client ip to Google. Don't know for what + + +###Web.config +If you want to, you can add those two keys in your Web.config. If you do that, there is no more need to pass those keys to the extension or the Attribute: + + +