Merge remote-tracking branch 'upstream/master'

Conflicts:
	OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs
This commit is contained in:
Jason Loeffler
2015-08-24 12:28:53 -05:00
27 changed files with 1504 additions and 36 deletions

View File

@@ -2,7 +2,7 @@
<package >
<metadata>
<id>Owin.Security.Providers</id>
<version>1.22.1</version>
<version>1.23</version>
<authors>Jerrie Pelser and contributors</authors>
<owners>Jerrie Pelser</owners>
<licenseUrl>http://opensource.org/licenses/MIT</licenseUrl>
@@ -19,16 +19,11 @@
Also adds generic OpenID 2.0 providers as well implementations for Steam and Wargaming.
</summary>
<releaseNotes>
Version 1.22
Enhancement
- Ability to specify prompt parameter for Salesforce provider
Version 1.22
Version 1.23
Added
- Added Imgur
- Added Backlog
- Added Shopify
- Added Cosign
</releaseNotes>
<copyright>Copyright 2013, 2014</copyright>
<tags>owin katana oauth LinkedIn Yahoo Google+ GitHub Reddit Instagram StackExchange SalesForce TripIt Buffer ArcGIS Dropbox Wordpress Battle.NET Yammer OpenID Steam Twitch</tags>

View File

@@ -11,6 +11,7 @@ using Microsoft.Owin.Security.Infrastructure;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Owin.Security.Providers.Backlog;
using System.Net.Http.Headers;
namespace Owin.Security.Providers.Backlog
{
@@ -72,9 +73,12 @@ namespace Owin.Security.Providers.Backlog
body.Add(new KeyValuePair<string, string>("client_secret", Options.ClientSecret));
// Get token
httpClient.DefaultRequestHeaders.Authorization = null;
HttpResponseMessage tokenResponse =
await httpClient.PostAsync(Options.TokenEndpoint, new FormUrlEncodedContent(body));
var tokenRequest = new HttpRequestMessage(HttpMethod.Post, Options.TokenEndpoint);
tokenRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
tokenRequest.Content = new FormUrlEncodedContent(body);
HttpResponseMessage tokenResponse = await httpClient.SendAsync(tokenRequest, Request.CallCancelled);
tokenResponse.EnsureSuccessStatusCode();
string text = await tokenResponse.Content.ReadAsStringAsync();
@@ -88,12 +92,13 @@ namespace Owin.Security.Providers.Backlog
string tokenType = (string)response.token_type;
// Get the Backlog user
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(tokenType, Uri.EscapeDataString(accessToken));
HttpResponseMessage graphResponse = await httpClient.GetAsync(
Options.UserInfoEndpoint, Request.CallCancelled);
var userRequest = new HttpRequestMessage(HttpMethod.Get, Options.UserInfoEndpoint);
userRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
userRequest.Headers.Authorization = new AuthenticationHeaderValue(tokenType, Uri.EscapeDataString(accessToken));
HttpResponseMessage userResponse = await httpClient.SendAsync(userRequest, Request.CallCancelled);
graphResponse.EnsureSuccessStatusCode();
text = await graphResponse.Content.ReadAsStringAsync();
userResponse.EnsureSuccessStatusCode();
text = await userResponse.Content.ReadAsStringAsync();
JObject user = JObject.Parse(text);
var context = new BacklogAuthenticatedContext(Context, user, accessToken, expires, refreshToken);

View File

@@ -0,0 +1,7 @@
namespace Owin.Security.Providers.Cosign
{
internal static class Constants
{
public const string DefaultAuthenticationType = "Cosign";
}
}

View File

@@ -0,0 +1,28 @@
using System;
namespace Owin.Security.Providers.Cosign
{
public static class CosignAuthenticationExtensions
{
public static IAppBuilder UseCosignAuthentication(this IAppBuilder app,
CosignAuthenticationOptions options)
{
if (app == null)
throw new ArgumentNullException("app");
if (options == null)
throw new ArgumentNullException("options");
app.Use(typeof(CosignAuthenticationMiddleware), app, options);
return app;
}
public static IAppBuilder UseCosignAuthentication(this IAppBuilder app, string clientId, string clientSecret)
{
return app.UseCosignAuthentication(new CosignAuthenticationOptions
{
});
}
}
}

View File

@@ -0,0 +1,345 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Eventing.Reader;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Owin;
using Microsoft.Owin.Logging;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Infrastructure;
using Owin.Security.Providers.Cosign.Provider;
namespace Owin.Security.Providers.Cosign
{
public class CosignAuthenticationHandler : AuthenticationHandler<CosignAuthenticationOptions>
{
/*
Cosign sends authenticated users to iis web site root (not to application).
We need redirect user back to Identity Server application.
This can be done with different approaches http handler, url rewrite...
Here is UrlRewrite configuration
<rewrite>
<rules>
<clear />
<rule name="Cosign-RedirectCore1" enabled="true" stopProcessing="true">
<match url="cosign/valid?" negate="false" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false">
<add input="{QUERY_STRING}" pattern="core=core1" />
</conditions>
<action type="Redirect" url="https://yourserver/host/path/signin-cosign" redirectType="SeeOther" />
</rule>
<rule name="Cosign-RedirectCore2" enabled="true" stopProcessing="true">
<match url="cosign/valid?" negate="false" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false">
<add input="{QUERY_STRING}" pattern="core=core2" />
</conditions>
<action type="Redirect" url="https://yourserver/host/path/signin-cosign" redirectType="SeeOther" />
</rule>
</rules>
</rewrite>
*/
private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string";
private readonly ILogger logger;
public CosignAuthenticationHandler(ILogger logger)
{
this.logger = logger;
}
protected override Task<AuthenticationTicket> AuthenticateCoreAsync()
{
AuthenticationProperties properties = null;
try
{
string serviceCookieValue = null;
string state = null;
/*BUG: IReadableStringCollection has a bug. Some charactes can be missed in the collection and replaces with blank space.
Example: having "x" character in QueryString will result in having " " in the collection.
I will use QueryString from Request object instead of IReadableStringCollection*/
//IReadableStringCollection query = Request.Query;
//IList<string> values = query.GetValues("cosign-" + Options.ClientServer);
//if (values != null && values.Count == 1)
//{
// serviceCookieValue = values[0];
//}
//values = query.GetValues("state");
//if (values != null && values.Count == 1)
//{
// state = values[0];
//}
string queryString = Request.QueryString.Value;
string[] values = queryString.Split(new string[] {"&"}, StringSplitOptions.RemoveEmptyEntries);
serviceCookieValue =
values.First(a => a.Contains(Options.ClientServer))
.Replace("cosign-" + Options.ClientServer + "=", "");
state =
values.First(a => a.Contains("state"))
.Replace("state=", "");
properties = Options.StateDataFormat.Unprotect(state);
if (properties == null)
{
return null;
}
//// OAuth2 10.12 CSRF
//if (!ValidateCorrelationId(properties, logger))
//{
// return new AuthenticationTicket(null, properties);
//}
// Get host related information.
IPHostEntry hostEntry = Dns.GetHostEntry(Options.CosignServer);
// Loop through the AddressList to obtain the supported AddressFamily. This is to avoid
// an exception that occurs when the host IP Address is not compatible with the address family
// (typical in the IPv6 case).
foreach (IPAddress address in hostEntry.AddressList)
{
IPEndPoint ipLocalEndPoint = new IPEndPoint(address, Options.CosignServicePort);
using (TcpClient tcpClient = new TcpClient())
{
tcpClient.Connect(address, Options.CosignServicePort);
if (tcpClient.Connected)
{
logger.WriteInformation("Cosign authenticaion handler. Connected to server ip: " + address);
//read message from connected server and validate response
NetworkStream networkStream = tcpClient.GetStream();
byte[] buffer = new byte[256];
var bytesRead = networkStream.ReadAsync(buffer, 0, buffer.Length);
var receivedData = Encoding.UTF8.GetString(buffer, 0, bytesRead.Result);
//expected message: 220 2 Collaborative Web Single Sign-On [COSIGNv3 FACTORS=5 REKEY]
if (receivedData.Substring(0, 3) != "220")
continue;
//initiate secure negotiation and validate resonse
//buffer = Encoding.UTF8.GetBytes("STARTTLS 2\r\n");
buffer = Encoding.UTF8.GetBytes("STARTTLS 2" + Environment.NewLine);
networkStream.Write(buffer, 0, buffer.Length);
networkStream.Flush();
buffer = new byte[256];
bytesRead = networkStream.ReadAsync(buffer, 0, buffer.Length);
receivedData = Encoding.UTF8.GetString(buffer, 0, bytesRead.Result);
//expected message: 220 Ready to start TLS
if (receivedData.Substring(0, 3) != "220")
continue;
SslStream sslStream = new SslStream(tcpClient.GetStream(), false, ValidateServerCertificate,
null);
X509CertificateCollection certs = GetCertificateCertificateCollection(Options.ClientServer,
StoreName.My,
StoreLocation.LocalMachine);
try
{
Task authResult = sslStream.AuthenticateAsClientAsync(Options.CosignServer, certs, SslProtocols.Tls, false);
authResult.GetAwaiter().GetResult();
}
catch (AuthenticationException e)
{
logger.WriteError(e.Message);
if (e.InnerException != null)
{
logger.WriteError(string.Format("Inner exception: {0}", e.InnerException.Message));
}
logger.WriteError("Authentication failed - closing the connection.");
tcpClient.Close();
continue;
}
catch (Exception ex)
{
logger.WriteError(ex.Message);
tcpClient.Close();
continue;
}
if (!sslStream.IsEncrypted || !sslStream.IsSigned || !sslStream.IsMutuallyAuthenticated)
continue;
// The server name must match the name on the server certificate.
if (!sslStream.IsAuthenticated)
continue;
buffer = new byte[256];
bytesRead = sslStream.ReadAsync(buffer, 0, buffer.Length);
receivedData = Encoding.UTF8.GetString(buffer, 0, bytesRead.Result);
//expected message: 220 2 Collaborative Web Single Sign-On [COSIGNv3 FACTORS=5 REKEY]
if (receivedData.Substring(0, 3) != "220")
continue;
byte[] data =
Encoding.UTF8.GetBytes("CHECK " + "cosign-" + Options.ClientServer + "=" +
serviceCookieValue + Environment.NewLine);
sslStream.Write(data, 0, data.Length);
sslStream.Flush();
buffer = new byte[256];
bytesRead = sslStream.ReadAsync(buffer, 0, buffer.Length);
receivedData = Encoding.UTF8.GetString(buffer, 0, bytesRead.Result);
switch (receivedData.Substring(0, 1))
{
case "2":
//Success
logger.WriteInformation("Cosign authenticaion handler. 2-Response from Server: Success.");
var context = new CosignAuthenticatedContext(Context, receivedData)
{
Identity = new ClaimsIdentity(
Options.AuthenticationType,
ClaimsIdentity.DefaultNameClaimType,
ClaimsIdentity.DefaultRoleClaimType)
};
var identity = new ClaimsIdentity(Options.SignInAsAuthenticationType);
if (!string.IsNullOrEmpty(context.Id))
{
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, context.Id,
XmlSchemaString, Options.AuthenticationType));
}
if (!string.IsNullOrEmpty(context.UserId))
{
identity.AddClaim(new Claim("UserId", context.UserId, XmlSchemaString,
Options.AuthenticationType));
}
if (!string.IsNullOrEmpty(context.IpAddress))
{
identity.AddClaim(new Claim("IpAddress", context.IpAddress, XmlSchemaString,
Options.AuthenticationType));
}
if (!string.IsNullOrEmpty(context.Realm))
{
identity.AddClaim(new Claim("Realm", context.Realm, XmlSchemaString,
Options.AuthenticationType));
}
context.Properties = properties;
return Task.FromResult(new AuthenticationTicket(identity, properties));
case "4":
//Logged out
logger.WriteInformation("Cosign authenticaion handler. Response from Server: 4-Logged out.");
break;
case "5":
//Try a different server
logger.WriteInformation("Cosign authenticaion handler. Response from Server: 5-Try different server.");
break;
default:
logger.WriteInformation("Cosign authenticaion handler. Response from Server: Undefined.");
break;
}
}
}
}
}
catch (Exception ex)
{
logger.WriteError(ex.Message);
}
return Task.FromResult(new AuthenticationTicket(null, properties));
}
protected override Task ApplyResponseChallengeAsync()
{
if (Response.StatusCode == 401)
{
var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode);
// Only react to 401 if there is an authentication challenge for the authentication
// type of this handler.
if (challenge != null)
{
var state = challenge.Properties;
if (string.IsNullOrEmpty(state.RedirectUri))
{
state.RedirectUri = Request.Uri.ToString();
}
var stateString = Options.StateDataFormat.Protect(state);
string loginUrl =
"https://" + Options.CosignServer + "/?cosign-" + Options.ClientServer +
"&state=" + Uri.EscapeDataString(stateString) +
"&core=" + Options.IdentityServerHostInstance;
logger.WriteInformation("Cosign authenticaion handler. Redirecting to cosign. " + loginUrl);
Response.Redirect(loginUrl);
}
}
return Task.FromResult<object>(null);
}
public override async Task<bool> InvokeAsync()
{
// This is always invoked on each request. For passive middleware, only do anything if this is
// for our callback path when the user is redirected back from the authentication provider.
if (Options.CallbackPath.HasValue && Options.CallbackPath == Request.Path)
{
var ticket = await AuthenticateAsync();
if (ticket != null)
{
Context.Authentication.SignIn(ticket.Properties, ticket.Identity);
Response.Redirect(ticket.Properties.RedirectUri);
// Prevent further processing by the owin pipeline.
return true;
}
}
// Let the rest of the pipeline run.
return false;
}
public static X509CertificateCollection GetCertificateCertificateCollection(string subjectName,
StoreName storeName,
StoreLocation storeLocation)
{
// The following code gets the cert from the keystore
X509Store store = new X509Store(storeName, storeLocation);
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certCollection =
store.Certificates.Find(X509FindType.FindBySubjectName,
subjectName,
false);
return certCollection;
}
private static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
return false;
}
}
}

