Skip to content

Commit d688ae5

Browse files
authored
Parse ExpiresOn time from access token if possible (Azure#13594)
1 parent f893a59 commit d688ae5

File tree

2 files changed

+119
-0
lines changed

2 files changed

+119
-0
lines changed

src/Accounts/Accounts/Token/GetAzureRmAccessToken.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@
1414

1515
using System;
1616
using System.Collections.Generic;
17+
using System.Linq;
1718
using System.Management.Automation;
19+
using System.Text.Json;
1820

1921
using Microsoft.Azure.Commands.Common.Authentication;
2022
using Microsoft.Azure.Commands.Common.Authentication.Abstractions;
2123
using Microsoft.Azure.Commands.Profile.Models;
24+
using Microsoft.Azure.Commands.Profile.Utilities;
2225
using Microsoft.Azure.Commands.ResourceManager.Common;
2326
using Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters;
2427
using Microsoft.Azure.PowerShell.Authenticators;
@@ -31,6 +34,7 @@ public class GetAzureRmAccessTokenCommand : AzureRMCmdlet
3134
{
3235
private const string ResourceUrlParameterSet = "ResourceUrl";
3336
private const string KnownResourceNameParameterSet = "KnownResourceTypeName";
37+
private static DateTimeOffset UnixEpoch = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);
3438

3539
[Parameter(ParameterSetName = ResourceUrlParameterSet,
3640
Mandatory = true,
@@ -108,6 +112,25 @@ public override void ExecuteCmdlet()
108112
UserId = accessToken.UserId,
109113
};
110114
result.ExpiresOn = (accessToken as MsalAccessToken)?.ExpiresOn ?? result.ExpiresOn;
115+
if(result.ExpiresOn == default(DateTimeOffset))
116+
{
117+
try
118+
{
119+
var tokenParts = accessToken.AccessToken.Split('.');
120+
var decodedToken = Base64UrlHelpers.DecodeToString(tokenParts[1]);
121+
122+
var tokenDocument = JsonDocument.Parse(decodedToken);
123+
int expSeconds = tokenDocument.RootElement.EnumerateObject()
124+
.Where(p => p.Name == "exp")
125+
.Select(p => p.Value.GetInt32())
126+
.First();
127+
result.ExpiresOn = UnixEpoch.AddSeconds(expSeconds);
128+
}
129+
catch(Exception e)//Ignore exception if fails to parse exp from token
130+
{
131+
WriteDebug("Unable to parse exp in token: " + e.ToString());
132+
}
133+
}
111134

112135
WriteObject(result);
113136
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// ----------------------------------------------------------------------------------
2+
//
3+
// Copyright Microsoft Corporation
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
// ----------------------------------------------------------------------------------
14+
15+
using System;
16+
using System.Globalization;
17+
using System.Text;
18+
19+
namespace Microsoft.Azure.Commands.Profile.Utilities
20+
{
21+
//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
22+
internal static class Base64UrlHelpers
23+
{
24+
private const char Base64PadCharacter = '=';
25+
private const char Base64Character62 = '+';
26+
private const char Base64Character63 = '/';
27+
private const char Base64UrlCharacter62 = '-';
28+
private const char Base64UrlCharacter63 = '_';
29+
private static readonly Encoding TextEncoding = Encoding.UTF8;
30+
31+
private static readonly string DoubleBase64PadCharacter = string.Format(CultureInfo.InvariantCulture, "{0}{0}",
32+
Base64PadCharacter);
33+
34+
//
35+
// The following functions perform base64url encoding which differs from regular base64 encoding as follows
36+
// * padding is skipped so the pad character '=' doesn't have to be percent encoded
37+
// * the 62nd and 63rd regular base64 encoding characters ('+' and '/') are replace with ('-' and '_')
38+
// The changes make the encoding alphabet file and URL safe
39+
// See RFC4648, section 5 for more info
40+
//
41+
public static string Encode(string arg)
42+
{
43+
if (arg == null)
44+
{
45+
return null;
46+
}
47+
48+
return Encode(TextEncoding.GetBytes(arg));
49+
}
50+
51+
public static string DecodeToString(string arg)
52+
{
53+
byte[] decoded = DecodeToBytes(arg);
54+
return Encoding.UTF8.GetString(decoded, 0, decoded.Length);
55+
}
56+
57+
public static byte[] DecodeToBytes(string arg)
58+
{
59+
string s = arg;
60+
s = s.Replace(Base64UrlCharacter62, Base64Character62); // 62nd char of encoding
61+
s = s.Replace(Base64UrlCharacter63, Base64Character63); // 63rd char of encoding
62+
63+
switch (s.Length % 4)
64+
{
65+
// Pad
66+
case 0:
67+
break; // No pad chars in this case
68+
case 2:
69+
s += DoubleBase64PadCharacter;
70+
break; // Two pad chars
71+
case 3:
72+
s += Base64PadCharacter;
73+
break; // One pad char
74+
default:
75+
throw new ArgumentException("Illegal base64url string!", nameof(arg));
76+
}
77+
78+
return Convert.FromBase64String(s); // Standard base64 decoder
79+
}
80+
81+
internal static string Encode(byte[] arg)
82+
{
83+
if (arg == null)
84+
{
85+
return null;
86+
}
87+
88+
string s = Convert.ToBase64String(arg);
89+
s = s.Split(Base64PadCharacter)[0]; // RemoveAccount any trailing padding
90+
s = s.Replace(Base64Character62, Base64UrlCharacter62); // 62nd char of encoding
91+
s = s.Replace(Base64Character63, Base64UrlCharacter63); // 63rd char of encoding
92+
93+
return s;
94+
}
95+
}
96+
}

0 commit comments

Comments
 (0)