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