View File

@@ -0,0 +1,59 @@
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;
using Owin.Security.Providers.Cosign.Provider;
using Owin.Security.Providers.Properties;
namespace Owin.Security.Providers.Cosign
{
public class CosignAuthenticationMiddleware : AuthenticationMiddleware<CosignAuthenticationOptions>
{
private readonly ILogger logger;
public CosignAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, CosignAuthenticationOptions options)
: base(next, options)
{
if (String.IsNullOrWhiteSpace(Options.ClientServer))
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture,
Resources.Exception_OptionMustBeProvided, "ClientServer"));
if (String.IsNullOrWhiteSpace(Options.CosignServer))
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture,
Resources.Exception_OptionMustBeProvided, "CosignServer"));
if ((Options.CosignServicePort==0))
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture,
Resources.Exception_OptionMustBeProvided, "CosignServicePort"));
logger = app.CreateLogger<CosignAuthenticationMiddleware>();
logger.WriteInformation("CosignAthenticationMiddleware has been created");
if (Options.Provider == null)
Options.Provider = new CosignAuthenticationProvider();
if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType))
{
options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType();
}
if (options.StateDataFormat == null)
{
var dataProtector = app.CreateDataProtector(typeof (CosignAuthenticationMiddleware).FullName,
options.AuthenticationType);
options.StateDataFormat = new PropertiesDataFormat(dataProtector);
}
}
protected override AuthenticationHandler<CosignAuthenticationOptions> CreateHandler()
{
return new CosignAuthenticationHandler(logger);
}
}
}

