/* * 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.users; import java.util.List; import org.apache.commons.codec.binary.Base64; import org.apache.commons.httpclient.HttpState; import org.apache.log4j.Logger; import org.parosproxy.paros.control.Control; import org.parosproxy.paros.model.Model; import org.parosproxy.paros.network.HttpMessage; import org.zaproxy.zap.authentication.AuthenticationCredentials; import org.zaproxy.zap.authentication.AuthenticationMethod; import org.zaproxy.zap.extension.authentication.ExtensionAuthentication; import org.zaproxy.zap.model.Context; import org.zaproxy.zap.session.SessionManagementMethod; import org.zaproxy.zap.session.WebSession; import org.zaproxy.zap.utils.Enableable; /** * ZAP representation of a web application user. */ public class User extends Enableable { /** The Constant log. */ private static final Logger log = Logger.getLogger(User.class); /** The id source. */ private static int ID_SOURCE = 0; /** The Constant FIELD_SEPARATOR used for separating Users's field during serialization. */ private static final String FIELD_SEPARATOR = ";"; /** The id. */ private int id; /** The name. */ private String name; /** The corresponding context id. */ private int contextId; /** The roles corresponding to this user. */ // TODO: Here for future use @SuppressWarnings("unused") private List<Role> roles; /** The authenticated session. */ private WebSession authenticatedSession; /** The authentication credentials that can be used for configuring the user. */ private AuthenticationCredentials authenticationCredentials; /** The extension auth. */ private static ExtensionAuthentication extensionAuth; /** The last successful auth time. */ private long lastSuccessfulAuthTime; /** The context. */ private Context context; /** * Instantiates a new user. * * @param contextId the context id * @param name the name */ public User(int contextId, String name) { super(); this.id = ID_SOURCE++; this.contextId = contextId; this.name = name; } /** * Instantiates a new user. * * @param contextId the context id * @param name the name * @param id the id */ public User(int contextId, String name, int id) { super(); this.id = id; if (this.id >= ID_SOURCE) ID_SOURCE = this.id + 1; this.contextId = contextId; this.name = name; } /** * Gets the name of the user. * * @return the name */ public String getName() { return name; } /** * Gets the context id. * * @return the context id */ public int getContextId() { return contextId; } /* * (non-Javadoc) * * @see java.lang.Object#toString() */ @Override public String toString() { return "User [id=" + id + ", name=" + name + ", contextId=" + contextId + ", enabled=" + isEnabled() + "]"; } /** * Lazy loader for getting the context to which this user corresponds. * * @return the context */ public Context getContext() { if (context == null) { context = Model.getSingleton().getSession().getContext(this.contextId); } return context; } /** * Gets the id. * * @return the id */ public int getId() { return this.id; } /** * Modifies a message so its Request Header/Body matches the web session corresponding to this * user. * * @param message the message */ public void processMessageToMatchUser(HttpMessage message) { // If the user is not yet authenticated, authenticate now // Make sure there are no simultaneous authentications for the same user synchronized (this) { if (this.requiresAuthentication()) { this.authenticate(); if (this.requiresAuthentication()) { log.info("Authentication failed for user: " + name); return; } } } // Modify the message accordingly getContext().getSessionManagementMethod().processMessageToMatchSession(message, authenticatedSession); } /** * Gets the configured authentication credentials of this user. * * @return the authentication credentials */ public AuthenticationCredentials getAuthenticationCredentials() { return authenticationCredentials; } /** * Sets the authentication credentials for the user. These will be used to authenticate the * user, if necessary. * * @param authenticationCredentials the new authentication credentials */ public void setAuthenticationCredentials(AuthenticationCredentials authenticationCredentials) { this.authenticationCredentials = authenticationCredentials; } /** * Checks if an authentication is needed and will be performed at the next call to * {@link #processMessageToMatchUser(HttpMessage)}. * * @return true, if requires authentication */ public boolean requiresAuthentication() { return authenticatedSession == null; } /** * Resets the existing authenticated session, causing subsequent calls to * {@link #processMessageToMatchUser(HttpMessage)} to reauthenticate. * * @param unauthenticatedMessage the unauthenticated message * */ public void queueAuthentication(HttpMessage unauthenticatedMessage) { synchronized (this) { if (unauthenticatedMessage.getTimeSentMillis() >= getLastSuccessfulAuthTime()) authenticatedSession = null; } } /** * Gets the last successful auth time. * * @return the time of last successful authentication */ protected long getLastSuccessfulAuthTime() { return lastSuccessfulAuthTime; } /** * Checks if the response received by the Http Message corresponds to this user. * * @param msg the msg * @return true, if is authenticated */ public boolean isAuthenticated(HttpMessage msg) { return getContext().getAuthenticationMethod().isAuthenticated(msg); } /** * Authenticates the user, using its authentication credentials and the authentication method * corresponding to its Context. * * @see SessionManagementMethod * @see AuthenticationMethod * @see Context */ public void authenticate() { log.info("Authenticating user: " + this.name); WebSession result = getContext().getAuthenticationMethod().authenticate( getContext().getSessionManagementMethod(), this.authenticationCredentials, this); // no issues appear if a simultaneous call to #queueAuthentication() is made synchronized (this) { this.lastSuccessfulAuthTime = System.currentTimeMillis(); this.authenticatedSession = result; } } /** * Gets a reference to the authentication extension. * * @return the authentication extension */ private static ExtensionAuthentication getAuthenticationExtension() { if (extensionAuth == null) { extensionAuth = (ExtensionAuthentication) Control.getSingleton().getExtensionLoader() .getExtension(ExtensionAuthentication.NAME); } return extensionAuth; } /** * Encodes the User in a String. Fields that contain strings are Base64 encoded. * * @param user the user * @return the string */ public static String encode(User user) { StringBuilder out = new StringBuilder(); out.append(user.id).append(FIELD_SEPARATOR); out.append(user.isEnabled()).append(FIELD_SEPARATOR); out.append(Base64.encodeBase64String(user.name.getBytes())).append(FIELD_SEPARATOR); out.append(user.getContext().getAuthenticationMethod().getType().getUniqueIdentifier()).append( FIELD_SEPARATOR); out.append(user.authenticationCredentials.encode(FIELD_SEPARATOR)); if (log.isDebugEnabled()) log.debug("Encoded user: " + out.toString()); return out.toString(); } /** * Decodes an User from an encoded string. The string provided as input should have been * obtained through calls to {@link #encode(User)}. * * @param contextId the ID of the context the user belongs to * @param encodedString the encoded string * @return the user */ public static User decode(int contextId, String encodedString) { // Added proxy call to help in testing return decode(contextId, encodedString, User.getAuthenticationExtension()); } /** * Helper method for decoding an user from an encoded string. See {@link #decode(int, String)}. * * @param contextId the ID of the context the user belongs to * @param encodedString the encoded string * @param authenticationExtension the authentication extension * @return the user */ protected static User decode(int contextId, String encodedString, ExtensionAuthentication authenticationExtension) { String[] pieces = encodedString.split(FIELD_SEPARATOR); User user = null; try { int id = Integer.parseInt(pieces[0]); if (id >= ID_SOURCE) ID_SOURCE = id + 1; boolean enabled = pieces[1].equals("true"); String name = new String(Base64.decodeBase64(pieces[2])); int authTypeId = Integer.parseInt(pieces[3]); user = new User(contextId, name, id); user.setEnabled(enabled); AuthenticationCredentials cred = authenticationExtension .getAuthenticationMethodTypeForIdentifier(authTypeId).createAuthenticationCredentials(); cred.decode(pieces[4]); user.setAuthenticationCredentials(cred); } catch (Exception ex) { log.error("An error occured while decoding user from: " + encodedString, ex); return null; } if (log.isDebugEnabled()) log.debug("Decoded user: " + user); return user; } @Override public int hashCode() { return id; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (this == obj) { return true; } if (getClass() != obj.getClass()) { return false; } User other = (User) obj; if (id != other.id) { return false; } return true; } /** * Sets the name. * * @param name the new name */ public void setName(String name) { this.name = name; } public HttpState getCorrespondingHttpState() { if (authenticatedSession != null) return authenticatedSession.getHttpState(); else return null; } public WebSession getAuthenticatedSession() { return authenticatedSession; } public void setAuthenticatedSession(WebSession session) { this.authenticatedSession = session; } }