1
0
mirror of https://github.com/bitwarden/server.git synced 2025-02-22 02:51:33 +01:00

azure functions project

This commit is contained in:
Kyle Spearrin 2017-09-08 12:38:09 -04:00
parent 7cdba3e81a
commit 8218ab468d
11 changed files with 380 additions and 1 deletions

View File

@ -41,7 +41,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jobs", "src\Jobs\Jobs.cspro
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper", "..\Dapper\Dapper\Dapper.csproj", "{6951E73D-1761-41F6-B5D3-BEF4C2F73EA3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BillingUpdater", "util\BillingUpdater\BillingUpdater.csproj", "{A0FBA4DF-2F24-45A6-B188-EBDBD2FAF445}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BillingUpdater", "util\BillingUpdater\BillingUpdater.csproj", "{A0FBA4DF-2F24-45A6-B188-EBDBD2FAF445}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Function", "util\Function\Function.csproj", "{A6C44A84-8E51-4C64-B9C4-7B7C23253345}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -95,6 +97,10 @@ Global
{A0FBA4DF-2F24-45A6-B188-EBDBD2FAF445}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A0FBA4DF-2F24-45A6-B188-EBDBD2FAF445}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A0FBA4DF-2F24-45A6-B188-EBDBD2FAF445}.Release|Any CPU.Build.0 = Release|Any CPU
{A6C44A84-8E51-4C64-B9C4-7B7C23253345}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A6C44A84-8E51-4C64-B9C4-7B7C23253345}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A6C44A84-8E51-4C64-B9C4-7B7C23253345}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A6C44A84-8E51-4C64-B9C4-7B7C23253345}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -111,6 +117,7 @@ Global
{7DCEBD8F-E5F3-4A3C-BD35-B64341590B74} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84D}
{6951E73D-1761-41F6-B5D3-BEF4C2F73EA3} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84D}
{A0FBA4DF-2F24-45A6-B188-EBDBD2FAF445} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84E}
{A6C44A84-8E51-4C64-B9C4-7B7C23253345} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E01CBF68-2E20-425F-9EDB-E0A6510CA92F}

70
util/Function/BlockIp.cs Normal file
View File

@ -0,0 +1,70 @@
using System;
using System.Configuration;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Bit.Function.Models;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using Newtonsoft.Json;
namespace Bit.Function
{
public static class BlockIp
{
[FunctionName("BlockIp")]
public static void Run(
[QueueTrigger("blockip", Connection = "")]string myQueueItem,
out string outputQueueItem,
TraceWriter log)
{
outputQueueItem = BlockIpAsync(myQueueItem).GetAwaiter().GetResult();
log.Info($"C# Queue trigger function processed: {myQueueItem}, outputted: {outputQueueItem}");
}
private static async Task<string> BlockIpAsync(string ipAddress)
{
var ipWhitelist = ConfigurationManager.AppSettings["WhitelistedIps"];
if(ipWhitelist != null && ipWhitelist.Split(',').Contains(ipAddress))
{
return null;
}
var xAuthEmail = ConfigurationManager.AppSettings["X-Auth-Email"];
var xAuthKey = ConfigurationManager.AppSettings["X-Auth-Key"];
var zoneId = ConfigurationManager.AppSettings["ZoneId"];
using(var client = new HttpClient())
{
client.BaseAddress = new Uri("https://api.cloudflare.com");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Add("X-Auth-Email", xAuthEmail);
client.DefaultRequestHeaders.Add("X-Auth-Key", xAuthKey);
var response = await client.PostAsJsonAsync(
$"/client/v4/zones/{zoneId}/firewall/access_rules/rules",
new
{
mode = "block",
configuration = new
{
target = "ip",
value = ipAddress
},
notes = $"Rate limit abuse on {DateTime.UtcNow.ToString()}."
});
var responseString = await response.Content.ReadAsStringAsync();
var responseJson = JsonConvert.DeserializeObject<AccessRuleResponse>(responseString);
if(!responseJson.Success)
{
return null;
}
// Uncomment whenever we can delay the returned message. Functions do not support that at this time.
return null; //responseJson.Result?.Id;
}
}
}
}

