diff options
| -rw-r--r-- | .gitignore | 448 | ||||
| -rw-r--r-- | LICENSE | 24 | ||||
| -rw-r--r-- | LICENSE_NOTICES | 31 | ||||
| -rw-r--r-- | README.md | 0 | ||||
| -rw-r--r-- | src/IOL.Helpers.sln | 34 | ||||
| -rw-r--r-- | src/IOL.Helpers/CryptographyHelpers.cs | 173 | ||||
| -rw-r--r-- | src/IOL.Helpers/DateTimeHelpers.cs | 22 | ||||
| -rw-r--r-- | src/IOL.Helpers/DoubleHelpers.cs | 11 | ||||
| -rw-r--r-- | src/IOL.Helpers/EnumHelpers.cs | 13 | ||||
| -rw-r--r-- | src/IOL.Helpers/HttpRequestHelpers.cs | 18 | ||||
| -rw-r--r-- | src/IOL.Helpers/IOL.Helpers.csproj | 12 | ||||
| -rw-r--r-- | src/IOL.Helpers/PasswordHelpers.cs | 70 | ||||
| -rw-r--r-- | src/IOL.Helpers/RandomStringGenerator.cs | 18 | ||||
| -rw-r--r-- | src/IOL.Helpers/SlugGenerator.cs | 108 | ||||
| -rw-r--r-- | src/IOL.Helpers/StringHelpers.cs | 67 | ||||
| -rw-r--r-- | src/IOL.Helpers/Validators.cs | 18 |
16 files changed, 1067 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..de03f94 --- /dev/null +++ b/.gitignore @@ -0,0 +1,448 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# Ionide - VsCode extension for F# Support +.ionide/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# JetBrains Rider +.idea/ +*.sln.iml + +## +## Visual Studio Code +## +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +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 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. + +For more information, please refer to <https://unlicense.org> diff --git a/LICENSE_NOTICES b/LICENSE_NOTICES new file mode 100644 index 0000000..c37df05 --- /dev/null +++ b/LICENSE_NOTICES @@ -0,0 +1,31 @@ +IOL.VippsEcommerce uses third-party libraries or other resources that may +be distributed under licenses different than the IOL.VippsEcommerce software. + +In the event that we accidentally failed to list a required notice, +please bring it to our attention through any of the ways detailed here: + + https://github.com/ivarlovlie/IOL.VippsEcommerce/issues + +The attached notices are provided for information only. + +For any licenses that require disclosure of source, sources are available at +https://github.com/ivarlovlie/IOL.VippsEcommerce/. + +License Notice for Microsoft.NET.Sdk +--------------------------- +https://github.com/dotnet/runtime/blob/main/LICENSE.TXT + + +License Notice for Microsoft.Extensions.DependencyInjection +--------------------------- +https://github.com/dotnet/runtime/blob/main/LICENSE.TXT + + +License Notice for Microsoft.Extensions.Http +--------------------------- +https://github.com/dotnet/runtime/blob/main/LICENSE.TXT + + +License Notice for Microsoft.Extensions.Logging +--------------------------- +https://github.com/dotnet/runtime/blob/main/LICENSE.TXT diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/README.md diff --git a/src/IOL.Helpers.sln b/src/IOL.Helpers.sln new file mode 100644 index 0000000..b166d6e --- /dev/null +++ b/src/IOL.Helpers.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.6.30114.105 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IOL.Helpers", "IOL.Helpers\IOL.Helpers.csproj", "{DE4D43B0-FBE6-40B7-9C8F-FFFA7987F87B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DE4D43B0-FBE6-40B7-9C8F-FFFA7987F87B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE4D43B0-FBE6-40B7-9C8F-FFFA7987F87B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE4D43B0-FBE6-40B7-9C8F-FFFA7987F87B}.Debug|x64.ActiveCfg = Debug|Any CPU + {DE4D43B0-FBE6-40B7-9C8F-FFFA7987F87B}.Debug|x64.Build.0 = Debug|Any CPU + {DE4D43B0-FBE6-40B7-9C8F-FFFA7987F87B}.Debug|x86.ActiveCfg = Debug|Any CPU + {DE4D43B0-FBE6-40B7-9C8F-FFFA7987F87B}.Debug|x86.Build.0 = Debug|Any CPU + {DE4D43B0-FBE6-40B7-9C8F-FFFA7987F87B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE4D43B0-FBE6-40B7-9C8F-FFFA7987F87B}.Release|Any CPU.Build.0 = Release|Any CPU + {DE4D43B0-FBE6-40B7-9C8F-FFFA7987F87B}.Release|x64.ActiveCfg = Release|Any CPU + {DE4D43B0-FBE6-40B7-9C8F-FFFA7987F87B}.Release|x64.Build.0 = Release|Any CPU + {DE4D43B0-FBE6-40B7-9C8F-FFFA7987F87B}.Release|x86.ActiveCfg = Release|Any CPU + {DE4D43B0-FBE6-40B7-9C8F-FFFA7987F87B}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/src/IOL.Helpers/CryptographyHelpers.cs b/src/IOL.Helpers/CryptographyHelpers.cs new file mode 100644 index 0000000..6ea18b6 --- /dev/null +++ b/src/IOL.Helpers/CryptographyHelpers.cs @@ -0,0 +1,173 @@ +using System; +using System.Linq; +using System.Security.Cryptography; +using System.Text; + +namespace IOL.Helpers +{ + public static class CryptographyHelpers + { + // https://github.com/DuendeSoftware/IdentityServer/blob/main/src/IdentityServer/Extensions/HashExtensions.cs + + private const int AES_BLOCK_BYTE_SIZE = 128 / 8; + private static readonly RandomNumberGenerator _random = RandomNumberGenerator.Create(); + + /// <summary> + /// Creates a MD5 hash of the specified input. + /// </summary> + /// <returns>A hash</returns> + public static string Md5(this string input, string salt = default) { + if (input.IsNullOrWhiteSpace()) return string.Empty; + + var hmacMd5 = salt.HasValue() ? new HMACMD5(Encoding.UTF8.GetBytes(salt ?? "")) : new HMACMD5(); + var saltedHash = hmacMd5.ComputeHash(Encoding.UTF8.GetBytes(input)); + return Convert.ToBase64String(saltedHash); + } + + + /// <summary> + /// Method to perform a very simple (and classical) encryption for a string. This is NOT at + /// all secure, it is only intended to make the string value non-obvious at a first glance. + /// + /// The shiftOrUnshift argument is an arbitrary "key value", and must be a non-zero integer + /// between -65535 and 65535 (inclusive). To decrypt the encrypted string you use the negative + /// value. For example, if you encrypt with -42, then you decrypt with +42, or vice-versa. + /// + /// This is inspired by, and largely based on, this: + /// https://stackoverflow.com/a/13026595/253938 + /// </summary> + /// <param name="inputString">string to be encrypted or decrypted, must not be null</param> + /// <param name="shiftOrUnshift">see above</param> + /// <returns>encrypted or decrypted string</returns> + public static string CaesarCipher(string inputString, int shiftOrUnshift) { + const int C64_K = ushort.MaxValue + 1; + if (inputString == null) throw new ArgumentException("Must not be null.", nameof(inputString)); + switch (shiftOrUnshift) { + case 0: throw new ArgumentException("Must not be zero.", nameof(shiftOrUnshift)); + case <= -C64_K: + case >= C64_K: throw new ArgumentException("Out of range.", nameof(shiftOrUnshift)); + } + + // Perform the Caesar cipher shifting, using modulo operator to provide wrap-around + var charArray = new char[inputString.Length]; + for (var i = 0; i < inputString.Length; i++) { + charArray[i] = + Convert.ToChar((Convert.ToInt32(inputString[i]) + shiftOrUnshift + C64_K) % C64_K); + } + + return new string(charArray); + } + + //https://tomrucki.com/posts/aes-encryption-in-csharp/ + public static string EncryptWithAes(this string toEncrypt, string password) { + var key = GetKey(password); + + using var aes = CreateAes(); + var iv = GenerateRandomBytes(AES_BLOCK_BYTE_SIZE); + var plainText = Encoding.UTF8.GetBytes(toEncrypt); + + using var encryptor = aes.CreateEncryptor(key, iv); + var cipherText = encryptor + .TransformFinalBlock(plainText, 0, plainText.Length); + + var result = new byte[iv.Length + cipherText.Length]; + iv.CopyTo(result, 0); + cipherText.CopyTo(result, iv.Length); + + return Convert.ToBase64String(result); + } + + private static Aes CreateAes() { + var aes = Aes.Create(); + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.PKCS7; + return aes; + } + + public static string DecryptWithAes(this string input, string password) { + var key = GetKey(password); + var encryptedData = Convert.FromBase64String(input); + + using var aes = CreateAes(); + var iv = encryptedData.Take(AES_BLOCK_BYTE_SIZE).ToArray(); + var cipherText = encryptedData.Skip(AES_BLOCK_BYTE_SIZE).ToArray(); + + using var decryptor = aes.CreateDecryptor(key, iv); + var decryptedBytes = decryptor + .TransformFinalBlock(cipherText, 0, cipherText.Length); + return Encoding.UTF8.GetString(decryptedBytes); + } + + private static byte[] GetKey(string password) { + var keyBytes = Encoding.UTF8.GetBytes(password); + using var md5 = MD5.Create(); + return md5.ComputeHash(keyBytes); + } + + private static byte[] GenerateRandomBytes(int numberOfBytes) { + var randomBytes = new byte[numberOfBytes]; + _random.GetBytes(randomBytes); + return randomBytes; + } + + + /// <summary> + /// Creates a SHA256 hash of the specified input. + /// </summary> + /// <param name="input">The input.</param> + /// <returns>A hash</returns> + public static string Sha256(this string input) { + if (input.IsNullOrWhiteSpace()) return string.Empty; + + using var sha = SHA256.Create(); + var bytes = Encoding.UTF8.GetBytes(input); + var hash = sha.ComputeHash(bytes); + + return Convert.ToBase64String(hash); + } + + /// <summary> + /// Creates a SHA256 hash of the specified input. + /// </summary> + /// <param name="input">The input.</param> + /// <returns>A hash.</returns> + public static byte[] Sha256(this byte[] input) { + if (input == null) { + return null; + } + + using var sha = SHA256.Create(); + return sha.ComputeHash(input); + } + + /// <summary> + /// Creates a SHA512 hash of the specified input. + /// </summary> + /// <param name="input">The input.</param> + /// <returns>A hash</returns> + public static string Sha512(this string input) { + if (input.IsNullOrWhiteSpace()) return string.Empty; + + using var sha = SHA512.Create(); + var bytes = Encoding.UTF8.GetBytes(input); + var hash = sha.ComputeHash(bytes); + + return Convert.ToBase64String(hash); + } + + + /// <summary> + /// Creates a SHA256 hash of the specified input. + /// </summary> + /// <param name="input">The input.</param> + /// <returns>A hash.</returns> + public static byte[] Sha512(this byte[] input) { + if (input == null) { + return null; + } + + using var sha = SHA512.Create(); + return sha.ComputeHash(input); + } + } +}
\ No newline at end of file diff --git a/src/IOL.Helpers/DateTimeHelpers.cs b/src/IOL.Helpers/DateTimeHelpers.cs new file mode 100644 index 0000000..07e951e --- /dev/null +++ b/src/IOL.Helpers/DateTimeHelpers.cs @@ -0,0 +1,22 @@ +using System; + +namespace IOL.Helpers +{ + public static class DateTimeHelpers + { + public static DateTime ToTimeZoneId(this DateTime value, string timeZoneId) { + try { + var cstZone = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId); + return TimeZoneInfo.ConvertTimeFromUtc(value, cstZone); + } catch (TimeZoneNotFoundException) { + Console.WriteLine("The registry does not define the " + timeZoneId + " zone."); + return default; + } catch (InvalidTimeZoneException) { + Console.WriteLine("Registry data on the " + timeZoneId + " zone has been corrupted."); + return default; + } + } + + public static DateTime ToOsloTimeZone(this DateTime value) => ToTimeZoneId(value, "Europe/Oslo"); + } +}
\ No newline at end of file diff --git a/src/IOL.Helpers/DoubleHelpers.cs b/src/IOL.Helpers/DoubleHelpers.cs new file mode 100644 index 0000000..91d88ee --- /dev/null +++ b/src/IOL.Helpers/DoubleHelpers.cs @@ -0,0 +1,11 @@ +using System; + +namespace IOL.Helpers +{ + public static class DoubleHelpers + { + public static string ToStringWithFixedDecimalPoints(this double value) { + return $"{Math.Truncate(value * 10) / 10:0.0}"; + } + } +}
\ No newline at end of file diff --git a/src/IOL.Helpers/EnumHelpers.cs b/src/IOL.Helpers/EnumHelpers.cs new file mode 100644 index 0000000..b63e045 --- /dev/null +++ b/src/IOL.Helpers/EnumHelpers.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace IOL.Helpers +{ + public static class EnumHelpers + { + public static IEnumerable<T> GetValues<T>() { + return Enum.GetValues(typeof(T)).Cast<T>(); + } + } +}
\ No newline at end of file diff --git a/src/IOL.Helpers/HttpRequestHelpers.cs b/src/IOL.Helpers/HttpRequestHelpers.cs new file mode 100644 index 0000000..5a0e145 --- /dev/null +++ b/src/IOL.Helpers/HttpRequestHelpers.cs @@ -0,0 +1,18 @@ +using System; +using Microsoft.AspNetCore.Http; + +namespace IOL.Helpers +{ + public static class HttpRequestHelpers + { + public static string GetAppHost(this HttpRequest request) { + var forwardedHostHeader = request.Headers["X-Forwarded-Host"].ToString(); + var forwardedProtoHeader = request.Headers["X-Forwarded-Proto"].ToString(); + if (forwardedHostHeader.HasValue()) { + return (forwardedProtoHeader ?? "https") + "://" + forwardedHostHeader; + } + + return request.Scheme + "://" + request.Host; + } + } +}
\ No newline at end of file diff --git a/src/IOL.Helpers/IOL.Helpers.csproj b/src/IOL.Helpers/IOL.Helpers.csproj new file mode 100644 index 0000000..2b2842a --- /dev/null +++ b/src/IOL.Helpers/IOL.Helpers.csproj @@ -0,0 +1,12 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net5.0</TargetFramework> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="5.0.4" /> + <PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" /> + </ItemGroup> + +</Project> diff --git a/src/IOL.Helpers/PasswordHelpers.cs b/src/IOL.Helpers/PasswordHelpers.cs new file mode 100644 index 0000000..5b85219 --- /dev/null +++ b/src/IOL.Helpers/PasswordHelpers.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using Microsoft.AspNetCore.Cryptography.KeyDerivation; + +namespace IOL.Helpers +{ + public static class PasswordHelper + { + private const int ITERATION_COUNT = 10000; + private const int SALT_SIZE = 128 / 8; + private const KeyDerivationPrf PRF = KeyDerivationPrf.HMACSHA256; + + public static string HashPassword(string value) { + using var rng = RandomNumberGenerator.Create(); + var salt = new byte[SALT_SIZE]; + rng.GetBytes(salt); + var subkey = KeyDerivation.Pbkdf2(value, salt, PRF, ITERATION_COUNT, 256 / 8); + var outputBytes = new byte[13 + salt.Length + subkey.Length]; + WriteNetworkByteOrder(outputBytes, 1, (uint) PRF); + WriteNetworkByteOrder(outputBytes, 5, (uint) ITERATION_COUNT); + WriteNetworkByteOrder(outputBytes, 9, (uint) SALT_SIZE); + Buffer.BlockCopy(salt, 0, outputBytes, 13, salt.Length); + Buffer.BlockCopy(subkey, 0, outputBytes, 13 + SALT_SIZE, subkey.Length); + return Convert.ToBase64String(outputBytes); + } + + public static bool Verify(string password, string hashedPassword) { + var decodedHashedPassword = Convert.FromBase64String(hashedPassword); + if (decodedHashedPassword.Length == 0) return false; + try { + // Read header information + var networkByteOrder = (KeyDerivationPrf) ReadNetworkByteOrder(decodedHashedPassword, 1); + var saltLength = (int) ReadNetworkByteOrder(decodedHashedPassword, 9); + + // Read the salt: must be >= 128 bits + if (saltLength < SALT_SIZE) return false; + var salt = new byte[saltLength]; + Buffer.BlockCopy(decodedHashedPassword, 13, salt, 0, salt.Length); + + // Read the subkey (the rest of the payload): must be >= 128 bits + var subkeyLength = decodedHashedPassword.Length - 13 - salt.Length; + if (subkeyLength < SALT_SIZE) return false; + var expectedSubkey = new byte[subkeyLength]; + Buffer.BlockCopy(decodedHashedPassword, 13 + salt.Length, expectedSubkey, 0, expectedSubkey.Length); + + // Hash the incoming password and verify it + var actualSubkey = + KeyDerivation.Pbkdf2(password, salt, networkByteOrder, ITERATION_COUNT, subkeyLength); + return CryptographicOperations.FixedTimeEquals(actualSubkey, expectedSubkey); + } catch { + return false; + } + } + + private static uint ReadNetworkByteOrder(IReadOnlyList<byte> buffer, int offset) { + return ((uint) buffer[offset + 0] << 24) + | ((uint) buffer[offset + 1] << 16) + | ((uint) buffer[offset + 2] << 8) + | buffer[offset + 3]; + } + + private static void WriteNetworkByteOrder(IList<byte> buffer, int offset, uint value) { + buffer[offset + 0] = (byte) (value >> 24); + buffer[offset + 1] = (byte) (value >> 16); + buffer[offset + 2] = (byte) (value >> 8); + buffer[offset + 3] = (byte) (value >> 0); + } + } +}
\ No newline at end of file diff --git a/src/IOL.Helpers/RandomStringGenerator.cs b/src/IOL.Helpers/RandomStringGenerator.cs new file mode 100644 index 0000000..0d691db --- /dev/null +++ b/src/IOL.Helpers/RandomStringGenerator.cs @@ -0,0 +1,18 @@ +using System; +using System.Linq; + +namespace IOL.Helpers +{ + public static class RandomString + { + private static readonly Random _random = new Random(); + + public static string Generate(int length, bool numeric = false) { + var chars = numeric switch { + false => "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", + true => "0123456789" + }; + return new string(Enumerable.Repeat(chars, length).Select(s => s[_random.Next(s.Length)]).ToArray()); + } + } +}
\ No newline at end of file diff --git a/src/IOL.Helpers/SlugGenerator.cs b/src/IOL.Helpers/SlugGenerator.cs new file mode 100644 index 0000000..3f53dd6 --- /dev/null +++ b/src/IOL.Helpers/SlugGenerator.cs @@ -0,0 +1,108 @@ +using System.Text; + +namespace IOL.Helpers +{ + public static class Slug + { + public static string Generate(bool toLower, params string[] values) { + return Create(string.Join("-", values), toLower); + } + + /// <summary> + /// Creates a slug. + /// References: + /// http://www.unicode.org/reports/tr15/tr15-34.html + /// https://meta.stackexchange.com/questions/7435/non-us-ascii-characters-dropped-from-full-profile-url/7696#7696 + /// https://stackoverflow.com/questions/25259/how-do-you-include-a-webpage-title-as-part-of-a-webpage-url/25486#25486 + /// https://stackoverflow.com/questions/3769457/how-can-i-remove-accents-on-a-string + /// </summary> + /// <param name="value"></param> + /// <param name="toLower"></param> + /// <returns>Slugified string</returns> + public static string Create(string value, bool toLower) { + if (string.IsNullOrWhiteSpace(value)) + return value; + + var normalised = value.Normalize(NormalizationForm.FormKD); + + const int MAXLEN = 80; + var len = normalised.Length; + var prevDash = false; + var sb = new StringBuilder(len); + + for (var i = 0; i < len; i++) { + var c = normalised[i]; + switch (c) { + case >= 'a' and <= 'z': + case >= '0' and <= '9': { + if (prevDash) { + sb.Append('-'); + prevDash = false; + } + + sb.Append(c); + break; + } + case >= 'A' and <= 'Z': { + if (prevDash) { + sb.Append('-'); + prevDash = false; + } + + // Tricky way to convert to lowercase + if (toLower) + sb.Append((char) (c | 32)); + else + sb.Append(c); + break; + } + case ' ': + case ',': + case '.': + case '/': + case '\\': + case '-': + case '_': + case '=': { + if (!prevDash && sb.Length > 0) { + prevDash = true; + } + + break; + } + default: { + var swap = ConvertEdgeCases(c, toLower); + if (swap != null) { + if (prevDash) { + sb.Append('-'); + prevDash = false; + } + + sb.Append(swap); + } + + break; + } + } + + if (sb.Length == MAXLEN) + break; + } + + return sb.ToString(); + } + + private static string ConvertEdgeCases(char c, bool toLower) => c switch { + 'ı' => "i", + 'ł' => "l", + 'Ł' => toLower ? "l" : "L", + 'đ' => "d", + 'ß' => "ss", + 'ø' => "o", + 'å' => "aa", + 'æ' => "ae", + 'Þ' => "th", + _ => null + }; + } +}
\ No newline at end of file diff --git a/src/IOL.Helpers/StringHelpers.cs b/src/IOL.Helpers/StringHelpers.cs new file mode 100644 index 0000000..1dde161 --- /dev/null +++ b/src/IOL.Helpers/StringHelpers.cs @@ -0,0 +1,67 @@ +using System; +using System.Text; +using System.Text.RegularExpressions; + +namespace IOL.Helpers +{ + public static class StringHelpers + { + public static bool IsNullOrWhiteSpace(this string value) { + return string.IsNullOrWhiteSpace(value); + } + + public static string Slugified(this string input) { + return Slug.Generate(true, input); + } + + public static bool HasValue(this string value) { + return !value.IsNullOrWhiteSpace(); + } + + public static Guid ToGuid(this string value) { + return Guid.Parse(value); + } + + public static string Base64Encode(this string text) { + return Convert.ToBase64String(Encoding.UTF8.GetBytes(text)); + } + + public static string ExtractFileName(this string value) { + if (value.IsNullOrWhiteSpace()) return default; + var lastIndex = value.LastIndexOf('.'); + return lastIndex <= 0 ? default : value.Substring(0, lastIndex); + } + + public static string ExtractExtension(this string value) { + if (value.IsNullOrWhiteSpace()) return default; + var lastIndex = value.LastIndexOf('.'); + return lastIndex <= 0 ? default : value.Substring(lastIndex); + } + + public static string Capitalize(this string input) { + return input.IsNullOrWhiteSpace() + ? input + : Regex.Replace(input, @"\b(\w)", m => m.Value.ToUpper(), RegexOptions.None); + } + + + /// <summary> + /// Check if the given MIME is a JSON MIME. + /// </summary> + /// <param name="mime">MIME</param> + /// <returns>Returns true if MIME type is json.</returns> + public static bool IsJsonMime(this string mime) { + var jsonRegex = new Regex("(?i)^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$"); + return mime != null && (jsonRegex.IsMatch(mime) || mime.Equals("application/json-patch+json")); + } + + public static string Obfuscate(this string value) { + var last4Chars = "****"; + if (value.HasValue() && value.Length > 4) { + last4Chars = value.Substring(value.Length - 4); + } + + return "****" + last4Chars; + } + } +}
\ No newline at end of file diff --git a/src/IOL.Helpers/Validators.cs b/src/IOL.Helpers/Validators.cs new file mode 100644 index 0000000..d60afcf --- /dev/null +++ b/src/IOL.Helpers/Validators.cs @@ -0,0 +1,18 @@ +using System.Net.Mail; +using System.Text.RegularExpressions; + +namespace IOL.Helpers +{ + public static class Validators + { + private static readonly Regex _norwegianPhoneNumber = new(@"^(0047|\+47|47)?[2-9]\d{7}$"); + + public static bool IsValidEmailAddress(this string value) { + return MailAddress.TryCreate(value, out _); + } + + public static bool IsValidNorwegianPhoneNumber(this string value) { + return _norwegianPhoneNumber.IsMatch(value); + } + } +}
\ No newline at end of file |