View File

@@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Net.Http;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Owin.Security.Providers.Cosign.Provider;
namespace Owin.Security.Providers.Cosign
{
public class CosignAuthenticationOptions : AuthenticationOptions
{
//full login url https://weblogin.umich.edu/?cosign-www.kbv.law.umich.edu&http://www.kbv.law.umich.edu/IdentityServer/core1
//return url https://www.kbv.law.umich.edu/cosign/valid?cosign-www.kbv.law.umich.edu=COSIGN_VALUE&http://www.kbv.law.umich.edu/IdentityServer/core1
//private const string LoginEndPoint = "https://weblogin.umich.edu/?";
//private const string cosignServer = "weblogin.umich.edu";
//private const string clientServer = "www.kbv.law.umich.edu";
/// <summary>
/// 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-cosign".
/// </summary>
public PathString CallbackPath { get; set; }
/// <summary>
/// Gets or sets the Cosgin server
/// </summary>
public string CosignServer { get; set; }
/// <summary>
/// Gets or sets the instance of Identity Server Host
/// </summary>
public string IdentityServerHostInstance { get; set; }
/// <summary>
/// Gets or sets the Cosign service name
/// </summary>
public string ClientServer { get; set; }
/// <summary>
/// Gets or sets the Cosign service port
/// </summary>
public int CosignServicePort { get; set; }
/// <summary>
/// Gets or sets the <see cref="ICosignAuthenticationProvider" /> used in the authentication events
/// </summary>
public ICosignAuthenticationProvider Provider { get; set; }
/// <summary>
/// Gets or sets the name of another authentication middleware which will be responsible for actually issuing a user
/// <see cref="System.Security.Claims.ClaimsIdentity" />.
/// </summary>
public string SignInAsAuthenticationType { get; set; }
/// <summary>
/// Gets or sets the type used to secure data handled by the middleware.
/// </summary>
public ISecureDataFormat<AuthenticationProperties> StateDataFormat { get; set; }
/// <summary>
/// Initializes a new <see cref="CosignAuthenticationOptions" />
/// </summary>
public CosignAuthenticationOptions(): base("Cosign")
{
//CosignServer = cosignServer;
//ClientServer = clientServer;
Description.Caption = Constants.DefaultAuthenticationType;
CallbackPath = new PathString("/signin-cosign");
AuthenticationMode = AuthenticationMode.Passive;
IdentityServerHostInstance = "";
}
}
}

View File

