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