Merge pull request #11 from tomasherceg/master

OpenID - generic protocol extension infrastructure & Simple Registration Extension implementation
This commit is contained in:
Jerrie Pelser
2014-05-05 09:25:53 +07:00
12 changed files with 278 additions and 7 deletions

View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Owin.Security.Providers.OpenID.Extensions
{
/// <summary>
/// Contains an extension method that makes reading the SREG fields easier.
/// </summary>
public static class OpenIDSimpleRegistrationAuthenticationContextExtensions
{
public static OpenIDSimpleRegistrationResult GetSimpleRegistrationResult(this OpenIDAuthenticatedContext context)
{
if (!context.ProtocolExtensionData.ContainsKey(typeof (OpenIDSimpleRegistrationExtension)))
{
return new OpenIDSimpleRegistrationResult();
}
else
{
return context.ProtocolExtensionData[typeof (OpenIDSimpleRegistrationExtension)] as OpenIDSimpleRegistrationResult;
}
}
}
}

View File

@@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
namespace Owin.Security.Providers.OpenID.Extensions
{
/// <summary>
/// Implements the OpenID Simple Registration Extension http://openid.net/specs/openid-simple-registration-extension-1_0.html
/// </summary>
public class OpenIDSimpleRegistrationExtension : IOpenIDProtocolExtension
{
private static readonly Dictionary<OpenIDSimpleRegistrationField, string> claimsMap = new Dictionary<OpenIDSimpleRegistrationField, string>()
{
{ OpenIDSimpleRegistrationField.NickName, "nickname" },
{ OpenIDSimpleRegistrationField.FullName, "fullname" },
{ OpenIDSimpleRegistrationField.Email, "email" },
{ OpenIDSimpleRegistrationField.DayOfBirth, "dob" },
{ OpenIDSimpleRegistrationField.Gender, "gender" },
{ OpenIDSimpleRegistrationField.PostCode, "postcode" },
{ OpenIDSimpleRegistrationField.Country, "country" },
{ OpenIDSimpleRegistrationField.Language, "language" },
{ OpenIDSimpleRegistrationField.Timezone, "timezone" }
};
private const string sregNamespace = "http://openid.net/extensions/sreg/1.1";
/// <summary>
/// Gets or sets a list of comma-separated SREG fields that are required.
/// </summary>
public HashSet<OpenIDSimpleRegistrationField> RequiredFields { get; private set; }
/// <summary>
/// Gets or sets a list of comma-separated SREG fields that are optional.
/// </summary>
public HashSet<OpenIDSimpleRegistrationField> OptionalFields { get; private set; }
/// <summary>
/// Gets or sets the SREG policy URL.
/// </summary>
public string PolicyUrl { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="OpenIDSimpleRegistrationExtension"/> class.
/// </summary>
public OpenIDSimpleRegistrationExtension()
{
RequiredFields = new HashSet<OpenIDSimpleRegistrationField>() { OpenIDSimpleRegistrationField.Email, OpenIDSimpleRegistrationField.FullName };
OptionalFields = new HashSet<OpenIDSimpleRegistrationField>();
PolicyUrl = string.Empty;
}
/// <summary>
/// Appends the SREG required attributes to the request URL constructed on challenge.
/// </summary>
public Task OnChallengeAsync(Microsoft.Owin.Security.AuthenticationResponseChallenge challenge, OpenIDAuthorizationEndpointInfo endpoint)
{
endpoint.Url += "&openid.ns.sreg=" + Uri.EscapeDataString(sregNamespace);
var requiredClaims = string.Join(",", RequiredFields.Select(f => claimsMap[f]));
endpoint.Url += "&openid.sreg.required=" + Uri.EscapeDataString(requiredClaims);
if (OptionalFields.Any())
{
var optionalClaims = string.Join(",", OptionalFields.Select(f => claimsMap[f]));
endpoint.Url += "&openid.sreg.optional=" + Uri.EscapeDataString(optionalClaims);
}
if (!string.IsNullOrEmpty(PolicyUrl))
{
endpoint.Url += "&openid.sreg.policy_url=" + Uri.EscapeDataString(PolicyUrl);
}
return Task.FromResult(0);
}
/// <summary>
/// Validates the authentication response message.
/// </summary>
public Task<bool> OnValidateMessageAsync(Infrastructure.Message message)
{
// no additional checks needed
return Task.FromResult(true);
}
/// <summary>
/// Extracts SREG attributes and returns the results.
/// </summary>
public Task<object> OnExtractResultsAsync(ClaimsIdentity identity, string claimedId, Infrastructure.Message message)
{
var result = new OpenIDSimpleRegistrationResult();
foreach (var claim in claimsMap)
{
string value;
if (message.TryGetValue(claim.Value + "." + sregNamespace, out value))
{
result.Values.Add(claim.Key, value);
}
}
return Task.FromResult((object)result);
}
}
}

View File

@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Owin.Security.Providers.OpenID.Extensions
{
public enum OpenIDSimpleRegistrationField
{
NickName,
FullName,
Email,
DayOfBirth,
Gender,
PostCode,
Country,
Language,
Timezone
}
}

View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Owin.Security.Providers.OpenID.Extensions
{
/// <summary>
/// Contains values of OpenID Simple Registration Extension fields.
/// </summary>
public class OpenIDSimpleRegistrationResult
{
public IDictionary<OpenIDSimpleRegistrationField, string> Values { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="OpenIDSimpleRegistrationResult"/> class.
/// </summary>
public OpenIDSimpleRegistrationResult()
{
Values = new Dictionary<OpenIDSimpleRegistrationField, string>();
}
/// <summary>
/// Gets the SREG field value.
/// </summary>
public string GetFieldValue(OpenIDSimpleRegistrationField field)
{
if (!Values.ContainsKey(field)) return null;
return Values[field];
}
}
}

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Owin.Security;
namespace Owin.Security.Providers.OpenID
{
public interface IOpenIDProtocolExtension
{
/// <summary>
/// Adds the required information in the authorization endpoint URL.
/// </summary>
Task OnChallengeAsync(AuthenticationResponseChallenge challenge, OpenIDAuthorizationEndpointInfo endpoint);
/// <summary>
/// Performs additional authentication response message validations.
/// </summary>
Task<bool> OnValidateMessageAsync(Infrastructure.Message message);
/// <summary>
/// Extracts the data form the authentication response message and returns them.
/// </summary>
Task<object> OnExtractResultsAsync(System.Security.Claims.ClaimsIdentity identity, string claimedId, Infrastructure.Message message);
}
}

View File

@@ -5,7 +5,7 @@ using System.Linq;
namespace Owin.Security.Providers.OpenID.Infrastructure
{
internal class Message
public class Message
{
public Message(IReadableStringCollection parameters, bool strict)
{

View File

@@ -1,7 +1,7 @@

namespace Owin.Security.Providers.OpenID.Infrastructure
{
internal class Property
public class Property
{
public string Key { get; set; }
public string Namespace { get; set; }

View File

@@ -153,6 +153,15 @@ namespace Owin.Security.Providers.OpenID
}
}
// Allow protocol extensions to add custom message validation rules
foreach (var protocolExtension in Options.ProtocolExtensions)
{
if (!await protocolExtension.OnValidateMessageAsync(message))
{
messageValidated = false;
}
}
if (messageValidated)
{
IDictionary<string, string> attributeExchangeProperties = new Dictionary<string, string>();
@@ -190,7 +199,7 @@ namespace Owin.Security.Providers.OpenID
}
SetIdentityInformations(identity, claimedId.Value, attributeExchangeProperties);
var context = new OpenIDAuthenticatedContext(
Context,
identity,
@@ -198,6 +207,14 @@ namespace Owin.Security.Providers.OpenID
responseMessage,
attributeExchangeProperties);
// Let protocol extensions to extract the results from the message
foreach (var protocolExtension in Options.ProtocolExtensions)
{
var result = await protocolExtension.OnExtractResultsAsync(identity, claimedId.Value, message);
context.ProtocolExtensionData[protocolExtension.GetType()] = result;
}
await Options.Provider.Authenticated(context);
return new AuthenticationTicket(context.Identity, context.Properties);
@@ -353,8 +370,18 @@ namespace Owin.Security.Providers.OpenID
"&openid.ax.required=" + Uri.EscapeDataString("email,name,first,last,email2,name2,first2,last2");
// allow protocol extensions to add their own attributes to the endpoint URL
var endpoint = new OpenIDAuthorizationEndpointInfo()
{
Url = authorizationEndpoint
};
foreach (var protocolExtension in Options.ProtocolExtensions)
{
await protocolExtension.OnChallengeAsync(challenge, endpoint);
}
Response.StatusCode = 302;
Response.Headers.Set("Location", authorizationEndpoint);
Response.Headers.Set("Location", endpoint.Url);
}
}
}

View File

@@ -1,4 +1,5 @@
using Microsoft.Owin;
using System.Collections.Generic;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using System;
using System.Net.Http;
@@ -77,6 +78,12 @@ namespace Owin.Security.Providers.OpenID
/// </summary>
public string ProviderLoginUri { get; set; }
/// <summary>
/// A list of protocol extensions.
/// </summary>
public List<IOpenIDProtocolExtension> ProtocolExtensions { get; set; }
/// <summary>
/// Initializes a new <see cref="OpenIDAuthenticationOptions"/>
/// </summary>
@@ -87,6 +94,7 @@ namespace Owin.Security.Providers.OpenID
CallbackPath = new PathString("/signin-openid");
AuthenticationMode = AuthenticationMode.Passive;
BackchannelTimeout = TimeSpan.FromSeconds(60);
ProtocolExtensions = new List<IOpenIDProtocolExtension>();
}
}
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Owin.Security.Providers.OpenID
{
public class OpenIDAuthorizationEndpointInfo
{
public string Url { get; set; }
}
}