@@ -0,0 +1,113 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Specialized;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Provider;
namespace Owin.Security.Providers.Cosign.Provider
{
/// <summary>
/// Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.
/// </summary>
public class CosignAuthenticatedContext : BaseContext
{
/// <summary>
/// Initializes a <see cref="CosignAuthenticatedContext"/>
/// </summary>
/// <param name="context">The OWIN environment</param>
/// <param name="cosignResponse">Response from Cosign server</param>
public CosignAuthenticatedContext(IOwinContext context, string cosignResponse): base(context)
{
CosignResposponse = cosignResponse;
string[] returnedData = CosignResposponse.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries);
Id = TryGetValue(returnedData, "id");
UserId = TryGetValue(returnedData, "userid");
IpAddress = TryGetValue(returnedData, "ipaddress");
Realm = TryGetValue(returnedData, "realm");
}
/// <summary>
/// Gets the Cosign response
/// </summary>
public string CosignResposponse { get; private set; }
/// <summary>
/// Gets the Cosign ID
/// </summary>
public string Id { get; private set; }
/// <summary>
/// Gets the Cosign userId
/// </summary>
public string UserId { get; private set; }
/// <summary>
/// Gets the <see cref="ClaimsIdentity"/> representing the user identity
/// </summary>
public ClaimsIdentity Identity { get; set; }
/// <summary>
/// Gets the <see cref="IpAddress"/> representing the user ipaddress
/// </summary>
public string IpAddress { get; set; }
/// <summary>
/// Gets the <see cref="Realm"/> representing the user realm
/// </summary>
public string Realm { get; set; }
/// <summary>
/// Gets or sets a property bag for common authentication properties
/// </summary>
public AuthenticationProperties Properties { get; set; }
private static string TryGetValue(string[] cosignData, string propertyName)
{
switch (propertyName.ToLower())
{
case "ipaddress":
if (cosignData.GetUpperBound(0)>=0)
return cosignData[1];
return "";
case "userid":
if (cosignData.GetUpperBound(0) >= 1)
return cosignData[2];
return "";
case "id":
if (cosignData.GetUpperBound(0) >= 1)
return sha256_hash( cosignData[2]);
return "";
case "realm":
if (cosignData.GetUpperBound(0) >=2)
return cosignData[3].Trim(new char[]{ Environment.NewLine.ToCharArray()[0]} );
return "";
default:
return "";
}
}
private static string sha256_hash(string value)
{
StringBuilder Sb = new StringBuilder();
using (SHA256 hash = SHA256.Create())
{
Encoding enc = Encoding.UTF8;
byte[] result = hash.ComputeHash(enc.GetBytes(value));
foreach (byte b in result)
Sb.Append(b.ToString("x2"));
}
return Sb.ToString();
}
}
}

View File

@@ -0,0 +1,50 @@
using System;
using System.Threading.Tasks;
namespace Owin.Security.Providers.Cosign.Provider
{
/// <summary>
/// Default <see cref="ICosignAuthenticationProvider"/> implementation.
/// </summary>
public class CosignAuthenticationProvider : ICosignAuthenticationProvider
{
/// <summary>
/// Initializes a <see cref="CosignAuthenticationProvider"/>
/// </summary>
public CosignAuthenticationProvider()
{
OnAuthenticated = context => Task.FromResult<object>(null);
OnReturnEndpoint = context => Task.FromResult<object>(null);
}
/// <summary>
/// Gets or sets the function that is invoked when the Authenticated method is invoked.
/// </summary>
public Func<CosignAuthenticatedContext, Task> OnAuthenticated { get; set; }
/// <summary>
/// Gets or sets the function that is invoked when the ReturnEndpoint method is invoked.
/// </summary>
public Func<CosignReturnEndpointContext, Task> OnReturnEndpoint { get; set; }
/// <summary>
/// Invoked whenever Cosign succesfully authenticates a user
/// </summary>
/// <param name="context">Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
public virtual Task Authenticated(CosignAuthenticatedContext context)
{
return OnAuthenticated(context);
}
/// <summary>
/// Invoked prior to the <see cref="System.Security.Claims.ClaimsIdentity"/> being saved in a local cookie and the browser being redirected to the originally requested URL.
/// </summary>
/// <param name="context"></param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
public virtual Task ReturnEndpoint(CosignReturnEndpointContext context)
{
return OnReturnEndpoint(context);
}
}
}

View File

@@ -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.Cosign.Provider
{
/// <summary>
/// Provides context information to middleware providers.
/// </summary>
public class CosignReturnEndpointContext : ReturnEndpointContext
{
/// <summary>
///
/// </summary>
/// <param name="context">OWIN environment</param>
/// <param name="ticket">The authentication ticket</param>
public CosignReturnEndpointContext(
IOwinContext context,
AuthenticationTicket ticket)
: base(context, ticket)
{
}
}
}

View File

@@ -0,0 +1,24 @@
using System.Threading.Tasks;
namespace Owin.Security.Providers.Cosign.Provider
{
/// <summary>
/// Specifies callback methods which the <see cref="CosignAuthenticationMiddleware"></see> invokes to enable developer control over the authentication process. />
/// </summary>
public interface ICosignAuthenticationProvider
{
/// <summary>
/// Invoked whenever Cosign succesfully authenticates a user
/// </summary>
/// <param name="context">Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
Task Authenticated(CosignAuthenticatedContext context);
/// <summary>
/// Invoked prior to the <see cref="System.Security.Claims.ClaimsIdentity"/> being saved in a local cookie and the browser being redirected to the originally requested URL.
/// </summary>
/// <param name="context"></param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
Task ReturnEndpoint(CosignReturnEndpointContext context);
}
}

View File

@@ -274,6 +274,15 @@
<Compile Include="Salesforce\SalesforceAuthenticationHandler.cs" />
<Compile Include="Salesforce\SalesforceAuthenticationMiddleware.cs" />
<Compile Include="Salesforce\SalesforceAuthenticationOptions.cs" />
<Compile Include="Shopify\Constants.cs" />
<Compile Include="Shopify\Provider\IShopifyAuthenticationProvider.cs" />
<Compile Include="Shopify\Provider\ShopifyAuthenticatedContext.cs" />
<Compile Include="Shopify\Provider\ShopifyAuthenticationProvider.cs" />
<Compile Include="Shopify\Provider\ShopifyReturnEndpointContext.cs" />
<Compile Include="Shopify\ShopifyAuthenticationExtensions.cs" />
<Compile Include="Shopify\ShopifyAuthenticationHandler.cs" />
<Compile Include="Shopify\ShopifyAuthenticationMiddleware.cs" />
<Compile Include="Shopify\ShopifyAuthenticationOptions.cs" />
<Compile Include="Slack\Constants.cs" />
<Compile Include="Slack\Provider\ISlackAuthenticationProvider.cs" />
<Compile Include="Slack\Provider\SlackAuthenticatedContext.cs" />

View File

@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.22.1.0")]
[assembly: AssemblyFileVersion("1.22.1.0")]
[assembly: AssemblyVersion("1.23.0.0")]
[assembly: AssemblyFileVersion("1.23.0.0")]

View File

@@ -0,0 +1,7 @@
namespace Owin.Security.Providers.Shopify
{
internal static class Constants
{
public const string DefaultAuthenticationType = "Shopify";
}
}

View File

