/* * 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.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLDecoder; import java.net.URLEncoder; import java.text.MessageFormat; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.swing.DefaultComboBoxModel; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.border.Border; import javax.swing.border.EmptyBorder; import javax.swing.plaf.basic.BasicComboBoxRenderer; import net.sf.json.JSONObject; import org.apache.commons.configuration.Configuration; import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.httpclient.URI; import org.apache.commons.httpclient.URIException; import org.apache.log4j.Logger; import org.parosproxy.paros.Constant; import org.parosproxy.paros.control.Control; import org.parosproxy.paros.db.DatabaseException; import org.parosproxy.paros.db.RecordContext; import org.parosproxy.paros.extension.ExtensionHook; import org.parosproxy.paros.model.Model; import org.parosproxy.paros.model.Session; import org.parosproxy.paros.model.SiteNode; import org.parosproxy.paros.network.HtmlParameter; import org.parosproxy.paros.network.HtmlParameter.Type; import org.parosproxy.paros.network.HttpHeader; import org.parosproxy.paros.network.HttpMalformedHeaderException; import org.parosproxy.paros.network.HttpMessage; import org.parosproxy.paros.network.HttpRequestHeader; import org.parosproxy.paros.network.HttpSender; import org.parosproxy.paros.view.SessionDialog; import org.parosproxy.paros.view.View; import org.zaproxy.zap.authentication.UsernamePasswordAuthenticationCredentials.UsernamePasswordAuthenticationCredentialsOptionsPanel; import org.zaproxy.zap.extension.api.ApiDynamicActionImplementor; import org.zaproxy.zap.extension.api.ApiException; import org.zaproxy.zap.extension.api.ApiResponse; import org.zaproxy.zap.extension.api.ApiResponseSet; import org.zaproxy.zap.extension.authentication.AuthenticationAPI; import org.zaproxy.zap.extension.authentication.ContextAuthenticationPanel; import org.zaproxy.zap.extension.users.ExtensionUserManagement; import org.zaproxy.zap.model.Context; 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.utils.ZapTextField; import org.zaproxy.zap.view.LayoutHelper; import org.zaproxy.zap.view.NodeSelectDialog; import org.zaproxy.zap.view.popup.PopupMenuItemContext; import org.zaproxy.zap.view.popup.PopupMenuItemSiteNodeContextMenuFactory; /** * The implementation for an {@link AuthenticationMethodType} where the Users are authenticated by * posting a form with user and password. */ public class FormBasedAuthenticationMethodType extends AuthenticationMethodType { public static final String CONTEXT_CONFIG_AUTH_FORM = AuthenticationMethod.CONTEXT_CONFIG_AUTH + ".form"; public static final String CONTEXT_CONFIG_AUTH_FORM_LOGINURL = CONTEXT_CONFIG_AUTH_FORM + ".loginurl"; public static final String CONTEXT_CONFIG_AUTH_FORM_LOGINBODY = CONTEXT_CONFIG_AUTH_FORM + ".loginbody"; private static final int METHOD_IDENTIFIER = 2; /** The Authentication method's name. */ private static final String METHOD_NAME = Constant.messages.getString("authentication.method.fb.name"); private static final String API_METHOD_NAME = "formBasedAuthentication"; private static final Logger log = Logger.getLogger(FormBasedAuthenticationMethodType.class); /** * The implementation for an {@link AuthenticationMethod} where the Users are authenticated by * posting a form with user and password. */ public static class FormBasedAuthenticationMethod extends AuthenticationMethod { private static final String ENCODING = "UTF-8"; private static final String LOGIN_ICON_RESOURCE = "/resource/icon/fugue/door-open-green-arrow.png"; public static final String MSG_USER_PATTERN = "{%username%}"; public static final String MSG_PASS_PATTERN = "{%password%}"; private HttpSender httpSender; private SiteNode markedLoginSiteNode; private SiteNode loginSiteNode = null; private String loginRequestURL; private String loginRequestBody; @Override public boolean isConfigured() { // check if the login url is valid return loginRequestURL != null && !loginRequestURL.isEmpty(); } @Override public AuthenticationCredentials createAuthenticationCredentials() { return new UsernamePasswordAuthenticationCredentials(); } @Override public AuthenticationMethodType getType() { return new FormBasedAuthenticationMethodType(); } protected HttpSender getHttpSender() { if (this.httpSender == null) { this.httpSender = new HttpSender(Model.getSingleton().getOptionsParam().getConnectionParam(), true, HttpSender.AUTHENTICATION_INITIATOR); } return httpSender; } /** * Prepares a request message, by filling the appropriate 'username' and 'password' fields * in the request URI and the POST data, if any. * * @param credentials the credentials * @return the HTTP message prepared for authentication * @throws URIException if failed to create the request URI * @throws HttpMalformedHeaderException if the constructed HTTP request is malformed * @throws DatabaseException if an error occurred while reading the request from database */ private HttpMessage prepareRequestMessage(UsernamePasswordAuthenticationCredentials credentials) throws URIException, HttpMalformedHeaderException, DatabaseException { // Replace the username and password in the uri String requestURL = loginRequestURL.replace(MSG_USER_PATTERN, encodeParameter(credentials.getUsername())); requestURL = requestURL.replace(MSG_PASS_PATTERN, encodeParameter(credentials.getPassword())); URI requestURI = new URI(requestURL, false); // Replace the username and password in the post data of the request, if needed String requestBody = null; if (loginRequestBody != null && !loginRequestBody.isEmpty()) { requestBody = loginRequestBody.replace(MSG_USER_PATTERN, encodeParameter(credentials.getUsername())); requestBody = requestBody.replace(MSG_PASS_PATTERN, encodeParameter(credentials.getPassword())); } // Prepare the actual message, either based on the existing one, or create a new one HttpMessage requestMessage; if (this.loginSiteNode != null) { // TODO: What happens if the SiteNode was deleted? requestMessage = loginSiteNode.getHistoryReference().getHttpMessage().cloneRequest(); requestMessage.getRequestHeader().setURI(requestURI); if (requestBody != null) { requestMessage.getRequestBody().setBody(requestBody); requestMessage.getRequestHeader().setHeader(HttpHeader.CONTENT_LENGTH, null); } } else { String method = (requestBody != null) ? HttpRequestHeader.POST : HttpRequestHeader.GET; requestMessage = new HttpMessage(); requestMessage.setRequestHeader( new HttpRequestHeader(method, requestURI, HttpHeader.HTTP10, Model.getSingleton().getOptionsParam().getConnectionParam())); if (requestBody != null) { requestMessage.getRequestBody().setBody(requestBody); } } return requestMessage; } private static String encodeParameter(String parameter) { try { return URLEncoder.encode(parameter, ENCODING); } catch (UnsupportedEncodingException ignore) { // UTF-8 is one of the standard charsets (see StandardCharsets.UTF_8). } return ""; } @Override public WebSession authenticate(SessionManagementMethod sessionManagementMethod, AuthenticationCredentials credentials, User user) throws AuthenticationMethod.UnsupportedAuthenticationCredentialsException { // type check if (!(credentials instanceof UsernamePasswordAuthenticationCredentials)) { throw new UnsupportedAuthenticationCredentialsException( "Form based authentication method only supports " + UsernamePasswordAuthenticationCredentials.class.getSimpleName() + ". Received: " + credentials.getClass()); } UsernamePasswordAuthenticationCredentials cred = (UsernamePasswordAuthenticationCredentials) credentials; if (!cred.isConfigured()) { log.warn("No credentials to authenticate user: " + user.getName()); return null; } // Prepare login message HttpMessage msg; try { msg = prepareRequestMessage(cred); } catch (Exception e) { log.error("Unable to prepare authentication message: " + e.getMessage(), e); return null; } // Make sure the message will be sent with a good WebSession that can record the changes if (user.getAuthenticatedSession() == null) user.setAuthenticatedSession(sessionManagementMethod.createEmptyWebSession()); msg.setRequestingUser(user); // Clear any session identifiers msg.getRequestHeader().setHeader(HttpRequestHeader.COOKIE, null); if (log.isDebugEnabled()) { log.debug("Authentication request header: \n" + msg.getRequestHeader()); if (!msg.getRequestHeader().getMethod().equals(HttpRequestHeader.GET)) log.debug("Authentication request body: \n" + msg.getRequestBody()); } // Send the authentication message try { getHttpSender().sendAndReceive(msg); } catch (IOException e) { log.error("Unable to send authentication message: " + e.getMessage()); return null; } if (this.isAuthenticated(msg)) { // Let the user know it worked AuthenticationHelper.notifyOutputAuthSuccessful(msg); } else { // Let the user know it failed AuthenticationHelper.notifyOutputAuthFailure(msg); } // Add message to history AuthenticationHelper.addAuthMessageToHistory(msg); // Return the web session as extracted by the session management method return sessionManagementMethod.extractWebSession(msg); } /** * Sets the login request as being an existing SiteNode. * * @param loginSiteNode the new login request * @throws Exception if an error occurred while obtaining the message from the node */ public void setLoginRequest(SiteNode loginSiteNode) throws Exception { this.loginSiteNode = loginSiteNode; HttpMessage requestMessage = loginSiteNode.getHistoryReference().getHttpMessage(); this.loginRequestURL = requestMessage.getRequestHeader().getURI().toString(); if (!requestMessage.getRequestHeader().getMethod().equalsIgnoreCase(HttpRequestHeader.GET)) { this.loginRequestBody = requestMessage.getRequestBody().toString(); } else { this.loginRequestBody = null; } } /** * Gets the login request url. * * @return the login request url */ public String getLoginRequestURL() { return loginRequestURL; } /** * Marks the provided Site Login as being a Login request. If {@code null} is provided, no * site node will be marked as login request (for the {@link Context} corresponding to this * AuthenticationMethod). * * @param sn the new login site node */ private void markLoginSiteNode(SiteNode sn) { // No need for resetting everything up if it's already the right node if (this.markedLoginSiteNode == sn) { return; } if (this.markedLoginSiteNode != null) { this.markedLoginSiteNode.removeCustomIcon(LOGIN_ICON_RESOURCE); } this.markedLoginSiteNode = sn; if (sn == null) { return; } sn.addCustomIcon(LOGIN_ICON_RESOURCE, false); } /** * Sets the login request, based on a given url and, if needed, post data. If post data is * provided, the assumed HTTP method is POST. * <p> * If there is a SiteNode that matches the URL and post data (with the exception of the * 'username' and 'password' parameters), it is marked as the 'Login' site node. * </p> * * @param url the url * @param postData the post data, or {@code null} if the request should be a GET one * @throws Exception the exception */ protected void setLoginRequest(String url, String postData) throws Exception { if (url == null || url.length() == 0) { this.loginRequestURL = null; this.loginRequestBody = null; this.loginSiteNode = null; } else { String method = HttpRequestHeader.GET; if (postData != null && postData.length() > 0) { method = HttpRequestHeader.POST; } URI uri = new URI(url, true); this.loginRequestURL = url; this.loginRequestBody = postData; // Note: The findNode just checks the parameter names, not their values // Note: No need to make sure the other parameters (besides user/password) are the // same, as POSTs with different values are not delimited in the SitesTree anyway // Note: Set the login site node anyway (even if null), to make sure any previously // marked SiteNode is unmarked this.loginSiteNode = Model.getSingleton().getSession().getSiteTree() .findNode(uri, method, postData); } } @Override public String toString() { return "FormBasedAuthenticationMethod [loginURI=" + loginRequestURL + "]"; } @Override public FormBasedAuthenticationMethod duplicate() { FormBasedAuthenticationMethod clonedMethod = new FormBasedAuthenticationMethod(); clonedMethod.loginRequestURL = this.loginRequestURL; clonedMethod.loginRequestBody = this.loginRequestBody; clonedMethod.loginSiteNode = this.loginSiteNode; clonedMethod.markedLoginSiteNode = this.markedLoginSiteNode; return clonedMethod; } @Override public void onMethodPersisted() { markLoginSiteNode(loginSiteNode); } @Override public void onMethodDiscarded() { markLoginSiteNode(null); } @Override public ApiResponse getApiResponseRepresentation() { Map<String, String> values = new HashMap<>(); values.put("methodName", API_METHOD_NAME); values.put("loginUrl", loginRequestURL); values.put("loginRequestData", this.loginRequestBody); return new ApiResponseSet<String>("method", values); } @Override public int hashCode() { final int prime = 31; int result = super.hashCode(); result = prime * result + ((loginRequestBody == null) ? 0 : loginRequestBody.hashCode()); result = prime * result + ((loginRequestURL == null) ? 0 : loginRequestURL.hashCode()); 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; FormBasedAuthenticationMethod other = (FormBasedAuthenticationMethod) obj; if (loginRequestBody == null) { if (other.loginRequestBody != null) return false; } else if (!loginRequestBody.equals(other.loginRequestBody)) return false; if (loginRequestURL == null) { if (other.loginRequestURL != null) return false; } else if (!loginRequestURL.equals(other.loginRequestURL)) return false; return true; } } /** * The Options Panel used for configuring a {@link FormBasedAuthenticationMethod}. */ private static class FormBasedAuthenticationMethodOptionsPanel extends AbstractAuthenticationMethodOptionsPanel { private static final long serialVersionUID = -9010956260384814566L; private static final String POST_DATA_LABEL = Constant.messages .getString("authentication.method.fb.field.label.postData"); private static final String USERNAME_PARAM_LABEL = Constant.messages .getString("authentication.method.fb.field.label.usernameParam"); private static final String PASSWORD_PARAM_LABEL = Constant.messages .getString("authentication.method.fb.field.label.passwordParam"); private static final String LOGIN_URL_LABEL = Constant.messages .getString("authentication.method.fb.field.label.loginUrl"); private static final String AUTH_DESCRIPTION = Constant.messages .getString("authentication.method.fb.field.label.description"); private ZapTextField loginUrlField; private ZapTextField postDataField; private JComboBox<HtmlParameter> usernameParameterCombo; private JComboBox<HtmlParameter> passwordParameterCombo; private FormBasedAuthenticationMethod authenticationMethod; private Context context; private ExtensionUserManagement userExt = null; public FormBasedAuthenticationMethodOptionsPanel(Context context) { super(); initialize(); this.context = context; } @SuppressWarnings("unchecked") private void initialize() { this.setLayout(new GridBagLayout()); this.add(new JLabel(LOGIN_URL_LABEL), LayoutHelper.getGBC(0, 0, 2, 1.0d, 0.0d)); JPanel urlSelectPanel = new JPanel(new GridBagLayout()); this.loginUrlField = new ZapTextField(); this.postDataField = new ZapTextField(); JButton selectButton = new JButton(Constant.messages.getString("all.button.select")); selectButton.setIcon(new ImageIcon(View.class.getResource("/resource/icon/16/094.png"))); // Globe // Add behaviour for Node Select dialog selectButton.addActionListener(new java.awt.event.ActionListener() { @Override public void actionPerformed(java.awt.event.ActionEvent e) { NodeSelectDialog nsd = new NodeSelectDialog(View.getSingleton().getMainFrame()); // Try to pre-select the node according to what has been inserted in the fields SiteNode node = null; if (loginUrlField.getText().trim().length() > 0) try { // If it's a POST query if (postDataField.getText().trim().length() > 0) node = Model .getSingleton() .getSession() .getSiteTree() .findNode(new URI(loginUrlField.getText(), false), HttpRequestHeader.POST, postDataField.getText()); else node = Model.getSingleton().getSession().getSiteTree() .findNode(new URI(loginUrlField.getText(), false)); } catch (Exception e2) { // Ignore. It means we could not properly get a node for the existing // value and does not have any harmful effects } // Show the dialog and wait for input node = nsd.showDialog(node); if (node != null && node.getHistoryReference() != null) { try { if (log.isInfoEnabled()) { log.info("Selected Form Based Auth Login URL via dialog: " + node.getHistoryReference().getURI().toString()); } loginUrlField.setText(node.getHistoryReference().getURI().toString()); postDataField.setText(node.getHistoryReference().getHttpMessage() .getRequestBody().toString()); updateParameters(); } catch (Exception e1) { log.error(e1.getMessage(), e1); } } } }); urlSelectPanel.add(this.loginUrlField, LayoutHelper.getGBC(0, 0, 1, 1.0D)); urlSelectPanel.add(selectButton, LayoutHelper.getGBC(1, 0, 1, 0.0D)); this.add(urlSelectPanel, LayoutHelper.getGBC(0, 1, 2, 1.0d, 0.0d)); this.add(new JLabel(POST_DATA_LABEL), LayoutHelper.getGBC(0, 2, 2, 1.0d, 0.0d)); this.add(this.postDataField, LayoutHelper.getGBC(0, 3, 2, 1.0d, 0.0d)); this.add(new JLabel(USERNAME_PARAM_LABEL), LayoutHelper.getGBC(0, 4, 1, 1.0d, 0.0d)); this.usernameParameterCombo = new JComboBox<>(); this.usernameParameterCombo.setRenderer(new HtmlParameterRenderer()); this.add(usernameParameterCombo, LayoutHelper.getGBC(0, 5, 1, 1.0d, 0.0d)); this.add(new JLabel(PASSWORD_PARAM_LABEL), LayoutHelper.getGBC(1, 4, 1, 1.0d, 0.0d)); this.passwordParameterCombo = new JComboBox<>(); this.passwordParameterCombo.setRenderer(new HtmlParameterRenderer()); this.add(passwordParameterCombo, LayoutHelper.getGBC(1, 5, 1, 1.0d, 0.0d)); this.add(new JLabel(AUTH_DESCRIPTION), LayoutHelper.getGBC(0, 8, 2, 1.0d, 0.0d)); // Make sure we update the parameters when something has been changed in the // postDataField this.postDataField.addFocusListener(new FocusListener() { @Override public void focusLost(FocusEvent e) { updateParameters(); } @Override public void focusGained(FocusEvent e) { } }); } @Override public void validateFields() { try { new URL(loginUrlField.getText()); } catch (Exception ex) { loginUrlField.requestFocusInWindow(); throw new IllegalStateException( Constant.messages.getString("authentication.method.fb.dialog.error.url.text")); } } private String replaceParameterValue(String originalString, HtmlParameter parameter, String replaceString) { String keyValueSeparator = context.getPostParamParser().getDefaultKeyValueSeparator(); String nameAndSeparator = parameter.getName() + keyValueSeparator; // Make sure we handle the case when there's only the parameter name in the POST data instead of // parameter name + separator + value (e.g. just 'param1&...' instead of 'param1=...&...') if (originalString.contains(nameAndSeparator)) return originalString.replace(nameAndSeparator + parameter.getValue(), nameAndSeparator + replaceString); else return originalString.replace(parameter.getName(), nameAndSeparator + replaceString); } private ExtensionUserManagement getUserExt() { if (userExt == null) { userExt = (ExtensionUserManagement) Control.getSingleton().getExtensionLoader().getExtension(ExtensionUserManagement.NAME); } return userExt; } @Override public void saveMethod() { try { String postData = postDataField.getText(); if (!postData.isEmpty()) { HtmlParameter userParam = (HtmlParameter) usernameParameterCombo.getSelectedItem(); HtmlParameter passwdParam = (HtmlParameter) passwordParameterCombo.getSelectedItem(); ExtensionUserManagement userExt = getUserExt(); if (userExt != null && userExt.getUIConfiguredUsers(context.getIndex()).size() == 0) { if (! userParam.getValue().contains(FormBasedAuthenticationMethod.MSG_USER_PATTERN) && ! passwdParam.getValue().contains(FormBasedAuthenticationMethod.MSG_PASS_PATTERN)) { // Add the user based on the details provided // Note that right now application/x-www-form-urlencoded forms are supported String userStr = URLDecoder.decode(userParam.getValue(), "UTF8"); String passwdStr = URLDecoder.decode(passwdParam.getValue(), "UTF8"); User user = new User(context.getIndex(), userStr); UsernamePasswordAuthenticationCredentials upac = new UsernamePasswordAuthenticationCredentials(userStr, passwdStr); user.setAuthenticationCredentials(upac); getUserExt().getContextUserAuthManager(context.getIndex()).addUser(user); } } postData = this.replaceParameterValue(postData, userParam, FormBasedAuthenticationMethod.MSG_USER_PATTERN); postData = this.replaceParameterValue(postData, passwdParam, FormBasedAuthenticationMethod.MSG_PASS_PATTERN); } getMethod().setLoginRequest(loginUrlField.getText(), postData); } catch (Exception e) { log.error(e.getMessage(), e); } } @Override public void bindMethod(AuthenticationMethod method) { this.authenticationMethod = (FormBasedAuthenticationMethod) method; this.loginUrlField.setText(authenticationMethod.loginRequestURL); this.postDataField.setText(authenticationMethod.loginRequestBody); updateParameters(); } /** * Gets the index of the parameter with a given value. * * @param params the params * @param value the value * @return the index of param with value, or -1 if no match was found */ private int getIndexOfParamWithValue(HtmlParameter[] params, String value) { for (int i = 0; i < params.length; i++) if (params[i].getValue().equals(value)) return i; return -1; } private void updateParameters() { try { Map<String, String> params = this.context.getPostParamParser().parse( this.postDataField.getText()); HtmlParameter[] paramsArray = mapToParamArray(params); this.usernameParameterCombo.setModel(new DefaultComboBoxModel<>(paramsArray)); this.passwordParameterCombo.setModel(new DefaultComboBoxModel<>(paramsArray)); int index = getIndexOfParamWithValue(paramsArray, FormBasedAuthenticationMethod.MSG_USER_PATTERN); if (index >= 0) { this.usernameParameterCombo.setSelectedIndex(index); } index = getIndexOfParamWithValue(paramsArray, FormBasedAuthenticationMethod.MSG_PASS_PATTERN); if (index >= 0) { this.passwordParameterCombo.setSelectedIndex(index); } } catch (Exception e) { log.error(e.getMessage(), e); } } private HtmlParameter[] mapToParamArray(Map<String, String> map) { HtmlParameter[] array = new HtmlParameter[map.size()]; int i = 0; for (Entry<String, String> param : map.entrySet()) { array[i++] = new HtmlParameter(Type.form, param.getKey(), param.getValue()); } return array; } @Override public FormBasedAuthenticationMethod getMethod() { return this.authenticationMethod; } } /** * A renderer for properly displaying the name of an HtmlParameter in a ComboBox. */ private static class HtmlParameterRenderer extends BasicComboBoxRenderer { private static final long serialVersionUID = 3654541772447187317L; private static final Border BORDER = new EmptyBorder(2, 3, 3, 3); @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) { setBorder(BORDER); HtmlParameter item = (HtmlParameter) value; setText(item.getName()); } return this; } } @Override public FormBasedAuthenticationMethod createAuthenticationMethod(int contextId) { return new FormBasedAuthenticationMethod(); } @Override public String getName() { return METHOD_NAME; } @Override public AbstractAuthenticationMethodOptionsPanel buildOptionsPanel(Context uiSharedContext) { return new FormBasedAuthenticationMethodOptionsPanel(uiSharedContext); } @Override public boolean hasOptionsPanel() { return true; } @Override public AbstractCredentialsOptionsPanel<? extends AuthenticationCredentials> buildCredentialsOptionsPanel( AuthenticationCredentials credentials, Context uiSharedContext) { return new UsernamePasswordAuthenticationCredentialsOptionsPanel( (UsernamePasswordAuthenticationCredentials) credentials); } @Override public boolean hasCredentialsOptionsPanel() { return true; } @Override public boolean isTypeForMethod(AuthenticationMethod method) { return (method instanceof FormBasedAuthenticationMethod); } @Override public void hook(ExtensionHook extensionHook) { extensionHook.getHookMenu().addPopupMenuItem(getPopupFlagLoginRequestMenuFactory()); } /** * Gets the popup menu factory for flagging login requests. * * @return the popup flag login request menu factory */ private PopupMenuItemSiteNodeContextMenuFactory getPopupFlagLoginRequestMenuFactory() { PopupMenuItemSiteNodeContextMenuFactory popupFlagLoginRequestMenuFactory = new PopupMenuItemSiteNodeContextMenuFactory( Constant.messages.getString("context.flag.popup")) { private static final long serialVersionUID = 8927418764L; @Override public PopupMenuItemContext getContextMenu(Context context, String parentMenu) { return new PopupMenuItemContext(context, parentMenu, MessageFormat.format( Constant.messages.getString("authentication.method.fb.popup.login.request"), context.getName())) { private static final long serialVersionUID = 1967885623005183801L; private ExtensionUserManagement usersExtension; private Context uiSharedContext; /** * Make sure the user acknowledges the Users corresponding to this context will * be deleted. * * @return true, if successful */ private boolean confirmUsersDeletion(Context uiSharedContext) { usersExtension = (ExtensionUserManagement) Control.getSingleton() .getExtensionLoader().getExtension(ExtensionUserManagement.NAME); if (usersExtension != null) { if (usersExtension.getSharedContextUsers(uiSharedContext).size() > 0) { int choice = JOptionPane.showConfirmDialog(this, Constant.messages .getString("authentication.dialog.confirmChange.label"), Constant.messages .getString("authentication.dialog.confirmChange.title"), JOptionPane.OK_CANCEL_OPTION); if (choice == JOptionPane.CANCEL_OPTION) { return false; } } } return true; } @Override public void performAction(SiteNode sn) { // Manually create the UI shared contexts so any modifications are done // on an UI shared Context, so changes can be undone by pressing Cancel SessionDialog sessionDialog = View.getSingleton().getSessionDialog(); sessionDialog.recreateUISharedContexts(Model.getSingleton().getSession()); uiSharedContext = sessionDialog.getUISharedContext(this.getContext().getIndex()); // Do the work/changes on the UI shared context if (this.getContext().getAuthenticationMethod() instanceof FormBasedAuthenticationMethod) { log.info("Selected new login request via PopupMenu. Changing existing Form-Based Authentication instance for Context " + getContext().getIndex()); FormBasedAuthenticationMethod method = (FormBasedAuthenticationMethod) uiSharedContext .getAuthenticationMethod(); try { method.setLoginRequest(sn); } catch (Exception e) { log.error("Failed to set login request: " + e.getMessage(), e); return; } // Show the session dialog without recreating UI Shared contexts View.getSingleton() .showSessionDialog( Model.getSingleton().getSession(), ContextAuthenticationPanel .buildName(this.getContext().getIndex()), false); } else { log.info("Selected new login request via PopupMenu. Creating new Form-Based Authentication instance for Context " + getContext().getIndex()); FormBasedAuthenticationMethod method = new FormBasedAuthenticationMethod(); try { method.setLoginRequest(sn); } catch (Exception e) { log.error("Failed to set login request: " + e.getMessage(), e); return; } if (!confirmUsersDeletion(uiSharedContext)) { log.debug("Cancelled change of authentication type."); return; } uiSharedContext.setAuthenticationMethod(method); // Show the session dialog without recreating UI Shared contexts // NOTE: First init the panels of the dialog so old users data gets // loaded and just then delete the users // from the UI data model, otherwise the 'real' users from the // non-shared context would be loaded // and would override any deletions made. View.getSingleton().showSessionDialog(Model.getSingleton().getSession(), ContextAuthenticationPanel.buildName(this.getContext().getIndex()), false, new Runnable() { @Override public void run() { // Removing the users from the 'shared context' (the UI) // will cause their removal at // save as well if (usersExtension != null) usersExtension.removeSharedContextUsers(uiSharedContext); } }); } } }; } @Override public int getParentMenuIndex() { return 3; } }; return popupFlagLoginRequestMenuFactory; } @Override public AuthenticationMethod loadMethodFromSession(Session session, int contextId) throws DatabaseException { FormBasedAuthenticationMethod method = new FormBasedAuthenticationMethod(); List<String> urls = session.getContextDataStrings(contextId, RecordContext.TYPE_AUTH_METHOD_FIELD_1); String url = ""; if (urls != null && urls.size() > 0) { url = urls.get(0); } List<String> postDatas = session.getContextDataStrings(contextId, RecordContext.TYPE_AUTH_METHOD_FIELD_2); String postData = null; if (postDatas != null && postDatas.size() > 0) { postData = postDatas.get(0); } try { method.setLoginRequest(url, postData); } catch (Exception e) { log.error("Unable to load FormBasedAuthenticationMethod. ", e); } return method; } @Override public void persistMethodToSession(Session session, int contextId, AuthenticationMethod authMethod) throws DatabaseException { if (!(authMethod instanceof FormBasedAuthenticationMethod)) { throw new UnsupportedAuthenticationMethodException( "Form based authentication type only supports: " + FormBasedAuthenticationMethod.class); } FormBasedAuthenticationMethod method = (FormBasedAuthenticationMethod) authMethod; session.setContextData(contextId, RecordContext.TYPE_AUTH_METHOD_FIELD_1, method.loginRequestURL); session.setContextData(contextId, RecordContext.TYPE_AUTH_METHOD_FIELD_2, method.loginRequestBody); } @Override public int getUniqueIdentifier() { return METHOD_IDENTIFIER; } @Override public UsernamePasswordAuthenticationCredentials createAuthenticationCredentials() { return new UsernamePasswordAuthenticationCredentials(); } /* API related constants and methods. */ private static final String PARAM_LOGIN_URL = "loginUrl"; private static final String PARAM_LOGIN_REQUEST_DATA = "loginRequestData"; @Override public ApiDynamicActionImplementor getSetMethodForContextApiAction() { return new ApiDynamicActionImplementor(API_METHOD_NAME, new String[] { PARAM_LOGIN_URL }, new String[] { PARAM_LOGIN_REQUEST_DATA }) { @Override public void handleAction(JSONObject params) throws ApiException { Context context = ApiUtils.getContextByParamId(params, AuthenticationAPI.PARAM_CONTEXT_ID); String loginUrl = ApiUtils.getNonEmptyStringParam(params, PARAM_LOGIN_URL); try { new URL(loginUrl); } catch (Exception ex) { throw new ApiException(ApiException.Type.ILLEGAL_PARAMETER, PARAM_LOGIN_URL); } String postData = ""; if (params.containsKey(PARAM_LOGIN_REQUEST_DATA)) { postData = params.getString(PARAM_LOGIN_REQUEST_DATA); } // Set the method FormBasedAuthenticationMethod method = createAuthenticationMethod(context.getIndex()); try { method.setLoginRequest(loginUrl, postData); } catch (Exception e) { throw new ApiException(ApiException.Type.INTERNAL_ERROR, e.getMessage()); } if (!context.getAuthenticationMethod().isSameType(method)) apiChangedAuthenticationMethodForContext(context.getIndex()); context.setAuthenticationMethod(method); } }; } @Override public ApiDynamicActionImplementor getSetCredentialsForUserApiAction() { return UsernamePasswordAuthenticationCredentials.getSetCredentialsForUserApiAction(this); } @Override public void exportData(Configuration config, AuthenticationMethod authMethod) { if (!(authMethod instanceof FormBasedAuthenticationMethod)) { throw new UnsupportedAuthenticationMethodException( "Form based authentication type only supports: " + FormBasedAuthenticationMethod.class.getName()); } FormBasedAuthenticationMethod method = (FormBasedAuthenticationMethod) authMethod; config.setProperty(CONTEXT_CONFIG_AUTH_FORM_LOGINURL, method.loginRequestURL); config.setProperty(CONTEXT_CONFIG_AUTH_FORM_LOGINBODY, method.loginRequestBody); } @Override public void importData(Configuration config, AuthenticationMethod authMethod) throws ConfigurationException { if (!(authMethod instanceof FormBasedAuthenticationMethod)) { throw new UnsupportedAuthenticationMethodException( "Form based authentication type only supports: " + FormBasedAuthenticationMethod.class.getName()); } FormBasedAuthenticationMethod method = (FormBasedAuthenticationMethod) authMethod; try { method.setLoginRequest(config.getString(CONTEXT_CONFIG_AUTH_FORM_LOGINURL), config.getString(CONTEXT_CONFIG_AUTH_FORM_LOGINBODY)); } catch (Exception e) { throw new ConfigurationException(e); } } }