Restore MsTeams legacy permission compatibility
This commit is contained in:
@@ -7,13 +7,13 @@ using System.Threading.Tasks;
|
|||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
using C4IT.Logging;
|
using C4IT.Logging;
|
||||||
using C4IT.MsGraph;
|
using C4IT.MsGraph;
|
||||||
using static C4IT.Logging.cLogManager;
|
using static C4IT.Logging.cLogManager;
|
||||||
using C4IT.Matrix42.ServerInfo;
|
using C4IT.Matrix42.ServerInfo;
|
||||||
using static C4IT.MsGraph.cMsGraphSharepoint;
|
using static C4IT.MsGraph.cMsGraphSharepoint;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace C4IT.LIAM
|
namespace C4IT.LIAM
|
||||||
{
|
{
|
||||||
@@ -28,37 +28,93 @@ namespace C4IT.LIAM
|
|||||||
|
|
||||||
public class cLiamProviderMsTeams : cLiamProviderBase
|
public class cLiamProviderMsTeams : cLiamProviderBase
|
||||||
{
|
{
|
||||||
public readonly cMsGraphSharepoint MsSharepoint = new cMsGraphSharepoint(new cMsGraphBase());
|
public readonly cMsGraphSharepoint MsSharepoint = new cMsGraphSharepoint(new cMsGraphBase());
|
||||||
public const string allowedMailNickNameCharacter = @"[^A-Za-z0-9!#$%&'*+\-/=?^_`{|}~]";
|
public const string allowedMailNickNameCharacter = @"[^A-Za-z0-9!#$%&'*+\-/=?^_`{|}~]";
|
||||||
|
|
||||||
public readonly bool WithoutPrivateChannels = true;
|
public readonly bool WithoutPrivateChannels = true;
|
||||||
|
|
||||||
private string lastErrorMessage = null;
|
private string lastErrorMessage = null;
|
||||||
|
|
||||||
private static readonly string[] RequiredGraphRoles = new[]
|
private sealed class GraphPermissionRequirement
|
||||||
{
|
{
|
||||||
"Application.Read.All",
|
public string Description { get; private set; }
|
||||||
"Channel.ReadBasic.All",
|
|
||||||
"Directory.Read.All",
|
public string[] AcceptedPermissions { get; private set; }
|
||||||
"Files.ReadWrite.All",
|
|
||||||
"Group.ReadWrite.All",
|
public GraphPermissionRequirement(string description, params string[] acceptedPermissions)
|
||||||
"GroupMember.Read.All",
|
{
|
||||||
"GroupMember.ReadWrite.All",
|
Description = description;
|
||||||
"Team.Create",
|
AcceptedPermissions = acceptedPermissions ?? new string[0];
|
||||||
"Team.ReadBasic.All",
|
}
|
||||||
"TeamSettings.Read.All",
|
}
|
||||||
"User.Read.All",
|
|
||||||
};
|
private static readonly GraphPermissionRequirement[] RequiredGraphPermissions = new[]
|
||||||
|
{
|
||||||
private void SetLastError(string message)
|
new GraphPermissionRequirement(
|
||||||
{
|
"Team lesen",
|
||||||
lastErrorMessage = string.IsNullOrWhiteSpace(message) ? null : message;
|
"Team.ReadBasic.All",
|
||||||
}
|
"Group.Read.All",
|
||||||
|
"Group.ReadWrite.All",
|
||||||
public cLiamProviderMsTeams(cLiamConfiguration LiamConfiguration, cLiamProviderData ProviderData) :
|
"Directory.Read.All",
|
||||||
base(LiamConfiguration, ProviderData)
|
"Directory.ReadWrite.All"),
|
||||||
{
|
new GraphPermissionRequirement(
|
||||||
WithoutPrivateChannels = AdditionalConfiguration.ContainsKey("WithoutPrivateChannels") ? AdditionalConfiguration["WithoutPrivateChannels"].ToLower() == "true" || AdditionalConfiguration["WithoutPrivateChannels"] == "1" : false;
|
"Channels lesen",
|
||||||
|
"Channel.ReadBasic.All",
|
||||||
|
"ChannelSettings.Read.All",
|
||||||
|
"ChannelSettings.ReadWrite.All",
|
||||||
|
"Group.Read.All",
|
||||||
|
"Group.ReadWrite.All",
|
||||||
|
"Directory.Read.All",
|
||||||
|
"Directory.ReadWrite.All"),
|
||||||
|
new GraphPermissionRequirement(
|
||||||
|
"Dateien lesen und schreiben",
|
||||||
|
"Files.ReadWrite.All",
|
||||||
|
"Sites.ReadWrite.All",
|
||||||
|
"Sites.FullControl.All"),
|
||||||
|
new GraphPermissionRequirement(
|
||||||
|
"Benutzer lesen",
|
||||||
|
"User.Read.All",
|
||||||
|
"User.ReadWrite.All",
|
||||||
|
"Directory.Read.All",
|
||||||
|
"Directory.ReadWrite.All"),
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly GraphPermissionRequirement[] CloneBaseGraphPermissions = new[]
|
||||||
|
{
|
||||||
|
new GraphPermissionRequirement("Teams klonen", "Team.Create"),
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly GraphPermissionRequirement[] CloneAppsGraphPermissions = new[]
|
||||||
|
{
|
||||||
|
new GraphPermissionRequirement("Apps mitklonen", "Application.Read.All", "Application.ReadWrite.All"),
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly GraphPermissionRequirement[] CloneSettingsGraphPermissions = new[]
|
||||||
|
{
|
||||||
|
new GraphPermissionRequirement("Team-Einstellungen mitklonen", "TeamSettings.Read.All", "TeamSettings.ReadWrite.All"),
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly GraphPermissionRequirement[] CloneMemberGraphPermissions = new[]
|
||||||
|
{
|
||||||
|
new GraphPermissionRequirement(
|
||||||
|
"Mitglieder mitklonen",
|
||||||
|
"GroupMember.Read.All",
|
||||||
|
"GroupMember.ReadWrite.All",
|
||||||
|
"Group.Read.All",
|
||||||
|
"Group.ReadWrite.All",
|
||||||
|
"Directory.Read.All",
|
||||||
|
"Directory.ReadWrite.All"),
|
||||||
|
};
|
||||||
|
|
||||||
|
private void SetLastError(string message)
|
||||||
|
{
|
||||||
|
lastErrorMessage = string.IsNullOrWhiteSpace(message) ? null : message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public cLiamProviderMsTeams(cLiamConfiguration LiamConfiguration, cLiamProviderData ProviderData) :
|
||||||
|
base(LiamConfiguration, ProviderData)
|
||||||
|
{
|
||||||
|
WithoutPrivateChannels = AdditionalConfiguration.ContainsKey("WithoutPrivateChannels") ? AdditionalConfiguration["WithoutPrivateChannels"].ToLower() == "true" || AdditionalConfiguration["WithoutPrivateChannels"] == "1" : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<bool> LogonAsync()
|
public override async Task<bool> LogonAsync()
|
||||||
@@ -68,90 +124,90 @@ namespace C4IT.LIAM
|
|||||||
|
|
||||||
public async Task<bool> LogonAsync(bool force = false)
|
public async Task<bool> LogonAsync(bool force = false)
|
||||||
{
|
{
|
||||||
if (!cC4ITLicenseM42ESM.Instance.IsValid || !cC4ITLicenseM42ESM.Instance.Modules.ContainsKey(LiamInitializer.msTeamsModuleId))
|
if (!cC4ITLicenseM42ESM.Instance.IsValid || !cC4ITLicenseM42ESM.Instance.Modules.ContainsKey(LiamInitializer.msTeamsModuleId))
|
||||||
{
|
{
|
||||||
LogEntry($"Error: License not valid", LogLevels.Error);
|
LogEntry($"Error: License not valid", LogLevels.Error);
|
||||||
SetLastError("License not valid");
|
SetLastError("License not valid");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!force && this.MsSharepoint.Base.IsOnline)
|
if (!force && this.MsSharepoint.Base.IsOnline)
|
||||||
{
|
{
|
||||||
if (!EnsureGraphPermissions(MsSharepoint.Base?.AccessToken))
|
if (!EnsureGraphPermissions(MsSharepoint.Base?.AccessToken))
|
||||||
return false;
|
return false;
|
||||||
SetLastError(null);
|
SetLastError(null);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var CM = MethodBase.GetCurrentMethod();
|
var CM = MethodBase.GetCurrentMethod();
|
||||||
LogMethodBegin(CM);
|
LogMethodBegin(CM);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var LI = new cMsGraphLogonInfo() {
|
var LI = new cMsGraphLogonInfo() {
|
||||||
Tenant = this.Domain,
|
Tenant = this.Domain,
|
||||||
ClientID = this.Credential?.Identification,
|
ClientID = this.Credential?.Identification,
|
||||||
ClientSecret = this.Credential?.Secret
|
ClientSecret = this.Credential?.Secret
|
||||||
};
|
};
|
||||||
var RetVal = await MsSharepoint.Base.LogonAsync(LI);
|
var RetVal = await MsSharepoint.Base.LogonAsync(LI);
|
||||||
if (!RetVal)
|
if (!RetVal)
|
||||||
{
|
{
|
||||||
SetLastError(MsSharepoint.Base?.LastErrorMessage ?? "MsTeams Logon fehlgeschlagen");
|
SetLastError(MsSharepoint.Base?.LastErrorMessage ?? "MsTeams Logon fehlgeschlagen");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!EnsureGraphPermissions(MsSharepoint.Base?.AccessToken))
|
if (!EnsureGraphPermissions(MsSharepoint.Base?.AccessToken))
|
||||||
return false;
|
return false;
|
||||||
SetLastError(null);
|
SetLastError(null);
|
||||||
return RetVal;
|
return RetVal;
|
||||||
}
|
}
|
||||||
catch (Exception E)
|
catch (Exception E)
|
||||||
{
|
{
|
||||||
LogException(E);
|
LogException(E);
|
||||||
SetLastError(E.Message);
|
SetLastError(E.Message);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
LogMethodEnd(CM);
|
LogMethodEnd(CM);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(lastErrorMessage))
|
if (string.IsNullOrWhiteSpace(lastErrorMessage))
|
||||||
SetLastError("MsTeams Logon fehlgeschlagen");
|
SetLastError("MsTeams Logon fehlgeschlagen");
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<List<cLiamDataAreaBase>> getDataAreasAsync(int Depth = -1)
|
public override async Task<List<cLiamDataAreaBase>> getDataAreasAsync(int Depth = -1)
|
||||||
{
|
{
|
||||||
var CM = MethodBase.GetCurrentMethod();
|
var CM = MethodBase.GetCurrentMethod();
|
||||||
LogMethodBegin(CM);
|
LogMethodBegin(CM);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!await LogonAsync())
|
if (!await LogonAsync())
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var DataAreas = new List<cLiamDataAreaBase>();
|
var DataAreas = new List<cLiamDataAreaBase>();
|
||||||
|
|
||||||
var DAL = await MsSharepoint.RequestTeamsListAsync();
|
var DAL = await MsSharepoint.RequestTeamsListAsync();
|
||||||
if (DAL == null)
|
if (DAL == null)
|
||||||
{
|
{
|
||||||
SetLastError(MsSharepoint.Base?.LastErrorMessage ?? "Konnte Teams-Liste nicht abrufen");
|
SetLastError(MsSharepoint.Base?.LastErrorMessage ?? "Konnte Teams-Liste nicht abrufen");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var Entry in DAL)
|
foreach (var Entry in DAL)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(this.DataAreaRegEx) && !Regex.Match(Entry.Key, this.DataAreaRegEx).Success)
|
if (!string.IsNullOrEmpty(this.DataAreaRegEx) && !Regex.Match(Entry.Key, this.DataAreaRegEx).Success)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
var MsTeam = await MsSharepoint.RequestGroupInfoAsync(Entry.Value);
|
||||||
|
if (MsTeam == null)
|
||||||
|
{
|
||||||
|
SetLastError(MsSharepoint.Base?.LastErrorMessage ?? $"Konnte Team-Informationen für '{Entry.Key}' nicht abrufen");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var Team = new cLiamMsTeamsTeam(this, MsTeam);
|
||||||
|
DataAreas.Add(Team);
|
||||||
|
}
|
||||||
|
|
||||||
var MsTeam = await MsSharepoint.RequestGroupInfoAsync(Entry.Value);
|
|
||||||
if (MsTeam == null)
|
|
||||||
{
|
|
||||||
SetLastError(MsSharepoint.Base?.LastErrorMessage ?? $"Konnte Team-Informationen für '{Entry.Key}' nicht abrufen");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var Team = new cLiamMsTeamsTeam(this, MsTeam);
|
|
||||||
DataAreas.Add(Team);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Depth > 0)
|
if (Depth > 0)
|
||||||
{
|
{
|
||||||
var allChilds = new List<cLiamDataAreaBase>();
|
var allChilds = new List<cLiamDataAreaBase>();
|
||||||
@@ -161,97 +217,142 @@ namespace C4IT.LIAM
|
|||||||
if (entryChilds != null && entryChilds.Count > 0)
|
if (entryChilds != null && entryChilds.Count > 0)
|
||||||
allChilds.AddRange(entryChilds);
|
allChilds.AddRange(entryChilds);
|
||||||
}
|
}
|
||||||
DataAreas.AddRange(allChilds);
|
DataAreas.AddRange(allChilds);
|
||||||
}
|
}
|
||||||
|
|
||||||
SetLastError(null);
|
SetLastError(null);
|
||||||
return DataAreas;
|
return DataAreas;
|
||||||
}
|
}
|
||||||
catch (Exception E)
|
catch (Exception E)
|
||||||
{
|
{
|
||||||
LogException(E);
|
LogException(E);
|
||||||
SetLastError(E.Message);
|
SetLastError(E.Message);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
LogMethodEnd(CM);
|
LogMethodEnd(CM);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool EnsureGraphPermissions(string accessToken)
|
private bool EnsureGraphPermissions(string accessToken)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(accessToken))
|
return EnsureGraphPermissions(accessToken, RequiredGraphPermissions, null);
|
||||||
{
|
}
|
||||||
SetLastError("Kein Access Token für Berechtigungsprüfung verfügbar");
|
|
||||||
return false;
|
private bool EnsureClonePermissions(string accessToken, int partsToClone)
|
||||||
}
|
{
|
||||||
|
var requirements = new List<GraphPermissionRequirement>(CloneBaseGraphPermissions);
|
||||||
try
|
var cloneParts = (CloneTeamRequest.ClonableTeamParts)partsToClone;
|
||||||
{
|
|
||||||
var parts = accessToken.Split('.');
|
if (cloneParts.HasFlag(CloneTeamRequest.ClonableTeamParts.Apps))
|
||||||
if (parts.Length < 2)
|
requirements.AddRange(CloneAppsGraphPermissions);
|
||||||
{
|
if (cloneParts.HasFlag(CloneTeamRequest.ClonableTeamParts.Settings))
|
||||||
SetLastError("Ungültiges Access Token");
|
requirements.AddRange(CloneSettingsGraphPermissions);
|
||||||
return false;
|
if (cloneParts.HasFlag(CloneTeamRequest.ClonableTeamParts.Members))
|
||||||
}
|
requirements.AddRange(CloneMemberGraphPermissions);
|
||||||
|
|
||||||
var payload = parts[1].Replace('-', '+').Replace('_', '/');
|
return EnsureGraphPermissions(accessToken, requirements, "Team-Klonen");
|
||||||
switch (payload.Length % 4)
|
}
|
||||||
{
|
|
||||||
case 2: payload += "=="; break;
|
private bool EnsureGraphPermissions(string accessToken, IEnumerable<GraphPermissionRequirement> requirements, string operationName)
|
||||||
case 3: payload += "="; break;
|
{
|
||||||
}
|
if (!TryGetGrantedGraphPermissions(accessToken, out var granted, out var errorMessage))
|
||||||
|
{
|
||||||
var payloadBytes = Convert.FromBase64String(payload);
|
SetLastError(errorMessage);
|
||||||
var payloadJson = Encoding.UTF8.GetString(payloadBytes);
|
return false;
|
||||||
var payloadObj = JsonConvert.DeserializeObject<JObject>(payloadJson);
|
}
|
||||||
if (payloadObj == null)
|
|
||||||
{
|
var missing = requirements
|
||||||
SetLastError("Token-Payload konnte nicht gelesen werden");
|
.Where(requirement => requirement.AcceptedPermissions == null || !requirement.AcceptedPermissions.Any(granted.Contains))
|
||||||
return false;
|
.Select(requirement => $"{requirement.Description} ({string.Join(" / ", requirement.AcceptedPermissions)})")
|
||||||
}
|
.ToList();
|
||||||
|
|
||||||
var granted = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
if (missing.Count > 0)
|
||||||
|
{
|
||||||
if (payloadObj.TryGetValue("roles", out var rolesToken) && rolesToken is JArray roleArray)
|
var prefix = string.IsNullOrWhiteSpace(operationName)
|
||||||
{
|
? "Fehlende Graph-Berechtigungen: "
|
||||||
foreach (var role in roleArray.Values<string>())
|
: $"Fehlende Graph-Berechtigungen für {operationName}: ";
|
||||||
{
|
SetLastError(prefix + string.Join(", ", missing));
|
||||||
if (!string.IsNullOrWhiteSpace(role))
|
return false;
|
||||||
granted.Add(role);
|
}
|
||||||
}
|
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
if (!granted.Any() && payloadObj.TryGetValue("scp", out var scopeToken))
|
|
||||||
{
|
private bool TryGetGrantedGraphPermissions(string accessToken, out HashSet<string> granted, out string errorMessage)
|
||||||
var scopes = scopeToken.Value<string>() ?? string.Empty;
|
{
|
||||||
foreach (var scope in scopes.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries))
|
granted = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
granted.Add(scope);
|
errorMessage = null;
|
||||||
}
|
|
||||||
|
if (string.IsNullOrWhiteSpace(accessToken))
|
||||||
var missing = RequiredGraphRoles.Where(required => !granted.Contains(required)).ToList();
|
{
|
||||||
if (missing.Count > 0)
|
errorMessage = "Kein Access Token für Berechtigungsprüfung verfügbar";
|
||||||
{
|
return false;
|
||||||
SetLastError("Fehlende Graph-Berechtigungen: " + string.Join(", ", missing));
|
}
|
||||||
return false;
|
|
||||||
}
|
try
|
||||||
|
{
|
||||||
return true;
|
var parts = accessToken.Split('.');
|
||||||
}
|
if (parts.Length < 2)
|
||||||
catch (Exception ex)
|
{
|
||||||
{
|
errorMessage = "Ungültiges Access Token";
|
||||||
SetLastError("Berechtigungsprüfung fehlgeschlagen: " + ex.Message);
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
}
|
var payload = parts[1].Replace('-', '+').Replace('_', '/');
|
||||||
|
switch (payload.Length % 4)
|
||||||
public override async Task<cLiamDataAreaBase> LoadDataArea(string UID)
|
{
|
||||||
{
|
case 2: payload += "=="; break;
|
||||||
var CM = MethodBase.GetCurrentMethod();
|
case 3: payload += "="; break;
|
||||||
LogMethodBegin(CM);
|
}
|
||||||
try
|
|
||||||
|
var payloadBytes = Convert.FromBase64String(payload);
|
||||||
|
var payloadJson = Encoding.UTF8.GetString(payloadBytes);
|
||||||
|
var payloadObj = JsonConvert.DeserializeObject<JObject>(payloadJson);
|
||||||
|
if (payloadObj == null)
|
||||||
|
{
|
||||||
|
errorMessage = "Token-Payload konnte nicht gelesen werden";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payloadObj.TryGetValue("roles", out var rolesToken) && rolesToken is JArray roleArray)
|
||||||
|
{
|
||||||
|
foreach (var role in roleArray.Values<string>())
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(role))
|
||||||
|
granted.Add(role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!granted.Any() && payloadObj.TryGetValue("scp", out var scopeToken))
|
||||||
|
{
|
||||||
|
var scopes = scopeToken.Value<string>() ?? string.Empty;
|
||||||
|
foreach (var scope in scopes.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries))
|
||||||
|
granted.Add(scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!granted.Any())
|
||||||
|
{
|
||||||
|
errorMessage = "Keine Graph-Berechtigungen im Access Token gefunden";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
errorMessage = "Berechtigungsprüfung fehlgeschlagen: " + ex.Message;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<cLiamDataAreaBase> LoadDataArea(string UID)
|
||||||
|
{
|
||||||
|
var CM = MethodBase.GetCurrentMethod();
|
||||||
|
LogMethodBegin(CM);
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (!cC4ITLicenseM42ESM.Instance.IsValid || !cC4ITLicenseM42ESM.Instance.Modules.ContainsKey(LiamInitializer.msTeamsModuleId))
|
if (!cC4ITLicenseM42ESM.Instance.IsValid || !cC4ITLicenseM42ESM.Instance.Modules.ContainsKey(LiamInitializer.msTeamsModuleId))
|
||||||
{
|
{
|
||||||
@@ -340,15 +441,20 @@ namespace C4IT.LIAM
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string GetLastErrorMessage()
|
public override string GetLastErrorMessage()
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(lastErrorMessage))
|
if (!string.IsNullOrWhiteSpace(lastErrorMessage))
|
||||||
return lastErrorMessage;
|
return lastErrorMessage;
|
||||||
return MsSharepoint?.Base?.LastErrorMessage ?? string.Empty;
|
return MsSharepoint?.Base?.LastErrorMessage ?? string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<cMsGraphResultBase> cloneTeam(string teamId, string name, string description, int visibility, int partsToClone, string additionalMembers, string additionalOwners)
|
public async Task<cMsGraphResultBase> cloneTeam(string teamId, string name, string description, int visibility, int partsToClone, string additionalMembers, string additionalOwners)
|
||||||
{
|
{
|
||||||
|
if (!await LogonAsync())
|
||||||
|
return null;
|
||||||
|
if (!EnsureClonePermissions(MsSharepoint.Base?.AccessToken, partsToClone))
|
||||||
|
return null;
|
||||||
|
|
||||||
var request = new CloneTeamRequest()
|
var request = new CloneTeamRequest()
|
||||||
{
|
{
|
||||||
DisplayName = name,
|
DisplayName = name,
|
||||||
|
|||||||
Reference in New Issue
Block a user