diff --git a/Owin.Security.Providers/DoYouBuzz/Constants.cs b/Owin.Security.Providers/DoYouBuzz/Constants.cs new file mode 100644 index 0000000..5d29c7d --- /dev/null +++ b/Owin.Security.Providers/DoYouBuzz/Constants.cs @@ -0,0 +1,7 @@ +namespace Owin.Security.Providers.DoYouBuzz +{ + internal static class Constants + { + public const string DefaultAuthenticationType = "DoYouBuzz"; + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/DoYouBuzz/DoYouBuzzAuthenticationExtensions.cs b/Owin.Security.Providers/DoYouBuzz/DoYouBuzzAuthenticationExtensions.cs new file mode 100644 index 0000000..95f1e17 --- /dev/null +++ b/Owin.Security.Providers/DoYouBuzz/DoYouBuzzAuthenticationExtensions.cs @@ -0,0 +1,28 @@ +using System; + +namespace Owin.Security.Providers.DoYouBuzz +{ + public static class DoYouBuzzAuthenticationExtensions + { + public static IAppBuilder UseDoYouBuzzAuthentication(this IAppBuilder app, DoYouBuzzAuthenticationOptions options) + { + if (app == null) + throw new ArgumentNullException("app"); + if (options == null) + throw new ArgumentNullException("options"); + + app.Use(typeof(DoYouBuzzAuthenticationMiddleware), app, options); + + return app; + } + + public static IAppBuilder UseDoYouBuzzAuthentication(this IAppBuilder app, string consumerKey, string consumerSecret) + { + return app.UseDoYouBuzzAuthentication(new DoYouBuzzAuthenticationOptions + { + ConsumerKey = consumerKey, + ConsumerSecret = consumerSecret + }); + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/DoYouBuzz/DoYouBuzzAuthenticationHandler.cs b/Owin.Security.Providers/DoYouBuzz/DoYouBuzzAuthenticationHandler.cs new file mode 100644 index 0000000..da85ce5 --- /dev/null +++ b/Owin.Security.Providers/DoYouBuzz/DoYouBuzzAuthenticationHandler.cs @@ -0,0 +1,434 @@ +using Microsoft.Owin; +using Microsoft.Owin.Helpers; +using Microsoft.Owin.Infrastructure; +using Microsoft.Owin.Logging; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Infrastructure; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Net.Http; +using System.Security.Claims; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using Newtonsoft.Json; +using Owin.Security.Providers.DoYouBuzz.Messages; +using Formatting = Newtonsoft.Json.Formatting; + +namespace Owin.Security.Providers.DoYouBuzz +{ + internal class DoYouBuzzAuthenticationHandler : AuthenticationHandler + { + private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + private const string StateCookie = "__DoYouBuzzState"; + private const string RequestTokenEndpoint = "http://www.doyoubuzz.com/fr/oauth/requestToken"; + private const string AuthenticationEndpoint = "http://www.doyoubuzz.com/fr/oauth/authorize?oauth_token="; + private const string AccessTokenEndpoint = "http://www.doyoubuzz.com/fr/oauth/accessToken"; + private const string UserInfoEndpoint = "https://api.doyoubuzz.com/user"; + + private readonly HttpClient _httpClient; + private readonly ILogger _logger; + + public DoYouBuzzAuthenticationHandler(HttpClient httpClient, ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + } + + public override async Task InvokeAsync() + { + if (Options.CallbackPath.HasValue && Options.CallbackPath == Request.Path) + { + return await InvokeReturnPathAsync(); + } + return false; + } + + protected override async Task AuthenticateCoreAsync() + { + AuthenticationProperties properties = null; + try + { + var query = Request.Query; + var protectedRequestToken = Request.Cookies[StateCookie]; + + var requestToken = Options.StateDataFormat.Unprotect(protectedRequestToken); + + if (requestToken == null) + { + _logger.WriteWarning("Invalid state"); + return null; + } + + properties = requestToken.Properties; + + var returnedToken = query.Get("oauth_token"); + if (string.IsNullOrWhiteSpace(returnedToken)) + { + _logger.WriteWarning("Missing oauth_token"); + return new AuthenticationTicket(null, properties); + } + + if (returnedToken != requestToken.Token) + { + _logger.WriteWarning("Unmatched token"); + return new AuthenticationTicket(null, properties); + } + + var oauthVerifier = query.Get("oauth_verifier"); + if (string.IsNullOrWhiteSpace(oauthVerifier)) + { + _logger.WriteWarning("Missing or blank oauth_verifier"); + return new AuthenticationTicket(null, properties); + } + + var accessToken = await ObtainAccessTokenAsync(Options.ConsumerKey, Options.ConsumerSecret, requestToken, oauthVerifier); + + var context = new DoYouBuzzAuthenticatedContext(Context, accessToken.UserId, accessToken.Token, accessToken.TokenSecret) + { + Identity = new ClaimsIdentity( + new[] + { + new Claim(ClaimTypes.NameIdentifier, accessToken.UserId, "http://www.w3.org/2001/XMLSchema#string", Options.AuthenticationType), + new Claim("urn:DoYouBuzz:userid", accessToken.UserId, "http://www.w3.org/2001/XMLSchema#string", Options.AuthenticationType), + }, + Options.AuthenticationType, + ClaimsIdentity.DefaultNameClaimType, + ClaimsIdentity.DefaultRoleClaimType), + Properties = requestToken.Properties + }; + + Response.Cookies.Delete(StateCookie); + + await Options.Provider.Authenticated(context); + + return new AuthenticationTicket(context.Identity, context.Properties); + } + catch (Exception ex) + { + _logger.WriteError("Authentication failed", ex); + return new AuthenticationTicket(null, properties); + } + } + + protected override async Task ApplyResponseChallengeAsync() + { + if (Response.StatusCode != 401) + { + return; + } + + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + + if (challenge != null) + { + var requestPrefix = Request.Scheme + "://" + Request.Host; + var callBackUrl = requestPrefix + RequestPathBase + Options.CallbackPath; + + var extra = challenge.Properties; + if (string.IsNullOrEmpty(extra.RedirectUri)) + { + extra.RedirectUri = requestPrefix + Request.PathBase + Request.Path + Request.QueryString; + } + + var requestToken = await ObtainRequestTokenAsync(Options.ConsumerKey, Options.ConsumerSecret, callBackUrl, extra); + + if (requestToken.CallbackConfirmed) + { + var doYouBuzzAuthenticationEndpoint = AuthenticationEndpoint + requestToken.Token; + + var cookieOptions = new CookieOptions + { + HttpOnly = true, + Secure = Request.IsSecure + }; + + Response.Cookies.Append(StateCookie, Options.StateDataFormat.Protect(requestToken), cookieOptions); + + var redirectContext = new DoYouBuzzApplyRedirectContext( + Context, Options, + extra, doYouBuzzAuthenticationEndpoint); + Options.Provider.ApplyRedirect(redirectContext); + } + else + { + _logger.WriteError("requestToken CallbackConfirmed!=true"); + } + } + } + + public async Task InvokeReturnPathAsync() + { + var model = await AuthenticateAsync(); + if (model == null) + { + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; + } + + var context = new DoYouBuzzReturnEndpointContext(Context, model) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = model.Properties.RedirectUri + }; + model.Properties.RedirectUri = null; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && context.Identity != null) + { + var signInIdentity = context.Identity; + if (!string.Equals(signInIdentity.AuthenticationType, context.SignInAsAuthenticationType, StringComparison.Ordinal)) + { + signInIdentity = new ClaimsIdentity(signInIdentity.Claims, context.SignInAsAuthenticationType, signInIdentity.NameClaimType, signInIdentity.RoleClaimType); + } + Context.Authentication.SignIn(context.Properties, signInIdentity); + } + + if (!context.IsRequestCompleted && context.RedirectUri != null) + { + if (context.Identity == null) + { + // add a redirect hint that sign-in failed in some way + context.RedirectUri = WebUtilities.AddQueryString(context.RedirectUri, "error", "access_denied"); + } + Response.Redirect(context.RedirectUri); + context.RequestCompleted(); + } + + return context.IsRequestCompleted; + } + + private async Task ObtainRequestTokenAsync(string consumerKey, string consumerSecret, string callBackUri, AuthenticationProperties properties) + { + _logger.WriteVerbose("ObtainRequestToken"); + + var nonce = Guid.NewGuid().ToString("N"); + + var authorizationParts = new SortedDictionary + { + { "oauth_callback", callBackUri }, + { "oauth_consumer_key", consumerKey }, + { "oauth_nonce", nonce }, + { "oauth_signature_method", "HMAC-SHA1" }, + { "oauth_timestamp", GenerateTimeStamp() }, + { "oauth_version", "1.0" } + }; + + var canonicalizedRequestBuilder = new StringBuilder(); + canonicalizedRequestBuilder.Append(HttpMethod.Post.Method); + canonicalizedRequestBuilder.Append("&"); + canonicalizedRequestBuilder.Append(Uri.EscapeDataString(RequestTokenEndpoint)); + canonicalizedRequestBuilder.Append("&"); + canonicalizedRequestBuilder.Append(Uri.EscapeDataString(GetParameters(authorizationParts))); + + var signature = ComputeSignature(consumerSecret, null, canonicalizedRequestBuilder.ToString()); + authorizationParts.Add("oauth_signature", signature); + + var authorizationHeaderBuilder = new StringBuilder(); + authorizationHeaderBuilder.Append("OAuth "); + foreach (var authorizationPart in authorizationParts) + { + authorizationHeaderBuilder.AppendFormat("{0}=\"{1}\", ", authorizationPart.Key, Uri.EscapeDataString(authorizationPart.Value)); + } + authorizationHeaderBuilder.Length = authorizationHeaderBuilder.Length - 2; + + var request = new HttpRequestMessage(HttpMethod.Post, RequestTokenEndpoint); + request.Headers.Add("Authorization", authorizationHeaderBuilder.ToString()); + + var response = await _httpClient.SendAsync(request, Request.CallCancelled); + response.EnsureSuccessStatusCode(); + var responseText = await response.Content.ReadAsStringAsync(); + + var responseParameters = WebHelpers.ParseForm(responseText); + if (string.Equals(responseParameters["oauth_callback_confirmed"], "true", StringComparison.InvariantCulture)) + { + return new RequestToken { Token = Uri.UnescapeDataString(responseParameters["oauth_token"]), TokenSecret = Uri.UnescapeDataString(responseParameters["oauth_token_secret"]), CallbackConfirmed = true, Properties = properties }; + } + + return new RequestToken(); + } + + private async Task ObtainAccessTokenAsync(string consumerKey, string consumerSecret, RequestToken token, string verifier) + { + //https://dev.DoYouBuzz.com/docs/authentication + + _logger.WriteVerbose("ObtainAccessToken"); + + var nonce = Guid.NewGuid().ToString("N"); + + var authorizationParts = new SortedDictionary + { + { "oauth_consumer_key", consumerKey }, + { "oauth_nonce", nonce }, + { "oauth_signature_method", "HMAC-SHA1" }, + { "oauth_token", token.Token }, + { "oauth_timestamp", GenerateTimeStamp() }, + { "oauth_verifier", verifier }, + { "oauth_version", "1.0" }, + }; + + var parameterBuilder = new StringBuilder(); + foreach (var authorizationKey in authorizationParts) + { + parameterBuilder.AppendFormat("{0}={1}&", Uri.EscapeDataString(authorizationKey.Key), Uri.EscapeDataString(authorizationKey.Value)); + } + parameterBuilder.Length--; + var parameterString = parameterBuilder.ToString(); + + var canonicalizedRequestBuilder = new StringBuilder(); + canonicalizedRequestBuilder.Append(HttpMethod.Post.Method); + canonicalizedRequestBuilder.Append("&"); + canonicalizedRequestBuilder.Append(Uri.EscapeDataString(AccessTokenEndpoint)); + canonicalizedRequestBuilder.Append("&"); + canonicalizedRequestBuilder.Append(Uri.EscapeDataString(parameterString)); + + var signature = ComputeSignature(consumerSecret, token.TokenSecret, canonicalizedRequestBuilder.ToString()); + authorizationParts.Add("oauth_signature", signature); + authorizationParts.Remove("oauth_verifier"); + + var authorizationHeaderBuilder = new StringBuilder(); + authorizationHeaderBuilder.Append("OAuth "); + foreach (var authorizationPart in authorizationParts) + { + authorizationHeaderBuilder.AppendFormat( + "{0}=\"{1}\", ", authorizationPart.Key, Uri.EscapeDataString(authorizationPart.Value)); + } + authorizationHeaderBuilder.Length = authorizationHeaderBuilder.Length - 2; + + var request = new HttpRequestMessage(HttpMethod.Post, AccessTokenEndpoint); + request.Headers.Add("Authorization", authorizationHeaderBuilder.ToString()); + + var formPairs = new List>() + { + new KeyValuePair("oauth_verifier", verifier) + }; + + request.Content = new FormUrlEncodedContent(formPairs); + + var response = await _httpClient.SendAsync(request, Request.CallCancelled); + + if (!response.IsSuccessStatusCode) + { + _logger.WriteError("AccessToken request failed with a status code of " + response.StatusCode); + response.EnsureSuccessStatusCode(); // throw + } + + var responseText = await response.Content.ReadAsStringAsync(); + + var responseParameters = WebHelpers.ParseForm(responseText); + + var accessToken = new AccessToken + { + Token = Uri.UnescapeDataString(responseParameters["oauth_token"]), + TokenSecret = Uri.UnescapeDataString(responseParameters["oauth_token_secret"]), + UserId = "", + }; + + var userInfo = GetUserInfo(consumerKey, consumerSecret, accessToken); + accessToken.UserId = userInfo.Id.ToString(); + + return accessToken; + } + + private async Task GetUserInfo(string consumerKey, string consumerSecret, AccessToken token) + { + _logger.WriteVerbose("ObtainAccessToken"); + + var nonce = Guid.NewGuid().ToString("N"); + + var authorizationParts = new SortedDictionary + { + { "oauth_consumer_key", consumerKey }, + { "oauth_nonce", nonce }, + { "oauth_signature_method", "HMAC-SHA1" }, + { "oauth_token", token.Token }, + { "oauth_timestamp", GenerateTimeStamp() }, + { "oauth_version", "1.0" }, + }; + + var parameterBuilder = new StringBuilder(); + foreach (var authorizationKey in authorizationParts) + { + parameterBuilder.AppendFormat("{0}={1}&", Uri.EscapeDataString(authorizationKey.Key), Uri.EscapeDataString(authorizationKey.Value)); + } + parameterBuilder.Length--; + var parameterString = parameterBuilder.ToString(); + + var canonicalizedRequestBuilder = new StringBuilder(); + canonicalizedRequestBuilder.Append(HttpMethod.Post.Method); + canonicalizedRequestBuilder.Append("&"); + canonicalizedRequestBuilder.Append(Uri.EscapeDataString(UserInfoEndpoint)); + canonicalizedRequestBuilder.Append("&"); + canonicalizedRequestBuilder.Append(Uri.EscapeDataString(parameterString)); + + var signature = ComputeSignature(consumerSecret, token.TokenSecret, canonicalizedRequestBuilder.ToString()); + authorizationParts.Add("oauth_signature", signature); + authorizationParts.Remove("oauth_verifier"); + + var authorizationHeaderBuilder = new StringBuilder(); + authorizationHeaderBuilder.Append("OAuth "); + foreach (var authorizationPart in authorizationParts) + { + authorizationHeaderBuilder.AppendFormat("{0}=\"{1}\", ", authorizationPart.Key, Uri.EscapeDataString(authorizationPart.Value)); + } + authorizationHeaderBuilder.Length = authorizationHeaderBuilder.Length - 2; + + var request = new HttpRequestMessage(HttpMethod.Post, UserInfoEndpoint); + request.Headers.Add("Authorization", authorizationHeaderBuilder.ToString()); + + var response = await _httpClient.SendAsync(request, Request.CallCancelled); + + if (!response.IsSuccessStatusCode) + { + _logger.WriteError("AccessToken request failed with a status code of " + response.StatusCode); + response.EnsureSuccessStatusCode(); // throw + } + + var responseText = await response.Content.ReadAsStringAsync(); + + var document = new XmlDocument(); + document.LoadXml(responseText); + + var json = JsonConvert.SerializeXmlNode(document, Formatting.None); + var userInfo = JsonConvert.DeserializeObject(json); + + return new AccessToken + { + Token = token.Token, + TokenSecret = token.TokenSecret, + UserId = userInfo.Id.ToString(), + }; + } + + private static string GetParameters(SortedDictionary parameters) + { + var parameterBuilder = new StringBuilder(); + foreach (var param in parameters) + { + parameterBuilder.AppendFormat("{0}={1}&", Uri.EscapeDataString(param.Key), Uri.EscapeDataString(param.Value)); + } + parameterBuilder.Length--; + return parameterBuilder.ToString(); + } + + private static string GenerateTimeStamp() + { + var secondsSinceUnixEpocStart = DateTime.UtcNow - Epoch; + return Convert.ToInt64(secondsSinceUnixEpocStart.TotalSeconds).ToString(CultureInfo.InvariantCulture); + } + + private static string ComputeSignature(string consumerSecret, string tokenSecret, string signatureData) + { + using (var algorithm = new HMACSHA1()) + { + algorithm.Key = Encoding.ASCII.GetBytes(string.Format(CultureInfo.InvariantCulture, "{0}&{1}", Uri.EscapeDataString(consumerSecret), string.IsNullOrEmpty(tokenSecret) ? string.Empty : Uri.EscapeDataString(tokenSecret))); + var hash = algorithm.ComputeHash(Encoding.ASCII.GetBytes(signatureData)); + return Convert.ToBase64String(hash); + } + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/DoYouBuzz/DoYouBuzzAuthenticationMiddleware.cs b/Owin.Security.Providers/DoYouBuzz/DoYouBuzzAuthenticationMiddleware.cs new file mode 100644 index 0000000..d77d0e0 --- /dev/null +++ b/Owin.Security.Providers/DoYouBuzz/DoYouBuzzAuthenticationMiddleware.cs @@ -0,0 +1,97 @@ +using Microsoft.Owin; +using Microsoft.Owin.Logging; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.DataHandler; +using Microsoft.Owin.Security.DataHandler.Encoder; +using Microsoft.Owin.Security.DataProtection; +using Microsoft.Owin.Security.Infrastructure; +using System; +using System.Globalization; +using System.Net.Http; +using Owin.Security.Providers.DoYouBuzz.Messages; +using Owin.Security.Providers.Properties; + +namespace Owin.Security.Providers.DoYouBuzz +{ + /// + /// OWIN middleware for authenticating users using DoYouBuzz + /// + public class DoYouBuzzAuthenticationMiddleware : AuthenticationMiddleware + { + private readonly ILogger _logger; + private readonly HttpClient _httpClient; + + /// + /// Initializes a + /// + /// The next middleware in the OWIN pipeline to invoke + /// The OWIN application + /// Configuration options for the middleware + public DoYouBuzzAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, DoYouBuzzAuthenticationOptions options) + : base(next, options) + { + _logger = app.CreateLogger(); + + if (string.IsNullOrWhiteSpace(Options.ConsumerSecret)) + { + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ConsumerSecret")); + } + + if (string.IsNullOrWhiteSpace(Options.ConsumerKey)) + { + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ConsumerKey")); + } + + if (Options.Provider == null) + { + Options.Provider = new DoYouBuzzAuthenticationProvider(); + } + if (Options.StateDataFormat == null) + { + var dataProtector = app.CreateDataProtector(typeof(DoYouBuzzAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); + Options.StateDataFormat = new SecureDataFormat(Serializers.RequestToken, dataProtector, TextEncodings.Base64Url); + } + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + { + Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); + } + + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)); + _httpClient.Timeout = Options.BackchannelTimeout; + _httpClient.MaxResponseContentBufferSize = 1024 * 1024 * 10; // 10 MB + _httpClient.DefaultRequestHeaders.Accept.ParseAdd("*/*"); + _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin DoYouBuzz middleware"); + _httpClient.DefaultRequestHeaders.ExpectContinue = false; + } + + /// + /// Provides the object for processing authentication-related requests. + /// + /// An configured with the supplied to the constructor. + protected override AuthenticationHandler CreateHandler() + { + return new DoYouBuzzAuthenticationHandler(_httpClient, _logger); + } + + private static HttpMessageHandler ResolveHttpMessageHandler(DoYouBuzzAuthenticationOptions options) + { + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + + // Set the cert validate callback + var webRequestHandler = handler as WebRequestHandler; + if (webRequestHandler == null) + { + if (options.BackchannelCertificateValidator != null) + { + throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); + } + } + else if (options.BackchannelCertificateValidator != null) + { + webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; + } + + return handler; + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/DoYouBuzz/DoYouBuzzAuthenticationOptions.cs b/Owin.Security.Providers/DoYouBuzz/DoYouBuzzAuthenticationOptions.cs new file mode 100644 index 0000000..3bd4d6d --- /dev/null +++ b/Owin.Security.Providers/DoYouBuzz/DoYouBuzzAuthenticationOptions.cs @@ -0,0 +1,95 @@ +using System; +using System.Net.Http; +using Microsoft.Owin.Security; +using Microsoft.Owin; +using Owin.Security.Providers.DoYouBuzz.Messages; + +namespace Owin.Security.Providers.DoYouBuzz +{ + /// + /// Options for the DoYouBuzz authentication middleware. + /// + public class DoYouBuzzAuthenticationOptions : AuthenticationOptions + { + /// + /// Initializes a new instance of the class. + /// + public DoYouBuzzAuthenticationOptions() + : base(Constants.DefaultAuthenticationType) + { + Caption = Constants.DefaultAuthenticationType; + CallbackPath = new PathString("/signin-doyoubuzz"); + AuthenticationMode = AuthenticationMode.Passive; + BackchannelTimeout = TimeSpan.FromSeconds(60); + } + + /// + /// Gets or sets the consumer key used to communicate with DoYouBuzz. + /// + /// The consumer key used to communicate with DoYouBuzz. + public string ConsumerKey { get; set; } + + /// + /// Gets or sets the consumer secret used to sign requests to DoYouBuzz. + /// + /// The consumer secret used to sign requests to DoYouBuzz. + public string ConsumerSecret { get; set; } + + /// + /// Gets or sets timeout value in milliseconds for back channel communications with DoYouBuzz. + /// + /// + /// The back channel timeout. + /// + public TimeSpan BackchannelTimeout { get; set; } + + /// + /// Gets or sets the a pinned certificate validator to use to validate the endpoints used + /// in back channel communications belong to DoYouBuzz. + /// + /// + /// The pinned certificate validator. + /// + /// If this property is null then the default certificate checks are performed, + /// validating the subject name and if the signing chain is a trusted party. + public ICertificateValidator BackchannelCertificateValidator { get; set; } + + /// + /// The HttpMessageHandler used to communicate with DoYouBuzz. + /// This cannot be set at the same time as BackchannelCertificateValidator unless the value + /// can be downcast to a WebRequestHandler. + /// + public HttpMessageHandler BackchannelHttpHandler { get; set; } + + /// + /// Get or sets the text that the user can display on a sign in user interface. + /// + public string Caption + { + get { return Description.Caption; } + set { Description.Caption = value; } + } + + /// + /// The request path within the application's base path where the user-agent will be returned. + /// The middleware will process this request when it arrives. + /// Default value is "/signin-DoYouBuzz". + /// + public PathString CallbackPath { get; set; } + + /// + /// Gets or sets the name of another authentication middleware which will be responsible for actually issuing a user . + /// + public string SignInAsAuthenticationType { get; set; } + + /// + /// Gets or sets the type used to secure data handled by the middleware. + /// + public ISecureDataFormat StateDataFormat { get; set; } + + /// + /// Gets or sets the used to handle authentication events. + /// + public IDoYouBuzzAuthenticationProvider Provider { get; set; } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/DoYouBuzz/Messages/AccessToken.cs b/Owin.Security.Providers/DoYouBuzz/Messages/AccessToken.cs new file mode 100644 index 0000000..9682c18 --- /dev/null +++ b/Owin.Security.Providers/DoYouBuzz/Messages/AccessToken.cs @@ -0,0 +1,13 @@ +namespace Owin.Security.Providers.DoYouBuzz.Messages +{ + /// + /// DoYouBuzz access token + /// + public class AccessToken : RequestToken + { + /// + /// Gets or sets the DoYouBuzz User ID + /// + public string UserId { get; set; } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/DoYouBuzz/Messages/RequestToken.cs b/Owin.Security.Providers/DoYouBuzz/Messages/RequestToken.cs new file mode 100644 index 0000000..f49c68c --- /dev/null +++ b/Owin.Security.Providers/DoYouBuzz/Messages/RequestToken.cs @@ -0,0 +1,30 @@ +using Microsoft.Owin.Security; + +namespace Owin.Security.Providers.DoYouBuzz.Messages +{ + /// + /// DoYouBuzz request token + /// + public class RequestToken + { + /// + /// Gets or sets the DoYouBuzz token + /// + public string Token { get; set; } + + /// + /// Gets or sets the DoYouBuzz token secret + /// + public string TokenSecret { get; set; } + + /// + /// Indicates that callback is confirmed or not + /// + public bool CallbackConfirmed { get; set; } + + /// + /// Gets or sets a property bag for common authentication properties + /// + public AuthenticationProperties Properties { get; set; } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/DoYouBuzz/Messages/RequestTokenSerializer.cs b/Owin.Security.Providers/DoYouBuzz/Messages/RequestTokenSerializer.cs new file mode 100644 index 0000000..3fe1a02 --- /dev/null +++ b/Owin.Security.Providers/DoYouBuzz/Messages/RequestTokenSerializer.cs @@ -0,0 +1,101 @@ +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.DataHandler.Serializer; +using System; +using System.IO; + +namespace Owin.Security.Providers.DoYouBuzz.Messages +{ + /// + /// Serializes and deserializes DoYouBuzz request and access tokens so that they can be used by other application components. + /// + public class RequestTokenSerializer : IDataSerializer + { + private const int FormatVersion = 1; + + /// + /// Serialize a request token + /// + /// The token to serialize + /// A byte array containing the serialized token + public virtual byte[] Serialize(RequestToken model) + { + using (var memory = new MemoryStream()) + { + using (var writer = new BinaryWriter(memory)) + { + Write(writer, model); + writer.Flush(); + return memory.ToArray(); + } + } + } + + /// + /// Deserializes a request token + /// + /// A byte array containing the serialized token + /// The DoYouBuzz request token + public virtual RequestToken Deserialize(byte[] data) + { + using (var memory = new MemoryStream(data)) + { + using (var reader = new BinaryReader(memory)) + { + return Read(reader); + } + } + } + + /// + /// Writes a DoYouBuzz request token as a series of bytes. Used by the method. + /// + /// The writer to use in writing the token + /// The token to write + public static void Write(BinaryWriter writer, RequestToken token) + { + if (writer == null) + { + throw new ArgumentNullException("writer"); + } + if (token == null) + { + throw new ArgumentNullException("token"); + } + + writer.Write(FormatVersion); + writer.Write(token.Token); + writer.Write(token.TokenSecret); + writer.Write(token.CallbackConfirmed); + PropertiesSerializer.Write(writer, token.Properties); + } + + /// + /// Reads a DoYouBuzz request token from a series of bytes. Used by the method. + /// + /// The reader to use in reading the token bytes + /// The token + public static RequestToken Read(BinaryReader reader) + { + if (reader == null) + { + throw new ArgumentNullException("reader"); + } + + if (reader.ReadInt32() != FormatVersion) + { + return null; + } + + string token = reader.ReadString(); + string tokenSecret = reader.ReadString(); + bool callbackConfirmed = reader.ReadBoolean(); + AuthenticationProperties properties = PropertiesSerializer.Read(reader); + if (properties == null) + { + return null; + } + + return new RequestToken { Token = token, TokenSecret = tokenSecret, CallbackConfirmed = callbackConfirmed, Properties = properties }; + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/DoYouBuzz/Messages/Resume.cs b/Owin.Security.Providers/DoYouBuzz/Messages/Resume.cs new file mode 100644 index 0000000..1a15c7a --- /dev/null +++ b/Owin.Security.Providers/DoYouBuzz/Messages/Resume.cs @@ -0,0 +1,45 @@ +using System; +using Newtonsoft.Json; + +namespace Owin.Security.Providers.DoYouBuzz.Messages +{ + [Serializable] + [JsonArray("resume")] + internal class Resume + { + /// + /// The unique identifier + /// + [JsonProperty("id")] + public int Id { get; set; } + + /// + /// The title + /// + [JsonProperty("title")] + public string Title { get; set; } + + /// + /// Indicates if this is the user's main resume + /// + [JsonProperty("main")] + public bool Main { get; set; } + + /// + /// The culture of the resume + /// + /// The possible values are + /// + /// fr_FR + /// en_US + /// en_UK + /// it_IT + /// es_ES + /// de_DE + /// + /// + /// + [JsonProperty("culture")] + public string Culture { get; set; } + } +} diff --git a/Owin.Security.Providers/DoYouBuzz/Messages/Serializer.cs b/Owin.Security.Providers/DoYouBuzz/Messages/Serializer.cs new file mode 100644 index 0000000..d5e3087 --- /dev/null +++ b/Owin.Security.Providers/DoYouBuzz/Messages/Serializer.cs @@ -0,0 +1,20 @@ +using Microsoft.Owin.Security.DataHandler.Serializer; + +namespace Owin.Security.Providers.DoYouBuzz.Messages +{ + /// + /// Provides access to a request token serializer + /// + public static class Serializers + { + static Serializers() + { + RequestToken = new RequestTokenSerializer(); + } + + /// + /// Gets or sets a statically-available serializer object. The value for this property will be by default. + /// + public static IDataSerializer RequestToken { get; set; } + } +} diff --git a/Owin.Security.Providers/DoYouBuzz/Messages/UserInfo.cs b/Owin.Security.Providers/DoYouBuzz/Messages/UserInfo.cs new file mode 100644 index 0000000..45c3699 --- /dev/null +++ b/Owin.Security.Providers/DoYouBuzz/Messages/UserInfo.cs @@ -0,0 +1,57 @@ +using System; +using Newtonsoft.Json; + +namespace Owin.Security.Providers.DoYouBuzz.Messages +{ + [Serializable] + internal class UserInfo + { + /// + /// The unique identifier + /// + [JsonProperty("id")] + public int Id { get; set; } + + /// + /// The last name + /// + [JsonProperty("lastname")] + public string LastName { get; set; } + + /// + /// The first name + /// + [JsonProperty("firstname")] + public string FirstName { get; set; } + + /// + /// The slug of the user's profile (append to http://www.doyoubuzz.com/ to get the user's main resume url) + /// + [JsonProperty("slug")] + public string Slug { get; set; } + + /// + /// The registration date + /// + [JsonProperty("registeredAt")] + public DateTime RegisteredAt { get; set; } + + /// + /// Indicates if the user is a Premium DoYouBuzz user + /// + [JsonProperty("premium")] + public bool Premium { get; set; } + + /// + /// The email verified + /// + [JsonProperty("email")] + public string Email { get; set; } + + /// + /// The user's resumes + /// + [JsonProperty("resumes")] + public Resume[] Resumes { get; set; } + } +} diff --git a/Owin.Security.Providers/DoYouBuzz/Provider/DoYouBuzzApplyRedirectContext.cs b/Owin.Security.Providers/DoYouBuzz/Provider/DoYouBuzzApplyRedirectContext.cs new file mode 100644 index 0000000..44a7d1e --- /dev/null +++ b/Owin.Security.Providers/DoYouBuzz/Provider/DoYouBuzzApplyRedirectContext.cs @@ -0,0 +1,36 @@ +using Microsoft.Owin; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Provider; + +namespace Owin.Security.Providers.DoYouBuzz +{ + /// + /// Context passed when a Challenge causes a redirect to authorize endpoint in the DoYouBuzz middleware + /// + public class DoYouBuzzApplyRedirectContext : BaseContext + { + /// + /// Creates a new context object. + /// + /// The OWIN request context + /// The DoYouBuzz middleware options + /// The authentication properties of the challenge + /// The initial redirect URI + public DoYouBuzzApplyRedirectContext(IOwinContext context, DoYouBuzzAuthenticationOptions options, AuthenticationProperties properties, string redirectUri) + : base(context, options) + { + RedirectUri = redirectUri; + Properties = properties; + } + + /// + /// Gets the URI used for the redirect operation. + /// + public string RedirectUri { get; private set; } + + /// + /// Gets the authentication properties of the challenge + /// + public AuthenticationProperties Properties { get; private set; } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/DoYouBuzz/Provider/DoYouBuzzAuthenticatedContext.cs b/Owin.Security.Providers/DoYouBuzz/Provider/DoYouBuzzAuthenticatedContext.cs new file mode 100644 index 0000000..74cb622 --- /dev/null +++ b/Owin.Security.Providers/DoYouBuzz/Provider/DoYouBuzzAuthenticatedContext.cs @@ -0,0 +1,53 @@ +using System.Security.Claims; +using Microsoft.Owin.Security.Provider; +using Microsoft.Owin; +using Microsoft.Owin.Security; + +namespace Owin.Security.Providers.DoYouBuzz +{ + /// + /// Contains information about the login session as well as the user . + /// + public class DoYouBuzzAuthenticatedContext : BaseContext + { + /// + /// Initializes a + /// + /// The OWIN environment + /// DoYouBuzz user ID + /// DoYouBuzz access token + /// DoYouBuzz access token secret + public DoYouBuzzAuthenticatedContext(IOwinContext context, string userId, string accessToken, string accessTokenSecret) + : base(context) + { + UserId = userId; + AccessToken = accessToken; + AccessTokenSecret = accessTokenSecret; + } + + /// + /// Gets the DoYouBuzz user ID + /// + public string UserId { get; private set; } + + /// + /// Gets the DoYouBuzz access token + /// + public string AccessToken { get; private set; } + + /// + /// Gets the DoYouBuzz access token secret + /// + public string AccessTokenSecret { get; private set; } + + /// + /// Gets the representing the user + /// + public ClaimsIdentity Identity { get; set; } + + /// + /// Gets or sets a property bag for common authentication properties + /// + public AuthenticationProperties Properties { get; set; } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/DoYouBuzz/Provider/DoYouBuzzAuthenticationProvider.cs b/Owin.Security.Providers/DoYouBuzz/Provider/DoYouBuzzAuthenticationProvider.cs new file mode 100644 index 0000000..b083e67 --- /dev/null +++ b/Owin.Security.Providers/DoYouBuzz/Provider/DoYouBuzzAuthenticationProvider.cs @@ -0,0 +1,67 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Owin.Security; + +namespace Owin.Security.Providers.DoYouBuzz +{ + /// + /// Default implementation. + /// + public class DoYouBuzzAuthenticationProvider : IDoYouBuzzAuthenticationProvider + { + /// + /// Initializes a + /// + public DoYouBuzzAuthenticationProvider() + { + OnAuthenticated = context => Task.FromResult(null); + OnReturnEndpoint = context => Task.FromResult(null); + OnApplyRedirect = context => + context.Response.Redirect(context.RedirectUri); + } + + /// + /// Gets or sets the function that is invoked when the Authenticated method is invoked. + /// + public Func OnAuthenticated { get; set; } + + /// + /// Gets or sets the function that is invoked when the ReturnEndpoint method is invoked. + /// + public Func OnReturnEndpoint { get; set; } + + /// + /// Gets or sets the delegate that is invoked when the ApplyRedirect method is invoked. + /// + public Action OnApplyRedirect { get; set; } + + /// + /// Invoked whenever DoYouBuzz successfully authenticates a user + /// + /// Contains information about the login session as well as the user . + /// A representing the completed operation. + public virtual Task Authenticated(DoYouBuzzAuthenticatedContext context) + { + return OnAuthenticated(context); + } + + /// + /// Invoked prior to the being saved in a local cookie and the browser being redirected to the originally requested URL. + /// + /// + /// A representing the completed operation. + public virtual Task ReturnEndpoint(DoYouBuzzReturnEndpointContext context) + { + return OnReturnEndpoint(context); + } + + /// + /// Called when a Challenge causes a redirect to authorize endpoint in the DoYouBuzz middleware + /// + /// Contains redirect URI and of the challenge + public virtual void ApplyRedirect(DoYouBuzzApplyRedirectContext context) + { + OnApplyRedirect(context); + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/DoYouBuzz/Provider/DoYouBuzzReturnEndpointContext.cs b/Owin.Security.Providers/DoYouBuzz/Provider/DoYouBuzzReturnEndpointContext.cs new file mode 100644 index 0000000..0d8eb89 --- /dev/null +++ b/Owin.Security.Providers/DoYouBuzz/Provider/DoYouBuzzReturnEndpointContext.cs @@ -0,0 +1,22 @@ +using Microsoft.Owin; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Provider; + +namespace Owin.Security.Providers.DoYouBuzz +{ + /// + /// Provides context information to middleware providers. + /// + public class DoYouBuzzReturnEndpointContext : ReturnEndpointContext + { + /// + /// Initializes a new . + /// + /// OWIN environment + /// The authentication ticket + public DoYouBuzzReturnEndpointContext(IOwinContext context, AuthenticationTicket ticket) + : base(context, ticket) + { + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/DoYouBuzz/Provider/IDoYouBuzzAuthenticationProvider.cs b/Owin.Security.Providers/DoYouBuzz/Provider/IDoYouBuzzAuthenticationProvider.cs new file mode 100644 index 0000000..3a5299e --- /dev/null +++ b/Owin.Security.Providers/DoYouBuzz/Provider/IDoYouBuzzAuthenticationProvider.cs @@ -0,0 +1,31 @@ +using System.Threading.Tasks; +using Microsoft.Owin.Security; + +namespace Owin.Security.Providers.DoYouBuzz +{ + /// + /// Specifies callback methods which the invokes to enable developer control over the authentication process. /> + /// + public interface IDoYouBuzzAuthenticationProvider + { + /// + /// Invoked whenever DoYouBuzz successfully authenticates a user + /// + /// Contains information about the login session as well as the user . + /// A representing the completed operation. + Task Authenticated(DoYouBuzzAuthenticatedContext context); + + /// + /// Invoked prior to the being saved in a local cookie and the browser being redirected to the originally requested URL. + /// + /// + /// A representing the completed operation. + Task ReturnEndpoint(DoYouBuzzReturnEndpointContext context); + + /// + /// Called when a Challenge causes a redirect to authorize endpoint in the DoYouBuzz middleware + /// + /// Contains redirect URI and of the challenge + void ApplyRedirect(DoYouBuzzApplyRedirectContext context); + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/Owin.Security.Providers.csproj b/Owin.Security.Providers/Owin.Security.Providers.csproj index fbdad08..eb17962 100644 --- a/Owin.Security.Providers/Owin.Security.Providers.csproj +++ b/Owin.Security.Providers/Owin.Security.Providers.csproj @@ -113,6 +113,22 @@ + + + + + + + + + + + + + + + + diff --git a/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs b/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs index 5de4dbd..e3afa95 100755 --- a/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs +++ b/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs @@ -38,6 +38,7 @@ using Owin.Security.Providers.Wargaming; using Owin.Security.Providers.WordPress; using Owin.Security.Providers.Yahoo; using Owin.Security.Providers.Backlog; +using Owin.Security.Providers.DoYouBuzz; using Owin.Security.Providers.Vimeo; using Owin.Security.Providers.Fitbit; using Owin.Security.Providers.Onshape; @@ -314,6 +315,8 @@ namespace OwinOAuthProvidersDemo //app.UseVKontakteAuthentication("", ""); //app.UseXingAuthentication("", ""); + + //app.UseDoYouBuzzAuthentication("", ""); } } }