introduce a very simple Nextcloud API

This commit is contained in:
2023-09-27 14:12:55 +02:00
parent 7ea59cddb1
commit 2c28feacb5
23 changed files with 927 additions and 1 deletions

View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Networking.CloudProviders.Nextcloud.OCS.APIs
{
public abstract class OcsApiBase
{
public OcsApi Manager { get; init; }
protected OcsApiBase(OcsApi manager)
{
Manager = manager;
}
}
}

View File

@@ -0,0 +1,23 @@
using Pilz.Networking.CloudProviders.Nextcloud.OCS.Responses.Cloud;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Networking.CloudProviders.Nextcloud.OCS.APIs
{
public class OcsApiCloud : OcsApiBase
{
public readonly static OcsApiUrlPath OCS_CLOUD_USER_METADATA = new("/ocs/v1.php/cloud/users/{0}");
public OcsApiCloud(OcsApi manager) : base(manager)
{
}
public Task<OcsResponseUser?> GetUserMeta(string username)
{
return Manager.MakeRequestJson<OcsResponseUser>(HttpMethod.Get, OCS_CLOUD_USER_METADATA.FillParameters(username));
}
}
}

View File

@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Networking.CloudProviders.Nextcloud.OCS.APIs
{
public class OcsApiCore : OcsApiBase
{
public static readonly OcsApiUrlPath OCS_CORE_APPPASSWORD = "/ocs/v2.php/core/apppassword";
public OcsApiCore(OcsApi manager) : base(manager)
{
}
public async Task<bool> DeleteAppPassword()
{
using var msg = await Manager.MakeRequest(HttpMethod.Delete, OCS_CORE_APPPASSWORD);
return msg != null && msg.IsSuccessStatusCode;
}
}
}

View File

