290 lines
11 KiB
C#
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);
|
|
}
|
|
}
|
|
} |