using Invercargill; using Invercargill.DataStructures; using Json; namespace Spry.Authentication { /** * User represents an authenticated user and implements the Identity interface * for integration with the Authorisation system. */ public class User : GLib.Object, Authorisation.Identity { // ========================================================================= // Identity Properties (required by Authorisation.Identity interface) // ========================================================================= // Backing fields for identity properties private string _id = ""; private string _username = ""; /** * Unique identifier for this user. * Read-only for Identity interface compliance. * Use set_id() to modify. */ public string id { get { return _id; } } /** * Human-readable name for this user. * Read-only for Identity interface compliance. * Use set_username() to modify. */ public string username { get { return _username; } } /** * Returns permissions as an array for the Identity interface. * Converts from internal Vector to string[] for serialization. */ public string[] permissions { owned get { var result = new string[0]; foreach (var perm in _permissions) { result += perm; } return result; } } /** * Returns user data as a Variant for embedding in authorisation tokens. * Includes email, app_data, and timestamps. */ public Variant data { owned get { return get_data_variant(); } } // ========================================================================= // Additional Authentication Data // ========================================================================= public string email { get; set; } public string password_hash { get; set; } // Metadata public DateTime created_at { get; set; } public DateTime? updated_at { get; set; } // Application-specific data - stored as JSON object public Dictionary app_data { get; set; default = new Dictionary(); } // ========================================================================= // Internal Storage // ========================================================================= // Internal vector storage for permissions (used for mutability) private Vector _permissions = new Vector(); // ========================================================================= // Setters for Identity Properties // ========================================================================= /** * Sets the user ID. */ public void set_id(string value) { _id = value; } /** * Sets the username. */ public void set_username(string value) { _username = value; } // ========================================================================= // Permission Management (for internal use) // ========================================================================= /** * Gets the internal permissions vector for mutation. * Used by PermissionService to add/remove permissions. */ public Vector get_permissions_vector() { return _permissions; } /** * Adds a permission to the user. */ public void add_permission(string permission) { _permissions.add(permission); } /** * Removes a permission from the user. */ public void remove_permission(string permission) { _permissions.remove(permission); } /** * Clears all permissions from the user. */ public void clear_permissions() { _permissions.clear(); } /** * Checks if the user has a specific permission. */ public bool has_permission(string permission) { foreach (var perm in _permissions) { if (perm == permission) { return true; } } return false; } // ========================================================================= // Helper Methods // ========================================================================= /** * Helper to convert user data to Variant for token embedding. */ private Variant get_data_variant() { var builder = new VariantBuilder(VariantType.VARDICT); builder.add("{sv}", "email", new Variant.string(email ?? "")); builder.add("{sv}", "created_at", new Variant.string(created_at != null ? created_at.format_iso8601() : "")); if (updated_at != null) { builder.add("{sv}", "updated_at", new Variant.string(((!)updated_at).format_iso8601())); } // Add app_data as a dictionary var app_data_builder = new VariantBuilder(VariantType.VARDICT); var iter = app_data.iterator(); while (iter.next()) { var pair = iter.get(); app_data_builder.add("{sv}", pair.key, new Variant.string(pair.value ?? "")); } builder.add("{sv}", "app_data", app_data_builder.end()); return builder.end(); } // ========================================================================= // Constructors and JSON Serialization // ========================================================================= public User() { _id = ""; _username = ""; email = ""; password_hash = ""; created_at = new DateTime.now_utc(); } public static User from_json(Json.Object obj) { var user = new User(); // Required string fields - use has_member and null coalescing for safety user.set_id(obj.has_member("id") ? (obj.get_string_member("id") ?? "") : ""); user.set_username(obj.has_member("username") ? (obj.get_string_member("username") ?? "") : ""); user.email = obj.has_member("email") ? (obj.get_string_member("email") ?? "") : ""; user.password_hash = obj.has_member("password_hash") ? (obj.get_string_member("password_hash") ?? "") : ""; // created_at - check member exists and value is not null/empty if (obj.has_member("created_at")) { var created_str = obj.get_string_member("created_at"); if (created_str != null && created_str.length > 0) { user.created_at = new DateTime.from_iso8601(created_str, new TimeZone.utc()); } } // updated_at (optional) - check member exists, is not null, and has value if (obj.has_member("updated_at")) { var member = obj.get_member("updated_at"); if (member != null && member.get_node_type() == Json.NodeType.VALUE) { var updated_str = obj.get_string_member("updated_at"); if (updated_str != null && updated_str.length > 0) { user.updated_at = new DateTime.from_iso8601(updated_str, new TimeZone.utc()); } } } // permissions (array of strings) - check member exists and is array if (obj.has_member("permissions")) { var member = obj.get_member("permissions"); if (member != null && member.get_node_type() == Json.NodeType.ARRAY) { var perms_array = obj.get_array_member("permissions"); if (perms_array != null) { foreach (var perm in perms_array.get_elements()) { var perm_str = perm.get_string(); if (perm_str != null) { user._permissions.add(perm_str); } } } } } // app_data (object with string values) - check member exists and is object if (obj.has_member("app_data")) { var member = obj.get_member("app_data"); if (member != null && member.get_node_type() == Json.NodeType.OBJECT) { var app_data_obj = obj.get_object_member("app_data"); if (app_data_obj != null) { foreach (var key in app_data_obj.get_members()) { var value = app_data_obj.get_string_member(key); user.app_data.set(key, value ?? ""); } } } } return user; } public Json.Object to_json() { var obj = new Json.Object(); // Use null coalescing to ensure we never pass null to set_string_member obj.set_string_member("id", id ?? ""); obj.set_string_member("username", username ?? ""); obj.set_string_member("email", email ?? ""); obj.set_string_member("password_hash", password_hash ?? ""); obj.set_string_member("created_at", created_at != null ? created_at.format_iso8601() : new DateTime.now_utc().format_iso8601()); // updated_at (optional) if (updated_at != null) { obj.set_string_member("updated_at", ((!)updated_at).format_iso8601()); } else { obj.set_null_member("updated_at"); } // permissions array - always create array even if empty var perms_array = new Json.Array(); if (_permissions != null) { foreach (var perm in _permissions) { if (perm != null) { perms_array.add_string_element(perm); } } } obj.set_array_member("permissions", perms_array); // app_data object - always create object even if empty var app_data_obj = new Json.Object(); if (app_data != null) { var iter = app_data.iterator(); while (iter.next()) { var pair = iter.get(); app_data_obj.set_string_member(pair.key ?? "", pair.value ?? ""); } } obj.set_object_member("app_data", app_data_obj); return obj; } } }