Skip to content

Commit

Permalink
Add a Salesforce provider
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrew Mattie authored and kevinchalet committed Jun 2, 2016
1 parent b9c840e commit 14618f6
Show file tree
Hide file tree
Showing 11 changed files with 411 additions and 0 deletions.
7 changes: 7 additions & 0 deletions AspNet.Security.OAuth.Providers.sln
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "AspNet.Security.OAuth.Vimeo
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "AspNet.Security.OAuth.ArcGIS", "src\AspNet.Security.OAuth.ArcGIS\AspNet.Security.OAuth.ArcGIS.xproj", "{1302E67D-5448-407B-8B8D-709A0BA458E8}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "AspNet.Security.OAuth.Salesforce", "src\AspNet.Security.OAuth.Salesforce\AspNet.Security.OAuth.Salesforce.xproj", "{5881BB63-89BD-4397-A61C-69B1844F7615}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -169,6 +171,10 @@ Global
{1302E67D-5448-407B-8B8D-709A0BA458E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1302E67D-5448-407B-8B8D-709A0BA458E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1302E67D-5448-407B-8B8D-709A0BA458E8}.Release|Any CPU.Build.0 = Release|Any CPU
{5881BB63-89BD-4397-A61C-69B1844F7615}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5881BB63-89BD-4397-A61C-69B1844F7615}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5881BB63-89BD-4397-A61C-69B1844F7615}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5881BB63-89BD-4397-A61C-69B1844F7615}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -200,5 +206,6 @@ Global
{9AA5F2CD-3AC4-4177-A8FE-82D67A0F36AC} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D}
{AC651C2B-C879-41E2-96E0-78D3F0888246} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D}
{1302E67D-5448-407B-8B8D-709A0BA458E8} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D}
{5881BB63-89BD-4397-A61C-69B1844F7615} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D}
EndGlobalSection
EndGlobal
1 change: 1 addition & 0 deletions samples/Mvc.Client/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"AspNet.Security.OAuth.Onshape": { "target": "project" },
"AspNet.Security.OAuth.Paypal": { "target": "project" },
"AspNet.Security.OAuth.Reddit": { "target": "project" },
"AspNet.Security.OAuth.Salesforce": { "target": "project" },
"AspNet.Security.OAuth.Slack": { "target": "project" },
"AspNet.Security.OAuth.SoundCloud": { "target": "project" },
"AspNet.Security.OAuth.Spotify": { "target": "project" },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>5881bb63-89bd-4397-a61c-69b1844f7615</ProjectGuid>
<RootNamespace>AspNet.Security.OAuth.Salesforce</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
* for more information concerning the license and the contributors participating to this project.
*/

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Authentication.OAuth;

