Files
BeWoPlaner/BeWoDatabaseTerminator/BeWoDatabaseTerminator/Program.cs

609 lines
26 KiB
C#

using System;
using System.Configuration;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using BeWoDatabaseTerminator.Data;
using BeWoDatabaseTerminator.Security;
using BeWoDatabaseTerminator.Utils;
using Newtonsoft.Json;
using MySqlHelper = BeWoDatabaseTerminator.MySql.MySqlHelper;
namespace BeWoDatabaseTerminator
{
internal class Program
{
private const string PubKeyX3 = "3082010A02820101009CD30CF05AE52E47B7725D3783B3686330EAD735261925E1BDBE35F170922FB7B84B4105ABA99E350858ECB12AC468870BA3E375E4E6F3A76271BA7981601FD7919A9FF3D0786771C8690E9591CFFEE699E9603C48CC7ECA4D7712249D471B5AEBB9EC1E37001C9CAC7BA705EACE4AEBBD41E53698B9CBFD6D3C9668DF232A42900C867467C87FA59AB8526114133F65E98287CBDBFA0E56F68689F3853F9786AFB0DC1AEF6B0D95167DC42BA065B299043675806BAC4AF31B9049782FA2964F2A20252904C674C0D031CD8F31389516BAA833B843F1B11FC3307FA27931133D2D36F8E3FCF2336AB93931C5AFC48D0D1D641633AAFA8429B6D40BC0D87DC3930203010001";
private const string PubKeyR3 = "3082010A0282010100BB021528CCF6A094D30F12EC8D5592C3F882F199A67A4288A75D26AAB52BB9C54CB1AF8E6BF975C8A3D70F4794145535578C9EA8A23919F5823C42A94E6EF53BC32EDB8DC0B05CF35938E7EDCF69F05A0B1BBEC094242587FA3771B313E71CACE19BEFDBE43B45524596A9C153CE34C852EEB5AEED8FDE6070E2A554ABB66D0E97A540346B2BD3BC66EB66347CFA6B8B8F572999F830175DBA726FFB81C5ADD286583D17C7E709BBF12BF786DCC1DA715DD446E3CCAD25C188BC60677566B3F118F7A25CE653FF3A88B647A5FF1318EA9809773F9D53F9CF01E5F5A6701714AF63A4FF99B3939DDC53A706FE48851DA169AE2575BB13CC5203F5ED51A18BDB150203010001";
private const string PubKeyE1 = "04245C2DA22AFD1C4BA65D97732731ACB2A06962EF65E8A6B0F0AC4B9FFF1C0B700FD3982F4DFC0F009B37F074055732972E05EF2A4325A3FB6E342713F64F7E69D302995EEB244792C1249BE6B1218FC12481FC68CC1F69BA58F51922F774C616";
private const string APIKEY = "CwjlSCbNqctEsofpGY64Z4aO6pShNYAdGtjMqHsjO64FuK23qoRDUqIjUC0P";
private static string _ConnectionStringServer;
private static string _ConnectionStringPassword;
private static string _ConnectionStringUserID;
private static string _ConnectionStringPort;
private static string _MultitenancyPath;
private static string _CreateScriptsFolderPath;
private static string _ServerName;
private static string _InsertScriptName;
private static string _CreateScriptName;
private static string _ChangesScriptName;
private static string _LogFilePath;
private static void PinCertificate()
{
ServicePointManager.ServerCertificateValidationCallback = PinPublicKey;
}
private static bool PinPublicKey(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslpolicyerrors)
{
try
{
if(certificate is null || chain is null)
{
return false;
}
var chainContainsPublicKey = false;
foreach(var element in chain.ChainElements)
{
var publicKey = element.Certificate.GetPublicKeyString();
if((publicKey?.Equals(PubKeyX3) ?? false) || (publicKey?.Equals(PubKeyR3) ?? false) || (publicKey?.Equals(PubKeyE1) ?? false))
{
chainContainsPublicKey = true;
}
}
var subjectAccepted = certificate.Subject.Trim().ToLower().EndsWith(".bewoplaner.de") || certificate.Subject.Trim().ToLower().EndsWith(".ownchat.de");
return chainContainsPublicKey && subjectAccepted;
}
catch(Exception)
{
return false;
}
}
private static async Task Main(string[] args)
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
PinCertificate();
_ServerName = ConfigurationManager.AppSettings["ServerName"];
_MultitenancyPath = ConfigurationManager.AppSettings["MultitenancyPath"];
_CreateScriptsFolderPath = ConfigurationManager.AppSettings["CreateScriptsFolderPath"];
_ConnectionStringServer = ConfigurationManager.AppSettings["ConnectionStringServer"];
_ConnectionStringPassword = ConfigurationManager.AppSettings["ConnectionStringPassword"];
_ConnectionStringUserID = ConfigurationManager.AppSettings["ConnectionStringUserId"];
_ConnectionStringPort = ConfigurationManager.AppSettings["ConnectionStringPort"];
_CreateScriptName = ConfigurationManager.AppSettings["CreateScriptName"];
_ChangesScriptName = ConfigurationManager.AppSettings["ChangesScriptName"];
_InsertScriptName = ConfigurationManager.AppSettings["InsertScriptName"];
_LogFilePath = ConfigurationManager.AppSettings["LogFilePath"];
if (!Directory.Exists(_LogFilePath))
{
Directory.CreateDirectory(_LogFilePath);
}
try
{
FileUtils.DeleteOldLogs(_LogFilePath);
}
catch(Exception exception)
{
FileUtils.WriteLog(exception?.ToString(), _LogFilePath);
}
string response = null;
try
{
FileUtils.WriteLog($"Lade ContractStatuses für {_ServerName} herunter.", _LogFilePath);
response = await GetDatabaseStatuses(_ServerName);
}
catch(Exception exception)
{
FileUtils.WriteLog(exception.ToString(), _LogFilePath);
LogUtils.LogError(exception);
}
FileUtils.WriteLog("Verarbeite Serverantwort.", _LogFilePath);
await ExecuteDatabaseChanges(response);
}
/// <summary>
/// Führt die Änderungen an den Datenbanken durch bzw. das Anlegen.
/// </summary>
/// <param name="serverResponse">Die Antwort des Servers mit den Stati als Json-String.</param>
/// <returns>Der Rückgabetyp bei asynchronen Methoden ist Task, wenn er sonst void wäre.</returns>
private static async Task ExecuteDatabaseChanges(string serverResponse)
{
var contractStatusCollection = serverResponse is null ? new ContractStatusCollection() : JsonConvert.DeserializeObject<ContractStatusCollection>(serverResponse);
if(!(contractStatusCollection is null))
{
var statusCount = contractStatusCollection.ContractStatuses.Count;
FileUtils.WriteLog($"Es wurde{(statusCount > 1 ? "n" : string.Empty)} {(statusCount > 1 ? statusCount.ToString() : statusCount == 0 ? "kein" : "ein")} ContractStat{(statusCount > 1 ? "i" : "us")} gefunden.", _LogFilePath);
foreach(var contractStatusKeyValuePair in contractStatusCollection.ContractStatuses)
{
var contractStatus = contractStatusKeyValuePair.Value;
if (contractStatus.Tenant == "3614370592")
{
}
else
{
continue;
}
if (contractStatus.Activate == 1)
{
FileUtils.WriteLog($"Aktiviere/Erstelle Datenbank `{contractStatus.Tenant}`.", _LogFilePath);
await ActivateDatabase(contractStatus.Tenant, contractStatus);
}
if(contractStatus.Deactivate == 1)
{
FileUtils.WriteLog($"Deaktiviere Datenbank `{contractStatus.Tenant}`.", _LogFilePath);
ToggleConfigActivation(contractStatus, false);
}
if(contractStatus.Delete == 1)
{
FileUtils.WriteLog($"Lösche Datenbank `{contractStatus.Tenant}`. Das Löschen ist zur Zeit deaktiviert. Config-Datei und Datenbank werden nicht gelöscht!", _LogFilePath);
//DeleteDatabase(contractStatus.Tenant);
}
}
}
}
/// <summary>
/// Extrahiert die MySQL-Verbindung aus der Config-File.
/// </summary>
/// <param name="configPath">Pfad zur Config-File.</param>
/// <returns>Gibt die MySQL-Verbindung zurück.</returns>
private static string ExtractConnectionString(string configPath)
{
var connectionString = string.Empty;
try
{
if(!File.Exists(configPath))
{
return null;
}
using(var reader = XmlReader.Create(configPath))
{
while(reader.Read())
{
if(reader.NodeType == XmlNodeType.Element)
{
if(reader.GetAttribute("name") == "connection.connection_string")
{
if(XNode.ReadFrom(reader) is XElement xElement)
{
connectionString = xElement.Value.Trim();
}
}
}
}
}
}
catch(Exception exception)
{
FileUtils.WriteLog(exception.ToString(), _LogFilePath);
LogUtils.LogError(exception);
}
// Server=127.0.0.1;Password=BeWoPl@ner!;User ID=BeWoUser;Initial Catalog=chattest;Port=2206
return connectionString;
}
/// <summary>
/// Extrahiert Server, Password, User ID und Port aus dem ConnectionString der Config-Datei.
/// </summary>
/// <param name="connectionString"></param>
private static void ExtractConnectionStringParts(string connectionString)
{
var splitConnectionString = connectionString.Split(';');
foreach(var part in splitConnectionString)
{
var x = part.Split('=');
if(x.Length == 2)
{
switch(x[0])
{
case "Server":
_ConnectionStringServer = x[1];
break;
case "Password":
_ConnectionStringPassword = x[1];
break;
case "User ID":
_ConnectionStringUserID = x[1];
break;
case "Port":
_ConnectionStringPort = x[1];
break;
}
}
}
}
/// <summary>
/// Datenbank wird angelegt oder reaktiviert.
/// </summary>
/// <param name="tenant">Kundennummer der zu anlegenden oder reaktivierenden Datenbank.</param>
/// <param name="contractStatus">ContractStatus der anzulegenden oder reaktivierenden Datenbank.</param>
/// <returns>Der Rückgabetyp bei asynchronen Methoden ist Task, wenn er sonst void wäre.</returns>
private static async Task ActivateDatabase(string tenant, ContractStatus contractStatus)
{
var connectionString = $"Server={_ConnectionStringServer};Password={_ConnectionStringPassword};User ID={_ConnectionStringUserID};Port={_ConnectionStringPort}";
var isExisting = new MySqlHelper(connectionString).CheckForDatabase(tenant);
var configFilePath = Path.Combine(_MultitenancyPath, $"{tenant}.config");
// Config-Datei existiert bereits
if (File.Exists(configFilePath))
{
FileUtils.WriteLog($"Die Config-Datei '{configFilePath}' existiert. Nix muss gemacht werden", _LogFilePath);
//Nichts machen
//FileUtils.WriteLog($"Die Config-Datei '{configFilePath}' existiert.", _LogFilePath);
//connectionString = isExisting ? ExtractConnectionString(configFilePath) : connectionString;
//if(!isExisting)
//{
// RunCreateScripts(tenant, connectionString);
//}
}
// Config-Datei ist deaktiviert
else if(File.Exists(Path.Combine(_MultitenancyPath, $"{tenant}.config.deactivated")))
{
FileUtils.WriteLog($"Die Config-Datei '{tenant}.config.deactivated' existiert.", _LogFilePath);
//connectionString = isExisting ? ExtractConnectionString(configFilePath) : connectionString;
ToggleConfigActivation(contractStatus, true);
//if(!isExisting)
//{
// RunCreateScripts(tenant, connectionString);
//}
}
// Config-Datei existiert nicht
else
{
FileUtils.WriteLog($"Die Config-Datei '{configFilePath}' existiert nicht.", _LogFilePath);
RunCreateScripts(tenant, connectionString);
await SendInitialPasswordToTool(contractStatus);
}
}
/// <summary>
/// Löscht die angegebene Datenbank.
/// </summary>
/// <param name="tenant">Kundennummer, die gleichzeitig auch der Name der Datenbank ist.</param>
private static void DeleteDatabase(ContractStatus contractStatus)
{
var configFile = Path.Combine(_MultitenancyPath, $"{contractStatus.Tenant}.config.deactivated");
if(File.Exists(configFile))
{
var connectionString = ExtractConnectionString(configFile);
var mySqlHelper = new MySqlHelper(connectionString);
mySqlHelper.DeleteDatabase(contractStatus.Tenant);
File.Delete(configFile);
FileUtils.WriteLog($"Datenbank `{contractStatus.Tenant}` und {contractStatus.Tenant}.config.deactivated wurden gelöscht.", _LogFilePath);
SendDBStatusToTool(contractStatus, false, true);
}
else
{
LogUtils.LogMessage($"Die Config-Datei für Datenbank `{contractStatus.Tenant}` existiert nicht.", ConsoleColor.Red, ConsoleColor.Black);
FileUtils.WriteLog($"{contractStatus.Tenant}.config nicht gefunden. Datenbank `{contractStatus.Tenant}`, falls vorhanden, nicht gelöscht.", _LogFilePath);
}
}
/// <summary>
/// Entfernt oder fügt ".deactivated" an die Config-Datei.
/// </summary>
/// <param name="tenant">Kundennummer der zu de- bwz. reaktivierenden Datenbank.</param>
/// <param name="isToBeActivated">Ob de- oder reaktiviert werden soll.</param>
private static void ToggleConfigActivation(ContractStatus contractStatus, bool isToBeActivated)
{
var fileName1 = isToBeActivated ? $"{contractStatus.Tenant}.config.deactivated" : $"{contractStatus.Tenant}.config";
var fileName2 = isToBeActivated ? $"{contractStatus.Tenant}.config" : $"{contractStatus.Tenant}.config.deactivated";
var path1 = Path.Combine(_MultitenancyPath, fileName1);
var path2 = Path.Combine(_MultitenancyPath, fileName2);
if(File.Exists(path1) && !File.Exists(path2))
{
File.Move(path1, path2);
if(File.Exists(path1))
{
File.Delete(path1);
if (!isToBeActivated)
{
SendDBStatusToTool(contractStatus, true, false);
}
}
FileUtils.WriteLog($"{contractStatus.Tenant}.config {(isToBeActivated == false ? "de" : string.Empty)}aktiviert.", _LogFilePath);
}
else
{
FileUtils.WriteLog($"Fehler beim {(isToBeActivated == false ? "Dea" : "A")}ktivieren: {fileName1} existiert nicht oder {fileName2} existiert.", _LogFilePath);
}
}
/// <summary>
/// Erzeugt eine neue Datenbank, passt sie dem neuesten Stand an und füllt sie mit Basisdaten.
/// </summary>
/// <param name="tenant">Kundennummer der neuen Datenbank.</param>
/// <param name="connectionString">MySQL-Verbindung aus der Config-Datei.</param>
private static void RunCreateScripts(string tenant, string connectionString)
{
#if DEBUG
if (!Directory.Exists(_CreateScriptsFolderPath))
{
Directory.CreateDirectory(_CreateScriptsFolderPath);
}
if (!Directory.Exists(_MultitenancyPath))
{
Directory.CreateDirectory(_MultitenancyPath);
}
#endif
if (Directory.Exists(_CreateScriptsFolderPath) && Directory.Exists(_MultitenancyPath))
{
FileUtils.GenerateNewConfigFile(Path.Combine(_MultitenancyPath, $"{tenant}.config"), connectionString);
var createScriptPath = Path.Combine(_CreateScriptsFolderPath, _CreateScriptName);
var changesScriptPath = Path.Combine(_CreateScriptsFolderPath, _ChangesScriptName);
var insertScriptPath = Path.Combine(_CreateScriptsFolderPath, _InsertScriptName);
if (File.Exists(createScriptPath) && File.Exists(changesScriptPath) && File.Exists(insertScriptPath))
{
var mySqlHelper = new MySqlHelper(connectionString);
var isExisting = mySqlHelper.CheckForDatabase(tenant);
if(!isExisting)
{
try
{
mySqlHelper.RunScript(createScriptPath, true, tenant);
mySqlHelper.RunScript(changesScriptPath, false, tenant);
mySqlHelper.RunScript(insertScriptPath, false, tenant);
FileUtils.WriteLog($"Skripte zur Erstellung von Datenbank `{tenant}` ausgeführt und Datenbank angelegt.", _LogFilePath);
}
catch(Exception exception)
{
FileUtils.WriteLog(exception?.ToString(), _LogFilePath);
LogUtils.LogError(exception);
}
}
}
}
else
{
if(!Directory.Exists(_CreateScriptsFolderPath))
{
FileUtils.WriteLog($"Das Verzeichnis CreateScriptsFolderPath '{_CreateScriptsFolderPath}' existiert nicht.", _LogFilePath);
}
if(!Directory.Exists(_LogFilePath))
{
FileUtils.WriteLog($"Das Verzeichnis LogFilePath '{_LogFilePath}' existiert nicht.", _LogFilePath);
}
}
}
/// <summary>
/// Erzeugt und schickt das Initialpasswort für eine neue Datenbank an das Tool mit Hilfe des apiKeys.
/// </summary>
/// <param name="contractStatus">ContractStatus für den eine neue Datenbank angelegt werden soll.</param>
/// <param name="apiKey">Der für das Tool nötige API-Key.</param>
/// <returns>Der Rückgabetyp bei asynchronen Methoden ist Task, wenn er sonst void wäre.</returns>
private static async Task SendInitialPasswordToTool(ContractStatus contractStatus)
{
var mySqlHelper1 = new MySqlHelper($"Server={_ConnectionStringServer};Password={_ConnectionStringPassword};User ID={_ConnectionStringUserID};Port={_ConnectionStringPort}");
var databaseExists = false;
try
{
databaseExists = mySqlHelper1.CheckForDatabase(contractStatus.Tenant);
}
catch(Exception exception)
{
FileUtils.WriteLog(exception?.ToString(), _LogFilePath);
}
if(!(contractStatus is null) && databaseExists)
{
var password = PasswordHelper.GenerateInitialPassword(new Random());
var uri = new Uri($"https://support.bewoplaner.de/api/save_initialpw.php?APIKEY1={APIKEY}&LicenseTypeID={contractStatus.LicenseTypeId}&CustomerID={contractStatus.Tenant}&InitialPW={password}");
var request = (HttpWebRequest) WebRequest.Create(uri);
var isSendInitialPasswordToApiSuccessful = false;
FileUtils.WriteLog($"Sende Initialpasswort für Datenbank `{contractStatus.Tenant}` an API.", _LogFilePath);
try
{
using(var response = (HttpWebResponse) await request.GetResponseAsync())
{
if(response.GetResponseStream() is Stream reponseStream)
{
using(var reader = new StreamReader(reponseStream, Encoding.UTF8))
{
var text = await reader.ReadToEndAsync();
var initialPasswordResponse = JsonConvert.DeserializeObject<InitialPasswordResponse>(text);
isSendInitialPasswordToApiSuccessful = initialPasswordResponse?.IsSuccess ?? false;
}
}
}
if(isSendInitialPasswordToApiSuccessful)
{
var configFilePath = Path.Combine(_MultitenancyPath, $"{contractStatus.Tenant}.config");
if(File.Exists(configFilePath))
{
var connectionString = ExtractConnectionString(configFilePath);
var mySqlHelper = new MySqlHelper(connectionString);
var personOid = mySqlHelper.InsertPerson();
if(!(personOid is null))
{
var employeeOid = mySqlHelper.InsertEmployee(personOid.Value);
if(!(employeeOid is null))
{
var applicationUserOid = mySqlHelper.InsertApplicationUser(employeeOid.Value, password);
if(!(applicationUserOid is null))
{
mySqlHelper.LinkInitialUserToAdminRights(applicationUserOid.Value);
FileUtils.WriteLog("Benutzer in der Datenbank angelegt.", _LogFilePath);
}
}
}
}
else
{
FileUtils.WriteLog($"{configFilePath} existiert nicht.", _LogFilePath);
}
}
else
{
FileUtils.WriteLog($"Das Setzen des Initialpassworts für Datenbank `{contractStatus.Tenant}` ist fehlgeschlagen!", _LogFilePath);
}
}
catch(Exception exception)
{
FileUtils.WriteLog(exception?.ToString(), _LogFilePath);
}
}
else
{
var logMessage = $"SendInitialPasswordToTool ContractStatus ist null";
FileUtils.WriteLog(logMessage, _LogFilePath);
}
}
private static async Task SendDBStatusToTool(ContractStatus contractStatus, bool deactivated, bool deleted)
{
if (contractStatus != null)
{
String t = "";
if (deactivated)
{
t = "1";
}
if (deleted)
{
t = "2";
}
var uri = new Uri($"https://support.bewoplaner.de/api/dbstate.php?APIKEY1={APIKEY}&LicenseTypeID={contractStatus.LicenseTypeId}&CustomerID={contractStatus.Tenant}&Type={t}");
var request = (HttpWebRequest)WebRequest.Create(uri);
FileUtils.WriteLog($"Sende Status für Datenbank `{contractStatus.Tenant}` an API.", _LogFilePath);
try
{
using (var response = (HttpWebResponse)await request.GetResponseAsync())
{
if (response.GetResponseStream() is Stream reponseStream)
{
using (var reader = new StreamReader(reponseStream, Encoding.UTF8))
{
var text = await reader.ReadToEndAsync();
}
}
}
}
catch (Exception exception)
{
FileUtils.WriteLog(exception?.ToString(), _LogFilePath);
}
}
else
{
var logMessage = $"SendDBStatusToTool ContractStatus ist null";
FileUtils.WriteLog(logMessage, _LogFilePath);
}
}
/// <summary>
/// Lädt die Stati für die Datenbanken, die geändert werden sollen.
/// </summary>
/// <param name="serverName">Name des Servers, z.B. "app6.bewoplaner.de"</param>
/// <param name="debugUri">Uri für zum Testen. Wird nur im Debug-Modus benutzt</param>
/// <returns></returns>
private static async Task<string> GetDatabaseStatuses(string serverName, string debugUri = null)
{
var uri = new Uri($"https://support.bewoplaner.de/api/getcontractstate.php?ContractsState={serverName}");
var request = (HttpWebRequest) WebRequest.Create(uri);
using(var response = (HttpWebResponse) await request.GetResponseAsync())
{
using(var stream = response.GetResponseStream())
{
if(!(stream is null))
{
using(var streamReader = new StreamReader(stream))
{
var json = await streamReader.ReadToEndAsync();
return json;
}
}
return null;
}
}
}
}
}