mirror of
https://github.com/duplicati/duplicati.git
synced 2026-05-06 07:16:38 -04:00
275 lines
9.7 KiB
C#
275 lines
9.7 KiB
C#
// Copyright (c) 2026 Duplicati Inc. All rights reserved.
|
|
|
|
using Google.Apis.Auth.OAuth2;
|
|
using Google.Apis.Auth.OAuth2.Flows;
|
|
using Google.Apis.Gmail.v1;
|
|
using Google.Apis.Admin.Directory.directory_v1;
|
|
using Google.Apis.Calendar.v3;
|
|
using Google.Apis.Drive.v3;
|
|
using Google.Apis.PeopleService.v1;
|
|
using Google.Apis.Tasks.v1;
|
|
using Google.Apis.Keep.v1;
|
|
using Google.Apis.Groupssettings.v1;
|
|
using Google.Apis.HangoutsChat.v1;
|
|
using Google.Apis.Services;
|
|
using Google.Apis.Http;
|
|
using Google.Apis.Util;
|
|
|
|
namespace Duplicati.Proprietary.GoogleWorkspace;
|
|
|
|
public class APIHelper
|
|
{
|
|
private readonly string? _clientId;
|
|
private readonly string? _clientSecret;
|
|
private readonly string? _refreshToken;
|
|
private readonly string? _serviceAccountJson;
|
|
private readonly string? _adminEmail;
|
|
private readonly bool _isRestoreOperation;
|
|
|
|
private DirectoryService? _directoryServiceForUsers;
|
|
private DirectoryService? _directoryServiceForGroups;
|
|
private DirectoryService? _directoryServiceForOrgUnits;
|
|
private DriveService? _driveService;
|
|
private GroupssettingsService? _groupsSettingsService;
|
|
|
|
public APIHelper(OptionsHelper.GoogleWorkspaceOptions options, bool isRestoreOperation)
|
|
{
|
|
_clientId = options.ClientId;
|
|
_clientSecret = options.ClientSecret;
|
|
_refreshToken = options.RefreshToken;
|
|
_serviceAccountJson = options.ServiceAccountJson;
|
|
_adminEmail = options.AdminEmail;
|
|
_isRestoreOperation = isRestoreOperation;
|
|
}
|
|
|
|
private BaseClientService.Initializer GetServiceInitializer(string? userId, IEnumerable<string> scopes)
|
|
{
|
|
var backoffInitializer = new BackOffInitializer();
|
|
if (!string.IsNullOrEmpty(_serviceAccountJson))
|
|
{
|
|
var credential = GoogleCredential.FromJson(_serviceAccountJson)
|
|
.CreateScoped(scopes.ToArray());
|
|
|
|
var userToImpersonate = userId ?? _adminEmail;
|
|
if (!string.IsNullOrEmpty(userToImpersonate))
|
|
{
|
|
credential = credential.CreateWithUser(userToImpersonate);
|
|
}
|
|
|
|
return new BaseClientService.Initializer()
|
|
{
|
|
HttpClientInitializer = new CompositeHttpClientInitializer(credential, backoffInitializer),
|
|
ApplicationName = "Duplicati",
|
|
};
|
|
}
|
|
else if (!string.IsNullOrEmpty(_clientId) && !string.IsNullOrEmpty(_clientSecret) && !string.IsNullOrEmpty(_refreshToken))
|
|
{
|
|
var credential = new UserCredential(new GoogleAuthorizationCodeFlow(
|
|
new GoogleAuthorizationCodeFlow.Initializer
|
|
{
|
|
ClientSecrets = new ClientSecrets
|
|
{
|
|
ClientId = _clientId,
|
|
ClientSecret = _clientSecret
|
|
}
|
|
}),
|
|
"user",
|
|
new Google.Apis.Auth.OAuth2.Responses.TokenResponse { RefreshToken = _refreshToken });
|
|
|
|
return new BaseClientService.Initializer()
|
|
{
|
|
HttpClientInitializer = new CompositeHttpClientInitializer(credential, backoffInitializer),
|
|
ApplicationName = "Duplicati",
|
|
};
|
|
}
|
|
else
|
|
{
|
|
throw new Exception("Missing credentials");
|
|
}
|
|
}
|
|
|
|
public void TestConnection()
|
|
{
|
|
if (_isRestoreOperation)
|
|
{
|
|
// Test connection by listing shared drives (Drive API is used for restore operations)
|
|
var driveService = GetDriveService();
|
|
driveService.Drives.List().Execute();
|
|
}
|
|
else
|
|
{
|
|
// Test connection by getting the Gmail service
|
|
var gmailService = GetGmailService("me");
|
|
gmailService.Users.GetProfile("me").Execute();
|
|
}
|
|
}
|
|
public GmailService GetGmailService(string userId)
|
|
{
|
|
var scopes = _isRestoreOperation
|
|
? new[] { "https://www.googleapis.com/auth/gmail.modify" }
|
|
: new[] { GmailService.Scope.GmailReadonly };
|
|
return new GmailService(GetServiceInitializer(userId, scopes));
|
|
}
|
|
|
|
public DirectoryService GetDirectoryServiceForUsers()
|
|
{
|
|
if (_directoryServiceForUsers == null)
|
|
{
|
|
var scopes = _isRestoreOperation
|
|
? new[] { DirectoryService.Scope.AdminDirectoryUser }
|
|
: new[] { DirectoryService.Scope.AdminDirectoryUserReadonly };
|
|
_directoryServiceForUsers = new DirectoryService(GetServiceInitializer(null, scopes));
|
|
}
|
|
return _directoryServiceForUsers!;
|
|
}
|
|
|
|
public DirectoryService GetDirectoryServiceForGroups()
|
|
{
|
|
if (_directoryServiceForGroups == null)
|
|
{
|
|
var scopes = _isRestoreOperation
|
|
? new[] { DirectoryService.Scope.AdminDirectoryGroup }
|
|
: new[] { DirectoryService.Scope.AdminDirectoryGroupReadonly };
|
|
_directoryServiceForGroups = new DirectoryService(GetServiceInitializer(null, scopes));
|
|
}
|
|
return _directoryServiceForGroups!;
|
|
}
|
|
|
|
public DirectoryService GetDirectoryServiceForOrgUnits()
|
|
{
|
|
if (_directoryServiceForOrgUnits == null)
|
|
{
|
|
var scopes = _isRestoreOperation
|
|
? new[] { DirectoryService.Scope.AdminDirectoryOrgunit }
|
|
: new[] { DirectoryService.Scope.AdminDirectoryOrgunitReadonly };
|
|
_directoryServiceForOrgUnits = new DirectoryService(GetServiceInitializer(null, scopes));
|
|
}
|
|
return _directoryServiceForOrgUnits!;
|
|
}
|
|
|
|
public CalendarService GetCalendarService(string userId)
|
|
{
|
|
var scopes = _isRestoreOperation
|
|
? new[] { CalendarService.Scope.Calendar }
|
|
: new[] { CalendarService.Scope.CalendarReadonly };
|
|
|
|
return new CalendarService(GetServiceInitializer(userId, scopes));
|
|
}
|
|
|
|
public CalendarService GetCalendarAclService(string userId)
|
|
{
|
|
// ACL reading requires WRITE permissions
|
|
var scopes = new[] { CalendarService.Scope.Calendar };
|
|
return new CalendarService(GetServiceInitializer(userId, scopes));
|
|
}
|
|
|
|
public DriveService GetDriveService(string? userId = null)
|
|
{
|
|
var scopes = _isRestoreOperation
|
|
? new[] { DriveService.Scope.Drive }
|
|
: new[] { DriveService.Scope.DriveReadonly };
|
|
|
|
if (userId != null)
|
|
{
|
|
return new DriveService(GetServiceInitializer(userId, scopes));
|
|
}
|
|
if (_driveService == null)
|
|
{
|
|
_driveService = new DriveService(GetServiceInitializer(null, scopes));
|
|
}
|
|
return _driveService!;
|
|
}
|
|
|
|
public PeopleServiceService GetPeopleService(string userId)
|
|
{
|
|
var scopes = _isRestoreOperation
|
|
? new[] { "https://www.googleapis.com/auth/contacts" }
|
|
: new[] { PeopleServiceService.Scope.ContactsReadonly };
|
|
|
|
return new PeopleServiceService(GetServiceInitializer(userId, scopes));
|
|
}
|
|
|
|
public TasksService GetTasksService(string userId)
|
|
{
|
|
var scopes = _isRestoreOperation
|
|
? new[] { TasksService.Scope.Tasks }
|
|
: new[] { TasksService.Scope.TasksReadonly };
|
|
|
|
return new TasksService(GetServiceInitializer(userId, scopes));
|
|
}
|
|
|
|
public KeepService GetKeepService(string userId)
|
|
{
|
|
var scopes = _isRestoreOperation
|
|
? new[] { KeepService.Scope.Keep }
|
|
: new[] { KeepService.Scope.KeepReadonly };
|
|
|
|
return new KeepService(GetServiceInitializer(userId, scopes));
|
|
}
|
|
|
|
public GroupssettingsService GetGroupsSettingsService()
|
|
{
|
|
if (_groupsSettingsService == null)
|
|
{
|
|
var scopes = new[] { GroupssettingsService.Scope.AppsGroupsSettings };
|
|
_groupsSettingsService = new GroupssettingsService(GetServiceInitializer(null, scopes));
|
|
}
|
|
return _groupsSettingsService!;
|
|
}
|
|
|
|
public HangoutsChatService GetChatService(string userId)
|
|
{
|
|
var scopes = _isRestoreOperation
|
|
? new[]
|
|
{
|
|
HangoutsChatService.Scope.ChatMessages
|
|
}
|
|
: new[]
|
|
{
|
|
HangoutsChatService.Scope.ChatSpacesReadonly,
|
|
HangoutsChatService.Scope.ChatMessagesReadonly,
|
|
HangoutsChatService.Scope.ChatMembershipsReadonly
|
|
};
|
|
|
|
return new HangoutsChatService(GetServiceInitializer(userId, scopes));
|
|
}
|
|
|
|
public HangoutsChatService GetChatCreateSpaceService(string userId)
|
|
{
|
|
var scopes = new[] { HangoutsChatService.Scope.ChatSpaces };
|
|
return new HangoutsChatService(GetServiceInitializer(userId, scopes));
|
|
}
|
|
|
|
private class CompositeHttpClientInitializer : IConfigurableHttpClientInitializer
|
|
{
|
|
private readonly IConfigurableHttpClientInitializer[] _initializers;
|
|
|
|
public CompositeHttpClientInitializer(params IConfigurableHttpClientInitializer[] initializers)
|
|
{
|
|
_initializers = initializers;
|
|
}
|
|
|
|
public void Initialize(ConfigurableHttpClient httpClient)
|
|
{
|
|
foreach (var initializer in _initializers)
|
|
{
|
|
initializer.Initialize(httpClient);
|
|
}
|
|
}
|
|
}
|
|
|
|
private class BackOffInitializer : IConfigurableHttpClientInitializer
|
|
{
|
|
public void Initialize(ConfigurableHttpClient httpClient)
|
|
{
|
|
var backoffHandler = new BackOffHandler(new BackOffHandler.Initializer(new ExponentialBackOff())
|
|
{
|
|
HandleUnsuccessfulResponseFunc = (r) => r.StatusCode == System.Net.HttpStatusCode.ServiceUnavailable || r.StatusCode == (System.Net.HttpStatusCode)429
|
|
});
|
|
|
|
httpClient.MessageHandler.AddUnsuccessfulResponseHandler(backoffHandler);
|
|
httpClient.MessageHandler.AddExceptionHandler(backoffHandler);
|
|
}
|
|
}
|
|
}
|