Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7b19890
Added safe rollout feature
timayabi2020 Apr 29, 2024
1f703d1
Updated file
timayabi2020 Apr 29, 2024
c760cfa
Update Auth helper and ConnectMgGraph classes
timayabi2020 Apr 30, 2024
660f478
Merge branch 'dev' of https://github.com/microsoftgraph/msgraph-sdk-p…
timayabi2020 Apr 30, 2024
c874739
Removed unnecessary debug
timayabi2020 Apr 30, 2024
fdac8de
Merge branch 'dev' into torus_migration
Ndiritu May 15, 2024
6e07725
Add Safe rollout parameter to access token paramter set
Ndiritu May 16, 2024
54ad296
Added Safe rollout feature on user provided
timayabi2020 May 17, 2024
ae5a0f0
Merge branch 'torus_migration' of https://github.com/microsoftgraph/m…
timayabi2020 May 17, 2024
de0efd7
updated endpoint
timayabi2020 May 17, 2024
970cd75
Merge branch 'dev' into torus_migration
Ndiritu May 20, 2024
eae203e
Add safe rollout parameter sets to support different auth flows
Ndiritu May 20, 2024
1105de3
Update login authority during safe rollout
Ndiritu May 20, 2024
9119652
Change safe rollout authority host to valid authority
Ndiritu Jun 3, 2024
06e6064
Add azure .NET sdk as submodule
Ndiritu Jun 4, 2024
357a24b
Bump version
Ndiritu Jun 4, 2024
d94e8f1
Fix Azure.Identity submodule reference in Authentication module
Ndiritu Jun 4, 2024
ecb7618
Change submodule branch
Ndiritu Jun 4, 2024
6158bb6
Update submodule
Ndiritu Jun 4, 2024
e2cef83
Add safe rollout as extra query parameter
Ndiritu Jun 4, 2024
e77a074
Add safe rollout as extra query parameter
Ndiritu Jun 5, 2024
58ab29b
Update submodule
Ndiritu Jun 5, 2024
269c76e
Update safe rollout query param string
Ndiritu Jun 5, 2024
f13219f
Add sub-claim to auth context
Ndiritu Jun 18, 2024
9906955
Enable safe rollout on app only auth
Ndiritu Jun 24, 2024
c8eeffa
Add sub-claim to auth context
Ndiritu Jun 24, 2024
a5588c5
Bump azure sdk submodule
Ndiritu Jun 24, 2024
f06f12a
Add safe rollout for environment credential login
Ndiritu Jun 24, 2024
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
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@
path = autorest.powershell
url = https://github.com/microsoftgraph/autorest.powershell
branch = powershell_v2
[submodule "azure-sdk-for-net"]
path = azure-sdk-for-net
url = https://github.com/Ndiritu/azure-sdk-for-net.git
branch = feat/extra-query-params
1 change: 1 addition & 0 deletions azure-sdk-for-net
Submodule azure-sdk-for-net added at bf7d0f
12 changes: 6 additions & 6 deletions config/ModuleMetadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,16 @@
],
"versions": {
"authentication": {
"prerelease": "",
"version": "2.19.0"
"prerelease": "preview2",
"version": "2.22.0"
},
"beta": {
"prerelease": "",
"version": "2.19.0"
"prerelease": "preview2",
"version": "2.22.0"
},
"v1.0": {
"prerelease": "",
"version": "2.19.0"
"prerelease": "preview2",
"version": "2.22.0"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,6 @@ public interface IAuthContext
ContextScope ContextScope { get; set; }
Version PSHostVersion { get; set; }
SecureString ClientSecret { get; set; }
string SubClaim { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
<LangVersion>9.0</LangVersion>
<TargetFrameworks>netstandard2.0;net6.0;net472</TargetFrameworks>
<RootNamespace>Microsoft.Graph.PowerShell.Authentication.Core</RootNamespace>
<Version>2.18.0</Version>
<VersionPrefix>2.22.0</VersionPrefix>
<VersionSuffix>preview2</VersionSuffix>
</PropertyGroup>
<PropertyGroup>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Identity" Version="1.11.0" />
<ProjectReference Include="..\..\..\azure-sdk-for-net\sdk\identity\Azure.Identity\src\Azure.Identity.csproj" />
<PackageReference Include="Azure.Identity.Broker" Version="1.1.0" />
<PackageReference Include="Microsoft.Graph.Core" Version="3.0.9" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
Expand Down
3 changes: 3 additions & 0 deletions src/Authentication/Authentication.Core/Models/JwtPayload.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,8 @@ internal partial class JwtPayload

[JsonPropertyName("upn")]
public string Upn { get; set; }

[JsonPropertyName("sub")]
public string Sub { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Microsoft.Identity.Client.Extensions.Msal;
using System;
using System.Diagnostics.Tracing;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
Expand All @@ -30,42 +31,49 @@ public static class AuthenticationHelpers
/// </summary>
/// <param name="authContext">The <see cref="IAuthContext"/> to get a token credential for.</param>
/// <returns>A <see cref="TokenCredential"/> based on provided <see cref="IAuthContext"/>.</returns>
public static async Task<TokenCredential> GetTokenCredentialAsync(IAuthContext authContext, CancellationToken cancellationToken = default)
public static async Task<TokenCredential> GetTokenCredentialAsync(IAuthContext authContext, bool safeRollOut, CancellationToken cancellationToken = default)
{
if (authContext is null)
throw new AuthenticationException(ErrorConstants.Message.MissingAuthContext);

switch (authContext.AuthType)
{
case AuthenticationType.Delegated:
if (authContext.TokenCredentialType == TokenCredentialType.InteractiveBrowser)
return await GetInteractiveBrowserCredentialAsync(authContext, cancellationToken).ConfigureAwait(false);
return await GetDeviceCodeCredentialAsync(authContext, cancellationToken).ConfigureAwait(false);
return await GetInteractiveBrowserCredentialAsync(authContext, safeRollOut, cancellationToken).ConfigureAwait(false);
return await GetDeviceCodeCredentialAsync(authContext, safeRollOut, cancellationToken).ConfigureAwait(false);
case AuthenticationType.AppOnly:
return authContext.TokenCredentialType == TokenCredentialType.ClientCertificate
? await GetClientCertificateCredentialAsync(authContext).ConfigureAwait(false)
: await GetClientSecretCredentialAsync(authContext).ConfigureAwait(false);
? await GetClientCertificateCredentialAsync(authContext, safeRollOut).ConfigureAwait(false)
: await GetClientSecretCredentialAsync(authContext, safeRollOut).ConfigureAwait(false);
case AuthenticationType.ManagedIdentity:
return await GetManagedIdentityCredentialAsync(authContext).ConfigureAwait(false);
case AuthenticationType.EnvironmentVariable:
return await GetEnvironmentCredentialAsync(authContext).ConfigureAwait(false);
return await GetEnvironmentCredentialAsync(authContext, safeRollOut).ConfigureAwait(false);
case AuthenticationType.UserProvidedAccessToken:
return new UserProvidedTokenCredential();
default:
throw new NotSupportedException($"{authContext.AuthType} is not supported.");
}
}

private static async Task<TokenCredential> GetEnvironmentCredentialAsync(IAuthContext authContext)
private static async Task<TokenCredential> GetEnvironmentCredentialAsync(IAuthContext authContext, bool safeRollout)
{
if (authContext is null)
throw new AuthenticationException(ErrorConstants.Message.MissingAuthContext);

var tokenCredentialOptions = new TokenCredentialOptions
{
AuthorityHost = new Uri(GetAuthorityUrl(authContext))
AuthorityHost = new Uri(GetAuthorityUrl(authContext, false))
};

if (safeRollout)
{
tokenCredentialOptions.ExtraQueryParameters = new Dictionary<string, string>
{
{ "safe_rollout", "apply:0238caeb-f6ca-4efc-afd0-a72e1273a8bc" }
};
}

if (IsAuthFlowNotSupported())
{
throw new Exception(string.Format(CultureInfo.InvariantCulture, ErrorConstants.Message.AuthNotSupported, "Username and password"));
Expand All @@ -86,16 +94,23 @@ private static bool IsWamSupported()
return GraphSession.Instance.GraphOption.EnableWAMForMSGraph && SharedUtilities.IsWindowsPlatform();
}

private static async Task<TokenCredential> GetClientSecretCredentialAsync(IAuthContext authContext)
private static async Task<TokenCredential> GetClientSecretCredentialAsync(IAuthContext authContext, bool safeRollout)
{
if (authContext is null)
throw new AuthenticationException(ErrorConstants.Message.MissingAuthContext);

var clientSecretCredentialOptions = new ClientSecretCredentialOptions
{
AuthorityHost = new Uri(GetAuthorityUrl(authContext)),
AuthorityHost = new Uri(GetAuthorityUrl(authContext, false)),
TokenCachePersistenceOptions = GetTokenCachePersistenceOptions(authContext)
};
if (safeRollout)
{
clientSecretCredentialOptions.ExtraQueryParameters = new Dictionary<string, string>
{
{ "safe_rollout", "apply:0238caeb-f6ca-4efc-afd0-a72e1273a8bc" }
};
}
var clientSecretCredential = new ClientSecretCredential(authContext.TenantId, authContext.ClientId, authContext.ClientSecret.ConvertToString(), clientSecretCredentialOptions);
return await Task.FromResult(clientSecretCredential).ConfigureAwait(false);
}
Expand All @@ -109,16 +124,23 @@ private static async Task<TokenCredential> GetManagedIdentityCredentialAsync(IAu
return await Task.FromResult(new ManagedIdentityCredential(userAccountId)).ConfigureAwait(false);
}

private static async Task<InteractiveBrowserCredential> GetInteractiveBrowserCredentialAsync(IAuthContext authContext, CancellationToken cancellationToken = default)
private static async Task<InteractiveBrowserCredential> GetInteractiveBrowserCredentialAsync(IAuthContext authContext,bool safeRollOut, CancellationToken cancellationToken = default)
{
if (authContext is null)
throw new AuthenticationException(ErrorConstants.Message.MissingAuthContext);
var interactiveOptions = IsWamSupported() ? new InteractiveBrowserCredentialBrokerOptions(WindowHandleUtlities.GetConsoleOrTerminalWindow()) : new InteractiveBrowserCredentialOptions();
interactiveOptions.ClientId = authContext.ClientId;
interactiveOptions.TenantId = authContext.TenantId ?? "common";
interactiveOptions.AuthorityHost = new Uri(GetAuthorityUrl(authContext));
interactiveOptions.TenantId = authContext.TenantId ?? "common";
interactiveOptions.AuthorityHost = new Uri(GetAuthorityUrl(authContext, safeRollOut));
Console.WriteLine($"Got authority host {interactiveOptions.AuthorityHost}");
interactiveOptions.TokenCachePersistenceOptions = GetTokenCachePersistenceOptions(authContext);

if (safeRollOut)
{
interactiveOptions.ExtraQueryParameters = new Dictionary<string, string>
{
{ "safe_rollout", "apply:0238caeb-f6ca-4efc-afd0-a72e1273a8bc" }
};
}
if (!File.Exists(Constants.AuthRecordPath))
{
AuthenticationRecord authRecord;
Expand Down Expand Up @@ -147,7 +169,7 @@ private static async Task<InteractiveBrowserCredential> GetInteractiveBrowserCre
return new InteractiveBrowserCredential(interactiveOptions);
}

private static async Task<DeviceCodeCredential> GetDeviceCodeCredentialAsync(IAuthContext authContext, CancellationToken cancellationToken = default)
private static async Task<DeviceCodeCredential> GetDeviceCodeCredentialAsync(IAuthContext authContext, bool safeRollOut, CancellationToken cancellationToken = default)
{
if (authContext is null)
throw new AuthenticationException(ErrorConstants.Message.MissingAuthContext);
Expand All @@ -156,14 +178,21 @@ private static async Task<DeviceCodeCredential> GetDeviceCodeCredentialAsync(IAu
{
ClientId = authContext.ClientId,
TenantId = authContext.TenantId,
AuthorityHost = new Uri(GetAuthorityUrl(authContext)),
AuthorityHost = new Uri(GetAuthorityUrl(authContext, false)),
TokenCachePersistenceOptions = GetTokenCachePersistenceOptions(authContext),
DeviceCodeCallback = (code, cancellation) =>
{
GraphSession.Instance.OutputWriter.WriteObject(code.Message);
return Task.CompletedTask;
}
};
if (safeRollOut)
{
deviceCodeOptions.ExtraQueryParameters = new Dictionary<string, string>
{
{ "safe_rollout", "apply:0238caeb-f6ca-4efc-afd0-a72e1273a8bc" }
};
}
if (!File.Exists(Constants.AuthRecordPath))
{
var deviceCodeCredential = new DeviceCodeCredential(deviceCodeOptions);
Expand All @@ -176,17 +205,24 @@ private static async Task<DeviceCodeCredential> GetDeviceCodeCredentialAsync(IAu
return new DeviceCodeCredential(deviceCodeOptions);
}

private static async Task<ClientCertificateCredential> GetClientCertificateCredentialAsync(IAuthContext authContext)
private static async Task<ClientCertificateCredential> GetClientCertificateCredentialAsync(IAuthContext authContext, bool safeRollOut)
{
if (authContext is null)
throw new AuthenticationException(ErrorConstants.Message.MissingAuthContext);

var clientCredentialOptions = new ClientCertificateCredentialOptions
{
AuthorityHost = new Uri(GetAuthorityUrl(authContext)),
AuthorityHost = new Uri(GetAuthorityUrl(authContext, false)),
TokenCachePersistenceOptions = GetTokenCachePersistenceOptions(authContext),
SendCertificateChain = authContext.SendCertificateChain
};
if (safeRollOut)
{
clientCredentialOptions.ExtraQueryParameters = new Dictionary<string, string>
{
{ "safe_rollout", "apply:0238caeb-f6ca-4efc-afd0-a72e1273a8bc" }
};
}
var clientCertificateCredential = new ClientCertificateCredential(authContext.TenantId, authContext.ClientId, GetCertificate(authContext), clientCredentialOptions);
return await Task.FromResult(clientCertificateCredential).ConfigureAwait(false);
}
Expand All @@ -211,7 +247,7 @@ public static async Task<AzureIdentityAccessTokenProvider> GetAuthenticationProv
return new AzureIdentityAccessTokenProvider(credential: tokenCrdential, scopes: GetScopes(authContext));
}

public static async Task<IAuthContext> AuthenticateAsync(IAuthContext authContext, CancellationToken cancellationToken)
public static async Task<IAuthContext> AuthenticateAsync(IAuthContext authContext, bool safeRollOut, CancellationToken cancellationToken)
{
if (authContext is null)
throw new AuthenticationException(ErrorConstants.Message.MissingAuthContext);
Expand All @@ -227,7 +263,7 @@ public static async Task<IAuthContext> AuthenticateAsync(IAuthContext authContex
(args, message) => GraphSession.Instance.OutputWriter.WriteDebug($"{message}"),
level: EventLevel.Informational))
{
signInAuthContext = await SignInAsync(authContext, cancellationToken).ConfigureAwait(false);
signInAuthContext = await SignInAsync(authContext, safeRollOut,cancellationToken).ConfigureAwait(false);
retrySignIn = false;
};
}
Expand Down Expand Up @@ -272,11 +308,11 @@ public static async Task<IAuthContext> AuthenticateAsync(IAuthContext authContex
return signInAuthContext;
}

private static async Task<IAuthContext> SignInAsync(IAuthContext authContext, CancellationToken cancellationToken = default)
private static async Task<IAuthContext> SignInAsync(IAuthContext authContext, bool safeRollOut, CancellationToken cancellationToken = default)
{
if (authContext is null)
throw new AuthenticationException(ErrorConstants.Message.MissingAuthContext);
var tokenCredential = await GetTokenCredentialAsync(authContext, cancellationToken).ConfigureAwait(false);
var tokenCredential = await GetTokenCredentialAsync(authContext, safeRollOut, cancellationToken).ConfigureAwait(false);
var token = await tokenCredential.GetTokenAsync(new TokenRequestContext(GetScopes(authContext)), cancellationToken).ConfigureAwait(false);
JwtHelpers.DecodeJWT(token.Token, account: null, ref authContext);
return authContext;
Expand Down Expand Up @@ -304,14 +340,14 @@ private static string[] GetScopes(IAuthContext authContext)
/// </summary>
/// <param name="authContext">The <see cref="IAuthContext"/> to get an authority URL for.</param>
/// <returns></returns>
private static string GetAuthorityUrl(IAuthContext authContext)
private static string GetAuthorityUrl(IAuthContext authContext, bool safeRollOut)
{
if (authContext is null)
throw new AuthenticationException(ErrorConstants.Message.MissingAuthContext);
string audience = authContext.TenantId ?? Constants.DefaultTenant;
return GraphSession.Instance.Environment != null
? $"{GraphSession.Instance.Environment.AzureADEndpoint}/{audience}"
: $"{Constants.DefaultAzureADEndpoint}/{audience}";
: $"{Constants.DefaultAzureADEndpoint}/{audience}";
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ internal static void DecodeJWT(string jwToken, IAccount account, ref IAuthContex
authContext.TenantId = jwtPayload?.Tid ?? account?.HomeAccountId?.TenantId;
authContext.AppName = jwtPayload?.AppDisplayname;
authContext.Account = jwtPayload?.Upn ?? account?.Username;
authContext.SubClaim = jwtPayload?.Sub;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
<PropertyGroup>
<TargetFrameworks>net6.0;net472</TargetFrameworks>
<IsPackable>false</IsPackable>
<Version>2.8.0</Version>
<VersionPrefix>2.22.0</VersionPrefix>
<VersionSuffix>preview2</VersionSuffix>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.2" />
Expand Down
6 changes: 6 additions & 0 deletions src/Authentication/Authentication.sln
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Graph.Authenticat
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenApiInfoGenerator", "..\..\tools\OpenApiInfoGenerator\OpenApiInfoGenerator\OpenApiInfoGenerator.csproj", "{6437E29A-E398-48EE-B7BE-27A8C922F13E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Identity", "..\..\azure-sdk-for-net\sdk\identity\Azure.Identity\src\Azure.Identity.csproj", "{BDAFCAD5-1DAB-4C57-A7D4-140E396CCC83}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -33,6 +35,10 @@ Global
{6437E29A-E398-48EE-B7BE-27A8C922F13E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6437E29A-E398-48EE-B7BE-27A8C922F13E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6437E29A-E398-48EE-B7BE-27A8C922F13E}.Release|Any CPU.Build.0 = Release|Any CPU
{BDAFCAD5-1DAB-4C57-A7D4-140E396CCC83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BDAFCAD5-1DAB-4C57-A7D4-140E396CCC83}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BDAFCAD5-1DAB-4C57-A7D4-140E396CCC83}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BDAFCAD5-1DAB-4C57-A7D4-140E396CCC83}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Loading