diff --git a/Pilz.Networking.CloudProviders.Nextcloud/Extensions.cs b/Pilz.Networking.CloudProviders.Nextcloud/Extensions.cs
new file mode 100644
index 0000000..24c928c
--- /dev/null
+++ b/Pilz.Networking.CloudProviders.Nextcloud/Extensions.cs
@@ -0,0 +1,37 @@
+using Pilz.Networking.CloudProviders.Nextcloud.Model;
+using Pilz.Networking.CloudProviders.Nextcloud.OCS;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Pilz.Networking.CloudProviders.Nextcloud
+{
+ public static class Extensions
+ {
+ public static string ToBasicAuth(this OcsApiAuthCredentials? credentials)
+ {
+ if (credentials != null)
+ {
+ var creds = $"{credentials?.LoginName}:{credentials?.AppPassword}";
+ var bytes = Encoding.UTF8.GetBytes(creds);
+ return "Basic " + Convert.ToBase64String(bytes);
+ }
+
+ return string.Empty;
+ }
+
+ public static OcsApiAuthCredentials? ToOcsApiAuthCredentials(this NextcloudLogin? login)
+ {
+ if (!string.IsNullOrEmpty(login?.LoginName) && !string.IsNullOrEmpty(login?.AppPassword))
+ return new OcsApiAuthCredentials(login.LoginName, login.AppPassword);
+ return null;
+ }
+
+ public static OcsApiUrlPath FillParameters(this OcsApiUrlPath path, params object?[] @params)
+ {
+ return (OcsApiUrlPath)string.Format(path, @params);
+ }
+ }
+}
diff --git a/Pilz.Networking.CloudProviders.Nextcloud/Model/NextcloudLogin.cs b/Pilz.Networking.CloudProviders.Nextcloud/Model/NextcloudLogin.cs
new file mode 100644
index 0000000..c30ee61
--- /dev/null
+++ b/Pilz.Networking.CloudProviders.Nextcloud/Model/NextcloudLogin.cs
@@ -0,0 +1,39 @@
+using Newtonsoft.Json;
+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.Model
+{
+ public class NextcloudLogin
+ {
+ ///
+ /// The server url the login credentials are for.
+ ///
+ public string? Server { get; set; }
+
+ ///
+ /// The login name (username or password) used for the login.
+ ///
+ public string? LoginName { get; set; }
+
+ ///
+ /// The app password that has been generated.
+ ///
+ public string? AppPassword { get; set; }
+
+ public NextcloudLogin()
+ {
+ }
+
+ public NextcloudLogin(OcsResponseLoginFlowV2Credentials response)
+ {
+ Server = response.Server;
+ LoginName = response.LoginName;
+ AppPassword = response.AppPassword;
+ }
+ }
+}
diff --git a/Pilz.Networking.CloudProviders.Nextcloud/Model/UserBackendCapabilities.cs b/Pilz.Networking.CloudProviders.Nextcloud/Model/UserBackendCapabilities.cs
new file mode 100644
index 0000000..b234ac4
--- /dev/null
+++ b/Pilz.Networking.CloudProviders.Nextcloud/Model/UserBackendCapabilities.cs
@@ -0,0 +1,22 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Pilz.Networking.CloudProviders.Nextcloud.Model
+{
+ public class UserBackendCapabilities
+ {
+ ///
+ /// Defines if the display name can be changed.
+ ///
+ public bool SetDisplayName { get; set; }
+
+ ///
+ /// Defines if the password can be changed.
+ ///
+ public bool SetPassword { get; set; }
+ }
+}
diff --git a/Pilz.Networking.CloudProviders.Nextcloud/Model/UserInfo.cs b/Pilz.Networking.CloudProviders.Nextcloud/Model/UserInfo.cs
new file mode 100644
index 0000000..e46205a
--- /dev/null
+++ b/Pilz.Networking.CloudProviders.Nextcloud/Model/UserInfo.cs
@@ -0,0 +1,131 @@
+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.Model
+{
+ public class UserInfo
+ {
+ ///
+ /// Defines if the user is enabled.
+ ///
+ public bool Enabled { get; set; }
+
+ ///
+ /// The location of the user's storage directory.
+ ///
+ public string? StorageLocation { get; set; }
+
+ ///
+ /// The uniquie user id that infos are for.
+ ///
+ public string? ID { get; set; }
+
+ ///
+ /// The last time when the user has logged in to its account.
+ ///
+ public DateTime LastLogin { get; set; }
+
+ ///
+ /// The backend of the user. Common values are "Database" or "LDAP".
+ ///
+ public string? Backend { get; set; }
+
+ ///
+ /// The Email address of the user.
+ ///
+ public string? Email { get; set; }
+
+ ///
+ /// The displayname of the user.
+ ///
+ public string? Displayname { get; set; }
+
+ ///
+ /// The displayname of the user.
+ ///
+ public string? Displayname2 { get; set; }
+
+ ///
+ /// The phone number of the user.
+ ///
+ public string? Phone { get; set; }
+
+ ///
+ /// The address of the user.
+ ///
+ public string? Address { get; set; }
+
+ ///
+ /// The Website of the user.
+ ///
+ public string? Website { get; set; }
+
+ ///
+ /// The twitter profile name of the user.
+ ///
+ public string? Twitter { get; set; }
+
+ ///
+ /// Defines the groups the user is member of.
+ ///
+ public string[] Groups { get; set; }
+
+ ///
+ /// The configured language of the user.
+ ///
+ public string? Language { get; set; }
+
+ ///
+ /// The configured location of the user.
+ ///
+ public string? Locale { get; set; }
+
+ ///
+ /// Quota informations for the user.
+ ///
+ public UserQuota Quota { get; } = new();
+
+ ///
+ /// Backend capabilities of the user.
+ ///
+ public UserBackendCapabilities BackendCapabilities { get; } = new();
+
+ public UserInfo(OcsResponseDataUser responseData)
+ {
+ Enabled = Convert.ToBoolean(responseData.Enabled);
+ StorageLocation = responseData.StorageLocation;
+ ID = responseData.ID;
+ LastLogin = Convert.ToDateTime(responseData.LastLogin);
+ Backend = responseData.Backend;
+ Email = responseData.Email;
+ Displayname = responseData.Displayname;
+ Displayname2 = responseData.Displayname2;
+ Phone = responseData.Phone;
+ Address = responseData.Address;
+ Website = responseData.Website;
+ Twitter = responseData.Twitter;
+ Groups = responseData.Groups ?? Array.Empty();
+ Language = responseData.Language;
+ Locale = responseData.Locale;
+
+ if (responseData.Quota != null)
+ {
+ Quota.Free = responseData.Quota.Free ?? 0;
+ Quota.Used = responseData.Quota.Used ?? 0;
+ Quota.Total = responseData.Quota.Total ?? 0;
+ Quota.Relative = responseData.Quota.Relative ?? 0.0F;
+ Quota.Quota = responseData.Quota.Quota ?? 0;
+ }
+
+ if (responseData.BackendCapabilities != null)
+ {
+ BackendCapabilities.SetDisplayName = Convert.ToBoolean(responseData.BackendCapabilities.SetDisplayName);
+ BackendCapabilities.SetPassword = Convert.ToBoolean(responseData.BackendCapabilities.SetPassword);
+ }
+ }
+ }
+}
diff --git a/Pilz.Networking.CloudProviders.Nextcloud/Model/UserQuota.cs b/Pilz.Networking.CloudProviders.Nextcloud/Model/UserQuota.cs
new file mode 100644
index 0000000..dcbef3c
--- /dev/null
+++ b/Pilz.Networking.CloudProviders.Nextcloud/Model/UserQuota.cs
@@ -0,0 +1,37 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Pilz.Networking.CloudProviders.Nextcloud.Model
+{
+ public class UserQuota
+ {
+ ///
+ /// Amount of free bytes left.
+ ///
+ public ulong Free { get; set; }
+
+ ///
+ /// Amount of already used bytes.
+ ///
+ public ulong Used { get; set; }
+
+ ///
+ /// Total amount of all bytes (free + used).
+ ///
+ public ulong Total { get; set; }
+
+ ///
+ /// Relative amount of used quota in percent.
+ ///
+ public float Relative { get; set; }
+
+ ///
+ /// Total amount of bytes available.
+ ///
+ public ulong Quota { get; set; }
+ }
+}
diff --git a/Pilz.Networking.CloudProviders.Nextcloud/NextcloudClient.cs b/Pilz.Networking.CloudProviders.Nextcloud/NextcloudClient.cs
new file mode 100644
index 0000000..874f68f
--- /dev/null
+++ b/Pilz.Networking.CloudProviders.Nextcloud/NextcloudClient.cs
@@ -0,0 +1,129 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Pilz.Networking.CloudProviders.Nextcloud;
+using Pilz.Networking.CloudProviders.Nextcloud.Model;
+using Pilz.Networking.CloudProviders.Nextcloud.OCS;
+using Pilz.Networking.CloudProviders.Nextcloud.OCS.Responses.LoginFlowV2;
+
+namespace ConsoleApp9
+{
+ public class NextcloudClient : IDisposable
+ {
+ private readonly OcsApi ocs = new();
+
+ public NextcloudLogin? CurrentLogin { get; private set; }
+
+ public NextcloudClient()
+ {
+ ocs.GetOcsApiAuthCredentails += Ocs_GetOcsApiAuthCredentails;
+ }
+
+ private void Ocs_GetOcsApiAuthCredentails(object sender, GetOcsApiAuthCredentailsEventArgs eventArgs)
+ {
+ if (sender == ocs)
+ eventArgs.Credentials = CurrentLogin.ToOcsApiAuthCredentials();
+ }
+
+ public async Task LoginAsync(NextcloudLogin login)
+ {
+ // Ensure we are logged out
+ await LogoutAsync();
+
+ // Temporary set user login
+ CurrentLogin = login;
+
+ // Try get user info & check if user is enabled
+ var userInfo = await GetUserInfoAsync();
+ var isValid = userInfo != null && userInfo.Enabled;
+
+ // If invalid, reset login credentials
+ if (!isValid)
+ CurrentLogin = null;
+
+ return userInfo;
+ }
+
+ public async Task LoginAsync(string baseUrl, CancellationToken cancellationToken)
+ {
+ // Ensure we are logged out
+ await LogoutAsync();
+
+ // Init the login process
+ var initResponse = await ocs.LoginFlowV2.Init(baseUrl);
+
+ if (!string.IsNullOrEmpty(initResponse?.LoginUrl) && initResponse.Poll != null)
+ {
+ // Open the browser
+ Process.Start(new ProcessStartInfo
+ {
+ FileName = initResponse.LoginUrl,
+ UseShellExecute = true
+ });
+
+ // Poll for credentails in intervals
+ OcsResponseLoginFlowV2Credentials? pollResponse = null;
+ while (!cancellationToken.IsCancellationRequested && pollResponse is null)
+ {
+ // Wait 5 seconds
+ Thread.Sleep(5000);
+
+ // Poll the credentials
+ if (!cancellationToken.IsCancellationRequested)
+ pollResponse = await ocs.LoginFlowV2.Poll(initResponse.Poll);
+ }
+
+ // Check login credentials
+ if (pollResponse is not null)
+ CurrentLogin = new(pollResponse);
+ }
+
+ return CurrentLogin;
+ }
+
+ public Task GetUserInfoAsync()
+ {
+ if (!string.IsNullOrEmpty(CurrentLogin?.LoginName))
+ return GetUserInfoAsync(CurrentLogin.LoginName);
+ else
+ return Task.FromResult(null);
+ }
+
+ public async Task GetUserInfoAsync(string username)
+ {
+ var result = await ocs.Cloud.GetUserMeta(username);
+
+ if (result?.Data != null)
+ return new UserInfo(result.Data);
+
+ return null;
+ }
+
+ public Task LogoutAsync()
+ {
+ return LogoutAsync(true);
+ }
+
+ public async Task LogoutAsync(bool logoutOnServer)
+ {
+ if (CurrentLogin != null)
+ {
+ // Delete currently used app password
+ await ocs.Core.DeleteAppPassword();
+
+ // Reset current login infos
+ CurrentLogin = null;
+ }
+ }
+
+ public void Dispose()
+ {
+ ocs.Dispose();
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/Pilz.Networking.CloudProviders.Nextcloud/OCS/APIs/OcsApiBase.cs b/Pilz.Networking.CloudProviders.Nextcloud/OCS/APIs/OcsApiBase.cs
new file mode 100644
index 0000000..a0ec4ed
--- /dev/null
+++ b/Pilz.Networking.CloudProviders.Nextcloud/OCS/APIs/OcsApiBase.cs
@@ -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;
+ }
+ }
+}
diff --git a/Pilz.Networking.CloudProviders.Nextcloud/OCS/APIs/OcsApiCloud.cs b/Pilz.Networking.CloudProviders.Nextcloud/OCS/APIs/OcsApiCloud.cs
new file mode 100644
index 0000000..21e4d95
--- /dev/null
+++ b/Pilz.Networking.CloudProviders.Nextcloud/OCS/APIs/OcsApiCloud.cs
@@ -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 GetUserMeta(string username)
+ {
+ return Manager.MakeRequestJson(HttpMethod.Get, OCS_CLOUD_USER_METADATA.FillParameters(username));
+ }
+ }
+}
diff --git a/Pilz.Networking.CloudProviders.Nextcloud/OCS/APIs/OcsApiCore.cs b/Pilz.Networking.CloudProviders.Nextcloud/OCS/APIs/OcsApiCore.cs
new file mode 100644
index 0000000..2c53cbe
--- /dev/null
+++ b/Pilz.Networking.CloudProviders.Nextcloud/OCS/APIs/OcsApiCore.cs
@@ -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 DeleteAppPassword()
+ {
+ using var msg = await Manager.MakeRequest(HttpMethod.Delete, OCS_CORE_APPPASSWORD);
+ return msg != null && msg.IsSuccessStatusCode;
+ }
+ }
+}
diff --git a/Pilz.Networking.CloudProviders.Nextcloud/OCS/APIs/OcsApiLoginFlowV2.cs b/Pilz.Networking.CloudProviders.Nextcloud/OCS/APIs/OcsApiLoginFlowV2.cs
new file mode 100644
index 0000000..357aeaa
--- /dev/null
+++ b/Pilz.Networking.CloudProviders.Nextcloud/OCS/APIs/OcsApiLoginFlowV2.cs
@@ -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 Init(string url)
+ {
+ return Manager.MakeRequestJson(HttpMethod.Get, url + OCS_LOGIN_INIT);
+ }
+
+ public Task Poll(OcsResponseLoginFlowV2.PollData poll)
+ {
+ ArgumentNullException.ThrowIfNull(poll?.Endpoint);
+ ArgumentNullException.ThrowIfNull(poll?.Token);
+
+ return Manager.MakeRequestJson(HttpMethod.Get, poll.Endpoint,
+ parameters: new Dictionary
+ {
+ { "token", poll.Token }
+ });
+ }
+ }
+}
diff --git a/Pilz.Networking.CloudProviders.Nextcloud/OCS/GetOcsApiAuthCredentailsEventArgs.cs b/Pilz.Networking.CloudProviders.Nextcloud/OCS/GetOcsApiAuthCredentailsEventArgs.cs
new file mode 100644
index 0000000..f7e4694
--- /dev/null
+++ b/Pilz.Networking.CloudProviders.Nextcloud/OCS/GetOcsApiAuthCredentailsEventArgs.cs
@@ -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; }
+ }
+}
diff --git a/Pilz.Networking.CloudProviders.Nextcloud/OCS/OcsApi.cs b/Pilz.Networking.CloudProviders.Nextcloud/OCS/OcsApi.cs
new file mode 100644
index 0000000..c87179d
--- /dev/null
+++ b/Pilz.Networking.CloudProviders.Nextcloud/OCS/OcsApi.cs
@@ -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 MakeRequestJson(HttpMethod httpMethod, OcsApiUrlPath url, bool useAuthentication = true, Dictionary? parameters = null)
+ {
+ return MakeRequestJson(httpMethod, BuildFullUrl(url), useAuthentication: useAuthentication, parameters: parameters);
+ }
+
+ public Task MakeRequest(HttpMethod httpMethod, OcsApiUrlPath url, bool useAuthentication = true, Dictionary? parameters = null)
+ {
+ return MakeRequest(httpMethod, BuildFullUrl(url), useAuthentication: useAuthentication, parameters: parameters);
+ }
+
+ public async Task MakeRequestJson(HttpMethod httpMethod, string url, bool useAuthentication = true, Dictionary? 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(bodyInit);
+ }
+
+ return default;
+ }
+
+ public async Task MakeRequest(HttpMethod httpMethod, string url, bool useAuthentication = true, Dictionary? 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);
+ }
+ }
+}
diff --git a/Pilz.Networking.CloudProviders.Nextcloud/OCS/OcsApiAuthCredentials.cs b/Pilz.Networking.CloudProviders.Nextcloud/OCS/OcsApiAuthCredentials.cs
new file mode 100644
index 0000000..6c24b71
--- /dev/null
+++ b/Pilz.Networking.CloudProviders.Nextcloud/OCS/OcsApiAuthCredentials.cs
@@ -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;
+ }
+ }
+}
diff --git a/Pilz.Networking.CloudProviders.Nextcloud/OCS/OcsApiUrlPath.cs b/Pilz.Networking.CloudProviders.Nextcloud/OCS/OcsApiUrlPath.cs
new file mode 100644
index 0000000..f32c375
--- /dev/null
+++ b/Pilz.Networking.CloudProviders.Nextcloud/OCS/OcsApiUrlPath.cs
@@ -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;
+ }
+ }
+}
diff --git a/Pilz.Networking.CloudProviders.Nextcloud/OCS/Responses/Cloud/OcsResponseDataUser.cs b/Pilz.Networking.CloudProviders.Nextcloud/OCS/Responses/Cloud/OcsResponseDataUser.cs
new file mode 100644
index 0000000..0000713
--- /dev/null
+++ b/Pilz.Networking.CloudProviders.Nextcloud/OCS/Responses/Cloud/OcsResponseDataUser.cs
@@ -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; }
+ }
+}
diff --git a/Pilz.Networking.CloudProviders.Nextcloud/OCS/Responses/Cloud/OcsResponseUser.cs b/Pilz.Networking.CloudProviders.Nextcloud/OCS/Responses/Cloud/OcsResponseUser.cs
new file mode 100644
index 0000000..f3f3e74
--- /dev/null
+++ b/Pilz.Networking.CloudProviders.Nextcloud/OCS/Responses/Cloud/OcsResponseUser.cs
@@ -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
+ {
+ }
+}
diff --git a/Pilz.Networking.CloudProviders.Nextcloud/OCS/Responses/LoginFlowV2/OcsResponseLoginFlowV2.cs b/Pilz.Networking.CloudProviders.Nextcloud/OCS/Responses/LoginFlowV2/OcsResponseLoginFlowV2.cs
new file mode 100644
index 0000000..11c45ff
--- /dev/null
+++ b/Pilz.Networking.CloudProviders.Nextcloud/OCS/Responses/LoginFlowV2/OcsResponseLoginFlowV2.cs
@@ -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
+ {
+ ///
+ /// The login token that has been created for the login process.
+ /// It can be used to poll the login state.
+ ///
+ [JsonProperty("token")]
+ public string? Token { get; set; }
+
+ ///
+ ///
+ ///
+ [JsonProperty("endpoint")]
+ public string? Endpoint { get; set; }
+ }
+
+ ///
+ ///
+ ///
+ [JsonProperty("poll")]
+ public PollData? Poll { get; set; }
+
+ ///
+ /// The temporary login url that should be used for login.
+ ///
+ [JsonProperty("login")]
+ public string? LoginUrl { get; set; }
+ }
+}
diff --git a/Pilz.Networking.CloudProviders.Nextcloud/OCS/Responses/LoginFlowV2/OcsResponseLoginFlowV2Credentials.cs b/Pilz.Networking.CloudProviders.Nextcloud/OCS/Responses/LoginFlowV2/OcsResponseLoginFlowV2Credentials.cs
new file mode 100644
index 0000000..121bf8c
--- /dev/null
+++ b/Pilz.Networking.CloudProviders.Nextcloud/OCS/Responses/LoginFlowV2/OcsResponseLoginFlowV2Credentials.cs
@@ -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
+ {
+ ///
+ /// The server url the login credentials are for.
+ ///
+ [JsonProperty("server")]
+ public string? Server { get; set; }
+
+ ///
+ /// The login name (username or password) used for the login.
+ ///
+ [JsonProperty("loginName")]
+ public string? LoginName { get; set; }
+
+ ///
+ /// The app password that has been generated.
+ ///
+ [JsonProperty("appPassword")]
+ public string? AppPassword { get; set; }
+ }
+}
diff --git a/Pilz.Networking.CloudProviders.Nextcloud/OCS/Responses/OcsResponse.cs b/Pilz.Networking.CloudProviders.Nextcloud/OCS/Responses/OcsResponse.cs
new file mode 100644
index 0000000..c21a6a4
--- /dev/null
+++ b/Pilz.Networking.CloudProviders.Nextcloud/OCS/Responses/OcsResponse.cs
@@ -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 where TMeta : OcsResponseMeta where TData : OcsResponseData
+ {
+ [JsonProperty("meta")]
+ public TMeta? Meta { get; set; }
+
+ [JsonProperty("data")]
+ public TData? Data { get; set; }
+ }
+
+ public class OcsResponse : OcsResponse where TData : OcsResponseData
+ {
+ }
+
+ public class OcsResponse : OcsResponse
+ {
+ }
+}
diff --git a/Pilz.Networking.CloudProviders.Nextcloud/OCS/Responses/OcsResponseData.cs b/Pilz.Networking.CloudProviders.Nextcloud/OCS/Responses/OcsResponseData.cs
new file mode 100644
index 0000000..cc3a1ac
--- /dev/null
+++ b/Pilz.Networking.CloudProviders.Nextcloud/OCS/Responses/OcsResponseData.cs
@@ -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
+ {
+ }
+}
diff --git a/Pilz.Networking.CloudProviders.Nextcloud/OCS/Responses/OcsResponseMeta.cs b/Pilz.Networking.CloudProviders.Nextcloud/OCS/Responses/OcsResponseMeta.cs
new file mode 100644
index 0000000..049eeb7
--- /dev/null
+++ b/Pilz.Networking.CloudProviders.Nextcloud/OCS/Responses/OcsResponseMeta.cs
@@ -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; }
+ }
+}
diff --git a/Pilz.Networking.CloudProviders.Nextcloud/Pilz.Networking.CloudProviders.Nextcloud.csproj b/Pilz.Networking.CloudProviders.Nextcloud/Pilz.Networking.CloudProviders.Nextcloud.csproj
new file mode 100644
index 0000000..c07d969
--- /dev/null
+++ b/Pilz.Networking.CloudProviders.Nextcloud/Pilz.Networking.CloudProviders.Nextcloud.csproj
@@ -0,0 +1,19 @@
+
+
+
+ net6.0
+ enable
+ enable
+
+
+
+ True
+ 1.yyyy.Mdd.Hmm
+ 1.2023.927.1412
+
+
+
+
+
+
+
diff --git a/Pilz.sln b/Pilz.sln
index 20a2f6a..1f05bbb 100644
--- a/Pilz.sln
+++ b/Pilz.sln
@@ -33,7 +33,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pilz.Cryptography", "Pilz.C
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pilz.UI.Telerik.SymbolFactory", "Pilz.UI.Telerik.SymbolFactory\Pilz.UI.Telerik.SymbolFactory.csproj", "{2B3B8161-29FF-4526-9082-4410AB5144A5}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pilz.UI.Telerik", "Pilz.UI.Telerik\Pilz.UI.Telerik.csproj", "{DF674119-CC28-40AA-968F-1E23D184A491}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pilz.UI.Telerik", "Pilz.UI.Telerik\Pilz.UI.Telerik.csproj", "{DF674119-CC28-40AA-968F-1E23D184A491}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pilz.Networking.CloudProviders.Nextcloud", "Pilz.Networking.CloudProviders.Nextcloud\Pilz.Networking.CloudProviders.Nextcloud.csproj", "{A91E966B-3A82-4F32-A703-2FC1C7654FD1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -171,6 +173,14 @@ Global
{DF674119-CC28-40AA-968F-1E23D184A491}.Release|Any CPU.Build.0 = Release|Any CPU
{DF674119-CC28-40AA-968F-1E23D184A491}.Release|x86.ActiveCfg = Release|Any CPU
{DF674119-CC28-40AA-968F-1E23D184A491}.Release|x86.Build.0 = Release|Any CPU
+ {A91E966B-3A82-4F32-A703-2FC1C7654FD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A91E966B-3A82-4F32-A703-2FC1C7654FD1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A91E966B-3A82-4F32-A703-2FC1C7654FD1}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A91E966B-3A82-4F32-A703-2FC1C7654FD1}.Debug|x86.Build.0 = Debug|Any CPU
+ {A91E966B-3A82-4F32-A703-2FC1C7654FD1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A91E966B-3A82-4F32-A703-2FC1C7654FD1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A91E966B-3A82-4F32-A703-2FC1C7654FD1}.Release|x86.ActiveCfg = Release|Any CPU
+ {A91E966B-3A82-4F32-A703-2FC1C7654FD1}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE