From 6485b3dd4e6ea16d1cb82441d394dd62403aeb34 Mon Sep 17 00:00:00 2001 From: Hugo Hedlund Date: Sun, 11 Oct 2020 18:37:19 +0200 Subject: [PATCH 1/7] Check if the generated url already exists. --- src/shortenerTools/UrlShortener/UrlShortener.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/shortenerTools/UrlShortener/UrlShortener.cs b/src/shortenerTools/UrlShortener/UrlShortener.cs index 93a312f6..647c497a 100644 --- a/src/shortenerTools/UrlShortener/UrlShortener.cs +++ b/src/shortenerTools/UrlShortener/UrlShortener.cs @@ -90,6 +90,10 @@ public static async Task Run( else { newRow = new ShortUrlEntity(longUrl, await Utility.GetValidEndUrl(vanity, stgHelper), title); + if (await stgHelper.IfShortUrlEntityExist(newRow)) + { + return req.CreateResponse(HttpStatusCode.Conflict, "This Short URL already exist."); + } } await stgHelper.SaveShortUrlEntity(newRow); From 15d0a50b55c9defc90f791c905b96df01284556b Mon Sep 17 00:00:00 2001 From: Hugo Hedlund Date: Sun, 11 Oct 2020 21:59:39 +0200 Subject: [PATCH 2/7] Added a check in Utility.cs, GetValidEndUrl, to see if the encoded vanity already exists. --- .../Domain/StorageTableHelper.cs | 28 +++++++++++++++++++ src/shortenerTools/Domain/Utility.cs | 5 +++- .../UrlShortener/UrlShortener.cs | 4 --- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/shortenerTools/Domain/StorageTableHelper.cs b/src/shortenerTools/Domain/StorageTableHelper.cs index 96e9f73d..938f744b 100644 --- a/src/shortenerTools/Domain/StorageTableHelper.cs +++ b/src/shortenerTools/Domain/StorageTableHelper.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Table; @@ -85,6 +87,32 @@ public async Task> GetAllStatsByVanity(string vanity) return lstShortUrl; } + /// + /// Returns the ShortUrlEntity of the + /// + /// + /// ShortUrlEntity + public async Task GetShortUrlEntityByVanity(string vanity) + { + var tblUrls = GetUrlsTable(); + TableContinuationToken token = null; + ShortUrlEntity shortUrlEntity = null; + do + { + TableQuery query = new TableQuery().Where( + filter: TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.Equal, vanity)); + var queryResult = await tblUrls.ExecuteQuerySegmentedAsync(query, token); + shortUrlEntity = queryResult.Results.FirstOrDefault(); + } while (token != null); + + return shortUrlEntity; + } + + public async Task IfShortUrlEntityExistByVanity(string vanity) + { + ShortUrlEntity shortUrlEntity = await GetShortUrlEntityByVanity(vanity); + return (shortUrlEntity != null); + } public async Task IfShortUrlEntityExist(ShortUrlEntity row) { diff --git a/src/shortenerTools/Domain/Utility.cs b/src/shortenerTools/Domain/Utility.cs index 2037cc6e..95f0fc47 100644 --- a/src/shortenerTools/Domain/Utility.cs +++ b/src/shortenerTools/Domain/Utility.cs @@ -13,7 +13,10 @@ public static async Task GetValidEndUrl(string vanity, StorageTableHelpe if(string.IsNullOrEmpty(vanity)) { var newKey = await stgHelper.GetNextTableId(); - string getCode() => Encode(newKey); + string getCode() => Encode(newKey); + if (await stgHelper.IfShortUrlEntityExistByVanity(getCode())) + return await GetValidEndUrl(vanity, stgHelper); + return string.Join(string.Empty, getCode()); } else diff --git a/src/shortenerTools/UrlShortener/UrlShortener.cs b/src/shortenerTools/UrlShortener/UrlShortener.cs index 647c497a..93a312f6 100644 --- a/src/shortenerTools/UrlShortener/UrlShortener.cs +++ b/src/shortenerTools/UrlShortener/UrlShortener.cs @@ -90,10 +90,6 @@ public static async Task Run( else { newRow = new ShortUrlEntity(longUrl, await Utility.GetValidEndUrl(vanity, stgHelper), title); - if (await stgHelper.IfShortUrlEntityExist(newRow)) - { - return req.CreateResponse(HttpStatusCode.Conflict, "This Short URL already exist."); - } } await stgHelper.SaveShortUrlEntity(newRow); From fde9caad07024eef185432bb15a0c9c7c4e789cf Mon Sep 17 00:00:00 2001 From: Chris Renton Date: Wed, 18 Nov 2020 21:17:34 +0000 Subject: [PATCH 3/7] custom vanity composition --- src/shortenerTools/Domain/Utility.cs | 66 ++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 9 deletions(-) diff --git a/src/shortenerTools/Domain/Utility.cs b/src/shortenerTools/Domain/Utility.cs index 2037cc6e..ade9f6ff 100644 --- a/src/shortenerTools/Domain/Utility.cs +++ b/src/shortenerTools/Domain/Utility.cs @@ -1,19 +1,26 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; using System.Linq; +using System.Security.Claims; using System.Threading.Tasks; namespace Cloud5mins.domain { public static class Utility { - private const string Alphabet = "abcdefghijklmnopqrstuvwxyz0123456789"; - private static readonly int Base = Alphabet.Length; + //reshuffled for randomisation, same unique characters just jumbled up, use your own for security + private const string ConversionCode = "aoq6lewdfit0nbvp3ukz8mc941gsj57r2hyx"; + private static readonly int Base = ConversionCode.Length; + private const int MinVanityLength = 4; + //private const string Alphabet = "abcdefghijklmnopqrstuvwxyz0123456789"; + //private static readonly int Base = Alphabet.Length; public static async Task GetValidEndUrl(string vanity, StorageTableHelper stgHelper) { - if(string.IsNullOrEmpty(vanity)) + if (string.IsNullOrEmpty(vanity)) { var newKey = await stgHelper.GetNextTableId(); - string getCode() => Encode(newKey); + string getCode() => Encode(newKey, MinVanityLength); return string.Join(string.Empty, getCode()); } else @@ -22,22 +29,63 @@ public static async Task GetValidEndUrl(string vanity, StorageTableHelpe } } - public static string Encode(int i) + public static string Encode(int i, int minVanityLength) { if (i == 0) - return Alphabet[0].ToString(); + return ConversionCode[0].ToString(); var s = string.Empty; while (i > 0) { - s += Alphabet[i % Base]; + s += ConversionCode[i % Base]; i = i / Base; + //if we setting a minimum length just extend the code accordingly + if (minVanityLength > 0) + { + while (s.Length < minVanityLength) + { + s += ConversionCode[s.Length % Base]; + } + } } return string.Join(string.Empty, s.Reverse()); } - public static string GetShortUrl(string host, string vanity){ - return host + "/" + vanity; + public static string GetShortUrl(string host, string vanity) + { + return host + "/" + vanity; + } + + public static IActionResult CatchUnauthorize(ClaimsPrincipal principal, ILogger log) + { + if (principal == null) + { + log.LogWarning("No principal."); + return new UnauthorizedResult(); + } + + if (principal.Identity == null) + { + log.LogWarning("No identity."); + return new UnauthorizedResult(); + } + + if (!principal.Identity.IsAuthenticated) + { + log.LogWarning("Request was not authenticated."); + return new UnauthorizedResult(); + } + + if (principal.FindFirst(ClaimTypes.GivenName) is null) + { + log.LogError("Claim not Found"); + return new BadRequestObjectResult(new + { + message = "Claim not Found", + StatusCode = System.Net.HttpStatusCode.BadRequest + }); + } + return null; } } } \ No newline at end of file From fcf05cdf5412e7d79129af512c761952dde2bd30 Mon Sep 17 00:00:00 2001 From: Chris Renton Date: Wed, 18 Nov 2020 21:36:53 +0000 Subject: [PATCH 4/7] remove comments --- src/shortenerTools/Domain/Utility.cs | 37 ---------------------------- 1 file changed, 37 deletions(-) diff --git a/src/shortenerTools/Domain/Utility.cs b/src/shortenerTools/Domain/Utility.cs index ade9f6ff..a27c95a8 100644 --- a/src/shortenerTools/Domain/Utility.cs +++ b/src/shortenerTools/Domain/Utility.cs @@ -1,7 +1,4 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; using System.Linq; -using System.Security.Claims; using System.Threading.Tasks; namespace Cloud5mins.domain @@ -12,8 +9,6 @@ public static class Utility private const string ConversionCode = "aoq6lewdfit0nbvp3ukz8mc941gsj57r2hyx"; private static readonly int Base = ConversionCode.Length; private const int MinVanityLength = 4; - //private const string Alphabet = "abcdefghijklmnopqrstuvwxyz0123456789"; - //private static readonly int Base = Alphabet.Length; public static async Task GetValidEndUrl(string vanity, StorageTableHelper stgHelper) { @@ -55,37 +50,5 @@ public static string GetShortUrl(string host, string vanity) { return host + "/" + vanity; } - - public static IActionResult CatchUnauthorize(ClaimsPrincipal principal, ILogger log) - { - if (principal == null) - { - log.LogWarning("No principal."); - return new UnauthorizedResult(); - } - - if (principal.Identity == null) - { - log.LogWarning("No identity."); - return new UnauthorizedResult(); - } - - if (!principal.Identity.IsAuthenticated) - { - log.LogWarning("Request was not authenticated."); - return new UnauthorizedResult(); - } - - if (principal.FindFirst(ClaimTypes.GivenName) is null) - { - log.LogError("Claim not Found"); - return new BadRequestObjectResult(new - { - message = "Claim not Found", - StatusCode = System.Net.HttpStatusCode.BadRequest - }); - } - return null; - } } } \ No newline at end of file From df9a950618b0fd59043112a7b4ff53e16c6fe1ae Mon Sep 17 00:00:00 2001 From: Chris Renton Date: Thu, 19 Nov 2020 10:19:41 +0000 Subject: [PATCH 5/7] Update Utility.cs --- src/shortenerTools/Domain/Utility.cs | 45 +++++++++++++++------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/src/shortenerTools/Domain/Utility.cs b/src/shortenerTools/Domain/Utility.cs index a27c95a8..297dfc7b 100644 --- a/src/shortenerTools/Domain/Utility.cs +++ b/src/shortenerTools/Domain/Utility.cs @@ -1,21 +1,23 @@ using System.Linq; +using System.Security.Cryptography; using System.Threading.Tasks; namespace Cloud5mins.domain { public static class Utility { - //reshuffled for randomisation, same unique characters just jumbled up, use your own for security - private const string ConversionCode = "aoq6lewdfit0nbvp3ukz8mc941gsj57r2hyx"; + //reshuffled for randomisation, same unique characters just jumbled up, you can replace with your own version + private const string ConversionCode = "FjTG0s5dgWkbLf_8etOZqMzNhmp7u6lUJoXIDiQB9-wRxCKyrPcv4En3Y21aASHV"; private static readonly int Base = ConversionCode.Length; - private const int MinVanityLength = 4; + //sets the length of the unique code to add to vanity + private const int MinVanityCodeLength = 5; public static async Task GetValidEndUrl(string vanity, StorageTableHelper stgHelper) { if (string.IsNullOrEmpty(vanity)) { var newKey = await stgHelper.GetNextTableId(); - string getCode() => Encode(newKey, MinVanityLength); + string getCode() => Encode(newKey); return string.Join(string.Empty, getCode()); } else @@ -24,31 +26,34 @@ public static async Task GetValidEndUrl(string vanity, StorageTableHelpe } } - public static string Encode(int i, int minVanityLength) + public static string Encode(int i) { if (i == 0) return ConversionCode[0].ToString(); - var s = string.Empty; - while (i > 0) - { - s += ConversionCode[i % Base]; - i = i / Base; - //if we setting a minimum length just extend the code accordingly - if (minVanityLength > 0) - { - while (s.Length < minVanityLength) - { - s += ConversionCode[s.Length % Base]; - } - } - } - return string.Join(string.Empty, s.Reverse()); + return GenerateUniqueRandomToken(i); } public static string GetShortUrl(string host, string vanity) { return host + "/" + vanity; } + + // generates a unique, random, and alphanumeric token for the use as a url + //(not entirely secure but not sequential so generally not guessable) + public static string GenerateUniqueRandomToken(int uniqueId) + { + using (var generator = new RNGCryptoServiceProvider()) + { + //minimum size I would suggest is 5, longer the better but we want short URLs! + var bytes = new byte[MinVanityCodeLength]; + generator.GetBytes(bytes); + var chars = bytes + .Select(b => ConversionCode[b % ConversionCode.Length]); + var token = new string(chars.ToArray()); + var reversedToken = string.Join(string.Empty, token.Reverse()); + return uniqueId + reversedToken; + } + } } } \ No newline at end of file From f739dca801f1b446c798b83cb918b3692c916d29 Mon Sep 17 00:00:00 2001 From: Chris Renton Date: Thu, 19 Nov 2020 23:21:47 +0000 Subject: [PATCH 6/7] deploy changes --- ...mmsshortenertools2fxfh - Zip Deploy.pubxml | 18 +++ .../profile.arm.json | 152 ++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 src/shortenerTools/Properties/PublishProfiles/patientcommsshortenertools2fxfh - Zip Deploy.pubxml create mode 100644 src/shortenerTools/Properties/ServiceDependencies/patientcommsshortenertools2fxfh - Zip Deploy/profile.arm.json diff --git a/src/shortenerTools/Properties/PublishProfiles/patientcommsshortenertools2fxfh - Zip Deploy.pubxml b/src/shortenerTools/Properties/PublishProfiles/patientcommsshortenertools2fxfh - Zip Deploy.pubxml new file mode 100644 index 00000000..fc49338b --- /dev/null +++ b/src/shortenerTools/Properties/PublishProfiles/patientcommsshortenertools2fxfh - Zip Deploy.pubxml @@ -0,0 +1,18 @@ + + + + + ZipDeploy + AzureWebSite + Release + Any CPU + http://patientcommsshortenertools2fxfh.azurewebsites.net + False + /subscriptions/1b64105e-78ea-4dde-b2b0-4031b5611c06/resourceGroups/PatientCommsGroupv2/providers/Microsoft.Web/sites/patientcommsshortenertools2fxfh + $patientcommsshortenertools2fxfh + <_SavePWD>True + https://patientcommsshortenertools2fxfh.scm.azurewebsites.net/ + + \ No newline at end of file diff --git a/src/shortenerTools/Properties/ServiceDependencies/patientcommsshortenertools2fxfh - Zip Deploy/profile.arm.json b/src/shortenerTools/Properties/ServiceDependencies/patientcommsshortenertools2fxfh - Zip Deploy/profile.arm.json new file mode 100644 index 00000000..b2d16d2e --- /dev/null +++ b/src/shortenerTools/Properties/ServiceDependencies/patientcommsshortenertools2fxfh - Zip Deploy/profile.arm.json @@ -0,0 +1,152 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_dependencyType": "function.windows.consumption" + }, + "parameters": { + "resourceGroupName": { + "type": "string", + "defaultValue": "PatientCommsGroupv2", + "metadata": { + "description": "Name of the resource group for the resource. It is recommended to put resources under same resource group for better tracking." + } + }, + "resourceGroupLocation": { + "type": "string", + "defaultValue": "uksouth", + "metadata": { + "description": "Location of the resource group. Resource groups could have different location than resources, however by default we use API versions from latest hybrid profile which support all locations for resource types we support." + } + }, + "resourceName": { + "type": "string", + "defaultValue": "patientcommsshortenertools2fxfh", + "metadata": { + "description": "Name of the main resource to be created by this template." + } + }, + "resourceLocation": { + "type": "string", + "defaultValue": "[parameters('resourceGroupLocation')]", + "metadata": { + "description": "Location of the resource. By default use resource group's location, unless the resource provider is not supported there." + } + } + }, + "resources": [ + { + "type": "Microsoft.Resources/resourceGroups", + "name": "[parameters('resourceGroupName')]", + "location": "[parameters('resourceGroupLocation')]", + "apiVersion": "2019-10-01" + }, + { + "type": "Microsoft.Resources/deployments", + "name": "[concat(parameters('resourceGroupName'), 'Deployment', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]", + "resourceGroup": "[parameters('resourceGroupName')]", + "apiVersion": "2019-10-01", + "dependsOn": [ + "[parameters('resourceGroupName')]" + ], + "properties": { + "mode": "Incremental", + "expressionEvaluationOptions": { + "scope": "inner" + }, + "parameters": { + "resourceGroupName": { + "value": "[parameters('resourceGroupName')]" + }, + "resourceGroupLocation": { + "value": "[parameters('resourceGroupLocation')]" + }, + "resourceName": { + "value": "[parameters('resourceName')]" + }, + "resourceLocation": { + "value": "[parameters('resourceLocation')]" + } + }, + "template": { + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "resourceGroupName": { + "type": "string" + }, + "resourceGroupLocation": { + "type": "string" + }, + "resourceName": { + "type": "string" + }, + "resourceLocation": { + "type": "string" + } + }, + "variables": { + "storage_name": "[toLower(concat('storage', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId))))]", + "storage_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Storage/storageAccounts/', variables('storage_name'))]", + "function_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Web/sites/', parameters('resourceName'))]" + }, + "resources": [ + { + "location": "[parameters('resourceGroupLocation')]", + "name": "[variables('storage_name')]", + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2017-10-01", + "tags": { + "[concat('hidden-related:', concat('/providers/Microsoft.Web/sites/', parameters('resourceName')))]": "empty" + }, + "properties": { + "supportsHttpsTrafficOnly": true + }, + "sku": { + "name": "Standard_LRS" + }, + "kind": "Storage" + }, + { + "location": "[parameters('resourceLocation')]", + "name": "[parameters('resourceName')]", + "type": "Microsoft.Web/sites", + "apiVersion": "2015-08-01", + "dependsOn": [ + "[variables('storage_ResourceId')]" + ], + "kind": "functionapp", + "properties": { + "name": "[parameters('resourceName')]", + "kind": "functionapp", + "httpsOnly": true, + "reserved": false + }, + "identity": { + "type": "SystemAssigned" + }, + "resources": [ + { + "name": "appsettings", + "type": "config", + "apiVersion": "2015-08-01", + "dependsOn": [ + "[variables('function_ResourceId')]" + ], + "properties": { + "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storage_name'), ';AccountKey=', listKeys(variables('storage_ResourceId'), '2017-10-01').keys[0].value, ';EndpointSuffix=', 'core.windows.net')]", + "WEBSITE_CONTENTSHARE": "[toLower(parameters('resourceName'))]", + "AzureWebJobsDashboard": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storage_name'), ';AccountKey=', listKeys(variables('storage_ResourceId'), '2017-10-01').keys[0].value, ';EndpointSuffix=', 'core.windows.net')]", + "AzureWebJobsStorage": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storage_name'), ';AccountKey=', listKeys(variables('storage_ResourceId'), '2017-10-01').keys[0].value, ';EndpointSuffix=', 'core.windows.net')]", + "FUNCTIONS_EXTENSION_VERSION": "~3", + "FUNCTIONS_WORKER_RUNTIME": "dotnet" + } + } + ] + } + ] + } + } + } + ] +} \ No newline at end of file From 1bd5e323f7551250c98fc9a075915ba6b96bbe10 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 7 Dec 2020 07:15:37 -0500 Subject: [PATCH 7/7] quickfix rename settings.json file --- src/shortenerTools/UrlRedirect/UrlRedirect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shortenerTools/UrlRedirect/UrlRedirect.cs b/src/shortenerTools/UrlRedirect/UrlRedirect.cs index a945abbc..358d0572 100644 --- a/src/shortenerTools/UrlRedirect/UrlRedirect.cs +++ b/src/shortenerTools/UrlRedirect/UrlRedirect.cs @@ -27,7 +27,7 @@ public static async Task Run( { var config = new ConfigurationBuilder() .SetBasePath(context.FunctionAppDirectory) - .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true) + .AddJsonFile("settings.json", optional: true, reloadOnChange: true) .AddEnvironmentVariables() .Build();