1 /**
2 * Copyright © DiamondMVC 2019
3 * License: MIT (https://github.com/DiamondMVC/Diamond/blob/master/LICENSE)
4 * Author: Jacob Jensen (bausshf)
5 */
6 module diamond.authentication.roles;
7 
8 import diamond.core.apptype;
9 
10 static if (isWeb)
11 {
12   import diamond.errors.checks;
13   import diamond.authentication.permissions;
14   import diamond.http;
15 
16   /// The storage key for the authentication roles.
17   private static const __gshared _roleStorageKey = "__D_AUTH_ROLE";
18 
19   /**
20   * Gets the role storage key based on the client's host.
21   * If the host isn't found then the defauly key is used.
22   * Params:
23   *   client = The client to retrieve the host from.
24   * Returns:
25   *   The role storage key.
26   */
27   private string getRoleStorageKey(HttpClient client)
28   {
29     import diamond.core.senc;
30     import diamond.core.webconfig;
31 
32     string key = "";
33 
34     if (webConfig && webConfig.mappedAuthKeys && webConfig.mappedAuthKeys.length)
35     {
36       key = webConfig.mappedAuthKeys.get(client.host, "");
37     }
38 
39     return SENC.encode(key) ~ _roleStorageKey;
40   }
41 
42   /// The roles.
43   private static __gshared Role[string] _roles;
44 
45   /// The default role.
46   package(diamond) static __gshared Role defaultRole;
47 
48   /// Gets a boolean determining whether there are roles or not.
49   @property bool hasRoles() { return _roles.length > 0 && defaultRole !is null; }
50 
51   /// Wrapper around a role.
52   final class Role
53   {
54     private:
55     /// The name.
56     string _name;
57 
58     /// The permissions.
59     Permission[string] _permissions;
60 
61     /// The parent role.
62     Role _parent;
63 
64     /**
65     * Creates a new role.
66     * Params:
67     *   name = The name of the role.
68     */
69     this(string name)
70     {
71       _name = name;
72     }
73 
74     /**
75     * Creates a new role.
76     * Params:
77     *   name =   The name of the role.
78     *   parent = The parent role.
79     */
80     this(string name, Role parent)
81     {
82       _name = name;
83       _parent = parent;
84     }
85 
86     public:
87     final:
88     @property
89     {
90       /// Gets the name.
91       string name() { return _name; }
92 
93       /// Gets the parent.
94       Role parent() { return _parent; }
95     }
96 
97     /**
98     * Adds a permission to the role.
99     * Params:
100     *   resource =      The resource.
101     *   readAccess =    Boolean determining whether the role has read-access.
102     *   writeAccess =   Boolean determining whether the role has write-access.
103     *   updateAccess =  Boolean determining whether the role has update-access.
104     *   deleteAccess =  Boolean determining whether the role has delete-access.
105     * Returns:
106     *   The role, allowing the function to be chained.
107     */
108     Role addPermission
109     (
110       string resource,
111       bool readAccess, bool writeAccess,
112       bool updateAccess, bool deleteAccess
113     )
114     {
115       enforce(resource, "Found no resource to create permisions for.");
116 
117       import std..string : strip;
118       import std.array : replace;
119 
120       resource = resource.strip();
121 
122       if (!resource.replace("/", "").strip().length)
123       {
124         import diamond.core : webConfig, firstToLower;
125 
126         resource = webConfig.homeRoute.firstToLower();
127       }
128 
129       if (resource[0] == '/')
130       {
131         resource = resource[1 .. $];
132       }
133 
134       if (resource[$-1] == '/')
135       {
136         resource = resource[0 .. $-1];
137       }
138 
139       _permissions[resource] = new Permission(resource,
140                                      readAccess, writeAccess,
141                                      updateAccess, deleteAccess
142       );
143 
144       return this;
145     }
146 
147     /**
148     * Checks whether the role has permission to a specific resource.
149     * Params:
150     *   resource =    The resource.
151     *   permission =  The permission.
152     * Returns:
153     *   True if the role has permission, false otherwise.
154     */
155     bool hasPermission(string resource, PermissionType permission)
156     {
157       enforce(resource, "Found no resource to check permisions for.");
158 
159       import std..string : strip;
160       import std.array : replace;
161 
162       resource = resource.strip();
163 
164       if (!resource || !resource.replace("/", "").strip().length)
165       {
166         import diamond.core : webConfig, firstToLower;
167 
168         resource = webConfig.homeRoute.firstToLower();
169       }
170 
171       if (resource[0] == '/')
172       {
173         resource = resource[1 .. $];
174       }
175 
176       if (resource[$-1] == '/')
177       {
178         resource = resource[0 .. $-1];
179       }
180 
181       auto permissionResource = _permissions.get(resource, null);
182 
183       if (!permissionResource)
184       {
185         if (_parent)
186         {
187           return _parent.hasPermission(resource, permission);
188         }
189 
190         return defaultPermission;
191       }
192 
193       final switch (permission)
194       {
195         case PermissionType.readAccess: return permissionResource.readAccess;
196         case PermissionType.writeAccess: return permissionResource.writeAccess;
197         case PermissionType.updateAccess: return permissionResource.updateAccess;
198         case PermissionType.deleteAccess: return permissionResource.deleteAccess;
199       }
200     }
201   }
202 
203   /**
204   * Gets a role by its name.
205   * Params:
206   *   name = The name of the role.
207   * Returns:
208   *   The role if found, defaultRole otherwise.
209   */
210   Role getRole(string name)
211   {
212     return _roles.get(name, defaultRole);
213   }
214 
215   /**
216   * Gets a role by its request.
217   * Params:
218   *   client = The client.
219   * Returns:
220   *   The role if existing, defaultRole otherwise.
221   */
222   Role getRole(HttpClient client)
223   {
224     enforce(client, "No client specified.");
225 
226     return client.getContext!Role(getRoleStorageKey(client), defaultRole);
227   }
228 
229   /**
230   * Sets the role.
231   * Params:
232   *   client =  The client.
233   *   role =    The role.
234   */
235   package(diamond.authentication) void setRole(HttpClient client, Role role)
236   {
237     enforce(client, "No client specified.");
238     enforce(role, "No role specified.");
239 
240     client.addContext(getRoleStorageKey(client), role);
241   }
242 
243   /**
244   * Sets the role from the session.
245   * Params:
246   *   client =           The client.
247   *   defaultIsInvalid = Boolean determining whether the default role is an invalid role.
248   * Returns:
249   *   Returns true if the role was set from the session.
250   */
251   package(diamond.authentication) bool setRoleFromSession
252   (
253     HttpClient client,
254     bool defaultIsInvalid
255   )
256   {
257     enforce(client, "No client specified.");
258 
259     auto sessionRole = client.session.getValue!string(getRoleStorageKey(client), null);
260 
261     if (sessionRole !is null)
262     {
263       auto role = getRole(sessionRole);
264 
265       if (defaultIsInvalid && role == defaultRole)
266       {
267         return false;
268       }
269 
270       setRole(client, role);
271       return true;
272     }
273 
274     return false;
275   }
276 
277   /**
278   * Sets the session role.
279   * Params:
280   *   client =  The client.
281   *   role =     The role.
282   */
283   package(diamond.authentication) void setSessionRole
284   (
285     HttpClient client, Role role
286   )
287   {
288     client.session.setValue(getRoleStorageKey(client), role.name);
289   }
290 
291   /**
292   * Sets the default role.
293   * Params:
294   *   role = The role.
295   */
296   void setDefaultRole(Role role)
297   {
298     enforce(role, "Cannot set the default role to null.");
299 
300     defaultRole = role;
301   }
302 
303   /**
304   * Adds a new role.
305   * Params:
306   *   name = The name of the role.
307   * Returns:
308   *   The role.
309   */
310   Role addRole(string name)
311   {
312     auto role = new Role(name);
313 
314     _roles[role.name] = role;
315 
316     return role;
317   }
318 
319   /**
320   * Adds a new role.
321   * Params:
322   *   name =   The name of the role.
323   *   parent = The parent role.
324   * Returns:
325   *   The role.
326   */
327   Role addRole(string name, Role parent)
328   {
329     auto role = new Role(name, parent);
330 
331     _roles[role.name] = role;
332 
333     return role;
334   }
335 }