Skip to content

Commit 7b1a13a

Browse files
author
Quoc Truong
committed
Merge pull request #391 from PowerShell/AddCredentialAttributeCheck
Add an extra check for CredentialAttribute for the credential rules
2 parents 3b3f0ed + 21269c8 commit 7b1a13a

8 files changed

+73
-19
lines changed

Rules/AvoidUserNameAndPasswordParams.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//
1212

1313
using System;
14+
using System.Linq;
1415
using System.Collections.Generic;
1516
using System.Management.Automation;
1617
using System.Management.Automation.Language;
@@ -55,7 +56,9 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
5556
TypeInfo paramType = (TypeInfo)paramAst.StaticType;
5657
String paramName = paramAst.Name.VariablePath.ToString();
5758

58-
if (paramType == typeof(PSCredential) || (paramType.IsArray && paramType.GetElementType() == typeof (PSCredential)))
59+
// if this is pscredential type with credential attribute, skip
60+
if ((paramType == typeof(PSCredential) || (paramType.IsArray && paramType.GetElementType() == typeof (PSCredential)))
61+
&& paramAst.Attributes.Any(paramAttribute => paramAttribute.TypeName.GetReflectionType() == typeof(CredentialAttribute)))
5962
{
6063
continue;
6164
}

Rules/Strings.Designer.cs

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Rules/Strings.resx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -217,13 +217,13 @@
217217
<value>One Char</value>
218218
</data>
219219
<data name="UsePSCredentialTypeDescription" xml:space="preserve">
220-
<value>Checks that cmdlets that have a Credential parameter accept PSCredential. This comes from the PowerShell teams best practices.</value>
220+
<value>Checks that cmdlets that have a Credential parameter accept PSCredential with CredentialAttribute. This comes from the PowerShell teams best practices.</value>
221221
</data>
222222
<data name="UsePSCredentialTypeError" xml:space="preserve">
223-
<value>The Credential parameter in '{0}' must be of the type PSCredential.</value>
223+
<value>The Credential parameter in '{0}' must be of the type PSCredential with CredentialAttribute.</value>
224224
</data>
225225
<data name="UsePSCredentialTypeErrorSB" xml:space="preserve">
226-
<value>The Credential parameter in a found script block must be of the type PSCredential.</value>
226+
<value>The Credential parameter in a found script block must be of the type PSCredential with CredentialAttribute.</value>
227227
</data>
228228
<data name="UsePSCredentialTypeCommonName" xml:space="preserve">
229229
<value>PSCredential</value>
@@ -511,10 +511,10 @@
511511
<value>Avoid Using Username and Password Parameters</value>
512512
</data>
513513
<data name="AvoidUsernameAndPasswordParamsDescription" xml:space="preserve">
514-
<value>Functions should only take in a credential parameter of type PSCredential instead of username and password parameters.</value>
514+
<value>Functions should only take in a credential parameter of type PSCredential with CredentialAttribute instead of username and password parameters.</value>
515515
</data>
516516
<data name="AvoidUsernameAndPasswordParamsError" xml:space="preserve">
517-
<value>Function '{0}' has both username and password parameters. A credential parameter of type PSCredential should be used.</value>
517+
<value>Function '{0}' has both username and password parameters. A credential parameter of type PSCredential with a CredentialAttribute should be used.</value>
518518
</data>
519519
<data name="AvoidUsernameAndPasswordParamsName" xml:space="preserve">
520520
<value>AvoidUsingUserNameAndPassWordParams</value>

Rules/UsePSCredentialType.cs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
//
1212

1313
using System;
14+
using System.Reflection;
15+
using System.Linq;
1416
using System.Collections.Generic;
1517
using System.Management.Automation;
1618
using System.Management.Automation.Language;
@@ -50,7 +52,7 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
5052
{
5153
foreach (ParameterAst parameter in funcDefAst.Parameters)
5254
{
53-
if (parameter.Name.VariablePath.UserPath.Equals("Credential", StringComparison.OrdinalIgnoreCase) && parameter.StaticType != typeof(PSCredential))
55+
if (WrongCredentialUsage(parameter))
5456
{
5557
yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.UsePSCredentialTypeError, funcName), funcDefAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName);
5658
}
@@ -61,7 +63,7 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
6163
{
6264
foreach (ParameterAst parameter in funcDefAst.Body.ParamBlock.Parameters)
6365
{
64-
if (parameter.Name.VariablePath.UserPath.Equals("Credential", StringComparison.OrdinalIgnoreCase) && parameter.StaticType != typeof(PSCredential))
66+
if (WrongCredentialUsage(parameter))
6567
{
6668
yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.UsePSCredentialTypeError, funcName), funcDefAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName);
6769
}
@@ -75,7 +77,7 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
7577
{
7678
foreach (ParameterAst parameter in scriptBlockAst.ParamBlock.Parameters)
7779
{
78-
if (parameter.Name.VariablePath.UserPath.Equals("Credential", StringComparison.OrdinalIgnoreCase) && parameter.StaticType != typeof(PSCredential))
80+
if (WrongCredentialUsage(parameter))
7981
{
8082
yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.UsePSCredentialTypeErrorSB), scriptBlockAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName);
8183
}
@@ -84,6 +86,24 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
8486
}
8587
}
8688

89+
private bool WrongCredentialUsage(ParameterAst parameter)
90+
{
91+
if (parameter.Name.VariablePath.UserPath.Equals("Credential", StringComparison.OrdinalIgnoreCase))
92+
{
93+
TypeInfo paramType = (TypeInfo)parameter.StaticType;
94+
95+
if ((paramType == typeof(PSCredential) || (paramType.IsArray && paramType.GetElementType() == typeof(PSCredential)))
96+
&& parameter.Attributes.Any(paramAttribute => paramAttribute.TypeName.GetReflectionType() == typeof(CredentialAttribute)))
97+
{
98+
return false;
99+
}
100+
101+
return true;
102+
}
103+
104+
return false;
105+
}
106+
87107
/// <summary>
88108
/// GetName: Retrieves the name of this rule.
89109
/// </summary>

Tests/Rules/AvoidUserNameAndPasswordParams.tests.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Import-Module PSScriptAnalyzer
22

3-
$violationMessage = "Function 'Verb-Noun' has both username and password parameters. A credential parameter of type PSCredential should be used."
3+
$violationMessage = "Function 'Verb-Noun' has both username and password parameters. A credential parameter of type PSCredential with a CredentialAttribute should be used."
44
$violationName = "PSAvoidUsingUserNameAndPasswordParams"
55
$directory = Split-Path -Parent $MyInvocation.MyCommand.Path
66
$violations = Invoke-ScriptAnalyzer $directory\AvoidUserNameAndPasswordParams.ps1 | Where-Object {$_.RuleName -eq $violationName}

Tests/Rules/AvoidUserNameAndPasswordParamsNoViolations.ps1

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,24 @@ function MyFunction2 ($param1, $passwords)
88

99
}
1010

11-
function MyFunction3 ([PSCredential]$username, $passwords)
11+
function MyFunction3
1212
{
13+
[CmdletBinding()]
14+
[Alias()]
15+
[OutputType([int])]
16+
Param
17+
(
18+
# Param1 help description
19+
[Parameter(Mandatory=$true,
20+
ValueFromPipelineByPropertyName=$true,
21+
Position=0)]
22+
[System.Management.Automation.CredentialAttribute()]
23+
[pscredential]
24+
$UserName,
25+
26+
# Param2 help description
27+
[pscredential]
28+
[System.Management.Automation.CredentialAttribute()]
29+
$Password
30+
)
1331
}

Tests/Rules/PSCredentialType.tests.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Import-Module PSScriptAnalyzer
2-
$violationMessage = "The Credential parameter in 'Credential' must be of the type PSCredential."
2+
$violationMessage = "The Credential parameter in 'Credential' must be of the type PSCredential with CredentialAttribute."
33
$violationName = "PSUsePSCredentialType"
44
$directory = Split-Path -Parent $MyInvocation.MyCommand.Path
55
$violations = Invoke-ScriptAnalyzer $directory\PSCredentialType.ps1 | Where-Object {$_.RuleName -eq $violationName}
Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1-
function Credential([pscredential]$credential) {
2-
1+
function Credential
2+
{
3+
[CmdletBinding()]
4+
[Alias()]
5+
[OutputType([int])]
6+
Param
7+
(
8+
# Param1 help description
9+
[Parameter(Mandatory=$true,
10+
ValueFromPipelineByPropertyName=$true,
11+
Position=0)]
12+
[System.Management.Automation.CredentialAttribute()]
13+
[pscredential]
14+
$Credential
15+
)
316
}

0 commit comments

Comments
 (0)