1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.cocoon.acting;
18
19 import org.apache.avalon.excalibur.datasource.DataSourceComponent;
20 import org.apache.avalon.framework.configuration.Configuration;
21 import org.apache.avalon.framework.parameters.Parameters;
22 import org.apache.avalon.framework.thread.ThreadSafe;
23 import org.apache.cocoon.Constants;
24 import org.apache.cocoon.environment.ObjectModelHelper;
25 import org.apache.cocoon.environment.Redirector;
26 import org.apache.cocoon.environment.Request;
27 import org.apache.cocoon.environment.Session;
28 import org.apache.cocoon.environment.SourceResolver;
29 import org.apache.commons.lang.BooleanUtils;
30 import org.apache.commons.lang.StringUtils;
31
32 import java.sql.Connection;
33 import java.sql.ResultSet;
34 import java.sql.PreparedStatement;
35 import java.util.Collections;
36 import java.util.HashMap;
37 import java.util.Map;
38
39 /**
40 * This action is used to authenticate user by comparing several request
41 * fields (username, password) with the values in database. The description of
42 * the process is given via external xml description file simiar to the one
43 * used for all actions derived from AbstractDatabaseAction.
44 * <pre>
45 * <root>
46 * <connection>personnel</connection>
47 * <table name="users_table>
48 * <select dbcol="username" request-param="username"
49 * to-session="username"/>
50 * <select dbcol="password" request-param="password"
51 * nullable="yes"/>
52 * <select dbcol="role" to-session="role" type="string"/>
53 * <select dbcol="skin" to-session="skin" type="string"/>
54 * </table>
55 * </root>
56 * </pre>
57 * The values specified via "request-param" describe the name of HTTP request
58 * parameter, "dbcol" indicates matching database column, "nullable" means
59 * that request-param which is null or empty will not be included in the WHERE
60 * clause. This way you can enable accounts with empty passwords, etc.
61 * "to-session" attribute indicates under which name the value obtained from
62 * database should be stored in the session. Of course new session is created
63 * when authorization is successfull. The "type" attribute can be either
64 * string, long or double and alters the type of object stored in session.
65 * Additionally all parameters that are
66 * propagated to the session are made available to the sitemap via {name}
67 * expression.
68 *
69 * If there is no need to touch the session object, providing just one-time
70 * verification, you can specify action parameter "create-session" to "no" or
71 * "false". No values are then propagated to the sesion and session object is
72 * not verified.
73 *
74 * @author <a href="mailto:Martin.Man@seznam.cz">Martin Man</a>
75 * @version CVS $Id: DatabaseAuthenticatorAction.java 433543 2006-08-22 06:22:54Z crossley $
76 */
77 public class DatabaseAuthenticatorAction extends AbstractDatabaseAction implements ThreadSafe
78 {
79 /**
80 * Main invocation routine.
81 */
82 public Map act (Redirector redirector, SourceResolver resolver, Map objectModel, String src,
83 Parameters parameters) throws Exception {
84 DataSourceComponent datasource = null;
85 Connection conn = null;
86 PreparedStatement st = null;
87 ResultSet rs = null;
88
89 // read global parameter settings
90 boolean reloadable = Constants.DESCRIPTOR_RELOADABLE_DEFAULT;
91
92 if (this.settings.containsKey("reloadable")) {
93 reloadable = Boolean.valueOf((String) this.settings.get("reloadable")).booleanValue();
94 }
95
96 // read local settings
97 try {
98 Configuration conf = this.getConfiguration (
99 parameters.getParameter ("descriptor", (String) this.settings.get("descriptor")),
100 resolver,
101 parameters.getParameterAsBoolean("reloadable",reloadable));
102 boolean cs = true;
103 String create_session = parameters.getParameter ("create-session",
104 (String) this.settings.get("create-session"));
105 if (create_session != null) {
106 cs = BooleanUtils.toBoolean(create_session.trim());
107 }
108
109 datasource = this.getDataSource(conf);
110 conn = datasource.getConnection();
111 Request req = ObjectModelHelper.getRequest(objectModel);
112
113 /* check request validity */
114 if (req == null) {
115 getLogger ().debug ("DBAUTH: no request object");
116 return null;
117 }
118
119 st = this.getAuthQuery (conn, conf, req);
120 if (st == null) {
121 getLogger ().debug ("DBAUTH: have not got query");
122 req.setAttribute("message", "The authenticator is misconfigured");
123 return null;
124 }
125
126 rs = st.executeQuery ();
127
128 if (rs.next ()) {
129 getLogger ().debug ("DBAUTH: authorized successfully");
130 Session session = null;
131
132 if (cs) {
133 session = req.getSession (false);
134 if (session != null)
135 session.invalidate ();
136 session = req.getSession (true);
137 if (session == null)
138 return null;
139 getLogger ().debug ("DBAUTH: session created");
140 } else {
141 getLogger ().debug ("DBAUTH: leaving session untouched");
142 }
143
144 HashMap actionMap = this.propagateParameters (conf, rs,
145 session);
146 if(!conn.getAutoCommit()) {
147 conn.commit();
148 }
149 return Collections.unmodifiableMap (actionMap);
150 }
151 if(!conn.getAutoCommit()) {
152 conn.rollback();
153 }
154
155 req.setAttribute("message", "The username or password were incorrect, please check your CAPS LOCK key and try again.");
156 getLogger ().debug ("DBAUTH: no results for query");
157 } catch (Exception e) {
158 if (conn != null) {
159 try {
160 if(!conn.getAutoCommit()) {
161 conn.rollback();
162 }
163 } catch (Exception se) {/* ignore */}
164 }
165 getLogger().debug ("exception: ", e);
166 return null;
167 } finally {
168 if (rs != null) rs.close();
169 if (st != null) st.close();
170 if (conn != null) {
171 try {
172 conn.close();
173 } catch (Exception e) {/* ignore */}
174 }
175 }
176 return null;
177 }
178
179 private PreparedStatement getAuthQuery(Connection conn, Configuration conf, Request req) {
180 StringBuffer queryBuffer = new StringBuffer("SELECT ");
181 StringBuffer queryBufferEnd = new StringBuffer("");
182 Configuration table = conf.getChild("table");
183 Configuration[] columns = table.getChildren("select");
184 try {
185 Object[] constraintValues = new Object[columns.length];
186 int constraints = 0;
187 for (int i = 0; i < columns.length; i++) {
188 String dbcol = columns[i].getAttribute("dbcol");
189 boolean nullable = false;
190 if (i > 0) {
191 queryBuffer.append (", ");
192 }
193 queryBuffer.append(dbcol);
194
195 String requestParameter = columns[i].getAttribute("request-param", null);
196 if (StringUtils.isNotBlank(requestParameter)) {
197 String nullstr = columns[i].getAttribute("nullable", null);
198 if (nullstr != null) {
199 nullable = BooleanUtils.toBoolean(nullstr.trim());
200 }
201 String constraintValue = req.getParameter(requestParameter);
202
203 // if there is a request parameter name,
204 // but not the value, we exit immediately do
205 // that authorization fails authomatically
206 if (StringUtils.isBlank(constraintValue) && !nullable) {
207 getLogger().debug("DBAUTH: request-param " + requestParameter + " does not exist");
208 return null;
209 }
210 if (constraints > 0) {
211 queryBufferEnd.append(" AND ");
212 }
213 queryBufferEnd.append(dbcol).append("= ?");
214 constraintValues[constraints++] = constraintValue;
215 }
216 }
217
218 queryBuffer.append(" FROM ");
219 queryBuffer.append(table.getAttribute("name"));
220 if (StringUtils.isNotBlank(queryBufferEnd.toString())) {
221 queryBuffer.append(" WHERE ").append(queryBufferEnd);
222 }
223
224 getLogger().debug("DBAUTH: query " + queryBuffer);
225
226 PreparedStatement st = conn.prepareStatement(queryBuffer.toString());
227
228 for (int i = 0; i < constraints; i++) {
229 getLogger().debug("DBAUTH: parameter " + (i+1) + " = [" + String.valueOf(constraintValues[i]) + "]");
230 st.setObject(i+1,constraintValues[i]);
231 }
232 return st;
233 }
234 catch (Exception e) {
235 getLogger().debug("DBAUTH: got exception: " + e);
236 }
237 return null;
238 }
239
240 private HashMap propagateParameters (Configuration conf, ResultSet rs,
241 Session session) {
242 Configuration table = conf.getChild ("table");
243 Configuration[] select = table.getChildren ("select");
244 String session_param, type;
245 HashMap map = new HashMap();
246 try {
247 for (int i = 0; i < select.length; i ++) {
248 try {
249 session_param = select[i].getAttribute ("to-session");
250 if (StringUtils.isNotBlank(session_param)) {
251 Object o = null;
252 String s = rs.getString (i + 1);
253 /* propagate to session */
254 type = select[i].getAttribute("type", "");
255 if (StringUtils.isBlank(type) || "string".equals (type)) {
256 o = s;
257 } else if ("long".equals (type)) {
258 Long l = Long.decode (s);
259 o = l;
260 } else if ("double".equals (type)) {
261 Double d = Double.valueOf (s);
262 o = d;
263 }
264 if (session != null) {
265 session.setAttribute (session_param, o);
266 getLogger ().debug ("DBAUTH: propagating param "
267 + session_param + "=" + s);
268 }
269 map.put (session_param, o);
270 }
271 } catch (Exception e) {
272 // Empty
273 }
274 }
275 return map;
276 } catch (Exception e) {
277 getLogger().debug("exception: ", e);
278 }
279 return null;
280 }
281 }