View File

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net461</TargetFramework>
<RootNamespace>Bit.Function</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.2" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Configuration" />
</ItemGroup>
<ItemGroup>
<None Update="host.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="local.settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>
</ItemGroup>
</Project>

View File

@ -0,0 +1,17 @@
using System;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
namespace Bit.Function
{
public static class KeepAlive
{
[FunctionName("KeepAlive")]
public static void Run(
[TimerTrigger("0 */15 * * * *")]TimerInfo myTimer,
TraceWriter log)
{
log.Info($"C# Timer trigger function executed at: {DateTime.Now}");
}
}
}

View File

@ -0,0 +1,8 @@
namespace Bit.Function.Models
{
public class AccessRuleResponse
{
public bool Success { get; set; }
public AccessRuleResultResponse Result { get; set; }
}
}

View File

@ -0,0 +1,15 @@
namespace Bit.Function.Models
{
public class AccessRuleResultResponse
{
public string Id { get; set; }
public string Notes { get; set; }
public ConfigurationResponse Configuration { get; set; }
public class ConfigurationResponse
{
public string Target { get; set; }
public string Value { get; set; }
}
}
}

View File

@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace Bit.Function.Models
{
public class ListResult
{
public bool Success { get; set; }
public List<AccessRuleResultResponse> Result { get; set; }
}
}

View File

@ -0,0 +1,132 @@
using System;
using System.Configuration;
using System.Net;
using System.Net.Http;
using System.Net.Mail;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;
namespace Bit.Function
{
public static class NewHelpdeskTicket
{
[FunctionName("NewHelpdeskTicket")]
public static HttpResponseMessage Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "api/newhelpdeskticket")]HttpRequestMessage req,
TraceWriter log)
{
//ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls |
// SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
var data = req.Content.ReadAsFormDataAsync().Result;
if(data == null)
{
return req.CreateResponse(HttpStatusCode.BadRequest, "No data provided.");
}
if(string.IsNullOrWhiteSpace(data["name"]))
{
return req.CreateResponse(HttpStatusCode.BadRequest, "Name is required.");
}
if(data["name"].Length > 50)
{
return req.CreateResponse(HttpStatusCode.BadRequest, "Name must be less than 50 characters.");
}
if(string.IsNullOrWhiteSpace(data["email"]))
{
return req.CreateResponse(HttpStatusCode.BadRequest, "Email is required.");
}
if(data["email"].Length > 50)
{
return req.CreateResponse(HttpStatusCode.BadRequest, "Email must be less than 50 characters.");
}
if(!data["email"].Contains("@") || !data["email"].Contains("."))
{
return req.CreateResponse(HttpStatusCode.BadRequest, "Email is not valid.");
}
if(string.IsNullOrWhiteSpace(data["message"]))
{
return req.CreateResponse(HttpStatusCode.BadRequest, "Message is required.");
}
//if(!await SubmitApiAsync(data["name"], data["email"], data["message"], log))
//{
// return req.CreateResponse(HttpStatusCode.BadRequest, "Ticket failed to create.");
//}
SubmitEmail(data["name"], data["email"], data["message"], log);
return req.CreateResponse(HttpStatusCode.OK, "Ticket created.");
}
private async static Task<bool> SubmitApiAsync(string name, string email, string message, TraceWriter log)
{
using(var client = new HttpClient())
{
client.BaseAddress = new Uri("https://bitwarden.freshdesk.com/api/v2");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Add("Authorization", MakeFreshdeskApiAuthHeader(log));
var response = await client.PostAsJsonAsync("tickets",
new
{
name = name,
email = email,
status = 2,
priority = 2,
source = 1,
subject = "bitwarden.com Website Contact",
description = FormatMessage(message)
});
return response.IsSuccessStatusCode;
}
}
private static void SubmitEmail(string name, string email, string message, TraceWriter log)
{
var sendgridApiKey = ConfigurationManager.AppSettings["SendgridApiKey"];
var client = new SmtpClient("smtp.sendgrid.net", /*465*/ 587)
{
//EnableSsl = true,
Credentials = new NetworkCredential("apikey", sendgridApiKey)
};
var fromAddress = new MailAddress(email, name, Encoding.UTF8);
var mailMessage = new MailMessage(fromAddress, new MailAddress("bitwardencomsupport@bitwarden.freshdesk.com"))
{
Subject = "bitwarden.com Website Contact",
Body = FormatMessage(message),
IsBodyHtml = true
};
client.SendCompleted += (s, e) =>
{
client.Dispose();
mailMessage.Dispose();
};
client.SendAsync(mailMessage, null);
}
private static string FormatMessage(string message)
{
return message.Replace("\r\n", "\n").Replace("\r", "\n").Replace("\n", "<br>");
}
private static string MakeFreshdeskApiAuthHeader(TraceWriter log)
{
var freshdeskApiKey = ConfigurationManager.AppSettings["FreshdeskApiKey"];
var b64Creds = Convert.ToBase64String(
Encoding.GetEncoding("ISO-8859-1").GetBytes(freshdeskApiKey + ":X"));
return b64Creds;
}
}
}

