Initial commit

This commit is contained in:
Mark van der Wal 2020-03-24 09:47:08 +01:00
commit 2bbae6f00d
20 changed files with 588 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.idea/
.vs/
bin/
obj/

29
FarmmapsApi/Extensions.cs Normal file
View File

@ -0,0 +1,29 @@
using System.Net.Http;
using FarmmapsApi.HttpMessageHandlers;
using FarmmapsApi.Models;
using FarmmapsApi.Services;
using IdentityModel.Client;
using Microsoft.Extensions.DependencyInjection;
namespace FarmmapsApi
{
public static class Extensions
{
public static IServiceCollection AddFarmmapsServices(this IServiceCollection serviceCollection, Configuration configuration)
{
return serviceCollection
.AddSingleton(configuration)
.AddSingleton<IDiscoveryCache>(sp =>
{
var httpFactory = sp.GetRequiredService<IHttpClientFactory>();
return new DiscoveryCache(configuration.DiscoveryEndpointUrl,
() => httpFactory.CreateClient());
})
.AddTransient<OpenIdConnectService>()
.AddTransient<FarmmapsAuthenticationHandler>()
.AddHttpClient<FarmmapsApiService>()
.AddHttpMessageHandler<FarmmapsAuthenticationHandler>()
.Services;;
}
}
}

View File

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.2" />
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.2" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.2" />
<PackageReference Include="IdentityModel.OidcClient" Version="3.1.2" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,24 @@
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using IdentityModel;
namespace FarmmapsApi.HttpMessageHandlers
{
public class FarmmapsAuthenticationHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (!request.Headers.Authorization.Scheme.Equals(OidcConstants.AuthenticationSchemes.AuthorizationHeaderBearer) ||
string.IsNullOrEmpty(request.Headers.Authorization.Parameter))
{
return new HttpResponseMessage(HttpStatusCode.Unauthorized)
{
Content = new StringContent("You must authenticate before using the API")
};
}
return await base.SendAsync(request, cancellationToken);
}
}
}

View File

@ -0,0 +1,13 @@
namespace FarmmapsApi.Models
{
public class Configuration
{
public string Authority { get; set; }
public string Endpoint { get; set; }
public string DiscoveryEndpointUrl { get; set; }
public string RedirectUri { get; set; }
public string ClientId { get; set; }
public string ClientSecret { get; set; }
public string[] Scopes { get; set; }
}
}

View File

@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
namespace FarmmapsApi.Models
{
public class Item
{
public Item()
{
Tags = new List<string>();
}
public string Code { get; set; }
public string Name { get; set; }
public DateTime? Created { get; set; }
public DateTime? Updated { get; set; }
public DateTime? DataDate { get; set; }
public string ItemType { get; set; }
public string SourceTask { get; set; }
public long Size { get; set; }
public int State { get; set; }
public string ParentCode { get; set; }
public JObject Geometry { get; set; }
public JObject Data { get; set; }
public IList<string> Tags { get; set; }
}
}

View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
namespace FarmmapsApi.Models
{
public class ItemRequest
{
public ItemRequest()
{
Tags = new List<string>();
}
public string ParentCode { get; set; }
public string ItemType { get; set; }
public string Name { get; set; }
public DateTime? DataDate { get; set; }
public JObject Geometry { get; set; }
public JObject Data { get; set; }
public IList<string> Tags { get; set; }
}
}

View File

@ -0,0 +1,22 @@
using System;
namespace FarmmapsApi.Models
{
public enum ItemTaskState
{
Error,
Ok,
Scheduled,
Processing,
}
public class ItemTaskStatus
{
public string TaskType { get; set; }
public string Code { get; set; }
public string Message { get; set; }
public ItemTaskState State { get; set; }
public DateTime? Started { get; set; }
public DateTime? Finished { get; set; }
}
}

View File

@ -0,0 +1,17 @@
using System.Collections.Generic;
namespace FarmmapsApi.Models
{
public class TaskRequest
{
public string TaskType { get; set; }
public string Delay { get; set; }
public Dictionary<string, string> attributes { get; set; }
public TaskRequest()
{
this.attributes = new Dictionary<string, string>();
}
}
}

View File

