blob: 12831fdbd705a7155d7cc47d2c69a8d99ffb9a87 [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.ki.web;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.ki.codec.Base64;
import org.apache.ki.mgt.AbstractRememberMeManager;
import org.apache.ki.web.attr.CookieAttribute;
import org.apache.ki.web.attr.WebAttribute;
import org.apache.ki.web.servlet.KiHttpServletRequest;
/**
* Remembers a Subject's identity by using a {@link WebAttribute WebAttribute} instance to retain
* the identity value between web requests.
*
* <p>This class's default <code>WebAttribute</code> instance is a {@link org.apache.ki.web.attr.CookieAttribute CookieAttribute}, storing
* the Subject's {@link org.apache.ki.subject.Subject#getPrincipals principals} in a <code>Cookie</code>. Note that
* because this class subclasses the <code>AbstractRememberMeManager</code> which already provides serialization and
* encryption logic, this class utilizes both for added security before setting the cookie value.</p>
*
* <p>This class also contains &quot;passthrough&quot; JavaBeans-compatible getters/setters for the underlying
* <code>CookieAttribute</code>'s properties to make configuration easier.</p>
*
* <p>Note however as a basic sanity check, these passthrough methods will first assert that the underlying
* {@link #getIdentityAttribute identityAttribute} is actually a {@link CookieAttribute CookieAttribute}. If it
* is not, an {@link IllegalStateException} will be thrown. Because the default instance of this class <em>is</em>
* already <code>CookieAttribute</code>, you would only ever experience the exception if you explicitly
* override the internal instance with a different type and accidentally call one of these JavaBeans passthrough
* methods.</p>
*
* <p>Just be aware of this if you manually override the {@link #getIdentityAttribute identityAttribute} property
* to be an instance of something <em>other</em> than a <code>CookieAttribute</code>.</p>
*
* @author Les Hazlewood
* @author Luis Arias
* @since 0.9
*/
public class WebRememberMeManager extends AbstractRememberMeManager {
//TODO - complete JavaDoc
private static transient final Logger log = LoggerFactory.getLogger(WebRememberMeManager.class);
/**
* The default name of the underlying rememberMe cookie which is <code>rememberMe</code>.
*/
public static final String DEFAULT_REMEMBER_ME_COOKIE_NAME = "rememberMe";
protected WebAttribute<String> identityAttribute = null;
public WebRememberMeManager() {
CookieAttribute<String> attr = new CookieAttribute<String>(DEFAULT_REMEMBER_ME_COOKIE_NAME);
attr.setCheckRequestParams(false);
//Peter (Apache Ki developer) said that Jetty didn't like the CookieAttribute.INDEFINITE value
// (Tomcat was ok with it), so just default to a few years for now. If anyone doesn't visit a site in 3 years
// after last login, I doubt any Ki users would mind their end-users to be forced to log in. - LAH.
attr.setMaxAge(CookieAttribute.ONE_YEAR * 3);
this.identityAttribute = attr;
}
public WebAttribute<String> getIdentityAttribute() {
return identityAttribute;
}
public void setIdentityAttribute(WebAttribute<String> identityAttribute) {
this.identityAttribute = identityAttribute;
}
protected void assertCookieAttribute() {
if (!(this.identityAttribute instanceof CookieAttribute)) {
String msg = "Attempting to access a Cookie property, but the underlying " +
WebAttribute.class.getName() + " instance is not a " +
CookieAttribute.class.getName() + " instance. This is expected if you " +
"are accessing or modifying a cookie property.";
throw new IllegalStateException(msg);
}
}
/**
* Passthrough JavaBeans property that will get the underyling rememberMe cookie's name.
*
* <p>The default value is {@link #DEFAULT_REMEMBER_ME_COOKIE_NAME}</p>
*
* <p>This method performs a quick <code>CookieAttribute</code> sanity check as described in the class-level JavaDoc.</p>
*
* @return the underlying rememberMe cookie's name
*/
public String getCookieName() {
assertCookieAttribute();
return ((CookieAttribute) this.identityAttribute).getName();
}
/**
* Passthrough JavaBeans property that will set the underyling rememberMe cookie's name.
*
* <p>The default value is {@link #DEFAULT_REMEMBER_ME_COOKIE_NAME}</p>
*
* <p>This method performs a quick <code>CookieAttribute</code> sanity check as described in the class-level JavaDoc.</p>
*
* @param name the name to assign to the underlying rememberMe cookie
*/
public void setCookieName(String name) {
assertCookieAttribute();
((CookieAttribute) this.identityAttribute).setName(name);
}
/**
* Passthrough JavaBeans property that will get the underyling rememberMe cookie's path.
*
* <p>This method performs a quick <code>CookieAttribute</code> sanity check as described in the class-level JavaDoc.</p>
*
* @return the underlying rememberMe cookie's path
*/
public String getCookiePath() {
assertCookieAttribute();
return ((CookieAttribute) this.identityAttribute).getPath();
}
/**
* Passthrough JavaBeans property that will set the underyling rememberMe cookie's path.
*
* <p>This method performs a quick <code>CookieAttribute</code> sanity check as described in the class-level JavaDoc.</p>
*
* @param path the path to assign to the underlying rememberMe cookie
*/
public void setCookiePath(String path) {
assertCookieAttribute();
((CookieAttribute) this.identityAttribute).setPath(path);
}
/**
* Passthrough JavaBeans property that will get the underyling rememberMe cookie's max age.
*
* <p>This method performs a quick <code>CookieAttribute</code> sanity check as described in the class-level JavaDoc.</p>
*
* @return the underlying rememberMe cookie's max age.
*/
public int getCookieMaxAge() {
assertCookieAttribute();
return ((CookieAttribute) this.identityAttribute).getMaxAge();
}
/**
* Passthrough JavaBeans property that will get the underyling rememberMe cookie's max age.
*
* <p>This method performs a quick <code>CookieAttribute</code> sanity check as described in the class-level JavaDoc.</p>
*
* @param maxAge the max age to assign to the underlying rememberMe cookie
*/
public void setCookieMaxAge(int maxAge) {
assertCookieAttribute();
((CookieAttribute) this.identityAttribute).setMaxAge(maxAge);
}
/**
* Passthrough JavaBeans property that will get the underyling rememberMe cookie's 'secure' status.
*
* <p>This method performs a quick <code>CookieAttribute</code> sanity check as described in the class-level JavaDoc.</p>
*
* @return the underlying rememberMe cookie's 'secure' flag
*/
public boolean isCookieSecure() {
assertCookieAttribute();
return ((CookieAttribute) this.identityAttribute).isSecure();
}
/**
* Passthrough JavaBeans property that will set the underyling rememberMe cookie's 'secure' status.
*
* <p>This method performs a quick <code>CookieAttribute</code> sanity check as described in the class-level JavaDoc.</p>
*
* @param secure the 'secure' flag to assign to the underlying rememberMe cookie.
*/
public void setCookieSecure(boolean secure) {
assertCookieAttribute();
((CookieAttribute) this.identityAttribute).setSecure(secure);
}
protected void rememberSerializedIdentity(byte[] serialized) {
ServletRequest request = WebUtils.getRequiredServletRequest();
ServletResponse response = WebUtils.getRequiredServletResponse();
//base 64 encode it and store as a cookie:
String base64 = Base64.encodeToString(serialized);
getIdentityAttribute().storeValue(base64, request, response);
}
protected boolean isIdentityRemoved() {
ServletRequest request = WebUtils.getServletRequest();
if ( request != null ) {
Boolean removed = (Boolean)request.getAttribute(KiHttpServletRequest.IDENTITY_REMOVED_KEY);
return removed != null && removed;
}
return false;
}
protected byte[] getSerializedRememberedIdentity() {
if ( isIdentityRemoved() ) {
return null;
}
ServletRequest request = WebUtils.getRequiredServletRequest();
ServletResponse response = WebUtils.getRequiredServletResponse();
String base64 = getIdentityAttribute().retrieveValue(request, response);
if (base64 != null) {
base64 = ensurePadding(base64);
if (log.isTraceEnabled()) {
log.trace("Acquired Base64 encoded identity [" + base64 + "]");
}
byte[] decoded = Base64.decode(base64);
if (log.isTraceEnabled()) {
log.trace("Base64 decoded byte array length: " + (decoded != null ? decoded.length : 0) + " bytes.");
}
return decoded;
} else {
//no cookie set - new site visitor?
return null;
}
}
/**
* Sometimes a user agent will send the rememberMe cookie value without padding,
* most likely because {@code =} is a separator in the cookie header. Contributed
* by Luis Arias.
*
* @param base64 the base64 encoded String that may need to be padded
* @return the base64 String padded if necessary.
*/
private String ensurePadding(String base64) {
int length = base64.length();
if (length % 4 != 0) {
StringBuffer sb = new StringBuffer(base64);
for (int i = 0; i < length % 4; ++i) {
sb.append('=');
}
base64 = sb.toString();
}
return base64;
}
protected void forgetIdentity() {
ServletRequest request = WebUtils.getRequiredServletRequest();
ServletResponse response = WebUtils.getRequiredServletResponse();
getIdentityAttribute().removeValue(request, response);
}
}