Compare commits

..

53 Commits

Author SHA1 Message Date
dependabot[bot]
110da0d69e Bump Newtonsoft.Json in /src/Owin.Security.Providers.ArcGISOnline
Bumps [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json) from 8.0.3 to 13.0.1.
- [Release notes](https://github.com/JamesNK/Newtonsoft.Json/releases)
- [Commits](https://github.com/JamesNK/Newtonsoft.Json/compare/8.0.3...13.0.1)

---
updated-dependencies:
- dependency-name: Newtonsoft.Json
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-22 18:17:54 +00:00
Tommy Parnell
8b382b0429 bump version 2019-02-11 08:34:59 -05:00
Tommy Parnell
8e6fedf25b Merge pull request #254 from gwilymatgearset/add-google-signin-provider
Add Google Sign-In provider
2019-02-11 08:32:04 -05:00
Tommy Parnell
13bbd8a89f Merge pull request #253 from todorm85/master
Migration to LinkedIn v2.0 API (deadline March 2019)
2019-02-11 08:31:45 -05:00
Gwilym Kuiper
4400102d13 Change DefaultAuthenticationType to Google 2019-01-29 15:05:28 +00:00
Gwilym Kuiper
c9e6d86bc8 Update some comments about Google+ -> Google 2019-01-29 14:56:02 +00:00
Gwilym Kuiper
4edd2ae28a Fix up API usage changes for Google 2019-01-29 14:56:02 +00:00
Gwilym Kuiper
1301c6c0d2 Update GoogleAuthenticationOptions for Google Signin 2019-01-29 14:53:30 +00:00
Gwilym Kuiper
0c529e18fe Rename GooglePlus to Google 2019-01-29 14:53:30 +00:00
Gwilym Kuiper
334ba8664f Fix up namespaces from GooglePlus to Google 2019-01-29 14:53:30 +00:00
Gwilym Kuiper
c046b5d6e1 Copy GooglePlus project into its own Google project 2019-01-29 14:53:24 +00:00
Todor Mitskovski
f018253a4f Migration to LinkedIn v2.0 API
https://engineering.linkedin.com/blog/2018/12/developer-program-updates
2019-01-17 18:48:18 +02:00
Tommy Parnell
39ed336c4d bump version 2018-11-04 09:09:28 -05:00
Tommy Parnell
6850514ca4 Merge pull request #238 from sprice88/master
Adding Configurable Display type to Salesforce Auth Request
2018-11-04 09:06:19 -05:00
Tommy Parnell
17ae5efa21 Merge pull request #248 from woutervs/master
Discord authenticated context mapping
2018-11-04 09:05:05 -05:00
Wouter Van Speybroeck
b86f2b65d3 Fix verified mapping onto context ignoring case for boolean 2018-10-15 14:58:06 +02:00
Wouter Van Speybroeck
c6586f2809 Merge pull request #1 from TerribleDev/master
Merge base into fork
2018-10-15 14:53:32 +02:00
Tommy Parnell
bc06288ea2 Merge pull request #246 from odahcam/patch-1
Fix main heading markup
2018-08-13 05:55:51 -04:00
Luiz Machado
02fa70bbe3 fix main heading 2018-08-08 16:09:44 -03:00
Tommy Parnell
d5d7c19a30 bump version 2018-06-12 21:58:07 -04:00
Tommy Parnell
05cd9a4fab Merge pull request #239 from pdsrebelo/master
Add a Request Prefix to Options so that we don't rely always on Request.Scheme or Request.Host.
2018-06-12 21:57:30 -04:00
Pedro Rebelo
b4447fa319 Little fix on the logic of the last commit. 2018-06-04 16:49:34 +01:00
Pedro Rebelo
c9dd2cc062 Add a Request Prefix to TwitchAuthenticationOptions so that we don't rely always on Request.Scheme or Request.Host. 2018-06-04 16:33:07 +01:00
sprice88
b64e7e5d3a Merge pull request #1 from sprice88/configurable_DisplayMode
Added Display mode to SalesforceAuthenticationOptions
2018-05-08 22:30:18 +01:00
Steve Price
b34579f54c Added Display mode to SalesforceAuthenticationOptions 2018-05-08 22:28:23 +01:00
Tommy Parnell
22c830dbfb bump version 2018-05-01 19:51:22 -04:00
Tommy Parnell
06457050a8 Merge pull request #236 from JPRuskin/patch-1
Update Steam OpenID Regex to handle HTTPS
2018-05-01 19:50:30 -04:00
James Ruskin
35ea1c9d79 Update Steam OpenID Regex to handle HTTPS
Steam have updated their implementation to return https URIs in claimedID (please see [this thread](https://www.reddit.com/r/Steam/comments/8a7gsu/steam_openid_broken_for_many_websites_fix_inside/)).

The current version of the `_accountIDRegex` does not handle this.

This change should fix this. Arguably, we should not include the option to accept http, as Steam have (seemingly) irrevocably changed this.

This should solve [Issue #234](https://github.com/TerribleDev/OwinOAuthProviders/issues/234).
2018-04-30 11:49:04 +01:00
Tommy Parnell
900c80a98f bump version 2018-04-01 02:25:35 -04:00
Wouter Van Speybroeck
f60e8b5ffa NameIDentifier should use Id from context instead of username (#232) 2018-04-01 02:25:11 -04:00
Tommy Parnell
c74cd15808 bump version 2018-03-06 21:24:46 -05:00
imaleksandr
26a690060a The "v" parameter with version number becomes obligatory for all API requests. An error will be returned if the parameter is not sent. (#228) 2018-03-06 21:24:17 -05:00
Tommy Parnell
113692b2e0 2.20.0 2018-02-20 21:33:30 -05:00
Ștefan Negrițoiu
88bf254a7b introduce Typeform OAuth provider (#226)
* add Option to specify Production vs. Sandbox Environment

also fixes issue
https://github.com/TerribleDev/OwinOAuthProviders/issues/54

* ability to specify Production vs. Sandbox environment per auth session in addition to global setting

* add examples to show usage of new Production vs. Sandbox Option for Salesforce provider

* initial commit for Typeform provider

* add Typeform project to solution

* use GUID for UserId and add warning regaring use for authentication
2018-02-20 21:30:58 -05:00
bernardocotta
38aa4bbed2 Fixed LinkedIn authorization and token endpoints. (#224)
* Changed LinkedInAuthenticationHandler to use the endpoints described at the documentation.

The authorization code generated by the old endpoint is not supported by the Recommended Jobs API.
2018-02-20 21:29:12 -05:00
Tommy Parnell
29be30bfd7 bump version 2017-12-29 01:58:14 -05:00
Tommy Parnell
b8055739ff rm semicolon 2017-12-29 01:57:41 -05:00
Ștefan Negrițoiu
7aeca07f08 Salesforce provider: add Option to specify Production vs. Sandbox environment (#223)
* add Option to specify Production vs. Sandbox Environment

also fixes issue
https://github.com/TerribleDev/OwinOAuthProviders/issues/54

* ability to specify Production vs. Sandbox environment per auth session in addition to global setting

* add examples to show usage of new Production vs. Sandbox Option for Salesforce provider
2017-12-29 01:56:14 -05:00
Tommy Parnell
fde4b5ebac fix rake 2017-12-08 11:20:09 -05:00
Tommy Parnell
d5b81fae9c fix sln 2017-12-08 10:51:14 -05:00
Tommy Parnell
bbd9e33570 bump version 2017-12-08 10:38:01 -05:00
Gavin Harriss
7bdabb4cb7 ArcGIS Portal provider (#214)
* ArcGIS Portal provider added
Originally attempted to inherit from ArcGIS Online provider, but a fair amount of refactoring is required to allow inheritance for everything that needs changing. To be revisited when time allows.
2017-12-08 10:37:16 -05:00
KB
91dbf99949 Change LangVersion to 6. #217 (#221) 2017-12-01 17:22:30 -05:00
Tommy Parnell
b4bd9587dc bump version 2017-11-03 00:42:48 -04:00
Joseph Pisano
43ee292d0c I added an auth provider for the Podbean API. (#218)
* Added the podbean auth provider.

* Updated Readme to include podbean

* Fixed Owin nuget reference

* Removed regions from around using statements, corrected the VS version in the solution file and changed the Startup.Auth in the demo project to use spaces instead of tabs.

* Added the OAuth refresh token to the PodbeanAuthenticatedContext.
2017-11-03 00:40:41 -04:00
Tommy Parnell
b351a6bf0f bump version 2017-10-13 09:42:25 -04:00
AlastairBateman
49e1126bc7 Updating DropboxAuthenticatedContext.cs and DropboxAuthennticationHandler.cs to use the Dropbox v2 API as they're shutting down their v1 API: https://blogs.dropbox.com/developers/2017/09/api-v1-shutdown-details/ (#216)
As per issue: https://github.com/TerribleDev/OwinOAuthProviders/issues/215
2017-10-13 09:32:16 -04:00
Hoku Kim
ba08bffec3 Added LinkedInAuthenticationOptions.Proxy host for web applications running behind a proxy. This allows the middleware to authenticate for the proxy request host instead of the application request host. (#213) 2017-10-13 09:32:06 -04:00
Tommy Parnell
9bb2a75712 remove unused file 2017-07-16 11:48:21 -04:00
Tommy Parnell
60f3438ce7 strong name all the things 2017-07-16 11:48:03 -04:00
Tommy Parnell
346facbe2f bump version 2017-05-19 17:05:23 -04:00
bkrupa
934d75e9ad Added ability to force verify on twitch authentication (#202) 2017-05-19 16:52:05 -04:00
bencurthoys
feba5cc2fb PayPal TLS Fix (#206)
* PayPal TLS Fix

PayPal requires TLS 1.2 now - Added comments explaining this is the
case, and additional error handling for when the callback fails when TLS
1.0 or 1.1 is set.

* Removed Sandbox Credentials

Tch.

* Changed exception to WebException

If the PayPalAuthenticationHandler sees that an error caused by the
wrong SecurityProtocol has occurred, it throws a new WebException
instead of just Exception

* Reverted Whitespace changes to Startup.Auth.cs

Whitespace somehow got changed. I think this reverts it.
2017-05-19 16:51:53 -04:00
139 changed files with 5135 additions and 420 deletions

View File

@@ -1,17 +1,17 @@
GEM
remote: http://rubygems.org/
specs:
albacore (2.0.4)
albacore (3.0.1)
map (~> 6.5)
nokogiri (~> 1.5)
rake (> 10)
rake (~> 10)
semver2 (~> 3.4)
map (6.6.0)
mini_portile2 (2.0.0)
nokogiri (1.6.7.2-x64-mingw32)
mini_portile2 (~> 2.0.0.rc2)
os (0.9.6)
rake (11.1.1)
mini_portile2 (2.3.0)
nokogiri (1.8.2-x64-mingw32)
mini_portile2 (~> 2.3.0)
os (1.0.0)
rake (10.5.0)
semver2 (3.4.2)
PLATFORMS
@@ -23,4 +23,4 @@ DEPENDENCIES
os
BUNDLED WITH
1.12.1
1.13.7

View File

@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.12
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.ArcGISOnline", "src\Owin.Security.Providers.ArcGISOnline\Owin.Security.Providers.ArcGISOnline.csproj", "{8A49FAEF-D365-4D25-942C-1CAD03845A5E}"
EndProject
@@ -108,6 +108,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Eve
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.WSO2", "src\Owin.Security.Providers.WSO2\Owin.Security.Providers.WSO2.csproj", "{8FD3A9CB-E684-42C0-A8BF-7746FDD3D43C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Podbean", "src\Owin.Security.Providers.Podbean\Owin.Security.Providers.Podbean.csproj", "{A7B95FD4-08AD-499F-B574-07560CC2A63F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.ArcGISPortal", "src\Owin.Security.Providers.ArcGISPortal\Owin.Security.Providers.ArcGISPortal.csproj", "{18547CA4-D7D3-43C2-81C2-A21FC8151A93}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Typeform", "src\Owin.Security.Providers.Typeform\Owin.Security.Providers.Typeform.csproj", "{C8862B45-E1D1-4AB7-A83D-3A2FD2A22526}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Google", "src\Owin.Security.Providers.Google\Owin.Security.Providers.Google.csproj", "{ED434959-8CF8-4CAB-83B3-E4A618327AB5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -326,8 +334,27 @@ Global
{8FD3A9CB-E684-42C0-A8BF-7746FDD3D43C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8FD3A9CB-E684-42C0-A8BF-7746FDD3D43C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8FD3A9CB-E684-42C0-A8BF-7746FDD3D43C}.Release|Any CPU.Build.0 = Release|Any CPU
{A7B95FD4-08AD-499F-B574-07560CC2A63F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A7B95FD4-08AD-499F-B574-07560CC2A63F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A7B95FD4-08AD-499F-B574-07560CC2A63F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A7B95FD4-08AD-499F-B574-07560CC2A63F}.Release|Any CPU.Build.0 = Release|Any CPU
{18547CA4-D7D3-43C2-81C2-A21FC8151A93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{18547CA4-D7D3-43C2-81C2-A21FC8151A93}.Debug|Any CPU.Build.0 = Debug|Any CPU
{18547CA4-D7D3-43C2-81C2-A21FC8151A93}.Release|Any CPU.ActiveCfg = Release|Any CPU
{18547CA4-D7D3-43C2-81C2-A21FC8151A93}.Release|Any CPU.Build.0 = Release|Any CPU
{C8862B45-E1D1-4AB7-A83D-3A2FD2A22526}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C8862B45-E1D1-4AB7-A83D-3A2FD2A22526}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C8862B45-E1D1-4AB7-A83D-3A2FD2A22526}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C8862B45-E1D1-4AB7-A83D-3A2FD2A22526}.Release|Any CPU.Build.0 = Release|Any CPU
{ED434959-8CF8-4CAB-83B3-E4A618327AB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ED434959-8CF8-4CAB-83B3-E4A618327AB5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ED434959-8CF8-4CAB-83B3-E4A618327AB5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ED434959-8CF8-4CAB-83B3-E4A618327AB5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {EDFDB942-6583-4AD9-A868-B354B5BF07FC}
EndGlobalSection
EndGlobal

View File

@@ -3,6 +3,9 @@ using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using Owin;
using Owin.Security.Providers.Evernote;
using Owin.Security.Providers.PayPal;
using Owin.Security.Providers.ArcGISPortal;
using Owin.Security.Providers.Typeform;
namespace OwinOAuthProvidersDemo
{
@@ -19,283 +22,353 @@ namespace OwinOAuthProvidersDemo
});
// Use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
//app.UseDeviantArtAuthentication("id", "secret");
//app.UseUntappdAuthentication("id", "secret");
// Uncomment the following lines to enable logging in with third party login providers
//app.UseMicrosoftAccountAuthentication(
// clientId: "",
// clientSecret: "");
//app.UseDeviantArtAuthentication("id", "secret");
//app.UseUntappdAuthentication("id", "secret");
// Uncomment the following lines to enable logging in with third party login providers
//app.UseMicrosoftAccountAuthentication(
// clientId: "",
// clientSecret: "");
//app.UseTwitterAuthentication(
// consumerKey: "",
// consumerSecret: "");
//app.UseTwitterAuthentication(
// consumerKey: "",
// consumerSecret: "");
//app.UseFacebookAuthentication(
// appId: "",
// appSecret: "");
//app.UseFacebookAuthentication(
// appId: "",
// appSecret: "");
//app.UseGoogleAuthentication();
//app.UseGoogleAuthentication();
//app.UseLinkedInAuthentication("", "");
//app.UseLinkedInAuthentication("", "");
//app.UseYahooAuthentication("", "");
//app.UseYahooAuthentication("", "");
//app.UseTripItAuthentication("", "");
//app.UseTripItAuthentication("", "");
//app.UseGitHubAuthentication("", "");
//app.UseGitHubAuthentication("", "");
//app.UseBufferAuthentication("", "");
//app.UseBufferAuthentication("", "");
//app.UseRedditAuthentication("", "");
//app.UseRedditAuthentication("", "");
//app.UseStackExchangeAuthentication(
// clientId: "",
// clientSecret: "",
// key: "");
//app.UseStackExchangeAuthentication(
// clientId: "",
// clientSecret: "",
// key: "");
//app.UseInstagramInAuthentication("", "");
//app.UseInstagramInAuthentication("", "");
//var options = new GooglePlusAuthenticationOptions
//{
// ClientId = "",
// ClientSecret = "",
// RequestOfflineAccess = true,
// Provider = new GooglePlusAuthenticationProvider
// {
// OnAuthenticated = async context => System.Diagnostics.Debug.WriteLine(String.Format("Refresh Token: {0}", context.RefreshToken))
// }
//};
//options.MomentTypes.Add("http://schemas.google.com/AddActivity");
//options.MomentTypes.Add("http://schemas.google.com/CheckInActivity");
//options.MomentTypes.Add("http://schemas.google.com/BuyActivity");
//app.UseGooglePlusAuthentication(options);
//var options = new GooglePlusAuthenticationOptions
//{
// ClientId = "",
// ClientSecret = "",
// RequestOfflineAccess = true,
// Provider = new GooglePlusAuthenticationProvider
// {
// OnAuthenticated = async context => System.Diagnostics.Debug.WriteLine(String.Format("Refresh Token: {0}", context.RefreshToken))
// }
//};
//options.MomentTypes.Add("http://schemas.google.com/AddActivity");
//options.MomentTypes.Add("http://schemas.google.com/CheckInActivity");
//options.MomentTypes.Add("http://schemas.google.com/BuyActivity");
//app.UseGooglePlusAuthentication(options);
/*
/*
* Twitch sign-ins use /signin-Twitch as the URL for authentication
*
*/
////Simple Twitch Sign-in
//app.UseTwitchAuthentication("", "");
////More complex Twitch Sign-in
//var opt = new TwitchAuthenticationOptions()
//{
// ClientId = "",
// ClientSecret = "",
// Provider = new TwitchAuthenticationProvider()
// {
// OnAuthenticated = async z =>
// {
//// Getting the twitch users picture
// z.Identity.AddClaim(new Claim("Picture", z.User.GetValue("logo").ToString()));
// }
//// You should be able to access these claims with HttpContext.GetOwinContext().Authentication.GetExternalLoginInfoAsync().Claims in your Account Controller
// // Commonly used in the ExternalLoginCallback() in AccountController.cs
// /*
////Simple Twitch Sign-in
//app.UseTwitchAuthentication("", "");
// if (user != null)
// {
// var claim = (await AuthenticationManager.GetExternalLoginInfoAsync()).ExternalIdentity.Claims.First(
// a => a.Type == "Picture");
// user.Claims.Add(new IdentityUserClaim() { ClaimType = claim.Type, ClaimValue = claim.Value });
// await SignInAsync(user, isPersistent: false);
// return RedirectToLocal(returnUrl);
// }
// */
// }
//};
//app.UseTwitchAuthentication(opt);
////More complex Twitch Sign-in
//var opt = new TwitchAuthenticationOptions()
//{
// ClientId = "",
// ClientSecret = "",
// Provider = new TwitchAuthenticationProvider()
// {
// OnAuthenticated = async z =>
// {
//// Getting the twitch users picture
// z.Identity.AddClaim(new Claim("Picture", z.User.GetValue("logo").ToString()));
// }
//// You should be able to access these claims with HttpContext.GetOwinContext().Authentication.GetExternalLoginInfoAsync().Claims in your Account Controller
// // Commonly used in the ExternalLoginCallback() in AccountController.cs
// /*
//app.UseOpenIDAuthentication("http://me.yahoo.com/", "Yahoo");
// if (user != null)
// {
// var claim = (await AuthenticationManager.GetExternalLoginInfoAsync()).ExternalIdentity.Claims.First(
// a => a.Type == "Picture");
// user.Claims.Add(new IdentityUserClaim() { ClaimType = claim.Type, ClaimValue = claim.Value });
// await SignInAsync(user, isPersistent: false);
// return RedirectToLocal(returnUrl);
// }
// */
// }
//};
//app.UseTwitchAuthentication(opt);
//app.UseOpenIDAuthentication("https://openid.stackexchange.com/", "StackExchange");
//app.UseOpenIDAuthentication("http://me.yahoo.com/", "Yahoo");
//app.UseOpenIDAuthentication("https://www.google.com/accounts/o8/id", "Google");
//app.UseOpenIDAuthentication("https://openid.stackexchange.com/", "StackExchange");
//app.UseSteamAuthentication(applicationKey: "");
//app.UseOpenIDAuthentication("https://www.google.com/accounts/o8/id", "Google");
//app.UseOpenIDAuthentication("http://orange.fr", "Orange");
// Use OpenId provider login uri instead of discovery uri
//app.UseOpenIDAuthentication("http://openid.orange.fr/server", "Orange", true);
//app.UseSteamAuthentication(applicationKey: "");
//app.UseSalesforceAuthentication(
// clientId: "",
// clientSecret: "");
//app.UseOpenIDAuthentication("http://orange.fr", "Orange");
// Use OpenId provider login uri instead of discovery uri
//app.UseOpenIDAuthentication("http://openid.orange.fr/server", "Orange", true);
//in scenarios where a sandbox URL needs to be used
//var salesforceOptions = new SalesforceAuthenticationOptions
//{
// Endpoints =
// new SalesforceAuthenticationOptions.SalesforceAuthenticationEndpoints
// {
// AuthorizationEndpoint =
// "https://ap1.salesforce.com/services/oauth2/authorize",
// TokenEndpoint = "https://ap1.salesforce.com/services/oauth2/token"
// },
// ClientId = "",
// ClientSecret = "",
// Provider = new SalesforceAuthenticationProvider()
// {
// OnAuthenticated = async context =>
// {
// System.Diagnostics.Debug.WriteLine(context.AccessToken);
// System.Diagnostics.Debug.WriteLine(context.RefreshToken);
// System.Diagnostics.Debug.WriteLine(context.OrganizationId);
// }
// }
//};
//app.UseSalesforceAuthentication(salesforceOptions);
//app.UseSalesforceAuthentication(
// clientId: "",
// clientSecret: "");
////app.UseShopifyAuthentication("", "");
// Salesforce Option 1: don't specify explicit Endpoint config and use Production endpoint defaults
//var salesforceOptions1 = new SalesforceAuthenticationOptions
//{
// ClientId = "",
// ClientSecret = "",
// Provider = new SalesforceAuthenticationProvider()
// {
// OnAuthenticated = async context =>
// {
// System.Diagnostics.Debug.WriteLine(context.AccessToken);
// System.Diagnostics.Debug.WriteLine(context.RefreshToken);
// System.Diagnostics.Debug.WriteLine(context.OrganizationId);
// }
// }
//};
//app.UseArcGISOnlineAuthentication(
// clientId: "",
// clientSecret: "");
// Salesforce Option 2: ask for Sandbox environment; no need to know what those endpoints are
//var salesforceOptions2 = new SalesforceAuthenticationOptions
//{
// Endpoints =
// new SalesforceAuthenticationOptions.SalesforceAuthenticationEndpoints
// {
// Environment = Owin.Security.Providers.Salesforce.Constants.SandboxEnvironment
// },
// ClientId = "",
// ClientSecret = "",
// Provider = new SalesforceAuthenticationProvider()
// {
// OnAuthenticated = async context =>
// {
// System.Diagnostics.Debug.WriteLine(context.AccessToken);
// System.Diagnostics.Debug.WriteLine(context.RefreshToken);
// System.Diagnostics.Debug.WriteLine(context.OrganizationId);
// }
// }
//};
//app.UseWordPressAuthentication(
// clientId: "",
// clientSecret: "");
// Salesforce Option 3: explicitly specify endpoints (will take precedence over Environment choice)
//var salesforceOptions3 = new SalesforceAuthenticationOptions
//{
// Endpoints =
// new SalesforceAuthenticationOptions.SalesforceAuthenticationEndpoints
// {
// AuthorizationEndpoint = "https://ap1.salesforce.com/services/oauth2/authorize",
// TokenEndpoint = "https://ap1.salesforce.com/services/oauth2/token"
// },
// ClientId = "",
// ClientSecret = "",
// Provider = new SalesforceAuthenticationProvider()
// {
// OnAuthenticated = async context =>
// {
// System.Diagnostics.Debug.WriteLine(context.AccessToken);
// System.Diagnostics.Debug.WriteLine(context.RefreshToken);
// System.Diagnostics.Debug.WriteLine(context.OrganizationId);
// }
// }
//};
//app.UseSalesforceAuthentication(salesforceOptions1);
//app.UseDropboxAuthentication(
// appKey: "",
// appSecret: "");
////app.UseShopifyAuthentication("", "");
//app.UseHealthGraphAuthentication(
// clientId: "",
// clientSecret: "");
//app.UseArcGISOnlineAuthentication(
// clientId: "",
// clientSecret: "");
//app.UseBoxAuthentication(
// appKey: "",
// appSecret: "");
//app.UseArcGISPortalAuthentication(new ArcGISPortalAuthenticationOptions(
// "My ArcGIS Portal",
// "https://arcgisportal.mydomain.com/",
// "",
// ""));
//app.UseBaiduAuthentication(
// apiKey: "",
// secretKey: "");
//app.UseWordPressAuthentication(
// clientId: "",
// clientSecret: "");
//app.UseBattleNetAuthentication(new BattleNetAuthenticationOptions
//{
// ClientId = "",
// ClientSecret = ""
//});
//app.UseBattleNetAuthentication(
// clientId: "",
// clientSecret: "");
//app.UseDropboxAuthentication(
// appKey: "",
// appSecret: "");
//app.UseAsanaAuthentication("", "");
//app.UseHealthGraphAuthentication(
// clientId: "",
// clientSecret: "");
//app.UseEveOnlineAuthentication("", "");
//app.UseBoxAuthentication(
// appKey: "",
// appSecret: "");
//app.UseSoundCloudAuthentication("", "");
//app.UseBaiduAuthentication(
// apiKey: "",
// secretKey: "");
//app.UseFoursquareAuthentication(
// clientId: "",
// clientSecret: "");
//app.UseBattleNetAuthentication(new BattleNetAuthenticationOptions
//{
// ClientId = "",
// ClientSecret = ""
//});
//app.UseBattleNetAuthentication(
// clientId: "",
// clientSecret: "");
//app.UsePayPalAuthentication(
// clientId: "",
// clientSecret: "",
// isSandbox: false);
//app.UseAsanaAuthentication("", "");
//app.UseWargamingAccountAuthentication("", WargamingAuthenticationOptions.Region.NorthAmerica);
//app.UseEveOnlineAuthentication("", "");
//app.UseFlickrAuthentication("", "");
//app.UseVisualStudioAuthentication(
// appId: "",
// appSecret: "");
//app.UseSoundCloudAuthentication("", "");
//app.UseSpotifyAuthentication(
// clientId: "",
// clientSecret: "");
//app.UseFoursquareAuthentication(
// clientId: "",
// clientSecret: "");
//var options = new SlackAuthenticationOptions
//{
// ClientId = "",
// ClientSecret = "",
// TeamId = "" // optional
//};
//options.Scope.Add("identify");
//app.UseSlackAuthentication(options);
// PayPal no longer supports Tls 1.0 or 1.1
// https://devblog.paypal.com/upcoming-security-changes-notice/
// http://stackoverflow.com/questions/34939523/the-request-was-aborted-could-not-create-ssl-tls-secure-channel-sandbox-account
// System.Net.ServicePointManager.SecurityProtocol = System.Net.ServicePointManager.SecurityProtocol | System.Net.SecurityProtocolType.Tls12;
// The Return URL must be configured in the PayPal application to https://[hostname][:port]/signin-paypal
// PayPalAuthenticationOptions payPalOptions = new PayPalAuthenticationOptions(true);
// payPalOptions.ClientId = "";
// payPalOptions.ClientSecret = "";
// payPalOptions.Scope.Add("email");
// payPalOptions.Scope.Add("profile");
// payPalOptions.Scope.Add("address");
// payPalOptions.Scope.Add("https://uri.paypal.com/services/expresscheckout");
//app.UseGitterAuthentication(
// clientId: "",
// clientSecret: ""
//);
// app.UsePayPalAuthentication(payPalOptions);
//app.UseImgurAuthentication(
// new ImgurAuthenticationOptions
// {
// ClientId = "",
// ClientSecret = ""
// });
//app.UseWargamingAccountAuthentication("", WargamingAuthenticationOptions.Region.NorthAmerica);
//var options = new BacklogAuthenticationOptions
//{
// ClientId = "",
// ClientSecret = "",
// ContractName = "",
// CallbackPath = new PathString(""), // ex.new PathString("/OauthTokenRequest")
// Provider = new BacklogAuthenticationProvider
// {
// OnAuthenticated = async context => await System.Threading.Tasks.Task.Run(()=> { System.Diagnostics.Debug.WriteLine(String.Format("Refresh Token: {0}", context.RefreshToken)); })
// }
//};
//app.UseFlickrAuthentication("", "");
//app.UseVisualStudioAuthentication(
// appId: "",
// appSecret: "");
//app.UseBacklogAuthentication(options);
//app.UseSpotifyAuthentication(
// clientId: "",
// clientSecret: "");
//var cosignOptions = new CosignAuthenticationOptions
//{
// AuthenticationType = "Cosign",
// SignInAsAuthenticationType = signInAsType,
// CosignServer = "weblogin.umich.edu",
// CosignServicePort = 6663,
// IdentityServerHostInstance = "core1",
// ClientServer = "cosignservername"
//};
//app.UseCosignAuthentication(cosignOptions);
//var options = new SlackAuthenticationOptions
//{
// ClientId = "",
// ClientSecret = "",
// TeamId = "" // optional
//};
//options.Scope.Add("identify");
//app.UseSlackAuthentication(options);
//app.UseVimeoAuthentication("", "");
//app.UseGitterAuthentication(
// clientId: "",
// clientSecret: ""
//);
//app.UseFitbitAuthentication(new FitbitAuthenticationOptions
//{
// ClientId = "",
// ClientSecret = ""
//});
//app.UseImgurAuthentication(
// new ImgurAuthenticationOptions
// {
// ClientId = "",
// ClientSecret = ""
// });
//app.UseOnshapeAuthentication(
// appKey: "",
// appSecret: "");
//
//
//app.UseOnshapeAuthentication(new OnshapeAuthenticationOptions()
//{
// AppKey = "",
// AppSecret = "",
// CallbackPath = new PathString("/oauthRedirect"),
// Hostname = "partner.dev.onshape.com"
//});
//var options = new BacklogAuthenticationOptions
//{
// ClientId = "",
// ClientSecret = "",
// ContractName = "",
// CallbackPath = new PathString(""), // ex.new PathString("/OauthTokenRequest")
// Provider = new BacklogAuthenticationProvider
// {
// OnAuthenticated = async context => await System.Threading.Tasks.Task.Run(()=> { System.Diagnostics.Debug.WriteLine(String.Format("Refresh Token: {0}", context.RefreshToken)); })
// }
//};
//app.UseVKontakteAuthentication("", "");
//app.UseBacklogAuthentication(options);
//app.UseXingAuthentication("", "");
//var cosignOptions = new CosignAuthenticationOptions
//{
// AuthenticationType = "Cosign",
// SignInAsAuthenticationType = signInAsType,
// CosignServer = "weblogin.umich.edu",
// CosignServicePort = 6663,
// IdentityServerHostInstance = "core1",
// ClientServer = "cosignservername"
//};
//app.UseCosignAuthentication(cosignOptions);
//app.UseDoYouBuzzAuthentication("", "");
//app.("", "");
//app.UseOrcidAuthentication("","");
//app.UseVimeoAuthentication("", "");
//app.UseDiscordAuthentication("", "");
//app.UseGeniAuthentication("", "");
//app.UseMyHeritageAuthentication("", "");
//app.UseFitbitAuthentication(new FitbitAuthenticationOptions
//{
// ClientId = "",
// ClientSecret = ""
//});
//app.UseWSO2Authentication("", "", "");
//app.UseOnshapeAuthentication(
// appKey: "",
// appSecret: "");
//
//
//app.UseOnshapeAuthentication(new OnshapeAuthenticationOptions()
//{
// AppKey = "",
// AppSecret = "",
// CallbackPath = new PathString("/oauthRedirect"),
// Hostname = "partner.dev.onshape.com"
//});
//app.UseEvernoteAuthentication(new EvernoteAuthenticationOptions()
//{
// IsSandBox = true,
// AppKey = "",
// AppSecret = ""
//});
}
}
//app.UseVKontakteAuthentication("", "");
//app.UseXingAuthentication("", "");
//app.UseDoYouBuzzAuthentication("", "");
//app.("", "");
//app.UseOrcidAuthentication("","");
//app.UseDiscordAuthentication("", "");
//app.UseGeniAuthentication("", "");
//app.UseMyHeritageAuthentication("", "");
//app.UseWSO2Authentication("", "", "");
//app.UseEvernoteAuthentication(new EvernoteAuthenticationOptions()
//{
// IsSandBox = true,
// AppKey = "",
// AppSecret = ""
//});
//app.UsePodbeanAuthentication(new PodbeanAuthenticationOptions
//{
// AppId = "",
// AppSecret = "",
// DebugUsingRequestHeadersToBuildBaseUri = true
//});
// WARNING:
// Typeform doesn't supply the user's ID so use this provider for authorization only, not authentication
// because each time you sign in with the same Typeform account it will yield a distinct UserId.
//var typeformOptions = new Owin.Security.Providers.Typeform.TypeformAuthenticationOptions
//{
// ClientId = "",
// ClientSecret = "",
//};
//typeformOptions.Scope.Add("forms:read");
//app.UseTypeformAuthentication(typeformOptions);
}
}
}

View File

@@ -419,6 +419,13 @@ namespace OwinOAuthProvidersDemo.Controllers
properties.Dictionary[ShopNameKey] = ShopName;
}
// if use Salesforce as OAuth provider you can ask for Sandbox auth endpoint
// for this particular request only
//properties.Dictionary.Add(
// Owin.Security.Providers.Salesforce.Constants.EnvironmentAuthenticationProperty,
// Owin.Security.Providers.Salesforce.Constants.SandboxEnvironment
// );
context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);
}
}

View File

@@ -22,6 +22,7 @@
<IISExpressUseClassicPipelineMode>false</IISExpressUseClassicPipelineMode>
<UseGlobalApplicationHostFile />
<TargetFrameworkProfile />
<Use64BitIISExpress />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -31,6 +32,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -39,6 +41,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Antlr3.Runtime, Version=3.5.0.2, Culture=neutral, PublicKeyToken=eb42632606e9261f, processorArchitecture=MSIL">
@@ -252,14 +255,226 @@
<Content Include="Scripts\jquery-2.2.3.min.map" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\base\Owin.Security.Providers.OpenIDBase\Owin.Security.Providers.OpenIDBase.csproj">
<Project>{4fd7b873-1994-4990-aa40-c37060121494}</Project>
<Name>Owin.Security.Providers.OpenIDBase</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.ArcGISOnline\Owin.Security.Providers.ArcGISOnline.csproj">
<Project>{8a49faef-d365-4d25-942c-1cad03845a5e}</Project>
<Name>Owin.Security.Providers.ArcGISOnline</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.ArcGISPortal\Owin.Security.Providers.ArcGISPortal.csproj">
<Project>{18547ca4-d7d3-43c2-81c2-a21fc8151a93}</Project>
<Name>Owin.Security.Providers.ArcGISPortal</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Asana\Owin.Security.Providers.Asana.csproj">
<Project>{f3e27220-1d8c-4037-94aa-7b7f4a12f351}</Project>
<Name>Owin.Security.Providers.Asana</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Backlog\Owin.Security.Providers.Backlog.csproj">
<Project>{2dc03778-9ef1-466a-83ec-7d8422decd23}</Project>
<Name>Owin.Security.Providers.Backlog</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Baidu\Owin.Security.Providers.Baidu.csproj">
<Project>{e2759807-4d7c-4288-aac8-f5b7b4616680}</Project>
<Name>Owin.Security.Providers.Baidu</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.BattleNet\Owin.Security.Providers.BattleNet.csproj">
<Project>{99a175da-ade4-436c-a272-c8ae44b7a086}</Project>
<Name>Owin.Security.Providers.BattleNet</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Bitbucket\Owin.Security.Providers.Bitbucket.csproj">
<Project>{e5212fc7-abcb-462f-9989-8e022dffe43c}</Project>
<Name>Owin.Security.Providers.Bitbucket</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Box\Owin.Security.Providers.Box.csproj">
<Project>{1aef8813-e1f9-41e1-bc8d-732960595e9f}</Project>
<Name>Owin.Security.Providers.Box</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Buffer\Owin.Security.Providers.Buffer.csproj">
<Project>{6f75fc1f-d9e9-49b3-a6ce-cfa8feea11a5}</Project>
<Name>Owin.Security.Providers.Buffer</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Cosign\Owin.Security.Providers.Cosign.csproj">
<Project>{1f1f8d6b-7219-46fa-93d3-8d3061a6cbbf}</Project>
<Name>Owin.Security.Providers.Cosign</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.DeviantArt\Owin.Security.Providers.DeviantArt.csproj">
<Project>{fabd2e54-976d-41f5-8800-dee58acc027c}</Project>
<Name>Owin.Security.Providers.DeviantArt</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Discord\Owin.Security.Providers.Discord.csproj">
<Project>{4be728eb-778a-41af-8dea-0c7159711d44}</Project>
<Name>Owin.Security.Providers.Discord</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.DoYouBuzz\Owin.Security.Providers.DoYouBuzz.csproj">
<Project>{4550d8bd-05a7-44f8-bbc0-c3d8e7af2912}</Project>
<Name>Owin.Security.Providers.DoYouBuzz</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Dropbox\Owin.Security.Providers.Dropbox.csproj">
<Project>{cef697b1-3651-49e5-9060-65f2e26c039c}</Project>
<Name>Owin.Security.Providers.Dropbox</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.EVEOnline\Owin.Security.Providers.EVEOnline.csproj">
<Project>{f5dc23f4-5042-4024-9e34-aca648602ba0}</Project>
<Name>Owin.Security.Providers.EVEOnline</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Evernote\Owin.Security.Providers.Evernote.csproj">
<Project>{b8f05057-8b9f-44ce-b983-99cb7c2e9e5a}</Project>
<Name>Owin.Security.Providers.Evernote</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Fitbit\Owin.Security.Providers.Fitbit.csproj">
<Project>{ca44d014-5a74-4749-a891-1f711fd3a266}</Project>
<Name>Owin.Security.Providers.Fitbit</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Flickr\Owin.Security.Providers.Flickr.csproj">
<Project>{af6cbeb8-5638-43d4-839e-c81f305960be}</Project>
<Name>Owin.Security.Providers.Flickr</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Foursquare\Owin.Security.Providers.Foursquare.csproj">
<Project>{8acd9194-1efe-4128-ac42-856d856332a4}</Project>
<Name>Owin.Security.Providers.Foursquare</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Geni\Owin.Security.Providers.Geni.csproj">
<Project>{9de25431-f935-48d7-8eb5-acb6f918111c}</Project>
<Name>Owin.Security.Providers.Geni</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.GitHub\Owin.Security.Providers.GitHub.csproj">
<Project>{803f9eb7-029c-45ac-ab81-135e60d5beae}</Project>
<Name>Owin.Security.Providers.GitHub</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Gitter\Owin.Security.Providers.Gitter.csproj">
<Project>{42ec50eb-0c51-460c-93a4-1e007bf1f323}</Project>
<Name>Owin.Security.Providers.Gitter</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.GooglePlus\Owin.Security.Providers.GooglePlus.csproj">
<Project>{d3fef959-0e0e-4f50-954c-f123a0b629dc}</Project>
<Name>Owin.Security.Providers.GooglePlus</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.HealthGraph\Owin.Security.Providers.HealthGraph.csproj">
<Project>{157bb715-29b2-4202-8a59-ccbacfcbedd3}</Project>
<Name>Owin.Security.Providers.HealthGraph</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Imgur\Owin.Security.Providers.Imgur.csproj">
<Project>{101841d3-645e-4a44-af8b-8aaa85ceea4e}</Project>
<Name>Owin.Security.Providers.Imgur</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Instagram\Owin.Security.Providers.Instagram.csproj">
<Project>{041178c4-6131-4d68-9896-ce33124d83a0}</Project>
<Name>Owin.Security.Providers.Instagram</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.LinkedIn\Owin.Security.Providers.LinkedIn.csproj">
<Project>{9fa87825-30e9-48d7-ac4a-39e8f0c2777c}</Project>
<Name>Owin.Security.Providers.LinkedIn</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.MyHeritage\Owin.Security.Providers.MyHeritage.csproj">
<Project>{84795078-31b5-4369-bd1b-f960165f8c71}</Project>
<Name>Owin.Security.Providers.MyHeritage</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Onshape\Owin.Security.Providers.Onshape.csproj">
<Project>{9fec99f8-6f45-40a2-8200-85381434c79a}</Project>
<Name>Owin.Security.Providers.Onshape</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.OpenID\Owin.Security.Providers.OpenID.csproj">
<Project>{90c152d7-9c66-4949-9998-c7ce48b593de}</Project>
<Name>Owin.Security.Providers.OpenID</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Orcid\Owin.Security.Providers.Orcid.csproj">
<Project>{89cb4342-e23d-4e7c-89e5-c369599a5860}</Project>
<Name>Owin.Security.Providers.Orcid</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.PayPal\Owin.Security.Providers.PayPal.csproj">
<Project>{f7129064-3db7-4b79-81d3-80130d664e45}</Project>
<Name>Owin.Security.Providers.PayPal</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Podbean\Owin.Security.Providers.Podbean.csproj">
<Project>{a7b95fd4-08ad-499f-b574-07560cc2a63f}</Project>
<Name>Owin.Security.Providers.Podbean</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Reddit\Owin.Security.Providers.Reddit.csproj">
<Project>{d0cd86c8-a6f9-4c6c-9bf0-eaa461e7fbad}</Project>
<Name>Owin.Security.Providers.Reddit</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Salesforce\Owin.Security.Providers.Salesforce.csproj">
<Project>{827a9d68-0dd4-4c5e-b763-8302faeedecc}</Project>
<Name>Owin.Security.Providers.Salesforce</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Shopify\Owin.Security.Providers.Shopify.csproj">
<Project>{67f12bfb-eb3a-4a86-b5dc-f4c066fdf792}</Project>
<Name>Owin.Security.Providers.Shopify</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.SlackProvider\Owin.Security.Providers.SlackProvider.csproj">
<Project>{3e6f293d-8500-428d-bdc9-27440cc91e16}</Project>
<Name>Owin.Security.Providers.SlackProvider</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.SoundCloud\Owin.Security.Providers.SoundCloud.csproj">
<Project>{2c959026-7058-4302-a6c4-dfd10a030585}</Project>
<Name>Owin.Security.Providers.SoundCloud</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Spotify\Owin.Security.Providers.Spotify.csproj">
<Project>{683b4041-a399-40ce-84b8-392f08a6805d}</Project>
<Name>Owin.Security.Providers.Spotify</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.StackExchange\Owin.Security.Providers.StackExchange.csproj">
<Project>{2c0e07ed-f26d-4ff8-8c3d-f760c09a2d5a}</Project>
<Name>Owin.Security.Providers.StackExchange</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Steam\Owin.Security.Providers.Steam.csproj">
<Project>{312c4ed7-8ca1-4723-9203-abc694dfdc7c}</Project>
<Name>Owin.Security.Providers.Steam</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.TripIt\Owin.Security.Providers.TripIt.csproj">
<Project>{b35e2616-dc00-48b4-bd58-7e23046257f1}</Project>
<Name>Owin.Security.Providers.TripIt</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Twitch\Owin.Security.Providers.Twitch.csproj">
<Project>{c3cf8734-6aac-4f59-9a3e-1cba8582cd48}</Project>
<Name>Owin.Security.Providers.Twitch</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Typeform\Owin.Security.Providers.Typeform.csproj">
<Project>{c8862b45-e1d1-4ab7-a83d-3a2fd2a22526}</Project>
<Name>Owin.Security.Providers.Typeform</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Untappd\Owin.Security.Providers.Untappd.csproj">
<Project>{3e89eca3-f4e7-4181-b26b-8250d5151044}</Project>
<Name>Owin.Security.Providers.Untappd</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Vimeo\Owin.Security.Providers.Vimeo.csproj">
<Project>{98ecc703-d651-4ead-a55d-aa3e903ae4d7}</Project>
<Name>Owin.Security.Providers.Vimeo</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.VisualStudio\Owin.Security.Providers.VisualStudio.csproj">
<Project>{3b19fa31-ddff-427f-9d73-f860de74bbc2}</Project>
<Name>Owin.Security.Providers.VisualStudio</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.VKontakte\Owin.Security.Providers.VKontakte.csproj">
<Project>{32d70e31-3799-482a-ac7a-081ff9206fc3}</Project>
<Name>Owin.Security.Providers.VKontakte</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Wargaming\Owin.Security.Providers.Wargaming.csproj">
<Project>{aa72bfce-8495-4a4d-988d-f8d490521776}</Project>
<Name>Owin.Security.Providers.Wargaming</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.WordPress\Owin.Security.Providers.WordPress.csproj">
<Project>{0ede8223-dd5f-4db8-a98a-64b1f4591f48}</Project>
<Name>Owin.Security.Providers.WordPress</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.WSO2\Owin.Security.Providers.WSO2.csproj">
<Project>{8fd3a9cb-e684-42c0-a8bf-7746fdd3d43c}</Project>
<Name>Owin.Security.Providers.WSO2</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Xing\Owin.Security.Providers.Xing.csproj">
<Project>{d497d8bd-6ef9-4c30-b195-b0dd153418d6}</Project>
<Name>Owin.Security.Providers.Xing</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Yahoo\Owin.Security.Providers.Yahoo.csproj">
<Project>{1765bedb-9e4b-468c-baf6-06784cdced67}</Project>
<Name>Owin.Security.Providers.Yahoo</Name>
</ProjectReference>
<ProjectReference Include="..\src\Owin.Security.Providers.Yammer\Owin.Security.Providers.Yammer.csproj">
<Project>{8d029a93-e687-4ddf-82b0-700ebbf477f7}</Project>
<Name>Owin.Security.Providers.Yammer</Name>
</ProjectReference>
</ItemGroup>
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>

View File

@@ -1,10 +1,11 @@
[![Build status](https://ci.appveyor.com/api/projects/status/gjlkpp86t8dw164f?svg=true)](https://ci.appveyor.com/project/tparnell8/owinoauthproviders)
#OWIN OAuth Providers
# OWIN OAuth Providers
Provides a set of extra authentication providers for OWIN ([Project Katana](http://katanaproject.codeplex.com/)). This project includes providers for:
- OAuth
- ArcGISOnline
- ArcGISPortal
- Asana
- Backlog
- Battle.net
@@ -29,6 +30,7 @@ Provides a set of extra authentication providers for OWIN ([Project Katana](http
- Onshape
- ORCID
- PayPal
- Podbean
- Reddit
- Salesforce
- Shopify

View File

@@ -9,13 +9,13 @@ require 'nokogiri'
require 'openssl'
import 'nuget.rake'
CLEAN.include(['src/**/obj', 'src/**/bin', 'tool', 'packages/**','src/**/*.nuspec', 'src/**/*.nupkg', 'tools', 'packages', '*.nupkg'])
CLEAN.include(['src/**/obj', 'Owin.Security.Providers.nuspec', 'Owin.Security.Providers-signed.nuspec' 'src/**/bin', 'tool', 'packages/**','src/**/*.nuspec', 'src/**/*.nupkg', 'tools', 'packages', '*.nupkg'])
Configuration = ENV['CONFIGURATION'] || 'Release'
PACKAGES = File.expand_path("packages")
TOOLS = File.expand_path("tools")
NUGET = File.expand_path("#{TOOLS}/nuget")
NUGET_EXE = File.expand_path("#{TOOLS}/nuget/nuget.exe")
@version = "2.12.0"
@version = "2.26.0"
PROJECTS = Dir.glob('src/*').select{|dir| File.directory? dir }
desc 'Retrieve things'
@@ -31,12 +31,15 @@ task :preflight => [:clean, :build, :nuspec_gen]
desc 'publish'
task :publish => [:preflight,:nuspec_gen, :nuspec_pack, :nuspec_publish]
build :compile do |t|
t.prop 'Configuration', Configuration
t.sln = 'OwinOAuthProviders.sln'
t.prop 'SignAssembly', 'true'
t.prop 'AssemblyOriginatorKeyFile', File.expand_path("keypair.snk")
end
desc "Generate nuspec files"
task :nuspec_gen do
template = ERB.new(File.read('nuspectemplate.nuspec.erb'))
@@ -51,6 +54,7 @@ task :nuspec_gen do
File.write('Owin.Security.Providers.nuspec', ERB.new(File.read('global.nuspec.erb')).result())
end
desc 'pack nuspec files'
task :nuspec_pack do
PROJECTS.each{|dir|
@@ -61,12 +65,13 @@ task :nuspec_pack do
sh "#{NUGET_EXE} pack Owin.Security.Providers.nuspec -Exclude \"**\""
end
desc 'publish nugets'
task :nuspec_publish do
PROJECTS.each{|dir|
Dir.chdir(dir) do
sh "#{NUGET_EXE} push #{FileList["*.nupkg"].first}"
sh "#{NUGET_EXE} push -Source https://api.nuget.org/v3/index.json #{FileList["*.nupkg"].first}"
end
}
sh "#{NUGET_EXE} push #{FileList["*.nupkg"].first}"
sh "#{NUGET_EXE} push -Source https://api.nuget.org/v3/index.json #{FileList["*.nupkg"].first}"
end

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
@@ -89,6 +91,10 @@
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<PropertyGroup>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>..\..\keypair.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

View File

@@ -2,7 +2,7 @@
<package >
<metadata>
<id>Owin.Security.Providers.OpenIDBase</id>
<version>2.1</version>
<version>2.1.1</version>
<authors>Jerrie Pelser, Tommy Parnell and contributors</authors>
<owners>Tommy Parnell</owners>
<licenseUrl>http://opensource.org/licenses/MIT</licenseUrl>

BIN
keypair.snk Normal file

Binary file not shown.

View File

@@ -10,12 +10,12 @@ namespace :nuget do
begin
FileUtils.mkdir_p("#{NUGET}")
File.open("#{NUGET}/nuget.exe", "wb") do |file|
file.write open('http://nuget.org/nuget.exe', {ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE}).read
file.write open('https://dist.nuget.org/win-x86-commandline/latest/nuget.exe', {ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE}).read
end
rescue
FileUtils.rm_rf("#{NUGET}/nuget.exe")
File.open("#{NUGET}/nuget.exe", "wb") do |file|
file.write open('https://dist.nuget.org/win-x86-commandline/v3.2.0/nuget.exe', {ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE}).read
file.write open('https://dist.nuget.org/win-x86-commandline/latest/nuget.exe', {ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE}).read
end
end
end

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

@@ -2,6 +2,6 @@
<packages>
<package id="Microsoft.Owin" version="3.0.1" targetFramework="net45" />
<package id="Microsoft.Owin.Security" version="3.0.1" targetFramework="net45" />
<package id="Newtonsoft.Json" version="8.0.3" targetFramework="net45" />
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net45" />
<package id="Owin" version="1.0" targetFramework="net45" />
</packages>

View File

@@ -0,0 +1,20 @@
using System;
namespace Owin.Security.Providers.ArcGISPortal
{
public static class ArcGISPortalAuthenticationExtensions
{
public static IAppBuilder UseArcGISPortalAuthentication(this IAppBuilder app,
ArcGISPortalAuthenticationOptions options)
{
if (app == null)
throw new ArgumentNullException(nameof(app));
if (options == null)
throw new ArgumentNullException(nameof(options));
app.Use(typeof(ArcGISPortalAuthenticationMiddleware), app, options);
return app;
}
}
}

View File

@@ -0,0 +1,234 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Owin.Infrastructure;
using Microsoft.Owin.Logging;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Infrastructure;
using Newtonsoft.Json;
namespace Owin.Security.Providers.ArcGISPortal
{
public class ArcGISPortalAuthenticationHandler : AuthenticationHandler<ArcGISPortalAuthenticationOptions>
{
private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string";
private readonly ILogger _logger;
private readonly HttpClient _httpClient;
private readonly string _host;
public ArcGISPortalAuthenticationHandler(HttpClient httpClient, ILogger logger, string host)
{
_httpClient = httpClient;
_logger = logger;
_host = host;
}
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 (values != null && values.Count == 1)
{
code = values[0];
}
values = query.GetValues("state");
if (values != null && values.Count == 1)
{
state = values[0];
}
properties = Options.StateDataFormat.Unprotect(state);
if (properties == null)
{
return null;
}
// OAuth2 10.12 CSRF
if (!ValidateCorrelationId(properties, _logger))
{
return new AuthenticationTicket(null, properties);
}
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>("grant_type", "authorization_code"),
new KeyValuePair<string, string>("code", code),
new KeyValuePair<string, string>("redirect_uri", redirectUri),
new KeyValuePair<string, string>("client_id", Options.ClientId),
new KeyValuePair<string, string>("client_secret", Options.ClientSecret)
};
// Request the token
var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.Endpoints.TokenEndpoint);
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;
var refreshToken = (string)response.refresh_token;
// Get the ArcGISPortal user
var userRequest = new HttpRequestMessage(HttpMethod.Get, Options.Endpoints.UserInfoEndpoint + "?f=json&token=" + Uri.EscapeDataString(accessToken));
userRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var userResponse = await _httpClient.SendAsync(userRequest, Request.CallCancelled);
userResponse.EnsureSuccessStatusCode();
text = await userResponse.Content.ReadAsStringAsync();
var user = JsonConvert.DeserializeObject<Provider.ArcGISPortalUser>(text);
var context = new ArcGISPortalAuthenticatedContext(Context, user, accessToken, refreshToken, _host)
{
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.Name))
{
context.Identity.AddClaim(new Claim("urn:ArcGISPortal:name", context.Name, XmlSchemaString, Options.AuthenticationType));
}
if (!string.IsNullOrEmpty(context.Link))
{
context.Identity.AddClaim(new Claim("urn:ArcGISPortal:url", context.Link, XmlSchemaString, Options.AuthenticationType));
}
context.Properties = properties;
await Options.Provider.Authenticated(context);
return new AuthenticationTicket(context.Identity, context.Properties);
}
catch (Exception ex)
{
_logger.WriteError(ex.Message);
}
return new AuthenticationTicket(null, properties);
}
protected override Task ApplyResponseChallengeAsync()
{
if (Response.StatusCode != 401)
{
return Task.FromResult<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;
}
GenerateCorrelationId(properties);
var state = Options.StateDataFormat.Protect(properties);
// comma separated
var scope = string.Join(",", Options.Scope);
var authorizationEndpoint =
Options.Endpoints.AuthorizationEndpoint +
"?client_id=" + Uri.EscapeDataString(Options.ClientId) +
"&response_type=" + Uri.EscapeDataString(scope) +
"&redirect_uri=" + Uri.EscapeDataString(redirectUri) +
"&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
var ticket = await AuthenticateAsync();
if (ticket == null)
{
_logger.WriteWarning("Invalid return state, unable to redirect.");
Response.StatusCode = 500;
return true;
}
var context = new ArcGISPortalReturnEndpointContext(Context, ticket)
{
SignInAsAuthenticationType = Options.SignInAsAuthenticationType,
RedirectUri = ticket.Properties.RedirectUri
};
await Options.Provider.ReturnEndpoint(context);
if (context.SignInAsAuthenticationType != null &&
context.Identity != null)
{
var grantIdentity = context.Identity;
if (!string.Equals(grantIdentity.AuthenticationType, context.SignInAsAuthenticationType, StringComparison.Ordinal))
{
grantIdentity = new ClaimsIdentity(grantIdentity.Claims, context.SignInAsAuthenticationType, grantIdentity.NameClaimType, grantIdentity.RoleClaimType);
}
Context.Authentication.SignIn(context.Properties, grantIdentity);
}
if (context.IsRequestCompleted || context.RedirectUri == null) return context.IsRequestCompleted;
var redirectUri = context.RedirectUri;
if (context.Identity == null)
{
// add a redirect hint that sign-in failed in some way
redirectUri = WebUtilities.AddQueryString(redirectUri, "error", "access_denied");
}
Response.Redirect(redirectUri);
context.RequestCompleted();
return context.IsRequestCompleted;
}
}
}

View File

@@ -0,0 +1,87 @@
using System;
using System.Globalization;
using System.Net.Http;
using Microsoft.Owin;
using Microsoft.Owin.Logging;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.DataHandler;
using Microsoft.Owin.Security.DataProtection;
using Microsoft.Owin.Security.Infrastructure;
namespace Owin.Security.Providers.ArcGISPortal
{
public class ArcGISPortalAuthenticationMiddleware : AuthenticationMiddleware<ArcGISPortalAuthenticationOptions>
{
private readonly HttpClient _httpClient;
private readonly ILogger _logger;
public ArcGISPortalAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app,
ArcGISPortalAuthenticationOptions options)
: base(next, options)
{
if (string.IsNullOrWhiteSpace(Options.Host))
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
Resources.Exception_OptionMustBeProvided, "Host"));
if (string.IsNullOrWhiteSpace(Options.ClientId))
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
Resources.Exception_OptionMustBeProvided, "ClientId"));
if (string.IsNullOrWhiteSpace(Options.ClientSecret))
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
Resources.Exception_OptionMustBeProvided, "ClientSecret"));
_logger = app.CreateLogger<ArcGISPortalAuthenticationMiddleware>();
if (Options.Provider == null)
Options.Provider = new ArcGISPortalAuthenticationProvider();
if (Options.StateDataFormat == null)
{
var dataProtector = app.CreateDataProtector(
typeof(ArcGISPortalAuthenticationMiddleware).FullName,
Options.AuthenticationType, "v2");
Options.StateDataFormat = new PropertiesDataFormat(dataProtector);
}
if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType))
Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType();
_httpClient = new HttpClient(ResolveHttpMessageHandler(Options))
{
Timeout = Options.BackchannelTimeout,
MaxResponseContentBufferSize = 1024 * 1024 * 10,
};
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin ArcGISPortal middleware");
_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.ArcGISPortal.ArcGISPortalAuthenticationOptions" /> supplied to the constructor.
/// </returns>
protected override AuthenticationHandler<ArcGISPortalAuthenticationOptions> CreateHandler()
{
return new ArcGISPortalAuthenticationHandler(_httpClient, _logger, Options.Host);
}
private static HttpMessageHandler ResolveHttpMessageHandler(ArcGISPortalAuthenticationOptions options)
{
var handler = options.BackchannelHttpHandler ?? new WebRequestHandler();
// If they provided a validator, apply it or fail.
if (options.BackchannelCertificateValidator == null) return handler;
// Set the cert validate callback
var webRequestHandler = handler as WebRequestHandler;
if (webRequestHandler == null)
{
throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch);
}
webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
return handler;
}
}
}

