package com.eviware.soapui.support.editor.inspectors.auth; import com.eviware.soapui.SoapUI; import com.eviware.soapui.config.AccessTokenStatusConfig; import com.eviware.soapui.impl.rest.OAuth1Profile; import com.eviware.soapui.support.MessageSupport; import com.eviware.soapui.support.UISupport; import com.eviware.soapui.support.components.SimpleBindingForm; import com.eviware.soapui.support.components.SimpleForm; import com.eviware.soapui.support.editor.inspectors.AbstractXmlInspector; import com.jgoodies.binding.PresentationModel; import com.jgoodies.binding.adapter.Bindings; import javax.annotation.Nonnull; import javax.swing.AbstractAction; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Font; import java.awt.Insets; import java.awt.MouseInfo; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.event.WindowFocusListener; public class OAuth1Form extends AbstractAuthenticationForm implements OAuth1TokenStatusChangeListener { public static final String ADVANCED_OPTIONS_BUTTON_NAME = "Advanced..."; static final ImageIcon SUCCESS_ICON = UISupport.createImageIcon("/check.png"); static final ImageIcon FAIL_ICON = UISupport.createImageIcon("/alert.png"); private static final MessageSupport messages = MessageSupport.getMessages(OAuth1Form.class); private static final int ACCESS_TOKEN_DIALOG_HORIZONTAL_OFFSET = 120; private static final Dimension HORIZONTAL_SPACING_IN_ACCESS_TOKEN_ROW = new Dimension(5, 0); private static final String ACCESS_TOKEN_LABEL = "Access Token"; private static final String TOKEN_SECRET_LABEL = "Token Secret"; private static final Insets TOKEN_FIELD_INSETS = new Insets(5, 5, 5, 5); private static final float TOKEN_STATUS_TEXT_FONT_SCALE = 0.95f; private static final String GET_ACCESS_TOKEN_BUTTON_DEFAULT_LABEL = "Get Token"; private final Color DEFAULT_COLOR = Color.WHITE; private final Color SUCCESS_COLOR = new Color(0xccffcb); private final Color FAIL_COLOR = new Color(0xffcccc); private final AbstractXmlInspector inspector; private final OAuth1TokenStatusChangeManager statusChangeManager; private OAuth1Profile profile; private JPanel formPanel; private boolean disclosureButtonDisabled; private boolean isMouseOnDisclosureLabel; private SimpleBindingForm oAuth1Form; private JTextField accessTokenField; private JLabel accessTokenStatusIcon; private JLabel accessTokenStatusText; private JTextField secretTokenField; private JLabel secretTokenStatusIcon; private JLabel secretTokenStatusText; private JLabel disclosureButton; private OAuth1GetTokenForm accessTokenForm; private SoapUIMainWindowFocusListener mainWindowFocusListener; public OAuth1Form(OAuth1Profile profile, AbstractXmlInspector inspector) { super(); this.profile = profile; this.inspector = inspector; statusChangeManager = new OAuth1TokenStatusChangeManager(this); statusChangeManager.register(); } void release() { SoapUI.getFrame().removeWindowFocusListener(mainWindowFocusListener); accessTokenForm.release(); oAuth1Form.getPresentationModel().release(); statusChangeManager.unregister(); } @Override public void onAccessTokenStatusChanged(@Nonnull AccessTokenStatusConfig.Enum status) { setAccessTokenStatusFeedback(status); } @Override public void onTokenSecretStatusChanged(@Nonnull AccessTokenStatusConfig.Enum status) { setSecretTokenStatusFeedback(status); } @Nonnull @Override public OAuth1Profile getProfile() { return profile; } @Override protected JPanel buildUI() { oAuth1Form = new SimpleBindingForm(new PresentationModel<OAuth1Profile>(profile)); addOAuth2Panel(oAuth1Form); if (profile.getAccessTokenStatus() != AccessTokenStatusConfig.RETRIEVAL_CANCELED) { profile.resetAccessTokenStatusToStartingStatus(); } setAccessTokenStatusFeedback(profile.getAccessTokenStatus()); setSecretTokenStatusFeedback(profile.getTokenSecretStatus()); return formPanel; } private void addOAuth2Panel(SimpleBindingForm oAuth2Form) { populateOAuth1Form(oAuth2Form); formPanel = new JPanel(new BorderLayout()); JPanel centerPanel = oAuth2Form.getPanel(); setBackgroundColorOnPanel(centerPanel); JPanel southPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); //TODO: add help link //JLabel oAuthDocumentationLink = UISupport.createLabelLink(HelpUrls.OAUTH_OVERVIEW, "Learn about OAuth 2"); //southPanel.add(oAuthDocumentationLink); //southPanel.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, CARD_BORDER_COLOR)); setBackgroundColorOnPanel(southPanel); formPanel.add(centerPanel, BorderLayout.CENTER); formPanel.add(southPanel, BorderLayout.SOUTH); setBorderOnPanel(formPanel); } private void populateOAuth1Form(SimpleBindingForm oAuth1Form) { initForm(oAuth1Form); oAuth1Form.addSpace(TOP_SPACING); accessTokenField = createTokenField("accessToken"); accessTokenStatusIcon = createTokenStatusIcon(); accessTokenStatusText = createAccessTokenStatusText(); JPanel accessTokenRowPanel = createAccessTokenRowPanel(accessTokenField, accessTokenStatusIcon, accessTokenStatusText); oAuth1Form.append(ACCESS_TOKEN_LABEL, accessTokenRowPanel); oAuth1Form.addInputFieldHintText(messages.get("OAuth1Form.AccessTokenField.Hint")); disclosureButton = new JLabel(GET_ACCESS_TOKEN_BUTTON_DEFAULT_LABEL); disclosureButton.setIcon(UISupport.createImageIcon("/pop-down-open.png")); disclosureButton.setName("oAuth2DisclosureButton"); oAuth1Form.addComponentWithoutLabel(disclosureButton); secretTokenField = createTokenField("tokenSecret"); secretTokenStatusIcon = createTokenStatusIcon(); secretTokenStatusText = createAccessTokenStatusText(); JPanel secretTokenRowPanel = createAccessTokenRowPanel(secretTokenField, secretTokenStatusIcon, secretTokenStatusText); oAuth1Form.append(TOKEN_SECRET_LABEL, secretTokenRowPanel); oAuth1Form.addInputFieldHintText(messages.get("OAuth1Form.SecretTokenField.Hint")); JButton advancedOptionsButton = new JButton(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { new OAuth1AdvancedOptionsDialog(profile); } }); advancedOptionsButton.setText(ADVANCED_OPTIONS_BUTTON_NAME); oAuth1Form.addLeftComponent(advancedOptionsButton); accessTokenForm = new OAuth1GetTokenForm(profile); final JDialog accessTokenFormDialog = accessTokenForm.getComponent(); disclosureButton.addMouseListener(new DisclosureButtonMouseListener(accessTokenFormDialog, disclosureButton)); accessTokenFormDialog.addWindowFocusListener(new AccessTokenFormDialogWindowListener(accessTokenFormDialog)); mainWindowFocusListener = new SoapUIMainWindowFocusListener(accessTokenFormDialog); SoapUI.getFrame().addWindowFocusListener(mainWindowFocusListener); } private JTextField createTokenField(String fieldName) { JTextField tokenField = new JTextField(); tokenField.setName(fieldName); tokenField.setColumns(SimpleForm.MEDIUM_TEXT_FIELD_COLUMNS); tokenField.setMargin(TOKEN_FIELD_INSETS); Bindings.bind(tokenField, oAuth1Form.getPresentationModel().getModel(fieldName)); return tokenField; } private JLabel createTokenStatusIcon() { JLabel tokenStatusIcon = new JLabel(); tokenStatusIcon.setVisible(false); return tokenStatusIcon; } private JLabel createAccessTokenStatusText() { JLabel tokenStatusText = new JLabel(); tokenStatusText.setFont(scaledFont(tokenStatusText, TOKEN_STATUS_TEXT_FONT_SCALE)); tokenStatusText.setVisible(false); tokenStatusText.setAlignmentX(Component.CENTER_ALIGNMENT); return tokenStatusText; } private JPanel createAccessTokenRowPanel(JTextField accessTokenField, JLabel accessTokenStatusIcon, JLabel accessTokenStatusText) { JPanel panel = new JPanel(); panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); panel.setBackground(CARD_BACKGROUND_COLOR); panel.add(accessTokenField); panel.add(Box.createRigidArea(HORIZONTAL_SPACING_IN_ACCESS_TOKEN_ROW)); panel.add(accessTokenStatusIcon); panel.add(Box.createRigidArea(HORIZONTAL_SPACING_IN_ACCESS_TOKEN_ROW)); panel.add(accessTokenStatusText); panel.add(Box.createRigidArea(HORIZONTAL_SPACING_IN_ACCESS_TOKEN_ROW)); return panel; } private Font scaledFont(JComponent component, float scale) { Font currentFont = component.getFont(); return currentFont.deriveFont((float) currentFont.getSize() * scale); } private void setAccessTokenFormDialogBoundsBelowTheButton(Point disclosureButtonLocation, JDialog accessTokenFormDialog, int disclosureButtonHeight) { accessTokenFormDialog.setLocation((int) disclosureButtonLocation.getX() - ACCESS_TOKEN_DIALOG_HORIZONTAL_OFFSET, (int) disclosureButtonLocation.getY() + disclosureButtonHeight); } private void setAccessTokenFormDialogBoundsAboveTheButton(Point disclosureButtonLocation, JDialog accessTokenFormDialog) { accessTokenFormDialog.setLocation((int) disclosureButtonLocation.getX() - ACCESS_TOKEN_DIALOG_HORIZONTAL_OFFSET, (int) disclosureButtonLocation.getY() - accessTokenFormDialog.getHeight()); } private void setAccessTokenStatusFeedback(AccessTokenStatusConfig.Enum status) { if (status == AccessTokenStatusConfig.UNKNOWN) { setAccessTokenDefaultFeedback(); } else if (status == AccessTokenStatusConfig.ENTERED_MANUALLY) { setAccessTokenEnteredManuallyFeedback(); } else if (status == AccessTokenStatusConfig.RETRIEVED_FROM_SERVER) { setAccessTokenSuccessfulFeedback(); } else if (status == AccessTokenStatusConfig.EXPIRED || status == AccessTokenStatusConfig.RETRIEVAL_CANCELED) { setAccessTokenFailedFeedback(); } else { setAccessTokenDefaultFeedback(); } } private void setSecretTokenStatusFeedback(AccessTokenStatusConfig.Enum status) { if (status == AccessTokenStatusConfig.UNKNOWN) { setSecretTokenDefaultFeedback(); } else if (status == AccessTokenStatusConfig.ENTERED_MANUALLY) { setSecretTokenEnteredManuallyFeedback(); } else if (status == AccessTokenStatusConfig.RETRIEVED_FROM_SERVER) { setSecretTokenSuccessfulFeedback(); } else if (status == AccessTokenStatusConfig.EXPIRED || status == AccessTokenStatusConfig.RETRIEVAL_CANCELED) { setSecretTokenFailedFeedback(); } else { setSecretTokenDefaultFeedback(); } } private void setSecretTokenEnteredManuallyFeedback() { secretTokenField.setBackground(DEFAULT_COLOR); secretTokenStatusIcon.setIcon(null); secretTokenStatusIcon.setVisible(false); secretTokenStatusText.setText(messages.get("OAuth1Form.EnteredManually")); secretTokenStatusText.setVisible(true); inspector.setIcon(ProfileSelectionForm.AUTH_ENABLED_ICON); } private void setSecretTokenSuccessfulFeedback() { secretTokenField.setBackground(SUCCESS_COLOR); secretTokenStatusIcon.setIcon(SUCCESS_ICON); secretTokenStatusIcon.setVisible(true); secretTokenStatusText.setText(messages.get("OAuth1Form.Retrieved")); secretTokenStatusText.setVisible(true); inspector.setIcon(ProfileSelectionForm.AUTH_ENABLED_ICON); } private void setSecretTokenFailedFeedback() { secretTokenField.setBackground(FAIL_COLOR); secretTokenStatusIcon.setIcon(FAIL_ICON); secretTokenStatusIcon.setVisible(true); secretTokenStatusText.setText(messages.get("OAuth1Form.FailedFeedback")); secretTokenStatusText.setVisible(true); inspector.setIcon(FAIL_ICON); } private void setSecretTokenDefaultFeedback() { secretTokenField.setBackground(DEFAULT_COLOR); secretTokenStatusIcon.setIcon(null); secretTokenStatusIcon.setVisible(false); secretTokenStatusText.setText(""); secretTokenStatusText.setVisible(false); inspector.setIcon(ProfileSelectionForm.AUTH_ENABLED_ICON); } private void setAccessTokenEnteredManuallyFeedback() { accessTokenField.setBackground(DEFAULT_COLOR); accessTokenStatusIcon.setIcon(null); accessTokenStatusIcon.setVisible(false); accessTokenStatusText.setText(messages.get("OAuth1Form.EnteredManually")); accessTokenStatusText.setVisible(true); inspector.setIcon(ProfileSelectionForm.AUTH_ENABLED_ICON); } private void setAccessTokenSuccessfulFeedback() { accessTokenField.setBackground(SUCCESS_COLOR); accessTokenStatusIcon.setIcon(SUCCESS_ICON); accessTokenStatusIcon.setVisible(true); accessTokenStatusText.setText(messages.get("OAuth1Form.Retrieved")); accessTokenStatusText.setVisible(true); inspector.setIcon(ProfileSelectionForm.AUTH_ENABLED_ICON); } private void setAccessTokenFailedFeedback() { accessTokenField.setBackground(FAIL_COLOR); accessTokenStatusIcon.setIcon(FAIL_ICON); accessTokenStatusIcon.setVisible(true); accessTokenStatusText.setText(messages.get("OAuth1Form.FailedFeedback")); accessTokenStatusText.setVisible(true); inspector.setIcon(FAIL_ICON); } private void setAccessTokenDefaultFeedback() { accessTokenField.setBackground(DEFAULT_COLOR); accessTokenStatusIcon.setIcon(null); accessTokenStatusIcon.setVisible(false); accessTokenStatusText.setText(""); accessTokenStatusText.setVisible(false); inspector.setIcon(ProfileSelectionForm.AUTH_ENABLED_ICON); } private void hideAccessTokenFormDialogAndEnableDisclosureButton(JDialog accessTokenFormDialog) { accessTokenFormDialog.setVisible(false); disclosureButton.setIcon(UISupport.createImageIcon("/pop-down-open.png")); // If the focus is lost due to click on the disclosure button then don't enable it yet, since it // will then show the dialog directly again. if (!isMouseOnDisclosureLabel) { disclosureButtonDisabled = false; } } private class DisclosureButtonMouseListener extends MouseAdapter { private final JDialog accessTokenFormDialog; private final JLabel disclosureButton; public DisclosureButtonMouseListener(JDialog accessTokenFormDialog, JLabel disclosureButton) { this.accessTokenFormDialog = accessTokenFormDialog; this.disclosureButton = disclosureButton; } @Override public void mouseClicked(MouseEvent e) { // Check if this click is to hide the access token form dialog if (disclosureButtonDisabled) { disclosureButtonDisabled = false; return; } JLabel source = (JLabel) e.getSource(); Point disclosureButtonLocation = source.getLocationOnScreen(); accessTokenFormDialog.pack(); accessTokenFormDialog.setVisible(true); disclosureButton.setIcon(UISupport.createImageIcon("/pop-down-close.png")); if (UISupport.isEnoughSpaceAvailableBelowComponent(disclosureButtonLocation, accessTokenFormDialog.getHeight(), source.getHeight())) { setAccessTokenFormDialogBoundsBelowTheButton(disclosureButtonLocation, accessTokenFormDialog, source.getHeight()); } else { setAccessTokenFormDialogBoundsAboveTheButton(disclosureButtonLocation, accessTokenFormDialog); } } @Override public void mouseEntered(MouseEvent e) { isMouseOnDisclosureLabel = true; } @Override public void mouseExited(MouseEvent e) { isMouseOnDisclosureLabel = false; } } private class AccessTokenFormDialogWindowListener implements WindowFocusListener { private final JDialog accessTokenFormDialog; public AccessTokenFormDialogWindowListener(JDialog accessTokenFormDialog) { this.accessTokenFormDialog = accessTokenFormDialog; } @Override public void windowGainedFocus(WindowEvent e) { disclosureButtonDisabled = true; } @Override public void windowLostFocus(WindowEvent e) { if (isMouseOnComponent(SoapUI.getFrame()) && !isMouseOnComponent(accessTokenFormDialog)) { hideAccessTokenFormDialogAndEnableDisclosureButton(accessTokenFormDialog); } } private boolean isMouseOnComponent(Component component) { if (!component.isShowing()) { return false; } Point mouseLocation = MouseInfo.getPointerInfo().getLocation(); Point componentLocationOnScreen = component.getLocationOnScreen(); return component.contains(mouseLocation.x - componentLocationOnScreen.x, mouseLocation.y - componentLocationOnScreen.y); } } private class SoapUIMainWindowFocusListener extends WindowAdapter { private final JDialog accessTokenFormDialog; public SoapUIMainWindowFocusListener(JDialog accessTokenFormDialog) { this.accessTokenFormDialog = accessTokenFormDialog; } @Override public void windowGainedFocus(WindowEvent e) { if (accessTokenFormDialog.isVisible()) { hideAccessTokenFormDialogAndEnableDisclosureButton(accessTokenFormDialog); } } } }