001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.acting;
018:
019: import org.apache.avalon.excalibur.datasource.DataSourceComponent;
020: import org.apache.avalon.framework.configuration.Configuration;
021: import org.apache.avalon.framework.parameters.Parameters;
022: import org.apache.avalon.framework.thread.ThreadSafe;
023: import org.apache.cocoon.Constants;
024: import org.apache.cocoon.environment.ObjectModelHelper;
025: import org.apache.cocoon.environment.Redirector;
026: import org.apache.cocoon.environment.Request;
027: import org.apache.cocoon.environment.Session;
028: import org.apache.cocoon.environment.SourceResolver;
029: import org.apache.commons.lang.BooleanUtils;
030: import org.apache.commons.lang.StringUtils;
031:
032: import java.sql.Connection;
033: import java.sql.ResultSet;
034: import java.sql.PreparedStatement;
035: import java.util.Collections;
036: import java.util.HashMap;
037: import java.util.Map;
038:
039: /**
040: * This action is used to authenticate user by comparing several request
041: * fields (username, password) with the values in database. The description of
042: * the process is given via external xml description file simiar to the one
043: * used for all actions derived from AbstractDatabaseAction.
044: * <pre>
045: * <root>
046: * <connection>personnel</connection>
047: * <table name="users_table>
048: * <select dbcol="username" request-param="username"
049: * to-session="username"/>
050: * <select dbcol="password" request-param="password"
051: * nullable="yes"/>
052: * <select dbcol="role" to-session="role" type="string"/>
053: * <select dbcol="skin" to-session="skin" type="string"/>
054: * </table>
055: * </root>
056: * </pre>
057: * The values specified via "request-param" describe the name of HTTP request
058: * parameter, "dbcol" indicates matching database column, "nullable" means
059: * that request-param which is null or empty will not be included in the WHERE
060: * clause. This way you can enable accounts with empty passwords, etc.
061: * "to-session" attribute indicates under which name the value obtained from
062: * database should be stored in the session. Of course new session is created
063: * when authorization is successfull. The "type" attribute can be either
064: * string, long or double and alters the type of object stored in session.
065: * Additionally all parameters that are
066: * propagated to the session are made available to the sitemap via {name}
067: * expression.
068: *
069: * If there is no need to touch the session object, providing just one-time
070: * verification, you can specify action parameter "create-session" to "no" or
071: * "false". No values are then propagated to the sesion and session object is
072: * not verified.
073: *
074: * @author <a href="mailto:Martin.Man@seznam.cz">Martin Man</a>
075: * @version CVS $Id: DatabaseAuthenticatorAction.java 433543 2006-08-22 06:22:54Z crossley $
076: */
077: public class DatabaseAuthenticatorAction extends AbstractDatabaseAction
078: implements ThreadSafe {
079: /**
080: * Main invocation routine.
081: */
082: public Map act(Redirector redirector, SourceResolver resolver,
083: Map objectModel, String src, Parameters parameters)
084: throws Exception {
085: DataSourceComponent datasource = null;
086: Connection conn = null;
087: PreparedStatement st = null;
088: ResultSet rs = null;
089:
090: // read global parameter settings
091: boolean reloadable = Constants.DESCRIPTOR_RELOADABLE_DEFAULT;
092:
093: if (this .settings.containsKey("reloadable")) {
094: reloadable = Boolean.valueOf(
095: (String) this .settings.get("reloadable"))
096: .booleanValue();
097: }
098:
099: // read local settings
100: try {
101: Configuration conf = this .getConfiguration(parameters
102: .getParameter("descriptor", (String) this .settings
103: .get("descriptor")), resolver, parameters
104: .getParameterAsBoolean("reloadable", reloadable));
105: boolean cs = true;
106: String create_session = parameters.getParameter(
107: "create-session", (String) this .settings
108: .get("create-session"));
109: if (create_session != null) {
110: cs = BooleanUtils.toBoolean(create_session.trim());
111: }
112:
113: datasource = this .getDataSource(conf);
114: conn = datasource.getConnection();
115: Request req = ObjectModelHelper.getRequest(objectModel);
116:
117: /* check request validity */
118: if (req == null) {
119: getLogger().debug("DBAUTH: no request object");
120: return null;
121: }
122:
123: st = this .getAuthQuery(conn, conf, req);
124: if (st == null) {
125: getLogger().debug("DBAUTH: have not got query");
126: req.setAttribute("message",
127: "The authenticator is misconfigured");
128: return null;
129: }
130:
131: rs = st.executeQuery();
132:
133: if (rs.next()) {
134: getLogger().debug("DBAUTH: authorized successfully");
135: Session session = null;
136:
137: if (cs) {
138: session = req.getSession(false);
139: if (session != null)
140: session.invalidate();
141: session = req.getSession(true);
142: if (session == null)
143: return null;
144: getLogger().debug("DBAUTH: session created");
145: } else {
146: getLogger().debug(
147: "DBAUTH: leaving session untouched");
148: }
149:
150: HashMap actionMap = this .propagateParameters(conf, rs,
151: session);
152: if (!conn.getAutoCommit()) {
153: conn.commit();
154: }
155: return Collections.unmodifiableMap(actionMap);
156: }
157: if (!conn.getAutoCommit()) {
158: conn.rollback();
159: }
160:
161: req
162: .setAttribute(
163: "message",
164: "The username or password were incorrect, please check your CAPS LOCK key and try again.");
165: getLogger().debug("DBAUTH: no results for query");
166: } catch (Exception e) {
167: if (conn != null) {
168: try {
169: if (!conn.getAutoCommit()) {
170: conn.rollback();
171: }
172: } catch (Exception se) {/* ignore */
173: }
174: }
175: getLogger().debug("exception: ", e);
176: return null;
177: } finally {
178: if (rs != null)
179: rs.close();
180: if (st != null)
181: st.close();
182: if (conn != null) {
183: try {
184: conn.close();
185: } catch (Exception e) {/* ignore */
186: }
187: }
188: }
189: return null;
190: }
191:
192: private PreparedStatement getAuthQuery(Connection conn,
193: Configuration conf, Request req) {
194: StringBuffer queryBuffer = new StringBuffer("SELECT ");
195: StringBuffer queryBufferEnd = new StringBuffer("");
196: Configuration table = conf.getChild("table");
197: Configuration[] columns = table.getChildren("select");
198: try {
199: Object[] constraintValues = new Object[columns.length];
200: int constraints = 0;
201: for (int i = 0; i < columns.length; i++) {
202: String dbcol = columns[i].getAttribute("dbcol");
203: boolean nullable = false;
204: if (i > 0) {
205: queryBuffer.append(", ");
206: }
207: queryBuffer.append(dbcol);
208:
209: String requestParameter = columns[i].getAttribute(
210: "request-param", null);
211: if (StringUtils.isNotBlank(requestParameter)) {
212: String nullstr = columns[i].getAttribute(
213: "nullable", null);
214: if (nullstr != null) {
215: nullable = BooleanUtils.toBoolean(nullstr
216: .trim());
217: }
218: String constraintValue = req
219: .getParameter(requestParameter);
220:
221: // if there is a request parameter name,
222: // but not the value, we exit immediately do
223: // that authorization fails authomatically
224: if (StringUtils.isBlank(constraintValue)
225: && !nullable) {
226: getLogger().debug(
227: "DBAUTH: request-param "
228: + requestParameter
229: + " does not exist");
230: return null;
231: }
232: if (constraints > 0) {
233: queryBufferEnd.append(" AND ");
234: }
235: queryBufferEnd.append(dbcol).append("= ?");
236: constraintValues[constraints++] = constraintValue;
237: }
238: }
239:
240: queryBuffer.append(" FROM ");
241: queryBuffer.append(table.getAttribute("name"));
242: if (StringUtils.isNotBlank(queryBufferEnd.toString())) {
243: queryBuffer.append(" WHERE ").append(queryBufferEnd);
244: }
245:
246: getLogger().debug("DBAUTH: query " + queryBuffer);
247:
248: PreparedStatement st = conn.prepareStatement(queryBuffer
249: .toString());
250:
251: for (int i = 0; i < constraints; i++) {
252: getLogger().debug(
253: "DBAUTH: parameter " + (i + 1) + " = ["
254: + String.valueOf(constraintValues[i])
255: + "]");
256: st.setObject(i + 1, constraintValues[i]);
257: }
258: return st;
259: } catch (Exception e) {
260: getLogger().debug("DBAUTH: got exception: " + e);
261: }
262: return null;
263: }
264:
265: private HashMap propagateParameters(Configuration conf,
266: ResultSet rs, Session session) {
267: Configuration table = conf.getChild("table");
268: Configuration[] select = table.getChildren("select");
269: String session_param, type;
270: HashMap map = new HashMap();
271: try {
272: for (int i = 0; i < select.length; i++) {
273: try {
274: session_param = select[i]
275: .getAttribute("to-session");
276: if (StringUtils.isNotBlank(session_param)) {
277: Object o = null;
278: String s = rs.getString(i + 1);
279: /* propagate to session */
280: type = select[i].getAttribute("type", "");
281: if (StringUtils.isBlank(type)
282: || "string".equals(type)) {
283: o = s;
284: } else if ("long".equals(type)) {
285: Long l = Long.decode(s);
286: o = l;
287: } else if ("double".equals(type)) {
288: Double d = Double.valueOf(s);
289: o = d;
290: }
291: if (session != null) {
292: session.setAttribute(session_param, o);
293: getLogger().debug(
294: "DBAUTH: propagating param "
295: + session_param + "=" + s);
296: }
297: map.put(session_param, o);
298: }
299: } catch (Exception e) {
300: // Empty
301: }
302: }
303: return map;
304: } catch (Exception e) {
305: getLogger().debug("exception: ", e);
306: }
307: return null;
308: }
309: }
|