Added support for defining the scope, if not using the default.
Added internal overwrite option to not rely on the general restore overwrite option.
This commit is contained in:
@@ -56,6 +56,11 @@ internal class APIHelper : IDisposable
|
||||
/// </summary>
|
||||
private readonly string? _certificatePassword;
|
||||
|
||||
/// <summary>
|
||||
/// The scope for the API requests
|
||||
/// </summary>
|
||||
private readonly string _scope;
|
||||
|
||||
/// <summary>
|
||||
/// The timeout options for the backend
|
||||
/// </summary>
|
||||
@@ -71,7 +76,8 @@ internal class APIHelper : IDisposable
|
||||
/// <param name="timeouts">The timeout options</param>
|
||||
/// <param name="certificatePath">The path to the certificate file</param>
|
||||
/// <param name="certificatePassword">The password for the certificate file</param>
|
||||
private APIHelper(HttpClient httpClient, AuthOptionsHelper.AuthOptions authOptions, string tenantId, string graphBaseUrl, TimeoutOptionsHelper.Timeouts timeouts, string? certificatePath, string? certificatePassword)
|
||||
/// <param name="scope">The scope for the API requests</param>
|
||||
private APIHelper(HttpClient httpClient, AuthOptionsHelper.AuthOptions authOptions, string tenantId, string graphBaseUrl, TimeoutOptionsHelper.Timeouts timeouts, string? certificatePath, string? certificatePassword, string scope)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_authOptions = authOptions;
|
||||
@@ -80,6 +86,7 @@ internal class APIHelper : IDisposable
|
||||
_timeouts = timeouts;
|
||||
_certificatePath = certificatePath;
|
||||
_certificatePassword = certificatePassword;
|
||||
_scope = scope;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -91,8 +98,9 @@ internal class APIHelper : IDisposable
|
||||
/// <param name="timeouts">The timeout options</param>
|
||||
/// <param name="certificatePath">The path to the certificate file</param>
|
||||
/// <param name="certificatePassword">The password for the certificate file</param>
|
||||
/// <param name="scope">The scope for the API requests</param>
|
||||
/// <returns>A new instance of the <see cref="APIHelper"/> class</returns>
|
||||
public static APIHelper Create(AuthOptionsHelper.AuthOptions authOptions, string tenantId, string graphBaseUrl, TimeoutOptionsHelper.Timeouts timeouts, string? certificatePath = null, string? certificatePassword = null)
|
||||
public static APIHelper Create(AuthOptionsHelper.AuthOptions authOptions, string tenantId, string graphBaseUrl, TimeoutOptionsHelper.Timeouts timeouts, string? certificatePath = null, string? certificatePassword = null, string scope = "https://graph.microsoft.com/.default")
|
||||
{
|
||||
var handler = new HttpClientHandler
|
||||
{
|
||||
@@ -103,7 +111,7 @@ internal class APIHelper : IDisposable
|
||||
httpClient.Timeout = Timeout.InfiniteTimeSpan;
|
||||
httpClient.DefaultRequestHeaders.Add("User-Agent", $"Duplicati/{System.Reflection.Assembly.GetExecutingAssembly().GetName().Version}");
|
||||
|
||||
return new APIHelper(httpClient, authOptions, tenantId, graphBaseUrl, timeouts, certificatePath, certificatePassword);
|
||||
return new APIHelper(httpClient, authOptions, tenantId, graphBaseUrl, timeouts, certificatePath, certificatePassword, scope);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -134,7 +142,7 @@ internal class APIHelper : IDisposable
|
||||
{
|
||||
["grant_type"] = "client_credentials",
|
||||
["client_id"] = _authOptions.Username!,
|
||||
["scope"] = $"{_graphBaseUrl.TrimEnd('/')}/.default"
|
||||
["scope"] = _scope
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_certificatePath))
|
||||
|
||||
@@ -12,6 +12,9 @@ internal static class OptionsHelper
|
||||
internal const string OFFICE_CERTIFICATE_PATH_OPTION = "office-certificate-path";
|
||||
internal const string OFFICE_CERTIFICATE_PASSWORD_OPTION = "office-certificate-password";
|
||||
internal const string OFFICE_GRAPH_BASE_OPTION = "office-graph-base-url";
|
||||
internal const string OFFICE_SCOPE_OPTION = "office-scope";
|
||||
internal const string OFFICE_SCOPE_OPTION_DEFAULT = "https://graph.microsoft.com/.default";
|
||||
internal const string OFFICE_IGNORE_EXISTING_OPTION = "office-ignore-existing";
|
||||
|
||||
internal const string DEFAULT_GRAPH_BASE_URL = "https://graph.microsoft.com";
|
||||
|
||||
@@ -23,7 +26,8 @@ internal static class OptionsHelper
|
||||
AuthOptionsHelper.AuthOptions AuthOptions,
|
||||
string GraphBaseUrl,
|
||||
string? CertificatePath,
|
||||
string? CertificatePassword
|
||||
string? CertificatePassword,
|
||||
string Scope
|
||||
);
|
||||
|
||||
internal static ParsedOptions ParseAndValidateOptions(string url, Dictionary<string, string?> options)
|
||||
@@ -41,12 +45,17 @@ internal static class OptionsHelper
|
||||
var _certificatePath = options.GetValueOrDefault(OFFICE_CERTIFICATE_PATH_OPTION);
|
||||
var _certificatePassword = options.GetValueOrDefault(OFFICE_CERTIFICATE_PASSWORD_OPTION);
|
||||
|
||||
var _scope = options.GetValueOrDefault(OFFICE_SCOPE_OPTION);
|
||||
if (string.IsNullOrWhiteSpace(_scope))
|
||||
_scope = OFFICE_SCOPE_OPTION_DEFAULT;
|
||||
|
||||
return new ParsedOptions(
|
||||
TenantId: _tenantId,
|
||||
AuthOptions: _authOptions,
|
||||
GraphBaseUrl: _graphBaseUrl,
|
||||
CertificatePath: _certificatePath,
|
||||
CertificatePassword: _certificatePassword
|
||||
CertificatePassword: _certificatePassword,
|
||||
Scope: _scope
|
||||
);
|
||||
}
|
||||
|
||||
@@ -57,6 +66,7 @@ internal static class OptionsHelper
|
||||
new CommandLineArgument(OFFICE_SECRET_OPTION, CommandLineArgument.ArgumentType.Password, Strings.OfficeSecretOptionShort, Strings.OfficeSecretOptionLong, null, [AuthOptionsHelper.AuthPasswordOption], null),
|
||||
new CommandLineArgument(OFFICE_CERTIFICATE_PATH_OPTION, CommandLineArgument.ArgumentType.Path, Strings.OfficeCertificatePathOptionShort, Strings.OfficeCertificatePathOptionLong),
|
||||
new CommandLineArgument(OFFICE_CERTIFICATE_PASSWORD_OPTION, CommandLineArgument.ArgumentType.Password, Strings.OfficeCertificatePasswordOptionShort, Strings.OfficeCertificatePasswordOptionLong),
|
||||
new CommandLineArgument(OFFICE_GRAPH_BASE_OPTION, CommandLineArgument.ArgumentType.String, Strings.OfficeGraphBaseOptionShort, Strings.OfficeGraphBaseOptionLong, DEFAULT_GRAPH_BASE_URL)
|
||||
new CommandLineArgument(OFFICE_GRAPH_BASE_OPTION, CommandLineArgument.ArgumentType.String, Strings.OfficeGraphBaseOptionShort, Strings.OfficeGraphBaseOptionLong, DEFAULT_GRAPH_BASE_URL),
|
||||
new CommandLineArgument(OFFICE_SCOPE_OPTION, CommandLineArgument.ArgumentType.String, Strings.OfficeScopeOptionShort, Strings.OfficeScopeOptionLong, OFFICE_SCOPE_OPTION_DEFAULT)
|
||||
];
|
||||
}
|
||||
@@ -497,7 +497,7 @@ partial class RestoreProvider
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Provider._overwrite && !string.IsNullOrWhiteSpace(eventItem.Subject))
|
||||
if (!Provider._ignoreExisting && !string.IsNullOrWhiteSpace(eventItem.Subject))
|
||||
{
|
||||
DateTimeOffset? start = null;
|
||||
if (eventItem.Start is JsonElement startElem && startElem.ValueKind == JsonValueKind.Object)
|
||||
@@ -542,7 +542,7 @@ partial class RestoreProvider
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Provider._overwrite && !string.IsNullOrWhiteSpace(eventItem.Subject))
|
||||
if (!Provider._ignoreExisting && !string.IsNullOrWhiteSpace(eventItem.Subject))
|
||||
{
|
||||
DateTimeOffset? start = null;
|
||||
if (eventItem.Start is JsonElement startElem && startElem.ValueKind == JsonValueKind.Object)
|
||||
|
||||
@@ -110,7 +110,7 @@ public partial class RestoreProvider : IRestoreDestinationProviderModule
|
||||
/// <summary>
|
||||
/// Whether to overwrite existing items
|
||||
/// </summary>
|
||||
internal readonly bool _overwrite;
|
||||
internal readonly bool _ignoreExisting;
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor for the restore provider.
|
||||
@@ -140,10 +140,14 @@ public partial class RestoreProvider : IRestoreDestinationProviderModule
|
||||
graphBaseUrl: parsedOptions.GraphBaseUrl,
|
||||
certificatePath: parsedOptions.CertificatePath,
|
||||
certificatePassword: parsedOptions.CertificatePassword,
|
||||
timeouts: TimeoutOptionsHelper.Parse(options)
|
||||
timeouts: TimeoutOptionsHelper.Parse(options),
|
||||
scope: parsedOptions.Scope
|
||||
);
|
||||
|
||||
_overwrite = Utility.ParseBoolOption(options, "overwrite");
|
||||
_ignoreExisting = Utility.ParseBoolOption(options, OptionsHelper.OFFICE_IGNORE_EXISTING_OPTION);
|
||||
var overwrite = Utility.ParseBoolOption(options, "overwrite");
|
||||
if (!overwrite)
|
||||
throw new UserInformationException(Strings.RestoreTargetMissingOverwriteOption(OptionsHelper.OFFICE_IGNORE_EXISTING_OPTION), "OverwriteOptionNotSet");
|
||||
|
||||
var sourceOpts = new Dictionary<string, string?>(options)
|
||||
{
|
||||
@@ -167,7 +171,7 @@ public partial class RestoreProvider : IRestoreDestinationProviderModule
|
||||
public string Description => Strings.ProviderDescription;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IList<ICommandLineArgument> SupportedCommands => OptionsHelper.SupportedCommands;
|
||||
public IList<ICommandLineArgument> SupportedCommands => OptionsHelper.SupportedCommands.Concat([new CommandLineArgument(OptionsHelper.OFFICE_IGNORE_EXISTING_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.OfficeIgnoreExistingOptionShort, Strings.OfficeIgnoreExistingOptionLong)]).ToList();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string TargetDestination => _restorePath;
|
||||
@@ -847,7 +851,7 @@ public partial class RestoreProvider : IRestoreDestinationProviderModule
|
||||
targetFolderId = mappedFolderId;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(internetMessageId) && await EmailApi.EmailExistsInFolderByInternetMessageIdAsync(userId, targetFolderId, internetMessageId, cancel))
|
||||
if (!_ignoreExisting && !string.IsNullOrWhiteSpace(internetMessageId) && await EmailApi.EmailExistsInFolderByInternetMessageIdAsync(userId, targetFolderId, internetMessageId, cancel))
|
||||
{
|
||||
Log.WriteInformationMessage(LOGTAG, "RestoreUserEmailsSkipExisting", null, $"Email with InternetMessageId {internetMessageId} already exists in target mailbox, skipping restore for {emailSource.Key}.");
|
||||
}
|
||||
@@ -1099,7 +1103,7 @@ public partial class RestoreProvider : IRestoreDestinationProviderModule
|
||||
|
||||
var displayName = file.Value.GetValueOrDefault("o365:Name") ?? Path.GetFileName(originalPath);
|
||||
|
||||
if (!_overwrite)
|
||||
if (!_ignoreExisting)
|
||||
{
|
||||
var existingItem = await DriveApi.GetDriveItemAsync(driveId, parentId, displayName, cancel);
|
||||
if (existingItem != null)
|
||||
@@ -1316,7 +1320,7 @@ public partial class RestoreProvider : IRestoreDestinationProviderModule
|
||||
targetFolderId = mappedFolderId;
|
||||
}
|
||||
|
||||
if (!_overwrite)
|
||||
if (!_ignoreExisting)
|
||||
{
|
||||
using (var checkStream = SystemIO.IO_OS.FileOpenRead(contentEntry))
|
||||
{
|
||||
@@ -1496,7 +1500,7 @@ public partial class RestoreProvider : IRestoreDestinationProviderModule
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_overwrite)
|
||||
if (!_ignoreExisting)
|
||||
{
|
||||
if (!existingTasksByPlan.TryGetValue(planId, out var existingTasks))
|
||||
{
|
||||
@@ -1576,7 +1580,7 @@ public partial class RestoreProvider : IRestoreDestinationProviderModule
|
||||
taskData?.AppliedCategories,
|
||||
cancel);
|
||||
|
||||
if (!_overwrite && existingTasksByPlan.TryGetValue(planId, out var currentTasks))
|
||||
if (!_ignoreExisting && existingTasksByPlan.TryGetValue(planId, out var currentTasks))
|
||||
{
|
||||
currentTasks.Add(newTask);
|
||||
}
|
||||
|
||||
@@ -129,7 +129,8 @@ public sealed partial class SourceProvider : ISourceProviderModule, IDisposable
|
||||
graphBaseUrl: parsedOptions.GraphBaseUrl,
|
||||
timeouts: _timeouts,
|
||||
certificatePath: parsedOptions.CertificatePath,
|
||||
certificatePassword: parsedOptions.CertificatePassword
|
||||
certificatePassword: parsedOptions.CertificatePassword,
|
||||
scope: parsedOptions.Scope
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,9 @@ internal static class Strings
|
||||
public static string MetadataStorageNotEnabled(string optionname) => LC.L(
|
||||
$"Storing metadata content in the database must be enabled to use the Microsoft 365 source provider. Use the option: --{optionname}");
|
||||
|
||||
public static string RestoreTargetMissingOverwriteOption(string optionname) => LC.L($"The Office365 restore target must have the overwrite option set to true. Use the provider specific option --{optionname} to avoid overwriting existing items.");
|
||||
|
||||
|
||||
public static string MissingTenantId => LC.L("A tenant ID must be supplied to query the Office 365 Management API.");
|
||||
|
||||
public static string OfficeTenantOptionShort => LC.L("Azure AD tenant identifier.");
|
||||
@@ -44,6 +47,12 @@ internal static class Strings
|
||||
|
||||
public static string OfficeGraphBaseOptionLong => LC.L("Base URL for Microsoft Graph if targeting a sovereign cloud.");
|
||||
|
||||
public static string OfficeIgnoreExistingOptionShort => LC.L("Ignore existing items.");
|
||||
public static string OfficeIgnoreExistingOptionLong => LC.L("If set, existing items in the destination will not be overwritten.");
|
||||
|
||||
public static string OfficeScopeOptionShort => LC.L("Microsoft Graph API scope.");
|
||||
public static string OfficeScopeOptionLong => LC.L("The scope to use when requesting an access token for the Microsoft Graph API.");
|
||||
|
||||
public static string WebModuleOperationShort => LC.L("The operation to perform.");
|
||||
public static string WebModuleOperationLong => LC.L("The operation that the web module should perform.");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user