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