View File

@@ -0,0 +1,171 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using Microsoft.Owin;
using Microsoft.Owin.Security;
namespace Owin.Security.Providers.ArcGISPortal
{
public class ArcGISPortalAuthenticationOptions : AuthenticationOptions
{
public class ArcGISPortalAuthenticationEndpoints
{
/// <summary>
/// Endpoint which is used to redirect users to request ArcGISPortal access
/// </summary>
/// <remarks>
/// Defaults to https://www.arcgis.com/sharing/rest/oauth2/authorize/
/// </remarks>
public string AuthorizationEndpoint { get; set; }
/// <summary>
/// Endpoint which is used to exchange code for access token
/// </summary>
/// <remarks>
/// Defaults to https://www.arcgis.com/sharing/rest/oauth2/token/
/// </remarks>
public string TokenEndpoint { get; set; }
/// <summary>
/// Endpoint which is used to obtain user information after authentication
/// </summary>
/// <remarks>
/// Defaults to https://www.arcgis.com/sharing/rest/community/self
/// </remarks>
public string UserInfoEndpoint { get; set; }
}
private const string AuthenticationTypeNameDefault = "ArcGIS Portal";
private const string AuthorizationEndPoint = "arcgis/sharing/rest/oauth2/authorize/";
private const string TokenEndpoint = "arcgis/sharing/rest/oauth2/token/";
private const string UserInfoEndpoint = "arcgis/sharing/rest/community/self";
/// <summary>
/// Gets or sets the a pinned certificate validator to use to validate the endpoints used
/// in back channel communications belong to ArcGISPortal.
/// </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 ArcGISPortal.
/// 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 ArcGISPortal.
/// </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-ArcGISPortal".
/// </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 ArcGIS Portal Authentication Type Name
/// Displayed to the user as the login option, and allows multiple ArcGIS Portal OAuth providers to be configured for use in the same application.
/// </summary>
public string AuthenticationTypeName { get; set; }
/// <summary>
/// Gets or sets the ArcGIS Portal Host (where the portal is installed e.g. https://arcgisportal.domain.com)
/// </summary>
public string Host { get; set; }
/// <summary>
/// Gets or sets the ArcGISPortal supplied Client ID
/// </summary>
public string ClientId { get; set; }
/// <summary>
/// Gets or sets the ArcGISPortal supplied Client Secret
/// </summary>
public string ClientSecret { get; set; }
/// <summary>
/// Gets the sets of OAuth endpoints used to authenticate against ArcGISPortal. Overriding these endpoints allows you to use ArcGISPortal Enterprise for
/// authentication.
/// </summary>
public ArcGISPortalAuthenticationEndpoints Endpoints { get; set; }
/// <summary>
/// Gets or sets the <see cref="IArcGISPortalAuthenticationProvider" /> used in the authentication events
/// </summary>
public IArcGISPortalAuthenticationProvider Provider { get; set; }
/// <summary>
/// A list of permissions to request.
/// </summary>
public IList<string> Scope { get; protected 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="ArcGISPortalAuthenticationOptions" />
/// </summary>
public ArcGISPortalAuthenticationOptions(string host, string clientId, string clientSecret) : this(AuthenticationTypeNameDefault, host, clientId, clientSecret) {}
/// <summary>
/// Initializes a new <see cref="ArcGISPortalAuthenticationOptions" />
/// </summary>
public ArcGISPortalAuthenticationOptions(string authenticationTypeName, string host, string clientId, string clientSecret) : base(authenticationTypeName)
{
AuthenticationTypeName = authenticationTypeName;
Host = host;
ClientId = clientId;
ClientSecret = clientSecret;
AuthenticationType = AuthenticationTypeName;
Caption = AuthenticationTypeName;
CallbackPath = new PathString("/signin-arcgis-portal");
AuthenticationMode = AuthenticationMode.Passive;
Scope = new List<string>
{
"code"
};
BackchannelTimeout = TimeSpan.FromSeconds(60);
Uri hostUri = new Uri(Host);
Endpoints = new ArcGISPortalAuthenticationEndpoints
{
AuthorizationEndpoint = new Uri(hostUri, AuthorizationEndPoint).ToString(),
TokenEndpoint = new Uri(hostUri, TokenEndpoint).ToString(),
UserInfoEndpoint = new Uri(hostUri, UserInfoEndpoint).ToString()
};
}
}
}

View File

@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{18547CA4-D7D3-43C2-81C2-A21FC8151A93}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Owin.Security.Providers.ArcGISPortal</RootNamespace>
<AssemblyName>Owin.Security.Providers.ArcGISPortal</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Owin.Security, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
<HintPath>..\..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Net.Http.WebRequest" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="ArcGISPortalAuthenticationHandler.cs" />
<Compile Include="ArcGISPortalAuthenticationExtensions.cs" />
<Compile Include="ArcGISPortalAuthenticationMiddleware.cs" />
<Compile Include="ArcGISPortalAuthenticationOptions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Provider\ArcGISPortalAuthenticationProvider.cs" />
<Compile Include="Provider\ArcGISPortalAuthenticatedContext.cs" />
<Compile Include="Provider\ArcGISPortalReturnEndpointContext.cs" />
<Compile Include="Provider\ArcGISPortalUser.cs" />
<Compile Include="Provider\IArcGISPortalAuthenticationProvider.cs" />
<Compile Include="Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Owin.Security.Providers.ArcGISPortal")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Owin.Security.Providers.ArcGISPortal")]
[assembly: AssemblyCopyright("Copyright © 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("18547ca4-d7d3-43c2-81c2-a21fc8151a93")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// 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.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -0,0 +1,81 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Security.Claims;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Provider;
using Owin.Security.Providers.ArcGISPortal.Provider;
using System;
namespace Owin.Security.Providers.ArcGISPortal
{
/// <summary>
/// Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.
/// </summary>
public class ArcGISPortalAuthenticatedContext : BaseContext
{
/// <summary>
/// Initializes a <see cref="ArcGISPortalAuthenticatedContext"/>
/// </summary>
/// <param name="context">The OWIN environment</param>
/// <param name="user">The ArcGIS Portal user</param>
/// <param name="accessToken">ArcGIS Portal Access token</param>
/// <param name="refreshToken">ArcGIS Portal Refresh token</param>
/// <param name="host">ArcGIS Portal Host</param>
public ArcGISPortalAuthenticatedContext(IOwinContext context, ArcGISPortalUser user, string accessToken, string refreshToken, string host)
: base(context)
{
Uri hostUri = new Uri(host);
AccessToken = accessToken;
RefreshToken = refreshToken;
Id = user.Username;
Name = user.FullName;
Link = new Uri(hostUri, "arcgis/sharing/rest/community/users/" + Id).ToString();
UserName = Id;
Email = user.Email;
}
/// <summary>
/// Gets the ArcGIS Portal access token
/// </summary>
public string AccessToken { get; private set; }
/// <summary>
/// Gets the ArcGIS Portal refresh token
/// </summary>
public string RefreshToken { get; private set; }
/// <summary>
/// Gets the ArcGIS Portal user ID
/// </summary>
public string Id { get; }
/// <summary>
/// Gets the user's name
/// </summary>
public string Name { get; private set; }
/// <summary>
/// Gets the user's email
/// </summary>
public string Email { get; private set; }
public string Link { get; private set; }
/// <summary>
/// Gets the ArcGIS Portal username
/// </summary>
public string UserName { get; private set; }
/// <summary>
/// Gets the <see cref="ClaimsIdentity"/> representing the user
/// </summary>
public ClaimsIdentity Identity { get; set; }
/// <summary>
/// Gets or sets a property bag for common authentication properties
/// </summary>
public AuthenticationProperties Properties { get; set; }
}
}

View File

@@ -0,0 +1,50 @@
using System;
using System.Threading.Tasks;
namespace Owin.Security.Providers.ArcGISPortal
{
/// <summary>
/// Default <see cref="IArcGISPortalAuthenticationProvider"/> implementation.
/// </summary>
public class ArcGISPortalAuthenticationProvider : IArcGISPortalAuthenticationProvider
{
/// <summary>
/// Initializes a <see cref="ArcGISPortalAuthenticationProvider"/>
/// </summary>
public ArcGISPortalAuthenticationProvider()
{
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<ArcGISPortalAuthenticatedContext, Task> OnAuthenticated { get; set; }
/// <summary>
/// Gets or sets the function that is invoked when the ReturnEndpoint method is invoked.
/// </summary>
public Func<ArcGISPortalReturnEndpointContext, Task> OnReturnEndpoint { get; set; }
/// <summary>
/// Invoked whenever ArcGISPortal successfully 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(ArcGISPortalAuthenticatedContext 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(ArcGISPortalReturnEndpointContext 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.ArcGISPortal
{
/// <summary>
/// Provides context information to middleware providers.
/// </summary>
public class ArcGISPortalReturnEndpointContext : ReturnEndpointContext
{
/// <summary>
///
/// </summary>
/// <param name="context">OWIN environment</param>
/// <param name="ticket">The authentication ticket</param>
public ArcGISPortalReturnEndpointContext(
IOwinContext context,
AuthenticationTicket ticket)
: base(context, ticket)
{
}
}
}

View File

@@ -0,0 +1,9 @@
namespace Owin.Security.Providers.ArcGISPortal.Provider
{
public class ArcGISPortalUser
{
public string Username { get; set; }
public string FullName { get; set; }
public string Email { get; set; }
}
}

View File

@@ -0,0 +1,24 @@
using System.Threading.Tasks;
namespace Owin.Security.Providers.ArcGISPortal
{
/// <summary>
/// Specifies callback methods which the <see cref="ArcGISPortalAuthenticationMiddleware"></see> invokes to enable developer control over the authentication process. />
/// </summary>
public interface IArcGISPortalAuthenticationProvider
{
/// <summary>
/// Invoked whenever ArcGISPortal successfully 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(ArcGISPortalAuthenticatedContext 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(ArcGISPortalReturnEndpointContext context);
}
}

View File

@@ -0,0 +1,81 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Owin.Security.Providers.ArcGISPortal {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.ArcGISPortal.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to The &apos;{0}&apos; option must be provided..
/// </summary>
internal static string Exception_OptionMustBeProvided {
get {
return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler..
/// </summary>
internal static string Exception_ValidatorHandlerMismatch {
get {
return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture);
}
}
}
}

View File

@@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Exception_OptionMustBeProvided" xml:space="preserve">
<value>The '{0}' option must be provided.</value>
</data>
<data name="Exception_ValidatorHandlerMismatch" xml:space="preserve">
<value>An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.</value>
</data>
</root>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Owin" version="3.0.1" targetFramework="net45" />
<package id="Microsoft.Owin.Security" version="3.0.1" targetFramework="net45" />
<package id="Newtonsoft.Json" version="8.0.3" targetFramework="net45" />
<package id="Owin" version="1.0" targetFramework="net45" />
</packages>

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

@@ -105,7 +105,7 @@ namespace Owin.Security.Providers.Discord
};
if (!string.IsNullOrEmpty(context.Id))
{
context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, context.UserName, XmlSchemaString, Options.AuthenticationType));
context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, context.Id, XmlSchemaString, Options.AuthenticationType));
}
if (!string.IsNullOrEmpty(context.UserName))
{

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

@@ -39,7 +39,7 @@ namespace Owin.Security.Providers.Discord.Provider
Discriminator = TryGetValue(user, "discriminator");
Avatar = TryGetValue(user, "avatar");
Email = TryGetValue(user, "email");
Verified = TryGetValue(user, "verified") == "true";
Verified = TryGetValue(user, "verified").ToLowerInvariant() == "true";
}
public string RefreshToken { get; set; }

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

