diff --git a/src/Microsoft.AspNetCore.Http.Abstractions/CookieBuilder.cs b/src/Microsoft.AspNetCore.Http.Abstractions/CookieBuilder.cs new file mode 100644 index 0000000000..d2aa125ff3 --- /dev/null +++ b/src/Microsoft.AspNetCore.Http.Abstractions/CookieBuilder.cs @@ -0,0 +1,93 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNetCore.Http +{ + /// + /// Defines settings used to create a cookie. + /// + public class CookieBuilder + { + /// + /// The name of the cookie. + /// + public virtual string Name { get; set; } + + /// + /// The cookie path. + /// + /// + /// Determines the value that will set on . + /// + public virtual string Path { get; set; } + + /// + /// The domain to associate the cookie with. + /// + /// + /// Determines the value that will set on . + /// + public virtual string Domain { get; set; } + + /// + /// Indicates whether a cookie is accessible by client-side script. + /// + /// + /// Determines the value that will set on . + /// + public virtual bool HttpOnly { get; set; } + + /// + /// The SameSite attribute of the cookie. The default value is + /// + /// + /// Determines the value that will set on . + /// + public virtual SameSiteMode SameSite { get; set; } = SameSiteMode.Lax; + + /// + /// The policy that will be used to determine . + /// This is determined from the passed to . + /// + public virtual CookieSecurePolicy SecurePolicy { get; set; } + + + /// + /// Gets or sets the lifespan of a cookie. + /// + public virtual TimeSpan? Expiration { get; set; } + + /// + /// Creates the cookie options from the given . + /// + /// The . + /// The cookie options. + public virtual CookieOptions Build(HttpContext context) => Build(context, DateTimeOffset.Now); + + /// + /// Creates the cookie options from the given with an expiration based on and . + /// + /// The . + /// The time to use as the base for computing . + /// The cookie options. + public virtual CookieOptions Build(HttpContext context, DateTimeOffset expiresFrom) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + return new CookieOptions + { + Path = Path ?? "/", + SameSite = SameSite, + HttpOnly = HttpOnly, + Domain = Domain, + Secure = SecurePolicy == CookieSecurePolicy.Always || (SecurePolicy == CookieSecurePolicy.SameAsRequest && context.Request.IsHttps), + Expires = Expiration.HasValue ? expiresFrom.Add(Expiration.Value) : default(DateTimeOffset?) + }; + } + } +} diff --git a/src/Microsoft.AspNetCore.Http.Features/CookieOptions.cs b/src/Microsoft.AspNetCore.Http.Features/CookieOptions.cs index d9e0047dca..017b6520fb 100644 --- a/src/Microsoft.AspNetCore.Http.Features/CookieOptions.cs +++ b/src/Microsoft.AspNetCore.Http.Features/CookieOptions.cs @@ -42,7 +42,6 @@ namespace Microsoft.AspNetCore.Http /// true to transmit the cookie only over an SSL connection (HTTPS); otherwise, false. public bool Secure { get; set; } - /// /// Gets or sets the value for the SameSite attribute of the cookie. The default value is /// diff --git a/test/Microsoft.AspNetCore.Http.Abstractions.Tests/CookieBuilderTests.cs b/test/Microsoft.AspNetCore.Http.Abstractions.Tests/CookieBuilderTests.cs new file mode 100644 index 0000000000..386374b211 --- /dev/null +++ b/test/Microsoft.AspNetCore.Http.Abstractions.Tests/CookieBuilderTests.cs @@ -0,0 +1,47 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Xunit; + +namespace Microsoft.AspNetCore.Http.Abstractions.Tests +{ + public class CookieBuilderTests + { + [Theory] + [InlineData(CookieSecurePolicy.Always, false, true)] + [InlineData(CookieSecurePolicy.Always, true, true)] + [InlineData(CookieSecurePolicy.SameAsRequest, true, true)] + [InlineData(CookieSecurePolicy.SameAsRequest, false, false)] + [InlineData(CookieSecurePolicy.None, true, false)] + [InlineData(CookieSecurePolicy.None, false, false)] + public void ConfiguresSecurePolicy(CookieSecurePolicy policy, bool requestIsHttps, bool secure) + { + var builder = new CookieBuilder + { + SecurePolicy = policy + }; + var context = new DefaultHttpContext(); + context.Request.IsHttps = requestIsHttps; + var options = builder.Build(context); + + Assert.Equal(secure, options.Secure); + } + + [Fact] + public void ComputesExpiration() + { + Assert.Null(new CookieBuilder().Build(new DefaultHttpContext()).Expires); + + var now = DateTimeOffset.Now; + var options = new CookieBuilder { Expiration = TimeSpan.FromHours(1) }.Build(new DefaultHttpContext(), now); + Assert.Equal(now.AddHours(1), options.Expires); + } + + [Fact] + public void CookieBuilderPreservesDefaultPath() + { + Assert.Equal(new CookieOptions().Path, new CookieBuilder().Build(new DefaultHttpContext()).Path); + } + } +}