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.framework.configuration.Configuration;
20 import org.apache.avalon.framework.parameters.Parameters;
21 import org.apache.avalon.framework.thread.ThreadSafe;
22
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
31 import org.apache.xpath.XPathAPI;
32 import org.apache.xpath.objects.XObject;
33 import org.w3c.dom.Node;
34 import org.xmldb.api.DatabaseManager;
35 import org.xmldb.api.base.Collection;
36 import org.xmldb.api.base.Database;
37 import org.xmldb.api.base.ResourceIterator;
38 import org.xmldb.api.base.ResourceSet;
39 import org.xmldb.api.base.XMLDBException;
40 import org.xmldb.api.modules.XMLResource;
41 import org.xmldb.api.modules.XPathQueryService;
42
43 import java.util.Collections;
44 import java.util.HashMap;
45 import java.util.Map;
46
47 /**
48 * This action is used to authenticate user by comparing several request
49 * fields (username, password) with the values in a DBXML compliant database.
50 * The description of the process is given via external xml description file
51 * simiar to the one used for all actions derived from AbstractDatabaseAction.
52 *
53 * <pre>
54 * <root>
55 * <connection>
56 * <driver>org.apache.xindice.client.xmldb.DatabaseImpl</driver>
57 * <base>xmldb:xindice:///db/beta</base>
58 * </connection>
59 *
60 * <root name="users">
61 * <select element="username" request-param="username" to-session="username"/>
62 * <select element="password" request-param="password" nullable="yes"/>
63 *
64 * <select element="role" to-session="role" type="string"/>
65 * <select element="skin" to-session="skin" type="string"/>
66 * </root>
67 * </root>
68 * </pre>
69 *
70 * The values specified via "request-param" describe the name of HTTP request
71 * parameter, "element" indicates matching document node, "nullable" means
72 * that request-param which is null or empty will not be included in the WHERE
73 * clause. This way you can enable accounts with empty passwords, etc.
74 * "to-session" attribute indicates under which name the value obtained from
75 * database should be stored in the session. Of course new session is created
76 * when authorization is successfull. The "type" attribute can be either
77 * string, long or double and alters the type of object stored in session.
78 * Additionally all parameters that are
79 * propagated to the session are made available to the sitemap via {name}
80 * expression.
81 *
82 * If there is no need to touch the session object, providing just one-time
83 * verification, you can specify action parameter "create-session" to "no" or
84 * "false". No values are then propagated to the sesion and session object is
85 * not verified.
86 *
87 * @author <a href="mailto:czoffoli@littlepenguin.org">Christian Zoffoli</a>
88 * @author <a href="mailto:Martin.Man@seznam.cz">Martin Man</a>
89 * @since 2002/02/03
90 * @version $Id: DbXMLAuthenticatorAction.java 433543 2006-08-22 06:22:54Z crossley $
91 *
92 * based on DatabaseAuthenticatorAction created by Martin Man <Martin.Man@seznam.cz>
93 */
94 public class DbXMLAuthenticatorAction extends AbstractDatabaseAction implements ThreadSafe
95 {
96
97 /**
98 * Main invocation routine.
99 */
100 public Map act (Redirector redirector, SourceResolver resolver, Map objectModel, String src,
101 Parameters parameters) throws Exception {
102
103 ResourceSet rs = null;
104
105 // read global parameter settings
106 boolean reloadable = Constants.DESCRIPTOR_RELOADABLE_DEFAULT;
107
108 if (this.settings.containsKey("reloadable")) {
109 reloadable = Boolean.valueOf((String) this.settings.get("reloadable")).booleanValue();
110 }
111
112 // read local settings
113 try {
114 Configuration conf = this.getConfiguration (
115 parameters.getParameter ("descriptor", (String) this.settings.get("descriptor")),
116 resolver,
117 parameters.getParameterAsBoolean("reloadable",
118 reloadable));
119
120
121 boolean cs = true;
122 String create_session = parameters.getParameter ("create-session", (String) this.settings.get("create-session"));
123
124 if (create_session != null && ("no".equals (create_session.trim ()) || "false".equals (create_session.trim ()))) {
125 cs = false;
126 }
127
128 Request req = ObjectModelHelper.getRequest(objectModel);
129
130 /* check request validity */
131 if (req == null) {
132 getLogger ().debug ("DBXMLAUTH: no request object");
133 return null;
134 }
135
136 rs = this.Authenticate( conf, req );
137
138 if (rs != null )
139 {
140 getLogger ().debug ("DBXMLAUTH: authorized successfully");
141 Session session = null;
142
143 if (cs) {
144 session = req.getSession (false);
145 if (session != null)
146 session.invalidate ();
147 session = req.getSession (true);
148 if (session == null)
149 return null;
150 getLogger ().debug ("DBXMLAUTH: session created");
151 } else {
152 getLogger ().debug ("DBXMLAUTH: leaving session untouched");
153 }
154
155 HashMap actionMap = this.propagateParameters (conf, rs, session);
156 return Collections.unmodifiableMap (actionMap);
157 } else {
158 //getLogger ().debug ("DBXMLAUTH: error ResourceSet is null");
159 }
160
161 req.setAttribute("message", "The username or password were incorrect, please check your CAPS LOCK key and try again.");
162 getLogger ().debug ("DBXMLAUTH: no results for query");
163
164 } catch (Exception e) {
165
166 getLogger().debug ("exception: ", e);
167 return null;
168 }
169
170 return null;
171 }
172
173
174 private String getAuthQuery ( Configuration conf, Request req )
175 {
176
177 StringBuffer queryBuffer = new StringBuffer ("//");
178 StringBuffer queryBufferEnd = new StringBuffer ("");
179
180 String dbcol, request_param, request_value, nullstr;
181 boolean nullable = false;
182
183 Configuration table = conf.getChild ("root");
184 Configuration[] select = table.getChildren ("select");
185
186 try {
187
188 queryBuffer.append (table.getAttribute ("name"));
189
190 for (int i = 0; i < select.length; i ++)
191 {
192
193 dbcol = "[" + select[i].getAttribute ("element");
194
195 try {
196 request_param = select[i].getAttribute ("request-param");
197 if (request_param == null ||
198 request_param.trim().equals ("")) {
199 continue;
200 }
201 } catch (Exception e) {
202 continue;
203 }
204
205 try {
206 nullstr = select[i].getAttribute ("nullable");
207
208 if (nullstr != null) nullstr = nullstr.trim ();
209
210 if (BooleanUtils.toBoolean(nullstr)) {
211 nullable = true;
212 }
213
214 } catch (Exception e1) {
215 }
216
217 /* if there is a request parameter name,
218 * but not the value, we exit immediately do
219 * that authorization fails authomatically */
220 request_value = req.getParameter (request_param);
221
222 if (request_value == null || request_value.trim().equals ("")) {
223 // value is null
224 if (!nullable) {
225 getLogger ().debug ("DBXMLAUTH: request-param " + request_param + " does not exist");
226 return null;
227 }
228 } else {
229 queryBufferEnd.append (dbcol).append("='").append(request_value).append("']");
230 }
231 }
232
233 if (!queryBufferEnd.toString ().trim ().equals (""))
234 queryBuffer.append (queryBufferEnd);
235
236 return queryBuffer.toString ();
237 } catch (Exception e) {
238 getLogger ().debug ("DBXMLAUTH: got exception: " + e);
239 return null;
240 }
241 }
242
243
244 private ResourceSet Authenticate( Configuration conf, Request req) throws Exception, XMLDBException {
245
246 ResourceSet rs = null;
247
248 String query = this.getAuthQuery (conf, req);
249 if (query == null) {
250 getLogger ().debug ("DBXMLAUTH: have not got query");
251 req.setAttribute("message", "The authenticator is misconfigured");
252 return null;
253 }
254 getLogger ().debug ("DBXMLAUTH: query is: " + query);
255
256
257 Collection col = CreateConnection(conf);
258
259 if (col != null) {
260 if (col.isOpen()) {
261 try {
262 XPathQueryService service = (XPathQueryService) col.getService("XPathQueryService", "1.0");
263
264 rs = service.query(query);
265 ResourceIterator results = rs.getIterator();
266
267 if (results.hasMoreResources() == false) {
268 getLogger ().debug ("DBXMLAUTH: auth failed");
269 return null;
270 } else {
271 getLogger ().debug ("DBXMLAUTH: auth OK");
272 return rs;
273 }
274 } catch (XMLDBException e) {
275 getLogger ().debug ("DBXMLAUTH: got exception: " + e);
276 return null;
277 } finally {
278 // close col
279 try {
280 col.close();
281 } catch (Exception e) { /* ignore */ }
282 getLogger ().debug ("DBXMLAUTH: collection closed");
283
284 }
285
286 } else {
287 getLogger ().debug ("DBXMLAUTH: error: collection closed !!");
288 }
289
290 } else {
291 getLogger ().debug ("DBXMLAUTH: couldn't open a connection with DB");
292
293 }
294
295 return null;
296 }
297
298
299 private Collection CreateConnection( Configuration conf ) throws Exception, XMLDBException {
300
301 Collection col = null;
302
303 Configuration conn = conf.getChild ("connection");
304
305 try {
306
307 Class c = Class.forName( conn.getChild("driver").getValue() );
308
309 Database database = (Database) c.newInstance();
310 DatabaseManager.registerDatabase(database);
311
312 col = DatabaseManager.getCollection( conn.getChild("base").getValue() );
313
314 } catch (XMLDBException e) {
315 getLogger ().debug ("DBXMLAUTH: Exception occured " + e.errorCode);
316 }
317
318 return col;
319 }
320
321 private HashMap propagateParameters (Configuration conf, ResourceSet resultSet, Session session) {
322
323 Configuration table = conf.getChild ("root");
324 Configuration[] select = table.getChildren ("select");
325 String session_param, type;
326 HashMap map = new HashMap();
327
328 XObject xo;
329 Node originalnode = null;
330
331 try {
332
333 ResourceIterator results = resultSet.getIterator();
334
335 // Create an XObject to be used in Xpath query
336 xo = new XObject();
337
338 // Retrieve the next node
339 XMLResource resource = (XMLResource) results.nextResource();
340
341 originalnode = resource.getContentAsDOM();
342
343 }
344 catch (Exception e) {
345 getLogger ().debug ("DBXMLAUTH: error creating XObject ");
346 }
347
348
349 try {
350 for (int i = 0; i < select.length; i ++) {
351 try {
352 session_param = select[i].getAttribute ("to-session");
353 if (session_param != null && !session_param.trim().equals (""))
354 {
355
356 String s = "";
357
358 try {
359 // Use Xalan xpath parser to extract data
360 xo = XPathAPI.eval(originalnode, "/" + table.getAttribute ("name") + "/" + select[i].getAttribute ("element") );
361 s = xo.toString();
362 }
363 catch (Exception e) {
364 }
365
366 /* propagate to session */
367 try {
368 type = select[i].getAttribute ("type");
369 } catch (Exception e) {
370 type = null;
371 }
372
373 if (type == null || "".equals (type.trim ())) {
374 type = "string";
375 }
376 Object o = null;
377
378 if ("string".equals (type)) {
379 o = s;
380 } else if ("long".equals (type)) {
381 Long l = Long.decode (s);
382 o = l;
383 } else if ("double".equals (type)) {
384 Double d = Double.valueOf (s);
385 o = d;
386 }
387
388 if (session != null) {
389 session.setAttribute (session_param, o);
390 getLogger ().debug ("DBXMLAUTH: propagating param " + session_param + "=" + s);
391 }
392 map.put (session_param, o);
393 }
394 } catch (Exception e) {
395 }
396 }
397 return map;
398 } catch (Exception e) {
399 getLogger().debug("exception: ", e);
400 }
401 return null;
402 }
403 }