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.config.Ini;
022import org.apache.shiro.util.CollectionUtils;
023import org.apache.shiro.util.StringUtils;
024import org.slf4j.Logger;
025import org.slf4j.LoggerFactory;
026
027/**
028 * A {@link org.apache.shiro.realm.Realm Realm} implementation that creates
029 * {@link org.apache.shiro.authc.SimpleAccount SimpleAccount} instances based on
030 * {@link Ini} configuration.
031 * <p/>
032 * This implementation looks for two {@link Ini.Section sections} in the {@code Ini} configuration:
033 * <pre>
034 * [users]
035 * # One or more {@link org.apache.shiro.realm.text.TextConfigurationRealm#setUserDefinitions(String) user definitions}
036 * ...
037 * [roles]
038 * # One or more {@link org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions(String) role definitions}</pre>
039 * <p/>
040 * This class also supports setting the {@link #setResourcePath(String) resourcePath} property to create account
041 * data from an .ini resource.  This will only be used if there isn't already account data in the Realm.
042 *
043 * @since 1.0
044 */
045public class IniRealm extends TextConfigurationRealm {
046
047    public static final String USERS_SECTION_NAME = "users";
048    public static final String ROLES_SECTION_NAME = "roles";
049
050    private static transient final Logger log = LoggerFactory.getLogger(IniRealm.class);
051
052    private String resourcePath;
053    private Ini ini; //reference added in 1.2 for SHIRO-322
054
055    public IniRealm() {
056        super();
057    }
058
059    /**
060     * This constructor will immediately process the definitions in the {@code Ini} argument.  If you need to perform
061     * additional configuration before processing (e.g. setting a permissionResolver, etc), do not call this
062     * constructor.  Instead, do the following:
063     * <ol>
064     * <li>Call the default no-arg constructor</li>
065     * <li>Set the Ini instance you wish to use via {@code #setIni}</li>
066     * <li>Set any other configuration properties</li>
067     * <li>Call {@link #init()}</li>
068     * </ol>
069     *
070     * @param ini the Ini instance which will be inspected to create accounts, groups and permissions for this realm.
071     */
072    public IniRealm(Ini ini) {
073        this();
074        processDefinitions(ini);
075    }
076
077    /**
078     * This constructor will immediately process the definitions in the {@code Ini} resolved from the specified
079     * {@code resourcePath}.  If you need to perform additional configuration before processing (e.g. setting a
080     * permissionResolver, etc), do not call this constructor.  Instead, do the following:
081     * <ol>
082     * <li>Call the default no-arg constructor</li>
083     * <li>Set the Ini instance you wish to use via {@code #setIni}</li>
084     * <li>Set any other configuration properties</li>
085     * <li>Call {@link #init()}</li>
086     * </ol>
087     *
088     * @param resourcePath the resource path of the Ini config which will be inspected to create accounts, groups and
089     *                     permissions for this realm.
090     */
091    public IniRealm(String resourcePath) {
092        this();
093        Ini ini = Ini.fromResourcePath(resourcePath);
094        this.ini = ini;
095        this.resourcePath = resourcePath;
096        processDefinitions(ini);
097    }
098
099    public String getResourcePath() {
100        return resourcePath;
101    }
102
103    public void setResourcePath(String resourcePath) {
104        this.resourcePath = resourcePath;
105    }
106
107    /**
108     * Returns the Ini instance used to configure this realm.  Provided for JavaBeans-style configuration of this
109     * realm, particularly useful in Dependency Injection environments.
110     * 
111     * @return the Ini instance which will be inspected to create accounts, groups and permissions for this realm.
112     */
113    public Ini getIni() {
114        return ini;
115    }
116
117    /**
118     * Sets the Ini instance used to configure this realm.  Provided for JavaBeans-style configuration of this
119     * realm, particularly useful in Dependency Injection environments.
120     * 
121     * @param ini the Ini instance which will be inspected to create accounts, groups and permissions for this realm.
122     */
123    public void setIni(Ini ini) {
124        this.ini = ini;
125    }
126
127    @Override
128    protected void onInit() {
129        super.onInit();
130
131        // This is an in-memory realm only - no need for an additional cache when we're already
132        // as memory-efficient as we can be.
133        
134        Ini ini = getIni();
135        String resourcePath = getResourcePath();
136                
137        if (!CollectionUtils.isEmpty(this.users) || !CollectionUtils.isEmpty(this.roles)) {
138            if (!CollectionUtils.isEmpty(ini)) {
139                log.warn("Users or Roles are already populated.  Configured Ini instance will be ignored.");
140            }
141            if (StringUtils.hasText(resourcePath)) {
142                log.warn("Users or Roles are already populated.  resourcePath '{}' will be ignored.", resourcePath);
143            }
144            
145            log.debug("Instance is already populated with users or roles.  No additional user/role population " +
146                    "will be performed.");
147            return;
148        }
149        
150        if (CollectionUtils.isEmpty(ini)) {
151            log.debug("No INI instance configuration present.  Checking resourcePath...");
152            
153            if (StringUtils.hasText(resourcePath)) {
154                log.debug("Resource path {} defined.  Creating INI instance.", resourcePath);
155                ini = Ini.fromResourcePath(resourcePath);
156                if (!CollectionUtils.isEmpty(ini)) {
157                    setIni(ini);
158                }
159            }
160        }
161        
162        if (CollectionUtils.isEmpty(ini)) {
163            String msg = "Ini instance and/or resourcePath resulted in null or empty Ini configuration.  Cannot " +
164                    "load account data.";
165            throw new IllegalStateException(msg);
166        }
167
168        processDefinitions(ini);
169    }
170
171    private void processDefinitions(Ini ini) {
172        if (CollectionUtils.isEmpty(ini)) {
173            log.warn("{} defined, but the ini instance is null or empty.", getClass().getSimpleName());
174            return;
175        }
176
177        Ini.Section rolesSection = ini.getSection(ROLES_SECTION_NAME);
178        if (!CollectionUtils.isEmpty(rolesSection)) {
179            log.debug("Discovered the [{}] section.  Processing...", ROLES_SECTION_NAME);
180            processRoleDefinitions(rolesSection);
181        }
182
183        Ini.Section usersSection = ini.getSection(USERS_SECTION_NAME);
184        if (!CollectionUtils.isEmpty(usersSection)) {
185            log.debug("Discovered the [{}] section.  Processing...", USERS_SECTION_NAME);
186            processUserDefinitions(usersSection);
187        } else {
188            log.info("{} defined, but there is no [{}] section defined.  This realm will not be populated with any " +
189                    "users and it is assumed that they will be populated programatically.  Users must be defined " +
190                    "for this Realm instance to be useful.", getClass().getSimpleName(), USERS_SECTION_NAME);
191        }
192    }
193}