Skip to content

Parse ExpiresOn time from access token if possible #13594

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
Nov 30, 2020
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
23 changes: 23 additions & 0 deletions src/Accounts/Accounts/Token/GetAzureRmAccessToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Text.Json;

using Microsoft.Azure.Commands.Common.Authentication;
using Microsoft.Azure.Commands.Common.Authentication.Abstractions;
using Microsoft.Azure.Commands.Profile.Models;
using Microsoft.Azure.Commands.Profile.Utilities;
using Microsoft.Azure.Commands.ResourceManager.Common;
using Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters;
using Microsoft.Azure.PowerShell.Authenticators;
Expand All @@ -31,6 +34,7 @@ public class GetAzureRmAccessTokenCommand : AzureRMCmdlet
{
private const string ResourceUrlParameterSet = "ResourceUrl";
private const string KnownResourceNameParameterSet = "KnownResourceTypeName";
private static DateTimeOffset UnixEpoch = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);

[Parameter(ParameterSetName = ResourceUrlParameterSet,
Mandatory = true,
Expand Down Expand Up @@ -108,6 +112,25 @@ public override void ExecuteCmdlet()
UserId = accessToken.UserId,
};
result.ExpiresOn = (accessToken as MsalAccessToken)?.ExpiresOn ?? result.ExpiresOn;
if(result.ExpiresOn == default(DateTimeOffset))
{
try
{
var tokenParts = accessToken.AccessToken.Split('.');
var decodedToken = Base64UrlHelpers.DecodeToString(tokenParts[1]);

var tokenDocument = JsonDocument.Parse(decodedToken);
int expSeconds = tokenDocument.RootElement.EnumerateObject()
.Where(p => p.Name == "exp")
.Select(p => p.Value.GetInt32())
.First();
result.ExpiresOn = UnixEpoch.AddSeconds(expSeconds);
}
catch(Exception e)//Ignore exception if fails to parse exp from token
{
WriteDebug("Unable to parse exp in token: " + e.ToString());
}
}

WriteObject(result);
}
Expand Down
96 changes: 96 additions & 0 deletions src/Accounts/Accounts/Utilities/Base64UrlHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// ----------------------------------------------------------------------------------
//
// Copyright Microsoft Corporation
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------------------------------------------------------------------------

using System;
using System.Globalization;
using System.Text;

namespace Microsoft.Azure.Commands.Profile.Utilities
{
//The source code is copied from https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/master/src/client/Microsoft.Identity.Client/Utils/Base64UrlHelpers.cs
internal static class Base64UrlHelpers
Copy link
Member

Choose a reason for hiding this comment

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

There's a very similar class in KeyVault: https://github.com/Azure/azure-powershell/blob/master/src/KeyVault/KeyVault/SecurityDomain/Models/Base64UrlEncoder.cs

I think we can move it to common lib

Copy link
Member

Choose a reason for hiding this comment

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

Will log an issue to dedup in next release

{
private const char Base64PadCharacter = '=';
private const char Base64Character62 = '+';
private const char Base64Character63 = '/';
private const char Base64UrlCharacter62 = '-';
private const char Base64UrlCharacter63 = '_';
private static readonly Encoding TextEncoding = Encoding.UTF8;

private static readonly string DoubleBase64PadCharacter = string.Format(CultureInfo.InvariantCulture, "{0}{0}",
Base64PadCharacter);

//
// The following functions perform base64url encoding which differs from regular base64 encoding as follows
// * padding is skipped so the pad character '=' doesn't have to be percent encoded
// * the 62nd and 63rd regular base64 encoding characters ('+' and '/') are replace with ('-' and '_')
// The changes make the encoding alphabet file and URL safe
// See RFC4648, section 5 for more info
//
public static string Encode(string arg)
{
if (arg == null)
{
return null;
}

return Encode(TextEncoding.GetBytes(arg));
}

public static string DecodeToString(string arg)
{
byte[] decoded = DecodeToBytes(arg);
return Encoding.UTF8.GetString(decoded, 0, decoded.Length);
}

public static byte[] DecodeToBytes(string arg)
{
string s = arg;
s = s.Replace(Base64UrlCharacter62, Base64Character62); // 62nd char of encoding
s = s.Replace(Base64UrlCharacter63, Base64Character63); // 63rd char of encoding

switch (s.Length % 4)
{
// Pad
case 0:
break; // No pad chars in this case
case 2:
s += DoubleBase64PadCharacter;
break; // Two pad chars
case 3:
s += Base64PadCharacter;
break; // One pad char
default:
throw new ArgumentException("Illegal base64url string!", nameof(arg));
}

return Convert.FromBase64String(s); // Standard base64 decoder
}

internal static string Encode(byte[] arg)
{
if (arg == null)
{
return null;
}

string s = Convert.ToBase64String(arg);
s = s.Split(Base64PadCharacter)[0]; // RemoveAccount any trailing padding
s = s.Replace(Base64Character62, Base64UrlCharacter62); // 62nd char of encoding
s = s.Replace(Base64Character63, Base64UrlCharacter63); // 63rd char of encoding

return s;
}
}
}