diff --git a/.gitignore b/.gitignore index b3aafc2..ee9cc72 100644 --- a/.gitignore +++ b/.gitignore @@ -70,6 +70,9 @@ ipch/ # Guidance Automation Toolkit *.gpState +# CodeRush is a .NET coding add-in +.cr/ + # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper diff --git a/OwinOAuthProviders.sln b/OwinOAuthProviders.sln index 9415453..804f7d7 100644 --- a/OwinOAuthProviders.sln +++ b/OwinOAuthProviders.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.25123.0 +VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.ArcGISOnline", "src\Owin.Security.Providers.ArcGISOnline\Owin.Security.Providers.ArcGISOnline.csproj", "{8A49FAEF-D365-4D25-942C-1CAD03845A5E}" EndProject @@ -100,6 +100,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Gen EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.MyHeritage", "src\Owin.Security.Providers.MyHeritage\Owin.Security.Providers.MyHeritage.csproj", "{84795078-31B5-4369-BD1B-F960165F8C71}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Box", "src\Owin.Security.Providers.Box\Owin.Security.Providers.Box.csproj", "{1AEF8813-E1F9-41E1-BC8D-732960595E9F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Baidu", "src\Owin.Security.Providers.Baidu\Owin.Security.Providers.Baidu.csproj", "{E2759807-4D7C-4288-AAC8-F5B7B4616680}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -302,6 +306,14 @@ Global {84795078-31B5-4369-BD1B-F960165F8C71}.Debug|Any CPU.Build.0 = Debug|Any CPU {84795078-31B5-4369-BD1B-F960165F8C71}.Release|Any CPU.ActiveCfg = Release|Any CPU {84795078-31B5-4369-BD1B-F960165F8C71}.Release|Any CPU.Build.0 = Release|Any CPU + {1AEF8813-E1F9-41E1-BC8D-732960595E9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1AEF8813-E1F9-41E1-BC8D-732960595E9F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1AEF8813-E1F9-41E1-BC8D-732960595E9F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1AEF8813-E1F9-41E1-BC8D-732960595E9F}.Release|Any CPU.Build.0 = Release|Any CPU + {E2759807-4D7C-4288-AAC8-F5B7B4616680}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E2759807-4D7C-4288-AAC8-F5B7B4616680}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E2759807-4D7C-4288-AAC8-F5B7B4616680}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E2759807-4D7C-4288-AAC8-F5B7B4616680}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs b/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs index 23ed743..f2d68ae 100755 --- a/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs +++ b/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs @@ -3,9 +3,6 @@ using Microsoft.Owin; using Microsoft.Owin.Security.Cookies; using Owin; -//using Owin.Security.Providers.Orcid; -//using Owin.Security.Providers.Discord; - namespace OwinOAuthProvidersDemo { public partial class Startup @@ -168,6 +165,14 @@ namespace OwinOAuthProvidersDemo // clientId: "", // clientSecret: ""); + //app.UseBoxAuthentication( + // appKey: "", + // appSecret: ""); + + //app.UseBaiduAuthentication( + // apiKey: "", + // secretKey: ""); + //app.UseBattleNetAuthentication(new BattleNetAuthenticationOptions //{ // ClientId = "", diff --git a/src/Owin.Security.Providers.Baidu/BaiduAuthenticationExtensions.cs b/src/Owin.Security.Providers.Baidu/BaiduAuthenticationExtensions.cs new file mode 100644 index 0000000..4283718 --- /dev/null +++ b/src/Owin.Security.Providers.Baidu/BaiduAuthenticationExtensions.cs @@ -0,0 +1,29 @@ +using System; + +namespace Owin.Security.Providers.Baidu +{ + public static class BaiduAuthenticationExtensions + { + public static IAppBuilder UseBaiduAuthentication(this IAppBuilder app, + BaiduAuthenticationOptions options) + { + if (app == null) + throw new ArgumentNullException(nameof(app)); + if (options == null) + throw new ArgumentNullException(nameof(options)); + + app.Use(typeof(BaiduAuthenticationMiddleware), app, options); + + return app; + } + + public static IAppBuilder UseBaiduAuthentication(this IAppBuilder app, string apiKey, string secretKey) + { + return app.UseBaiduAuthentication(new BaiduAuthenticationOptions + { + ApiKey = apiKey, + SecretKey = secretKey + }); + } + } +} \ No newline at end of file diff --git a/src/Owin.Security.Providers.Baidu/BaiduAuthenticationHandler.cs b/src/Owin.Security.Providers.Baidu/BaiduAuthenticationHandler.cs new file mode 100644 index 0000000..ffa57ba --- /dev/null +++ b/src/Owin.Security.Providers.Baidu/BaiduAuthenticationHandler.cs @@ -0,0 +1,236 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.Owin; +using Microsoft.Owin.Infrastructure; +using Microsoft.Owin.Logging; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Infrastructure; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Net.Http.Headers; + +namespace Owin.Security.Providers.Baidu +{ + public class BaiduAuthenticationHandler : AuthenticationHandler + { + private const string StateCookie = "_BaiduState"; + private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; + private const string TokenEndpoint = "https://openapi.baidu.com/oauth/2.0/token"; + private const string UserInfoEndpoint = "https://openapi.baidu.com/rest/2.0/passport/users/getInfo"; + private const string AuthEndpoint = "http://openapi.baidu.com/oauth/2.0/authorize"; + + private readonly ILogger _logger; + private readonly HttpClient _httpClient; + + public BaiduAuthenticationHandler(HttpClient httpClient, ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + } + + protected override async Task AuthenticateCoreAsync() + { + AuthenticationProperties properties = null; + + try + { + string code = null; + + var query = Request.Query; + var values = query.GetValues("code"); + if (values != null && values.Count == 1) + { + code = values[0]; + } + + var state = Request.Cookies[StateCookie]; + properties = Options.StateDataFormat.Unprotect(state); + if (properties == null) + { + return null; + } + + // OAuth2 10.12 CSRF + if (!ValidateCorrelationId(properties, _logger)) + { + return new AuthenticationTicket(null, properties); + } + + // Check for error + if (Request.Query.Get("error") != null) + return new AuthenticationTicket(null, properties); + + var requestPrefix = Request.Scheme + "://" + Request.Host; + var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + + // Build up the body for the token request + var body = new List> + { + new KeyValuePair("grant_type", "authorization_code"), + new KeyValuePair("code", code), + new KeyValuePair("redirect_uri", redirectUri), + new KeyValuePair("client_id", Options.ApiKey), + new KeyValuePair("client_secret", Options.SecretKey) + }; + + // Request the token + var tokenResponse = + await _httpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(body)); + tokenResponse.EnsureSuccessStatusCode(); + var text = await tokenResponse.Content.ReadAsStringAsync(); + + // Deserializes the token response + dynamic response = JsonConvert.DeserializeObject(text); + var accessToken = (string)response.access_token; + + // Get the Baidu user + // http://developer.baidu.com/wiki/index.php?title=docs/oauth/rest/file_data_apis_list#.E8.BF.94.E5.9B.9E.E7.94.A8.E6.88.B7.E8.AF.A6.E7.BB.86.E8.B5.84.E6.96.99 + var userResponse = await _httpClient.GetAsync( + UserInfoEndpoint + "?access_token=" + Uri.EscapeDataString(accessToken), Request.CallCancelled); + + userResponse.EnsureSuccessStatusCode(); + text = await userResponse.Content.ReadAsStringAsync(); + var user = JObject.Parse(text); + + var context = new BaiduAuthenticatedContext(Context, user, accessToken) + { + Identity = new ClaimsIdentity( + Options.AuthenticationType, + ClaimsIdentity.DefaultNameClaimType, + ClaimsIdentity.DefaultRoleClaimType) + }; + if (!string.IsNullOrEmpty(context.Userid)) + { + context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, context.Userid, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.UserName)) + { + context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.UserName, 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); + } + + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + + if (challenge == null) return Task.FromResult(null); + var baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + + var properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) + { + properties.RedirectUri = currentUri; + } + + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + // comma separated + var scope = string.Join(" ", Options.Scope); + + var state = Options.StateDataFormat.Protect(properties); + + var authorizationEndpoint = + AuthEndpoint + + "?response_type=code" + + "&client_id=" + Uri.EscapeDataString(Options.ApiKey) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + + "&scope=" + Uri.EscapeDataString(scope) + + "&state=" + Uri.EscapeDataString(state); + + var cookieOptions = new CookieOptions + { + HttpOnly = true, + Secure = Request.IsSecure + }; + + Response.StatusCode = 302; + Response.Cookies.Append(StateCookie, Options.StateDataFormat.Protect(properties), cookieOptions); + Response.Headers.Set("Location", 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) return false; + // TODO: error responses + + var ticket = await AuthenticateAsync(); + if (ticket == null) + { + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; + } + + var context = new BaiduReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; + } + } +} \ No newline at end of file diff --git a/src/Owin.Security.Providers.Baidu/BaiduAuthenticationMiddleware.cs b/src/Owin.Security.Providers.Baidu/BaiduAuthenticationMiddleware.cs new file mode 100644 index 0000000..cb1783e --- /dev/null +++ b/src/Owin.Security.Providers.Baidu/BaiduAuthenticationMiddleware.cs @@ -0,0 +1,82 @@ +using System; +using System.Globalization; +using System.Net.Http; +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; + +namespace Owin.Security.Providers.Baidu +{ + public class BaiduAuthenticationMiddleware : AuthenticationMiddleware + { + private readonly HttpClient _httpClient; + private readonly ILogger _logger; + + public BaiduAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, + BaiduAuthenticationOptions options) + : base(next, options) + { + if (string.IsNullOrWhiteSpace(Options.ApiKey)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, + Resources.Exception_OptionMustBeProvided, nameof(Options.ApiKey))); + if (string.IsNullOrWhiteSpace(Options.SecretKey)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, + Resources.Exception_OptionMustBeProvided, nameof(Options.SecretKey))); + + _logger = app.CreateLogger(); + + if (Options.Provider == null) + Options.Provider = new BaiduAuthenticationProvider(); + + if (Options.StateDataFormat == null) + { + var dataProtector = app.CreateDataProtector( + typeof (BaiduAuthenticationMiddleware).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 BaiduAuthenticationHandler(_httpClient, _logger); + } + + private static HttpMessageHandler ResolveHttpMessageHandler(BaiduAuthenticationOptions options) + { + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + + // If they provided a validator, apply it or fail. + if (options.BackchannelCertificateValidator == null) return handler; + // 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/src/Owin.Security.Providers.Baidu/BaiduAuthenticationOptions.cs b/src/Owin.Security.Providers.Baidu/BaiduAuthenticationOptions.cs new file mode 100644 index 0000000..d078b9e --- /dev/null +++ b/src/Owin.Security.Providers.Baidu/BaiduAuthenticationOptions.cs @@ -0,0 +1,102 @@ +using System; +using System.Net.Http; +using Microsoft.Owin; +using Microsoft.Owin.Security; +using System.Collections.Generic; + +namespace Owin.Security.Providers.Baidu +{ + public class BaiduAuthenticationOptions : AuthenticationOptions + { + /// + /// Gets or sets the a pinned certificate validator to use to validate the endpoints used + /// in back channel communications belong to Baidu + /// + /// + /// 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 Baidu. + /// 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 Baidu. + /// + /// + /// 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-dropbox". + /// + 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 Baidu supplied API Key + /// + public string ApiKey { get; set; } + + /// + /// Gets or sets the Baidu supplied Secret Key + /// + public string SecretKey { get; set; } + + /// + /// A list of permissions to request. + /// + public IList Scope { get; private set; } + + /// + /// Gets or sets the used in the authentication events + /// + public IBaiduAuthenticationProvider Provider { 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 BaiduAuthenticationOptions() + : base("Baidu") + { + Caption = Constants.DefaultAuthenticationType; + CallbackPath = new PathString("/signin-baidu"); + AuthenticationMode = AuthenticationMode.Passive; + Scope = new List + { + "basic" + }; + BackchannelTimeout = TimeSpan.FromSeconds(60); + } + } +} \ No newline at end of file diff --git a/src/Owin.Security.Providers.Baidu/Constants.cs b/src/Owin.Security.Providers.Baidu/Constants.cs new file mode 100644 index 0000000..ee851cd --- /dev/null +++ b/src/Owin.Security.Providers.Baidu/Constants.cs @@ -0,0 +1,7 @@ +namespace Owin.Security.Providers.Baidu +{ + internal static class Constants + { + public const string DefaultAuthenticationType = "Baidu"; + } +} \ No newline at end of file diff --git a/src/Owin.Security.Providers.Baidu/Owin.Security.Providers.Baidu.csproj b/src/Owin.Security.Providers.Baidu/Owin.Security.Providers.Baidu.csproj new file mode 100644 index 0000000..c5d0453 --- /dev/null +++ b/src/Owin.Security.Providers.Baidu/Owin.Security.Providers.Baidu.csproj @@ -0,0 +1,102 @@ + + + + + Debug + AnyCPU + {E2759807-4D7C-4288-AAC8-F5B7B4616680} + Library + Properties + Owin.Security.Providers.Baidu + Owin.Security.Providers.Baidu + v4.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Baidu/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.Baidu/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..d9e83e3 --- /dev/null +++ b/src/Owin.Security.Providers.Baidu/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.Baidu")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.Baidu")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("AD2018C1-FA82-4DC3-8CD4-BCC4D232A678")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/src/Owin.Security.Providers.Baidu/Provider/BaiduAuthenticatedContext.cs b/src/Owin.Security.Providers.Baidu/Provider/BaiduAuthenticatedContext.cs new file mode 100644 index 0000000..228459a --- /dev/null +++ b/src/Owin.Security.Providers.Baidu/Provider/BaiduAuthenticatedContext.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 System.Security.Claims; +using Microsoft.Owin; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Provider; +using Newtonsoft.Json.Linq; + +namespace Owin.Security.Providers.Baidu +{ + /// + /// Contains information about the login session as well as the user . + /// + public class BaiduAuthenticatedContext : BaseContext + { + /// + /// Initializes a + /// + /// The OWIN environment + /// The JSON-serialized user + /// Baidu Access token + public BaiduAuthenticatedContext(IOwinContext context, JObject user, string accessToken) + : base(context) + { + AccessToken = accessToken; + User = user; + + Userid = TryGetValue(user, "userid"); + UserName = TryGetValue(user, "username"); + RealName = TryGetValue(user, "realname"); + } + + /// + /// Gets the JSON-serialized user + /// + /// + /// Contains the Baidu user obtained from the endpoint https://api.dropbox.com/1/account/info + /// + public JObject User { get; private set; } + + /// + /// Gets the Baidu OAuth access token + /// + public string AccessToken { get; private set; } + + /// + /// Gets the Baidu user ID + /// + public string Userid { get; private set; } + + /// + /// The name of the user + /// + public string UserName { get; private set; } + + /// + /// The real name of the user + /// + public string RealName { 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; + } + } +} diff --git a/src/Owin.Security.Providers.Baidu/Provider/BaiduAuthenticationProvider.cs b/src/Owin.Security.Providers.Baidu/Provider/BaiduAuthenticationProvider.cs new file mode 100644 index 0000000..1bfe2de --- /dev/null +++ b/src/Owin.Security.Providers.Baidu/Provider/BaiduAuthenticationProvider.cs @@ -0,0 +1,50 @@ +using System; +using System.Threading.Tasks; + +namespace Owin.Security.Providers.Baidu +{ + /// + /// Default implementation. + /// + public class BaiduAuthenticationProvider : IBaiduAuthenticationProvider + { + /// + /// Initializes a + /// + public BaiduAuthenticationProvider() + { + 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 Baidu successfully authenticates a user + /// + /// Contains information about the login session as well as the user . + /// A representing the completed operation. + public virtual Task Authenticated(BaiduAuthenticatedContext 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(BaiduReturnEndpointContext context) + { + return OnReturnEndpoint(context); + } + } +} \ No newline at end of file diff --git a/src/Owin.Security.Providers.Baidu/Provider/BaiduReturnEndpointContext.cs b/src/Owin.Security.Providers.Baidu/Provider/BaiduReturnEndpointContext.cs new file mode 100644 index 0000000..aed1995 --- /dev/null +++ b/src/Owin.Security.Providers.Baidu/Provider/BaiduReturnEndpointContext.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.Baidu +{ + /// + /// Provides context information to middleware providers. + /// + public class BaiduReturnEndpointContext : ReturnEndpointContext + { + /// + /// + /// + /// OWIN environment + /// The authentication ticket + public BaiduReturnEndpointContext( + IOwinContext context, + AuthenticationTicket ticket) + : base(context, ticket) + { + } + } +} diff --git a/src/Owin.Security.Providers.Baidu/Provider/IBaiduAuthenticationProvider.cs b/src/Owin.Security.Providers.Baidu/Provider/IBaiduAuthenticationProvider.cs new file mode 100644 index 0000000..d0e8d62 --- /dev/null +++ b/src/Owin.Security.Providers.Baidu/Provider/IBaiduAuthenticationProvider.cs @@ -0,0 +1,24 @@ +using System.Threading.Tasks; + +namespace Owin.Security.Providers.Baidu +{ + /// + /// Specifies callback methods which the invokes to enable developer control over the authentication process. /> + /// + public interface IBaiduAuthenticationProvider + { + /// + /// Invoked whenever Baidu successfully authenticates a user + /// + /// Contains information about the login session as well as the user . + /// A representing the completed operation. + Task Authenticated(BaiduAuthenticatedContext 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(BaiduReturnEndpointContext context); + } +} \ No newline at end of file diff --git a/src/Owin.Security.Providers.Baidu/Resources.Designer.cs b/src/Owin.Security.Providers.Baidu/Resources.Designer.cs new file mode 100644 index 0000000..73eddda --- /dev/null +++ b/src/Owin.Security.Providers.Baidu/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.Baidu { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.Baidu.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.Baidu/Resources.resx b/src/Owin.Security.Providers.Baidu/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.Baidu/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Baidu/packages.config b/src/Owin.Security.Providers.Baidu/packages.config new file mode 100644 index 0000000..cbfe6a2 --- /dev/null +++ b/src/Owin.Security.Providers.Baidu/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Box/BoxAuthenticationExtensions.cs b/src/Owin.Security.Providers.Box/BoxAuthenticationExtensions.cs new file mode 100644 index 0000000..6a5e5ce --- /dev/null +++ b/src/Owin.Security.Providers.Box/BoxAuthenticationExtensions.cs @@ -0,0 +1,29 @@ +using System; + +namespace Owin.Security.Providers.Box +{ + public static class BoxAuthenticationExtensions + { + public static IAppBuilder UseBoxAuthentication(this IAppBuilder app, + BoxAuthenticationOptions options) + { + if (app == null) + throw new ArgumentNullException(nameof(app)); + if (options == null) + throw new ArgumentNullException(nameof(options)); + + app.Use(typeof(BoxAuthenticationMiddleware), app, options); + + return app; + } + + public static IAppBuilder UseBoxAuthentication(this IAppBuilder app, string appKey, string appSecret) + { + return app.UseBoxAuthentication(new BoxAuthenticationOptions + { + ClientId = appKey, + ClientSecret = appSecret + }); + } + } +} \ No newline at end of file diff --git a/src/Owin.Security.Providers.Box/BoxAuthenticationHandler.cs b/src/Owin.Security.Providers.Box/BoxAuthenticationHandler.cs new file mode 100644 index 0000000..7049a73 --- /dev/null +++ b/src/Owin.Security.Providers.Box/BoxAuthenticationHandler.cs @@ -0,0 +1,230 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.Owin; +using Microsoft.Owin.Infrastructure; +using Microsoft.Owin.Logging; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Infrastructure; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Net.Http.Headers; + +namespace Owin.Security.Providers.Box +{ + public class BoxAuthenticationHandler : AuthenticationHandler + { + private const string StateCookie = "_BoxState"; + private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; + private const string TokenEndpoint = "https://api.box.com/oauth2/token"; + private const string UserInfoEndpoint = "https://api.box.com/2.0/users/me"; + + private readonly ILogger _logger; + private readonly HttpClient _httpClient; + + public BoxAuthenticationHandler(HttpClient httpClient, ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + } + + protected override async Task AuthenticateCoreAsync() + { + AuthenticationProperties properties = null; + + try + { + string code = null; + + var query = Request.Query; + var values = query.GetValues("code"); + if (values != null && values.Count == 1) + { + code = values[0]; + } + + var state = Request.Cookies[StateCookie]; + properties = Options.StateDataFormat.Unprotect(state); + if (properties == null) + { + return null; + } + + // OAuth2 10.12 CSRF + if (!ValidateCorrelationId(properties, _logger)) + { + return new AuthenticationTicket(null, properties); + } + + // Check for error + if (Request.Query.Get("error") != null) + return new AuthenticationTicket(null, properties); + + var requestPrefix = Request.Scheme + "://" + Request.Host; + var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + + // Build up the body for the token request + var body = new List> + { + new KeyValuePair("grant_type", "authorization_code"), + new KeyValuePair("code", code), + new KeyValuePair("redirect_uri", redirectUri), + new KeyValuePair("client_id", Options.ClientId), + new KeyValuePair("client_secret", Options.ClientSecret) + }; + + // Request the token + var tokenResponse = + await _httpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(body)); + tokenResponse.EnsureSuccessStatusCode(); + var text = await tokenResponse.Content.ReadAsStringAsync(); + + // Deserializes the token response + dynamic response = JsonConvert.DeserializeObject(text); + var accessToken = (string)response.access_token; + + // Get the Box user + // https://docs.box.com/reference#get-the-current-users-information + var userRequest = new HttpRequestMessage(HttpMethod.Get, UserInfoEndpoint); + userRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + userRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + var userResponse = await _httpClient.SendAsync(userRequest, Request.CallCancelled); + userResponse.EnsureSuccessStatusCode(); + text = await userResponse.Content.ReadAsStringAsync(); + var user = JObject.Parse(text); + + var context = new BoxAuthenticatedContext(Context, user, accessToken) + { + 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); + } + + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + + if (challenge == null) return Task.FromResult(null); + var baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + + var properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) + { + properties.RedirectUri = currentUri; + } + + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + var authorizationEndpoint = + "https://api.box.com/oauth2/authorize" + + "?response_type=code" + + "&client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&state=authenticated" + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri); + + var cookieOptions = new CookieOptions + { + HttpOnly = true, + Secure = Request.IsSecure + }; + + Response.StatusCode = 302; + Response.Cookies.Append(StateCookie, Options.StateDataFormat.Protect(properties), cookieOptions); + Response.Headers.Set("Location", 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) return false; + // TODO: error responses + + var ticket = await AuthenticateAsync(); + if (ticket == null) + { + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; + } + + var context = new BoxReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; + } + } +} \ No newline at end of file diff --git a/src/Owin.Security.Providers.Box/BoxAuthenticationMiddleware.cs b/src/Owin.Security.Providers.Box/BoxAuthenticationMiddleware.cs new file mode 100644 index 0000000..ab100ec --- /dev/null +++ b/src/Owin.Security.Providers.Box/BoxAuthenticationMiddleware.cs @@ -0,0 +1,82 @@ +using System; +using System.Globalization; +using System.Net.Http; +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; + +namespace Owin.Security.Providers.Box +{ + public class BoxAuthenticationMiddleware : AuthenticationMiddleware + { + private readonly HttpClient _httpClient; + private readonly ILogger _logger; + + public BoxAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, + BoxAuthenticationOptions options) + : base(next, options) + { + if (string.IsNullOrWhiteSpace(Options.ClientId)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, + Resources.Exception_OptionMustBeProvided, nameof(Options.ClientId))); + if (string.IsNullOrWhiteSpace(Options.ClientSecret)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, + Resources.Exception_OptionMustBeProvided, nameof(Options.ClientSecret))); + + _logger = app.CreateLogger(); + + if (Options.Provider == null) + Options.Provider = new BoxAuthenticationProvider(); + + if (Options.StateDataFormat == null) + { + var dataProtector = app.CreateDataProtector( + typeof (BoxAuthenticationMiddleware).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 BoxAuthenticationHandler(_httpClient, _logger); + } + + private static HttpMessageHandler ResolveHttpMessageHandler(BoxAuthenticationOptions options) + { + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + + // If they provided a validator, apply it or fail. + if (options.BackchannelCertificateValidator == null) return handler; + // 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/src/Owin.Security.Providers.Box/BoxAuthenticationOptions.cs b/src/Owin.Security.Providers.Box/BoxAuthenticationOptions.cs new file mode 100644 index 0000000..f03782e --- /dev/null +++ b/src/Owin.Security.Providers.Box/BoxAuthenticationOptions.cs @@ -0,0 +1,92 @@ +using System; +using System.Net.Http; +using Microsoft.Owin; +using Microsoft.Owin.Security; + +namespace Owin.Security.Providers.Box +{ + public class BoxAuthenticationOptions : AuthenticationOptions + { + /// + /// Gets or sets the a pinned certificate validator to use to validate the endpoints used + /// in back channel communications belong to Box + /// + /// + /// 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 Box. + /// 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 Box. + /// + /// + /// 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-box". + /// + 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 Box supplied Client ID + /// + public string ClientId { get; set; } + + /// + /// Gets or sets the Box supplied Client Secret + /// + public string ClientSecret { get; set; } + + /// + /// Gets or sets the used in the authentication events + /// + public IBoxAuthenticationProvider Provider { 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 BoxAuthenticationOptions() + : base("Box") + { + Caption = Constants.DefaultAuthenticationType; + CallbackPath = new PathString("/signin-box"); + AuthenticationMode = AuthenticationMode.Passive; + BackchannelTimeout = TimeSpan.FromSeconds(60); + } + } +} \ No newline at end of file diff --git a/src/Owin.Security.Providers.Box/Constants.cs b/src/Owin.Security.Providers.Box/Constants.cs new file mode 100644 index 0000000..10c5d20 --- /dev/null +++ b/src/Owin.Security.Providers.Box/Constants.cs @@ -0,0 +1,7 @@ +namespace Owin.Security.Providers.Box +{ + internal static class Constants + { + public const string DefaultAuthenticationType = "Box"; + } +} \ No newline at end of file diff --git a/src/Owin.Security.Providers.Box/Owin.Security.Providers.Box.csproj b/src/Owin.Security.Providers.Box/Owin.Security.Providers.Box.csproj new file mode 100644 index 0000000..d8f1c13 --- /dev/null +++ b/src/Owin.Security.Providers.Box/Owin.Security.Providers.Box.csproj @@ -0,0 +1,102 @@ + + + + + Debug + AnyCPU + {1AEF8813-E1F9-41E1-BC8D-732960595E9F} + Library + Properties + Owin.Security.Providers.Box + Owin.Security.Providers.Box + v4.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Box/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.Box/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..8bf3c0e --- /dev/null +++ b/src/Owin.Security.Providers.Box/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.Box")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.Box")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("1242F9A5-AEF8-44F8-A255-F0D24DAE0A2C")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/src/Owin.Security.Providers.Box/Provider/BoxAuthenticatedContext.cs b/src/Owin.Security.Providers.Box/Provider/BoxAuthenticatedContext.cs new file mode 100644 index 0000000..cb6afe5 --- /dev/null +++ b/src/Owin.Security.Providers.Box/Provider/BoxAuthenticatedContext.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 System.Security.Claims; +using Microsoft.Owin; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Provider; +using Newtonsoft.Json.Linq; + +namespace Owin.Security.Providers.Box +{ + /// + /// Contains information about the login session as well as the user . + /// + public class BoxAuthenticatedContext : BaseContext + { + /// + /// Initializes a + /// + /// The OWIN environment + /// The JSON-serialized user + /// Box Access token + public BoxAuthenticatedContext(IOwinContext context, JObject user, string accessToken) + : base(context) + { + AccessToken = accessToken; + User = user; + + Id = TryGetValue(user, "id"); + Name = TryGetValue(user, "name"); + Login = TryGetValue(user, "login"); + } + + /// + /// Gets the JSON-serialized user + /// + /// + /// Contains the Box user obtained from the endpoint https://api.box.com/2.0/users/me + /// + public JObject User { get; private set; } + + /// + /// Gets the Box OAuth access token + /// + public string AccessToken { get; private set; } + + /// + /// Gets the Box user ID + /// + public string Id { get; private set; } + + /// + /// The name of the user + /// + public string Name { get; private set; } + + /// + /// The login of the user + /// + public string Login { 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; + } + } +} diff --git a/src/Owin.Security.Providers.Box/Provider/BoxAuthenticationProvider.cs b/src/Owin.Security.Providers.Box/Provider/BoxAuthenticationProvider.cs new file mode 100644 index 0000000..6104588 --- /dev/null +++ b/src/Owin.Security.Providers.Box/Provider/BoxAuthenticationProvider.cs @@ -0,0 +1,50 @@ +using System; +using System.Threading.Tasks; + +namespace Owin.Security.Providers.Box +{ + /// + /// Default implementation. + /// + public class BoxAuthenticationProvider : IBoxAuthenticationProvider + { + /// + /// Initializes a + /// + public BoxAuthenticationProvider() + { + 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 Box successfully authenticates a user + /// + /// Contains information about the login session as well as the user . + /// A representing the completed operation. + public virtual Task Authenticated(BoxAuthenticatedContext 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(BoxReturnEndpointContext context) + { + return OnReturnEndpoint(context); + } + } +} \ No newline at end of file diff --git a/src/Owin.Security.Providers.Box/Provider/BoxReturnEndpointContext.cs b/src/Owin.Security.Providers.Box/Provider/BoxReturnEndpointContext.cs new file mode 100644 index 0000000..682cb1d --- /dev/null +++ b/src/Owin.Security.Providers.Box/Provider/BoxReturnEndpointContext.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.Box +{ + /// + /// Provides context information to middleware providers. + /// + public class BoxReturnEndpointContext : ReturnEndpointContext + { + /// + /// + /// + /// OWIN environment + /// The authentication ticket + public BoxReturnEndpointContext( + IOwinContext context, + AuthenticationTicket ticket) + : base(context, ticket) + { + } + } +} diff --git a/src/Owin.Security.Providers.Box/Provider/IBoxAuthenticationProvider.cs b/src/Owin.Security.Providers.Box/Provider/IBoxAuthenticationProvider.cs new file mode 100644 index 0000000..0127d08 --- /dev/null +++ b/src/Owin.Security.Providers.Box/Provider/IBoxAuthenticationProvider.cs @@ -0,0 +1,24 @@ +using System.Threading.Tasks; + +namespace Owin.Security.Providers.Box +{ + /// + /// Specifies callback methods which the invokes to enable developer control over the authentication process. /> + /// + public interface IBoxAuthenticationProvider + { + /// + /// Invoked whenever Box successfully authenticates a user + /// + /// Contains information about the login session as well as the user . + /// A representing the completed operation. + Task Authenticated(BoxAuthenticatedContext 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(BoxReturnEndpointContext context); + } +} \ No newline at end of file diff --git a/src/Owin.Security.Providers.Box/Resources.Designer.cs b/src/Owin.Security.Providers.Box/Resources.Designer.cs new file mode 100644 index 0000000..b80e39c --- /dev/null +++ b/src/Owin.Security.Providers.Box/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.Box { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.Box.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.Box/Resources.resx b/src/Owin.Security.Providers.Box/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.Box/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Box/packages.config b/src/Owin.Security.Providers.Box/packages.config new file mode 100644 index 0000000..cbfe6a2 --- /dev/null +++ b/src/Owin.Security.Providers.Box/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file