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.realm.text.TextConfigurationRealm;
20 
21 
22 import hunt.shiro.authc.SimpleAccount;
23 import hunt.shiro.authz.permission.Permission;
24 import hunt.shiro.authz.SimpleRole;
25 import hunt.shiro.Exceptions;
26 import hunt.shiro.realm.SimpleAccountRealm;
27 import hunt.shiro.util.PermissionUtils;
28 import hunt.text.StringUtils;
29 
30 // import java.text.ParseException;
31 
32 import hunt.Exceptions;
33 import hunt.collection;
34 import hunt.logging;
35 import hunt.String;
36 
37 import std.string;
38 // import java.util.Scanner;
39 
40 
41 /**
42  * A SimpleAccountRealm that enables text-based configuration of the initial User, Role, and Permission objects
43  * created at startup.
44  * <p/>
45  * Each User account definition specifies the username, password, and roles for a user.  Each Role definition
46  * specifies a name and an optional collection of assigned Permissions.  Users can be assigned Roles, and Roles can be
47  * assigned Permissions.  By transitive association, each User 'has' all of their Role's Permissions.
48  * <p/>
49  * User and user-to-role definitions are specified via the {@link #setUserDefinitions} method and
50  * Role-to-permission definitions are specified via the {@link #setRoleDefinitions} method.
51  *
52  */
53 class TextConfigurationRealm : SimpleAccountRealm {
54 
55     //TODO - complete JavaDoc
56 
57     private string userDefinitions;
58     private string roleDefinitions;
59 
60     this() {
61         super();
62     }
63 
64     /**
65      * Will call 'processDefinitions' on startup.
66      *
67      * @see <a href="https://issues.apache.org/jira/browse/SHIRO-223">SHIRO-223</a>
68      */
69     override
70     protected void onInit() {
71         super.onInit();
72         processDefinitions();
73     }
74 
75      string getUserDefinitions() {
76         return userDefinitions;
77     }
78 
79     /**
80      * <p>Sets a newline (\n) delimited string that defines user-to-password-and-role(s) key/value pairs according
81      * to the following format:
82      * <p/>
83      * <p><code><em>username</em> = <em>password</em>, role1, role2,...</code></p>
84      * <p/>
85      * <p>Here are some examples of what these lines might look like:</p>
86      * <p/>
87      * <p><code>root = <em>reallyHardToGuessPassword</em>, administrator<br/>
88      * jsmith = <em>jsmithsPassword</em>, manager, engineer, employee<br/>
89      * abrown = <em>abrownsPassword</em>, qa, employee<br/>
90      * djones = <em>djonesPassword</em>, qa, contractor<br/>
91      * guest = <em>guestPassword</em></code></p>
92      *
93      * @param userDefinitions the user definitions to be parsed and converted to Map.Entry elements
94      */
95      void setUserDefinitions(string userDefinitions) {
96         this.userDefinitions = userDefinitions;
97     }
98 
99      string getRoleDefinitions() {
100         return roleDefinitions;
101     }
102 
103     /**
104      * Sets a newline (\n) delimited string that defines role-to-permission definitions.
105      * <p/>
106      * <p>Each line within the string must define a role-to-permission(s) key/value mapping with the
107      * equals character signifies the key/value separation, like so:</p>
108      * <p/>
109      * <p><code><em>rolename</em> = <em>permissionDefinition1</em>, <em>permissionDefinition2</em>, ...</code></p>
110      * <p/>
111      * <p>where <em>permissionDefinition</em> is an arbitrary string, but must people will want to use
112      * Strings that conform to the {@link hunt.shiro.authz.permission.WildcardPermission WildcardPermission}
113      * format for ease of use and flexibility.  Note that if an individual <em>permissionDefinition</em> needs to
114      * be internally comma-delimited (e.g. <code>printer:5thFloor:print,info</code>), you will need to surround that
115      * definition with double quotes (&quot;) to avoid parsing errors (e.g.
116      * <code>&quot;printer:5thFloor:print,info&quot;</code>).
117      * <p/>
118      * <p><b>NOTE:</b> if you have roles that don't require permission associations, don't include them in this
119      * definition - just defining the role name in the {@link #setUserDefinitions(string) userDefinitions} is
120      * enough to create the role if it does not yet exist.  This property is really only for configuring realms that
121      * have one or more assigned Permission.
122      *
123      * @param roleDefinitions the role definitions to be parsed at initialization
124      */
125      void setRoleDefinitions(string roleDefinitions) {
126         this.roleDefinitions = roleDefinitions;
127     }
128 
129     protected void processDefinitions() {
130         try {
131             processRoleDefinitions();
132             processUserDefinitions();
133         } catch (ParseException e) {
134             string msg = "Unable to parse user and/or role definitions.";
135             throw new ConfigurationException(msg, e);
136         }
137     }
138 
139     protected void processRoleDefinitions(){
140         string roleDefinitions = getRoleDefinitions();
141         if (roleDefinitions  is null) {
142             return;
143         }
144         Map!(string, string) roleDefs = toMap(toLines(roleDefinitions));
145         processRoleDefinitions(roleDefs);
146     }
147 
148     protected void processRoleDefinitions(Map!(string, string) roleDefs) {
149         if (roleDefs  is null || roleDefs.isEmpty()) {
150             return;
151         }
152         foreach(string rolename ; roleDefs.byKey()) {
153             string value = roleDefs.get(rolename);
154 
155             SimpleRole role = getRole(rolename);
156             if (role  is null) {
157                 role = new SimpleRole(rolename);
158                 add(role);
159             }
160 
161             Set!(Permission) permissions = PermissionUtils.resolveDelimitedPermissions(value, getPermissionResolver());
162             role.setPermissions(permissions);
163         }
164     }
165 
166     protected void processUserDefinitions(){
167         string userDefinitions = getUserDefinitions();
168         if (userDefinitions  is null) {
169             return;
170         }
171 
172         Map!(string, string) userDefs = toMap(toLines(userDefinitions));
173 
174         processUserDefinitions(userDefs);
175     }
176 
177     protected void processUserDefinitions(Map!(string, string) userDefs) {
178         if (userDefs  is null || userDefs.isEmpty()) {
179             return;
180         }
181         foreach(string username ; userDefs.byKey()) {
182 
183             string value = userDefs.get(username);
184             // infof("username=%s, value=%s", username, value);
185 
186             string[] passwordAndRolesArray = split(value, ","); 
187             string password = passwordAndRolesArray[0];
188 
189             SimpleAccount account = getUser(username);
190             if (account is null) {
191                 account = new SimpleAccount(new String(username), new String(password), getName());
192                 add(account);
193             }
194             account.setCredentials(new String(password));
195 
196             if (passwordAndRolesArray.length > 1) {
197                 for (int i = 1; i < passwordAndRolesArray.length; i++) {
198                     string rolename = passwordAndRolesArray[i].strip();
199                     account.addRole(rolename);
200 
201                     // tracef("username=%s, rolename=%s", username, rolename);
202 
203                     SimpleRole role = getRole(rolename);
204                     if (role !is null) {
205                         account.addObjectPermissions(role.getPermissions());
206                     }
207                 }
208             } else {
209                 account.setRoles(null);
210             }
211         }
212     }
213 
214     protected static Set!(string) toLines(string s) {
215         LinkedHashSet!(string) set = new LinkedHashSet!(string)();
216         // Scanner scanner = new Scanner(s);
217         // while (scanner.hasNextLine()) {
218         //     set.add(scanner.nextLine());
219         // }
220         implementationMissing(false);
221         return set;
222     }
223 
224     protected static Map!(string, string) toMap(Collection!(string) keyValuePairs){
225         if (keyValuePairs  is null || keyValuePairs.isEmpty()) {
226             return null;
227         }
228 
229         Map!(string, string) pairs = new HashMap!(string, string)();
230         foreach(string pairString ; keyValuePairs) {
231             // string[] pair = StringUtils.splitKeyValue(pairString);
232             // if (pair !is null) {
233             //     pairs.put(pair[0].strip(), pair[1].strip());
234             // }
235         }
236 
237             implementationMissing(false);
238         return pairs;
239     }
240 }