diff --git a/Rules/AvoidUserNameAndPasswordParams.cs b/Rules/AvoidUserNameAndPasswordParams.cs index 823ca6e3c..422b58080 100644 --- a/Rules/AvoidUserNameAndPasswordParams.cs +++ b/Rules/AvoidUserNameAndPasswordParams.cs @@ -11,6 +11,7 @@ // using System; +using System.Linq; using System.Collections.Generic; using System.Management.Automation; using System.Management.Automation.Language; @@ -55,7 +56,9 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) TypeInfo paramType = (TypeInfo)paramAst.StaticType; String paramName = paramAst.Name.VariablePath.ToString(); - if (paramType == typeof(PSCredential) || (paramType.IsArray && paramType.GetElementType() == typeof (PSCredential))) + // if this is pscredential type with credential attribute, skip + if ((paramType == typeof(PSCredential) || (paramType.IsArray && paramType.GetElementType() == typeof (PSCredential))) + && paramAst.Attributes.Any(paramAttribute => paramAttribute.TypeName.GetReflectionType() == typeof(CredentialAttribute))) { continue; } diff --git a/Rules/Strings.Designer.cs b/Rules/Strings.Designer.cs index 448fb0efe..22b8a9191 100644 --- a/Rules/Strings.Designer.cs +++ b/Rules/Strings.Designer.cs @@ -421,7 +421,7 @@ internal static string AvoidUsernameAndPasswordParamsCommonName { } /// - /// Looks up a localized string similar to Functions should only take in a credential parameter of type PSCredential instead of username and password parameters.. + /// Looks up a localized string similar to Functions should only take in a credential parameter of type PSCredential with CredentialAttribute instead of username and password parameters.. /// internal static string AvoidUsernameAndPasswordParamsDescription { get { @@ -430,7 +430,7 @@ internal static string AvoidUsernameAndPasswordParamsDescription { } /// - /// Looks up a localized string similar to Function '{0}' has both username and password parameters. A credential parameter of type PSCredential should be used.. + /// Looks up a localized string similar to Function '{0}' has both username and password parameters. A credential parameter of type PSCredential with a CredentialAttribute should be used.. /// internal static string AvoidUsernameAndPasswordParamsError { get { @@ -1771,7 +1771,7 @@ internal static string UsePSCredentialTypeCommonName { } /// - /// Looks up a localized string similar to Checks that cmdlets that have a Credential parameter accept PSCredential. This comes from the PowerShell teams best practices.. + /// Looks up a localized string similar to Checks that cmdlets that have a Credential parameter accept PSCredential with CredentialAttribute. This comes from the PowerShell teams best practices.. /// internal static string UsePSCredentialTypeDescription { get { @@ -1780,7 +1780,7 @@ internal static string UsePSCredentialTypeDescription { } /// - /// Looks up a localized string similar to The Credential parameter in '{0}' must be of the type PSCredential.. + /// Looks up a localized string similar to The Credential parameter in '{0}' must be of the type PSCredential with CredentialAttribute.. /// internal static string UsePSCredentialTypeError { get { @@ -1789,7 +1789,7 @@ internal static string UsePSCredentialTypeError { } /// - /// Looks up a localized string similar to The Credential parameter in a found script block must be of the type PSCredential.. + /// Looks up a localized string similar to The Credential parameter in a found script block must be of the type PSCredential with CredentialAttribute.. /// internal static string UsePSCredentialTypeErrorSB { get { diff --git a/Rules/Strings.resx b/Rules/Strings.resx index f297f16b0..d27bbb8d7 100644 --- a/Rules/Strings.resx +++ b/Rules/Strings.resx @@ -217,13 +217,13 @@ One Char - Checks that cmdlets that have a Credential parameter accept PSCredential. This comes from the PowerShell teams best practices. + Checks that cmdlets that have a Credential parameter accept PSCredential with CredentialAttribute. This comes from the PowerShell teams best practices. - The Credential parameter in '{0}' must be of the type PSCredential. + The Credential parameter in '{0}' must be of the type PSCredential with CredentialAttribute. - The Credential parameter in a found script block must be of the type PSCredential. + The Credential parameter in a found script block must be of the type PSCredential with CredentialAttribute. PSCredential @@ -511,10 +511,10 @@ Avoid Using Username and Password Parameters - Functions should only take in a credential parameter of type PSCredential instead of username and password parameters. + Functions should only take in a credential parameter of type PSCredential with CredentialAttribute instead of username and password parameters. - Function '{0}' has both username and password parameters. A credential parameter of type PSCredential should be used. + Function '{0}' has both username and password parameters. A credential parameter of type PSCredential with a CredentialAttribute should be used. AvoidUsingUserNameAndPassWordParams diff --git a/Rules/UsePSCredentialType.cs b/Rules/UsePSCredentialType.cs index 99ddbd70f..57e4309a0 100644 --- a/Rules/UsePSCredentialType.cs +++ b/Rules/UsePSCredentialType.cs @@ -11,6 +11,8 @@ // using System; +using System.Reflection; +using System.Linq; using System.Collections.Generic; using System.Management.Automation; using System.Management.Automation.Language; @@ -50,7 +52,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) { foreach (ParameterAst parameter in funcDefAst.Parameters) { - if (parameter.Name.VariablePath.UserPath.Equals("Credential", StringComparison.OrdinalIgnoreCase) && parameter.StaticType != typeof(PSCredential)) + if (WrongCredentialUsage(parameter)) { yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.UsePSCredentialTypeError, funcName), funcDefAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName); } @@ -61,7 +63,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) { foreach (ParameterAst parameter in funcDefAst.Body.ParamBlock.Parameters) { - if (parameter.Name.VariablePath.UserPath.Equals("Credential", StringComparison.OrdinalIgnoreCase) && parameter.StaticType != typeof(PSCredential)) + if (WrongCredentialUsage(parameter)) { yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.UsePSCredentialTypeError, funcName), funcDefAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName); } @@ -75,7 +77,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) { foreach (ParameterAst parameter in scriptBlockAst.ParamBlock.Parameters) { - if (parameter.Name.VariablePath.UserPath.Equals("Credential", StringComparison.OrdinalIgnoreCase) && parameter.StaticType != typeof(PSCredential)) + if (WrongCredentialUsage(parameter)) { yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.UsePSCredentialTypeErrorSB), scriptBlockAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName); } @@ -84,6 +86,24 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) } } + private bool WrongCredentialUsage(ParameterAst parameter) + { + if (parameter.Name.VariablePath.UserPath.Equals("Credential", StringComparison.OrdinalIgnoreCase)) + { + TypeInfo paramType = (TypeInfo)parameter.StaticType; + + if ((paramType == typeof(PSCredential) || (paramType.IsArray && paramType.GetElementType() == typeof(PSCredential))) + && parameter.Attributes.Any(paramAttribute => paramAttribute.TypeName.GetReflectionType() == typeof(CredentialAttribute))) + { + return false; + } + + return true; + } + + return false; + } + /// /// GetName: Retrieves the name of this rule. /// diff --git a/Tests/Rules/AvoidUserNameAndPasswordParams.tests.ps1 b/Tests/Rules/AvoidUserNameAndPasswordParams.tests.ps1 index 14f567160..358997fe8 100644 --- a/Tests/Rules/AvoidUserNameAndPasswordParams.tests.ps1 +++ b/Tests/Rules/AvoidUserNameAndPasswordParams.tests.ps1 @@ -1,6 +1,6 @@ Import-Module PSScriptAnalyzer -$violationMessage = "Function 'Verb-Noun' has both username and password parameters. A credential parameter of type PSCredential should be used." +$violationMessage = "Function 'Verb-Noun' has both username and password parameters. A credential parameter of type PSCredential with a CredentialAttribute should be used." $violationName = "PSAvoidUsingUserNameAndPasswordParams" $directory = Split-Path -Parent $MyInvocation.MyCommand.Path $violations = Invoke-ScriptAnalyzer $directory\AvoidUserNameAndPasswordParams.ps1 | Where-Object {$_.RuleName -eq $violationName} diff --git a/Tests/Rules/AvoidUserNameAndPasswordParamsNoViolations.ps1 b/Tests/Rules/AvoidUserNameAndPasswordParamsNoViolations.ps1 index f964458cf..b909d7b59 100644 --- a/Tests/Rules/AvoidUserNameAndPasswordParamsNoViolations.ps1 +++ b/Tests/Rules/AvoidUserNameAndPasswordParamsNoViolations.ps1 @@ -8,6 +8,24 @@ function MyFunction2 ($param1, $passwords) } -function MyFunction3 ([PSCredential]$username, $passwords) +function MyFunction3 { + [CmdletBinding()] + [Alias()] + [OutputType([int])] + Param + ( + # Param1 help description + [Parameter(Mandatory=$true, + ValueFromPipelineByPropertyName=$true, + Position=0)] + [System.Management.Automation.CredentialAttribute()] + [pscredential] + $UserName, + + # Param2 help description + [pscredential] + [System.Management.Automation.CredentialAttribute()] + $Password + ) } diff --git a/Tests/Rules/PSCredentialType.tests.ps1 b/Tests/Rules/PSCredentialType.tests.ps1 index 50a5f083c..ccf13b6ef 100644 --- a/Tests/Rules/PSCredentialType.tests.ps1 +++ b/Tests/Rules/PSCredentialType.tests.ps1 @@ -1,5 +1,5 @@ Import-Module PSScriptAnalyzer -$violationMessage = "The Credential parameter in 'Credential' must be of the type PSCredential." +$violationMessage = "The Credential parameter in 'Credential' must be of the type PSCredential with CredentialAttribute." $violationName = "PSUsePSCredentialType" $directory = Split-Path -Parent $MyInvocation.MyCommand.Path $violations = Invoke-ScriptAnalyzer $directory\PSCredentialType.ps1 | Where-Object {$_.RuleName -eq $violationName} diff --git a/Tests/Rules/PSCredentialTypeNoViolations.ps1 b/Tests/Rules/PSCredentialTypeNoViolations.ps1 index ebe613883..b907929b8 100644 --- a/Tests/Rules/PSCredentialTypeNoViolations.ps1 +++ b/Tests/Rules/PSCredentialTypeNoViolations.ps1 @@ -1,3 +1,16 @@ -function Credential([pscredential]$credential) { - +function Credential +{ + [CmdletBinding()] + [Alias()] + [OutputType([int])] + Param + ( + # Param1 help description + [Parameter(Mandatory=$true, + ValueFromPipelineByPropertyName=$true, + Position=0)] + [System.Management.Automation.CredentialAttribute()] + [pscredential] + $Credential + ) } \ No newline at end of file