@@ -17,8 +17,9 @@ namespace Owin.Security.Providers.Dropbox
{
private const string StateCookie = "_DropboxState";
private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string";
private const string TokenEndpoint = "https://api.dropbox.com/1/oauth2/token";
private const string UserInfoEndpoint = "https://api.dropbox.com/1/account/info";
private const string TokenEndpoint = "https://api.dropbox.com/oauth2/token";
private const string UserInfoEndpoint = "https://api.dropbox.com/2/users/get_current_account";
private readonly ILogger _logger;
private readonly HttpClient _httpClient;
@@ -65,13 +66,13 @@ namespace Owin.Security.Providers.Dropbox
var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath;
// Build up the body for the token request
var body = new List<KeyValuePair<string, string>>
var body = new Dictionary<string, string>
{
new KeyValuePair<string, string>("grant_type", "authorization_code"),
new KeyValuePair<string, string>("code", code),
new KeyValuePair<string, string>("redirect_uri", redirectUri),
new KeyValuePair<string, string>("client_id", Options.AppKey),
new KeyValuePair<string, string>("client_secret", Options.AppSecret)
{ "grant_type", "authorization_code" },
{ "code", code },
{"redirect_uri", redirectUri },
{ "client_id", Options.AppKey },
{ "client_secret", Options.AppSecret }
};
// Request the token
@@ -85,8 +86,8 @@ namespace Owin.Security.Providers.Dropbox
var accessToken = (string)response.access_token;
// Get the Dropbox user
var graphResponse = await _httpClient.GetAsync(
UserInfoEndpoint + "?access_token=" + Uri.EscapeDataString(accessToken), Request.CallCancelled);
_httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
var graphResponse = await _httpClient.PostAsync(UserInfoEndpoint, null);
graphResponse.EnsureSuccessStatusCode();
text = await graphResponse.Content.ReadAsStringAsync();
var user = JObject.Parse(text);
@@ -154,7 +155,7 @@ namespace Owin.Security.Providers.Dropbox
GenerateCorrelationId(properties);
var authorizationEndpoint =
"https://www.dropbox.com/1/oauth2/authorize" +
"https://www.dropbox.com/oauth2/authorize" +
"?response_type=code" +
"&client_id=" + Uri.EscapeDataString(Options.AppKey) +
"&redirect_uri=" + Uri.EscapeDataString(redirectUri);

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

@@ -25,7 +25,7 @@ namespace Owin.Security.Providers.Dropbox
AccessToken = accessToken;
User = user;
Id = TryGetValue(user, "uid");
Id = TryGetValue(user, "account_id");
Name = TryGetValue(user, "display_name");
}

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

