diff --git a/src/Microsoft.AspNet.Abstractions/HttpContext.cs b/src/Microsoft.AspNet.Abstractions/HttpContext.cs index 9f2eac3614..7856d02d99 100644 --- a/src/Microsoft.AspNet.Abstractions/HttpContext.cs +++ b/src/Microsoft.AspNet.Abstractions/HttpContext.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Security.Claims; +using Microsoft.AspNet.Abstractions.Security; namespace Microsoft.AspNet.Abstractions { @@ -8,6 +10,10 @@ namespace Microsoft.AspNet.Abstractions public abstract HttpRequest Request { get; } public abstract HttpResponse Response { get; } + + public abstract AuthenticationManager Authentication { get; } + + public abstract ClaimsPrincipal User { get; set; } public abstract IDictionary Items { get; } diff --git a/src/Microsoft.AspNet.Abstractions/Security/AuthenticateResult.cs b/src/Microsoft.AspNet.Abstractions/Security/AuthenticateResult.cs new file mode 100644 index 0000000000..2584f395fa --- /dev/null +++ b/src/Microsoft.AspNet.Abstractions/Security/AuthenticateResult.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Security.Claims; +using System.Security.Principal; +using Microsoft.AspNet.HttpFeature.Security; + +namespace Microsoft.AspNet.Abstractions.Security +{ + /// + /// Acts as the return value from calls to the IAuthenticationManager's AuthenticeAsync methods. + /// + public class AuthenticationResult + { + /// + /// Create an instance of the result object + /// + /// Assigned to Identity. May be null. + /// Assigned to Properties. Contains extra information carried along with the identity. + /// Assigned to Description. Contains information describing the authentication provider. + public AuthenticationResult(IIdentity identity, AuthenticationProperties properties, AuthenticationDescription description) + { + if (properties == null) + { + throw new ArgumentNullException("properties"); + } + if (description == null) + { + throw new ArgumentNullException("description"); + } + if (identity != null) + { + Identity = identity as ClaimsIdentity ?? new ClaimsIdentity(identity); + } + Properties = properties; + Description = description; + } + + /// + /// Contains the claims that were authenticated by the given AuthenticationType. If the authentication + /// type was not successful the Identity property will be null. + /// + public ClaimsIdentity Identity { get; private set; } + + /// + /// Contains extra values that were provided with the original SignIn call. + /// + public AuthenticationProperties Properties { get; private set; } + + /// + /// Contains description properties for the middleware authentication type in general. Does not + /// vary per request. + /// + public AuthenticationDescription Description { get; private set; } + } +} diff --git a/src/Microsoft.AspNet.Abstractions/Security/AuthenticationDescription.cs b/src/Microsoft.AspNet.Abstractions/Security/AuthenticationDescription.cs new file mode 100644 index 0000000000..de627373a5 --- /dev/null +++ b/src/Microsoft.AspNet.Abstractions/Security/AuthenticationDescription.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using Microsoft.AspNet.HttpFeature.Security; + +namespace Microsoft.AspNet.Abstractions.Security +{ + /// + /// Contains information describing an authentication provider. + /// + public class AuthenticationDescription + { + private const string CaptionPropertyKey = "Caption"; + private const string AuthenticationTypePropertyKey = "AuthenticationType"; + + /// + /// Initializes a new instance of the class + /// + public AuthenticationDescription() + { + Dictionary = new Dictionary(StringComparer.Ordinal); + } + + /// + /// Initializes a new instance of the class + /// + /// + public AuthenticationDescription(IDictionary properties) + { + if (properties == null) + { + throw new ArgumentNullException("properties"); + } + Dictionary = properties; + } + + /// + /// Contains metadata about the authentication provider. + /// + public IDictionary Dictionary { get; private set; } + + /// + /// Gets or sets the name used to reference the authentication middleware instance. + /// + public string AuthenticationType + { + get { return GetString(AuthenticationTypePropertyKey); } + set { Dictionary[AuthenticationTypePropertyKey] = value; } + } + + /// + /// Gets or sets the display name for the authentication provider. + /// + public string Caption + { + get { return GetString(CaptionPropertyKey); } + set { Dictionary[CaptionPropertyKey] = value; } + } + + private string GetString(string name) + { + object value; + if (Dictionary.TryGetValue(name, out value)) + { + return Convert.ToString(value, CultureInfo.InvariantCulture); + } + return null; + } + } +} diff --git a/src/Microsoft.AspNet.Abstractions/Security/AuthenticationManager.cs b/src/Microsoft.AspNet.Abstractions/Security/AuthenticationManager.cs new file mode 100644 index 0000000000..36ed704cc9 --- /dev/null +++ b/src/Microsoft.AspNet.Abstractions/Security/AuthenticationManager.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Security.Claims; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Abstractions.Security +{ + public abstract class AuthenticationManager + { + public abstract HttpContext HttpContext { get; } + + public abstract IEnumerable GetAuthenticationTypes(); + public abstract IEnumerable GetAuthenticationTypes(Func predicate); + + public abstract AuthenticationResult Authenticate(string authenticationType); // TODO: Is sync a good idea? + public abstract IEnumerable Authenticate(IList authenticationTypes); + + public abstract Task AuthenticateAsync(string authenticationType); + public abstract Task> AuthenticateAsync(IList authenticationTypes); + + public abstract void Challenge(); + public abstract void Challenge(AuthenticationProperties properties); + public abstract void Challenge(string authenticationType); + public abstract void Challenge(string authenticationType, AuthenticationProperties properties); + public abstract void Challenge(IList authenticationTypes); + public abstract void Challenge(IList authenticationTypes, AuthenticationProperties properties); + + public abstract void SignIn(ClaimsPrincipal user); // TODO: This took multiple identities in Katana. Is that needed? + public abstract void SignIn(ClaimsPrincipal user, AuthenticationProperties properties); // TODO: ClaimsIdentity vs ClaimsPrincipal? + + public abstract void SignOut(); + public abstract void SignOut(string authenticationType); + public abstract void SignOut(IList authenticationTypes); + } +} diff --git a/src/Microsoft.AspNet.Abstractions/Security/AuthenticationProperties.cs b/src/Microsoft.AspNet.Abstractions/Security/AuthenticationProperties.cs new file mode 100644 index 0000000000..9d03d42c92 --- /dev/null +++ b/src/Microsoft.AspNet.Abstractions/Security/AuthenticationProperties.cs @@ -0,0 +1,164 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using Microsoft.AspNet.HttpFeature.Security; + +namespace Microsoft.AspNet.Abstractions.Security +{ + /// + /// Dictionary used to store state values about the authentication session. + /// + public class AuthenticationProperties + { + internal const string IssuedUtcKey = ".issued"; + internal const string ExpiresUtcKey = ".expires"; + internal const string IsPersistentKey = ".persistent"; + internal const string RedirectUriKey = ".redirect"; + internal const string UtcDateTimeFormat = "r"; + + /// + /// Initializes a new instance of the class + /// + public AuthenticationProperties() + : this(null) + { + } + + /// + /// Initializes a new instance of the class + /// + /// + public AuthenticationProperties(IDictionary dictionary) + { + Dictionary = dictionary ?? new Dictionary(StringComparer.Ordinal); + } + + /// + /// State values about the authentication session. + /// + public IDictionary Dictionary { get; private set; } + + /// + /// Gets or sets whether the authentication session is persisted across multiple requests. + /// + public bool IsPersistent + { + get { return Dictionary.ContainsKey(IsPersistentKey); } + set + { + if (Dictionary.ContainsKey(IsPersistentKey)) + { + if (!value) + { + Dictionary.Remove(IsPersistentKey); + } + } + else + { + if (value) + { + Dictionary.Add(IsPersistentKey, string.Empty); + } + } + } + } + + /// + /// Gets or sets the full path or absolute URI to be used as an http redirect response value. + /// + [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "By design")] + public string RedirectUri + { + get + { + string value; + return Dictionary.TryGetValue(RedirectUriKey, out value) ? value : null; + } + set + { + if (value != null) + { + Dictionary[RedirectUriKey] = value; + } + else + { + if (Dictionary.ContainsKey(RedirectUriKey)) + { + Dictionary.Remove(RedirectUriKey); + } + } + } + } + + /// + /// Gets or sets the time at which the authentication ticket was issued. + /// + public DateTimeOffset? IssuedUtc + { + get + { + string value; + if (Dictionary.TryGetValue(IssuedUtcKey, out value)) + { + DateTimeOffset dateTimeOffset; + if (DateTimeOffset.TryParseExact(value, UtcDateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out dateTimeOffset)) + { + return dateTimeOffset; + } + } + return null; + } + set + { + if (value.HasValue) + { + Dictionary[IssuedUtcKey] = value.Value.ToString(UtcDateTimeFormat, CultureInfo.InvariantCulture); + } + else + { + if (Dictionary.ContainsKey(IssuedUtcKey)) + { + Dictionary.Remove(IssuedUtcKey); + } + } + } + } + + /// + /// Gets or sets the time at which the authentication ticket expires. + /// + public DateTimeOffset? ExpiresUtc + { + get + { + string value; + if (Dictionary.TryGetValue(ExpiresUtcKey, out value)) + { + DateTimeOffset dateTimeOffset; + if (DateTimeOffset.TryParseExact(value, UtcDateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out dateTimeOffset)) + { + return dateTimeOffset; + } + } + return null; + } + set + { + if (value.HasValue) + { + Dictionary[ExpiresUtcKey] = value.Value.ToString(UtcDateTimeFormat, CultureInfo.InvariantCulture); + } + else + { + if (Dictionary.ContainsKey(ExpiresUtcKey)) + { + Dictionary.Remove(ExpiresUtcKey); + } + } + } + } + } +} diff --git a/src/Microsoft.AspNet.Abstractions/project.json b/src/Microsoft.AspNet.Abstractions/project.json index e0db39cefa..4b9c817d53 100644 --- a/src/Microsoft.AspNet.Abstractions/project.json +++ b/src/Microsoft.AspNet.Abstractions/project.json @@ -1,17 +1,23 @@ { "version": "0.1-alpha-*", - "dependencies": {}, + "dependencies": { + "Microsoft.AspNet.HttpFeature": "" + }, "configurations": { "net45": {}, "k10": { "dependencies": { + "System.Collections": "4.0.0.0", "System.ComponentModel": "4.0.0.0", "System.Diagnostics.Tools": "4.0.0.0", + "System.Globalization": "4.0.10.0", "System.IO": "4.0.0.0", "System.Linq": "4.0.0.0", "System.Runtime": "4.0.20.0", "System.Runtime.Extensions": "4.0.10.0", "System.Runtime.InteropServices": "4.0.20.0", + "System.Security.Claims": "0.1-alpha-*", + "System.Security.Principal" : "4.0.0.0", "System.Threading.Tasks": "4.0.10.0" } } diff --git a/src/Microsoft.AspNet.HttpFeature/Security/IAuthenticateContext.cs b/src/Microsoft.AspNet.HttpFeature/Security/IAuthenticateContext.cs new file mode 100644 index 0000000000..7f617e08a2 --- /dev/null +++ b/src/Microsoft.AspNet.HttpFeature/Security/IAuthenticateContext.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.Security.Claims; + +namespace Microsoft.AspNet.HttpFeature.Security +{ + public interface IAuthenticateContext + { + IList AuthenticationTypes { get; } + + void Authenticated(ClaimsIdentity identity, IDictionary properties, IDictionary description); + + void NotAuthenticated(string authenticationType, IDictionary properties, IDictionary description); + } +} diff --git a/src/Microsoft.AspNet.HttpFeature/Security/IAuthenticationChallenge.cs b/src/Microsoft.AspNet.HttpFeature/Security/IAuthenticationChallenge.cs deleted file mode 100644 index 4f8324d45e..0000000000 --- a/src/Microsoft.AspNet.HttpFeature/Security/IAuthenticationChallenge.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections.Generic; - -namespace Microsoft.AspNet.HttpFeature.Security -{ - public interface IAuthenticationChallenge - { - IEnumerable AuthenticationTypes { get; } - IDictionary Properties { get; } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.HttpFeature/Security/IAuthenticationDescription.cs b/src/Microsoft.AspNet.HttpFeature/Security/IAuthenticationDescription.cs deleted file mode 100644 index f7cca258f5..0000000000 --- a/src/Microsoft.AspNet.HttpFeature/Security/IAuthenticationDescription.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Collections.Generic; - -namespace Microsoft.AspNet.HttpFeature.Security -{ - public interface IAuthenticationDescription - { - IDictionary Properties { get; set; } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.HttpFeature/Security/IAuthenticationHandler.cs b/src/Microsoft.AspNet.HttpFeature/Security/IAuthenticationHandler.cs new file mode 100644 index 0000000000..66c82fccfc --- /dev/null +++ b/src/Microsoft.AspNet.HttpFeature/Security/IAuthenticationHandler.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.HttpFeature.Security +{ + public delegate void DescriptionDelegate(IDictionary description, object state); + + public interface IAuthenticationHandler + { + void GetDescriptions(DescriptionDelegate callback, object state); + + void Authenticate(IAuthenticateContext context); // TODO: (maybe?) + Task AuthenticateAsync(IAuthenticateContext context); + + void Challenge(IChallengeContext context); + void SignIn(ISignInContext context); + void SignOut(ISignOutContext context); + } +} diff --git a/src/Microsoft.AspNet.HttpFeature/Security/IAuthenticationResult.cs b/src/Microsoft.AspNet.HttpFeature/Security/IAuthenticationResult.cs deleted file mode 100644 index b047bae8bd..0000000000 --- a/src/Microsoft.AspNet.HttpFeature/Security/IAuthenticationResult.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; -using System.Security.Claims; -using Microsoft.AspNet.HttpFeature.Security; - -// ReSharper disable once CheckNamespace -namespace Microsoft.AspNet.Interfaces.Security -{ - public interface IAuthenticationResult - { - ClaimsIdentity Identity { get; } - IDictionary Properties { get; } - IAuthenticationDescription Description { get; } - } -} diff --git a/src/Microsoft.AspNet.HttpFeature/Security/IAuthenticationSignOut.cs b/src/Microsoft.AspNet.HttpFeature/Security/IAuthenticationSignOut.cs deleted file mode 100644 index bab55a23e1..0000000000 --- a/src/Microsoft.AspNet.HttpFeature/Security/IAuthenticationSignOut.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Collections.Generic; - -namespace Microsoft.AspNet.HttpFeature.Security -{ - public interface IAuthenticationSignOut - { - IEnumerable AuthenticationTypes { get; } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.HttpFeature/Security/IChallengeContext .cs b/src/Microsoft.AspNet.HttpFeature/Security/IChallengeContext .cs new file mode 100644 index 0000000000..9b80cdd5be --- /dev/null +++ b/src/Microsoft.AspNet.HttpFeature/Security/IChallengeContext .cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace Microsoft.AspNet.HttpFeature.Security +{ + public interface IChallengeContext + { + IList AuthenticationTypes {get;} + IDictionary Properties {get;} + + void Ack(string authenticationType, IDictionary description); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.HttpFeature/Security/IHttpAuthentication.cs b/src/Microsoft.AspNet.HttpFeature/Security/IHttpAuthentication.cs index 477df94a12..d1a7c16596 100644 --- a/src/Microsoft.AspNet.HttpFeature/Security/IHttpAuthentication.cs +++ b/src/Microsoft.AspNet.HttpFeature/Security/IHttpAuthentication.cs @@ -1,19 +1,10 @@ -using System.Collections.Generic; -using System.Security.Principal; -using System.Threading.Tasks; -using Microsoft.AspNet.Interfaces.Security; +using System.Security.Claims; namespace Microsoft.AspNet.HttpFeature.Security { public interface IHttpAuthentication { - IPrincipal User { get; set; } - - IEnumerable Authenticate(string[] authenticationTypes); - Task> AuthenticateAsync(string[] authenticationTypes); - - IAuthenticationChallenge ChallengeDetails { get; set; } - IAuthenticationSignIn SignInDetails { get; set; } - IAuthenticationSignOut SignOutDetails { get; set; } + ClaimsPrincipal User { get; set; } + IAuthenticationHandler Handler { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.HttpFeature/Security/IAuthenticationSignIn.cs b/src/Microsoft.AspNet.HttpFeature/Security/ISignInContext.cs similarity index 64% rename from src/Microsoft.AspNet.HttpFeature/Security/IAuthenticationSignIn.cs rename to src/Microsoft.AspNet.HttpFeature/Security/ISignInContext.cs index 6033b173c5..abcfd2e6bd 100644 --- a/src/Microsoft.AspNet.HttpFeature/Security/IAuthenticationSignIn.cs +++ b/src/Microsoft.AspNet.HttpFeature/Security/ISignInContext.cs @@ -3,9 +3,11 @@ using System.Security.Claims; namespace Microsoft.AspNet.HttpFeature.Security { - public interface IAuthenticationSignIn + public interface ISignInContext { ClaimsPrincipal User { get; } IDictionary Properties { get; } + + void Ack(string authenticationType, IDictionary description); } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.HttpFeature/Security/ISignOutContext .cs b/src/Microsoft.AspNet.HttpFeature/Security/ISignOutContext .cs new file mode 100644 index 0000000000..97df86501c --- /dev/null +++ b/src/Microsoft.AspNet.HttpFeature/Security/ISignOutContext .cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Microsoft.AspNet.HttpFeature.Security +{ + public interface ISignOutContext + { + IList AuthenticationTypes { get; } + + void Ack(string authenticationType, IDictionary description); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.PipelineCore/DefaultHttpContext.cs b/src/Microsoft.AspNet.PipelineCore/DefaultHttpContext.cs index da5e692686..b4632aabb7 100644 --- a/src/Microsoft.AspNet.PipelineCore/DefaultHttpContext.cs +++ b/src/Microsoft.AspNet.PipelineCore/DefaultHttpContext.cs @@ -1,8 +1,12 @@ using System; using System.Collections.Generic; +using System.Security.Claims; using Microsoft.AspNet.Abstractions; +using Microsoft.AspNet.Abstractions.Security; using Microsoft.AspNet.FeatureModel; +using Microsoft.AspNet.HttpFeature.Security; using Microsoft.AspNet.PipelineCore.Infrastructure; +using Microsoft.AspNet.PipelineCore.Security; namespace Microsoft.AspNet.PipelineCore { @@ -10,9 +14,11 @@ namespace Microsoft.AspNet.PipelineCore { private readonly HttpRequest _request; private readonly HttpResponse _response; + private readonly AuthenticationManager _authentication; private FeatureReference _canHasItems; private FeatureReference _canHasServiceProviders; + private FeatureReference _auth; private IFeatureCollection _features; public DefaultHttpContext(IFeatureCollection features) @@ -20,9 +26,11 @@ namespace Microsoft.AspNet.PipelineCore _features = features; _request = new DefaultHttpRequest(this, features); _response = new DefaultHttpResponse(this, features); + _authentication = new DefaultAuthenticationManager(this, features); _canHasItems = FeatureReference.Default; _canHasServiceProviders = FeatureReference.Default; + _auth = FeatureReference.Default; } ICanHasItems CanHasItems @@ -35,10 +43,23 @@ namespace Microsoft.AspNet.PipelineCore get { return _canHasServiceProviders.Fetch(_features) ?? _canHasServiceProviders.Update(_features, new DefaultCanHasServiceProviders()); } } + private IHttpAuthentication HttpAuthentication + { + get { return _auth.Fetch(_features) ?? _auth.Update(_features, new DefaultHttpAuthentication()); } + } + public override HttpRequest Request { get { return _request; } } public override HttpResponse Response { get { return _response; } } + public override AuthenticationManager Authentication { get { return _authentication; } } + + public override ClaimsPrincipal User + { + get { return HttpAuthentication.User; } + set { HttpAuthentication.User = value; } + } + public override IDictionary Items { get { return CanHasItems.Items; } diff --git a/src/Microsoft.AspNet.PipelineCore/Security/AuthenticateContext.cs b/src/Microsoft.AspNet.PipelineCore/Security/AuthenticateContext.cs new file mode 100644 index 0000000000..f9d6359260 --- /dev/null +++ b/src/Microsoft.AspNet.PipelineCore/Security/AuthenticateContext.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNet.Abstractions.Security; +using Microsoft.AspNet.HttpFeature.Security; + +namespace Microsoft.AspNet.PipelineCore.Security +{ + public class AuthenticateContext : IAuthenticateContext + { + public AuthenticateContext(IList authenticationTypes) + { + if (authenticationTypes == null) + { + throw new ArgumentNullException("authenticationType"); + } + AuthenticationTypes = authenticationTypes; + Results = new List(); + } + + public IList AuthenticationTypes { get; private set; } + + public IList Results { get; private set; } + + public void Authenticated(ClaimsIdentity identity, IDictionary properties, IDictionary description) + { + Results.Add(new AuthenticationResult(identity, new AuthenticationProperties(properties), new AuthenticationDescription(description))); + } + + public void NotAuthenticated(string authenticationType, IDictionary properties, IDictionary description) + { + } + } +} diff --git a/src/Microsoft.AspNet.PipelineCore/Security/ChallengeContext.cs b/src/Microsoft.AspNet.PipelineCore/Security/ChallengeContext.cs new file mode 100644 index 0000000000..7ce8d1c039 --- /dev/null +++ b/src/Microsoft.AspNet.PipelineCore/Security/ChallengeContext.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNet.HttpFeature.Security; + +namespace Microsoft.AspNet.PipelineCore.Security +{ + public class ChallengeContext : IChallengeContext + { + public ChallengeContext(IList authenticationTypes, IDictionary properties) + { + if (authenticationTypes == null) + { + throw new ArgumentNullException(); + } + AuthenticationTypes = authenticationTypes; + Properties = properties ?? new Dictionary(StringComparer.Ordinal); + } + + public IList AuthenticationTypes { get; private set; } + + public IDictionary Properties { get; private set; } + + public void Ack(string authenticationType, IDictionary description) + { + } + } +} diff --git a/src/Microsoft.AspNet.PipelineCore/Security/DefaultAuthenticationManager.cs b/src/Microsoft.AspNet.PipelineCore/Security/DefaultAuthenticationManager.cs new file mode 100644 index 0000000000..39f47c0acf --- /dev/null +++ b/src/Microsoft.AspNet.PipelineCore/Security/DefaultAuthenticationManager.cs @@ -0,0 +1,196 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Security.Principal; +using System.Threading.Tasks; +using Microsoft.AspNet.Abstractions; +using Microsoft.AspNet.Abstractions.Security; +using Microsoft.AspNet.FeatureModel; +using Microsoft.AspNet.HttpFeature; +using Microsoft.AspNet.HttpFeature.Security; +using Microsoft.AspNet.PipelineCore.Infrastructure; + +namespace Microsoft.AspNet.PipelineCore.Security +{ + public class DefaultAuthenticationManager : AuthenticationManager + { + private readonly DefaultHttpContext _context; + private readonly IFeatureCollection _features; + + private readonly FeatureReference _authentication = FeatureReference.Default; + private readonly FeatureReference _response = FeatureReference.Default; + + public DefaultAuthenticationManager(DefaultHttpContext context, IFeatureCollection features) + { + _context = context; + _features = features; + } + + private IHttpAuthentication HttpAuthentication + { + get { return _authentication.Fetch(_features) ?? _authentication.Update(_features, new DefaultHttpAuthentication()); } + } + + public override HttpContext HttpContext { get { return _context; } } + + private IHttpResponseInformation HttpResponseInformation + { + get { return _response.Fetch(_features); } + } + + public override IEnumerable GetAuthenticationTypes() + { + return GetAuthenticationTypes(_ => true); + } + + public override IEnumerable GetAuthenticationTypes(Func predicate) + { + var descriptions = new List(); + var handler = HttpAuthentication.Handler; + if (handler != null) + { + // TODO: static delegate field + handler.GetDescriptions(GetAuthenticationTypesCallback, descriptions); + } + return descriptions; + } + + private static void GetAuthenticationTypesCallback(IDictionary description, object state) + { + var localDescriptions = (List)state; + localDescriptions.Add(new AuthenticationDescription(description)); + } + + public override AuthenticationResult Authenticate(string authenticationType) + { + return Authenticate(new[] { authenticationType }).SingleOrDefault(); + } + + public override IEnumerable Authenticate(IList authenticationTypes) + { + HttpResponseInformation.StatusCode = 401; + var handler = HttpAuthentication.Handler; + if (handler == null) + { + // TODO: InvalidOperationException? No auth types supported? + return new AuthenticationResult[0]; + } + + var authenticateContext = new AuthenticateContext(authenticationTypes); + handler.Authenticate(authenticateContext); + // TODO: Verify all types ack'd + + return authenticateContext.Results; + } + + public override async Task AuthenticateAsync(string authenticationType) + { + return (await AuthenticateAsync(new[] { authenticationType })).SingleOrDefault(); + } + + public override async Task> AuthenticateAsync(IList authenticationTypes) + { + HttpResponseInformation.StatusCode = 401; + var handler = HttpAuthentication.Handler; + if (handler == null) + { + // TODO: InvalidOperationException? No auth types supported? + return new AuthenticationResult[0]; + } + + var authenticateContext = new AuthenticateContext(authenticationTypes); + await handler.AuthenticateAsync(authenticateContext); + // TODO: Verify all types ack'd + + return authenticateContext.Results; + } + + public override void Challenge() + { + Challenge(new string[0]); + } + + public override void Challenge(AuthenticationProperties properties) + { + Challenge(new string[0], properties); + } + + public override void Challenge(string authenticationType) + { + Challenge(new[] { authenticationType }); + } + + public override void Challenge(string authenticationType, AuthenticationProperties properties) + { + Challenge(new[] { authenticationType }, properties); + } + + public override void Challenge(IList authenticationTypes) + { + Challenge(authenticationTypes, null); + } + + public override void Challenge(IList authenticationTypes, AuthenticationProperties properties) + { + HttpResponseInformation.StatusCode = 401; + var handler = HttpAuthentication.Handler; + if (handler == null) + { + // TODO: InvalidOperationException? No auth types supported? If authTypes.Length > 1? + return; + } + + var challengeContext = new ChallengeContext(authenticationTypes, properties == null ? null : properties.Dictionary); + handler.Challenge(challengeContext); + // TODO: Verify all types ack'd + } + + public override void SignIn(ClaimsPrincipal user) + { + SignIn(user, null); + } + + public override void SignIn(ClaimsPrincipal user, AuthenticationProperties properties) + { + HttpResponseInformation.StatusCode = 401; + var handler = HttpAuthentication.Handler; + if (handler == null) + { + // TODO: InvalidOperationException? No auth types supported? + return; + } + + var signInContext = new SignInContext(user, properties == null ? null : properties.Dictionary); + handler.SignIn(signInContext); + // TODO: Verify all types ack'd + } + + public override void SignOut() + { + SignOut(new string[0]); + } + + public override void SignOut(string authenticationType) + { + SignOut(new[] { authenticationType }); + } + + public override void SignOut(IList authenticationTypes) + { + HttpResponseInformation.StatusCode = 401; + var handler = HttpAuthentication.Handler; + if (handler == null) + { + // TODO: InvalidOperationException? No auth types supported? + return; + } + + var signOutContext = new SignOutContext(authenticationTypes); + handler.SignOut(signOutContext); + // TODO: Verify all types ack'd + } + } +} diff --git a/src/Microsoft.AspNet.PipelineCore/Security/DefaultHttpAuthentication.cs b/src/Microsoft.AspNet.PipelineCore/Security/DefaultHttpAuthentication.cs new file mode 100644 index 0000000000..519b1a6c5e --- /dev/null +++ b/src/Microsoft.AspNet.PipelineCore/Security/DefaultHttpAuthentication.cs @@ -0,0 +1,24 @@ +using System.Security.Claims; +using Microsoft.AspNet.HttpFeature.Security; + +namespace Microsoft.AspNet.PipelineCore.Security +{ + public class DefaultHttpAuthentication : IHttpAuthentication + { + public DefaultHttpAuthentication() + { + } + + public ClaimsPrincipal User + { + get; + set; + } + + public IAuthenticationHandler Handler + { + get; + set; + } + } +} diff --git a/src/Microsoft.AspNet.PipelineCore/Security/SignInContext.cs b/src/Microsoft.AspNet.PipelineCore/Security/SignInContext.cs new file mode 100644 index 0000000000..2f48119b88 --- /dev/null +++ b/src/Microsoft.AspNet.PipelineCore/Security/SignInContext.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Security.Claims; +using Microsoft.AspNet.HttpFeature.Security; + +namespace Microsoft.AspNet.PipelineCore.Security +{ + public class SignInContext : ISignInContext + { + public SignInContext(ClaimsPrincipal user, IDictionary dictionary) + { + if (user == null) + { + throw new ArgumentNullException("user"); + } + User = user; + Properties = dictionary ?? new Dictionary(StringComparer.Ordinal); + } + + public ClaimsPrincipal User { get; private set; } + + public IDictionary Properties { get; private set; } + + public void Ack(string authenticationType, IDictionary description) + { + } + } +} diff --git a/src/Microsoft.AspNet.PipelineCore/Security/SignOutContext.cs b/src/Microsoft.AspNet.PipelineCore/Security/SignOutContext.cs new file mode 100644 index 0000000000..b99950d3bc --- /dev/null +++ b/src/Microsoft.AspNet.PipelineCore/Security/SignOutContext.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using Microsoft.AspNet.HttpFeature.Security; + +namespace Microsoft.AspNet.PipelineCore.Security +{ + public class SignOutContext : ISignOutContext + { + public SignOutContext(IList authenticationTypes) + { + if (authenticationTypes == null) + { + throw new ArgumentNullException("authenticationTypes"); + } + AuthenticationTypes = authenticationTypes; + } + + public IList AuthenticationTypes { get; private set; } + + public void Ack(string authenticationType, IDictionary description) + { + } + } +} diff --git a/src/Microsoft.AspNet.PipelineCore/project.json b/src/Microsoft.AspNet.PipelineCore/project.json index 58a9d9ef86..e8e90fdd08 100644 --- a/src/Microsoft.AspNet.PipelineCore/project.json +++ b/src/Microsoft.AspNet.PipelineCore/project.json @@ -20,6 +20,8 @@ "System.Runtime": "4.0.20.0", "System.Runtime.Extensions": "4.0.10.0", "System.Runtime.InteropServices": "4.0.20.0", + "System.Security.Claims": "0.1-alpha-*", + "System.Security.Principal" : "4.0.0.0", "System.Text.Encoding": "4.0.20.0", "System.Threading.Tasks": "4.0.10.0" }