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)
This commit is contained in:
Mark McDowall 2022-05-23 20:41:50 -07:00 committed by Bogdan
parent ed1364b6ff
commit 5034a211cb
9 changed files with 38 additions and 22 deletions

View file

@ -24,14 +24,14 @@ public override ApplicationResource ToResource(ApplicationDefinition definition)
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;

View file

@ -33,14 +33,14 @@ public override DownloadClientResource ToResource(DownloadClientDefinition defin
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;

View file

@ -25,14 +25,14 @@ public override IndexerProxyResource ToResource(IndexerProxyDefinition definitio
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;
}

View file

@ -104,14 +104,14 @@ public override IndexerResource ToResource(IndexerDefinition definition)
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))
{

View file

@ -43,14 +43,14 @@ public override NotificationResource ToResource(NotificationDefinition definitio
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;

View file

@ -142,7 +142,8 @@ public virtual ActionResult<TProviderResource> UpdateProvider([FromBody] TBulkPr
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))
{

View file

@ -45,7 +45,7 @@ public virtual TProviderResource ToResource(TProviderDefinition definition)
};
}
public virtual TProviderDefinition ToModel(TProviderResource resource)
public virtual TProviderDefinition ToModel(TProviderResource resource, TProviderDefinition existingDefinition)
{
if (resource == null)
{
@ -65,7 +65,7 @@ public virtual TProviderDefinition ToModel(TProviderResource resource)
};
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;
}

View file

@ -16,10 +16,10 @@ public class Field
public string Type { get; set; }
public bool Advanced { get; set; }
public List<SelectOption> 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; }

View file

@ -15,6 +15,7 @@ namespace Prowlarr.Http.ClientSchema
{
public static class SchemaBuilder
{
private const string PRIVATE_VALUE = "********";
private static Dictionary<Type, FieldMapping[]> _mappings = new Dictionary<Type, FieldMapping[]>();
private static ILocalizationService _localizationService;
@ -36,13 +37,19 @@ public static List<Field> ToSchema(object model)
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<Field> fields, Type targetType)
public static object ReadFromSchema(List<Field> fields, Type targetType, object model)
{
Ensure.That(targetType, () => targetType).IsNotNull();
@ -57,18 +64,25 @@ public static object ReadFromSchema(List<Field> fields, Type targetType)
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<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.
// But it's probably not needed till performance issues pop up.
public static FieldMapping[] GetFieldMappings(Type type)
@ -127,6 +141,7 @@ private static FieldMapping[] GetFieldMapping(Type type, string prefix, Func<obj
Advanced = fieldAttribute.Advanced,
Type = fieldAttribute.Type.ToString().FirstCharToLower(),
Section = fieldAttribute.Section,
Privacy = fieldAttribute.Privacy,
Placeholder = fieldAttribute.Placeholder
};
@ -159,7 +174,7 @@ private static FieldMapping[] GetFieldMapping(Type type, string prefix, Func<obj
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