From 508a15e09ac1b08a90837d371353cdf11cd9ee3c 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) --- .../AutoTagging/AutoTaggingResource.cs | 3 +- .../CustomFormats/CustomFormatResource.cs | 5 ++- .../DownloadClient/DownloadClientResource.cs | 4 +- .../ImportLists/ImportListResource.cs | 4 +- src/Radarr.Api.V3/Indexers/IndexerResource.cs | 4 +- .../Metadata/MetadataResource.cs | 4 +- .../Notifications/NotificationResource.cs | 4 +- src/Radarr.Api.V3/ProviderControllerBase.cs | 3 +- src/Radarr.Api.V3/ProviderResource.cs | 4 +- src/Radarr.Http/ClientSchema/Field.cs | 1 + src/Radarr.Http/ClientSchema/SchemaBuilder.cs | 37 ++++++++++++++----- 11 files changed, 47 insertions(+), 26 deletions(-) diff --git a/src/Radarr.Api.V3/AutoTagging/AutoTaggingResource.cs b/src/Radarr.Api.V3/AutoTagging/AutoTaggingResource.cs index 46f344624..487e7d6cc 100644 --- a/src/Radarr.Api.V3/AutoTagging/AutoTaggingResource.cs +++ b/src/Radarr.Api.V3/AutoTagging/AutoTaggingResource.cs @@ -66,8 +66,7 @@ namespace Radarr.Api.V3.AutoTagging // Finding the exact current specification isn't possible given the dynamic nature of them and the possibility that multiple // of the same type exist within the same format. Passing in null is safe as long as there never exists a specification that // relies on additional privacy. - // TODO: Check ReadFromSchema for third argument - var spec = (IAutoTaggingSpecification)SchemaBuilder.ReadFromSchema(resource.Fields, type); + var spec = (IAutoTaggingSpecification)SchemaBuilder.ReadFromSchema(resource.Fields, type, null); spec.Name = resource.Name; spec.Negate = resource.Negate; return spec; diff --git a/src/Radarr.Api.V3/CustomFormats/CustomFormatResource.cs b/src/Radarr.Api.V3/CustomFormats/CustomFormatResource.cs index c88b034b0..3b22e4d66 100644 --- a/src/Radarr.Api.V3/CustomFormats/CustomFormatResource.cs +++ b/src/Radarr.Api.V3/CustomFormats/CustomFormatResource.cs @@ -65,7 +65,10 @@ namespace Radarr.Api.V3.CustomFormats var type = matchingSpec.GetType(); - var spec = (ICustomFormatSpecification)SchemaBuilder.ReadFromSchema(resource.Fields, type); + // Finding the exact current specification isn't possible given the dynamic nature of them and the possibility that multiple + // of the same type exist within the same format. Passing in null is safe as long as there never exists a specification that + // relies on additional privacy. + var spec = (ICustomFormatSpecification)SchemaBuilder.ReadFromSchema(resource.Fields, type, null); spec.Name = resource.Name; spec.Negate = resource.Negate; spec.Required = resource.Required; diff --git a/src/Radarr.Api.V3/DownloadClient/DownloadClientResource.cs b/src/Radarr.Api.V3/DownloadClient/DownloadClientResource.cs index 4776e098a..c17a149d4 100644 --- a/src/Radarr.Api.V3/DownloadClient/DownloadClientResource.cs +++ b/src/Radarr.Api.V3/DownloadClient/DownloadClientResource.cs @@ -32,14 +32,14 @@ namespace Radarr.Api.V3.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/Radarr.Api.V3/ImportLists/ImportListResource.cs b/src/Radarr.Api.V3/ImportLists/ImportListResource.cs index 95cee0aa2..b1606f31e 100644 --- a/src/Radarr.Api.V3/ImportLists/ImportListResource.cs +++ b/src/Radarr.Api.V3/ImportLists/ImportListResource.cs @@ -43,14 +43,14 @@ namespace Radarr.Api.V3.ImportLists return resource; } - public override ImportListDefinition ToModel(ImportListResource resource) + public override ImportListDefinition ToModel(ImportListResource resource, ImportListDefinition existingDefinition) { if (resource == null) { return null; } - var definition = base.ToModel(resource); + var definition = base.ToModel(resource, existingDefinition); definition.Enabled = resource.Enabled; definition.EnableAuto = resource.EnableAuto; diff --git a/src/Radarr.Api.V3/Indexers/IndexerResource.cs b/src/Radarr.Api.V3/Indexers/IndexerResource.cs index c5e02c2c2..d35b64247 100644 --- a/src/Radarr.Api.V3/Indexers/IndexerResource.cs +++ b/src/Radarr.Api.V3/Indexers/IndexerResource.cs @@ -37,14 +37,14 @@ namespace Radarr.Api.V3.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); definition.EnableRss = resource.EnableRss; definition.EnableAutomaticSearch = resource.EnableAutomaticSearch; diff --git a/src/Radarr.Api.V3/Metadata/MetadataResource.cs b/src/Radarr.Api.V3/Metadata/MetadataResource.cs index 9cdf5be30..97baaea82 100644 --- a/src/Radarr.Api.V3/Metadata/MetadataResource.cs +++ b/src/Radarr.Api.V3/Metadata/MetadataResource.cs @@ -23,14 +23,14 @@ namespace Radarr.Api.V3.Metadata return resource; } - public override MetadataDefinition ToModel(MetadataResource resource) + public override MetadataDefinition ToModel(MetadataResource resource, MetadataDefinition existingDefinition) { if (resource == null) { return null; } - var definition = base.ToModel(resource); + var definition = base.ToModel(resource, existingDefinition); definition.Enable = resource.Enable; diff --git a/src/Radarr.Api.V3/Notifications/NotificationResource.cs b/src/Radarr.Api.V3/Notifications/NotificationResource.cs index 1040dbeda..2aa6b259e 100644 --- a/src/Radarr.Api.V3/Notifications/NotificationResource.cs +++ b/src/Radarr.Api.V3/Notifications/NotificationResource.cs @@ -73,14 +73,14 @@ namespace Radarr.Api.V3.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.OnDownload = resource.OnDownload; diff --git a/src/Radarr.Api.V3/ProviderControllerBase.cs b/src/Radarr.Api.V3/ProviderControllerBase.cs index 26c94c480..bbf2ae219 100644 --- a/src/Radarr.Api.V3/ProviderControllerBase.cs +++ b/src/Radarr.Api.V3/ProviderControllerBase.cs @@ -142,7 +142,8 @@ namespace Radarr.Api.V3 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/Radarr.Api.V3/ProviderResource.cs b/src/Radarr.Api.V3/ProviderResource.cs index e5e201a26..c00af6449 100644 --- a/src/Radarr.Api.V3/ProviderResource.cs +++ b/src/Radarr.Api.V3/ProviderResource.cs @@ -44,7 +44,7 @@ namespace Radarr.Api.V3 }; } - public virtual TProviderDefinition ToModel(TProviderResource resource) + public virtual TProviderDefinition ToModel(TProviderResource resource, TProviderDefinition existingDefinition) { if (resource == null) { @@ -64,7 +64,7 @@ namespace Radarr.Api.V3 }; 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/Radarr.Http/ClientSchema/Field.cs b/src/Radarr.Http/ClientSchema/Field.cs index 38b830caf..8bcb3424a 100644 --- a/src/Radarr.Http/ClientSchema/Field.cs +++ b/src/Radarr.Http/ClientSchema/Field.cs @@ -19,6 +19,7 @@ namespace Radarr.Http.ClientSchema 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 Field Clone() diff --git a/src/Radarr.Http/ClientSchema/SchemaBuilder.cs b/src/Radarr.Http/ClientSchema/SchemaBuilder.cs index 97be63bf7..66d610ceb 100644 --- a/src/Radarr.Http/ClientSchema/SchemaBuilder.cs +++ b/src/Radarr.Http/ClientSchema/SchemaBuilder.cs @@ -13,6 +13,7 @@ namespace Radarr.Http.ClientSchema { public static class SchemaBuilder { + private const string PRIVATE_VALUE = "********"; private static Dictionary _mappings = new Dictionary(); public static List ToSchema(object model) @@ -26,7 +27,15 @@ namespace Radarr.Http.ClientSchema foreach (var mapping in mappings) { var field = mapping.Field.Clone(); - field.Value = mapping.GetterFunc(model); + + if (field.Privacy == PrivacyLevel.ApiKey || field.Privacy == PrivacyLevel.Password) + { + field.Value = PRIVATE_VALUE; + } + else + { + field.Value = mapping.GetterFunc(model); + } result.Add(field); } @@ -34,7 +43,7 @@ namespace Radarr.Http.ClientSchema 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(); @@ -49,18 +58,25 @@ namespace Radarr.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) @@ -105,6 +121,7 @@ namespace Radarr.Http.ClientSchema Advanced = fieldAttribute.Advanced, Type = fieldAttribute.Type.ToString().FirstCharToLower(), Section = fieldAttribute.Section, + Privacy = fieldAttribute.Privacy, Placeholder = fieldAttribute.Placeholder }; @@ -132,7 +149,7 @@ namespace Radarr.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 @@ -255,7 +272,7 @@ namespace Radarr.Http.ClientSchema } else { - return fieldValue.ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(p => p.Trim()); + return fieldValue.ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => v.Trim()); } }; }