/* * 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.wicket.security.strategies; import java.io.Serializable; import java.util.Map; import org.apache.wicket.Application; import org.apache.wicket.Component; import org.apache.wicket.RequestCycle; import org.apache.wicket.Session; import org.apache.wicket.authorization.Action; import org.apache.wicket.authorization.IAuthorizationStrategy; import org.apache.wicket.model.IModel; import org.apache.wicket.security.WaspApplication; import org.apache.wicket.security.WaspSession; import org.apache.wicket.security.actions.WaspAction; import org.apache.wicket.security.actions.WaspActionFactory; import org.apache.wicket.security.authentication.LoginException; import org.apache.wicket.security.checks.ISecurityCheck; import org.apache.wicket.security.components.ISecureComponent; import org.apache.wicket.security.components.SecureComponentHelper; import org.apache.wicket.security.log.AuthorizationErrorKey; import org.apache.wicket.security.log.AuthorizationMessageSource; import org.apache.wicket.security.log.IAuthorizationMessageSource; import org.apache.wicket.security.models.ISecureModel; import org.apache.wicket.util.string.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Base class for every strategy. Checks Authorization and authentication at the class, * component and model levels. * * @author marrink */ public abstract class WaspAuthorizationStrategy implements IAuthorizationStrategy, Serializable { private static final long serialVersionUID = 1L; private static final Logger log = LoggerFactory.getLogger(WaspAuthorizationStrategy.class); /** * Key used to store the {@link IAuthorizationMessageSource} in the * {@link RequestCycle} metadata. */ protected static final AuthorizationErrorKey MESSAGE_KEY = new AuthorizationErrorKey(); private static final ThreadLocal<StrategyResolver> resolver = new ThreadLocal<StrategyResolver>(); /** * Performs the actual authorization check on the component. * * @param component * @param action * @return true if authorized, false otherwise */ public abstract boolean isComponentAuthorized(Component component, WaspAction action); /** * Performs the actual authorization check on the model of the component. * * @param model * the model * @param component * component 'owning' the model if available * @param action * the action to check * @return true if authorized, false otherwise */ public abstract boolean isModelAuthorized(ISecureModel< ? > model, Component component, WaspAction action); /** * Performs the actual authorization check on the component class. * * @param clazz * typically a component * @param action * the action to check * @return true if authorized, false otherwise */ public abstract boolean isClassAuthorized(Class< ? > clazz, WaspAction action); /** * Performs the authentication check. * * @param component * the wicket component * * @return true if the user is authenticated, false otherwise */ public abstract boolean isComponentAuthenticated(Component component); /** * Performs the authentication check. * * @param model * the (secure) model * @param component * the component owning the model * * @return true if the user is authenticated, false otherwise */ public abstract boolean isModelAuthenticated(IModel< ? > model, Component component); /** * Performs the authentication check. * * @param clazz * the class of the wicket component * * @return true if the user is authenticated, false otherwise */ public abstract boolean isClassAuthenticated(Class< ? > clazz); /** * Checks if there is a user logged in at all. This will return true after a * successful {@link #login(Object)} and false after a successful * {@link #logoff(Object)}. Note that in a multi-login scenario this method returns * true until all successful logins are countered with a successful logoff. * * @return true while a user is logged in, false otherwise */ public abstract boolean isUserAuthenticated(); /** * Attempts to log the user in. Note to implementations: It is generally considered a * bad idea to store the context if it contains sensitive data (like a plaintext * password). * * @param context * a not further specified object that provides all the information to log * the user on * @throws LoginException * if the login is unsuccessful */ public abstract void login(Object context) throws LoginException; /** * Log the user off. With the help of a context implementations might facilitate multi * level login / logoff. * * @param context * a not further specified object, might be null * @return true if the logoff was successful, false otherwise */ public abstract boolean logoff(Object context); /** * Called at the end of the sessions lifecycle to perform some clean up. */ public abstract void destroy(); /** * * @see org.apache.wicket.authorization.IAuthorizationStrategy#isActionAuthorized(org.apache.wicket.Component, * org.apache.wicket.authorization.Action) */ public boolean isActionAuthorized(Component component, Action action) { if (component != null) { ISecurityCheck check = getSecurityCheck(component); if (check != null) { if (check.isActionAuthorized(getActionFactory().getAction(action))) return true; IAuthorizationMessageSource message = getMessageSource(); if (message != null) { message.setComponent(component); message.addVariable("wicket.action", action); message.addVariable("wasp.action", getActionFactory().getAction(action)); logMessage(message); } return false; } IModel< ? > model = component.getDefaultModel(); if (model instanceof ISecureModel< ? >) { if (((ISecureModel< ? >) model).isAuthorized(component, getActionFactory() .getAction(action))) return true; IAuthorizationMessageSource message = getMessageSource(); if (message != null) { message.setComponent(component); message.addVariable("wicket.action", action); message.addVariable("wasp.action", getActionFactory().getAction(action)); logMessage(message); } return false; } } return true; } /** * Logs a message indication an action was denied. The message is retrieved through * localization with the following resource key:"authorization.denied" (no quotes). * This method removes the messagesource from the requestcycle. * * @param message * messagesource * @see #logMessage(String, Map, IAuthorizationMessageSource, boolean) */ protected final void logMessage(IAuthorizationMessageSource message) { logMessage("authorization.denied", null, message, true); } /** * Logs a message indication an action was denied. The message is retrieved through * localization with the specified resource key. Optionally the message can be removed * from the requestcycle, if it is not removed the message might be processed multiple * times (which might be what you want if you want to use different resource keys). * This invokes {@link #logMessage(String, Map, IAuthorizationMessageSource)} * * * @param key * the resource key to lookup the message * @param variables * optional map containing additional variables that can be used during the * message lookup * @param message * messagesource * @param remove * flag indicating if the message should be removed or not * @see #removeMessageSource() * @see #logMessage(String, Map, IAuthorizationMessageSource) */ protected final void logMessage(String key, Map<String, Object> variables, IAuthorizationMessageSource message, boolean remove) { if (message == null || Strings.isEmpty(key)) { if (remove) removeMessageSource(); return; } logMessage(key, variables, message); if (remove) removeMessageSource(); } /** * Logs a message indication an action was denied. The message is retrieved through * localization with the specified resource key. This default implementation simply * does something like <code>log.debug(...)</code> Overwrite this method if you want * for example wicket to print feedback messages with something like * <code>Session.get().error(...)</code> * * @param key * the resource key for the message * @param variables * optional map containing additional variables that can be used during * message construction * @param message * the messagesource */ protected void logMessage(String key, Map<String, Object> variables, IAuthorizationMessageSource message) { String msg = message.getMessage(key); if (!Strings.isEmpty(msg)) log.debug(message.substitute(msg, variables)); } /** * Removes the message from the {@link RequestCycle}'s metadata. */ protected final void removeMessageSource() { RequestCycle.get().setMetaData(MESSAGE_KEY, null); } /** * Retrieves the messagesource from the {@link RequestCycle}'s metadata. * * @return the messagesource or null if there is none */ protected final IAuthorizationMessageSource getMessageSource() { return getMessageSource(false); } /** * Retrieves the messagesource from the {@link RequestCycle}'s metadata. optionally * creating a new one if there is not already one. * * @param create * @return the messagesource or null if there is none and the create flag was false */ protected final IAuthorizationMessageSource getMessageSource(boolean create) { IAuthorizationMessageSource resource = null; if (RequestCycle.get() != null) resource = RequestCycle.get().getMetaData(MESSAGE_KEY); if (resource == null && create) { resource = createMessageSource(); RequestCycle.get().setMetaData(MESSAGE_KEY, resource); } return resource; } /** * Creates a new {@link IAuthorizationMessageSource}. Subclasses can override this to * return there own implementation. * * @return a new IErrorMessageSource, never null */ protected IAuthorizationMessageSource createMessageSource() { return new AuthorizationMessageSource(); } /** * Indicates if messages about denied actions should be logged. Default is to use the * slf4 log implementation checking if debug messages should be printed. for example * using log4j as the logging implementation for slf4j logging could be turned on by * putting the following line in your log4.properties</br> <code> * log4j.category.org.apache.wicket.security.strategies.WaspAuthorizationStrategy=DEBUG * </code> * * @return true if messages should be logged, false otherwise */ protected boolean logMessages() { return log.isDebugEnabled(); } /** * We cannot assume everybody uses the here specified public methods to store the * securitycheck, so we check if the component is a ISecureComponent and if so use the * getSecurityCheck on the secure component else we fall back to the * SecureComponentHelper. * * @param component * @return the security check of the component or null if the component does not have * one * @see SecureComponentHelper#getSecurityCheck(Component) */ protected final ISecurityCheck getSecurityCheck(Component component) { if (component instanceof ISecureComponent) return ((ISecureComponent) component).getSecurityCheck(); return SecureComponentHelper.getSecurityCheck(component); } /** * Shortcut to the actionfactory. * * @return the actionfactory from the application */ protected final WaspActionFactory getActionFactory() { return ((WaspApplication) Application.get()).getActionFactory(); } /** * Returns the WaspAuthorizationStrategy. This defaults to * {@link WaspSession#getAuthorizationStrategy()}, but a different implementation can * be registered via a {@link StrategyResolver}. * * @return */ public static WaspAuthorizationStrategy get() { StrategyResolver threadResolver = resolver.get(); return threadResolver == null ? (WaspAuthorizationStrategy) ((WaspSession) Session.get()) .getAuthorizationStrategy() : threadResolver.getStrategy(); } /** * Sets the StrategyResolver for the current thread * * @param threadResolver */ public static void setStrategyResolver(StrategyResolver threadResolver) { resolver.set(threadResolver); } }