@@ -21,6 +21,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -29,6 +30,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Accessibility" />

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

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

View File

@@ -0,0 +1,29 @@
using System;
namespace Owin.Security.Providers.Google
{
public static class GoogleAuthenticationExtensions
{
public static IAppBuilder UseGoogleAuthentication(this IAppBuilder app,
GoogleAuthenticationOptions options)
{
if (app == null)
throw new ArgumentNullException(nameof(app));
if (options == null)
throw new ArgumentNullException(nameof(options));
app.Use(typeof(GoogleAuthenticationMiddleware), app, options);
return app;
}
public static IAppBuilder UseGoogleAuthentication(this IAppBuilder app, string clientId, string clientSecret)
{
return app.UseGoogleAuthentication(new GoogleAuthenticationOptions
{
ClientId = clientId,
ClientSecret = clientSecret
});
}
}
}

View File

@@ -0,0 +1,246 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Security.Claims;
using System.Threading.Tasks;
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 Owin.Security.Providers.Google.Provider;
namespace Owin.Security.Providers.Google
{
public class GoogleAuthenticationHandler : AuthenticationHandler<GoogleAuthenticationOptions>
{
private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string";
private const string TokenEndpoint = "https://accounts.google.com/o/oauth2/token";
// TODO: This url should come from here: https://accounts.google.com/.well-known/openid-configuration
// TODO: as described by https://developers.google.com/identity/protocols/OpenIDConnect#discovery
private const string UserInfoEndpoint = "https://www.googleapis.com/oauth2/v3/userinfo";
private readonly ILogger _logger;
private readonly HttpClient _httpClient;
public GoogleAuthenticationHandler(HttpClient httpClient, ILogger logger)
{
_httpClient = httpClient;
_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 (values != null && values.Count == 1)
{
code = values[0];
}
values = query.GetValues("state");
if (values != null && values.Count == 1)
{
state = values[0];
}
properties = Options.StateDataFormat.Unprotect(state);
if (properties == null)
{
return null;
}
// OAuth2 10.12 CSRF
if (!ValidateCorrelationId(properties, _logger))
{
return new AuthenticationTicket(null, properties);
}
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>("grant_type", "authorization_code"),
new KeyValuePair<string, string>("code", code),
new KeyValuePair<string, string>("redirect_uri", redirectUri),
new KeyValuePair<string, string>("client_id", Options.ClientId),
new KeyValuePair<string, string>("client_secret", Options.ClientSecret)
};
// Request the token
var tokenResponse =
await _httpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(body));
tokenResponse.EnsureSuccessStatusCode();
var text = await tokenResponse.Content.ReadAsStringAsync();
// Deserializes the token response
dynamic response = JsonConvert.DeserializeObject<dynamic>(text);
var accessToken = (string)response.access_token;
var expires = (string) response.expires_in;
string refreshToken = null;
if (response.refresh_token != null)
refreshToken = (string) response.refresh_token;
// Get the Google user
var graphResponse = await _httpClient.GetAsync(
UserInfoEndpoint + "?access_token=" + Uri.EscapeDataString(accessToken), Request.CallCancelled);
graphResponse.EnsureSuccessStatusCode();
text = await graphResponse.Content.ReadAsStringAsync();
var userInfo = JObject.Parse(text);
var context = new GoogleAuthenticatedContext(Context, userInfo, accessToken, expires, refreshToken)
{
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.Name))
{
context.Identity.AddClaim(new Claim("urn:google:name", context.Name, XmlSchemaString, Options.AuthenticationType));
}
if (!string.IsNullOrEmpty(context.Link))
{
context.Identity.AddClaim(new Claim("urn:google:url", context.Link, XmlSchemaString, Options.AuthenticationType));
}
context.Properties = properties;
await Options.Provider.Authenticated(context);
return new AuthenticationTicket(context.Identity, context.Properties);
}
catch (Exception ex)
{
_logger.WriteError(ex.Message);
}
return new AuthenticationTicket(null, properties);
}
protected override Task ApplyResponseChallengeAsync()
{
if (Response.StatusCode != 401)
{
return Task.FromResult<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);
// comma separated
var scope = string.Join(" ", Options.Scope);
var state = Options.StateDataFormat.Protect(properties);
var authorizationEndpoint =
"https://accounts.google.com/o/oauth2/auth" +
"?response_type=code" +
"&client_id=" + Uri.EscapeDataString(Options.ClientId) +
"&redirect_uri=" + Uri.EscapeDataString(redirectUri) +
"&scope=" + Uri.EscapeDataString(scope) +
"&state=" + Uri.EscapeDataString(state);
// Check if offline access was requested
if (Options.RequestOfflineAccess)
authorizationEndpoint += "&access_type=offline";
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
var ticket = await AuthenticateAsync();
if (ticket == null)
{
_logger.WriteWarning("Invalid return state, unable to redirect.");
Response.StatusCode = 500;
return true;
}
var context = new GoogleReturnEndpointContext(Context, ticket)
{
SignInAsAuthenticationType = Options.SignInAsAuthenticationType,
RedirectUri = ticket.Properties.RedirectUri
};
await Options.Provider.ReturnEndpoint(context);
if (context.SignInAsAuthenticationType != null &&
context.Identity != null)
{
var grantIdentity = context.Identity;
if (!string.Equals(grantIdentity.AuthenticationType, context.SignInAsAuthenticationType, StringComparison.Ordinal))
{
grantIdentity = new ClaimsIdentity(grantIdentity.Claims, context.SignInAsAuthenticationType, grantIdentity.NameClaimType, grantIdentity.RoleClaimType);
}
Context.Authentication.SignIn(context.Properties, grantIdentity);
}
if (context.IsRequestCompleted || context.RedirectUri == null) return context.IsRequestCompleted;
var redirectUri = context.RedirectUri;
if (context.Identity == null)
{
// add a redirect hint that sign-in failed in some way
redirectUri = WebUtilities.AddQueryString(redirectUri, "error", "access_denied");
}
Response.Redirect(redirectUri);
context.RequestCompleted();
return context.IsRequestCompleted;
}
}
}

