Beispiel #1
0
        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)));
        }
Beispiel #2
0
        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());
        }
Beispiel #3
0
        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));
        }
Beispiel #4
0
        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");
        }
Beispiel #5
0
        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());
        }
Beispiel #6
0
        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());
        }
Beispiel #7
0
        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));
        }
Beispiel #8
0
        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}");
        }
Beispiel #9
0
        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);
                    }
                }
            }
        }