Files
SDL/VERAG_PROG_ALLGEMEIN/Schnittstellen/ATEZ/RELAYHUB/cRelayHubToken.vb
2025-09-05 08:54:23 +02:00

200 lines
7.4 KiB
VB.net

Imports System
Imports System.IO
Imports System.Net.Http
Imports System.Text
Imports System.Web
Imports Newtonsoft.Json
' NuGet: Newtonsoft.Json (>= 13.x)
Public Class cRelayHubToken
' ======= KONFIG =======
Private Shared ReadOnly TOKEN_ENDPOINT As String =
"https://dev-kc.singlewindow.io/auth/realms/agsw/protocol/openid-connect/token"
Private Shared ReadOnly CLIENT_ID As String = "agsw-admin"
' Gewünscht: Zugangsdaten in der Klasse definieren
Private Shared ReadOnly USERNAME As String = "andreas.test@test.com"
Private Shared ReadOnly PASSWORD As String = "Password.123"
' Token-File pro Benutzer unter %AppData%
Private Shared ReadOnly StorePath As String =
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"RelayHub", "token.json")
' Sicherheitspuffer, bevor wir erneuern (Sekunden)
Private Const ExpirySkewSeconds As Integer = 60
' ======= ÖFFENTLICHE API =======
''' <summary>
''' Liefert einen gültigen Access Token (nie Leerstring).
''' </summary>
Public Shared Function GetAccessToken() As String
Dim store = LoadStore()
' 1) Wenn wir einen (noch) gültigen Token haben
If store IsNot Nothing AndAlso Not String.IsNullOrWhiteSpace(store.AccessToken) Then
If store.ExpiresAtUtc > DateTimeOffset.UtcNow.AddSeconds(ExpirySkewSeconds) Then
Return store.AccessToken
End If
End If
' 2) Versuche Refresh, falls vorhanden
If store IsNot Nothing AndAlso Not String.IsNullOrWhiteSpace(store.RefreshToken) Then
store = TryRefresh(store.RefreshToken)
End If
' 3) Fallback: Password-Grant Login
If store Is Nothing OrElse String.IsNullOrWhiteSpace(store.AccessToken) Then
store = PasswordLogin()
End If
' Validierung
If store Is Nothing OrElse String.IsNullOrWhiteSpace(store.AccessToken) Then
Throw New ApplicationException("Konnte keinen gültigen Access Token erhalten (leer).")
End If
' Persistieren & zurück
SaveStore(store)
Return store.AccessToken
End Function
' ======= INTERNES =======
Private Shared Function PasswordLogin() As TokenStore
Dim form = New Dictionary(Of String, String) From {
{"grant_type", "password"},
{"username", USERNAME},
{"password", PASSWORD},
{"client_id", CLIENT_ID}
}
Dim resp = PostForm(form)
Dim token = ParseTokenResponse(resp)
Return token
End Function
Private Shared Function TryRefresh(refreshToken As String) As TokenStore
Try
Dim form = New Dictionary(Of String, String) From {
{"grant_type", "refresh_token"},
{"refresh_token", refreshToken},
{"client_id", CLIENT_ID}
}
Dim resp = PostForm(form)
Dim token = ParseTokenResponse(resp)
' Nur speichern, wenn ein Access Token vorhanden ist
If Not String.IsNullOrWhiteSpace(token.AccessToken) Then
SaveStore(token)
Return token
End If
Catch ex As Exception
' Ignorieren -> fällt auf PasswordLogin zurück
End Try
Return Nothing
End Function
Private Shared Function PostForm(formFields As Dictionary(Of String, String)) As String
Using client As New HttpClient()
Using content As New FormUrlEncodedContent(formFields)
Dim response = client.PostAsync(TOKEN_ENDPOINT, content).Result
Dim body = response.Content.ReadAsStringAsync().Result
If Not response.IsSuccessStatusCode Then
Throw New ApplicationException(
$"Token-Endpoint Fehler ({CInt(response.StatusCode)}): {body}")
End If
Return body
End Using
End Using
End Function
Private Shared Function ParseTokenResponse(json As String) As TokenStore
Dim r = JsonConvert.DeserializeObject(Of TokenResponse)(json)
If r Is Nothing OrElse String.IsNullOrWhiteSpace(r.access_token) Then
Throw New ApplicationException("Token-Antwort ungültig oder ohne access_token.")
End If
Dim now = DateTimeOffset.UtcNow
Dim expiresIn = If(r.expires_in <= 0, 3600, r.expires_in) ' Fallback 1h
Dim store = New TokenStore With {
.AccessToken = r.access_token.Trim(),
.RefreshToken = If(r.refresh_token, String.Empty),
.ExpiresAtUtc = now.AddSeconds(expiresIn)
}
' === Konsolen-Ausgabe ===
Console.WriteLine("== Neuer Token erhalten ==")
Console.WriteLine("Access Token: " & store.AccessToken)
Console.WriteLine("Refresh Token: " & store.RefreshToken)
Console.WriteLine("Gültig bis UTC: " & store.ExpiresAtUtc.ToString("yyyy-MM-dd HH:mm:ss"))
Return store
End Function
' ======= PERSISTENZ =======
Private Shared Function LoadStore() As TokenStore
Try
If File.Exists(StorePath) Then
Dim json = File.ReadAllText(StorePath, Encoding.UTF8)
Dim s = JsonConvert.DeserializeObject(Of TokenStore)(json)
' Ausgabe in Konsole
If s IsNot Nothing Then
Console.WriteLine("== Token aus Datei geladen ==")
Console.WriteLine("Access Token: " & (If(String.IsNullOrWhiteSpace(s.AccessToken), "<leer>", s.AccessToken)))
Console.WriteLine("Refresh Token: " & (If(String.IsNullOrWhiteSpace(s.RefreshToken), "<leer>", s.RefreshToken)))
Console.WriteLine("Gültig bis UTC: " & s.ExpiresAtUtc.ToString("yyyy-MM-dd HH:mm:ss"))
End If
Return s
Else
' Datei existiert nicht -> Info ausgeben
Console.WriteLine("Keine Token-Datei vorhanden, neuer Login erforderlich.")
End If
Catch ex As Exception
Console.WriteLine("Fehler beim Laden der Token-Datei: " & ex.Message)
' Datei defekt? -> Ignorieren, neu holen
End Try
Return Nothing
End Function
Private Shared Sub SaveStore(store As TokenStore)
Try
Dim dir = Path.GetDirectoryName(StorePath)
If Not Directory.Exists(dir) Then Directory.CreateDirectory(dir)
' Datei immer neu schreiben/überschreiben
Dim json = JsonConvert.SerializeObject(store, Formatting.Indented)
File.WriteAllText(StorePath, json, Encoding.UTF8)
Console.WriteLine("Token-Datei gespeichert: " & StorePath)
Catch ex As Exception
Console.WriteLine("Fehler beim Speichern der Token-Datei: " & ex.Message)
End Try
End Sub
' ======= DTOs =======
Private Class TokenResponse
Public Property access_token As String
Public Property expires_in As Integer
Public Property refresh_expires_in As Integer
Public Property refresh_token As String
Public Property token_type As String
Public Property scope As String
' … weitere Felder bei Bedarf
End Class
Private Class TokenStore
Public Property AccessToken As String
Public Property RefreshToken As String
Public Property ExpiresAtUtc As DateTimeOffset
End Class
End Class