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