From 5034a211cbe2cdf64f2d2ec58f38639c42277a20 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Mon, 23 May 2022 20:41:50 -0700 Subject: [PATCH] New: Don't return API Keys and Passwords via the API (cherry picked from commit 570be882154e73f8ad1de5b16b957bcb964697fd) Don't replace private values that haven't been set (cherry picked from commit 52760e0908fa9852ed8a770f1916bb582eb8c8b4) --- .../Applications/ApplicationResource.cs | 4 +-- .../DownloadClient/DownloadClientResource.cs | 4 +-- .../IndexerProxies/IndexerProxyResource.cs | 4 +-- .../Indexers/IndexerResource.cs | 4 +-- .../Notifications/NotificationResource.cs | 4 +-- src/Prowlarr.Api.V1/ProviderControllerBase.cs | 3 +- src/Prowlarr.Api.V1/ProviderResource.cs | 4 +-- src/Prowlarr.Http/ClientSchema/Field.cs | 2 +- .../ClientSchema/SchemaBuilder.cs | 31 ++++++++++++++----- 9 files changed, 38 insertions(+), 22 deletions(-) diff --git a/src/Prowlarr.Api.V1/Applications/ApplicationResource.cs b/src/Prowlarr.Api.V1/Applications/ApplicationResource.cs index 5c1374ea6..100d9083a 100644 --- a/src/Prowlarr.Api.V1/Applications/ApplicationResource.cs +++ b/src/Prowlarr.Api.V1/Applications/ApplicationResource.cs @@ -24,14 +24,14 @@ namespace Prowlarr.Api.V1.Applications return resource; } - public override ApplicationDefinition ToModel(ApplicationResource resource) + public override ApplicationDefinition ToModel(ApplicationResource resource, ApplicationDefinition existingDefinition) { if (resource == null) { return default; } - var definition = base.ToModel(resource); + var definition = base.ToModel(resource, existingDefinition); definition.SyncLevel = resource.SyncLevel; diff --git a/src/Prowlarr.Api.V1/DownloadClient/DownloadClientResource.cs b/src/Prowlarr.Api.V1/DownloadClient/DownloadClientResource.cs index 7376d6fe4..4ff1e5b53 100644 --- a/src/Prowlarr.Api.V1/DownloadClient/DownloadClientResource.cs +++ b/src/Prowlarr.Api.V1/DownloadClient/DownloadClientResource.cs @@ -33,14 +33,14 @@ namespace Prowlarr.Api.V1.DownloadClient return resource; } - public override DownloadClientDefinition ToModel(DownloadClientResource resource) + public override DownloadClientDefinition ToModel(DownloadClientResource resource, DownloadClientDefinition existingDefinition) { if (resource == null) { return null; } - var definition = base.ToModel(resource); + var definition = base.ToModel(resource, existingDefinition); definition.Enable = resource.Enable; definition.Protocol = resource.Protocol; diff --git a/src/Prowlarr.Api.V1/IndexerProxies/IndexerProxyResource.cs b/src/Prowlarr.Api.V1/IndexerProxies/IndexerProxyResource.cs index 6b8719289..8416464de 100644 --- a/src/Prowlarr.Api.V1/IndexerProxies/IndexerProxyResource.cs +++ b/src/Prowlarr.Api.V1/IndexerProxies/IndexerProxyResource.cs @@ -25,14 +25,14 @@ namespace Prowlarr.Api.V1.IndexerProxies return resource; } - public override IndexerProxyDefinition ToModel(IndexerProxyResource resource) + public override IndexerProxyDefinition ToModel(IndexerProxyResource resource, IndexerProxyDefinition existingDefinition) { if (resource == null) { return default(IndexerProxyDefinition); } - var definition = base.ToModel(resource); + var definition = base.ToModel(resource, existingDefinition); return definition; } diff --git a/src/Prowlarr.Api.V1/Indexers/IndexerResource.cs b/src/Prowlarr.Api.V1/Indexers/IndexerResource.cs index 832d7f219..e82ad2ab5 100644 --- a/src/Prowlarr.Api.V1/Indexers/IndexerResource.cs +++ b/src/Prowlarr.Api.V1/Indexers/IndexerResource.cs @@ -104,14 +104,14 @@ namespace Prowlarr.Api.V1.Indexers return resource; } - public override IndexerDefinition ToModel(IndexerResource resource) + public override IndexerDefinition ToModel(IndexerResource resource, IndexerDefinition existingDefinition) { if (resource == null) { return null; } - var definition = base.ToModel(resource); + var definition = base.ToModel(resource, existingDefinition); if (resource.Implementation == nameof(Cardigann)) { diff --git a/src/Prowlarr.Api.V1/Notifications/NotificationResource.cs b/src/Prowlarr.Api.V1/Notifications/NotificationResource.cs index 1951866ac..dcac3f6d4 100644 --- a/src/Prowlarr.Api.V1/Notifications/NotificationResource.cs +++ b/src/Prowlarr.Api.V1/Notifications/NotificationResource.cs @@ -43,14 +43,14 @@ namespace Prowlarr.Api.V1.Notifications return resource; } - public override NotificationDefinition ToModel(NotificationResource resource) + public override NotificationDefinition ToModel(NotificationResource resource, NotificationDefinition existingDefinition) { if (resource == null) { return default(NotificationDefinition); } - var definition = base.ToModel(resource); + var definition = base.ToModel(resource, existingDefinition); definition.OnGrab = resource.OnGrab; definition.SupportsOnGrab = resource.SupportsOnGrab; diff --git a/src/Prowlarr.Api.V1/ProviderControllerBase.cs b/src/Prowlarr.Api.V1/ProviderControllerBase.cs index 809697a06..f65b916bb 100644 --- a/src/Prowlarr.Api.V1/ProviderControllerBase.cs +++ b/src/Prowlarr.Api.V1/ProviderControllerBase.cs @@ -142,7 +142,8 @@ namespace Prowlarr.Api.V1 private TProviderDefinition GetDefinition(TProviderResource providerResource, bool validate, bool includeWarnings, bool forceValidate) { - var definition = _resourceMapper.ToModel(providerResource); + var existingDefinition = providerResource.Id > 0 ? _providerFactory.Find(providerResource.Id) : null; + var definition = _resourceMapper.ToModel(providerResource, existingDefinition); if (validate && (definition.Enable || forceValidate)) { diff --git a/src/Prowlarr.Api.V1/ProviderResource.cs b/src/Prowlarr.Api.V1/ProviderResource.cs index f39294f28..3de6725c7 100644 --- a/src/Prowlarr.Api.V1/ProviderResource.cs +++ b/src/Prowlarr.Api.V1/ProviderResource.cs @@ -45,7 +45,7 @@ namespace Prowlarr.Api.V1 }; } - public virtual TProviderDefinition ToModel(TProviderResource resource) + public virtual TProviderDefinition ToModel(TProviderResource resource, TProviderDefinition existingDefinition) { if (resource == null) { @@ -65,7 +65,7 @@ namespace Prowlarr.Api.V1 }; var configContract = ReflectionExtensions.CoreAssembly.FindTypeByName(definition.ConfigContract); - definition.Settings = (IProviderConfig)SchemaBuilder.ReadFromSchema(resource.Fields, configContract); + definition.Settings = (IProviderConfig)SchemaBuilder.ReadFromSchema(resource.Fields, configContract, existingDefinition?.Settings); return definition; } diff --git a/src/Prowlarr.Http/ClientSchema/Field.cs b/src/Prowlarr.Http/ClientSchema/Field.cs index 7ceed1ae9..1af56b6d0 100644 --- a/src/Prowlarr.Http/ClientSchema/Field.cs +++ b/src/Prowlarr.Http/ClientSchema/Field.cs @@ -16,10 +16,10 @@ namespace Prowlarr.Http.ClientSchema public string Type { get; set; } public bool Advanced { get; set; } public List SelectOptions { get; set; } - public string SelectOptionsProviderAction { get; set; } public string Section { get; set; } public string Hidden { get; set; } + public PrivacyLevel Privacy { get; set; } public string Placeholder { get; set; } public bool IsFloat { get; set; } diff --git a/src/Prowlarr.Http/ClientSchema/SchemaBuilder.cs b/src/Prowlarr.Http/ClientSchema/SchemaBuilder.cs index ddfbe40f9..c5154335d 100644 --- a/src/Prowlarr.Http/ClientSchema/SchemaBuilder.cs +++ b/src/Prowlarr.Http/ClientSchema/SchemaBuilder.cs @@ -15,6 +15,7 @@ namespace Prowlarr.Http.ClientSchema { public static class SchemaBuilder { + private const string PRIVATE_VALUE = "********"; private static Dictionary _mappings = new Dictionary(); private static ILocalizationService _localizationService; @@ -36,13 +37,19 @@ namespace Prowlarr.Http.ClientSchema var field = mapping.Field.Clone(); field.Value = mapping.GetterFunc(model); + if (field.Value != null && !field.Value.Equals(string.Empty) && + (field.Privacy == PrivacyLevel.ApiKey || field.Privacy == PrivacyLevel.Password)) + { + field.Value = PRIVATE_VALUE; + } + result.Add(field); } return result.OrderBy(r => r.Order).ToList(); } - public static object ReadFromSchema(List fields, Type targetType) + public static object ReadFromSchema(List fields, Type targetType, object model) { Ensure.That(targetType, () => targetType).IsNotNull(); @@ -57,18 +64,25 @@ namespace Prowlarr.Http.ClientSchema if (field != null) { - mapping.SetterFunc(target, field.Value); + // Use the Privacy property from the mapping's field as Privacy may not be set in the API request (nor is it required) + if ((mapping.Field.Privacy == PrivacyLevel.ApiKey || mapping.Field.Privacy == PrivacyLevel.Password) && + (field.Value?.ToString()?.Equals(PRIVATE_VALUE) ?? false) && + model != null) + { + var existingValue = mapping.GetterFunc(model); + + mapping.SetterFunc(target, existingValue); + } + else + { + mapping.SetterFunc(target, field.Value); + } } } return target; } - public static T ReadFromSchema(List fields) - { - return (T)ReadFromSchema(fields, typeof(T)); - } - // Ideally this function should begin a System.Linq.Expression expression tree since it's faster. // But it's probably not needed till performance issues pop up. public static FieldMapping[] GetFieldMappings(Type type) @@ -127,6 +141,7 @@ namespace Prowlarr.Http.ClientSchema Advanced = fieldAttribute.Advanced, Type = fieldAttribute.Type.ToString().FirstCharToLower(), Section = fieldAttribute.Section, + Privacy = fieldAttribute.Privacy, Placeholder = fieldAttribute.Placeholder }; @@ -159,7 +174,7 @@ namespace Prowlarr.Http.ClientSchema Field = field, PropertyType = propertyInfo.PropertyType, GetterFunc = t => propertyInfo.GetValue(targetSelector(t), null), - SetterFunc = (t, v) => propertyInfo.SetValue(targetSelector(t), valueConverter(v), null) + SetterFunc = (t, v) => propertyInfo.SetValue(targetSelector(t), v?.GetType() == propertyInfo.PropertyType ? v : valueConverter(v), null) }); } else