View File

@@ -0,0 +1,83 @@
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.Google.Provider;
namespace Owin.Security.Providers.Google
{
public class GoogleAuthenticationMiddleware : AuthenticationMiddleware<GoogleAuthenticationOptions>
{
private readonly HttpClient _httpClient;
private readonly ILogger _logger;
public GoogleAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app,
GoogleAuthenticationOptions options)
: base(next, options)
{
if (string.IsNullOrWhiteSpace(Options.ClientId))
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
Resources.Exception_OptionMustBeProvided, "ClientId"));
if (string.IsNullOrWhiteSpace(Options.ClientSecret))
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
Resources.Exception_OptionMustBeProvided, "ClientSecret"));
_logger = app.CreateLogger<GoogleAuthenticationMiddleware>();
if (Options.Provider == null)
Options.Provider = new GoogleAuthenticationProvider();
if (Options.StateDataFormat == null)
{
var dataProtector = app.CreateDataProtector(
typeof (GoogleAuthenticationMiddleware).FullName,
Options.AuthenticationType, "v1");
Options.StateDataFormat = new PropertiesDataFormat(dataProtector);
}
if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType))
Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType();
_httpClient = new HttpClient(ResolveHttpMessageHandler(Options))
{
Timeout = Options.BackchannelTimeout,
MaxResponseContentBufferSize = 1024*1024*10
};
}
/// <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.GooglePlus.GooglePlusAuthenticationOptions" /> supplied to the constructor.
/// </returns>
protected override AuthenticationHandler<GoogleAuthenticationOptions> CreateHandler()
{
return new GoogleAuthenticationHandler(_httpClient, _logger);
}
private static HttpMessageHandler ResolveHttpMessageHandler(GoogleAuthenticationOptions options)
{
var handler = options.BackchannelHttpHandler ?? new WebRequestHandler();
// If they provided a validator, apply it or fail.
if (options.BackchannelCertificateValidator == null) return handler;
// Set the cert validate callback
var webRequestHandler = handler as WebRequestHandler;
if (webRequestHandler == null)
{
throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch);
}
webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
return handler;
}
}
}

View File

