-
Notifications
You must be signed in to change notification settings - Fork 99
/
ConnectedDevicesManager.cs
278 lines (244 loc) · 12 KB
/
ConnectedDevicesManager.cs
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
//*********************************************************
//
// Copyright (c) Microsoft. All rights reserved.
// This code is licensed under the Microsoft Public License.
// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************
using Microsoft.ConnectedDevices;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Threading.Tasks;
using Windows.Storage;
namespace SDKTemplate
{
public class ConnectedDevicesManager
{
private ConnectedDevicesPlatform m_platform;
private List<Account> m_accounts = new List<Account>();
public IReadOnlyList<Account> Accounts
{
get
{
return m_accounts.Where((x) => x.RegistrationState == AccountRegistrationState.InAppCacheAndSdkCache).ToList();
}
}
public event EventHandler AccountsChanged;
static readonly string AccountsKey = "Accounts";
static readonly string CCSResource = "https://cdpcs.access.microsoft.com";
// This is a singleton object which holds onto the app's ConnectedDevicesPlatform and
// handles account management. This is accessed via App.Current.ConnectedDevicesManager
public ConnectedDevicesManager()
{
// Construct and initialize a platform. All we are doing here is hooking up event handlers before
// calling ConnectedDevicesPlatform.Start(). After Start() is called events may begin to fire.
m_platform = new ConnectedDevicesPlatform();
m_platform.AccountManager.AccessTokenRequested += AccountManager_AccessTokenRequestedAsync;
m_platform.AccountManager.AccessTokenInvalidated += AccountManager_AccessTokenInvalidated;
m_platform.NotificationRegistrationManager.NotificationRegistrationStateChanged += NotificationRegistrationManager_NotificationRegistrationStateChanged;
m_platform.Start();
// Pull the accounts from our app's cache and synchronize the list with the apps cached by
// ConnectedDevicesPlatform.AccountManager.
DeserializeAccounts();
// Finally initialize the accounts. This will refresh registrations when needed, add missing accounts,
// and remove stale accounts from the ConnectedDevicesPlatform.AccountManager.
Task.Run(() => InitializeAccountsAsync());
}
private void DeserializeAccounts()
{
// Add all our cached accounts.
var sdkCachedAccounts = m_platform.AccountManager.Accounts.ToList();
var appCachedAccounts = ApplicationData.Current.LocalSettings.Values[AccountsKey] as string;
if (!String.IsNullOrEmpty(appCachedAccounts))
{
DeserializeAppCachedAccounts(appCachedAccounts, sdkCachedAccounts);
}
// Add the remaining SDK only accounts (these need to be removed from the SDK)
foreach (var sdkCachedAccount in sdkCachedAccounts)
{
m_accounts.Add(new Account(m_platform, sdkCachedAccount.Id, sdkCachedAccount.Type, null, AccountRegistrationState.InSdkCacheOnly));
}
}
private void DeserializeAppCachedAccounts(String jsonCachedAccounts, List<ConnectedDevicesAccount> sdkCachedAccounts)
{
MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonCachedAccounts));
DataContractJsonSerializer serializer = new DataContractJsonSerializer(m_accounts.GetType());
List <Account> appCachedAccounts = serializer.ReadObject(stream) as List<Account>;
var authContext = new AuthenticationContext("https://login.microsoftonline.com/common");
var adalCachedItems = authContext.TokenCache.ReadItems();
foreach (var account in appCachedAccounts)
{
if (account.Type == ConnectedDevicesAccountType.AAD)
{
// AAD accounts are also cached in ADAL, which is where the actual token logic lives.
// If the account isn't available in our ADAL cache then it's not usable. Ideally this
// shouldn't happen.
var adalCachedItem = adalCachedItems.FirstOrDefault((x) => x.UniqueId == account.Id);
if (adalCachedItem == null)
{
continue;
}
}
// Check if the account is also present in ConnectedDevicesPlatform.AccountManager.
AccountRegistrationState registrationState;
var sdkAccount = sdkCachedAccounts.Find((x) => account.EqualsTo(x));
if (sdkAccount == null)
{
// Account not found in the SDK cache. Later when Account.InitializeAsync runs this will
// add the account to the SDK cache and perform registration.
registrationState = AccountRegistrationState.InAppCacheOnly;
}
else
{
// Account found in the SDK cache, remove it from the list of sdkCachedAccounts. After
// all the appCachedAccounts have been processed any accounts remaining in sdkCachedAccounts
// are only in the SDK cache, and should be removed.
registrationState = AccountRegistrationState.InAppCacheAndSdkCache;
sdkCachedAccounts.RemoveAll((x) => account.EqualsTo(x));
}
m_accounts.Add(new Account(m_platform, account.Id, account.Type, account.Token, registrationState));
}
}
private async Task InitializeAccountsAsync()
{
foreach (var account in m_accounts)
{
await account.InitializeAccountAsync();
}
// All accounts which can be in a good state should be. Remove any accounts which aren't
m_accounts.RemoveAll((x) => x.RegistrationState != AccountRegistrationState.InAppCacheAndSdkCache);
AccountListChanged();
}
private void AccountListChanged()
{
AccountsChanged.Invoke(this, new EventArgs());
SerializeAccountsToCache();
}
private void SerializeAccountsToCache()
{
using (MemoryStream stream = new MemoryStream())
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(m_accounts.GetType());
serializer.WriteObject(stream, m_accounts);
byte[] json = stream.ToArray();
ApplicationData.Current.LocalSettings.Values[AccountsKey] = Encoding.UTF8.GetString(json, 0, json.Length);
}
}
private void AccountManager_AccessTokenInvalidated(ConnectedDevicesAccountManager sender, ConnectedDevicesAccessTokenInvalidatedEventArgs args)
{
Logger.Instance.LogMessage($"Token Invalidated. AccountId: {args.Account.Id}, AccountType: {args.Account.Id}, scopes: {string.Join(" ", args.Scopes)}");
}
private async void NotificationRegistrationManager_NotificationRegistrationStateChanged(ConnectedDevicesNotificationRegistrationManager sender, ConnectedDevicesNotificationRegistrationStateChangedEventArgs args)
{
if ((args.State == ConnectedDevicesNotificationRegistrationState.Expired) || (args.State == ConnectedDevicesNotificationRegistrationState.Expiring))
{
var account = m_accounts.Find((x) => x.EqualsTo(args.Account));
if (account != null)
{
await account.RegisterAccountWithSdkAsync();
}
}
}
private async void AccountManager_AccessTokenRequestedAsync(ConnectedDevicesAccountManager sender, ConnectedDevicesAccessTokenRequestedEventArgs args)
{
Logger.Instance.LogMessage($"Token requested by platform for {args.Request.Account.Id} and {string.Join(" ", args.Request.Scopes)}");
var account = m_accounts.Find((x) => x.EqualsTo(args.Request.Account));
if (account != null)
{
try
{
var accessToken = await account.GetAccessTokenAsync(args.Request.Scopes);
Logger.Instance.LogMessage($"Token : {accessToken}");
args.Request.CompleteWithAccessToken(accessToken);
}
catch (Exception ex)
{
Logger.Instance.LogMessage($"Token request failed: {ex.Message}");
args.Request.CompleteWithErrorMessage(ex.Message);
}
}
}
public async Task<bool> SignInAadAsync()
{
try
{
var authResult = await Account.GetAadTokenAsync(CCSResource);
var account = new Account(m_platform, authResult.UserInfo.UniqueId,
ConnectedDevicesAccountType.AAD, authResult.AccessToken, AccountRegistrationState.InAppCacheOnly);
m_accounts.Add(account);
await account.InitializeAccountAsync();
AccountListChanged();
return true;
}
catch
{
return false;
}
}
public async Task<bool> SignInMsaAsync()
{
string refreshToken = await MSAOAuthHelpers.GetRefreshTokenAsync();
if (!string.IsNullOrEmpty(refreshToken))
{
var account = new Account(m_platform, Guid.NewGuid().ToString(),
ConnectedDevicesAccountType.MSA, refreshToken, AccountRegistrationState.InAppCacheOnly);
m_accounts.Add(account);
await account.InitializeAccountAsync();
AccountListChanged();
return true;
}
return false;
}
public async Task LogoutAsync(Account account)
{
// First log the account out from the ConnectedDevices SDK. The SDK may call back for access tokens to perform
// unregistration with services
await account.LogoutAsync();
// Next remove the account locally
m_accounts.RemoveAll((x) => x.Id == account.Id);
if (account.Type == ConnectedDevicesAccountType.AAD)
{
var authContext = new AuthenticationContext("https://login.microsoftonline.com/common");
var cacheItems = authContext.TokenCache.ReadItems();
var cacheItem = cacheItems.FirstOrDefault((x) => x.UniqueId == account.Id);
if (cacheItem != null)
{
authContext.TokenCache.DeleteItem(cacheItem);
}
}
AccountListChanged();
}
public async Task ReceiveNotificationAsync(string content)
{
ConnectedDevicesNotification notification = ConnectedDevicesNotification.TryParse(content);
if (notification != null)
{
await m_platform.ProcessNotificationAsync(notification);
// Wait for 15 seconds for platform to process to notification
await Task.Delay(TimeSpan.FromSeconds(15));
}
}
public async Task RefreshAsync()
{
foreach (var account in m_accounts)
{
await account.UserNotifications?.RefreshAsync();
}
}
public async Task ActivateAsync(string id, bool dismiss)
{
foreach (var account in m_accounts)
{
await account.UserNotifications?.ActivateAsync(id, dismiss);
}
}
}
}