/*
* 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.authentication;
import java.awt.Component;
import java.awt.GridBagLayout;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.plaf.basic.BasicComboBoxRenderer;
import net.sf.json.JSONObject;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.httpclient.Cookie;
import org.apache.log4j.Logger;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.control.Control;
import org.parosproxy.paros.extension.ExtensionHook;
import org.parosproxy.paros.model.Session;
import org.zaproxy.zap.extension.api.ApiDynamicActionImplementor;
import org.zaproxy.zap.extension.api.ApiException;
import org.zaproxy.zap.extension.api.ApiException.Type;
import org.zaproxy.zap.extension.api.ApiResponse;
import org.zaproxy.zap.extension.api.ApiResponseElement;
import org.zaproxy.zap.extension.api.ApiResponseSet;
import org.zaproxy.zap.extension.authentication.AuthenticationAPI;
import org.zaproxy.zap.extension.httpsessions.ExtensionHttpSessions;
import org.zaproxy.zap.extension.httpsessions.HttpSession;
import org.zaproxy.zap.extension.users.ExtensionUserManagement;
import org.zaproxy.zap.extension.users.UsersAPI;
import org.zaproxy.zap.model.Context;
import org.zaproxy.zap.session.CookieBasedSessionManagementMethodType;
import org.zaproxy.zap.session.SessionManagementMethod;
import org.zaproxy.zap.session.WebSession;
import org.zaproxy.zap.users.User;
import org.zaproxy.zap.utils.ApiUtils;
import org.zaproxy.zap.view.LayoutHelper;
/**
* The implementation for an {@link AuthenticationMethodType} where the user manually authenticates and then just
* selects an already authenticated {@link WebSession}.
*/
public class ManualAuthenticationMethodType extends AuthenticationMethodType {
private static final int METHOD_IDENTIFIER = 0;
/** The Authentication method's name. */
private static final String METHOD_NAME = Constant.messages.getString("authentication.method.manual.name");
private static final String API_METHOD_NAME = "manualAuthentication";
/**
* The implementation for an {@link AuthenticationMethod} where the user manually authenticates and then just
* selects an already authenticated {@link WebSession}.
*/
public static class ManualAuthenticationMethod extends AuthenticationMethod {
private int contextId;
public ManualAuthenticationMethod(int contextId) {
super();
this.contextId = contextId;
}
protected int getContextId() {
return contextId;
}
@Override
public boolean isConfigured() {
// Nothing to configure
return true;
}
@Override
public AuthenticationCredentials createAuthenticationCredentials() {
return new ManualAuthenticationCredentials();
}
@Override
public WebSession authenticate(SessionManagementMethod sessionManagementMethod, AuthenticationCredentials credentials,
User user) {
// Check proper type
if (!(credentials instanceof ManualAuthenticationCredentials)) {
Logger.getLogger(ManualAuthenticationMethod.class).error(
"Manual authentication credentials should be used for Manual authentication.");
throw new UnsupportedAuthenticationCredentialsException(
"Manual authentication credentials should be used for Manual authentication.");
}
// Build a new WebSession based on the values from the HttpSession
// TODO: Changes in either the WebSession or the HttpSession are not
// visible in the other
ManualAuthenticationCredentials mc = (ManualAuthenticationCredentials) credentials;
WebSession session = new CookieBasedSessionManagementMethodType.CookieBasedSession();
for (Entry<String, Cookie> v : mc.getSelectedSession().getTokenValuesUnmodifiableMap().entrySet()) {
session.getHttpState().addCookie(v.getValue());
}
return session;
}
@Override
public AuthenticationMethodType getType() {
return new ManualAuthenticationMethodType();
}
@Override
public AuthenticationMethod duplicate() {
return new ManualAuthenticationMethod(contextId);
}
@Override
public void onMethodPersisted() {
// Do nothing
}
@Override
public void onMethodDiscarded() {
// Do nothing
}
@Override
public ApiResponse getApiResponseRepresentation() {
return new ApiResponseElement("methodName", API_METHOD_NAME);
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + contextId;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
ManualAuthenticationMethod other = (ManualAuthenticationMethod) obj;
if (contextId != other.contextId)
return false;
return true;
}
}
/**
* A credentials implementation that allows users to manually select an existing {@link WebSession} that corresponds
* to an already authenticated session.
*/
private static class ManualAuthenticationCredentials implements AuthenticationCredentials {
/**
* The Constant defining the name/type in api calls. Should not be localized.
*/
private static final String API_NAME = "ManualAuthenticationCredentials";
private HttpSession selectedSession;
protected HttpSession getSelectedSession() {
return selectedSession;
}
@Override
public boolean isConfigured() {
return selectedSession != null;
}
protected void setSelectedSession(HttpSession selectedSession) {
this.selectedSession = selectedSession;
}
@Override
public String encode(String parentStringSeparator) {
return Base64.encodeBase64String(selectedSession.getName().getBytes());
}
@Override
public void decode(String encodedCredentials) {
// TODO: Currently, cannot be decoded as HttpSessions are not
// persisted.
throw new IllegalStateException("Manual Authentication Credentials cannot be decoded.");
}
@Override
public ApiResponse getApiResponseRepresentation() {
Map<String, String> values = new HashMap<>();
values.put("type", API_NAME);
values.put("sessionName", selectedSession != null ? selectedSession.getName() : "");
return new ApiResponseSet<String>("credentials", values);
}
}
/**
* The option panel for configuring {@link ManualAuthenticationCredentials} objects.
*/
private static class ManualAuthenticationCredentialsOptionsPanel extends
AbstractCredentialsOptionsPanel<ManualAuthenticationCredentials> {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = -8081914793980311435L;
private static final Logger log = Logger.getLogger(ManualAuthenticationCredentialsOptionsPanel.class);
private JComboBox<HttpSession> sessionsComboBox;
private Context uiSharedContext;
public ManualAuthenticationCredentialsOptionsPanel(ManualAuthenticationCredentials credentials, Context uiSharedContext) {
super(credentials);
this.uiSharedContext = uiSharedContext;
initialize();
}
/**
* Initialize the panel.
*/
@SuppressWarnings("unchecked")
protected void initialize() {
this.setLayout(new GridBagLayout());
JLabel sessionsLabel = new JLabel(Constant.messages.getString("authentication.method.manual.field.session"));
this.add(sessionsLabel, LayoutHelper.getGBC(0, 0, 1, 0.5D));
this.add(getSessionsComboBox(), LayoutHelper.getGBC(1, 0, 1, 0.5D));
this.getSessionsComboBox().setRenderer(new HttpSessionRenderer());
this.add(new JLabel(Constant.messages.getString("authentication.method.manual.field.description")),
LayoutHelper.getGBC(0, 1, 2, 0.0d, 0.0d));
}
/**
* A renderer for properly displaying the name of an HttpSession in a ComboBox.
*/
private static class HttpSessionRenderer extends BasicComboBoxRenderer {
private static final long serialVersionUID = 3654541772447187317L;
@Override
@SuppressWarnings("rawtypes")
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
boolean cellHasFocus) {
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (value != null) {
HttpSession item = (HttpSession) value;
setText(item.getName());
}
return this;
}
}
private JComboBox<HttpSession> getSessionsComboBox() {
if (sessionsComboBox == null) {
ExtensionHttpSessions extensionHttpSessions = (ExtensionHttpSessions) Control.getSingleton().getExtensionLoader()
.getExtension(ExtensionHttpSessions.NAME);
List<HttpSession> sessions = extensionHttpSessions.getHttpSessionsForContext(uiSharedContext);
if (log.isDebugEnabled())
log.debug("Found sessions for Manual Authentication Config: " + sessions);
sessionsComboBox = new JComboBox<>(sessions.toArray(new HttpSession[sessions.size()]));
sessionsComboBox.setSelectedItem(this.getCredentials().getSelectedSession());
}
return sessionsComboBox;
}
@Override
public boolean validateFields() {
if (sessionsComboBox.getSelectedIndex() < 0) {
JOptionPane.showMessageDialog(this,
Constant.messages.getString("authentication.method.manual.dialog.error.nosession.text"),
Constant.messages.getString("authentication.method.manual.dialog.error.title"),
JOptionPane.WARNING_MESSAGE);
sessionsComboBox.requestFocusInWindow();
return false;
}
return true;
}
@Override
public void saveCredentials() {
log.info("Saving Manual Authentication Method: " + getSessionsComboBox().getSelectedItem());
getCredentials().setSelectedSession((HttpSession) getSessionsComboBox().getSelectedItem());
}
}
@Override
public String getName() {
return METHOD_NAME;
}
@Override
public boolean hasOptionsPanel() {
// No options panel for the method
return false;
}
@Override
public boolean hasCredentialsOptionsPanel() {
return true;
}
@Override
public ManualAuthenticationMethod createAuthenticationMethod(int contextId) {
return new ManualAuthenticationMethod(contextId);
}
@Override
public AbstractAuthenticationMethodOptionsPanel buildOptionsPanel(Context uiSharedContext) {
// Not needed
return null;
}
@Override
public AbstractCredentialsOptionsPanel<? extends AuthenticationCredentials> buildCredentialsOptionsPanel(
AuthenticationCredentials credentials, Context uiSharedContext) {
return new ManualAuthenticationCredentialsOptionsPanel((ManualAuthenticationCredentials) credentials, uiSharedContext);
}
@Override
public boolean isTypeForMethod(AuthenticationMethod method) {
return (method instanceof ManualAuthenticationMethod);
}
@Override
public void hook(ExtensionHook extensionHook) {
// Do nothing
}
@Override
public AuthenticationMethod loadMethodFromSession(Session session, int contextId) {
return new ManualAuthenticationMethod(contextId);
}
@Override
public void persistMethodToSession(Session session, int contextId, AuthenticationMethod authMethod) {
// Nothing to persist
}
@Override
public int getUniqueIdentifier() {
return METHOD_IDENTIFIER;
}
@Override
public ManualAuthenticationCredentials createAuthenticationCredentials() {
return new ManualAuthenticationCredentials();
}
public static ManualAuthenticationCredentials createAuthenticationCredentials(HttpSession session) {
ManualAuthenticationCredentials c = new ManualAuthenticationCredentials();
c.setSelectedSession(session);
return c;
}
/* API related constants and methods */
private static final String ACTION_SET_CREDENTIALS = "manualAuthenticationCredentials";
private static final String PARAM_SESSION_NAME = "sessionName";
@Override
public ApiDynamicActionImplementor getSetMethodForContextApiAction() {
return new ApiDynamicActionImplementor(API_METHOD_NAME, null, null) {
@Override
public void handleAction(JSONObject params) throws ApiException {
Context context = ApiUtils.getContextByParamId(params, AuthenticationAPI.PARAM_CONTEXT_ID);
ManualAuthenticationMethod method = createAuthenticationMethod(context.getIndex());
if (!context.getAuthenticationMethod().isSameType(method)) {
apiChangedAuthenticationMethodForContext(context.getIndex());
}
context.setAuthenticationMethod(method);
}
};
}
@Override
public ApiDynamicActionImplementor getSetCredentialsForUserApiAction() {
return new ApiDynamicActionImplementor(ACTION_SET_CREDENTIALS, new String[] { PARAM_SESSION_NAME }, null) {
@Override
public void handleAction(JSONObject params) throws ApiException {
Context context = ApiUtils.getContextByParamId(params, UsersAPI.PARAM_CONTEXT_ID);
int userId = ApiUtils.getIntParam(params, UsersAPI.PARAM_USER_ID);
// Make sure the type of authentication method is compatible
if (!isTypeForMethod(context.getAuthenticationMethod())) {
throw new ApiException(ApiException.Type.ILLEGAL_PARAMETER,
"User's credentials should match authentication method type of the context: "
+ context.getAuthenticationMethod().getType().getName());
}
// NOTE: no need to check if extension is loaded as this method
// is called only if
// the Users
// extension is loaded
ExtensionUserManagement extensionUserManagement = (ExtensionUserManagement) Control.getSingleton()
.getExtensionLoader().getExtension(ExtensionUserManagement.NAME);
User user = extensionUserManagement.getContextUserAuthManager(context.getIndex()).getUserById(userId);
if (user == null) {
throw new ApiException(Type.USER_NOT_FOUND, UsersAPI.PARAM_USER_ID);
}
String sessionName = ApiUtils.getNonEmptyStringParam(params, PARAM_SESSION_NAME);
// Get the matching session
ExtensionHttpSessions extensionHttpSessions = (ExtensionHttpSessions) Control.getSingleton().getExtensionLoader()
.getExtension(ExtensionHttpSessions.NAME);
if (extensionHttpSessions == null) {
throw new ApiException(Type.NO_IMPLEMENTOR, "HttpSessions extension is not loaded.");
}
List<HttpSession> sessions = extensionHttpSessions.getHttpSessionsForContext(context);
HttpSession matchedSession = null;
for (HttpSession session : sessions) {
if (session.getName().equals(sessionName)) {
matchedSession = session;
break;
}
}
if (matchedSession == null) {
throw new ApiException(ApiException.Type.DOES_NOT_EXIST, PARAM_SESSION_NAME);
}
// Set the credentials
ManualAuthenticationCredentials credentials = createAuthenticationCredentials();
credentials.setSelectedSession(matchedSession);
user.setAuthenticationCredentials(credentials);
}
};
}
@Override
public void exportData(Configuration config, AuthenticationMethod authMethod) {
// Nothing to do
}
@Override
public void importData(Configuration config, AuthenticationMethod authMethod) throws ConfigurationException {
// Nothing to do
}
}