You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jspwiki.apache.org by aj...@apache.org on 2008/02/13 06:54:24 UTC
svn commit: r627255 [10/41] - in
/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src: ./ com/ com/ecyrd/
com/ecyrd/jspwiki/ com/ecyrd/jspwiki/action/ com/ecyrd/jspwiki/attachment/
com/ecyrd/jspwiki/auth/ com/ecyrd/jspwiki/auth/acl/ com/ecyrd/jspwiki...
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/AttachmentServlet.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/AttachmentServlet.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/AttachmentServlet.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/AttachmentServlet.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,837 @@
+/*
+ JSPWiki - a JSP-based WikiWiki clone.
+
+ Copyright (C) 2001-2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package com.ecyrd.jspwiki.attachment;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.SocketException;
+import java.security.Permission;
+import java.security.Principal;
+import java.util.Enumeration;
+import java.util.Properties;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import net.iamvegan.multipartrequest.HttpServletMultipartRequest;
+import net.iamvegan.multipartrequest.MultipartFile;
+import net.iamvegan.multipartrequest.ProgressListener;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.log4j.Logger;
+
+import com.ecyrd.jspwiki.*;
+import com.ecyrd.jspwiki.action.AttachActionBean;
+import com.ecyrd.jspwiki.action.UploadActionBean;
+import com.ecyrd.jspwiki.auth.AuthorizationManager;
+import com.ecyrd.jspwiki.auth.permissions.PermissionFactory;
+import com.ecyrd.jspwiki.dav.AttachmentDavProvider;
+import com.ecyrd.jspwiki.dav.DavPath;
+import com.ecyrd.jspwiki.dav.DavProvider;
+import com.ecyrd.jspwiki.dav.WebdavServlet;
+import com.ecyrd.jspwiki.dav.methods.DavMethod;
+import com.ecyrd.jspwiki.dav.methods.PropFindMethod;
+import com.ecyrd.jspwiki.filters.RedirectException;
+import com.ecyrd.jspwiki.providers.ProviderException;
+import com.ecyrd.jspwiki.ui.progress.ProgressItem;
+import com.ecyrd.jspwiki.util.HttpUtil;
+
+
+/**
+ * This is the chief JSPWiki attachment management servlet. It is used for
+ * both uploading new content and downloading old content. It can handle
+ * most common cases, e.g. check for modifications and return 304's as necessary.
+ * <p>
+ * Authentication is done using JSPWiki's normal AAA framework.
+ * <p>
+ * This servlet is also capable of managing dynamically created attachments.
+ *
+ * @author Erik Bunn
+ * @author Janne Jalkanen
+ *
+ * @since 1.9.45.
+ */
+public class AttachmentServlet
+ extends WebdavServlet
+{
+ private static final int BUFFER_SIZE = 8192;
+
+ private static final long serialVersionUID = 3257282552187531320L;
+
+ private WikiEngine m_engine;
+ static Logger log = Logger.getLogger(AttachmentServlet.class.getName());
+
+ private static final String HDR_VERSION = "version";
+ // private static final String HDR_NAME = "page";
+
+ /** Default expiry period is 1 day */
+ protected static final long DEFAULT_EXPIRY = 1 * 24 * 60 * 60 * 1000;
+
+ private String m_tmpDir;
+
+ private DavProvider m_attachmentProvider;
+
+ /**
+ * The maximum size that an attachment can be.
+ */
+ private int m_maxSize = Integer.MAX_VALUE;
+
+ /**
+ * List of attachment types which are allowed
+ */
+
+ private String[] m_allowedPatterns;
+
+ private String[] m_forbiddenPatterns;
+
+ //
+ // Not static as DateFormat objects are not thread safe.
+ // Used to handle the RFC date format = Sat, 13 Apr 2002 13:23:01 GMT
+ //
+ //private final DateFormat rfcDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z");
+
+ /**
+ * Initializes the servlet from WikiEngine properties.
+ *
+ * {@inheritDoc}
+ */
+ public void init( ServletConfig config )
+ throws ServletException
+ {
+ super.init( config );
+
+ m_engine = WikiEngine.getInstance( config );
+ Properties props = m_engine.getWikiProperties();
+
+ m_attachmentProvider = new AttachmentDavProvider( m_engine );
+ m_tmpDir = m_engine.getWorkDir()+File.separator+"attach-tmp";
+
+ m_maxSize = TextUtil.getIntegerProperty( props,
+ AttachmentManager.PROP_MAXSIZE,
+ Integer.MAX_VALUE );
+
+ String allowed = TextUtil.getStringProperty( props,
+ AttachmentManager.PROP_ALLOWEDEXTENSIONS,
+ null );
+
+ if( allowed != null && allowed.length() > 0 )
+ m_allowedPatterns = allowed.toLowerCase().split("\\s");
+ else
+ m_allowedPatterns = new String[0];
+
+ String forbidden = TextUtil.getStringProperty( props,
+ AttachmentManager.PROP_FORDBIDDENEXTENSIONS,
+ null );
+
+ if( forbidden != null && forbidden.length() > 0 )
+ m_forbiddenPatterns = forbidden.toLowerCase().split("\\s");
+ else
+ m_forbiddenPatterns = new String[0];
+
+ File f = new File( m_tmpDir );
+ if( !f.exists() )
+ {
+ f.mkdirs();
+ }
+ else if( !f.isDirectory() )
+ {
+ log.fatal("A file already exists where the temporary dir is supposed to be: "+m_tmpDir+". Please remove it.");
+ }
+
+ log.debug( "UploadServlet initialized. Using " +
+ m_tmpDir + " for temporary storage." );
+ }
+
+ private boolean isTypeAllowed( String name )
+ {
+ if( name == null || name.length() == 0 ) return false;
+
+ name = name.toLowerCase();
+
+ for( int i = 0; i < m_forbiddenPatterns.length; i++ )
+ {
+ if( name.endsWith(m_forbiddenPatterns[i]) && m_forbiddenPatterns[i].length() > 0 )
+ return false;
+ }
+
+ for( int i = 0; i < m_allowedPatterns.length; i++ )
+ {
+ if( name.endsWith(m_allowedPatterns[i]) && m_allowedPatterns[i].length() > 0 )
+ return true;
+ }
+
+ return m_allowedPatterns.length == 0;
+ }
+
+ /**
+ * Implements the PROPFIND method.
+ *
+ * @param req The servlet request
+ * @param res The servlet response
+ * @throws IOException If input/output fails
+ * @throws ServletException If the servlet has issues
+ */
+ public void doPropFind( HttpServletRequest req, HttpServletResponse res )
+ throws IOException, ServletException
+ {
+ DavMethod dm = new PropFindMethod( m_attachmentProvider );
+
+ String p = new String(req.getPathInfo().getBytes("ISO-8859-1"), "UTF-8");
+
+ DavPath path = new DavPath( p );
+
+ dm.execute( req, res, path );
+ }
+ /**
+ * Implements the OPTIONS method.
+ *
+ * @param req The servlet request
+ * @param res The servlet response
+ */
+
+ protected void doOptions( HttpServletRequest req, HttpServletResponse res )
+ {
+ res.setHeader( "DAV", "1" ); // We support only Class 1
+ res.setHeader( "Allow", "GET, PUT, POST, OPTIONS, PROPFIND, PROPPATCH, MOVE, COPY, DELETE");
+ res.setStatus( HttpServletResponse.SC_OK );
+ }
+
+ /**
+ * Serves a GET with two parameters: 'wikiname' specifying the wikiname
+ * of the attachment, 'version' specifying the version indicator.
+ *
+ * {@inheritDoc}
+ */
+
+ // FIXME: Messages would need to be localized somehow.
+ public void doGet( HttpServletRequest req, HttpServletResponse res )
+ throws IOException, ServletException
+ {
+ WikiContext context;
+ try
+ {
+ context = (WikiContext)m_engine.getWikiActionBeanFactory().newActionBean( req, res, AttachActionBean.class );
+ }
+ catch ( WikiException e )
+ {
+ throw new ServletException( e.getMessage() );
+ }
+
+ String version = req.getParameter( HDR_VERSION );
+ String nextPage = req.getParameter( "nextpage" );
+
+ String msg = "An error occurred. Ouch.";
+ int ver = WikiProvider.LATEST_VERSION;
+
+ AttachmentManager mgr = m_engine.getAttachmentManager();
+ AuthorizationManager authmgr = m_engine.getAuthorizationManager();
+
+
+ String page = context.getPage().getName();
+
+ if( page == null )
+ {
+ log.info("Invalid attachment name.");
+ res.sendError( HttpServletResponse.SC_BAD_REQUEST );
+ return;
+ }
+
+ OutputStream out = null;
+ InputStream in = null;
+
+ try
+ {
+ log.debug("Attempting to download att "+page+", version "+version);
+ if( version != null )
+ {
+ ver = Integer.parseInt( version );
+ }
+
+ Attachment att = mgr.getAttachmentInfo( page, ver );
+
+ if( att != null )
+ {
+ //
+ // Check if the user has permission for this attachment
+ //
+
+ Permission permission = PermissionFactory.getPagePermission( att, "view" );
+ if( !authmgr.checkPermission( context.getWikiSession(), permission ) )
+ {
+ log.debug("User does not have permission for this");
+ res.sendError( HttpServletResponse.SC_FORBIDDEN );
+ return;
+ }
+
+
+ //
+ // Check if the client already has a version of this attachment.
+ //
+ if( HttpUtil.checkFor304( req, att ) )
+ {
+ log.debug("Client has latest version already, sending 304...");
+ res.sendError( HttpServletResponse.SC_NOT_MODIFIED );
+ return;
+ }
+
+ String mimetype = getMimeType( context, att.getFileName() );
+
+ res.setContentType( mimetype );
+
+ //
+ // We use 'inline' instead of 'attachment' so that user agents
+ // can try to automatically open the file.
+ //
+
+ res.addHeader( "Content-Disposition",
+ "inline; filename=\"" + att.getFileName() + "\";" );
+
+ res.addDateHeader("Last-Modified",att.getLastModified().getTime());
+
+ if( !att.isCacheable() )
+ {
+ res.addHeader( "Pragma", "no-cache" );
+ res.addHeader( "Cache-control", "no-cache" );
+ }
+
+ // If a size is provided by the provider, report it.
+ if( att.getSize() >= 0 )
+ {
+ // log.info("size:"+att.getSize());
+ res.setContentLength( (int)att.getSize() );
+ }
+
+ out = res.getOutputStream();
+ in = mgr.getAttachmentStream( context, att );
+
+ int read = 0;
+ byte[] buffer = new byte[BUFFER_SIZE];
+
+ while( (read = in.read( buffer )) > -1 )
+ {
+ out.write( buffer, 0, read );
+ }
+
+ if(log.isDebugEnabled())
+ {
+ msg = "Attachment "+att.getFileName()+" sent to "+req.getRemoteUser()+" on "+req.getRemoteAddr();
+ log.debug( msg );
+ }
+ if( nextPage != null ) res.sendRedirect( nextPage );
+
+ return;
+ }
+
+ msg = "Attachment '" + page + "', version " + ver +
+ " does not exist.";
+
+ log.info( msg );
+ res.sendError( HttpServletResponse.SC_NOT_FOUND,
+ msg );
+ return;
+ }
+ catch( ProviderException pe )
+ {
+ msg = "Provider error: "+pe.getMessage();
+
+ log.debug("Provider failed while reading", pe);
+ //
+ // This might fail, if the response is already committed. So in that
+ // case we just log it.
+ //
+ try
+ {
+ res.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+ msg );
+ }
+ catch( IllegalStateException e ) {}
+ return;
+ }
+ catch( NumberFormatException nfe )
+ {
+ msg = "Invalid version number (" + version + ")";
+ res.sendError( HttpServletResponse.SC_BAD_REQUEST,
+ msg );
+ return;
+ }
+ catch( SocketException se )
+ {
+ //
+ // These are very common in download situations due to aggressive
+ // clients. No need to try and send an error.
+ //
+ log.debug("I/O exception during download",se);
+ return;
+ }
+ catch( IOException ioe )
+ {
+ //
+ // Client dropped the connection or something else happened.
+ // We don't know where the error came from, so we'll at least
+ // try to send an error and catch it quietly if it doesn't quite work.
+ //
+ msg = "Error: " + ioe.getMessage();
+ log.debug("I/O exception during download",ioe);
+
+ try
+ {
+ res.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+ msg );
+ }
+ catch( IllegalStateException e ) {}
+ return;
+ }
+ finally
+ {
+ if( in != null )
+ {
+ try
+ {
+ in.close();
+ }
+ catch( IOException e ) {}
+ }
+
+ //
+ // Quite often, aggressive clients close the connection when they have
+ // received the last bits. Therefore, we close the output, but ignore
+ // any exception that might come out of it.
+ //
+
+ if( out != null )
+ {
+ try
+ {
+ out.close();
+ }
+ catch( IOException e ) {}
+ }
+ }
+ }
+
+ /**
+ * Returns the mime type for this particular file. Case does not matter.
+ *
+ * @param ctx WikiContext; required to access the ServletContext of the request.
+ * @param fileName The name to check for.
+ * @return A valid mime type, or application/binary, if not recognized
+ */
+ private static String getMimeType(WikiContext ctx, String fileName )
+ {
+ String mimetype = null;
+
+ HttpServletRequest req = ctx.getHttpRequest();
+ if( req != null )
+ {
+ ServletContext s = req.getSession().getServletContext();
+
+ if( s != null )
+ {
+ mimetype = s.getMimeType( fileName.toLowerCase() );
+ }
+ }
+
+ if( mimetype == null )
+ {
+ mimetype = "application/binary";
+ }
+
+ return mimetype;
+ }
+
+
+ /**
+ * Grabs mime/multipart data and stores it into the temporary area.
+ * Uses other parameters to determine which name to store as.
+ *
+ * <p>The input to this servlet is generated by an HTML FORM with
+ * two parts. The first, named 'page', is the WikiName identifier
+ * for the parent file. The second, named 'content', is the binary
+ * content of the file.
+ *
+ * {@inheritDoc}
+ */
+ public void doPost( HttpServletRequest req, HttpServletResponse res )
+ throws IOException, ServletException
+ {
+ try
+ {
+ String nextPage = upload( req, res );
+ req.getSession().removeAttribute("msg");
+ res.sendRedirect( nextPage );
+ }
+ catch( RedirectException e )
+ {
+ WikiSession session = WikiSession.getWikiSession( m_engine, req );
+ session.addMessage( e.getMessage() );
+
+ req.getSession().setAttribute("msg", e.getMessage());
+ res.sendRedirect( e.getRedirect() );
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void doPut( HttpServletRequest req, HttpServletResponse res )
+ throws IOException, ServletException
+ {
+ String p = new String(req.getPathInfo().getBytes("ISO-8859-1"), "UTF-8");
+ DavPath path = new DavPath( p );
+
+ try
+ {
+ InputStream data = req.getInputStream();
+
+ WikiContext context;
+ String errorPage; // If something bad happened, Upload should be able to take care of most stuff
+
+ try
+ {
+ context = (WikiContext)m_engine.getWikiActionBeanFactory().newActionBean( req, res, UploadActionBean .class );
+ }
+ catch ( WikiException e )
+ {
+ throw new ServletException( e.getMessage() );
+ }
+
+ String wikipage = path.get( 0 );
+
+ errorPage = context.getContext().getURL( UploadActionBean.class,
+ wikipage );
+
+ String changeNote = null; // FIXME: Does not quite work
+
+ boolean created = executeUpload( context, data,
+ path.getName(),
+ errorPage, wikipage,
+ changeNote,
+ req.getContentLength() );
+
+ if( created )
+ res.sendError( HttpServletResponse.SC_CREATED );
+ else
+ res.sendError( HttpServletResponse.SC_OK );
+ }
+ catch( ProviderException e )
+ {
+ res.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+ e.getMessage() );
+ }
+ catch( RedirectException e )
+ {
+ res.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+ e.getMessage() );
+ }
+ }
+
+ /**
+ * Validates the next page to be on the same server as this webapp.
+ * Fixes [JSPWIKI-46].
+ */
+
+ private String validateNextPage( String nextPage, String errorPage )
+ {
+ if( nextPage.indexOf("://") != -1 )
+ {
+ // It's an absolute link, so unless it starts with our address, we'll
+ // log an error.
+
+ if( !nextPage.startsWith( m_engine.getBaseURL() ) )
+ {
+ log.warn("Detected phishing attempt by redirecting to an unsecure location: "+nextPage);
+ nextPage = errorPage;
+ }
+ }
+
+ return nextPage;
+ }
+
+ /**
+ * Uploads a specific mime multipart input set, intercepts exceptions.
+ *
+ * @param req The servlet request
+ * @return The page to which we should go next.
+ * @throws RedirectException If there's an error and a redirection is needed
+ * @throws IOException If upload fails
+ */
+ protected String upload( HttpServletRequest req, HttpServletResponse res )
+ throws RedirectException,
+ IOException
+ {
+ String msg = "";
+ String attName = "(unknown)";
+ String nextPage;
+
+ String progressId = req.getParameter( "progressid" );
+
+ try
+ {
+ HttpServletMultipartRequest multi;
+
+ // Create the context _before_ Multipart operations, otherwise
+ // strict servlet containers may fail when setting encoding.
+ WikiContext context;
+ try
+ {
+ context = (WikiContext)m_engine.getWikiActionBeanFactory().newActionBean( req, res, AttachActionBean.class );
+ }
+ catch ( WikiException e )
+ {
+ throw new IOException( e.getMessage() );
+ }
+
+ UploadListener pl = new UploadListener();
+
+ m_engine.getProgressManager().startProgress( pl, progressId );
+
+ multi = new HttpServletMultipartRequest( req,
+ Long.MAX_VALUE,
+ HttpServletMultipartRequest.SAVE_TO_TMPDIR,
+ HttpServletMultipartRequest.ABORT_ON_MAX_LENGTH,
+ "UTF-8",
+ pl );
+
+ String errorPage = context.getContext().getURL( UploadActionBean.class, context.getPage().getName() );
+ nextPage = validateNextPage( multi.getParameter( "nextpage" ), errorPage );
+ String wikipage = multi.getParameter( "page" );
+ String changeNote = multi.getParameter( "changenote" );
+
+ //
+ // FIXME: Kludge alert. We must end up with the parent page name,
+ // if this is an upload of a new revision
+ //
+
+ int x = wikipage.indexOf("/");
+
+ if( x != -1 ) wikipage = wikipage.substring(0,x);
+
+ //
+ // Go through all files being uploaded.
+ //
+ Enumeration files = multi.getFileParameterNames();
+ long fileSize = 0L;
+ while( files.hasMoreElements() )
+ {
+ String part = (String) files.nextElement();
+ MultipartFile multiFile = multi.getFileParameter(part);
+ fileSize += multiFile.getSize();
+ InputStream in = multiFile.getInputStream();
+
+ String filename = multiFile.getName();
+
+ executeUpload( context, in, filename, nextPage, wikipage, changeNote, fileSize );
+ }
+
+ // Inform the JSP page of which file we are handling:
+ // req.setAttribute( ATTR_ATTACHMENT, wikiname );
+ }
+ catch( ProviderException e )
+ {
+ msg = "Upload failed because the provider failed: "+e.getMessage();
+ log.warn( msg + " (attachment: " + attName + ")", e );
+
+ throw new IOException(msg);
+ }
+ catch( IOException e )
+ {
+ // Show the submit page again, but with a bit more
+ // intimidating output.
+ msg = "Upload failure: " + e.getMessage();
+ log.warn( msg + " (attachment: " + attName + ")", e );
+
+ throw e;
+ }
+ finally
+ {
+ m_engine.getProgressManager().stopProgress( progressId );
+ // FIXME: In case of exceptions should absolutely
+ // remove the uploaded file.
+ }
+
+ return nextPage;
+ }
+
+
+ /**
+ *
+ * @param context the wiki context
+ * @param data the input stream data
+ * @param filename the name of the file to upload
+ * @param errorPage the place to which you want to get a redirection
+ * @param parentPage the page to which the file should be attached
+ * @param changenote The change note
+ * @param contentLength The content length
+ * @return <code>true</code> if upload results in the creation of a new page;
+ * <code>false</code> otherwise
+ * @throws RedirectException If the content needs to be redirected
+ * @throws IOException If there is a problem in the upload.
+ * @throws ProviderException If there is a problem in the backend.
+ */
+ protected boolean executeUpload( WikiContext context, InputStream data,
+ String filename, String errorPage,
+ String parentPage, String changenote,
+ long contentLength )
+ throws RedirectException,
+ IOException, ProviderException
+ {
+ boolean created = false;
+
+ //
+ // FIXME: This has the unfortunate side effect that it will receive the
+ // contents. But we can't figure out the page to redirect to
+ // before we receive the file, due to the stupid constructor of MultipartRequest.
+ //
+
+ if( !context.hasAdminPermissions() )
+ {
+ if( contentLength > m_maxSize )
+ {
+ // FIXME: Does not delete the received files.
+ throw new RedirectException( "File exceeds maximum size ("+m_maxSize+" bytes)",
+ errorPage );
+ }
+
+ if( !isTypeAllowed(filename) )
+ {
+ throw new RedirectException( "Files of this type may not be uploaded to this wiki",
+ errorPage );
+ }
+ }
+
+ Principal user = context.getCurrentUser();
+
+ AttachmentManager mgr = m_engine.getAttachmentManager();
+
+ if( filename == null || filename.trim().length() == 0 )
+ {
+ log.error("Empty file name given.");
+
+ throw new RedirectException("Empty file name given.",
+ errorPage);
+ }
+
+ //
+ // Should help with IE 5.22 on OSX
+ //
+ filename = filename.trim();
+
+ //
+ // Remove any characters that might be a problem. Most
+ // importantly - characters that might stop processing
+ // of the URL.
+ //
+ filename = StringUtils.replaceChars( filename, "#?\"'", "____" );
+
+ log.debug("file="+filename);
+
+ if( data == null )
+ {
+ log.error("File could not be opened.");
+
+ throw new RedirectException("File could not be opened.",
+ errorPage);
+ }
+
+ //
+ // Check whether we already have this kind of a page.
+ // If the "page" parameter already defines an attachment
+ // name for an update, then we just use that file.
+ // Otherwise we create a new attachment, and use the
+ // filename given. Incidentally, this will also mean
+ // that if the user uploads a file with the exact
+ // same name than some other previous attachment,
+ // then that attachment gains a new version.
+ //
+
+ Attachment att = mgr.getAttachmentInfo( context.getPage().getName() );
+
+ if( att == null )
+ {
+ att = new Attachment( m_engine, parentPage, filename );
+ created = true;
+ }
+ att.setSize( contentLength );
+
+ //
+ // Check if we're allowed to do this?
+ //
+
+ Permission permission = PermissionFactory.getPagePermission( att, "upload" );
+ if( m_engine.getAuthorizationManager().checkPermission( context.getWikiSession(),
+ permission ) )
+ {
+ if( user != null )
+ {
+ att.setAuthor( user.getName() );
+ }
+
+ if( changenote != null && changenote.length() > 0 )
+ {
+ att.setAttribute( WikiPage.CHANGENOTE, changenote );
+ }
+
+ m_engine.getAttachmentManager().storeAttachment( att, data );
+
+ log.info( "User " + user + " uploaded attachment to " + parentPage +
+ " called "+filename+", size " + att.getSize() );
+ }
+ else
+ {
+ throw new RedirectException("No permission to upload a file",
+ errorPage);
+ }
+
+ return created;
+ }
+
+ /**
+ * Provides tracking for upload progress.
+ *
+ * @author Janne Jalkanen
+ */
+ private class UploadListener
+ extends ProgressItem
+ implements ProgressListener
+ {
+ public long m_currentBytes;
+ public long m_totalBytes;
+ public String m_uid;
+
+ public void update(long recvdBytes, long totalBytes, int item)
+ {
+ m_currentBytes = recvdBytes;
+ m_totalBytes = totalBytes;
+ }
+
+ public int getProgress()
+ {
+ return (int) (((float)m_currentBytes / m_totalBytes) * 100 + 0.5);
+ }
+ }
+
+}
+
+
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/DynamicAttachment.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/DynamicAttachment.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/DynamicAttachment.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/DynamicAttachment.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,104 @@
+/*
+ JSPWiki - a JSP-based WikiWiki clone.
+
+ Copyright (C) 2001-2007 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package com.ecyrd.jspwiki.attachment;
+
+import com.ecyrd.jspwiki.WikiEngine;
+
+/**
+ * A DynamicAttachment is an attachment which does not really exist, but is
+ * created dynamically by a JSPWiki component.
+ * <p>
+ * Note that a DynamicAttachment might not be available before it is actually
+ * created by a component (e.g. plugin), and therefore trying to access it
+ * before that component has been invoked, might result in a surprising 404.
+ * <p>
+ * DynamicAttachments are not listed among regular attachments in the current
+ * version.
+ * <p>
+ * Usage:
+ *
+ * <pre>
+ *
+ * class MyDynamicComponent implements DynamicAttachmentProvider
+ * {
+ * ...
+ *
+ * DynamicAttachment destatt = mgr.getDynamicAttachment( destattname );
+ *
+ * if( destatt == null )
+ * {
+ * destatt = new DynamicAttachment( context.getEngine(),
+ * context.getPage().getName(),
+ * destfilename,
+ * this );
+ * destatt.setCacheable( false );
+ * }
+ *
+ * // This is used to check whether the attachment is modified or not
+ * // so don't forget to update this if your attachment source changes!
+ * // Else JSPWiki will be serving 304s to anyone who asks...
+ *
+ * destatt.setLastModified( context.getPage().getLastModified() );
+ * mgr.storeDynamicAttachment( context, destatt );
+ * ...
+ *
+ * public InputStream getAttachmentData( WikiContext context, Attachment att )
+ * throws IOException
+ * {
+ * byte[] bytes = "This is a test".getBytes();
+ *
+ * return new ByteArrayInputStream( bytes );
+ * }
+ * </pre>
+ *
+ * @author Janne Jalkanen
+ * @since 2.5.34
+ */
+public class DynamicAttachment extends Attachment
+{
+ private DynamicAttachmentProvider m_provider = null;
+
+ /**
+ * Creates a DynamicAttachment.
+ *
+ * @param engine The engine which owns this attachment
+ * @param parentPage The page which owns this attachment
+ * @param fileName The filename of the attachment
+ * @param provider The provider which will be used to generate the attachment.
+ */
+ public DynamicAttachment(WikiEngine engine,
+ String parentPage,
+ String fileName,
+ DynamicAttachmentProvider provider)
+ {
+ super(engine, parentPage, fileName);
+ m_provider = provider;
+ }
+
+ /**
+ * Returns the provider which is used to generate this attachment.
+ *
+ * @return A Provider component for this attachment.
+ */
+ public DynamicAttachmentProvider getProvider()
+ {
+ return m_provider;
+ }
+}
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/DynamicAttachmentProvider.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/DynamicAttachmentProvider.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/DynamicAttachmentProvider.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/DynamicAttachmentProvider.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,53 @@
+/*
+ JSPWiki - a JSP-based WikiWiki clone.
+
+ Copyright (C) 2001-2007 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package com.ecyrd.jspwiki.attachment;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import com.ecyrd.jspwiki.WikiContext;
+import com.ecyrd.jspwiki.providers.ProviderException;
+
+/**
+ * Provides the data for an attachment. Please note that there will
+ * be a strong reference retained for the provider for each Attachment
+ * it provides, so do try to keep the object light. Also, reuse objects
+ * if possible.
+ * <p>
+ * The Provider needs to be thread-safe.
+ *
+ * @author Janne Jalkanen
+ * @since 2.5.34
+ */
+public interface DynamicAttachmentProvider
+{
+ /**
+ * Returns a stream of data for this attachment. The stream will be
+ * closed by AttachmentServlet.
+ *
+ * @param context A Wiki Context
+ * @param att The Attachment for which the data should be received.
+ * @return InputStream for the data.
+ * @throws ProviderException If something goes wrong internally
+ * @throws IOException If something goes wrong when reading the data
+ */
+ public InputStream getAttachmentData( WikiContext context, Attachment att )
+ throws ProviderException, IOException;
+}
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/package.html
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/package.html?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/package.html (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/package.html Tue Feb 12 21:53:55 2008
@@ -0,0 +1,29 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<title>Insert title here</title>
+</head>
+<body>
+
+<p>Attachment management, uploading and downloading.</p>
+
+<h3>Package Specification</h3>
+
+<p>This package manages the JSPWiki attachment subsystem. It consists of three basic components:</p>
+
+<ul>
+<li>AttachmentServlet - the servlet which allows both uploading and downloading servlets.</li>
+<li>AttachmentManager - The JSPWiki Manager component which manages the storage of attachments.</li>
+<li>Attachment - A special kind of a {@link com.ecyrd.jspwiki.WikiPage} which stores a handle
+ to the attachment data.</li>
+</ul>
+
+<p>Attachments can either be static (i.e. real data, stored somewhere on a filesystem), or <i>dynamic</i>,
+which means that they're generated on the fly by a {@link com.ecyrd.jspwiki.attachment.DynamicAttachmentProvider}.</p>
+
+
+<h3>Related Documentation</h3>
+
+</body>
+</html>
\ No newline at end of file
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/AuthenticationManager.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/AuthenticationManager.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/AuthenticationManager.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/AuthenticationManager.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,614 @@
+/*
+ * JSPWiki - a JSP-based WikiWiki clone. Copyright (C) 2001-2003 Janne Jalkanen
+ * (Janne.Jalkanen@iki.fi) This program is free software; you can redistribute
+ * it and/or modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of the
+ * License, or (at your option) any later version. This program is distributed
+ * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details. You should have
+ * received a copy of the GNU Lesser General Public License along with this
+ * program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package com.ecyrd.jspwiki.auth;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.Principal;
+import java.security.PrivilegedAction;
+import java.util.Properties;
+
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.AccountExpiredException;
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
+import javax.security.auth.login.CredentialExpiredException;
+import javax.security.auth.login.FailedLoginException;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.apache.log4j.Logger;
+
+import com.ecyrd.jspwiki.TextUtil;
+import com.ecyrd.jspwiki.WikiEngine;
+import com.ecyrd.jspwiki.WikiException;
+import com.ecyrd.jspwiki.WikiSession;
+import com.ecyrd.jspwiki.auth.authorize.Role;
+import com.ecyrd.jspwiki.auth.authorize.WebContainerAuthorizer;
+import com.ecyrd.jspwiki.auth.login.CookieAssertionLoginModule;
+import com.ecyrd.jspwiki.auth.login.CookieAuthenticationLoginModule;
+import com.ecyrd.jspwiki.auth.login.WebContainerCallbackHandler;
+import com.ecyrd.jspwiki.auth.login.WikiCallbackHandler;
+import com.ecyrd.jspwiki.event.WikiEventListener;
+import com.ecyrd.jspwiki.event.WikiEventManager;
+import com.ecyrd.jspwiki.event.WikiSecurityEvent;
+
+/**
+ * Manages authentication activities for a WikiEngine: user login, logout, and
+ * credential refreshes. This class uses JAAS to determine how users log in.
+ * @author Andrew Jaquith
+ * @author Janne Jalkanen
+ * @author Erik Bunn
+ * @since 2.3
+ */
+public final class AuthenticationManager
+{
+
+ /** The name of the built-in cookie assertion module */
+ public static final String COOKIE_MODULE = CookieAssertionLoginModule.class.getName();
+
+ /** The name of the built-in cookie authentication module */
+ public static final String COOKIE_AUTHENTICATION_MODULE = CookieAuthenticationLoginModule.class.getName();
+
+ /** The JAAS application name for the web container authentication stack. */
+ public static final String LOGIN_CONTAINER = "JSPWiki-container";
+
+ /** The JAAS application name for the JSPWiki custom authentication stack. */
+ public static final String LOGIN_CUSTOM = "JSPWiki-custom";
+
+ /** If this jspwiki.properties property is <code>true</code>, logs the IP address of the editor on saving. */
+ public static final String PROP_STOREIPADDRESS = "jspwiki.storeIPAddress";
+
+ protected static final Logger log = Logger.getLogger( AuthenticationManager.class );
+
+ /** Was JAAS login config already set before we startd up? */
+ protected boolean m_isJaasConfiguredAtStartup = false;
+
+ /** Static Boolean for lazily-initializing the "allows assertions" flag */
+ private static Boolean c_allowsAssertions = null;
+
+ /** Static Boolean for lazily-initializing the "allows cookie authentication" flag */
+ private static Boolean c_allowsAuthentication = null;
+
+ private WikiEngine m_engine = null;
+
+ /** If true, logs the IP address of the editor */
+ private boolean m_storeIPAddress = true;
+
+ /** Value specifying that the user wants to use the container-managed security, just like
+ * in JSPWiki 2.2.
+ */
+ public static final String SECURITY_OFF = "off";
+
+ /** Just to provide compatibility with the old versions. The same
+ * as SECURITY_OFF.
+ *
+ * @deprecated
+ */
+ protected static final String SECURITY_CONTAINER = "container";
+
+ /** Value specifying that the user wants to use the built-in JAAS-based system */
+ public static final String SECURITY_JAAS = "jaas";
+
+ /**
+ * This property determines whether we use JSPWiki authentication or not.
+ * Possible values are AUTH_JAAS or AUTH_CONTAINER.
+ *
+ */
+
+ public static final String PROP_SECURITY = "jspwiki.security";
+ private static final String PROP_JAAS_CONFIG = "java.security.auth.login.config";
+ private static final String DEFAULT_JAAS_CONFIG = "jspwiki.jaas";
+
+ private static boolean c_useJAAS = true;
+
+ /**
+ * Creates an AuthenticationManager instance for the given WikiEngine and
+ * the specified set of properties. All initialization for the modules is
+ * done here.
+ * @param engine the wiki engine
+ * @param props the properties used to initialize the wiki engine
+ * @throws WikiException if the AuthenticationManager cannot be initialized
+ */
+ public final void initialize( WikiEngine engine, Properties props ) throws WikiException
+ {
+ m_engine = engine;
+ m_storeIPAddress = TextUtil.getBooleanProperty( props, PROP_STOREIPADDRESS, m_storeIPAddress );
+ m_isJaasConfiguredAtStartup = PolicyLoader.isJaasConfigured();
+
+ // Yes, writing to a static field is done here on purpose.
+ c_useJAAS = SECURITY_JAAS.equals(props.getProperty( PROP_SECURITY, SECURITY_JAAS ));
+
+ if( !c_useJAAS ) return;
+
+ //
+ // The rest is JAAS implementation
+ //
+
+ log.info( "Checking JAAS configuration..." );
+
+ if (! m_isJaasConfiguredAtStartup )
+ {
+ URL config = findConfigFile( engine, DEFAULT_JAAS_CONFIG );
+ log.info("JAAS not configured. Installing default configuration: " + config
+ + ". You can set the "+PROP_JAAS_CONFIG+" system property to point to your "
+ + "jspwiki.jaas file, or add the entries from jspwiki.jaas to your own "
+ + "JAAS configuration file.");
+ try
+ {
+ PolicyLoader.setJaasConfiguration( config );
+ }
+ catch ( SecurityException e)
+ {
+ log.error("Could not configure JAAS: " + e.getMessage());
+ }
+ }
+ else
+ {
+ log.info("JAAS already configured by some other application (leaving it alone...)");
+ }
+ }
+
+ /**
+ * Returns true if this WikiEngine uses container-managed authentication.
+ * This method is used primarily for cosmetic purposes in the JSP tier, and
+ * performs no meaningful security function per se. Delegates to
+ * {@link com.ecyrd.jspwiki.auth.authorize.WebContainerAuthorizer#isContainerAuthorized()},
+ * if used as the external authorizer; otherwise, returns <code>false</code>.
+ * @return <code>true</code> if the wiki's authentication is managed by
+ * the container, <code>false</code> otherwise
+ */
+ public final boolean isContainerAuthenticated()
+ {
+ if( !c_useJAAS ) return true;
+
+ try
+ {
+ Authorizer authorizer = m_engine.getAuthorizationManager().getAuthorizer();
+ if ( authorizer instanceof WebContainerAuthorizer )
+ {
+ return ( ( WebContainerAuthorizer )authorizer ).isContainerAuthorized();
+ }
+ }
+ catch ( WikiException e )
+ {
+ // It's probably ok to fail silently...
+ }
+ return false;
+ }
+
+ /**
+ * <p>Logs in the user by attempting to populate a WikiSession Subject from
+ * a web servlet request. This method leverages container-managed authentication.
+ * This method logs in the user if the user's status is "unknown" to the
+ * WikiSession, or if the Http servlet container's authentication status has
+ * changed. This method assumes that the HttpServletRequest is not null; otherwise,
+ * an IllegalStateException is thrown. This method is a <em>privileged</em> action;
+ * the caller must posess the (name here) permission.</p>
+ * <p>If <code>request</code> is <code>null</code>, or the WikiSession
+ * cannot be located for this request, this method throws an {@link IllegalStateException}.</p>
+ * methods return null
+ * @param request servlet request for this user
+ * @return the result of the login operation: <code>true</code> if the user logged in
+ * successfully; <code>false</code> otherwise
+ * @throws com.ecyrd.jspwiki.auth.WikiSecurityException if the Authorizer or UserManager cannot be obtained
+ * @since 2.3
+ */
+ public final boolean login( HttpServletRequest request ) throws WikiSecurityException
+ {
+ if ( request == null )
+ {
+ throw new IllegalStateException( "Wiki context's HttpRequest may not be null" );
+ }
+
+ WikiSession wikiSession = WikiSession.getWikiSession( m_engine, request );
+ if ( wikiSession == null )
+ {
+ throw new IllegalStateException( "Wiki context's WikiSession may not be null" );
+ }
+
+ // If using JAAS, try to log in; otherwise logins "always" succeed
+ boolean login = true;
+ if( c_useJAAS )
+ {
+ AuthorizationManager authMgr = m_engine.getAuthorizationManager();
+ CallbackHandler handler = new WebContainerCallbackHandler(
+ m_engine,
+ request,
+ authMgr.getAuthorizer() );
+ login = doLogin( wikiSession, handler, LOGIN_CONTAINER );
+ }
+ return login;
+ }
+
+ /**
+ * Attempts to perform a WikiSession login for the given username/password
+ * combination. This is custom authentication.
+ * @param session the current wiki session; may not be null.
+ * @param username The user name. This is a login name, not a WikiName. In
+ * most cases they are the same, but in some cases, they might
+ * not be.
+ * @param password The password
+ * @return true, if the username/password is valid
+ * @throws com.ecyrd.jspwiki.auth.WikiSecurityException if the Authorizer or UserManager cannot be obtained
+ */
+ public final boolean login( WikiSession session, String username, String password ) throws WikiSecurityException
+ {
+ if ( session == null )
+ {
+ log.error( "No wiki session provided, cannot log in." );
+ return false;
+ }
+
+ UserManager userMgr = m_engine.getUserManager();
+ CallbackHandler handler = new WikiCallbackHandler(
+ userMgr.getUserDatabase(),
+ username,
+ password );
+ return doLogin( session, handler, LOGIN_CUSTOM );
+ }
+
+ /**
+ * Logs the user out by retrieving the WikiSession associated with the
+ * HttpServletRequest and unbinding all of the Subject's Principals,
+ * except for {@link Role#ALL}, {@link Role#ANONYMOUS}.
+ * is a cheap-and-cheerful way to do it without invoking JAAS LoginModules.
+ * The logout operation will also flush the JSESSIONID cookie from
+ * the user's browser session, if it was set.
+ * @param request the current HTTP request
+ */
+ public final void logout( HttpServletRequest request )
+ {
+ if( request == null )
+ {
+ log.error( "No HTTP reqest provided; cannot log out." );
+ return;
+ }
+
+ HttpSession session = request.getSession();
+ String sid = ( session == null ) ? "(null)" : session.getId();
+ if( log.isDebugEnabled() )
+ {
+ log.debug( "Invalidating WikiSession for session ID=" + sid );
+ }
+ // Retrieve the associated WikiSession and clear the Principal set
+ WikiSession wikiSession = WikiSession.getWikiSession( m_engine, request );
+ Principal originalPrincipal = wikiSession.getLoginPrincipal();
+ wikiSession.invalidate();
+
+ // Remove the wikiSession from the WikiSession cache
+ WikiSession.removeWikiSession( m_engine, request );
+
+ // We need to flush the HTTP session too
+ if ( session != null )
+ {
+ session.invalidate();
+ }
+
+ // Log the event
+ fireEvent( WikiSecurityEvent.LOGOUT, originalPrincipal, null );
+ }
+
+ /**
+ * Determines whether this WikiEngine allows users to assert identities using
+ * cookies instead of passwords. This is determined by inspecting
+ * the LoginConfiguration for application <code>JSPWiki-container</code>.
+ * @return <code>true</code> if cookies are allowed
+ */
+ public static final boolean allowsCookieAssertions()
+ {
+ if( !c_useJAAS ) return true;
+
+ // Lazily initialize
+ if( c_allowsAssertions == null )
+ {
+ c_allowsAssertions = Boolean.FALSE;
+
+ // Figure out whether cookie assertions are allowed
+ Configuration loginConfig = (Configuration)AccessController.doPrivileged(new PrivilegedAction()
+ {
+ public Object run()
+ {
+ return Configuration.getConfiguration();
+ }
+ });
+
+ if (loginConfig != null)
+ {
+ AppConfigurationEntry[] configs = loginConfig.getAppConfigurationEntry( LOGIN_CONTAINER );
+ if( configs != null )
+ {
+ for ( int i = 0; i < configs.length; i++ )
+ {
+ AppConfigurationEntry config = configs[i];
+ if ( COOKIE_MODULE.equals( config.getLoginModuleName() ) )
+ {
+ c_allowsAssertions = Boolean.TRUE;
+ }
+ }
+ }
+ }
+ }
+
+ return c_allowsAssertions.booleanValue();
+ }
+
+ /**
+ * Determines whether this WikiEngine allows users to authenticate using
+ * cookies instead of passwords. This is determined by inspecting
+ * the LoginConfiguration for application <code>JSPWiki-container</code>.
+ * @return <code>true</code> if cookies are allowed for authentication
+ * @since 2.5.62
+ */
+ public static final boolean allowsCookieAuthentication()
+ {
+ if( !c_useJAAS ) return true;
+
+ // Lazily initialize
+ if( c_allowsAuthentication == null )
+ {
+ c_allowsAuthentication = Boolean.FALSE;
+
+ // Figure out whether cookie assertions are allowed
+ Configuration loginConfig = (Configuration)AccessController.doPrivileged(new PrivilegedAction()
+ {
+ public Object run()
+ {
+ return Configuration.getConfiguration();
+ }
+ });
+
+ if (loginConfig != null)
+ {
+ AppConfigurationEntry[] configs = loginConfig.getAppConfigurationEntry( LOGIN_CONTAINER );
+
+ if( configs != null )
+ {
+ for ( int i = 0; i < configs.length; i++ )
+ {
+ AppConfigurationEntry config = configs[i];
+ if ( COOKIE_AUTHENTICATION_MODULE.equals( config.getLoginModuleName() ) )
+ {
+ c_allowsAuthentication = Boolean.TRUE;
+ }
+ }
+ }
+ }
+ }
+
+ return c_allowsAuthentication.booleanValue();
+ }
+ /**
+ * Determines whether the supplied Principal is a "role principal".
+ * @param principal the principal to test
+ * @return <code>true</code> if the Principal is of type
+ * {@link GroupPrincipal} or
+ * {@link com.ecyrd.jspwiki.auth.authorize.Role},
+ * <code>false</code> otherwise
+ */
+ public static final boolean isRolePrincipal( Principal principal )
+ {
+ return principal instanceof Role || principal instanceof GroupPrincipal;
+ }
+
+ /**
+ * Determines whether the supplied Principal is a "user principal".
+ * @param principal the principal to test
+ * @return <code>false</code> if the Principal is of type
+ * {@link GroupPrincipal} or
+ * {@link com.ecyrd.jspwiki.auth.authorize.Role},
+ * <code>true</code> otherwise
+ */
+ public static final boolean isUserPrincipal( Principal principal )
+ {
+ return !isRolePrincipal( principal );
+ }
+
+ /**
+ * Log in to the application using a given JAAS LoginConfiguration. Any
+ * configuration error
+ * @param wikiSession the current wiki session, to which the Subject will be associated
+ * @param handler handles callbacks sent by the LoginModules in the configuration
+ * @param application the name of the application whose LoginConfiguration should be used
+ * @return the result of the login
+ * @throws WikiSecurityException
+ */
+ private final boolean doLogin( final WikiSession wikiSession, final CallbackHandler handler, final String application ) throws WikiSecurityException
+ {
+ try
+ {
+ LoginContext loginContext = (LoginContext)AccessController.doPrivileged(new PrivilegedAction()
+ {
+ public Object run()
+ {
+ try
+ {
+ return wikiSession.getLoginContext( application, handler );
+ }
+ catch( LoginException e )
+ {
+ log.error( "Couldn't retrieve login configuration.\nMessage="
+ + e.getLocalizedMessage() );
+ return null;
+ }
+ }
+ });
+
+ if( loginContext != null )
+ {
+ loginContext.login();
+ fireEvent( WikiSecurityEvent.LOGIN_INITIATED, null, wikiSession );
+ }
+ else
+ {
+ log.error("No login context. Please double-check that JSPWiki found your 'jspwiki.jaas' file or the contents have been appended to your regular JAAS file.");
+ return false;
+ }
+
+ // Fire event for the correct authentication event
+ if ( wikiSession.isAnonymous() )
+ {
+ fireEvent( WikiSecurityEvent.LOGIN_ANONYMOUS, wikiSession.getLoginPrincipal(), wikiSession );
+ }
+ else if ( wikiSession.isAsserted() )
+ {
+ fireEvent( WikiSecurityEvent.LOGIN_ASSERTED, wikiSession.getLoginPrincipal(), wikiSession );
+ }
+ else if ( wikiSession.isAuthenticated() )
+ {
+ fireEvent( WikiSecurityEvent.LOGIN_AUTHENTICATED, wikiSession.getLoginPrincipal(), wikiSession );
+ }
+
+ return true;
+ }
+ catch( FailedLoginException e )
+ {
+ //
+ // Just a mistyped password or a cracking attempt. No need to worry
+ // and alert the admin
+ //
+ log.info("Failed login: "+e.getLocalizedMessage());
+ fireEvent( WikiSecurityEvent.LOGIN_FAILED, wikiSession.getLoginPrincipal(), wikiSession );
+ return false;
+ }
+ catch( AccountExpiredException e )
+ {
+ log.info("Expired account: "+e.getLocalizedMessage());
+ fireEvent( WikiSecurityEvent.LOGIN_ACCOUNT_EXPIRED, wikiSession.getLoginPrincipal(), wikiSession );
+ return false;
+ }
+ catch( CredentialExpiredException e )
+ {
+ log.info("Credentials expired: "+e.getLocalizedMessage());
+ fireEvent( WikiSecurityEvent.LOGIN_CREDENTIAL_EXPIRED, wikiSession.getLoginPrincipal(), wikiSession );
+ return false;
+ }
+ catch( LoginException e )
+ {
+ //
+ // This should only be caught if something unforeseen happens,
+ // so therefore we can log it as an error.
+ //
+ log.error( "Couldn't log in.\nMessage="
+ + e.getLocalizedMessage() );
+ return false;
+ }
+ catch( SecurityException e )
+ {
+ log.error( "Could not log in. Please check that your jaas.config file is found.", e );
+ return false;
+ }
+ }
+
+ /**
+ * Looks up and obtains a configuration file inside the WEB-INF folder of a
+ * wiki webapp.
+ * @param engine the wiki engine
+ * @param name the file to obtain, <em>e.g.</em>, <code>jspwiki.policy</code>
+ * @return the URL to the file
+ */
+ protected static final URL findConfigFile( WikiEngine engine, String name )
+ {
+ // Try creating an absolute path first
+ File defaultFile = null;
+ if( engine.getRootPath() != null )
+ {
+ defaultFile = new File( engine.getRootPath() + "/WEB-INF/" + name );
+ }
+ if ( defaultFile != null && defaultFile.exists() )
+ {
+ try
+ {
+ return defaultFile.toURL();
+ }
+ catch ( MalformedURLException e)
+ {
+ // Shouldn't happen, but log it if it does
+ log.warn( "Malformed URL: " + e.getMessage() );
+ }
+
+ }
+
+ // Ok, the absolute path didn't work; try other methods
+ ClassLoader cl = AuthenticationManager.class.getClassLoader();
+
+ URL path = cl.getResource("/WEB-INF/"+name);
+
+ if( path == null )
+ path = cl.getResource("/"+name);
+
+ if( path == null )
+ path = cl.getResource(name);
+
+ if( path == null && engine.getServletContext() != null )
+ {
+ try
+ {
+ path = engine.getServletContext().getResource("/WEB-INF/"+name);
+ }
+ catch( MalformedURLException e )
+ {
+ // This should never happen unless I screw up
+ log.fatal("Your code is b0rked. You are a bad person.");
+ }
+ }
+
+ return path;
+ }
+
+
+ // events processing .......................................................
+
+ /**
+ * Registers a WikiEventListener with this instance.
+ * This is a convenience method.
+ * @param listener the event listener
+ */
+ public final synchronized void addWikiEventListener( WikiEventListener listener )
+ {
+ WikiEventManager.addWikiEventListener( this, listener );
+ }
+
+ /**
+ * Un-registers a WikiEventListener with this instance.
+ * This is a convenience method.
+ * @param listener the event listener
+ */
+ public final synchronized void removeWikiEventListener( WikiEventListener listener )
+ {
+ WikiEventManager.removeWikiEventListener( this, listener );
+ }
+
+ /**
+ * Fires a WikiSecurityEvent of the provided type, Principal and target Object
+ * to all registered listeners.
+ *
+ * @see com.ecyrd.jspwiki.event.WikiSecurityEvent
+ * @param type the event type to be fired
+ * @param principal the subject of the event, which may be <code>null</code>
+ * @param target the changed Object, which may be <code>null</code>
+ */
+ protected final void fireEvent( int type, Principal principal, Object target )
+ {
+ if ( WikiEventManager.isListening(this) )
+ {
+ WikiEventManager.fireEvent(this,new WikiSecurityEvent(this,type,principal,target));
+ }
+ }
+
+}