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.util.StringUtils;
028
029import java.text.ParseException;
030import 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 */
046public 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}