-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAzureAuthenticationSample.cs
More file actions
338 lines (308 loc) · 15.3 KB
/
AzureAuthenticationSample.cs
File metadata and controls
338 lines (308 loc) · 15.3 KB
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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
// <copyright file="AzureAuthenticationSample.cs" company="Nakamir, Inc.">
// Copyright (c) Nakamir, Inc. All rights reserved.
// </copyright>
namespace SKAzureCloud;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using global::Azure.Core;
using global::Azure.Storage.Blobs;
using Nakamir.Common;
using Nakamir.Security;
using Nakamir.Security.LoginProviders;
using StereoKit;
using StereoKit.Framework;
using Windows.ApplicationModel.Core;
using Windows.System;
internal class AzureAuthenticationSample
{
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// REPLACE THE PARAMETERS BELOW ///
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private const string ClientId = "bafda199-b430-450b-b9fa-eeca607e5438";
private const string TenantId = "ad853c1d-e755-42bb-92fb-c8ae3c392583";
private const string CustomTenantId = "common"; // Alternatively "[Enter your tenant, as obtained from the Azure portal, e.g. nakamir.onmicrosoft.com]"
private const string Resource = "organizations";
private const string CustomResource = "consumers";
private const string BlobUrl = "https://your-storage-account.blob.core.windows.net/container-name/blob-name";
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private static bool UseCustomTenantId;
private static bool UseCustomResource;
private static string _resultText = string.Empty;
private static Pose _menuPose = new(Input.Head.Forward * 0.5f, Quat.LookAt(Input.Head.Forward, Input.Head.position));
private static MenuState _menuState = MenuState.SignedOut;
private enum MenuState
{
SignedOut,
SigningIn,
SignedIn,
SigningOut,
BlobStorageTest,
};
private static LoginProviderType _currentProviderType = LoginProviderType.MSAL;
private enum LoginProviderType
{
MSAL,
WAB,
WAM,
WAMWAB,
WAP
};
public IUserStore UserStore { get; } = new ApplicationDataUserStore();
public ILoginProvider CurrentLoginProvider { get; set; }
internal static void Main(string[] _)
{
AzureAuthenticationSample sample = new();
void ChangeLoginProvider(LoginProviderType providerType)
{
string tenantId = UseCustomTenantId ? CustomTenantId : TenantId;
string resource = UseCustomResource ? CustomResource : Resource;
sample.CurrentLoginProvider = providerType switch
{
LoginProviderType.MSAL => new MSALLoginProvider(sample.UserStore, ClientId, tenantId,
redirectUri: "https://login.microsoftonline.com/common/oauth2/nativeclient", () =>
{
dynamic coreWindow = CoreApplication.MainView.CoreWindow;
ICoreWindowInterop interop = (ICoreWindowInterop)coreWindow;
return interop.WindowHandle;
}),
LoginProviderType.WAB => new WABLoginProvider(sample.UserStore, ClientId, tenantId),
LoginProviderType.WAM => new WAMLoginProvider(sample.UserStore, ClientId, tenantId, resource, biometricsRequired: false),
LoginProviderType.WAMWAB => new WAMWABLoginProvider(sample.UserStore, ClientId, tenantId, resource),
LoginProviderType.WAP => new WAPLoginProvider(sample.UserStore),
_ => throw new NotImplementedException($"Login provider for {providerType} is not implemented.")
};
}
ChangeLoginProvider(_currentProviderType);
// Initialize StereoKit
SKSettings settings = new()
{
appName = nameof(AzureAuthenticationSample),
assetsFolder = "Assets",
};
if (!SK.Initialize(settings)) { Environment.Exit(1); }
SK.AddStepper<LogWindow>();
Matrix floorTransform = Matrix.TS(0, -1.5f, 0, V.XYZ(30, 0.1f, 30));
Material floorMaterial = new(Shader.FromFile("floor.hlsl"))
{
Transparency = Transparency.Blend
};
string selectedUsername = string.Empty;
Dictionary<string, string> foundUsers = [];
// Watches user events on the device.
var userWatcher = User.CreateWatcher();
userWatcher.Added += async (_, args) =>
{
string username = await args.User.GetPropertyAsync(KnownUserProperties.AccountName) as string;
SK.ExecuteOnMain(() => foundUsers.TryAdd(args.User.NonRoamableId, username));
};
userWatcher.Updated += async (_, args) =>
{
string username = await args.User.GetPropertyAsync(KnownUserProperties.AccountName) as string;
SK.ExecuteOnMain(() => { if (foundUsers.ContainsKey(args.User.NonRoamableId)) foundUsers[args.User.NonRoamableId] = username; });
};
userWatcher.Removed += async (_, args) =>
{
string username = await args.User.GetPropertyAsync(KnownUserProperties.AccountName) as string;
SK.ExecuteOnMain(() => { if (foundUsers.ContainsKey(args.User.NonRoamableId)) foundUsers.Remove(args.User.NonRoamableId); });
};
userWatcher.Start();
CancellationTokenSource cancellationToken = new();
try
{
// Core application loop
SK.Run(() =>
{
UI.WindowBegin("Azure Menu", ref _menuPose, new Vec2(0.53f, 0));
UI.PushEnabled(_menuState == MenuState.SignedOut);
UI.Label("Provider: ");
UI.SameLine();
if (UI.Button(sample.CurrentLoginProvider.ProviderName))
{
// Toggle through the LoginProviderType enum values
_currentProviderType = _currentProviderType switch
{
LoginProviderType.MSAL => LoginProviderType.WAB,
LoginProviderType.WAB => LoginProviderType.WAM,
LoginProviderType.WAM => LoginProviderType.WAMWAB,
LoginProviderType.WAMWAB => LoginProviderType.WAP,
LoginProviderType.WAP => LoginProviderType.MSAL,
_ => _currentProviderType // Fallback in case no match (though this shouldn't happen)
};
ChangeLoginProvider(_currentProviderType);
}
UI.Label("Tenant: ");
UI.SameLine();
if (UI.Button(UseCustomTenantId ? CustomTenantId : TenantId))
{
UseCustomTenantId = !UseCustomTenantId;
ChangeLoginProvider(_currentProviderType);
}
if (_currentProviderType == LoginProviderType.WAM || _currentProviderType == LoginProviderType.WAMWAB)
{
UI.Label("Resource:");
UI.SameLine();
if (UI.Button(UseCustomResource ? CustomResource : Resource))
{
UseCustomResource = !UseCustomResource;
ChangeLoginProvider(_currentProviderType);
}
}
if (_currentProviderType == LoginProviderType.MSAL && sample.CurrentLoginProvider is MSALLoginProvider msalLoginProvider)
{
UI.Label("Use Device Code Flow?");
UI.SameLine();
bool useDeviceCodeFlow = msalLoginProvider.UseDeviceCodeFlow;
if (UI.Toggle(useDeviceCodeFlow ? "Yes" : "No", ref useDeviceCodeFlow))
{
msalLoginProvider.UseDeviceCodeFlow = useDeviceCodeFlow;
}
UI.Label("Use Embedded or System UI?");
UI.SameLine();
bool useEmbedded = msalLoginProvider.UseEmbedded;
if (UI.Button(useEmbedded ? "Embedded" : "System"))
{
msalLoginProvider.UseEmbedded = !msalLoginProvider.UseEmbedded;
}
}
UI.PopEnabled();
if (_menuState != MenuState.SigningIn && _menuState != MenuState.SigningOut)
{
if (UI.Button("Authenticate User"))
{
_menuState = MenuState.SigningIn;
cancellationToken ??= new();
if (sample.CurrentLoginProvider is WAMLoginProvider wamLoginProvider)
{
wamLoginProvider.Resource = "https://graph.microsoft.com";
}
else if (sample.CurrentLoginProvider is WAMWABLoginProvider wamWabLoginProvider)
{
wamWabLoginProvider.Resource = "https://graph.microsoft.com";
}
sample.CurrentLoginProvider.LoginAsync(["User.Read"]).SafeFireAndCallback(result => SK.ExecuteOnMain(()
=> _menuState = string.IsNullOrEmpty(result) ? MenuState.SignedOut : MenuState.SignedIn));
}
else if (UI.Button("Upload Blob"))
{
_menuState = MenuState.SigningIn;
cancellationToken ??= new();
if (sample.CurrentLoginProvider is WAMLoginProvider wamLoginProvider)
{
wamLoginProvider.Resource = "https://storage.azure.com";
}
else if (sample.CurrentLoginProvider is WAMWABLoginProvider wamWabLoginProvider)
{
wamWabLoginProvider.Resource = "https://storage.azure.com";
}
sample.UploadBlobAsync(BlobUrl, ["https://storage.azure.com/user_impersonation"], cancellationToken.Token).SafeFireAndCallback(result =>
SK.ExecuteOnMain(() => _menuState = result ? MenuState.SignedIn : MenuState.SignedOut));
}
if (foundUsers.Count > 0 && _currentProviderType == LoginProviderType.MSAL && sample.CurrentLoginProvider is MSALLoginProvider loginHintProvider)
{
UI.Label("Select an account below to be the login hint:");
foreach (string foundUser in foundUsers.Values)
{
bool isUser = !string.IsNullOrEmpty(loginHintProvider.LoginHint) && foundUser.Equals(loginHintProvider.LoginHint, StringComparison.Ordinal);
if (UI.Toggle(foundUser, ref isUser))
{
loginHintProvider.LoginHint = isUser ? foundUser : null;
}
}
}
}
if (_menuState == MenuState.SigningIn && UI.Button("Cancel"))
{
cancellationToken.Cancel();
cancellationToken.Dispose();
cancellationToken = null;
}
if (_menuState == MenuState.SignedIn && UI.Button("Sign Out"))
{
_menuState = MenuState.SigningOut;
_resultText = string.Empty;
sample.CurrentLoginProvider.LogoutAsync().SafeFireAndCallback(() =>
SK.ExecuteOnMain(() => _menuState = string.IsNullOrEmpty(sample.CurrentLoginProvider.AccessToken) ? MenuState.SignedOut : MenuState.SignedIn));
}
if (UI.Button("Exit"))
{
SK.Quit();
}
string status = _menuState switch
{
MenuState.SignedOut => "Signed Out",
MenuState.SigningIn => "Signing In...",
MenuState.SignedIn => "Signed in as " + sample.CurrentLoginProvider.Username,
MenuState.SigningOut => "Signing Out...",
MenuState.BlobStorageTest => "Uploading and downloading blob...",
_ => string.Empty,
};
UI.Text(status);
if (!string.IsNullOrEmpty(_resultText))
{
UI.Text(_resultText);
}
UI.WindowEnd();
});
}
finally
{
cancellationToken?.Dispose();
userWatcher.Stop();
}
}
/// <summary>
/// Gets token using MSAL and uses it to access Azure Blob Storage.
/// After uploading a text file to a predefined container, it reads the content of the text file.
/// </summary>
/// <returns>True if the blob uploaded and downloaded successfully.</returns>
private async Task<bool> UploadBlobAsync(string blobUrl, string[] scopes, CancellationToken cancellationToken = default)
{
bool success = false;
try
{
string accessToken = await CurrentLoginProvider.LoginAsync(scopes);
if (string.IsNullOrEmpty(accessToken))
{
throw new InvalidOperationException("Could not sign in!");
}
SK.ExecuteOnMain(() => _menuState = MenuState.BlobStorageTest);
TokenCredential tokenCredential = new AccessTokenCredential(accessToken);
BlobClient blobClient = new(new(blobUrl), tokenCredential);
await blobClient.DeleteIfExistsAsync();
// Upload content
var uploadResponse = await blobClient.UploadAsync(new BinaryData(Guid.NewGuid().ToByteArray()), cancellationToken);
Log.Info($"Created container:{blobClient.Name} at{uploadResponse.Value.LastModified}");
// Download content to verify
var content = await blobClient.DownloadContentAsync(cancellationToken);
var contentGuid = new Guid(content.Value.Content.ToArray());
Log.Info($"File: {blobClient.Name}, Content: {contentGuid}");
success = true;
}
catch (Exception ex)
{
Log.Err($"Error {nameof(UploadBlobAsync)}:{Environment.NewLine}{ex}");
}
return success;
}
private class AccessTokenCredential(string accessToken) : TokenCredential
{
public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken)
{
return new AccessToken(accessToken, DateTimeOffset.MaxValue);
}
public override ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken)
{
return new ValueTask<AccessToken>(new AccessToken(accessToken, DateTimeOffset.MaxValue));
}
}
[ComImport, Guid("45D64A29-A63E-4CB6-B498-5781D298CB4F")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ICoreWindowInterop
{
public IntPtr WindowHandle { get; }
public bool MessageHandled { set; }
}
}