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.config.Ini;
022    import org.apache.shiro.util.CollectionUtils;
023    import org.apache.shiro.util.StringUtils;
024    import org.slf4j.Logger;
025    import 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     */
045    public 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    }