@@ -0,0 +1,24 @@
namespace Owin.Security.Providers.Shopify
{
using System.Threading.Tasks;
/// <summary>
/// Specifies callback methods which the <see cref="ShopifyAuthenticationMiddleware"/> invokes to enable developer control over the authentication process.
/// </summary>
public interface IShopifyAuthenticationProvider
{
/// <summary>
/// Invoked whenever Shopify shop successfully authenticates a user.
/// </summary>
/// <param name="context">Contains information about the login session as well as the shop <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
Task Authenticated(ShopifyAuthenticatedContext context);
/// <summary>
/// Invoked prior to the <see cref="System.Security.Claims.ClaimsIdentity"/> being saved in a local cookie and the browser being redirected to the originally requested URL.
/// </summary>
/// <param name="context">Instance of return endpoint context.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
Task ReturnEndpoint(ShopifyReturnEndpointContext context);
}
}

View File

@@ -0,0 +1,85 @@
namespace Owin.Security.Providers.Shopify
{
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Provider;
using Newtonsoft.Json.Linq;
using System.Security.Claims;
public class ShopifyAuthenticatedContext : BaseContext
{
/// <summary>
/// Initializes a new instance of the <see cref="ShopifyAuthenticatedContext"/> class.
/// </summary>
/// <param name="context">The OWIN environment.</param>
/// <param name="shop">The JSON-serialized shop.</param>
/// <param name="accessToken">Shopify shop access token.</param>
public ShopifyAuthenticatedContext(IOwinContext context, JObject shop, string accessToken)
: base(context)
{
Shop = shop;
AccessToken = accessToken;
Id = TryGetValue(shop, "id");
var fullShopifyDomainName = TryGetValue(shop, "myshopify_domain");
UserName = string.IsNullOrWhiteSpace(fullShopifyDomainName) ? null : fullShopifyDomainName.Replace(".myshopify.com", "");
Email = TryGetValue(shop, "email");
ShopName = TryGetValue(shop, "name");
}
/// <summary>
/// Gets the JSON-serialized Shopify shop.
/// </summary>
/// <remarks>Contains the Shopify shop information obtained from the Shop endpoint. By default this is https://{shopname}.myshopify.com/admin/shop but it can be overridden in the options.</remarks>
public JObject Shop { get; private set; }
/// <summary>
/// Gets the Shopify shop access token
/// </summary>
public string AccessToken { get; private set; }
/// <summary>
/// Gets the Shopify shop Id.
/// </summary>
public string Id { get; private set; }
/// <summary>
/// Gets the Shopify shop domain name.
/// </summary>
/// <remarks>{shop_domain_name}.myshopify.com - without the ".myshopify.com" to be used as suggested username.</remarks>
public string UserName { get; private set; }
/// <summary>
/// Gets the Shopify shop primary email address.
/// </summary>
public string Email { get; private set; }
/// <summary>
/// Gets the Shopify shop name.
/// </summary>
public string ShopName { get; private set; }
/// <summary>
/// Gets the <see cref="ClaimsIdentity"/> representing the Shopify shop.
/// </summary>
public ClaimsIdentity Identity { get; set; }
/// <summary>
/// Gets or sets a property bag for common authentication properties
/// </summary>
public AuthenticationProperties Properties { get; set; }
private static string TryGetValue(JToken shop, string propertyName)
{
if (shop != null && shop.First != null && shop.First.First != null)
{
var propertyValue = shop.First.First[propertyName];
if (propertyValue != null)
return propertyValue.ToString();
}
return null;
}
}
}

View File

@@ -0,0 +1,50 @@
namespace Owin.Security.Providers.Shopify
{
using System;
using System.Threading.Tasks;
/// <summary>
/// Default <see cref="IShopifyAuthenticationProvider"/> implementation.
/// </summary>
public class ShopifyAuthenticationProvider : IShopifyAuthenticationProvider
{
/// <summary>
/// Initializes a new instance of the <see cref="ShopifyAuthenticationProvider"/> class.
/// </summary>
public ShopifyAuthenticationProvider()
{
OnAuthenticated = context => Task.FromResult<object>(null);
OnReturnEndpoint = context => Task.FromResult<object>(null);
}
/// <summary>
/// Gets or sets the function that is invoked when the Authenticated method is invoked.
/// </summary>
public Func<ShopifyAuthenticatedContext, Task> OnAuthenticated { get; set; }
/// <summary>
/// Gets or sets the function that is invoked when the ReturnEndpoint method is invoked.
/// </summary>
public Func<ShopifyReturnEndpointContext, Task> OnReturnEndpoint { get; set; }
/// <summary>
/// Invoked whenever Shopify shop successfully authenticates a user.
/// </summary>
/// <param name="context">Contains information about the login session as well as the shop <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
public virtual Task Authenticated(ShopifyAuthenticatedContext context)
{
return OnAuthenticated(context);
}
/// <summary>
/// Invoked prior to the <see cref="System.Security.Claims.ClaimsIdentity"/> being saved in a local cookie and the browser being redirected to the originally requested URL.
/// </summary>
/// <param name="context">Instance of return endpoint context.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
public virtual Task ReturnEndpoint(ShopifyReturnEndpointContext context)
{
return OnReturnEndpoint(context);
}
}
}

View File

@@ -0,0 +1,22 @@
namespace Owin.Security.Providers.Shopify
{
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Provider;
/// <summary>
/// Provides context information to middleware providers.
/// </summary>
public class ShopifyReturnEndpointContext : ReturnEndpointContext
{
/// <summary>
/// Initializes a new instance of the <see cref="ShopifyReturnEndpointContext"/> class.
/// </summary>
/// <param name="context">OWIN environment.</param>
/// <param name="ticket">The authentication ticket.</param>
public ShopifyReturnEndpointContext(IOwinContext context, AuthenticationTicket ticket)
: base(context, ticket)
{
}
}
}

View File

