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 (&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    
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    }