001: /* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
002: *
003: * Licensed under the Apache License, Version 2.0 (the "License");
004: * you may not use this file except in compliance with the License.
005: * You may obtain a copy of the License at
006: *
007: * http://www.apache.org/licenses/LICENSE-2.0
008: *
009: * Unless required by applicable law or agreed to in writing, software
010: * distributed under the License is distributed on an "AS IS" BASIS,
011: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: * See the License for the specific language governing permissions and
013: * limitations under the License.
014: */
015:
016: package org.acegisecurity.userdetails.jdbc;
017:
018: import org.acegisecurity.GrantedAuthority;
019: import org.acegisecurity.GrantedAuthorityImpl;
020:
021: import org.acegisecurity.userdetails.User;
022: import org.acegisecurity.userdetails.UserDetails;
023: import org.acegisecurity.userdetails.UserDetailsService;
024: import org.acegisecurity.userdetails.UsernameNotFoundException;
025:
026: import org.springframework.context.ApplicationContextException;
027:
028: import org.springframework.dao.DataAccessException;
029:
030: import org.springframework.jdbc.core.SqlParameter;
031: import org.springframework.jdbc.core.support.JdbcDaoSupport;
032: import org.springframework.jdbc.object.MappingSqlQuery;
033:
034: import java.sql.ResultSet;
035: import java.sql.SQLException;
036: import java.sql.Types;
037:
038: import java.util.List;
039:
040: import javax.sql.DataSource;
041:
042: /**
043: * <p>Retrieves user details (username, password, enabled flag, and authorities) from a JDBC location.</p>
044: * <p>A default database structure is assumed, (see {@link #DEF_USERS_BY_USERNAME_QUERY} and {@link
045: * #DEF_AUTHORITIES_BY_USERNAME_QUERY}, which most users of this class will need to override, if using an existing
046: * scheme. This may be done by setting the default query strings used. If this does not provide enough flexibility,
047: * another strategy would be to subclass this class and override the {@link MappingSqlQuery} instances used, via the
048: * {@link #initMappingSqlQueries()} extension point.</p>
049: * <p>In order to minimise backward compatibility issues, this DAO does not recognise the expiration of user
050: * accounts or the expiration of user credentials. However, it does recognise and honour the user enabled/disabled
051: * column.</p>
052: *
053: * @author Ben Alex
054: * @author colin sampaleanu
055: * @version $Id: JdbcDaoImpl.java 1784 2007-02-24 21:00:24Z luke_t $
056: */
057: public class JdbcDaoImpl extends JdbcDaoSupport implements
058: UserDetailsService {
059: //~ Static fields/initializers =====================================================================================
060:
061: public static final String DEF_USERS_BY_USERNAME_QUERY = "SELECT username,password,enabled FROM users WHERE username = ?";
062: public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY = "SELECT username,authority FROM authorities WHERE username = ?";
063:
064: //~ Instance fields ================================================================================================
065:
066: protected MappingSqlQuery authoritiesByUsernameMapping;
067: protected MappingSqlQuery usersByUsernameMapping;
068: private String authoritiesByUsernameQuery;
069: private String rolePrefix = "";
070: private String usersByUsernameQuery;
071: private boolean usernameBasedPrimaryKey = true;
072:
073: //~ Constructors ===================================================================================================
074:
075: public JdbcDaoImpl() {
076: usersByUsernameQuery = DEF_USERS_BY_USERNAME_QUERY;
077: authoritiesByUsernameQuery = DEF_AUTHORITIES_BY_USERNAME_QUERY;
078: }
079:
080: //~ Methods ========================================================================================================
081:
082: /**
083: * Allows subclasses to add their own granted authorities to the list to be returned in the
084: * <code>User</code>.
085: *
086: * @param username the username, for use by finder methods
087: * @param authorities the current granted authorities, as populated from the <code>authoritiesByUsername</code>
088: * mapping
089: */
090: protected void addCustomAuthorities(String username,
091: List authorities) {
092: }
093:
094: public String getAuthoritiesByUsernameQuery() {
095: return authoritiesByUsernameQuery;
096: }
097:
098: public String getRolePrefix() {
099: return rolePrefix;
100: }
101:
102: public String getUsersByUsernameQuery() {
103: return usersByUsernameQuery;
104: }
105:
106: protected void initDao() throws ApplicationContextException {
107: initMappingSqlQueries();
108: }
109:
110: /**
111: * Extension point to allow other MappingSqlQuery objects to be substituted in a subclass
112: */
113: protected void initMappingSqlQueries() {
114: this .usersByUsernameMapping = new UsersByUsernameMapping(
115: getDataSource());
116: this .authoritiesByUsernameMapping = new AuthoritiesByUsernameMapping(
117: getDataSource());
118: }
119:
120: public boolean isUsernameBasedPrimaryKey() {
121: return usernameBasedPrimaryKey;
122: }
123:
124: public UserDetails loadUserByUsername(String username)
125: throws UsernameNotFoundException, DataAccessException {
126: List users = usersByUsernameMapping.execute(username);
127:
128: if (users.size() == 0) {
129: throw new UsernameNotFoundException("User not found");
130: }
131:
132: UserDetails user = (UserDetails) users.get(0); // contains no GrantedAuthority[]
133:
134: List dbAuths = authoritiesByUsernameMapping.execute(user
135: .getUsername());
136:
137: addCustomAuthorities(user.getUsername(), dbAuths);
138:
139: if (dbAuths.size() == 0) {
140: throw new UsernameNotFoundException(
141: "User has no GrantedAuthority");
142: }
143:
144: GrantedAuthority[] arrayAuths = (GrantedAuthority[]) dbAuths
145: .toArray(new GrantedAuthority[dbAuths.size()]);
146:
147: String returnUsername = user.getUsername();
148:
149: if (!usernameBasedPrimaryKey) {
150: returnUsername = username;
151: }
152:
153: return new User(returnUsername, user.getPassword(), user
154: .isEnabled(), true, true, true, arrayAuths);
155: }
156:
157: /**
158: * Allows the default query string used to retrieve authorities based on username to be overriden, if
159: * default table or column names need to be changed. The default query is {@link
160: * #DEF_AUTHORITIES_BY_USERNAME_QUERY}; when modifying this query, ensure that all returned columns are mapped
161: * back to the same column names as in the default query.
162: *
163: * @param queryString The query string to set
164: */
165: public void setAuthoritiesByUsernameQuery(String queryString) {
166: authoritiesByUsernameQuery = queryString;
167: }
168:
169: /**
170: * Allows a default role prefix to be specified. If this is set to a non-empty value, then it is
171: * automatically prepended to any roles read in from the db. This may for example be used to add the
172: * <code>ROLE_</code> prefix expected to exist in role names (by default) by some other Acegi Security framework
173: * classes, in the case that the prefix is not already present in the db.
174: *
175: * @param rolePrefix the new prefix
176: */
177: public void setRolePrefix(String rolePrefix) {
178: this .rolePrefix = rolePrefix;
179: }
180:
181: /**
182: * If <code>true</code> (the default), indicates the {@link #getUsersByUsernameQuery()} returns a username
183: * in response to a query. If <code>false</code>, indicates that a primary key is used instead. If set to
184: * <code>true</code>, the class will use the database-derived username in the returned <code>UserDetails</code>.
185: * If <code>false</code>, the class will use the {@link #loadUserByUsername(String)} derived username in the
186: * returned <code>UserDetails</code>.
187: *
188: * @param usernameBasedPrimaryKey <code>true</code> if the mapping queries return the username <code>String</code>,
189: * or <code>false</code> if the mapping returns a database primary key.
190: */
191: public void setUsernameBasedPrimaryKey(
192: boolean usernameBasedPrimaryKey) {
193: this .usernameBasedPrimaryKey = usernameBasedPrimaryKey;
194: }
195:
196: /**
197: * Allows the default query string used to retrieve users based on username to be overriden, if default
198: * table or column names need to be changed. The default query is {@link #DEF_USERS_BY_USERNAME_QUERY}; when
199: * modifying this query, ensure that all returned columns are mapped back to the same column names as in the
200: * default query. If the 'enabled' column does not exist in the source db, a permanent true value for this column
201: * may be returned by using a query similar to <br><pre>
202: * "SELECT username,password,'true' as enabled FROM users WHERE username = ?"</pre>
203: *
204: * @param usersByUsernameQueryString The query string to set
205: */
206: public void setUsersByUsernameQuery(
207: String usersByUsernameQueryString) {
208: this .usersByUsernameQuery = usersByUsernameQueryString;
209: }
210:
211: //~ Inner Classes ==================================================================================================
212:
213: /**
214: * Query object to look up a user's authorities.
215: */
216: protected class AuthoritiesByUsernameMapping extends
217: MappingSqlQuery {
218: protected AuthoritiesByUsernameMapping(DataSource ds) {
219: super (ds, authoritiesByUsernameQuery);
220: declareParameter(new SqlParameter(Types.VARCHAR));
221: compile();
222: }
223:
224: protected Object mapRow(ResultSet rs, int rownum)
225: throws SQLException {
226: String roleName = rolePrefix + rs.getString(2);
227: GrantedAuthorityImpl authority = new GrantedAuthorityImpl(
228: roleName);
229:
230: return authority;
231: }
232: }
233:
234: /**
235: * Query object to look up a user.
236: */
237: protected class UsersByUsernameMapping extends MappingSqlQuery {
238: protected UsersByUsernameMapping(DataSource ds) {
239: super (ds, usersByUsernameQuery);
240: declareParameter(new SqlParameter(Types.VARCHAR));
241: compile();
242: }
243:
244: protected Object mapRow(ResultSet rs, int rownum)
245: throws SQLException {
246: String username = rs.getString(1);
247: String password = rs.getString(2);
248: boolean enabled = rs.getBoolean(3);
249: UserDetails user = new User(username, password, enabled,
250: true, true, true,
251: new GrantedAuthority[] { new GrantedAuthorityImpl(
252: "HOLDER") });
253:
254: return user;
255: }
256: }
257: }
|