1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied.  See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  */
19 module hunt.shiro.config.IniSecurityManagerFactory;
20 
21 import hunt.shiro.config.Ini;
22 import hunt.shiro.config.IniFactorySupport;
23 import hunt.shiro.config.ReflectionBuilder;
24 
25 import hunt.shiro.mgt.DefaultSecurityManager;
26 import hunt.shiro.mgt.RealmSecurityManager;
27 import hunt.shiro.mgt.SecurityManager;
28 import hunt.shiro.realm.Realm;
29 import hunt.shiro.realm.RealmFactory;
30 import hunt.shiro.realm.text.IniRealm;
31 import hunt.shiro.util.CollectionUtils;
32 import hunt.shiro.util.Common;
33 import hunt.shiro.util.LifecycleUtils;
34 import hunt.logging.Logger;
35 
36 import hunt.collection;
37 import hunt.Exceptions;
38 import hunt.util.Configuration;
39 
40 import std.string;
41 import std.traits;
42 
43 
44 /**
45  * A {@link Factory} that creates {@link SecurityManager} instances based on {@link Ini} configuration.
46  *
47  * @since 1.0
48  * deprecated("") use Shiro's {@code Environment} mechanisms instead.
49  */
50 // deprecated("")
51 class IniSecurityManagerFactory : IniFactorySupport!(SecurityManager) {
52 
53      enum string MAIN_SECTION_NAME = "main";
54 
55      enum string SECURITY_MANAGER_NAME = "securityManager";
56      enum string INI_REALM_NAME = "iniRealm";
57 
58 
59 
60     private ReflectionBuilder builder;
61 
62     /**
63      * Creates a new instance.  See the {@link #getInstance()} JavaDoc for detailed explanation of how an INI
64      * source will be resolved to use to build the instance.
65      */
66      this() {
67         this.builder = new ReflectionBuilder();
68     }
69 
70      this(Ini config) {
71         this();
72         setIni(config);
73     }
74 
75      this(string iniResourcePath) {
76         this(Ini.fromResourcePath(iniResourcePath));
77     }
78 
79     //  Map!(string, T) getBeans() {
80     //     return this.builder !is null ? Collections.unmodifiableMap(builder.getObjects()) : null;
81     // }
82 
83      void destroy() {
84         if(getReflectionBuilder() !is null) {
85             getReflectionBuilder().destroy();
86         }
87     }
88 
89     private SecurityManager getSecurityManagerBean() {
90         return getReflectionBuilder().getBean!SecurityManager(SECURITY_MANAGER_NAME);
91     }
92 
93     override protected SecurityManager createDefaultInstance() {
94         return new DefaultSecurityManager();
95     }
96 
97     override protected SecurityManager createInstance(Ini ini) {
98         if (ini is null || ini.isEmpty()) {
99             throw new NullPointerException("Ini argument cannot be null or empty.");
100         }
101         SecurityManager securityManager = createSecurityManager(ini);
102         if (securityManager  is null) {
103             string msg = "SecurityManager instance cannot be null.";
104             throw new ConfigurationException(msg);
105         }
106         return securityManager;
107     }
108 
109     private SecurityManager createSecurityManager(Ini ini) {
110         return createSecurityManager(ini, getConfigSection(ini));
111     }
112 
113     private IniSection getConfigSection(Ini ini) {
114 
115         IniSection mainSection = ini.getSection(MAIN_SECTION_NAME);
116         if (CollectionUtils.isEmpty(mainSection)) {
117             //try the default:
118             mainSection = ini.getSection(Ini.DEFAULT_SECTION_NAME);
119         }
120         return mainSection;
121     }
122 
123     protected bool isAutoApplyRealms(SecurityManager securityManager) {
124         bool autoApply = true;
125         auto securityManagerCast = cast(RealmSecurityManager)securityManager;
126         if (securityManagerCast !is null) {
127             //only apply realms if they haven't been explicitly set by the user:
128             RealmSecurityManager realmSecurityManager = securityManagerCast;
129             Realm[] realms = realmSecurityManager.getRealms();
130             if (!realms.empty()) {
131                 info("Realms have been explicitly set on the SecurityManager instance - auto-setting of " ~
132                         "realms will not occur.");
133                 autoApply = false;
134             }
135         }
136         return autoApply;
137     }
138 
139     //@SuppressWarnings({"unchecked"})
140     private SecurityManager createSecurityManager(Ini ini, IniSection mainSection) {
141 
142         getReflectionBuilder().setObjects(createDefaults(ini, mainSection));
143         Map!(string, Object) objects = buildInstances(mainSection);
144 
145         SecurityManager securityManager = getSecurityManagerBean();
146         bool autoApplyRealms = isAutoApplyRealms(securityManager);
147 
148 
149         if (autoApplyRealms) {
150             //realms and realm factory might have been created - pull them out first so we can
151             //initialize the securityManager:
152             Realm[] realms = getRealms(objects);
153             version(HUNT_DEBUG) infof("realms=%d, objects=%d", realms.length, objects.size());
154             //set them on the SecurityManager
155             if (!realms.empty()) {
156                 applyRealmsToSecurityManager(realms, securityManager);
157             }
158         }
159 
160         return securityManager;
161     }
162 
163     protected Map!(string, Object) createDefaults(Ini ini, IniSection mainSection) {
164         Map!(string, Object) defaults = new LinkedHashMap!(string, Object)();
165 
166         SecurityManager securityManager = createDefaultInstance();
167         defaults.put(SECURITY_MANAGER_NAME, cast(Object)securityManager);
168 
169         if (shouldImplicitlyCreateRealm(ini)) {
170             Realm realm = createRealm(ini);
171             if (realm !is null) {
172                 defaults.put(INI_REALM_NAME, cast(Object)realm);
173             }
174         }
175 
176         // The values from 'getDefaults()' will override the above.
177         Map!(string, SecurityManager) defaultBeans = getDefaults();
178         if (!CollectionUtils.isEmpty(defaultBeans)) {
179             foreach(string key, SecurityManager value; defaultBeans)
180             defaults.put(key, cast(Object)value);
181         }
182 
183         return defaults;
184     }
185 
186     private Map!(string, Object) buildInstances(IniSection section) {
187         return getReflectionBuilder().buildObjects(section);
188     }
189 
190     private void addToRealms(ref Realm[] realms, RealmFactory factory) {
191         LifecycleUtils.init(cast(Object)factory);
192         Realm[] factoryRealms = factory.getRealms();
193         //SHIRO-238: check factoryRealms (was 'realms'):
194         if (!factoryRealms.empty()) {
195             realms ~= factoryRealms;
196         }
197     }
198 
199     private Realm[] getRealms(Map!(string, Object) instances) {
200 
201         //realms and realm factory might have been created - pull them out first so we can
202         //initialize the securityManager:
203         // List!(Realm) realms = new ArrayList!(Realm)();
204         Realm[] realms;
205 
206         //iterate over the map entries to pull out the realm factory(s):
207         foreach (string name, Object value; instances) {
208             RealmFactory bf = cast(RealmFactory) value;
209             Realm realm = cast(Realm) value;
210             if (bf !is null) {
211                 addToRealms(realms, bf);
212             } else if (realm !is null) {
213                 //set the name if null:
214                 string existingName = realm.getName();
215                 if (existingName  is null || existingName.startsWith(typeid(realm).name)) {
216                     Nameable nameable = cast(Nameable) realm;
217                     if (nameable !is null) {
218                         nameable.setName(name);
219                         tracef("Applied name '%s' to Nameable realm instance %s", name, realm);
220                     } else {
221                         info("Realm does not implement the %s interface.  Configured name will not be applied.",
222                                 fullyQualifiedName!(Nameable));
223                     }
224                 }
225                 realms ~=  realm;
226             }
227         }
228 
229         return realms;
230     }
231 
232     private void assertRealmSecurityManager(SecurityManager securityManager) {
233         if (securityManager  is null) {
234             throw new NullPointerException("securityManager instance cannot be null");
235         }
236         RealmSecurityManager rsm = cast(RealmSecurityManager)securityManager;
237         if (securityManager is null) {
238             string msg = "securityManager instance is not a " ~ typeid(RealmSecurityManager).name ~
239                     " instance.  This is required to access or configure realms on the instance.";
240             throw new ConfigurationException(msg);
241         }
242     }
243 
244     protected void applyRealmsToSecurityManager(Realm[] realms, SecurityManager securityManager) {
245         assertRealmSecurityManager(securityManager);
246         (cast(RealmSecurityManager) securityManager).setRealms(realms);
247     }
248 
249     /**
250      * Returns {@code true} if the Ini contains account data and a {@code Realm} should be implicitly
251      * {@link #createRealm(Ini) created} to reflect the account data, {@code false} if no realm should be implicitly
252      * created.
253      *
254      * @param ini the Ini instance to inspect for account data resulting in an implicitly created realm.
255      * @return {@code true} if the Ini contains account data and a {@code Realm} should be implicitly
256      *         {@link #createRealm(Ini) created} to reflect the account data, {@code false} if no realm should be
257      *         implicitly created.
258      */
259     protected bool shouldImplicitlyCreateRealm(Ini ini) {
260         return !CollectionUtils.isEmpty(ini) &&
261                 (!CollectionUtils.isEmpty(ini.getSection(IniRealm.ROLES_SECTION_NAME)) ||
262                         !CollectionUtils.isEmpty(ini.getSection(IniRealm.USERS_SECTION_NAME)));
263     }
264 
265     /**
266      * Creates a {@code Realm} from the Ini instance containing account data.
267      *
268      * @param ini the Ini instance from which to acquire the account data.
269      * @return a new Realm instance reflecting the account data discovered in the {@code Ini}.
270      */
271     protected Realm createRealm(Ini ini) {
272         //IniRealm realm = new IniRealm(ini); changed to support SHIRO-322
273         IniRealm realm = new IniRealm();
274         realm.setName(INI_REALM_NAME);
275         realm.setIni(ini); //added for SHIRO-322
276         return realm;
277     }
278 
279     /**
280      * Returns the ReflectionBuilder instance used to create SecurityManagers object graph.
281      * @return ReflectionBuilder instance used to create SecurityManagers object graph.
282      * @since 1.4
283      */
284     ReflectionBuilder getReflectionBuilder() {
285         return builder;
286     }
287 
288     /**
289      * Sets the ReflectionBuilder that will be used to create the SecurityManager based on the contents of
290      * the Ini configuration.
291      * @param builder The ReflectionBuilder used to parse the Ini configuration.
292      * @since 1.4
293      */
294 
295     void setReflectionBuilder(ReflectionBuilder builder) {
296         this.builder = builder;
297     }
298 }