1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 module hunt.shiro.authz.permission.WildcardPermission; 20 21 import hunt.shiro.authz.permission.Permission; 22 import hunt.shiro.util.CollectionUtils; 23 // import hunt.shiro.util.StringUtils; 24 25 //import hunt.util.Common; 26 27 import hunt.collection; 28 import hunt.Exceptions; 29 import hunt.util.StringBuilder; 30 import hunt.text.StringUtils; 31 32 import std.array; 33 import std.string; 34 35 36 /** 37 * A <code>WildcardPermission</code> is a very flexible permission construct supporting multiple levels of 38 * permission matching. However, most people will probably follow some standard conventions as explained below. 39 * <p/> 40 * <h3>Simple Usage</h3> 41 * <p/> 42 * In the simplest form, <code>WildcardPermission</code> can be used as a simple permission string. You could grant a 43 * user an "editNewsletter" permission and then check to see if the user has the editNewsletter 44 * permission by calling 45 * <p/> 46 * <code>subject.isPermitted("editNewsletter")</code> 47 * <p/> 48 * This is (mostly) equivalent to 49 * <p/> 50 * <code>subject.isPermitted( new WildcardPermission("editNewsletter") )</code> 51 * <p/> 52 * but more on that later. 53 * <p/> 54 * The simple permission string may work for simple applications, but it requires you to have permissions like 55 * <code>"viewNewsletter"</code>, <code>"deleteNewsletter"</code>, 56 * <code>"createNewsletter"</code>, etc. You can also grant a user <code>"*"</code> permissions 57 * using the wildcard character (giving this class its name), which means they have <em>all</em> permissions. But 58 * using this approach there's no way to just say a user has "all newsletter permissions". 59 * <p/> 60 * For this reason, <code>WildcardPermission</code> supports multiple <em>levels</em> of permissioning. 61 * <p/> 62 * <h3>Multiple Levels</h3> 63 * <p/> 64 * WildcardPermission</code> also supports the concept of multiple <em>levels</em>. For example, you could 65 * restructure the previous simple example by granting a user the permission <code>"newsletter:edit"</code>. 66 * The colon in this example is a special character used by the <code>WildcardPermission</code> that delimits the 67 * next token in the permission. 68 * <p/> 69 * In this example, the first token is the <em>domain</em> that is being operated on 70 * and the second token is the <em>action</em> being performed. Each level can contain multiple values. So you 71 * could simply grant a user the permission <code>"newsletter:view,edit,create"</code> which gives them 72 * access to perform <code>view</code>, <code>edit</code>, and <code>create</code> actions in the <code>newsletter</code> 73 * <em>domain</em>. Then you could check to see if the user has the <code>"newsletter:create"</code> 74 * permission by calling 75 * <p/> 76 * <code>subject.isPermitted("newsletter:create")</code> 77 * <p/> 78 * (which would return true). 79 * <p/> 80 * In addition to granting multiple permissions via a single string, you can grant all permission for a particular 81 * level. So if you wanted to grant a user all actions in the <code>newsletter</code> domain, you could simply give 82 * them <code>"newsletter:*"</code>. Now, any permission check for <code>"newsletter:XXX"</code> 83 * will return <code>true</code>. It is also possible to use the wildcard token at the domain level (or both): so you 84 * could grant a user the <code>"view"</code> action across all domains <code>"*:view"</code>. 85 * <p/> 86 * <h3>Instance-level Access Control</h3> 87 * <p/> 88 * Another common usage of the <code>WildcardPermission</code> is to model instance-level Access Control Lists. 89 * In this scenario you use three tokens - the first is the <em>domain</em>, the second is the <em>action</em>, and 90 * the third is the <em>instance</em> you are acting on. 91 * <p/> 92 * So for example you could grant a user <code>"newsletter:edit:12,13,18"</code>. In this example, assume 93 * that the third token is the system's ID of the newsletter. That would allow the user to edit newsletters 94 * <code>12</code>, <code>13</code>, and <code>18</code>. This is an extremely powerful way to express permissions, 95 * since you can now say things like <code>"newsletter:*:13"</code> (grant a user all actions for newsletter 96 * <code>13</code>), <code>"newsletter:view,create,edit:*"</code> (allow the user to 97 * <code>view</code>, <code>create</code>, or <code>edit</code> <em>any</em> newsletter), or 98 * <code>"newsletter:*:*</code> (allow the user to perform <em>any</em> action on <em>any</em> newsletter). 99 * <p/> 100 * To perform checks against these instance-level permissions, the application should include the instance ID in the 101 * permission check like so: 102 * <p/> 103 * <code>subject.isPermitted( "newsletter:edit:13" )</code> 104 * <p/> 105 * There is no limit to the number of tokens that can be used, so it is up to your imagination in terms of ways that 106 * this could be used in your application. However, the Shiro team likes to standardize some common usages shown 107 * above to help people get started and provide consistency in the Shiro community. 108 * 109 */ 110 //class WildcardPermission : Permission, Serializable { 111 class WildcardPermission : Permission { 112 113 //TODO - JavaDoc methods 114 115 /*-------------------------------------------- 116 | C O N S T A N T S | 117 ============================================*/ 118 protected enum string WILDCARD_TOKEN = "*"; 119 protected enum string PART_DIVIDER_TOKEN = ":"; 120 protected enum string SUBPART_DIVIDER_TOKEN = ","; 121 protected enum bool DEFAULT_CASE_SENSITIVE = false; 122 123 /*-------------------------------------------- 124 | I N S T A N C E V A R I A B L E S | 125 ============================================*/ 126 private List!(Set!(string)) parts; 127 128 /*-------------------------------------------- 129 | C O N S T R U C T O R S | 130 ============================================*/ 131 /** 132 * Default no-arg constructor for subclasses only - end-user developers instantiating Permission instances must 133 * provide a wildcard string at a minimum, since Permission instances are immutable once instantiated. 134 * <p/> 135 * Note that the WildcardPermission class is very robust and typically subclasses are not necessary unless you 136 * wish to create type-safe Permission objects that would be used in your application, such as perhaps a 137 * {@code UserPermission}, {@code SystemPermission}, {@code PrinterPermission}, etc. If you want such type-safe 138 * permission usage, consider subclassing the {@link DomainPermission DomainPermission} class for your needs. 139 */ 140 protected this() { 141 } 142 143 this(string wildcardString) { 144 this(wildcardString, DEFAULT_CASE_SENSITIVE); 145 } 146 147 this(string wildcardString, bool caseSensitive) { 148 setParts(wildcardString, caseSensitive); 149 } 150 151 protected void setParts(string wildcardString) { 152 setParts(wildcardString, DEFAULT_CASE_SENSITIVE); 153 } 154 155 protected void setParts(string wildcardString, bool caseSensitive) { 156 wildcardString = wildcardString.strip(); 157 158 if (wildcardString.empty()) { 159 throw new IllegalArgumentException("Wildcard string cannot be null or empty." ~ 160 " Make sure permission strings are properly formatted."); 161 } 162 163 if (!caseSensitive) { 164 wildcardString = wildcardString.toLower(); 165 } 166 167 string[] parts = wildcardString.split(PART_DIVIDER_TOKEN); 168 169 this.parts = new ArrayList!(Set!(string))(); 170 foreach(string part ; parts) { 171 172 string[] sp = part.split(SUBPART_DIVIDER_TOKEN); 173 if (sp.empty()) { 174 throw new IllegalArgumentException("Wildcard string cannot contain parts with only dividers." ~ 175 " Make sure permission strings are properly formatted."); 176 } 177 Set!(string) subparts = new LinkedHashSet!string(sp); 178 this.parts.add(subparts); 179 } 180 181 if (this.parts.isEmpty()) { 182 throw new IllegalArgumentException("Wildcard string cannot contain only dividers." ~ 183 " Make sure permission strings are properly formatted."); 184 } 185 } 186 187 /*-------------------------------------------- 188 | A C C E S S O R S / M O D I F I E R S | 189 ============================================*/ 190 protected List!(Set!(string)) getParts() { 191 return this.parts; 192 } 193 194 /** 195 * Sets the pre-split string parts of this <code>WildcardPermission</code>. 196 * @param parts pre-split string parts. 197 */ 198 protected void setParts(List!(Set!(string)) parts) { 199 this.parts = parts; 200 } 201 202 /*-------------------------------------------- 203 | M E T H O D S | 204 ============================================*/ 205 206 bool implies(Permission p) { 207 // By default only supports comparisons with other WildcardPermissions 208 auto pCast = cast(WildcardPermission)p; 209 if (pCast is null) { 210 return false; 211 } 212 213 WildcardPermission wp = pCast; 214 215 List!(Set!(string)) otherParts = wp.getParts(); 216 217 int i = 0; 218 foreach (Set!(string) otherPart; otherParts) { 219 // If this permission has less parts than the other permission, everything after the number of parts contained 220 // in this permission is automatically implied, so return true 221 if (getParts().size() - 1 < i) { 222 return true; 223 } else { 224 Set!(string) part = getParts().get(i); 225 if (!part.contains(WILDCARD_TOKEN) && !part.containsAll(otherPart)) { 226 return false; 227 } 228 i++; 229 } 230 } 231 232 // If this permission has more parts than the other parts, only imply it if all of the other parts are wildcards 233 for (; i < getParts().size(); i++) { 234 Set!(string) part = getParts().get(i); 235 if (!part.contains(WILDCARD_TOKEN)) { 236 return false; 237 } 238 } 239 240 return true; 241 } 242 243 override string toString() { 244 StringBuilder buffer = new StringBuilder(); 245 foreach (Set!(string) part; parts) { 246 if (buffer.length() > 0) { 247 buffer.append(PART_DIVIDER_TOKEN); 248 } 249 bool isFirst = true; 250 foreach(string s; part) { 251 if (!isFirst) { 252 buffer.append(SUBPART_DIVIDER_TOKEN); 253 } 254 isFirst = false; 255 buffer.append(s); 256 } 257 } 258 return buffer.toString(); 259 } 260 261 override bool opEquals(Object o) { 262 auto oCast = cast(WildcardPermission)o; 263 if (oCast !is null) { 264 WildcardPermission wp = oCast; 265 return parts == wp.parts; 266 } 267 return false; 268 } 269 270 override size_t toHash() @trusted nothrow { 271 return parts.toHash(); 272 } 273 274 int opCmp(Permission o) { 275 return super.opCmp(cast(WildcardPermission)o); 276 } 277 278 }