public static async Task <IActionResult> RedirectAndHostGet( [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "_api/v1/host/redirect/{host}/{key}")] HttpRequest req, [Table(TableNames.Redirects)] CloudTable redirectTable, [Table(TableNames.Domains)] CloudTable domainTable, string host, string key, ILogger log, ExecutionContext context, ClaimsPrincipal claimsPrincipal) { List <DomainEntity> domainEntity = await DomainEntity.get(domainTable, host); if (domainEntity == null) { return(new NotFoundResult()); } RedirectEntity entity = await RedirectEntity.get(redirectTable, domainEntity.First().Account, key); if (entity == null) { return(new NotFoundResult()); } if (entity.Recycled) { return(new NotFoundResult()); } return(new OkObjectResult(new RedirectEntity(entity.PartitionKey, entity.RowKey, entity.RedirectTo, 0, new Dictionary <string, int>(), DateTime.Now, false))); }
public static async Task <IActionResult> DomainDelete( [HttpTrigger(AuthorizationLevel.Anonymous, "delete", Route = "_api/v1/domain/{key}")] HttpRequest req, [Table(TableNames.Domains)] CloudTable domainTable, string key, ILogger log, ExecutionContext context, ClaimsPrincipal claimsPrincipal) { if (!claimsPrincipal.Identity.IsAuthenticated) { return(new UnauthorizedResult()); } List <DomainEntity> entities = await DomainEntity.get(domainTable, key); if (entities == null) { return(new NotFoundResult()); } DomainEntity[] filteredEntities = entities.Where(domain => domain.Account == claimsPrincipal.Identity.Name).ToArray(); if (filteredEntities.Length == 0) { return(new NotFoundResult()); } bool deleteSuccess = await DomainEntity.delete(domainTable, filteredEntities.First()); return(deleteSuccess ? (IActionResult) new OkObjectResult(filteredEntities.First()) : new BadRequestResult()); }
public static async Task <IActionResult> DomainsGet( [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "_api/v1/domains")] HttpRequest req, [Table(TableNames.Domains)] CloudTable domainTable, ILogger log, ExecutionContext context, ClaimsPrincipal claimsPrincipal) { if (!claimsPrincipal.Identity.IsAuthenticated) { return(new UnauthorizedResult()); } List <DomainEntity> entities = await DomainEntity.get(domainTable, null); if (entities == null) { log.LogInformation($"No domains found in the table"); return(new NotFoundResult()); } DomainEntity[] filteredEntities = entities.Where(domain => domain.Account == claimsPrincipal.Identity.Name).ToArray(); if (filteredEntities.Length == 0) { log.LogInformation($"No domains found with account {claimsPrincipal.Identity.Name}"); return(new NotFoundResult()); } return(new OkObjectResult(filteredEntities)); }
public static async void ProcessClicks( [QueueTrigger(QueueNames.ProcessClicks)] string queuedHttpRequestString, [Table(TableNames.Redirects)] CloudTable redirectTable, [Table(TableNames.Domains)] CloudTable domainTable, [Queue(QueueNames.ProcessClicksGeo), StorageAccount("AzureWebJobsStorage")] ICollector <HttpRequestEntity> processClicksGeoQueue, ILogger log, ExecutionContext context) { HttpRequestEntity queuedHttpRequest = JsonConvert.DeserializeObject <HttpRequestEntity>(queuedHttpRequestString); var config = new ConfigurationBuilder() .SetBasePath(context.FunctionAppDirectory) .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true) .AddEnvironmentVariables() .Build(); string nodeMaster = config["NODE_SYNC_MASTER_CONN"]; if (nodeMaster != null) { CloudStorageAccount storageAccount = CloudStorageAccount.Parse(nodeMaster); CloudQueueClient queueClient = storageAccount.CreateCloudQueueClient(); CloudQueue destinationProcessClicksQueue = queueClient.GetQueueReference(QueueNames.ProcessClicks); await destinationProcessClicksQueue.AddMessageAsync(new CloudQueueMessage(JsonConvert.SerializeObject(queuedHttpRequest))); return; } List <DomainEntity> domains = await DomainEntity.get(domainTable, queuedHttpRequest.Host); if (domains == null) { throw new Exception($"Unable to process Geo lookup - domain {queuedHttpRequest.Host} wasn't found"); } string path = queuedHttpRequest.Path.Value.Substring(1); RedirectEntity redirect = await RedirectEntity.get(redirectTable, domains.First().Account, path); if (redirect != null) { redirect.ClickCount++; await RedirectEntity.put(redirectTable, redirect); processClicksGeoQueue.Add(queuedHttpRequest); log.LogInformation($"Successfully processed click for redirect query {queuedHttpRequest.Path} from {queuedHttpRequest.RemoteIpAddress}"); return; } log.LogError($"Http request {queuedHttpRequest.Path} for click handling doesn't match handled hosts"); throw new System.Exception($"Http request {queuedHttpRequest.Path} for click handling doesn't match handled hosts"); }
public static async Task <IActionResult> DomainPatch( [HttpTrigger(AuthorizationLevel.Anonymous, "patch", Route = "_api/v1/domain/{key}")] HttpRequest req, [Table(TableNames.Domains)] CloudTable domainTable, string key, ILogger log, ExecutionContext context, ClaimsPrincipal claimsPrincipal) { if (!claimsPrincipal.Identity.IsAuthenticated) { return(new UnauthorizedResult()); } string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); dynamic entity = JsonConvert.DeserializeObject <dynamic>(requestBody); log.LogInformation($"Getting Domain row for values {claimsPrincipal.Identity.Name} and {entity.RowKey}"); List <DomainEntity> entities = await DomainEntity.get(domainTable, key); if (entities == null) { return(new BadRequestObjectResult($"Domain with {key} doesn't exist for {claimsPrincipal.Identity.Name}")); } DomainEntity[] filteredEntities = entities.Where(domain => domain.Account == claimsPrincipal.Identity.Name).ToArray(); if (filteredEntities.Length == 0) { return(new UnauthorizedResult()); } filteredEntities[0].Configured = entity.configured ??= filteredEntities[0].Configured; filteredEntities[0].SslConfigured = entity.sslConfigured ??= filteredEntities[0].SslConfigured; bool success = await DomainEntity.put(domainTable, filteredEntities[0]); if (!success) { return(new BadRequestObjectResult($"Error occurred updating domain {key} for {claimsPrincipal.Identity.Name}")); } return(new OkResult()); }
public static async Task <IActionResult> DomainPost( [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "_api/v1/domain")] HttpRequest req, [Table(TableNames.Domains)] CloudTable domainTable, ILogger log, ExecutionContext context, ClaimsPrincipal claimsPrincipal) { if (!claimsPrincipal.Identity.IsAuthenticated) { return(new UnauthorizedResult()); } string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); DomainEntity entity = JsonConvert.DeserializeObject <DomainEntity>(requestBody); if (entity.RowKey == null) { return(new BadRequestObjectResult($"Please specify the RowKey in the request body")); } log.LogInformation($"Getting Domain row for values {claimsPrincipal.Identity.Name} and {entity.RowKey}"); List <DomainEntity> existingEntities = await DomainEntity.get(domainTable, entity.RowKey); if (existingEntities != null) { return(new BadRequestObjectResult($"Domain with {entity.RowKey} already exists")); } bool success = await DomainEntity.put(domainTable, entity.RowKey, claimsPrincipal.Identity.Name); if (!success) { return(new BadRequestObjectResult($"Error occurred creating {entity.RowKey} already exists for {claimsPrincipal.Identity.Name}")); } return(new OkResult()); }
public static async Task <IActionResult> ShortNameGet( [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "{shortName:regex([\\w\\d]+)}")] HttpRequest req, [Table(TableNames.Redirects)] CloudTable redirectTable, [Table(TableNames.Domains)] CloudTable domainTable, [Queue(QueueNames.ProcessClicks), StorageAccount("AzureWebJobsStorage")] ICollector <HttpRequestEntity> processClicksQueue, [Queue(QueueNames.NotFoundClicks), StorageAccount("AzureWebJobsStorage")] ICollector <HttpRequestEntity> notFoundClicksQueue, string shortName, ILogger log, ExecutionContext context) { List <DomainEntity> domains = await DomainEntity.get(domainTable, req.Host.Value); if (domains == null) { notFoundClicksQueue.Add(new NotFoundEntity(req, "Domain not handled")); return(new NotFoundResult()); } RedirectEntity redirect = await RedirectEntity.get(redirectTable, domains.First().Account, shortName); if (redirect == null) { var config = new ConfigurationBuilder() .SetBasePath(context.FunctionAppDirectory) .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true) .AddEnvironmentVariables() .Build(); string nodeMaster = config["NODE_SYNC_MASTER_HOST"]; if (nodeMaster == null) { notFoundClicksQueue.Add(new NotFoundEntity(req, "Node master not found")); return(new NotFoundResult()); } string nodeMasterLookupUri = $"https://{nodeMaster}/_api/v1/host/redirect/{req.Host.Value}/{shortName}"; var client = new HttpClient(); var getResponse = await client.GetAsync(nodeMasterLookupUri); if (getResponse.StatusCode != HttpStatusCode.OK) { notFoundClicksQueue.Add(new NotFoundEntity(req, "Node master returned not found")); return(new NotFoundResult()); } string masterResponseString = await getResponse.Content.ReadAsStringAsync(); redirect = JsonConvert.DeserializeObject <RedirectEntity>(masterResponseString); await RedirectEntity.put(redirectTable, redirect); } if (redirect.Recycled) { notFoundClicksQueue.Add(new NotFoundEntity(req, "Short name has been recycled")); return(new NotFoundResult()); } req.HttpContext.Response.Headers.Add("Cache-Control", "no-cache,no-store"); req.HttpContext.Response.Headers.Add("Expires", DateTime.Now.AddMinutes(5).ToUniversalTime().ToString("ddd, dd MMM yyyy HH:mm:ss G\\MT")); req.HttpContext.Response.Headers.Add("Vary", "Origin"); processClicksQueue.Add(new HttpRequestEntity(req)); return(new RedirectResult(redirect.RedirectTo, true)); }
public static async void ProcessClicksForGeo( [QueueTrigger(QueueNames.ProcessClicksGeo)] string queuedHttpRequestString, [Table(TableNames.Redirects)] CloudTable redirectTable, [Table(TableNames.Domains)] CloudTable domainTable, [Table(TableNames.Geos)] CloudTable geoTable, ILogger log, ExecutionContext context) { HttpRequestEntity queuedHttpRequest = JsonConvert.DeserializeObject <HttpRequestEntity>(queuedHttpRequestString); var config = new ConfigurationBuilder() .SetBasePath(context.FunctionAppDirectory) .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true) .AddEnvironmentVariables() .Build(); string ipLookupUrl = $"{config["FREEGEOIP_HOST"] ??= "https://freegeoip.app"}/json/{queuedHttpRequest.RemoteIpAddress}"; log.LogInformation($"Looking up freegeoip: {ipLookupUrl}."); var client = new HttpClient(); var getResponse = await client.GetAsync(ipLookupUrl); if (getResponse.StatusCode == HttpStatusCode.OK) { List <DomainEntity> domains = await DomainEntity.get(domainTable, queuedHttpRequest.Host); if (domains == null) { throw new Exception($"Unable to process Geo lookup - domain {queuedHttpRequest.Host} wasn't found"); } string path = queuedHttpRequest.Path.Value.Substring(1); RedirectEntity redirect = await RedirectEntity.get(redirectTable, domains.First().Account, path); if (redirect != null) { string ipResponseString = await getResponse.Content.ReadAsStringAsync(); dynamic ipResponse = JsonConvert.DeserializeObject <dynamic>(ipResponseString); GeoEntity geoEntity = new GeoEntity(ipResponse); await GeoEntity.put(geoTable, geoEntity); Dictionary <string, int> _geoCount = JsonConvert.DeserializeObject <Dictionary <string, int> >(redirect.GeoCount ??= "{}"); if (_geoCount.ContainsKey(geoEntity.RowKey)) { log.LogInformation($"Incrementing GeoCount for redirect entity {queuedHttpRequest.Path}"); _geoCount[geoEntity.RowKey] = _geoCount[geoEntity.RowKey] + 1; } else { log.LogInformation($"Creating GeoCount for redirect entity {queuedHttpRequest.Path}"); _geoCount.Add(geoEntity.RowKey, 1); } redirect.GeoCount = JsonConvert.SerializeObject(_geoCount); await RedirectEntity.put(redirectTable, redirect); return; } log.LogError($"Http request {queuedHttpRequest.Path} for click handling doesn't match handled path"); throw new System.Exception($"Http request {queuedHttpRequest.Path} for click handling doesn't match handled path"); } log.LogError($"Free geo ip lookup for IP {queuedHttpRequest.RemoteIpAddress} failed with status code {getResponse.StatusCode}"); throw new System.Exception($"Free geo ip lookup for IP {queuedHttpRequest.RemoteIpAddress} failed with status code {getResponse.StatusCode}"); }
public static async void ProcessRedirectsSync( [QueueTrigger(QueueNames.SynchroniseRedirects)] string node, [Table(TableNames.Redirects)] CloudTable redirectTable, [Table(TableNames.Domains)] CloudTable domainTable, [Queue(QueueNames.ProcessClicksGeo), StorageAccount("AzureWebJobsStorage")] ICollector <HttpRequestEntity> processClicksGeoQueue, ILogger log, ExecutionContext context) { var config = new ConfigurationBuilder() .SetBasePath(context.FunctionAppDirectory) .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true) .AddEnvironmentVariables() .Build(); string connectionString = config[$"NODE_SYNC_CONNECTION_{node}"]; if (connectionString == null) { throw new Exception($"No connection string found for node [{node}]. Aborting."); } CloudStorageAccount storageAccount = CloudStorageAccount.Parse(connectionString); CloudTableClient tableClient = storageAccount.CreateCloudTableClient(); CloudTable destinationRedirectTable = tableClient.GetTableReference(TableNames.Redirects); CloudTable destinationDomainTable = tableClient.GetTableReference(TableNames.Domains); await destinationRedirectTable.CreateIfNotExistsAsync(); await destinationDomainTable.CreateIfNotExistsAsync(); List <DomainEntity> domains = await DomainEntity.get(domainTable, null); List <string> uniqueAccounts = new List <string>(); foreach (DomainEntity domain in domains) { await DomainEntity.put(destinationDomainTable, domain); if (uniqueAccounts.FindIndex(checkAccount => checkAccount == domain.Account) == -1) { uniqueAccounts.Add(domain.Account); } } List <DomainEntity> destinationDomains = await DomainEntity.get(destinationDomainTable, null); foreach (DomainEntity destinationDomain in destinationDomains) { if (domains.FindIndex(checkDomain => checkDomain.RowKey == destinationDomain.RowKey) == -1) { await DomainEntity.delete(destinationRedirectTable, destinationDomain); } } foreach (string account in uniqueAccounts) { List <RedirectEntity> redirects = await RedirectEntity.get(redirectTable, account); foreach (RedirectEntity redirect in redirects) { await RedirectEntity.put(destinationRedirectTable, redirect); } List <RedirectEntity> destinationRedirects = await RedirectEntity.get(destinationRedirectTable, account); foreach (RedirectEntity destinationRedirect in destinationRedirects) { if (redirects.FindIndex(checkRedirect => checkRedirect.RowKey == destinationRedirect.RowKey) == -1) { await RedirectEntity.delete(destinationRedirectTable, destinationRedirect); } } } }