@@ -0,0 +1,45 @@
namespace Owin.Security.Providers.Shopify
{
using System;
public static class ShopifyAuthenticationExtensions
{
/// <summary>
/// Use Shopify Shop OAuth authentication.
/// </summary>
/// <param name="app">Instance of <see cref="IAppBuilder"/>.</param>
/// <param name="options">Shopify overrided authentication options.</param>
/// <returns>Returns instance of <see cref="IAppBuilder"/>.</returns>
public static IAppBuilder UseShopifyAuthentication(this IAppBuilder app, ShopifyAuthenticationOptions options)
{
if (null == app)
{
throw new ArgumentNullException("app");
}
if (null == options)
{
throw new ArgumentNullException("options");
}
app.Use(typeof(ShopifyAuthenticationMiddleware), app, options);
return app;
}
/// <summary>
/// Use Shopify Shop OAuth authentication with default authentication options.
/// </summary>
/// <param name="app">Instance of <see cref="IAppBuilder"/>.</param>
/// <param name="apiKey">Shopify App - API key.</param>
/// <param name="apiSecret">Shopify App - API secret.</param>
/// <returns>Returns instance of <see cref="IAppBuilder"/>.</returns>
public static IAppBuilder UseShopifyAuthentication(this IAppBuilder app, string apiKey, string apiSecret)
{
return app.UseShopifyAuthentication(new ShopifyAuthenticationOptions
{
ApiKey = apiKey,
ApiSecret = apiSecret
});
}
}
}

View File

@@ -0,0 +1,230 @@
namespace Owin.Security.Providers.Shopify
{
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;
using System.Collections.Generic;
using System.Globalization;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Threading.Tasks;
public class ShopifyAuthenticationHandler : AuthenticationHandler<ShopifyAuthenticationOptions>
{
private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string";
private readonly ILogger logger;
private readonly HttpClient httpClient;
public ShopifyAuthenticationHandler(HttpClient httpClient, ILogger logger)
{
this.httpClient = httpClient;
this.logger = logger;
}
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
{
AuthenticationProperties properties = null;
try
{
string code = null;
string state = null;
var query = Request.Query;
var values = query.GetValues("code");
if (null != values && 1 == values.Count)
{
code = values[0];
}
values = query.GetValues("state");
if (null != values && 1 == values.Count)
{
state = values[0];
}
properties = Options.StateDataFormat.Unprotect(state);
if (null == properties)
{
return null;
}
//// OAuth2 10.12 CSRF
if (!ValidateCorrelationId(properties, logger))
{
return new AuthenticationTicket(null, properties);
}
var currentShopifyShopName = properties.Dictionary["ShopName"];
if (string.IsNullOrWhiteSpace(currentShopifyShopName))
{
return null;
}
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<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("code", code),
new KeyValuePair<string, string>("redirect_uri", redirectUri),
new KeyValuePair<string, string>("client_id", Options.ApiKey),
new KeyValuePair<string, string>("client_secret", Options.ApiSecret)
};
//// Request the token
var requestMessage = new HttpRequestMessage(HttpMethod.Post, string.Format(CultureInfo.CurrentCulture, Options.Endpoints.TokenEndpoint, currentShopifyShopName));
requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
requestMessage.Content = new FormUrlEncodedContent(body);
var tokenResponse = await httpClient.SendAsync(requestMessage);
tokenResponse.EnsureSuccessStatusCode();
var text = await tokenResponse.Content.ReadAsStringAsync();
//// Deserializes the token response
dynamic response = JsonConvert.DeserializeObject<dynamic>(text);
var accessToken = (string)response.access_token;
//// Get the Shopify shop information
var shopRequest = new HttpRequestMessage(HttpMethod.Get, string.Format(CultureInfo.CurrentCulture, Options.Endpoints.ShopInfoEndpoint, currentShopifyShopName) + "?access_token=" + Uri.EscapeDataString(accessToken));
shopRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var shopResponse = await httpClient.SendAsync(shopRequest, Request.CallCancelled);
shopResponse.EnsureSuccessStatusCode();
text = await shopResponse.Content.ReadAsStringAsync();
var shopifyShop = JObject.Parse(text);
var context = new ShopifyAuthenticatedContext(Context, shopifyShop, 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.UserName))
{
context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.UserName, XmlSchemaString, Options.AuthenticationType));
}
if (!string.IsNullOrEmpty(context.Email))
{
context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.Email, XmlSchemaString, Options.AuthenticationType));
}
if (!string.IsNullOrEmpty(context.ShopName))
{
context.Identity.AddClaim(new Claim("urn:shopify:shopname", context.ShopName, XmlSchemaString, Options.AuthenticationType));
}
context.Properties = properties;
await Options.Provider.Authenticated(context);
return new AuthenticationTicket(context.Identity, context.Properties);
}
catch (Exception exception)
{
logger.WriteError(exception.Message);
}
return new AuthenticationTicket(null, properties);
}
protected override Task ApplyResponseChallengeAsync()
{
if (Response.StatusCode != 401)
{
return Task.FromResult<object>(null);
}
var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode);
if (challenge == null)
{
return Task.FromResult<object>(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 scope = string.Join(",", Options.Scope);
var state = Options.StateDataFormat.Protect(properties);
var authorizationEndpoint =
string.Format(CultureInfo.CurrentCulture, Options.Endpoints.AuthorizationEndpoint, challenge.Properties.Dictionary["ShopName"]) +
"?client_id=" + Uri.EscapeDataString(Options.ApiKey) +
"&redirect_uri=" + Uri.EscapeDataString(redirectUri) +
"&scope=" + Uri.EscapeDataString(scope) +
"&state=" + Uri.EscapeDataString(state);
Response.Redirect(authorizationEndpoint);
return Task.FromResult<object>(null);
}
public override async Task<bool> InvokeAsync()
{
return await InvokeReplyPathAsync();
}
private async Task<bool> InvokeReplyPathAsync()
{
if (!Options.CallbackPath.HasValue || Options.CallbackPath != Request.Path)
{
return false;
}
//// TODO: error responses (I have no idea what this error responses TODO means :o)
var ticket = await AuthenticateAsync();
if (null == ticket)
{
logger.WriteWarning("Invalid return state, unable to redirect.");
Response.StatusCode = 500;
return true;
}
var context = new ShopifyReturnEndpointContext(Context, ticket)
{
SignInAsAuthenticationType = Options.SignInAsAuthenticationType,
RedirectUri = ticket.Properties.RedirectUri
};
await Options.Provider.ReturnEndpoint(context);
if (null != context.SignInAsAuthenticationType && null != context.Identity)
{
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 || null == context.RedirectUri)
{
return context.IsRequestCompleted;
}
var redirectUri = context.RedirectUri;
if (null == context.Identity)
{
//// 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;
}
}
}

View File

