diff --git a/Design/Rule0043SecretText.cs b/Design/Rule0043SecretText.cs new file mode 100644 index 00000000..255b110e --- /dev/null +++ b/Design/Rule0043SecretText.cs @@ -0,0 +1,77 @@ +using System.Collections.Immutable; +using Microsoft.Dynamics.Nav.CodeAnalysis; +using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics; +using Microsoft.Dynamics.Nav.CodeAnalysis.Symbols; + +namespace BusinessCentral.LinterCop.Design +{ + [DiagnosticAnalyzer] + public class Rule0043SecretText : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(DiagnosticDescriptors.Rule0043SecretText); + + private static readonly string authorization = "Authorization"; + private static readonly List buildInMethodNames = new List + { + "add", + "getvalues", + "tryaddwithoutvalidation" + }; + + public override void Initialize(AnalysisContext context) => context.RegisterOperationAction(new Action(this.AnalyzeHttpObjects), OperationKind.InvocationExpression); + + private void AnalyzeHttpObjects(OperationAnalysisContext ctx) + { + if (!VersionChecker.IsSupported(ctx.ContainingSymbol, VersionCompatibility.Fall2023OrGreater)) return; + + if (ctx.ContainingSymbol.GetContainingObjectTypeSymbol().IsObsoletePending || ctx.ContainingSymbol.GetContainingObjectTypeSymbol().IsObsoleteRemoved) return; + if (ctx.ContainingSymbol.IsObsoletePending || ctx.ContainingSymbol.IsObsoleteRemoved) return; + + IInvocationExpression operation = (IInvocationExpression)ctx.Operation; + + // We need at least two arguments + if (operation.Arguments.Count() < 2) return; + + switch (operation.TargetMethod.MethodKind) + { + case MethodKind.BuiltInMethod: + if (!buildInMethodNames.Contains(operation.TargetMethod.Name.ToLowerInvariant())) return; + + // We need to verify that the method is called from a HttpHeaders or HttpClient object + if (!ctx.Operation.DescendantsAndSelf().Where(x => x.GetSymbol() != null) + .Where(x => x.Type.GetNavTypeKindSafe() == NavTypeKind.HttpHeaders || x.Type.GetNavTypeKindSafe() == NavTypeKind.HttpClient) + .Any()) return; + break; + case MethodKind.Method: + if (operation.TargetMethod.ContainingType.GetNavTypeKindSafe() != NavTypeKind.Codeunit) return; + ICodeunitTypeSymbol codeunitTypeSymbol = (ICodeunitTypeSymbol)operation.TargetMethod.GetContainingObjectTypeSymbol(); + if (!SemanticFacts.IsSameName(((INamespaceSymbol)codeunitTypeSymbol.ContainingSymbol).QualifiedName, "System.RestClient")) return; + if (!SemanticFacts.IsSameName(codeunitTypeSymbol.Name, "Rest Client")) return; + if (!SemanticFacts.IsSameName(operation.TargetMethod.Name, "SetDefaultRequestHeader")) return; + break; + default: + return; + } + + if (!IsAuthorizationArgument(operation.Arguments[0])) return; + + if (operation.Arguments[1].Parameter.OriginalDefinition.GetTypeSymbol().GetNavTypeKindSafe() != NavTypeKind.SecretText) + ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0043SecretText, ctx.Operation.Syntax.GetLocation())); + } + + private static bool IsAuthorizationArgument(IArgument argument) + { + switch (argument.Syntax.Kind) + { + case SyntaxKind.LiteralExpression: + return SemanticFacts.IsSameName(argument.Value.ConstantValue.Value.ToString(), authorization); + case SyntaxKind.IdentifierName: + IOperation operand = ((IConversionExpression)argument.Value).Operand; + ILabelTypeSymbol label = (ILabelTypeSymbol)operand.GetSymbol().OriginalDefinition.GetTypeSymbol(); + return SemanticFacts.IsSameName(label.GetLabelText(), authorization); + default: + return false; + } + } + } +} \ No newline at end of file diff --git a/LinterCop.ruleset.json b/LinterCop.ruleset.json index 8eb136fd..2dec42ba 100644 --- a/LinterCop.ruleset.json +++ b/LinterCop.ruleset.json @@ -211,6 +211,11 @@ "id": "LC0042", "action": "Warning", "justification": "AutoCalcFields should only be used for FlowFields or Blob fields." + }, + { + "id": "LC0043", + "action": "Info", + "justification": "Use SecretText type to protect credentials and sensitive textual values from being revealed." } ] } \ No newline at end of file diff --git a/LinterCopAnalyzers.Generated.cs b/LinterCopAnalyzers.Generated.cs index 3a8283a8..ad028c97 100644 --- a/LinterCopAnalyzers.Generated.cs +++ b/LinterCopAnalyzers.Generated.cs @@ -48,5 +48,6 @@ public static class DiagnosticDescriptors public static readonly DiagnosticDescriptor Rule0040ExplicitlySetRunTrigger = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0040", (LocalizableString)new LocalizableResourceString("Rule0040ExplicitlySetRunTriggerTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0040ExplicitlySetRunTriggerFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0040ExplicitlySetRunTriggerDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0040"); public static readonly DiagnosticDescriptor Rule0041EmptyCaptionLocked = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0041", (LocalizableString)new LocalizableResourceString("Rule0041EmptyCaptionLockedTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0041EmptyCaptionLockedFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0041EmptyCaptionLockedDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0041"); public static readonly DiagnosticDescriptor Rule0042AutoCalcFieldsOnNormalFields = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0042", (LocalizableString)new LocalizableResourceString("Rule0042AutoCalcFieldsOnNormalFieldsTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0042AutoCalcFieldsOnNormalFieldsFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Warning, true, (LocalizableString)new LocalizableResourceString("Rule0042AutoCalcFieldsOnNormalFieldsDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0042"); + public static readonly DiagnosticDescriptor Rule0043SecretText = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0043", (LocalizableString)new LocalizableResourceString("Rule0043SecretTextTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0043SecretTextFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0043SecretTextDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0043"); } } \ No newline at end of file diff --git a/LinterCopAnalyzers.resx b/LinterCopAnalyzers.resx index cfca8930..4483746b 100644 --- a/LinterCopAnalyzers.resx +++ b/LinterCopAnalyzers.resx @@ -483,4 +483,13 @@ The SetAutoCalcFields method should only be used with FlowFields or fields of type Blob. The field {0} is not a FlowField or of type Blob. + + Use SecretText type to protect credentials and sensitive textual values from being revealed. + + + Use SecretText type to protect credentials and sensitive textual values from being revealed. + + + Use SecretText type to protect credentials and sensitive textual values from being revealed. + \ No newline at end of file diff --git a/README.md b/README.md index 3f1385ab..d0cb9927 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ Further note that you should have BcContainerHelper version 2.0.16 (or newer) in |[LC0040](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0040)|Explicitly set the `RunTrigger` parameter on build-in methods.|Info| |[LC0041](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0041)|Empty Captions should be `Locked`.|Info| |[LC0042](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0042)|`AutoCalcFields` should only be used for FlowFields or Blob fields.|Warning| - +|[LC0043](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0043)|Use `SecretText` type to protect credentials and sensitive textual values from being revealed.|Info| ## Configuration