/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* Copyright 2013 The ZAP Development Team
*
* Licensed 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.zaproxy.zap.extension.forceduser;
import java.awt.event.ActionEvent;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.ImageIcon;
import javax.swing.JToggleButton;
import org.apache.commons.configuration.Configuration;
import org.apache.log4j.Logger;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.control.Control;
import org.parosproxy.paros.db.RecordContext;
import org.parosproxy.paros.extension.ExtensionAdaptor;
import org.parosproxy.paros.extension.ExtensionHook;
import org.parosproxy.paros.model.Model;
import org.parosproxy.paros.model.Session;
import org.parosproxy.paros.network.HttpMessage;
import org.parosproxy.paros.network.HttpSender;
import org.parosproxy.paros.view.View;
import org.zaproxy.zap.extension.users.ExtensionUserManagement;
import org.zaproxy.zap.model.Context;
import org.zaproxy.zap.model.ContextDataFactory;
import org.zaproxy.zap.network.HttpSenderListener;
import org.zaproxy.zap.users.User;
import org.zaproxy.zap.view.AbstractContextPropertiesPanel;
import org.zaproxy.zap.view.ContextPanelFactory;
import org.zaproxy.zap.view.ZapToggleButton;
/**
* The ForcedUser Extension allows ZAP user to force all requests that correspond to a given Context
* to be sent from the point of view of a User.
*/
public class ExtensionForcedUser extends ExtensionAdaptor implements ContextPanelFactory, HttpSenderListener,
ContextDataFactory {
/** The Constant EXTENSION DEPENDENCIES. */
private static final List<Class<?>> EXTENSION_DEPENDENCIES;
static {
// Prepare a list of Extensions on which this extension depends
List<Class<?>> dependencies = new ArrayList<>(1);
dependencies.add(ExtensionUserManagement.class);
EXTENSION_DEPENDENCIES = Collections.unmodifiableList(dependencies);
}
private static final String FORCED_USER_MODE_OFF_ICON_RESOURCE = "/resource/icon/16/forcedUserOff.png";
private static final String FORCED_USER_MODE_ON_ICON_RESOURCE = "/resource/icon/16/forcedUserOn.png";
private static final String BUTTON_LABEL_ON = Constant.messages.getString("forceduser.toolbar.button.on");
private static final String BUTTON_LABEL_OFF = Constant.messages
.getString("forceduser.toolbar.button.off");
private static final String BUTTON_LABEL_DISABLED = Constant.messages
.getString("forceduser.toolbar.button.disabled");
/** The NAME of the extension. */
public static final String NAME = "ExtensionForcedUser";
/** The Constant log. */
private static final Logger log = Logger.getLogger(ExtensionForcedUser.class);
/** The map of context panels. */
private Map<Integer, ContextForcedUserPanel> contextPanelsMap = new HashMap<>();
/** The map of forced users for each context. */
private Map<Integer, User> contextForcedUsersMap = new HashMap<>();
private ExtensionUserManagement extensionUserManagement;
private boolean forcedUserModeEnabled = false;
private ZapToggleButton forcedUserModeButton;
private ForcedUserAPI api;
/**
* Instantiates a new forced user extension.
*/
public ExtensionForcedUser() {
super();
initialize();
}
/**
* Initialize the extension.
*/
private void initialize() {
this.setName(NAME);
this.setOrder(202);
}
@Override
public void hook(ExtensionHook extensionHook) {
super.hook(extensionHook);
// Register this where needed
Model.getSingleton().addContextDataFactory(this);
if (getView() != null) {
// Factory for generating Session Context UserAuth panels
getView().addContextPanelFactory(this);
View.getSingleton().addMainToolbarButton(getForcedUserModeToggleButton());
}
// Register as Http Sender listener
HttpSender.addListener(this);
// Prepare API
this.api = new ForcedUserAPI(this);
extensionHook.addApiImplementor(api);
}
private void updateForcedUserModeToggleButtonEnabledState() {
if (getView() != null) {
forcedUserModeButton.setSelected(forcedUserModeEnabled);
}
}
protected void setForcedUserModeEnabled(boolean forcedUserModeEnabled) {
this.forcedUserModeEnabled = forcedUserModeEnabled;
updateForcedUserModeToggleButtonEnabledState();
}
private void setForcedUserModeToggleButtonState(boolean enabled) {
if (enabled) {
updateForcedUserModeToggleButtonEnabledState();
this.getForcedUserModeToggleButton().setEnabled(true);
} else {
this.forcedUserModeEnabled = false;
this.getForcedUserModeToggleButton().setSelected(false);
this.getForcedUserModeToggleButton().setEnabled(false);
}
}
private void updateForcedUserModeToggleButtonState() {
if (contextForcedUsersMap.isEmpty()) {
if (this.getForcedUserModeToggleButton().isEnabled())
this.setForcedUserModeToggleButtonState(false);
} else {
if (!this.getForcedUserModeToggleButton().isEnabled())
this.setForcedUserModeToggleButtonState(true);
}
}
private JToggleButton getForcedUserModeToggleButton() {
if (forcedUserModeButton == null) {
forcedUserModeButton = new ZapToggleButton();
forcedUserModeButton.setIcon(new ImageIcon(ExtensionForcedUser.class
.getResource(FORCED_USER_MODE_OFF_ICON_RESOURCE)));
forcedUserModeButton.setSelectedIcon(new ImageIcon(ExtensionForcedUser.class
.getResource(FORCED_USER_MODE_ON_ICON_RESOURCE)));
forcedUserModeButton.setToolTipText(BUTTON_LABEL_OFF);
forcedUserModeButton.setSelectedToolTipText(BUTTON_LABEL_ON);
forcedUserModeButton.setDisabledToolTipText(BUTTON_LABEL_DISABLED);
forcedUserModeButton.setEnabled(false); // Disable until login and one indicator flagged
forcedUserModeButton.addActionListener(new java.awt.event.ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
setForcedUserModeEnabled(getForcedUserModeToggleButton().isSelected());
}
});
}
return forcedUserModeButton;
}
protected ExtensionUserManagement getUserManagementExtension() {
if (extensionUserManagement == null) {
extensionUserManagement = (ExtensionUserManagement) Control.getSingleton().getExtensionLoader()
.getExtension(ExtensionUserManagement.NAME);
}
return extensionUserManagement;
}
public boolean isForcedUserModeEnabled() {
return forcedUserModeEnabled;
}
/**
* Sets the forced user for a context.
*
* @param contextId the context id
* @param user the user
*/
public void setForcedUser(int contextId, User user) {
if (user != null)
this.contextForcedUsersMap.put(contextId, user);
else
this.contextForcedUsersMap.remove(contextId);
this.updateForcedUserModeToggleButtonState();
}
/**
* Sets the forced user for a context, based on the user id.
*
* @param contextId the context id
* @param userId the user id
* @throws IllegalStateException if no user was found that matches the provided id.
*/
public void setForcedUser(int contextId, int userId) throws IllegalStateException {
User user = getUserManagementExtension().getContextUserAuthManager(contextId).getUserById(userId);
if (user == null)
throw new IllegalStateException("No user matching the provided id was found.");
setForcedUser(contextId, user);
}
/**
* Gets the forced user for a context.
*
* @param contextId the context id
* @return the forced user
*/
public User getForcedUser(int contextId) {
return this.contextForcedUsersMap.get(contextId);
}
@Override
public List<Class<?>> getDependencies() {
return EXTENSION_DEPENDENCIES;
}
@Override
public AbstractContextPropertiesPanel getContextPanel(Context context) {
ContextForcedUserPanel panel = this.contextPanelsMap.get(context.getIndex());
if (panel == null) {
panel = new ContextForcedUserPanel(this, context.getIndex());
this.contextPanelsMap.put(context.getIndex(), panel);
}
return panel;
}
@Override
public URL getURL() {
try {
return new URL(Constant.ZAP_HOMEPAGE);
} catch (MalformedURLException e) {
return null;
}
}
@Override
public int getOrder() {
// Make sure we load this extension after the user management extension so that we hook
// after it so that we register as a ContextData factory later so that our loadContextData
// is called after the Users' Extension so that the forced user was already loaded after a
// session loading
return ExtensionUserManagement.EXTENSION_ORDER + 10;
}
@Override
public String getAuthor() {
return Constant.ZAP_TEAM;
}
@Override
public void discardContexts() {
this.contextForcedUsersMap.clear();
this.contextPanelsMap.clear();
// Make sure the status of the toggle button is properly updated when changing the session
updateForcedUserModeToggleButtonState();
}
@Override
public void discardContext(Context ctx) {
this.contextForcedUsersMap.remove(ctx.getIndex());
this.contextPanelsMap.remove(ctx.getIndex());
// Make sure the status of the toggle button is properly updated when changing the session
updateForcedUserModeToggleButtonState();
}
@Override
public int getListenerOrder() {
// Later so any modifications or requested users are visible
return 9998;
}
@Override
public void onHttpRequestSend(HttpMessage msg, int initiator, HttpSender sender) {
if (!forcedUserModeEnabled || msg.getResponseBody() == null || msg.getRequestHeader().isImage()
|| (initiator == HttpSender.AUTHENTICATION_INITIATOR || initiator == HttpSender.CHECK_FOR_UPDATES_INITIATOR)) {
// Not relevant
return;
}
// The message is already being sent from the POV of another user
if (msg.getRequestingUser() != null)
return;
// Is the message in any of the contexts?
List<Context> contexts = Model.getSingleton().getSession().getContexts();
User requestingUser = null;
for (Context context : contexts) {
if (context.isInContext(msg.getRequestHeader().getURI().toString())) {
// Is there enough info
if (contextForcedUsersMap.containsKey(context.getIndex())) {
requestingUser = contextForcedUsersMap.get(context.getIndex());
break;
}
}
}
if (requestingUser == null || !requestingUser.isEnabled())
return;
if (log.isDebugEnabled()) {
log.debug("Modifying request message (" + msg.getRequestHeader().getURI() + ") to match user: "
+ requestingUser);
}
msg.setRequestingUser(requestingUser);
}
@Override
public void onHttpResponseReceive(HttpMessage msg, int initiator, HttpSender sender) {
// Nothing to do
}
@Override
public void loadContextData(Session session, Context context) {
try {
// Load the forced user id for this context
List<String> forcedUserS = session.getContextDataStrings(context.getIndex(),
RecordContext.TYPE_FORCED_USER_ID);
if (forcedUserS != null && forcedUserS.size() > 0) {
int forcedUserId = Integer.parseInt(forcedUserS.get(0));
setForcedUser(context.getIndex(), forcedUserId);
}
} catch (Exception e) {
log.error("Unable to load forced user.", e);
}
}
@Override
public void persistContextData(Session session, Context context) {
try {
// Save only if we have anything to save
if (getForcedUser(context.getIndex()) != null) {
session.setContextData(context.getIndex(), RecordContext.TYPE_FORCED_USER_ID,
Integer.toString(getForcedUser(context.getIndex()).getId()));
// Note: Do not persist whether the 'Forced User Mode' is enabled as there's no need
// for this and the mode can be easily enabled/disabled directly
} else {
// If we don't have a forced user, force deletion of any previous values
session.clearContextDataForType(context.getIndex(), RecordContext.TYPE_FORCED_USER_ID);
}
} catch (Exception e) {
log.error("Unable to persist forced user.", e);
}
}
@Override
public void exportContextData(Context ctx, Configuration config) {
User user = getForcedUser(ctx.getIndex());
if (user != null) {
config.setProperty("context.forceduser", user.getId());
} else {
config.setProperty("context.forceduser", -1);
}
}
@Override
public void importContextData(Context ctx, Configuration config) {
int id = config.getInt("context.forceduser");
if (id >= 0) {
this.setForcedUser(ctx.getIndex(), id);
}
}
/**
* No database tables used, so all supported
*/
@Override
public boolean supportsDb(String type) {
return true;
}
}