diff --git a/OwinOAuthProviders.sln b/OwinOAuthProviders.sln
index b984d9e..1d462ce 100644
--- a/OwinOAuthProviders.sln
+++ b/OwinOAuthProviders.sln
@@ -108,6 +108,8 @@ 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
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -326,8 +328,15 @@ 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
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {EDFDB942-6583-4AD9-A868-B354B5BF07FC}
+ EndGlobalSection
EndGlobal
diff --git a/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs b/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs
index 0a98ee8..b1bf7d8 100755
--- a/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs
+++ b/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs
@@ -307,6 +307,13 @@ namespace OwinOAuthProvidersDemo
// AppKey = "",
// AppSecret = ""
//});
+
+ //app.UsePodbeanAuthentication(new PodbeanAuthenticationOptions
+ //{
+ // AppId = "",
+ // AppSecret = "",
+ // DebugUsingRequestHeadersToBuildBaseUri = true
+ //});
}
}
}
\ No newline at end of file
diff --git a/OwinOAuthProvidersDemo/OwinOAuthProvidersDemo.csproj b/OwinOAuthProvidersDemo/OwinOAuthProvidersDemo.csproj
index ee56195..969ad49 100644
--- a/OwinOAuthProvidersDemo/OwinOAuthProvidersDemo.csproj
+++ b/OwinOAuthProvidersDemo/OwinOAuthProvidersDemo.csproj
@@ -22,6 +22,7 @@
false
+
true
@@ -380,6 +381,10 @@
{f7129064-3db7-4b79-81d3-80130d664e45}
Owin.Security.Providers.PayPal
+
+ {a7b95fd4-08ad-499f-b574-07560cc2a63f}
+ Owin.Security.Providers.Podbean
+
{d0cd86c8-a6f9-4c6c-9bf0-eaa461e7fbad}
Owin.Security.Providers.Reddit
diff --git a/README.md b/README.md
index 2bf8d6d..9bcbf4d 100644
--- a/README.md
+++ b/README.md
@@ -29,6 +29,7 @@ Provides a set of extra authentication providers for OWIN ([Project Katana](http
- Onshape
- ORCID
- PayPal
+ - Podbean
- Reddit
- Salesforce
- Shopify
diff --git a/src/Owin.Security.Providers.Podbean/Constants.cs b/src/Owin.Security.Providers.Podbean/Constants.cs
new file mode 100644
index 0000000..9be8f40
--- /dev/null
+++ b/src/Owin.Security.Providers.Podbean/Constants.cs
@@ -0,0 +1,7 @@
+namespace Owin.Security.Providers.Podbean
+{
+ internal static class Constants
+ {
+ public const string DefaultAuthenticationType = "Podbean";
+ }
+}
\ No newline at end of file
diff --git a/src/Owin.Security.Providers.Podbean/Owin.Security.Providers.Podbean.csproj b/src/Owin.Security.Providers.Podbean/Owin.Security.Providers.Podbean.csproj
new file mode 100644
index 0000000..ae986db
--- /dev/null
+++ b/src/Owin.Security.Providers.Podbean/Owin.Security.Providers.Podbean.csproj
@@ -0,0 +1,98 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {A7B95FD4-08AD-499F-B574-07560CC2A63F}
+ Library
+ Properties
+ Owin.Security.Providers.Podbean
+ Owin.Security.Providers.Podbean
+ v4.5
+ 512
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll
+
+
+ ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll
+
+
+ ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll
+
+
+ ..\..\packages\Owin.1.0\lib\net40\Owin.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Resources.resx
+ True
+ True
+
+
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(PostBuildEventDependsOn);
+ PostBuildMacros;
+
+
+
\ No newline at end of file
diff --git a/src/Owin.Security.Providers.Podbean/PodbeanAuthenticationExtensions.cs b/src/Owin.Security.Providers.Podbean/PodbeanAuthenticationExtensions.cs
new file mode 100644
index 0000000..1c03556
--- /dev/null
+++ b/src/Owin.Security.Providers.Podbean/PodbeanAuthenticationExtensions.cs
@@ -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
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Owin.Security.Providers.Podbean/PodbeanAuthenticationHandler.cs b/src/Owin.Security.Providers.Podbean/PodbeanAuthenticationHandler.cs
new file mode 100644
index 0000000..495ac8f
--- /dev/null
+++ b/src/Owin.Security.Providers.Podbean/PodbeanAuthenticationHandler.cs
@@ -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
+ {
+ 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 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>
+ {
+ new KeyValuePair("grant_type", "authorization_code"),
+ new KeyValuePair("code", code),
+ new KeyValuePair("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(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();
+
+ // 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(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