View Javadoc
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 (&quot;) to avoid parsing errors (e.g.
115      * <code>&quot;printer:5thFloor:print,info&quot;</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 }