TeamsNetphoneLink/TeamsNetphoneLinkWPF/Communication/TeamsGraph.cs
2025-03-25 22:43:13 +01:00

290 lines
11 KiB
C#

using Azure.Core;
using Azure.Identity;
using Azure.Identity.Broker;
using Microsoft.Graph;
using Microsoft.Graph.Drives.Item.Items.Item.Workbook.Functions.Cosh;
using Microsoft.Graph.Me.Presence.SetUserPreferredPresence;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Broker;
using Microsoft.Identity.Client.Extensions.Msal;
using System.Net.Http.Headers;
using System.Net.Http;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows;
using TeamsLocalLibary;
using static System.Formats.Asn1.AsnWriter;
using Microsoft.Identity.Client.NativeInterop;
using TeamsNetphoneLink.WPF;
using TeamsNetphoneLink.Communication;
using System;
using System.Text.RegularExpressions;
namespace TeamsNetphoneLink.Teams
{
public delegate void PresenceStausEventHandler(Microsoft.Graph.Models.Presence presence);
public class TeamsGraph : TeamsGraphEventHandlers
{
private GraphServiceClient graphClient;
public bool Authenticated { get; private set; }
private Timer presenceStatusTimer;
private Microsoft.Graph.Models.Presence lastPresence;
public void CheckPresenceStatusTimer()
{
presenceStatusTimer = new Timer(CheckPresenceStatus, null, Timeout.Infinite, 2000);
presenceStatusTimer.Change(0, 1000);
}
public void CheckPresenceStatus(object state)
{
if (graphClient is not null && Authenticated)
{
try
{
var presence = graphClient.Me.Presence.GetAsync().GetAwaiter().GetResult();
// If lastPresence is null, initialize it with the current presence
if (lastPresence == null)
{
lastPresence = presence;
}
// Check if Availability has changed
if (lastPresence.Availability != presence.Availability)
{
OnAvailabilityChanged(presence);
}
// Check if Activity has changed
if (lastPresence.Activity != presence.Activity)
{
OnActivityChanged(presence);
}
// Update lastPresence to the current presence
lastPresence = presence;
}
catch (Exception ex)
{
Console.WriteLine($"Error checking presence state: {ex.Message}");
}
}
}
public async Task<bool> IsCachedAccounts()
{
IPublicClientApplication app = PublicClientApplicationBuilder.Create(Settings.Default.AppID)
.WithDefaultRedirectUri()
.WithAuthority(String.Format("https://login.microsoftonline.com/{0}", Settings.Default.TenantID))
.Build();
// Register MSAL cache
var storage = new StorageCreationPropertiesBuilder("teamsnetphonelink.msal.cache", MsalCacheHelper.UserRootDirectory).Build();
var cacheHelper = await MsalCacheHelper.CreateAsync(storage);
cacheHelper.RegisterCache(app.UserTokenCache);
IEnumerable<IAccount> accounts = await app.GetAccountsAsync();
return accounts.Any();
}
// Authentifizierungsmethode mit Azure Identity
public async Task<bool> AuthenticateAsync(bool clearCache = false, bool silent = true)
{
try
{
// Check if caching is enabled in settings
bool useCache = Settings.Default.SaveEntraCredentials;
IPublicClientApplication app = PublicClientApplicationBuilder.Create(Settings.Default.AppID)
.WithDefaultRedirectUri()
.WithAuthority(String.Format("https://login.microsoftonline.com/{0}", Settings.Default.TenantID))
.Build();
// Register MSAL cache only if caching is enabled
if (useCache)
{
var storage = new StorageCreationPropertiesBuilder("teamsnetphonelink.msal.cache", MsalCacheHelper.UserRootDirectory).Build();
var cacheHelper = await MsalCacheHelper.CreateAsync(storage);
cacheHelper.RegisterCache(app.UserTokenCache);
}
IEnumerable<IAccount> accounts = await app.GetAccountsAsync();
//Alle Accounts aus dem Cache entfernen wenn clearCache gesetzt ist
if (clearCache)
{
foreach(var account in accounts)
await app.RemoveAsync(account);
}
// Try to use the previously signed-in account from the cache
var existingAccount = accounts.FirstOrDefault();
AuthenticationResult authentication;
if (existingAccount is not null && useCache && !clearCache)
{
Console.WriteLine("Attempting to acquire token silently using cached account.");
try
{
authentication = await app.AcquireTokenSilent(new[] { "Presence.ReadWrite", "offline_access" }, existingAccount)
.ExecuteAsync();
await InitializeGraphClient(authentication.AccessToken);
return Authenticated;
}
catch (MsalUiRequiredException)
{
Console.WriteLine("Silent token acquisition failed. Falling back to interactive authentication.");
}
}
// If no cached account or silent authentication fails, prompt the user for authentication
Console.WriteLine("Prompting user for authentication.");
authentication = await app.AcquireTokenInteractive(new[] { "Presence.ReadWrite", "offline_access" })
.ExecuteAsync();
await InitializeGraphClient(authentication.AccessToken);
return Authenticated;
}
catch (Exception ex)
{
ExceptionWindowHelper.Show(this.GetType().Name, MethodBase.GetCurrentMethod().Name, "Fehler bei der Authentifizierung", ex.Message, ex.StackTrace);
Console.WriteLine($"Error during authentication: {ex.Message}");
return Authenticated;
}
}
// Helper method to initialize GraphServiceClient
private async Task InitializeGraphClient(string accessToken)
{
graphClient = new GraphServiceClient(new HttpClient(new AuthHandler(accessToken, new HttpClientHandler())));
Authenticated = await TestAuthentication();
}
// Testung der Anmeldung und Zugriffsrechte
public async Task<bool> TestAuthentication()
{
try
{
// Test-Anfrage, um Authentifizierung zu validieren
await graphClient.Me.Presence.GetAsync();
return true; // Anmeldung und Zugriffsrechte korrekt
}
catch (Exception ex)
{
ExceptionWindowHelper.Show(this.GetType().Name, MethodBase.GetCurrentMethod().Name, "Authentifizierung fehlerhaft", ex.Message, ex.StackTrace);
return false; // Anmeldung und Zugriffsrechte felerhaft
}
}
// Methode zum Setzen des Präsenzstatus
public async Task<bool> SetPresenceAsync(PresenceState presenceState)
{
if (graphClient is null || !Authenticated)
{
Console.WriteLine("Authentifizierung nicht abgeschlossen. Präsenz kann nicht gesetzt werden.");
return false;
}
// Zuordnen des Präsenzstatus zu Verfügbarkeit und Aktivität
var presenceMap = new Dictionary<PresenceState, (string Availability, string Activity)>
{
{ PresenceState.Available, ("Available", "Available") },
{ PresenceState.Busy, ("Busy", "Busy") },
{ PresenceState.DoNotDisturb, ("DoNotDisturb", "DoNotDisturb") },
{ PresenceState.BeRightBack, ("BeRightBack", "BeRightBack") },
{ PresenceState.Away, ("Away", "Away") },
{ PresenceState.Offline, ("Offline", "OffWork") }
};
if (!presenceMap.ContainsKey(presenceState))
{
Console.WriteLine("Ungültiger Präsenzstatus.");
ExceptionWindowHelper.Show(this.GetType().Name, MethodBase.GetCurrentMethod().Name, "Ungültiger Präsenzstatus", presenceState.ToString());
return false; // Ungültiger Status
}
var (availability, activity) = presenceMap[presenceState];
var requestBody = new SetUserPreferredPresencePostRequestBody
{
Availability = availability,
Activity = activity,
ExpirationDuration = TimeSpan.FromHours(2) // Ablaufzeit der Präsenz
};
try
{
await graphClient.Me.Presence.SetUserPreferredPresence.PostAsync(requestBody);
return true; // Präsenz erfolgreich gesetzt
}
catch (Exception ex)
{
if(ex.GetType() != typeof(Microsoft.Graph.Models.ODataErrors.ODataError))
{
ExceptionWindowHelper.Show(this.GetType().Name, MethodBase.GetCurrentMethod().Name, "Fehler beim Setzen der Präsenz", ex.Message, ex.StackTrace);
}
return false; // Fehler beim Setzen der Präsenz
}
}
// Enum für die verschiedenen Präsenzstatus
public enum PresenceState
{
Available,
Busy,
DoNotDisturb,
BeRightBack,
Away,
Offline
}
private static Regex isGuid =
new Regex(@"^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$", RegexOptions.Compiled);
public static bool IsGuid(string candidate)
{
bool isValid = false;
if (candidate != null)
{
if (isGuid.IsMatch(candidate))
{
isValid = true;
}
}
return isValid;
}
}
// Custom HttpMessageHandler to inject the access token
public class AuthHandler : DelegatingHandler
{
private readonly string _accessToken;
public AuthHandler(string accessToken, HttpMessageHandler innerHandler)
: base(innerHandler)
{
_accessToken = accessToken;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// Add the access token to the request headers
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _accessToken);
return await base.SendAsync(request, cancellationToken);
}
}
}