namespace AspNet.Security.OAuth.Salesforce {
/// <summary>
/// Default values used by the Salesforce authentication middleware.
/// </summary>
public static class SalesforceAuthenticationDefaults {
/// <summary>
/// Default value for <see cref="AuthenticationOptions.AuthenticationScheme"/>.
/// </summary>
public const string AuthenticationScheme = "Salesforce";

/// <summary>
/// Default value for <see cref="RemoteAuthenticationOptions.DisplayName"/>.
/// </summary>
public const string DisplayName = "Salesforce";

/// <summary>
/// Default value for <see cref="AuthenticationOptions.ClaimsIssuer"/>.
/// </summary>
public const string Issuer = "Salesforce";

/// <summary>
/// Default value for <see cref="RemoteAuthenticationOptions.CallbackPath"/>.
/// </summary>
public const string CallbackPath = "/signin-salesforce";

/// <summary>
/// Default value for the Salesforce environment (production or test)
/// </summary>
public const SalesforceAuthenticationEnvironment Environment = SalesforceAuthenticationEnvironment.Production;

public static class Production {
/// <summary>
/// Default value for <see cref="OAuthOptions.AuthorizationEndpoint"/>.
/// </summary>
public const string AuthorizationEndpoint = "https://login.salesforce.com/services/oauth2/authorize";

/// <summary>
/// Default value for <see cref="OAuthOptions.TokenEndpoint"/>.
/// </summary>
public const string TokenEndpoint = "https://login.salesforce.com/services/oauth2/token";
}

public static class Test {
/// <summary>
/// Default value for <see cref="OAuthOptions.AuthorizationEndpoint"/>.
/// </summary>
public const string AuthorizationEndpoint = "https://test.salesforce.com/services/oauth2/authorize";

/// <summary>
/// Default value for <see cref="OAuthOptions.TokenEndpoint"/>.
/// </summary>
public const string TokenEndpoint = "https://test.salesforce.com/services/oauth2/token";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
* for more information concerning the license and the contributors participating to this project.
*/

namespace AspNet.Security.OAuth.Salesforce {
/// <summary>
/// Defines a list of environments used to determine the appropriate
/// OAuth2 endpoints when communicating with Salesforce.
/// </summary>
public enum SalesforceAuthenticationEnvironment {
/// <summary>
/// Use login.salesforce.com in the OAuth2 endpoints.
/// </summary>
Production = 0,

/// <summary>
/// Uses test.salesforce.com in the OAuth2 endpoints.
/// </summary>
Test = 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
* for more information concerning the license and the contributors participating to this project.
*/

using System;
using AspNet.Security.OAuth.Salesforce;
using JetBrains.Annotations;
using Microsoft.Extensions.Options;

namespace Microsoft.AspNetCore.Builder {
/// <summary>
/// Extension methods to add Salesforce authentication capabilities to an HTTP application pipeline.
/// </summary>
public static class SalesforceAuthenticationExtensions {
/// <summary>
/// Adds the <see cref="SalesforceAuthenticationMiddleware"/> middleware to the specified
/// <see cref="IApplicationBuilder"/>, which enables Salesforce authentication capabilities.
/// </summary>
/// <param name="app">The <see cref="IApplicationBuilder"/> to add the middleware to.</param>
/// <param name="options">A <see cref="SalesforceAuthenticationOptions"/> that specifies options for the middleware.</param>
/// <returns>A reference to this instance after the operation has completed.</returns>
public static IApplicationBuilder UseSalesforceAuthentication(
[NotNull] this IApplicationBuilder app,
[NotNull] SalesforceAuthenticationOptions options) {
if (app == null) {
throw new ArgumentNullException(nameof(app));
}

if (options == null) {
throw new ArgumentNullException(nameof(options));
}

return app.UseMiddleware<SalesforceAuthenticationMiddleware>(Options.Create(options));
}

/// <summary>
/// Adds the <see cref="SalesforceAuthenticationMiddleware"/> middleware to the specified
/// <see cref="IApplicationBuilder"/>, which enables Salesforce authentication capabilities.
/// </summary>
/// <param name="app">The <see cref="IApplicationBuilder"/> to add the middleware to.</param>
/// <param name="configuration">An action delegate to configure the provided <see cref="SalesforceAuthenticationOptions"/>.</param>
/// <returns>A reference to this instance after the operation has completed.</returns>
public static IApplicationBuilder UseSalesforceAuthentication(
[NotNull] this IApplicationBuilder app,
[NotNull] Action<SalesforceAuthenticationOptions> configuration) {
if (app == null) {
throw new ArgumentNullException(nameof(app));
}

if (configuration == null) {
throw new ArgumentNullException(nameof(configuration));
}

var options = new SalesforceAuthenticationOptions();
configuration(options);

return app.UseMiddleware<SalesforceAuthenticationMiddleware>(Options.Create(options));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
* for more information concerning the license and the contributors participating to this project.
*/

using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Threading.Tasks;
using AspNet.Security.OAuth.Extensions;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.OAuth;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;

namespace AspNet.Security.OAuth.Salesforce {
public class SalesforceAuthenticationHandler : OAuthHandler<SalesforceAuthenticationOptions> {
public SalesforceAuthenticationHandler([NotNull] HttpClient client)
: base(client) {
}

protected override async Task<AuthenticationTicket> CreateTicketAsync([NotNull] ClaimsIdentity identity,
[NotNull] AuthenticationProperties properties, [NotNull] OAuthTokenResponse tokens) {
// Note: unlike the other social providers, the userinfo endpoint is user-specific and can't be set globally.
// For more information, see https://developer.salesforce.com/page/Digging_Deeper_into_OAuth_2.0_on_Force.com
var request = new HttpRequestMessage(HttpMethod.Get, tokens.Response.Value<string>("id"));

request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

var response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted);
if (!response.IsSuccessStatusCode) {
Logger.LogError("An error occurred when retrieving the user profile: the remote server " +
"returned a {Status} response with the following payload: {Headers} {Body}.",
/* Status: */ response.StatusCode,
/* Headers: */ response.Headers.ToString(),
/* Body: */ await response.Content.ReadAsStringAsync());

throw new HttpRequestException("An error occurred when retrieving the user from the Salesforce identity service.");
}

var payload = JObject.Parse(await response.Content.ReadAsStringAsync());

identity.AddOptionalClaim(ClaimTypes.NameIdentifier, SalesforceAuthenticationHelper.GetUserIdentifier(payload), Options.ClaimsIssuer)
.AddOptionalClaim(ClaimTypes.Name, SalesforceAuthenticationHelper.GetUserName(payload), Options.ClaimsIssuer)
.AddOptionalClaim("urn:salesforce:email", SalesforceAuthenticationHelper.GetEmail(payload), Options.ClaimsIssuer)
.AddOptionalClaim("urn:salesforce:thumbnail_photo", SalesforceAuthenticationHelper.GetThumbnailPhoto(payload), Options.ClaimsIssuer)
.AddOptionalClaim("urn:salesforce:utc_offset", SalesforceAuthenticationHelper.GetUtcOffset(payload).ToString(), Options.ClaimsIssuer)
.AddOptionalClaim("urn:salesforce:rest_url", SalesforceAuthenticationHelper.GetRestUrl(payload), Options.ClaimsIssuer);

var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, properties, Options.AuthenticationScheme);

var context = new OAuthCreatingTicketContext(ticket, Context, Options, Backchannel, tokens, payload);
await Options.Events.CreatingTicket(context);

return context.Ticket;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
* for more information concerning the license and the contributors participating to this project.
*/

using JetBrains.Annotations;
using Newtonsoft.Json.Linq;

namespace AspNet.Security.OAuth.Salesforce {
/// <summary>
/// Contains static methods that allow to extract user's information from a <see cref="JObject"/>
/// instance retrieved from Salesforce after a successful authentication process.
/// </summary>
public static class SalesforceAuthenticationHelper {
/// <summary>
/// Gets the Salesforce ID corresponding to the authenticated user.
/// </summary>
public static string GetUserIdentifier([NotNull] JObject user) => user.Value<string>("user_id");

/// <summary>
/// Gets the user's name.
/// </summary>
public static string GetUserName([NotNull] JObject user) => user.Value<string>("user_name");

/// <summary>
/// Gets the user's email.
/// </summary>
public static string GetEmail([NotNull] JObject user) => user.Value<string>("email");

/// <summary>
/// Gets the user's thumbnail photo.
/// </summary>
public static string GetThumbnailPhoto([NotNull] JObject user) => user["photos"]?.Value<string>("thumbnail");

/// <summary>
/// Gets the user's UTC offset, in milliseconds.
/// </summary>
public static int GetUtcOffset([NotNull] JObject user) => user.Value<int>("utcOffset");

/// <summary>
/// Gets the REST URL returned from the identity service.
/// </summary>
public static string GetRestUrl([NotNull] JObject user) => user["urls"]?.Value<string>("rest");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
* for more information concerning the license and the contributors participating to this project.
*/

using System.Text.Encodings.Web;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.OAuth;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace AspNet.Security.OAuth.Salesforce {
public class SalesforceAuthenticationMiddleware : OAuthMiddleware<SalesforceAuthenticationOptions> {
public SalesforceAuthenticationMiddleware(
[NotNull] RequestDelegate next,
[NotNull] IOptions<SalesforceAuthenticationOptions> options,
[NotNull] IDataProtectionProvider dataProtectionProvider,
[NotNull] ILoggerFactory loggerFactory,
[NotNull] UrlEncoder encoder,
[NotNull] IOptions<SharedAuthenticationOptions> externalOptions)
: base(next, dataProtectionProvider, loggerFactory, encoder, externalOptions, options) {
}

protected override AuthenticationHandler<SalesforceAuthenticationOptions> CreateHandler() {
return new SalesforceAuthenticationHandler(Backchannel);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
* for more information concerning the license and the contributors participating to this project.
*/

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;

namespace AspNet.Security.OAuth.Salesforce {
/// <summary>
/// Defines a set of options used by <see cref="SalesforceAuthenticationHandler"/>.
/// </summary>
public class SalesforceAuthenticationOptions : OAuthOptions {
public SalesforceAuthenticationOptions() {
AuthenticationScheme = SalesforceAuthenticationDefaults.AuthenticationScheme;
DisplayName = SalesforceAuthenticationDefaults.DisplayName;
ClaimsIssuer = SalesforceAuthenticationDefaults.Issuer;

CallbackPath = new PathString(SalesforceAuthenticationDefaults.CallbackPath);

Environment = SalesforceAuthenticationDefaults.Environment;
}

public SalesforceAuthenticationEnvironment Environment {
set {
switch (value) {
case SalesforceAuthenticationEnvironment.Production:
AuthorizationEndpoint = SalesforceAuthenticationDefaults.Production.AuthorizationEndpoint;
TokenEndpoint = SalesforceAuthenticationDefaults.Production.TokenEndpoint;
break;

case SalesforceAuthenticationEnvironment.Test:
AuthorizationEndpoint = SalesforceAuthenticationDefaults.Test.AuthorizationEndpoint;
TokenEndpoint = SalesforceAuthenticationDefaults.Test.TokenEndpoint;
break;

default:
throw new ArgumentOutOfRangeException(nameof(value), value, "Unsupported Salesforce environment");
}
}
}
}
}

0 comments on commit 14618f6

Please sign in to comment.