View File

@@ -1,4 +1,5 @@
using Microsoft.Owin;
using System;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Provider;
using System.Collections.Generic;
@@ -12,7 +13,7 @@ namespace Owin.Security.Providers.OpenID
/// </summary>
public class OpenIDAuthenticatedContext : BaseContext
{
/// <summary>
/// <summary>
/// Initializes a <see cref="OpenIDAuthenticatedContext"/>
/// </summary>
/// <param name="context">The OWIN environment</param>
@@ -32,6 +33,7 @@ namespace Owin.Security.Providers.OpenID
Properties = properties;
ResponseMessage = responseMessage;
AttributeExchangeProperties = attributeExchangeProperties;
ProtocolExtensionData = new Dictionary<Type, object>();
}
/// <summary>
@@ -47,5 +49,7 @@ namespace Owin.Security.Providers.OpenID
public XElement ResponseMessage { get; set; }
public IDictionary<string, string> AttributeExchangeProperties { get; private set; }
public IDictionary<Type, object> ProtocolExtensionData { get; private set; }
}
}

View File

@@ -83,12 +83,18 @@
<Compile Include="LinkedIn\Provider\LinkedInReturnEndpointContext.cs" />
<Compile Include="LinkedIn\Provider\ILinkedInAuthenticationProvider.cs" />
<Compile Include="OpenID\Constants.cs" />
<Compile Include="OpenID\Extensions\OpenIDSimpleRegistrationAuthenticationContextExtensions.cs" />
<Compile Include="OpenID\Extensions\OpenIDSimpleRegistrationExtension.cs" />
<Compile Include="OpenID\Extensions\OpenIDSimpleRegistrationResult.cs" />
<Compile Include="OpenID\Extensions\OpenIDSimpleRegistrationField.cs" />
<Compile Include="OpenID\Infrastructure\Message.cs" />
<Compile Include="OpenID\Infrastructure\Property.cs" />
<Compile Include="OpenID\IOpenIDProtocolExtension.cs" />
<Compile Include="OpenID\OpenIDAuthenticationExtensions.cs" />
<Compile Include="OpenID\OpenIDAuthenticationHandler.cs" />
<Compile Include="OpenID\OpenIDAuthenticationMiddleware.cs" />
<Compile Include="OpenID\OpenIDAuthenticationOptions.cs" />
<Compile Include="OpenID\OpenIDAuthorizationEndpointInfo.cs" />
<Compile Include="OpenID\Provider\IOpenIDAuthenticationProvider.cs" />
<Compile Include="OpenID\Provider\OpenIDAuthenticatedContext.cs" />
<Compile Include="OpenID\Provider\OpenIDAuthenticationProvider.cs" />