This commit is contained in:
Drechsler, Meik
2025-10-15 14:56:07 +02:00
commit f563d78417
896 changed files with 654481 additions and 0 deletions

View File

@@ -0,0 +1,535 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using C4IT.Logging;
namespace C4IT.MsGraph
{
public class cMsGraphBase
{
public enum eHttpMethod { get, post, put, delete};
public const string constAzureLoginUrl = "https://login.microsoftonline.com/{Tenant}/oauth2/v2.0/token";
public const string constAzureAuthorizeUrl = "https://login.microsoftonline.com/{Tenant}/oauth2/v2.0/authorize";
public const string constMsGraphUrl = "https://graph.microsoft.com/{Version}/{Request}";
public const string constMsGraphApplicationGet = "applications?$filter=appId eq '{AppID}'";
public const string constMsGraphProfileGet = "me";
public bool IsOnline { get; private set; } = false;
public string AccessToken { get; private set; } = null;
public DateTime TokenExpiresIn { get; private set; } = DateTime.MinValue;
private cMsGraphLogonInfo privLogonInfo = null;
public Exception LastException { get; private set; } = null;
public string LastErrorMessage { get; private set; } = null;
public cMsGraphResultApplication Me { get; private set; } = null;
public HttpStatusCode? LastHttpErrorCode { get; private set; } = null;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ResetError()
{
LastException = null;
LastErrorMessage = null;
LastHttpErrorCode = null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetErrorException(string Action, Exception E, LogLevels lev = LogLevels.Error)
{
LastException = E;
LastErrorMessage = Action + ": " + E.Message;
cLogManager.LogEntry(Action, lev);
}
private async Task<bool> privLogonAsync(cMsGraphLogonInfo LogonInfo)
{
try
{
ResetError();
IsOnline = false;
// prepare the http login request
var postData = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("client_id", LogonInfo.ClientID),
new KeyValuePair<string, string>("scope", LogonInfo.Scope),
new KeyValuePair<string, string>("redirect_uri", $"https://login.microsoftonline.com/{LogonInfo.Tenant}/oauth2/nativeclient"),
new KeyValuePair<string, string>("grant_type", "client_credentials"),
new KeyValuePair<string, string>("client_secret", LogonInfo.ClientSecret)
};
var formData = new FormUrlEncodedContent(postData);
var strUrl = constAzureLoginUrl.Replace("{Tenant}", LogonInfo.Tenant);
var request = new HttpRequestMessage(System.Net.Http.HttpMethod.Post, strUrl)
{
Content = formData
};
// do the http login request
using (var httpClient = new HttpClient())
{
var response = await httpClient.SendAsync(request);
LastHttpErrorCode = response.StatusCode;
if (!response.IsSuccessStatusCode)
{
LastErrorMessage = $"HTTP request error {response.StatusCode} while Azure tenant login: {response.ReasonPhrase}";
return false;
}
// get the response content & the access token
var content = await response.Content.ReadAsStringAsync();
dynamic iToken = JsonConvert.DeserializeObject(content);
AccessToken = iToken.access_token;
int ExpiresIn = iToken.expires_in;
TokenExpiresIn = DateTime.UtcNow + TimeSpan.FromSeconds(ExpiresIn * 0.7);
var TokenType = iToken.token_type;
if (TokenType != "Bearer")
return false;
}
if (AccessToken != null)
{
IsOnline = true;
Me = await RequestApplicationAsync(LogonInfo.ClientID);
return true;
}
}
catch (Exception E)
{
SetErrorException("HTTP exception error while Azure tenant login", E, LogLevels.Debug);
cLogManager.LogException(E, LogLevels.Debug);
}
return false;
}
private async Task<bool> privRelogon()
{
if (DateTime.UtcNow < TokenExpiresIn)
return true;
if (privLogonInfo == null)
return false;
var RetVal = await privLogonAsync(privLogonInfo);
return RetVal;
}
public async Task<bool> LogonAsync(cMsGraphLogonInfo LogonInfo)
{
var RetVal = await privLogonAsync(LogonInfo);
if (RetVal == true)
privLogonInfo = LogonInfo;
return RetVal;
}
private async Task<dynamic> privRequestAsync(string Url, eHttpMethod httpMethod = eHttpMethod.get, object JsonData = null, bool retryForbidden = false)
{
ResetError();
try
{
await RequestCriticalSection.Semaphore.WaitAsync();
if (!IsOnline)
return null;
if (!await privRelogon())
return null;
var retryCount = 0;
while (true)
{
// create the request
using (var handler = new HttpClientHandler())
{
using (var client = new HttpClient(handler))
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", AccessToken);
HttpResponseMessage response;
StringContent CO = null;
if (JsonData != null)
{
var strJson = JsonConvert.SerializeObject(JsonData);
CO = new StringContent(strJson, Encoding.UTF8, "application/json");
}
switch (httpMethod)
{
case eHttpMethod.get:
response = await client.GetAsync(Url);
break;
case eHttpMethod.post:
response = await client.PostAsync(Url, CO);
break;
case eHttpMethod.delete:
response = await client.DeleteAsync(Url);
break;
case eHttpMethod.put:
response = await client.PutAsync(Url, CO);
break;
default:
return null;
}
LastHttpErrorCode = response.StatusCode;
if (!response.IsSuccessStatusCode)
{
if (retryForbidden && (LastHttpErrorCode == HttpStatusCode.Forbidden) && (retryCount < 2))
{
retryCount++;
await (Task.Delay(100));
}
else
{
LastErrorMessage = $"HTTP request error {(int)response.StatusCode} while MS Graph request '{Url}': {response.ReasonPhrase}";
if (cLogManager.DefaultLogger.IsDebug)
cLogManager.DefaultLogger.LogEntry(LogLevels.Warning, LastErrorMessage);
return null;
}
}
else
{
if (response.StatusCode == HttpStatusCode.NoContent)
return true;
var content = await response.Content.ReadAsStringAsync();
dynamic jsonData = JsonConvert.DeserializeObject(content); ;
return jsonData;
}
}
}
}
}
catch (Exception E)
{
SetErrorException("HTTP exception error while while MS Graph request '{Request}'", E);
cLogManager.LogException(E);
}
finally
{
RequestCriticalSection.Semaphore.Release();
}
return null;
}
public async Task<cMsGraphResultBase> RequestAsync(string Request, bool UseBeta = false, eHttpMethod httpMethod = eHttpMethod.get, object JsonData = null, bool retryForbidden = false)
{
try
{
// get the request url
var strVersion = "v1.0";
if (UseBeta)
strVersion = "beta";
var strUrl = constMsGraphUrl.Replace("{Version}", strVersion);
strUrl = strUrl.Replace("{Request}", Request);
var data = await privRequestAsync(strUrl, httpMethod, JsonData, retryForbidden);
if (data == null)
return null;
if (data is bool isValid)
{
if (isValid)
return new cMsGraphResultBase(null);
else
return null;
}
var RetVal = new cMsGraphResultBase(data);
return RetVal;
}
catch (Exception E)
{
cLogManager.LogException(E);
}
return null;
}
public async Task<cMsGraphResultList> RequestListAsync(string Request, bool UseBeta = false, bool loadPaged = false, bool retryForbidden = false)
{
try
{
// get the request url
var strVersion = "v1.0";
if (UseBeta)
strVersion = "beta";
var strUrl = constMsGraphUrl.Replace("{Version}", strVersion);
strUrl = strUrl.Replace("{Request}", Request);
var res = await privRequestAsync(strUrl, retryForbidden: retryForbidden);
if (res != null)
{
var RetVal = new cMsGraphResultList(retryForbidden);
RetVal.AddResult(res);
if (!RetVal.hasRemaining || loadPaged)
return RetVal;
while (RetVal.hasRemaining)
{
if (!await RequestNextAsync(RetVal))
return RetVal;
}
return RetVal;
}
}
catch (Exception E)
{
cLogManager.DefaultLogger.LogException(E);
}
return null;
}
public async Task<bool> RequestNextAsync(cMsGraphResultList List)
{
try
{
var res = await privRequestAsync(List.NextResultUrl, retryForbidden: List.retryForbidden);
if (res != null)
{
List.AddResult(res);
return true;
}
}
catch (Exception E)
{
cLogManager.DefaultLogger.LogException(E);
}
return false;
}
public async Task<cMsGraphResultUser> RequestProfileAsync()
{
ResetError();
try
{
var Result = await RequestAsync(constMsGraphProfileGet);
if (Result != null)
return new cMsGraphResultUser(Result);
}
catch (Exception E)
{
cLogManager.LogException(E);
}
return null;
}
public async Task<cMsGraphResultApplication> RequestApplicationAsync(string AppID)
{
ResetError();
try
{
var strRequest = constMsGraphApplicationGet.Replace("{AppID}", AppID);
var Result = await RequestAsync(strRequest, retryForbidden: true);
if (Result != null)
{
dynamic lst = Result.Result.value;
if (Result.Result.value.Count >= 1)
{
dynamic app = Result.Result.value[0];
var RetVal = new cMsGraphResultBase(app);
return new cMsGraphResultApplication(RetVal);
}
return new cMsGraphResultApplication(Result);
}
}
catch (Exception E)
{
cLogManager.LogException(E);
}
return null;
}
}
public class cMsGraphLogonInfo
{
public string Tenant;
public string ClientID;
public string ClientSecret;
public string Scope = "https://graph.microsoft.com/.default";
}
public static class RequestCriticalSection
{
public static SemaphoreSlim Semaphore = new SemaphoreSlim(1, 1);
}
public class cMsGraphResultBase
{
public string ID { get; private set; } = null;
public string ODataId { get; private set; } = null;
public string DisplayName { get; private set; } = null;
public string Context { get; private set; } = null;
public dynamic Result { get; private set; } = null;
public cMsGraphResultBase(dynamic Result)
{
this.Result = Result;
try { ID = Result.id; } catch { };
try {
if (Result.TryGetValue("displayName", out JToken JT1))
DisplayName = Result.displayName;
else if (Result.TryGetValue("name", out JToken JT2))
DisplayName = Result.name;
}
catch { }
try { Context = Result["@odata.context"]; } catch { }
ODataId = GetStringFromDynamic(Result, "@odata.id");
if (string.IsNullOrEmpty(ODataId))
ODataId = @"https://graph.microsoft.com/v1.0/directoryObjects/" + ID;
}
public cMsGraphResultBase(cMsGraphResultBase Result)
{
if (Result == null)
return;
this.Result = Result.Result;
ID = Result.ID;
ODataId = Result.ODataId;
DisplayName = Result.DisplayName;
Context = Result.Context;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetStringFromDynamic(dynamic O, string ProperyName)
{
try
{
return (string)O[ProperyName];
}
catch { }
return null;
}
}
public class cMsGraphResultList : List<cMsGraphResultBase>
{
public string NextResultUrl { get; private set; } = null;
public bool retryForbidden { get; private set; } = false;
public cMsGraphResultList(bool retryForbidden)
{
this.retryForbidden = retryForbidden;
}
public bool hasRemaining
{
get
{
return !string.IsNullOrEmpty(NextResultUrl);
}
}
public void AddResult(dynamic Result)
{
try
{
NextResultUrl = null;
if (Result.TryGetValue("@odata.nextLink", out JToken JT))
{
var JO = (JValue)JT;
NextResultUrl = Result["@odata.nextLink"];
}
if (Result.TryGetValue("value", out JToken JT2))
{
foreach (dynamic Entry in Result.value)
try
{
var val = new cMsGraphResultBase(Entry);
this.Add(val);
}
catch (Exception E)
{
cLogManager.DefaultLogger.LogException(E);
}
}
}
catch (Exception E)
{
cLogManager.DefaultLogger.LogException(E);
}
}
}
public class cMsGraphResultUser : cMsGraphResultBase
{
public string UserPrincipalName { get; private set; } = null;
public string EMail { get; private set; } = null;
public string GivenName { get; private set; } = null;
public string SurName { get; private set; } = null;
public string JobTitle { get; private set; } = null;
public string OfficeLocation { get; private set; } = null;
public string PreferredLanguage { get; private set; } = null;
public cMsGraphResultUser(cMsGraphResultBase Result) : base(Result)
{
UserPrincipalName = GetStringFromDynamic(Result.Result, "userPrincipalName");
EMail = GetStringFromDynamic(Result.Result, "mail");
GivenName = GetStringFromDynamic(Result.Result, "givenName");
SurName = GetStringFromDynamic(Result.Result, "surname");
JobTitle = GetStringFromDynamic(Result.Result, "jobTitle");
OfficeLocation = GetStringFromDynamic(Result.Result, "officeLocation");
PreferredLanguage = GetStringFromDynamic(Result.Result, "preferredLanguage");
}
}
public class cMsGraphCollectionUsers : Dictionary<string, cMsGraphResultUser>
{
public cMsGraphCollectionUsers() { }
public cMsGraphCollectionUsers(int n) : base(n) { }
public void Add(cMsGraphResultUser User)
{
if (!this.ContainsKey(User.ID))
this.Add(User.ID, User);
}
}
public class cMsGraphResultApplication : cMsGraphResultBase
{
public string AppID { get; private set; } = null;
public string PublisherDomain { get; private set; } = null;
public cMsGraphResultApplication(cMsGraphResultBase Result) : base(Result)
{
try { AppID = Result.Result.appId; } catch { };
try { PublisherDomain = Result.Result.publisherDomain; } catch { };
}
}
}