Skip to content

Commit

Permalink
#231 - Analyze actual usage of properties and include the results in …
Browse files Browse the repository at this point in the history
…the security overview
  • Loading branch information
ascott18 committed Apr 29, 2022
1 parent ba52ccf commit 555aca4
Show file tree
Hide file tree
Showing 20 changed files with 282 additions and 147 deletions.
1 change: 1 addition & 0 deletions src/Coalesce.Web.Vue/src/components/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<div class="nav-items">

<v-btn text to="/">Home</v-btn>
<v-btn text href="/coalesce-security">Security Overview</v-btn>

<v-menu offset-y>
<template #activator="{on}">
Expand Down
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<RepositoryUrl>https://github.com/IntelliTect/Coalesce</RepositoryUrl>
<RepositoryType>git</RepositoryType>

<LangVersion>8.0</LangVersion>
<LangVersion>9.0</LangVersion>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ private void WriteClassMethodMetadata(TypeScriptCodeBuilder b, ClassViewModel mo

using (b.Block("return:", ','))
{
WriteValueCommonMetadata(b, new MethodReturnViewModel(method));
WriteValueCommonMetadata(b, method.Return);
b.StringProp("role", "value");
}
}
Expand Down Expand Up @@ -666,39 +666,5 @@ private void WriteTypeCommonMetadata(TypeScriptCodeBuilder b, TypeViewModel type

}
}

/// <summary>
/// Shim of a method's return value so its metadata can be emitted
/// with WriteValueCommonMetadata
/// </summary>
private class MethodReturnViewModel : IValueViewModel
{
#nullable enable
public MethodReturnViewModel(MethodViewModel method)
{
Method = method;
Type = method.ResultType;
}

public string Name => "$return";

public string JsVariable => Name;

public string DisplayName => "Result"; // TODO: i18n

public string? Description => null;

public TypeViewModel Type { get; }

public TypeViewModel PureType => Type.PureType;

public MethodViewModel Method { get; }

public object? GetAttributeValue<TAttribute>(string valueName) where TAttribute : Attribute
=> Method.GetAttributeValue<TAttribute>(valueName);

public bool HasAttribute<TAttribute>() where TAttribute : Attribute
=> Method.HasAttribute<TAttribute>();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,15 @@ ICollection<ExternalParent> collection
return collection.FirstOrDefault() ?? single;
}

[Coalesce, Execute]
public ExternalParentAsOutputOnly MethodWithExternalTypesWithSinglePurpose(
ExternalParentAsInputOnly single,
ICollection<ExternalParentAsInputOnly> collection
)
{
return new ExternalParentAsOutputOnly();
}

[Coalesce, Execute]
public void MethodWithSingleFileParameter(IFile file) { }

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,29 @@ public class ExternalParent
public ICollection<ExternalChild?> RefNullableICollection { get; set; }
public ICollection<ExternalChild>? RefICollectionNullable { get; set; }
}

public class ExternalChild
{
public string Value { get; set; }
}

public class ExternalParentAsInputOnly
{
public ExternalChildAsInputOnly Child { get; set; }
}
public class ExternalChildAsInputOnly
{
public string Value { get; set; }
public ExternalParentAsInputOnly Recursive { get; set; }
}

public class ExternalParentAsOutputOnly
{
public ExternalChildAsOutputOnly Child { get; set; }
}
public class ExternalChildAsOutputOnly
{
public string Value { get; set; }
public ExternalParentAsOutputOnly Recursive { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using IntelliTect.Coalesce.Tests.Util;
using IntelliTect.Coalesce.Tests.TargetClasses.TestDbContext;
using IntelliTect.Coalesce.Tests.Util;
using System;
using System.Collections.Generic;
using System.Text;
Expand Down Expand Up @@ -44,5 +45,19 @@ public void DefaultOrderBy_UsesNavigationDirectlyWhenOrderingByUnorderableRefNav
}
);
}

