Merge remote-tracking branch 'upstream/master'
Conflicts: OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
<package >
|
<package >
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>Owin.Security.Providers</id>
|
<id>Owin.Security.Providers</id>
|
||||||
<version>1.22.1</version>
|
<version>1.23</version>
|
||||||
<authors>Jerrie Pelser and contributors</authors>
|
<authors>Jerrie Pelser and contributors</authors>
|
||||||
<owners>Jerrie Pelser</owners>
|
<owners>Jerrie Pelser</owners>
|
||||||
<licenseUrl>http://opensource.org/licenses/MIT</licenseUrl>
|
<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.
|
Also adds generic OpenID 2.0 providers as well implementations for Steam and Wargaming.
|
||||||
</summary>
|
</summary>
|
||||||
<releaseNotes>
|
<releaseNotes>
|
||||||
Version 1.22
|
Version 1.23
|
||||||
|
|
||||||
Enhancement
|
|
||||||
- Ability to specify prompt parameter for Salesforce provider
|
|
||||||
|
|
||||||
Version 1.22
|
|
||||||
|
|
||||||
Added
|
Added
|
||||||
- Added Imgur
|
- Added Shopify
|
||||||
- Added Backlog
|
- Added Cosign
|
||||||
</releaseNotes>
|
</releaseNotes>
|
||||||
<copyright>Copyright 2013, 2014</copyright>
|
<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>
|
<tags>owin katana oauth LinkedIn Yahoo Google+ GitHub Reddit Instagram StackExchange SalesForce TripIt Buffer ArcGIS Dropbox Wordpress Battle.NET Yammer OpenID Steam Twitch</tags>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using Microsoft.Owin.Security.Infrastructure;
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Owin.Security.Providers.Backlog;
|
using Owin.Security.Providers.Backlog;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
|
||||||
namespace Owin.Security.Providers.Backlog
|
namespace Owin.Security.Providers.Backlog
|
||||||
{
|
{
|
||||||
@@ -72,9 +73,12 @@ namespace Owin.Security.Providers.Backlog
|
|||||||
body.Add(new KeyValuePair<string, string>("client_secret", Options.ClientSecret));
|
body.Add(new KeyValuePair<string, string>("client_secret", Options.ClientSecret));
|
||||||
|
|
||||||
// Get token
|
// Get token
|
||||||
httpClient.DefaultRequestHeaders.Authorization = null;
|
var tokenRequest = new HttpRequestMessage(HttpMethod.Post, Options.TokenEndpoint);
|
||||||
HttpResponseMessage tokenResponse =
|
tokenRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||||
await httpClient.PostAsync(Options.TokenEndpoint, new FormUrlEncodedContent(body));
|
tokenRequest.Content = new FormUrlEncodedContent(body);
|
||||||
|
|
||||||
|
HttpResponseMessage tokenResponse = await httpClient.SendAsync(tokenRequest, Request.CallCancelled);
|
||||||
|
|
||||||
tokenResponse.EnsureSuccessStatusCode();
|
tokenResponse.EnsureSuccessStatusCode();
|
||||||
string text = await tokenResponse.Content.ReadAsStringAsync();
|
string text = await tokenResponse.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
@@ -88,12 +92,13 @@ namespace Owin.Security.Providers.Backlog
|
|||||||
string tokenType = (string)response.token_type;
|
string tokenType = (string)response.token_type;
|
||||||
|
|
||||||
// Get the Backlog user
|
// Get the Backlog user
|
||||||
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(tokenType, Uri.EscapeDataString(accessToken));
|
var userRequest = new HttpRequestMessage(HttpMethod.Get, Options.UserInfoEndpoint);
|
||||||
HttpResponseMessage graphResponse = await httpClient.GetAsync(
|
userRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||||
Options.UserInfoEndpoint, Request.CallCancelled);
|
userRequest.Headers.Authorization = new AuthenticationHeaderValue(tokenType, Uri.EscapeDataString(accessToken));
|
||||||
|
HttpResponseMessage userResponse = await httpClient.SendAsync(userRequest, Request.CallCancelled);
|
||||||
|
|
||||||
graphResponse.EnsureSuccessStatusCode();
|
userResponse.EnsureSuccessStatusCode();
|
||||||
text = await graphResponse.Content.ReadAsStringAsync();
|
text = await userResponse.Content.ReadAsStringAsync();
|
||||||
JObject user = JObject.Parse(text);
|
JObject user = JObject.Parse(text);
|
||||||
|
|
||||||
var context = new BacklogAuthenticatedContext(Context, user, accessToken, expires, refreshToken);
|
var context = new BacklogAuthenticatedContext(Context, user, accessToken, expires, refreshToken);
|
||||||
|
|||||||
7
Owin.Security.Providers/Cosign/Constants.cs
Normal file
7
Owin.Security.Providers/Cosign/Constants.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Owin.Security.Providers.Cosign
|
||||||
|
{
|
||||||
|
internal static class Constants
|
||||||
|
{
|
||||||
|
public const string DefaultAuthenticationType = "Cosign";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
{
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
345
Owin.Security.Providers/Cosign/CosignAuthenticationHandler.cs
Normal file
345
Owin.Security.Providers/Cosign/CosignAuthenticationHandler.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 = "";
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -274,6 +274,15 @@
|
|||||||
<Compile Include="Salesforce\SalesforceAuthenticationHandler.cs" />
|
<Compile Include="Salesforce\SalesforceAuthenticationHandler.cs" />
|
||||||
<Compile Include="Salesforce\SalesforceAuthenticationMiddleware.cs" />
|
<Compile Include="Salesforce\SalesforceAuthenticationMiddleware.cs" />
|
||||||
<Compile Include="Salesforce\SalesforceAuthenticationOptions.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\Constants.cs" />
|
||||||
<Compile Include="Slack\Provider\ISlackAuthenticationProvider.cs" />
|
<Compile Include="Slack\Provider\ISlackAuthenticationProvider.cs" />
|
||||||
<Compile Include="Slack\Provider\SlackAuthenticatedContext.cs" />
|
<Compile Include="Slack\Provider\SlackAuthenticatedContext.cs" />
|
||||||
|
|||||||
@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
|
|||||||
// You can specify all the values or you can default the Build and Revision Numbers
|
// You can specify all the values or you can default the Build and Revision Numbers
|
||||||
// by using the '*' as shown below:
|
// by using the '*' as shown below:
|
||||||
// [assembly: AssemblyVersion("1.0.*")]
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
[assembly: AssemblyVersion("1.22.1.0")]
|
[assembly: AssemblyVersion("1.23.0.0")]
|
||||||
[assembly: AssemblyFileVersion("1.22.1.0")]
|
[assembly: AssemblyFileVersion("1.23.0.0")]
|
||||||
|
|||||||
7
Owin.Security.Providers/Shopify/Constants.cs
Normal file
7
Owin.Security.Providers/Shopify/Constants.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Owin.Security.Providers.Shopify
|
||||||
|
{
|
||||||
|
internal static class Constants
|
||||||
|
{
|
||||||
|
public const string DefaultAuthenticationType = "Shopify";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
230
Owin.Security.Providers/Shopify/ShopifyAuthenticationHandler.cs
Normal file
230
Owin.Security.Providers/Shopify/ShopifyAuthenticationHandler.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
129
Owin.Security.Providers/Shopify/ShopifyAuthenticationOptions.cs
Normal file
129
Owin.Security.Providers/Shopify/ShopifyAuthenticationOptions.cs
Normal 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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,7 @@ using Owin.Security.Providers.SoundCloud;
|
|||||||
using Owin.Security.Providers.Spotify;
|
using Owin.Security.Providers.Spotify;
|
||||||
using Owin.Security.Providers.StackExchange;
|
using Owin.Security.Providers.StackExchange;
|
||||||
using Owin.Security.Providers.Steam;
|
using Owin.Security.Providers.Steam;
|
||||||
|
using Owin.Security.Providers.Shopify;
|
||||||
using Owin.Security.Providers.TripIt;
|
using Owin.Security.Providers.TripIt;
|
||||||
using Owin.Security.Providers.Twitch;
|
using Owin.Security.Providers.Twitch;
|
||||||
using Owin.Security.Providers.Untappd;
|
using Owin.Security.Providers.Untappd;
|
||||||
@@ -54,7 +55,7 @@ namespace OwinOAuthProvidersDemo
|
|||||||
});
|
});
|
||||||
// Use a cookie to temporarily store information about a user logging in with a third party login provider
|
// Use a cookie to temporarily store information about a user logging in with a third party login provider
|
||||||
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
|
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
|
||||||
//app.UseDeviantArtAuthentication("id", "secret");
|
//app.UseDeviantArtAuthentication("id", "secret");
|
||||||
//app.UseUntappdAuthentication("id", "secret");
|
//app.UseUntappdAuthentication("id", "secret");
|
||||||
// Uncomment the following lines to enable logging in with third party login providers
|
// Uncomment the following lines to enable logging in with third party login providers
|
||||||
//app.UseMicrosoftAccountAuthentication(
|
//app.UseMicrosoftAccountAuthentication(
|
||||||
@@ -186,6 +187,8 @@ namespace OwinOAuthProvidersDemo
|
|||||||
//};
|
//};
|
||||||
//app.UseSalesforceAuthentication(salesforceOptions);
|
//app.UseSalesforceAuthentication(salesforceOptions);
|
||||||
|
|
||||||
|
////app.UseShopifyAuthentication("", "");
|
||||||
|
|
||||||
//app.UseArcGISOnlineAuthentication(
|
//app.UseArcGISOnlineAuthentication(
|
||||||
// clientId: "",
|
// clientId: "",
|
||||||
// clientSecret: "");
|
// clientSecret: "");
|
||||||
@@ -202,7 +205,6 @@ namespace OwinOAuthProvidersDemo
|
|||||||
// clientId: "",
|
// clientId: "",
|
||||||
// clientSecret: "");
|
// clientSecret: "");
|
||||||
|
|
||||||
|
|
||||||
//app.UseBattleNetAuthentication(new BattleNetAuthenticationOptions
|
//app.UseBattleNetAuthentication(new BattleNetAuthenticationOptions
|
||||||
//{
|
//{
|
||||||
// ClientId = "",
|
// ClientId = "",
|
||||||
@@ -273,11 +275,22 @@ namespace OwinOAuthProvidersDemo
|
|||||||
|
|
||||||
//app.UseBacklogAuthentication(options);
|
//app.UseBacklogAuthentication(options);
|
||||||
|
|
||||||
// app.UseFitbitAuthentication(new FitbitAuthenticationOptions
|
//var cosignOptions = new CosignAuthenticationOptions
|
||||||
// {
|
//{
|
||||||
// ClientId = "",
|
// AuthenticationType = "Cosign",
|
||||||
// ClientSecret = ""
|
// SignInAsAuthenticationType = signInAsType,
|
||||||
// });
|
// CosignServer = "weblogin.umich.edu",
|
||||||
|
// CosignServicePort = 6663,
|
||||||
|
// IdentityServerHostInstance = "core1",
|
||||||
|
// ClientServer = "cosignservername"
|
||||||
|
//};
|
||||||
|
//app.UseCosignAuthentication(cosignOptions);
|
||||||
|
|
||||||
|
//app.UseFitbitAuthentication(new FitbitAuthenticationOptions
|
||||||
|
//{
|
||||||
|
// ClientId = "",
|
||||||
|
// ClientSecret = ""
|
||||||
|
//});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -182,15 +182,26 @@ namespace OwinOAuthProvidersDemo.Controllers
|
|||||||
return View(model);
|
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
|
// POST: /Account/ExternalLogin
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[ValidateAntiForgeryToken]
|
[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
|
// 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
|
// POST: /Account/LinkLogin
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[ValidateAntiForgeryToken]
|
[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
|
// 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
|
#region Helpers
|
||||||
// Used for XSRF protection when adding external logins
|
// Used for XSRF protection when adding external logins
|
||||||
private const string XsrfKey = "XsrfId";
|
private const string XsrfKey = "XsrfId";
|
||||||
|
// Used for Shopify external login to provide shopname while building endpoints.
|
||||||
|
private const string ShopNameKey = "ShopName";
|
||||||
|
|
||||||
private IAuthenticationManager AuthenticationManager
|
private IAuthenticationManager AuthenticationManager
|
||||||
{
|
{
|
||||||
@@ -380,28 +393,36 @@ namespace OwinOAuthProvidersDemo.Controllers
|
|||||||
|
|
||||||
private class ChallengeResult : HttpUnauthorizedResult
|
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;
|
LoginProvider = provider;
|
||||||
RedirectUri = redirectUri;
|
RedirectUri = redirectUri;
|
||||||
UserId = userId;
|
UserId = userId;
|
||||||
|
ShopName = shopName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string LoginProvider { get; set; }
|
public string LoginProvider { get; set; }
|
||||||
public string RedirectUri { get; set; }
|
public string RedirectUri { get; set; }
|
||||||
public string UserId { get; set; }
|
public string UserId { get; set; }
|
||||||
|
public string ShopName { get; set; }
|
||||||
|
|
||||||
public override void ExecuteResult(ControllerContext context)
|
public override void ExecuteResult(ControllerContext context)
|
||||||
{
|
{
|
||||||
var properties = new AuthenticationProperties() { RedirectUri = RedirectUri };
|
var properties = new AuthenticationProperties() { RedirectUri = RedirectUri };
|
||||||
if (UserId != null)
|
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);
|
context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,10 @@
|
|||||||
<p>
|
<p>
|
||||||
@foreach (AuthenticationDescription p in loginProviders)
|
@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>
|
<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>
|
</p>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ Provides a set of extra authentication providers for OWIN ([Project Katana](http
|
|||||||
- Backlog
|
- Backlog
|
||||||
- Battle.net
|
- Battle.net
|
||||||
- Buffer
|
- Buffer
|
||||||
|
- Cosign
|
||||||
- DeviantArt
|
- DeviantArt
|
||||||
- Dropbox
|
- Dropbox
|
||||||
- EVEOnline
|
- EVEOnline
|
||||||
@@ -25,6 +26,7 @@ Provides a set of extra authentication providers for OWIN ([Project Katana](http
|
|||||||
- PayPal
|
- PayPal
|
||||||
- Reddit
|
- Reddit
|
||||||
- Salesforce
|
- Salesforce
|
||||||
|
- Shopify
|
||||||
- Slack
|
- Slack
|
||||||
- SoundCloud
|
- SoundCloud
|
||||||
- Spotify
|
- Spotify
|
||||||
@@ -42,7 +44,7 @@ Provides a set of extra authentication providers for OWIN ([Project Katana](http
|
|||||||
- Wargaming
|
- Wargaming
|
||||||
|
|
||||||
## Implementation Guides
|
## 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
|
## Installation
|
||||||
To use these providers you will need to install the ```Owin.Security.Providers``` NuGet package.
|
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)
|
* Anthony Ruffino (https://github.com/AnthonyRuffino)
|
||||||
* Tommy Parnell (https://github.com/tparnell8)
|
* Tommy Parnell (https://github.com/tparnell8)
|
||||||
* Maxime Roussin-Bélanger (https://github.com/Lorac)
|
* 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
|
For most accurate and up to date list of contributors please see https://github.com/RockstarLabs/OwinOAuthProviders/graphs/contributors
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
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
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
Reference in New Issue
Block a user