diff --git a/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticationProperties.cs b/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticationProperties.cs index 9d1e670ea8..271329209a 100644 --- a/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticationProperties.cs +++ b/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticationProperties.cs @@ -20,20 +20,29 @@ namespace Microsoft.AspNetCore.Authentication internal const string UtcDateTimeFormat = "r"; /// - /// Initializes a new instance of the class + /// Initializes a new instance of the class. /// public AuthenticationProperties() - : this(items: null) - { - } + : this(items: null, parameters: null) + { } /// - /// Initializes a new instance of the class + /// Initializes a new instance of the class. /// - /// + /// State values dictionary to use. public AuthenticationProperties(IDictionary items) + : this(items, parameters: null) + { } + + /// + /// Initializes a new instance of the class. + /// + /// State values dictionary to use. + /// Parameters dictionary to use. + public AuthenticationProperties(IDictionary items, IDictionary parameters) { Items = items ?? new Dictionary(StringComparer.Ordinal); + Parameters = parameters ?? new Dictionary(StringComparer.Ordinal); } /// @@ -41,6 +50,12 @@ namespace Microsoft.AspNetCore.Authentication /// public IDictionary Items { get; } + /// + /// Collection of parameters that are passed to the authentication handler. These are not intended for + /// serialization or persistence, only for flowing data between call sites. + /// + public IDictionary Parameters { get; } + /// /// Gets or sets whether the authentication session is persisted across multiple requests. /// @@ -86,12 +101,22 @@ namespace Microsoft.AspNetCore.Authentication set => SetBool(RefreshKey, value); } - private string GetString(string key) + /// + /// Get a string value from the collection. + /// + /// Property key. + /// Retrieved value or null if the property is not set. + public string GetString(string key) { return Items.TryGetValue(key, out string value) ? value : null; } - private void SetString(string key, string value) + /// + /// Set a string value in the collection. + /// + /// Property key. + /// Value to set or null to remove the property. + public void SetString(string key, string value) { if (value != null) { @@ -103,16 +128,44 @@ namespace Microsoft.AspNetCore.Authentication } } - private bool? GetBool(string key) + /// + /// Get a parameter from the collection. + /// + /// Parameter type. + /// Parameter key. + /// Retrieved value or the default value if the property is not set. + public T GetParameter(string key) + => Parameters.TryGetValue(key, out var obj) && obj is T value ? value : default; + + /// + /// Set a parameter value in the collection. + /// + /// Parameter type. + /// Parameter key. + /// Value to set. + public void SetParameter(string key, T value) + => Parameters[key] = value; + + /// + /// Get a bool value from the collection. + /// + /// Property key. + /// Retrieved value or null if the property is not set. + protected bool? GetBool(string key) { - if (Items.TryGetValue(key, out string value) && bool.TryParse(value, out bool refresh)) + if (Items.TryGetValue(key, out string value) && bool.TryParse(value, out bool boolValue)) { - return refresh; + return boolValue; } return null; } - private void SetBool(string key, bool? value) + /// + /// Set a bool value in the collection. + /// + /// Property key. + /// Value to set or null to remove the property. + protected void SetBool(string key, bool? value) { if (value.HasValue) { @@ -124,7 +177,12 @@ namespace Microsoft.AspNetCore.Authentication } } - private DateTimeOffset? GetDateTimeOffset(string key) + /// + /// Get a DateTimeOffset value from the collection. + /// + /// Property key. + /// Retrieved value or null if the property is not set. + protected DateTimeOffset? GetDateTimeOffset(string key) { if (Items.TryGetValue(key, out string value) && DateTimeOffset.TryParseExact(value, UtcDateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out DateTimeOffset dateTimeOffset)) @@ -134,7 +192,12 @@ namespace Microsoft.AspNetCore.Authentication return null; } - private void SetDateTimeOffset(string key, DateTimeOffset? value) + /// + /// Set a DateTimeOffset value in the collection. + /// + /// Property key. + /// Value to set or null to remove the property. + protected void SetDateTimeOffset(string key, DateTimeOffset? value) { if (value.HasValue) { diff --git a/test/Microsoft.AspNetCore.Authentication.Core.Test/AuthenticationPropertiesTests.cs b/test/Microsoft.AspNetCore.Authentication.Core.Test/AuthenticationPropertiesTests.cs new file mode 100644 index 0000000000..639c9b558e --- /dev/null +++ b/test/Microsoft.AspNetCore.Authentication.Core.Test/AuthenticationPropertiesTests.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace Microsoft.AspNetCore.Authentication.Core.Test +{ + public class AuthenticationPropertiesTests + { + [Fact] + public void DefaultConstructor_EmptyCollections() + { + var props = new AuthenticationProperties(); + Assert.Empty(props.Items); + Assert.Empty(props.Parameters); + } + + [Fact] + public void ItemsConstructor_ReusesItemsDictionary() + { + var items = new Dictionary + { + ["foo"] = "bar", + }; + var props = new AuthenticationProperties(items); + Assert.Same(items, props.Items); + Assert.Empty(props.Parameters); + } + + [Fact] + public void FullConstructor_ReusesDictionaries() + { + var items = new Dictionary + { + ["foo"] = "bar", + }; + var parameters = new Dictionary + { + ["number"] = 1234, + ["list"] = new List { "a", "b", "c" }, + }; + var props = new AuthenticationProperties(items, parameters); + Assert.Same(items, props.Items); + Assert.Same(parameters, props.Parameters); + } + + [Fact] + public void GetSetString() + { + var props = new AuthenticationProperties(); + Assert.Null(props.GetString("foo")); + Assert.Equal(0, props.Items.Count); + + props.SetString("foo", "foo bar"); + Assert.Equal("foo bar", props.GetString("foo")); + Assert.Equal("foo bar", props.Items["foo"]); + Assert.Equal(1, props.Items.Count); + + props.SetString("foo", "foo baz"); + Assert.Equal("foo baz", props.GetString("foo")); + Assert.Equal("foo baz", props.Items["foo"]); + Assert.Equal(1, props.Items.Count); + + props.SetString("bar", "xy"); + Assert.Equal("xy", props.GetString("bar")); + Assert.Equal("xy", props.Items["bar"]); + Assert.Equal(2, props.Items.Count); + + props.SetString("bar", string.Empty); + Assert.Equal(string.Empty, props.GetString("bar")); + Assert.Equal(string.Empty, props.Items["bar"]); + + props.SetString("foo", null); + Assert.Null(props.GetString("foo")); + Assert.Equal(1, props.Items.Count); + } + + [Fact] + public void GetSetParameter_String() + { + var props = new AuthenticationProperties(); + Assert.Null(props.GetParameter("foo")); + Assert.Equal(0, props.Parameters.Count); + + props.SetParameter("foo", "foo bar"); + Assert.Equal("foo bar", props.GetParameter("foo")); + Assert.Equal("foo bar", props.Parameters["foo"]); + Assert.Equal(1, props.Parameters.Count); + + props.SetParameter("foo", null); + Assert.Null(props.GetParameter("foo")); + Assert.Null(props.Parameters["foo"]); + Assert.Equal(1, props.Parameters.Count); + } + + [Fact] + public void GetSetParameter_Int() + { + var props = new AuthenticationProperties(); + Assert.Null(props.GetParameter("foo")); + Assert.Equal(0, props.Parameters.Count); + + props.SetParameter("foo", 123); + Assert.Equal(123, props.GetParameter("foo")); + Assert.Equal(123, props.Parameters["foo"]); + Assert.Equal(1, props.Parameters.Count); + + props.SetParameter("foo", null); + Assert.Null(props.GetParameter("foo")); + Assert.Null(props.Parameters["foo"]); + Assert.Equal(1, props.Parameters.Count); + } + + [Fact] + public void GetSetParameter_Collection() + { + var props = new AuthenticationProperties(); + Assert.Null(props.GetParameter("foo")); + Assert.Equal(0, props.Parameters.Count); + + var list = new string[] { "a", "b", "c" }; + props.SetParameter>("foo", list); + Assert.Equal(new string[] { "a", "b", "c" }, props.GetParameter>("foo")); + Assert.Same(list, props.Parameters["foo"]); + Assert.Equal(1, props.Parameters.Count); + + props.SetParameter>("foo", null); + Assert.Null(props.GetParameter>("foo")); + Assert.Null(props.Parameters["foo"]); + Assert.Equal(1, props.Parameters.Count); + } + + [Fact] + public void IsPersistent_Test() + { + var props = new AuthenticationProperties(); + Assert.False(props.IsPersistent); + + props.IsPersistent = true; + Assert.True(props.IsPersistent); + Assert.Equal(string.Empty, props.Items.First().Value); + + props.Items.Clear(); + Assert.False(props.IsPersistent); + } + + [Fact] + public void RedirectUri_Test() + { + var props = new AuthenticationProperties(); + Assert.Null(props.RedirectUri); + + props.RedirectUri = "http://example.com"; + Assert.Equal("http://example.com", props.RedirectUri); + Assert.Equal("http://example.com", props.Items.First().Value); + + props.Items.Clear(); + Assert.Null(props.RedirectUri); + } + + [Fact] + public void IssuedUtc_Test() + { + var props = new AuthenticationProperties(); + Assert.Null(props.IssuedUtc); + + props.IssuedUtc = new DateTimeOffset(new DateTime(2018, 03, 21, 0, 0, 0, DateTimeKind.Utc)); + Assert.Equal(new DateTimeOffset(new DateTime(2018, 03, 21, 0, 0, 0, DateTimeKind.Utc)), props.IssuedUtc); + Assert.Equal("Wed, 21 Mar 2018 00:00:00 GMT", props.Items.First().Value); + + props.Items.Clear(); + Assert.Null(props.IssuedUtc); + } + + [Fact] + public void ExpiresUtc_Test() + { + var props = new AuthenticationProperties(); + Assert.Null(props.ExpiresUtc); + + props.ExpiresUtc = new DateTimeOffset(new DateTime(2018, 03, 19, 12, 34, 56, DateTimeKind.Utc)); + Assert.Equal(new DateTimeOffset(new DateTime(2018, 03, 19, 12, 34, 56, DateTimeKind.Utc)), props.ExpiresUtc); + Assert.Equal("Mon, 19 Mar 2018 12:34:56 GMT", props.Items.First().Value); + + props.Items.Clear(); + Assert.Null(props.ExpiresUtc); + } + + [Fact] + public void AllowRefresh_Test() + { + var props = new AuthenticationProperties(); + Assert.Null(props.AllowRefresh); + + props.AllowRefresh = true; + Assert.True(props.AllowRefresh); + Assert.Equal("True", props.Items.First().Value); + + props.AllowRefresh = false; + Assert.False(props.AllowRefresh); + Assert.Equal("False", props.Items.First().Value); + + props.Items.Clear(); + Assert.Null(props.AllowRefresh); + } + } +}