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.realm.text.IniRealm;
20 
21 import hunt.shiro.realm.text.TextConfigurationRealm;
22 
23 import hunt.shiro.config.Ini;
24 import hunt.shiro.util.CollectionUtils;
25 // import hunt.shiro.util.StringUtils;
26 import hunt.logging.Logger;
27 import hunt.util.Configuration;
28 
29 import hunt.Exceptions;
30 import std.array;
31 
32 /**
33  * A {@link hunt.shiro.realm.Realm Realm} implementation that creates
34  * {@link hunt.shiro.authc.SimpleAccount SimpleAccount} instances based on
35  * {@link Ini} configuration.
36  * <p/>
37  * This implementation looks for two {@link IniSection sections} in the {@code Ini} configuration:
38  * <pre>
39  * [users]
40  * # One or more {@link hunt.shiro.realm.text.TextConfigurationRealm#setUserDefinitions(string) user definitions}
41  * ...
42  * [roles]
43  * # One or more {@link hunt.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions(string) role definitions}</pre>
44  * <p/>
45  * This class also supports setting the {@link #setResourcePath(string) resourcePath} property to create account
46  * data from an .ini resource.  This will only be used if there isn't already account data in the Realm.
47  *
48  */
49 class IniRealm : TextConfigurationRealm {
50 
51     enum string USERS_SECTION_NAME = "users";
52     enum string ROLES_SECTION_NAME = "roles";
53 
54 
55     private string resourcePath;
56     private Ini ini; //reference added in 1.2 for SHIRO-322
57 
58     this() {
59         super();
60     }
61 
62     /**
63      * This constructor will immediately process the definitions in the {@code Ini} argument.  If you need to perform
64      * additional configuration before processing (e.g. setting a permissionResolver, etc), do not call this
65      * constructor.  Instead, do the following:
66      * <ol>
67      * <li>Call the default no-arg constructor</li>
68      * <li>Set the Ini instance you wish to use via {@code #setIni}</li>
69      * <li>Set any other configuration properties</li>
70      * <li>Call {@link #init()}</li>
71      * </ol>
72      *
73      * @param ini the Ini instance which will be inspected to create accounts, groups and permissions for this realm.
74      */
75      this(Ini ini) {
76         this();
77         processDefinitions(ini);
78     }
79 
80     /**
81      * This constructor will immediately process the definitions in the {@code Ini} resolved from the specified
82      * {@code resourcePath}.  If you need to perform additional configuration before processing (e.g. setting a
83      * permissionResolver, etc), do not call this constructor.  Instead, do the following:
84      * <ol>
85      * <li>Call the default no-arg constructor</li>
86      * <li>Set the Ini instance you wish to use via {@code #setIni}</li>
87      * <li>Set any other configuration properties</li>
88      * <li>Call {@link #init()}</li>
89      * </ol>
90      *
91      * @param resourcePath the resource path of the Ini config which will be inspected to create accounts, groups and
92      *                     permissions for this realm.
93      */
94      this(string resourcePath) {
95         this();
96         Ini ini = Ini.fromResourcePath(resourcePath);
97         this.ini = ini;
98         this.resourcePath = resourcePath;
99         processDefinitions(ini);
100     }
101 
102      string getResourcePath() {
103         return resourcePath;
104     }
105 
106      void setResourcePath(string resourcePath) {
107         this.resourcePath = resourcePath;
108     }
109 
110     /**
111      * Returns the Ini instance used to configure this realm.  Provided for JavaBeans-style configuration of this
112      * realm, particularly useful in Dependency Injection environments.
113      * 
114      * @return the Ini instance which will be inspected to create accounts, groups and permissions for this realm.
115      */
116      Ini getIni() {
117         return ini;
118     }
119 
120     /**
121      * Sets the Ini instance used to configure this realm.  Provided for JavaBeans-style configuration of this
122      * realm, particularly useful in Dependency Injection environments.
123      * 
124      * @param ini the Ini instance which will be inspected to create accounts, groups and permissions for this realm.
125      */
126      void setIni(Ini ini) {
127         this.ini = ini;
128     }
129 
130     override
131     protected void onInit() {
132         super.onInit();
133 
134         // This is an in-memory realm only - no need for an additional cache when we're already
135         // as memory-efficient as we can be.
136         
137         Ini ini = getIni();
138         string resourcePath = getResourcePath();
139                 
140         if (!CollectionUtils.isEmpty(this.users) || !CollectionUtils.isEmpty(this.roles)) {
141             if (ini !is null && !ini.isEmpty()) {
142                 warning("Users or Roles are already populated.  Configured Ini instance will be ignored.");
143             }
144             if (!resourcePath.empty()) {
145                 warning("Users or Roles are already populated.  resourcePath '%s' will be ignored.", resourcePath);
146             }
147             
148             tracef("Instance is already populated with users or roles.  No additional user/role population " ~
149                     "will be performed.");
150             return;
151         }
152         
153         if (ini is null || ini.isEmpty()) {
154             tracef("No INI instance configuration present.  Checking resourcePath...");
155             
156             if (!resourcePath.empty()) {
157                 tracef("Resource path %s defined.  Creating INI instance.", resourcePath);
158                 ini = Ini.fromResourcePath(resourcePath);
159                 if (ini !is null && !ini.isEmpty()) {
160                     setIni(ini);
161                 }
162             }
163         }
164         
165         if (ini is null || ini.isEmpty()) {
166             string msg = "Ini instance and/or resourcePath resulted in null or empty Ini configuration.  Cannot " ~
167                     "load account data.";
168             throw new IllegalStateException(msg);
169         }
170 
171         processDefinitions(ini);
172     }
173 
174     private void processDefinitions(Ini ini) {
175 
176         if (CollectionUtils.isEmpty(ini)) {
177             warning("%s defined, but the ini instance is null or empty.", typeid(this).name);
178             return;
179         }
180 
181         IniSection rolesSection = ini.getSection(ROLES_SECTION_NAME);
182         if (!CollectionUtils.isEmpty(rolesSection)) {
183             tracef("Discovered the [%s] section.  Processing...", ROLES_SECTION_NAME);
184             processRoleDefinitions(rolesSection);
185         }
186 
187         IniSection usersSection = ini.getSection(USERS_SECTION_NAME);
188         if (!CollectionUtils.isEmpty(usersSection)) {
189             tracef("Discovered the [%s] section.  Processing...", USERS_SECTION_NAME);
190             processUserDefinitions(usersSection);
191         } else {
192             info("%s defined, but there is no [%s] section defined.  This realm will not be populated with any " ~
193                     "users and it is assumed that they will be populated programatically.  Users must be defined " ~
194                     "for this Realm instance to be useful.", typeid(this).name, USERS_SECTION_NAME);
195         }
196     }
197 }