@@ -0,0 +1,110 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Owin.Security.Providers.Google.Provider;
namespace Owin.Security.Providers.Google
{
public class GoogleAuthenticationOptions : AuthenticationOptions
{
/// <summary>
/// Gets or sets the a pinned certificate validator to use to validate the endpoints used
/// in back channel communications belonging to Google.
/// </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 Google.
/// 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 Google.
/// </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-google".
/// </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 Google supplied Client ID
/// </summary>
public string ClientId { get; set; }
/// <summary>
/// Gets or sets the Google supplied Client Secret
/// </summary>
public string ClientSecret { get; set; }
/// <summary>
/// Gets or sets the <see cref="IGoogleAuthenticationProvider" /> used in the authentication events
/// </summary>
public IGoogleAuthenticationProvider Provider { get; set; }
/// <summary>
/// Gets or sets whether to request offline access. If offline access is requested the <see cref="GoogleAuthenticatedContext"/> will contain a Refresh Token.
/// </summary>
public bool RequestOfflineAccess { 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 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="GoogleAuthenticationOptions" />
/// </summary>
public GoogleAuthenticationOptions()
: base("Google")
{
Caption = Constants.DefaultAuthenticationType;
CallbackPath = new PathString("/signin-google");
AuthenticationMode = AuthenticationMode.Passive;
Scope = new List<string>
{
"openid",
"profile",
"email"
};
BackchannelTimeout = TimeSpan.FromSeconds(60);
}
}
}

View File

@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{ED434959-8CF8-4CAB-83B3-E4A618327AB5}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Owin.Security.Providers.Google</RootNamespace>
<AssemblyName>Owin.Security.Providers.Google</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Owin.Security, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
<HintPath>..\..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Net.Http.WebRequest" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Constants.cs" />
<Compile Include="GoogleAuthenticationExtensions.cs" />
<Compile Include="GoogleAuthenticationHandler.cs" />
<Compile Include="GoogleAuthenticationMiddleware.cs" />
<Compile Include="GoogleAuthenticationOptions.cs" />
<Compile Include="Provider\GoogleAuthenticatedContext.cs" />
<Compile Include="Provider\GoogleAuthenticationProvider.cs" />
<Compile Include="Provider\GoogleReturnEndpointContext.cs" />
<Compile Include="Provider\IGoogleAuthenticationProvider.cs" />
<Compile Include="Resources.Designer.cs">
<DependentUpon>Resources.resx</DependentUpon>
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="PostBuildMacros">
<GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
<Output TaskParameter="Assemblies" ItemName="Targets" />
</GetAssemblyIdentity>
<ItemGroup>
<VersionNumber Include="@(Targets->'%(Version)')" />
</ItemGroup>
</Target>
<PropertyGroup>
<PreBuildEvent>
</PreBuildEvent>
<PostBuildEventDependsOn>
$(PostBuildEventDependsOn);
PostBuildMacros;
</PostBuildEventDependsOn>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,15 @@
using System.Reflection;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("Owin.Security.Providers.Google")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Owin.Security.Providers.Google")]
[assembly: AssemblyCopyright("Copyright © 2019")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
[assembly: Guid("ed434959-8cf8-4cab-83b3-e4a618327ab5")]
[assembly: AssemblyVersion("2.0.0.0")]
[assembly: AssemblyFileVersion("2.0.0.0")]

View File

@@ -0,0 +1,111 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Globalization;
using System.Security.Claims;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Provider;
using Newtonsoft.Json.Linq;
namespace Owin.Security.Providers.Google.Provider
{
/// <summary>
/// Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.
/// </summary>
public class GoogleAuthenticatedContext : BaseContext
{
/// <summary>
/// Initializes a <see cref="GoogleAuthenticatedContext"/>
/// </summary>
/// <param name="context">The OWIN environment</param>
/// <param name="userInfo">The JSON-serialized user_info. Format described here: https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims</param>
/// <param name="accessToken">Google Access token</param>
/// <param name="expires">Seconds until expiration</param>
/// <param name="refreshToken"></param>
public GoogleAuthenticatedContext(IOwinContext context, JObject userInfo, string accessToken, string expires, string refreshToken)
: base(context)
{
UserInfo = userInfo;
AccessToken = accessToken;
RefreshToken = refreshToken;
int expiresValue;
if (int.TryParse(expires, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue))
{
ExpiresIn = TimeSpan.FromSeconds(expiresValue);
}
// See https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims for a list of properties
Id = TryGetValue(userInfo, "sub");
Name = TryGetValue(userInfo, "name");
Link = TryGetValue(userInfo, "profile");
UserName = TryGetValue(userInfo, "name").Replace(" ", "");
var email = TryGetValue(userInfo, "email");
if (email != null)
Email = email;
}
/// <summary>
/// Gets the JSON-serialized user
/// </summary>
/// <remarks>
/// Contains the Google user obtained from the endpoint https://www.googleapis.com/oauth2/v3/userinfo
/// </remarks>
public JObject UserInfo { get; private set; }
/// <summary>
/// Gets the Google OAuth access token
/// </summary>
public string AccessToken { get; private set; }
/// <summary>
/// Gets the Google OAuth refresh token. This is only available when the RequestOfflineAccess property of <see cref="GoogleAuthenticationOptions"/> is set to true
/// </summary>
public string RefreshToken { get; private set; }
/// <summary>
/// Gets the Google access token expiration time
/// </summary>
public TimeSpan? ExpiresIn { get; set; }
/// <summary>
/// Gets the Google user ID
/// </summary>
public string Id { get; private set; }
/// <summary>
/// Gets the user's name
/// </summary>
public string Name { get; private set; }
public string Link { get; private set; }
/// <summary>
/// Gets the Google username
/// </summary>
public string UserName { get; private set; }
/// <summary>
/// Gets the Google email address for the account
/// </summary>
public string Email { get; private set; }
/// <summary>
/// Gets the <see cref="ClaimsIdentity"/> representing the user
/// </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(JObject user, string propertyName)
{
JToken value;
return user.TryGetValue(propertyName, out value) ? value.ToString() : null;
}
}
}

View File

@@ -0,0 +1,50 @@
using System;
using System.Threading.Tasks;
namespace Owin.Security.Providers.Google.Provider
{
/// <summary>
/// Default <see cref="IGoogleAuthenticationProvider"/> implementation.
/// </summary>
public class GoogleAuthenticationProvider : IGoogleAuthenticationProvider
{
/// <summary>
/// Initializes a <see cref="GoogleAuthenticationProvider"/>
/// </summary>
public GoogleAuthenticationProvider()
{
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<GoogleAuthenticatedContext, Task> OnAuthenticated { get; set; }
/// <summary>
/// Gets or sets the function that is invoked when the ReturnEndpoint method is invoked.
/// </summary>
public Func<GoogleReturnEndpointContext, Task> OnReturnEndpoint { get; set; }
/// <summary>
/// Invoked whenever Google successfully 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(GoogleAuthenticatedContext 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(GoogleReturnEndpointContext 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.Google.Provider
{
/// <summary>
/// Provides context information to middleware providers.
/// </summary>
public class GoogleReturnEndpointContext : ReturnEndpointContext
{
/// <summary>
///
/// </summary>
/// <param name="context">OWIN environment</param>
/// <param name="ticket">The authentication ticket</param>
public GoogleReturnEndpointContext(
IOwinContext context,
AuthenticationTicket ticket)
: base(context, ticket)
{
}
}
}

View File

@@ -0,0 +1,24 @@
using System.Threading.Tasks;
namespace Owin.Security.Providers.Google.Provider
{
/// <summary>
/// Specifies callback methods which the <see cref="GoogleAuthenticationMiddleware"></see> invokes to enable developer control over the authentication process. />
/// </summary>
public interface IGoogleAuthenticationProvider
{
/// <summary>
/// Invoked whenever Google successfully 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(GoogleAuthenticatedContext 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(GoogleReturnEndpointContext context);
}
}

View File

@@ -0,0 +1,81 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Owin.Security.Providers.Google {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.GooglePlus.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to The &apos;{0}&apos; option must be provided..
/// </summary>
internal static string Exception_OptionMustBeProvided {
get {
return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler..
/// </summary>
internal static string Exception_ValidatorHandlerMismatch {
get {
return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture);
}
}
}
}

View File

@@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Exception_OptionMustBeProvided" xml:space="preserve">
<value>The '{0}' option must be provided.</value>
</data>
<data name="Exception_ValidatorHandlerMismatch" xml:space="preserve">
<value>An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.</value>
</data>
</root>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Owin" version="3.0.1" targetFramework="net45" />
<package id="Microsoft.Owin.Security" version="3.0.1" targetFramework="net45" />
<package id="Newtonsoft.Json" version="8.0.3" targetFramework="net45" />
<package id="Owin" version="1.0" targetFramework="net45" />
</packages>

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Claims;
using System.Threading.Tasks;
@@ -16,8 +17,10 @@ namespace Owin.Security.Providers.LinkedIn
public class LinkedInAuthenticationHandler : AuthenticationHandler<LinkedInAuthenticationOptions>
{
private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string";
private const string TokenEndpoint = "https://www.linkedin.com/uas/oauth2/accessToken";
private const string UserInfoEndpoint = "https://api.linkedin.com/v1/people/";
private const string TokenEndpoint = "https://www.linkedin.com/oauth/v2/accessToken";
private const string UserInfoEndpoint = "https://api.linkedin.com/v2/me";
private const string AuthorizationEndpoint = "https://www.linkedin.com/oauth/v2/authorization";
private const string EmailEndpoint = "https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))";
private readonly ILogger _logger;
private readonly HttpClient _httpClient;
@@ -34,21 +37,7 @@ namespace Owin.Security.Providers.LinkedIn
try
{
string code = null;
string state = null;
var query = Request.Query;
var values = query.GetValues("code");
if (values != null && values.Count == 1)
{
code = values[0];
}
values = query.GetValues("state");
if (values != null && values.Count == 1)
{
state = values[0];
}
var state = GetQueryValue("state");
properties = Options.StateDataFormat.Unprotect(state);
if (properties == null)
{
@@ -61,94 +50,31 @@ namespace Owin.Security.Providers.LinkedIn
return new AuthenticationTicket(null, properties);
}
var requestPrefix = Request.Scheme + "://" + Request.Host;
var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath;
// Build up the body for the token request
var body = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", "authorization_code"),
new KeyValuePair<string, string>("code", code),
new KeyValuePair<string, string>("redirect_uri", redirectUri),
new KeyValuePair<string, string>("client_id", Options.ClientId),
new KeyValuePair<string, string>("client_secret", Options.ClientSecret)
};
// Request the token
var tokenResponse = await _httpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(body));
tokenResponse.EnsureSuccessStatusCode();
var text = await tokenResponse.Content.ReadAsStringAsync();
// Deserializes the token response
dynamic response = JsonConvert.DeserializeObject<dynamic>(text);
var code = GetQueryValue("code");
var accessTokenResponse = await GetAccessToken(code);
dynamic response = JsonConvert.DeserializeObject<dynamic>(accessTokenResponse);
var accessToken = (string)response.access_token;
var expires = (string) response.expires_in;
var expires = (string)response.expires_in;
// Get the LinkedIn user
var userInfoEndpoint = UserInfoEndpoint
+ "~:("+ string.Join(",", Options.ProfileFields.Distinct().ToArray()) +")"
+ "?oauth2_access_token=" + Uri.EscapeDataString(accessToken);
var userRequest = new HttpRequestMessage(HttpMethod.Get, userInfoEndpoint);
userRequest.Headers.Add("x-li-format", "json");
var graphResponse = await _httpClient.SendAsync(userRequest, Request.CallCancelled);
graphResponse.EnsureSuccessStatusCode();
text = await graphResponse.Content.ReadAsStringAsync();
var user = JObject.Parse(text);
var userInfoResponse = await GetUserInfo(accessToken);
var user = JObject.Parse(userInfoResponse);
string email = null;
if (Options.Scope.Contains(LinkedInAuthenticationOptions.EmailAddressScopeName))
{
email = await GetUserEmail(accessToken);
}
var context = new LinkedInAuthenticatedContext(Context, user, accessToken, expires)
var context = new LinkedInAuthenticatedContext(Context, user, accessToken, expires, email)
{
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.Email))
{
context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.Email, XmlSchemaString, Options.AuthenticationType));
}
if (!string.IsNullOrEmpty(context.GivenName))
{
context.Identity.AddClaim(new Claim(ClaimTypes.GivenName, context.GivenName, XmlSchemaString, Options.AuthenticationType));
}
if (!string.IsNullOrEmpty(context.FamilyName))
{
context.Identity.AddClaim(new Claim(ClaimTypes.Surname, context.FamilyName, XmlSchemaString, Options.AuthenticationType));
}
if (!string.IsNullOrEmpty(context.Name))
{
context.Identity.AddClaim(new Claim(ClaimTypes.Name, context.Name, XmlSchemaString, Options.AuthenticationType));
context.Identity.AddClaim(new Claim("urn:linkedin:name", context.Name, XmlSchemaString, Options.AuthenticationType));
}
if (!string.IsNullOrEmpty(context.Industry))
{
context.Identity.AddClaim(new Claim("urn:linkedin:industry", context.Industry, XmlSchemaString, Options.AuthenticationType));
}
if (!string.IsNullOrEmpty(context.Positions))
{
context.Identity.AddClaim(new Claim("urn:linkedin:positions", context.Positions, XmlSchemaString, Options.AuthenticationType));
}
if (!string.IsNullOrEmpty(context.Summary))
{
context.Identity.AddClaim(new Claim("urn:linkedin:summary", context.Summary, XmlSchemaString, Options.AuthenticationType));
}
if (!string.IsNullOrEmpty(context.Headline))
{
context.Identity.AddClaim(new Claim("urn:linkedin:headline", context.Headline, XmlSchemaString, Options.AuthenticationType));
}
if (!string.IsNullOrEmpty(context.Link))
{
context.Identity.AddClaim(new Claim("urn:linkedin:url", context.Link, XmlSchemaString, Options.AuthenticationType));
}
if (!string.IsNullOrEmpty(context.AccessToken))
{
context.Identity.AddClaim(new Claim("urn:linkedin:accesstoken", context.AccessToken, XmlSchemaString, Options.AuthenticationType));
}
context.Properties = properties;
AddClaimsToContextIdentity(context);
context.Properties = properties;
await Options.Provider.Authenticated(context);
return new AuthenticationTicket(context.Identity, context.Properties);
@@ -173,7 +99,7 @@ namespace Owin.Security.Providers.LinkedIn
var baseUri =
Request.Scheme +
Uri.SchemeDelimiter +
Request.Host +
this.GetHostName() +
Request.PathBase;
var currentUri =
@@ -206,7 +132,7 @@ namespace Owin.Security.Providers.LinkedIn
var state = Options.StateDataFormat.Protect(properties);
var authorizationEndpoint =
"https://www.linkedin.com/uas/oauth2/authorization" +
AuthorizationEndpoint +
"?response_type=code" +
"&client_id=" + Uri.EscapeDataString(Options.ClientId) +
"&redirect_uri=" + Uri.EscapeDataString(redirectUri) +
@@ -271,5 +197,133 @@ namespace Owin.Security.Providers.LinkedIn
return context.IsRequestCompleted;
}
/// <summary>
/// Gets proxy host name from <see cref="LinkedInAuthenticationOptions"/> if it is set.
/// If proxy host name is not set, gets application request host name.
/// </summary>
/// <returns>Host name.</returns>
private string GetHostName()
{
return string.IsNullOrWhiteSpace(Options.ProxyHost) ? Request.Host.ToString() : Options.ProxyHost;
}
private static void SetAuthorizedRequestHeaders(string accessToken, HttpRequestMessage userRequest)
{
userRequest.Headers.Add("x-li-format", "json");
userRequest.Headers.Add("Authorization", "Bearer " + accessToken);
}
private async Task<string> GetUserEmail(string accessToken)
{
var emailRequest = new HttpRequestMessage(HttpMethod.Get, EmailEndpoint);
SetAuthorizedRequestHeaders(accessToken, emailRequest);
var graphResponse = await _httpClient.SendAsync(emailRequest, Request.CallCancelled);
try
{
graphResponse.EnsureSuccessStatusCode();
}
catch (Exception e)
{
_logger.WriteError(string.Format("Retrieving the user email using authorization from provider {0} failed. Message: {1}", Options.AuthenticationType, e.Message));
throw;
}
var text = await graphResponse.Content.ReadAsStringAsync();
var emailResponse = JObject.Parse(text);
string email = null;
var emailValue = emailResponse.SelectToken("elements[0].handle~.emailAddress");
if (emailValue != null)
{
email = emailValue.Value<string>();
}
else
{
var errorMessageValue = emailResponse.SelectToken("elements[0].handle!.message");
var errorMessage = string.Empty;
if (errorMessageValue != null)
{
errorMessage = errorMessageValue.Value<string>();
}
_logger.WriteWarning("Could not retrieve the user email from LinkedIn. Message: " + errorMessage);
}
return await Task.FromResult<string>(email);
}
private async Task<string> GetAccessToken(string code)
{
var requestPrefix = Request.Scheme + "://" + this.GetHostName();
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>("grant_type", "authorization_code"),
new KeyValuePair<string, string>("code", code),
new KeyValuePair<string, string>("redirect_uri", redirectUri),
new KeyValuePair<string, string>("client_id", Options.ClientId),
new KeyValuePair<string, string>("client_secret", Options.ClientSecret)
};
// Request the token
var tokenResponse = await _httpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(body));
tokenResponse.EnsureSuccessStatusCode();
return await tokenResponse.Content.ReadAsStringAsync();
}
private async Task<string> GetUserInfo(string accessToken)
{
var userInfoEndpoint = UserInfoEndpoint
+ "?projection=(" + string.Join(",", Options.ProfileFields.Distinct().ToArray()) + ")";
var userRequest = new HttpRequestMessage(HttpMethod.Get, userInfoEndpoint);
SetAuthorizedRequestHeaders(accessToken, userRequest);
var graphResponse = await _httpClient.SendAsync(userRequest, Request.CallCancelled);
graphResponse.EnsureSuccessStatusCode();
return await graphResponse.Content.ReadAsStringAsync();
}
private void AddClaimsToContextIdentity(LinkedInAuthenticatedContext context)
{
if (!string.IsNullOrEmpty(context.Id))
{
context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, context.Id, XmlSchemaString, Options.AuthenticationType));
}
if (!string.IsNullOrEmpty(context.Email))
{
context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.Email, XmlSchemaString, Options.AuthenticationType));
}
if (!string.IsNullOrEmpty(context.GivenName))
{
context.Identity.AddClaim(new Claim(ClaimTypes.GivenName, context.GivenName, XmlSchemaString, Options.AuthenticationType));
}
if (!string.IsNullOrEmpty(context.FamilyName))
{
context.Identity.AddClaim(new Claim(ClaimTypes.Surname, context.FamilyName, XmlSchemaString, Options.AuthenticationType));
}
if (!string.IsNullOrEmpty(context.Name))
{
context.Identity.AddClaim(new Claim(ClaimTypes.Name, context.Name, XmlSchemaString, Options.AuthenticationType));
context.Identity.AddClaim(new Claim("urn:linkedin:name", context.Name, XmlSchemaString, Options.AuthenticationType));
}
if (!string.IsNullOrEmpty(context.AccessToken))
{
context.Identity.AddClaim(new Claim("urn:linkedin:accesstoken", context.AccessToken, XmlSchemaString, Options.AuthenticationType));
}
}
private string GetQueryValue(string name)
{
string result = null;
var query = Request.Query;
var values = query.GetValues(name);
if (values != null && values.Count == 1)
{
result = values[0];
}
return result;
}
}
}

View File

