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 */ 019package org.apache.shiro.realm.text; 020 021import org.apache.shiro.authc.SimpleAccount; 022import org.apache.shiro.authz.Permission; 023import org.apache.shiro.authz.SimpleRole; 024import org.apache.shiro.config.ConfigurationException; 025import org.apache.shiro.realm.SimpleAccountRealm; 026import org.apache.shiro.util.PermissionUtils; 027import org.apache.shiro.lang.util.StringUtils; 028 029import java.text.ParseException; 030import java.util.Collection; 031import java.util.HashMap; 032import java.util.LinkedHashSet; 033import java.util.Map; 034import java.util.Scanner; 035import java.util.Set; 036 037 038/** 039 * A SimpleAccountRealm that enables text-based configuration of the initial User, Role, and Permission objects 040 * created at startup. 041 * <p/> 042 * Each User account definition specifies the username, password, and roles for a user. Each Role definition 043 * specifies a name and an optional collection of assigned Permissions. Users can be assigned Roles, and Roles can be 044 * assigned Permissions. By transitive association, each User 'has' all of their Role's Permissions. 045 * <p/> 046 * User and user-to-role definitions are specified via the {@link #setUserDefinitions} method and 047 * Role-to-permission definitions are specified via the {@link #setRoleDefinitions} method. 048 * 049 * @since 0.9 050 */ 051public class TextConfigurationRealm extends SimpleAccountRealm { 052 053 //TODO - complete JavaDoc 054 055 private volatile String userDefinitions; 056 private volatile String roleDefinitions; 057 058 public TextConfigurationRealm() { 059 super(); 060 } 061 062 /** 063 * Will call 'processDefinitions' on startup. 064 * 065 * @see <a href="https://issues.apache.org/jira/browse/SHIRO-223">SHIRO-223</a> 066 * @since 1.2 067 */ 068 @Override 069 protected void onInit() { 070 super.onInit(); 071 processDefinitions(); 072 } 073 074 public String getUserDefinitions() { 075 return userDefinitions; 076 } 077 078 /** 079 * <p>Sets a newline (\n) delimited String that defines user-to-password-and-role(s) key/value pairs according 080 * to the following format: 081 * <p/> 082 * <p><code><em>username</em> = <em>password</em>, role1, role2,...</code></p> 083 * <p/> 084 * <p>Here are some examples of what these lines might look like:</p> 085 * <p/> 086 * <p><code>root = <em>reallyHardToGuessPassword</em>, administrator<br/> 087 * jsmith = <em>jsmithsPassword</em>, manager, engineer, employee<br/> 088 * abrown = <em>abrownsPassword</em>, qa, employee<br/> 089 * djones = <em>djonesPassword</em>, qa, contractor<br/> 090 * guest = <em>guestPassword</em></code></p> 091 * 092 * @param userDefinitions the user definitions to be parsed and converted to Map.Entry elements 093 */ 094 public void setUserDefinitions(String userDefinitions) { 095 this.userDefinitions = userDefinitions; 096 } 097 098 public String getRoleDefinitions() { 099 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}