224 lines
8.2 KiB
VB.net
224 lines
8.2 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://login.singlewindow.io/auth/realms/agsw/protocol/openid-connect/token"
|
|
|
|
|
|
|
|
' Private Shared ReadOnly TOKEN_ENDPOINT As String = "https://dev-kc.singlewindow.io/auth/realms/agsw/protocol/openid-connect/token"
|
|
|
|
|
|
' curl --location 'https://login.singlewindow.io/auth/realms/agsw/protocol/openid-connect/token' \
|
|
'--header 'Content-Type: application/x-www-form-urlencoded' \
|
|
'--data-urlencode 'grant_type=password' \
|
|
'--data-urlencode 'password=Dhub1234*' \
|
|
'--data-urlencode 'username=dhub@verag.ag' \
|
|
'--data-urlencode 'client_id=agsw-admin'
|
|
|
|
|
|
|
|
Private Shared ReadOnly CLIENT_ID As String = "agsw-admin"
|
|
|
|
' Gewünscht: Zugangsdaten in der Klasse definieren
|
|
Private Shared ReadOnly USERNAME As String = "dhub@verag.ag" '"andreas.test@test.com"
|
|
Private Shared ReadOnly PASSWORD As String = "Dhub1234*" '"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
|
|
|
|
Public Shared Sub DeleteStore()
|
|
Try
|
|
If System.IO.File.Exists(StorePath) Then System.IO.File.Delete(StorePath)
|
|
|
|
Console.WriteLine("Token-Datei gelöscht: " & StorePath)
|
|
Catch ex As Exception
|
|
Console.WriteLine("Fehler beim Löschen 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
|