@ -0,0 +1,8 @@
namespace FarmmapsApi.Models
{
public class UserRoot
{
public string Name { get; set; }
public string Code { get; set; }
}
}

View File

@ -0,0 +1,18 @@
namespace FarmmapsApi
{
public static class ResourceEndpoints
{
public const string CURRENTUSER_RESOURCE = "currentuser";
public const string MYROOTS_RESOURCE = "folders/my_roots";
public const string ITEMS_RESOURCE = "items/{0}";
public const string ITEMS_CREATE_RESOURCE = "items";
public const string ITEMS_DOWNLOAD_RESOURCE = "items/{0}/download";
public const string ITEMS_CHILDREN_RESOURCE = "items/{0}/children";
public const string ITEMS_DELETE_RESOURCE = "items/delete";
public const string ITEMTASK_REQUEST_RESOURCE = "items/{0}/tasks";
public const string ITEMTASKS_RESOURCE = "items/{0}/tasks";
public const string ITEMTASK_RESOURCE = "items/{0}/tasks/{1}";
}
}

View File

@ -0,0 +1,189 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Mime;
using System.Security.Authentication;
using System.Threading.Tasks;
using FarmmapsApi.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using static IdentityModel.OidcConstants;
namespace FarmmapsApi.Services
{
public class FarmmapsApiService
{
private readonly HttpClient _httpClient;
private readonly OpenIdConnectService _openIdConnectService;
private readonly Configuration _configuration;
public FarmmapsApiService(HttpClient httpClient,
OpenIdConnectService openIdConnectService, Configuration configuration)
{
_httpClient = httpClient;
_openIdConnectService = openIdConnectService;
_configuration = configuration;
_httpClient.BaseAddress = new Uri(configuration.Endpoint);
_httpClient.DefaultRequestHeaders.Add("Accept", MediaTypeNames.Application.Json);
}
public async Task AuthenticateAsync()
{
if (_httpClient.DefaultRequestHeaders.Authorization != null &&
_httpClient.DefaultRequestHeaders.Authorization.Scheme !=
AuthenticationSchemes.AuthorizationHeaderBearer)
throw new AuthenticationException("Already seems to be authenticated");
var disco = await _openIdConnectService.GetDiscoveryDocumentAsync();
var token = await _openIdConnectService.GetTokenClientCredentialsAsync(disco.TokenEndpoint,
_configuration.ClientId, _configuration.ClientSecret);
if (token.IsError)
throw new AuthenticationException(token.Error);
_httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue(AuthenticationSchemes.AuthorizationHeaderBearer,
token.AccessToken);
}
public async Task<string> GetCurrentUserCodeAsync()
{
var response = await _httpClient.GetAsync(ResourceEndpoints.CURRENTUSER_RESOURCE);
if (!response.IsSuccessStatusCode)
throw new Exception(response.ReasonPhrase);
var jsonString = await response.Content.ReadAsStringAsync();
var json = JsonConvert.DeserializeObject<JObject>(jsonString);
return json["code"].Value<string>();
}
public async Task<IEnumerable<UserRoot>> GetCurrentUserRootsAsync()
{
var response = await _httpClient.GetAsync(ResourceEndpoints.MYROOTS_RESOURCE);
if (!response.IsSuccessStatusCode)
throw new Exception(response.ReasonPhrase);
var jsonString = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<List<UserRoot>>(jsonString);
}
public async Task<Item> GetItemAsync(string itemCode)
{
var resourceUri = string.Format(ResourceEndpoints.ITEMS_RESOURCE, itemCode);
var response = await _httpClient.GetAsync(resourceUri);
if (!response.IsSuccessStatusCode)
throw new Exception(response.ReasonPhrase);
var jsonString = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<Item>(jsonString);
}
public async Task<IEnumerable<Item>> GetItemChildrenAsync(string itemCode)
{
var resourceUri = string.Format(ResourceEndpoints.ITEMS_CHILDREN_RESOURCE, itemCode);
var response = await _httpClient.GetAsync(resourceUri);
if (!response.IsSuccessStatusCode)
throw new Exception(response.ReasonPhrase);
var jsonString = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<IEnumerable<Item>>(jsonString);
}
public async Task<Item> CreateItemAsync(ItemRequest itemRequest)
{
var jsonString = JsonConvert.SerializeObject(itemRequest);
var response = await _httpClient.PostAsync(ResourceEndpoints.ITEMS_CREATE_RESOURCE,
new StringContent(jsonString));
if (!response.IsSuccessStatusCode)
throw new Exception(response.ReasonPhrase);
var jsonStringResponse = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<Item>(jsonStringResponse);
}
public async Task DeleteItemAsync(string itemCode)
{
var resourceUri = string.Format(ResourceEndpoints.ITEMS_RESOURCE, itemCode);
var response = await _httpClient.DeleteAsync(resourceUri);
if (!response.IsSuccessStatusCode)
throw new Exception(response.ReasonPhrase);
}
public async Task DeleteItemsAsync(IList<string> itemCodes)
{
var jsonString = JsonConvert.SerializeObject(itemCodes);
var content = new StringContent(jsonString);
var response = await _httpClient.PostAsync(ResourceEndpoints.ITEMS_DELETE_RESOURCE, content);
if (!response.IsSuccessStatusCode)
throw new Exception(response.ReasonPhrase);
}
public async Task DownloadItemAsync(string itemCode, string filePath)
{
var resourceUri = string.Format(ResourceEndpoints.ITEMS_DOWNLOAD_RESOURCE, itemCode);
var response = await _httpClient.GetAsync(resourceUri, HttpCompletionOption.ResponseHeadersRead);
if(response.IsSuccessStatusCode)
{
await using Stream streamToReadFrom = await response.Content.ReadAsStreamAsync();
await using Stream streamToWriteTo = File.Open(filePath, FileMode.Create);
await streamToReadFrom.CopyToAsync(streamToWriteTo);
}
else
{
throw new FileNotFoundException(response.ReasonPhrase);
}
}
public async Task<string> QueueTaskAsync(string itemCode, TaskRequest taskRequest)
{
var resourceUri = string.Format(ResourceEndpoints.ITEMTASK_REQUEST_RESOURCE, itemCode);
var jsonString = JsonConvert.SerializeObject(taskRequest);
var response = await _httpClient.PostAsync(resourceUri, new StringContent(jsonString));
if (!response.IsSuccessStatusCode)
throw new Exception(response.ReasonPhrase);
var jsonStringResponse = await response.Content.ReadAsStringAsync();
var json = JsonConvert.DeserializeObject<JObject>(jsonStringResponse);
return json["code"].Value<string>();
}
public async Task<IEnumerable<ItemTaskStatus>> GetTasksStatusAsync(string itemCode)
{
var resourceUri = string.Format(ResourceEndpoints.ITEMTASKS_RESOURCE, itemCode);
var response = await _httpClient.GetAsync(resourceUri);
if (!response.IsSuccessStatusCode)
throw new Exception(response.ReasonPhrase);
var jsonString = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<List<ItemTaskStatus>>(jsonString);
}
public async Task<ItemTaskStatus> GetTaskStatusAsync(string itemCode, string itemTaskCode)
{
var resourceUri = string.Format(ResourceEndpoints.ITEMTASK_RESOURCE, itemCode, itemTaskCode);
var response = await _httpClient.GetAsync(resourceUri);
if (!response.IsSuccessStatusCode)
throw new Exception(response.ReasonPhrase);
var jsonString = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<ItemTaskStatus>(jsonString);
}
}
}

