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.jdbc.JdbcRealm;
20 
21 // import hunt.shiro.authc.AccountException;
22 // import hunt.shiro.Exceptions;
23 // import hunt.shiro.authc.AuthenticationInfo;
24 // import hunt.shiro.authc.AuthenticationToken;
25 // import hunt.shiro.authc.SimpleAuthenticationInfo;
26 // import hunt.shiro.Exceptions;
27 // import hunt.shiro.authc.UsernamePasswordToken;
28 // import hunt.shiro.Exceptions;
29 // import hunt.shiro.authz.AuthorizationInfo;
30 // import hunt.shiro.authz.SimpleAuthorizationInfo;
31 // import hunt.shiro.Exceptions;
32 // import hunt.shiro.realm.AuthorizingRealm;
33 // import hunt.shiro.subject.PrincipalCollection;
34 // import hunt.shiro.util.ByteSource;
35 // import hunt.shiro.util.JdbcUtils;
36 // import hunt.logging.Logger;
37 
38 // import javax.sql.DataSource;
39 // import java.sql.Connection;
40 // import java.sql.PreparedStatement;
41 // import java.sql.ResultSet;
42 // import java.sql.SQLException;
43 // import hunt.collection;
44 // import java.util.LinkedHashSet;
45 // import hunt.collection.Set;
46 
47 
48 // /**
49 //  * Realm that allows authentication and authorization via JDBC calls.  The default queries suggest a potential schema
50 //  * for retrieving the user's password for authentication, and querying for a user's roles and permissions.  The
51 //  * default queries can be overridden by setting the query properties of the realm.
52 //  * <p/>
53 //  * If the default implementation
54 //  * of authentication and authorization cannot handle your schema, this class can be subclassed and the
55 //  * appropriate methods overridden. (usually {@link #doGetAuthenticationInfo(hunt.shiro.authc.AuthenticationToken)},
56 //  * {@link #getRoleNamesForUser(java.sql.Connection,string)}, and/or {@link #getPermissions(java.sql.Connection,string,java.util.Collection)}
57 //  * <p/>
58 //  * This realm supports caching by extending from {@link hunt.shiro.realm.AuthorizingRealm}.
59 //  *
60 //  */
61 // class JdbcRealm : AuthorizingRealm {
62 
63 //     //TODO - complete JavaDoc
64 
65 //     /*--------------------------------------------
66 //     |             C O N S T A N T S             |
67 //     ============================================*/
68 //     /**
69 //      * The default query used to retrieve account data for the user.
70 //      */
71 //     protected enum string DEFAULT_AUTHENTICATION_QUERY = "select password from users where username = ?";
72     
73 //     /**
74 //      * The default query used to retrieve account data for the user when {@link #saltStyle} is COLUMN.
75 //      */
76 //     protected enum string DEFAULT_SALTED_AUTHENTICATION_QUERY = "select password, password_salt from users where username = ?";
77 
78 //     /**
79 //      * The default query used to retrieve the roles that apply to a user.
80 //      */
81 //     protected enum string DEFAULT_USER_ROLES_QUERY = "select role_name from user_roles where username = ?";
82 
83 //     /**
84 //      * The default query used to retrieve permissions that apply to a particular role.
85 //      */
86 //     protected enum string DEFAULT_PERMISSIONS_QUERY = "select permission from roles_permissions where role_name = ?";
87 
88 
89     
90 //     /**
91 //      * Password hash salt configuration. <ul>
92 //      *   <li>NO_SALT - password hashes are not salted.</li>
93 //      *   <li>CRYPT - password hashes are stored in unix crypt format.</li>
94 //      *   <li>COLUMN - salt is in a separate column in the database.</li> 
95 //      *   <li>EXTERNAL - salt is not stored in the database. {@link #getSaltForUser(string)} will be called
96 //      *       to get the salt</li></ul>
97 //      */
98 //      enum SaltStyle {NO_SALT, CRYPT, COLUMN, EXTERNAL};
99 
100 //     /*--------------------------------------------
101 //     |    I N S T A N C E   V A R I A B L E S    |
102 //     ============================================*/
103 //     protected DataSource dataSource;
104 
105 //     protected string authenticationQuery = DEFAULT_AUTHENTICATION_QUERY;
106 
107 //     protected string userRolesQuery = DEFAULT_USER_ROLES_QUERY;
108 
109 //     protected string permissionsQuery = DEFAULT_PERMISSIONS_QUERY;
110 
111 //     protected bool permissionsLookupEnabled = false;
112     
113 //     protected SaltStyle saltStyle = SaltStyle.NO_SALT;
114 
115 //     /*--------------------------------------------
116 //     |         C O N S T R U C T O R S           |
117 //     ============================================*/
118 
119 //     /*--------------------------------------------
120 //     |  A C C E S S O R S / M O D I F I E R S    |
121 //     ============================================*/
122     
123 //     /**
124 //      * Sets the datasource that should be used to retrieve connections used by this realm.
125 //      *
126 //      * @param dataSource the SQL data source.
127 //      */
128 //      void setDataSource(DataSource dataSource) {
129 //         this.dataSource = dataSource;
130 //     }
131 
132 //     /**
133 //      * Overrides the default query used to retrieve a user's password during authentication.  When using the default
134 //      * implementation, this query must take the user's username as a single parameter and return a single result
135 //      * with the user's password as the first column.  If you require a solution that does not match this query
136 //      * structure, you can override {@link #doGetAuthenticationInfo(hunt.shiro.authc.AuthenticationToken)} or
137 //      * just {@link #getPasswordForUser(java.sql.Connection,string)}
138 //      *
139 //      * @param authenticationQuery the query to use for authentication.
140 //      * @see #DEFAULT_AUTHENTICATION_QUERY
141 //      */
142 //      void setAuthenticationQuery(string authenticationQuery) {
143 //         this.authenticationQuery = authenticationQuery;
144 //     }
145 
146 //     /**
147 //      * Overrides the default query used to retrieve a user's roles during authorization.  When using the default
148 //      * implementation, this query must take the user's username as a single parameter and return a row
149 //      * per role with a single column containing the role name.  If you require a solution that does not match this query
150 //      * structure, you can override {@link #doGetAuthorizationInfo(PrincipalCollection)} or just
151 //      * {@link #getRoleNamesForUser(java.sql.Connection,string)}
152 //      *
153 //      * @param userRolesQuery the query to use for retrieving a user's roles.
154 //      * @see #DEFAULT_USER_ROLES_QUERY
155 //      */
156 //      void setUserRolesQuery(string userRolesQuery) {
157 //         this.userRolesQuery = userRolesQuery;
158 //     }
159 
160 //     /**
161 //      * Overrides the default query used to retrieve a user's permissions during authorization.  When using the default
162 //      * implementation, this query must take a role name as the single parameter and return a row
163 //      * per permission with three columns containing the fully qualified name of the permission class, the permission
164 //      * name, and the permission actions (in that order).  If you require a solution that does not match this query
165 //      * structure, you can override {@link #doGetAuthorizationInfo(hunt.shiro.subject.PrincipalCollection)} or just
166 //      * {@link #getPermissions(java.sql.Connection,string,java.util.Collection)}</p>
167 //      * <p/>
168 //      * <b>Permissions are only retrieved if you set {@link #permissionsLookupEnabled} to true.  Otherwise,
169 //      * this query is ignored.</b>
170 //      *
171 //      * @param permissionsQuery the query to use for retrieving permissions for a role.
172 //      * @see #DEFAULT_PERMISSIONS_QUERY
173 //      * @see #setPermissionsLookupEnabled(bool)
174 //      */
175 //      void setPermissionsQuery(string permissionsQuery) {
176 //         this.permissionsQuery = permissionsQuery;
177 //     }
178 
179 //     /**
180 //      * Enables lookup of permissions during authorization.  The default is "false" - meaning that only roles
181 //      * are associated with a user.  Set this to true in order to lookup roles <b>and</b> permissions.
182 //      *
183 //      * @param permissionsLookupEnabled true if permissions should be looked up during authorization, or false if only
184 //      *                                 roles should be looked up.
185 //      */
186 //      void setPermissionsLookupEnabled(bool permissionsLookupEnabled) {
187 //         this.permissionsLookupEnabled = permissionsLookupEnabled;
188 //     }
189     
190 //     /**
191 //      * Sets the salt style.  See {@link #saltStyle}.
192 //      * 
193 //      * @param saltStyle new SaltStyle to set.
194 //      */
195 //      void setSaltStyle(SaltStyle saltStyle) {
196 //         this.saltStyle = saltStyle;
197 //         if (saltStyle == SaltStyle.COLUMN && authenticationQuery== DEFAULT_AUTHENTICATION_QUERY) {
198 //             authenticationQuery = DEFAULT_SALTED_AUTHENTICATION_QUERY;
199 //         }
200 //     }
201 
202 //     /*--------------------------------------------
203 //     |               M E T H O D S               |
204 //     ============================================*/
205 
206 //     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token){
207 
208 //         UsernamePasswordToken upToken = cast(UsernamePasswordToken) token;
209 //         string username = upToken.getUsername();
210 
211 //         // Null username is invalid
212 //         if (username  is null) {
213 //             throw new AccountException("Null usernames are not allowed by this realm.");
214 //         }
215 
216 //         Connection conn = null;
217 //         SimpleAuthenticationInfo info = null;
218 //         try {
219 //             conn = dataSource.getConnection();
220 
221 //             string password = null;
222 //             string salt = null;
223 //             switch (saltStyle) {
224 //             case NO_SALT:
225 //                 password = getPasswordForUser(conn, username)[0];
226 //                 break;
227 //             case CRYPT:
228 //                 // TODO: separate password and hash from getPasswordForUser[0]
229 //                 throw new ConfigurationException("Not implemented yet");
230 //                 //break;
231 //             case COLUMN:
232 //                 string[] queryResults = getPasswordForUser(conn, username);
233 //                 password = queryResults[0];
234 //                 salt = queryResults[1];
235 //                 break;
236 //             case EXTERNAL:
237 //                 password = getPasswordForUser(conn, username)[0];
238 //                 salt = getSaltForUser(username);
239 //             }
240 
241 //             if (password  is null) {
242 //                 throw new UnknownAccountException("No account found for user [" ~ username ~ "]");
243 //             }
244 
245 //             info = new SimpleAuthenticationInfo(username, password.toCharArray(), getName());
246             
247 //             if (salt !is null) {
248 //                 info.setCredentialsSalt(ByteSourceUtil.bytes(salt));
249 //             }
250 
251 //         } catch (SQLException e) {
252 //             final string message = "There was a SQL error while authenticating user [" ~ username ~ "]";
253 //             version(HUNT_DEBUG) {
254 //                 log.error(message, e);
255 //             }
256 
257 //             // Rethrow any SQL errors as an authentication exception
258 //             throw new AuthenticationException(message, e);
259 //         } finally {
260 //             JdbcUtils.closeConnection(conn);
261 //         }
262 
263 //         return info;
264 //     }
265 
266 //     private string[] getPasswordForUser(Connection conn, string username){
267 
268 //         string[] result;
269 //         bool returningSeparatedSalt = false;
270 //         switch (saltStyle) {
271 //         case NO_SALT:
272 //         case CRYPT:
273 //         case EXTERNAL:
274 //             result = new string[1];
275 //             break;
276 //         default:
277 //             result = new string[2];
278 //             returningSeparatedSalt = true;
279 //         }
280         
281 //         PreparedStatement ps = null;
282 //         ResultSet rs = null;
283 //         try {
284 //             ps = conn.prepareStatement(authenticationQuery);
285 //             ps.setString(1, username);
286 
287 //             // Execute query
288 //             rs = ps.executeQuery();
289 
290 //             // Loop over results - although we are only expecting one result, since usernames should be unique
291 //             bool foundResult = false;
292 //             while (rs.next()) {
293 
294 //                 // Check to ensure only one row is processed
295 //                 if (foundResult) {
296 //                     throw new AuthenticationException("More than one user row found for user [" ~ username ~ "]. Usernames must be unique.");
297 //                 }
298 
299 //                 result[0] = rs.getString(1);
300 //                 if (returningSeparatedSalt) {
301 //                     result[1] = rs.getString(2);
302 //                 }
303 
304 //                 foundResult = true;
305 //             }
306 //         } finally {
307 //             JdbcUtils.closeResultSet(rs);
308 //             JdbcUtils.closeStatement(ps);
309 //         }
310 
311 //         return result;
312 //     }
313 
314 //     /**
315 //      * This implementation of the interface expects the principals collection to return a string username keyed off of
316 //      * this realm's {@link #getName() name}
317 //      *
318 //      * @see #getAuthorizationInfo(hunt.shiro.subject.PrincipalCollection)
319 //      */
320 //     override
321 //     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
322 
323 //         //null usernames are invalid
324 //         if (principals  is null) {
325 //             throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
326 //         }
327 
328 //         string username = cast(string) getAvailablePrincipal(principals);
329 
330 //         Connection conn = null;
331 //         Set!(string) roleNames = null;
332 //         Set!(string) permissions = null;
333 //         try {
334 //             conn = dataSource.getConnection();
335 
336 //             // Retrieve roles and permissions from database
337 //             roleNames = getRoleNamesForUser(conn, username);
338 //             if (permissionsLookupEnabled) {
339 //                 permissions = getPermissions(conn, username, roleNames);
340 //             }
341 
342 //         } catch (SQLException e) {
343 //             final string message = "There was a SQL error while authorizing user [" ~ username ~ "]";
344 //             version(HUNT_DEBUG) {
345 //                 log.error(message, e);
346 //             }
347 
348 //             // Rethrow any SQL errors as an authorization exception
349 //             throw new AuthorizationException(message, e);
350 //         } finally {
351 //             JdbcUtils.closeConnection(conn);
352 //         }
353 
354 //         SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roleNames);
355 //         info.setStringPermissions(permissions);
356 //         return info;
357 
358 //     }
359 
360 //     protected Set!(string) getRoleNamesForUser(Connection conn, string username){
361 //         PreparedStatement ps = null;
362 //         ResultSet rs = null;
363 //         Set!(string) roleNames = new LinkedHashSet!(string)();
364 //         try {
365 //             ps = conn.prepareStatement(userRolesQuery);
366 //             ps.setString(1, username);
367 
368 //             // Execute query
369 //             rs = ps.executeQuery();
370 
371 //             // Loop over results and add each returned role to a set
372 //             while (rs.next()) {
373 
374 //                 string roleName = rs.getString(1);
375 
376 //                 // Add the role to the list of names if it isn't null
377 //                 if (roleName !is null) {
378 //                     roleNames.add(roleName);
379 //                 } else {
380 //                     version(HUNT_DEBUG) {
381 //                         warning("Null role name found while retrieving role names for user [" ~ username ~ "]");
382 //                     }
383 //                 }
384 //             }
385 //         } finally {
386 //             JdbcUtils.closeResultSet(rs);
387 //             JdbcUtils.closeStatement(ps);
388 //         }
389 //         return roleNames;
390 //     }
391 
392 //     protected Set!(string) getPermissions(Connection conn, string username, Collection!(string) roleNames){
393 //         PreparedStatement ps = null;
394 //         Set!(string) permissions = new LinkedHashSet!(string)();
395 //         try {
396 //             ps = conn.prepareStatement(permissionsQuery);
397 //             foreach(string roleName ; roleNames) {
398 
399 //                 ps.setString(1, roleName);
400 
401 //                 ResultSet rs = null;
402 
403 //                 try {
404 //                     // Execute query
405 //                     rs = ps.executeQuery();
406 
407 //                     // Loop over results and add each returned role to a set
408 //                     while (rs.next()) {
409 
410 //                         string permissionString = rs.getString(1);
411 
412 //                         // Add the permission to the set of permissions
413 //                         permissions.add(permissionString);
414 //                     }
415 //                 } finally {
416 //                     JdbcUtils.closeResultSet(rs);
417 //                 }
418 
419 //             }
420 //         } finally {
421 //             JdbcUtils.closeStatement(ps);
422 //         }
423 
424 //         return permissions;
425 //     }
426     
427 //     protected string getSaltForUser(string username) {
428 //         return username;
429 //     }
430 
431 // }