using System;
using System.Net.Http;
using System.Text;
namespace Deploymentcenter.Client
{
///
/// Zugangsdaten fuer die Release-Ablage.
///
/// Die Verzeichnisse unter /releases/ liegen hinter HTTP-Basic-Auth. Wer
/// herunterladen darf, entscheidet der Lizenzschluessel: Eine Anwendung
/// mit gueltiger Lizenz versorgt sich damit selbst weiter, ohne dass
/// zusaetzliche Geheimnisse verteilt werden muessten. Zuvor war die Ablage
/// offen - und damit auch fuer jeden im Internet herunterladbar.
///
/// Bei einer Erstinstallation gibt es noch keinen Schluessel; dort treten
/// die Zugangsdaten des Installationskontos an seine Stelle.
///
public sealed class ReleaseCredentials
{
private ReleaseCredentials(string user, string password)
{
User = user;
Password = password;
}
public string User { get; }
public string Password { get; }
///
/// Zugang ueber den Lizenzschluessel.
///
/// Der Benutzername wird aus dem Schluessel abgeleitet, das Passwort
/// ist der Schluessel selbst. Der Grund liegt im htpasswd-Format:
/// gehasht wird dort nur die Passwortspalte. Stuende der Schluessel
/// auch als Benutzername in der Datei, waere sie eine vollstaendige
/// Klartext-Kundenliste und der Hash daneben blosse Dekoration.
///
/// MUSS zeichengenau mit ReleaseGuard::licenseUsername() auf dem
/// Server uebereinstimmen.
///
public static ReleaseCredentials? FromLicenseKey(string? licenseKey)
{
string key = (licenseKey ?? string.Empty).Trim();
if (key.Length == 0)
return null;
return new ReleaseCredentials(UsernameForLicenseKey(key), key);
}
///
/// Ableitung des Benutzernamens: "lic_" plus die ersten 16 Hexzeichen
/// des SHA-256 ueber den Schluessel.
///
public static string UsernameForLicenseKey(string licenseKey)
{
using var sha256 = System.Security.Cryptography.SHA256.Create();
byte[] hash = sha256.ComputeHash(Encoding.UTF8.GetBytes((licenseKey ?? string.Empty).Trim()));
var builder = new StringBuilder("lic_", 20);
for (int i = 0; i < 8; i++)
{
builder.Append(hash[i].ToString("x2"));
}
return builder.ToString();
}
/// Zugang ueber ein Installationskonto.
public static ReleaseCredentials? FromUser(string? user, string? password)
{
string u = (user ?? string.Empty).Trim();
string p = password ?? string.Empty;
return u.Length == 0 ? null : new ReleaseCredentials(u, p);
}
/// Wert fuer den Authorization-Header.
public string ToHeaderValue()
{
string raw = User + ":" + Password;
return "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(raw));
}
///
/// Haengt den Header an eine Anfrage. Ohne Zugangsdaten passiert
/// nichts - eine noch ungeschuetzte Ablage bleibt damit erreichbar.
///
public static void Apply(HttpRequestMessage request, ReleaseCredentials? credentials)
{
if (credentials == null || request == null)
return;
// Nicht doppelt setzen, falls der Aufrufer schon etwas mitgibt.
if (request.Headers.Contains("Authorization"))
return;
request.Headers.TryAddWithoutValidation("Authorization", credentials.ToHeaderValue());
}
///
/// Erklaerung fuer den Menschen davor, wenn der Server 401 antwortet.
/// Ohne diesen Hinweis sieht ein abgelaufener Vertrag aus wie ein
/// Netzwerkfehler, und man sucht an der falschen Stelle.
///
public static string DescribeUnauthorized(ReleaseCredentials? credentials)
{
if (credentials == null)
{
return "Die Release-Ablage verlangt Zugangsdaten, es wurden aber keine mitgegeben. "
+ "Erwartet wird der Lizenzschluessel dieser Installation.";
}
return "Die Release-Ablage hat die Zugangsdaten abgelehnt. Moegliche Gruende: die Lizenz "
+ "ist abgelaufen, wurde widerrufen oder gehoert zu einem anderen Produkt.";
}
}
}