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    
054        public IniRealm() {
055            super();
056        }
057    
058        public IniRealm(Ini ini) {
059            this();
060            processDefinitions(ini);
061        }
062    
063        public IniRealm(String resourcePath) {
064            this();
065            Ini ini = Ini.fromResourcePath(resourcePath);
066            this.resourcePath = resourcePath;
067            processDefinitions(ini);
068        }
069    
070        public String getResourcePath() {
071            return resourcePath;
072        }
073    
074        public void setResourcePath(String resourcePath) {
075            this.resourcePath = resourcePath;
076        }
077    
078        @Override
079        protected void onInit() {
080            // This is an in-memory realm only - no need for an additional cache when we're already
081            // as memory-efficient as we can be.
082            String resourcePath = getResourcePath();
083    
084            if (CollectionUtils.isEmpty(this.users) && CollectionUtils.isEmpty(this.roles)) {
085                //no account data manually populated - try the resource path:
086                if (StringUtils.hasText(resourcePath)) {
087                    log.debug("Resource path {} defined.  Creating INI instance.", resourcePath);
088                    Ini ini = Ini.fromResourcePath(resourcePath);
089                    processDefinitions(ini);
090                } else {
091                    throw new IllegalStateException("No resource path was specified.  Cannot load account data.");
092                }
093            } else {
094                if (StringUtils.hasText(resourcePath)) {
095                    log.warn("Users or Roles are already populated.  Resource path property will be ignored.");
096                }
097            }
098        }
099    
100        private void processDefinitions(Ini ini) {
101            if (CollectionUtils.isEmpty(ini)) {
102                log.warn("{} defined, but the ini instance is null or empty.", getClass().getSimpleName());
103                return;
104            }
105    
106            Ini.Section rolesSection = ini.getSection(ROLES_SECTION_NAME);
107            if (!CollectionUtils.isEmpty(rolesSection)) {
108                log.debug("Discovered the [{}] section.  Processing...", ROLES_SECTION_NAME);
109                processRoleDefinitions(rolesSection);
110            }
111    
112            Ini.Section usersSection = ini.getSection(USERS_SECTION_NAME);
113            if (!CollectionUtils.isEmpty(usersSection)) {
114                log.debug("Discovered the [{}] section.  Processing...", USERS_SECTION_NAME);
115                processUserDefinitions(usersSection);
116            } else {
117                log.info("{} defined, but there is no [{}] section defined.  This realm will not be populated with any " +
118                        "users and it is assumed that they will be populated programatically.  Users must be defined " +
119                        "for this Realm instance to be useful.", getClass().getSimpleName(), USERS_SECTION_NAME);
120            }
121        }
122    }