Skip to content

Commit 8870bdd

Browse files
committed
add auth app
1 parent a0e262b commit 8870bdd

12 files changed

Lines changed: 327 additions & 6 deletions

CometD.NetCore.Salesforce.sln

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
1313
Directory.Build.props = Directory.Build.props
1414
LICENSE = LICENSE
1515
README.md = README.md
16-
src\TestApp\TestApp.csproj = src\TestApp\TestApp.csproj
1716
EndProjectSection
1817
EndProject
1918
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestApp", "src\TestApp\TestApp.csproj", "{6FACF1B8-5D3D-48BF-B2A9-FA06E195D1B6}"

Directory.Build.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44
<Authors>kdcllc</Authors>
55
<LangVersion>latest</LangVersion>
66
<VersionPrefix>1.0.0</VersionPrefix>
7+
<Copyright>King David Consulting LLC</Copyright>
78
</PropertyGroup>
89
</Project>

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
This repo contains the CometD .NET Core implementation for Salesforce Platform events.
55
1. `CometD.NetCore2.Salesforce`
66
- Salesforce Platform Events as Event Bus [eShopOnContainers](https://github.com/dotnet-architecture/eShopOnContainers)
7-
2. AuthApp
7+
2. `AuthApp`
88
- utility to retrieve `Access Token` and `Refresh Token` to be used by `TestApp`.
9-
3. TestApp
9+
3. `TestApp`
1010
- sample application to test the code.
1111

1212
## Nuget Packages

src/AuthApp/AuthApp.csproj

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>netcoreapp2.1</TargetFramework>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<PackageReference Include="Microsoft.AspNetCore.Hosting.Server.Abstractions" Version="2.1.1" />
10+
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.1.1" />
11+
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="2.1.1" />
12+
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.1.1" />
13+
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.1.1" />
14+
<PackageReference Include="Microsoft.Extensions.Hosting" Version="2.1.1" />
15+
<PackageReference Include="NetCoreForce.Client" Version="2.2.0" />
16+
</ItemGroup>
17+
<ItemGroup>
18+
<Content Include="appsettings*.json" CopyToOutputDirectory="PreserveNewest" />
19+
<Content Include="hostsettings*.json" CopyToOutputDirectory="PreserveNewest" />
20+
</ItemGroup>
21+
<ItemGroup>
22+
<None Remove="appsettings.Development.json" />
23+
<None Remove="hostsettings.json" />
24+
</ItemGroup>
25+
<ItemGroup>
26+
<Content Include="hostsettings.json">
27+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
28+
</Content>
29+
</ItemGroup>
30+
<ItemGroup>
31+
<Content Update="appsettings.Development.json">
32+
<DependentUpon>appsettings.json</DependentUpon>
33+
</Content>
34+
</ItemGroup>
35+
</Project>

src/AuthApp/ConsoleHandler.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.Runtime.InteropServices;
4+
5+
namespace AuthApp
6+
{
7+
public class ConsoleHandler
8+
{
9+
10+
public static void ShowConsole()
11+
{
12+
var console = GetConsoleWindow();
13+
ShowWindow(console, 5);
14+
}
15+
16+
public static void HideConsole()
17+
{
18+
var console = GetConsoleWindow();
19+
ShowWindow(console, 0);
20+
}
21+
22+
#pragma warning disable IDE0040 // Add accessibility modifiers
23+
24+
[DllImport("kernel32.dll")]
25+
static extern IntPtr GetConsoleWindow();
26+
27+
[DllImport("user32.dll")]
28+
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
29+
30+
#pragma warning restore IDE0040 // Add accessibility modifiers
31+
32+
33+
public static Process OpenBrowser(string url)
34+
{
35+
try
36+
{
37+
return Process.Start(url);
38+
}
39+
catch
40+
{
41+
// hack because of this: https://github.com/dotnet/corefx/issues/10361
42+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
43+
{
44+
url = url.Replace("&", "^&");
45+
return Process.Start(new ProcessStartInfo("cmd", $"/c start {url}") { CreateNoWindow = true });
46+
}
47+
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
48+
{
49+
return Process.Start("xdg-open", url);
50+
}
51+
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
52+
{
53+
return Process.Start("open", url);
54+
}
55+
else
56+
{
57+
throw;
58+
}
59+
}
60+
}
61+
}
62+
}

src/AuthApp/Host/CustomHost.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using System.IO;
3+
using System.Threading.Tasks;
4+
using Microsoft.Extensions.Configuration;
5+
using Microsoft.Extensions.DependencyInjection;
6+
using Microsoft.Extensions.Hosting;
7+
8+
namespace AuthApp.Host
9+
{
10+
public class CustomHost
11+
{
12+
private IHost _host;
13+
14+
public CustomHost()
15+
{
16+
_host = new HostBuilder()
17+
.ConfigureHostConfiguration(configHost =>
18+
{
19+
configHost.SetBasePath(Directory.GetCurrentDirectory());
20+
configHost.AddJsonFile("hostsettings.json", optional: true);
21+
configHost.AddEnvironmentVariables(prefix: "PREFIX_");
22+
})
23+
.ConfigureAppConfiguration((hostContext, config) =>
24+
{
25+
config.AddEnvironmentVariables();
26+
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
27+
config.AddJsonFile(
28+
$"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json",
29+
optional: true);
30+
31+
})
32+
.ConfigureServices((hostContext, services) =>
33+
{
34+
var config = new SfConfig();
35+
hostContext.Configuration.Bind("Salesforce", config);
36+
services.AddSingleton(config);
37+
38+
services.AddHostedService<HttpServer>();
39+
})
40+
.UseConsoleLifetime()
41+
.Build();
42+
}
43+
44+
public void Start()
45+
{
46+
_host.Start();
47+
}
48+
49+
public async Task StopAsync()
50+
{
51+
await _host.StopAsync(TimeSpan.FromSeconds(5));
52+
_host.Dispose();
53+
}
54+
}
55+
}

src/AuthApp/Host/HttpServer.cs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
using System;
2+
using System.IO;
3+
using System.Net;
4+
using System.Net.Sockets;
5+
using System.Text;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Microsoft.Extensions.Hosting;
9+
using NetCoreForce.Client;
10+
11+
namespace AuthApp.Host
12+
{
13+
/// <summary>
14+
/// Web Server OAuth Authentication Flow
15+
/// https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/intro_understanding_web_server_oauth_flow.htm
16+
/// </summary>
17+
public class HttpServer : BackgroundService
18+
{
19+
private readonly SfConfig _config;
20+
private bool isCompleted = false;
21+
22+
public HttpServer(SfConfig config)
23+
{
24+
_config = config;
25+
}
26+
27+
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
28+
{
29+
Console.WriteLine($"{nameof(HttpServer)} is starting.");
30+
var http = new HttpListener();
31+
var redirectURI = string.Format("http://{0}:{1}/", "localhost", GetRandomUnusedPort());
32+
http.Prefixes.Add(redirectURI);
33+
http.Start();
34+
35+
var authUrl = GetAuthorizationUrl(redirectURI);
36+
Console.WriteLine($"Opening a browser window with Url: {authUrl}");
37+
ConsoleHandler.HideConsole();
38+
39+
var process = ConsoleHandler.OpenBrowser(authUrl);
40+
var context = await http.GetContextAsync();
41+
42+
while (!stoppingToken.IsCancellationRequested || isCompleted )
43+
{
44+
Console.WriteLine($"{nameof(HttpServer)} is running");
45+
46+
if (context != null)
47+
{
48+
ConsoleHandler.ShowConsole();
49+
50+
var responseOutput = await ShowBrowserMessage(context);
51+
52+
responseOutput.Close();
53+
54+
55+
if (context.Request.QueryString.Get("error") != null)
56+
{
57+
Console.WriteLine(string.Format("OAuth authorization error: {0}.", context.Request.QueryString.Get("error")));
58+
}
59+
if (context.Request.QueryString.Get("code") == null)
60+
{
61+
Console.WriteLine("Malformed authorization response. " + context.Request.QueryString);
62+
}
63+
64+
// Authorization code the consumer must use to obtain the access and refresh tokens.
65+
// The authorization code expires after 15 minutes.
66+
var code = context.Request.QueryString.Get("code");
67+
68+
var auth = new AuthenticationClient();
69+
await auth.WebServerAsync(_config.ClientId,
70+
_config.ClientSecret,
71+
redirectURI,
72+
code,
73+
$"{_config.LoginUrl}/services/oauth2/token");
74+
75+
76+
77+
Console.WriteLine($"Your access_token is {auth.AccessInfo.AccessToken}");
78+
Console.WriteLine($"Your refresh_token is {auth.AccessInfo.RefreshToken}");
79+
80+
isCompleted = true;
81+
}
82+
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
83+
}
84+
85+
http.Stop();
86+
Console.WriteLine($"{nameof(HttpServer)} is stopping.");
87+
}
88+
89+
private int GetRandomUnusedPort()
90+
{
91+
var listener = new TcpListener(IPAddress.Loopback, 5050);
92+
listener.Start();
93+
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
94+
listener.Stop();
95+
return port;
96+
}
97+
98+
private string GetAuthorizationUrl(string redirectURI)
99+
{
100+
var authEndpoint = $"{_config.LoginUrl}/services/oauth2/authorize";
101+
var url = $"{authEndpoint}?response_type=code&access_type=offline&scope=openid%20profile%20api%20refresh_token%20offline_access&redirect_uri={Uri.EscapeDataString(redirectURI)}&client_id={_config.ClientId}";
102+
return url;
103+
}
104+
105+
private static async Task<Stream> ShowBrowserMessage(HttpListenerContext context)
106+
{
107+
var response = context.Response;
108+
var responseString = string.Format(@"
109+
<html>
110+
<body>Please return to the console to retrieve access and refresh tokens.</body>
111+
</html>");
112+
113+
var buffer = Encoding.UTF8.GetBytes(responseString);
114+
response.ContentLength64 = buffer.Length;
115+
var responseOutput = response.OutputStream;
116+
await responseOutput.WriteAsync(buffer, 0, buffer.Length);
117+
return responseOutput;
118+
}
119+
}
120+
}

src/AuthApp/Host/SfConfig.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace AuthApp.Host
2+
{
3+
public class SfConfig
4+
{
5+
public string ClientId { get; set; }
6+
public string ClientSecret { get; set; }
7+
public string LoginUrl { get; set; } = @"https://test.salesforce.com";
8+
}
9+
}

src/AuthApp/Program.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System;
2+
using System.IO;
3+
using System.Threading.Tasks;
4+
using AuthApp.Host;
5+
using Microsoft.Extensions.Configuration;
6+
7+
namespace AuthApp
8+
{
9+
public class Program
10+
{
11+
public static async Task Main(string[] args)
12+
{
13+
14+
var host = new CustomHost();
15+
16+
Console.WriteLine("Starting!");
17+
host.Start();
18+
Console.WriteLine("Started!");
19+
Console.ReadLine();
20+
21+
Console.WriteLine("Stopping!");
22+
await host.StopAsync();
23+
Console.WriteLine("Stopped!");
24+
25+
26+
}
27+
28+
29+
30+
}
31+
32+
}

src/AuthApp/appsettings.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"Salesforce": {
3+
"ClientId": "",
4+
"ClientSecret": "",
5+
"LoginUrl": "https://login.salesforce.com"
6+
}
7+
}

0 commit comments

Comments
 (0)