Skip to content

Commit 0309635

Browse files
msJinLeiisra-felwyunchi-ms
authored
Enable Retrieve Auxiliary Auth Header by tenant ID (#409)
* Enable TenantId to Retrieve Auxiliary Auth Header To Fix Azure/azure-powershell#17407 * Update src/ResourceManager/Version2016_09_01/AzureRMCmdlet.cs Co-authored-by: Yeming Liu <11371776+isra-fel@users.noreply.github.com> * Update src/ResourceManager/Version2016_09_01/AzureRMCmdlet.cs Co-authored-by: Yunchi Wang <54880216+wyunchi-ms@users.noreply.github.com> * Address review comments * Fix test error --------- Co-authored-by: Yeming Liu <11371776+isra-fel@users.noreply.github.com> Co-authored-by: Yunchi Wang <54880216+wyunchi-ms@users.noreply.github.com>
1 parent ceb2c41 commit 0309635

File tree

3 files changed

+240
-0
lines changed

3 files changed

+240
-0
lines changed
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
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 Microsoft.Azure.Commands.Common.Authentication;
16+
using Microsoft.Azure.Commands.Common.Authentication.Abstractions;
17+
using Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core;
18+
using Microsoft.Azure.Commands.ResourceManager.Common;
19+
20+
using Moq;
21+
22+
using System;
23+
using System.Collections.Generic;
24+
using System.Linq;
25+
using System.Security;
26+
using System.Text.RegularExpressions;
27+
28+
using Xunit;
29+
30+
namespace Microsoft.Azure.Commands.ResourceManager.Test
31+
{
32+
public class AzureRMCmdletAuthenticationUnitTests
33+
{
34+
35+
private static readonly AzureAccount azureAccount = new AzureAccount()
36+
{
37+
Id = "fakeUserId",
38+
Type = AzureAccount.AccountType.User
39+
};
40+
41+
private static IAzureSession CreateAuthenticationFactory()
42+
{
43+
var session = new Mock<IAzureSession>();
44+
var authFactory = new Mock<IAuthenticationFactory>();
45+
authFactory.Setup(f => f.Authenticate(
46+
It.IsAny<IAzureAccount>(),
47+
It.IsAny<IAzureEnvironment>(),
48+
It.IsAny<string>(),
49+
It.IsAny<SecureString>(),
50+
It.IsAny<string>(),
51+
It.IsAny<Action<string>>(),
52+
It.IsAny<string>())).Returns<IAzureAccount, IAzureEnvironment, string, SecureString, string, Action<string>, string>
53+
((a, e, t, w, b, p, r) => new MockAccessToken(azureAccount.Id, t, azureAccount.Type, t));
54+
session.SetupProperty(m => m.AuthenticationFactory, authFactory.Object);
55+
return session.Object;
56+
}
57+
58+
private static AzureRmProfileProvider CreateAzureRmProfile()
59+
{
60+
var context = new Mock<IAzureContext>();
61+
context.SetupProperty(m => m.Account, azureAccount);
62+
63+
var profile = new Mock<IAzureContextContainer>();
64+
profile.SetupProperty(m => m.DefaultContext, context.Object);
65+
66+
var profileProvider = new Mock<AzureRmProfileProvider>();
67+
profileProvider.SetupProperty(m => m.Profile, profile.Object);
68+
return profileProvider.Object;
69+
}
70+
71+
private static void InitializeSessionAndProfile()
72+
{
73+
AzureSession.Initialize(CreateAuthenticationFactory, true);
74+
AzureSession.Modify(s => s.ARMContextSaveMode = ContextSaveMode.Process);
75+
AzureRmProfileProvider.SetInstance(() => CreateAzureRmProfile(), true);
76+
}
77+
78+
private static void Dispose()
79+
{
80+
AzureRmProfileProvider.SetInstance(() => null, true);
81+
AzureSession.Modify(s => s = null);
82+
}
83+
84+
public class MockCmdlet : AzureRMCmdlet
85+
{
86+
public IDictionary<string, IList<string>> GetAuxilaryAuthHeaderByTenatIds(IEnumerable<string> tenantIds)
87+
{
88+
return base.GetAuxiliaryAuthHeaderFromTenantIds(tenantIds);
89+
}
90+
91+
}
92+
93+
[Fact]
94+
public void TestGetAuxHeaderByTenantIds()
95+
{
96+
try
97+
{
98+
InitializeSessionAndProfile();
99+
var cmdlet = new MockCmdlet();
100+
var tenants = new List<string>() { "tenant0", "tenant1", "tenant2" };
101+
102+
var header = cmdlet.GetAuxilaryAuthHeaderByTenatIds(tenants);
103+
104+
Assert.Equal(1, header.Count);
105+
var h = header.First();
106+
Assert.Equal("x-ms-authorization-auxiliary", h.Key);
107+
Assert.Single(h.Value);
108+
var tokens = h.Value.First().Split(';');
109+
var regex = new Regex(@"Bearer ([0-9a-zA-Z]+)");
110+
for (int i = 0; i < tenants.Count; ++i)
111+
{
112+
var match = regex.Match(tokens[i]);
113+
Assert.True(match.Success);
114+
match.Groups[1].Value.StartsWith(tenants[i]);
115+
}
116+
}
117+
finally
118+
{
119+
Dispose();
120+
}
121+
}
122+
123+
[Fact]
124+
public void TestGetAuxHeaderByEmptyTenantIds()
125+
{
126+
try
127+
{
128+
InitializeSessionAndProfile();
129+
var cmdlet = new MockCmdlet();
130+
var tenants = new List<string>();
131+
132+
var header = cmdlet.GetAuxilaryAuthHeaderByTenatIds(tenants);
133+
134+
Assert.Null(header);
135+
}
136+
finally
137+
{
138+
Dispose();
139+
}
140+
}
141+
142+
[Fact]
143+
public void TestGetAuxHeaderByExcessTenantIds()
144+
{
145+
try
146+
{
147+
InitializeSessionAndProfile();
148+
var cmdlet = new MockCmdlet();
149+
var tenants = new List<string>() { "tenant0", "tenant1", "tenant2", "tenants3" };
150+
151+
Assert.Throws<ArgumentException>(() => cmdlet.GetAuxilaryAuthHeaderByTenatIds(tenants));
152+
}
153+
finally
154+
{
155+
Dispose();
156+
}
157+
}
158+
}
159+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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 Microsoft.Azure.Commands.Common.Authentication;
16+
using System;
17+
using System.Collections.Generic;
18+
19+
namespace Microsoft.Azure.Commands.ResourceManager.Test
20+
{
21+
public class MockAccessToken : IRenewableToken
22+
{
23+
public string TenantId { get; set; }
24+
25+
public string UserId { get; set; }
26+
27+
public string LoginType { get; set; }
28+
29+
public string AccessToken { get; set; }
30+
31+
public DateTimeOffset ExpiresOn { get; set; }
32+
33+
public MockAccessToken() { }
34+
35+
public MockAccessToken(string userId, string tenantId, string loginType, string tokenStartWith)
36+
{
37+
UserId = userId;
38+
HomeAccountId = "fakeAccountId";
39+
var current = DateTime.UtcNow;
40+
AccessToken = tokenStartWith + Convert.ToBase64String(BitConverter.GetBytes(current.Ticks));
41+
ExpiresOn = current.AddYears(1);
42+
43+
TenantId = tenantId;
44+
LoginType = loginType;
45+
}
46+
47+
public void AuthorizeRequest(Action<string, string> authTokenSetter)
48+
{
49+
authTokenSetter("Bearer", AccessToken);
50+
}
51+
52+
public string HomeAccountId { get; set; }
53+
54+
public IDictionary<string, string> ExtendedProperties => throw new NotImplementedException();
55+
}
56+
}

src/ResourceManager/Version2016_09_01/AzureRMCmdlet.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,31 @@ private bool ShouldCloneDefaultProfile()
147147

148148
private IAzureContextContainer _clonedDefaultProfile;
149149

150+
protected IDictionary<string, IList<string>> GetAuxiliaryAuthHeaderFromTenantIds(IEnumerable<string> tenantIds)
151+
{
152+
if ((tenantIds != null) && tenantIds.Any())
153+
{
154+
// WE can only fill in tokens for 3 tennats in the aux header, if tehre are more tenants fail now
155+
if (tenantIds.Count() > MAX_NUMBER_OF_TOKENS_ALLOWED_IN_AUX_HEADER)
156+
{
157+
throw new ArgumentException($"Number of tenants (tenants other than the one in the current context), that the requested resources belong to, exceeds maximum allowed number of {MAX_NUMBER_OF_TOKENS_ALLOWED_IN_AUX_HEADER}");
158+
}
159+
160+
//get the tokens for each tenant and prepare the string in the following format :
161+
//"Header Value :: Bearer <auxiliary token1>;EncryptedBearer <auxiliary token2>; Bearer <auxiliary token3>"
162+
var tokens = tenantIds
163+
.Select(t => $"{AUX_TOKEN_PREFIX} {GetTokenForTenant(t)?.AccessToken}")?
164+
.ConcatStrings(AUX_TOKEN_APPEND_CHAR);
165+
166+
var auxHeader = new Dictionary<String, IList<String>>()
167+
{
168+
{ AUX_HEADER_NAME, new List<string>(1) { tokens } }
169+
};
170+
return auxHeader;
171+
}
172+
return null;
173+
}
174+
150175
protected IDictionary<String, List<String>> GetAuxilaryAuthHeaderFromResourceIds(List<String> resourceIds)
151176
{
152177
IDictionary<String, List<String>> auxHeader = null;

0 commit comments

Comments
 (0)