New: Don't return API Keys and Passwords via the API

This commit is contained in:
Mark McDowall 2022-05-23 20:41:50 -07:00
parent b154b00c61
commit 570be88215
10 changed files with 51 additions and 29 deletions

View File

@ -48,7 +48,11 @@ namespace Sonarr.Api.V3.CustomFormats
private static ICustomFormatSpecification MapSpecification(CustomFormatSpecificationSchema resource, List<ICustomFormatSpecification> specifications) private static ICustomFormatSpecification MapSpecification(CustomFormatSpecificationSchema resource, List<ICustomFormatSpecification> specifications)
{ {
var type = specifications.SingleOrDefault(x => x.GetType().Name == resource.Implementation).GetType(); var type = specifications.SingleOrDefault(x => x.GetType().Name == resource.Implementation).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.Name = resource.Name;
spec.Negate = resource.Negate; spec.Negate = resource.Negate;
spec.Required = resource.Required; spec.Required = resource.Required;

View File

@ -1,4 +1,4 @@
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
namespace Sonarr.Api.V3.DownloadClient namespace Sonarr.Api.V3.DownloadClient
@ -32,14 +32,14 @@ namespace Sonarr.Api.V3.DownloadClient
return resource; return resource;
} }
public override DownloadClientDefinition ToModel(DownloadClientResource resource) public override DownloadClientDefinition ToModel(DownloadClientResource resource, DownloadClientDefinition existingDefinition)
{ {
if (resource == null) if (resource == null)
{ {
return null; return null;
} }
var definition = base.ToModel(resource); var definition = base.ToModel(resource, existingDefinition);
definition.Enable = resource.Enable; definition.Enable = resource.Enable;
definition.Protocol = resource.Protocol; definition.Protocol = resource.Protocol;

View File

@ -40,14 +40,14 @@ namespace Sonarr.Api.V3.ImportLists
return resource; return resource;
} }
public override ImportListDefinition ToModel(ImportListResource resource) public override ImportListDefinition ToModel(ImportListResource resource, ImportListDefinition existingDefinition)
{ {
if (resource == null) if (resource == null)
{ {
return null; return null;
} }
var definition = base.ToModel(resource); var definition = base.ToModel(resource, existingDefinition);
definition.EnableAutomaticAdd = resource.EnableAutomaticAdd; definition.EnableAutomaticAdd = resource.EnableAutomaticAdd;
definition.ShouldMonitor = resource.ShouldMonitor; definition.ShouldMonitor = resource.ShouldMonitor;

View File

@ -39,14 +39,14 @@ namespace Sonarr.Api.V3.Indexers
return resource; return resource;
} }
public override IndexerDefinition ToModel(IndexerResource resource) public override IndexerDefinition ToModel(IndexerResource resource, IndexerDefinition existingDefinition)
{ {
if (resource == null) if (resource == null)
{ {
return null; return null;
} }
var definition = base.ToModel(resource); var definition = base.ToModel(resource, existingDefinition);
definition.EnableRss = resource.EnableRss; definition.EnableRss = resource.EnableRss;
definition.EnableAutomaticSearch = resource.EnableAutomaticSearch; definition.EnableAutomaticSearch = resource.EnableAutomaticSearch;

View File

@ -1,4 +1,4 @@
using NzbDrone.Core.Extras.Metadata; using NzbDrone.Core.Extras.Metadata;
namespace Sonarr.Api.V3.Metadata namespace Sonarr.Api.V3.Metadata
{ {
@ -23,14 +23,14 @@ namespace Sonarr.Api.V3.Metadata
return resource; return resource;
} }
public override MetadataDefinition ToModel(MetadataResource resource) public override MetadataDefinition ToModel(MetadataResource resource, MetadataDefinition existingDefinition)
{ {
if (resource == null) if (resource == null)
{ {
return null; return null;
} }
var definition = base.ToModel(resource); var definition = base.ToModel(resource, existingDefinition);
definition.Enable = resource.Enable; definition.Enable = resource.Enable;

View File

@ -1,4 +1,4 @@
using NzbDrone.Core.Notifications; using NzbDrone.Core.Notifications;
namespace Sonarr.Api.V3.Notifications namespace Sonarr.Api.V3.Notifications
{ {
@ -61,14 +61,14 @@ namespace Sonarr.Api.V3.Notifications
return resource; return resource;
} }
public override NotificationDefinition ToModel(NotificationResource resource) public override NotificationDefinition ToModel(NotificationResource resource, NotificationDefinition existingDefinition)
{ {
if (resource == null) if (resource == null)
{ {
return default(NotificationDefinition); return default(NotificationDefinition);
} }
var definition = base.ToModel(resource); var definition = base.ToModel(resource, existingDefinition);
definition.OnGrab = resource.OnGrab; definition.OnGrab = resource.OnGrab;
definition.OnDownload = resource.OnDownload; definition.OnDownload = resource.OnDownload;

View File

@ -92,7 +92,8 @@ namespace Sonarr.Api.V3
private TProviderDefinition GetDefinition(TProviderResource providerResource, bool validate, bool includeWarnings, bool forceValidate) 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)) if (validate && (definition.Enable || forceValidate))
{ {

View File

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using NzbDrone.Common.Reflection; using NzbDrone.Common.Reflection;
using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.ThingiProvider;
using Sonarr.Http.ClientSchema; using Sonarr.Http.ClientSchema;
@ -42,7 +42,7 @@ namespace Sonarr.Api.V3
}; };
} }
public virtual TProviderDefinition ToModel(TProviderResource resource) public virtual TProviderDefinition ToModel(TProviderResource resource, TProviderDefinition existingDefinition)
{ {
if (resource == null) if (resource == null)
{ {
@ -62,7 +62,7 @@ namespace Sonarr.Api.V3
}; };
var configContract = ReflectionExtensions.CoreAssembly.FindTypeByName(definition.ConfigContract); 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; return definition;
} }

View File

@ -18,7 +18,7 @@ namespace Sonarr.Http.ClientSchema
public string SelectOptionsProviderAction { get; set; } public string SelectOptionsProviderAction { get; set; }
public string Section { get; set; } public string Section { get; set; }
public string Hidden { get; set; } public string Hidden { get; set; }
public PrivacyLevel Privacy { get; set; }
public Field Clone() public Field Clone()
{ {
return (Field)MemberwiseClone(); return (Field)MemberwiseClone();

View File

@ -13,6 +13,7 @@ namespace Sonarr.Http.ClientSchema
{ {
public static class SchemaBuilder public static class SchemaBuilder
{ {
private static readonly string PRIVATE_VALUE = "********";
private static Dictionary<Type, FieldMapping[]> _mappings = new Dictionary<Type, FieldMapping[]>(); private static Dictionary<Type, FieldMapping[]> _mappings = new Dictionary<Type, FieldMapping[]>();
public static List<Field> ToSchema(object model) public static List<Field> ToSchema(object model)
@ -26,7 +27,15 @@ namespace Sonarr.Http.ClientSchema
foreach (var mapping in mappings) foreach (var mapping in mappings)
{ {
var field = mapping.Field.Clone(); 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); result.Add(field);
} }
@ -34,7 +43,7 @@ namespace Sonarr.Http.ClientSchema
return result.OrderBy(r => r.Order).ToList(); return result.OrderBy(r => r.Order).ToList();
} }
public static object ReadFromSchema(List<Field> fields, Type targetType) public static object ReadFromSchema(List<Field> fields, Type targetType, object model)
{ {
Ensure.That(targetType, () => targetType).IsNotNull(); Ensure.That(targetType, () => targetType).IsNotNull();
@ -49,18 +58,25 @@ namespace Sonarr.Http.ClientSchema
if (field != null) 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; return target;
} }
public static T ReadFromSchema<T>(List<Field> fields)
{
return (T)ReadFromSchema(fields, typeof(T));
}
// Ideally this function should begin a System.Linq.Expression expression tree since it's faster. // 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. // But it's probably not needed till performance issues pop up.
public static FieldMapping[] GetFieldMappings(Type type) public static FieldMapping[] GetFieldMappings(Type type)
@ -104,7 +120,8 @@ namespace Sonarr.Http.ClientSchema
Order = fieldAttribute.Order, Order = fieldAttribute.Order,
Advanced = fieldAttribute.Advanced, Advanced = fieldAttribute.Advanced,
Type = fieldAttribute.Type.ToString().FirstCharToLower(), Type = fieldAttribute.Type.ToString().FirstCharToLower(),
Section = fieldAttribute.Section Section = fieldAttribute.Section,
Privacy = fieldAttribute.Privacy
}; };
if (fieldAttribute.Type == FieldType.Select || fieldAttribute.Type == FieldType.TagSelect) if (fieldAttribute.Type == FieldType.Select || fieldAttribute.Type == FieldType.TagSelect)
@ -131,7 +148,7 @@ namespace Sonarr.Http.ClientSchema
Field = field, Field = field,
PropertyType = propertyInfo.PropertyType, PropertyType = propertyInfo.PropertyType,
GetterFunc = t => propertyInfo.GetValue(targetSelector(t), null), 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 else