Skip to content
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

Add URI parameter to Invoke-AzRestMethod #16534

Merged
merged 6 commits into from
Nov 30, 2021
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
2 changes: 1 addition & 1 deletion src/Accounts/Accounts.Test/InvokeAzRestTests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ function Test-InvokeAzRest
Assert-AreEqual $put $response.Method
Assert-NotNull $response.Content

$response = Invoke-AzRest -ResourceGroupName $name -ApiVersion $api -Method $get
$response = Invoke-AzRest -ResourceGroupName $name -ApiVersion $api

Assert-AreEqual 200 $response.StatusCode
Assert-AreEqual $get $response.Method
Expand Down
1 change: 1 addition & 0 deletions src/Accounts/Accounts/ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
* Added optional parameter `MicrosoftGraphAccessToken` to `Connect-AzAccount`
* Added optional parameters `MicrosoftGraphEndpointResourceId`, `MicrosoftGraphUrl` to `Add-AzEnvironment` and `Set-AzEnvironment`
* Added `-AccountId` property to `UserWithSubscriptionId` parameter set of `Connect-AzAccount` which allows a user name to be pre-selected for interactive logins
* Added `-Uri` and `-ResourceId` to `Invoke-AzRestMethod`
* Added Environment auto completer to the following cmdlets: Connect-AzAccount, Get-AzEnvironment, Set-AzEnvironment, and Remove-AzEnvironment [#15991]
* Added module name and version to User-Agent string [#16291]

Expand Down
179 changes: 153 additions & 26 deletions src/Accounts/Accounts/Rest/InvokeAzRestMethodCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,16 @@

using Microsoft.Azure.Commands.Common.Authentication;
using Microsoft.Azure.Commands.Common.Authentication.Abstractions;
using Microsoft.Azure.Commands.Common.Exceptions;
using Microsoft.Azure.Commands.Profile.Models;
using Microsoft.Azure.Commands.ResourceManager.Common;
using System.Management.Automation;
using Microsoft.Azure.Internal.Common;
using Microsoft.Rest;
using Microsoft.Rest.Azure;
using Microsoft.WindowsAzure.Commands.Utilities.Common;
using System.Text;
using System.Linq;
using System;
using System.Management.Automation;
using System.Text;

namespace Microsoft.Azure.Commands.Profile.Rest
{
Expand All @@ -32,6 +33,7 @@ public class InvokeAzRestMethodCommand : AzureRMCmdlet
#region Parameter Set

public const string ByPath = "ByPath";
public const string ByURI = "ByURI";
public const string ByParameters = "ByParameters";

#endregion
Expand Down Expand Up @@ -66,10 +68,21 @@ public class InvokeAzRestMethodCommand : AzureRMCmdlet
[ValidateNotNullOrEmpty]
public string ApiVersion { get; set; }

[Parameter(Mandatory = true, HelpMessage = "Http Method")]
[Parameter(ParameterSetName = ByURI, Mandatory = true, Position = 1, HelpMessage = "Uniform Resource Identifier of the Azure resources. " +
"The target resource needs to support Azure AD authentication and the access token is derived according to resource id. " +
"If resource id is not set, its value is derived according to built-in service suffixes in current Azure Environment.")]
[ValidateNotNullOrEmpty]
public Uri Uri { get; set; }

[Parameter(ParameterSetName = ByURI, Mandatory = false, HelpMessage = "Identifier URI specified by the REST API you are calling. " +
"It shouldn't be the resource id of Azure Resource Manager.")]
[ValidateNotNullOrEmpty]
public Uri ResourceId { get; set; }

[Parameter(Mandatory = false, HelpMessage = "Specifies the method used for the web request. Defaults to GET.")]
[ValidateSet("GET", "POST", "PUT", "PATCH", "DELETE", IgnoreCase = true)]
[ValidateNotNullOrEmpty]
public string Method { get; set; }
public string Method { get; set; } = "GET";

[Parameter(Mandatory = false, HelpMessage = "JSON format payload")]
[ValidateNotNullOrEmpty]
Expand All @@ -81,20 +94,6 @@ public class InvokeAzRestMethodCommand : AzureRMCmdlet
#endregion

IAzureContext context;
private IAzureRestClient _client;
private IAzureRestClient ServiceClient
{
get
{
if (_client == null)
{
var clientFactory = AzureSession.Instance.ClientFactory;
_client = clientFactory.CreateArmClient<AzureRestClient>(context, AzureEnvironment.Endpoint.ResourceManager);
}

return _client;
}
}

public override void ExecuteCmdlet()
{
Expand All @@ -103,45 +102,173 @@ public override void ExecuteCmdlet()
context = DefaultContext;
AzureOperationResponse<string> response;

if (!this.IsParameterBound(c => c.Path))
if (ByParameters.Equals(this.ParameterSetName))
{
this.Path = ConstructPath(this.IsParameterBound(c => c.SubscriptionId) ? this.SubscriptionId : context.Subscription.Id, this.ResourceGroupName, this.ResourceProviderName, this.ResourceType, this.Name);
this.Path += $"?api-version={this.ApiVersion}";
}
else if (ByURI.Equals(this.ParameterSetName))
{
this.Path = Uri.PathAndQuery;
}

IAzureRestClient serviceClient = null;
if (ByPath.Equals(this.ParameterSetName) || ByParameters.Equals(this.ParameterSetName))
{
serviceClient = AzureSession.Instance.ClientFactory.CreateArmClient<AzureRestClient>(context, AzureEnvironment.Endpoint.ResourceManager);
}
else if (ByURI.Equals(this.ParameterSetName))
{
string targetResourceIdKey = null;
string resourceId = this.IsParameterBound(c => c.ResourceId) ? ResourceId.ToString() : null;
if(this.IsParameterBound(c => c.ResourceId)
&& context.Environment.ActiveDirectoryServiceEndpointResourceId.Equals(resourceId)
&& !HasSameEndpoint(Uri.Authority, context.Environment.ResourceManagerUrl))
{
throw new AzPSArgumentException("The resource ID of Azure Resource Manager cannot be used for other endpoint. Please make sure to input the correct resource ID that matches the request URI.",
nameof(ResourceId));
}
var targetResourceId = string.IsNullOrEmpty(resourceId) ? MatchResourceId(context, Uri.Authority, out targetResourceIdKey) : resourceId;
if (string.IsNullOrWhiteSpace(targetResourceId))
{
throw new AzPSArgumentException("Cannot find resource id(audience) for authentication", nameof(ResourceId));
}

ServiceClientCredentials creds = null;
if (AzureSession.Instance.AuthenticationFactory is Commands.Common.Authentication.Factories.AuthenticationFactory factory)
{
creds = factory.GetServiceClientCredentials(context, targetResourceIdKey, targetResourceId);
}
else
{
creds = AzureSession.Instance.AuthenticationFactory.GetServiceClientCredentials(context, targetResourceId);
}
Uri baseUri = new Uri($"{Uri.Scheme}://{Uri.Authority}");
serviceClient = AzureSession.Instance.ClientFactory.CreateCustomArmClient<AzureRestClient>(baseUri, creds);
}
else
{
WriteErrorWithTimestamp("Parameter set is not implemented");
}

switch (this.Method.ToUpper())
{
case "GET":
response = ServiceClient
response = serviceClient
.Operations
.GetResourceWithFullResponse(this.Path, this.ApiVersion);
break;
case "POST":
response = ServiceClient
response = serviceClient
.Operations
.PostResourceWithFullResponse(this.Path, this.ApiVersion, this.Payload);
break;
case "PUT":
response = ServiceClient
response = serviceClient
.Operations
.PutResourceWithFullResponse(this.Path, this.ApiVersion, this.Payload);
break;
case "PATCH":
response = ServiceClient
response = serviceClient
.Operations
.PatchResourceWithFullResponse(this.Path, this.ApiVersion, this.Payload);
break;
case "DELETE":
response = ServiceClient
response = serviceClient
.Operations
.DeleteResourceWithFullResponse(this.Path, this.ApiVersion);
break;
default:
throw new PSArgumentException("Invalid HTTP Method");
throw new AzPSArgumentException("Invalid HTTP Method", nameof(Method));
}

WriteObject(new PSHttpResponse(response));
}

private string MatchResourceId(IAzureContext context, string authority, out string targetResourceIdKey)
{
var env = context.Environment;
targetResourceIdKey = null;
if (HasSameEndpoint(authority, env.ResourceManagerUrl))
{
targetResourceIdKey = AzureEnvironment.Endpoint.ActiveDirectoryServiceEndpointResourceId;
return env.ActiveDirectoryServiceEndpointResourceId;
}
if (HasSameEndpoint(authority, env.ExtendedProperties[AzureEnvironment.ExtendedEndpoint.MicrosoftGraphUrl]))
{
targetResourceIdKey = AzureEnvironment.ExtendedEndpoint.MicrosoftGraphEndpointResourceId;
return env.ExtendedProperties[AzureEnvironment.ExtendedEndpoint.MicrosoftGraphEndpointResourceId];
}
if (HasSameEndpointSuffix(authority, env.AzureKeyVaultDnsSuffix))
{
targetResourceIdKey = AzureEnvironment.Endpoint.AzureKeyVaultServiceEndpointResourceId;
return env.AzureKeyVaultServiceEndpointResourceId;
}
if (HasSameEndpointSuffix(authority, env.ExtendedProperties[AzureEnvironment.ExtendedEndpoint.ManagedHsmServiceEndpointSuffix]))
{
targetResourceIdKey = AzureEnvironment.ExtendedEndpoint.ManagedHsmServiceEndpointResourceId;
return env.ExtendedProperties[AzureEnvironment.ExtendedEndpoint.ManagedHsmServiceEndpointResourceId];
}
if (HasSameEndpointSuffix(authority, env.ExtendedProperties[AzureEnvironment.ExtendedEndpoint.AzureSynapseAnalyticsEndpointSuffix]))
{
targetResourceIdKey = AzureEnvironment.ExtendedEndpoint.AzureSynapseAnalyticsEndpointResourceId;
return env.ExtendedProperties[AzureEnvironment.ExtendedEndpoint.AzureSynapseAnalyticsEndpointResourceId];
}
if (HasSameEndpoint(authority, env.ExtendedProperties[AzureEnvironment.ExtendedEndpoint.OperationalInsightsEndpoint]))
{
targetResourceIdKey = AzureEnvironment.ExtendedEndpoint.OperationalInsightsEndpointResourceId;
return env.ExtendedProperties[AzureEnvironment.ExtendedEndpoint.OperationalInsightsEndpointResourceId];
}
if (HasSameEndpointSuffix(authority, env.ExtendedProperties[AzureEnvironment.ExtendedEndpoint.AzureAttestationServiceEndpointSuffix]))
{
targetResourceIdKey = AzureEnvironment.ExtendedEndpoint.AzureAttestationServiceEndpointResourceId;
return env.ExtendedProperties[AzureEnvironment.ExtendedEndpoint.AzureAttestationServiceEndpointResourceId];
}
if (HasSameEndpointSuffix(authority, env.ExtendedProperties[AzureEnvironment.ExtendedEndpoint.AnalysisServicesEndpointSuffix]))
{
targetResourceIdKey = AzureEnvironment.ExtendedEndpoint.AnalysisServicesEndpointResourceId;
return env.ExtendedProperties[AzureEnvironment.ExtendedEndpoint.AnalysisServicesEndpointResourceId];
}
if (HasSameEndpointSuffix(authority, env.AzureDataLakeAnalyticsCatalogAndJobEndpointSuffix))
{
targetResourceIdKey = AzureEnvironment.Endpoint.DataLakeEndpointResourceId;
return env.DataLakeEndpointResourceId;
}
if (HasSameEndpointSuffix(authority, env.AzureDataLakeStoreFileSystemEndpointSuffix))
{
targetResourceIdKey = AzureEnvironment.Endpoint.DataLakeEndpointResourceId;
return env.DataLakeEndpointResourceId;
}
return null;
}

private bool HasSameEndpointSuffix(string sourceAuthority, string targetSuffix)
{
if (string.IsNullOrWhiteSpace(targetSuffix) || string.IsNullOrWhiteSpace(sourceAuthority))
{
return false;
}
return sourceAuthority.EndsWith(targetSuffix, StringComparison.OrdinalIgnoreCase);
}

private bool HasSameEndpoint(string sourceAuthority, string targetUri)
{
if (string.IsNullOrWhiteSpace(targetUri) || string.IsNullOrWhiteSpace(sourceAuthority))
{
return false;
}
try
{
var targetAuthority = (new Uri(targetUri)).Authority;
return sourceAuthority.Equals(targetAuthority, StringComparison.OrdinalIgnoreCase);
}
catch
{
WriteDebug($"Cannot get authority from {targetUri}");
}
return false;
}

public void ValidateParameters()
{
if (this.IsParameterBound(c => this.ResourceType) && !this.IsParameterBound(c => this.Name) && this.ResourceType.Length > 1)
Expand Down
57 changes: 54 additions & 3 deletions src/Accounts/Accounts/help/Invoke-AzRestMethod.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,23 @@ Construct and perform HTTP request to Azure resource management endpoint only

### ByPath (Default)
```
Invoke-AzRestMethod -Path <String> -Method <String> [-Payload <String>] [-AsJob]
Invoke-AzRestMethod -Path <String> [-Method <String>] [-Payload <String>] [-AsJob]
[-DefaultProfile <IAzureContextContainer>] [-WhatIf] [-Confirm] [<CommonParameters>]
```

### ByParameters
```
Invoke-AzRestMethod [-SubscriptionId <String>] [-ResourceGroupName <String>] [-ResourceProviderName <String>]
[-ResourceType <String[]>] [-Name <String[]>] -ApiVersion <String> -Method <String> [-Payload <String>]
[-ResourceType <String[]>] [-Name <String[]>] -ApiVersion <String> [-Method <String>] [-Payload <String>]
[-AsJob] [-DefaultProfile <IAzureContextContainer>] [-WhatIf] [-Confirm] [<CommonParameters>]
```

### ByURI
```
Invoke-AzRestMethod [-Uri] <Uri> [-ResourceId <Uri>] [-Method <String>] [-Payload <String>] [-AsJob]
[-DefaultProfile <IAzureContextContainer>] [-WhatIf] [-Confirm] [<CommonParameters>]
```

## DESCRIPTION
Construct and perform HTTP request to Azure resource management endpoint only

Expand Down Expand Up @@ -75,6 +81,21 @@ Content : {

Get log analytics workspace by path

### Example 2
```powershell
Invoke-AzRestMethod https://graph.microsoft.com/v1.0/me
```

```output
Headers : {[Date, System.String[]], [Cache-Control, System.String[]], [Transfer-Encoding, System.String[]], [Strict-Transport-Security, System.String[]]…}
Version : 1.1
StatusCode : 200
Method : GET
Content : {"@odata.context":"https://graph.microsoft.com/v1.0/$metadata#users/$entity","businessPhones":["......}
```

Get current signed in user via MicrosoftGraph API. This example is equivalent to `Get-AzADUser -SignedIn`.

## PARAMETERS

### -ApiVersion
Expand Down Expand Up @@ -131,7 +152,7 @@ Parameter Sets: (All)
Aliases:
Accepted values: GET, POST, PUT, PATCH, DELETE

Required: True
Required: False
Position: Named
Default value: None
Accept pipeline input: False
Expand Down Expand Up @@ -198,6 +219,21 @@ Accept pipeline input: False
Accept wildcard characters: False
```

### -ResourceId
Identifier URI specified by the REST API you are calling. It shouldn't be the resource id of Azure Resource Manager.

```yaml
Type: System.Uri
Parameter Sets: ByURI
Aliases:

Required: False
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
```

### -ResourceProviderName
Target Resource Provider Name

Expand Down Expand Up @@ -243,6 +279,21 @@ Accept pipeline input: False
Accept wildcard characters: False
```

### -Uri
Uniform Resource Identifier of the Azure resources. The target resource needs to support Azure AD authentication and the access token is derived according to resource id. If resource id is not set, its value is derived according to built-in service suffixes in current Azure Environment.

```yaml
Type: System.Uri
Parameter Sets: ByURI
Aliases:

Required: True
Position: 1
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
```

### -Confirm
Prompts you for confirmation before running the cmdlet.

Expand Down
21 changes: 0 additions & 21 deletions src/Accounts/Authentication.ResourceManager/Constants.cs

This file was deleted.

Loading