@@ -8,6 +8,11 @@ namespace Owin.Security.Providers.LinkedIn
{
public class LinkedInAuthenticationOptions : AuthenticationOptions
{
/// <summary>
/// The calim name that is used to request permission to retrieve the user address during the authorization call
/// </summary>
public const string EmailAddressScopeName = "r_emailaddress";
/// <summary>
/// Gets or sets the a pinned certificate validator to use to validate the endpoints used
/// in back channel communications belong to LinkedIn.
@@ -43,6 +48,16 @@ namespace Owin.Security.Providers.LinkedIn
/// </summary>
public PathString CallbackPath { get; set; }
/// <summary>
/// Gets or sets the middleware host name.
/// The middleware processes the <see cref="CallbackPath"/> on this host name instead of the application's request host.
/// If this is not set, the application's request host will be used.
/// </summary>
/// <remarks>
/// Use this property when running behind a proxy.
/// </remarks>
public string ProxyHost { get; set; }
/// <summary>
/// Get or sets the text that the user can display on a sign in user interface.
/// </summary>
@@ -108,22 +123,15 @@ namespace Owin.Security.Providers.LinkedIn
AuthenticationMode = AuthenticationMode.Passive;
Scope = new List<string>
{
"r_basicprofile",
"r_emailaddress"
"r_liteprofile",
EmailAddressScopeName
};
ProfileFields = new List<string>
{
"id",
"first-name",
"last-name",
"formatted-name",
"email-address",
"public-profile-url",
"picture-url",
"industry",
"headline",
"summary",
"positions"
"firstName",
"lastName",
"profilePicture(displayImage~:playableStreams)"
};
BackchannelTimeout = TimeSpan.FromSeconds(60);
}

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

@@ -6,8 +6,8 @@ using System.Security.Claims;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Provider;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Owin.Security.Providers.LinkedIn
{
@@ -26,26 +26,35 @@ namespace Owin.Security.Providers.LinkedIn
public LinkedInAuthenticatedContext(IOwinContext context, JObject user, string accessToken, string expires)
: base(context)
{
User = user;
AccessToken = accessToken;
this.User = user;
this.AccessToken = accessToken;
int expiresValue;
if (int.TryParse(expires, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue))
{
ExpiresIn = TimeSpan.FromSeconds(expiresValue);
this.ExpiresIn = TimeSpan.FromSeconds(expiresValue);
}
Id = TryGetValue(user, "id");
Name = TryGetValue(user, "formattedName");
FamilyName = TryGetValue(user, "lastName");
GivenName = TryGetValue(user, "firstName");
Link = TryGetValue(user, "publicProfileUrl");
UserName = TryGetValue(user, "formattedName").Replace(" ", "");
Email = TryGetValue(user, "emailAddress");
Industry = TryGetValue(user, "industry");
Summary = TryGetValue(user, "summary");
Headline = TryGetValue(user, "headline");
Positions = TryGetValueAndSerialize(user, "positions");
this.Id = TryGetValue(user, "id");
this.FamilyName = TryGetLocalizedValue(user, "lastName");
this.GivenName = TryGetLocalizedValue(user, "firstName");
if (this.FamilyName != null || this.GivenName != null)
{
this.Name = string.Join(" ", this.GivenName, this.FamilyName);
}
}
/// <summary>
/// Initializes a <see cref="LinkedInAuthenticatedContext"/>
/// </summary>
/// <param name="context">The OWIN environment</param>
/// <param name="user">The JSON-serialized user</param>
/// <param name="accessToken">LinkedIn Access token</param>
/// <param name="expires">Seconds until expiration</param>
/// <param name="expires">User email returned from dedicated endpoint</param>
public LinkedInAuthenticatedContext(IOwinContext context, JObject user, string accessToken, string expires, string email) : this(context, user, accessToken, expires)
{
this.Email = email;
}
/// <summary>
@@ -95,25 +104,30 @@ namespace Owin.Security.Providers.LinkedIn
/// <summary>
/// Describes the users membership profile
/// </summary>
[Obsolete("LinkedIn doesn't return this claim anymore.")]
public string Summary { get; private set; }
/// <summary>
/// Industry the member belongs to
/// https://developer.linkedin.com/docs/reference/industry-codes
/// </summary>
[Obsolete("LinkedIn doesn't return this claim anymore.")]
public string Industry { get; set; }
/// <summary>
/// The members headline
/// </summary>
[Obsolete("LinkedIn doesn't return this claim anymore.")]
public string Headline { get; set; }
/// <summary>
/// Member's current positions
/// https://developer.linkedin.com/docs/fields/positions
/// </summary>
[Obsolete("LinkedIn doesn't return this claim anymore.")]
public string Positions { get; set; }
[Obsolete("LinkedIn doesn't return this claim anymore.")]
public string Link { get; private set; }
/// <summary>
@@ -142,5 +156,28 @@ namespace Owin.Security.Providers.LinkedIn
JToken value;
return user.TryGetValue(propertyName, out value) ? JsonConvert.SerializeObject(value) : null;
}
private static string TryGetLocalizedValue(JObject container, string propertyName)
{
var localizedValues = container.SelectToken(propertyName + ".localized") as JObject;
if (localizedValues == null)
{
return null;
}
var defaultValue = TryGetValue(localizedValues, "en-US");
if (defaultValue != null)
{
return defaultValue;
}
if (localizedValues.HasValues)
{
return localizedValues.First.First.Value<string>();
}
return null;
}
}
}

View File

@@ -21,6 +21,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -29,6 +30,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
@@ -46,9 +48,8 @@
<HintPath>..\..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Owin.Security.Providers.OpenIDBase, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Owin.Security.Providers.OpenIDBase.2.1\lib\net45\Owin.Security.Providers.OpenIDBase.dll</HintPath>
<Private>True</Private>
<Reference Include="Owin.Security.Providers.OpenIDBase, Version=2.0.0.0, Culture=neutral, PublicKeyToken=1328b8c16b108c43, processorArchitecture=MSIL">
<HintPath>..\..\packages\Owin.Security.Providers.OpenIDBase.2.1.1\lib\net45\Owin.Security.Providers.OpenIDBase.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />

View File

@@ -4,5 +4,5 @@
<package id="Microsoft.Owin.Security" version="3.0.1" targetFramework="net45" />
<package id="Newtonsoft.Json" version="8.0.3" targetFramework="net45" />
<package id="Owin" version="1.0" targetFramework="net45" />
<package id="Owin.Security.Providers.OpenIDBase" version="2.1" targetFramework="net45" />
<package id="Owin.Security.Providers.OpenIDBase" version="2.1.1" targetFramework="net45" />
</packages>

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

@@ -81,9 +81,25 @@ namespace Owin.Security.Providers.PayPal
new KeyValuePair<string, string>("code", code),
new KeyValuePair<string, string>("redirect_uri", redirectUri),
});
var tokenResponse = await _httpClient.SendAsync(requestMessage);
tokenResponse.EnsureSuccessStatusCode();
var text = await tokenResponse.Content.ReadAsStringAsync();
string text = "";
try
{
var tokenResponse = await _httpClient.SendAsync(requestMessage);
tokenResponse.EnsureSuccessStatusCode();
text = await tokenResponse.Content.ReadAsStringAsync();
}
catch (HttpRequestException ex)
{
if (ex.InnerException is System.Net.WebException && ex.InnerException.Message.Contains("TLS"))
{
if (!System.Net.ServicePointManager.SecurityProtocol.HasFlag(System.Net.SecurityProtocolType.Tls12))
{
throw new System.Net.WebException("PayPal requires TLS v1.2. TLS v1.0 and v1.1 connections will be refused. Set System.Net.ServicePointManager.SecurityProtocol = System.Net.ServicePointManager.SecurityProtocol | System.Net.SecurityProtocolType.Tls12", ex.InnerException);
}
}
throw;
}
// Deserializes the token response
var response = JsonConvert.DeserializeObject<dynamic>(text);

View File

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

View File

@@ -0,0 +1,100 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{A7B95FD4-08AD-499F-B574-07560CC2A63F}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Owin.Security.Providers.Podbean</RootNamespace>
<AssemblyName>Owin.Security.Providers.Podbean</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Owin.Security, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
<HintPath>..\..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Net.Http.WebRequest" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="PodbeanAuthenticationExtensions.cs" />
<Compile Include="PodbeanAuthenticationHandler.cs" />
<Compile Include="PodbeanAuthenticationMiddleware.cs" />
<Compile Include="PodbeanAuthenticationOptions.cs" />
<Compile Include="Constants.cs" />
<Compile Include="Provider\PodbeanAuthenticatedContext.cs" />
<Compile Include="Provider\PodbeanAuthenticationProvider.cs" />
<Compile Include="Provider\PodbeanReturnEndpointContext.cs" />
<Compile Include="Provider\IPodbeanAuthenticationProvider.cs" />
<Compile Include="Resources.Designer.cs">
<DependentUpon>Resources.resx</DependentUpon>
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="PostBuildMacros">
<GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
<Output TaskParameter="Assemblies" ItemName="Targets" />
</GetAssemblyIdentity>
<ItemGroup>
<VersionNumber Include="@(Targets->'%(Version)')" />
</ItemGroup>
</Target>
<PropertyGroup>
<PreBuildEvent>
</PreBuildEvent>
<PostBuildEventDependsOn>
$(PostBuildEventDependsOn);
PostBuildMacros;
</PostBuildEventDependsOn>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,33 @@
#region
using System;
#endregion
namespace Owin.Security.Providers.Podbean
{
public static class PodbeanAuthenticationExtensions
{
public static IAppBuilder UsePodbeanAuthentication(this IAppBuilder app,
PodbeanAuthenticationOptions options)
{
if (app == null)
throw new ArgumentNullException(nameof(app));
if (options == null)
throw new ArgumentNullException(nameof(options));
app.Use(typeof(PodbeanAuthenticationMiddleware), app, options);
return app;
}
public static IAppBuilder UsePodbeanAuthentication(this IAppBuilder app, string appId, string appSecret)
{
return app.UsePodbeanAuthentication(new PodbeanAuthenticationOptions
{
AppId = appId,
AppSecret = appSecret
});
}
}
}

View File

@@ -0,0 +1,246 @@
#region
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Security.Claims;
using System.Threading.Tasks;
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 Microsoft.Owin;
using System.Net.Http.Headers;
#endregion
namespace Owin.Security.Providers.Podbean
{
public class PodbeanAuthenticationHandler : AuthenticationHandler<PodbeanAuthenticationOptions>
{
private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string";
private const string TokenEndpoint = "https://api.podbean.com/v1/oauth/token";
private const string PodcastIdEndpoint = "https://api.podbean.com/v1/oauth/debugToken";
private const string PodcastEndpoint = "https://api.podbean.com/v1/podcast";
private readonly HttpClient _httpClient;
private readonly ILogger _logger;
public PodbeanAuthenticationHandler(HttpClient httpClient, ILogger logger)
{
_httpClient = httpClient;
_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 (values != null && values.Count == 1)
code = values[0];
values = query.GetValues("state");
if (values != null && values.Count == 1)
state = values[0];
properties = Options.StateDataFormat.Unprotect(state);
if (properties == null)
return null;
// OAuth2 10.12 CSRF
if (!ValidateCorrelationId(properties, _logger))
return new AuthenticationTicket(null, properties);
var requestPrefix = GetBaseUri(Request);
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>("grant_type", "authorization_code"),
new KeyValuePair<string, string>("code", code),
new KeyValuePair<string, string>("redirect_uri", redirectUri)
};
// Request the token
var tokenRequest = new HttpRequestMessage(HttpMethod.Post, TokenEndpoint);
tokenRequest.Headers.Authorization =
new AuthenticationHeaderValue("Basic", Base64Encode($"{Options.AppId}:{Options.AppSecret}"));
tokenRequest.Content = new FormUrlEncodedContent(body);
var tokenResponse = await _httpClient.SendAsync(tokenRequest, Request.CallCancelled);
tokenResponse.EnsureSuccessStatusCode();
var text = await tokenResponse.Content.ReadAsStringAsync();
// Deserializes the token response
dynamic token = JsonConvert.DeserializeObject<dynamic>(text);
var accessToken = (string)token.access_token;
var refreshToken = (string)token.refresh_token;
var expires = (string)token.expires_in;
// Get the Podbean podcast
var podcastResponse = await _httpClient.GetAsync(
$"{PodcastEndpoint}?access_token={Uri.EscapeDataString(accessToken)}", Request.CallCancelled);
podcastResponse.EnsureSuccessStatusCode();
text = await podcastResponse.Content.ReadAsStringAsync();
var podcast = JObject.Parse(text)["podcast"].ToObject<Podcast>();
// Get the Podbean podcast id
var podcastIdRequest = new HttpRequestMessage(HttpMethod.Get, $"{PodcastIdEndpoint}?access_token={Uri.EscapeDataString(accessToken)}");
podcastIdRequest.Headers.Authorization =
new AuthenticationHeaderValue("Basic", Base64Encode($"{Options.AppId}:{Options.AppSecret}"));
var podcastIdResponse = await _httpClient.SendAsync(podcastIdRequest, Request.CallCancelled);
podcastIdResponse.EnsureSuccessStatusCode();
text = await podcastIdResponse.Content.ReadAsStringAsync();
var podcastId = JsonConvert.DeserializeObject<dynamic>(text);
podcast.Id = (string)podcastId.podcast_id;
var context = new PodbeanAuthenticatedContext(Context, podcast, accessToken, refreshToken, expires)
{
Identity = new ClaimsIdentity(
Options.AuthenticationType,
ClaimsIdentity.DefaultNameClaimType,
ClaimsIdentity.DefaultRoleClaimType)
};
if (!string.IsNullOrEmpty(context.Id))
context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, context.Id, XmlSchemaString,
Options.AuthenticationType));
if (!string.IsNullOrEmpty(context.Name))
context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Name,
XmlSchemaString, Options.AuthenticationType));
context.Properties = properties;
await Options.Provider.Authenticated(context);
return new AuthenticationTicket(context.Identity, context.Properties);
}
catch (Exception ex)
{
_logger.WriteError(ex.Message);
}
return new AuthenticationTicket(null, properties);
}
protected override Task ApplyResponseChallengeAsync()
{
if (Response.StatusCode != 401)
return Task.FromResult<object>(null);
var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode);
if (challenge == null) return Task.FromResult<object>(null);
var baseUri = GetBaseUri(Request);
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 state = Options.StateDataFormat.Protect(properties);
var scope = string.Join(" ", Options.Scope);
var authorizationEndpoint =
"https://api.podbean.com/v1/dialog/oauth" +
"?response_type=code" +
"&client_id=" + Uri.EscapeDataString(Options.AppId) +
"&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
var ticket = await AuthenticateAsync();
if (ticket == null)
{
_logger.WriteWarning("Invalid return state, unable to redirect.");
Response.StatusCode = 500;
return true;
}
var context = new PodbeanReturnEndpointContext(Context, ticket)
{
SignInAsAuthenticationType = Options.SignInAsAuthenticationType,
RedirectUri = ticket.Properties.RedirectUri
};
await Options.Provider.ReturnEndpoint(context);
if (context.SignInAsAuthenticationType != null &&
context.Identity != null)
{
var grantIdentity = context.Identity;
if (!string.Equals(grantIdentity.AuthenticationType, context.SignInAsAuthenticationType,
StringComparison.Ordinal))
grantIdentity = new ClaimsIdentity(grantIdentity.Claims, context.SignInAsAuthenticationType,
grantIdentity.NameClaimType, grantIdentity.RoleClaimType);
Context.Authentication.SignIn(context.Properties, grantIdentity);
}
if (context.IsRequestCompleted || context.RedirectUri == null) return context.IsRequestCompleted;
var redirectUri = context.RedirectUri;
if (context.Identity == null)
redirectUri = WebUtilities.AddQueryString(redirectUri, "error", "access_denied");
Response.Redirect(redirectUri);
context.RequestCompleted();
return context.IsRequestCompleted;
}
private static string Base64Encode(string plainText)
{
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
return System.Convert.ToBase64String(plainTextBytes);
}
private string GetBaseUri(IOwinRequest request)
{
if (Options.DebugUsingRequestHeadersToBuildBaseUri &&
request.Headers["X-Original-Host"] != null &&
request.Headers["X-Forwarded-Proto"] != null)
{
return request.Headers["X-Forwarded-Proto"] + Uri.SchemeDelimiter + request.Headers["X-Original-Host"];
}
var baseUri =
request.Scheme +
Uri.SchemeDelimiter +
request.Host +
request.PathBase;
return baseUri;
}
}
}

View File

