{"action":"created","comment":{"url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/pulls/comments/237322057","pull_request_review_id":179591403,"id":237322057,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIzNzMyMjA1Nw==","diff_hunk":"@@ -0,0 +1,551 @@\n+﻿// ------------------------------------------------------------------------------\n+// \n+// Copyright (c) Microsoft Corporation.\n+// All rights reserved.\n+// \n+// This code is licensed under the MIT License.\n+// \n+// Permission is hereby granted, free of charge, to any person obtaining a copy\n+// of this software and associated documentation files(the \"Software\"), to deal\n+// in the Software without restriction, including without limitation the rights\n+// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell\n+// copies of the Software, and to permit persons to whom the Software is\n+// furnished to do so, subject to the following conditions :\n+// \n+// The above copyright notice and this permission notice shall be included in\n+// all copies or substantial portions of the Software.\n+// \n+// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE\n+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n+// THE SOFTWARE.\n+// \n+// ------------------------------------------------------------------------------\n+\n+using System;\n+using System.Collections.Generic;\n+using System.Linq;\n+using System.Text;\n+using Microsoft.Identity.Client.CacheV2.Impl.Utils;\n+using Microsoft.Identity.Client.CacheV2.Schema;\n+using Microsoft.Identity.Json;\n+using Microsoft.Identity.Json.Linq;\n+\n+namespace Microsoft.Identity.Client.CacheV2.Impl\n+{\n+    internal class PathStorageWorker : IStorageWorker\n+    {\n+        private readonly ICredentialPathManager _credentialPathManager;\n+        private readonly ICachePathStorage _fileIo;\n+\n+        public PathStorageWorker(ICachePathStorage fileIo, ICredentialPathManager credentialPathManager)\n+        {\n+            _fileIo = fileIo;\n+            _credentialPathManager = credentialPathManager;\n+        }\n+\n+        public IEnumerable<Credential> ReadCredentials(\n+            string homeAccountId,\n+            string environment,\n+            string realm,\n+            string clientId,\n+            string familyId,\n+            string target,\n+            ISet<CredentialType> types)\n+        {\n+            var credentials = new List<Credential>();\n+\n+            foreach (var credentialType in types)\n+            {\n+                switch (credentialType)\n+                {\n+                case CredentialType.OAuth2AccessToken:\n+                    ReadAccessTokens(\n+                        homeAccountId,\n+                        environment,\n+                        realm,\n+                        clientId,\n+                        target,\n+                        credentials);\n+                    break;\n+                case CredentialType.OAuth2RefreshToken:\n+                    ReadRefreshTokens(\n+                        homeAccountId,\n+                        environment,\n+                        clientId,\n+                        familyId,\n+                        credentials);\n+                    break;\n+                case CredentialType.OidcIdToken:\n+                    ReadIdTokens(\n+                        homeAccountId,\n+                        environment,\n+                        realm,\n+                        clientId,\n+                        credentials);\n+                    break;\n+                }\n+            }\n+\n+            return credentials;\n+        }\n+\n+        public void WriteCredentials(IEnumerable<Credential> credentials)\n+        {\n+            foreach (var credential in credentials)\n+            {\n+                var credentialJson = StorageJsonUtils.CredentialToJson(credential);\n+                string credentialPath = GetCredentialPath(credential);\n+\n+                if (credential.CredentialType == CredentialType.OAuth2AccessToken)\n+                {\n+                    ReadModifyWrite(\n+                        credentialPath,\n+                        (fileContentJson) =>\n+                        {\n+                            AddAccessTokenWithScopes(fileContentJson, credentialJson);\n+                            return fileContentJson;\n+                        });\n+                }\n+                else\n+                {\n+                    ReadModifyWrite(\n+                        credentialPath,\n+                        (fileContentJson) =>\n+                        {\n+                            JsonMerge(credentialJson, fileContentJson);\n+                            return credentialJson;\n+                        });\n+                }\n+            }\n+        }\n+\n+        public void DeleteCredentials(\n+            string homeAccountId,\n+            string environment,\n+            string realm,\n+            string clientId,\n+            string familyId,\n+            string target,\n+            ISet<CredentialType> types)\n+        {\n+            foreach (var type in types)\n+            {\n+                switch (type)\n+                {\n+                case CredentialType.OAuth2AccessToken:\n+                    DeleteAccessTokens(\n+                        homeAccountId,\n+                        environment,\n+                        realm,\n+                        clientId,\n+                        target);\n+                    break;\n+                case CredentialType.OAuth2RefreshToken:\n+                    DeleteRefreshTokens(homeAccountId, environment, clientId, familyId);\n+                    break;\n+                case CredentialType.OidcIdToken:\n+                    DeleteIdTokens(homeAccountId, environment, realm, clientId);\n+                    break;\n+                }\n+            }\n+        }\n+\n+        public Microsoft.Identity.Client.CacheV2.Schema.Account ReadAccount(string homeAccountId, string environment, string realm)\n+        {\n+            var fileContentJson = Read(_credentialPathManager.GetAccountPath(homeAccountId, environment, realm));\n+            return fileContentJson.HasValues ? StorageJsonUtils.AccountFromJson(fileContentJson) : null;\n+        }\n+\n+        public void WriteAccount(Microsoft.Identity.Client.CacheV2.Schema.Account account)\n+        {\n+            var accountJson = StorageJsonUtils.AccountToJson(account);\n+            string accountPath = _credentialPathManager.GetAccountPath(account);\n+\n+            ReadModifyWrite(\n+                accountPath,\n+                fileContentJson =>\n+                {\n+                    JsonMerge(accountJson, fileContentJson);\n+                    return accountJson;\n+                });\n+        }\n+\n+        public void DeleteAccount(string homeAccountId, string environment, string realm)\n+        {\n+            _fileIo.DeleteFile(_credentialPathManager.GetAccountPath(homeAccountId, environment, realm));\n+        }\n+\n+        public void DeleteAccounts(string homeAccountId, string environment)\n+        {\n+            _fileIo.DeleteContent(_credentialPathManager.GetAccountsPath(homeAccountId, environment));\n+        }\n+\n+        public AppMetadata ReadAppMetadata(string environment, string clientId)\n+        {\n+            string appMetadataPath = _credentialPathManager.GetAppMetadataPath(environment, clientId);\n+            var appMetadataJson = Read(appMetadataPath);\n+            return appMetadataJson.HasValues ? StorageJsonUtils.AppMetadataFromJson(appMetadataJson) : null;\n+        }\n+\n+        public void WriteAppMetadata(AppMetadata appMetadata)\n+        {\n+            var appMetadataJson = StorageJsonUtils.AppMetadataToJson(appMetadata);\n+            string appMetadataPath = _credentialPathManager.GetAppMetadataPath(appMetadata);\n+            ReadModifyWrite(\n+                appMetadataPath,\n+                fileContent =>\n+                {\n+                    JsonMerge(appMetadataJson, fileContent);\n+                    return appMetadataJson;\n+                });\n+        }\n+\n+        private void ReadRefreshTokens(\n+            string homeAccountId,\n+            string environment,\n+            string clientId,\n+            string familyId,\n+            List<Credential> credentials)\n+        {\n+            string credentialPath;\n+\n+            // Read the RT\n+            if (!string.IsNullOrWhiteSpace(clientId))\n+            {\n+                credentialPath = _credentialPathManager.GetCredentialPath(\n+                    homeAccountId,\n+                    environment,\n+                    string.Empty,\n+                    clientId,\n+                    string.Empty,\n+                    CredentialType.OAuth2RefreshToken);\n+                ReadCredential(credentialPath, credentials);\n+            }\n+\n+            // Read the FRT\n+            if (!string.IsNullOrWhiteSpace(familyId))\n+            {\n+                credentialPath = _credentialPathManager.GetCredentialPath(\n+                    homeAccountId,\n+                    environment,\n+                    string.Empty,\n+                    string.Empty,\n+                    familyId,\n+                    CredentialType.OAuth2RefreshToken);\n+                ReadCredential(credentialPath, credentials);\n+            }\n+        }\n+\n+        private void ReadAccessTokens(\n+            string homeAccountId,\n+            string environment,\n+            string realm,\n+            string clientId,\n+            string target,\n+            List<Credential> credentials)\n+        {\n+            string credentialPath = _credentialPathManager.GetCredentialPath(\n+                homeAccountId,\n+                environment,\n+                realm,\n+                clientId,\n+                string.Empty,\n+                CredentialType.OAuth2AccessToken);\n+            var fileContentJson = Read(credentialPath);\n+            var accessToken = FindAccessTokenWithScopes(fileContentJson, target);\n+            if (accessToken != null)\n+            {\n+                credentials.Add(accessToken);\n+            }\n+        }\n+\n+        private KeyValuePair<string, JToken>? FindAccessTokenIterWithScopes(JObject accessTokens, string target)\n+        {\n+            HashSet<string> requestedScopes = SplitAndNormalizeScopes(target);\n+            if (!requestedScopes.Any())\n+            {\n+                throw new InvalidOperationException(\"no access token scopes requested\"); // todo: storage exception\n+            }\n+\n+            foreach (KeyValuePair<string, JToken> token in accessTokens)\n+            {\n+                HashSet<string> currentTokenScopes = SplitAndNormalizeScopes(token.Key);\n+                if (requestedScopes.IsSubsetOf(currentTokenScopes))\n+                {\n+                    return token;\n+                }\n+            }\n+\n+            return null;\n+        }\n+\n+        public Credential FindAccessTokenWithScopes(JObject accessTokens, string target)\n+        {\n+            KeyValuePair<string, JToken>? kvp = FindAccessTokenIterWithScopes(accessTokens, target);\n+            if (kvp.HasValue)\n+            {\n+                return StorageJsonUtils.CredentialFromJson(JObject.Parse(kvp.Value.Value.ToString()));\n+            }\n+\n+            return null;\n+        }\n+\n+        internal HashSet<string> SplitAndNormalizeScopes(string target)\n+        {\n+            var scopes = ScopeUtils.SplitScopes(target);\n+            var normalizedScopes = new HashSet<string>();\n+            foreach (string scope in scopes)\n+            {\n+                normalizedScopes.Add(NormalizeKey(scope));\n+            }\n+\n+            return normalizedScopes;\n+        }\n+\n+        private void ReadIdTokens(\n+            string homeAccountId,\n+            string environment,\n+            string realm,\n+            string clientId,\n+            List<Credential> credentials)\n+        {\n+            string credentialPath = _credentialPathManager.GetCredentialPath(\n+                homeAccountId,\n+                environment,\n+                realm,\n+                clientId,\n+                string.Empty,\n+                CredentialType.OidcIdToken);\n+            ReadCredential(credentialPath, credentials);\n+        }\n+\n+        private void ReadCredential(string relativePath, List<Credential> credentials)\n+        {\n+            var json = Read(relativePath);\n+            if (json != null && !json.IsEmpty())\n+            {\n+                credentials.Add(StorageJsonUtils.CredentialFromJson(json));\n+            }\n+        }\n+\n+        public JObject Read(string relativePath)\n+        {\n+            byte[] fileContent = _fileIo.Read(relativePath);\n+            return fileContent.Length == 0 ? new JObject() : DecryptParse(fileContent, relativePath);\n+        }\n+\n+        private JObject DecryptParse(byte[] fileContent, string relativePath)\n+        {\n+            if (fileContent == null || !fileContent.Any())\n+            {\n+                return new JObject();\n+            }\n+\n+            try\n+            {\n+                byte[] decryptedFileContent = Decrypt(fileContent);\n+                return JObject.Parse(Encoding.UTF8.GetString(decryptedFileContent, 0, decryptedFileContent.Length));\n+            }\n+            catch (JsonReaderException)\n+            {\n+            }\n+            //catch (CryptoException)\n+            //{\n+\n+            //}\n+\n+            return new JObject();\n+        }\n+\n+        private string NormalizeKey(string data)\n+        {\n+            return data.ToLowerInvariant().Trim();\n+        }\n+\n+        internal string GetCredentialPath(Credential credential)\n+        {\n+            return _credentialPathManager.GetCredentialPath(\n+                credential.HomeAccountId,\n+                credential.Environment,\n+                credential.Realm,\n+                credential.ClientId,\n+                credential.FamilyId,\n+                credential.CredentialType);\n+        }\n+\n+        public void AddAccessTokenWithScopes(JObject accessTokens, JObject tokenToAdd)\n+        {\n+            if (!tokenToAdd.ContainsKey(StorageJsonKeys.Target))\n+            {\n+                throw new ArgumentException(\"target field missing from access token\", nameof(tokenToAdd));\n+            }\n+\n+            string requestedTarget = tokenToAdd.GetValue(StorageJsonKeys.Target).ToObject<string>();\n+            HashSet<string> requestedScopes = SplitAndNormalizeScopes(requestedTarget);\n+\n+            if (!requestedScopes.Any())\n+            {\n+                throw new InvalidOperationException(\"access token has no scopes\");\n+            }\n+\n+            var keysToRemove = new List<string>();\n+\n+            foreach (KeyValuePair<string, JToken> token in accessTokens)\n+            {\n+                HashSet<string> currentTokenScopes = SplitAndNormalizeScopes(token.Key);\n+                if (HashSetsAreIntersecting(requestedScopes, currentTokenScopes))\n+                {\n+                    keysToRemove.Add(token.Key);\n+                }\n+            }\n+\n+            foreach (string key in keysToRemove)\n+            {\n+                accessTokens.Remove(key);\n+            }\n+\n+            accessTokens[requestedTarget] = tokenToAdd;\n+        }\n+\n+        private bool HashSetsAreIntersecting(HashSet<string> lhs, HashSet<string> rhs)\n+        {\n+            HashSet<string> smallerSet = lhs.Count < rhs.Count ? lhs : rhs;\n+            HashSet<string> largerSet = lhs.Count < rhs.Count ? rhs : lhs;\n+\n+            foreach (string item in smallerSet)\n+            {\n+                if (largerSet.Contains(item))\n+                {\n+                    return true;\n+                }\n+            }\n+\n+            return false;\n+        }\n+\n+        private void DeleteIdTokens(\n+            string homeAccountId,\n+            string environment,\n+            string realm,\n+            string clientId)\n+        {\n+            _fileIo.DeleteFile(\n+                _credentialPathManager.GetCredentialPath(\n+                    homeAccountId,\n+                    environment,\n+                    realm,\n+                    clientId,\n+                    string.Empty,\n+                    CredentialType.OidcIdToken));\n+        }\n+\n+        private void DeleteRefreshTokens(\n+            string homeAccountId,\n+            string environment,\n+            string clientId,\n+            string familyId)\n+        {\n+            if (!string.IsNullOrWhiteSpace(familyId))\n+            {\n+                _fileIo.DeleteFile(\n+                    _credentialPathManager.GetCredentialPath(\n+                        homeAccountId,\n+                        environment,\n+                        string.Empty,\n+                        string.Empty,\n+                        familyId,\n+                        CredentialType.OAuth2RefreshToken));\n+            }\n+\n+            if (!string.IsNullOrWhiteSpace(clientId))\n+            {\n+                _fileIo.DeleteFile(\n+                    _credentialPathManager.GetCredentialPath(\n+                        homeAccountId,\n+                        environment,\n+                        string.Empty,\n+                        clientId,\n+                        string.Empty,\n+                        CredentialType.OAuth2RefreshToken));\n+            }\n+        }\n+\n+        private void DeleteAccessTokens(\n+            string homeAccountId,\n+            string environment,\n+            string realm,\n+            string clientId,\n+            string target)\n+        {\n+            string credentialPath = _credentialPathManager.GetCredentialPath(\n+                homeAccountId,\n+                environment,\n+                realm,\n+                clientId,\n+                string.Empty,\n+                CredentialType.OAuth2AccessToken);\n+            bool isFileEmpty = false;\n+            ReadModifyWrite(\n+                credentialPath,\n+                fileContentJson =>\n+                {\n+                    RemoveAccessTokenWithScopes(fileContentJson, target);\n+                    if (!fileContentJson.HasValues)\n+                    {\n+                        isFileEmpty = true;\n+                    }\n+\n+                    return fileContentJson;\n+                });\n+\n+            if (isFileEmpty)\n+            {\n+                _fileIo.DeleteFile(credentialPath);\n+            }\n+        }\n+\n+        public void RemoveAccessTokenWithScopes(JObject accessTokens, string target)\n+        {\n+            KeyValuePair<string, JToken>? kvp = FindAccessTokenIterWithScopes(accessTokens, target);\n+            if (kvp.HasValue)\n+            {\n+                accessTokens.Remove(kvp.Value.Key);\n+            }\n+        }\n+\n+        public void ReadModifyWrite(string relativePath, Func<JObject, JObject> modify)\n+        {\n+            _fileIo.ReadModifyWrite(\n+                relativePath,\n+                existingBytes =>\n+                {\n+                    var fileContentJson = DecryptParse(existingBytes, relativePath);\n+                    fileContentJson = modify(fileContentJson);\n+                    string serializedJson = fileContentJson.ToString();\n+                    return Encrypt(serializedJson);\n+                });\n+        }\n+\n+        public byte[] Encrypt(string input)\n+        {\n+            return Encoding.UTF8.GetBytes(input);\n+        }\n+\n+        private byte[] Decrypt(byte[] input)\n+        {\n+            return input;\n+        }","path":"msal/src/Microsoft.Identity.Client/CacheV2/Impl/PathStorageWorker.cs","position":541,"original_position":541,"commit_id":"24d1424ae0f2c871da7922435b632f78c0232a25","original_commit_id":"24805241f071c859269a24d76334ff102c1f1248","user":{"login":"MarkZuber","id":7103178,"node_id":"MDQ6VXNlcjcxMDMxNzg=","avatar_url":"https://avatars0.githubusercontent.com/u/7103178?v=4","gravatar_id":"","url":"https://api.github.com/users/MarkZuber","html_url":"https://github.com/MarkZuber","followers_url":"https://api.github.com/users/MarkZuber/followers","following_url":"https://api.github.com/users/MarkZuber/following{/other_user}","gists_url":"https://api.github.com/users/MarkZuber/gists{/gist_id}","starred_url":"https://api.github.com/users/MarkZuber/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MarkZuber/subscriptions","organizations_url":"https://api.github.com/users/MarkZuber/orgs","repos_url":"https://api.github.com/users/MarkZuber/repos","events_url":"https://api.github.com/users/MarkZuber/events{/privacy}","received_events_url":"https://api.github.com/users/MarkZuber/received_events","type":"User","site_admin":false},"body":"added TODO block here","created_at":"2018-11-29T01:10:11Z","updated_at":"2018-11-29T01:10:11Z","html_url":"https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/pull/1433#discussion_r237322057","pull_request_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/pulls/1433","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/pulls/comments/237322057"},"html":{"href":"https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/pull/1433#discussion_r237322057"},"pull_request":{"href":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/pulls/1433"}},"in_reply_to_id":236925771},"pull_request":{"url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/pulls/1433","id":233741999,"node_id":"MDExOlB1bGxSZXF1ZXN0MjMzNzQxOTk5","html_url":"https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/pull/1433","diff_url":"https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/pull/1433.diff","patch_url":"https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/pull/1433.patch","issue_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/issues/1433","number":1433,"state":"open","locked":false,"title":"First round of CacheV2 infrastructure","user":{"login":"MarkZuber","id":7103178,"node_id":"MDQ6VXNlcjcxMDMxNzg=","avatar_url":"https://avatars0.githubusercontent.com/u/7103178?v=4","gravatar_id":"","url":"https://api.github.com/users/MarkZuber","html_url":"https://github.com/MarkZuber","followers_url":"https://api.github.com/users/MarkZuber/followers","following_url":"https://api.github.com/users/MarkZuber/following{/other_user}","gists_url":"https://api.github.com/users/MarkZuber/gists{/gist_id}","starred_url":"https://api.github.com/users/MarkZuber/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MarkZuber/subscriptions","organizations_url":"https://api.github.com/users/MarkZuber/orgs","repos_url":"https://api.github.com/users/MarkZuber/repos","events_url":"https://api.github.com/users/MarkZuber/events{/privacy}","received_events_url":"https://api.github.com/users/MarkZuber/received_events","type":"User","site_admin":false},"body":"This brings in the first round of code needed for interaction with per-platform persistence (like MSALC++ is doing).  It's based off of the msal c++ cachemanager/storagemanager architecture with the goal of being able to share token persistence with those platforms (e.g. VS share with Office).\r\n\r\nThe cachev2 is NOT wired up in any way to real code yet.  I have some of that for a later checkin, but want to start with the v2 cache infra isolated, get feedback and polish in place, and flesh out remaining test infrastructure.  ","created_at":"2018-11-27T00:33:32Z","updated_at":"2018-11-29T01:10:11Z","closed_at":null,"merged_at":null,"merge_commit_sha":"a600fc05d8fa3d616d8cc206e898d5b061ca8884","assignee":null,"assignees":[],"requested_reviewers":[{"login":"bgavrilMS","id":12273384,"node_id":"MDQ6VXNlcjEyMjczMzg0","avatar_url":"https://avatars1.githubusercontent.com/u/12273384?v=4","gravatar_id":"","url":"https://api.github.com/users/bgavrilMS","html_url":"https://github.com/bgavrilMS","followers_url":"https://api.github.com/users/bgavrilMS/followers","following_url":"https://api.github.com/users/bgavrilMS/following{/other_user}","gists_url":"https://api.github.com/users/bgavrilMS/gists{/gist_id}","starred_url":"https://api.github.com/users/bgavrilMS/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bgavrilMS/subscriptions","organizations_url":"https://api.github.com/users/bgavrilMS/orgs","repos_url":"https://api.github.com/users/bgavrilMS/repos","events_url":"https://api.github.com/users/bgavrilMS/events{/privacy}","received_events_url":"https://api.github.com/users/bgavrilMS/received_events","type":"User","site_admin":false},{"login":"jmprieur","id":13203188,"node_id":"MDQ6VXNlcjEzMjAzMTg4","avatar_url":"https://avatars0.githubusercontent.com/u/13203188?v=4","gravatar_id":"","url":"https://api.github.com/users/jmprieur","html_url":"https://github.com/jmprieur","followers_url":"https://api.github.com/users/jmprieur/followers","following_url":"https://api.github.com/users/jmprieur/following{/other_user}","gists_url":"https://api.github.com/users/jmprieur/gists{/gist_id}","starred_url":"https://api.github.com/users/jmprieur/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jmprieur/subscriptions","organizations_url":"https://api.github.com/users/jmprieur/orgs","repos_url":"https://api.github.com/users/jmprieur/repos","events_url":"https://api.github.com/users/jmprieur/events{/privacy}","received_events_url":"https://api.github.com/users/jmprieur/received_events","type":"User","site_admin":false},{"login":"trwalke","id":30090357,"node_id":"MDQ6VXNlcjMwMDkwMzU3","avatar_url":"https://avatars1.githubusercontent.com/u/30090357?v=4","gravatar_id":"","url":"https://api.github.com/users/trwalke","html_url":"https://github.com/trwalke","followers_url":"https://api.github.com/users/trwalke/followers","following_url":"https://api.github.com/users/trwalke/following{/other_user}","gists_url":"https://api.github.com/users/trwalke/gists{/gist_id}","starred_url":"https://api.github.com/users/trwalke/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/trwalke/subscriptions","organizations_url":"https://api.github.com/users/trwalke/orgs","repos_url":"https://api.github.com/users/trwalke/repos","events_url":"https://api.github.com/users/trwalke/events{/privacy}","received_events_url":"https://api.github.com/users/trwalke/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/pulls/1433/commits","review_comments_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/pulls/1433/comments","review_comment_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/pulls/comments{/number}","comments_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/issues/1433/comments","statuses_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/statuses/24d1424ae0f2c871da7922435b632f78c0232a25","head":{"label":"AzureAD:markzuber/newcache","ref":"markzuber/newcache","sha":"24d1424ae0f2c871da7922435b632f78c0232a25","user":{"login":"AzureAD","id":2966316,"node_id":"MDEyOk9yZ2FuaXphdGlvbjI5NjYzMTY=","avatar_url":"https://avatars1.githubusercontent.com/u/2966316?v=4","gravatar_id":"","url":"https://api.github.com/users/AzureAD","html_url":"https://github.com/AzureAD","followers_url":"https://api.github.com/users/AzureAD/followers","following_url":"https://api.github.com/users/AzureAD/following{/other_user}","gists_url":"https://api.github.com/users/AzureAD/gists{/gist_id}","starred_url":"https://api.github.com/users/AzureAD/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AzureAD/subscriptions","organizations_url":"https://api.github.com/users/AzureAD/orgs","repos_url":"https://api.github.com/users/AzureAD/repos","events_url":"https://api.github.com/users/AzureAD/events{/privacy}","received_events_url":"https://api.github.com/users/AzureAD/received_events","type":"Organization","site_admin":false},"repo":{"id":19123031,"node_id":"MDEwOlJlcG9zaXRvcnkxOTEyMzAzMQ==","name":"azure-activedirectory-library-for-dotnet","full_name":"AzureAD/azure-activedirectory-library-for-dotnet","private":false,"owner":{"login":"AzureAD","id":2966316,"node_id":"MDEyOk9yZ2FuaXphdGlvbjI5NjYzMTY=","avatar_url":"https://avatars1.githubusercontent.com/u/2966316?v=4","gravatar_id":"","url":"https://api.github.com/users/AzureAD","html_url":"https://github.com/AzureAD","followers_url":"https://api.github.com/users/AzureAD/followers","following_url":"https://api.github.com/users/AzureAD/following{/other_user}","gists_url":"https://api.github.com/users/AzureAD/gists{/gist_id}","starred_url":"https://api.github.com/users/AzureAD/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AzureAD/subscriptions","organizations_url":"https://api.github.com/users/AzureAD/orgs","repos_url":"https://api.github.com/users/AzureAD/repos","events_url":"https://api.github.com/users/AzureAD/events{/privacy}","received_events_url":"https://api.github.com/users/AzureAD/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/AzureAD/azure-activedirectory-library-for-dotnet","description":"ADAL and MSAL authentication libraries for .net","fork":false,"url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet","forks_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/forks","keys_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/keys{/key_id}","collaborators_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/teams","hooks_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/hooks","issue_events_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/issues/events{/number}","events_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/events","assignees_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/assignees{/user}","branches_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/branches{/branch}","tags_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/tags","blobs_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/git/refs{/sha}","trees_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/git/trees{/sha}","statuses_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/statuses/{sha}","languages_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/languages","stargazers_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/stargazers","contributors_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/contributors","subscribers_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/subscribers","subscription_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/subscription","commits_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/commits{/sha}","git_commits_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/git/commits{/sha}","comments_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/comments{/number}","issue_comment_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/issues/comments{/number}","contents_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/contents/{+path}","compare_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/compare/{base}...{head}","merges_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/merges","archive_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/downloads","issues_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/issues{/number}","pulls_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/pulls{/number}","milestones_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/milestones{/number}","notifications_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/labels{/name}","releases_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/releases{/id}","deployments_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/deployments","created_at":"2014-04-24T20:29:51Z","updated_at":"2018-11-29T00:44:32Z","pushed_at":"2018-11-29T00:44:33Z","git_url":"git://github.com/AzureAD/azure-activedirectory-library-for-dotnet.git","ssh_url":"git@github.com:AzureAD/azure-activedirectory-library-for-dotnet.git","clone_url":"https://github.com/AzureAD/azure-activedirectory-library-for-dotnet.git","svn_url":"https://github.com/AzureAD/azure-activedirectory-library-for-dotnet","homepage":"http://aka.ms/aaddev","size":15359,"stargazers_count":242,"watchers_count":242,"language":"C#","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":180,"mirror_url":null,"archived":false,"open_issues_count":49,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":180,"open_issues":49,"watchers":242,"default_branch":"dev"}},"base":{"label":"AzureAD:dev","ref":"dev","sha":"93a2a16c2dbda2d7de9fb50d183bb5fd85ee1383","user":{"login":"AzureAD","id":2966316,"node_id":"MDEyOk9yZ2FuaXphdGlvbjI5NjYzMTY=","avatar_url":"https://avatars1.githubusercontent.com/u/2966316?v=4","gravatar_id":"","url":"https://api.github.com/users/AzureAD","html_url":"https://github.com/AzureAD","followers_url":"https://api.github.com/users/AzureAD/followers","following_url":"https://api.github.com/users/AzureAD/following{/other_user}","gists_url":"https://api.github.com/users/AzureAD/gists{/gist_id}","starred_url":"https://api.github.com/users/AzureAD/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AzureAD/subscriptions","organizations_url":"https://api.github.com/users/AzureAD/orgs","repos_url":"https://api.github.com/users/AzureAD/repos","events_url":"https://api.github.com/users/AzureAD/events{/privacy}","received_events_url":"https://api.github.com/users/AzureAD/received_events","type":"Organization","site_admin":false},"repo":{"id":19123031,"node_id":"MDEwOlJlcG9zaXRvcnkxOTEyMzAzMQ==","name":"azure-activedirectory-library-for-dotnet","full_name":"AzureAD/azure-activedirectory-library-for-dotnet","private":false,"owner":{"login":"AzureAD","id":2966316,"node_id":"MDEyOk9yZ2FuaXphdGlvbjI5NjYzMTY=","avatar_url":"https://avatars1.githubusercontent.com/u/2966316?v=4","gravatar_id":"","url":"https://api.github.com/users/AzureAD","html_url":"https://github.com/AzureAD","followers_url":"https://api.github.com/users/AzureAD/followers","following_url":"https://api.github.com/users/AzureAD/following{/other_user}","gists_url":"https://api.github.com/users/AzureAD/gists{/gist_id}","starred_url":"https://api.github.com/users/AzureAD/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AzureAD/subscriptions","organizations_url":"https://api.github.com/users/AzureAD/orgs","repos_url":"https://api.github.com/users/AzureAD/repos","events_url":"https://api.github.com/users/AzureAD/events{/privacy}","received_events_url":"https://api.github.com/users/AzureAD/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/AzureAD/azure-activedirectory-library-for-dotnet","description":"ADAL and MSAL authentication libraries for .net","fork":false,"url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet","forks_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/forks","keys_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/keys{/key_id}","collaborators_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/teams","hooks_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/hooks","issue_events_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/issues/events{/number}","events_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/events","assignees_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/assignees{/user}","branches_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/branches{/branch}","tags_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/tags","blobs_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/git/refs{/sha}","trees_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/git/trees{/sha}","statuses_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/statuses/{sha}","languages_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/languages","stargazers_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/stargazers","contributors_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/contributors","subscribers_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/subscribers","subscription_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/subscription","commits_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/commits{/sha}","git_commits_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/git/commits{/sha}","comments_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/comments{/number}","issue_comment_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/issues/comments{/number}","contents_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/contents/{+path}","compare_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/compare/{base}...{head}","merges_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/merges","archive_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/downloads","issues_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/issues{/number}","pulls_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/pulls{/number}","milestones_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/milestones{/number}","notifications_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/labels{/name}","releases_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/releases{/id}","deployments_url":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/deployments","created_at":"2014-04-24T20:29:51Z","updated_at":"2018-11-29T00:44:32Z","pushed_at":"2018-11-29T00:44:33Z","git_url":"git://github.com/AzureAD/azure-activedirectory-library-for-dotnet.git","ssh_url":"git@github.com:AzureAD/azure-activedirectory-library-for-dotnet.git","clone_url":"https://github.com/AzureAD/azure-activedirectory-library-for-dotnet.git","svn_url":"https://github.com/AzureAD/azure-activedirectory-library-for-dotnet","homepage":"http://aka.ms/aaddev","size":15359,"stargazers_count":242,"watchers_count":242,"language":"C#","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":180,"mirror_url":null,"archived":false,"open_issues_count":49,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":180,"open_issues":49,"watchers":242,"default_branch":"dev"}},"_links":{"self":{"href":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/pulls/1433"},"html":{"href":"https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/pull/1433"},"issue":{"href":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/issues/1433"},"comments":{"href":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/issues/1433/comments"},"review_comments":{"href":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/pulls/1433/comments"},"review_comment":{"href":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/pulls/1433/commits"},"statuses":{"href":"https://api.github.com/repos/AzureAD/azure-activedirectory-library-for-dotnet/statuses/24d1424ae0f2c871da7922435b632f78c0232a25"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"MarkZuber"}}