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.config;
020
021import org.apache.shiro.mgt.DefaultSecurityManager;
022import org.apache.shiro.mgt.RealmSecurityManager;
023import org.apache.shiro.mgt.SecurityManager;
024import org.apache.shiro.realm.Realm;
025import org.apache.shiro.realm.RealmFactory;
026import org.apache.shiro.realm.text.IniRealm;
027import org.apache.shiro.util.CollectionUtils;
028import org.apache.shiro.util.Factory;
029import org.apache.shiro.util.LifecycleUtils;
030import org.apache.shiro.util.Nameable;
031import org.slf4j.Logger;
032import org.slf4j.LoggerFactory;
033
034import java.util.ArrayList;
035import java.util.Collection;
036import java.util.Collections;
037import java.util.LinkedHashMap;
038import java.util.List;
039import 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 */
046public 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}