/// <summary> /// Initializes a new instance of the <see cref="SFInstallableDblResource" /> class. /// </summary> /// <param name="userSecret">The user secret.</param> /// <param name="paratextOptions">The paratext options.</param> /// <param name="restClientFactory">The rest client factory.</param> /// <param name="fileSystemService">The file system service.</param> /// <param name="jwtTokenHelper">The JWT token helper.</param> /// <remarks> /// This is a convenience constructor for unit tests. /// </remarks> internal SFInstallableDblResource(UserSecret userSecret, ParatextOptions paratextOptions, ISFRestClientFactory restClientFactory, IFileSystemService fileSystemService, IJwtTokenHelper jwtTokenHelper) : this(userSecret, paratextOptions, restClientFactory, fileSystemService, jwtTokenHelper, new ParatextProjectDeleter(), new ParatextMigrationOperations(), new ParatextZippedResourcePasswordProvider(paratextOptions)) { }
public ParatextService(IWebHostEnvironment env, IOptions <ParatextOptions> paratextOptions, IRepository <UserSecret> userSecretRepository, IRealtimeService realtimeService, IExceptionHandler exceptionHandler, IOptions <SiteOptions> siteOptions, IFileSystemService fileSystemService, ILogger <ParatextService> logger, IJwtTokenHelper jwtTokenHelper, IParatextDataHelper paratextDataHelper, IInternetSharedRepositorySourceProvider internetSharedRepositorySourceProvider, ISFRestClientFactory restClientFactory) { _paratextOptions = paratextOptions; _userSecretRepository = userSecretRepository; _realtimeService = realtimeService; _exceptionHandler = exceptionHandler; _siteOptions = siteOptions; _fileSystemService = fileSystemService; _logger = logger; _jwtTokenHelper = jwtTokenHelper; _paratextDataHelper = paratextDataHelper; _internetSharedRepositorySourceProvider = internetSharedRepositorySourceProvider; _restClientFactory = restClientFactory; _httpClientHandler = new HttpClientHandler(); _registryClient = new HttpClient(_httpClientHandler); if (env.IsDevelopment() || env.IsEnvironment("DevelopmentBeta") || env.IsEnvironment("Testing") || env.IsEnvironment("TestingBeta")) { _httpClientHandler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; // This should be paratext-qa.thedigitalbiblelibrary.org, but it's broken as of 2021-04 and // qa.thedigitalbiblelibrary.org should be just as good, at least for the time being. _dblServerUri = "https://qa.thedigitalbiblelibrary.org/"; _registryServerUri = "https://registry-dev.paratext.org"; _registryClient.BaseAddress = new Uri(_registryServerUri); _sendReceiveServerUri = InternetAccess.uriDevelopment; } else { _registryClient.BaseAddress = new Uri(_registryServerUri); } _registryClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); ScrTextCollection = new LazyScrTextCollection(); HgWrapper = new HgWrapper(); SharingLogicWrapper = new SharingLogicWrapper(); Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); }
/// <summary> /// Initializes a new instance of the <see cref="SFInstallableDblResource" /> class. /// </summary> /// <param name="userSecret">The user secret.</param> /// <param name="paratextOptions">The paratext options.</param> /// <param name="restClientFactory">The rest client factory.</param> /// <param name="fileSystemService">The file system service.</param> /// <param name="jwtTokenHelper">The JWT token helper.</param> /// <param name="projectDeleter">The project deleter.</param> /// <param name="migrationOperations">The migration operations.</param> /// <param name="passwordProvider">The password provider.</param> /// <exception cref="ArgumentNullException">restClientFactory</exception> private SFInstallableDblResource(UserSecret userSecret, ParatextOptions paratextOptions, ISFRestClientFactory restClientFactory, IFileSystemService fileSystemService, IJwtTokenHelper jwtTokenHelper, IProjectDeleter projectDeleter, IMigrationOperations migrationOperations, IZippedResourcePasswordProvider passwordProvider) : base(projectDeleter, migrationOperations, passwordProvider) { this._userSecret = userSecret; this._paratextOptions = paratextOptions; this._restClientFactory = restClientFactory; this._fileSystemService = fileSystemService; this._jwtTokenHelper = jwtTokenHelper; if (this._restClientFactory == null) { throw new ArgumentNullException(nameof(restClientFactory)); } else if (this._fileSystemService == null) { throw new ArgumentNullException(nameof(fileSystemService)); } }
/// <summary> /// Converts the JSON response to a list of Installable DBL Resources. /// </summary> /// <param name="baseUri">The base URI.</param> /// <param name="response">The response.</param> /// <param name="restClientFactory">The rest client factory.</param> /// <param name="fileSystemService">The file system service.</param> /// <param name="jwtTokenHelper">The JWT token helper.</param> /// <param name="createdTimestamp">The created timestamp.</param> /// <param name="userSecret">The user secret.</param> /// <param name="paratextOptions">The paratext options.</param> /// <param name="projectDeleter">The project deleter.</param> /// <param name="migrationOperations">The migration operations.</param> /// <param name="passwordProvider">The password provider.</param> /// <returns> /// The Installable Resources. /// </returns> private static IEnumerable <SFInstallableDblResource> ConvertJsonResponseToInstallableDblResources( string baseUri, string response, ISFRestClientFactory restClientFactory, IFileSystemService fileSystemService, IJwtTokenHelper jwtTokenHelper, DateTime createdTimestamp, UserSecret userSecret, ParatextOptions paratextOptions, IProjectDeleter projectDeleter, IMigrationOperations migrationOperations, IZippedResourcePasswordProvider passwordProvider) { if (!string.IsNullOrWhiteSpace(response)) { JObject jsonResources; try { jsonResources = JObject.Parse(response); } catch (JsonReaderException) { // Ignore the exception and just return empty result // This is probably caused by partial result from poor connection to DBL yield break; } foreach (JToken jsonResource in jsonResources["resources"] as JArray ?? new JArray()) { var name = (string)jsonResource["name"]; var nameCommon = (string)jsonResource["nameCommon"]; var fullname = (string)jsonResource["fullname"]; if (string.IsNullOrWhiteSpace(fullname)) { fullname = nameCommon; } var languageName = (string)jsonResource["languageName"]; var id = (string)jsonResource["id"]; var revision = (string)jsonResource["revision"]; var permissionsChecksum = (string)jsonResource["permissions-checksum"]; var manifestChecksum = (string)jsonResource["p8z-manifest-checksum"]; var languageIdLdml = (string)jsonResource["languageLDMLId"]; var languageIdCode = (string)jsonResource["languageCode"]; LanguageId languageId = migrationOperations.DetermineBestLangIdToUseForResource(languageIdLdml, languageIdCode); if (string.IsNullOrEmpty(languageId.Id)) { languageId = LanguageIdHelper.FromCommonLanguageName(languageName); } else { languageId = LanguageId.FromEthnologueCode(languageId.Id); } string url = BuildDblResourceEntriesUrl(baseUri, id); var resource = new SFInstallableDblResource(userSecret, paratextOptions, restClientFactory, fileSystemService, jwtTokenHelper, projectDeleter, migrationOperations, passwordProvider) { DisplayName = name, Name = name, FullName = fullname, LanguageID = languageId, DblSourceUrl = url, DBLEntryUid = id, DBLRevision = int.Parse(revision), PermissionsChecksum = permissionsChecksum, ManifestChecksum = manifestChecksum, CreatedTimestamp = createdTimestamp, }; resource.LanguageName = MacroLanguageHelper.GetMacroLanguage(resource.LanguageID) ?? languageName; yield return(resource); } } }
/// <summary> /// Return a list of resources which this user is allowed to install from DBL. /// If we cannot contact DBL, return an empty list. /// </summary> /// <param name="userSecret">The user secret.</param> /// <param name="paratextOptions">The paratext options.</param> /// <param name="restClientFactory">The rest client factory.</param> /// <param name="fileSystemService">The file system service.</param> /// <param name="jwtTokenHelper">The JWT token helper.</param> /// <param name="baseUrl">The base URL.</param> /// <returns>The Installable Resources.</returns> /// <exception cref="ArgumentNullException">restClientFactory /// or /// userSecret</exception> /// <remarks>Tests on this method can be found in ParatextServiceTests.cs calling GetResources().</remarks> public static IEnumerable <SFInstallableDblResource> GetInstallableDblResources(UserSecret userSecret, ParatextOptions paratextOptions, ISFRestClientFactory restClientFactory, IFileSystemService fileSystemService, IJwtTokenHelper jwtTokenHelper, IExceptionHandler exceptionHandler, string baseUrl = null) { // Parameter check (just like the constructor) if (restClientFactory == null) { throw new ArgumentNullException(nameof(restClientFactory)); } else if (userSecret == null) { throw new ArgumentNullException(nameof(userSecret)); } ISFRestClient client = restClientFactory.Create(string.Empty, ApplicationProduct.DefaultVersion, userSecret); baseUrl = string.IsNullOrWhiteSpace(baseUrl) ? InternetAccess.ParatextDBLServer : baseUrl; string response = null; try { response = client.Get(BuildDblResourceEntriesUrl(baseUrl)); } catch (WebException e) { // If we get a temporary 401 Unauthorized response, return an empty list. string errorExplanation = "GetInstallableDblResources failed when attempting to inquire about" + $" resources and is ignoring error {e}"; var report = new Exception(errorExplanation); // Report to bugsnag, but don't throw. exceptionHandler.ReportException(report); return(Enumerable.Empty <SFInstallableDblResource>()); } IEnumerable <SFInstallableDblResource> resources = ConvertJsonResponseToInstallableDblResources(baseUrl, response, restClientFactory, fileSystemService, jwtTokenHelper, DateTime.Now, userSecret, paratextOptions, new ParatextProjectDeleter(), new ParatextMigrationOperations(), new ParatextZippedResourcePasswordProvider(paratextOptions)); return(resources); }
/// <summary> /// Checks the resource permission. /// </summary> /// <param name="id">The identifier.</param> /// <param name="userSecret">The user secret.</param> /// <param name="paratextOptions">The paratext options.</param> /// <param name="restClientFactory">The rest client factory.</param> /// <param name="fileSystemService">The file system service.</param> /// <param name="jwtTokenHelper">The JWT token helper.</param> /// <param name="baseUrl">The base URL.</param> /// <returns> /// <c>true</c> if the user has permission to access the resource; otherwise, <c>false</c>. /// </returns> /// <exception cref="ArgumentNullException">id /// or /// userSecret /// or /// restClientFactory</exception> public static bool CheckResourcePermission(string id, UserSecret userSecret, ParatextOptions paratextOptions, ISFRestClientFactory restClientFactory, IFileSystemService fileSystemService, IJwtTokenHelper jwtTokenHelper, IExceptionHandler exceptionHandler, string baseUrl = null) { // Parameter check if (string.IsNullOrWhiteSpace(id)) { throw new ArgumentNullException(nameof(id)); } else if (userSecret == null) { throw new ArgumentNullException(nameof(userSecret)); } else if (restClientFactory == null) { throw new ArgumentNullException(nameof(restClientFactory)); } ISFRestClient client = restClientFactory.Create(string.Empty, ApplicationProduct.DefaultVersion, userSecret); baseUrl = string.IsNullOrWhiteSpace(baseUrl) ? InternetAccess.ParatextDBLServer : baseUrl; try { _ = client.Head(BuildDblResourceEntriesUrl(baseUrl, id)); return(true); } catch (Exception ex) { // Normally we would catch the specific WebException, // but something in ParatextData is interfering with it. if (ex.InnerException?.Message.StartsWith("401: ", StringComparison.OrdinalIgnoreCase) ?? false) { // A 401 error means unauthorized (probably a bad token) return(false); } else if (ex.InnerException?.Message.StartsWith("403: ", StringComparison.OrdinalIgnoreCase) ?? false) { // A 403 error means no access. return(false); } else if (ex.InnerException?.Message.StartsWith("404: ", StringComparison.OrdinalIgnoreCase) ?? false) { // A 404 error means that the resource is not on the server return(false); } else if (ex.InnerException?.Message.StartsWith("405: ", StringComparison.OrdinalIgnoreCase) ?? false) { // A 405 means that HEAD request does not work on the server, so we will use the resource list // This is slower (although faster than a GET request on the resource), but more reliable IEnumerable <SFInstallableDblResource> resources = GetInstallableDblResources( userSecret, paratextOptions, restClientFactory, fileSystemService, jwtTokenHelper, exceptionHandler, baseUrl); return(resources.Any(r => r.DBLEntryUid == id)); } else if (ex.Source == "NSubstitute") { // This occurs during unit tests to test whether there is permission or not return(false); } else { // An unknown error throw; } } }