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 // }