User.vala 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. using Invercargill;
  2. using Invercargill.DataStructures;
  3. using Json;
  4. namespace Spry.Authentication {
  5. /**
  6. * User represents an authenticated user and implements the Identity interface
  7. * for integration with the Authorisation system.
  8. */
  9. public class User : GLib.Object, Authorisation.Identity {
  10. // =========================================================================
  11. // Identity Properties (required by Authorisation.Identity interface)
  12. // =========================================================================
  13. // Backing fields for identity properties
  14. private string _id = "";
  15. private string _username = "";
  16. /**
  17. * Unique identifier for this user.
  18. * Read-only for Identity interface compliance.
  19. * Use set_id() to modify.
  20. */
  21. public string id {
  22. get { return _id; }
  23. }
  24. /**
  25. * Human-readable name for this user.
  26. * Read-only for Identity interface compliance.
  27. * Use set_username() to modify.
  28. */
  29. public string username {
  30. get { return _username; }
  31. }
  32. /**
  33. * Returns permissions as an array for the Identity interface.
  34. * Converts from internal Vector<string> to string[] for serialization.
  35. */
  36. public string[] permissions {
  37. owned get {
  38. var result = new string[0];
  39. foreach (var perm in _permissions) {
  40. result += perm;
  41. }
  42. return result;
  43. }
  44. }
  45. /**
  46. * Returns user data as a Variant for embedding in authorisation tokens.
  47. * Includes email, app_data, and timestamps.
  48. */
  49. public Variant data {
  50. owned get {
  51. return get_data_variant();
  52. }
  53. }
  54. // =========================================================================
  55. // Additional Authentication Data
  56. // =========================================================================
  57. public string email { get; set; }
  58. public string password_hash { get; set; }
  59. // Metadata
  60. public DateTime created_at { get; set; }
  61. public DateTime? updated_at { get; set; }
  62. // Application-specific data - stored as JSON object
  63. public Dictionary<string, string> app_data { get; set; default = new Dictionary<string, string>(); }
  64. // =========================================================================
  65. // Internal Storage
  66. // =========================================================================
  67. // Internal vector storage for permissions (used for mutability)
  68. private Vector<string> _permissions = new Vector<string>();
  69. // =========================================================================
  70. // Setters for Identity Properties
  71. // =========================================================================
  72. /**
  73. * Sets the user ID.
  74. */
  75. public void set_id(string value) {
  76. _id = value;
  77. }
  78. /**
  79. * Sets the username.
  80. */
  81. public void set_username(string value) {
  82. _username = value;
  83. }
  84. // =========================================================================
  85. // Permission Management (for internal use)
  86. // =========================================================================
  87. /**
  88. * Gets the internal permissions vector for mutation.
  89. * Used by PermissionService to add/remove permissions.
  90. */
  91. public Vector<string> get_permissions_vector() {
  92. return _permissions;
  93. }
  94. /**
  95. * Adds a permission to the user.
  96. */
  97. public void add_permission(string permission) {
  98. _permissions.add(permission);
  99. }
  100. /**
  101. * Removes a permission from the user.
  102. */
  103. public void remove_permission(string permission) {
  104. _permissions.remove(permission);
  105. }
  106. /**
  107. * Clears all permissions from the user.
  108. */
  109. public void clear_permissions() {
  110. _permissions.clear();
  111. }
  112. /**
  113. * Checks if the user has a specific permission.
  114. */
  115. public bool has_permission(string permission) {
  116. foreach (var perm in _permissions) {
  117. if (perm == permission) {
  118. return true;
  119. }
  120. }
  121. return false;
  122. }
  123. // =========================================================================
  124. // Helper Methods
  125. // =========================================================================
  126. /**
  127. * Helper to convert user data to Variant for token embedding.
  128. */
  129. private Variant get_data_variant() {
  130. var builder = new VariantBuilder(VariantType.VARDICT);
  131. builder.add("{sv}", "email", new Variant.string(email ?? ""));
  132. builder.add("{sv}", "created_at", new Variant.string(created_at != null ? created_at.format_iso8601() : ""));
  133. if (updated_at != null) {
  134. builder.add("{sv}", "updated_at", new Variant.string(((!)updated_at).format_iso8601()));
  135. }
  136. // Add app_data as a dictionary
  137. var app_data_builder = new VariantBuilder(VariantType.VARDICT);
  138. var iter = app_data.iterator();
  139. while (iter.next()) {
  140. var pair = iter.get();
  141. app_data_builder.add("{sv}", pair.key, new Variant.string(pair.value ?? ""));
  142. }
  143. builder.add("{sv}", "app_data", app_data_builder.end());
  144. return builder.end();
  145. }
  146. // =========================================================================
  147. // Constructors and JSON Serialization
  148. // =========================================================================
  149. public User() {
  150. _id = "";
  151. _username = "";
  152. email = "";
  153. password_hash = "";
  154. created_at = new DateTime.now_utc();
  155. }
  156. public static User from_json(Json.Object obj) {
  157. var user = new User();
  158. // Required string fields - use has_member and null coalescing for safety
  159. user.set_id(obj.has_member("id") ? (obj.get_string_member("id") ?? "") : "");
  160. user.set_username(obj.has_member("username") ? (obj.get_string_member("username") ?? "") : "");
  161. user.email = obj.has_member("email") ? (obj.get_string_member("email") ?? "") : "";
  162. user.password_hash = obj.has_member("password_hash") ? (obj.get_string_member("password_hash") ?? "") : "";
  163. // created_at - check member exists and value is not null/empty
  164. if (obj.has_member("created_at")) {
  165. var created_str = obj.get_string_member("created_at");
  166. if (created_str != null && created_str.length > 0) {
  167. user.created_at = new DateTime.from_iso8601(created_str, new TimeZone.utc());
  168. }
  169. }
  170. // updated_at (optional) - check member exists, is not null, and has value
  171. if (obj.has_member("updated_at")) {
  172. var member = obj.get_member("updated_at");
  173. if (member != null && member.get_node_type() == Json.NodeType.VALUE) {
  174. var updated_str = obj.get_string_member("updated_at");
  175. if (updated_str != null && updated_str.length > 0) {
  176. user.updated_at = new DateTime.from_iso8601(updated_str, new TimeZone.utc());
  177. }
  178. }
  179. }
  180. // permissions (array of strings) - check member exists and is array
  181. if (obj.has_member("permissions")) {
  182. var member = obj.get_member("permissions");
  183. if (member != null && member.get_node_type() == Json.NodeType.ARRAY) {
  184. var perms_array = obj.get_array_member("permissions");
  185. if (perms_array != null) {
  186. foreach (var perm in perms_array.get_elements()) {
  187. var perm_str = perm.get_string();
  188. if (perm_str != null) {
  189. user._permissions.add(perm_str);
  190. }
  191. }
  192. }
  193. }
  194. }
  195. // app_data (object with string values) - check member exists and is object
  196. if (obj.has_member("app_data")) {
  197. var member = obj.get_member("app_data");
  198. if (member != null && member.get_node_type() == Json.NodeType.OBJECT) {
  199. var app_data_obj = obj.get_object_member("app_data");
  200. if (app_data_obj != null) {
  201. foreach (var key in app_data_obj.get_members()) {
  202. var value = app_data_obj.get_string_member(key);
  203. user.app_data.set(key, value ?? "");
  204. }
  205. }
  206. }
  207. }
  208. return user;
  209. }
  210. public Json.Object to_json() {
  211. var obj = new Json.Object();
  212. // Use null coalescing to ensure we never pass null to set_string_member
  213. obj.set_string_member("id", id ?? "");
  214. obj.set_string_member("username", username ?? "");
  215. obj.set_string_member("email", email ?? "");
  216. obj.set_string_member("password_hash", password_hash ?? "");
  217. obj.set_string_member("created_at", created_at != null ? created_at.format_iso8601() : new DateTime.now_utc().format_iso8601());
  218. // updated_at (optional)
  219. if (updated_at != null) {
  220. obj.set_string_member("updated_at", ((!)updated_at).format_iso8601());
  221. } else {
  222. obj.set_null_member("updated_at");
  223. }
  224. // permissions array - always create array even if empty
  225. var perms_array = new Json.Array();
  226. if (_permissions != null) {
  227. foreach (var perm in _permissions) {
  228. if (perm != null) {
  229. perms_array.add_string_element(perm);
  230. }
  231. }
  232. }
  233. obj.set_array_member("permissions", perms_array);
  234. // app_data object - always create object even if empty
  235. var app_data_obj = new Json.Object();
  236. if (app_data != null) {
  237. var iter = app_data.iterator();
  238. while (iter.next()) {
  239. var pair = iter.get();
  240. app_data_obj.set_string_member(pair.key ?? "", pair.value ?? "");
  241. }
  242. }
  243. obj.set_object_member("app_data", app_data_obj);
  244. return obj;
  245. }
  246. }
  247. }