diff --git a/LibsAndSamples.sln b/LibsAndSamples.sln index 8b6d414107..3af5aaf11c 100644 --- a/LibsAndSamples.sln +++ b/LibsAndSamples.sln @@ -36,8 +36,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetCoreTestApp", "tests\dev EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DesktopTestApp", "tests\devapps\DesktopTestApp\DesktopTestApp.csproj", "{3A8EC886-C71D-4384-BFE4-73378CC7293D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleApp", "samples\desktop\SampleApp\SampleApp.csproj", "{85397CDA-120A-4626-9865-AD79EBFAC794}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Identity.Test.UIAutomation.Infrastructure", "tests\Microsoft.Identity.Test.Core.UIAutomation\Microsoft.Identity.Test.UIAutomation.Infrastructure.csproj", "{3DC6EC76-D350-4D43-B206-A4CEC8AA36D4}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Identity.Test.Android.UIAutomation", "tests\Microsoft.Identity.Test.Android.UIAutomation\Microsoft.Identity.Test.Android.UIAutomation.csproj", "{5FF7878D-FEF2-4CAE-8ABA-36C01806D9CE}" @@ -92,6 +90,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommonCache.Test.Unit", "te EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Identity.Test.Integration", "tests\Microsoft.Identity.Test.Integration\Microsoft.Identity.Test.Integration.csproj", "{C98AAC90-C9D4-4DAD-975F-A898A7F89B2D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetFxConsoleTestApp", "tests\devapps\NetFxConsoleTestApp\NetFxConsoleTestApp.csproj", "{24D2AF71-A01E-4450-8C3F-B9923A81134B}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommonCache.Test.AdalV5", "tests\CacheCompat\CommonCache.Test.AdalV5\CommonCache.Test.AdalV5.csproj", "{4DDB6B3A-F95A-4F34-B0AE-E3C909D68A11}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommonCache.Test.MsalJava", "tests\CacheCompat\CommonCache.Test.MsalJava\CommonCache.Test.MsalJava.csproj", "{B6BE589B-1DBB-4C36-803F-6D425723FA74}" @@ -137,10 +137,6 @@ Global {3A8EC886-C71D-4384-BFE4-73378CC7293D}.Debug|Any CPU.Build.0 = Debug|Any CPU {3A8EC886-C71D-4384-BFE4-73378CC7293D}.Release|Any CPU.ActiveCfg = Release|Any CPU {3A8EC886-C71D-4384-BFE4-73378CC7293D}.Release|Any CPU.Build.0 = Release|Any CPU - {85397CDA-120A-4626-9865-AD79EBFAC794}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {85397CDA-120A-4626-9865-AD79EBFAC794}.Debug|Any CPU.Build.0 = Debug|Any CPU - {85397CDA-120A-4626-9865-AD79EBFAC794}.Release|Any CPU.ActiveCfg = Release|Any CPU - {85397CDA-120A-4626-9865-AD79EBFAC794}.Release|Any CPU.Build.0 = Release|Any CPU {3DC6EC76-D350-4D43-B206-A4CEC8AA36D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3DC6EC76-D350-4D43-B206-A4CEC8AA36D4}.Debug|Any CPU.Build.0 = Debug|Any CPU {3DC6EC76-D350-4D43-B206-A4CEC8AA36D4}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -209,6 +205,10 @@ Global {C98AAC90-C9D4-4DAD-975F-A898A7F89B2D}.Debug|Any CPU.Build.0 = Debug|Any CPU {C98AAC90-C9D4-4DAD-975F-A898A7F89B2D}.Release|Any CPU.ActiveCfg = Release|Any CPU {C98AAC90-C9D4-4DAD-975F-A898A7F89B2D}.Release|Any CPU.Build.0 = Release|Any CPU + {24D2AF71-A01E-4450-8C3F-B9923A81134B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {24D2AF71-A01E-4450-8C3F-B9923A81134B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24D2AF71-A01E-4450-8C3F-B9923A81134B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {24D2AF71-A01E-4450-8C3F-B9923A81134B}.Release|Any CPU.Build.0 = Release|Any CPU {4DDB6B3A-F95A-4F34-B0AE-E3C909D68A11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4DDB6B3A-F95A-4F34-B0AE-E3C909D68A11}.Debug|Any CPU.Build.0 = Debug|Any CPU {4DDB6B3A-F95A-4F34-B0AE-E3C909D68A11}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -240,7 +240,6 @@ Global {0B22271B-163F-429B-A472-4C7BF531CEE9} = {3761DEF3-458C-4CE2-BE94-4FC9D12217CC} {72F0ADCA-3A0B-44AA-8129-21A1B27CD841} = {34BE693E-3496-45A4-B1D2-D3A0E068EEDB} {3A8EC886-C71D-4384-BFE4-73378CC7293D} = {34BE693E-3496-45A4-B1D2-D3A0E068EEDB} - {85397CDA-120A-4626-9865-AD79EBFAC794} = {34BE693E-3496-45A4-B1D2-D3A0E068EEDB} {3DC6EC76-D350-4D43-B206-A4CEC8AA36D4} = {9B0B5396-4D95-4C15-82ED-DC22B5A3123F} {5FF7878D-FEF2-4CAE-8ABA-36C01806D9CE} = {9B0B5396-4D95-4C15-82ED-DC22B5A3123F} {A181778D-5917-41CE-AA5F-7DAAA7B7F5BB} = {9B0B5396-4D95-4C15-82ED-DC22B5A3123F} @@ -262,6 +261,7 @@ Global {B6BE589B-1DBB-4C36-803F-6D425723FA74} = {58B95AA7-BE17-40F2-9C9A-FA27C282A10A} {CA3F8F2E-7DF2-45A6-BC81-5DA5692FCC11} = {58B95AA7-BE17-40F2-9C9A-FA27C282A10A} {2EAA7878-3E2A-4355-9841-430BF45EA8BD} = {58B95AA7-BE17-40F2-9C9A-FA27C282A10A} + {24D2AF71-A01E-4450-8C3F-B9923A81134B} = {34BE693E-3496-45A4-B1D2-D3A0E068EEDB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {020399A9-DC27-4B82-9CAA-EF488665AC27} diff --git a/samples/desktop/SampleApp/App.config b/samples/desktop/SampleApp/App.config deleted file mode 100644 index 3dbff35f48..0000000000 --- a/samples/desktop/SampleApp/App.config +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/samples/desktop/SampleApp/CachePersistence.cs b/samples/desktop/SampleApp/CachePersistence.cs deleted file mode 100644 index 94ec3bf8df..0000000000 --- a/samples/desktop/SampleApp/CachePersistence.cs +++ /dev/null @@ -1,79 +0,0 @@ -//------------------------------------------------------------------------------ -// -// Copyright (c) Microsoft Corporation. -// All rights reserved. -// -// This code is licensed under the MIT License. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files(the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions : -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// -//------------------------------------------------------------------------------ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.Identity.Client; - -namespace SampleApp -{ - class CachePersistence - { - private static readonly TokenCache userTokenCache = new TokenCache(); - - public static TokenCache GetUserCache() - { - lock (FileLock) - { - userTokenCache.SetBeforeAccess(BeforeAccessNotification); - userTokenCache.SetAfterAccess(AfterAccessNotification); - return userTokenCache; - } - } - - public static string CacheFilePath = System.Reflection.Assembly.GetExecutingAssembly().Location + "msalcache.txt"; - - private static readonly object FileLock = new object(); - - public static void BeforeAccessNotification(TokenCacheNotificationArgs args) - { - lock (FileLock) - { - args.TokenCache.DeserializeMsalV3(File.Exists(CacheFilePath) - ? File.ReadAllBytes(CacheFilePath) - : null); - } - } - - public static void AfterAccessNotification(TokenCacheNotificationArgs args) - { - // if the access operation resulted in a cache update - if (args.HasStateChanged) - { - lock (FileLock) - { - // reflect changesgs in the persistent store - File.WriteAllBytes(CacheFilePath, args.TokenCache.SerializeMsalV3()); - } - } - } - } -} diff --git a/samples/desktop/SampleApp/MainForm.Designer.cs b/samples/desktop/SampleApp/MainForm.Designer.cs deleted file mode 100644 index 026114effc..0000000000 --- a/samples/desktop/SampleApp/MainForm.Designer.cs +++ /dev/null @@ -1,168 +0,0 @@ -namespace SampleApp -{ - partial class MainForm - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); - this.tabControl1 = new System.Windows.Forms.TabControl(); - this.signInPage = new System.Windows.Forms.TabPage(); - this.acquireTokenUsernamePasswordButton = new System.Windows.Forms.Button(); - this.signOutButton1 = new System.Windows.Forms.Button(); - this.tokenResultBox = new System.Windows.Forms.TextBox(); - this.pictureBox1 = new System.Windows.Forms.PictureBox(); - this.calendarPage = new System.Windows.Forms.TabPage(); - this.calendarTextBox = new System.Windows.Forms.TextBox(); - this.acquireTokenWIAButton = new System.Windows.Forms.Button(); - this.tabControl1.SuspendLayout(); - this.signInPage.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); - this.SuspendLayout(); - // - // tabControl1 - // - this.tabControl1.Controls.Add(this.signInPage); - this.tabControl1.Controls.Add(this.calendarPage); - this.tabControl1.Location = new System.Drawing.Point(-1, -1); - this.tabControl1.Margin = new System.Windows.Forms.Padding(2); - this.tabControl1.Name = "tabControl1"; - this.tabControl1.SelectedIndex = 0; - this.tabControl1.Size = new System.Drawing.Size(542, 383); - this.tabControl1.TabIndex = 0; - // - // signInPage - // - this.signInPage.Controls.Add(this.acquireTokenWIAButton); - this.signInPage.Controls.Add(this.acquireTokenUsernamePasswordButton); - this.signInPage.Controls.Add(this.signOutButton1); - this.signInPage.Controls.Add(this.tokenResultBox); - this.signInPage.Controls.Add(this.pictureBox1); - this.signInPage.Location = new System.Drawing.Point(4, 22); - this.signInPage.Margin = new System.Windows.Forms.Padding(2); - this.signInPage.Name = "signInPage"; - this.signInPage.Padding = new System.Windows.Forms.Padding(2); - this.signInPage.Size = new System.Drawing.Size(534, 357); - this.signInPage.TabIndex = 0; - this.signInPage.Text = "signInPage"; - this.signInPage.UseVisualStyleBackColor = true; - // - // acquireTokenUsernamePasswordButton - // - this.acquireTokenUsernamePasswordButton.Location = new System.Drawing.Point(21, 83); - this.acquireTokenUsernamePasswordButton.Name = "acquireTokenUsernamePasswordButton"; - this.acquireTokenUsernamePasswordButton.Size = new System.Drawing.Size(227, 40); - this.acquireTokenUsernamePasswordButton.TabIndex = 3; - this.acquireTokenUsernamePasswordButton.Text = "Sign in with username/password"; - this.acquireTokenUsernamePasswordButton.UseVisualStyleBackColor = true; - this.acquireTokenUsernamePasswordButton.Click += new System.EventHandler(this.acquireTokenUsernamePasswordButton_Click); - // - // signOutButton1 - // - this.signOutButton1.Location = new System.Drawing.Point(426, 319); - this.signOutButton1.Name = "signOutButton1"; - this.signOutButton1.Size = new System.Drawing.Size(79, 25); - this.signOutButton1.TabIndex = 2; - this.signOutButton1.Text = "Sign Out"; - this.signOutButton1.UseVisualStyleBackColor = true; - this.signOutButton1.Click += new System.EventHandler(this.signOutButton1_Click); - // - // tokenResultBox - // - this.tokenResultBox.Location = new System.Drawing.Point(19, 213); - this.tokenResultBox.Multiline = true; - this.tokenResultBox.Name = "tokenResultBox"; - this.tokenResultBox.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; - this.tokenResultBox.Size = new System.Drawing.Size(366, 96); - this.tokenResultBox.TabIndex = 1; - // - // pictureBox1 - // - this.pictureBox1.Image = ((System.Drawing.Image)(resources.GetObject("pictureBox1.Image"))); - this.pictureBox1.Location = new System.Drawing.Point(19, 27); - this.pictureBox1.Margin = new System.Windows.Forms.Padding(2); - this.pictureBox1.Name = "pictureBox1"; - this.pictureBox1.Size = new System.Drawing.Size(230, 40); - this.pictureBox1.SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize; - this.pictureBox1.TabIndex = 0; - this.pictureBox1.TabStop = false; - this.pictureBox1.Click += new System.EventHandler(this.pictureBox1_Click); - // - // calendarPage - // - this.calendarPage.Location = new System.Drawing.Point(4, 22); - this.calendarPage.Name = "calendarPage"; - this.calendarPage.Size = new System.Drawing.Size(534, 357); - this.calendarPage.TabIndex = 1; - // - // calendarTextBox - // - this.calendarTextBox.Location = new System.Drawing.Point(0, 0); - this.calendarTextBox.Name = "calendarTextBox"; - this.calendarTextBox.Size = new System.Drawing.Size(100, 20); - this.calendarTextBox.TabIndex = 0; - // - // acquireTokenWIAButton - // - this.acquireTokenWIAButton.Location = new System.Drawing.Point(21, 139); - this.acquireTokenWIAButton.Name = "acquireTokenWIAButton"; - this.acquireTokenWIAButton.Size = new System.Drawing.Size(227, 41); - this.acquireTokenWIAButton.TabIndex = 4; - this.acquireTokenWIAButton.Text = "Acquire token with Integrated Windows Auth"; - this.acquireTokenWIAButton.UseVisualStyleBackColor = true; - this.acquireTokenWIAButton.Click += new System.EventHandler(this.acquireTokenWIAButton_Click); - // - // MainForm - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(539, 378); - this.Controls.Add(this.tabControl1); - this.Margin = new System.Windows.Forms.Padding(2); - this.Name = "MainForm"; - this.Text = "MainForm"; - this.tabControl1.ResumeLayout(false); - this.signInPage.ResumeLayout(false); - this.signInPage.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); - this.ResumeLayout(false); - - } - - #endregion - - private System.Windows.Forms.TabControl tabControl1; - private System.Windows.Forms.TabPage signInPage; - private System.Windows.Forms.TabPage calendarPage; - private System.Windows.Forms.PictureBox pictureBox1; - private System.Windows.Forms.TextBox tokenResultBox; - private System.Windows.Forms.TextBox calendarTextBox; - private System.Windows.Forms.Button signOutButton1; - private System.Windows.Forms.Button acquireTokenUsernamePasswordButton; - private System.Windows.Forms.Button acquireTokenWIAButton; - } -} - diff --git a/samples/desktop/SampleApp/MainForm.cs b/samples/desktop/SampleApp/MainForm.cs deleted file mode 100644 index 14cf108e2f..0000000000 --- a/samples/desktop/SampleApp/MainForm.cs +++ /dev/null @@ -1,136 +0,0 @@ -//------------------------------------------------------------------------------ -// -// Copyright (c) Microsoft Corporation. -// All rights reserved. -// -// This code is licensed under the MIT License. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files(the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions : -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// -//------------------------------------------------------------------------------ - -using System; -using System.Drawing; -using System.Linq; -using System.Security; -using System.Threading.Tasks; -using System.Windows.Forms; -using Microsoft.Identity.Client; - -namespace SampleApp -{ - public partial class MainForm : Form - { - private readonly MsalAuthHelper _msalHelper = new MsalAuthHelper("1950a258-227b-4e31-a9cf-717495945fc2"); - private IAccount account = null; - private string token; - - public MainForm() - { - InitializeComponent(); - tabControl1.Appearance = TabAppearance.FlatButtons; - tabControl1.ItemSize = new Size(0, 1); - tabControl1.SizeMode = TabSizeMode.Fixed; - account = _msalHelper.Application.GetAccountsAsync().Result.FirstOrDefault(); - tabControl1.SelectedIndexChanged += TabControl1_SelectedIndexChanged; - - signInPage.BackColor = Color.FromArgb(255, 67, 143, 255); - if (account != null) - { - tabControl1.SelectedTab = calendarPage; - } - } - - private async void TabControl1_SelectedIndexChanged(object sender, EventArgs e) - { - if ((sender as TabControl).TabIndex == 1) - { - token = await _msalHelper.GetTokenForCurrentAccountAsync(new[] { "user.read" }, account) - .ConfigureAwait(true); - } - } - - private void pictureBox1_Click(object sender, EventArgs e) - { - AcquireTokenAsync().Wait(); - UpdateResponse(token); - } - - private async Task AcquireTokenAsync() - { - token = await _msalHelper.GetTokenForCurrentAccountAsync(new[] { "user.read" }, account).ConfigureAwait(true); - } - - private async void acquireTokenUsernamePasswordButton_Click(object sender, EventArgs e) - { - tokenResultBox.Text = string.Empty; - SecureString securePassword = ConvertToSecureString(tokenResultBox); - token = await _msalHelper.GetTokenWithUsernamePasswordAsync(new[] { "user.read" }, securePassword).ConfigureAwait(true); - } - - private SecureString ConvertToSecureString(TextBox textBox) - { - if (tokenResultBox.Text.Length > 0) - { - SecureString securePassword = new SecureString(); - tokenResultBox.Text.ToCharArray().ToList().ForEach(p => securePassword.AppendChar(p)); - securePassword.MakeReadOnly(); - return securePassword; - } - return null; - } - - private void UpdateResponse(string token) - { - if (token != null) - { - tokenResultBox.Text = "Result:\n" + token; - } - else - { - tokenResultBox.Text = "Authentication failed. No access token returned"; - } - } - - private async void signOutButton1_Click(object sender, EventArgs e) - { - var accounts = await _msalHelper.Application.GetAccountsAsync().ConfigureAwait(false); - - if (accounts.Any()) - { - try - { - await _msalHelper.Application.RemoveAsync(accounts.FirstOrDefault()).ConfigureAwait(true); - tokenResultBox.Text = "User has signed-out"; - } - catch (MsalException ex) - { - tokenResultBox.Text = $"Error signing-out user: {ex.Message}"; - } - } - - } - - private async void acquireTokenWIAButton_Click(object sender, EventArgs e) - { - string user = ""; - token = await _msalHelper.GetTokenWithIWAAsync(new[] { "user.read" }, user).ConfigureAwait(true); - } - } -} diff --git a/samples/desktop/SampleApp/MainForm.resx b/samples/desktop/SampleApp/MainForm.resx deleted file mode 100644 index 31c5d86faa..0000000000 --- a/samples/desktop/SampleApp/MainForm.resx +++ /dev/null @@ -1,151 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - - - iVBORw0KGgoAAAANSUhEUgAAAOYAAAAoCAIAAABl6hEoAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EwAACxMBAJqcGAAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAVdSURBVHhe7Zex - ThwxEIZ5ljxR3oUWKR0VEi01SkdLkxS0tNS0tEiRUtAmH3zLaGLvHXcR3J6P+YRO9tgej2f+9S5HZ0Ux - FCXZYjAmyf4pir1HrZZki2FQqyXZYhjUakm2GAa1WpIthkGtlmSLYVCrJdliGNRqSbYYBrVaki2GQa1u - JNnfX7/M/jH07efR7J8Li+IdUasl2WU4OTk5Pj5+eHiY+p+Du7u78/NzDs7xJ9M2qNXFJEv0l5eXRC+n - p6dYsD89PdHmSI+Pj858Rz7UeXB/f8+JqM3Un2Mryd7c3Lwk6bnSHGGyJtQBMJPubo65LQTjqYHGZN0G - tbqMZG9vbw09c319zdBBShYl0c0CtXjbShYUZQYn09h+S9ZT5LQQJ0Un1Kn/Fmp1GclasIuLC4LWwnlM - 90HSC/Q/JOuS/vKm6tgpPL/7nMOrqysi9GISHzbONfXfQq0uIFlkSqDAbTSZDp13kSxiVZfNKi9URhna - Z8n6HTikZMHUc8tO/X9pyonE46uXJbzsbHtDe8cwgfmWjeV8eLi2Jzvn69n5uMIzbcg5zTghNBFhxMvX - CWydK6HaMnqIMIwfWB7vnIaQrJNzhB4BSyPZfExhyDlAw7BjVXMFYrFGQCPcSsQMLJysLxLM/5/QdhcT - kmEoz5Qc7SxqdeFvWTLbpAOadEeiJbpZstTbVcGq82fnIdlmiz4kwMgQG+UuxOOBH6BhhWzHtEDnjNJW - 5UE4b9AJQeoZDU0Dr88J9hCfdv1HEnp9+M+uq+L4SraJSkKazajHBL/gGxhFtYad+XDJfgSkLD/HZlBy - uq0WFifk6zBLFqwWCdJtXBgN2bmShdCKzimh3QyeGYoKMVNX1tKC6ccKxUzIm4oWQvUe8pLLSzIhWdrK - y2wYUrbPSjaOGTlhmh5cBXYdosvyeBS1AN7IuW0/6uiiPKeZdjJgXYhN5zFBjea69Ilaj1rdSLJH33/N - /j2P/Tia/9sM0hHCzdcVXdOthvI5rRNkyVo2sfyRqYa+llh0BV7/q5JoqFaLOWyNBehaV35p95XIm4qW - OHIcSgU36NwzGqGny5uukWyvlaBRFfQJh+zcNtNiL/CJZdPIJER67R6IZMXDWHvI6TZBUVogKVggSzYn - vbdksvMmp9BbMnqmVFaIXx8PdGalFVxfibypNJY4VJ4TsCNDpMIua4ElZMwGxqwq54S3PodBswp6C2S1 - xWGBXXQ7mzfzAEZ4UJJVAeDZ+nTnDDaJ2KVkjZOCsYVzvPP4fbltp0eur0TeVBoLZ6HbzAkaycZrxF+N - TaKy/9mLU/r0zk7upxGwYQBJO/BbllORF84z9V/TFKHndKs/LM7Pj/juJQsMAerUP/EwvylzXwnaWHLJ - tRgGbCXZeMKBtsY1knV5HqVhPnstOpnlcSvHcrxZO18m4MFdTk5oY7EuzNF5FGKVZGH21D1qdRnJGmhD - JC6nm8l2A08OpmbHknV3JkRFKZU+Qz1WIjuxnOIxXRKlipzMFk/RhGRBNcS9DmskC45mVkkW4lLIOGe2 - djmZDURomaCXrA98MHv2jFpd5sOAuHMSyZEZlCbdNCKJvIzinItI1i8BiJtGPeUlOO8toVq1nsOAbSWr - JUttvWTBtAjJMf5ZyQKT9QAkP55PiO8BYChvQfay3HESeoVeshD5hEjpKtTqXnzLboXVyoIoPglqdd8l - y2OaH2UeSp/+VZdoccCo1Y0kuyDxxsyg2jdfIsXhoVb3XbKQv6uAbun1c6JWB5BsUYhaLckWw6BWS7LF - MKjVkmwxDGq1JFsMg1otyRbDoFZLssUwqNWSbDEManWSbFGMQkm2GIqzs7/MHPG5WEiSKQAAAABJRU5E - rkJggg== - - - \ No newline at end of file diff --git a/samples/desktop/SampleApp/MsalAuthHelper.cs b/samples/desktop/SampleApp/MsalAuthHelper.cs deleted file mode 100644 index fb6de3ef38..0000000000 --- a/samples/desktop/SampleApp/MsalAuthHelper.cs +++ /dev/null @@ -1,106 +0,0 @@ -//------------------------------------------------------------------------------ -// -// Copyright (c) Microsoft Corporation. -// All rights reserved. -// -// This code is licensed under the MIT License. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files(the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions : -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// -//------------------------------------------------------------------------------ - -using System; -using System.Collections.Generic; -using System.Security; -using System.Threading.Tasks; -using System.Windows.Forms; -using Microsoft.Identity.Client; - -namespace SampleApp -{ - class MsalAuthHelper - { - private readonly string _clientId; - private readonly string user = ""; //can be empty for IWA and U/P - - public PublicClientApplication Application { get; private set; } - - public MsalAuthHelper(string clientId) - { - _clientId = clientId; - Application = new PublicClientApplication(_clientId, "https://login.microsoftonline.com/organizations/", - CachePersistence.GetUserCache()); - } - - public async Task GetTokenForCurrentAccountAsync(IEnumerable scopes, IAccount account) - { - AuthenticationResult result = null; - try - { - result = await Application.AcquireTokenSilentAsync(scopes, account).ConfigureAwait(false); - return result.AccessToken; - } - catch (MsalUiRequiredException) - { - result = await Application.AcquireTokenAsync(scopes).ConfigureAwait(false); - return result.AccessToken; - } - catch (Exception ex) - { - MessageBox.Show(ex.Message, "Failed to get token", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - - return null; - } - - public async Task GetTokenWithUsernamePasswordAsync(IEnumerable scopes, SecureString password) - { - AuthenticationResult result = null; - - try - { - result = await Application.AcquireTokenByUsernamePasswordAsync(scopes, user, password).ConfigureAwait(false); - return result.AccessToken; - } - catch (Exception ex) - { - MessageBox.Show(ex.Message, "Failed to get token", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - - return null; - } - - internal async Task GetTokenWithIWAAsync(string[] scopes, string user) - { - AuthenticationResult result = null; - - try - { - result = await Application.AcquireTokenByIntegratedWindowsAuthAsync(scopes, user).ConfigureAwait(false); - return result.AccessToken; - } - catch (Exception ex) - { - MessageBox.Show(ex.Message, "Failed to get token", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - - return null; - } - } -} diff --git a/samples/desktop/SampleApp/Properties/AssemblyInfo.cs b/samples/desktop/SampleApp/Properties/AssemblyInfo.cs deleted file mode 100644 index ce47ab7460..0000000000 --- a/samples/desktop/SampleApp/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,63 +0,0 @@ -//------------------------------------------------------------------------------ -// -// Copyright (c) Microsoft Corporation. -// All rights reserved. -// -// This code is licensed under the MIT License. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files(the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions : -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// -//------------------------------------------------------------------------------ - -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("SampleApp")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("SampleApp")] -[assembly: AssemblyCopyright("Copyright © 2017")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("85397cda-120a-4626-9865-ad79ebfac794")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/samples/desktop/SampleApp/Properties/Resources.Designer.cs b/samples/desktop/SampleApp/Properties/Resources.Designer.cs deleted file mode 100644 index cee8fb430d..0000000000 --- a/samples/desktop/SampleApp/Properties/Resources.Designer.cs +++ /dev/null @@ -1,63 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace SampleApp.Properties { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SampleApp.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - } -} diff --git a/samples/desktop/SampleApp/Properties/Resources.resx b/samples/desktop/SampleApp/Properties/Resources.resx deleted file mode 100644 index af7dbebbac..0000000000 --- a/samples/desktop/SampleApp/Properties/Resources.resx +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/samples/desktop/SampleApp/Properties/Settings.Designer.cs b/samples/desktop/SampleApp/Properties/Settings.Designer.cs deleted file mode 100644 index 9d3c33171c..0000000000 --- a/samples/desktop/SampleApp/Properties/Settings.Designer.cs +++ /dev/null @@ -1,26 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace SampleApp.Properties { - - - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.0.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { - - private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default { - get { - return defaultInstance; - } - } - } -} diff --git a/samples/desktop/SampleApp/Properties/Settings.settings b/samples/desktop/SampleApp/Properties/Settings.settings deleted file mode 100644 index 39645652af..0000000000 --- a/samples/desktop/SampleApp/Properties/Settings.settings +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/samples/desktop/SampleApp/no_photo.png b/samples/desktop/SampleApp/no_photo.png deleted file mode 100644 index d1cc67cc29..0000000000 Binary files a/samples/desktop/SampleApp/no_photo.png and /dev/null differ diff --git a/samples/desktop/SampleApp/sign-in-with-microsoft-light.png b/samples/desktop/SampleApp/sign-in-with-microsoft-light.png deleted file mode 100644 index 7fd7bab862..0000000000 Binary files a/samples/desktop/SampleApp/sign-in-with-microsoft-light.png and /dev/null differ diff --git a/src/Microsoft.Identity.Client/Cache/CacheFallbackOperations.cs b/src/Microsoft.Identity.Client/Cache/CacheFallbackOperations.cs index 5358f16d90..c930abd3cf 100644 --- a/src/Microsoft.Identity.Client/Cache/CacheFallbackOperations.cs +++ b/src/Microsoft.Identity.Client/Cache/CacheFallbackOperations.cs @@ -57,7 +57,13 @@ public static void WriteAdalRefreshToken( { if (rtItem == null) { - logger.Info("No refresh token available. Skipping MSAL refresh token cache write"); + logger.Info("No refresh token available. Skipping writing to ADAL legacy cache."); + return; + } + + if (!string.IsNullOrEmpty(rtItem.FamilyId)) + { + logger.Info("Not writing FRT in ADAL legacy cache"); return; } @@ -82,7 +88,10 @@ public static void WriteAdalRefreshToken( ResourceInResponse = scope }; - IDictionary dictionary = AdalCacheOperations.Deserialize(logger, legacyCachePersistence.LoadCache()); + IDictionary dictionary = AdalCacheOperations.Deserialize( + logger, + legacyCachePersistence.LoadCache()); + dictionary[key] = wrapper; legacyCachePersistence.WriteCache(AdalCacheOperations.Serialize(logger, dictionary)); } diff --git a/src/Microsoft.Identity.Client/Cache/CacheSessionManager.cs b/src/Microsoft.Identity.Client/Cache/CacheSessionManager.cs index 48706212d8..d013416a59 100644 --- a/src/Microsoft.Identity.Client/Cache/CacheSessionManager.cs +++ b/src/Microsoft.Identity.Client/Cache/CacheSessionManager.cs @@ -52,9 +52,9 @@ public Task FindAccessTokenAsync() return TokenCacheInternal.FindAccessTokenAsync(_requestParams); } - public Tuple SaveAccessAndRefreshToken(MsalTokenResponse tokenResponse) + public Tuple SaveTokenResponse(MsalTokenResponse tokenResponse) { - return TokenCacheInternal.SaveAccessAndRefreshToken(_requestParams, tokenResponse); + return TokenCacheInternal.SaveTokenResponse(_requestParams, tokenResponse); } public MsalIdTokenCacheItem GetIdTokenCacheItem(MsalIdTokenCacheKey idTokenCacheKey) @@ -62,9 +62,24 @@ public MsalIdTokenCacheItem GetIdTokenCacheItem(MsalIdTokenCacheKey idTokenCache return TokenCacheInternal.GetIdTokenCacheItem(idTokenCacheKey, _requestParams.RequestContext); } + public Task FindFamilyRefreshTokenAsync(string familyId) + { + if (String.IsNullOrEmpty(familyId)) + { + throw new ArgumentNullException(nameof(familyId)); + } + + return TokenCacheInternal.FindRefreshTokenAsync(_requestParams, familyId); + } + public Task FindRefreshTokenAsync() { return TokenCacheInternal.FindRefreshTokenAsync(_requestParams); } + + public Task IsAppFociMemberAsync(string familyId) + { + return TokenCacheInternal.IsFociMemberAsync(_requestParams, familyId); + } } -} \ No newline at end of file +} diff --git a/src/Microsoft.Identity.Client/Cache/ICacheSessionManager.cs b/src/Microsoft.Identity.Client/Cache/ICacheSessionManager.cs index 5056846761..816b75dfde 100644 --- a/src/Microsoft.Identity.Client/Cache/ICacheSessionManager.cs +++ b/src/Microsoft.Identity.Client/Cache/ICacheSessionManager.cs @@ -38,8 +38,10 @@ internal interface ICacheSessionManager ITokenCacheInternal TokenCacheInternal { get; } bool HasCache { get; } Task FindAccessTokenAsync(); - Tuple SaveAccessAndRefreshToken(MsalTokenResponse tokenResponse); + Tuple SaveTokenResponse(MsalTokenResponse tokenResponse); MsalIdTokenCacheItem GetIdTokenCacheItem(MsalIdTokenCacheKey idTokenCacheKey); Task FindRefreshTokenAsync(); + Task FindFamilyRefreshTokenAsync(string familyId); + Task IsAppFociMemberAsync(string familyId); } -} \ No newline at end of file +} diff --git a/src/Microsoft.Identity.Client/Cache/ITokenCacheAccessor.cs b/src/Microsoft.Identity.Client/Cache/ITokenCacheAccessor.cs index bc8695eab9..140ab0498a 100644 --- a/src/Microsoft.Identity.Client/Cache/ITokenCacheAccessor.cs +++ b/src/Microsoft.Identity.Client/Cache/ITokenCacheAccessor.cs @@ -33,13 +33,6 @@ namespace Microsoft.Identity.Client.Cache { internal interface ITokenCacheAccessor { - int RefreshTokenCount { get; } - int AccessTokenCount { get; } - int AccountCount { get; } - int IdTokenCount { get; } - void ClearRefreshTokens(); - void ClearAccessTokens(); - void SaveAccessToken(MsalAccessTokenCacheItem item); void SaveRefreshToken(MsalRefreshTokenCacheItem item); @@ -48,6 +41,8 @@ internal interface ITokenCacheAccessor void SaveAccount(MsalAccountCacheItem item); + void SaveAppMetadata(MsalAppMetadataCacheItem item); + MsalAccessTokenCacheItem GetAccessToken(MsalAccessTokenCacheKey accessTokenKey); MsalRefreshTokenCacheItem GetRefreshToken(MsalRefreshTokenCacheKey refreshTokenKey); @@ -56,6 +51,8 @@ internal interface ITokenCacheAccessor MsalAccountCacheItem GetAccount(MsalAccountCacheKey accountKey); + MsalAppMetadataCacheItem GetAppMetadata(MsalAppMetadataCacheKey appMetadataKey); + void DeleteAccessToken(MsalAccessTokenCacheKey cacheKey); void DeleteRefreshToken(MsalRefreshTokenCacheKey cacheKey); @@ -64,13 +61,16 @@ internal interface ITokenCacheAccessor void DeleteAccount(MsalAccountCacheKey cacheKey); - ICollection GetAllAccessTokens(); + IEnumerable GetAllAccessTokens(); + + IEnumerable GetAllRefreshTokens(); - ICollection GetAllRefreshTokens(); + IEnumerable GetAllIdTokens(); - ICollection GetAllIdTokens(); + IEnumerable GetAllAccounts(); + + IEnumerable GetAllAppMetadata(); - ICollection GetAllAccounts(); #if iOS void SetiOSKeychainSecurityGroup(string keychainSecurityGroup); diff --git a/src/Microsoft.Identity.Client/Cache/Items/CacheSerializationContract.cs b/src/Microsoft.Identity.Client/Cache/Items/CacheSerializationContract.cs index cd2af0eaa8..4c9c4cc819 100644 --- a/src/Microsoft.Identity.Client/Cache/Items/CacheSerializationContract.cs +++ b/src/Microsoft.Identity.Client/Cache/Items/CacheSerializationContract.cs @@ -25,10 +25,7 @@ // // ------------------------------------------------------------------------------ -using System; using System.Collections.Generic; -using System.Diagnostics; -using Microsoft.Identity.Json; using Microsoft.Identity.Json.Linq; namespace Microsoft.Identity.Client.Cache.Items @@ -41,9 +38,14 @@ internal class CacheSerializationContract public Dictionary RefreshTokens { get; set; } = new Dictionary(); - public Dictionary IdTokens { get; set; } = new Dictionary(); + public Dictionary IdTokens { get; set; } = + new Dictionary(); - public Dictionary Accounts { get; set; } = new Dictionary(); + public Dictionary Accounts { get; set; } = + new Dictionary(); + + public Dictionary AppMetadata { get; set; } = + new Dictionary(); internal static CacheSerializationContract FromJsonString(string json) { @@ -106,6 +108,20 @@ internal static CacheSerializationContract FromJsonString(string json) } } + // App Metadata + if (root.ContainsKey(StorageJsonValues.AppMetadata)) + { + foreach (var token in root[StorageJsonValues.AppMetadata] + .Values()) + { + if (token is JObject j) + { + var item = MsalAppMetadataCacheItem.FromJObject(j); + contract.AppMetadata[item.GetKey().ToString()] = item; + } + } + } + return contract; } @@ -149,7 +165,17 @@ internal string ToJsonString() root[StorageJsonValues.AccountRootKey] = accountsRoot; + // App Metadata + var appMetadataRoot = new JObject(); + foreach (var kvp in AppMetadata) + { + appMetadataRoot[kvp.Key] = kvp.Value.ToJObject(); + } + + root[StorageJsonValues.AppMetadata] = appMetadataRoot; + + return root.ToString(); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.Identity.Client/Cache/Items/MsalAccountCacheItem.cs b/src/Microsoft.Identity.Client/Cache/Items/MsalAccountCacheItem.cs index e14bb60d72..ac739618c8 100644 --- a/src/Microsoft.Identity.Client/Cache/Items/MsalAccountCacheItem.cs +++ b/src/Microsoft.Identity.Client/Cache/Items/MsalAccountCacheItem.cs @@ -34,7 +34,6 @@ namespace Microsoft.Identity.Client.Cache.Items { - [DataContract] internal class MsalAccountCacheItem : MsalCacheItemBase { internal MsalAccountCacheItem() @@ -42,22 +41,6 @@ internal MsalAccountCacheItem() AuthorityType = Cache.AuthorityType.MSSTS.ToString(); } - internal MsalAccountCacheItem(string environment, MsalTokenResponse response) - : this() - { - var idToken = IdToken.Parse(response.IdToken); - - Init( - environment, - idToken?.ObjectId, - response.ClientInfo, - idToken.Name, - idToken.PreferredUsername, - idToken.TenantId, - idToken.GivenName, - idToken.FamilyName); - } - internal MsalAccountCacheItem( string environment, MsalTokenResponse response, @@ -78,7 +61,7 @@ internal MsalAccountCacheItem( idToken.FamilyName); } - internal MsalAccountCacheItem( + internal /* for test */ MsalAccountCacheItem( string environment, string localAccountId, string rawClientInfo, @@ -101,7 +84,7 @@ internal MsalAccountCacheItem( } internal string TenantId { get; set; } - public string PreferredUsername { get; internal set; } + internal string PreferredUsername { get; set; } internal string Name { get; set; } internal string GivenName { get; set; } internal string FamilyName { get; set; } @@ -132,7 +115,7 @@ private void Init( internal MsalAccountCacheKey GetKey() { - return new MsalAccountCacheKey(Environment, TenantId, HomeAccountId, PreferredUsername); + return new MsalAccountCacheKey(Environment, TenantId, HomeAccountId, PreferredUsername, AuthorityType); } internal static MsalAccountCacheItem FromJsonString(string json) @@ -180,4 +163,4 @@ internal string ToJsonString() .ToString(); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.Identity.Client/Cache/Items/MsalAppMetadataCacheItem.cs b/src/Microsoft.Identity.Client/Cache/Items/MsalAppMetadataCacheItem.cs new file mode 100644 index 0000000000..db7b0e0c2d --- /dev/null +++ b/src/Microsoft.Identity.Client/Cache/Items/MsalAppMetadataCacheItem.cs @@ -0,0 +1,136 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using Microsoft.Identity.Client.Cache.Keys; +using Microsoft.Identity.Client.Utils; +using Microsoft.Identity.Json.Linq; + +namespace Microsoft.Identity.Client.Cache.Items +{ + /// + /// Apps shouldn't rely on its presence, unless the app itself wrote it. It means that SDK should translate absense of app metadata to the default values of its required fields. + /// Other apps that don't support app metadata should never remove existing app metadata. + /// App metadata is a non-removable entity.It means there's no need for a public API to remove app metadata, and it shouldn't be removed when removeAccount is called. + /// App metadata is a non-secret entity. It means that it cannot store any secret information, like tokens, nor PII, like username etc. + /// App metadata can be extended by adding additional fields when required.Absense of any non-required field should translate to default values for those field. + /// + internal class MsalAppMetadataCacheItem : MsalItemWithAdditionalFields, IEquatable + { + public MsalAppMetadataCacheItem(string clientId, string env, string familyId) + { + this.ClientId = clientId; + this.Environment = env; + this.FamilyId = familyId; + } + + /// mandatory + public string ClientId { get; } + + /// mandatory + + public string Environment { get; } + + /// + /// The family id of which this application is part of. This is an internal feature and there is currently a single app, + /// with id 1. If familyId is empty, it means an app is not part of a family. A missing entry means unkown status. + /// + public string FamilyId { get; } + + public MsalAppMetadataCacheKey GetKey() + { + return new MsalAppMetadataCacheKey(ClientId, Environment); + } + + internal static MsalAppMetadataCacheItem FromJsonString(string json) + { + return FromJObject(JObject.Parse(json)); + } + + internal static MsalAppMetadataCacheItem FromJObject(JObject j) + { + string clientId = JsonUtils.ExtractExistingOrEmptyString(j, StorageJsonKeys.ClientId); + string environment = JsonUtils.ExtractExistingOrEmptyString(j, StorageJsonKeys.Environment); + string familyId = JsonUtils.ExtractExistingOrEmptyString(j, StorageJsonKeys.FamilyId); + + var item = new MsalAppMetadataCacheItem(clientId, environment, familyId); + + item.PopulateFieldsFromJObject(j); + + return item; + } + + internal string ToJsonString() + { + return ToJObject() + .ToString(); + } + + internal override JObject ToJObject() + { + var json = base.ToJObject(); + + json[StorageJsonKeys.Environment] = Environment; + json[StorageJsonKeys.ClientId] = ClientId; + json[StorageJsonKeys.FamilyId] = FamilyId; + + return json; + } + + #region Equals and GetHashCode + + public override bool Equals(object obj) + { + return obj is MsalAppMetadataCacheItem item && + Equals(item); + + } + + public override int GetHashCode() + { + var hashCode = -1793347351; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ClientId); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Environment); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(FamilyId); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(AdditionalFieldsJson); + + return hashCode; + } + + public bool Equals(MsalAppMetadataCacheItem item) + { + return ClientId == item.ClientId && + Environment == item.Environment && + FamilyId == item.FamilyId && + base.AdditionalFieldsJson == item.AdditionalFieldsJson; + } + + #endregion + } +} diff --git a/src/Microsoft.Identity.Client/Cache/Items/MsalCacheItemBase.cs b/src/Microsoft.Identity.Client/Cache/Items/MsalCacheItemBase.cs index 4543afd2d9..e5478350a6 100644 --- a/src/Microsoft.Identity.Client/Cache/Items/MsalCacheItemBase.cs +++ b/src/Microsoft.Identity.Client/Cache/Items/MsalCacheItemBase.cs @@ -32,9 +32,8 @@ namespace Microsoft.Identity.Client.Cache.Items { - internal abstract class MsalCacheItemBase + internal abstract class MsalCacheItemBase : MsalItemWithAdditionalFields { - internal string AdditionalFieldsJson { get; set; } = "{}"; internal string HomeAccountId { get; set; } internal string Environment { get; set; } internal string RawClientInfo { get; set; } @@ -48,7 +47,7 @@ internal void InitUserIdentifier() } } - internal virtual void PopulateFieldsFromJObject(JObject j) + internal override void PopulateFieldsFromJObject(JObject j) { HomeAccountId = JsonUtils.ExtractExistingOrEmptyString(j, StorageJsonKeys.HomeAccountId); Environment = JsonUtils.ExtractExistingOrEmptyString(j, StorageJsonKeys.Environment); @@ -56,12 +55,12 @@ internal virtual void PopulateFieldsFromJObject(JObject j) // Important: order matters. This MUST be the last one called since it will extract the // remaining fields out. - AdditionalFieldsJson = j.ToString(); + base.PopulateFieldsFromJObject(j); } - internal virtual JObject ToJObject() + internal override JObject ToJObject() { - var json = string.IsNullOrWhiteSpace(AdditionalFieldsJson) ? new JObject() : JObject.Parse(AdditionalFieldsJson); + var json = base.ToJObject(); json[StorageJsonKeys.HomeAccountId] = HomeAccountId; json[StorageJsonKeys.Environment] = Environment; json[StorageJsonKeys.ClientInfo] = RawClientInfo; @@ -69,4 +68,4 @@ internal virtual JObject ToJObject() return json; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.Identity.Client/Cache/Items/MsalItemWithAdditionalFields.cs b/src/Microsoft.Identity.Client/Cache/Items/MsalItemWithAdditionalFields.cs new file mode 100644 index 0000000000..c11f916c33 --- /dev/null +++ b/src/Microsoft.Identity.Client/Cache/Items/MsalItemWithAdditionalFields.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Identity.Json.Linq; + +namespace Microsoft.Identity.Client.Cache.Items +{ + internal abstract class MsalItemWithAdditionalFields + { + internal string AdditionalFieldsJson { get; set; } = "{}"; + + /// + ///Important: order matters. This MUST be the last one called since it will extract the + /// remaining fields out. + /// + internal virtual void PopulateFieldsFromJObject(JObject j) + { + AdditionalFieldsJson = j.ToString(); + } + + + internal virtual JObject ToJObject() + { + var json = string.IsNullOrWhiteSpace(AdditionalFieldsJson) ? new JObject() : JObject.Parse(AdditionalFieldsJson); + + return json; + } + } +} diff --git a/src/Microsoft.Identity.Client/Cache/Items/MsalRefreshTokenCacheItem.cs b/src/Microsoft.Identity.Client/Cache/Items/MsalRefreshTokenCacheItem.cs index 714e51d811..8877848e27 100644 --- a/src/Microsoft.Identity.Client/Cache/Items/MsalRefreshTokenCacheItem.cs +++ b/src/Microsoft.Identity.Client/Cache/Items/MsalRefreshTokenCacheItem.cs @@ -28,6 +28,7 @@ using System.Runtime.Serialization; using Microsoft.Identity.Client.Cache.Keys; using Microsoft.Identity.Client.OAuth2; +using Microsoft.Identity.Client.Utils; using Microsoft.Identity.Json.Linq; namespace Microsoft.Identity.Client.Cache.Items @@ -39,8 +40,11 @@ internal MsalRefreshTokenCacheItem() CredentialType = StorageJsonValues.CredentialTypeRefreshToken; } - internal MsalRefreshTokenCacheItem(string environment, string clientId, MsalTokenResponse response) - : this(environment, clientId, response.RefreshToken, response.ClientInfo) + internal MsalRefreshTokenCacheItem( + string environment, + string clientId, + MsalTokenResponse response) + : this(environment, clientId, response.RefreshToken, response.ClientInfo, response.FamilyId) { } @@ -48,20 +52,27 @@ internal MsalRefreshTokenCacheItem( string environment, string clientId, string secret, - string rawClientInfo) + string rawClientInfo, + string familyId = null) : this() { ClientId = clientId; Environment = environment; Secret = secret; RawClientInfo = rawClientInfo; + FamilyId = familyId; InitUserIdentifier(); } + /// + /// Optional. A value here means the token in an FRT. + /// + public string FamilyId { get; set; } + internal MsalRefreshTokenCacheKey GetKey() { - return new MsalRefreshTokenCacheKey(Environment, ClientId, HomeAccountId); + return new MsalRefreshTokenCacheKey(Environment, ClientId, HomeAccountId, FamilyId); } internal static MsalRefreshTokenCacheItem FromJsonString(string json) @@ -72,6 +83,8 @@ internal static MsalRefreshTokenCacheItem FromJsonString(string json) internal static MsalRefreshTokenCacheItem FromJObject(JObject j) { var item = new MsalRefreshTokenCacheItem(); + item.FamilyId = JsonUtils.ExtractExistingOrEmptyString(j, StorageJsonKeys.FamilyId); + item.PopulateFieldsFromJObject(j); return item; @@ -80,6 +93,9 @@ internal static MsalRefreshTokenCacheItem FromJObject(JObject j) internal override JObject ToJObject() { var json = base.ToJObject(); + + json[StorageJsonKeys.FamilyId] = FamilyId; + return json; } @@ -88,4 +104,4 @@ internal string ToJsonString() return ToJObject().ToString(); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.Identity.Client/Cache/Keys/IiOSKey.cs b/src/Microsoft.Identity.Client/Cache/Keys/IiOSKey.cs new file mode 100644 index 0000000000..345de79b97 --- /dev/null +++ b/src/Microsoft.Identity.Client/Cache/Keys/IiOSKey.cs @@ -0,0 +1,13 @@ +namespace Microsoft.Identity.Client.Cache.Keys +{ + internal interface IiOSKey + { + string iOSAccount { get; } + + string iOSGeneric { get; } + + string iOSService { get; } + + int iOSType { get; } + } +} diff --git a/src/Microsoft.Identity.Client/Cache/Keys/MsalAccessTokenCacheKey.cs b/src/Microsoft.Identity.Client/Cache/Keys/MsalAccessTokenCacheKey.cs index 6d06bbf2c4..f9509a59d3 100644 --- a/src/Microsoft.Identity.Client/Cache/Keys/MsalAccessTokenCacheKey.cs +++ b/src/Microsoft.Identity.Client/Cache/Keys/MsalAccessTokenCacheKey.cs @@ -35,7 +35,7 @@ namespace Microsoft.Identity.Client.Cache.Keys /// format of the key is not important for this library, as long as it is unique. /// /// The format of the key is platform dependent - internal class MsalAccessTokenCacheKey + internal class MsalAccessTokenCacheKey : IiOSKey { private readonly string _clientId; private readonly string _environment; @@ -78,53 +78,16 @@ public override string ToString() _normalizedScopes); } - #region UWP - - /// - /// Gets a key that is smaller than 255 characters, which is a limitation for - /// UWP storage. This is done by hashing the scopes and env. - /// - /// - /// accountId - two guids plus separator - 73 chars - /// "accesstoken" string - 11 chars - /// env - a sha256 string - 44 chars - /// clientid - a guid - 36 chars - /// tenantid - a guid - 36 chars - /// scopes - a sha256 string - 44 chars - /// delimiters - 4 chars - /// total: 248 chars - /// - public string GetUWPFixedSizeKey(ICryptographyManager cryptographyManager) - { - return MsalCacheKeys.GetCredentialKey( - _homeAccountId, - cryptographyManager.CreateSha256Hash(_environment), - StorageJsonValues.CredentialTypeAccessToken, - _clientId, - _tenantId, - cryptographyManager.CreateSha256Hash( - _normalizedScopes)); // can't use scopes and env because they are of variable length - } - - #endregion - #region iOS - public string GetiOSAccountKey() - { - return MsalCacheKeys.GetiOSAccountKey(_homeAccountId, _environment); - } + public string iOSAccount => MsalCacheKeys.GetiOSAccountKey(_homeAccountId, _environment); - public string GetiOSServiceKey() - { - return MsalCacheKeys.GetiOSServiceKey(StorageJsonValues.CredentialTypeAccessToken, _clientId, _tenantId, _normalizedScopes); - } + public string iOSService => MsalCacheKeys.GetiOSServiceKey(StorageJsonValues.CredentialTypeAccessToken, _clientId, _tenantId, _normalizedScopes); - public string GetiOSGenericKey() - { - return MsalCacheKeys.GetiOSGenericKey(StorageJsonValues.CredentialTypeAccessToken, _clientId, _tenantId); - } + public string iOSGeneric => MsalCacheKeys.GetiOSGenericKey(StorageJsonValues.CredentialTypeAccessToken, _clientId, _tenantId); + + public int iOSType => (int)MsalCacheKeys.iOSCredentialAttrType.AccessToken; #endregion } -} \ No newline at end of file +} diff --git a/src/Microsoft.Identity.Client/Cache/Keys/MsalAccountCacheKey.cs b/src/Microsoft.Identity.Client/Cache/Keys/MsalAccountCacheKey.cs index 850da9132e..545d973e4b 100644 --- a/src/Microsoft.Identity.Client/Cache/Keys/MsalAccountCacheKey.cs +++ b/src/Microsoft.Identity.Client/Cache/Keys/MsalAccountCacheKey.cs @@ -34,14 +34,15 @@ namespace Microsoft.Identity.Client.Cache.Keys /// An object representing the key of the token cache Account dictionary. The /// format of the key is not important for this library, as long as it is unique. /// - internal class MsalAccountCacheKey + internal class MsalAccountCacheKey : IiOSKey { private readonly string _environment; private readonly string _homeAccountId; private readonly string _tenantId; private readonly string _username; + private readonly string _authorityType; - public MsalAccountCacheKey(string environment, string tenantId, string userIdentifier, string username) + public MsalAccountCacheKey(string environment, string tenantId, string userIdentifier, string username, string authorityType) { if (string.IsNullOrEmpty(environment)) { @@ -52,6 +53,7 @@ public MsalAccountCacheKey(string environment, string tenantId, string userIdent _environment = environment; _homeAccountId = userIdentifier; _username = username; + _authorityType = authorityType; } public override string ToString() @@ -67,27 +69,26 @@ public override string ToString() #region iOS - public string GetiOSAccountKey() + public string iOSAccount { - var stringBuilder = new StringBuilder(); + get + { + var stringBuilder = new StringBuilder(); - stringBuilder.Append(_homeAccountId ?? ""); - stringBuilder.Append(MsalCacheKeys.CacheKeyDelimiter); + stringBuilder.Append(_homeAccountId ?? ""); + stringBuilder.Append(MsalCacheKeys.CacheKeyDelimiter); - stringBuilder.Append(_environment); + stringBuilder.Append(_environment); - return stringBuilder.ToString().ToLowerInvariant(); + return stringBuilder.ToString().ToLowerInvariant(); + } } - public string GetiOSServiceKey() - { - return (_tenantId ?? "").ToLowerInvariant(); - } + public string iOSGeneric => _username.ToLowerInvariant(); - public string GetiOSGenericKey() - { - return _username.ToLowerInvariant(); - } + public string iOSService => (_tenantId ?? "").ToLowerInvariant(); + + public int iOSType => MsalCacheKeys.iOSAuthorityTypeToAttrType[_authorityType]; #endregion diff --git a/src/Microsoft.Identity.Client/Cache/Keys/MsalAppMetadataCacheKey.cs b/src/Microsoft.Identity.Client/Cache/Keys/MsalAppMetadataCacheKey.cs new file mode 100644 index 0000000000..03a0fafebe --- /dev/null +++ b/src/Microsoft.Identity.Client/Cache/Keys/MsalAppMetadataCacheKey.cs @@ -0,0 +1,68 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +using System; + +namespace Microsoft.Identity.Client.Cache.Keys +{ + /// + /// App metadata is an optional entity in cache and can be used by apps to store additional metadata applicable to a particular client. + /// + internal class MsalAppMetadataCacheKey : IiOSKey + { + private readonly string _clientId; + private readonly string _environment; + + public MsalAppMetadataCacheKey(string clientId, string environment) + { + _clientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); + _environment = environment ?? throw new ArgumentNullException(nameof(environment)); + } + + /// + /// Ex: appmetadata-login.microsoftonline.com-b6c69a37-df96-4db0-9088-2ab96e1d8215 + /// + /// + public override string ToString() + { + return ($"{StorageJsonKeys.AppMetadata}{MsalCacheKeys.CacheKeyDelimiter}" + + $"{_environment}{MsalCacheKeys.CacheKeyDelimiter}{_clientId}").ToLowerInvariant(); + } + + #region iOS + + public string iOSService => $"{StorageJsonValues.AppMetadata}{MsalCacheKeys.CacheKeyDelimiter}{_clientId}".ToLowerInvariant(); + + public string iOSGeneric => "1"; + + public string iOSAccount => $"{_environment}".ToLowerInvariant(); + + public int iOSType => (int)MsalCacheKeys.iOSCredentialAttrType.AppMetadata; + + #endregion + } +} diff --git a/src/Microsoft.Identity.Client/Cache/Keys/MsalCacheKeys.cs b/src/Microsoft.Identity.Client/Cache/Keys/MsalCacheKeys.cs index 87344faf12..686074e5b8 100644 --- a/src/Microsoft.Identity.Client/Cache/Keys/MsalCacheKeys.cs +++ b/src/Microsoft.Identity.Client/Cache/Keys/MsalCacheKeys.cs @@ -25,17 +25,15 @@ // //------------------------------------------------------------------------------ +using System.Collections.Generic; using System.Text; namespace Microsoft.Identity.Client.Cache.Keys { - internal class MsalCacheKeys + internal partial class MsalCacheKeys { public const string CacheKeyDelimiter = "-"; - //public const string IdToken = "IdToken"; - //public const string AccessToken = "AccessToken"; - //public const string RefreshToken = "RefreshToken"; public static string GetCredentialKey(string homeAccountId, string environment, string keyDescriptor, string clientId, string tenantId, string scopes) { @@ -106,5 +104,17 @@ public static string GetiOSGenericKey(string keyDescriptor, string clientId, str return stringBuilder.ToString().ToLowerInvariant(); } + +#region iOS + + internal static readonly Dictionary iOSAuthorityTypeToAttrType = new Dictionary() + { + {AuthorityType.AAD.ToString(), 1001}, + {AuthorityType.MSA.ToString(), 1002}, + {AuthorityType.MSSTS.ToString(), 1003}, + {AuthorityType.OTHER.ToString(), 1004}, + }; + + #endregion } } diff --git a/src/Microsoft.Identity.Client/Cache/Keys/MsalIdTokenCacheKey.cs b/src/Microsoft.Identity.Client/Cache/Keys/MsalIdTokenCacheKey.cs index 5d6ce9dcf9..878a908229 100644 --- a/src/Microsoft.Identity.Client/Cache/Keys/MsalIdTokenCacheKey.cs +++ b/src/Microsoft.Identity.Client/Cache/Keys/MsalIdTokenCacheKey.cs @@ -33,7 +33,7 @@ namespace Microsoft.Identity.Client.Cache.Keys /// An object representing the key of the token cache Id Token dictionary. The /// format of the key is not important for this library, as long as it is unique. /// - internal class MsalIdTokenCacheKey + internal class MsalIdTokenCacheKey : IiOSKey { private readonly string _environment; private readonly string _homeAccountId; @@ -77,20 +77,14 @@ public override string ToString() #region iOS - public string GetiOSAccountKey() - { - return MsalCacheKeys.GetiOSAccountKey(_homeAccountId, _environment); - } - public string GetiOSServiceKey() - { - return MsalCacheKeys.GetiOSServiceKey(StorageJsonValues.CredentialTypeIdToken, _clientId, _tenantId, scopes: null); - } + public string iOSAccount => MsalCacheKeys.GetiOSAccountKey(_homeAccountId, _environment); - public string GetiOSGenericKey() - { - return MsalCacheKeys.GetiOSGenericKey(StorageJsonValues.CredentialTypeIdToken, _clientId, _tenantId); - } + public string iOSGeneric => MsalCacheKeys.GetiOSGenericKey(StorageJsonValues.CredentialTypeIdToken, _clientId, _tenantId); + + public string iOSService => MsalCacheKeys.GetiOSServiceKey(StorageJsonValues.CredentialTypeIdToken, _clientId, _tenantId, scopes: null); + + public int iOSType => (int)MsalCacheKeys.iOSCredentialAttrType.IdToken; #endregion } diff --git a/src/Microsoft.Identity.Client/Cache/Keys/MsalRefreshTokenCacheKey.cs b/src/Microsoft.Identity.Client/Cache/Keys/MsalRefreshTokenCacheKey.cs index 3adc5f3bc9..8d27680721 100644 --- a/src/Microsoft.Identity.Client/Cache/Keys/MsalRefreshTokenCacheKey.cs +++ b/src/Microsoft.Identity.Client/Cache/Keys/MsalRefreshTokenCacheKey.cs @@ -33,13 +33,25 @@ namespace Microsoft.Identity.Client.Cache.Keys /// An object representing the key of the token cache RT dictionary. The /// format of the key is not important for this library, as long as it is unique. /// - internal class MsalRefreshTokenCacheKey + /// + /// Normal RTs are scoped by env, account_id and clientID + /// FRTs are scoped by env, account_id and familyID (clientID exists, but is irrelevant) + /// + internal class MsalRefreshTokenCacheKey : IiOSKey //TODO bogavril: add a base class with FRT key? { private readonly string _environment; private readonly string _homeAccountId; private readonly string _clientId; + private readonly string _familyId; - internal MsalRefreshTokenCacheKey(string environment, string clientId, string userIdentifier) + /// + /// Constructor + /// + /// + /// + /// + /// Can be null or empty, denoting a normal RT. A value signifies an FRT. + internal MsalRefreshTokenCacheKey(string environment, string clientId, string userIdentifier, string familyId) { if (string.IsNullOrEmpty(environment)) { @@ -54,36 +66,71 @@ internal MsalRefreshTokenCacheKey(string environment, string clientId, string us _environment = environment; _homeAccountId = userIdentifier; _clientId = clientId; + _familyId = familyId; } public override string ToString() { + // FRT + if (!String.IsNullOrWhiteSpace(_familyId)) + { + string d = MsalCacheKeys.CacheKeyDelimiter; + return $"{_homeAccountId}{d}{_environment}{d}{StorageJsonValues.CredentialTypeRefreshToken}{d}{_familyId}{d}{d}".ToLowerInvariant(); + + } + return MsalCacheKeys.GetCredentialKey( - _homeAccountId, - _environment, - StorageJsonValues.CredentialTypeRefreshToken, - _clientId, - tenantId: null, - scopes: null); + _homeAccountId, + _environment, + StorageJsonValues.CredentialTypeRefreshToken, + _clientId, + tenantId: null, + scopes: null); } #region iOS - public string GetiOSAccountKey() + public string iOSAccount { - return MsalCacheKeys.GetiOSAccountKey(_homeAccountId, _environment); + get + { + return MsalCacheKeys.GetiOSAccountKey(_homeAccountId, _environment); + } } - public string GetiOSServiceKey() + public string iOSGeneric { - return MsalCacheKeys.GetiOSServiceKey(StorageJsonValues.CredentialTypeRefreshToken, _clientId, tenantId: null, scopes: null); + get + { + // FRT + if (!String.IsNullOrWhiteSpace(_familyId)) + { + return $"{StorageJsonValues.CredentialTypeRefreshToken}{MsalCacheKeys.CacheKeyDelimiter}{_familyId}{MsalCacheKeys.CacheKeyDelimiter}".ToLowerInvariant(); + } + + return MsalCacheKeys.GetiOSGenericKey(StorageJsonValues.CredentialTypeRefreshToken, _clientId, tenantId: null); + + } } - public string GetiOSGenericKey() + public string iOSService { - return MsalCacheKeys.GetiOSGenericKey(StorageJsonValues.CredentialTypeRefreshToken, _clientId, tenantId: null); + get + { + // FRT + if (!String.IsNullOrWhiteSpace(_familyId)) + { + return $"{StorageJsonValues.CredentialTypeRefreshToken}{MsalCacheKeys.CacheKeyDelimiter}{_familyId}{MsalCacheKeys.CacheKeyDelimiter}{MsalCacheKeys.CacheKeyDelimiter}".ToLowerInvariant(); + } + + return MsalCacheKeys.GetiOSServiceKey(StorageJsonValues.CredentialTypeRefreshToken, _clientId, tenantId: null, scopes: null); + } } + public int iOSType => (int)MsalCacheKeys.iOSCredentialAttrType.RefreshToken; + + + #endregion } } diff --git a/src/Microsoft.Identity.Client/Cache/Keys/iOSCredentialAttrType.cs b/src/Microsoft.Identity.Client/Cache/Keys/iOSCredentialAttrType.cs new file mode 100644 index 0000000000..f6651a6521 --- /dev/null +++ b/src/Microsoft.Identity.Client/Cache/Keys/iOSCredentialAttrType.cs @@ -0,0 +1,45 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.Identity.Client.Cache.Keys +{ + internal partial class MsalCacheKeys + { + #region iOS + + internal enum iOSCredentialAttrType + { + AccessToken = 2001, + RefreshToken = 2002, + IdToken = 2003, + Password = 2004, + AppMetadata = 3001 + } + + #endregion + } +} diff --git a/src/Microsoft.Identity.Client/Cache/StorageJsonKeys.cs b/src/Microsoft.Identity.Client/Cache/StorageJsonKeys.cs index f586688fb4..1e86cbdec8 100644 --- a/src/Microsoft.Identity.Client/Cache/StorageJsonKeys.cs +++ b/src/Microsoft.Identity.Client/Cache/StorageJsonKeys.cs @@ -50,6 +50,8 @@ internal static class StorageJsonKeys public const string ExtendedExpiresOn = "extended_expires_on"; public const string ClientInfo = "client_info"; public const string FamilyId = "family_id"; + public const string AppMetadata = "appmetadata"; + // todo(cache): this needs to be added to the spec. needed for OBO flow on .NET. public const string UserAssertionHash = "user_assertion_hash"; @@ -58,4 +60,4 @@ internal static class StorageJsonKeys // this is here for back compat public const string ExtendedExpiresOn_MsalCompat = "ext_expires_on"; } -} \ No newline at end of file +} diff --git a/src/Microsoft.Identity.Client/Cache/StorageJsonValues.cs b/src/Microsoft.Identity.Client/Cache/StorageJsonValues.cs index 4c0e6db6e6..154e08b3b2 100644 --- a/src/Microsoft.Identity.Client/Cache/StorageJsonValues.cs +++ b/src/Microsoft.Identity.Client/Cache/StorageJsonValues.cs @@ -38,5 +38,7 @@ internal static class StorageJsonValues public const string CredentialTypeIdToken = "IdToken"; public const string AccountRootKey = "Account"; public const string CredentialTypeOther = "Other"; + + public const string AppMetadata = "AppMetadata"; } -} \ No newline at end of file +} diff --git a/src/Microsoft.Identity.Client/Cache/TokenCacheJsonSerializer.cs b/src/Microsoft.Identity.Client/Cache/TokenCacheJsonSerializer.cs index 54b3c55b65..540a381897 100644 --- a/src/Microsoft.Identity.Client/Cache/TokenCacheJsonSerializer.cs +++ b/src/Microsoft.Identity.Client/Cache/TokenCacheJsonSerializer.cs @@ -68,6 +68,12 @@ public byte[] Serialize() .ToString()] = accountItem; } + foreach (var appMetadata in _accessor.GetAllAppMetadata()) + { + cache.AppMetadata[appMetadata.GetKey() + .ToString()] = appMetadata; + } + return cache.ToJsonString() .ToByteArray(); } @@ -116,6 +122,14 @@ public void Deserialize(byte[] bytes) _accessor.SaveAccount(account); } } + + if (cache.AppMetadata != null) + { + foreach (var appMetadata in cache.AppMetadata.Values) + { + _accessor.SaveAppMetadata(appMetadata); + } + } } } -} \ No newline at end of file +} diff --git a/src/Microsoft.Identity.Client/ITokenCacheInternal.cs b/src/Microsoft.Identity.Client/ITokenCacheInternal.cs index f5870e1415..81ccfe4aa6 100644 --- a/src/Microsoft.Identity.Client/ITokenCacheInternal.cs +++ b/src/Microsoft.Identity.Client/ITokenCacheInternal.cs @@ -44,13 +44,23 @@ internal interface ITokenCacheInternal : ITokenCache void RemoveAccount(IAccount account, RequestContext requestContext); IEnumerable GetAccounts(string authority); - Tuple SaveAccessAndRefreshToken( + /// + /// Persists the AT and RT and updates app metadata (FOCI) + /// + /// + Tuple SaveTokenResponse( AuthenticationRequestParameters authenticationRequestParameters, MsalTokenResponse msalTokenResponse); Task FindAccessTokenAsync(AuthenticationRequestParameters authenticationRequestParameters); MsalIdTokenCacheItem GetIdTokenCacheItem(MsalIdTokenCacheKey getIdTokenItemKey, RequestContext requestContext); - Task FindRefreshTokenAsync(AuthenticationRequestParameters authenticationRequestParameters); + + /// + /// Returns a RT for the request. If familyId is specified, it tries to return the FRT. + /// + Task FindRefreshTokenAsync( + AuthenticationRequestParameters authenticationRequestParameters, + string familyId = null); void SetIosKeychainSecurityGroup(string securityGroup); @@ -64,8 +74,14 @@ Tuple SaveAccessAndRefreshToken( IEnumerable GetAllIdTokens(bool filterByClientId); IEnumerable GetAllAccounts(); + /// + /// FOCI - check in the app metadata to see if the app is part of the family + /// + /// null if unkown, true or false if app metadata has details + Task IsFociMemberAsync(AuthenticationRequestParameters authenticationRequestParameters, string familyId); + void ClearAdalCache(); void ClearMsalCache(); void Clear(); } -} \ No newline at end of file +} diff --git a/src/Microsoft.Identity.Client/Internal/Requests/InteractiveRequest.cs b/src/Microsoft.Identity.Client/Internal/Requests/InteractiveRequest.cs index 0e83273644..57fe0bdeec 100644 --- a/src/Microsoft.Identity.Client/Internal/Requests/InteractiveRequest.cs +++ b/src/Microsoft.Identity.Client/Internal/Requests/InteractiveRequest.cs @@ -92,15 +92,18 @@ internal override async Task ExecuteAsync(CancellationToke await AcquireAuthorizationAsync(cancellationToken).ConfigureAwait(false); VerifyAuthorizationResult(); - BrokerInteractiveRequest brokerInteractiveRequest = new BrokerInteractiveRequest( - AuthenticationRequestParameters, - _interactiveParameters, - ServiceBundle, - _authorizationResult); - - if (AuthenticationRequestParameters.IsBrokerEnabled || brokerInteractiveRequest.IsBrokerInvocationRequired()) + if (AuthenticationRequestParameters.IsBrokerEnabled) { - _msalTokenResponse = await brokerInteractiveRequest.SendTokenRequestToBrokerAsync().ConfigureAwait(false); + var brokerInteractiveRequest = new BrokerInteractiveRequest( + AuthenticationRequestParameters, + _interactiveParameters, + ServiceBundle, + _authorizationResult); + + if (brokerInteractiveRequest.IsBrokerInvocationRequired()) + { + _msalTokenResponse = await brokerInteractiveRequest.SendTokenRequestToBrokerAsync().ConfigureAwait(false); + } } else { @@ -255,7 +258,7 @@ private Dictionary CreateAuthorizationRequestParameters() private void VerifyAuthorizationResult() { - if (_authorizationResult.Status == AuthorizationStatus.Success && + if (_authorizationResult.Status == AuthorizationStatus.Success && !_state.Equals(_authorizationResult.State, StringComparison.OrdinalIgnoreCase)) { @@ -286,4 +289,4 @@ private void VerifyAuthorizationResult() } } } -} \ No newline at end of file +} diff --git a/src/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs b/src/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs index 9466e05ab6..b0e7b6c1d2 100644 --- a/src/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs +++ b/src/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs @@ -216,7 +216,7 @@ protected AuthenticationResult CacheTokenResponseAndCreateAuthenticationResult(M { AuthenticationRequestParameters.RequestContext.Logger.Info("Saving Token Response to cache.."); - var tuple = CacheManager.SaveAccessAndRefreshToken(msalTokenResponse); + var tuple = CacheManager.SaveTokenResponse(msalTokenResponse); return new AuthenticationResult(tuple.Item1, tuple.Item2); } else diff --git a/src/Microsoft.Identity.Client/Internal/Requests/SilentRequest.cs b/src/Microsoft.Identity.Client/Internal/Requests/SilentRequest.cs index b397c8808e..673047a484 100644 --- a/src/Microsoft.Identity.Client/Internal/Requests/SilentRequest.cs +++ b/src/Microsoft.Identity.Client/Internal/Requests/SilentRequest.cs @@ -34,12 +34,14 @@ using Microsoft.Identity.Client.OAuth2; using Microsoft.Identity.Client.TelemetryCore; using Microsoft.Identity.Client.Internal.Broker; +using System; namespace Microsoft.Identity.Client.Internal.Requests { internal class SilentRequest : RequestBase { private readonly AcquireTokenSilentParameters _silentParameters; + private const string TheOnlyFamilyId = "1"; public SilentRequest( IServiceBundle serviceBundle, @@ -59,33 +61,84 @@ internal override async Task ExecuteAsync(CancellationToke "Token cache is set to null. Silent requests cannot be executed."); } - MsalAccessTokenCacheItem msalAccessTokenItem = null; - // Look for access token if (!_silentParameters.ForceRefresh) { - msalAccessTokenItem = + MsalAccessTokenCacheItem msalAccessTokenItem = await CacheManager.FindAccessTokenAsync().ConfigureAwait(false); + + if (msalAccessTokenItem != null) + { + var msalIdTokenItem = CacheManager.GetIdTokenCacheItem(msalAccessTokenItem.GetIdTokenItemKey()); + return new AuthenticationResult(msalAccessTokenItem, msalIdTokenItem); + } } - if (msalAccessTokenItem != null) + // Try FOCI first + MsalTokenResponse msalTokenResponse = await TryGetTokenUsingFociAsync(cancellationToken) + .ConfigureAwait(false); + + // Normal, non-FOCI flow + if (msalTokenResponse == null) { - var msalIdTokenItem = CacheManager.GetIdTokenCacheItem(msalAccessTokenItem.GetIdTokenItemKey()); + // Look for a refresh token + MsalRefreshTokenCacheItem appRefreshToken = await FindRefreshTokenOrFailAsync() + .ConfigureAwait(false); - return new AuthenticationResult(msalAccessTokenItem, msalIdTokenItem); + msalTokenResponse = await RefreshAccessTokenAsync(appRefreshToken, cancellationToken) + .ConfigureAwait(false); } + return CacheTokenResponseAndCreateAuthenticationResult(msalTokenResponse); + } - var msalRefreshTokenItem = await CacheManager.FindRefreshTokenAsync().ConfigureAwait(false); - - if (msalRefreshTokenItem == null) + private async Task TryGetTokenUsingFociAsync(CancellationToken cancellationToken) + { + if (!ServiceBundle.PlatformProxy.GetFeatureFlags().IsFociEnabled) { - AuthenticationRequestParameters.RequestContext.Logger.Verbose("No Refresh Token was found in the cache"); + return null; + } - throw new MsalUiRequiredException( - MsalUiRequiredException.NoTokensFoundError, - "No Refresh Token found in the cache"); + var logger = AuthenticationRequestParameters.RequestContext.Logger; + + // If the app was just added to the family, the app metadata will reflect this + // after the first RT exchanged. + bool? isFamilyMember = await CacheManager.IsAppFociMemberAsync(TheOnlyFamilyId).ConfigureAwait(false); + + if (isFamilyMember.HasValue && isFamilyMember.Value == false) + { + AuthenticationRequestParameters.RequestContext.Logger.Verbose( + "[FOCI] App is not part of the family, skipping FOCI."); + + return null; + } + + logger.Verbose("[FOCI] App is part of the family or unkown, looking for FRT"); + var familyRefreshToken = await CacheManager.FindFamilyRefreshTokenAsync(TheOnlyFamilyId).ConfigureAwait(false); + logger.Verbose("[FOCI] FRT found? " + (familyRefreshToken != null)); + + if (familyRefreshToken != null) + { + try + { + MsalTokenResponse frtTokenResponse = await RefreshAccessTokenAsync(familyRefreshToken, cancellationToken) + .ConfigureAwait(false); + + logger.Verbose("[FOCI] FRT exchanged succeeded"); + return frtTokenResponse; + } + catch (MsalServiceException) + { + logger.Error("[FOCI] FRT exchanged failed " + (familyRefreshToken != null)); + return null; + } } + return null; + + } + + private async Task RefreshAccessTokenAsync(MsalRefreshTokenCacheItem msalRefreshTokenItem, CancellationToken cancellationToken) + { AuthenticationRequestParameters.RequestContext.Logger.Verbose("Refreshing access token..."); await ResolveAuthorityEndpointsAsync().ConfigureAwait(false); @@ -99,7 +152,22 @@ internal override async Task ExecuteAsync(CancellationToke "Refresh token was missing from the token refresh response, so the refresh token in the request is returned instead"); } - return CacheTokenResponseAndCreateAuthenticationResult(msalTokenResponse); + return msalTokenResponse; + } + + private async Task FindRefreshTokenOrFailAsync() + { + var msalRefreshTokenItem = await CacheManager.FindRefreshTokenAsync().ConfigureAwait(false); + if (msalRefreshTokenItem == null) + { + AuthenticationRequestParameters.RequestContext.Logger.Verbose("No Refresh Token was found in the cache"); + + throw new MsalUiRequiredException( + MsalUiRequiredException.NoTokensFoundError, + "No Refresh Token found in the cache"); + } + + return msalRefreshTokenItem; } protected override void EnrichTelemetryApiEvent(ApiEvent apiEvent) @@ -121,4 +189,4 @@ private Dictionary GetBodyParameters(string refreshTokenSecret) return dict; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj b/src/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj index 276754ccb0..8455fd4077 100644 --- a/src/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj +++ b/src/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj @@ -24,7 +24,4 @@ - - - \ No newline at end of file diff --git a/src/Microsoft.Identity.Client/OAuth2/MsalTokenResponse.cs b/src/Microsoft.Identity.Client/OAuth2/MsalTokenResponse.cs index 195fa8f959..de814ac078 100644 --- a/src/Microsoft.Identity.Client/OAuth2/MsalTokenResponse.cs +++ b/src/Microsoft.Identity.Client/OAuth2/MsalTokenResponse.cs @@ -48,6 +48,7 @@ internal class TokenResponseClaim : OAuth2ResponseBaseClaim public const string CreatedOn = "created_on"; public const string ExtendedExpiresIn = "ext_expires_in"; public const string Authority = "authority"; + public const string FamilyId = "foci"; } [DataContract] @@ -96,6 +97,12 @@ public long ExtendedExpiresIn } } + /// + /// Optional field, FOCI support. + /// + [DataMember(Name=TokenResponseClaim.FamilyId, IsRequired = false)] + public string FamilyId { get; set; } + public DateTimeOffset AccessTokenExpiresOn { get; private set; } public DateTimeOffset AccessTokenExtendedExpiresOn { get; private set; } @@ -134,4 +141,4 @@ internal static MsalTokenResponse CreateFromBrokerResponse(Dictionary - /// The main entry point for the application. + /// FOCI is not currently supported on Android because app metadata serialization is not defined. /// - [STAThread] - static void Main() - { - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); - Application.Run(new MainForm()); - } + public bool IsFociEnabled => false; } } diff --git a/src/Microsoft.Identity.Client/Platforms/Android/AndroidPlatformProxy.cs b/src/Microsoft.Identity.Client/Platforms/Android/AndroidPlatformProxy.cs index d753df5a99..8a8e0d34d8 100644 --- a/src/Microsoft.Identity.Client/Platforms/Android/AndroidPlatformProxy.cs +++ b/src/Microsoft.Identity.Client/Platforms/Android/AndroidPlatformProxy.cs @@ -165,5 +165,7 @@ protected override IWebUIFactory CreateWebUiFactory() protected override ICryptographyManager InternalGetCryptographyManager() => new AndroidCryptographyManager(); protected override IPlatformLogger InternalGetPlatformLogger() => new AndroidPlatformLogger(); + + protected override IFeatureFlags CreateFeatureFlags() => new AndroidFeatureFlags(); } } diff --git a/src/Microsoft.Identity.Client/Platforms/Android/AndroidTokenCacheAccessor.cs b/src/Microsoft.Identity.Client/Platforms/Android/AndroidTokenCacheAccessor.cs index 17775602d7..2842b91104 100644 --- a/src/Microsoft.Identity.Client/Platforms/Android/AndroidTokenCacheAccessor.cs +++ b/src/Microsoft.Identity.Client/Platforms/Android/AndroidTokenCacheAccessor.cs @@ -137,22 +137,22 @@ private void DeleteAll(ISharedPreferences sharedPreferences) editor.Apply(); } - public ICollection GetAllAccessTokens() + public IEnumerable GetAllAccessTokens() { return _accessTokenSharedPreference.All.Values.Cast().Select(x => MsalAccessTokenCacheItem.FromJsonString(x)).ToList(); } - public ICollection GetAllRefreshTokens() + public IEnumerable GetAllRefreshTokens() { return _refreshTokenSharedPreference.All.Values.Cast().Select(x => MsalRefreshTokenCacheItem.FromJsonString(x)).ToList(); } - public ICollection GetAllIdTokens() + public IEnumerable GetAllIdTokens() { return _idTokenSharedPreference.All.Values.Cast().Select(x => MsalIdTokenCacheItem.FromJsonString(x)).ToList(); } - public ICollection GetAllAccounts() + public IEnumerable GetAllAccounts() { return _accountSharedPreference.All.Values.Cast().Select(x => MsalAccountCacheItem.FromJsonString(x)).ToList(); } @@ -185,28 +185,31 @@ public MsalAccountCacheItem GetAccount(MsalAccountCacheKey accountKey) return MsalAccountCacheItem.FromJsonString(_accountSharedPreference.GetString(accountKey.ToString(), null)); } - /// - public int RefreshTokenCount => throw new NotImplementedException(); - - /// - public int AccessTokenCount => throw new NotImplementedException(); + #region App Metadata - not used on Android + public MsalAppMetadataCacheItem ReadAppMetadata(MsalAppMetadataCacheKey appMetadataKey) + { + throw new NotImplementedException(); + } - /// - public int AccountCount => throw new NotImplementedException(); + public void WriteAppMetadata(MsalAppMetadataCacheItem appMetadata) + { + throw new NotImplementedException(); + } - /// - public int IdTokenCount => throw new NotImplementedException(); + public void SaveAppMetadata(MsalAppMetadataCacheItem item) + { + throw new NotImplementedException(); + } - /// - public void ClearRefreshTokens() + public IEnumerable GetAllAppMetadata() { throw new NotImplementedException(); } - /// - public void ClearAccessTokens() + public MsalAppMetadataCacheItem GetAppMetadata(MsalAppMetadataCacheKey appMetadataKey) { throw new NotImplementedException(); } + #endregion } } diff --git a/src/Microsoft.Identity.Client/Platforms/Mac/MacFeatureFlags.cs b/src/Microsoft.Identity.Client/Platforms/Mac/MacFeatureFlags.cs new file mode 100644 index 0000000000..c4fb2bccec --- /dev/null +++ b/src/Microsoft.Identity.Client/Platforms/Mac/MacFeatureFlags.cs @@ -0,0 +1,36 @@ +//---------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +using Microsoft.Identity.Client.PlatformsCommon.Interfaces; + +namespace Microsoft.Identity.Client.Platforms.Mac +{ + internal class MacFeatureFlags : IFeatureFlags + { + public bool IsFociEnabled => true; + } +} diff --git a/src/Microsoft.Identity.Client/Platforms/Mac/MacPlatformProxy.cs b/src/Microsoft.Identity.Client/Platforms/Mac/MacPlatformProxy.cs index 6b52a5d350..fc98579a76 100644 --- a/src/Microsoft.Identity.Client/Platforms/Mac/MacPlatformProxy.cs +++ b/src/Microsoft.Identity.Client/Platforms/Mac/MacPlatformProxy.cs @@ -161,5 +161,7 @@ public override ITokenCacheAccessor CreateTokenCacheAccessor() protected override IWebUIFactory CreateWebUiFactory() => new MacUIFactory(); protected override ICryptographyManager InternalGetCryptographyManager() => new MacCryptographyManager(); protected override IPlatformLogger InternalGetPlatformLogger() => new ConsolePlatformLogger(); + + protected override IFeatureFlags CreateFeatureFlags() => new MacFeatureFlags(); } } diff --git a/src/Microsoft.Identity.Client/Platforms/iOS/iOSFeatureFlags.cs b/src/Microsoft.Identity.Client/Platforms/iOS/iOSFeatureFlags.cs new file mode 100644 index 0000000000..bfcbc5f8d3 --- /dev/null +++ b/src/Microsoft.Identity.Client/Platforms/iOS/iOSFeatureFlags.cs @@ -0,0 +1,39 @@ +//---------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +using Microsoft.Identity.Client.PlatformsCommon.Interfaces; + +namespace Microsoft.Identity.Client.Platforms.iOS +{ + internal class iOSFeatureFlags : IFeatureFlags + { + /// + /// FOCI has not been tested on iOS + /// + public bool IsFociEnabled => false; + } +} diff --git a/src/Microsoft.Identity.Client/Platforms/iOS/iOSPlatformProxy.cs b/src/Microsoft.Identity.Client/Platforms/iOS/iOSPlatformProxy.cs index fce015fc97..02c626c945 100644 --- a/src/Microsoft.Identity.Client/Platforms/iOS/iOSPlatformProxy.cs +++ b/src/Microsoft.Identity.Client/Platforms/iOS/iOSPlatformProxy.cs @@ -151,5 +151,7 @@ protected override IWebUIFactory CreateWebUiFactory() protected override ICryptographyManager InternalGetCryptographyManager() => new iOSCryptographyManager(); protected override IPlatformLogger InternalGetPlatformLogger() => new ConsolePlatformLogger(); + + protected override IFeatureFlags CreateFeatureFlags() => new iOSFeatureFlags(); } -} \ No newline at end of file +} diff --git a/src/Microsoft.Identity.Client/Platforms/iOS/iOSTokenCacheAccessor.cs b/src/Microsoft.Identity.Client/Platforms/iOS/iOSTokenCacheAccessor.cs index 103e2303c7..0b09394f4d 100644 --- a/src/Microsoft.Identity.Client/Platforms/iOS/iOSTokenCacheAccessor.cs +++ b/src/Microsoft.Identity.Client/Platforms/iOS/iOSTokenCacheAccessor.cs @@ -35,7 +35,6 @@ using Microsoft.Identity.Client.Cache.Keys; using Microsoft.Identity.Client.Core; using Microsoft.Identity.Client.Exceptions; -using Microsoft.Identity.Client.Utils; using Security; namespace Microsoft.Identity.Client.Platforms.iOS @@ -43,21 +42,7 @@ namespace Microsoft.Identity.Client.Platforms.iOS internal class iOSTokenCacheAccessor : ITokenCacheAccessor { public const string CacheKeyDelimiter = "-"; - private static readonly Dictionary AuthorityTypeToAttrType = new Dictionary() - { - {AuthorityType.AAD.ToString(), 1001}, - {AuthorityType.MSA.ToString(), 1002}, - {AuthorityType.MSSTS.ToString(), 1003}, - {AuthorityType.OTHER.ToString(), 1004}, - }; - private enum CredentialAttrType - { - AccessToken = 2001, - RefreshToken = 2002, - IdToken = 2003, - Password = 2004 - } private const bool _defaultSyncSetting = false; private const SecAccessible _defaultAccessiblityPolicy = SecAccessible.AfterFirstUnlockThisDeviceOnly; @@ -66,7 +51,7 @@ private enum CredentialAttrType // Identifier for the keychain item used to retrieve current team ID private const string TeamIdKey = "DotNetTeamIDHint"; - private string keychainGroup; + private string _keychainGroup; private readonly RequestContext _requestContext; private string GetBundleId() @@ -78,11 +63,11 @@ public void SetiOSKeychainSecurityGroup(string keychainSecurityGroup) { if (keychainSecurityGroup == null) { - keychainGroup = GetBundleId(); + _keychainGroup = GetBundleId(); } else { - keychainGroup = GetTeamId() + '.' + keychainSecurityGroup; + _keychainGroup = GetTeamId() + '.' + keychainSecurityGroup; } } @@ -115,7 +100,7 @@ private string GetTeamId() public iOSTokenCacheAccessor() { - keychainGroup = GetTeamId() + '.' + DefaultKeychainGroup; + _keychainGroup = GetTeamId() + '.' + DefaultKeychainGroup; } public iOSTokenCacheAccessor(RequestContext requestContext) : this() @@ -125,127 +110,81 @@ public iOSTokenCacheAccessor(RequestContext requestContext) : this() public void SaveAccessToken(MsalAccessTokenCacheItem item) { - var key = item.GetKey(); - - var account = key.GetiOSAccountKey(); - var service = key.GetiOSServiceKey(); - var generic = key.GetiOSGenericKey(); - var type = (int)CredentialAttrType.AccessToken; - - var value = item.ToJsonString(); - - Save(account, service, generic, type, value); + IiOSKey key = item.GetKey(); + Save(key, item.ToJsonString()); } public void SaveRefreshToken(MsalRefreshTokenCacheItem item) { - var key = item.GetKey(); - var account = key.GetiOSAccountKey(); - var service = key.GetiOSServiceKey(); - var generic = key.GetiOSGenericKey(); - - var type = (int)CredentialAttrType.RefreshToken; - - var value = item.ToJsonString(); - - Save(account, service, generic, type, value); + Save(item.GetKey(), item.ToJsonString()); } public void SaveIdToken(MsalIdTokenCacheItem item) { - var key = item.GetKey(); - var account = key.GetiOSAccountKey(); - var service = key.GetiOSServiceKey(); - var generic = key.GetiOSGenericKey(); - - var type = (int)CredentialAttrType.IdToken; - - var value = item.ToJsonString(); - - Save(account, service, generic, type, value); + Save(item.GetKey(), item.ToJsonString()); } public void SaveAccount(MsalAccountCacheItem item) { - var key = item.GetKey(); - var account = key.GetiOSAccountKey(); - var service = key.GetiOSServiceKey(); - var generic = key.GetiOSGenericKey(); - - var type = AuthorityTypeToAttrType[item.AuthorityType]; - - var value = item.ToJsonString(); - - Save(account, service, generic, type, value); + Save(item.GetKey(), item.ToJsonString()); } public void DeleteAccessToken(MsalAccessTokenCacheKey cacheKey) { - var account = cacheKey.GetiOSAccountKey(); - var service = cacheKey.GetiOSServiceKey(); - - var type = (int)CredentialAttrType.AccessToken; - - Remove(account, service, type); + Remove(cacheKey); } public void DeleteRefreshToken(MsalRefreshTokenCacheKey cacheKey) { - var account = cacheKey.GetiOSAccountKey(); - var service = cacheKey.GetiOSServiceKey(); - - var type = (int)CredentialAttrType.RefreshToken; - - Remove(account, service, type); + Remove(cacheKey); } public void DeleteIdToken(MsalIdTokenCacheKey cacheKey) { - var account = cacheKey.GetiOSAccountKey(); - var service = cacheKey.GetiOSServiceKey(); - - var type = (int)CredentialAttrType.IdToken; - - Remove(account, service, type); + Remove(cacheKey); } public void DeleteAccount(MsalAccountCacheKey cacheKey) { - var account = cacheKey.GetiOSAccountKey(); - var service = cacheKey.GetiOSServiceKey(); - - var type = AuthorityTypeToAttrType[AuthorityType.MSSTS.ToString()]; - - Remove(account, service, type); + Remove(cacheKey); } - public ICollection GetAllAccessTokens() + + public IEnumerable GetAllAccessTokens() { - return GetValues((int)CredentialAttrType.AccessToken).Select(x => MsalAccessTokenCacheItem.FromJsonString(x)).ToList(); + return GetPayloadAsString((int)MsalCacheKeys.iOSCredentialAttrType.AccessToken) + .Select(x => MsalAccessTokenCacheItem.FromJsonString(x)) + .ToList(); } - public ICollection GetAllRefreshTokens() + public IEnumerable GetAllRefreshTokens() { - return GetValues((int)CredentialAttrType.RefreshToken).Select(x => MsalRefreshTokenCacheItem.FromJsonString(x)).ToList(); + return GetPayloadAsString((int)MsalCacheKeys.iOSCredentialAttrType.RefreshToken) + .Select(x => MsalRefreshTokenCacheItem.FromJsonString(x)) + .ToList(); } - public ICollection GetAllIdTokens() + public IEnumerable GetAllIdTokens() { - return GetValues((int)CredentialAttrType.IdToken).Select(x => MsalIdTokenCacheItem.FromJsonString(x)).ToList(); + return GetPayloadAsString((int)MsalCacheKeys.iOSCredentialAttrType.IdToken) + .Select(x => MsalIdTokenCacheItem.FromJsonString(x)) + .ToList(); } - public ICollection GetAllAccounts() + public IEnumerable GetAllAccounts() { - return GetValues(AuthorityTypeToAttrType[AuthorityType.MSSTS.ToString()]).Select(x => MsalAccountCacheItem.FromJsonString(x)).ToList(); + return GetPayloadAsString(MsalCacheKeys.iOSAuthorityTypeToAttrType[AuthorityType.MSSTS.ToString()]) + .Select(x => MsalAccountCacheItem.FromJsonString(x)) + .ToList(); } - private string GetValue(string account, string service, int type) + private string GetPayload(IiOSKey key) { var queryRecord = new SecRecord(SecKind.GenericPassword) { - Account = account, - Service = service, - CreatorType = type, - AccessGroup = keychainGroup + Account = key.iOSAccount, + Service = key.iOSService, + CreatorType = key.iOSType, + AccessGroup = _keychainGroup }; var match = SecKeyChain.QueryAsRecord(queryRecord, out SecStatusCode resultCode); @@ -255,15 +194,15 @@ private string GetValue(string account, string service, int type) : string.Empty; } - private ICollection GetValues(int type) + private ICollection GetPayloadAsString(int type) { var queryRecord = new SecRecord(SecKind.GenericPassword) { CreatorType = type, - AccessGroup = keychainGroup + AccessGroup = _keychainGroup }; - SecRecord[] records = SecKeyChain.QueryAsRecord(queryRecord, Int32.MaxValue, out SecStatusCode resultCode); + SecRecord[] records = SecKeyChain.QueryAsRecord(queryRecord, int.MaxValue, out SecStatusCode resultCode); ICollection res = new List(); @@ -279,9 +218,19 @@ private ICollection GetValues(int type) return res; } - private SecStatusCode Save(string account, string service, string generic, int type, string value) + private SecStatusCode Save(IiOSKey key, string payload) { - SecRecord recordToSave = CreateRecord(account, service, generic, type, value); + var recordToSave = new SecRecord(SecKind.GenericPassword) + { + Account = key.iOSAccount, + Service = key.iOSService, + Generic = key.iOSGeneric, + CreatorType = key.iOSType, + ValueData = NSData.FromString(payload, NSStringEncoding.UTF8), + AccessGroup = _keychainGroup, + Accessible = _defaultAccessiblityPolicy, + Synchronizable = _defaultSyncSetting, + }; var secStatusCode = Update(recordToSave); @@ -303,29 +252,15 @@ private SecStatusCode Save(string account, string service, string generic, int t return secStatusCode; } - private SecRecord CreateRecord(string account, string service, string generic, int type, string value) - { - return new SecRecord(SecKind.GenericPassword) - { - Account = account, - Service = service, - Generic = generic, - CreatorType = type, - ValueData = NSData.FromString(value, NSStringEncoding.UTF8), - AccessGroup = keychainGroup, - Accessible = _defaultAccessiblityPolicy, - Synchronizable = _defaultSyncSetting, - }; - } - private SecStatusCode Remove(string account, string service, int type) + private SecStatusCode Remove(IiOSKey key) { var record = new SecRecord(SecKind.GenericPassword) { - Account = account, - Service = service, - CreatorType = type, - AccessGroup = keychainGroup + Account = key.iOSAccount, + Service = key.iOSService, + CreatorType = key.iOSType, + AccessGroup = _keychainGroup }; return SecKeyChain.Remove(record); @@ -338,7 +273,7 @@ private SecStatusCode Update(SecRecord updatedRecord) Account = updatedRecord.Account, Service = updatedRecord.Service, CreatorType = updatedRecord.CreatorType, - AccessGroup = keychainGroup + AccessGroup = _keychainGroup }; var attributesToUpdate = new SecRecord() { @@ -348,88 +283,73 @@ private SecStatusCode Update(SecRecord updatedRecord) return SecKeyChain.Update(currentRecord, attributesToUpdate); } - private void RemoveAll(int type) + private void RemoveByType(int type) { var queryRecord = new SecRecord(SecKind.GenericPassword) { CreatorType = type, - AccessGroup = keychainGroup + AccessGroup = _keychainGroup }; SecKeyChain.Remove(queryRecord); } public void Clear() { - RemoveAll((int)CredentialAttrType.AccessToken); - RemoveAll((int)CredentialAttrType.RefreshToken); - RemoveAll((int)CredentialAttrType.IdToken); + RemoveByType((int)MsalCacheKeys.iOSCredentialAttrType.AccessToken); + RemoveByType((int)MsalCacheKeys.iOSCredentialAttrType.RefreshToken); + RemoveByType((int)MsalCacheKeys.iOSCredentialAttrType.IdToken); - RemoveAll(AuthorityTypeToAttrType[AuthorityType.MSSTS.ToString()]); + RemoveByType(MsalCacheKeys.iOSAuthorityTypeToAttrType[AuthorityType.MSSTS.ToString()]); } public MsalAccessTokenCacheItem GetAccessToken(MsalAccessTokenCacheKey accessTokenKey) { - var account = accessTokenKey.GetiOSAccountKey(); - var service = accessTokenKey.GetiOSServiceKey(); - - var type = (int)CredentialAttrType.AccessToken; - - return MsalAccessTokenCacheItem.FromJsonString(GetValue(account, service, type)); + return MsalAccessTokenCacheItem.FromJsonString(GetPayload(accessTokenKey)); } public MsalRefreshTokenCacheItem GetRefreshToken(MsalRefreshTokenCacheKey refreshTokenKey) { - var account = refreshTokenKey.GetiOSAccountKey(); - var service = refreshTokenKey.GetiOSServiceKey(); - - - var type = (int)CredentialAttrType.RefreshToken; - - return MsalRefreshTokenCacheItem.FromJsonString(GetValue(account, service, type)); + return MsalRefreshTokenCacheItem.FromJsonString(GetPayload(refreshTokenKey)); } public MsalIdTokenCacheItem GetIdToken(MsalIdTokenCacheKey idTokenKey) { - var account = idTokenKey.GetiOSAccountKey(); - var service = idTokenKey.GetiOSServiceKey(); + return MsalIdTokenCacheItem.FromJsonString(GetPayload(idTokenKey)); - var type = (int)CredentialAttrType.IdToken; - - return MsalIdTokenCacheItem.FromJsonString(GetValue(account, service, type)); } public MsalAccountCacheItem GetAccount(MsalAccountCacheKey accountKey) { - var account = accountKey.GetiOSAccountKey(); - var service = accountKey.GetiOSServiceKey(); - - var type = AuthorityTypeToAttrType[AuthorityType.MSSTS.ToString()]; - - return MsalAccountCacheItem.FromJsonString(GetValue(account, service, type)); + return MsalAccountCacheItem.FromJsonString(GetPayload(accountKey)); } - /// - public int RefreshTokenCount => throw new NotImplementedException(); - - /// - public int AccessTokenCount => throw new NotImplementedException(); + #region AppMetatada - not implemented on iOS + public MsalAppMetadataCacheItem ReadAppMetadata(MsalAppMetadataCacheKey appMetadataKey) + { + //return MsalAppMetadataCacheItem.FromJsonString(GetPayload(appMetadataKey)); + throw new NotImplementedException(); + } - /// - public int AccountCount => throw new NotImplementedException(); + public void WriteAppMetadata(MsalAppMetadataCacheItem appMetadata) + { + //Save(appMetadata.GetKey(), appMetadata.ToJsonString()); + throw new NotImplementedException(); + } - /// - public int IdTokenCount => throw new NotImplementedException(); + public void SaveAppMetadata(MsalAppMetadataCacheItem item) + { + throw new NotImplementedException(); + } - /// - public void ClearRefreshTokens() + public IEnumerable GetAllAppMetadata() { throw new NotImplementedException(); } - /// - public void ClearAccessTokens() + public MsalAppMetadataCacheItem GetAppMetadata(MsalAppMetadataCacheKey appMetadataKey) { throw new NotImplementedException(); } + #endregion } } diff --git a/src/Microsoft.Identity.Client/Platforms/net45/NetDesktopFeatureFlags.cs b/src/Microsoft.Identity.Client/Platforms/net45/NetDesktopFeatureFlags.cs new file mode 100644 index 0000000000..ff08e0b8e7 --- /dev/null +++ b/src/Microsoft.Identity.Client/Platforms/net45/NetDesktopFeatureFlags.cs @@ -0,0 +1,36 @@ +//---------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +using Microsoft.Identity.Client.PlatformsCommon.Interfaces; + +namespace Microsoft.Identity.Client.Platforms.net45 +{ + internal class NetDesktopFeatureFlags : IFeatureFlags + { + public bool IsFociEnabled => true; + } +} diff --git a/src/Microsoft.Identity.Client/Platforms/net45/NetDesktopPlatformProxy.cs b/src/Microsoft.Identity.Client/Platforms/net45/NetDesktopPlatformProxy.cs index 130b438f92..6ac08f58bd 100644 --- a/src/Microsoft.Identity.Client/Platforms/net45/NetDesktopPlatformProxy.cs +++ b/src/Microsoft.Identity.Client/Platforms/net45/NetDesktopPlatformProxy.cs @@ -232,15 +232,11 @@ protected override string InternalGetProductName() } /// - protected override ICryptographyManager InternalGetCryptographyManager() - { - return new NetDesktopCryptographyManager(); - } + protected override ICryptographyManager InternalGetCryptographyManager() => new NetDesktopCryptographyManager(); /// - protected override IPlatformLogger InternalGetPlatformLogger() - { - return new EventSourcePlatformLogger(); - } + protected override IPlatformLogger InternalGetPlatformLogger() => new EventSourcePlatformLogger(); + + protected override IFeatureFlags CreateFeatureFlags() => new NetDesktopFeatureFlags(); } -} \ No newline at end of file +} diff --git a/src/Microsoft.Identity.Client/Platforms/netcore/NetCoreFeatureFlags.cs b/src/Microsoft.Identity.Client/Platforms/netcore/NetCoreFeatureFlags.cs new file mode 100644 index 0000000000..50a33dfb32 --- /dev/null +++ b/src/Microsoft.Identity.Client/Platforms/netcore/NetCoreFeatureFlags.cs @@ -0,0 +1,36 @@ +//---------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +using Microsoft.Identity.Client.PlatformsCommon.Interfaces; + +namespace Microsoft.Identity.Client.Platforms.netcore +{ + internal class NetCoreFeatureFlags : IFeatureFlags + { + public bool IsFociEnabled => true; + } +} diff --git a/src/Microsoft.Identity.Client/Platforms/netcore/NetCorePlatformProxy.cs b/src/Microsoft.Identity.Client/Platforms/netcore/NetCorePlatformProxy.cs index 2315bad0be..1ef8416fb7 100644 --- a/src/Microsoft.Identity.Client/Platforms/netcore/NetCorePlatformProxy.cs +++ b/src/Microsoft.Identity.Client/Platforms/netcore/NetCorePlatformProxy.cs @@ -147,5 +147,7 @@ public override ITokenCacheAccessor CreateTokenCacheAccessor() protected override IWebUIFactory CreateWebUiFactory() => new WebUIFactory(); protected override ICryptographyManager InternalGetCryptographyManager() => new NetCoreCryptographyManager(); protected override IPlatformLogger InternalGetPlatformLogger() => new EventSourcePlatformLogger(); + + protected override IFeatureFlags CreateFeatureFlags() => new NetCoreFeatureFlags(); } } diff --git a/src/Microsoft.Identity.Client/Platforms/netstandard13/NetDesktopFeatureFlags.cs b/src/Microsoft.Identity.Client/Platforms/netstandard13/NetDesktopFeatureFlags.cs new file mode 100644 index 0000000000..bd9a3acc7f --- /dev/null +++ b/src/Microsoft.Identity.Client/Platforms/netstandard13/NetDesktopFeatureFlags.cs @@ -0,0 +1,36 @@ +//---------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +using Microsoft.Identity.Client.PlatformsCommon.Interfaces; + +namespace Microsoft.Identity.Client.Platforms.Android +{ + internal class NetDesktopFeatureFlags : IFeatureFlags + { + public bool IsFociEnabled => true; + } +} diff --git a/src/Microsoft.Identity.Client/Platforms/netstandard13/NetStandard13PlatformProxy.cs b/src/Microsoft.Identity.Client/Platforms/netstandard13/NetStandard13PlatformProxy.cs index 5296e8d337..028c205a73 100644 --- a/src/Microsoft.Identity.Client/Platforms/netstandard13/NetStandard13PlatformProxy.cs +++ b/src/Microsoft.Identity.Client/Platforms/netstandard13/NetStandard13PlatformProxy.cs @@ -146,5 +146,7 @@ public override ITokenCacheAccessor CreateTokenCacheAccessor() protected override IWebUIFactory CreateWebUiFactory() => new WebUIFactory(); protected override ICryptographyManager InternalGetCryptographyManager() => new NetStandard13CryptographyManager(); protected override IPlatformLogger InternalGetPlatformLogger() => new EventSourcePlatformLogger(); + + protected override IFeatureFlags CreateFeatureFlags() => new NetStandardFeatureFlags(); } } diff --git a/src/Microsoft.Identity.Client/Platforms/netstandard13/NetStandardFeatureFlags.cs b/src/Microsoft.Identity.Client/Platforms/netstandard13/NetStandardFeatureFlags.cs new file mode 100644 index 0000000000..770027af59 --- /dev/null +++ b/src/Microsoft.Identity.Client/Platforms/netstandard13/NetStandardFeatureFlags.cs @@ -0,0 +1,39 @@ +//---------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +using Microsoft.Identity.Client.PlatformsCommon.Interfaces; + +namespace Microsoft.Identity.Client.Platforms.netstandard13 +{ + /// + /// These control the behaviour of platforms targetting directly NetStandard (e.g. WinRT) + /// + internal class NetStandardFeatureFlags : IFeatureFlags + { + public bool IsFociEnabled => true; + } +} diff --git a/src/Microsoft.Identity.Client/Platforms/uap/UapFeatureFlags.cs b/src/Microsoft.Identity.Client/Platforms/uap/UapFeatureFlags.cs new file mode 100644 index 0000000000..9beafae50c --- /dev/null +++ b/src/Microsoft.Identity.Client/Platforms/uap/UapFeatureFlags.cs @@ -0,0 +1,36 @@ +//---------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +using Microsoft.Identity.Client.PlatformsCommon.Interfaces; + +namespace Microsoft.Identity.Client.Platforms.uap +{ + internal class UapFeatureFlags : IFeatureFlags + { + public bool IsFociEnabled => true; + } +} diff --git a/src/Microsoft.Identity.Client/Platforms/uap/UapPlatformProxy.cs b/src/Microsoft.Identity.Client/Platforms/uap/UapPlatformProxy.cs index 7eb8260945..8451a82dde 100644 --- a/src/Microsoft.Identity.Client/Platforms/uap/UapPlatformProxy.cs +++ b/src/Microsoft.Identity.Client/Platforms/uap/UapPlatformProxy.cs @@ -201,24 +201,16 @@ protected override string InternalGetDeviceId() return new EasClientDeviceInformation()?.Id.ToString(); } - public override ILegacyCachePersistence CreateLegacyCachePersistence() - { - return new UapLegacyCachePersistence(Logger, CryptographyManager); - } + public override ILegacyCachePersistence CreateLegacyCachePersistence() => new UapLegacyCachePersistence(Logger, CryptographyManager); - public override ITokenCacheAccessor CreateTokenCacheAccessor() - { - return new InMemoryTokenCacheAccessor(); - } + public override ITokenCacheAccessor CreateTokenCacheAccessor() => new InMemoryTokenCacheAccessor(); - public override ITokenCacheBlobStorage CreateTokenCacheBlobStorage() - { - return new UapTokenCacheBlobStorage(CryptographyManager, Logger); - } + public override ITokenCacheBlobStorage CreateTokenCacheBlobStorage() => new UapTokenCacheBlobStorage(CryptographyManager, Logger); protected override IWebUIFactory CreateWebUiFactory() => new WebUIFactory(); protected override ICryptographyManager InternalGetCryptographyManager() => new UapCryptographyManager(); protected override IPlatformLogger InternalGetPlatformLogger() => new EventSourcePlatformLogger(); + protected override IFeatureFlags CreateFeatureFlags() => new UapFeatureFlags(); } } diff --git a/src/Microsoft.Identity.Client/PlatformsCommon/Interfaces/IFeatureFlags.cs b/src/Microsoft.Identity.Client/PlatformsCommon/Interfaces/IFeatureFlags.cs new file mode 100644 index 0000000000..099feab333 --- /dev/null +++ b/src/Microsoft.Identity.Client/PlatformsCommon/Interfaces/IFeatureFlags.cs @@ -0,0 +1,34 @@ +// ------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// ------------------------------------------------------------------------------ + +namespace Microsoft.Identity.Client.PlatformsCommon.Interfaces +{ + internal interface IFeatureFlags + { + bool IsFociEnabled { get; } + } +} diff --git a/src/Microsoft.Identity.Client/PlatformsCommon/Interfaces/IPlatformProxy.cs b/src/Microsoft.Identity.Client/PlatformsCommon/Interfaces/IPlatformProxy.cs index 5c9ae483a3..ee9153c307 100644 --- a/src/Microsoft.Identity.Client/PlatformsCommon/Interfaces/IPlatformProxy.cs +++ b/src/Microsoft.Identity.Client/PlatformsCommon/Interfaces/IPlatformProxy.cs @@ -105,6 +105,10 @@ internal interface IPlatformProxy IPlatformLogger PlatformLogger { get; } IWebUIFactory GetWebUiFactory(); - void SetWebUiFactory(IWebUIFactory webUiFactory); + void /* for test */ SetWebUiFactory(IWebUIFactory webUiFactory); + + IFeatureFlags GetFeatureFlags(); + + void /* for test */ SetFeatureFlags(IFeatureFlags featureFlags); } } diff --git a/src/Microsoft.Identity.Client/PlatformsCommon/Shared/AbstractPlatformProxy.cs b/src/Microsoft.Identity.Client/PlatformsCommon/Shared/AbstractPlatformProxy.cs index 3645d2ce4c..e8b13841f3 100644 --- a/src/Microsoft.Identity.Client/PlatformsCommon/Shared/AbstractPlatformProxy.cs +++ b/src/Microsoft.Identity.Client/PlatformsCommon/Shared/AbstractPlatformProxy.cs @@ -61,6 +61,8 @@ protected AbstractPlatformProxy(ICoreLogger logger) } protected IWebUIFactory OverloadWebUiFactory { get; set; } + protected IFeatureFlags OverloadFeatureFlags { get; set; } + protected ICoreLogger Logger { get; } /// @@ -148,6 +150,7 @@ public string GetProductName() public IPlatformLogger PlatformLogger => _platformLogger.Value; protected abstract IWebUIFactory CreateWebUiFactory(); + protected abstract IFeatureFlags CreateFeatureFlags(); protected abstract string InternalGetDeviceModel(); protected abstract string InternalGetOperatingSystem(); protected abstract string InternalGetProcessorArchitecture(); @@ -162,5 +165,15 @@ public virtual ITokenCacheBlobStorage CreateTokenCacheBlobStorage() { return new NullTokenCacheBlobStorage(); } + + public virtual IFeatureFlags GetFeatureFlags() + { + return OverloadFeatureFlags ?? CreateFeatureFlags(); + } + + public void SetFeatureFlags(IFeatureFlags featureFlags) + { + OverloadFeatureFlags = featureFlags; + } } -} \ No newline at end of file +} diff --git a/src/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryTokenCacheAccessor.cs b/src/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryTokenCacheAccessor.cs index 8513e5f713..27d74f4ef7 100644 --- a/src/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryTokenCacheAccessor.cs +++ b/src/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryTokenCacheAccessor.cs @@ -41,65 +41,49 @@ namespace Microsoft.Identity.Client.PlatformsCommon.Shared /// internal class InMemoryTokenCacheAccessor : ITokenCacheAccessor { - internal readonly IDictionary AccessTokenCacheDictionary = - new ConcurrentDictionary(); + private readonly IDictionary _accessTokenCacheDictionary = + new Dictionary(); - internal readonly IDictionary RefreshTokenCacheDictionary = - new ConcurrentDictionary(); + private readonly IDictionary _refreshTokenCacheDictionary = + new Dictionary(); - internal readonly IDictionary IdTokenCacheDictionary = - new ConcurrentDictionary(); + private readonly IDictionary _idTokenCacheDictionary = + new Dictionary(); - internal readonly IDictionary AccountCacheDictionary = - new ConcurrentDictionary(); + private readonly IDictionary _accountCacheDictionary = + new Dictionary(); - /// - public int RefreshTokenCount => RefreshTokenCacheDictionary.Count; - - /// - public int AccessTokenCount => AccessTokenCacheDictionary.Count; - - /// - public int AccountCount => AccountCacheDictionary.Count; - - /// - public int IdTokenCount => IdTokenCacheDictionary.Count; - - /// - public void ClearRefreshTokens() - { - RefreshTokenCacheDictionary.Clear(); - } - - /// - public void ClearAccessTokens() - { - AccessTokenCacheDictionary.Clear(); - } + private readonly IDictionary _appMetadataDictionary = + new Dictionary(); public void SaveAccessToken(MsalAccessTokenCacheItem item) { - AccessTokenCacheDictionary[item.GetKey().ToString()] = item; + _accessTokenCacheDictionary[item.GetKey().ToString()] = item; } public void SaveRefreshToken(MsalRefreshTokenCacheItem item) { - RefreshTokenCacheDictionary[item.GetKey().ToString()] = item; + _refreshTokenCacheDictionary[item.GetKey().ToString()] = item; } public void SaveIdToken(MsalIdTokenCacheItem item) { - IdTokenCacheDictionary[item.GetKey().ToString()] = item; + _idTokenCacheDictionary[item.GetKey().ToString()] = item; } public void SaveAccount(MsalAccountCacheItem item) { - AccountCacheDictionary[item.GetKey().ToString()] = item; + _accountCacheDictionary[item.GetKey().ToString()] = item; + } + + public void SaveAppMetadata(MsalAppMetadataCacheItem item) + { + _appMetadataDictionary[item.GetKey().ToString()] = item; } public MsalAccessTokenCacheItem GetAccessToken(MsalAccessTokenCacheKey accessTokenKey) { - if (AccessTokenCacheDictionary.TryGetValue(accessTokenKey.ToString(), out var cacheItem)) + if (_accessTokenCacheDictionary.TryGetValue(accessTokenKey.ToString(), out var cacheItem)) { return cacheItem; } @@ -109,7 +93,7 @@ public MsalAccessTokenCacheItem GetAccessToken(MsalAccessTokenCacheKey accessTok public MsalRefreshTokenCacheItem GetRefreshToken(MsalRefreshTokenCacheKey refreshTokenKey) { - if (RefreshTokenCacheDictionary.TryGetValue(refreshTokenKey.ToString(), out var cacheItem)) + if (_refreshTokenCacheDictionary.TryGetValue(refreshTokenKey.ToString(), out var cacheItem)) { return cacheItem; } @@ -119,7 +103,7 @@ public MsalRefreshTokenCacheItem GetRefreshToken(MsalRefreshTokenCacheKey refres public MsalIdTokenCacheItem GetIdToken(MsalIdTokenCacheKey idTokenKey) { - if (IdTokenCacheDictionary.TryGetValue(idTokenKey.ToString(), out var cacheItem)) + if (_idTokenCacheDictionary.TryGetValue(idTokenKey.ToString(), out var cacheItem)) { return cacheItem; } @@ -128,7 +112,7 @@ public MsalIdTokenCacheItem GetIdToken(MsalIdTokenCacheKey idTokenKey) public MsalAccountCacheItem GetAccount(MsalAccountCacheKey accountKey) { - if (AccountCacheDictionary.TryGetValue(accountKey.ToString(), out var cacheItem)) + if (_accountCacheDictionary.TryGetValue(accountKey.ToString(), out var cacheItem)) { return cacheItem; } @@ -138,42 +122,53 @@ public MsalAccountCacheItem GetAccount(MsalAccountCacheKey accountKey) public void DeleteAccessToken(MsalAccessTokenCacheKey cacheKey) { - AccessTokenCacheDictionary.Remove(cacheKey.ToString()); + _accessTokenCacheDictionary.Remove(cacheKey.ToString()); } public void DeleteRefreshToken(MsalRefreshTokenCacheKey cacheKey) { - RefreshTokenCacheDictionary.Remove(cacheKey.ToString()); + _refreshTokenCacheDictionary.Remove(cacheKey.ToString()); } public void DeleteIdToken(MsalIdTokenCacheKey cacheKey) { - IdTokenCacheDictionary.Remove(cacheKey.ToString()); + _idTokenCacheDictionary.Remove(cacheKey.ToString()); } public void DeleteAccount(MsalAccountCacheKey cacheKey) { - AccountCacheDictionary.Remove(cacheKey.ToString()); + _accountCacheDictionary.Remove(cacheKey.ToString()); } - public ICollection GetAllAccessTokens() + public IEnumerable GetAllAccessTokens() { - return new ReadOnlyCollection(AccessTokenCacheDictionary.Values.ToList()); + return new ReadOnlyCollection( + _accessTokenCacheDictionary.Values.ToList()); } - public ICollection GetAllRefreshTokens() + public IEnumerable GetAllRefreshTokens() { - return new ReadOnlyCollection(RefreshTokenCacheDictionary.Values.ToList()); + return new ReadOnlyCollection( + _refreshTokenCacheDictionary.Values.ToList()); + + } + + public IEnumerable GetAllIdTokens() + { + return new ReadOnlyCollection( + _idTokenCacheDictionary.Values.ToList()); } - public ICollection GetAllIdTokens() + public IEnumerable GetAllAccounts() { - return new ReadOnlyCollection(IdTokenCacheDictionary.Values.ToList()); + return new ReadOnlyCollection( + _accountCacheDictionary.Values.ToList()); } - public ICollection GetAllAccounts() + public IEnumerable GetAllAppMetadata() { - return new ReadOnlyCollection(AccountCacheDictionary.Values.ToList()); + return new ReadOnlyCollection( + _appMetadataDictionary.Values.ToList()); } public void SetiOSKeychainSecurityGroup(string keychainSecurityGroup) @@ -183,10 +178,20 @@ public void SetiOSKeychainSecurityGroup(string keychainSecurityGroup) public void Clear() { - AccessTokenCacheDictionary.Clear(); - RefreshTokenCacheDictionary.Clear(); - IdTokenCacheDictionary.Clear(); - AccountCacheDictionary.Clear(); + _accessTokenCacheDictionary.Clear(); + _refreshTokenCacheDictionary.Clear(); + _idTokenCacheDictionary.Clear(); + _accountCacheDictionary.Clear(); + // app metadata isn't removable + } + + public MsalAppMetadataCacheItem GetAppMetadata(MsalAppMetadataCacheKey appMetadataKey) + { + if (_appMetadataDictionary.TryGetValue(appMetadataKey.ToString(), out var cacheItem)) + { + return cacheItem; + } + return null; } } } diff --git a/src/Microsoft.Identity.Client/TokenCache.cs b/src/Microsoft.Identity.Client/TokenCache.cs index 64d7c2945a..ccfcec5099 100644 --- a/src/Microsoft.Identity.Client/TokenCache.cs +++ b/src/Microsoft.Identity.Client/TokenCache.cs @@ -63,6 +63,8 @@ public sealed class TokenCache : ITokenCacheInternal private ICoreLogger _logger => ServiceBundle.DefaultLogger; private readonly ITokenCacheBlobStorage _defaultTokenCacheBlobStorage; + private readonly IFeatureFlags _featureFlags; + private TokenCacheCallback _userConfiguredBeforeAccess; private TokenCacheCallback _userConfiguredAfterAccess; private TokenCacheCallback _userConfiguredBeforeWrite; @@ -83,18 +85,19 @@ public sealed class TokenCache : ITokenCacheInternal /// The recommended way to get a cache is by using /// and IConfidentialClientApplication.AppTokenCache once the app is created. /// - public TokenCache() + public TokenCache() : this((IServiceBundle)null) { - ServiceBundle = null; - var proxy = PlatformProxyFactory.CreatePlatformProxy(null); - _accessor = proxy.CreateTokenCacheAccessor(); - _defaultTokenCacheBlobStorage = proxy.CreateTokenCacheBlobStorage(); - LegacyCachePersistence = proxy.CreateLegacyCachePersistence(); } - internal TokenCache(IServiceBundle serviceBundle) : this() + internal TokenCache(IServiceBundle serviceBundle) { SetServiceBundle(serviceBundle); + + var proxy = serviceBundle?.PlatformProxy ?? PlatformProxyFactory.CreatePlatformProxy(null); + _accessor = proxy.CreateTokenCacheAccessor(); + _featureFlags = proxy.GetFeatureFlags(); + _defaultTokenCacheBlobStorage = proxy.CreateTokenCacheBlobStorage(); + LegacyCachePersistence = proxy.CreateLegacyCachePersistence(); } internal void SetServiceBundle(IServiceBundle serviceBundle) @@ -111,11 +114,11 @@ internal void SetServiceBundle(IServiceBundle serviceBundle) /// /// internal TokenCache(IServiceBundle serviceBundle, ILegacyCachePersistence legacyCachePersistenceForTest) + : this(serviceBundle) { - ServiceBundle = serviceBundle; - _accessor = ServiceBundle.PlatformProxy.CreateTokenCacheAccessor(); + SetServiceBundle(serviceBundle); + LegacyCachePersistence = legacyCachePersistenceForTest; - _defaultTokenCacheBlobStorage = ServiceBundle.PlatformProxy.CreateTokenCacheBlobStorage(); } /// @@ -134,6 +137,7 @@ internal TokenCache(IServiceBundle serviceBundle, ILegacyCachePersistence legacy internal string ClientId => ServiceBundle.Config.ClientId; + #region Notifications /// /// Notification method called before any library method accesses the cache. /// @@ -202,7 +206,9 @@ internal void OnBeforeWrite(TokenCacheNotificationArgs args) BeforeWrite?.Invoke(args); } - Tuple ITokenCacheInternal.SaveAccessAndRefreshToken( + #endregion + + Tuple ITokenCacheInternal.SaveTokenResponse( AuthenticationRequestParameters requestParams, MsalTokenResponse response) { @@ -268,35 +274,40 @@ Tuple ITokenCacheInternal.SaveAc _accessor.SaveAccessToken(msalAccessTokenCacheItem); - if (idToken != null) - { - _accessor.SaveIdToken(msalIdTokenCacheItem); - - var msalAccountCacheItem = new MsalAccountCacheItem(preferredEnvironmentHost, response, preferredUsername, tenantId); - - _accessor.SaveAccount(msalAccountCacheItem); - } + if (idToken != null) + { + _accessor.SaveIdToken(msalIdTokenCacheItem); + var msalAccountCacheItem = new MsalAccountCacheItem(preferredEnvironmentHost, response, preferredUsername, tenantId); + _accessor.SaveAccount(msalAccountCacheItem); + } - // if server returns the refresh token back, save it in the cache. - if (response.RefreshToken != null) + // if server returns the refresh token back, save it in the cache. + if (response.RefreshToken != null) + { + msalRefreshTokenCacheItem = new MsalRefreshTokenCacheItem(preferredEnvironmentHost, requestParams.ClientId, response); + if (!_featureFlags.IsFociEnabled) { - msalRefreshTokenCacheItem = new MsalRefreshTokenCacheItem(preferredEnvironmentHost, requestParams.ClientId, response); - requestParams.RequestContext.Logger.Info("Saving RT in cache..."); - _accessor.SaveRefreshToken(msalRefreshTokenCacheItem); + msalRefreshTokenCacheItem.FamilyId = null; } - // save RT in ADAL cache for public clients - // do not save RT in ADAL cache for MSAL B2C scenarios - if (!requestParams.IsClientCredentialRequest && !requestParams.AuthorityInfo.AuthorityType.Equals(AppConfig.AuthorityType.B2C)) - { - CacheFallbackOperations.WriteAdalRefreshToken( - _logger, - LegacyCachePersistence, - msalRefreshTokenCacheItem, - msalIdTokenCacheItem, - Authority.CreateAuthorityUriWithHost(requestParams.TenantUpdatedCanonicalAuthority, preferredEnvironmentHost), - msalIdTokenCacheItem.IdToken.ObjectId, response.Scope); - } + requestParams.RequestContext.Logger.Info("Saving RT in cache..."); + _accessor.SaveRefreshToken(msalRefreshTokenCacheItem); + } + + UpdateAppMetadata(requestParams.ClientId, preferredEnvironmentHost, response.FamilyId); + + // save RT in ADAL cache for public clients + // do not save RT in ADAL cache for MSAL B2C scenarios + if (!requestParams.IsClientCredentialRequest && !requestParams.AuthorityInfo.AuthorityType.Equals(AppConfig.AuthorityType.B2C)) + { + CacheFallbackOperations.WriteAdalRefreshToken( + _logger, + LegacyCachePersistence, + msalRefreshTokenCacheItem, + msalIdTokenCacheItem, + Authority.CreateAuthorityUriWithHost(requestParams.TenantUpdatedCanonicalAuthority, preferredEnvironmentHost), + msalIdTokenCacheItem.IdToken.ObjectId, response.Scope); + } } finally @@ -315,6 +326,15 @@ Tuple ITokenCacheInternal.SaveAc } } + private void UpdateAppMetadata(string clientId, string environment, string familyId) + { + if (_featureFlags.IsFociEnabled) + { + var metadataCacheItem = new MsalAppMetadataCacheItem(clientId, environment, familyId); + _accessor.SaveAppMetadata(metadataCacheItem); + } + } + private void DeleteAccessTokensWithIntersectingScopes(AuthenticationRequestParameters requestParams, ISet environmentAliases, string tenantId, SortedSet scopeSet, string homeAccountId) { @@ -543,7 +563,9 @@ private string GetAccessTokenExpireLogMessageContent(MsalAccessTokenCacheItem ms msalAccessTokenCacheItem.ExtendedExpiresOn); } - async Task ITokenCacheInternal.FindRefreshTokenAsync(AuthenticationRequestParameters requestParams) + async Task ITokenCacheInternal.FindRefreshTokenAsync( + AuthenticationRequestParameters requestParams, + string familyId) { using (ServiceBundle.TelemetryManager.CreateTelemetryHelper(requestParams.RequestContext.TelemetryRequestId, requestParams.RequestContext.ClientId, new CacheEvent(CacheEvent.TokenCacheLookup) { TokenType = CacheEvent.TokenTypes.RT })) @@ -573,30 +595,24 @@ async Task ITokenCacheInternal.FindRefreshTokenAsync( Account = requestParams.Account }; - MsalRefreshTokenCacheKey key = new MsalRefreshTokenCacheKey( - preferredEnvironmentHost, requestParams.ClientId, requestParams.Account?.HomeAccountId?.Identifier); + // make sure to check preferredEnvironmentHost first + var allEnvAliases = new List() { preferredEnvironmentHost }; + allEnvAliases.AddRange(environmentAliases); + + var keysAcrossEnvs = allEnvAliases.Select(ea => new MsalRefreshTokenCacheKey( + ea, + requestParams.ClientId, + requestParams.Account?.HomeAccountId?.Identifier, + familyId)); + OnBeforeAccess(args); try { - MsalRefreshTokenCacheItem msalRefreshTokenCacheItem = _accessor.GetRefreshToken(key); - - // trying to find rt by authority aliases - if (msalRefreshTokenCacheItem == null) - { - var refreshTokens = _accessor.GetAllRefreshTokens(); - - foreach (var refreshToken in refreshTokens) - { - if (refreshToken.ClientId.Equals(requestParams.ClientId, StringComparison.OrdinalIgnoreCase) && - environmentAliases.Contains(refreshToken.Environment) && - requestParams.Account?.HomeAccountId?.Identifier == refreshToken.HomeAccountId) - { - msalRefreshTokenCacheItem = refreshToken; - continue; - } - } - } + // Try to load from all env aliases, but stop at the first valid one + MsalRefreshTokenCacheItem msalRefreshTokenCacheItem = keysAcrossEnvs + .Select(key => _accessor.GetRefreshToken(key)) + .FirstOrDefault(item => item != null); requestParams.RequestContext.Logger.Info("Refresh token found in the cache? - " + (msalRefreshTokenCacheItem != null)); @@ -607,19 +623,22 @@ async Task ITokenCacheInternal.FindRefreshTokenAsync( requestParams.RequestContext.Logger.Info("Checking ADAL cache for matching RT"); - if (requestParams.Account == null) + // ADAL legacy cache does not store FRTs + if (requestParams.Account != null && string.IsNullOrEmpty(familyId)) { - return null; + return CacheFallbackOperations.GetAdalEntryForMsal( + _logger, + LegacyCachePersistence, + preferredEnvironmentHost, + environmentAliases, + requestParams.ClientId, + requestParams.LoginHint, + requestParams.Account.HomeAccountId?.Identifier, + null); } - return CacheFallbackOperations.GetAdalEntryForMsal( - _logger, - LegacyCachePersistence, - preferredEnvironmentHost, - environmentAliases, - requestParams.ClientId, - requestParams.LoginHint, - requestParams.Account.HomeAccountId?.Identifier, - null); + + return null; + } finally { @@ -629,6 +648,55 @@ async Task ITokenCacheInternal.FindRefreshTokenAsync( } } + async Task ITokenCacheInternal.IsFociMemberAsync(AuthenticationRequestParameters requestParams, string familyId) + { + var logger = requestParams.RequestContext.Logger; + if (requestParams?.AuthorityInfo?.CanonicalAuthority == null) + { + logger.Warning("No authority details, can't check app metadta. Returning unkown"); + return null; + } + + var instanceDiscoveryMetadataEntry = await GetCachedOrDiscoverAuthorityMetaDataAsync( + requestParams.AuthorityInfo.CanonicalAuthority, + requestParams.RequestContext).ConfigureAwait(false); + + var environmentAliases = GetEnvironmentAliases( + requestParams.AuthorityInfo.CanonicalAuthority, + instanceDiscoveryMetadataEntry); + + TokenCacheNotificationArgs args = new TokenCacheNotificationArgs + { + TokenCache = this, + ClientId = ClientId, + Account = requestParams?.Account, + HasStateChanged = false + }; + + //TODO: bogavril - is the env ok here? Can I cache it or pass it in? + MsalAppMetadataCacheItem appMetadata; + lock (LockObject) + { + OnBeforeAccess(args); + + appMetadata = + environmentAliases + .Select(env => _accessor.GetAppMetadata(new MsalAppMetadataCacheKey(ClientId, env))) + .FirstOrDefault(item => item != null); + + OnAfterAccess(args); + } + + if (appMetadata == null) + { + logger.Warning("No app metadata found. Returning unkown"); + return null; + } + + return appMetadata.FamilyId == familyId; + } + + /// public void SetIosKeychainSecurityGroup(string securityGroup) { @@ -745,7 +813,7 @@ IEnumerable ITokenCacheInternal.GetAccounts(string authority) OnBeforeAccess(args); try { - tokenCacheItems = ((ITokenCacheInternal)this).GetAllRefreshTokens(true); + tokenCacheItems = ((ITokenCacheInternal)this).GetAllRefreshTokens(false); accountCacheItems = ((ITokenCacheInternal)this).GetAllAccounts(); adalUsersResult = CacheFallbackOperations.GetAllAdalUsersForMsal(_logger, LegacyCachePersistence, ClientId); } @@ -784,7 +852,7 @@ IEnumerable ITokenCacheInternal.GetAccounts(string authority) } var accounts = new List(clientInfoToAccountMap.Values); - List uniqueUserNames = clientInfoToAccountMap.Values.Select(o => o.Username).Distinct().ToList(); + var uniqueUserNames = clientInfoToAccountMap.Values.Select(o => o.Username).Distinct().ToList(); foreach (AdalUserInfo user in adalUsersWithoutClientInfo) { @@ -839,6 +907,8 @@ IEnumerable ITokenCacheInternal.GetAllAccounts() } } + + #region Removal methods void ITokenCacheInternal.RemoveAccount(IAccount account, RequestContext requestContext) { lock (LockObject) @@ -884,7 +954,9 @@ void ITokenCacheInternal.RemoveMsalAccount(IAccount account, RequestContext requ // adalv3 account return; } - var allRefreshTokens = ((ITokenCacheInternal)this).GetAllRefreshTokens(true) + + // Delete ALL refresh tokens associated with this account + var allRefreshTokens = ((ITokenCacheInternal)this).GetAllRefreshTokens(false) .Where(item => item.HomeAccountId.Equals(account.HomeAccountId.Identifier, StringComparison.OrdinalIgnoreCase)) .ToList(); @@ -894,7 +966,7 @@ void ITokenCacheInternal.RemoveMsalAccount(IAccount account, RequestContext requ } requestContext.Logger.Info("Deleted refresh token count - " + allRefreshTokens.Count); - IList allAccessTokens = ((ITokenCacheInternal)this).GetAllAccessTokens(true) + IList allAccessTokens = ((ITokenCacheInternal)this).GetAllAccessTokens(false) .Where(item => item.HomeAccountId.Equals(account.HomeAccountId.Identifier, StringComparison.OrdinalIgnoreCase)) .ToList(); foreach (MsalAccessTokenCacheItem accessTokenCacheItem in allAccessTokens) @@ -904,7 +976,7 @@ void ITokenCacheInternal.RemoveMsalAccount(IAccount account, RequestContext requ requestContext.Logger.Info("Deleted access token count - " + allAccessTokens.Count); - var allIdTokens = ((ITokenCacheInternal)this).GetAllIdTokens(true) + var allIdTokens = ((ITokenCacheInternal)this).GetAllIdTokens(false) .Where(item => item.HomeAccountId.Equals(account.HomeAccountId.Identifier, StringComparison.OrdinalIgnoreCase)) .ToList(); foreach (MsalIdTokenCacheItem idTokenCacheItem in allIdTokens) @@ -913,6 +985,13 @@ void ITokenCacheInternal.RemoveMsalAccount(IAccount account, RequestContext requ } requestContext.Logger.Info("Deleted Id token count - " + allIdTokens.Count); + + ((ITokenCacheInternal)this).GetAllAccounts() + .Where(item => item.HomeAccountId.Equals(account.HomeAccountId.Identifier, StringComparison.OrdinalIgnoreCase) && + item.PreferredUsername.Equals(account.Username, StringComparison.OrdinalIgnoreCase)) + .ToList() + .ForEach(accItem => _accessor.DeleteAccount(accItem.GetKey())); + } internal void RemoveAdalUser(IAccount account) @@ -967,6 +1046,10 @@ void ITokenCacheInternal.ClearMsalCache() _accessor.Clear(); } + #endregion + + #region Serialization + private bool UserHasConfiguredBlobSerialization() { return _userConfiguredBeforeAccess != null || @@ -974,7 +1057,7 @@ private bool UserHasConfiguredBlobSerialization() _userConfiguredBeforeWrite != null; } -#if !ANDROID_BUILDTIME && !iOS_BUILDTIME +#if !ANDROID_BUILDTIME && !iOS_BUILDTIME /// /// Sets a delegate to be notified before any library method accesses the cache. This gives an option to the @@ -1242,7 +1325,9 @@ private static void GuardOnMobilePlatforms() "For more details about custom token cache serialization, visit https://aka.ms/msal-net-serialization"); #endif } -#endif // !ANDROID_BUILDTIME && !iOS_BUILDTIME + +#endif // !ANDROID_BUILDTIME && !iOS_BUILDTIME + #endregion } } diff --git a/tests/Microsoft.Identity.Test.Common/Core/Helpers/AssertException.cs b/tests/Microsoft.Identity.Test.Common/Core/Helpers/AssertException.cs index a3cfc4fffe..c27858b403 100644 --- a/tests/Microsoft.Identity.Test.Common/Core/Helpers/AssertException.cs +++ b/tests/Microsoft.Identity.Test.Common/Core/Helpers/AssertException.cs @@ -1,4 +1,4 @@ -//---------------------------------------------------------------------- +//---------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. // All rights reserved. @@ -115,6 +115,11 @@ public static T TaskThrows(Func testCode, bool allowDerived = false) if (exception is AggregateException aggEx) { + if (aggEx.InnerException.GetType() == typeof(AssertFailedException)) + { + throw aggEx.InnerException; + } + var exceptionsMatching = aggEx.InnerExceptions.OfType().ToList(); if (!exceptionsMatching.Any()) diff --git a/tests/Microsoft.Identity.Test.Common/Core/Helpers/TokenCacheExtensions.cs b/tests/Microsoft.Identity.Test.Common/Core/Helpers/TokenCacheExtensions.cs index 5e50cacd67..60655ad5a6 100644 --- a/tests/Microsoft.Identity.Test.Common/Core/Helpers/TokenCacheExtensions.cs +++ b/tests/Microsoft.Identity.Test.Common/Core/Helpers/TokenCacheExtensions.cs @@ -28,6 +28,7 @@ using System; using System.Collections.Generic; using Microsoft.Identity.Client; +using Microsoft.Identity.Client.Cache; using Microsoft.Identity.Client.Cache.Items; using Microsoft.Identity.Client.Core; @@ -107,5 +108,21 @@ public static MsalAccountCacheItem GetAccount( return null; } + + public static void ClearAccessTokens(this ITokenCacheAccessor accessor) + { + foreach (var item in accessor.GetAllAccessTokens()) + { + accessor.DeleteAccessToken(item.GetKey()); + } + } + + public static void ClearRefreshTokens(this ITokenCacheAccessor accessor) + { + foreach (var item in accessor.GetAllRefreshTokens()) + { + accessor.DeleteRefreshToken(item.GetKey()); + } + } } -} \ No newline at end of file +} diff --git a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHelpers.cs b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHelpers.cs index 6813776e00..c33dc090a5 100644 --- a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHelpers.cs +++ b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHelpers.cs @@ -56,6 +56,15 @@ internal static class MockHelpers ":\"" + CreateIdToken(MsalTestConstants.UniqueId, MsalTestConstants.DisplayableId) + "\",\"id_token_expires_in\":\"3600\"}"; + public static readonly string FociTokenResponse = + "{\"token_type\":\"Bearer\",\"expires_in\":\"3599\",\"scope\":" + + "\"r1/scope1 r1/scope2\",\"access_token\":\"some-access-token\"" + + ",\"foci\":\"1\"" + + ",\"refresh_token\":\"OAAsomethingencryptedQwgAA\",\"client_info\"" + + ":\"" + CreateClientInfo() + "\",\"id_token\"" + + ":\"" + CreateIdToken(MsalTestConstants.UniqueId, MsalTestConstants.DisplayableId) + + "\",\"id_token_expires_in\":\"3600\"}"; + public static string CreateClientInfo() { return CreateClientInfo(MsalTestConstants.Uid, MsalTestConstants.Utid); @@ -125,9 +134,10 @@ public static HttpResponseMessage CreateSuccessTokenResponseMessage(string scope scopes, idToken, clientInfo)); } - public static HttpResponseMessage CreateSuccessTokenResponseMessage() + public static HttpResponseMessage CreateSuccessTokenResponseMessage(bool foci = false) { - return CreateSuccessResponseMessage(DefaultTokenResponse); + return CreateSuccessResponseMessage( + foci ? FociTokenResponse : DefaultTokenResponse); } public static HttpResponseMessage CreateInvalidGrantTokenResponseMessage() @@ -200,16 +210,17 @@ public static HttpResponseMessage CreateSuccessfulClientCredentialTokenResponseM "{\"token_type\":\"Bearer\",\"expires_in\":\"3599\",\"access_token\":\"" + token + "\"}"); } - public static HttpResponseMessage CreateSuccessTokenResponseMessage(string uniqueId, string displayableId, string[] scope) + public static HttpResponseMessage CreateSuccessTokenResponseMessage(string uniqueId, string displayableId, string[] scope, bool foci = false) { string idToken = CreateIdToken(uniqueId, displayableId, MsalTestConstants.Utid); HttpResponseMessage responseMessage = new HttpResponseMessage(HttpStatusCode.OK); - HttpContent content = - new StringContent("{\"token_type\":\"Bearer\",\"expires_in\":\"3599\",\"scope\":\"" + + string stringContent = "{\"token_type\":\"Bearer\",\"expires_in\":\"3599\",\"scope\":\"" + scope.AsSingleString() + "\",\"access_token\":\"some-access-token\",\"refresh_token\":\"OAAsomethingencryptedQwgAA\",\"id_token\":\"" + idToken + - "\",\"id_token_expires_in\":\"3600\",\"client_info\":\"" + CreateClientInfo() + "\"}"); + (foci ? "\",\"foci\":\"1" : "") + + "\",\"id_token_expires_in\":\"3600\",\"client_info\":\"" + CreateClientInfo() + "\"}"; + HttpContent content = new StringContent(stringContent); responseMessage.Content = content; return responseMessage; } diff --git a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManager.cs b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManager.cs index 21a8e6e2ef..70ad7f423f 100644 --- a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManager.cs +++ b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManager.cs @@ -68,6 +68,11 @@ public void AddMockHandler(HttpMessageHandler handler) protected override HttpClient GetHttpClient() { + if (_httpMessageHandlerQueue.Count == 0) + { + Assert.Fail("The MockHttpManager's queue is empty. Cannot serve another response"); + } + var messageHandler = _httpMessageHandlerQueue.Dequeue(); var httpClient = new HttpClient(messageHandler) { @@ -80,4 +85,4 @@ protected override HttpClient GetHttpClient() return httpClient; } } -} \ No newline at end of file +} diff --git a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManagerExtensions.cs b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManagerExtensions.cs index 0819fc8c60..abb8c6388d 100644 --- a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManagerExtensions.cs +++ b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManagerExtensions.cs @@ -66,7 +66,8 @@ public static void AddSuccessTokenResponseMockHandlerForPost( this MockHttpManager httpManager, string authority, IDictionary bodyParameters = null, - IDictionary queryParameters = null) + IDictionary queryParameters = null, + bool foci = false) { httpManager.AddMockHandler( new MockHttpMessageHandler() @@ -75,7 +76,7 @@ public static void AddSuccessTokenResponseMockHandlerForPost( ExpectedMethod = HttpMethod.Post, ExpectedPostData = bodyParameters, ExpectedQueryParams = queryParameters, - ResponseMessage = MockHelpers.CreateSuccessTokenResponseMessage() + ResponseMessage = MockHelpers.CreateSuccessTokenResponseMessage(foci) }); } @@ -167,4 +168,4 @@ public static void AddFailingRequest(this MockHttpManager httpManager, Exception }); } } -} \ No newline at end of file +} diff --git a/tests/Microsoft.Identity.Test.Common/Core/Mocks/TokenCacheHelper.cs b/tests/Microsoft.Identity.Test.Common/Core/Mocks/TokenCacheHelper.cs index f430a1e7a1..f4ef400843 100644 --- a/tests/Microsoft.Identity.Test.Common/Core/Mocks/TokenCacheHelper.cs +++ b/tests/Microsoft.Identity.Test.Common/Core/Mocks/TokenCacheHelper.cs @@ -56,11 +56,12 @@ internal void PopulateCacheForClientCredential(ITokenCacheAccessor accessor) internal void PopulateCache( ITokenCacheAccessor accessor, string uid = MsalTestConstants.Uid, - string utid = MsalTestConstants.Utid) + string utid = MsalTestConstants.Utid, + string clientId = MsalTestConstants.ClientId) { MsalAccessTokenCacheItem atItem = new MsalAccessTokenCacheItem( MsalTestConstants.ProductionPrefCacheEnvironment, - MsalTestConstants.ClientId, + clientId, MsalTestConstants.Scope.AsSingleString(), utid, "", @@ -72,8 +73,8 @@ internal void PopulateCache( accessor.SaveAccessToken(atItem); var idTokenCacheItem = new MsalIdTokenCacheItem( - MsalTestConstants.ProductionPrefCacheEnvironment, - MsalTestConstants.ClientId, + MsalTestConstants.ProductionPrefCacheEnvironment, + clientId, MockHelpers.CreateIdToken(MsalTestConstants.UniqueId + "more", MsalTestConstants.DisplayableId), MockHelpers.CreateClientInfo(uid, utid), utid); @@ -94,7 +95,7 @@ internal void PopulateCache( atItem = new MsalAccessTokenCacheItem( MsalTestConstants.ProductionPrefCacheEnvironment, - MsalTestConstants.ClientId, + clientId, MsalTestConstants.ScopeForAnotherResource.AsSingleString(), utid, "", @@ -105,7 +106,14 @@ internal void PopulateCache( // add another access token accessor.SaveAccessToken(atItem); - AddRefreshTokenToCache(accessor,uid, utid, MsalTestConstants.Name); + AddRefreshTokenToCache(accessor,uid, utid, clientId); + + var appMetadataItem = new MsalAppMetadataCacheItem( + clientId, + MsalTestConstants.ProductionPrefCacheEnvironment, + null); + + accessor.SaveAppMetadata(appMetadataItem); } internal void PopulateCacheWithOneAccessToken(ITokenCacheAccessor accessor) @@ -146,13 +154,17 @@ internal void PopulateCacheWithOneAccessToken(ITokenCacheAccessor accessor) new DateTimeOffset(DateTime.UtcNow + TimeSpan.FromSeconds(ValidExtendedExpiresIn)), MockHelpers.CreateClientInfo()); - AddRefreshTokenToCache(accessor, MsalTestConstants.Uid, MsalTestConstants.Utid, MsalTestConstants.Name); + AddRefreshTokenToCache(accessor, MsalTestConstants.Uid, MsalTestConstants.Utid); } - public static void AddRefreshTokenToCache(ITokenCacheAccessor accessor, string uid, string utid, string name) + public static void AddRefreshTokenToCache( + ITokenCacheAccessor accessor, + string uid, + string utid, + string clientId = MsalTestConstants.ClientId) { var rtItem = new MsalRefreshTokenCacheItem - (MsalTestConstants.ProductionPrefCacheEnvironment, MsalTestConstants.ClientId, "someRT", MockHelpers.CreateClientInfo(uid, utid)); + (MsalTestConstants.ProductionPrefCacheEnvironment, clientId, "someRT", MockHelpers.CreateClientInfo(uid, utid)); accessor.SaveRefreshToken(rtItem); } @@ -177,4 +189,4 @@ public static void AddAccountToCache(ITokenCacheAccessor accessor, string uid, s accessor.SaveAccount(accountCacheItem); } } -} \ No newline at end of file +} diff --git a/tests/Microsoft.Identity.Test.Common/MsalTestConstants.cs b/tests/Microsoft.Identity.Test.Common/MsalTestConstants.cs index db82a7d7a2..7e52bf3e4f 100644 --- a/tests/Microsoft.Identity.Test.Common/MsalTestConstants.cs +++ b/tests/Microsoft.Identity.Test.Common/MsalTestConstants.cs @@ -29,6 +29,9 @@ using System.Globalization; using Microsoft.Identity.Client; using Microsoft.Identity.Client.Cache; +using Microsoft.Identity.Client.OAuth2; +using Microsoft.Identity.Client.Utils; +using Microsoft.Identity.Test.Common.Core.Mocks; namespace Microsoft.Identity.Test.Unit { @@ -67,7 +70,8 @@ internal static class MsalTestConstants public const string B2CLoginAuthorityMoonCake = "https://sometenantid.b2clogin.cn/tfp/sometenantid/policy/"; public const string B2CLoginAuthorityBlackforest = "https://sometenantid.b2clogin.de/tfp/sometenantid/policy/"; public const string ClientId = "d3adb33f-c0de-ed0c-c0de-deadb33fc0d3"; - public static readonly string ClientId_1 = "d3adb33f-c1de-ed1c-c1de-deadb33fc1d3"; + public const string ClientId2 = "d3adb33f-c1de-ed1c-c1de-deadb33fc1d3"; + public const string FamilyId = "1"; public const string UniqueId = "unique_id"; public const string IdentityProvider = "my-idp"; public const string Name = "First Last"; @@ -124,6 +128,21 @@ public static string CreateUserIdentifier(string uid, string utid) return string.Format(CultureInfo.InvariantCulture, "{0}.{1}", uid, utid); } + public static MsalTokenResponse CreateMsalTokenResponse() + { + return new MsalTokenResponse + { + IdToken = MockHelpers.CreateIdToken(MsalTestConstants.UniqueId, MsalTestConstants.DisplayableId), + AccessToken = "access-token", + ClientInfo = MockHelpers.CreateClientInfo(), + ExpiresIn = 3599, + CorrelationId = "correlation-id", + RefreshToken = "refresh-token", + Scope = MsalTestConstants.Scope.AsSingleString(), + TokenType = "Bearer" + }; + } + public static readonly Account User = new Account(UserIdentifier, DisplayableId, ProductionPrefNetworkEnvironment); public static readonly string OnPremiseAuthority = "https://fs.contoso.com/adfs/"; @@ -149,4 +168,4 @@ public static string CreateUserIdentifier(string uid, string utid) public static readonly ClientCredential CredentialWithSecret = new ClientCredential(ClientSecret); #endif } -} \ No newline at end of file +} diff --git a/tests/Microsoft.Identity.Test.SideBySide/UnifiedCacheTests.cs b/tests/Microsoft.Identity.Test.SideBySide/UnifiedCacheTests.cs index 05374de015..32b3af93af 100644 --- a/tests/Microsoft.Identity.Test.SideBySide/UnifiedCacheTests.cs +++ b/tests/Microsoft.Identity.Test.SideBySide/UnifiedCacheTests.cs @@ -459,10 +459,10 @@ public async Task UnifiedCache_Adalv3ToAdalV4ToMsal2MigrationIntegrationTestAsyn Assert.IsNotNull(account.Environment); // validate that Adal writes only RT and Account cache entities in Msal format - Assert.AreEqual(0, ((ITokenCacheInternal)msalCache).Accessor.AccessTokenCount); - Assert.AreEqual(1, ((ITokenCacheInternal)msalCache).Accessor.RefreshTokenCount); - Assert.AreEqual(0, ((ITokenCacheInternal)msalCache).Accessor.IdTokenCount); - Assert.AreEqual(1, ((ITokenCacheInternal)msalCache).Accessor.AccountCount); + Assert.AreEqual(0, ((ITokenCacheInternal)msalCache).Accessor.GetAllAccessTokens().Count()); + Assert.AreEqual(1, ((ITokenCacheInternal)msalCache).Accessor.GetAllRefreshTokens().Count()); + Assert.AreEqual(0, ((ITokenCacheInternal)msalCache).Accessor.GetAllIdTokens().Count()); + Assert.AreEqual(1, ((ITokenCacheInternal)msalCache).Accessor.GetAllAccounts().Count()); // make sure that adal v4 RT is usable by Msal msalAuthResult = await msalPublicClient.AcquireTokenSilentAsync(MsalScopes, account).ConfigureAwait(false); @@ -528,10 +528,10 @@ private void AssertMsalCacheIsEmpty() TokenCache = msalCache }); - Assert.AreEqual(0, ((ITokenCacheInternal)msalCache).Accessor.AccessTokenCount); - Assert.AreEqual(0, ((ITokenCacheInternal)msalCache).Accessor.RefreshTokenCount); - Assert.AreEqual(0, ((ITokenCacheInternal)msalCache).Accessor.IdTokenCount); - Assert.AreEqual(0, ((ITokenCacheInternal)msalCache).Accessor.AccountCount); + Assert.AreEqual(0, ((ITokenCacheInternal)msalCache).Accessor.GetAllAccessTokens().Count()); + Assert.AreEqual(0, ((ITokenCacheInternal)msalCache).Accessor.GetAllRefreshTokens().Count()); + Assert.AreEqual(0, ((ITokenCacheInternal)msalCache).Accessor.GetAllIdTokens().Count()); + Assert.AreEqual(0, ((ITokenCacheInternal)msalCache).Accessor.GetAllAccounts().Count()); } private void AssertNoCredentialsInMsalCache() @@ -541,9 +541,10 @@ private void AssertNoCredentialsInMsalCache() TokenCache = msalCache }); - Assert.AreEqual(0, ((ITokenCacheInternal)msalCache).Accessor.AccessTokenCount); - Assert.AreEqual(0, ((ITokenCacheInternal)msalCache).Accessor.RefreshTokenCount); - Assert.AreEqual(0, ((ITokenCacheInternal)msalCache).Accessor.IdTokenCount); + Assert.AreEqual(0, ((ITokenCacheInternal)msalCache).Accessor.GetAllAccessTokens().Count()); + Assert.AreEqual(0, ((ITokenCacheInternal)msalCache).Accessor.GetAllRefreshTokens().Count()); + Assert.AreEqual(0, ((ITokenCacheInternal)msalCache).Accessor.GetAllIdTokens().Count()); + Assert.AreEqual(0, ((ITokenCacheInternal)msalCache).Accessor.GetAllAccounts().Count()); } } } diff --git a/tests/Microsoft.Identity.Test.Unit.net45/ApplicationGrantIntegrationTest.cs b/tests/Microsoft.Identity.Test.Unit.net45/ApplicationGrantIntegrationTest.cs index c6e50c458f..8f7c7669e9 100644 --- a/tests/Microsoft.Identity.Test.Unit.net45/ApplicationGrantIntegrationTest.cs +++ b/tests/Microsoft.Identity.Test.Unit.net45/ApplicationGrantIntegrationTest.cs @@ -26,6 +26,7 @@ //------------------------------------------------------------------------------ using System; +using System.Linq; using System.Threading.Tasks; using Microsoft.Identity.Client; using Microsoft.Identity.Client.AppConfig; @@ -68,19 +69,19 @@ public async Task ApplicationGrantIntegrationTestAsync() Assert.IsNull(res.Account); // make sure user cache is empty - Assert.AreEqual(0, userCache.Accessor.AccessTokenCount); - Assert.AreEqual(0, userCache.Accessor.RefreshTokenCount); - Assert.AreEqual(0, userCache.Accessor.IdTokenCount); - Assert.AreEqual(0, userCache.Accessor.AccountCount); + Assert.AreEqual(0, userCache.Accessor.GetAllAccessTokens().Count()); + Assert.AreEqual(0, userCache.Accessor.GetAllRefreshTokens().Count()); + Assert.AreEqual(0, userCache.Accessor.GetAllIdTokens().Count()); + Assert.AreEqual(0, userCache.Accessor.GetAllAccounts().Count()); // make sure nothing was written to legacy cache Assert.IsNull(userCache.LegacyPersistence.LoadCache()); // make sure only AT entity was stored in the App msal cache - Assert.AreEqual(1, appCache.Accessor.AccessTokenCount); - Assert.AreEqual(0, appCache.Accessor.RefreshTokenCount); - Assert.AreEqual(0, appCache.Accessor.IdTokenCount); - Assert.AreEqual(0, appCache.Accessor.AccountCount); + Assert.AreEqual(1, userCache.Accessor.GetAllAccessTokens().Count()); + Assert.AreEqual(0, appCache.Accessor.GetAllRefreshTokens().Count()); + Assert.AreEqual(0, appCache.Accessor.GetAllIdTokens().Count()); + Assert.AreEqual(0, appCache.Accessor.GetAllAccounts().Count()); Assert.IsNull(appCache.LegacyPersistence.LoadCache()); diff --git a/tests/Microsoft.Identity.Test.Unit.net45/AuthorityAliasesTests.cs b/tests/Microsoft.Identity.Test.Unit.net45/AuthorityAliasesTests.cs index 9765f82b2d..9fca44bbdc 100644 --- a/tests/Microsoft.Identity.Test.Unit.net45/AuthorityAliasesTests.cs +++ b/tests/Microsoft.Identity.Test.Unit.net45/AuthorityAliasesTests.cs @@ -210,4 +210,4 @@ private void ValidateCacheEntitiesEnvironment(ITokenCacheInternal cache, string } } } -} \ No newline at end of file +} diff --git a/tests/Microsoft.Identity.Test.Unit.net45/CacheTests/CacheSerializationTests.cs b/tests/Microsoft.Identity.Test.Unit.net45/CacheTests/CacheSerializationTests.cs index dbee1050a2..5bfb485b7f 100644 --- a/tests/Microsoft.Identity.Test.Unit.net45/CacheTests/CacheSerializationTests.cs +++ b/tests/Microsoft.Identity.Test.Unit.net45/CacheTests/CacheSerializationTests.cs @@ -25,6 +25,7 @@ // // ------------------------------------------------------------------------------ +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -41,6 +42,11 @@ namespace Microsoft.Identity.Test.Unit.CacheTests [TestClass] public class CacheSerializationTests { + private static readonly IEnumerable s_appMetadataKeys = new[] { + StorageJsonKeys.ClientId , + StorageJsonKeys.Environment, + StorageJsonKeys.FamilyId}; + private MsalAccessTokenCacheItem CreateAccessTokenItem() { return new MsalAccessTokenCacheItem @@ -115,30 +121,39 @@ private ITokenCacheAccessor CreateTokenCacheAccessor() { var item = CreateAccessTokenItem(); item.Environment = item.Environment + $"_{i}"; // ensure we get unique cache keys - accessor.AccessTokenCacheDictionary[item.GetKey().ToString()] = item; + accessor.SaveAccessToken(item); } for (int i = 1; i <= NumRefreshTokens; i++) { var item = CreateRefreshTokenItem(); item.Environment = item.Environment + $"_{i}"; // ensure we get unique cache keys - accessor.RefreshTokenCacheDictionary[item.GetKey().ToString()] = item; + accessor.SaveRefreshToken(item); } + // Create an FRT + var frt = CreateRefreshTokenItem(); + frt.FamilyId = "1"; + accessor.SaveRefreshToken(frt); + for (int i = 1; i <= NumIdTokens; i++) { var item = CreateIdTokenItem(); item.Environment = item.Environment + $"_{i}"; // ensure we get unique cache keys - accessor.IdTokenCacheDictionary[item.GetKey().ToString()] = item; + accessor.SaveIdToken(item); } for (int i = 1; i <= NumAccounts; i++) { var item = CreateAccountItem(); item.Environment = item.Environment + $"_{i}"; // ensure we get unique cache keys - accessor.AccountCacheDictionary[item.GetKey().ToString()] = item; + accessor.SaveAccount(item); } + accessor.SaveAppMetadata(new MsalAppMetadataCacheItem(MsalTestConstants.ClientId, "env_1", "1")); + accessor.SaveAppMetadata(new MsalAppMetadataCacheItem(MsalTestConstants.ClientId, "env_2", "")); + accessor.SaveAppMetadata(new MsalAppMetadataCacheItem(MsalTestConstants.ClientId2, "env_1", "another_family")); + return accessor; } @@ -200,6 +215,29 @@ public void TestSerializeMsalRefreshTokenCacheItem() AssertRefreshTokenCacheItemsAreEqual(item, item2); } + [TestMethod] + public void Test_FRT_SerializeDeserialize() + { + var item1 = CreateRefreshTokenItem(); + item1.FamilyId = null; + var item2 = CreateRefreshTokenItem(); + item2.FamilyId = ""; + var item3 = CreateRefreshTokenItem(); + item3.FamilyId = "1"; + + var json1 = item1.ToJsonString(); + var json2 = item2.ToJsonString(); + var json3 = item3.ToJsonString(); + + var reserialized1 = MsalRefreshTokenCacheItem.FromJsonString(json1); + var reserialized2 = MsalRefreshTokenCacheItem.FromJsonString(json2); + var reserialized3 = MsalRefreshTokenCacheItem.FromJsonString(json3); + + AssertRefreshTokenCacheItemsAreEqual(item1, reserialized1); + AssertRefreshTokenCacheItemsAreEqual(item2, reserialized2); + AssertRefreshTokenCacheItemsAreEqual(item3, reserialized3); + } + [TestMethod] public void TestSerializeMsalRefreshTokenCacheItemWithAdditionalFields() { @@ -326,6 +364,43 @@ public void TestMsalAccountCacheItem_HasProperJObjectFields() #endregion // ACCOUNT TESTS + #region APP METADATA TESTS + + [TestMethod] + public void TestAppMetadata_SerializeDeserialize() + { + var item = new MsalAppMetadataCacheItem(MsalTestConstants.ClientId, "env", "1"); + string asJson = item.ToJsonString(); + var item2 = MsalAppMetadataCacheItem.FromJsonString(asJson); + + Assert.AreEqual(item, item2); + } + + [TestMethod] + public void TestAppMetadata_Supports_AdditionalFields() + { + var item = new MsalAppMetadataCacheItem(MsalTestConstants.ClientId, "env", "1"); + + // Add an unknown field into the json + var asJObject = item.ToJObject(); + AssertContainsKeys(asJObject, s_appMetadataKeys); + + asJObject["unsupported_field_name"] = "this is a value"; + + // Ensure unknown field remains in the AdditionalFieldsJson block + var item2 = MsalAppMetadataCacheItem.FromJObject(asJObject); + Assert.AreEqual("{\r\n \"unsupported_field_name\": \"this is a value\"\r\n}", item2.AdditionalFieldsJson); + + // Ensure additional fields make the round trip into json + asJObject = item2.ToJObject(); + AssertContainsKeys(asJObject, s_appMetadataKeys); + AssertContainsKeys(asJObject, new[] { "unsupported_field_name" }); + } + + + #endregion // APP METADATA TESTS + + #region DICTIONARY SERIALIZATION TESTS [TestMethod] @@ -351,21 +426,31 @@ public void TestDictionarySerialization() #region JSON SERIALIZATION TESTS [TestMethod] + [DeploymentItem(@"Resources\ExpectedTokenCache.json")] public void TestJsonSerialization() { + string expectedJson = File.ReadAllText(ResourceHelper.GetTestResourceRelativePath("ExpectedTokenCache.json")); var accessor = CreateTokenCacheAccessor(); var s1 = new TokenCacheJsonSerializer(accessor); byte[] bytes = s1.Serialize(); - string json = new UTF8Encoding().GetString(bytes); + string actualJson = new UTF8Encoding().GetString(bytes); + + Assert.IsTrue(JToken.DeepEquals(JObject.Parse(actualJson), JObject.Parse(expectedJson))); var otherAccessor = new InMemoryTokenCacheAccessor(); var s2 = new TokenCacheJsonSerializer(otherAccessor); s2.Deserialize(bytes); AssertAccessorsAreEqual(accessor, otherAccessor); + + // serialize again to detect errors that come from deserialization + byte[] bytes2 = s2.Serialize(); + string actualJson2 = new UTF8Encoding().GetString(bytes2); + Assert.IsTrue(JToken.DeepEquals(JObject.Parse(actualJson2), JObject.Parse(expectedJson))); } + #endregion // JSON SERIALIZATION TESTS [TestMethod] @@ -379,10 +464,10 @@ public void TestPythonCacheSerializationInterop() byte[] bytes = File.ReadAllBytes(pythonBinFilePath); s.Deserialize(bytes); - Assert.AreEqual(0, accessor.AccessTokenCount, nameof(accessor.AccessTokenCount)); - Assert.AreEqual(0, accessor.RefreshTokenCount, nameof(accessor.RefreshTokenCount)); - Assert.AreEqual(0, accessor.IdTokenCount, nameof(accessor.IdTokenCount)); - Assert.AreEqual(0, accessor.AccountCount, nameof(accessor.AccountCount)); + Assert.AreEqual(0, accessor.GetAllAccessTokens().Count()); + Assert.AreEqual(0, accessor.GetAllRefreshTokens().Count()); + Assert.AreEqual(0, accessor.GetAllIdTokens().Count()); + Assert.AreEqual(0, accessor.GetAllAccounts().Count()); } [TestMethod] @@ -395,10 +480,11 @@ public void TestMsalNet2XCacheSerializationInterop() byte[] bytes = File.ReadAllBytes(binFilePath); s.Deserialize(bytes); - Assert.AreEqual(1, accessor.AccessTokenCount, nameof(accessor.AccessTokenCount)); - Assert.AreEqual(1, accessor.RefreshTokenCount, nameof(accessor.RefreshTokenCount)); - Assert.AreEqual(1, accessor.IdTokenCount, nameof(accessor.IdTokenCount)); - Assert.AreEqual(1, accessor.AccountCount, nameof(accessor.AccountCount)); + Assert.AreEqual(1, accessor.GetAllAccessTokens().Count()); + Assert.AreEqual(1, accessor.GetAllRefreshTokens().Count()); + Assert.AreEqual(1, accessor.GetAllIdTokens().Count()); + Assert.AreEqual(1, accessor.GetAllAccounts().Count()); + Assert.AreEqual(0, accessor.GetAllAppMetadata().Count()); var expectedAccessTokenItem = new MsalAccessTokenCacheItem { @@ -414,7 +500,7 @@ public void TestMsalNet2XCacheSerializationInterop() NormalizedScopes = "User.Read User.ReadBasic.All profile openid email", UserAssertionHash = string.Empty }; - AssertAccessTokenCacheItemsAreEqual(expectedAccessTokenItem, accessor.AccessTokenCacheDictionary.Values.First()); + AssertAccessTokenCacheItemsAreEqual(expectedAccessTokenItem, accessor.GetAllAccessTokens().First()); var expectedRefreshTokenItem = new MsalRefreshTokenCacheItem { @@ -423,7 +509,7 @@ public void TestMsalNet2XCacheSerializationInterop() RawClientInfo = string.Empty, ClientId = "b945c513-3946-4ecd-b179-6499803a2167" }; - AssertRefreshTokenCacheItemsAreEqual(expectedRefreshTokenItem, accessor.RefreshTokenCacheDictionary.Values.First()); + AssertRefreshTokenCacheItemsAreEqual(expectedRefreshTokenItem, accessor.GetAllRefreshTokens().First()); var expectedIdTokenItem = new MsalIdTokenCacheItem { @@ -433,7 +519,7 @@ public void TestMsalNet2XCacheSerializationInterop() ClientId = "b945c513-3946-4ecd-b179-6499803a2167", TenantId = "26039cce-489d-4002-8293-5b0c5134eacb" }; - AssertIdTokenCacheItemsAreEqual(expectedIdTokenItem, accessor.IdTokenCacheDictionary.Values.First()); + AssertIdTokenCacheItemsAreEqual(expectedIdTokenItem, accessor.GetAllIdTokens().First()); var expectedAccountItem = new MsalAccountCacheItem { @@ -447,15 +533,15 @@ public void TestMsalNet2XCacheSerializationInterop() LocalAccountId = "13dd2c19-84cd-416a-ae7d-49573e425619", TenantId = "26039cce-489d-4002-8293-5b0c5134eacb" }; - AssertAccountCacheItemsAreEqual(expectedAccountItem, accessor.AccountCacheDictionary.Values.First()); + AssertAccountCacheItemsAreEqual(expectedAccountItem, accessor.GetAllAccounts().First()); } private void AssertAccessorsAreEqual(ITokenCacheAccessor expected, ITokenCacheAccessor actual) { - Assert.AreEqual(expected.AccessTokenCount, actual.AccessTokenCount); - Assert.AreEqual(expected.RefreshTokenCount, actual.RefreshTokenCount); - Assert.AreEqual(expected.IdTokenCount, actual.IdTokenCount); - Assert.AreEqual(expected.AccountCount, actual.AccountCount); + Assert.AreEqual(expected.GetAllAccessTokens().Count(), actual.GetAllAccessTokens().Count()); + Assert.AreEqual(expected.GetAllRefreshTokens().Count(), actual.GetAllRefreshTokens().Count()); + Assert.AreEqual(expected.GetAllIdTokens().Count(), actual.GetAllIdTokens().Count()); + Assert.AreEqual(expected.GetAllAccounts().Count(), actual.GetAllAccounts().Count()); } private void AssertContainsKey(JObject j, string key) @@ -582,6 +668,17 @@ private void AssertAccessTokenCacheItemsAreEqual(MsalAccessTokenCacheItem expect private void AssertRefreshTokenCacheItemsAreEqual(MsalRefreshTokenCacheItem expected, MsalRefreshTokenCacheItem actual) { AssertCredentialCacheItemBaseItemsAreEqual(expected, actual); + + if (string.IsNullOrEmpty(expected.FamilyId)) + { + Assert.IsTrue(string.IsNullOrEmpty(actual.FamilyId)); + } + else + { + Assert.AreEqual(expected.FamilyId, actual.FamilyId); + } + + } private void AssertIdTokenCacheItemsAreEqual(MsalIdTokenCacheItem expected, MsalIdTokenCacheItem actual) @@ -603,4 +700,4 @@ private void AssertAccountCacheItemsAreEqual(MsalAccountCacheItem expected, Msal Assert.AreEqual(expected.TenantId, actual.TenantId, nameof(actual.TenantId)); } } -} \ No newline at end of file +} diff --git a/tests/Microsoft.Identity.Test.Unit.net45/CacheTests/TokenCacheTests.cs b/tests/Microsoft.Identity.Test.Unit.net45/CacheTests/TokenCacheTests.cs index fc973de539..c4cd60eed2 100644 --- a/tests/Microsoft.Identity.Test.Unit.net45/CacheTests/TokenCacheTests.cs +++ b/tests/Microsoft.Identity.Test.Unit.net45/CacheTests/TokenCacheTests.cs @@ -1,4 +1,4 @@ -// ------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. // All rights reserved. @@ -28,22 +28,21 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using Microsoft.Identity.Client; using Microsoft.Identity.Client.ApiConfig.Parameters; -using Microsoft.Identity.Client.Core; -using Microsoft.Identity.Client.Internal; -using Microsoft.Identity.Client.Internal.Requests; using Microsoft.Identity.Client.Cache; using Microsoft.Identity.Client.Cache.Items; +using Microsoft.Identity.Client.Core; using Microsoft.Identity.Client.Instance; +using Microsoft.Identity.Client.Internal.Requests; using Microsoft.Identity.Client.OAuth2; -using Microsoft.Identity.Client.TelemetryCore; +using Microsoft.Identity.Client.PlatformsCommon.Interfaces; using Microsoft.Identity.Client.Utils; +using Microsoft.Identity.Test.Common; +using Microsoft.Identity.Test.Common.Core.Helpers; using Microsoft.Identity.Test.Common.Core.Mocks; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Microsoft.Identity.Client.PlatformsCommon.Factories; -using Microsoft.Identity.Test.Common; +using NSubstitute; namespace Microsoft.Identity.Test.Unit.CacheTests { @@ -392,27 +391,17 @@ public void DoNotSaveRefreshTokenInAdalCacheForMsalB2CAuthorityTest() var serviceBundle = TestCommon.CreateDefaultServiceBundle(); ITokenCacheInternal cache = new TokenCache(serviceBundle); - var response = new MsalTokenResponse - { - IdToken = MockHelpers.CreateIdToken(MsalTestConstants.UniqueId, MsalTestConstants.DisplayableId), - AccessToken = "access-token", - ClientInfo = MockHelpers.CreateClientInfo(), - ExpiresIn = 3599, - CorrelationId = "correlation-id", - RefreshToken = "refresh-token", - Scope = MsalTestConstants.Scope.AsSingleString(), - TokenType = "Bearer" - }; + MsalTokenResponse response = MsalTestConstants.CreateMsalTokenResponse(); var requestParams = CreateAuthenticationRequestParameters(serviceBundle, authority: Authority.CreateAuthority(serviceBundle, MsalTestConstants.B2CAuthority)); requestParams.TenantUpdatedCanonicalAuthority = MsalTestConstants.AuthorityTestTenant; AddHostToInstanceCache(serviceBundle, MsalTestConstants.ProductionPrefNetworkEnvironment); - cache.SaveAccessAndRefreshToken(requestParams, response); + cache.SaveTokenResponse(requestParams, response); - Assert.AreEqual(1, cache.Accessor.RefreshTokenCount); - Assert.AreEqual(1, cache.Accessor.AccessTokenCount); + Assert.AreEqual(1, cache.Accessor.GetAllRefreshTokens().Count()); + Assert.AreEqual(1, cache.Accessor.GetAllAccessTokens().Count()); IDictionary dictionary = AdalCacheOperations.Deserialize(serviceBundle.DefaultLogger, cache.LegacyPersistence.LoadCache()); @@ -551,74 +540,137 @@ public void SaveAccessAndRefreshTokenWithEmptyCacheTest() var serviceBundle = TestCommon.CreateDefaultServiceBundle(); ITokenCacheInternal cache = new TokenCache(serviceBundle); - var response = new MsalTokenResponse + MsalTokenResponse response = MsalTestConstants.CreateMsalTokenResponse(); + + var requestParams = CreateAuthenticationRequestParameters(serviceBundle); + requestParams.TenantUpdatedCanonicalAuthority = MsalTestConstants.AuthorityTestTenant; + + AddHostToInstanceCache(serviceBundle, MsalTestConstants.ProductionPrefNetworkEnvironment); + + cache.SaveTokenResponse(requestParams, response); + + cache.Accessor.AssertItemCount( + expectedAtCount: 1, + expectedRtCount: 1, + expectedAccountCount: 1, + expectedIdtCount: 1, + expectedAppMetadataCount: 1); + + var metadata = cache.Accessor.GetAllAppMetadata().First(); + Assert.AreEqual(MsalTestConstants.ClientId, metadata.ClientId); + Assert.AreEqual(MsalTestConstants.ProductionPrefNetworkEnvironment, metadata.Environment); + Assert.IsNull(metadata.FamilyId); + } + + [TestMethod] + public void NoAppMetadata_WhenFociIsDisabled() + { + using (var harness = new MockHttpAndServiceBundle()) { - IdToken = MockHelpers.CreateIdToken(MsalTestConstants.UniqueId, MsalTestConstants.DisplayableId), - AccessToken = "access-token", - ClientInfo = MockHelpers.CreateClientInfo(), - ExpiresIn = 3599, - CorrelationId = "correlation-id", - RefreshToken = "refresh-token", - Scope = MsalTestConstants.Scope.AsSingleString(), - TokenType = "Bearer" - }; + // Arrange + var testFlags = Substitute.For(); + testFlags.IsFociEnabled.Returns(false); + + harness.ServiceBundle.PlatformProxy.SetFeatureFlags(testFlags); + + ITokenCacheInternal cache = new TokenCache(harness.ServiceBundle); + MsalTokenResponse response = MsalTestConstants.CreateMsalTokenResponse(); + var requestParams = CreateAuthenticationRequestParameters(harness.ServiceBundle); + requestParams.TenantUpdatedCanonicalAuthority = MsalTestConstants.AuthorityTestTenant; + AddHostToInstanceCache(harness.ServiceBundle, MsalTestConstants.ProductionPrefNetworkEnvironment); + + // Act + cache.SaveTokenResponse(requestParams, response); + + // Assert + cache.Accessor.AssertItemCount( + expectedAtCount: 1, + expectedRtCount: 1, + expectedAccountCount: 1, + expectedIdtCount: 1, + expectedAppMetadataCount: 0); + + // Don't save RT as an FRT if FOCI is disabled + Assert.IsTrue(string.IsNullOrEmpty(cache.Accessor.GetAllRefreshTokens().First().FamilyId)); + } + } + + [TestMethod] + public void SaveMultipleAppmetadata() + { + var serviceBundle = TestCommon.CreateDefaultServiceBundle(); + ITokenCacheInternal cache = new TokenCache(serviceBundle); + + MsalTokenResponse response = MsalTestConstants.CreateMsalTokenResponse(); + MsalTokenResponse response2 = MsalTestConstants.CreateMsalTokenResponse(); + response2.FamilyId = "1"; var requestParams = CreateAuthenticationRequestParameters(serviceBundle); requestParams.TenantUpdatedCanonicalAuthority = MsalTestConstants.AuthorityTestTenant; AddHostToInstanceCache(serviceBundle, MsalTestConstants.ProductionPrefNetworkEnvironment); - cache.SaveAccessAndRefreshToken(requestParams, response); + cache.SaveTokenResponse(requestParams, response); + cache.SaveTokenResponse(requestParams, response2); + + cache.Accessor.AssertItemCount( + expectedAtCount: 1, + expectedRtCount: 2, // a normal RT and an FRT + expectedAccountCount: 1, + expectedIdtCount: 1, + expectedAppMetadataCount: 1); - Assert.AreEqual(1, cache.Accessor.RefreshTokenCount); - Assert.AreEqual(1, cache.Accessor.AccessTokenCount); + var metadata = cache.Accessor.GetAllAppMetadata().First(); + Assert.AreEqual(MsalTestConstants.ClientId, metadata.ClientId); + Assert.AreEqual(MsalTestConstants.ProductionPrefNetworkEnvironment, metadata.Environment); + Assert.AreEqual(MsalTestConstants.FamilyId, metadata.FamilyId); + + Assert.IsTrue(cache.Accessor.GetAllRefreshTokens().Any(rt => rt.FamilyId == "1")); + Assert.IsTrue(cache.Accessor.GetAllRefreshTokens().Any(rt => string.IsNullOrEmpty(rt.FamilyId))); } + + [TestMethod] + public void CreateFrtFromTokenResponse() + { + MsalTokenResponse response = MsalTestConstants.CreateMsalTokenResponse(); + response.FamilyId = "1"; + + var frt = new MsalRefreshTokenCacheItem("env", MsalTestConstants.ClientId, response); + + Assert.AreEqual("1", frt.FamilyId); + } + + [TestMethod] [TestCategory("TokenCacheTests")] public void SaveAccessAndRefreshTokenWithMoreScopesTest() { var serviceBundle = TestCommon.CreateDefaultServiceBundle(); ITokenCacheInternal cache = new TokenCache(serviceBundle); - - var response = new MsalTokenResponse - { - IdToken = MockHelpers.CreateIdToken(MsalTestConstants.UniqueId, MsalTestConstants.DisplayableId), - ClientInfo = MockHelpers.CreateClientInfo(), - AccessToken = "access-token", - ExpiresIn = 3599, - CorrelationId = "correlation-id", - RefreshToken = "refresh-token", - Scope = MsalTestConstants.Scope.AsSingleString(), - TokenType = "Bearer" - }; + MsalTokenResponse response = MsalTestConstants.CreateMsalTokenResponse(); var requestParams = CreateAuthenticationRequestParameters(serviceBundle); requestParams.TenantUpdatedCanonicalAuthority = MsalTestConstants.AuthorityTestTenant; AddHostToInstanceCache(serviceBundle, MsalTestConstants.ProductionPrefNetworkEnvironment); - cache.SaveAccessAndRefreshToken(requestParams, response); + cache.SaveTokenResponse(requestParams, response); - Assert.AreEqual(1, cache.Accessor.RefreshTokenCount); - Assert.AreEqual(1, cache.Accessor.AccessTokenCount); + Assert.AreEqual(1, cache.Accessor.GetAllRefreshTokens().Count()); + Assert.AreEqual(1, cache.Accessor.GetAllAccessTokens().Count()); - response = new MsalTokenResponse - { - IdToken = MockHelpers.CreateIdToken(MsalTestConstants.UniqueId, MsalTestConstants.DisplayableId), - ClientInfo = MockHelpers.CreateClientInfo(), - AccessToken = "access-token-2", - ExpiresIn = 3599, - CorrelationId = "correlation-id", - RefreshToken = "refresh-token-2", - Scope = MsalTestConstants.Scope.AsSingleString() + " another-scope", - TokenType = "Bearer" - }; + Assert.IsNull(cache.Accessor.GetAllRefreshTokens().First().FamilyId); - cache.SaveAccessAndRefreshToken(requestParams, response); + response = MsalTestConstants.CreateMsalTokenResponse(); + response.Scope = MsalTestConstants.Scope.AsSingleString() + " another-scope"; + response.AccessToken = "access-token-2"; + response.RefreshToken = "refresh-token-2"; - Assert.AreEqual(1, cache.Accessor.RefreshTokenCount); - Assert.AreEqual(1, cache.Accessor.AccessTokenCount); + cache.SaveTokenResponse(requestParams, response); + + Assert.AreEqual(1, cache.Accessor.GetAllRefreshTokens().Count()); + Assert.AreEqual(1, cache.Accessor.GetAllAccessTokens().Count()); Assert.AreEqual("refresh-token-2", cache.GetAllRefreshTokens(true).First().Secret); Assert.AreEqual("access-token-2", cache.GetAllAccessTokens(true).First().Secret); @@ -630,42 +682,24 @@ public void SaveAccessAndRefreshTokenWithLessScopesTest() { var serviceBundle = TestCommon.CreateDefaultServiceBundle(); ITokenCacheInternal cache = new TokenCache(serviceBundle); - - var response = new MsalTokenResponse - { - IdToken = MockHelpers.CreateIdToken(MsalTestConstants.UniqueId, MsalTestConstants.DisplayableId), - ClientInfo = MockHelpers.CreateClientInfo(), - AccessToken = "access-token", - ExpiresIn = 3599, - CorrelationId = "correlation-id", - RefreshToken = "refresh-token", - Scope = MsalTestConstants.Scope.AsSingleString(), - TokenType = "Bearer" - }; + MsalTokenResponse response = MsalTestConstants.CreateMsalTokenResponse(); var requestParams = CreateAuthenticationRequestParameters(serviceBundle); requestParams.TenantUpdatedCanonicalAuthority = MsalTestConstants.AuthorityTestTenant; AddHostToInstanceCache(serviceBundle, MsalTestConstants.ProductionPrefNetworkEnvironment); - cache.SaveAccessAndRefreshToken(requestParams, response); + cache.SaveTokenResponse(requestParams, response); - response = new MsalTokenResponse - { - IdToken = MockHelpers.CreateIdToken(MsalTestConstants.UniqueId, MsalTestConstants.DisplayableId), - ClientInfo = MockHelpers.CreateClientInfo(), - AccessToken = "access-token-2", - ExpiresIn = 3599, - CorrelationId = "correlation-id", - RefreshToken = "refresh-token-2", - Scope = MsalTestConstants.Scope.First(), - TokenType = "Bearer" - }; + response = MsalTestConstants.CreateMsalTokenResponse(); + response.Scope = MsalTestConstants.Scope.First(); + response.AccessToken = "access-token-2"; + response.RefreshToken = "refresh-token-2"; - cache.SaveAccessAndRefreshToken(requestParams, response); + cache.SaveTokenResponse(requestParams, response); - Assert.AreEqual(1, cache.Accessor.RefreshTokenCount); - Assert.AreEqual(1, cache.Accessor.AccessTokenCount); + Assert.AreEqual(1, cache.Accessor.GetAllRefreshTokens().Count()); + Assert.AreEqual(1, cache.Accessor.GetAllAccessTokens().Count()); Assert.AreEqual("refresh-token-2", cache.GetAllRefreshTokens(true).First().Secret); Assert.AreEqual("access-token-2", cache.GetAllAccessTokens(true).First().Secret); } @@ -676,42 +710,24 @@ public void SaveAccessAndRefreshTokenWithIntersectingScopesTest() { var serviceBundle = TestCommon.CreateDefaultServiceBundle(); ITokenCacheInternal cache = new TokenCache(serviceBundle); - - var response = new MsalTokenResponse - { - IdToken = MockHelpers.CreateIdToken(MsalTestConstants.UniqueId, MsalTestConstants.DisplayableId), - AccessToken = "access-token", - ClientInfo = MockHelpers.CreateClientInfo(), - ExpiresIn = 3599, - CorrelationId = "correlation-id", - RefreshToken = "refresh-token", - Scope = MsalTestConstants.Scope.AsSingleString(), - TokenType = "Bearer" - }; + MsalTokenResponse response = MsalTestConstants.CreateMsalTokenResponse(); var requestParams = CreateAuthenticationRequestParameters(serviceBundle); requestParams.TenantUpdatedCanonicalAuthority = MsalTestConstants.AuthorityTestTenant; AddHostToInstanceCache(serviceBundle, MsalTestConstants.ProductionPrefNetworkEnvironment); - cache.SaveAccessAndRefreshToken(requestParams, response); + cache.SaveTokenResponse(requestParams, response); - response = new MsalTokenResponse - { - IdToken = MockHelpers.CreateIdToken(MsalTestConstants.UniqueId, MsalTestConstants.DisplayableId), - ClientInfo = MockHelpers.CreateClientInfo(), - AccessToken = "access-token-2", - ExpiresIn = 3599, - CorrelationId = "correlation-id", - RefreshToken = "refresh-token-2", - Scope = MsalTestConstants.Scope.AsSingleString() + " random-scope", - TokenType = "Bearer" - }; + response = MsalTestConstants.CreateMsalTokenResponse(); + response.Scope = MsalTestConstants.Scope.AsSingleString() + " random-scope"; + response.AccessToken = "access-token-2"; + response.RefreshToken = "refresh-token-2"; - cache.SaveAccessAndRefreshToken(requestParams, response); + cache.SaveTokenResponse(requestParams, response); - Assert.AreEqual(1, cache.Accessor.RefreshTokenCount); - Assert.AreEqual(1, cache.Accessor.AccessTokenCount); + Assert.AreEqual(1, cache.Accessor.GetAllRefreshTokens().Count()); + Assert.AreEqual(1, cache.Accessor.GetAllAccessTokens().Count()); Assert.AreEqual("refresh-token-2", cache.GetAllRefreshTokens(true).First().Secret); Assert.AreEqual("access-token-2", cache.GetAllAccessTokens(true).First().Secret); @@ -743,49 +759,30 @@ public void SaveAccessAndRefreshTokenWithDifferentAuthoritySameUserTest() { var serviceBundle = TestCommon.CreateDefaultServiceBundle(); ITokenCacheInternal cache = new TokenCache(serviceBundle); - - var response = new MsalTokenResponse - { - IdToken = MockHelpers.CreateIdToken(MsalTestConstants.UniqueId, MsalTestConstants.DisplayableId), - ClientInfo = MockHelpers.CreateClientInfo(), - AccessToken = "access-token", - ExpiresIn = 3599, - CorrelationId = "correlation-id", - RefreshToken = "refresh-token", - Scope = MsalTestConstants.Scope.AsSingleString(), - TokenType = "Bearer" - }; + MsalTokenResponse response = MsalTestConstants.CreateMsalTokenResponse(); var requestParams = CreateAuthenticationRequestParameters(serviceBundle); requestParams.TenantUpdatedCanonicalAuthority = MsalTestConstants.AuthorityHomeTenant; AddHostToInstanceCache(serviceBundle, MsalTestConstants.ProductionPrefNetworkEnvironment); - cache.SaveAccessAndRefreshToken(requestParams, response); - - response = new MsalTokenResponse - { - IdToken = MockHelpers.CreateIdToken(MsalTestConstants.UniqueId, MsalTestConstants.DisplayableId), - ClientInfo = MockHelpers.CreateClientInfo(), - AccessToken = "access-token-2", - ExpiresIn = 3599, - CorrelationId = "correlation-id", - RefreshToken = "refresh-token-2", - Scope = MsalTestConstants.Scope.AsSingleString() + " another-scope", - TokenType = "Bearer" - }; + cache.SaveTokenResponse(requestParams, response); + response = MsalTestConstants.CreateMsalTokenResponse(); + response.Scope = MsalTestConstants.Scope.AsSingleString() + " another-scope"; + response.AccessToken = "access-token-2"; + response.RefreshToken = "refresh-token-2"; requestParams = CreateAuthenticationRequestParameters(serviceBundle); requestParams.TenantUpdatedCanonicalAuthority = MsalTestConstants.AuthorityGuestTenant; - ((ITokenCache)cache).SetAfterAccess(AfterAccessChangedNotification); - cache.SaveAccessAndRefreshToken(requestParams, response); + cache.SetAfterAccess(AfterAccessChangedNotification); + cache.SaveTokenResponse(requestParams, response); #pragma warning disable CS0618 // Type or member is obsolete Assert.IsFalse(((TokenCache)cache).HasStateChanged); #pragma warning restore CS0618 // Type or member is obsolete - Assert.AreEqual(1, cache.Accessor.RefreshTokenCount); - Assert.AreEqual(2, cache.Accessor.AccessTokenCount); + Assert.AreEqual(1, cache.Accessor.GetAllRefreshTokens().Count()); + Assert.AreEqual(2, cache.Accessor.GetAllAccessTokens().Count()); Assert.AreEqual("refresh-token-2", cache.GetAllRefreshTokens(true).First().Secret); } @@ -812,17 +809,7 @@ public void SerializeDeserializeCacheTest() var serviceBundle = TestCommon.CreateDefaultServiceBundle(); ITokenCacheInternal cache = new TokenCache(serviceBundle); - var response = new MsalTokenResponse - { - IdToken = MockHelpers.CreateIdToken(MsalTestConstants.UniqueId, MsalTestConstants.DisplayableId), - ClientInfo = MockHelpers.CreateClientInfo(), - AccessToken = "access-token", - ExpiresIn = 3599, - CorrelationId = "correlation-id", - RefreshToken = "refresh-token", - Scope = MsalTestConstants.Scope.AsSingleString(), - TokenType = "Bearer" - }; + MsalTokenResponse response = MsalTestConstants.CreateMsalTokenResponse(); var requestContext = RequestContext.CreateForTest(serviceBundle); var requestParams = CreateAuthenticationRequestParameters(serviceBundle, requestContext: requestContext); @@ -830,29 +817,27 @@ public void SerializeDeserializeCacheTest() AddHostToInstanceCache(serviceBundle, MsalTestConstants.ProductionPrefNetworkEnvironment); - cache.SaveAccessAndRefreshToken(requestParams, response); - byte[] serializedCache = ((ITokenCache)cache).SerializeMsalV3(); - - string cacheString = new UTF8Encoding().GetString(serializedCache); + cache.SaveTokenResponse(requestParams, response); + byte[] serializedCache = cache.SerializeMsalV3(); cache.Accessor.ClearAccessTokens(); cache.Accessor.ClearRefreshTokens(); - Assert.AreEqual(0, cache.Accessor.RefreshTokenCount); - Assert.AreEqual(0, cache.Accessor.AccessTokenCount); + Assert.AreEqual(0, cache.Accessor.GetAllRefreshTokens().Count()); + Assert.AreEqual(0, cache.Accessor.GetAllAccessTokens().Count()); - ((ITokenCache)cache).DeserializeMsalV3(serializedCache); + cache.DeserializeMsalV3(serializedCache); - Assert.AreEqual(1, cache.Accessor.RefreshTokenCount); - Assert.AreEqual(1, cache.Accessor.AccessTokenCount); + Assert.AreEqual(1, cache.Accessor.GetAllRefreshTokens().Count()); + Assert.AreEqual(1, cache.Accessor.GetAllAccessTokens().Count()); - serializedCache = ((ITokenCache)cache).SerializeMsalV3(); - ((ITokenCache)cache).DeserializeMsalV3(serializedCache); + serializedCache = cache.SerializeMsalV3(); + cache.DeserializeMsalV3(serializedCache); // item count should not change because old cache entries should have // been overriden - Assert.AreEqual(1, cache.Accessor.RefreshTokenCount); - Assert.AreEqual(1, cache.Accessor.AccessTokenCount); + Assert.AreEqual(1, cache.Accessor.GetAllRefreshTokens().Count()); + Assert.AreEqual(1, cache.Accessor.GetAllAccessTokens().Count()); var atItem = cache.GetAllAccessTokens(true).First(); Assert.AreEqual(response.AccessToken, atItem.Secret); @@ -907,31 +892,21 @@ public void CacheB2CTokenTest() $"https://login.microsoftonline.com/tfp/{tenantID}/somePolicy/oauth2/v2.0/authorize"); // creating IDToken with empty tenantID and displayableID/PreferredUserName for B2C scenario - var response = new MsalTokenResponse - { - IdToken = MockHelpers.CreateIdToken(string.Empty, string.Empty, string.Empty), - ClientInfo = MockHelpers.CreateClientInfo(), - AccessToken = "access-token", - ExpiresIn = 3599, - CorrelationId = "correlation-id", - RefreshToken = "refresh-token", - Scope = MsalTestConstants.Scope.AsSingleString(), - TokenType = "Bearer" - }; + MsalTokenResponse response = MsalTestConstants.CreateMsalTokenResponse(); var requestContext = RequestContext.CreateForTest(serviceBundle); var requestParams = CreateAuthenticationRequestParameters(serviceBundle, authority, requestContext: requestContext); requestParams.TenantUpdatedCanonicalAuthority = MsalTestConstants.AuthorityTestTenant; - cache.SaveAccessAndRefreshToken(requestParams, response); + cache.SaveTokenResponse(requestParams, response); - Assert.AreEqual(1, cache.Accessor.RefreshTokenCount); - Assert.AreEqual(1, cache.Accessor.AccessTokenCount); + Assert.AreEqual(1, cache.Accessor.GetAllRefreshTokens().Count()); + Assert.AreEqual(1, cache.Accessor.GetAllAccessTokens().Count()); } private AuthenticationRequestParameters CreateAuthenticationRequestParameters( - IServiceBundle serviceBundle, - Authority authority = null, + IServiceBundle serviceBundle, + Authority authority = null, SortedSet scopes = null, RequestContext requestContext = null) { @@ -955,6 +930,90 @@ public void TestCacheDeserializeWithoutServiceBundle() { var tokenCache = new TokenCache(); tokenCache.DeserializeMsalV3(new byte[0]); + } + + [TestMethod] + public void TestIsFociMember() + { + // Arrange + using (var harness = new MockHttpAndServiceBundle()) + { + harness.HttpManager.AddInstanceDiscoveryMockHandler(); + ITokenCacheInternal cache = new TokenCache(harness.ServiceBundle); + AuthenticationRequestParameters requestParams = harness.CreateAuthenticationRequestParameters( + MsalTestConstants.AuthorityTestTenant, + MsalTestConstants.Scope, + account: MsalTestConstants.User); + + // Act + bool? result = cache.IsFociMemberAsync(requestParams, "1").Result; + + // Assert + Assert.IsNull(result, "No app metadata, should return null which indicates "); + + ValidateIsFociMember(cache, requestParams, + metadataFamilyId: "1", + expectedResult: true, // checks for familyId "1" + errMessage: "Valid app metadata, should return true because family Id matches"); + + + ValidateIsFociMember(cache, requestParams, + metadataFamilyId: "2", + expectedResult: false, // checks for familyId "1" + errMessage: "Valid app metadata, should return false because family Id does not match"); + + + ValidateIsFociMember(cache, requestParams, + metadataFamilyId: null, + expectedResult: false, // checks for familyId "1" + errMessage: "Valid app metadata showing that the app is not member of any family"); + } + } + + [TestMethod] + public void TestIsFociMember_EnvAlias() + { + // Arrange + using (var harness = new MockHttpAndServiceBundle()) + { + harness.HttpManager.AddInstanceDiscoveryMockHandler(); + ITokenCacheInternal cache = new TokenCache(harness.ServiceBundle); + AuthenticationRequestParameters requestParams = harness.CreateAuthenticationRequestParameters( + MsalTestConstants.AuthorityTestTenant, + MsalTestConstants.Scope, + account: MsalTestConstants.User); + + cache.Accessor.SaveAppMetadata( + new MsalAppMetadataCacheItem( + MsalTestConstants.ClientId, + MsalTestConstants.ProductionNotPrefEnvironmentAlias, + "1")); + + // Act + bool? result = cache.IsFociMemberAsync(requestParams, "1").Result; //requst params uses ProductionPrefEnvAlias + + // Assert + Assert.AreEqual(true, result.Value); + } + } + + private void ValidateIsFociMember( + ITokenCacheInternal cache, + AuthenticationRequestParameters requestParams, + string metadataFamilyId, + bool? expectedResult, + string errMessage) + { + // Arrange + var metadata = new MsalAppMetadataCacheItem(MsalTestConstants.ClientId, MsalTestConstants.ProductionPrefCacheEnvironment, metadataFamilyId); + cache.Accessor.SaveAppMetadata(metadata); + + // Act + var result = cache.IsFociMemberAsync(requestParams, "1").Result; + + // Assert + Assert.AreEqual(expectedResult, result, errMessage); + Assert.AreEqual(1, cache.Accessor.GetAllAppMetadata().Count()); } /* diff --git a/tests/Microsoft.Identity.Test.Unit.net45/CacheTests/UnifiedCacheFormatTests.cs b/tests/Microsoft.Identity.Test.Unit.net45/CacheTests/UnifiedCacheFormatTests.cs index edf3a83f56..70b487a540 100644 --- a/tests/Microsoft.Identity.Test.Unit.net45/CacheTests/UnifiedCacheFormatTests.cs +++ b/tests/Microsoft.Identity.Test.Unit.net45/CacheTests/UnifiedCacheFormatTests.cs @@ -33,9 +33,8 @@ using System.Net.Http; using Microsoft.Identity.Client; using Microsoft.Identity.Client.AppConfig; +using Microsoft.Identity.Client.Cache.Keys; using Microsoft.Identity.Client.Core; -using Microsoft.Identity.Client.Internal; -using Microsoft.Identity.Client.Instance; using Microsoft.Identity.Client.UI; using Microsoft.Identity.Client.Utils; using Microsoft.Identity.Test.Common; @@ -65,35 +64,35 @@ private void TestInitialize(MockHttpManager httpManager) MsalTestConstants.GetDiscoveryEndpoint(MsalTestConstants.AuthorityCommonTenant))); } - private string ClientId; - private string RequestAuthority; + private string _clientId; + private string _requestAuthority; - private string TokenResponse; - private string IdTokenResponse; + private string _tokenResponse; + private string _idTokenResponse; - private string ExpectedAtCacheKey; - private string ExpectedAtCacheKeyIosService; - private string ExpectedAtCacheKeyIosAccount; - private string ExpectedAtCacheKeyIosGeneric; - private string ExpectedAtCacheValue; + private string _expectedAtCacheKey; + private string _expectedAtCacheKeyIosService; + private string _expectedAtCacheKeyIosAccount; + private string _expectedAtCacheKeyIosGeneric; + private string _expectedAtCacheValue; - private string ExpectedIdTokenCacheKey; - private string ExpectedIdTokenCacheKeyIosService; - private string ExpectedIdTokenCacheKeyIosAccount; - private string ExpectedIdTokenCacheKeyIosGeneric; - private string ExpectedIdTokenCacheValue; + private string _expectedIdTokenCacheKey; + private string _expectedIdTokenCacheKeyIosService; + private string _expectedIdTokenCacheKeyIosAccount; + private string _expectedIdTokenCacheKeyIosGeneric; + private string _expectedIdTokenCacheValue; - private string ExpectedRtCacheKey; - private string ExpectedRtCacheKeyIosService; - private string ExpectedRtCacheKeyIosAccount; - private string ExpectedRtCacheKeyIosGeneric; - private string ExpectedRtCacheValue; + private string _expectedRtCacheKey; + private string _expectedRtCacheKeyIosService; + private string _expectedRtCacheKeyIosAccount; + private string _expectedRtCacheKeyIosGeneric; - private string ExpectedAccountCacheKey; - private string ExpectedAccountCacheKeyIosService; - private string ExpectedAccountCacheKeyIosAccount; - private string ExpectedAccountCacheKeyIosGeneric; - private string ExpectedAccountCacheValue; + private string _expectedAccountCacheKey; + private string _expectedAccountCacheKeyIosService; + private string _expectedAccountCacheKeyIosAccount; + private string _expectedAccountCacheKeyIosGeneric; + private string _expectedAccountCacheValue; + private string _expectedRtCacheValue; private readonly RequestContext requestContext = RequestContext.CreateForTest(); @@ -104,45 +103,45 @@ private void IntitTestData(string fileName) string json = r.ReadToEnd(); var configJson = JsonConvert.DeserializeObject(json); - ClientId = configJson.GetValue("client_id").ToString(); - RequestAuthority = configJson.GetValue("authority").ToString(); + _clientId = configJson.GetValue("client_id").ToString(); + _requestAuthority = configJson.GetValue("authority").ToString(); - TokenResponse = configJson.GetValue("token_response").ToString(); - IdTokenResponse = configJson.GetValue("id_token_response").ToString(); + _tokenResponse = configJson.GetValue("token_response").ToString(); + _idTokenResponse = configJson.GetValue("id_token_response").ToString(); - ExpectedAtCacheKey = configJson.GetValue("at_cache_key").ToString(); - ExpectedAtCacheKeyIosService = configJson.GetValue("at_cache_key_ios_service").ToString(); - ExpectedAtCacheKeyIosAccount = configJson.GetValue("at_cache_key_ios_account").ToString(); - ExpectedAtCacheKeyIosGeneric = configJson.GetValue("at_cache_key_ios_generic").ToString(); - ExpectedAtCacheKey = configJson.GetValue("at_cache_key").ToString(); + _expectedAtCacheKey = configJson.GetValue("at_cache_key").ToString(); + _expectedAtCacheKeyIosService = configJson.GetValue("at_cache_key_ios_service").ToString(); + _expectedAtCacheKeyIosAccount = configJson.GetValue("at_cache_key_ios_account").ToString(); + _expectedAtCacheKeyIosGeneric = configJson.GetValue("at_cache_key_ios_generic").ToString(); + _expectedAtCacheKey = configJson.GetValue("at_cache_key").ToString(); - ExpectedAtCacheValue = configJson.GetValue("at_cache_value").ToString(); + _expectedAtCacheValue = configJson.GetValue("at_cache_value").ToString(); - ExpectedIdTokenCacheKey = configJson.GetValue("id_token_cache_key").ToString(); - ExpectedIdTokenCacheKeyIosService = configJson.GetValue("id_token_cache_key_ios_service").ToString(); - ExpectedIdTokenCacheKeyIosAccount = configJson.GetValue("id_token_cache_key_ios_account").ToString(); - ExpectedIdTokenCacheKeyIosGeneric = configJson.GetValue("id_token_cache_key_ios_generic").ToString(); - ExpectedIdTokenCacheValue = configJson.GetValue("id_token_cache_value").ToString(); + _expectedIdTokenCacheKey = configJson.GetValue("id_token_cache_key").ToString(); + _expectedIdTokenCacheKeyIosService = configJson.GetValue("id_token_cache_key_ios_service").ToString(); + _expectedIdTokenCacheKeyIosAccount = configJson.GetValue("id_token_cache_key_ios_account").ToString(); + _expectedIdTokenCacheKeyIosGeneric = configJson.GetValue("id_token_cache_key_ios_generic").ToString(); + _expectedIdTokenCacheValue = configJson.GetValue("id_token_cache_value").ToString(); - ExpectedRtCacheKey = configJson.GetValue("rt_cache_key").ToString(); - ExpectedRtCacheKeyIosService = configJson.GetValue("rt_cache_key_ios_service").ToString(); - ExpectedRtCacheKeyIosAccount = configJson.GetValue("rt_cache_key_ios_account").ToString(); - ExpectedRtCacheKeyIosGeneric = configJson.GetValue("rt_cache_key_ios_generic").ToString(); - ExpectedRtCacheValue = configJson.GetValue("rt_cache_value").ToString(); + _expectedRtCacheKey = configJson.GetValue("rt_cache_key").ToString(); + _expectedRtCacheKeyIosService = configJson.GetValue("rt_cache_key_ios_service").ToString(); + _expectedRtCacheKeyIosAccount = configJson.GetValue("rt_cache_key_ios_account").ToString(); + _expectedRtCacheKeyIosGeneric = configJson.GetValue("rt_cache_key_ios_generic").ToString(); + _expectedRtCacheValue = configJson.GetValue("rt_cache_value").ToString(); - ExpectedAccountCacheKey = configJson.GetValue("account_cache_key").ToString(); - ExpectedAccountCacheKeyIosService = configJson.GetValue("account_cache_key_ios_service").ToString(); - ExpectedAccountCacheKeyIosAccount = configJson.GetValue("account_cache_key_ios_account").ToString(); - ExpectedAccountCacheKeyIosGeneric = configJson.GetValue("account_cache_key_ios_generic").ToString(); - ExpectedAccountCacheValue = configJson.GetValue("account_cache_value").ToString(); + _expectedAccountCacheKey = configJson.GetValue("account_cache_key").ToString(); + _expectedAccountCacheKeyIosService = configJson.GetValue("account_cache_key_ios_service").ToString(); + _expectedAccountCacheKeyIosAccount = configJson.GetValue("account_cache_key_ios_account").ToString(); + _expectedAccountCacheKeyIosGeneric = configJson.GetValue("account_cache_key_ios_generic").ToString(); + _expectedAccountCacheValue = configJson.GetValue("account_cache_value").ToString(); - var idTokenSecret = CreateIdToken(IdTokenResponse); + var idTokenSecret = CreateIdToken(_idTokenResponse); - TokenResponse = string.Format - (CultureInfo.InvariantCulture, "{" + TokenResponse + "}", idTokenSecret); + _tokenResponse = string.Format + (CultureInfo.InvariantCulture, "{" + _tokenResponse + "}", idTokenSecret); - ExpectedIdTokenCacheValue = string.Format - (CultureInfo.InvariantCulture, "{" + ExpectedIdTokenCacheValue + "}", idTokenSecret); + _expectedIdTokenCacheValue = string.Format + (CultureInfo.InvariantCulture, "{" + _expectedIdTokenCacheValue + "}", idTokenSecret); } } @@ -194,8 +193,8 @@ public void RunCacheFormatValidation() TestInitialize(harness.HttpManager); PublicClientApplication app = PublicClientApplicationBuilder - .Create(ClientId) - .WithAuthority(new Uri(RequestAuthority), true) + .Create(_clientId) + .WithAuthority(new Uri(_requestAuthority), true) .WithHttpManager(harness.HttpManager) .BuildConcrete(); @@ -213,7 +212,7 @@ public void RunCacheFormatValidation() harness.HttpManager.AddMockHandler(new MockHttpMessageHandler { ExpectedMethod = HttpMethod.Post, - ResponseMessage = MockHelpers.CreateSuccessResponseMessage(TokenResponse) + ResponseMessage = MockHelpers.CreateSuccessResponseMessage(_tokenResponse) }); AuthenticationResult result = app.AcquireTokenAsync(MsalTestConstants.Scope).Result; @@ -232,7 +231,7 @@ private void ValidateAt(ITokenCacheInternal cache) Assert.AreEqual(1, atList.Count); var actualPayload = JsonConvert.DeserializeObject(atList.First().ToJsonString()); - var expectedPayload = JsonConvert.DeserializeObject(ExpectedAtCacheValue); + var expectedPayload = JsonConvert.DeserializeObject(_expectedAtCacheValue); foreach (KeyValuePair prop in expectedPayload) { @@ -256,11 +255,13 @@ private void ValidateAt(ITokenCacheInternal cache) var atCacheItem = cache.GetAllAccessTokens(true).First(); var key = atCacheItem.GetKey(); - Assert.AreEqual(ExpectedAtCacheKey, key.ToString()); + Assert.AreEqual(_expectedAtCacheKey, key.ToString()); - Assert.AreEqual(ExpectedAtCacheKeyIosService, key.GetiOSServiceKey()); - Assert.AreEqual(ExpectedAtCacheKeyIosAccount, key.GetiOSAccountKey()); - Assert.AreEqual(ExpectedAtCacheKeyIosGeneric, key.GetiOSGenericKey()); + Assert.AreEqual(_expectedAtCacheKeyIosService, key.iOSService); + Assert.AreEqual(_expectedAtCacheKeyIosAccount, key.iOSAccount); + Assert.AreEqual(_expectedAtCacheKeyIosGeneric, key.iOSGeneric); + Assert.AreEqual(_expectedAtCacheKeyIosGeneric, key.iOSGeneric); + Assert.AreEqual((int)MsalCacheKeys.iOSCredentialAttrType.AccessToken, key.iOSType); } private void ValidateRt(ITokenCacheInternal cache) @@ -272,11 +273,13 @@ private void ValidateRt(ITokenCacheInternal cache) var rtCacheItem = cache.GetAllRefreshTokens(true).First(); var key = rtCacheItem.GetKey(); - Assert.AreEqual(ExpectedRtCacheKey, key.ToString()); + Assert.AreEqual(_expectedRtCacheKey, key.ToString()); + + Assert.AreEqual(_expectedRtCacheKeyIosService, key.iOSService); + Assert.AreEqual(_expectedRtCacheKeyIosAccount, key.iOSAccount); + Assert.AreEqual(_expectedRtCacheKeyIosGeneric, key.iOSGeneric); + Assert.AreEqual((int)MsalCacheKeys.iOSCredentialAttrType.RefreshToken, key.iOSType); - Assert.AreEqual(ExpectedRtCacheKeyIosService, key.GetiOSServiceKey()); - Assert.AreEqual(ExpectedRtCacheKeyIosAccount, key.GetiOSAccountKey()); - Assert.AreEqual(ExpectedRtCacheKeyIosGeneric, key.GetiOSGenericKey()); } private void ValidateIdToken(ITokenCacheInternal cache) @@ -288,11 +291,13 @@ private void ValidateIdToken(ITokenCacheInternal cache) var idTokenCacheItem = cache.GetAllIdTokens(true).First(); var key = idTokenCacheItem.GetKey(); - Assert.AreEqual(ExpectedIdTokenCacheKey, key.ToString()); + Assert.AreEqual(_expectedIdTokenCacheKey, key.ToString()); + + Assert.AreEqual(_expectedIdTokenCacheKeyIosService, key.iOSService); + Assert.AreEqual(_expectedIdTokenCacheKeyIosAccount, key.iOSAccount); + Assert.AreEqual(_expectedIdTokenCacheKeyIosGeneric, key.iOSGeneric); + Assert.AreEqual((int)MsalCacheKeys.iOSCredentialAttrType.IdToken, key.iOSType); - Assert.AreEqual(ExpectedIdTokenCacheKeyIosService, key.GetiOSServiceKey()); - Assert.AreEqual(ExpectedIdTokenCacheKeyIosAccount, key.GetiOSAccountKey()); - Assert.AreEqual(ExpectedIdTokenCacheKeyIosGeneric, key.GetiOSGenericKey()); } private void ValidateAccount(ITokenCacheInternal cache) @@ -304,11 +309,13 @@ private void ValidateAccount(ITokenCacheInternal cache) var accountCacheItem = cache.GetAllAccounts().First(); var key = accountCacheItem.GetKey(); - Assert.AreEqual(ExpectedAccountCacheKey, key.ToString()); + Assert.AreEqual(_expectedAccountCacheKey, key.ToString()); + + Assert.AreEqual(_expectedAccountCacheKeyIosService, key.iOSService); + Assert.AreEqual(_expectedAccountCacheKeyIosAccount, key.iOSAccount); + Assert.AreEqual(_expectedAccountCacheKeyIosGeneric, key.iOSGeneric); + Assert.AreEqual(MsalCacheKeys.iOSAuthorityTypeToAttrType["MSSTS"], key.iOSType); - Assert.AreEqual(ExpectedAccountCacheKeyIosService, key.GetiOSServiceKey()); - Assert.AreEqual(ExpectedAccountCacheKeyIosAccount, key.GetiOSAccountKey()); - Assert.AreEqual(ExpectedAccountCacheKeyIosGeneric, key.GetiOSGenericKey()); } private void ValidateCacheEntityValue(string expectedEntityValue, ICollection entities) @@ -331,4 +338,4 @@ private void ValidateCacheEntityValue(string expectedEntityValue, ICollection(CacheFallbackOperations.DifferentEnvError)); } + + [TestMethod] + public void DoNotWriteFRTs() + { + // Arrange + _legacyCachePersistence.ThrowOnWrite = true; + + var rtItem = new MsalRefreshTokenCacheItem( + MsalTestConstants.ProductionPrefNetworkEnvironment, + MsalTestConstants.ClientId, + "someRT", + MockHelpers.CreateClientInfo("u1", "ut1"), + "familyId"); + + var idTokenCacheItem = new MsalIdTokenCacheItem( + MsalTestConstants.ProductionPrefNetworkEnvironment, // different env + MsalTestConstants.ClientId, + MockHelpers.CreateIdToken("u1", "username"), + MockHelpers.CreateClientInfo("u1", "ut1"), + "ut1"); + + // Act + CacheFallbackOperations.WriteAdalRefreshToken( + _logger, + _legacyCachePersistence, + rtItem, + idTokenCacheItem, + "https://some_env.com/common", // yet another env + "uid", + "scope1"); + + AssertCacheEntryCount(0); + } + private void PopulateLegacyCache(ILegacyCachePersistence legacyCachePersistence) { PopulateLegacyWithRtAndId( diff --git a/tests/Microsoft.Identity.Test.Unit.net45/CoreTests/CacheTests/MsalTokenCacheKeysTests.cs b/tests/Microsoft.Identity.Test.Unit.net45/CoreTests/CacheTests/MsalTokenCacheKeysTests.cs index a4bc6c85ed..9e9dfce6d0 100644 --- a/tests/Microsoft.Identity.Test.Unit.net45/CoreTests/CacheTests/MsalTokenCacheKeysTests.cs +++ b/tests/Microsoft.Identity.Test.Unit.net45/CoreTests/CacheTests/MsalTokenCacheKeysTests.cs @@ -26,10 +26,7 @@ //------------------------------------------------------------------------------ using System; -using System.Linq; -using Microsoft.Identity.Client.Cache; using Microsoft.Identity.Client.Cache.Keys; -using Microsoft.Identity.Test.Common; using Microsoft.Identity.Test.Common.Core.Helpers; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -41,10 +38,10 @@ public class MsalTokenCacheKeysTests [TestMethod] public void ArgNull() { - AssertException.Throws(() => new MsalRefreshTokenCacheKey("", "clientId", "uid")); - AssertException.Throws(() => new MsalRefreshTokenCacheKey(null, "clientId", "uid")); - AssertException.Throws(() => new MsalRefreshTokenCacheKey(null, "clientId", "uid")); - AssertException.Throws(() => new MsalRefreshTokenCacheKey("env", null, "uid")); + AssertException.Throws(() => new MsalRefreshTokenCacheKey("", "clientId", "uid", "1")); + AssertException.Throws(() => new MsalRefreshTokenCacheKey(null, "clientId", "uid", "1")); + AssertException.Throws(() => new MsalRefreshTokenCacheKey(null, "clientId", "uid", "1")); + AssertException.Throws(() => new MsalRefreshTokenCacheKey("env", null, "uid", "1")); AssertException.Throws(() => new MsalIdTokenCacheKey("", "tid", "uid", "cid")); AssertException.Throws(() => new MsalIdTokenCacheKey(null, "tid", "uid", "cid")); @@ -56,74 +53,90 @@ public void ArgNull() AssertException.Throws(() => new MsalAccessTokenCacheKey("env", "tid", "uid", "", "scopes")); AssertException.Throws(() => new MsalAccessTokenCacheKey("env", "tid", "uid", null, "scopes")); - AssertException.Throws(() => new MsalAccountCacheKey("", "tid", "uid", "localid")); - AssertException.Throws(() => new MsalAccountCacheKey(null, "tid", "uid", "localid")); + AssertException.Throws(() => new MsalAccountCacheKey("", "tid", "uid", "localid", "aad")); + AssertException.Throws(() => new MsalAccountCacheKey(null, "tid", "uid", "localid", "msa")); } [TestMethod] public void MsalAccessTokenCacheKey() { - MsalAccessTokenCacheKey key = new MsalAccessTokenCacheKey("login.microsoftonline.com", "contoso.com", "uid.utid", "clientid", "user.read user.write"); + var key = new MsalAccessTokenCacheKey("login.microsoftonline.com", "contoso.com", "uid.utid", "clientid", "user.read user.write"); Assert.AreEqual("uid.utid-login.microsoftonline.com-accesstoken-clientid-contoso.com-user.read user.write", key.ToString()); - Assert.AreEqual("uid.utid-login.microsoftonline.com", key.GetiOSAccountKey()); - Assert.AreEqual("accesstoken-clientid-contoso.com-user.read user.write", key.GetiOSServiceKey()); - Assert.AreEqual("accesstoken-clientid-contoso.com", key.GetiOSGenericKey()); - var serviceBundle = TestCommon.CreateDefaultServiceBundle(); - Assert.AreEqual("uid.utid-m7wizgxzfro0k4ytgwbclbecpmuf5trhsuba0vptum8=-accesstoken-clientid-contoso.com-n5wvhdusof/wfsjgk1muxrk89nwfynymsl4qefkynbu=", key.GetUWPFixedSizeKey(serviceBundle.PlatformProxy.CryptographyManager)); + Assert.AreEqual("uid.utid-login.microsoftonline.com", key.iOSAccount); + Assert.AreEqual("accesstoken-clientid-contoso.com-user.read user.write", key.iOSService); + Assert.AreEqual("accesstoken-clientid-contoso.com", key.iOSGeneric); + Assert.AreEqual((int)MsalCacheKeys.iOSCredentialAttrType.AccessToken, key.iOSType); } [TestMethod] public void MsalRefreshTokenCacheKey() { - MsalRefreshTokenCacheKey key = new MsalRefreshTokenCacheKey("login.microsoftonline.com", "clientid", "uid.utid"); + var key = new MsalRefreshTokenCacheKey("login.microsoftonline.com", "clientid", "uid.utid", ""); Assert.AreEqual("uid.utid-login.microsoftonline.com-refreshtoken-clientid--", key.ToString()); - Assert.AreEqual("uid.utid-login.microsoftonline.com", key.GetiOSAccountKey()); - Assert.AreEqual("refreshtoken-clientid--", key.GetiOSServiceKey()); - Assert.AreEqual("refreshtoken-clientid-", key.GetiOSGenericKey()); + Assert.AreEqual("uid.utid-login.microsoftonline.com", key.iOSAccount); + Assert.AreEqual("refreshtoken-clientid--", key.iOSService); + Assert.AreEqual("refreshtoken-clientid-", key.iOSGeneric); + Assert.AreEqual((int)MsalCacheKeys.iOSCredentialAttrType.RefreshToken, key.iOSType); } [TestMethod] - public void MsalAccessTokenCacheKey_IsDifferentWhenEnvAndScopesAreDifferent() + public void MsalFamilyRefreshTokenCacheKey() { - MsalAccessTokenCacheKey key1 = new MsalAccessTokenCacheKey("env", "tid", "uid", "cid", "scope1 scope2"); - MsalAccessTokenCacheKey key2 = new MsalAccessTokenCacheKey("env", "tid", "uid", "cid", - string.Join(" ", Enumerable.Range(1, 100).Select(i => "scope" + i))); + var key = new MsalRefreshTokenCacheKey("login.microsoftonline.com", "CLIENT_ID_NOT_USED", "uid.utid", "1"); - var serviceBundle = TestCommon.CreateDefaultServiceBundle(); - var crypto = serviceBundle.PlatformProxy.CryptographyManager; - - Assert.AreNotEqual(key1.GetUWPFixedSizeKey(crypto), key2.GetUWPFixedSizeKey(crypto)); - Assert.IsTrue(key2.GetUWPFixedSizeKey(crypto).Length < 255); - Assert.IsTrue(key1.GetUWPFixedSizeKey(crypto).Length < 255); + Assert.AreEqual("uid.utid-login.microsoftonline.com-refreshtoken-1--", key.ToString()); + Assert.AreEqual("uid.utid-login.microsoftonline.com", key.iOSAccount); + Assert.AreEqual("refreshtoken-1--", key.iOSService); + Assert.AreEqual("refreshtoken-1-", key.iOSGeneric); + Assert.AreEqual((int)MsalCacheKeys.iOSCredentialAttrType.RefreshToken, key.iOSType); } [TestMethod] public void MsalIdTokenCacheKey() { - MsalIdTokenCacheKey key = new MsalIdTokenCacheKey("login.microsoftonline.com", "contoso.com", "uid.utid", "clientid"); + var key = new MsalIdTokenCacheKey("login.microsoftonline.com", "contoso.com", "uid.utid", "clientid"); Assert.AreEqual("uid.utid-login.microsoftonline.com-idtoken-clientid-contoso.com-", key.ToString()); - Assert.AreEqual("uid.utid-login.microsoftonline.com", key.GetiOSAccountKey()); - Assert.AreEqual("idtoken-clientid-contoso.com-", key.GetiOSServiceKey()); - Assert.AreEqual("idtoken-clientid-contoso.com", key.GetiOSGenericKey()); + Assert.AreEqual("uid.utid-login.microsoftonline.com", key.iOSAccount); + Assert.AreEqual("idtoken-clientid-contoso.com-", key.iOSService); + Assert.AreEqual("idtoken-clientid-contoso.com", key.iOSGeneric); + Assert.AreEqual((int)MsalCacheKeys.iOSCredentialAttrType.IdToken, key.iOSType); } [TestMethod] public void MsalAccountCacheKey() { - MsalAccountCacheKey key = new MsalAccountCacheKey("login.microsoftonline.com", "contoso.com", "uid.utid", "localId"); + var key = new MsalAccountCacheKey( + "login.microsoftonline.com", + "contoso.com", + "uid.utid", + "localId", + "AAD"); Assert.AreEqual("uid.utid-login.microsoftonline.com-contoso.com", key.ToString()); - Assert.AreEqual("uid.utid-login.microsoftonline.com", key.GetiOSAccountKey()); - Assert.AreEqual("contoso.com", key.GetiOSServiceKey()); - Assert.AreEqual("localid", key.GetiOSGenericKey()); + Assert.AreEqual("uid.utid-login.microsoftonline.com", key.iOSAccount); + Assert.AreEqual("contoso.com", key.iOSService); + Assert.AreEqual("localid", key.iOSGeneric); + Assert.AreEqual(MsalCacheKeys.iOSAuthorityTypeToAttrType["AAD"], key.iOSType); + + } + + [TestMethod] + public void MsalAppMetadataCacheKey() + { + var key = new MsalAppMetadataCacheKey("clientid", "login.microsoftonline.com"); + + Assert.AreEqual("appmetadata-clientid", key.iOSService); + Assert.AreEqual("login.microsoftonline.com", key.iOSAccount); + Assert.AreEqual("1", key.iOSGeneric); + Assert.AreEqual((int)MsalCacheKeys.iOSCredentialAttrType.AppMetadata, key.iOSType); } } } diff --git a/tests/Microsoft.Identity.Test.Unit.net45/CoreTests/OAuth2Tests/TokenResponseTests.cs b/tests/Microsoft.Identity.Test.Unit.net45/CoreTests/OAuth2Tests/TokenResponseTests.cs index 3f5d013699..5e7b508246 100644 --- a/tests/Microsoft.Identity.Test.Unit.net45/CoreTests/OAuth2Tests/TokenResponseTests.cs +++ b/tests/Microsoft.Identity.Test.Unit.net45/CoreTests/OAuth2Tests/TokenResponseTests.cs @@ -50,17 +50,8 @@ public void ExpirationTimeTest() DateTimeOffset current = DateTimeOffset.UtcNow; const long ExpiresInSeconds = 3599; - MsalTokenResponse response = new MsalTokenResponse - { - IdToken = - "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSIsImtpZCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSJ9.eyJhdWQiOiI3YzdhMmY3MC1jYWVmLTQ1YzgtOWE2Yy0wOTE2MzM1MDFkZTQiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vODE2OTAyODYtNTA1NC00Zjk3LWI3MDgtNTQxNjU0Y2Q5MjFhL3YyLjAvIiwiaWF0IjoxNDU1NTc2MjM1LCJuYmYiOjE0NTU1NzYyMzUsImV4cCI6MTQ1NTU4MDEzNSwibmFtZSI6IkFEQUwgT2JqLUMgLSBFMkUiLCJvaWQiOiIxZTcwYThlZi1jYjIwLTQxOWMtYjhhNy1hNDJlZDJmYTIyNzciLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJlMmVAYWRhbG9iamMub25taWNyb3NvZnQuY29tIiwic3ViIjoibHJxVDlsQXQzSUlhS3hHanE2UlNReFRqN3diV3Q2RnpaMFU3NkJZMEJINCIsInRpZCI6IjgxNjkwMjg2LTUwNTQtNGY5Ny1iNzA4LTU0MTY1NGNkOTIxYSIsInZlciI6IjIuMCJ9.axS_-N3Z3b1GnZftxb6dKtMeooldoIQ_B7YrVO4CQI9xhHI1_Vl-dXfsFHBPRvIvXBEfBEehaaWq9B9P_CD5TpQXGycsYS08knHf_QpHIJ9WQbBIJ774divakx7kN6x7IxjoD1PrfRfo2QZsLLAz-1n-NHt7FwtkBQpKTDfgc6cVShy9isaJt5WoxfUM1eNo1HK_YjHj7Q5-n-XiZEbe-8m-7nqwBw86QDlLdk7dBhhCzVzXZb_5HCHI-23xZLYR34RoW7ljYEG4P8auEcML1haS4MN83VKRorMyljAIoA4YOgbfnvnlAlxRz_rtAAcjNqaUpIwzadGzd-QVbyoKPQ", - AccessToken = "access-token", - ExpiresIn = ExpiresInSeconds, - CorrelationId = "correlation-id", - RefreshToken = "refresh-token", - Scope = "scope1 scope2", - TokenType = "Bearer" - }; + var response = MsalTestConstants.CreateMsalTokenResponse(); + Assert.IsTrue(response.AccessTokenExpiresOn.Subtract(current) >= TimeSpan.FromSeconds(ExpiresInSeconds)); } diff --git a/tests/Microsoft.Identity.Test.Unit.net45/OAuthClientTests.cs b/tests/Microsoft.Identity.Test.Unit.net45/OAuthClientTests.cs new file mode 100644 index 0000000000..f9b6284d09 --- /dev/null +++ b/tests/Microsoft.Identity.Test.Unit.net45/OAuthClientTests.cs @@ -0,0 +1,239 @@ +// ------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// ------------------------------------------------------------------------------ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Identity.Client; +using Microsoft.Identity.Client.ApiConfig.Parameters; +using Microsoft.Identity.Client.Exceptions; +using Microsoft.Identity.Client.Internal.Requests; +using Microsoft.Identity.Test.Common; +using Microsoft.Identity.Test.Common.Core.Mocks; +using Microsoft.Identity.Test.Common.Mocks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using static Microsoft.Identity.Test.Unit.RequestsTests.InteractiveRequestTests; + +namespace Microsoft.Identity.Test.Unit +{ + [TestClass] + public class OAuthClientTests + { + [TestInitialize] + public void TestInitialize() + { + TestCommon.ResetStateAndInitMsal(); + } + + [TestMethod] + public void RedirectUriContainsFragmentErrorTest() + { + try + { + using (var harness = new MockHttpAndServiceBundle()) + { + AuthenticationRequestParameters parameters = harness.CreateAuthenticationRequestParameters( + MsalTestConstants.AuthorityHomeTenant, + MsalTestConstants.Scope, + null, + extraQueryParameters: new Dictionary + { + {"extra", "qp"} + }); + parameters.RedirectUri = new Uri("some://uri#fragment=not-so-good"); + parameters.LoginHint = MsalTestConstants.DisplayableId; + var interactiveParameters = new AcquireTokenInteractiveParameters + { + Prompt = Prompt.ForceLogin, + ExtraScopesToConsent = MsalTestConstants.ScopeForAnotherResource.ToArray(), + }; + + new InteractiveRequest( + harness.ServiceBundle, + parameters, + interactiveParameters, + new MockWebUI()); + + Assert.Fail("ArgumentException should be thrown here"); + } + } + catch (ArgumentException ae) + { + Assert.IsTrue(ae.Message.Contains(MsalErrorMessage.RedirectUriContainsFragment)); + } + } + + [TestMethod] + public void OAuthClient_FailsWithServiceExceptionWhenItCannotParseJsonResponse() + { + ValidateOathClient( + MockHelpers.CreateTooManyRequestsNonJsonResponse(), + exception => + { + MsalServiceException serverEx = exception.InnerException as MsalServiceException; + Assert.IsNotNull(serverEx); + Assert.AreEqual(429, serverEx.StatusCode); + Assert.AreEqual(MockHelpers.TooManyRequestsContent, serverEx.ResponseBody); + Assert.AreEqual(MockHelpers.TestRetryAfterDuration, serverEx.Headers.RetryAfter.Delta); + Assert.AreEqual(MsalError.NonParsableOAuthError, serverEx.ErrorCode); + }); + } + + [TestMethod] + public void OAuthClient_FailsWithServiceExceptionWhenItCanParseJsonResponse() + { + ValidateOathClient( + MockHelpers.CreateTooManyRequestsJsonResponse(), + exception => + { + MsalServiceException serverEx = exception.InnerException as MsalServiceException; + Assert.IsNotNull(serverEx); + Assert.AreEqual(429, serverEx.StatusCode); + Assert.AreEqual(MockHelpers.TestRetryAfterDuration, serverEx.Headers.RetryAfter.Delta); + Assert.AreEqual("Server overload", serverEx.ErrorCode); + }); + } + + [TestMethod] + public void OAuthClient_FailsWithServiceExceptionWhenEntireResponseIsNull() + { + ValidateOathClient( + null, + exception => + { + var innerException = exception.InnerException as InvalidOperationException; + Assert.IsNotNull(innerException); + }); + } + + [TestMethod] + public void OAuthClient_FailsWithServiceExceptionWhenResponseIsEmpty() + { + ValidateOathClient( + MockHelpers.CreateEmptyResponseMessage(), + exception => + { + var serverEx = exception.InnerException as MsalServiceException; + Assert.IsNotNull(serverEx); + Assert.AreEqual((int)HttpStatusCode.BadRequest, serverEx.StatusCode); + Assert.IsNotNull(serverEx.ResponseBody); + Assert.AreEqual(MsalError.HttpStatusCodeNotOk, serverEx.ErrorCode); + }); + } + + [TestMethod] + public void OAuthClient_FailsWithServiceExceptionWhenResponseIsNull() + { + ValidateOathClient( + MockHelpers.CreateNullResponseMessage(), + exception => + { + var serverEx = exception.InnerException as MsalServiceException; + Assert.IsNotNull(serverEx); + Assert.AreEqual((int)HttpStatusCode.BadRequest, serverEx.StatusCode); + Assert.IsNull(serverEx.ResponseBody); + Assert.AreEqual(MsalError.HttpStatusCodeNotOk, serverEx.ErrorCode); + }); + } + + [TestMethod] + public void OAuthClient_FailsWithServiceExceptionWhenResponseDoesNotContainAnErrorField() + { + ValidateOathClient( + MockHelpers.CreateNoErrorFieldResponseMessage(), + exception => + { + var serverEx = exception.InnerException as MsalServiceException; + Assert.IsNotNull(serverEx); + Assert.AreEqual((int)HttpStatusCode.BadRequest, serverEx.StatusCode); + Assert.IsNotNull(serverEx.ResponseBody); + Assert.AreEqual(MsalError.HttpStatusCodeNotOk, serverEx.ErrorCode); + }); + } + + [TestMethod] + public void OAuthClient_FailsWithServiceExceptionWhenResponseIsHttpNotFound() + { + ValidateOathClient( + MockHelpers.CreateHttpStatusNotFoundResponseMessage(), + exception => + { + var serverEx = exception.InnerException as MsalServiceException; + Assert.IsNotNull(serverEx); + Assert.AreEqual((int)HttpStatusCode.NotFound, serverEx.StatusCode); + Assert.IsNotNull(serverEx.ResponseBody); + Assert.AreEqual(MsalError.HttpStatusNotFound, serverEx.ErrorCode); + }); + } + + private static void ValidateOathClient(HttpResponseMessage httpResponseMessage, Action validationHandler) + { + using (MockHttpAndServiceBundle harness = new MockHttpAndServiceBundle()) + { + harness.HttpManager.AddMockHandler( + new MockHttpMessageHandler + { + ExpectedMethod = HttpMethod.Get, + ResponseMessage = httpResponseMessage + }); + + AuthenticationRequestParameters parameters = harness.CreateAuthenticationRequestParameters( + MsalTestConstants.AuthorityHomeTenant, + MsalTestConstants.Scope, + null); + parameters.RedirectUri = new Uri("some://uri"); + parameters.LoginHint = MsalTestConstants.DisplayableId; + AcquireTokenInteractiveParameters interactiveParameters = new AcquireTokenInteractiveParameters + { + Prompt = Prompt.SelectAccount, + ExtraScopesToConsent = MsalTestConstants.ScopeForAnotherResource.ToArray(), + }; + + InteractiveRequest request = new InteractiveRequest( + harness.ServiceBundle, + parameters, + interactiveParameters, + new MockWebUI()); + + try + { + request.ExecuteAsync(CancellationToken.None).Wait(); + Assert.Fail("MsalException should have been thrown here"); + } + catch (Exception exc) + { + validationHandler(exc); + } + } + } + + } +} diff --git a/tests/Microsoft.Identity.Test.Unit.net45/PublicApiTests/AcquireTokenSilentTests.cs b/tests/Microsoft.Identity.Test.Unit.net45/PublicApiTests/AcquireTokenSilentTests.cs index 66e6ad1e40..5be37a5301 100644 --- a/tests/Microsoft.Identity.Test.Unit.net45/PublicApiTests/AcquireTokenSilentTests.cs +++ b/tests/Microsoft.Identity.Test.Unit.net45/PublicApiTests/AcquireTokenSilentTests.cs @@ -138,7 +138,7 @@ public void AcquireTokenSilentScopeAndUserOverloadWithNoMatchingScopesInCacheTes Assert.IsNotNull(result); Assert.AreEqual(MsalTestConstants.DisplayableId, result.Account.Username); Assert.AreEqual(MsalTestConstants.ScopeForAnotherResource.AsSingleString(), result.Scopes.AsSingleString()); - Assert.AreEqual(2, app.UserTokenCacheInternal.Accessor.AccessTokenCount); + Assert.AreEqual(2, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count()); } } @@ -248,9 +248,8 @@ public void AcquireTokenSilentCacheOnlyLookupTest() Assert.AreEqual(MsalTestConstants.DisplayableId, result.Account.Username); Assert.AreEqual(MsalTestConstants.Scope.AsSingleString(), result.Scopes.AsSingleString()); - Assert.AreEqual(2, app.UserTokenCacheInternal.Accessor.AccessTokenCount); - Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.RefreshTokenCount); - + Assert.AreEqual(2, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count()); + Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.GetAllRefreshTokens().Count()); Assert.IsNotNull(receiver.EventsReceived.Find(anEvent => // Expect finding such an event anEvent[EventBase.EventNameKey].EndsWith("api_event") && anEvent[ApiEvent.WasSuccessfulKey] == "true" && anEvent[ApiEvent.ApiIdKey] == "31")); @@ -369,8 +368,8 @@ public void AcquireTokenSilentForceRefreshTest() Assert.AreEqual(MsalTestConstants.DisplayableId, result.Account.Username); Assert.AreEqual(MsalTestConstants.Scope.ToArray().AsSingleString(), result.Scopes.AsSingleString()); - Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.AccessTokenCount); - Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.RefreshTokenCount); + Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count()); + Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.GetAllRefreshTokens().Count()); } } @@ -413,9 +412,8 @@ public void AcquireTokenSilentForceRefreshMultipleTenantsTest() Assert.AreEqual(MsalTestConstants.DisplayableId, result.Account.Username); Assert.AreEqual(MsalTestConstants.Scope.ToArray().AsSingleString(), result.Scopes.AsSingleString()); - Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.AccessTokenCount); - Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.RefreshTokenCount); - + Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count()); + Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.GetAllRefreshTokens().Count()); httpManager.AddMockHandlerForTenantEndpointDiscovery(MsalTestConstants.AuthorityGuidTenant2); httpManager.AddMockHandler( @@ -441,8 +439,8 @@ public void AcquireTokenSilentForceRefreshMultipleTenantsTest() Assert.AreEqual(MsalTestConstants.DisplayableId, result2.Account.Username); Assert.AreEqual(MsalTestConstants.Scope.ToArray().AsSingleString(), result2.Scopes.AsSingleString()); - Assert.AreEqual(2, app.UserTokenCacheInternal.Accessor.AccessTokenCount); - Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.RefreshTokenCount); + Assert.AreEqual(2, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count()); + Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.GetAllRefreshTokens().Count()); httpManager.AddMockHandlerForTenantEndpointDiscovery(MsalTestConstants.AuthorityGuidTenant); @@ -469,8 +467,8 @@ public void AcquireTokenSilentForceRefreshMultipleTenantsTest() Assert.AreEqual(MsalTestConstants.DisplayableId, result3.Account.Username); Assert.AreEqual(MsalTestConstants.Scope.ToArray().AsSingleString(), result3.Scopes.AsSingleString()); - Assert.AreEqual(3, app.UserTokenCacheInternal.Accessor.AccessTokenCount); - Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.RefreshTokenCount); + Assert.AreEqual(3, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count()); + Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.GetAllRefreshTokens().Count()); // Use same authority as above, number of access tokens should remain constant httpManager.AddMockHandler( @@ -494,8 +492,8 @@ public void AcquireTokenSilentForceRefreshMultipleTenantsTest() Assert.AreEqual(MsalTestConstants.DisplayableId, result4.Account.Username); Assert.AreEqual(MsalTestConstants.Scope.ToArray().AsSingleString(), result4.Scopes.AsSingleString()); - Assert.AreEqual(3, app.UserTokenCacheInternal.Accessor.AccessTokenCount); - Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.RefreshTokenCount); + Assert.AreEqual(3, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count()); + Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.GetAllRefreshTokens().Count()); } } @@ -538,8 +536,8 @@ public void AcquireTokenSilentForceRefreshFalseMultipleTenantsTest() Assert.AreEqual(MsalTestConstants.DisplayableId, result.Account.Username); Assert.AreEqual(MsalTestConstants.Scope.ToArray().AsSingleString(), result.Scopes.AsSingleString()); - Assert.AreEqual(2, app.UserTokenCacheInternal.Accessor.AccessTokenCount); - Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.RefreshTokenCount); + Assert.AreEqual(2, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count()); + Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.GetAllRefreshTokens().Count()); httpManager.AddMockHandlerForTenantEndpointDiscovery(MsalTestConstants.AuthorityGuidTenant2); @@ -566,8 +564,8 @@ public void AcquireTokenSilentForceRefreshFalseMultipleTenantsTest() Assert.AreEqual(MsalTestConstants.DisplayableId, result2.Account.Username); Assert.AreEqual(MsalTestConstants.Scope.ToArray().AsSingleString(), result2.Scopes.AsSingleString()); - Assert.AreEqual(3, app.UserTokenCacheInternal.Accessor.AccessTokenCount); - Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.RefreshTokenCount); + Assert.AreEqual(3, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count()); + Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.GetAllRefreshTokens().Count()); httpManager.AddMockHandlerForTenantEndpointDiscovery(MsalTestConstants.AuthorityGuidTenant); @@ -594,8 +592,8 @@ public void AcquireTokenSilentForceRefreshFalseMultipleTenantsTest() Assert.AreEqual(MsalTestConstants.DisplayableId, result3.Account.Username); Assert.AreEqual(MsalTestConstants.Scope.ToArray().AsSingleString(), result3.Scopes.AsSingleString()); - Assert.AreEqual(4, app.UserTokenCacheInternal.Accessor.AccessTokenCount); - Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.RefreshTokenCount); + Assert.AreEqual(4, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count()); + Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.GetAllRefreshTokens().Count()); } } diff --git a/tests/Microsoft.Identity.Test.Unit.net45/PublicApiTests/ConfidentialClientApplicationTests.cs b/tests/Microsoft.Identity.Test.Unit.net45/PublicApiTests/ConfidentialClientApplicationTests.cs index 1c58e393a1..3a7fff45bb 100644 --- a/tests/Microsoft.Identity.Test.Unit.net45/PublicApiTests/ConfidentialClientApplicationTests.cs +++ b/tests/Microsoft.Identity.Test.Unit.net45/PublicApiTests/ConfidentialClientApplicationTests.cs @@ -251,12 +251,12 @@ public async Task ConfidentialClientUsingSecretTestAsync() Assert.AreEqual(MsalTestConstants.Scope.AsSingleString(), result.Scopes.AsSingleString()); // make sure user token cache is empty - Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.AccessTokenCount); - Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.RefreshTokenCount); + Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count()); + Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllRefreshTokens().Count()); // check app token cache count to be 1 - Assert.AreEqual(1, app.AppTokenCacheInternal.Accessor.AccessTokenCount); - Assert.AreEqual(0, app.AppTokenCacheInternal.Accessor.RefreshTokenCount); // no refresh tokens are returned + Assert.AreEqual(1, app.AppTokenCacheInternal.Accessor.GetAllAccessTokens().Count()); + Assert.AreEqual(0, app.AppTokenCacheInternal.Accessor.GetAllRefreshTokens().Count()); // call AcquireTokenForClientAsync again to get result back from the cache result = await app.AcquireTokenForClientAsync(MsalTestConstants.Scope.ToArray()).ConfigureAwait(false); @@ -265,12 +265,12 @@ public async Task ConfidentialClientUsingSecretTestAsync() Assert.AreEqual(MsalTestConstants.Scope.AsSingleString(), result.Scopes.AsSingleString()); // make sure user token cache is empty - Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.AccessTokenCount); - Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.RefreshTokenCount); + Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count()); + Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllRefreshTokens().Count()); // check app token cache count to be 1 - Assert.AreEqual(1, app.AppTokenCacheInternal.Accessor.AccessTokenCount); - Assert.AreEqual(0, app.AppTokenCacheInternal.Accessor.RefreshTokenCount); // no refresh tokens are returned + Assert.AreEqual(1, app.AppTokenCacheInternal.Accessor.GetAllAccessTokens().Count()); + Assert.AreEqual(0, app.AppTokenCacheInternal.Accessor.GetAllRefreshTokens().Count()); } } @@ -317,12 +317,12 @@ public async Task ConfidentialClientUsingCertificateTestAsync() Assert.AreEqual(MsalTestConstants.Scope.AsSingleString(), result.Scopes.AsSingleString()); // make sure user token cache is empty - Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.AccessTokenCount); - Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.RefreshTokenCount); + Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count()); + Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllRefreshTokens().Count()); // check app token cache count to be 1 - Assert.AreEqual(1, app.AppTokenCacheInternal.Accessor.AccessTokenCount); - Assert.AreEqual(0, app.AppTokenCacheInternal.Accessor.RefreshTokenCount); // no refresh tokens are returned + Assert.AreEqual(1, app.AppTokenCacheInternal.Accessor.GetAllAccessTokens().Count()); + Assert.AreEqual(0, app.AppTokenCacheInternal.Accessor.GetAllRefreshTokens().Count()); // no RTs are returned // assert client credential Assert.IsNotNull(cc.Assertion); @@ -671,8 +671,8 @@ public async Task AuthorizationCodeRequestTestAsync() var result = await app.AcquireTokenByAuthorizationCodeAsync("some-code", MsalTestConstants.Scope) .ConfigureAwait(false); Assert.IsNotNull(result); - Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.AccessTokenCount); - Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.RefreshTokenCount); + Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count()); + Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.GetAllRefreshTokens().Count()); app = ConfidentialClientApplicationBuilder.Create(MsalTestConstants.ClientId) .WithAuthority(new Uri("https://" + MsalTestConstants.ProductionPrefNetworkEnvironment + "/tfp/home/policy"), true) @@ -708,16 +708,16 @@ public async Task AcquireTokenByRefreshTokenTestAsync() var result = await (app as IByRefreshToken).AcquireTokenByRefreshTokenAsync(null, "SomeRefreshToken").ConfigureAwait(false); - Assert.AreEqual(app.UserTokenCacheInternal.Accessor.RefreshTokenCount, 1); - Assert.AreEqual(app.UserTokenCacheInternal.Accessor.AccessTokenCount, 1); + Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count()); + Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.GetAllRefreshTokens().Count()); Assert.IsNotNull(result.AccessToken); Assert.AreEqual(result.AccessToken, "some-access-token"); app.UserTokenCacheInternal.Clear(); httpManager.AddSuccessTokenResponseMockHandlerForPost(MsalTestConstants.AuthorityCommonTenant); result = await ((IByRefreshToken)app).AcquireTokenByRefreshTokenAsync(MsalTestConstants.Scope, "SomeRefreshToken").ConfigureAwait(false); - Assert.AreEqual(app.UserTokenCacheInternal.Accessor.RefreshTokenCount, 1); - Assert.AreEqual(app.UserTokenCacheInternal.Accessor.AccessTokenCount, 1); + Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count()); + Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.GetAllRefreshTokens().Count()); Assert.IsNotNull(result.AccessToken); Assert.AreEqual(result.AccessToken, "some-access-token"); } @@ -780,4 +780,4 @@ private void AfterCacheAccess(TokenCacheNotificationArgs args) } } -#endif \ No newline at end of file +#endif diff --git a/tests/Microsoft.Identity.Test.Unit.net45/PublicApiTests/PublicClientApplicationTests.cs b/tests/Microsoft.Identity.Test.Unit.net45/PublicApiTests/PublicClientApplicationTests.cs index e717216ce0..8c1f87fc74 100644 --- a/tests/Microsoft.Identity.Test.Unit.net45/PublicApiTests/PublicClientApplicationTests.cs +++ b/tests/Microsoft.Identity.Test.Unit.net45/PublicApiTests/PublicClientApplicationTests.cs @@ -283,15 +283,7 @@ public void AcquireTokenSameUserTest() PublicClientApplication app = PublicClientApplicationBuilder.Create(MsalTestConstants.ClientId) .WithAuthority(new Uri(ClientApplicationBase.DefaultAuthority), true) .WithHttpManager(harness.HttpManager) - .BuildConcrete(); - - MockWebUI ui = new MockWebUI() - { - MockResult = new AuthorizationResult( - AuthorizationStatus.Success, - MsalTestConstants.AuthorityHomeTenant + "?code=some-code") - }; - + .BuildConcrete(); MsalMockHelpers.ConfigureMockWebUI( app.ServiceBundle.PlatformProxy, new AuthorizationResult(AuthorizationStatus.Success, app.AppConfig.RedirectUri + "?code=some-code")); @@ -454,7 +446,7 @@ public void AcquireTokenDifferentUserReturnedFromServiceTest() var users = app.GetAccountsAsync().Result; Assert.AreEqual(1, users.Count()); - Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.AccessTokenCount); + Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count()); } } @@ -511,7 +503,7 @@ public void AcquireTokenNullUserPassedInAndNewUserReturnedFromServiceTest() Assert.AreEqual(MsalTestConstants.DisplayableId, result.Account.Username); var users = app.GetAccountsAsync().Result; Assert.AreEqual(2, users.Count()); - Assert.AreEqual(2, app.UserTokenCacheInternal.Accessor.AccessTokenCount); + Assert.AreEqual(2, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count()); } } @@ -578,7 +570,7 @@ public void GetUsersTest() app.UserTokenCacheInternal.Accessor.SaveAccount(accountCacheItem); - Assert.AreEqual(2, app.UserTokenCacheInternal.Accessor.RefreshTokenCount); + Assert.AreEqual(2, app.UserTokenCacheInternal.Accessor.GetAllRefreshTokens().Count()); users = app.GetAccountsAsync().Result; Assert.IsNotNull(users); Assert.AreEqual(2, users.Count()); @@ -591,13 +583,61 @@ public void GetUsersTest() MockHelpers.CreateClientInfo(MsalTestConstants.Uid + "more1", MsalTestConstants.Utid)); app.UserTokenCacheInternal.Accessor.SaveRefreshToken(rtItem); - Assert.AreEqual(3, app.UserTokenCacheInternal.Accessor.RefreshTokenCount); + Assert.AreEqual(3, app.UserTokenCacheInternal.Accessor.GetAllRefreshTokens().Count()); users = app.GetAccountsAsync().Result; Assert.IsNotNull(users); Assert.AreEqual(2, users.Count()); } } + [TestMethod] + public async Task TestAccountAcrossMultipleClientIdsAsync() + { + // Arrange + + PublicClientApplication app = PublicClientApplicationBuilder.Create(MsalTestConstants.ClientId).BuildConcrete(); + + // Populate with tokens tied to ClientId2 + _tokenCacheHelper.PopulateCache(app.UserTokenCacheInternal.Accessor, clientId: MsalTestConstants.ClientId2); + + app.UserTokenCacheInternal.Accessor.AssertItemCount( + expectedAtCount: 2, + expectedRtCount: 1, + expectedAccountCount: 1, + expectedIdtCount: 1, + expectedAppMetadataCount: 1); + + // Act + var accounts = await app.GetAccountsAsync().ConfigureAwait(false); + + // Assert - an account is returned even if app is scoped to ClientId1 + Assert.AreEqual(1, accounts.Count()); + + // Arrange + + // Populate for clientid2 + _tokenCacheHelper.PopulateCache(app.UserTokenCacheInternal.Accessor, clientId: MsalTestConstants.ClientId); + + app.UserTokenCacheInternal.Accessor.AssertItemCount( + expectedAtCount: 4, + expectedRtCount: 2, + expectedAccountCount: 1, // still 1 account + expectedIdtCount: 2, + expectedAppMetadataCount: 2); + + // Act + await app.RemoveAsync(accounts.Single()).ConfigureAwait(false); + + // Assert + app.UserTokenCacheInternal.Accessor.AssertItemCount( + expectedAtCount: 0, + expectedRtCount: 0, + expectedAccountCount: 0, + expectedIdtCount: 0, + expectedAppMetadataCount: 2); // app metadata is never deleted + + } + [TestMethod] [TestCategory("PublicClientApplicationTests")] public void GetUsersAndSignThemOutTest() @@ -610,8 +650,8 @@ public void GetUsersAndSignThemOutTest() app.RemoveAsync(user).Wait(); } - Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.AccessTokenCount); - Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.RefreshTokenCount); + Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count()); + Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllRefreshTokens().Count()); } diff --git a/tests/Microsoft.Identity.Test.Unit.net45/RequestsTests/BrokerRequestTests.cs b/tests/Microsoft.Identity.Test.Unit.net45/RequestsTests/BrokerRequestTests.cs index 8dd902499a..6867c42e18 100644 --- a/tests/Microsoft.Identity.Test.Unit.net45/RequestsTests/BrokerRequestTests.cs +++ b/tests/Microsoft.Identity.Test.Unit.net45/RequestsTests/BrokerRequestTests.cs @@ -131,7 +131,7 @@ public void BrokerUnknownErrorResponseTest() } - private void ValidateBrokerResponse(MsalTokenResponse msalTokenResponse, OAuthClientValidationHandler validationHandler) + private void ValidateBrokerResponse(MsalTokenResponse msalTokenResponse, Action validationHandler) { try { diff --git a/tests/Microsoft.Identity.Test.Unit.net45/RequestsTests/DeviceCodeRequestTests.cs b/tests/Microsoft.Identity.Test.Unit.net45/RequestsTests/DeviceCodeRequestTests.cs index e07e16653d..9b7be4b027 100644 --- a/tests/Microsoft.Identity.Test.Unit.net45/RequestsTests/DeviceCodeRequestTests.cs +++ b/tests/Microsoft.Identity.Test.Unit.net45/RequestsTests/DeviceCodeRequestTests.cs @@ -100,10 +100,10 @@ public void TestDeviceCodeAuthSuccess() var cache = parameters.CacheSessionManager.TokenCacheInternal; // Check that cache is empty - Assert.AreEqual(0, cache.Accessor.AccessTokenCount); - Assert.AreEqual(0, cache.Accessor.AccountCount); - Assert.AreEqual(0, cache.Accessor.IdTokenCount); - Assert.AreEqual(0, cache.Accessor.RefreshTokenCount); + Assert.AreEqual(0, cache.Accessor.GetAllAccessTokens().Count()); + Assert.AreEqual(0, cache.Accessor.GetAllRefreshTokens().Count()); + Assert.AreEqual(0, cache.Accessor.GetAllIdTokens().Count()); + Assert.AreEqual(0, cache.Accessor.GetAllAccounts().Count()); DeviceCodeResult actualDeviceCodeResult = null; @@ -135,10 +135,10 @@ public void TestDeviceCodeAuthSuccess() CoreAssert.AreScopesEqual(expectedScopes.AsSingleString(), actualDeviceCodeResult.Scopes.AsSingleString()); // Validate that entries were added to cache - Assert.AreEqual(1, cache.Accessor.AccessTokenCount); - Assert.AreEqual(1, cache.Accessor.AccountCount); - Assert.AreEqual(1, cache.Accessor.IdTokenCount); - Assert.AreEqual(1, cache.Accessor.RefreshTokenCount); + Assert.AreEqual(1, cache.Accessor.GetAllAccessTokens().Count()); + Assert.AreEqual(1, cache.Accessor.GetAllRefreshTokens().Count()); + Assert.AreEqual(1, cache.Accessor.GetAllIdTokens().Count()); + Assert.AreEqual(1, cache.Accessor.GetAllAccounts().Count()); } } @@ -333,4 +333,4 @@ private class _LogData public bool IsPii { get; set; } } } -} \ No newline at end of file +} diff --git a/tests/Microsoft.Identity.Test.Unit.net45/RequestsTests/FociTests.cs b/tests/Microsoft.Identity.Test.Unit.net45/RequestsTests/FociTests.cs new file mode 100644 index 0000000000..e6846c2aff --- /dev/null +++ b/tests/Microsoft.Identity.Test.Unit.net45/RequestsTests/FociTests.cs @@ -0,0 +1,320 @@ +// ------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// ------------------------------------------------------------------------------ + +using System; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Identity.Client; +using Microsoft.Identity.Client.AppConfig; +using Microsoft.Identity.Client.UI; +using Microsoft.Identity.Client.Utils; +using Microsoft.Identity.Test.Common; +using Microsoft.Identity.Test.Common.Core.Helpers; +using Microsoft.Identity.Test.Common.Core.Mocks; +using Microsoft.Identity.Test.Common.Mocks; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Identity.Test.Unit.RequestsTests +{ + [TestClass] + public class FociTests + { + private enum ServerTokenResponse + { + NonFociToken, + FociToken, + Error + } + + private string _inMemoryCache; + private PublicClientApplication _appA; + private PublicClientApplication _appB; + private MockHttpAndServiceBundle _harness; + private bool _instanceAndEndpointRequestPerformed = false; + + [TestInitialize] + public void Init() + { + TestCommon.ResetStateAndInitMsal(); + _inMemoryCache = "{}"; + _instanceAndEndpointRequestPerformed = false; + } + + /// + /// A and B apps part of the family. A acquires a token interactively. B can now acquire a token silently. + /// + [TestMethod] + public async Task FociHappyPathAsync() + { + // Arrange + using (_harness = new MockHttpAndServiceBundle()) + { + InitApps(); + + // Act + await InteractiveAsync(_appA, ServerTokenResponse.FociToken).ConfigureAwait(false); + await SilentAsync(_appB, ServerTokenResponse.FociToken).ConfigureAwait(false); + + // Assert + await AssertAccountsAsync().ConfigureAwait(false); + + // Make sure smth reloads the cache before using the Accessor from the other app (GetAccounts will) + Assert.AreEqual(2, _appA.UserTokenCacheInternal.Accessor.GetAllAppMetadata().Count()); + Assert.IsTrue(_appA.UserTokenCacheInternal.Accessor.GetAllAppMetadata().All(am => am.FamilyId == "1")); + Assert.AreEqual("1", _appA.UserTokenCacheInternal.Accessor.GetAllRefreshTokens().Single().FamilyId); + } + } + + /// + /// A is part of the family, B is not. B fails gracefully trying to get a token silently + /// + [TestMethod] + public async Task FociAndNonFociAppsCoexistAsync() + { + using (_harness = new MockHttpAndServiceBundle()) + { + InitApps(); + + // Act + await InteractiveAsync(_appA, ServerTokenResponse.FociToken).ConfigureAwait(false); + + // B cannot acquire a token interactivelty, but will try to use FRT + AssertException.TaskThrows(() => SilentAsync(_appB, ServerTokenResponse.Error)); + + // B can resume acquiring tokens silently via the normal RT, after a non + await InteractiveAsync(_appB, ServerTokenResponse.NonFociToken).ConfigureAwait(false); + await SilentAsync(_appB, ServerTokenResponse.NonFociToken).ConfigureAwait(false); + + // Assert + await AssertAccountsAsync().ConfigureAwait(false); + AssertAppHasRT(_appB); + AssertFRTExists(); + + } + } + + /// + /// B is not part of the family but has an RT. B joins the family. B starts using the FRT. + /// + [TestMethod] + public async Task FociAppWithTokensJoinsFamilyAsync() + { + using (_harness = new MockHttpAndServiceBundle()) + { + InitApps(); + + // A is in the family and has brought down an FRT. B has brought down it's RT. + await InteractiveAsync(_appA, ServerTokenResponse.FociToken).ConfigureAwait(false); + await InteractiveAsync(_appB, ServerTokenResponse.NonFociToken).ConfigureAwait(false); + + // B refreshes it's RT and gets an FRT response + await SilentAsync(_appB, ServerTokenResponse.FociToken).ConfigureAwait(false); + + // remove B's RT + var art = _appB.UserTokenCacheInternal.Accessor.GetAllRefreshTokens() + .Single(rt => _appB.ClientId == rt.ClientId && string.IsNullOrEmpty(rt.FamilyId)); + _appB.UserTokenCacheInternal.Accessor.DeleteRefreshToken(art.GetKey()); + + // B can still use the FRT + await SilentAsync(_appB, ServerTokenResponse.FociToken).ConfigureAwait(false); + + // Assert + await AssertAccountsAsync().ConfigureAwait(false); + AssertFRTExists(); + } + } + + /// + /// A and B apps part of the family. B leaves the family. B fails gracefully trying to get a token silently + /// + [TestMethod] + public async Task FociAppLeavesFamilyAsync() + { + using (_harness = new MockHttpAndServiceBundle()) + { + InitApps(); + + // A and B are part of the family + await InteractiveAsync(_appA, ServerTokenResponse.FociToken).ConfigureAwait(false); + await SilentAsync(_appB, ServerTokenResponse.FociToken).ConfigureAwait(false); + + // B leaves the family -> STS will not refresh its token based on the FRT + AssertException.TaskThrows(() => SilentAsync(_appB, ServerTokenResponse.Error)); + + // B can resume acquiring tokens silently via the normal RT, after an interactive flow + await InteractiveAsync(_appB, ServerTokenResponse.NonFociToken).ConfigureAwait(false); + await SilentAsync(_appB, ServerTokenResponse.NonFociToken).ConfigureAwait(false); + + // Assert + await AssertAccountsAsync().ConfigureAwait(false); + + AssertAppHasRT(_appB); + AssertFRTExists(); + } + } + + + private void AssertAppMetadata(PublicClientApplication app, bool partOfFamily) + { + Assert.IsNotNull( + app.UserTokenCacheInternal.Accessor.GetAllAppMetadata() + .Single(m => m.ClientId == app.ClientId && + partOfFamily == !string.IsNullOrEmpty(m.FamilyId))); + } + + private async Task SilentAsync( + PublicClientApplication app, + ServerTokenResponse serverTokenResponse) + { + var account = (await app.GetAccountsAsync().ConfigureAwait(false)).Single(); + + // 2 network calls - one for endpoint discovery on the tenanted authority, one to refresh the token + if (!_instanceAndEndpointRequestPerformed) + { + _instanceAndEndpointRequestPerformed = true; + _harness.HttpManager.AddInstanceDiscoveryMockHandler(); + + _harness.HttpManager.AddMockHandlerForTenantEndpointDiscovery(MsalTestConstants.AuthorityUtidTenant); + } + + _harness.HttpManager.AddMockHandler( + new MockHttpMessageHandler() + { + ExpectedMethod = HttpMethod.Post, + ResponseMessage = + (serverTokenResponse == ServerTokenResponse.Error) ? + MockHelpers.CreateInvalidGrantTokenResponseMessage() : + MockHelpers.CreateSuccessTokenResponseMessage( + MsalTestConstants.UniqueId, + MsalTestConstants.DisplayableId, + MsalTestConstants.Scope.ToArray(), + foci: serverTokenResponse == ServerTokenResponse.FociToken) + }); + + AuthenticationResult resultB = await app.AcquireTokenSilent(MsalTestConstants.Scope, account) + .WithForceRefresh(true) + .ExecuteAsync() + .ConfigureAwait(false); + + Assert.IsNotNull(resultB.AccessToken); + AssertAppMetadata(app, serverTokenResponse == ServerTokenResponse.FociToken); + + } + + private async Task InteractiveAsync(PublicClientApplication app, ServerTokenResponse serverTokenResponse) + { + if (serverTokenResponse == ServerTokenResponse.Error) + { + throw new NotImplementedException("test error"); + } + + if (!_instanceAndEndpointRequestPerformed) + { + _instanceAndEndpointRequestPerformed = true; + + _harness.HttpManager.AddInstanceDiscoveryMockHandler(); + _harness.HttpManager.AddMockHandlerForTenantEndpointDiscovery(MsalTestConstants.AuthorityUtidTenant); + } + + MsalMockHelpers.ConfigureMockWebUI( + app.ServiceBundle.PlatformProxy, + new AuthorizationResult(AuthorizationStatus.Success, app.AppConfig.RedirectUri + "?code=some-code")); + + _harness.HttpManager.AddSuccessTokenResponseMockHandlerForPost( + MsalTestConstants.AuthorityUtidTenant, + foci: serverTokenResponse == ServerTokenResponse.FociToken); + + // Acquire token interactively for A + AuthenticationResult result = await app.AcquireTokenAsync(MsalTestConstants.Scope).ConfigureAwait(false); + Assert.IsNotNull(result.Account); + AssertAppMetadata(app, serverTokenResponse == ServerTokenResponse.FociToken); + + } + + private void InitApps() + { + + _appA = PublicClientApplicationBuilder.Create(MsalTestConstants.ClientId) + .WithHttpManager(_harness.HttpManager) + .WithAuthority(MsalTestConstants.AuthorityUtidTenant) + .BuildConcrete(); + + _appB = PublicClientApplicationBuilder.Create(MsalTestConstants.ClientId2) + .WithHttpManager(_harness.HttpManager) + .WithAuthority(MsalTestConstants.AuthorityUtidTenant) + .BuildConcrete(); + ConfigureCacheSerialization(_appA); + ConfigureCacheSerialization(_appB); + } + + + private void ConfigureCacheSerialization(IPublicClientApplication pca) + { + pca.UserTokenCache.SetBeforeAccess(notificationArgs => + { + byte[] bytes = Encoding.UTF8.GetBytes(_inMemoryCache); + notificationArgs.TokenCache.DeserializeMsalV3(bytes); + }); + + pca.UserTokenCache.SetAfterAccess(notificationArgs => + { + if (notificationArgs.HasStateChanged) + { + byte[] bytes = notificationArgs.TokenCache.SerializeMsalV3(); + _inMemoryCache = Encoding.UTF8.GetString(bytes); + } + }); + } + + + private async Task AssertAccountsAsync() + { + Assert.AreEqual(1, (await _appA.GetAccountsAsync().ConfigureAwait(false)).Count()); + Assert.AreEqual(1, (await _appB.GetAccountsAsync().ConfigureAwait(false)).Count()); + } + + + private void AssertFRTExists() + { + Assert.IsTrue(_appA.UserTokenCacheInternal.Accessor.GetAllRefreshTokens() + .Any(rt => !string.IsNullOrEmpty(rt.FamilyId)), + "The FRT still exists"); + } + + private void AssertAppHasRT(PublicClientApplication app) + { + Assert.IsTrue(app.UserTokenCacheInternal.Accessor.GetAllRefreshTokens() + .Any(rt => rt.ClientId == _appB.ClientId && string.IsNullOrEmpty(rt.FamilyId)), + "App B has a normal RT associated"); + + } + + + } +} diff --git a/tests/Microsoft.Identity.Test.Unit.net45/RequestsTests/IntegratedWindowsAuthUsernamePasswordTests.cs b/tests/Microsoft.Identity.Test.Unit.net45/RequestsTests/IntegratedWindowsAuthUsernamePasswordTests.cs index 771aa60353..1d5bc08101 100644 --- a/tests/Microsoft.Identity.Test.Unit.net45/RequestsTests/IntegratedWindowsAuthUsernamePasswordTests.cs +++ b/tests/Microsoft.Identity.Test.Unit.net45/RequestsTests/IntegratedWindowsAuthUsernamePasswordTests.cs @@ -395,7 +395,7 @@ public void MexEndpointFailsToResolveTest() Assert.AreEqual("parsing_ws_metadata_exchange_failed", result.ErrorCode); // There should be no cached entries. - Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.AccessTokenCount); + Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count()); } } @@ -431,7 +431,7 @@ public void MexDoesNotReturnAuthEndpointTest() Assert.AreEqual(MsalError.ParsingWsTrustResponseFailed, result.ErrorCode); // There should be no cached entries. - Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.AccessTokenCount); + Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count()); } } @@ -465,7 +465,7 @@ public void MexParsingFailsTest() Assert.AreEqual("Response status code does not indicate success: 404 (NotFound).", result.Message); // There should be no cached entries. - Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.AccessTokenCount); + Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count()); } } @@ -504,7 +504,7 @@ public void FederatedUsernameNullPasswordTest() Assert.AreEqual(MsalError.ParsingWsTrustResponseFailed, result.ErrorCode); // There should be no cached entries. - Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.AccessTokenCount); + Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count()); } } @@ -547,7 +547,7 @@ public void FederatedUsernamePasswordCommonAuthorityTest() Assert.AreEqual(MsalError.InvalidRequest, result.ErrorCode); // There should be no cached entries. - Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.AccessTokenCount); + Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count()); } } @@ -599,7 +599,7 @@ public void ManagedUsernamePasswordCommonAuthorityTest() Assert.AreEqual(MsalError.InvalidRequest, result.ErrorCode); // There should be no cached entries. - Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.AccessTokenCount); + Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count()); } } @@ -669,7 +669,7 @@ public void ManagedUsernameNoPasswordAcquireTokenTest() Assert.AreEqual(MsalError.PasswordRequiredForManagedUserError, result.ErrorCode); // There should be no cached entries. - Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.AccessTokenCount); + Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count()); } } @@ -715,7 +715,7 @@ public void ManagedUsernameIncorrectPasswordAcquireTokenTest() Assert.AreEqual(MsalError.InvalidGrantError, result.ErrorCode); // There should be no cached entries. - Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.AccessTokenCount); + Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count()); } } #endif diff --git a/tests/Microsoft.Identity.Test.Unit.net45/RequestsTests/InteractiveRequestTests.cs b/tests/Microsoft.Identity.Test.Unit.net45/RequestsTests/InteractiveRequestTests.cs index ab83cb7e45..278dfa534c 100644 --- a/tests/Microsoft.Identity.Test.Unit.net45/RequestsTests/InteractiveRequestTests.cs +++ b/tests/Microsoft.Identity.Test.Unit.net45/RequestsTests/InteractiveRequestTests.cs @@ -1,4 +1,4 @@ -// ------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. // All rights reserved. @@ -25,8 +25,15 @@ // // ------------------------------------------------------------------------------ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; using Microsoft.Identity.Client; using Microsoft.Identity.Client.ApiConfig.Parameters; +using Microsoft.Identity.Client.Cache.Items; using Microsoft.Identity.Client.Exceptions; using Microsoft.Identity.Client.Internal.Requests; using Microsoft.Identity.Client.OAuth2; @@ -38,21 +45,12 @@ using Microsoft.Identity.Test.Common.Mocks; using Microsoft.Identity.Test.Unit.PublicApiTests; using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Identity.Client.Cache.Items; -using Microsoft.Identity.Client.Internal.Broker; namespace Microsoft.Identity.Test.Unit.RequestsTests { [TestClass] public class InteractiveRequestTests - { + { [TestInitialize] public void TestInitialize() { @@ -115,8 +113,8 @@ public async Task WithExtraQueryParamsAndClaimsAsync() AuthenticationResult result = await request.RunAsync(CancellationToken.None).ConfigureAwait(false); Assert.IsNotNull(result); - Assert.AreEqual(1, ((ITokenCacheInternal)cache).Accessor.RefreshTokenCount); - Assert.AreEqual(1, ((ITokenCacheInternal)cache).Accessor.AccessTokenCount); + Assert.AreEqual(1, ((ITokenCacheInternal)cache).Accessor.GetAllRefreshTokens().Count()); + Assert.AreEqual(1, ((ITokenCacheInternal)cache).Accessor.GetAllAccessTokens().Count()); Assert.AreEqual(result.AccessToken, "some-access-token"); } } @@ -182,8 +180,8 @@ public void NoCacheLookup() task.Wait(); AuthenticationResult result = task.Result; Assert.IsNotNull(result); - Assert.AreEqual(1, ((ITokenCacheInternal)cache).Accessor.RefreshTokenCount); - Assert.AreEqual(2, ((ITokenCacheInternal)cache).Accessor.AccessTokenCount); + Assert.AreEqual(1, ((ITokenCacheInternal)cache).Accessor.GetAllRefreshTokens().Count()); + Assert.AreEqual(2, ((ITokenCacheInternal)cache).Accessor.GetAllAccessTokens().Count()); Assert.AreEqual(result.AccessToken, "some-access-token"); Assert.IsNotNull( @@ -243,159 +241,6 @@ public void RedirectUriContainsFragmentErrorTest() } } - [TestMethod] - [TestCategory("InteractiveRequestTests")] - public void OAuthClient_FailsWithServiceExceptionWhenItCannotParseJsonResponse() - { - ValidateOathClient( - MockHelpers.CreateTooManyRequestsNonJsonResponse(), - exception => - { - MsalServiceException serverEx = exception.InnerException as MsalServiceException; - Assert.IsNotNull(serverEx); - Assert.AreEqual(429, serverEx.StatusCode); - Assert.AreEqual(MockHelpers.TooManyRequestsContent, serverEx.ResponseBody); - Assert.AreEqual(MockHelpers.TestRetryAfterDuration, serverEx.Headers.RetryAfter.Delta); - Assert.AreEqual(MsalError.NonParsableOAuthError, serverEx.ErrorCode); - }); - } - - [TestMethod] - [TestCategory("InteractiveRequestTests")] - public void OAuthClient_FailsWithServiceExceptionWhenItCanParseJsonResponse() - { - ValidateOathClient( - MockHelpers.CreateTooManyRequestsJsonResponse(), - exception => - { - MsalServiceException serverEx = exception.InnerException as MsalServiceException; - Assert.IsNotNull(serverEx); - Assert.AreEqual(429, serverEx.StatusCode); - Assert.AreEqual(MockHelpers.TestRetryAfterDuration, serverEx.Headers.RetryAfter.Delta); - Assert.AreEqual("Server overload", serverEx.ErrorCode); - }); - } - - [TestMethod] - [TestCategory("InteractiveRequestTests")] - public void OAuthClient_FailsWithServiceExceptionWhenEntireResponseIsNull() - { - ValidateOathClient( - null, - exception => - { - var innerException = exception.InnerException as InvalidOperationException; - Assert.IsNotNull(innerException); - }); - } - - [TestMethod] - [TestCategory("InteractiveRequestTests")] - public void OAuthClient_FailsWithServiceExceptionWhenResponseIsEmpty() - { - ValidateOathClient( - MockHelpers.CreateEmptyResponseMessage(), - exception => - { - var serverEx = exception.InnerException as MsalServiceException; - Assert.IsNotNull(serverEx); - Assert.AreEqual((int)HttpStatusCode.BadRequest, serverEx.StatusCode); - Assert.IsNotNull(serverEx.ResponseBody); - Assert.AreEqual(MsalError.HttpStatusCodeNotOk, serverEx.ErrorCode); - }); - } - - [TestMethod] - [TestCategory("InteractiveRequestTests")] - public void OAuthClient_FailsWithServiceExceptionWhenResponseIsNull() - { - ValidateOathClient( - MockHelpers.CreateNullResponseMessage(), - exception => - { - var serverEx = exception.InnerException as MsalServiceException; - Assert.IsNotNull(serverEx); - Assert.AreEqual((int)HttpStatusCode.BadRequest, serverEx.StatusCode); - Assert.IsNull(serverEx.ResponseBody); - Assert.AreEqual(MsalError.HttpStatusCodeNotOk, serverEx.ErrorCode); - }); - } - - [TestMethod] - [TestCategory("InteractiveRequestTests")] - public void OAuthClient_FailsWithServiceExceptionWhenResponseDoesNotContainAnErrorField() - { - ValidateOathClient( - MockHelpers.CreateNoErrorFieldResponseMessage(), - exception => - { - var serverEx = exception.InnerException as MsalServiceException; - Assert.IsNotNull(serverEx); - Assert.AreEqual((int)HttpStatusCode.BadRequest, serverEx.StatusCode); - Assert.IsNotNull(serverEx.ResponseBody); - Assert.AreEqual(MsalError.HttpStatusCodeNotOk, serverEx.ErrorCode); - }); - } - - [TestMethod] - [TestCategory("InteractiveRequestTests")] - public void OAuthClient_FailsWithServiceExceptionWhenResponseIsHttpNotFound() - { - ValidateOathClient( - MockHelpers.CreateHttpStatusNotFoundResponseMessage(), - exception => - { - var serverEx = exception.InnerException as MsalServiceException; - Assert.IsNotNull(serverEx); - Assert.AreEqual((int)HttpStatusCode.NotFound, serverEx.StatusCode); - Assert.IsNotNull(serverEx.ResponseBody); - Assert.AreEqual(MsalError.HttpStatusNotFound, serverEx.ErrorCode); - }); - } - - internal delegate void OAuthClientValidationHandler(Exception ex); - - private static void ValidateOathClient(HttpResponseMessage httpResponseMessage, OAuthClientValidationHandler validationHandler) - { - using (MockHttpAndServiceBundle harness = new MockHttpAndServiceBundle()) - { - harness.HttpManager.AddMockHandler( - new MockHttpMessageHandler - { - ExpectedMethod = HttpMethod.Get, - ResponseMessage = httpResponseMessage - }); - - AuthenticationRequestParameters parameters = harness.CreateAuthenticationRequestParameters( - MsalTestConstants.AuthorityHomeTenant, - MsalTestConstants.Scope, - null); - parameters.RedirectUri = new Uri("some://uri"); - parameters.LoginHint = MsalTestConstants.DisplayableId; - AcquireTokenInteractiveParameters interactiveParameters = new AcquireTokenInteractiveParameters - { - Prompt = Prompt.SelectAccount, - ExtraScopesToConsent = MsalTestConstants.ScopeForAnotherResource.ToArray(), - }; - - InteractiveRequest request = new InteractiveRequest( - harness.ServiceBundle, - parameters, - interactiveParameters, - new MockWebUI()); - - try - { - request.ExecuteAsync(CancellationToken.None).Wait(); - Assert.Fail("MsalException should have been thrown here"); - } - catch (Exception exc) - { - validationHandler(exc); - } - } - } - [TestMethod] [TestCategory("InteractiveRequestTests")] public void VerifyAuthorizationResultTest() diff --git a/tests/Microsoft.Identity.Test.Unit.net45/Resources/ExpectedTokenCache.json b/tests/Microsoft.Identity.Test.Unit.net45/Resources/ExpectedTokenCache.json new file mode 100644 index 0000000000..f7616bb7c3 --- /dev/null +++ b/tests/Microsoft.Identity.Test.Unit.net45/Resources/ExpectedTokenCache.json @@ -0,0 +1,201 @@ +{ + "AccessToken": { + "my-uid.my-utid-env_1-accesstoken-d3adb33f-c0de-ed0c-c0de-deadb33fc0d3-the_tenant_id-r1/scope1 r1/scope2": { + "home_account_id": "my-uid.my-utid", + "environment": "env_1", + "client_info": null, + "client_id": "d3adb33f-c0de-ed0c-c0de-deadb33fc0d3", + "secret": "access_token_secret", + "credential_type": "AccessToken", + "realm": "the_tenant_id", + "target": "r1/scope1 r1/scope2", + "user_assertion_hash": "assertion hash", + "cached_at": "34567", + "expires_on": "12345", + "extended_expires_on": "23456", + "ext_expires_on": "23456" + }, + "my-uid.my-utid-env_2-accesstoken-d3adb33f-c0de-ed0c-c0de-deadb33fc0d3-the_tenant_id-r1/scope1 r1/scope2": { + "home_account_id": "my-uid.my-utid", + "environment": "env_2", + "client_info": null, + "client_id": "d3adb33f-c0de-ed0c-c0de-deadb33fc0d3", + "secret": "access_token_secret", + "credential_type": "AccessToken", + "realm": "the_tenant_id", + "target": "r1/scope1 r1/scope2", + "user_assertion_hash": "assertion hash", + "cached_at": "34567", + "expires_on": "12345", + "extended_expires_on": "23456", + "ext_expires_on": "23456" + }, + "my-uid.my-utid-env_3-accesstoken-d3adb33f-c0de-ed0c-c0de-deadb33fc0d3-the_tenant_id-r1/scope1 r1/scope2": { + "home_account_id": "my-uid.my-utid", + "environment": "env_3", + "client_info": null, + "client_id": "d3adb33f-c0de-ed0c-c0de-deadb33fc0d3", + "secret": "access_token_secret", + "credential_type": "AccessToken", + "realm": "the_tenant_id", + "target": "r1/scope1 r1/scope2", + "user_assertion_hash": "assertion hash", + "cached_at": "34567", + "expires_on": "12345", + "extended_expires_on": "23456", + "ext_expires_on": "23456" + }, + "my-uid.my-utid-env_4-accesstoken-d3adb33f-c0de-ed0c-c0de-deadb33fc0d3-the_tenant_id-r1/scope1 r1/scope2": { + "home_account_id": "my-uid.my-utid", + "environment": "env_4", + "client_info": null, + "client_id": "d3adb33f-c0de-ed0c-c0de-deadb33fc0d3", + "secret": "access_token_secret", + "credential_type": "AccessToken", + "realm": "the_tenant_id", + "target": "r1/scope1 r1/scope2", + "user_assertion_hash": "assertion hash", + "cached_at": "34567", + "expires_on": "12345", + "extended_expires_on": "23456", + "ext_expires_on": "23456" + }, + "my-uid.my-utid-env_5-accesstoken-d3adb33f-c0de-ed0c-c0de-deadb33fc0d3-the_tenant_id-r1/scope1 r1/scope2": { + "home_account_id": "my-uid.my-utid", + "environment": "env_5", + "client_info": null, + "client_id": "d3adb33f-c0de-ed0c-c0de-deadb33fc0d3", + "secret": "access_token_secret", + "credential_type": "AccessToken", + "realm": "the_tenant_id", + "target": "r1/scope1 r1/scope2", + "user_assertion_hash": "assertion hash", + "cached_at": "34567", + "expires_on": "12345", + "extended_expires_on": "23456", + "ext_expires_on": "23456" + } + }, + "RefreshToken": { + "my-uid.my-utid-env_1-refreshtoken-d3adb33f-c0de-ed0c-c0de-deadb33fc0d3--": { + "home_account_id": "my-uid.my-utid", + "environment": "env_1", + "client_info": null, + "client_id": "d3adb33f-c0de-ed0c-c0de-deadb33fc0d3", + "secret": "access_token_secret", + "credential_type": "RefreshToken", + "family_id": null + }, + "my-uid.my-utid-env_2-refreshtoken-d3adb33f-c0de-ed0c-c0de-deadb33fc0d3--": { + "home_account_id": "my-uid.my-utid", + "environment": "env_2", + "client_info": null, + "client_id": "d3adb33f-c0de-ed0c-c0de-deadb33fc0d3", + "secret": "access_token_secret", + "credential_type": "RefreshToken", + "family_id": null + }, + "my-uid.my-utid-env_3-refreshtoken-d3adb33f-c0de-ed0c-c0de-deadb33fc0d3--": { + "home_account_id": "my-uid.my-utid", + "environment": "env_3", + "client_info": null, + "client_id": "d3adb33f-c0de-ed0c-c0de-deadb33fc0d3", + "secret": "access_token_secret", + "credential_type": "RefreshToken", + "family_id": null + }, + "my-uid.my-utid-env-refreshtoken-1--": { + "home_account_id": "my-uid.my-utid", + "environment": "env", + "client_info": null, + "client_id": "d3adb33f-c0de-ed0c-c0de-deadb33fc0d3", + "secret": "access_token_secret", + "credential_type": "RefreshToken", + "family_id": "1" + } + }, + "IdToken": { + "my-uid.my-utid-env_1-idtoken-d3adb33f-c0de-ed0c-c0de-deadb33fc0d3-the_tenant_id-": { + "home_account_id": "my-uid.my-utid", + "environment": "env_1", + "client_info": null, + "client_id": "d3adb33f-c0de-ed0c-c0de-deadb33fc0d3", + "secret": "access_token_secret", + "credential_type": "IdToken", + "realm": "the_tenant_id" + }, + "my-uid.my-utid-env_2-idtoken-d3adb33f-c0de-ed0c-c0de-deadb33fc0d3-the_tenant_id-": { + "home_account_id": "my-uid.my-utid", + "environment": "env_2", + "client_info": null, + "client_id": "d3adb33f-c0de-ed0c-c0de-deadb33fc0d3", + "secret": "access_token_secret", + "credential_type": "IdToken", + "realm": "the_tenant_id" + }, + "my-uid.my-utid-env_3-idtoken-d3adb33f-c0de-ed0c-c0de-deadb33fc0d3-the_tenant_id-": { + "home_account_id": "my-uid.my-utid", + "environment": "env_3", + "client_info": null, + "client_id": "d3adb33f-c0de-ed0c-c0de-deadb33fc0d3", + "secret": "access_token_secret", + "credential_type": "IdToken", + "realm": "the_tenant_id" + } + }, + "Account": { + "my-uid.my-utid-env_1-the_tenant_id": { + "home_account_id": "my-uid.my-utid", + "environment": "env_1", + "client_info": null, + "username": "joe@localhost.com", + "name": "First Last", + "given_name": "Joe", + "family_name": "Doe", + "local_account_id": "test_local_account_id", + "authority_type": "authority type", + "realm": "the_tenant_id" + }, + "my-uid.my-utid-env_2-the_tenant_id": { + "home_account_id": "my-uid.my-utid", + "environment": "env_2", + "client_info": null, + "username": "joe@localhost.com", + "name": "First Last", + "given_name": "Joe", + "family_name": "Doe", + "local_account_id": "test_local_account_id", + "authority_type": "authority type", + "realm": "the_tenant_id" + }, + "my-uid.my-utid-env_3-the_tenant_id": { + "home_account_id": "my-uid.my-utid", + "environment": "env_3", + "client_info": null, + "username": "joe@localhost.com", + "name": "First Last", + "given_name": "Joe", + "family_name": "Doe", + "local_account_id": "test_local_account_id", + "authority_type": "authority type", + "realm": "the_tenant_id" + } + }, + "AppMetadata": { + "appmetadata-env_1-d3adb33f-c0de-ed0c-c0de-deadb33fc0d3": { + "environment": "env_1", + "client_id": "d3adb33f-c0de-ed0c-c0de-deadb33fc0d3", + "family_id": "1" + }, + "appmetadata-env_2-d3adb33f-c0de-ed0c-c0de-deadb33fc0d3": { + "environment": "env_2", + "client_id": "d3adb33f-c0de-ed0c-c0de-deadb33fc0d3", + "family_id": "" + }, + "appmetadata-env_1-d3adb33f-c1de-ed1c-c1de-deadb33fc1d3": { + "environment": "env_1", + "client_id": "d3adb33f-c1de-ed1c-c1de-deadb33fc1d3", + "family_id": "another_family" + } + } +} diff --git a/tests/Microsoft.Identity.Test.Unit.net45/TestExtensions.cs b/tests/Microsoft.Identity.Test.Unit.net45/TestExtensions.cs new file mode 100644 index 0000000000..b64625af89 --- /dev/null +++ b/tests/Microsoft.Identity.Test.Unit.net45/TestExtensions.cs @@ -0,0 +1,52 @@ +//---------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +using System.Linq; +using Microsoft.Identity.Client.Cache; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Identity.Test.Unit +{ + public static class TestExtensions + { + internal static void AssertItemCount( + this ITokenCacheAccessor accessor, + int expectedAtCount, + int expectedRtCount, + int expectedIdtCount, + int expectedAccountCount, + int expectedAppMetadataCount = 0) + { + Assert.AreEqual(expectedAtCount, accessor.GetAllAccessTokens().Count()); + Assert.AreEqual(expectedRtCount, accessor.GetAllRefreshTokens().Count()); + Assert.AreEqual(expectedIdtCount, accessor.GetAllIdTokens().Count()); + Assert.AreEqual(expectedAccountCount, accessor.GetAllAccounts().Count()); + Assert.AreEqual(expectedAppMetadataCount, accessor.GetAllAppMetadata().Count()); + } + + } +} diff --git a/tests/devapps/NetFxConsoleTestApp/App.config b/tests/devapps/NetFxConsoleTestApp/App.config new file mode 100644 index 0000000000..56efbc7b5f --- /dev/null +++ b/tests/devapps/NetFxConsoleTestApp/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/desktop/SampleApp/SampleApp.csproj b/tests/devapps/NetFxConsoleTestApp/NetFxConsoleTestApp.csproj similarity index 53% rename from samples/desktop/SampleApp/SampleApp.csproj rename to tests/devapps/NetFxConsoleTestApp/NetFxConsoleTestApp.csproj index cb3f6df322..06151a9807 100644 --- a/samples/desktop/SampleApp/SampleApp.csproj +++ b/tests/devapps/NetFxConsoleTestApp/NetFxConsoleTestApp.csproj @@ -4,13 +4,14 @@ Debug AnyCPU - {85397CDA-120A-4626-9865-AD79EBFAC794} - WinExe - SampleApp - SampleApp - v4.6.1 + {24D2AF71-A01E-4450-8C3F-B9923A81134B} + Exe + NetFxConsoleTestApp + NetFxConsoleTestApp + v4.7.2 512 - + true + true AnyCPU @@ -31,9 +32,6 @@ prompt 4 - - true - @@ -41,56 +39,19 @@ - - - - - - Form - - - MainForm.cs - - - - MainForm.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - True - Resources.resx - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - True - Settings.settings - True - - - - All - - - {3433eb33-114a-4db7-bc57-14f17f55da3c} + {60117a9b-4bb8-472e-bfff-52cbf67ca95a} Microsoft.Identity.Client diff --git a/tests/devapps/NetFxConsoleTestApp/Program.cs b/tests/devapps/NetFxConsoleTestApp/Program.cs new file mode 100644 index 0000000000..f1b36e670d --- /dev/null +++ b/tests/devapps/NetFxConsoleTestApp/Program.cs @@ -0,0 +1,277 @@ +//---------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Security; +using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; +using Microsoft.Identity.Client; +using Microsoft.Identity.Client.AppConfig; + +namespace NetCoreTestApp +{ + public class Program + { + // TODO: replace with FOCI family members IDs + // DO NOT CHECK THESE IN + private const string FAMILY_MEMBER_1 = ""; // Office + private const string FAMILY_MEMBER_2 = ""; // Teams + private const string NON_FAMILY_MEMBER = "0615b6ca-88d4-4884-8729-b178178f7c27"; + + + private static readonly string[] s_scopes = new[] { "https://graph.microsoft.com/.default" }; + + private static readonly string s_cacheFilePath = System.Reflection.Assembly.GetExecutingAssembly().Location + ".msalcache.json"; + + // These 2 apps share the cache + private static IPublicClientApplication s_pcaFam1; + private static IPublicClientApplication s_pcaFam2; + private static IPublicClientApplication s_pcaNonFam; + + public static void Main(string[] args) + { + s_pcaFam1 = PublicClientApplicationBuilder + .Create(FAMILY_MEMBER_1) + .WithLogging(Log, LogLevel.Verbose, true) + .Build(); + + s_pcaFam2 = PublicClientApplicationBuilder + .Create(FAMILY_MEMBER_2) + .WithLogging(Log, LogLevel.Verbose, true) + .Build(); + + s_pcaNonFam = PublicClientApplicationBuilder + .Create(NON_FAMILY_MEMBER) + .WithLogging(Log, LogLevel.Verbose, true) + .Build(); + + + SetCacheSerializationToFile(s_pcaFam1); + SetCacheSerializationToFile(s_pcaFam2); + SetCacheSerializationToFile(s_pcaNonFam); + + RunConsoleAppLogicAsync().Wait(); + } + + private static void SetCacheSerializationToFile(IPublicClientApplication pca1) + { + pca1.UserTokenCache.SetBeforeAccess(notificationArgs => + { + notificationArgs.TokenCache.DeserializeMsalV3(File.Exists(s_cacheFilePath) + ? File.ReadAllBytes(s_cacheFilePath) + : null); + }); + pca1.UserTokenCache.SetAfterAccess(notificationArgs => + { + // if the access operation resulted in a cache update + if (notificationArgs.HasStateChanged) + { + // reflect changes in the persistent store + File.WriteAllBytes(s_cacheFilePath, notificationArgs.TokenCache.SerializeMsalV3()); + } + }); + } + + private static async Task RunConsoleAppLogicAsync() + { + while (true) + { + Console.Clear(); + + await DisplayAccountsAsync(s_pcaFam1).ConfigureAwait(false); + + // display menu + Console.WriteLine(@" + 1. Acquire Token App1 (family member) + 2. Acquire Token App2 (family member) + 3. Acquire Token App3 (non-family member) + 4. Acquire Token Silent App1 (family member) + 5. Acquire Token Silent App2 (family member) + 6. Acquire Token Silent App3 (non-family member) + + + 7. Clear cache + 0. Exit App + Enter your Selection: "); + int.TryParse(Console.ReadLine(), out var selection); + + Task authTask = null; + + try + { + switch (selection) + { + case 1: + authTask = GetInteractiveAuthTaskAsync(s_pcaFam1); + FetchTokenAsync(s_pcaNonFam, authTask).GetAwaiter().GetResult(); + break; + case 2: + authTask = GetInteractiveAuthTaskAsync(s_pcaFam2); + FetchTokenAsync(s_pcaNonFam, authTask).GetAwaiter().GetResult(); + break; + case 3: + authTask = GetInteractiveAuthTaskAsync(s_pcaNonFam); + FetchTokenAsync(s_pcaNonFam, authTask).GetAwaiter().GetResult(); + break; + case 4: + authTask = GetSilentAuthTaskAsync(s_pcaFam1); + FetchTokenAsync(s_pcaFam1, authTask).GetAwaiter().GetResult(); + break; + case 5: + authTask = GetSilentAuthTaskAsync(s_pcaFam2); + FetchTokenAsync(s_pcaNonFam, authTask).GetAwaiter().GetResult(); + break; + case 6: + authTask = GetSilentAuthTaskAsync(s_pcaNonFam); + FetchTokenAsync(s_pcaNonFam, authTask).GetAwaiter().GetResult(); + break; + + case 7: + var accounts1 = await s_pcaFam1.GetAccountsAsync().ConfigureAwait(false); + var accounts2 = await s_pcaFam1.GetAccountsAsync().ConfigureAwait(false); + var accounts3 = await s_pcaFam1.GetAccountsAsync().ConfigureAwait(false); + + + foreach (var acc in accounts1) + { + await s_pcaFam1.RemoveAsync(acc).ConfigureAwait(false); + } + + break; + case 0: + return; + default: + break; + } + + } + catch (Exception ex) + { + Log(LogLevel.Error, ex.Message, false); + Log(LogLevel.Error, ex.StackTrace, false); + } + + Console.WriteLine("\n\nHit 'ENTER' to continue..."); + Console.ReadLine(); + } + } + + private static Task GetInteractiveAuthTaskAsync(IPublicClientApplication pca) + { + return pca.AcquireTokenInteractive(s_scopes, null).ExecuteAsync(); + } + + private static Task GetSilentAuthTaskAsync(IPublicClientApplication pca) + { + // get all serialized accounts + // get all RTs WHERE rt.client == app.client OR app is part of family or unkown + // JOIN acounts and RTs ON homeAccountID + + // A -> interactive auth -> account, RT1 + // B -> GetAccounts -> NULL + + var accounts = pca.GetAccountsAsync().GetAwaiter().GetResult(); + if (accounts.Count() > 1) + { + Log(LogLevel.Error, "Not expecting to handle multiple accounts", false); + return null; + } + + //return pca.AcquireTokenSilent(s_scopes, accounts.FirstOrDefault()).ExecuteAsync(); + return pca.AcquireTokenSilent(s_scopes, "bogavril@microsoft.com").ExecuteAsync(); + + } + + private static async Task FetchTokenAsync(IPublicClientApplication pca, Task authTask) + { + if (authTask == null) + { + return; + } + + await authTask.ConfigureAwait(false); + + Console.BackgroundColor = ConsoleColor.DarkGreen; + Console.WriteLine("Token is {0}", authTask.Result.AccessToken); + Console.ResetColor(); + + + Console.BackgroundColor = ConsoleColor.DarkMagenta; + await DisplayAccountsAsync(pca).ConfigureAwait(false); + + Console.ResetColor(); + } + + + + private static async Task DisplayAccountsAsync(IPublicClientApplication pca) + { + IEnumerable accounts = await pca.GetAccountsAsync().ConfigureAwait(false); + + Console.WriteLine(string.Format(CultureInfo.CurrentCulture, "For the public client, the tokenCache contains {0} token(s)", accounts.Count())); + + foreach (var account in accounts) + { + Console.WriteLine("PCA account for: " + account.Username + "\n"); + } + } + + private static void Log(LogLevel level, string message, bool containsPii) + { + if (!containsPii) + { + Console.BackgroundColor = ConsoleColor.DarkBlue; + } + + switch (level) + { + case LogLevel.Error: + Console.ForegroundColor = ConsoleColor.Red; + break; + case LogLevel.Warning: + Console.ForegroundColor = ConsoleColor.Yellow; + break; + case LogLevel.Verbose: + Console.ForegroundColor = ConsoleColor.Gray; + break; + default: + break; + } + + Console.WriteLine($"{level} {message}"); + Console.ResetColor(); + } + + + + + } +} diff --git a/tests/devapps/NetFxConsoleTestApp/Properties/AssemblyInfo.cs b/tests/devapps/NetFxConsoleTestApp/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..351596c94f --- /dev/null +++ b/tests/devapps/NetFxConsoleTestApp/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NetFxConsoleTestApp")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("NetFxConsoleTestApp")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("24d2af71-a01e-4450-8c3f-b9923a81134b")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/tests/devapps/XForms/XForms/SettingsPage.xaml.cs b/tests/devapps/XForms/XForms/SettingsPage.xaml.cs index 0ec0a0f02d..f47a780dda 100644 --- a/tests/devapps/XForms/XForms/SettingsPage.xaml.cs +++ b/tests/devapps/XForms/XForms/SettingsPage.xaml.cs @@ -27,6 +27,7 @@ using System; using System.Globalization; +using System.Linq; using Xamarin.Forms; using Xamarin.Forms.Xaml; @@ -50,10 +51,10 @@ private void RefreshView() authority.Text = App.Authority; clientIdEntry.Text = App.ClientId; - numOfAtItems.Text = App.MsalPublicClient.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count.ToString(CultureInfo.InvariantCulture); - numOfRtItems.Text = App.MsalPublicClient.UserTokenCacheInternal.Accessor.GetAllRefreshTokens().Count.ToString(CultureInfo.InvariantCulture); - numOfIdItems.Text = App.MsalPublicClient.UserTokenCacheInternal.Accessor.GetAllIdTokens().Count.ToString(CultureInfo.InvariantCulture); - numOfAccountItems.Text = App.MsalPublicClient.UserTokenCacheInternal.Accessor.GetAllAccounts().Count.ToString(CultureInfo.InvariantCulture); + numOfAtItems.Text = App.MsalPublicClient.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count().ToString(CultureInfo.InvariantCulture); + numOfRtItems.Text = App.MsalPublicClient.UserTokenCacheInternal.Accessor.GetAllRefreshTokens().Count().ToString(CultureInfo.InvariantCulture); + numOfIdItems.Text = App.MsalPublicClient.UserTokenCacheInternal.Accessor.GetAllIdTokens().Count().ToString(CultureInfo.InvariantCulture); + numOfAccountItems.Text = App.MsalPublicClient.UserTokenCacheInternal.Accessor.GetAllAccounts().Count().ToString(CultureInfo.InvariantCulture); validateAuthoritySwitch.IsToggled = App.ValidateAuthority; RedirectUriLabel.Text = App.MsalPublicClient.AppConfig.RedirectUri;