@@ -0,0 +1,84 @@
#region
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;
#endregion
namespace Owin.Security.Providers.Podbean
{
public class PodbeanAuthenticationMiddleware : AuthenticationMiddleware<PodbeanAuthenticationOptions>
{
private readonly HttpClient _httpClient;
private readonly ILogger _logger;
public PodbeanAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app,
PodbeanAuthenticationOptions options)
: base(next, options)
{
if (string.IsNullOrWhiteSpace(Options.AppId))
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
Resources.Exception_OptionMustBeProvided, "AppId"));
if (string.IsNullOrWhiteSpace(Options.AppSecret))
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
Resources.Exception_OptionMustBeProvided, "AppSecret"));
_logger = app.CreateLogger<PodbeanAuthenticationMiddleware>();
if (Options.Provider == null)
Options.Provider = new PodbeanAuthenticationProvider();
if (Options.StateDataFormat == null)
{
var dataProtector = app.CreateDataProtector(
typeof(PodbeanAuthenticationMiddleware).FullName,
Options.AuthenticationType, "v1");
Options.StateDataFormat = new PropertiesDataFormat(dataProtector);
}
if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType))
Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType();
_httpClient = new HttpClient(ResolveHttpMessageHandler(Options))
{
Timeout = Options.BackchannelTimeout,
MaxResponseContentBufferSize = 1024 * 1024 * 10
};
}
/// <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.Podbean.PodbeanAuthenticationOptions" /> supplied to the constructor.
/// </returns>
protected override AuthenticationHandler<PodbeanAuthenticationOptions> CreateHandler()
{
return new PodbeanAuthenticationHandler(_httpClient, _logger);
}
private static HttpMessageHandler ResolveHttpMessageHandler(PodbeanAuthenticationOptions options)
{
var handler = options.BackchannelHttpHandler ?? new WebRequestHandler();
// If they provided a validator, apply it or fail.
if (options.BackchannelCertificateValidator == null) return handler;
// Set the cert validate callback
var webRequestHandler = handler as WebRequestHandler;
if (webRequestHandler == null)
throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch);
webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
return handler;
}
}
}

View File

@@ -0,0 +1,110 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using Microsoft.Owin;
using Microsoft.Owin.Security;
namespace Owin.Security.Providers.Podbean
{
public class PodbeanAuthenticationOptions : AuthenticationOptions
{
/// <summary>
/// Initializes a new <see cref="PodbeanAuthenticationOptions" />
/// </summary>
public PodbeanAuthenticationOptions()
: base("Podbean")
{
Caption = Constants.DefaultAuthenticationType;
CallbackPath = new PathString("/signin-podbean");
AuthenticationMode = AuthenticationMode.Passive;
Scope = new List<string>
{
"podcast_read"
};
BackchannelTimeout = TimeSpan.FromSeconds(60);
}
/// <summary>
/// Gets or sets the a pinned certificate validator to use to validate the endpoints used
/// in back channel communications belong to Podbean
/// </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 Podbean.
/// 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 Podbean.
/// </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-Podbean".
/// </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 Podbean supplied App ID
/// </summary>
public string AppId { get; set; }
/// <summary>
/// Gets or sets the Podbean supplied App Secret
/// </summary>
public string AppSecret { get; set; }
/// <summary>
/// Set this value to true to debug locally using a service such as https://ngrok.io.
/// Podbean doesn't allow you to redirect to localhost. Ngrok and similar services
/// set the X-Original-Host and X-Forwarded-Proto headers to build the base Uri for
/// redirects back to localhost.
/// </summary>
public bool DebugUsingRequestHeadersToBuildBaseUri { get; set; }
/// <summary>
/// A list of permissions to request.
/// </summary>
public IList<string> Scope { get; private set; }
/// <summary>
/// Gets or sets the <see cref="IPodbeanAuthenticationProvider" /> used in the authentication events
/// </summary>
public IPodbeanAuthenticationProvider 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; }
}
}

View File

@@ -0,0 +1,15 @@
using System.Reflection;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("Owin.Security.Providers.Podbean")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Owin.Security.Providers.Podbean")]
[assembly: AssemblyCopyright("Copyright © 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
[assembly: Guid("38815307-3360-4f54-9204-03fae50160bc")]
[assembly: AssemblyVersion("2.0.0.0")]
[assembly: AssemblyFileVersion("2.0.0.0")]

View File

@@ -0,0 +1,33 @@
#region
using System.Threading.Tasks;
#endregion
namespace Owin.Security.Providers.Podbean
{
/// <summary>
/// Specifies callback methods which the <see cref="PodbeanAuthenticationMiddleware"></see> invokes to enable developer
/// control over the authentication process. />
/// </summary>
public interface IPodbeanAuthenticationProvider
{
/// <summary>
/// Invoked whenever Podbean successfully 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(PodbeanAuthenticatedContext 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(PodbeanReturnEndpointContext context);
}
}

View File

@@ -0,0 +1,130 @@
#region
using System;
using System.Globalization;
using System.Security.Claims;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Provider;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
#endregion
namespace Owin.Security.Providers.Podbean
{
/// <summary>
/// Contains information about the login session as well as the user
/// <see cref="System.Security.Claims.ClaimsIdentity" />.
/// </summary>
public class PodbeanAuthenticatedContext : BaseContext
{
/// <summary>
/// Initializes a <see cref="PodbeanAuthenticatedContext" />
/// </summary>
/// <param name="context">The OWIN environment</param>
/// <param name="podcast">The <see cref="Podcast"/></param>
/// <param name="accessToken">Podbean Access token</param>
/// <param name="refreshToken">Podbean Refresh token</param>
/// <param name="expires">Seconds until expiration</param>
public PodbeanAuthenticatedContext(IOwinContext context, Podcast podcast, string accessToken, string refreshToken, string expires)
: base(context)
{
Podcast = podcast;
Id = podcast.Id;
Name = podcast.Title;
AccessToken = accessToken;
RefreshToken = refreshToken;
int expiresValue;
if (int.TryParse(expires, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue))
ExpiresIn = TimeSpan.FromSeconds(expiresValue);
}
/// <summary>
/// Gets the JSON-serialized user
/// </summary>
/// <remarks>
/// Contains the Podbean user obtained from the endpoint https://api.Podbeanapp.com/1/user.json
/// </remarks>
public Podcast Podcast { get; }
/// <summary>
/// Gets the Podbean OAuth access token
/// </summary>
public string AccessToken { get; }
/// <summary>
/// Gets the Podbean OAuth refresh token
/// </summary>
public string RefreshToken { get; }
/// <summary>
/// Gets the Podbean access token expiration time
/// </summary>
public TimeSpan? ExpiresIn { get; set; }
/// <summary>
/// Gets the Podbean Podcast ID
/// </summary>
public string Id { get; }
/// <summary>
/// The name of the user
/// </summary>
public string Name { get; }
/// <summary>
/// Gets the <see cref="ClaimsIdentity" /> representing the user
/// </summary>
public ClaimsIdentity Identity { get; set; }
/// <summary>
/// Gets or sets a property bag for common authentication properties
/// </summary>
public AuthenticationProperties Properties { get; set; }
}
public class Podcast
{
/// <summary>
/// The Id of the <see cref="Podcast"/>
/// </summary>
public string Id { get; set; }
/// <summary>
/// The title of the <see cref="Podcast"/>
/// </summary>
public string Title { get; set; }
/// <summary>
/// The description of the <see cref="Podcast"/>
/// </summary>
[JsonProperty(PropertyName = "desc")]
public string Description { get; set; }
/// <summary>
/// A url to an image representing the logo of the <see cref="Podcast"/>
/// </summary>
public string Logo { get; set; }
/// <summary>
/// A url to the <see cref="Podcast"/>'s website
/// </summary>
public string Website { get; set; }
/// <summary>
/// The name of the category of the <see cref="Podcast"/>
/// </summary>
[JsonProperty(PropertyName = "category_name")]
public string CategoryName { get; set; }
/// <summary>
/// The episode types of the <see cref="Podcast"/>.
/// The possible value is a combination of public, premium or private
/// </summary>
[JsonProperty(PropertyName = "allow_episode_type")]
public string[] AllowEpisodeTypes { get; set; }
}
}

View File

@@ -0,0 +1,58 @@
#region
using System;
using System.Threading.Tasks;
#endregion
namespace Owin.Security.Providers.Podbean
{
/// <summary>
/// Default <see cref="IPodbeanAuthenticationProvider" /> implementation.
/// </summary>
public class PodbeanAuthenticationProvider : IPodbeanAuthenticationProvider
{
/// <summary>
/// Initializes a <see cref="PodbeanAuthenticationProvider" />
/// </summary>
public PodbeanAuthenticationProvider()
{
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<PodbeanAuthenticatedContext, Task> OnAuthenticated { get; set; }
/// <summary>
/// Gets or sets the function that is invoked when the ReturnEndpoint method is invoked.
/// </summary>
public Func<PodbeanReturnEndpointContext, Task> OnReturnEndpoint { get; set; }
/// <summary>
/// Invoked whenever Podbean successfully 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(PodbeanAuthenticatedContext 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(PodbeanReturnEndpointContext context)
{
return OnReturnEndpoint(context);
}
}
}

View File

@@ -0,0 +1,23 @@
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Provider;
namespace Owin.Security.Providers.Podbean
{
/// <summary>
/// Provides context information to middleware providers.
/// </summary>
public class PodbeanReturnEndpointContext : ReturnEndpointContext
{
/// <summary>
/// </summary>
/// <param name="context">OWIN environment</param>
/// <param name="ticket">The authentication ticket</param>
public PodbeanReturnEndpointContext(
IOwinContext context,
AuthenticationTicket ticket)
: base(context, ticket)
{
}
}
}

View File

@@ -0,0 +1,81 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Owin.Security.Providers.Podbean {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.Podbean.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to The &apos;{0}&apos; option must be provided..
/// </summary>
internal static string Exception_OptionMustBeProvided {
get {
return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler..
/// </summary>
internal static string Exception_ValidatorHandlerMismatch {
get {
return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture);
}
}
}
}

View File

@@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Exception_OptionMustBeProvided" xml:space="preserve">
<value>The '{0}' option must be provided.</value>
</data>
<data name="Exception_ValidatorHandlerMismatch" xml:space="preserve">
<value>An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.</value>
</data>
</root>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Owin" version="3.0.1" targetFramework="net45" />
<package id="Microsoft.Owin.Security" version="3.0.1" targetFramework="net45" />
<package id="Newtonsoft.Json" version="8.0.3" targetFramework="net45" />
<package id="Owin" version="1.0" targetFramework="net45" />
</packages>

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

@@ -1,7 +1,11 @@
namespace Owin.Security.Providers.Salesforce
{
internal static class Constants
public static class Constants
{
public const string DefaultAuthenticationType = "Salesforce";
public const string EnvironmentAuthenticationProperty = "Environment";
public const string ProductionEnvironment = "Production";
public const string SandboxEnvironment = "Sandbox";
}
}

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

View File

@@ -18,6 +18,12 @@ namespace Owin.Security.Providers.Salesforce
{
private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string";
private const string ProductionHost = "https://login.salesforce.com";
private const string SandboxHost = "https://test.salesforce.com";
private const string AuthorizationEndpoint = "/services/oauth2/authorize";
private const string TokenEndpoint = "/services/oauth2/token";
private readonly ILogger _logger;
private readonly HttpClient _httpClient;
@@ -74,7 +80,7 @@ namespace Owin.Security.Providers.Salesforce
};
// Request the token
var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.Endpoints.TokenEndpoint);
var requestMessage = new HttpRequestMessage(HttpMethod.Post, ComposeTokenEndpoint(properties));
requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
requestMessage.Content = new FormUrlEncodedContent(body);
var tokenResponse = await _httpClient.SendAsync(requestMessage);
@@ -186,8 +192,10 @@ namespace Owin.Security.Providers.Salesforce
var state = Options.StateDataFormat.Protect(properties);
var authorizationEndpoint =
$"{Options.Endpoints.AuthorizationEndpoint}?response_type={"code"}&client_id={Options.ClientId}&redirect_uri={HttpUtility.UrlEncode(redirectUri)}&display={"page"}&immediate={false}&state={Uri.EscapeDataString(state)}";
var authorizationEndpoint = ComposeAuthorizationEndpoint(properties);
authorizationEndpoint =
$"{authorizationEndpoint}?response_type={"code"}&client_id={Options.ClientId}&redirect_uri={HttpUtility.UrlEncode(redirectUri)}&display={Options.DisplayMode}&immediate={false}&state={Uri.EscapeDataString(state)}";
if (Options.Scope != null && Options.Scope.Count > 0)
{
@@ -253,5 +261,66 @@ namespace Owin.Security.Providers.Salesforce
return context.IsRequestCompleted;
}
private string ComposeAuthorizationEndpoint(AuthenticationProperties properties) {
string endpointPath = AuthorizationEndpoint;
string endpoint =
!String.IsNullOrEmpty(Options.Endpoints.AuthorizationEndpoint) ?
Options.Endpoints.AuthorizationEndpoint :
ComposeEndpoint(properties, endpointPath);
// if AuthenticationProperties for this session specifies an environment property
// it should take precedence over the value in AuthenticationOptions
string environmentProperty = null;
if (properties.Dictionary.TryGetValue(Constants.EnvironmentAuthenticationProperty, out environmentProperty)) {
endpoint =
environmentProperty == Constants.SandboxEnvironment ?
SandboxHost + endpointPath :
ProductionHost + endpointPath;
}
return endpoint;
}
private string ComposeTokenEndpoint(AuthenticationProperties properties) {
string endpointPath = TokenEndpoint;
string endpoint =
!String.IsNullOrEmpty(Options.Endpoints.TokenEndpoint) ?
Options.Endpoints.TokenEndpoint :
ComposeEndpoint(properties, endpointPath);
// if AuthenticationProperties for this session specifies an environment property
// it should take precedence over the value in AuthenticationOptions
string environmentProperty = null; ;
if (properties.Dictionary.TryGetValue(Constants.EnvironmentAuthenticationProperty, out environmentProperty)) {
endpoint =
environmentProperty == Constants.SandboxEnvironment ?
SandboxHost + endpointPath :
ProductionHost + endpointPath;
}
return endpoint;
}
private string ComposeEndpoint(AuthenticationProperties properties, string endpointPath) {
string endpoint =
!String.IsNullOrEmpty(Options.Endpoints.Environment) && Options.Endpoints.Environment == Constants.SandboxEnvironment ?
SandboxHost + endpointPath :
ProductionHost + endpointPath;
// if AuthenticationProperties for this session specifies an environment property
// it should take precedence over the value in AuthenticationOptions
string environmentProperty = null; ;
if (properties.Dictionary.TryGetValue(Constants.EnvironmentAuthenticationProperty, out environmentProperty)) {
endpoint =
environmentProperty == Constants.SandboxEnvironment ?
SandboxHost + endpointPath :
ProductionHost + endpointPath;
}
return endpoint;
}
}
}

View File

@@ -19,11 +19,28 @@ namespace Owin.Security.Providers.Salesforce
/// Endpoint which is used to exchange code for access token
/// </summary>
public string TokenEndpoint { get; set; }
/// <summary>
/// Production or Sandbox. Use Constants.ProductionEnvironment or Constants.SandboxEnvironment
/// </summary>
public string Environment { get; set; }
}
private const string AuthorizationEndPoint = "";
private const string TokenEndpoint = "";
/// <summary>
/// Options for Display Mode
/// Changes the login and authorization pages display type. Salesforce supports these values.
/// page—Full-page authorization screen(default)
/// popup—Compact dialog optimized for modern web browser popup windows
/// touch—Mobile-optimized dialog designed for modern smartphones, such as Android and iPhone
/// mobile—Mobile-optimized dialog designed for less capable smartphones, such as BlackBerry OS 5
/// </summary>
public enum Display{
page,
popup,
touch,
mobile
}
/// <summary>
/// Gets or sets the a pinned certificate validator to use to validate the endpoints used
/// in back channel communications belong to Salesforce.
@@ -79,8 +96,8 @@ namespace Owin.Security.Providers.Salesforce
public string ClientSecret { get; set; }
/// <summary>
/// Gets the sets of OAuth endpoints used to authenticate against Salesforce. Overriding these endpoints allows you to use Salesforce Enterprise for
/// authentication.
/// Gets the sets of OAuth endpoints used to authenticate against Salesforce.
/// Overriding these endpoints allows you to use Salesforce Enterprise for authentication.
/// </summary>
public SalesforceAuthenticationEndpoints Endpoints { get; set; }
@@ -108,6 +125,10 @@ namespace Owin.Security.Providers.Salesforce
/// <see cref="System.Security.Claims.ClaimsIdentity" />.
/// </summary>
public string SignInAsAuthenticationType { get; set; }
/// <summary>
/// Gets or sets the display—(Optional)
/// </summary>
public Display DisplayMode { get; set; }
/// <summary>
/// Gets or sets the type used to secure data handled by the middleware.
@@ -127,8 +148,8 @@ namespace Owin.Security.Providers.Salesforce
BackchannelTimeout = TimeSpan.FromSeconds(60);
Endpoints = new SalesforceAuthenticationEndpoints
{
AuthorizationEndpoint = AuthorizationEndPoint,
TokenEndpoint = TokenEndpoint
AuthorizationEndpoint = null,
TokenEndpoint = null
};
}
}

View File

@@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

Some files were not shown because too many files have changed in this diff Show More