View File

@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Net.Http;
using System.Threading.Tasks;
using Bit.Function.Models;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using Newtonsoft.Json;
namespace Bit.Function
{
public static class UnblockIp
{
[FunctionName("UnblockIp")]
public static void Run(
[QueueTrigger("unblockip", Connection = "")]string myQueueItem,
TraceWriter log)
{
log.Info($"C# Queue trigger function processed: {myQueueItem}");
UnblockIpAsync(myQueueItem, log).Wait();
}
private static async Task UnblockIpAsync(string id, TraceWriter log)
{
if(id == null)
{
return;
}
var zoneId = ConfigurationManager.AppSettings["ZoneId"];
var xAuthEmail = ConfigurationManager.AppSettings["X-Auth-Email"];
var xAuthKey = ConfigurationManager.AppSettings["X-Auth-Key"];
if(id.Contains(".") || id.Contains(":"))
{
// IP address messages.
using(var client = new HttpClient())
{
client.BaseAddress = new Uri("https://api.cloudflare.com");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Add("X-Auth-Email", xAuthEmail);
client.DefaultRequestHeaders.Add("X-Auth-Key", xAuthKey);
var response = await client.GetAsync($"/client/v4/zones/{zoneId}/firewall/access_rules/rules?" +
$"configuration_target=ip&configuration_value={id}");
var responseString = await response.Content.ReadAsStringAsync();
var responseJson = JsonConvert.DeserializeObject<ListResult>(responseString);
if(!responseJson.Success)
{
return;
}
foreach(var rule in responseJson.Result)
{
if(rule.Configuration?.Value != id)
{
continue;
}
log.Info($"Unblock IP {id}, {rule.Id}");
await DeleteRuleAsync(zoneId, xAuthEmail, xAuthKey, rule.Id);
}
}
}
else
{
log.Info($"Unblock Id {id}");
await DeleteRuleAsync(zoneId, xAuthEmail, xAuthKey, id);
}
}
private static async Task DeleteRuleAsync(string zoneId, string xAuthEmail, string xAuthKey, string id)
{
var path = $"/client/v4/zones/{zoneId}/firewall/access_rules/rules/{id}";
using(var client = new HttpClient())
{
client.BaseAddress = new Uri("https://api.cloudflare.com");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Add("X-Auth-Email", xAuthEmail);
client.DefaultRequestHeaders.Add("X-Auth-Key", xAuthKey);
await client.DeleteAsync(path);
}
}
}
}

2
util/Function/host.json Normal file
View File

@ -0,0 +1,2 @@
{
}

View File

@ -0,0 +1,7 @@
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "",
"AzureWebJobsDashboard": ""
}
}