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 &quot;editNewsletter&quot; permission and then check to see if the user has the editNewsletter
44  * permission by calling
45  * <p/>
46  * <code>subject.isPermitted(&quot;editNewsletter&quot;)</code>
47  * <p/>
48  * This is (mostly) equivalent to
49  * <p/>
50  * <code>subject.isPermitted( new WildcardPermission(&quot;editNewsletter&quot;) )</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>&quot;viewNewsletter&quot;</code>, <code>&quot;deleteNewsletter&quot;</code>,
56  * <code>&quot;createNewsletter&quot;</code>, etc. You can also grant a user <code>&quot;*&quot;</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 &quot;all newsletter permissions&quot;.
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>&quot;newsletter:edit&quot;</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>&quot;newsletter:view,edit,create&quot;</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>&quot;newsletter:create&quot;</code>
74  * permission by calling
75  * <p/>
76  * <code>subject.isPermitted(&quot;newsletter:create&quot;)</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>&quot;newsletter:*&quot;</code>. Now, any permission check for <code>&quot;newsletter:XXX&quot;</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>&quot;view&quot;</code> action across all domains <code>&quot;*:view&quot;</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>&quot;newsletter:edit:12,13,18&quot;</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>&quot;newsletter:*:13&quot;</code> (grant a user all actions for newsletter
96  * <code>13</code>), <code>&quot;newsletter:view,create,edit:*&quot;</code> (allow the user to
97  * <code>view</code>, <code>create</code>, or <code>edit</code> <em>any</em> newsletter), or
98  * <code>&quot;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( &quot;newsletter:edit:13&quot; )</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 }