View File

@ -0,0 +1,54 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using FarmmapsApi.Models;
using IdentityModel.Client;
namespace FarmmapsApi.Services
{
public class OpenIdConnectService
{
private readonly IDiscoveryCache _discoveryCache;
private readonly Configuration _configuration;
private readonly HttpClient _httpClient;
public OpenIdConnectService(IDiscoveryCache discoveryCache,
IHttpClientFactory httpFactory, Configuration configuration)
{
_discoveryCache = discoveryCache;
_configuration = configuration;
_httpClient = httpFactory.CreateClient();
}
public async Task<DiscoveryDocumentResponse> GetDiscoveryDocumentAsync()
{
var disco = await _discoveryCache.GetAsync();
if (disco.IsError)
throw new Exception(disco.Error);
return disco;
}
public async Task<TokenResponse> GetTokenClientCredentialsAsync(string tokenEndpointUrl, string clientId, string clientSecret)
{
return await _httpClient.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest()
{
Address = tokenEndpointUrl,
ClientId = clientId,
ClientSecret = clientSecret,
Scope = string.Join(" ", _configuration.Scopes)
});
}
public async Task<TokenResponse> RefreshTokensAsync(string tokenEndpointUrl, string refreshToken)
{
return await _httpClient.RequestRefreshTokenAsync(new RefreshTokenRequest()
{
Address = tokenEndpointUrl,
ClientId = _configuration.ClientId,
ClientSecret = _configuration.ClientSecret,
RefreshToken = refreshToken
});
}
}
}

