// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.oauth; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.BorderLayout; import java.awt.Color; import java.awt.FlowLayout; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ItemEvent; import java.util.concurrent.Executor; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JLabel; import javax.swing.JPanel; import org.openstreetmap.josm.data.oauth.OAuthToken; import org.openstreetmap.josm.gui.preferences.server.OAuthAccessTokenHolder; import org.openstreetmap.josm.gui.util.GuiHelper; import org.openstreetmap.josm.gui.widgets.HtmlPanel; import org.openstreetmap.josm.gui.widgets.JMultilineLabel; import org.openstreetmap.josm.gui.widgets.JosmTextField; import org.openstreetmap.josm.tools.ImageProvider; import org.openstreetmap.josm.tools.OpenBrowser; /** * This is the UI for running a semic-automic authorisation procedure. * * In contrast to the fully-automatic procedure the user is dispatched to an * external browser for login and authorisation. * * @since 2746 */ public class SemiAutomaticAuthorizationUI extends AbstractAuthorizationUI { private final AccessTokenInfoPanel pnlAccessTokenInfo = new AccessTokenInfoPanel(); private transient OAuthToken requestToken; private RetrieveRequestTokenPanel pnlRetrieveRequestToken; private RetrieveAccessTokenPanel pnlRetrieveAccessToken; private ShowAccessTokenPanel pnlShowAccessToken; private final transient Executor executor; /** * build the UI */ protected final void build() { setLayout(new BorderLayout()); setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); pnlRetrieveRequestToken = new RetrieveRequestTokenPanel(); pnlRetrieveAccessToken = new RetrieveAccessTokenPanel(); pnlShowAccessToken = new ShowAccessTokenPanel(); add(pnlRetrieveRequestToken, BorderLayout.CENTER); } /** * Constructs a new {@code SemiAutomaticAuthorizationUI} for the given API URL. * @param apiUrl The OSM API URL * @param executor the executor used for running the HTTP requests for the authorization * @since 5422 */ public SemiAutomaticAuthorizationUI(String apiUrl, Executor executor) { super(apiUrl); this.executor = executor; build(); } @Override public boolean isSaveAccessTokenToPreferences() { return pnlAccessTokenInfo.isSaveToPreferences(); } protected void transitionToRetrieveAccessToken() { OsmOAuthAuthorizationClient client = new OsmOAuthAuthorizationClient( getAdvancedPropertiesPanel().getAdvancedParameters() ); String authoriseUrl = client.getAuthoriseUrl(requestToken); OpenBrowser.displayUrl(authoriseUrl); removeAll(); pnlRetrieveAccessToken.setAuthoriseUrl(authoriseUrl); add(pnlRetrieveAccessToken, BorderLayout.CENTER); pnlRetrieveAccessToken.invalidate(); validate(); repaint(); } protected void transitionToRetrieveRequestToken() { requestToken = null; setAccessToken(null); removeAll(); add(pnlRetrieveRequestToken, BorderLayout.CENTER); pnlRetrieveRequestToken.invalidate(); validate(); repaint(); } protected void transitionToShowAccessToken() { removeAll(); add(pnlShowAccessToken, BorderLayout.CENTER); pnlShowAccessToken.invalidate(); validate(); repaint(); pnlShowAccessToken.setAccessToken(getAccessToken()); } static class StepLabel extends JLabel { StepLabel(String text) { super(text); setFont(getFont().deriveFont(16f)); } } /** * This is the panel displayed in the first step of the semi-automatic authorisation process. */ private class RetrieveRequestTokenPanel extends JPanel { /** * Constructs a new {@code RetrieveRequestTokenPanel}. */ RetrieveRequestTokenPanel() { build(); } protected JPanel buildAdvancedParametersPanel() { JPanel pnl = new JPanel(new GridBagLayout()); GridBagConstraints gc = new GridBagConstraints(); gc.anchor = GridBagConstraints.NORTHWEST; gc.fill = GridBagConstraints.HORIZONTAL; gc.weightx = 0.0; gc.insets = new Insets(0, 0, 0, 3); JCheckBox cbShowAdvancedParameters = new JCheckBox(); pnl.add(cbShowAdvancedParameters, gc); cbShowAdvancedParameters.setSelected(false); cbShowAdvancedParameters.addItemListener( evt -> getAdvancedPropertiesPanel().setVisible(evt.getStateChange() == ItemEvent.SELECTED) ); gc.gridx = 1; gc.weightx = 1.0; JMultilineLabel lbl = new JMultilineLabel(tr("Display Advanced OAuth Parameters")); lbl.setFont(lbl.getFont().deriveFont(Font.PLAIN)); pnl.add(lbl, gc); gc.gridy = 1; gc.gridx = 1; gc.insets = new Insets(3, 0, 3, 0); gc.fill = GridBagConstraints.BOTH; gc.weightx = 1.0; gc.weighty = 1.0; pnl.add(getAdvancedPropertiesPanel(), gc); getAdvancedPropertiesPanel().setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder(Color.GRAY, 1), BorderFactory.createEmptyBorder(3, 3, 3, 3) ) ); getAdvancedPropertiesPanel().setVisible(false); return pnl; } protected JPanel buildCommandPanel() { JPanel pnl = new JPanel(new GridBagLayout()); GridBagConstraints gc = new GridBagConstraints(); gc.anchor = GridBagConstraints.NORTHWEST; gc.fill = GridBagConstraints.BOTH; gc.weightx = 1.0; gc.weighty = 1.0; gc.insets = new Insets(0, 0, 0, 3); HtmlPanel h = new HtmlPanel(); h.setText(tr("<html>" + "Please click on <strong>{0}</strong> to retrieve an OAuth Request Token from " + "''{1}''.</html>", tr("Retrieve Request Token"), getAdvancedPropertiesPanel().getAdvancedParameters().getRequestTokenUrl() )); pnl.add(h, gc); JPanel pnl1 = new JPanel(new FlowLayout(FlowLayout.LEFT)); pnl1.add(new JButton(new RetrieveRequestTokenAction())); gc.fill = GridBagConstraints.HORIZONTAL; gc.weightx = 1.0; gc.gridy = 1; pnl.add(pnl1, gc); return pnl; } protected final void build() { setLayout(new BorderLayout(0, 5)); add(new StepLabel(tr("<html>Step 1/3: Retrieve an OAuth Request Token</html>")), BorderLayout.NORTH); add(buildAdvancedParametersPanel(), BorderLayout.CENTER); add(buildCommandPanel(), BorderLayout.SOUTH); } } /** * This is the panel displayed in the second step of the semi-automatic authorization process. */ private class RetrieveAccessTokenPanel extends JPanel { private final JosmTextField tfAuthoriseUrl = new JosmTextField(); /** * Constructs a new {@code RetrieveAccessTokenPanel}. */ RetrieveAccessTokenPanel() { build(); } protected JPanel buildTitlePanel() { JPanel pnl = new JPanel(new BorderLayout()); pnl.add(new StepLabel(tr("<html>Step 2/3: Authorize and retrieve an Access Token</html>")), BorderLayout.CENTER); return pnl; } protected JPanel buildContentPanel() { JPanel pnl = new JPanel(new GridBagLayout()); GridBagConstraints gc = new GridBagConstraints(); gc.anchor = GridBagConstraints.NORTHWEST; gc.fill = GridBagConstraints.HORIZONTAL; gc.weightx = 1.0; gc.gridwidth = 2; HtmlPanel html = new HtmlPanel(); html.setText(tr("<html>" + "JOSM successfully retrieved a Request Token. " + "JOSM is now launching an authorization page in an external browser. " + "Please login with your OSM username and password and follow the instructions " + "to authorize the Request Token. Then switch back to this dialog and click on " + "<strong>{0}</strong><br><br>" + "If launching the external browser fails you can copy the following authorize URL " + "and paste it into the address field of your browser.</html>", tr("Request Access Token") )); pnl.add(html, gc); gc.gridx = 0; gc.gridy = 1; gc.weightx = 0.0; gc.gridwidth = 1; pnl.add(new JLabel(tr("Authorize URL:")), gc); gc.gridx = 1; gc.weightx = 1.0; pnl.add(tfAuthoriseUrl, gc); tfAuthoriseUrl.setEditable(false); return pnl; } protected JPanel buildActionPanel() { JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT)); pnl.add(new JButton(new BackAction())); pnl.add(new JButton(new RetrieveAccessTokenAction())); return pnl; } protected final void build() { setLayout(new BorderLayout()); add(buildTitlePanel(), BorderLayout.NORTH); add(buildContentPanel(), BorderLayout.CENTER); add(buildActionPanel(), BorderLayout.SOUTH); } public void setAuthoriseUrl(String url) { tfAuthoriseUrl.setText(url); } /** * Action to go back to step 1 in the process */ class BackAction extends AbstractAction { BackAction() { putValue(NAME, tr("Back")); putValue(SHORT_DESCRIPTION, tr("Go back to step 1/3")); new ImageProvider("dialogs", "previous").getResource().attachImageIcon(this); } @Override public void actionPerformed(ActionEvent arg0) { transitionToRetrieveRequestToken(); } } } /** * Displays the retrieved Access Token in step 3. */ class ShowAccessTokenPanel extends JPanel { /** * Constructs a new {@code ShowAccessTokenPanel}. */ ShowAccessTokenPanel() { build(); } protected JPanel buildTitlePanel() { JPanel pnl = new JPanel(new BorderLayout()); pnl.add(new StepLabel(tr("<html>Step 3/3: Successfully retrieved an Access Token</html>")), BorderLayout.CENTER); return pnl; } protected JPanel buildContentPanel() { JPanel pnl = new JPanel(new GridBagLayout()); GridBagConstraints gc = new GridBagConstraints(); gc.anchor = GridBagConstraints.NORTHWEST; gc.fill = GridBagConstraints.HORIZONTAL; gc.weightx = 1.0; HtmlPanel html = new HtmlPanel(); html.setText(tr("<html>" + "JOSM has successfully retrieved an Access Token. " + "You can now accept this token. JOSM will use it in the future for authentication " + "and authorization to the OSM server.<br><br>" + "The access token is: </html>" )); pnl.add(html, gc); gc.gridx = 0; gc.gridy = 1; gc.weightx = 1.0; gc.gridwidth = 1; pnl.add(pnlAccessTokenInfo, gc); pnlAccessTokenInfo.setSaveToPreferences( OAuthAccessTokenHolder.getInstance().isSaveToPreferences() ); return pnl; } protected JPanel buildActionPanel() { JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT)); pnl.add(new JButton(new RestartAction())); pnl.add(new JButton(new TestAccessTokenAction())); return pnl; } protected final void build() { setLayout(new BorderLayout()); add(buildTitlePanel(), BorderLayout.NORTH); add(buildContentPanel(), BorderLayout.CENTER); add(buildActionPanel(), BorderLayout.SOUTH); } /** * Action to go back to step 1 in the process */ class RestartAction extends AbstractAction { RestartAction() { putValue(NAME, tr("Restart")); putValue(SHORT_DESCRIPTION, tr("Go back to step 1/3")); new ImageProvider("dialogs", "previous").getResource().attachImageIcon(this); } @Override public void actionPerformed(ActionEvent arg0) { transitionToRetrieveRequestToken(); } } public void setAccessToken(OAuthToken accessToken) { pnlAccessTokenInfo.setAccessToken(accessToken); } } /** * Action for retrieving a request token */ class RetrieveRequestTokenAction extends AbstractAction { RetrieveRequestTokenAction() { putValue(NAME, tr("Retrieve Request Token")); new ImageProvider("oauth", "oauth-small").getResource().attachImageIcon(this); putValue(SHORT_DESCRIPTION, tr("Click to retrieve a Request Token")); } @Override public void actionPerformed(ActionEvent evt) { final RetrieveRequestTokenTask task = new RetrieveRequestTokenTask( SemiAutomaticAuthorizationUI.this, getAdvancedPropertiesPanel().getAdvancedParameters() ); executor.execute(task); Runnable r = () -> { if (task.isCanceled()) return; if (task.getRequestToken() == null) return; requestToken = task.getRequestToken(); GuiHelper.runInEDT(SemiAutomaticAuthorizationUI.this::transitionToRetrieveAccessToken); }; executor.execute(r); } } /** * Action for retrieving an Access Token */ class RetrieveAccessTokenAction extends AbstractAction { RetrieveAccessTokenAction() { putValue(NAME, tr("Retrieve Access Token")); new ImageProvider("oauth", "oauth-small").getResource().attachImageIcon(this); putValue(SHORT_DESCRIPTION, tr("Click to retrieve an Access Token")); } @Override public void actionPerformed(ActionEvent evt) { final RetrieveAccessTokenTask task = new RetrieveAccessTokenTask( SemiAutomaticAuthorizationUI.this, getAdvancedPropertiesPanel().getAdvancedParameters(), requestToken ); executor.execute(task); Runnable r = () -> { if (task.isCanceled()) return; if (task.getAccessToken() == null) return; GuiHelper.runInEDT(() -> { setAccessToken(task.getAccessToken()); transitionToShowAccessToken(); }); }; executor.execute(r); } } /** * Action for testing an Access Token */ class TestAccessTokenAction extends AbstractAction { TestAccessTokenAction() { putValue(NAME, tr("Test Access Token")); new ImageProvider("oauth", "oauth-small").getResource().attachImageIcon(this); putValue(SHORT_DESCRIPTION, tr("Click to test the Access Token")); } @Override public void actionPerformed(ActionEvent evt) { TestAccessTokenTask task = new TestAccessTokenTask( SemiAutomaticAuthorizationUI.this, getApiUrl(), getAdvancedPropertiesPanel().getAdvancedParameters(), getAccessToken() ); executor.execute(task); } } }