@@ -0,0 +1,89 @@
namespace Owin.Security.Providers.Shopify
{
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 Properties;
using System;
using System.Globalization;
using System.Net.Http;
public class ShopifyAuthenticationMiddleware : AuthenticationMiddleware<ShopifyAuthenticationOptions>
{
private readonly HttpClient httpClient;
private readonly ILogger logger;
public ShopifyAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, ShopifyAuthenticationOptions options)
: base(next, options)
{
if (string.IsNullOrWhiteSpace(Options.ApiKey))
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ApiKey"));
}
if (string.IsNullOrWhiteSpace(Options.ApiSecret))
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ApiSecret"));
}
this.logger = app.CreateLogger<ShopifyAuthenticationMiddleware>();
if (null == Options.Provider)
{
Options.Provider = new ShopifyAuthenticationProvider();
}
if (null == Options.StateDataFormat)
{
var dataProtector = app.CreateDataProtector(typeof(ShopifyAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1");
Options.StateDataFormat = new PropertiesDataFormat(dataProtector);
}
if (string.IsNullOrWhiteSpace(Options.SignInAsAuthenticationType))
{
Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType();
}
this.httpClient = new HttpClient(ResolveHttpMessageHandler(Options))
{
Timeout = Options.BackchannelTimeout,
MaxResponseContentBufferSize = 1024 * 1024 * 10
};
this.httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin Shopify middleware");
this.httpClient.DefaultRequestHeaders.ExpectContinue = false;
}
/// <summary>
/// Provides the <see cref="T:Microsoft.Owin.Security.Infrastructure.AuthenticationHandler" /> object for processing authentication-related requests.
/// </summary>
/// <returns>An <see cref="T:Microsoft.Owin.Security.Infrastructure.AuthenticationHandler" /> configured with the <see cref="T:Owin.Security.Providers.Shopify.ShopifyAuthenticationOptions" /> supplied to the constructor.</returns>
protected override AuthenticationHandler<ShopifyAuthenticationOptions> CreateHandler()
{
return new ShopifyAuthenticationHandler(httpClient, logger);
}
private static HttpMessageHandler ResolveHttpMessageHandler(ShopifyAuthenticationOptions options)
{
var handler = options.BackchannelHttpHandler ?? new WebRequestHandler();
//// If they provided a validator, apply it or fail.
if (null == options.BackchannelCertificateValidator)
{
return handler;
}
//// Set the cert validate callback
var webRequestHandler = handler as WebRequestHandler;
if (null == webRequestHandler)
{
throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch);
}
webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
return handler;
}
}
}

View File

