blob: 13eb9e5ad391e9ba1d6e733f7839678d99c657e4 [file] [log] [blame]
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.wiki.auth.login;
import java.io.*;
import java.util.UUID;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import org.apache.wiki.WikiEngine;
import org.apache.wiki.auth.WikiPrincipal;
import org.apache.wiki.util.FileUtil;
import org.apache.wiki.util.HttpUtil;
import org.apache.wiki.util.TextUtil;
/**
* Logs in an user based on a cookie stored in the user's computer. The cookie
* information is stored in the <code>jspwiki.workDir</code>, under the directory
* {@value #COOKIE_DIR}. For security purposes it is a very, very good idea
* to prevent access to this directory by everyone except the web server process;
* otherwise people having read access to this directory may be able to spoof
* other users.
* <p>
* The cookie directory is scrubbed of old entries at regular intervals.
* <p>
* This module must be used with a CallbackHandler (such as
* {@link WebContainerCallbackHandler}) that supports the following Callback
* types:
* </p>
* <ol>
* <li>{@link HttpRequestCallback}- supplies the cookie, which should contain
* an unique id for fetching the UID.</li>
* <li>{@link WikiEngineCallback} - allows access to the WikiEngine itself.
* </ol>
* <p>
* After authentication, a generic WikiPrincipal based on the username will be
* created and associated with the Subject.
* </p>
* @see javax.security.auth.spi.LoginModule#commit()
* @see CookieAssertionLoginModule
* @since 2.5.62
*/
public class CookieAuthenticationLoginModule extends AbstractLoginModule
{
private static final Logger log = Logger.getLogger( CookieAuthenticationLoginModule.class );
private static final String LOGIN_COOKIE_NAME = "JSPWikiUID";
/** The directory name under which the cookies are stored. The value is {@value}. */
protected static final String COOKIE_DIR = "logincookies";
/**
* User property for setting how long the cookie is stored on the user's computer.
* The value is {@value}. The default expiry time is 14 days.
*/
public static final String PROP_LOGIN_EXPIRY_DAYS = "jspwiki.cookieAuthentication.expiry";
/**
* Built-in value for storing the cookie.
*/
private static final int DEFAULT_EXPIRY_DAYS = 14;
private static long c_lastScrubTime = 0L;
/** Describes how often we scrub the cookieDir directory.
*/
private static final long SCRUB_PERIOD = 60*60*1000L; // In milliseconds
/**
* @see javax.security.auth.spi.LoginModule#login()
*
* {@inheritDoc}
*/
public boolean login() throws LoginException
{
// Otherwise, let's go and look for the cookie!
HttpRequestCallback hcb = new HttpRequestCallback();
//UserDatabaseCallback ucb = new UserDatabaseCallback();
WikiEngineCallback wcb = new WikiEngineCallback();
Callback[] callbacks = new Callback[]
{ hcb, wcb };
try
{
m_handler.handle( callbacks );
HttpServletRequest request = hcb.getRequest();
String uid = getLoginCookie( request );
if( uid != null )
{
WikiEngine engine = wcb.getEngine();
File cookieFile = getCookieFile(engine, uid);
if( cookieFile != null && cookieFile.exists() && cookieFile.canRead() )
{
Reader in = null;
try
{
in = new BufferedReader( new InputStreamReader( new FileInputStream( cookieFile ), "UTF-8" ) );
String username = FileUtil.readContents( in );
if ( log.isDebugEnabled() )
{
log.debug( "Logged in cookie authenticated name=" + username );
}
// If login succeeds, commit these principals/roles
m_principals.add( new WikiPrincipal( username, WikiPrincipal.LOGIN_NAME ) );
//
// Tag the file so that we know that it has been accessed recently.
//
cookieFile.setLastModified( System.currentTimeMillis() );
return true;
}
catch( IOException e )
{
return false;
}
finally
{
if( in != null ) in.close();
}
}
}
}
catch( IOException e )
{
String message = "IO exception; disallowing login.";
log.error( message, e );
throw new LoginException( message );
}
catch( UnsupportedCallbackException e )
{
String message = "Unable to handle callback; disallowing login.";
log.error( message, e );
throw new LoginException( message );
}
return false;
}
/**
* Attempts to locate the cookie file.
* @param engine WikiEngine
* @param uid An unique ID fetched from the user cookie
* @return A File handle, or null, if there was a problem.
*/
private static File getCookieFile(WikiEngine engine, String uid)
{
File cookieDir = new File( engine.getWorkDir(), COOKIE_DIR );
if( !cookieDir.exists() )
{
cookieDir.mkdirs();
}
if( !cookieDir.canRead() )
{
log.error("Cannot read from cookie directory!"+cookieDir.getAbsolutePath());
return null;
}
if( !cookieDir.canWrite() )
{
log.error("Cannot write to cookie directory!"+cookieDir.getAbsolutePath());
return null;
}
//
// Scrub away old files
//
long now = System.currentTimeMillis();
if( now > (c_lastScrubTime+SCRUB_PERIOD ) )
{
scrub( TextUtil.getIntegerProperty( engine.getWikiProperties(),
PROP_LOGIN_EXPIRY_DAYS,
DEFAULT_EXPIRY_DAYS ),
cookieDir );
c_lastScrubTime = now;
}
//
// Find the cookie file
//
File cookieFile = new File( cookieDir, uid );
return cookieFile;
}
/**
* Extracts the login cookie UID from the servlet request.
*
* @param request The HttpServletRequest
* @return The UID value from the cookie, or null, if no such cookie exists.
*/
private static String getLoginCookie(HttpServletRequest request)
{
String cookie = HttpUtil.retrieveCookieValue( request, LOGIN_COOKIE_NAME );
return cookie;
}
/**
* Sets a login cookie based on properties set by the user. This method also
* creates the cookie uid-username mapping in the work directory.
*
* @param engine The WikiEngine
* @param response The HttpServletResponse
* @param username The username for whom to create the cookie.
*/
// FIXME3.0: For 3.0, switch to using java.util.UUID so that we can
// get rid of jug.jar
public static void setLoginCookie( WikiEngine engine, HttpServletResponse response, String username )
{
UUID uid = UUID.randomUUID();
int days = TextUtil.getIntegerProperty( engine.getWikiProperties(),
PROP_LOGIN_EXPIRY_DAYS,
DEFAULT_EXPIRY_DAYS );
Cookie userId = new Cookie( LOGIN_COOKIE_NAME, uid.toString() );
userId.setMaxAge( days * 24 * 60 * 60 );
response.addCookie( userId );
File cf = getCookieFile( engine, uid.toString() );
Writer out = null;
try
{
//
// Write the cookie content to the cookie store file.
//
out = new BufferedWriter( new OutputStreamWriter( new FileOutputStream(cf), "UTF-8" ) );
FileUtil.copyContents( new StringReader(username), out );
if( log.isDebugEnabled() )
{
log.debug( "Created login cookie for user "+username+" for "+days+" days" );
}
}
catch( IOException ex )
{
log.error("Unable to create cookie file to store user id: "+uid);
}
finally
{
if( out != null )
{
try
{
out.close();
}
catch( IOException ex )
{
log.error("Unable to close stream");
}
}
}
}
/**
* Clears away the login cookie, and removes the uid-username mapping file as well.
*
* @param engine WikiEngine
* @param request Servlet request
* @param response Servlet response
*/
public static void clearLoginCookie( WikiEngine engine, HttpServletRequest request, HttpServletResponse response )
{
Cookie userId = new Cookie( LOGIN_COOKIE_NAME, "" );
userId.setMaxAge( 0 );
response.addCookie( userId );
String uid = getLoginCookie( request );
if( uid != null )
{
File cf = getCookieFile( engine, uid );
if( cf != null )
{
cf.delete();
}
}
}
/**
* Goes through the cookie directory and removes any obsolete files.
* The scrubbing takes place one day after the cookie was supposed to expire.
* However, if the user has logged in during the expiry period, the expiry is
* reset, and the cookie file left here.
*
* @param days
* @param cookieDir
*/
private static synchronized void scrub( int days, File cookieDir )
{
log.debug("Scrubbing cookieDir...");
File[] files = cookieDir.listFiles();
long obsoleteDateLimit = System.currentTimeMillis() - ((long)days+1) * 24 * 60 * 60 * 1000L;
int deleteCount = 0;
for( int i = 0; i < files.length; i++ )
{
File f = files[i];
long lastModified = f.lastModified();
if( lastModified < obsoleteDateLimit )
{
f.delete();
deleteCount++;
}
}
log.debug("Removed "+deleteCount+" obsolete cookie logins");
}
}