Compare commits

..

3 Commits

Author SHA1 Message Date
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
19 changed files with 1074 additions and 1 deletions

View File

@@ -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

View File

@@ -307,6 +307,13 @@ namespace OwinOAuthProvidersDemo
// AppKey = "",
// AppSecret = ""
//});
//app.UsePodbeanAuthentication(new PodbeanAuthenticationOptions
//{
// AppId = "",
// AppSecret = "",
// DebugUsingRequestHeadersToBuildBaseUri = true
//});
}
}
}

View File

@@ -22,6 +22,7 @@
<IISExpressUseClassicPipelineMode>false</IISExpressUseClassicPipelineMode>
<UseGlobalApplicationHostFile />
<TargetFrameworkProfile />
<Use64BitIISExpress />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -380,6 +381,10 @@
<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>

View File

@@ -29,6 +29,7 @@ Provides a set of extra authentication providers for OWIN ([Project Katana](http
- Onshape
- ORCID
- PayPal
- Podbean
- Reddit
- Salesforce
- Shopify

View File

@@ -15,7 +15,7 @@ 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.15.1"
@version = "2.17.0"
PROJECTS = Dir.glob('src/*').select{|dir| File.directory? dir }
desc 'Retrieve things'

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,98 @@
<?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>
</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="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>