Skip to content

Commit d8fe4a8

Browse files
author
Lee
committed
Initial upload
1 parent b6f9483 commit d8fe4a8

File tree

863 files changed

+62236
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

863 files changed

+62236
-0
lines changed
202 KB
Binary file not shown.

.vs/CSharpApp/config/applicationhost.config

Lines changed: 995 additions & 0 deletions
Large diffs are not rendered by default.

.vs/CSharpApp/v16/.suo

41 KB
Binary file not shown.

CSharpApp.sln

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 16
4+
VisualStudioVersion = 16.0.30011.22
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpApp", "CSharpApp\CSharpApp.csproj", "{1EB26CA6-E4A2-400E-8859-D7A9975257F4}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
Release|Any CPU = Release|Any CPU
12+
EndGlobalSection
13+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
14+
{1EB26CA6-E4A2-400E-8859-D7A9975257F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15+
{1EB26CA6-E4A2-400E-8859-D7A9975257F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
16+
{1EB26CA6-E4A2-400E-8859-D7A9975257F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
17+
{1EB26CA6-E4A2-400E-8859-D7A9975257F4}.Release|Any CPU.Build.0 = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(SolutionProperties) = preSolution
20+
HideSolutionNode = FALSE
21+
EndGlobalSection
22+
GlobalSection(ExtensibilityGlobals) = postSolution
23+
SolutionGuid = {AB5ADC39-CD40-4A00-BEC1-1BD18C560D95}
24+
EndGlobalSection
25+
EndGlobal

CSharpApp/CSharpApp.csproj

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netcoreapp3.0</TargetFramework>
5+
<Authors>John Deere</Authors>
6+
<Company>John Deere</Company>
7+
<Product>JDLinkMachineDataAPI-OAuth2-CSharp-Example</Product>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.0.0" />
12+
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.0.0" />
13+
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.0.0" />
14+
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
15+
</ItemGroup>
16+
17+
</Project>

CSharpApp/CSharpApp.csproj.user

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<PropertyGroup>
4+
<Controller_SelectedScaffolderID>MvcControllerEmptyScaffolder</Controller_SelectedScaffolderID>
5+
<Controller_SelectedScaffolderCategoryPath>root/Controller</Controller_SelectedScaffolderCategoryPath>
6+
<WebStackScaffolding_ControllerDialogWidth>600</WebStackScaffolding_ControllerDialogWidth>
7+
</PropertyGroup>
8+
</Project>
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
using System.Net.Mime;
2+
using Microsoft.AspNetCore.Mvc;
3+
using Microsoft.Extensions.Configuration;
4+
using CSharpApp.Models;
5+
using System.Net.Http;
6+
using System.Net.Http.Headers;
7+
using System.Threading.Tasks;
8+
using Newtonsoft.Json;
9+
using System.Collections.Generic;
10+
using Microsoft.AspNetCore.WebUtilities;
11+
using System;
12+
using System.Text;
13+
using System.Xml;
14+
15+
namespace CSharpApp.Controllers
16+
{
17+
public class HomeController : Controller
18+
{
19+
private readonly IConfiguration _configuration;
20+
private static JDeere _settings = new JDeere();
21+
public HomeController(IConfiguration configuration)
22+
{
23+
_configuration = configuration;
24+
}
25+
26+
public IActionResult Index()
27+
{
28+
_settings.ClientId = _configuration["JDeere:ClientId"];
29+
_settings.ClientSecret = _configuration["JDeere:ClientSecret"];
30+
_settings.WellKnown = _configuration["JDeere:WellKnown"];
31+
_settings.ServerUrl = _configuration["JDeere:ServerUrl"];
32+
_settings.CallbackUrl = _settings.ServerUrl + _configuration["JDeere:Callback"];
33+
_settings.Scopes = _configuration["JDeere:Scopes"];
34+
_settings.State = _configuration["JDeere:State"];
35+
_settings.APIURL = _configuration["JDeere:ApiUrl"];
36+
37+
ViewBag.Settings = _settings;
38+
39+
return View();
40+
}
41+
42+
[HttpPost]
43+
public async Task<IActionResult> Index(string ClientId, string ClientSecret, string WellKnown, string CallbackUrl, string Scopes, string State)
44+
{
45+
_settings.ClientId = ClientId;
46+
_settings.ClientSecret = ClientSecret;
47+
_settings.WellKnown = WellKnown;
48+
_settings.CallbackUrl = CallbackUrl;
49+
_settings.Scopes = Scopes;
50+
_settings.State = State;
51+
52+
ViewBag.State = State;
53+
Dictionary<string, object> oAuthMetadata = await GetOAuthMetadata(WellKnown);
54+
string authEndpoint = oAuthMetadata["authorization_endpoint"].ToString();
55+
56+
var queryParameters = new Dictionary<string, string>();
57+
queryParameters.Add("response_type", "code");
58+
queryParameters.Add("scope", Scopes);
59+
queryParameters.Add("client_id", ClientId);
60+
queryParameters.Add("state", State);
61+
queryParameters.Add("redirect_uri", CallbackUrl);
62+
63+
string redirectUrl = QueryHelpers.AddQueryString(authEndpoint, queryParameters);
64+
65+
return Redirect(redirectUrl);
66+
}
67+
68+
[Route("/callback")]
69+
public async Task<IActionResult> Callback(string code, string state)
70+
{
71+
Dictionary<string, object> oAuthMetadata = await GetOAuthMetadata(_settings.WellKnown);
72+
string tokenEndpoint = oAuthMetadata["token_endpoint"].ToString();
73+
74+
var queryParameters = new Dictionary<string, string>();
75+
queryParameters.Add("grant_type", "authorization_code");
76+
queryParameters.Add("code", code);
77+
queryParameters.Add("redirect_uri", _settings.CallbackUrl);
78+
79+
HttpClient client = new HttpClient();
80+
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
81+
client.DefaultRequestHeaders.Add("authorization", $"Basic {GetBase64EncodedClientCredentials()}");
82+
83+
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, tokenEndpoint)
84+
{
85+
Content = new FormUrlEncodedContent(queryParameters)
86+
};
87+
88+
HttpResponseMessage response = await client.SendAsync(request);
89+
var responseContent = await response.Content.ReadAsStringAsync();
90+
_settings.AccessToken = JsonConvert.DeserializeObject<Token>(responseContent);
91+
92+
string organizationAccessUrl = await NeedsOrganizationAccess();
93+
if (organizationAccessUrl != null)
94+
{
95+
return Redirect(organizationAccessUrl);
96+
}
97+
98+
ViewBag.Settings = _settings;
99+
100+
return View("Index");
101+
}
102+
103+
[HttpPost]
104+
[Route("/call-api")]
105+
public async Task<IActionResult> CallAPI(string url)
106+
{
107+
108+
var response = await SecuredApiGetRequest(url);
109+
110+
111+
response.EnsureSuccessStatusCode();
112+
113+
var responseBody = await response.Content.ReadAsStringAsync();
114+
ViewBag.APIResponse = responseBody;
115+
ViewBag.Settings = _settings;
116+
117+
return View("Index");
118+
}
119+
120+
[Route("/refresh-access-token")]
121+
public async Task<IActionResult> RefreshAccessToken()
122+
{
123+
Dictionary<string, object> oAuthMetadata = await GetOAuthMetadata(_settings.WellKnown);
124+
string tokenEndpoint = oAuthMetadata["token_endpoint"].ToString();
125+
126+
var queryParameters = new Dictionary<string, string>();
127+
queryParameters.Add("grant_type", "refresh_token");
128+
queryParameters.Add("refresh_token", _settings.AccessToken.refresh_token);
129+
queryParameters.Add("redirect_uri", _settings.CallbackUrl);
130+
131+
HttpClient client = new HttpClient();
132+
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
133+
client.DefaultRequestHeaders.Add("authorization", $"Basic {GetBase64EncodedClientCredentials()}");
134+
135+
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, tokenEndpoint)
136+
{
137+
Content = new FormUrlEncodedContent(queryParameters)
138+
};
139+
140+
HttpResponseMessage response = await client.SendAsync(request);
141+
var responseContent = await response.Content.ReadAsStringAsync();
142+
_settings.AccessToken = JsonConvert.DeserializeObject<Token>(responseContent);
143+
144+
ViewBag.Settings = _settings;
145+
146+
return View("Index");
147+
}
148+
149+
private string GetBase64EncodedClientCredentials()
150+
{
151+
byte[] credentialBytes = Encoding.UTF8.GetBytes($"{_settings.ClientId}:{_settings.ClientSecret}");
152+
return Convert.ToBase64String(credentialBytes);
153+
}
154+
155+
private static async Task<Dictionary<string, object>> GetOAuthMetadata(string WellKnown)
156+
{
157+
HttpClient client = new HttpClient();
158+
var response = await client.GetAsync(WellKnown);
159+
160+
response.EnsureSuccessStatusCode();
161+
162+
var responseContent = await response.Content.ReadAsStringAsync();
163+
var oAuthMetadata = JsonConvert.DeserializeObject<Dictionary<string, object>>(responseContent);
164+
return oAuthMetadata;
165+
}
166+
167+
private async Task<HttpResponseMessage> SecuredApiGetRequest(string url)
168+
{
169+
var client = new HttpClient();
170+
var token = _settings.AccessToken.access_token;
171+
172+
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
173+
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
174+
175+
176+
177+
return await client.GetAsync(url);
178+
}
179+
180+
/// <summary>Check to see if the 'connections' rel is present for any organization.
181+
/// If the rel is present it means the oauth application has not completed it's
182+
/// access to an organization and must redirect the user to the uri provided
183+
/// in the link.</summary>
184+
/// <returns>A redirect uri if the <code>connections</code>
185+
/// connections rel is present or <null> if no redirect iszz
186+
/// required to finish the setup.</returns>
187+
private async Task<string> NeedsOrganizationAccess()
188+
{
189+
var response = await SecuredApiGetRequest(_settings.APIURL + "/1");
190+
191+
response.EnsureSuccessStatusCode();
192+
var responseContent = await response.Content.ReadAsStringAsync();
193+
194+
XmlDocument doc = new XmlDocument();
195+
196+
doc.LoadXml(responseContent);
197+
198+
XmlNodeList data = doc.GetElementsByTagName("Links");
199+
200+
for (int i = 0; i < data.Count; i++)
201+
{
202+
203+
if (data[i].InnerText.StartsWith("connections"))
204+
{
205+
string connectionsLink = data[i].LastChild.LastChild.Value;
206+
207+
return QueryHelpers.AddQueryString(connectionsLink, "redirect_uri", _settings.ServerUrl);
208+
}
209+
210+
}
211+
return null;
212+
}
213+
}
214+
}
215+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Microsoft.Extensions.Options;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Threading.Tasks;
6+
7+
namespace CSharpApp.Interfaces
8+
{
9+
public interface IWritableOptions<out T> : IOptions<T> where T : class, new()
10+
{
11+
void Update(Action<T> applyChanges);
12+
}
13+
}
14+
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
using Microsoft.AspNetCore.Authentication.Cookies;
2+
using Microsoft.AspNetCore.Authentication.OAuth;
3+
using Microsoft.AspNetCore.Hosting;
4+
using Microsoft.AspNetCore.Http;
5+
using Microsoft.Extensions.Configuration;
6+
using Microsoft.Extensions.DependencyInjection;
7+
using Microsoft.Extensions.Options;
8+
using CSharpApp.Interfaces;
9+
using CSharpApp.Models;
10+
using System;
11+
using System.Text;
12+
using System.Threading.Tasks;
13+
14+
namespace CSharpApp.Interface
15+
{
16+
public static class ServiceCollectionExtensions
17+
{
18+
public static void ConfigureWritable<T>(
19+
this IServiceCollection services,
20+
IConfigurationSection section,
21+
string file = "appsettings.json") where T : class, new()
22+
{
23+
services.Configure<T>(section);
24+
services.AddTransient<IWritableOptions<T>>(provider =>
25+
{
26+
var configuration = (IConfigurationRoot)provider.GetService<IConfiguration>();
27+
var environment = provider.GetService<IWebHostEnvironment>();
28+
var options = provider.GetService<IOptionsMonitor<T>>();
29+
return new WritableOptions<T>(environment, options, configuration, section.Key, file);
30+
});
31+
}
32+
public static void ConfigureAPIOptions(this IServiceCollection services, IConfiguration configuration)
33+
{
34+
services.AddOptions();
35+
services.Configure<JDeere>(configuration.GetSection("JDeere"));
36+
services.AddAuthentication(config =>
37+
{
38+
// We check the cookie to confirm that we are authenticated
39+
config.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; ;
40+
// When we sign in we will deal out a cookie
41+
config.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
42+
// use this to check if we are allowed to do something.
43+
config.DefaultChallengeScheme = "OurServer";
44+
45+
})
46+
.AddCookie()
47+
48+
.AddOAuth("OurServer", config =>
49+
{
50+
config.ClientId = configuration["JDeere:ClientId"];
51+
config.ClientSecret = configuration["JDeere:ClientSecret"];
52+
config.CallbackPath = "/callback";
53+
config.AuthorizationEndpoint = configuration["JDeere:AuthorizationEndpoint"];
54+
config.TokenEndpoint = configuration["JDeere:TokenEndpoint"];
55+
config.Scope.Add(configuration["JDeere:Scopes"]);
56+
config.SaveTokens = true;
57+
config.Events = new OAuthEvents()
58+
{
59+
OnCreatingTicket = context =>
60+
{
61+
byte[] ConvertFromBase64String(string input)
62+
{
63+
var accessToken = context.AccessToken;
64+
65+
var base64payload = accessToken.Split('.')[1];
66+
if (String.IsNullOrWhiteSpace(base64payload)) return null;
67+
try
68+
{
69+
string working = base64payload.Replace('-', '+').Replace('_', '/'); ;
70+
while (working.Length % 4 != 0)
71+
{
72+
working += '=';
73+
}
74+
return Convert.FromBase64String(working);
75+
}
76+
catch (Exception)
77+
{
78+
return null;
79+
}
80+
}
81+
var bytes = ConvertFromBase64String(context.AccessToken);
82+
var jsonPayload = Encoding.UTF8.GetString(bytes);
83+
84+
return Task.CompletedTask;
85+
}
86+
};
87+
});
88+
}
89+
public static void ConfigureDependencies(this IServiceCollection services, IConfiguration configuration)
90+
{
91+
services.AddSingleton<IConfiguration>(configuration);
92+
}
93+
}
94+
}
95+
96+
97+
98+
99+
100+
101+

0 commit comments

Comments
 (0)