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.config;
020    
021    import org.apache.shiro.mgt.DefaultSecurityManager;
022    import org.apache.shiro.mgt.RealmSecurityManager;
023    import org.apache.shiro.mgt.SecurityManager;
024    import org.apache.shiro.realm.Realm;
025    import org.apache.shiro.realm.RealmFactory;
026    import org.apache.shiro.realm.text.IniRealm;
027    import org.apache.shiro.util.CollectionUtils;
028    import org.apache.shiro.util.Factory;
029    import org.apache.shiro.util.LifecycleUtils;
030    import org.apache.shiro.util.Nameable;
031    import org.slf4j.Logger;
032    import org.slf4j.LoggerFactory;
033    
034    import java.util.*;
035    
036    /**
037     * A {@link Factory} that creates {@link SecurityManager} instances based on {@link Ini} configuration.
038     *
039     * @author The Apache Shiro Project (shiro-dev@incubator.apache.org)
040     * @since 1.0
041     */
042    public class IniSecurityManagerFactory extends IniFactorySupport<SecurityManager> {
043    
044        public static final String MAIN_SECTION_NAME = "main";
045    
046        public static final String SECURITY_MANAGER_NAME = "securityManager";
047        public static final String INI_REALM_NAME = "iniRealm";
048    
049        private static transient final Logger log = LoggerFactory.getLogger(IniSecurityManagerFactory.class);
050    
051        private ReflectionBuilder builder;
052    
053        /**
054         * Creates a new instance.  See the {@link #getInstance()} JavaDoc for detailed explanation of how an INI
055         * source will be resolved to use to build the instance.
056         */
057        public IniSecurityManagerFactory() {
058        }
059    
060        public IniSecurityManagerFactory(Ini config) {
061            setIni(config);
062        }
063    
064        public IniSecurityManagerFactory(String iniResourcePath) {
065            this(Ini.fromResourcePath(iniResourcePath));
066        }
067    
068        public Map<String, ?> getBeans() {
069            return this.builder != null ? Collections.unmodifiableMap(builder.getObjects()) : null;
070        }
071    
072        private SecurityManager getSecurityManagerBean() {
073            return builder.getBean(SECURITY_MANAGER_NAME, SecurityManager.class);
074        }
075    
076        protected SecurityManager createDefaultInstance() {
077            return new DefaultSecurityManager();
078        }
079    
080        protected SecurityManager createInstance(Ini ini) {
081            if (CollectionUtils.isEmpty(ini)) {
082                throw new NullPointerException("Ini argument cannot be null or empty.");
083            }
084            SecurityManager securityManager = createSecurityManager(ini);
085            if (securityManager == null) {
086                String msg = SecurityManager.class + " instance cannot be null.";
087                throw new ConfigurationException(msg);
088            }
089            return securityManager;
090        }
091    
092        private SecurityManager createSecurityManager(Ini ini) {
093            Ini.Section mainSection = ini.getSection(MAIN_SECTION_NAME);
094            if (CollectionUtils.isEmpty(mainSection)) {
095                //try the default:
096                mainSection = ini.getSection(Ini.DEFAULT_SECTION_NAME);
097            }
098            return createSecurityManager(ini, mainSection);
099        }
100    
101        protected boolean isAutoApplyRealms(SecurityManager securityManager) {
102            boolean autoApply = true;
103            if (securityManager instanceof RealmSecurityManager) {
104                //only apply realms if they haven't been explicitly set by the user:
105                RealmSecurityManager realmSecurityManager = (RealmSecurityManager) securityManager;
106                Collection<Realm> realms = realmSecurityManager.getRealms();
107                if (!CollectionUtils.isEmpty(realms)) {
108                    log.info("Realms have been explicitly set on the SecurityManager instance - auto-setting of " +
109                            "realms will not occur.");
110                    autoApply = false;
111                }
112            }
113            return autoApply;
114        }
115    
116        @SuppressWarnings({"unchecked"})
117        private SecurityManager createSecurityManager(Ini ini, Ini.Section mainSection) {
118    
119            Map<String, ?> defaults = createDefaults(ini, mainSection);
120            Map<String, ?> objects = buildInstances(mainSection, defaults);
121    
122            SecurityManager securityManager = getSecurityManagerBean();
123    
124            boolean autoApplyRealms = isAutoApplyRealms(securityManager);
125    
126            if (autoApplyRealms) {
127                //realms and realm factory might have been created - pull them out first so we can
128                //initialize the securityManager:
129                Collection<Realm> realms = getRealms(objects);
130                //set them on the SecurityManager
131                if (!CollectionUtils.isEmpty(realms)) {
132                    applyRealmsToSecurityManager(realms, securityManager);
133                }
134            }
135    
136            initRealms(securityManager);
137    
138            return securityManager;
139        }
140    
141        private void initRealms(SecurityManager securityManager) {
142            Collection<Realm> realms = getRealms(securityManager);
143            if (!CollectionUtils.isEmpty(realms)) {
144                LifecycleUtils.init(realms);
145            }
146        }
147    
148        private Collection<Realm> getRealms(SecurityManager securityManager) {
149            assertRealmSecurityManager(securityManager);
150            return ((RealmSecurityManager) securityManager).getRealms();
151        }
152    
153        protected Map<String, ?> createDefaults(Ini ini, Ini.Section mainSection) {
154            Map<String, Object> defaults = new LinkedHashMap<String, Object>();
155    
156            SecurityManager securityManager = createDefaultInstance();
157            defaults.put(SECURITY_MANAGER_NAME, securityManager);
158    
159            if (shouldImplicitlyCreateRealm(ini)) {
160                Realm realm = createRealm(ini);
161                if (realm != null) {
162                    defaults.put(INI_REALM_NAME, realm);
163                }
164            }
165    
166            return defaults;
167        }
168    
169        private Map<String, ?> buildInstances(Ini.Section section, Map<String, ?> defaults) {
170            this.builder = new ReflectionBuilder(defaults);
171            return this.builder.buildObjects(section);
172        }
173    
174        private void addToRealms(Collection<Realm> realms, RealmFactory factory) {
175            LifecycleUtils.init(factory);
176            Collection<Realm> factoryRealms = factory.getRealms();
177            if (!CollectionUtils.isEmpty(realms)) {
178                realms.addAll(factoryRealms);
179            }
180        }
181    
182        private Collection<Realm> getRealms(Map<String, ?> instances) {
183    
184            //realms and realm factory might have been created - pull them out first so we can
185            //initialize the securityManager:
186            List<Realm> realms = new ArrayList<Realm>();
187    
188            //iterate over the map entries to pull out the realm factory(s):
189            for (Map.Entry<String, ?> entry : instances.entrySet()) {
190    
191                String name = entry.getKey();
192                Object value = entry.getValue();
193    
194                if (value instanceof RealmFactory) {
195                    addToRealms(realms, (RealmFactory) value);
196                } else if (value instanceof Realm) {
197                    Realm realm = (Realm) value;
198                    //set the name if null:
199                    String existingName = realm.getName();
200                    if (existingName == null || existingName.startsWith(realm.getClass().getName())) {
201                        if (realm instanceof Nameable) {
202                            ((Nameable) realm).setName(name);
203                            log.debug("Applied name '{}' to Nameable realm instance {}", name, realm);
204                        } else {
205                            log.info("Realm does not implement the {} interface.  Configured name will not be applied.",
206                                    Nameable.class.getName());
207                        }
208                    }
209                    realms.add(realm);
210                }
211            }
212    
213            return realms;
214        }
215    
216        private void assertRealmSecurityManager(SecurityManager securityManager) {
217            if (securityManager == null) {
218                throw new NullPointerException("securityManager instance cannot be null");
219            }
220            if (!(securityManager instanceof RealmSecurityManager)) {
221                String msg = "securityManager instance is not a " + RealmSecurityManager.class.getName() +
222                        " instance.  This is required to access or configure realms on the instance.";
223                throw new ConfigurationException(msg);
224            }
225        }
226    
227        protected void applyRealmsToSecurityManager(Collection<Realm> realms, SecurityManager securityManager) {
228            assertRealmSecurityManager(securityManager);
229            ((RealmSecurityManager) securityManager).setRealms(realms);
230        }
231    
232        /**
233         * Returns {@code true} if the Ini contains account data and a {@code Realm} should be implicitly
234         * {@link #createRealm(Ini) created} to reflect the account data, {@code false} if no realm should be implicitly
235         * created.
236         *
237         * @param ini the Ini instance to inspect for account data resulting in an implicitly created realm.
238         * @return {@code true} if the Ini contains account data and a {@code Realm} should be implicitly
239         *         {@link #createRealm(Ini) created} to reflect the account data, {@code false} if no realm should be
240         *         implicitly created.
241         */
242        protected boolean shouldImplicitlyCreateRealm(Ini ini) {
243            return !CollectionUtils.isEmpty(ini) &&
244                    (!CollectionUtils.isEmpty(ini.getSection(IniRealm.ROLES_SECTION_NAME)) ||
245                            !CollectionUtils.isEmpty(ini.getSection(IniRealm.USERS_SECTION_NAME)));
246        }
247    
248        /**
249         * Creates a {@code Realm} from the Ini instance containing account data.
250         *
251         * @param ini the Ini instance from which to acquire the account data.
252         * @return a new Realm instance reflecting the account data discovered in the {@code Ini}.
253         */
254        protected Realm createRealm(Ini ini) {
255            IniRealm realm = new IniRealm(ini);
256            realm.setName(INI_REALM_NAME);
257            return realm;
258        }
259    }