[Theory]
[ClassViewModelData(typeof(ExternalParentAsInputOnly), false, true)]
[ClassViewModelData(typeof(ExternalChildAsInputOnly), false, true)]
[ClassViewModelData(typeof(ExternalParentAsOutputOnly), true, false)]
[ClassViewModelData(typeof(ExternalChildAsOutputOnly), true, false)]
public void ExternalType_PropertySecurityReflectsActualUsage(ClassViewModelData data, bool read, bool write)
{
Assert.All(data.ClassViewModel.ClientProperties, p =>
{
Assert.Equal(read, !p.SecurityInfo.Read.IsUnused);
Assert.Equal(write, !p.SecurityInfo.Edit.IsUnused);
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,23 +57,19 @@ string expectedPropertyType
}


// SymbolClassViewModelData here is being used because
// we don't ever use this method in reflection contexts.
// It does seem to actually output correct type for Reflection, but it emits
// "System.Int32" instead of "int", among other things, failing the assertions.
[Theory]
[SymbolClassViewModelData(typeof(ExternalParent), nameof(ExternalParent.ValueArray), "int[]")]
[SymbolClassViewModelData(typeof(ExternalParent), nameof(ExternalParent.ValueNullableArray), "int?[]")]
[SymbolClassViewModelData(typeof(ExternalParent), nameof(ExternalParent.ValueArrayNullable), "int[]")]
[SymbolClassViewModelData(typeof(ExternalParent), nameof(ExternalParent.ValueICollection), "System.Collections.Generic.ICollection<int>")]
[SymbolClassViewModelData(typeof(ExternalParent), nameof(ExternalParent.ValueNullableICollection), "System.Collections.Generic.ICollection<int?>")]
[SymbolClassViewModelData(typeof(ExternalParent), nameof(ExternalParent.ValueICollectionNullable), "System.Collections.Generic.ICollection<int>")]
[SymbolClassViewModelData(typeof(ExternalParent), nameof(ExternalParent.RefArray), "MyProject.ExternalChildDtoGen[]")]
[SymbolClassViewModelData(typeof(ExternalParent), nameof(ExternalParent.RefNullableArray), "MyProject.ExternalChildDtoGen[]")]
[SymbolClassViewModelData(typeof(ExternalParent), nameof(ExternalParent.RefArrayNullable), "MyProject.ExternalChildDtoGen[]")]
[SymbolClassViewModelData(typeof(ExternalParent), nameof(ExternalParent.RefICollection), "System.Collections.Generic.ICollection<MyProject.ExternalChildDtoGen>")]
[SymbolClassViewModelData(typeof(ExternalParent), nameof(ExternalParent.RefNullableICollection), "System.Collections.Generic.ICollection<MyProject.ExternalChildDtoGen>")]
[SymbolClassViewModelData(typeof(ExternalParent), nameof(ExternalParent.RefICollectionNullable), "System.Collections.Generic.ICollection<MyProject.ExternalChildDtoGen>")]
[ClassViewModelData(typeof(ExternalParent), nameof(ExternalParent.ValueArray), "int[]")]
[ClassViewModelData(typeof(ExternalParent), nameof(ExternalParent.ValueNullableArray), "int?[]")]
[ClassViewModelData(typeof(ExternalParent), nameof(ExternalParent.ValueArrayNullable), "int[]")]
[ClassViewModelData(typeof(ExternalParent), nameof(ExternalParent.ValueICollection), "System.Collections.Generic.ICollection<int>")]
[ClassViewModelData(typeof(ExternalParent), nameof(ExternalParent.ValueNullableICollection), "System.Collections.Generic.ICollection<int?>")]
[ClassViewModelData(typeof(ExternalParent), nameof(ExternalParent.ValueICollectionNullable), "System.Collections.Generic.ICollection<int>")]
[ClassViewModelData(typeof(ExternalParent), nameof(ExternalParent.RefArray), "MyProject.ExternalChildDtoGen[]")]
[ClassViewModelData(typeof(ExternalParent), nameof(ExternalParent.RefNullableArray), "MyProject.ExternalChildDtoGen[]")]
[ClassViewModelData(typeof(ExternalParent), nameof(ExternalParent.RefArrayNullable), "MyProject.ExternalChildDtoGen[]")]
[ClassViewModelData(typeof(ExternalParent), nameof(ExternalParent.RefICollection), "System.Collections.Generic.ICollection<MyProject.ExternalChildDtoGen>")]
[ClassViewModelData(typeof(ExternalParent), nameof(ExternalParent.RefNullableICollection), "System.Collections.Generic.ICollection<MyProject.ExternalChildDtoGen>")]
[ClassViewModelData(typeof(ExternalParent), nameof(ExternalParent.RefICollectionNullable), "System.Collections.Generic.ICollection<MyProject.ExternalChildDtoGen>")]
public void NullableTypeForDto_HandlesCollectionsProperly(
ClassViewModelData data,
string propertyName,
Expand Down
7 changes: 5 additions & 2 deletions src/IntelliTect.Coalesce.Tests/Util/ClassViewModelData.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using IntelliTect.Coalesce.TypeDefinition;
using IntelliTect.Coalesce.Models;
using IntelliTect.Coalesce.TypeDefinition;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Expand Down Expand Up @@ -60,7 +61,9 @@ public void Deserialize(IXunitSerializationInfo info)
var targetType = info.GetValue<string>(nameof(TargetType));
var viewModelType = info.GetValue<string>(nameof(ViewModelType));

TargetType = Type.GetType(targetType);
TargetType = Type.GetType(targetType) ??
typeof(ApiResult).Assembly.GetType(targetType) ??
throw new Exception($"Unable to locate type {targetType}");

switch (viewModelType)
{
Expand Down
38 changes: 20 additions & 18 deletions src/IntelliTect.Coalesce/Application/SecurityOverview.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,9 @@

.toc a {
transition: all 50ms ease-in-out;
letter-spacing: 0.04rem;
}
.toc a.active {
font-weight: 500;
letter-spacing: 0rem;
}
</style>
</head>
Expand Down Expand Up @@ -73,23 +71,22 @@ <h6 class="mt-4">External Types</h6>
defined using
<a
href="https://coalesce.readthedocs.io/en/latest/pages/modeling/model-components/attributes/security-attribute"
> attributes </a
>attributes</a
>.
</p>
<p>
Due to their nature, dynamically implemented security rules are
not accounted for - for example, custom logic implemented inside
<a
href="https://coalesce.readthedocs.io/en/latest/pages/modeling/model-components/data-sources/"
>Data Sources</a
>
Data Sources
</a>
and
<a
href="https://coalesce.readthedocs.io/en/latest/pages/modeling/model-components/behaviors/"
>
Behaviors </a
>. You should write automated tests to verify the proper functioning of your custom code.
>Behaviors</a
>. You should write automated tests to verify the proper
functioning of your custom code.
</p>

<div class="alert alert-warning" role="alert">
Expand Down Expand Up @@ -133,7 +130,7 @@ <h4 class="card-title">
</div>

<div class="row">
<div class="col col-4">
<div class="col" style="flex-basis: 450px; max-width: 700px">
<div class="card mb-4">
<div class="card-body">
<h4 class="card-title">Generated DTO Properties</h4>
Expand All @@ -144,7 +141,7 @@ <h4 class="card-title">Generated DTO Properties</h4>
</div>
</div>

<div class="col col-4">
<div class="col" style="flex-basis: 450px; max-width: 700px">
<div class="card mb-4">
<div class="card-body">
<h4 class="card-title">Methods</h4>
Expand All @@ -153,16 +150,16 @@ <h4 class="card-title">Methods</h4>
</div>
</div>

<div class="col col-4">
<div class="col" style="flex-basis: 500px">
<div class="card mb-4">
<div class="card-body">
<template v-if="type.behaviorsTypeName">
<h4 class="card-title">Behaviors</h4>
<span class=" text-break">
<span class="text-break">
{{type.behaviorsTypeName}}
</span>
<br>
<br>
<br />
<br />
</template>

<h4 class="card-title">Data Sources</h4>
Expand Down Expand Up @@ -213,7 +210,7 @@ <h1>
</h1>

<div class="row">
<div class="col col-4">
<div class="col" style="flex-basis: 450px; max-width: 700px">
<div class="card mb-4">
<div class="card-body">
<h4 class="card-title">Methods</h4>
Expand All @@ -232,7 +229,7 @@ <h4 class="card-title">Methods</h4>
<h1>{{type.name}}</h1>

<div class="row">
<div class="col col-4">
<div class="col" style="flex-basis: 450px; max-width: 700px">
<div class="card mb-4">
<div class="card-body">
<h4 class="card-title">Generated DTO Properties</h4>
Expand Down Expand Up @@ -265,7 +262,7 @@ <h4 class="card-title">Generated DTO Properties</h4>
<small>
(<template v-for="(param, idx) in method.parameters">
<span v-if="idx > 0">, </span>
<type-display :member="param.type"></type-display> </template
<type-display :member="param.type"></type-display> {{param.name}} </template
>) => <type-display :member="method.return"></type-display>
</small>
</td>
Expand Down Expand Up @@ -323,7 +320,12 @@ <h4 class="card-title">Generated DTO Properties</h4>
<span v-else-if="action.allowAnonymous" class="badge bg-danger">
Allow Anonymous
</span>
<span v-else class="badge bg-success">
<span
v-else
class="badge"
:class="action.isUnused ? 'border border-success text-dark' : 'bg-success'"
:title="action.isUnused ? 'Analyzed as unused by any API endpoint.' : ''"
>
Allow {{'allowAnonymous' in action ? 'Authenticated' : ''}}
<div v-if="action.roleList.length" class="fw-normal text-start">
{{action.roles}}
Expand Down
7 changes: 2 additions & 5 deletions src/IntelliTect.Coalesce/TypeDefinition/ClassViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public abstract class ClassViewModel : IAttributeProvider
protected IReadOnlyCollection<PropertyViewModel>? _Properties;
protected IReadOnlyCollection<MethodViewModel>? _Methods;

internal HashSet<IValueViewModel> _Usages = new HashSet<IValueViewModel>();

public ReflectionRepository? ReflectionRepository => Type.ReflectionRepository;

protected ClassViewModel(TypeViewModel type)
Expand Down Expand Up @@ -179,11 +181,6 @@ internal IReadOnlyCollection<PropertyViewModel> Properties
/// </summary>
public IEnumerable<PropertyViewModel> ClientProperties => Properties.Where(p => p.IsClientProperty);

/// <summary>
/// Properties on the class that are available on the admin page. This is not filtered by IsHidden.
/// </summary>
public IEnumerable<PropertyViewModel> AdminPageProperties => ClientProperties;

public IEnumerable<PropertyViewModel> DataSourceParameters => Properties
.Where(p =>
!p.IsInternalUse && p.HasPublicSetter && p.HasAttribute<CoalesceAttribute>()
Expand Down
6 changes: 6 additions & 0 deletions src/IntelliTect.Coalesce/TypeDefinition/IValueViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,14 @@ public interface IValueViewModel : IAttributeProvider

string? Description { get; }

/// <summary>
/// Gets the raw, unaltered type of the value.
/// </summary>
TypeViewModel Type { get; }

/// <summary>
/// Gets the type without any collection around it.
/// </summary>
TypeViewModel PureType { get; }
}
}
38 changes: 38 additions & 0 deletions src/IntelliTect.Coalesce/TypeDefinition/MethodReturnViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;

#nullable enable

namespace IntelliTect.Coalesce.TypeDefinition
{
/// <summary>
/// Shim of a method's return value so it can be treated as an IValueViewModel.
/// </summary>
public class MethodReturnViewModel : IValueViewModel
{
internal MethodReturnViewModel(MethodViewModel method)
{
Method = method;
Type = method.ResultType;
}

public string Name => "$return";

public string JsVariable => Name;

public string DisplayName => "Result"; // TODO: i18n

public string? Description => null;

public TypeViewModel Type { get; }

public TypeViewModel PureType => Type.PureType;

public MethodViewModel Method { get; }

public object? GetAttributeValue<TAttribute>(string valueName) where TAttribute : Attribute
=> Method.GetAttributeValue<TAttribute>(valueName);

public bool HasAttribute<TAttribute>() where TAttribute : Attribute
=> Method.HasAttribute<TAttribute>();
}
}

0 comments on commit 555aca4

Please sign in to comment.