diff --git a/Owin.Security.Providers/Owin.Security.Providers.csproj b/Owin.Security.Providers/Owin.Security.Providers.csproj
index f3f4ec6..892c561 100644
--- a/Owin.Security.Providers/Owin.Security.Providers.csproj
+++ b/Owin.Security.Providers/Owin.Security.Providers.csproj
@@ -336,6 +336,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/Owin.Security.Providers/Vimeo/Constants.cs b/Owin.Security.Providers/Vimeo/Constants.cs
new file mode 100644
index 0000000..5a49741
--- /dev/null
+++ b/Owin.Security.Providers/Vimeo/Constants.cs
@@ -0,0 +1,8 @@
+
+namespace Owin.Security.Providers.Vimeo
+{
+ internal static class Constants
+ {
+ public const string DefaultAuthenticationType = "Vimeo";
+ }
+}
diff --git a/Owin.Security.Providers/Vimeo/Provider/IVimeoAuthenticationProvider.cs b/Owin.Security.Providers/Vimeo/Provider/IVimeoAuthenticationProvider.cs
new file mode 100644
index 0000000..a361ac8
--- /dev/null
+++ b/Owin.Security.Providers/Vimeo/Provider/IVimeoAuthenticationProvider.cs
@@ -0,0 +1,24 @@
+using System.Threading.Tasks;
+
+namespace Owin.Security.Providers.Vimeo
+{
+ ///
+ /// Specifies callback methods which the invokes to enable developer control over the authentication process. />
+ ///
+ public interface IVimeoAuthenticationProvider
+ {
+ ///
+ /// Invoked whenever Vimeo succesfully authenticates a user
+ ///
+ /// Contains information about the login session as well as the user .
+ /// A representing the completed operation.
+ Task Authenticated(VimeoAuthenticatedContext 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(VimeoReturnEndpointContext context);
+ }
+}
\ No newline at end of file
diff --git a/Owin.Security.Providers/Vimeo/Provider/VimeoAuthenticatedContext.cs b/Owin.Security.Providers/Vimeo/Provider/VimeoAuthenticatedContext.cs
new file mode 100644
index 0000000..45d7550
--- /dev/null
+++ b/Owin.Security.Providers/Vimeo/Provider/VimeoAuthenticatedContext.cs
@@ -0,0 +1,77 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
+
+using Microsoft.Owin;
+using Microsoft.Owin.Security;
+using Microsoft.Owin.Security.Provider;
+using Newtonsoft.Json.Linq;
+using System.Security.Claims;
+
+namespace Owin.Security.Providers.Vimeo
+{
+ ///
+ /// Contains information about the login session as well as the user .
+ ///
+ public class VimeoAuthenticatedContext : BaseContext
+ {
+ ///
+ /// Initializes a
+ ///
+ /// The OWIN environment
+ /// The JSON-serialized user
+ /// Vimeo Access token
+ public VimeoAuthenticatedContext(IOwinContext context, JObject user, string accessToken)
+ : base(context)
+ {
+ User = user;
+ AccessToken = accessToken;
+
+ Name = TryGetValue(user, "name");
+
+ var uri = TryGetValue(user, "uri");
+ if (!string.IsNullOrEmpty(uri))
+ {
+ Id = uri.Substring(uri.LastIndexOf("/") + 1); // parse format /users/123456
+ }
+ }
+
+ ///
+ /// Gets the JSON-serialized user
+ ///
+ ///
+ /// Contains the Vimeo user included in the Authentication response
+ /// https://developer.vimeo.com/api/endpoints/me
+ ///
+ public JObject User { get; private set; }
+
+ ///
+ /// Gets the Vimeo OAuth access token
+ ///
+ public string AccessToken { get; private set; }
+
+ ///
+ /// Gets the Vimeo user ID
+ ///
+ public string Id { get; private set; }
+
+ ///
+ /// Get the name of the user
+ ///
+ public string Name { 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; }
+
+ private static string TryGetValue(JObject user, string propertyName)
+ {
+ JToken value;
+ return user.TryGetValue(propertyName, out value) ? value.ToString() : null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Owin.Security.Providers/Vimeo/Provider/VimeoAuthenticationProvider.cs b/Owin.Security.Providers/Vimeo/Provider/VimeoAuthenticationProvider.cs
new file mode 100644
index 0000000..a373ae3
--- /dev/null
+++ b/Owin.Security.Providers/Vimeo/Provider/VimeoAuthenticationProvider.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Owin.Security.Providers.Vimeo
+{
+ ///
+ /// Default implementation.
+ ///
+ public class VimeoAuthenticationProvider : IVimeoAuthenticationProvider
+ {
+ ///
+ /// Initializes a
+ ///
+ public VimeoAuthenticationProvider()
+ {
+ OnAuthenticated = context => Task.FromResult(null);
+ OnReturnEndpoint = context => Task.FromResult(null);
+ }
+
+ ///
+ /// 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; }
+
+ ///
+ /// Invoked whenever Vimeo succesfully authenticates a user
+ ///
+ /// Contains information about the login session as well as the user .
+ /// A representing the completed operation.
+ public virtual Task Authenticated(VimeoAuthenticatedContext 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(VimeoReturnEndpointContext context)
+ {
+ return OnReturnEndpoint(context);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Owin.Security.Providers/Vimeo/Provider/VimeoReturnEndpointContext.cs b/Owin.Security.Providers/Vimeo/Provider/VimeoReturnEndpointContext.cs
new file mode 100644
index 0000000..637c818
--- /dev/null
+++ b/Owin.Security.Providers/Vimeo/Provider/VimeoReturnEndpointContext.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
+
+using Microsoft.Owin;
+using Microsoft.Owin.Security;
+using Microsoft.Owin.Security.Provider;
+
+namespace Owin.Security.Providers.Vimeo
+{
+ ///
+ /// Provides context information to middleware providers.
+ ///
+ public class VimeoReturnEndpointContext : ReturnEndpointContext
+ {
+ ///
+ /// Initializes a
+ ///
+ /// OWIN environment
+ /// The authentication ticket
+ public VimeoReturnEndpointContext(
+ IOwinContext context,
+ AuthenticationTicket ticket)
+ : base(context, ticket)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/Owin.Security.Providers/Vimeo/VimeoAuthenticationExtensions.cs b/Owin.Security.Providers/Vimeo/VimeoAuthenticationExtensions.cs
new file mode 100644
index 0000000..3bb7a9e
--- /dev/null
+++ b/Owin.Security.Providers/Vimeo/VimeoAuthenticationExtensions.cs
@@ -0,0 +1,33 @@
+using System;
+
+namespace Owin.Security.Providers.Vimeo
+{
+ public static class VimeoAuthenticationExtensions
+ {
+ public static IAppBuilder UseVimeoAuthentication(this IAppBuilder app, VimeoAuthenticationOptions options)
+ {
+ if (app == null)
+ throw new ArgumentNullException("app");
+ if (options == null)
+ throw new ArgumentNullException("options");
+
+ app.Use(typeof(VimeoAuthenticationMiddleware), app, options);
+
+ return app;
+ }
+
+ public static IAppBuilder UseVimeoAuthentication(this IAppBuilder app, string clientId, string clientSecret)
+ {
+ if (string.IsNullOrEmpty(clientId))
+ throw new ArgumentNullException("clientId");
+ if (string.IsNullOrEmpty(clientSecret))
+ throw new ArgumentNullException("clientSecret");
+
+ return app.UseVimeoAuthentication(new VimeoAuthenticationOptions
+ {
+ ClientId = clientId,
+ ClientSecret = clientSecret
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/Owin.Security.Providers/Vimeo/VimeoAuthenticationHandler.cs b/Owin.Security.Providers/Vimeo/VimeoAuthenticationHandler.cs
new file mode 100644
index 0000000..11dd159
--- /dev/null
+++ b/Owin.Security.Providers/Vimeo/VimeoAuthenticationHandler.cs
@@ -0,0 +1,220 @@
+using Microsoft.Owin;
+using Microsoft.Owin.Infrastructure;
+using Microsoft.Owin.Logging;
+using Microsoft.Owin.Security;
+using Microsoft.Owin.Security.Infrastructure;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Security.Claims;
+using System.Threading.Tasks;
+
+namespace Owin.Security.Providers.Vimeo
+{
+ public class VimeoAuthenticationHandler : AuthenticationHandler
+ {
+ private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string";
+ private const string TokenEndpoint = "https://api.vimeo.com/oauth/access_token";
+ private const string AuthorizationEndpoint = "https://api.vimeo.com/oauth/authorize";
+
+ private readonly ILogger logger;
+ private readonly HttpClient httpClient;
+
+ public VimeoAuthenticationHandler(HttpClient httpClient, ILogger logger)
+ {
+ this.httpClient = httpClient;
+ this.logger = logger;
+ }
+
+ protected override async Task AuthenticateCoreAsync()
+ {
+ AuthenticationProperties properties = null;
+
+ try
+ {
+ string code = null;
+ string state = null;
+
+ IReadableStringCollection query = Request.Query;
+ IList values = query.GetValues("code");
+ if (values != null && values.Count == 1)
+ {
+ code = values[0];
+ }
+ values = query.GetValues("state");
+ if (values != null && values.Count == 1)
+ {
+ state = values[0];
+ }
+
+ properties = Options.StateDataFormat.Unprotect(state);
+ if (properties == null)
+ {
+ return null;
+ }
+
+ // OAuth2 10.12 CSRF
+ if (!ValidateCorrelationId(properties, logger))
+ {
+ return new AuthenticationTicket(null, properties);
+ }
+
+ string requestPrefix = Request.Scheme + "://" + Request.Host;
+ string redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath;
+
+ // Build up the body for the token request
+ var body = new List>();
+ body.Add(new KeyValuePair("grant_type", "authorization_code"));
+ body.Add(new KeyValuePair("code", code));
+ body.Add(new KeyValuePair("redirect_uri", redirectUri));
+ body.Add(new KeyValuePair("client_id", Options.ClientId));
+ body.Add(new KeyValuePair("client_secret", Options.ClientSecret));
+
+ // Request the token
+ HttpResponseMessage tokenResponse =
+ await httpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(body));
+ tokenResponse.EnsureSuccessStatusCode();
+ string text = await tokenResponse.Content.ReadAsStringAsync();
+
+ // Deserializes the token response
+ dynamic response = JsonConvert.DeserializeObject(text);
+ string accessToken = (string)response.access_token;
+
+ // Vimeo includes the user information in the response
+ var context = new VimeoAuthenticatedContext(Context, response.user, accessToken);
+ context.Identity = new ClaimsIdentity(
+ Options.AuthenticationType,
+ ClaimsIdentity.DefaultNameClaimType,
+ ClaimsIdentity.DefaultRoleClaimType);
+
+ if (!string.IsNullOrEmpty(context.Id))
+ {
+ context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, context.Id, XmlSchemaString, Options.AuthenticationType));
+ }
+
+ if (!string.IsNullOrEmpty(context.Name))
+ {
+ context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Name, XmlSchemaString, Options.AuthenticationType));
+ }
+
+ context.Properties = properties;
+
+ await Options.Provider.Authenticated(context);
+
+ return new AuthenticationTicket(context.Identity, context.Properties);
+ }
+ catch (Exception ex)
+ {
+ logger.WriteError(ex.Message);
+ }
+ return new AuthenticationTicket(null, properties);
+ }
+
+ protected override Task ApplyResponseChallengeAsync()
+ {
+ if (Response.StatusCode != 401)
+ {
+ return Task.FromResult(null);
+ }
+
+ AuthenticationResponseChallenge challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode);
+
+ if (challenge != null)
+ {
+ string baseUri =
+ Request.Scheme +
+ Uri.SchemeDelimiter +
+ Request.Host +
+ Request.PathBase;
+
+ string currentUri =
+ baseUri +
+ Request.Path +
+ Request.QueryString;
+
+ string redirectUri =
+ baseUri +
+ Options.CallbackPath;
+
+ AuthenticationProperties properties = challenge.Properties;
+ if (string.IsNullOrEmpty(properties.RedirectUri))
+ {
+ properties.RedirectUri = currentUri;
+ }
+
+ // OAuth2 10.12 CSRF
+ GenerateCorrelationId(properties);
+
+ string state = Options.StateDataFormat.Protect(properties);
+
+ string authorizationEndpoint =
+ AuthorizationEndpoint +
+ "?response_type=code" +
+ "&client_id=" + Uri.EscapeDataString(Options.ClientId) +
+ "&redirect_uri=" + Uri.EscapeDataString(redirectUri) +
+ "&state=" + Uri.EscapeDataString(state);
+
+ if (!string.IsNullOrWhiteSpace(Options.Scope))
+ {
+ authorizationEndpoint += "&scope=" + Uri.EscapeDataString(Options.Scope);
+ }
+
+ Response.Redirect(authorizationEndpoint);
+ }
+
+ return Task.FromResult(null);
+ }
+
+ public override async Task InvokeAsync()
+ {
+ return await InvokeReplyPathAsync();
+ }
+
+ private async Task InvokeReplyPathAsync()
+ {
+ if (Options.CallbackPath.HasValue && Options.CallbackPath == Request.Path)
+ {
+ AuthenticationTicket ticket = await AuthenticateAsync();
+ if (ticket == null)
+ {
+ logger.WriteWarning("Invalid return state, unable to redirect.");
+ Response.StatusCode = 500;
+ return true;
+ }
+
+ var context = new VimeoReturnEndpointContext(Context, ticket);
+ context.SignInAsAuthenticationType = Options.SignInAsAuthenticationType;
+ context.RedirectUri = ticket.Properties.RedirectUri;
+
+ await Options.Provider.ReturnEndpoint(context);
+
+ if (context.SignInAsAuthenticationType != null &&
+ context.Identity != null)
+ {
+ ClaimsIdentity grantIdentity = context.Identity;
+ if (!string.Equals(grantIdentity.AuthenticationType, context.SignInAsAuthenticationType, StringComparison.Ordinal))
+ {
+ grantIdentity = new ClaimsIdentity(grantIdentity.Claims, context.SignInAsAuthenticationType, grantIdentity.NameClaimType, grantIdentity.RoleClaimType);
+ }
+ Context.Authentication.SignIn(context.Properties, grantIdentity);
+ }
+
+ if (!context.IsRequestCompleted && context.RedirectUri != null)
+ {
+ string redirectUri = context.RedirectUri;
+ if (context.Identity == null)
+ {
+ // add a redirect hint that sign-in failed in some way
+ redirectUri = WebUtilities.AddQueryString(redirectUri, "error", "access_denied");
+ }
+ Response.Redirect(redirectUri);
+ context.RequestCompleted();
+ }
+
+ return context.IsRequestCompleted;
+ }
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Owin.Security.Providers/Vimeo/VimeoAuthenticationMiddleware.cs b/Owin.Security.Providers/Vimeo/VimeoAuthenticationMiddleware.cs
new file mode 100644
index 0000000..2a6228e
--- /dev/null
+++ b/Owin.Security.Providers/Vimeo/VimeoAuthenticationMiddleware.cs
@@ -0,0 +1,84 @@
+using Microsoft.Owin;
+using Microsoft.Owin.Logging;
+using Microsoft.Owin.Security;
+using Microsoft.Owin.Security.DataHandler;
+using Microsoft.Owin.Security.DataProtection;
+using Microsoft.Owin.Security.Infrastructure;
+using Owin.Security.Providers.Properties;
+using System;
+using System.Globalization;
+using System.Net.Http;
+
+namespace Owin.Security.Providers.Vimeo
+{
+ public class VimeoAuthenticationMiddleware : AuthenticationMiddleware
+ {
+ private readonly HttpClient httpClient;
+ private readonly ILogger logger;
+
+ public VimeoAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, VimeoAuthenticationOptions options)
+ : base(next, options)
+ {
+ if (String.IsNullOrWhiteSpace(Options.ClientId))
+ throw new ArgumentException(String.Format(CultureInfo.CurrentCulture,
+ Resources.Exception_OptionMustBeProvided, "ClientId"));
+ if (String.IsNullOrWhiteSpace(Options.ClientSecret))
+ throw new ArgumentException(String.Format(CultureInfo.CurrentCulture,
+ Resources.Exception_OptionMustBeProvided, "ClientSecret"));
+
+ logger = app.CreateLogger();
+
+ if (Options.Provider == null)
+ Options.Provider = new VimeoAuthenticationProvider();
+
+ if (Options.StateDataFormat == null)
+ {
+ IDataProtector dataProtector = app.CreateDataProtector(
+ typeof(VimeoAuthenticationMiddleware).FullName,
+ Options.AuthenticationType, "v1");
+ Options.StateDataFormat = new PropertiesDataFormat(dataProtector);
+ }
+
+ if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType))
+ Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType();
+
+ httpClient = new HttpClient(ResolveHttpMessageHandler(Options))
+ {
+ Timeout = Options.BackchannelTimeout,
+ MaxResponseContentBufferSize = 1024 * 1024 * 10
+ };
+ }
+
+ ///
+ /// Provides the object for processing
+ /// authentication-related requests.
+ ///
+ ///
+ /// An configured with the
+ /// supplied to the constructor.
+ ///
+ protected override AuthenticationHandler CreateHandler()
+ {
+ return new VimeoAuthenticationHandler(httpClient, logger);
+ }
+
+ private HttpMessageHandler ResolveHttpMessageHandler(VimeoAuthenticationOptions options)
+ {
+ HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler();
+
+ // If they provided a validator, apply it or fail.
+ if (options.BackchannelCertificateValidator != null)
+ {
+ // Set the cert validate callback
+ var webRequestHandler = handler as WebRequestHandler;
+ if (webRequestHandler == null)
+ {
+ throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch);
+ }
+ webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
+ }
+
+ return handler;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Owin.Security.Providers/Vimeo/VimeoAuthenticationOptions.cs b/Owin.Security.Providers/Vimeo/VimeoAuthenticationOptions.cs
new file mode 100644
index 0000000..c6a6720
--- /dev/null
+++ b/Owin.Security.Providers/Vimeo/VimeoAuthenticationOptions.cs
@@ -0,0 +1,97 @@
+using Microsoft.Owin;
+using Microsoft.Owin.Security;
+using System;
+using System.Net.Http;
+
+namespace Owin.Security.Providers.Vimeo
+{
+ public class VimeoAuthenticationOptions : AuthenticationOptions
+ {
+ ///
+ /// Gets or sets the a pinned certificate validator to use to validate the endpoints used
+ /// in back channel communications belong to Vimeo
+ ///
+ ///
+ /// 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 Vimeo.
+ /// This cannot be set at the same time as BackchannelCertificateValidator unless the value
+ /// can be downcast to a WebRequestHandler.
+ ///
+ public HttpMessageHandler BackchannelHttpHandler { get; set; }
+
+ ///
+ /// Gets or sets timeout value in milliseconds for back channel communications with Vimeo.
+ ///
+ ///
+ /// The back channel timeout in milliseconds.
+ ///
+ public TimeSpan BackchannelTimeout { get; set; }
+
+ ///
+ /// 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-vimeo".
+ ///
+ public PathString CallbackPath { 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; }
+ }
+
+ ///
+ /// Gets or sets the Vimeo supplied Client ID
+ ///
+ public string ClientId { get; set; }
+
+ ///
+ /// Gets or sets the Vimeo supplied Client Secret
+ ///
+ public string ClientSecret { get; set; }
+
+ ///
+ /// Gets or sets the used in the authentication events
+ ///
+ public IVimeoAuthenticationProvider Provider { get; set; }
+
+ ///
+ /// A list of permissions to request as per https://developer.vimeo.com/api/authentication
+ ///
+ public string Scope { 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; }
+
+ ///
+ /// Initializes a new
+ ///
+ public VimeoAuthenticationOptions()
+ : base("Vimeo")
+ {
+ Caption = Constants.DefaultAuthenticationType;
+ CallbackPath = new PathString("/signin-vimeo");
+ AuthenticationMode = AuthenticationMode.Passive;
+ BackchannelTimeout = TimeSpan.FromSeconds(60);
+ }
+ }
+}
\ No newline at end of file
diff --git a/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs b/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs
index 5d0b905..087d8a5 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.Vimeo;
namespace OwinOAuthProvidersDemo
{
@@ -284,6 +285,8 @@ namespace OwinOAuthProvidersDemo
// ClientServer = "cosignservername"
//};
//app.UseCosignAuthentication(cosignOptions);
+
+ //app.UseVimeoAuthentication("", "");
}
}
}