forked from pnp/powershell
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathTokenHandling.cs
194 lines (179 loc) · 9.7 KB
/
TokenHandling.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
using Microsoft.SharePoint.Client;
using PnP.PowerShell.Commands.Attributes;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Management.Automation;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
namespace PnP.PowerShell.Commands.Base
{
internal static class TokenHandler
{
internal static void ValidateTokenForPermissions(Type cmdletType, string token)
{
string[] requiredScopes = null;
var requiredScopesAttribute = (RequiredMinimalApiPermissions)Attribute.GetCustomAttribute(cmdletType, typeof(RequiredMinimalApiPermissions));
if (requiredScopesAttribute != null)
{
requiredScopes = requiredScopesAttribute.PermissionScopes;
}
if (requiredScopes.Length > 0)
{
var decodedToken = new JwtSecurityToken(token);
var roles = decodedToken.Claims.FirstOrDefault(c => c.Type == "roles");
if (roles != null)
{
foreach (var permission in requiredScopes)
{
if (!roles.Value.ToLower().Contains(permission.ToLower()))
{
throw new PSArgumentException($"Authorization Denied: Token used does not contain permission scope '{permission}'");
}
}
}
roles = decodedToken.Claims.FirstOrDefault(c => c.Type == "scp");
if (roles != null)
{
foreach (var permission in requiredScopes)
{
if (!roles.Value.ToLower().Contains(permission.ToLower()))
{
throw new PSArgumentException($"Authorization Denied: Token used does not contain permission scope '{permission}'");
}
}
}
}
}
internal static string GetAccessToken(Type cmdletType, string appOnlyDefaultScope, PnPConnection connection)
{
var contextSettings = connection.Context.GetContextSettings();
var authManager = contextSettings.AuthenticationManager;
if (authManager != null)
{
if (contextSettings.Type == Framework.Utilities.Context.ClientContextType.SharePointACSAppOnly)
{
// When connected using ACS, we cannot get a token for another endpoint
throw new PSInvalidOperationException("Trying to get a token for a different endpoint while being connected through an ACS token is not possible. Please connect differently.");
}
string[] requiredScopes = null;
RequiredMinimalApiPermissions requiredScopesAttribute = null;
if (cmdletType != null)
{
requiredScopesAttribute = (RequiredMinimalApiPermissions)Attribute.GetCustomAttribute(cmdletType, typeof(RequiredMinimalApiPermissions));
}
if (requiredScopesAttribute != null)
{
requiredScopes = requiredScopesAttribute.PermissionScopes;
}
if (contextSettings.Type == Framework.Utilities.Context.ClientContextType.AzureADCertificate)
{
requiredScopes = new[] { appOnlyDefaultScope }; // override for app only
}
if (requiredScopes == null && !string.IsNullOrEmpty(appOnlyDefaultScope))
{
requiredScopes = new[] { appOnlyDefaultScope };
}
var accessToken = authManager.GetAccessTokenAsync(requiredScopes).GetAwaiter().GetResult();
return accessToken;
}
return null;
}
/// <summary>
/// Returns an access token based on a Managed Identity. Only works within Azure components supporting managed identities such as Azure Functions and Azure Runbooks.
/// </summary>
/// <param name="cmdlet">The cmdlet scope in which this code runs. Used to write logging to.</param>
/// <param name="httpClient">The HttpClient that will be reused to fetch the token to avoid port exhaustion</param>
/// <param name="defaultResource">If the cmdlet being executed does not have an attribute to indicate the required permissions, this permission will be requested instead. Optional.</param>
/// <param name="userAssignedManagedIdentityObjectId">The object/principal Id of the user assigned managed identity to be used. If omitted, a system assigned managed identity will be used.</param>
/// <returns>Access token</returns>
/// <exception cref="PSInvalidOperationException">Thrown if unable to retrieve an access token through a managed identity</exception>
internal static async Task<string> GetManagedIdentityTokenAsync(Cmdlet cmdlet, HttpClient httpClient, string defaultResource, string userAssignedManagedIdentityObjectId = null)
{
string requiredScope = null;
var requiredScopesAttribute = (RequiredMinimalApiPermissions)Attribute.GetCustomAttribute(cmdlet.GetType(), typeof(RequiredMinimalApiPermissions));
if (requiredScopesAttribute != null)
{
requiredScope = requiredScopesAttribute.PermissionScopes.First();
if (requiredScope.ToLower().StartsWith("https://"))
{
var uri = new Uri(requiredScope);
requiredScope = $"https://{uri.Host}/";
}
else
{
requiredScope = defaultResource;
}
cmdlet.WriteVerbose($"Using scope {requiredScope} for managed identity token coming from the cmdlet permission attribute");
}
else
{
requiredScope = defaultResource;
cmdlet.WriteVerbose($"Using scope {requiredScope} for managed identity token coming from the passed in default resource");
}
var endPoint = Environment.GetEnvironmentVariable("IDENTITY_ENDPOINT");
cmdlet.WriteVerbose($"Using identity endpoint: {endPoint}");
var identityHeader = Environment.GetEnvironmentVariable("IDENTITY_HEADER");
cmdlet.WriteVerbose($"Using identity header: {identityHeader}");
if (string.IsNullOrEmpty(endPoint))
{
endPoint = Environment.GetEnvironmentVariable("MSI_ENDPOINT");
identityHeader = Environment.GetEnvironmentVariable("MSI_SECRET");
}
if (string.IsNullOrEmpty(endPoint))
{
// additional fallback
// using well-known endpoint for Instance Metadata Service, useful in Azure VM scenario.
// https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http
endPoint = "http://169.254.169.254/metadata/identity/oauth2/token";
}
if (!string.IsNullOrEmpty(endPoint))
{
var tokenRequestUrl = $"{endPoint}?resource={requiredScope}&api-version=2019-08-01";
// Check if we're using a user assigned managed identity
if(!string.IsNullOrEmpty(userAssignedManagedIdentityObjectId))
{
// User assigned managed identity will be used, provide the object/pricipal Id of the user assigned managed identity to use
cmdlet.WriteVerbose($"Using the user assigned managed identity with object/principal ID: {userAssignedManagedIdentityObjectId}");
tokenRequestUrl += $"&principal_id={userAssignedManagedIdentityObjectId}";
}
else
{
cmdlet.WriteVerbose("Using the system assigned managed identity");
}
using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, tokenRequestUrl))
{
requestMessage.Version = new Version(2, 0);
requestMessage.Headers.Add("Metadata", "true");
if (!string.IsNullOrEmpty(identityHeader))
{
requestMessage.Headers.Add("X-IDENTITY-HEADER", identityHeader);
}
cmdlet.WriteVerbose($"Sending token request to {tokenRequestUrl}");
var response = await httpClient.SendAsync(requestMessage).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var responseElement = JsonSerializer.Deserialize<JsonElement>(responseContent);
if (responseElement.TryGetProperty("access_token", out JsonElement accessTokenElement))
{
var accessToken = accessTokenElement.GetString();
return accessToken;
}
}
else
{
var errorMessage = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new PSInvalidOperationException(errorMessage);
}
}
}
else
{
throw new PSInvalidOperationException("Cannot determine Managed Identity Endpoint URL to acquire token.");
}
return null;
}
}
}