22
FarmmapsApiSamples.sln Normal file
View File

@ -0,0 +1,22 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FarmmapsApiSamples", "FarmmapsApiSamples\FarmmapsApiSamples.csproj", "{E08EF7E9-F09E-42D8-825C-164E458C78F4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FarmmapsApi", "FarmmapsApi\FarmmapsApi.csproj", "{1FA9E50B-F45E-4534-953A-37C783D03C74}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E08EF7E9-F09E-42D8-825C-164E458C78F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E08EF7E9-F09E-42D8-825C-164E458C78F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E08EF7E9-F09E-42D8-825C-164E458C78F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E08EF7E9-F09E-42D8-825C-164E458C78F4}.Release|Any CPU.Build.0 = Release|Any CPU
{1FA9E50B-F45E-4534-953A-37C783D03C74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1FA9E50B-F45E-4534-953A-37C783D03C74}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1FA9E50B-F45E-4534-953A-37C783D03C74}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1FA9E50B-F45E-4534-953A-37C783D03C74}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,42 @@
using System;
using System.Threading.Tasks;
using FarmmapsApi.Services;
using Microsoft.Extensions.Logging;
namespace FarmmapsApiSamples
{
public class DefaultApp : IApp
{
private readonly ILogger<DefaultApp> _logger;
private readonly FarmmapsApiService _farmmapsApiService;
public DefaultApp(ILogger<DefaultApp> logger, FarmmapsApiService farmmapsApiService)
{
_logger = logger;
_farmmapsApiService = farmmapsApiService;
}
public async Task RunAsync()
{
try
{
await _farmmapsApiService.AuthenticateAsync();
_logger.LogInformation("Authenticated client credentials");
var user = await _farmmapsApiService.GetCurrentUserCodeAsync();
_logger.LogInformation($"Usercode: {user}");
var roots = await _farmmapsApiService.GetCurrentUserRootsAsync();
foreach (var userRoot in roots)
{
_logger.LogInformation($"{userRoot.Name} - {userRoot.Code}");
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
}
}
}
}

View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FarmmapsApi\FarmmapsApi.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace FarmmapsApiSamples
{
public interface IApp
{
Task RunAsync();
}
}

View File

@ -0,0 +1,31 @@
using System.Threading.Tasks;
using FarmmapsApi;
using FarmmapsApi.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace FarmmapsApiSamples
{
class Program
{
private static async Task Main(string[] args)
{
IConfiguration config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", false, true)
.Build();
var configuration = config.Get<Configuration>();
var serviceProvider = new ServiceCollection()
.AddLogging(opts => opts
.AddConsole()
.AddFilter("System.Net.Http", LogLevel.Warning))
.AddFarmmapsServices(configuration)
.AddSingleton<IApp, DefaultApp>()
.BuildServiceProvider();
await serviceProvider.GetService<IApp>().RunAsync();
}
}
}

View File

@ -0,0 +1,9 @@
{
"Authority": "https://accounts.farmmaps.awtest.nl/",
"Endpoint": "https://farmmaps.awtest.nl/api/v1/",
"DiscoveryEndpointUrl": "https://accounts.farmmaps.awtest.nl/.well-known/openid-configuration",
"RedirectUri": "http://example.nl/api",
"ClientId": "",
"ClientSecret": "",
"Scopes": ["api"]
}

1
README.MD Normal file
View File

@ -0,0 +1 @@
Put your clientId and clientSecret in the appsettings.json