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 volatile String userDefinitions;
051        private volatile 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 (&quot;) to avoid parsing errors (e.g.
110         * <code>&quot;printer:5thFloor:print,info&quot;</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            for (String rolename : roleDefs.keySet()) {
147                String value = roleDefs.get(rolename);
148    
149                SimpleRole role = getRole(rolename);
150                if (role == null) {
151                    role = new SimpleRole(rolename);
152                    add(role);
153                }
154    
155                Set<Permission> permissions = PermissionUtils.resolveDelimitedPermissions(value, getPermissionResolver());
156                role.setPermissions(permissions);
157            }
158        }
159    
160        protected void processUserDefinitions() throws ParseException {
161            String userDefinitions = getUserDefinitions();
162            if (userDefinitions == null) {
163                return;
164            }
165    
166            Map<String, String> userDefs = toMap(toLines(userDefinitions));
167    
168            processUserDefinitions(userDefs);
169        }
170    
171        protected void processUserDefinitions(Map<String, String> userDefs) {
172            if (userDefs == null || userDefs.isEmpty()) {
173                return;
174            }
175            for (String username : userDefs.keySet()) {
176    
177                String value = userDefs.get(username);
178    
179                String[] passwordAndRolesArray = StringUtils.split(value);
180    
181                String password = passwordAndRolesArray[0];
182    
183                SimpleAccount account = getUser(username);
184                if (account == null) {
185                    account = new SimpleAccount(username, password, getName());
186                    add(account);
187                }
188                account.setCredentials(password);
189    
190                if (passwordAndRolesArray.length > 1) {
191                    for (int i = 1; i < passwordAndRolesArray.length; i++) {
192                        String rolename = passwordAndRolesArray[i];
193                        account.addRole(rolename);
194    
195                        SimpleRole role = getRole(rolename);
196                        if (role != null) {
197                            account.addObjectPermissions(role.getPermissions());
198                        }
199                    }
200                } else {
201                    account.setRoles(null);
202                }
203            }
204        }
205    
206        protected static Set<String> toLines(String s) {
207            LinkedHashSet<String> set = new LinkedHashSet<String>();
208            Scanner scanner = new Scanner(s);
209            while (scanner.hasNextLine()) {
210                set.add(scanner.nextLine());
211            }
212            return set;
213        }
214    
215        protected static Map<String, String> toMap(Collection<String> keyValuePairs) throws ParseException {
216            if (keyValuePairs == null || keyValuePairs.isEmpty()) {
217                return null;
218            }
219    
220            Map<String, String> pairs = new HashMap<String, String>();
221            for (String pairString : keyValuePairs) {
222                String[] pair = StringUtils.splitKeyValue(pairString);
223                if (pair != null) {
224                    pairs.put(pair[0].trim(), pair[1].trim());
225                }
226            }
227    
228            return pairs;
229        }
230    }