@@ -0,0 +1,129 @@
namespace Owin.Security.Providers.Shopify
{
using Microsoft.Owin;
using Microsoft.Owin.Security;
using System;
using System.Collections.Generic;
using System.Net.Http;
public class ShopifyAuthenticationOptions : AuthenticationOptions
{
public class ShopifyAuthenticationEndpoints
{
/// <summary>
/// Endpoint which is used to redirect users to request Shopify shop access.
/// </summary>
/// <remarks>Defaults to https://{shop}.myshopify.com/admin/oauth/authorize.</remarks>
public string AuthorizationEndpoint { get; set; }
/// <summary>
/// Endpoint which is used to exchange code for access token
/// </summary>
/// <remarks>Defaults to https://{shop}.myshopify.com/admin/oauth/access_token.</remarks>
public string TokenEndpoint { get; set; }
/// <summary>
/// Endpoint which is used to obtain shop information after authentication
/// </summary>
/// <remarks>Defaults to https://{shop}.myshopify.com/admin/shop.</remarks>
public string ShopInfoEndpoint { get; set; }
}
private const string DefaultAuthorizationEndPoint = "https://{0}.myshopify.com/admin/oauth/authorize";
private const string DefaultTokenEndpoint = "https://{0}.myshopify.com/admin/oauth/access_token";
private const string DefaultShopInfoEndpoint = "https://{0}.myshopify.com/admin/shop";
/// <summary>
/// Gets or sets the a pinned certificate validator to use to validate the endpoints used in back channel communications belong to Shopify.
/// </summary>
/// <value>The pinned certificate validator.</value>
/// <remarks>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.</remarks>
public ICertificateValidator BackchannelCertificateValidator { get; set; }
/// <summary>
/// The HttpMessageHandler used to communicate with Shopify. This cannot be set at the same time as BackchannelCertificateValidator unless the value can be downcast to a WebRequestHandler.
/// </summary>
public HttpMessageHandler BackchannelHttpHandler { get; set; }
/// <summary>
/// Gets or sets timeout value in milliseconds for back channel communications with Shopify.
/// </summary>
/// <value>The back channel timeout in milliseconds.</value>
public TimeSpan BackchannelTimeout { get; set; }
/// <summary>
/// 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-shopify".
/// </summary>
public PathString CallbackPath { get; set; }
/// <summary>
/// Get or sets the text that the user can display on a sign in user interface.
/// </summary>
public string Caption
{
get
{
return Description.Caption;
}
set
{
Description.Caption = value;
}
}
/// <summary>
/// Gets or sets the Shopify app API key.
/// </summary>
public string ApiKey { get; set; }
/// <summary>
/// Gets or sets the Shopify app API secret.
/// </summary>
public string ApiSecret { get; set; }
/// <summary>
/// Gets the sets of OAuth endpoints used to authenticate against Shopify shop.
/// </summary>
public ShopifyAuthenticationEndpoints Endpoints { get; set; }
/// <summary>
/// Gets or sets the <see cref="IShopifyAuthenticationProvider" /> used in the authentication events
/// </summary>
public IShopifyAuthenticationProvider Provider { get; set; }
/// <summary>
/// A list of permissions to request.
/// </summary>
public IList<string> Scope { get; private set; }
/// <summary>
/// Gets or sets the name of another authentication middleware which will be responsible for actually issuing a shop <see cref="System.Security.Claims.ClaimsIdentity" />.
/// </summary>
public string SignInAsAuthenticationType { get; set; }
/// <summary>
/// Gets or sets the type used to secure data handled by the middleware.
/// </summary>
public ISecureDataFormat<AuthenticationProperties> StateDataFormat { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="ShopifyAuthenticationOptions" /> class.
/// </summary>
public ShopifyAuthenticationOptions()
: base("Shopify")
{
Caption = Constants.DefaultAuthenticationType;
CallbackPath = new PathString("/signin-shopify");
AuthenticationMode = AuthenticationMode.Passive;
Scope = new List<string> { "read_content" };
BackchannelTimeout = TimeSpan.FromSeconds(60);
Endpoints = new ShopifyAuthenticationEndpoints
{
AuthorizationEndpoint = DefaultAuthorizationEndPoint,
TokenEndpoint = DefaultTokenEndpoint,
ShopInfoEndpoint = DefaultShopInfoEndpoint
};
}
}
}

View File

@@ -30,6 +30,7 @@ using Owin.Security.Providers.SoundCloud;
using Owin.Security.Providers.Spotify;
using Owin.Security.Providers.StackExchange;
using Owin.Security.Providers.Steam;
using Owin.Security.Providers.Shopify;
using Owin.Security.Providers.TripIt;
using Owin.Security.Providers.Twitch;
using Owin.Security.Providers.Untappd;
@@ -186,6 +187,8 @@ namespace OwinOAuthProvidersDemo
//};
//app.UseSalesforceAuthentication(salesforceOptions);
////app.UseShopifyAuthentication("", "");
//app.UseArcGISOnlineAuthentication(
// clientId: "",
// clientSecret: "");
@@ -202,7 +205,6 @@ namespace OwinOAuthProvidersDemo
// clientId: "",
// clientSecret: "");
//app.UseBattleNetAuthentication(new BattleNetAuthenticationOptions
//{
// ClientId = "",
@@ -273,6 +275,17 @@ namespace OwinOAuthProvidersDemo
//app.UseBacklogAuthentication(options);
//var cosignOptions = new CosignAuthenticationOptions
//{
// AuthenticationType = "Cosign",
// SignInAsAuthenticationType = signInAsType,
// CosignServer = "weblogin.umich.edu",
// CosignServicePort = 6663,
// IdentityServerHostInstance = "core1",
// ClientServer = "cosignservername"
//};
//app.UseCosignAuthentication(cosignOptions);
//app.UseFitbitAuthentication(new FitbitAuthenticationOptions
//{
// ClientId = "",

View File

@@ -182,15 +182,26 @@ namespace OwinOAuthProvidersDemo.Controllers
return View(model);
}
//////
////// POST: /Account/ExternalLogin
////[HttpPost]
////[AllowAnonymous]
////[ValidateAntiForgeryToken]
////public ActionResult ExternalLogin(string provider, string returnUrl)
////{
//// // Request a redirect to the external login provider
//// return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
////}
//
// POST: /Account/ExternalLogin
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLogin(string provider, string returnUrl)
public ActionResult ExternalLogin(string provider, string returnUrl, string shopName = "")
{
// Request a redirect to the external login provider
return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }), null, shopName);
}
//
@@ -224,10 +235,10 @@ namespace OwinOAuthProvidersDemo.Controllers
// POST: /Account/LinkLogin
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LinkLogin(string provider)
public ActionResult LinkLogin(string provider, string shopName)
{
// Request a redirect to the external login provider to link a login for the current user
return new ChallengeResult(provider, Url.Action("LinkLoginCallback", "Account"), User.Identity.GetUserId());
return new ChallengeResult(provider, Url.Action("LinkLoginCallback", "Account"), User.Identity.GetUserId(), shopName);
}
//
@@ -324,6 +335,8 @@ namespace OwinOAuthProvidersDemo.Controllers
#region Helpers
// Used for XSRF protection when adding external logins
private const string XsrfKey = "XsrfId";
// Used for Shopify external login to provide shopname while building endpoints.
private const string ShopNameKey = "ShopName";
private IAuthenticationManager AuthenticationManager
{
@@ -380,28 +393,36 @@ namespace OwinOAuthProvidersDemo.Controllers
private class ChallengeResult : HttpUnauthorizedResult
{
public ChallengeResult(string provider, string redirectUri) : this(provider, redirectUri, null)
public ChallengeResult(string provider, string redirectUri) : this(provider, redirectUri, null, null)
{
}
public ChallengeResult(string provider, string redirectUri, string userId)
public ChallengeResult(string provider, string redirectUri, string userId, string shopName)
{
LoginProvider = provider;
RedirectUri = redirectUri;
UserId = userId;
ShopName = shopName;
}
public string LoginProvider { get; set; }
public string RedirectUri { get; set; }
public string UserId { get; set; }
public string ShopName { get; set; }
public override void ExecuteResult(ControllerContext context)
{
var properties = new AuthenticationProperties() { RedirectUri = RedirectUri };
if (UserId != null)
{
properties.Dictionary[XsrfKey] = UserId;
properties.Dictionary[XsrfKey] = this.UserId;
}
if (!string.IsNullOrWhiteSpace(this.ShopName))
{
properties.Dictionary[ShopNameKey] = this.ShopName;
}
context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);
}
}

View File

@@ -24,6 +24,10 @@
<p>
@foreach (AuthenticationDescription p in loginProviders)
{
if (p.AuthenticationType.Equals("Shopify"))
{
@Html.TextBox("shopName")
}
<button type="submit" class="btn btn-default" id="@p.AuthenticationType" name="provider" value="@p.AuthenticationType" title="Log in using your @p.Caption account">@p.AuthenticationType</button>
}
</p>

View File

@@ -10,6 +10,7 @@ Provides a set of extra authentication providers for OWIN ([Project Katana](http
- Backlog
- Battle.net
- Buffer
- Cosign
- DeviantArt
- Dropbox
- EVEOnline
@@ -25,6 +26,7 @@ Provides a set of extra authentication providers for OWIN ([Project Katana](http
- PayPal
- Reddit
- Salesforce
- Shopify
- Slack
- SoundCloud
- Spotify
@@ -42,7 +44,7 @@ Provides a set of extra authentication providers for OWIN ([Project Katana](http
- Wargaming
## Implementation Guides
For guides on how to implement these providers, please visit my blog, [Be a Big Rockstar](http://www.beabigrockstar.com).
For above listed provider implementation guide, visit Jerrie Pelser's blog - [Be a Big Rockstar](http://www.beabigrockstar.com)
## Installation
To use these providers you will need to install the ```Owin.Security.Providers``` NuGet package.
@@ -72,6 +74,7 @@ A big thanks goes out to all these contributors without whom this would not have
* Anthony Ruffino (https://github.com/AnthonyRuffino)
* Tommy Parnell (https://github.com/tparnell8)
* Maxime Roussin-Bélanger (https://github.com/Lorac)
* Jaspalsinh Chauhan (https://github.com/jsinh)
For most accurate and up to date list of contributors please see https://github.com/RockstarLabs/OwinOAuthProviders/graphs/contributors

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2014 Jerrie Pelser
Copyright (c) 2014, 2015 Jerrie Pelser
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal