Skip to content

force input parameter in NewAzureRoleAssignmentCommand to follow vali… #4019

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@
<Compile Include="Providers\UnregisterResourceProviderCmdletTests.cs" />
<Compile Include="ResourceGroupDeployments\RemoveAzureResourceGroupDeploymentCommandTests.cs" />
<Compile Include="ResourceGroups\SetAzureResourceGroupCommandTests.cs" />
<Compile Include="RoleAssignment\RoleAssignmentUnitTests.cs" />
<Compile Include="ScenarioTests\ActiveDirectoryTests.cs" />
<Compile Include="ScenarioTests\AuthorizationTests.cs" />
<Content Include="ScenarioTests\ProviderFeatureTests.ps1">
Expand Down Expand Up @@ -664,6 +665,9 @@
<None Include="SessionRecords\Microsoft.Azure.Commands.Resources.Test.ScenarioTests.RoleAssignmentTests\RaUserPermissions_Test.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="SessionRecords\Microsoft.Azure.Commands.Resources.Test.ScenarioTests.RoleAssignmentTests\RaValidateInputParameters.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="SessionRecords\Microsoft.Azure.Commands.Resources.Test.ScenarioTests.RoleDefinitionTests\RdNegativeScenarios.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using Microsoft.Azure.Commands.Resources.Models.Authorization;
using Microsoft.WindowsAzure.Commands.ScenarioTest;
using System;
using System.Collections.Generic;
using Xunit;

namespace Microsoft.Azure.Commands.Resources.Test {
public class RoleAssignmentUnitTests
{
private void VerifyInvalidScope(string scope, string error)
{
try
{
AuthorizationClient.ValidateScope(scope);
Assert.True(false);
}
catch(ArgumentException ex)
{
Assert.Equal(ex.Message, error);
}
}

private void VerifyValidScope(string scope)
{
AuthorizationClient.ValidateScope(scope);
}

[Fact]
[Trait(Category.AcceptanceType, Category.CheckIn)]
public void VerifyInvalidScopes()
{
Dictionary<string, string> scopeAndErrors = new Dictionary<string, string>();
scopeAndErrors.Add("test", "Scope 'test' should begin with '/subscriptions' or '/providers'.");
scopeAndErrors.Add("/subscriptions/e9ee799d-6ab2-4084-b952-e7c86344bbab/Should be 'ResourceGroups'/any group name", "Scope '/subscriptions/e9ee799d-6ab2-4084-b952-e7c86344bbab/Should be 'ResourceGroups'/any group name' should begin with '/subscriptions/<subid>/resourceGroups'.");
scopeAndErrors.Add("/subscriptions/e9ee799d-6ab2-4084-b952-e7c86344bbab/ResourceGroups", "Scope '/subscriptions/e9ee799d-6ab2-4084-b952-e7c86344bbab/ResourceGroups' should have even number of parts.");
scopeAndErrors.Add("/subscriptions/e9ee799d-6ab2-4084-b952-e7c86344bbab/ResourceGroups/", "Scope '/subscriptions/e9ee799d-6ab2-4084-b952-e7c86344bbab/ResourceGroups/' should not have any empty part.");
scopeAndErrors.Add("/subscriptions/e9ee799d-6ab2-4084-b952-e7c86344bbab/ResourceGroups/groupname/Should be 'Providers'/any provider name", "Scope '/subscriptions/e9ee799d-6ab2-4084-b952-e7c86344bbab/ResourceGroups/groupname/Should be 'Providers'/any provider name' should begin with '/subscriptions/<subid>/resourceGroups/<groupname>/providers'.");
scopeAndErrors.Add("/subscriptions/e9ee799d-6ab2-4084-b952-e7c86344bbab/ResourceGroups/groupname/Providers/providername", "Scope '/subscriptions/e9ee799d-6ab2-4084-b952-e7c86344bbab/ResourceGroups/groupname/Providers/providername' should have at least one pair of resource type and resource name. e.g. '/subscriptions/<subid>/resourceGroups/<groupname>/providers/<providername>/<resourcetype>/<resourcename>'.");
foreach (var kvp in scopeAndErrors)
{
VerifyInvalidScope(kvp.Key, kvp.Value);
}
}

[Fact]
[Trait(Category.AcceptanceType, Category.CheckIn)]
public void VerifyValidScopes()
{
List<string> validScopes = new List<string>();
validScopes.Add("/");
validScopes.Add("/subscriptions/e9ee799d-6ab2-4084-b952-e7c86344bbab");
validScopes.Add("/providers/providername");
validScopes.Add("/subscriptions/e9ee799d-6ab2-4084-b952-e7c86344bbab/ResourceGroups/groupname");
validScopes.Add("/subscriptions/e9ee799d-6ab2-4084-b952-e7c86344bbab/ResourceGroups/groupname/Providers/providername/type/typename");

foreach (var scope in validScopes)
{
VerifyValidScope(scope);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,14 @@ public void RaByResource()
{
ResourcesController.NewInstance.RunPsTest("Test-RaByResource");
}


[Fact]
[Trait(Category.AcceptanceType, Category.CheckIn)]
public void RaValidateInputParameters()
Copy link
Member

@markcowl markcowl May 26, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is necessary, but not sufficient. Please include some unit tests of just the validation method. This will allow you to easily cover all that code without requiring a powershell invocation. Make sure to cover each of the negative and positive outcomes [/, tenant resources, subscription, resource group, top-level resource, child resource, grandchild resource] #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unit test added, but basically it is doing same thing as the powershell scripts:)


In reply to: 118623381 [](ancestors = 118623381)

Copy link
Contributor Author

@wisexp wisexp May 31, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@markcowl #Resolved

{
ResourcesController.NewInstance.RunPsTest("Test-RaValidateInputParameters");
}

[Fact]
[Trait(Category.AcceptanceType, Category.CheckIn)]
public void RaByServicePrincipal()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,60 @@ function Test-RaByResource
VerifyRoleAssignmentDeleted $newAssignment
}

<#
.SYNOPSIS
Tests validate input parameters
#>
function Test-RaValidateInputParameters
{
# Setup
Add-Type -Path ".\\Microsoft.Azure.Commands.Resources.dll"

$definitionName = 'Owner'
$groups = Get-AzureRmADGroup | Select-Object -Last 1 -Wait
Assert-AreEqual 1 $groups.Count "There should be at least one group to run the test."
$resourceGroups = Get-AzureRmResourceGroup | Select-Object -Last 1 -Wait
Assert-AreEqual 1 $resourceGroups.Count "No resource group found. Unable to run the test."
$resource = Get-AzureRmResource | Select-Object -Last 1 -Wait
Assert-NotNull $resource "Cannot find any resource to continue test execution."

# Test
# Check if Scope is valid.
$scope = "/subscriptions/e9ee799d-6ab2-4084-b952-e7c86344bbab/Should be 'ResourceGroups'/any group name"
$invalidScope = "Scope '/subscriptions/e9ee799d-6ab2-4084-b952-e7c86344bbab/Should be 'ResourceGroups'/any group name' should begin with '/subscriptions/<subid>/resourceGroups'."
Assert-Throws { New-AzureRmRoleAssignment -Scope $scope -ObjectId $groups[0].Id.Guid -RoleDefinitionName $definitionName } $invalidScope

$scope = "/subscriptions/e9ee799d-6ab2-4084-b952-e7c86344bbab/ResourceGroups"
$invalidScope = "Scope '/subscriptions/e9ee799d-6ab2-4084-b952-e7c86344bbab/ResourceGroups' should have even number of parts."
Assert-Throws { New-AzureRmRoleAssignment -Scope $scope -ObjectId $groups[0].Id.Guid -RoleDefinitionName $definitionName } $invalidScope

$scope = "/subscriptions/e9ee799d-6ab2-4084-b952-e7c86344bbab/ResourceGroups/"
$invalidScope = "Scope '/subscriptions/e9ee799d-6ab2-4084-b952-e7c86344bbab/ResourceGroups/' should not have any empty part."
Assert-Throws { New-AzureRmRoleAssignment -Scope $scope -ObjectId $groups[0].Id.Guid -RoleDefinitionName $definitionName } $invalidScope

$scope = "/subscriptions/e9ee799d-6ab2-4084-b952-e7c86344bbab/ResourceGroups/groupname/Should be 'Providers'/any provider name"
$invalidScope = "Scope '/subscriptions/e9ee799d-6ab2-4084-b952-e7c86344bbab/ResourceGroups/groupname/Should be 'Providers'/any provider name' should begin with '/subscriptions/<subid>/resourceGroups/<groupname>/providers'."
Assert-Throws { New-AzureRmRoleAssignment -Scope $scope -ObjectId $groups[0].Id.Guid -RoleDefinitionName $definitionName } $invalidScope

$scope = "/subscriptions/e9ee799d-6ab2-4084-b952-e7c86344bbab/ResourceGroups/groupname/Providers/providername"
$invalidScope = "Scope '/subscriptions/e9ee799d-6ab2-4084-b952-e7c86344bbab/ResourceGroups/groupname/Providers/providername' should have at least one pair of resource type and resource name. e.g. '/subscriptions/<subid>/resourceGroups/<groupname>/providers/<providername>/<resourcetype>/<resourcename>'."
Assert-Throws { New-AzureRmRoleAssignment -Scope $scope -ObjectId $groups[0].Id.Guid -RoleDefinitionName $definitionName } $invalidScope

# Check if ResourceType is valid
[Microsoft.Azure.Commands.Resources.Models.Authorization.AuthorizationClient]::RoleAssignmentNames.Enqueue("4FAB3AF0-E7CF-4305-97D1-23A65EDCE8E6")
Assert-AreEqual $resource.ResourceType "Microsoft.Sql/servers"

# Below invalid resource type should not return 'Not supported api version'.
$resource.ResourceType = "Microsoft.Sql/"
$invalidResourceType = "Scope '/subscriptions/4004a9fd-d58e-48dc-aeb2-4a4aec58606f/resourceGroups/testrg16987/providers/Microsoft.Sql/testserver13673' should have even number of parts."
Assert-Throws { New-AzureRmRoleAssignment `
-ObjectId $groups[0].Id.Guid `
-RoleDefinitionName $definitionName `
-ResourceGroupName $resource.ResourceGroupName `
-ResourceType $resource.ResourceType `
-ResourceName $resource.Name } $invalidResourceType
}

<#
.SYNOPSIS
Tests verifies creation and deletion of a RoleAssignments for Service principal name
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,11 @@ public PSRoleAssignment CreateRoleAssignment(FilterRoleAssignmentsOptions parame
{
Guid principalId = ActiveDirectoryClient.GetObjectId(parameters.ADObjectFilter);
Guid roleAssignmentId = RoleAssignmentNames.Count == 0 ? Guid.NewGuid() : RoleAssignmentNames.Dequeue();
string scope = parameters.Scope;
ValidateScope(scope);
Copy link
Contributor

@darshanhs90 darshanhs90 Jun 2, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ValidateScope(scope); [](start = 11, length = 22)

just a afterthought,we can use the same function for other operations also correct to validate the scope(maybe a change as part of a different CR)?
As i see other operations like get and remove also have a set of parameters which takes in scope #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will consider this in a separate change.


In reply to: 119961507 [](ancestors = 119961507)

string roleDefinitionId = !string.IsNullOrEmpty(parameters.RoleDefinitionName)
? AuthorizationHelper.ConstructFullyQualifiedRoleDefinitionIdFromScopeAndIdAsGuid(parameters.Scope, GetSingleRoleDefinitionByName(parameters.RoleDefinitionName, parameters.Scope).Id)
: AuthorizationHelper.ConstructFullyQualifiedRoleDefinitionIdFromScopeAndIdAsGuid(parameters.Scope, parameters.RoleDefinitionId);
? AuthorizationHelper.ConstructFullyQualifiedRoleDefinitionIdFromScopeAndIdAsGuid(scope, GetSingleRoleDefinitionByName(parameters.RoleDefinitionName, scope).Id)
: AuthorizationHelper.ConstructFullyQualifiedRoleDefinitionIdFromScopeAndIdAsGuid(scope, parameters.RoleDefinitionId);

RoleAssignmentCreateParameters createParameters = new RoleAssignmentCreateParameters
{
Expand Down Expand Up @@ -498,5 +500,66 @@ private static void ValidateRoleDefinition(PSRoleDefinition roleDefinition)
throw new ArgumentException(ProjectResources.InvalidActions);
}
}

public static void ValidateScope(string scope)
Copy link

@perseusCode perseusCode Jun 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I am assuming that this is just the scope and you'll append /providers/Microsoft.Authorization/roleAssignments after this #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes.


In reply to: 120759096 [](ancestors = 120759096)

{

if (scope.Length == 0 || !scope.StartsWith("/"))
{
throw new ArgumentException(string.Format(ProjectResources.ScopeShouldBeginWithSubscriptionsOrProviders, scope));
}

// "/" is a valid scope.
if (string.Compare(scope, "/") == 0)
Copy link

@perseusCode perseusCode Jun 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is "/" really a valid scope .. for tenant admins I suppose ? Would a /providers/Microsoft.Authorization/roleAssignments create an assignment ? #ByDesign

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes "/" is a valid scope. everything starts from "providers/Microsoft.Authorization" is not part of the scope.


In reply to: 120757288 [](ancestors = 120757288)

{
return;
}

var parts = scope.Substring(1).Split('/'); // Skip the leading '/'

if (parts.Contains(string.Empty))
Copy link

@perseusCode perseusCode May 30, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

string.Empty [](start = 31, length = 12)

Aren't the double slashes "//" ignored by the service. I thought that should be the case (It'll be if the scope is derived from scope.) #ByDesign

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ARM does ignore the extra slash, but it is not clear to end user, and i don't think there is no benefit for end user to type double slash in the scope.


In reply to: 119178261 [](ancestors = 119178261)

{
throw new ArgumentException(string.Format(ProjectResources.ScopeShouldHaveNoEmptyPart, scope));
}

int count = parts.Count();

if (count % 2 != 0)
{
throw new ArgumentException(string.Format(ProjectResources.ScopeShouldHaveEvenNumberOfParts, scope));
}

// For Scope that on tenant level, without subscriptions, should start with "providers"
// in this case we only ensure that the number of parts is even.
if (string.Compare(parts[0], "providers", true) == 0)
{
return;
}
else if (string.Compare(parts[0], "subscriptions", true) != 0)
{
throw new ArgumentException(string.Format(ProjectResources.ScopeShouldBeginWithSubscriptionsOrProviders, scope));
}

// Now check the case that scope begins with '/subscriptions'

if (count >= 4 && string.Compare(parts[2], "resourceGroups", true) != 0)
Copy link

@perseusCode perseusCode Jun 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All these rules make sense to me. Is there a way to make sure that are there any current role assignments which are at scope which don't adhere to these rules.

At the end of the day you guys are the master of role assignments and the best judge for what the valid scope is and what is not.

One way would be to add the same validation in the RP as well for the scope and make sure that both the validations in service and at client side matches. #ByDesign

Copy link

@perseusCode perseusCode Jun 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may also do a dry run with these validations at the service side #ByDesign

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on RP side we do have similar check. i've checked all the role assignments (~1.6M) in microsoft tenant that all scopes in these assignments passed this check.


In reply to: 120758308 [](ancestors = 120758308)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have similar check on RP side.


In reply to: 120759174 [](ancestors = 120759174)

{
throw new ArgumentException(string.Format(ProjectResources.ScopeShouldBeginWithSubscriptionsAndResourceGroups, scope));
}

if (count >= 6)
{
if (string.Compare(parts[4], "providers", true) != 0)
{
throw new ArgumentException(string.Format(ProjectResources.ScopeShouldBeginWithSubscriptionsAndResourceGroupsAndProviders, scope));
}

if (count < 8)
{
throw new ArgumentException(string.Format(ProjectResources.ScopeShouldHaveAtLeastOnePairOfResourceTypeAndName, scope));
}
}

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,19 @@ public string Scope
{
get
{
string result;
string resourceIdentifier = ResourceIdentifier.ToString();

if (!string.IsNullOrEmpty(scope))
{
result = scope;
}
else if (!string.IsNullOrEmpty(resourceIdentifier))
{
result = resourceIdentifier;
return scope;
}
else

string resourceIdentifier = ResourceIdentifier.ToString();

if (!string.IsNullOrEmpty(resourceIdentifier))
{
result = null;
return resourceIdentifier;
}

return result;
return null;
}
set
{
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,24 @@
<data name="ResourceProviderNotFound" xml:space="preserve">
<value>Could not find the resource provider '{0}'.</value>
</data>
<data name="ScopeShouldBeginWithSubscriptionsAndResourceGroups" xml:space="preserve">
<value>Scope '{0}' should begin with '/subscriptions/&lt;subid&gt;/resourceGroups'.</value>
</data>
<data name="ScopeShouldBeginWithSubscriptionsOrProviders" xml:space="preserve">
<value>Scope '{0}' should begin with '/subscriptions' or '/providers'.</value>
</data>
<data name="ScopeShouldBeginWithSubscriptionsAndResourceGroupsAndProviders" xml:space="preserve">
<value>Scope '{0}' should begin with '/subscriptions/&lt;subid&gt;/resourceGroups/&lt;groupname&gt;/providers'.</value>
</data>
<data name="ScopeShouldHaveAtLeastOnePairOfResourceTypeAndName" xml:space="preserve">
<value>Scope '{0}' should have at least one pair of resource type and resource name. e.g. '/subscriptions/&lt;subid&gt;/resourceGroups/&lt;groupname&gt;/providers/&lt;providername&gt;/&lt;resourcetype&gt;/&lt;resourcename&gt;'.</value>
</data>
<data name="ScopeShouldHaveEvenNumberOfParts" xml:space="preserve">
<value>Scope '{0}' should have even number of parts.</value>
</data>
<data name="ScopeShouldHaveNoEmptyPart" xml:space="preserve">
<value>Scope '{0}' should not have any empty part.</value>
</data>
<data name="UnregisteringProvider" xml:space="preserve">
<value>Are you sure you want to unregister the provider '{0}'</value>
</data>
Expand Down