ConnectManager.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822
  1. using MediaBrowser.Common.Configuration;
  2. using MediaBrowser.Common.Net;
  3. using MediaBrowser.Controller;
  4. using MediaBrowser.Controller.Configuration;
  5. using MediaBrowser.Controller.Connect;
  6. using MediaBrowser.Controller.Entities;
  7. using MediaBrowser.Controller.Library;
  8. using MediaBrowser.Controller.Providers;
  9. using MediaBrowser.Controller.Security;
  10. using MediaBrowser.Model.Connect;
  11. using MediaBrowser.Model.Entities;
  12. using MediaBrowser.Model.Logging;
  13. using MediaBrowser.Model.Net;
  14. using MediaBrowser.Model.Serialization;
  15. using System;
  16. using System.Collections.Generic;
  17. using System.Globalization;
  18. using System.IO;
  19. using System.Linq;
  20. using System.Net;
  21. using System.Text;
  22. using System.Threading;
  23. using System.Threading.Tasks;
  24. namespace MediaBrowser.Server.Implementations.Connect
  25. {
  26. public class ConnectManager : IConnectManager
  27. {
  28. private readonly SemaphoreSlim _operationLock = new SemaphoreSlim(1, 1);
  29. private readonly ILogger _logger;
  30. private readonly IApplicationPaths _appPaths;
  31. private readonly IJsonSerializer _json;
  32. private readonly IEncryptionManager _encryption;
  33. private readonly IHttpClient _httpClient;
  34. private readonly IServerApplicationHost _appHost;
  35. private readonly IServerConfigurationManager _config;
  36. private readonly IUserManager _userManager;
  37. private readonly IProviderManager _providerManager;
  38. private ConnectData _data = new ConnectData();
  39. public string ConnectServerId
  40. {
  41. get { return _data.ServerId; }
  42. }
  43. public string ConnectAccessKey
  44. {
  45. get { return _data.AccessKey; }
  46. }
  47. public string DiscoveredWanIpAddress { get; private set; }
  48. public string WanIpAddress
  49. {
  50. get
  51. {
  52. var address = _config.Configuration.WanDdns;
  53. if (string.IsNullOrWhiteSpace(address))
  54. {
  55. address = DiscoveredWanIpAddress;
  56. }
  57. return address;
  58. }
  59. }
  60. public string WanApiAddress
  61. {
  62. get
  63. {
  64. var ip = WanIpAddress;
  65. if (!string.IsNullOrEmpty(ip))
  66. {
  67. if (!ip.StartsWith("http://", StringComparison.OrdinalIgnoreCase) &&
  68. !ip.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
  69. {
  70. ip = "http://" + ip;
  71. }
  72. return ip + ":" + _config.Configuration.PublicPort.ToString(CultureInfo.InvariantCulture);
  73. }
  74. return null;
  75. }
  76. }
  77. public ConnectManager(ILogger logger,
  78. IApplicationPaths appPaths,
  79. IJsonSerializer json,
  80. IEncryptionManager encryption,
  81. IHttpClient httpClient,
  82. IServerApplicationHost appHost,
  83. IServerConfigurationManager config, IUserManager userManager, IProviderManager providerManager)
  84. {
  85. _logger = logger;
  86. _appPaths = appPaths;
  87. _json = json;
  88. _encryption = encryption;
  89. _httpClient = httpClient;
  90. _appHost = appHost;
  91. _config = config;
  92. _userManager = userManager;
  93. _providerManager = providerManager;
  94. LoadCachedData();
  95. }
  96. internal void OnWanAddressResolved(string address)
  97. {
  98. DiscoveredWanIpAddress = address;
  99. UpdateConnectInfo();
  100. }
  101. private async void UpdateConnectInfo()
  102. {
  103. await _operationLock.WaitAsync().ConfigureAwait(false);
  104. try
  105. {
  106. await UpdateConnectInfoInternal().ConfigureAwait(false);
  107. }
  108. finally
  109. {
  110. _operationLock.Release();
  111. }
  112. }
  113. private async Task UpdateConnectInfoInternal()
  114. {
  115. var wanApiAddress = WanApiAddress;
  116. if (string.IsNullOrWhiteSpace(wanApiAddress))
  117. {
  118. _logger.Warn("Cannot update Media Browser Connect information without a WanApiAddress");
  119. return;
  120. }
  121. try
  122. {
  123. var hasExistingRecord = !string.IsNullOrWhiteSpace(ConnectServerId) &&
  124. !string.IsNullOrWhiteSpace(ConnectAccessKey);
  125. var createNewRegistration = !hasExistingRecord;
  126. if (hasExistingRecord)
  127. {
  128. try
  129. {
  130. await UpdateServerRegistration(wanApiAddress).ConfigureAwait(false);
  131. }
  132. catch (HttpException ex)
  133. {
  134. if (!ex.StatusCode.HasValue || !new[] { HttpStatusCode.NotFound, HttpStatusCode.Unauthorized }.Contains(ex.StatusCode.Value))
  135. {
  136. throw;
  137. }
  138. createNewRegistration = true;
  139. }
  140. }
  141. if (createNewRegistration)
  142. {
  143. await CreateServerRegistration(wanApiAddress).ConfigureAwait(false);
  144. }
  145. await RefreshAuthorizationsInternal(true, CancellationToken.None).ConfigureAwait(false);
  146. }
  147. catch (Exception ex)
  148. {
  149. _logger.ErrorException("Error registering with Connect", ex);
  150. }
  151. }
  152. private async Task CreateServerRegistration(string wanApiAddress)
  153. {
  154. var url = "Servers";
  155. url = GetConnectUrl(url);
  156. var postData = new Dictionary<string, string>
  157. {
  158. {"name", _appHost.FriendlyName},
  159. {"url", wanApiAddress},
  160. {"systemId", _appHost.SystemId}
  161. };
  162. using (var stream = await _httpClient.Post(url, postData, CancellationToken.None).ConfigureAwait(false))
  163. {
  164. var data = _json.DeserializeFromStream<ServerRegistrationResponse>(stream);
  165. _data.ServerId = data.Id;
  166. _data.AccessKey = data.AccessKey;
  167. CacheData();
  168. }
  169. }
  170. private async Task UpdateServerRegistration(string wanApiAddress)
  171. {
  172. var url = "Servers";
  173. url = GetConnectUrl(url);
  174. url += "?id=" + ConnectServerId;
  175. var options = new HttpRequestOptions
  176. {
  177. Url = url,
  178. CancellationToken = CancellationToken.None
  179. };
  180. options.SetPostData(new Dictionary<string, string>
  181. {
  182. {"name", _appHost.FriendlyName},
  183. {"url", wanApiAddress},
  184. {"systemId", _appHost.SystemId}
  185. });
  186. SetServerAccessToken(options);
  187. // No need to examine the response
  188. using (var stream = (await _httpClient.Post(options).ConfigureAwait(false)).Content)
  189. {
  190. }
  191. }
  192. private readonly object _dataFileLock = new object();
  193. private string CacheFilePath
  194. {
  195. get { return Path.Combine(_appPaths.DataPath, "connect.txt"); }
  196. }
  197. private void CacheData()
  198. {
  199. var path = CacheFilePath;
  200. try
  201. {
  202. Directory.CreateDirectory(Path.GetDirectoryName(path));
  203. var json = _json.SerializeToString(_data);
  204. var encrypted = _encryption.EncryptString(json);
  205. lock (_dataFileLock)
  206. {
  207. File.WriteAllText(path, encrypted, Encoding.UTF8);
  208. }
  209. }
  210. catch (Exception ex)
  211. {
  212. _logger.ErrorException("Error saving data", ex);
  213. }
  214. }
  215. private void LoadCachedData()
  216. {
  217. var path = CacheFilePath;
  218. try
  219. {
  220. lock (_dataFileLock)
  221. {
  222. var encrypted = File.ReadAllText(path, Encoding.UTF8);
  223. var json = _encryption.DecryptString(encrypted);
  224. _data = _json.DeserializeFromString<ConnectData>(json);
  225. }
  226. }
  227. catch (IOException)
  228. {
  229. // File isn't there. no biggie
  230. }
  231. catch (Exception ex)
  232. {
  233. _logger.ErrorException("Error loading data", ex);
  234. }
  235. }
  236. private User GetUser(string id)
  237. {
  238. var user = _userManager.GetUserById(id);
  239. if (user == null)
  240. {
  241. throw new ArgumentException("User not found.");
  242. }
  243. return user;
  244. }
  245. private string GetConnectUrl(string handler)
  246. {
  247. return "https://connect.mediabrowser.tv/service/" + handler;
  248. }
  249. public async Task<UserLinkResult> LinkUser(string userId, string connectUsername)
  250. {
  251. await _operationLock.WaitAsync().ConfigureAwait(false);
  252. try
  253. {
  254. return await LinkUserInternal(userId, connectUsername).ConfigureAwait(false);
  255. }
  256. finally
  257. {
  258. _operationLock.Release();
  259. }
  260. }
  261. private async Task<UserLinkResult> LinkUserInternal(string userId, string connectUsername)
  262. {
  263. if (string.IsNullOrWhiteSpace(connectUsername))
  264. {
  265. throw new ArgumentNullException("connectUsername");
  266. }
  267. var connectUser = await GetConnectUser(new ConnectUserQuery
  268. {
  269. Name = connectUsername
  270. }, CancellationToken.None).ConfigureAwait(false);
  271. if (!connectUser.IsActive)
  272. {
  273. throw new ArgumentException("The Media Browser account has been disabled.");
  274. }
  275. var user = GetUser(userId);
  276. if (!string.IsNullOrWhiteSpace(user.ConnectUserId))
  277. {
  278. await RemoveConnect(user, connectUser.Id).ConfigureAwait(false);
  279. }
  280. var url = GetConnectUrl("ServerAuthorizations");
  281. var options = new HttpRequestOptions
  282. {
  283. Url = url,
  284. CancellationToken = CancellationToken.None
  285. };
  286. var accessToken = Guid.NewGuid().ToString("N");
  287. var postData = new Dictionary<string, string>
  288. {
  289. {"serverId", ConnectServerId},
  290. {"userId", connectUser.Id},
  291. {"userType", "Linked"},
  292. {"accessToken", accessToken}
  293. };
  294. options.SetPostData(postData);
  295. SetServerAccessToken(options);
  296. var result = new UserLinkResult();
  297. // No need to examine the response
  298. using (var stream = (await _httpClient.Post(options).ConfigureAwait(false)).Content)
  299. {
  300. var response = _json.DeserializeFromStream<ServerUserAuthorizationResponse>(stream);
  301. result.IsPending = string.Equals(response.AcceptStatus, "waiting", StringComparison.OrdinalIgnoreCase);
  302. }
  303. user.ConnectAccessKey = accessToken;
  304. user.ConnectUserName = connectUser.Name;
  305. user.ConnectUserId = connectUser.Id;
  306. user.ConnectLinkType = UserLinkType.LinkedUser;
  307. await user.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
  308. user.Configuration.SyncConnectImage = false;
  309. user.Configuration.SyncConnectName = false;
  310. _userManager.UpdateConfiguration(user, user.Configuration);
  311. await RefreshAuthorizationsInternal(false, CancellationToken.None).ConfigureAwait(false);
  312. return result;
  313. }
  314. public async Task<UserLinkResult> InviteUser(string sendingUserId, string connectUsername)
  315. {
  316. await _operationLock.WaitAsync().ConfigureAwait(false);
  317. try
  318. {
  319. return await InviteUserInternal(sendingUserId, connectUsername).ConfigureAwait(false);
  320. }
  321. finally
  322. {
  323. _operationLock.Release();
  324. }
  325. }
  326. private async Task<UserLinkResult> InviteUserInternal(string sendingUserId, string connectUsername)
  327. {
  328. if (string.IsNullOrWhiteSpace(connectUsername))
  329. {
  330. throw new ArgumentNullException("connectUsername");
  331. }
  332. var connectUser = await GetConnectUser(new ConnectUserQuery
  333. {
  334. Name = connectUsername
  335. }, CancellationToken.None).ConfigureAwait(false);
  336. if (!connectUser.IsActive)
  337. {
  338. throw new ArgumentException("The Media Browser account has been disabled.");
  339. }
  340. var url = GetConnectUrl("ServerAuthorizations");
  341. var options = new HttpRequestOptions
  342. {
  343. Url = url,
  344. CancellationToken = CancellationToken.None
  345. };
  346. var accessToken = Guid.NewGuid().ToString("N");
  347. var sendingUser = GetUser(sendingUserId);
  348. var postData = new Dictionary<string, string>
  349. {
  350. {"serverId", ConnectServerId},
  351. {"userId", connectUser.Id},
  352. {"userType", "Guest"},
  353. {"accessToken", accessToken},
  354. {"requesterUserName", sendingUser.ConnectUserName}
  355. };
  356. options.SetPostData(postData);
  357. SetServerAccessToken(options);
  358. var result = new UserLinkResult();
  359. // No need to examine the response
  360. using (var stream = (await _httpClient.Post(options).ConfigureAwait(false)).Content)
  361. {
  362. var response = _json.DeserializeFromStream<ServerUserAuthorizationResponse>(stream);
  363. result.IsPending = string.Equals(response.AcceptStatus, "waiting", StringComparison.OrdinalIgnoreCase);
  364. }
  365. await RefreshAuthorizationsInternal(false, CancellationToken.None).ConfigureAwait(false);
  366. return result;
  367. }
  368. public Task RemoveConnect(string userId)
  369. {
  370. var user = GetUser(userId);
  371. return RemoveConnect(user, user.ConnectUserId);
  372. }
  373. private async Task RemoveConnect(User user, string connectUserId)
  374. {
  375. if (!string.IsNullOrWhiteSpace(connectUserId))
  376. {
  377. await CancelAuthorizationByConnectUserId(connectUserId).ConfigureAwait(false);
  378. }
  379. user.ConnectAccessKey = null;
  380. user.ConnectUserName = null;
  381. user.ConnectUserId = null;
  382. user.ConnectLinkType = null;
  383. await user.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
  384. }
  385. private async Task<ConnectUser> GetConnectUser(ConnectUserQuery query, CancellationToken cancellationToken)
  386. {
  387. var url = GetConnectUrl("user");
  388. if (!string.IsNullOrWhiteSpace(query.Id))
  389. {
  390. url = url + "?id=" + WebUtility.UrlEncode(query.Id);
  391. }
  392. else if (!string.IsNullOrWhiteSpace(query.Name))
  393. {
  394. url = url + "?name=" + WebUtility.UrlEncode(query.Name);
  395. }
  396. else if (!string.IsNullOrWhiteSpace(query.Email))
  397. {
  398. url = url + "?name=" + WebUtility.UrlEncode(query.Email);
  399. }
  400. var options = new HttpRequestOptions
  401. {
  402. CancellationToken = cancellationToken,
  403. Url = url
  404. };
  405. SetServerAccessToken(options);
  406. using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
  407. {
  408. var response = _json.DeserializeFromStream<GetConnectUserResponse>(stream);
  409. return new ConnectUser
  410. {
  411. Email = response.Email,
  412. Id = response.Id,
  413. Name = response.Name,
  414. IsActive = response.IsActive,
  415. ImageUrl = response.ImageUrl
  416. };
  417. }
  418. }
  419. private void SetServerAccessToken(HttpRequestOptions options)
  420. {
  421. options.RequestHeaders.Add("X-Connect-Token", ConnectAccessKey);
  422. }
  423. public async Task RefreshAuthorizations(CancellationToken cancellationToken)
  424. {
  425. await _operationLock.WaitAsync(cancellationToken).ConfigureAwait(false);
  426. try
  427. {
  428. await RefreshAuthorizationsInternal(true, cancellationToken).ConfigureAwait(false);
  429. }
  430. finally
  431. {
  432. _operationLock.Release();
  433. }
  434. }
  435. private async Task RefreshAuthorizationsInternal(bool refreshImages, CancellationToken cancellationToken)
  436. {
  437. var url = GetConnectUrl("ServerAuthorizations");
  438. url += "?serverId=" + ConnectServerId;
  439. var options = new HttpRequestOptions
  440. {
  441. Url = url,
  442. CancellationToken = cancellationToken
  443. };
  444. SetServerAccessToken(options);
  445. try
  446. {
  447. using (var stream = (await _httpClient.SendAsync(options, "GET").ConfigureAwait(false)).Content)
  448. {
  449. var list = _json.DeserializeFromStream<List<ServerUserAuthorizationResponse>>(stream);
  450. await RefreshAuthorizations(list, refreshImages).ConfigureAwait(false);
  451. }
  452. }
  453. catch (Exception ex)
  454. {
  455. _logger.ErrorException("Error refreshing server authorizations.", ex);
  456. }
  457. }
  458. private readonly SemaphoreSlim _connectImageSemaphore = new SemaphoreSlim(5, 5);
  459. private async Task RefreshAuthorizations(List<ServerUserAuthorizationResponse> list, bool refreshImages)
  460. {
  461. var users = _userManager.Users.ToList();
  462. // Handle existing authorizations that were removed by the Connect server
  463. // Handle existing authorizations whose status may have been updated
  464. foreach (var user in users)
  465. {
  466. if (!string.IsNullOrWhiteSpace(user.ConnectUserId))
  467. {
  468. var connectEntry = list.FirstOrDefault(i => string.Equals(i.UserId, user.ConnectUserId, StringComparison.OrdinalIgnoreCase));
  469. if (connectEntry == null)
  470. {
  471. user.ConnectUserId = null;
  472. user.ConnectAccessKey = null;
  473. user.ConnectUserName = null;
  474. user.ConnectLinkType = null;
  475. await _userManager.UpdateUser(user).ConfigureAwait(false);
  476. if (user.ConnectLinkType.HasValue && user.ConnectLinkType.Value == UserLinkType.Guest)
  477. {
  478. _logger.Debug("Deleting guest user {0}", user.Name);
  479. await _userManager.DeleteUser(user).ConfigureAwait(false);
  480. }
  481. }
  482. else
  483. {
  484. var changed = !string.Equals(user.ConnectAccessKey, connectEntry.AccessToken, StringComparison.OrdinalIgnoreCase);
  485. if (changed)
  486. {
  487. user.ConnectUserId = connectEntry.UserId;
  488. user.ConnectAccessKey = connectEntry.AccessToken;
  489. await _userManager.UpdateUser(user).ConfigureAwait(false);
  490. }
  491. }
  492. }
  493. }
  494. users = _userManager.Users.ToList();
  495. var pending = new List<ConnectAuthorization>();
  496. foreach (var connectEntry in list)
  497. {
  498. if (string.Equals(connectEntry.UserType, "guest", StringComparison.OrdinalIgnoreCase))
  499. {
  500. if (string.Equals(connectEntry.AcceptStatus, "accepted", StringComparison.OrdinalIgnoreCase))
  501. {
  502. var user = users.FirstOrDefault(i => string.Equals(i.ConnectUserId, connectEntry.UserId, StringComparison.OrdinalIgnoreCase));
  503. if (user == null)
  504. {
  505. // Add user
  506. user = await _userManager.CreateUser(connectEntry.UserName).ConfigureAwait(false);
  507. user.ConnectUserName = connectEntry.UserName;
  508. user.ConnectUserId = connectEntry.UserId;
  509. user.ConnectLinkType = UserLinkType.Guest;
  510. user.ConnectAccessKey = connectEntry.AccessToken;
  511. await _userManager.UpdateUser(user).ConfigureAwait(false);
  512. user.Configuration.SyncConnectImage = true;
  513. user.Configuration.SyncConnectName = true;
  514. user.Configuration.IsHidden = true;
  515. _userManager.UpdateConfiguration(user, user.Configuration);
  516. }
  517. }
  518. else if (string.Equals(connectEntry.AcceptStatus, "waiting", StringComparison.OrdinalIgnoreCase))
  519. {
  520. pending.Add(new ConnectAuthorization
  521. {
  522. ConnectUserId = connectEntry.UserId,
  523. ImageUrl = connectEntry.UserImageUrl,
  524. UserName = connectEntry.UserName,
  525. Id = connectEntry.Id
  526. });
  527. }
  528. }
  529. }
  530. _data.PendingAuthorizations = pending;
  531. CacheData();
  532. await RefreshGuestNames(list, refreshImages).ConfigureAwait(false);
  533. }
  534. private async Task RefreshGuestNames(List<ServerUserAuthorizationResponse> list, bool refreshImages)
  535. {
  536. var users = _userManager.Users
  537. .Where(i => !string.IsNullOrEmpty(i.ConnectUserId) &&
  538. (i.Configuration.SyncConnectImage || i.Configuration.SyncConnectName))
  539. .ToList();
  540. foreach (var user in users)
  541. {
  542. var authorization = list.FirstOrDefault(i => string.Equals(i.UserId, user.ConnectUserId, StringComparison.Ordinal));
  543. if (authorization == null)
  544. {
  545. _logger.Warn("Unable to find connect authorization record for user {0}", user.Name);
  546. continue;
  547. }
  548. if (user.Configuration.SyncConnectName)
  549. {
  550. var changed = !string.Equals(authorization.UserName, user.Name, StringComparison.OrdinalIgnoreCase);
  551. if (changed)
  552. {
  553. await user.Rename(authorization.UserName).ConfigureAwait(false);
  554. }
  555. }
  556. if (user.Configuration.SyncConnectImage)
  557. {
  558. var imageUrl = authorization.UserImageUrl;
  559. if (!string.IsNullOrWhiteSpace(imageUrl))
  560. {
  561. var changed = false;
  562. if (!user.HasImage(ImageType.Primary))
  563. {
  564. changed = true;
  565. }
  566. else if (refreshImages)
  567. {
  568. using (var response = await _httpClient.SendAsync(new HttpRequestOptions
  569. {
  570. Url = imageUrl,
  571. BufferContent = false
  572. }, "HEAD").ConfigureAwait(false))
  573. {
  574. var length = response.ContentLength;
  575. if (length != new FileInfo(user.GetImageInfo(ImageType.Primary, 0).Path).Length)
  576. {
  577. changed = true;
  578. }
  579. }
  580. }
  581. if (changed)
  582. {
  583. await _providerManager.SaveImage(user, imageUrl, _connectImageSemaphore, ImageType.Primary, null, CancellationToken.None).ConfigureAwait(false);
  584. await user.RefreshMetadata(new MetadataRefreshOptions
  585. {
  586. ForceSave = true,
  587. }, CancellationToken.None).ConfigureAwait(false);
  588. }
  589. }
  590. }
  591. }
  592. }
  593. public async Task<List<ConnectAuthorization>> GetPendingGuests()
  594. {
  595. var time = DateTime.UtcNow - _data.LastAuthorizationsRefresh;
  596. if (time.TotalMinutes >= 5)
  597. {
  598. await _operationLock.WaitAsync(CancellationToken.None).ConfigureAwait(false);
  599. try
  600. {
  601. await RefreshAuthorizationsInternal(false, CancellationToken.None).ConfigureAwait(false);
  602. _data.LastAuthorizationsRefresh = DateTime.UtcNow;
  603. CacheData();
  604. }
  605. finally
  606. {
  607. _operationLock.Release();
  608. }
  609. }
  610. return _data.PendingAuthorizations.ToList();
  611. }
  612. public async Task CancelAuthorization(string id)
  613. {
  614. await _operationLock.WaitAsync().ConfigureAwait(false);
  615. try
  616. {
  617. await CancelAuthorizationInternal(id).ConfigureAwait(false);
  618. }
  619. finally
  620. {
  621. _operationLock.Release();
  622. }
  623. }
  624. private async Task CancelAuthorizationInternal(string id)
  625. {
  626. var connectUserId = _data.PendingAuthorizations
  627. .First(i => string.Equals(i.Id, id, StringComparison.Ordinal))
  628. .ConnectUserId;
  629. await CancelAuthorizationByConnectUserId(connectUserId).ConfigureAwait(false);
  630. await RefreshAuthorizationsInternal(false, CancellationToken.None).ConfigureAwait(false);
  631. }
  632. private async Task CancelAuthorizationByConnectUserId(string connectUserId)
  633. {
  634. var url = GetConnectUrl("ServerAuthorizations");
  635. var options = new HttpRequestOptions
  636. {
  637. Url = url,
  638. CancellationToken = CancellationToken.None
  639. };
  640. var postData = new Dictionary<string, string>
  641. {
  642. {"serverId", ConnectServerId},
  643. {"userId", connectUserId}
  644. };
  645. options.SetPostData(postData);
  646. SetServerAccessToken(options);
  647. try
  648. {
  649. // No need to examine the response
  650. using (var stream = (await _httpClient.SendAsync(options, "DELETE").ConfigureAwait(false)).Content)
  651. {
  652. }
  653. }
  654. catch (HttpException ex)
  655. {
  656. // If connect says the auth doesn't exist, we can handle that gracefully since this is a remove operation
  657. if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound)
  658. {
  659. throw;
  660. }
  661. _logger.Debug("Connect returned a 404 when removing a user auth link. Handling it.");
  662. }
  663. }
  664. }
  665. }