@@ -0,0 +1,35 @@
using Pilz.Networking.CloudProviders.Nextcloud.OCS.Responses.LoginFlowV2;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Networking.CloudProviders.Nextcloud.OCS.APIs
{
public class OcsApiLoginFlowV2 : OcsApiBase
{
private const string OCS_LOGIN_INIT = "/index.php/login/v2";
public OcsApiLoginFlowV2(OcsApi manager) : base(manager)
{
}
public Task<OcsResponseLoginFlowV2?> Init(string url)
{
return Manager.MakeRequestJson<OcsResponseLoginFlowV2>(HttpMethod.Get, url + OCS_LOGIN_INIT);
}
public Task<OcsResponseLoginFlowV2Credentials?> Poll(OcsResponseLoginFlowV2.PollData poll)
{
ArgumentNullException.ThrowIfNull(poll?.Endpoint);
ArgumentNullException.ThrowIfNull(poll?.Token);
return Manager.MakeRequestJson<OcsResponseLoginFlowV2Credentials?>(HttpMethod.Get, poll.Endpoint,
parameters: new Dictionary<string, string>
{
{ "token", poll.Token }
});
}
}
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Networking.CloudProviders.Nextcloud.OCS
{
public delegate void GetOcsApiAuthCredentailsEventHandler(object sender, GetOcsApiAuthCredentailsEventArgs eventArgs);
public class GetOcsApiAuthCredentailsEventArgs : EventArgs
{
public OcsApiAuthCredentials? Credentials { get; set; }
}
}

View File

@@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Http.Headers;
using Newtonsoft.Json;
using System.Diagnostics;
using System.Net;
using Pilz.Networking.CloudProviders.Nextcloud.OCS.APIs;
namespace Pilz.Networking.CloudProviders.Nextcloud.OCS
{
public class OcsApi : IDisposable
{
public event GetOcsApiAuthCredentailsEventHandler? GetOcsApiAuthCredentails;
private readonly HttpClient client = new();
public string BaseUrl { get; set; } = string.Empty;
public OcsApiLoginFlowV2 LoginFlowV2 { get; init; }
public OcsApiCore Core { get; init; }
public OcsApiCloud Cloud { get; init; }
public OcsApi()
{
LoginFlowV2 = new(this);
Core = new(this);
Cloud = new(this);
}
public string BuildFullUrl(OcsApiUrlPath path)
{
return BaseUrl + path;
}
public Task<TResponse?> MakeRequestJson<TResponse>(HttpMethod httpMethod, OcsApiUrlPath url, bool useAuthentication = true, Dictionary<string, string>? parameters = null)
{
return MakeRequestJson<TResponse>(httpMethod, BuildFullUrl(url), useAuthentication: useAuthentication, parameters: parameters);
}
public Task<HttpResponseMessage> MakeRequest(HttpMethod httpMethod, OcsApiUrlPath url, bool useAuthentication = true, Dictionary<string, string>? parameters = null)
{
return MakeRequest(httpMethod, BuildFullUrl(url), useAuthentication: useAuthentication, parameters: parameters);
}
public async Task<TResponse?> MakeRequestJson<TResponse>(HttpMethod httpMethod, string url, bool useAuthentication = true, Dictionary<string, string>? parameters = null)
{
using var responseInit = await MakeRequest(httpMethod, url, useAuthentication: useAuthentication, parameters: parameters);
if (responseInit != null)
{
var bodyInit = await responseInit.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<TResponse>(bodyInit);
}
return default;
}
public async Task<HttpResponseMessage> MakeRequest(HttpMethod httpMethod, string url, bool useAuthentication = true, Dictionary<string, string>? parameters = null)
{
OcsApiAuthCredentials? authentication;
string @params;
// Get authentication
if (useAuthentication)
{
var args = new GetOcsApiAuthCredentailsEventArgs();
GetOcsApiAuthCredentails?.Invoke(this, args);
authentication = args.Credentials;
}
else
authentication = null;
// Parse params
if (parameters != null)
@params = "?" + string.Join(",", parameters.Select(p => $"{p.Key}={p.Value}"));
else
@params = string.Empty;
// Send request
var request = new HttpRequestMessage
{
Method = httpMethod ?? HttpMethod.Post,
RequestUri = new Uri(url + @params),
Headers =
{
{ "Accept", "application/json" },
{ "OCS-APIREQUEST", "true" },
{ "Authorization", authentication.ToBasicAuth() }
},
};
return await client.SendAsync(request);
}
public void Dispose()
{
client.Dispose();
GC.SuppressFinalize(this);
}
}
}

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Networking.CloudProviders.Nextcloud.OCS
{
public struct OcsApiAuthCredentials
{
public string LoginName { get; set; }
public string AppPassword { get; set; }
public OcsApiAuthCredentials(string loginName, string appPassword)
{
LoginName = loginName;
AppPassword = appPassword;
}
}
}

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Networking.CloudProviders.Nextcloud.OCS
{
public readonly struct OcsApiUrlPath
{
private readonly string path;
public OcsApiUrlPath()
{
path = string.Empty;
}
public OcsApiUrlPath(string path)
{
this.path = path;
}
public static implicit operator string(OcsApiUrlPath o) => o.path;
public static implicit operator OcsApiUrlPath(string o) => new(o);
public override readonly string ToString()
{
return path;
}
}
}

View File

@@ -0,0 +1,90 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Networking.CloudProviders.Nextcloud.OCS.Responses.Cloud
{
public class OcsResponseDataUser : OcsResponseData
{
public class ResponseQuota
{
[JsonProperty("free")]
public ulong? Free { get; set; }
[JsonProperty("used")]
public ulong? Used { get; set; }
[JsonProperty("total")]
public ulong? Total { get; set; }
[JsonProperty("relative")]
public float? Relative { get; set; }
[JsonProperty("quota")]
public ulong? Quota { get; set; }
}
public class ResponseBackendCapabilities
{
[JsonProperty("setDisplayName")]
public int? SetDisplayName { get; set; }
[JsonProperty("setPassword")]
public int? SetPassword { get; set; }
}
[JsonProperty("enabled")]
public int? Enabled { get; set; }
[JsonProperty("storageLocation")]
public string? StorageLocation { get; set; }
[JsonProperty("id")]
public string? ID { get; set; }
[JsonProperty("lastLogin")]
public ulong? LastLogin { get; set; }
[JsonProperty("backend")]
public string? Backend { get; set; }
[JsonProperty("quota")]
public ResponseQuota? Quota { get; set; }
[JsonProperty("email")]
public string? Email { get; set; }
[JsonProperty("displayname")]
public string? Displayname { get; set; }
[JsonProperty("display-name")]
public string? Displayname2 { get; set; }
[JsonProperty("phone")]
public string? Phone { get; set; }
[JsonProperty("address")]
public string? Address { get; set; }
[JsonProperty("website")]
public string? Website { get; set; }
[JsonProperty("twitter")]
public string? Twitter { get; set; }
[JsonProperty("groups")]
public string[]? Groups { get; set; }
[JsonProperty("language")]
public string? Language { get; set; }
[JsonProperty("locale")]
public string? Locale { get; set; }
[JsonProperty("backendCapabilities")]
public ResponseBackendCapabilities? BackendCapabilities { get; set; }
}
}

View File

@@ -0,0 +1,13 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Networking.CloudProviders.Nextcloud.OCS.Responses.Cloud
{
public class OcsResponseUser : OcsResponse<OcsResponseDataUser>
{
}
}

View File

@@ -0,0 +1,40 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Networking.CloudProviders.Nextcloud.OCS.Responses.LoginFlowV2
{
public class OcsResponseLoginFlowV2
{
public class PollData
{
/// <summary>
/// The login token that has been created for the login process.
/// It can be used to poll the login state.
/// </summary>
[JsonProperty("token")]
public string? Token { get; set; }
/// <summary>
///
/// </summary>
[JsonProperty("endpoint")]
public string? Endpoint { get; set; }
}
/// <summary>
///
/// </summary>
[JsonProperty("poll")]
public PollData? Poll { get; set; }
/// <summary>
/// The temporary login url that should be used for login.
/// </summary>
[JsonProperty("login")]
public string? LoginUrl { get; set; }
}
}

View File

@@ -0,0 +1,30 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Networking.CloudProviders.Nextcloud.OCS.Responses.LoginFlowV2
{
public class OcsResponseLoginFlowV2Credentials
{
/// <summary>
/// The server url the login credentials are for.
/// </summary>
[JsonProperty("server")]
public string? Server { get; set; }
/// <summary>
/// The login name (username or password) used for the login.
/// </summary>
[JsonProperty("loginName")]
public string? LoginName { get; set; }
/// <summary>
/// The app password that has been generated.
/// </summary>
[JsonProperty("appPassword")]
public string? AppPassword { get; set; }
}
}

View File

@@ -0,0 +1,26 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Networking.CloudProviders.Nextcloud.OCS.Responses
{
public class OcsResponse<TMeta, TData> where TMeta : OcsResponseMeta where TData : OcsResponseData
{
[JsonProperty("meta")]
public TMeta? Meta { get; set; }
[JsonProperty("data")]
public TData? Data { get; set; }
}
public class OcsResponse<TData> : OcsResponse<OcsResponseMeta, TData> where TData : OcsResponseData
{
}
public class OcsResponse : OcsResponse<OcsResponseMeta, OcsResponseData>
{
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Networking.CloudProviders.Nextcloud.OCS.Responses
{
public class OcsResponseData
{
}
}

View File

@@ -0,0 +1,21 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Networking.CloudProviders.Nextcloud.OCS.Responses
{
public class OcsResponseMeta
{
[JsonProperty("status")]
public string? Status { get; set; }
[JsonProperty("statuscode")]
public int? StatusCode { get; set; }
[JsonProperty("message")]
public string? Message { get; set; }
}
}