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 }