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 definitinos are specified via the {@link #setUserDefinitions} method and
042     * Role-to-permission definitions are specified via the {@link #setRoleDefinitions} method.
043     *
044     * @author Les Hazlewood
045     * @since 0.9
046     */
047    public class TextConfigurationRealm extends SimpleAccountRealm {
048    
049        //TODO - complete JavaDoc
050    
051        private String userDefinitions;
052        private String roleDefinitions;
053    
054        public TextConfigurationRealm() {
055            super();
056        }
057    
058        public String getUserDefinitions() {
059            return userDefinitions;
060        }
061    
062        /**
063         * <p>Sets a newline (\n) delimited String that defines user-to-password-and-role(s) key/value pairs according
064         * to the following format:
065         * <p/>
066         * <p><code><em>username</em> = <em>password</em>, role1, role2,...</code></p>
067         * <p/>
068         * <p>Here are some examples of what these lines might look like:</p>
069         * <p/>
070         * <p><code>root = <em>reallyHardToGuessPassword</em>, administrator<br/>
071         * jsmith = <em>jsmithsPassword</em>, manager, engineer, employee<br/>
072         * abrown = <em>abrownsPassword</em>, qa, employee<br/>
073         * djones = <em>djonesPassword</em>, qa, contractor<br/>
074         * guest = <em>guestPassword</em></code></p>
075         *
076         * @param userDefinitions the user definitions to be parsed and converted to Map.Entry elements
077         */
078        public void setUserDefinitions(String userDefinitions) {
079            this.userDefinitions = userDefinitions;
080        }
081    
082        public String getRoleDefinitions() {
083            return roleDefinitions;
084        }
085    
086        /**
087         * Sets a newline (\n) delimited String that defines role-to-permission definitions.
088         * <p/>
089         * <p>Each line within the string must define a role-to-permission(s) key/value mapping with the
090         * equals character signifies the key/value separation, like so:</p>
091         * <p/>
092         * <p><code><em>rolename</em> = <em>permissionDefinition1</em>, <em>permissionDefinition2</em>, ...</code></p>
093         * <p/>
094         * <p>where <em>permissionDefinition</em> is an arbitrary String, but must people will want to use
095         * Strings that conform to the {@link org.apache.shiro.authz.permission.WildcardPermission WildcardPermission}
096         * format for ease of use and flexibility.  Note that if an individual <em>permissionDefnition</em> needs to
097         * be internally comma-delimited (e.g. <code>printer:5thFloor:print,info</code>), you will need to surround that
098         * definition with double quotes (&quot;) to avoid parsing errors (e.g.
099         * <code>&quot;printer:5thFloor:print,info&quot;</code>).
100         * <p/>
101         * <p><b>NOTE:</b> if you have roles that don't require permission associations, don't include them in this
102         * definition - just defining the role name in the {@link #setUserDefinitions(String) userDefinitions} is
103         * enough to create the role if it does not yet exist.  This property is really only for configuring realms that
104         * have one or more assigned Permission.
105         *
106         * @param roleDefinitions the role definitions to be parsed at initialization
107         */
108        public void setRoleDefinitions(String roleDefinitions) {
109            this.roleDefinitions = roleDefinitions;
110        }
111    
112        protected void processDefinitions() {
113            try {
114                processRoleDefinitions();
115                processUserDefinitions();
116            } catch (ParseException e) {
117                String msg = "Unable to parse user and/or role definitions.";
118                throw new ConfigurationException(msg, e);
119            }
120        }
121    
122        protected void processRoleDefinitions() throws ParseException {
123            String roleDefinitions = getRoleDefinitions();
124            if (roleDefinitions == null) {
125                return;
126            }
127            Map<String, String> roleDefs = toMap(toLines(roleDefinitions));
128            processRoleDefinitions(roleDefs);
129        }
130    
131        protected void processRoleDefinitions(Map<String, String> roleDefs) {
132            if (roleDefs == null || roleDefs.isEmpty()) {
133                return;
134            }
135    
136            for (String rolename : roleDefs.keySet()) {
137                String value = roleDefs.get(rolename);
138    
139                SimpleRole role = getRole(rolename);
140                if (role == null) {
141                    role = new SimpleRole(rolename);
142                    add(role);
143                }
144    
145                Set<Permission> permissions = PermissionUtils.resolveDelimitedPermissions(value, getPermissionResolver());
146                role.setPermissions(permissions);
147            }
148        }
149    
150        protected void processUserDefinitions() throws ParseException {
151    
152            String userDefinitions = getUserDefinitions();
153            if (userDefinitions == null) {
154                return;
155            }
156    
157            Map<String, String> userDefs = toMap(toLines(userDefinitions));
158    
159            processUserDefinitions(userDefs);
160        }
161    
162        protected void processUserDefinitions(Map<String, String> userDefs) {
163            if (userDefs == null || userDefs.isEmpty()) {
164                return;
165            }
166    
167            for (String username : userDefs.keySet()) {
168    
169                String value = userDefs.get(username);
170    
171                String[] passwordAndRolesArray = StringUtils.split(value);
172    
173                String password = passwordAndRolesArray[0];
174    
175                SimpleAccount account = getUser(username);
176                if (account == null) {
177                    account = new SimpleAccount(username, password, getName());
178                    add(account);
179                }
180                account.setCredentials(password);
181    
182                if (passwordAndRolesArray.length > 1) {
183                    for (int i = 1; i < passwordAndRolesArray.length; i++) {
184                        String rolename = passwordAndRolesArray[i];
185                        account.addRole(rolename);
186    
187                        SimpleRole role = getRole(rolename);
188                        if (role != null) {
189                            account.addObjectPermissions(role.getPermissions());
190                        }
191                    }
192                } else {
193                    account.setRoles(null);
194                }
195            }
196        }
197    
198        protected static Set<String> toLines(String s) {
199            LinkedHashSet<String> set = new LinkedHashSet<String>();
200            Scanner scanner = new Scanner(s);
201            while (scanner.hasNextLine()) {
202                set.add(scanner.nextLine());
203            }
204            return set;
205        }
206    
207        protected static Map<String, String> toMap(Collection<String> keyValuePairs) throws ParseException {
208            if (keyValuePairs == null || keyValuePairs.isEmpty()) {
209                return null;
210            }
211    
212            Map<String, String> pairs = new HashMap<String, String>();
213            for (String pairString : keyValuePairs) {
214                String[] pair = StringUtils.splitKeyValue(pairString);
215                if (pair != null) {
216                    pairs.put(pair[0].trim(), pair[1].trim());
217                }
218            }
219    
220            return pairs;
221        }
222    }