001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019 package org.apache.shiro.realm.text;
020
021 import org.apache.shiro.authc.SimpleAccount;
022 import org.apache.shiro.authz.Permission;
023 import org.apache.shiro.authz.SimpleRole;
024 import org.apache.shiro.config.ConfigurationException;
025 import org.apache.shiro.realm.SimpleAccountRealm;
026 import org.apache.shiro.util.PermissionUtils;
027 import org.apache.shiro.util.StringUtils;
028
029 import java.text.ParseException;
030 import java.util.*;
031
032
033 /**
034 * A SimpleAccountRealm that enables text-based configuration of the initial User, Role, and Permission objects
035 * created at startup.
036 * <p/>
037 * Each User account definition specifies the username, password, and roles for a user. Each Role definition
038 * specifies a name and an optional collection of assigned Permissions. Users can be assigned Roles, and Roles can be
039 * assigned Permissions. By transitive association, each User 'has' all of their Role's Permissions.
040 * <p/>
041 * User and user-to-role definitions are specified via the {@link #setUserDefinitions} method and
042 * Role-to-permission definitions are specified via the {@link #setRoleDefinitions} method.
043 *
044 * @since 0.9
045 */
046 public class TextConfigurationRealm extends SimpleAccountRealm {
047
048 //TODO - complete JavaDoc
049
050 private String userDefinitions;
051 private String roleDefinitions;
052
053 public TextConfigurationRealm() {
054 super();
055 }
056
057 /**
058 * Will call 'processDefinitions' on startup.
059 *
060 * @since 1.2
061 * @see <a href="https://issues.apache.org/jira/browse/SHIRO-223">SHIRO-223</a>
062 */
063 @Override
064 protected void onInit() {
065 super.onInit();
066 processDefinitions();
067 }
068
069 public String getUserDefinitions() {
070 return userDefinitions;
071 }
072
073 /**
074 * <p>Sets a newline (\n) delimited String that defines user-to-password-and-role(s) key/value pairs according
075 * to the following format:
076 * <p/>
077 * <p><code><em>username</em> = <em>password</em>, role1, role2,...</code></p>
078 * <p/>
079 * <p>Here are some examples of what these lines might look like:</p>
080 * <p/>
081 * <p><code>root = <em>reallyHardToGuessPassword</em>, administrator<br/>
082 * jsmith = <em>jsmithsPassword</em>, manager, engineer, employee<br/>
083 * abrown = <em>abrownsPassword</em>, qa, employee<br/>
084 * djones = <em>djonesPassword</em>, qa, contractor<br/>
085 * guest = <em>guestPassword</em></code></p>
086 *
087 * @param userDefinitions the user definitions to be parsed and converted to Map.Entry elements
088 */
089 public void setUserDefinitions(String userDefinitions) {
090 this.userDefinitions = userDefinitions;
091 }
092
093 public String getRoleDefinitions() {
094 return roleDefinitions;
095 }
096
097 /**
098 * Sets a newline (\n) delimited String that defines role-to-permission definitions.
099 * <p/>
100 * <p>Each line within the string must define a role-to-permission(s) key/value mapping with the
101 * equals character signifies the key/value separation, like so:</p>
102 * <p/>
103 * <p><code><em>rolename</em> = <em>permissionDefinition1</em>, <em>permissionDefinition2</em>, ...</code></p>
104 * <p/>
105 * <p>where <em>permissionDefinition</em> is an arbitrary String, but must people will want to use
106 * Strings that conform to the {@link org.apache.shiro.authz.permission.WildcardPermission WildcardPermission}
107 * format for ease of use and flexibility. Note that if an individual <em>permissionDefnition</em> needs to
108 * be internally comma-delimited (e.g. <code>printer:5thFloor:print,info</code>), you will need to surround that
109 * definition with double quotes (") to avoid parsing errors (e.g.
110 * <code>"printer:5thFloor:print,info"</code>).
111 * <p/>
112 * <p><b>NOTE:</b> if you have roles that don't require permission associations, don't include them in this
113 * definition - just defining the role name in the {@link #setUserDefinitions(String) userDefinitions} is
114 * enough to create the role if it does not yet exist. This property is really only for configuring realms that
115 * have one or more assigned Permission.
116 *
117 * @param roleDefinitions the role definitions to be parsed at initialization
118 */
119 public void setRoleDefinitions(String roleDefinitions) {
120 this.roleDefinitions = roleDefinitions;
121 }
122
123 protected void processDefinitions() {
124 try {
125 processRoleDefinitions();
126 processUserDefinitions();
127 } catch (ParseException e) {
128 String msg = "Unable to parse user and/or role definitions.";
129 throw new ConfigurationException(msg, e);
130 }
131 }
132
133 protected void processRoleDefinitions() throws ParseException {
134 String roleDefinitions = getRoleDefinitions();
135 if (roleDefinitions == null) {
136 return;
137 }
138 Map<String, String> roleDefs = toMap(toLines(roleDefinitions));
139 processRoleDefinitions(roleDefs);
140 }
141
142 protected void processRoleDefinitions(Map<String, String> roleDefs) {
143 if (roleDefs == null || roleDefs.isEmpty()) {
144 return;
145 }
146
147 for (String rolename : roleDefs.keySet()) {
148 String value = roleDefs.get(rolename);
149
150 SimpleRole role = getRole(rolename);
151 if (role == null) {
152 role = new SimpleRole(rolename);
153 add(role);
154 }
155
156 Set<Permission> permissions = PermissionUtils.resolveDelimitedPermissions(value, getPermissionResolver());
157 role.setPermissions(permissions);
158 }
159 }
160
161 protected void processUserDefinitions() throws ParseException {
162
163 String userDefinitions = getUserDefinitions();
164 if (userDefinitions == null) {
165 return;
166 }
167
168 Map<String, String> userDefs = toMap(toLines(userDefinitions));
169
170 processUserDefinitions(userDefs);
171 }
172
173 protected void processUserDefinitions(Map<String, String> userDefs) {
174 if (userDefs == null || userDefs.isEmpty()) {
175 return;
176 }
177
178 for (String username : userDefs.keySet()) {
179
180 String value = userDefs.get(username);
181
182 String[] passwordAndRolesArray = StringUtils.split(value);
183
184 String password = passwordAndRolesArray[0];
185
186 SimpleAccount account = getUser(username);
187 if (account == null) {
188 account = new SimpleAccount(username, password, getName());
189 add(account);
190 }
191 account.setCredentials(password);
192
193 if (passwordAndRolesArray.length > 1) {
194 for (int i = 1; i < passwordAndRolesArray.length; i++) {
195 String rolename = passwordAndRolesArray[i];
196 account.addRole(rolename);
197
198 SimpleRole role = getRole(rolename);
199 if (role != null) {
200 account.addObjectPermissions(role.getPermissions());
201 }
202 }
203 } else {
204 account.setRoles(null);
205 }
206 }
207 }
208
209 protected static Set<String> toLines(String s) {
210 LinkedHashSet<String> set = new LinkedHashSet<String>();
211 Scanner scanner = new Scanner(s);
212 while (scanner.hasNextLine()) {
213 set.add(scanner.nextLine());
214 }
215 return set;
216 }
217
218 protected static Map<String, String> toMap(Collection<String> keyValuePairs) throws ParseException {
219 if (keyValuePairs == null || keyValuePairs.isEmpty()) {
220 return null;
221 }
222
223 Map<String, String> pairs = new HashMap<String, String>();
224 for (String pairString : keyValuePairs) {
225 String[] pair = StringUtils.splitKeyValue(pairString);
226 if (pair != null) {
227 pairs.put(pair[0].trim(), pair[1].trim());
228 }
229 }
230
231 return pairs;
232 }
233 }