001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.oauth; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.BorderLayout; 007import java.awt.Color; 008import java.awt.FlowLayout; 009import java.awt.Font; 010import java.awt.GridBagConstraints; 011import java.awt.GridBagLayout; 012import java.awt.Insets; 013import java.awt.event.ActionEvent; 014import java.awt.event.ItemEvent; 015import java.util.concurrent.Executor; 016 017import javax.swing.AbstractAction; 018import javax.swing.BorderFactory; 019import javax.swing.JButton; 020import javax.swing.JCheckBox; 021import javax.swing.JLabel; 022import javax.swing.JPanel; 023 024import org.openstreetmap.josm.data.oauth.OAuthToken; 025import org.openstreetmap.josm.gui.preferences.server.OAuthAccessTokenHolder; 026import org.openstreetmap.josm.gui.util.GuiHelper; 027import org.openstreetmap.josm.gui.widgets.HtmlPanel; 028import org.openstreetmap.josm.gui.widgets.JMultilineLabel; 029import org.openstreetmap.josm.gui.widgets.JosmTextField; 030import org.openstreetmap.josm.tools.ImageProvider; 031import org.openstreetmap.josm.tools.OpenBrowser; 032 033/** 034 * This is the UI for running a semic-automic authorisation procedure. 035 * 036 * In contrast to the fully-automatic procedure the user is dispatched to an 037 * external browser for login and authorisation. 038 * 039 * @since 2746 040 */ 041public class SemiAutomaticAuthorizationUI extends AbstractAuthorizationUI { 042 private final AccessTokenInfoPanel pnlAccessTokenInfo = new AccessTokenInfoPanel(); 043 private transient OAuthToken requestToken; 044 045 private RetrieveRequestTokenPanel pnlRetrieveRequestToken; 046 private RetrieveAccessTokenPanel pnlRetrieveAccessToken; 047 private ShowAccessTokenPanel pnlShowAccessToken; 048 private final transient Executor executor; 049 050 /** 051 * build the UI 052 */ 053 protected final void build() { 054 setLayout(new BorderLayout()); 055 setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); 056 pnlRetrieveRequestToken = new RetrieveRequestTokenPanel(); 057 pnlRetrieveAccessToken = new RetrieveAccessTokenPanel(); 058 pnlShowAccessToken = new ShowAccessTokenPanel(); 059 add(pnlRetrieveRequestToken, BorderLayout.CENTER); 060 } 061 062 /** 063 * Constructs a new {@code SemiAutomaticAuthorizationUI} for the given API URL. 064 * @param apiUrl The OSM API URL 065 * @param executor the executor used for running the HTTP requests for the authorization 066 * @since 5422 067 */ 068 public SemiAutomaticAuthorizationUI(String apiUrl, Executor executor) { 069 super(apiUrl); 070 this.executor = executor; 071 build(); 072 } 073 074 @Override 075 public boolean isSaveAccessTokenToPreferences() { 076 return pnlAccessTokenInfo.isSaveToPreferences(); 077 } 078 079 protected void transitionToRetrieveAccessToken() { 080 OsmOAuthAuthorizationClient client = new OsmOAuthAuthorizationClient( 081 getAdvancedPropertiesPanel().getAdvancedParameters() 082 ); 083 String authoriseUrl = client.getAuthoriseUrl(requestToken); 084 OpenBrowser.displayUrl(authoriseUrl); 085 086 removeAll(); 087 pnlRetrieveAccessToken.setAuthoriseUrl(authoriseUrl); 088 add(pnlRetrieveAccessToken, BorderLayout.CENTER); 089 pnlRetrieveAccessToken.invalidate(); 090 validate(); 091 repaint(); 092 } 093 094 protected void transitionToRetrieveRequestToken() { 095 requestToken = null; 096 setAccessToken(null); 097 removeAll(); 098 add(pnlRetrieveRequestToken, BorderLayout.CENTER); 099 pnlRetrieveRequestToken.invalidate(); 100 validate(); 101 repaint(); 102 } 103 104 protected void transitionToShowAccessToken() { 105 removeAll(); 106 add(pnlShowAccessToken, BorderLayout.CENTER); 107 pnlShowAccessToken.invalidate(); 108 validate(); 109 repaint(); 110 pnlShowAccessToken.setAccessToken(getAccessToken()); 111 } 112 113 static class StepLabel extends JLabel { 114 StepLabel(String text) { 115 super(text); 116 setFont(getFont().deriveFont(16f)); 117 } 118 } 119 120 /** 121 * This is the panel displayed in the first step of the semi-automatic authorisation process. 122 */ 123 private class RetrieveRequestTokenPanel extends JPanel { 124 125 /** 126 * Constructs a new {@code RetrieveRequestTokenPanel}. 127 */ 128 RetrieveRequestTokenPanel() { 129 build(); 130 } 131 132 protected JPanel buildAdvancedParametersPanel() { 133 JPanel pnl = new JPanel(new GridBagLayout()); 134 GridBagConstraints gc = new GridBagConstraints(); 135 136 gc.anchor = GridBagConstraints.NORTHWEST; 137 gc.fill = GridBagConstraints.HORIZONTAL; 138 gc.weightx = 0.0; 139 gc.insets = new Insets(0, 0, 0, 3); 140 JCheckBox cbShowAdvancedParameters = new JCheckBox(); 141 pnl.add(cbShowAdvancedParameters, gc); 142 cbShowAdvancedParameters.setSelected(false); 143 cbShowAdvancedParameters.addItemListener( 144 evt -> getAdvancedPropertiesPanel().setVisible(evt.getStateChange() == ItemEvent.SELECTED) 145 ); 146 147 gc.gridx = 1; 148 gc.weightx = 1.0; 149 JMultilineLabel lbl = new JMultilineLabel(tr("Display Advanced OAuth Parameters")); 150 lbl.setFont(lbl.getFont().deriveFont(Font.PLAIN)); 151 pnl.add(lbl, gc); 152 153 gc.gridy = 1; 154 gc.gridx = 1; 155 gc.insets = new Insets(3, 0, 3, 0); 156 gc.fill = GridBagConstraints.BOTH; 157 gc.weightx = 1.0; 158 gc.weighty = 1.0; 159 pnl.add(getAdvancedPropertiesPanel(), gc); 160 getAdvancedPropertiesPanel().setBorder( 161 BorderFactory.createCompoundBorder( 162 BorderFactory.createLineBorder(Color.GRAY, 1), 163 BorderFactory.createEmptyBorder(3, 3, 3, 3) 164 ) 165 ); 166 getAdvancedPropertiesPanel().setVisible(false); 167 return pnl; 168 } 169 170 protected JPanel buildCommandPanel() { 171 JPanel pnl = new JPanel(new GridBagLayout()); 172 GridBagConstraints gc = new GridBagConstraints(); 173 174 gc.anchor = GridBagConstraints.NORTHWEST; 175 gc.fill = GridBagConstraints.BOTH; 176 gc.weightx = 1.0; 177 gc.weighty = 1.0; 178 gc.insets = new Insets(0, 0, 0, 3); 179 180 181 HtmlPanel h = new HtmlPanel(); 182 h.setText(tr("<html>" 183 + "Please click on <strong>{0}</strong> to retrieve an OAuth Request Token from " 184 + "''{1}''.</html>", 185 tr("Retrieve Request Token"), 186 getAdvancedPropertiesPanel().getAdvancedParameters().getRequestTokenUrl() 187 )); 188 pnl.add(h, gc); 189 190 JPanel pnl1 = new JPanel(new FlowLayout(FlowLayout.LEFT)); 191 pnl1.add(new JButton(new RetrieveRequestTokenAction())); 192 gc.fill = GridBagConstraints.HORIZONTAL; 193 gc.weightx = 1.0; 194 gc.gridy = 1; 195 pnl.add(pnl1, gc); 196 return pnl; 197 198 } 199 200 protected final void build() { 201 setLayout(new BorderLayout(0, 5)); 202 add(new StepLabel(tr("<html>Step 1/3: Retrieve an OAuth Request Token</html>")), BorderLayout.NORTH); 203 add(buildAdvancedParametersPanel(), BorderLayout.CENTER); 204 add(buildCommandPanel(), BorderLayout.SOUTH); 205 } 206 } 207 208 /** 209 * This is the panel displayed in the second step of the semi-automatic authorization process. 210 */ 211 private class RetrieveAccessTokenPanel extends JPanel { 212 213 private final JosmTextField tfAuthoriseUrl = new JosmTextField(); 214 215 /** 216 * Constructs a new {@code RetrieveAccessTokenPanel}. 217 */ 218 RetrieveAccessTokenPanel() { 219 build(); 220 } 221 222 protected JPanel buildTitlePanel() { 223 JPanel pnl = new JPanel(new BorderLayout()); 224 pnl.add(new StepLabel(tr("<html>Step 2/3: Authorize and retrieve an Access Token</html>")), BorderLayout.CENTER); 225 return pnl; 226 } 227 228 protected JPanel buildContentPanel() { 229 JPanel pnl = new JPanel(new GridBagLayout()); 230 GridBagConstraints gc = new GridBagConstraints(); 231 232 gc.anchor = GridBagConstraints.NORTHWEST; 233 gc.fill = GridBagConstraints.HORIZONTAL; 234 gc.weightx = 1.0; 235 gc.gridwidth = 2; 236 HtmlPanel html = new HtmlPanel(); 237 html.setText(tr("<html>" 238 + "JOSM successfully retrieved a Request Token. " 239 + "JOSM is now launching an authorization page in an external browser. " 240 + "Please login with your OSM username and password and follow the instructions " 241 + "to authorize the Request Token. Then switch back to this dialog and click on " 242 + "<strong>{0}</strong><br><br>" 243 + "If launching the external browser fails you can copy the following authorize URL " 244 + "and paste it into the address field of your browser.</html>", 245 tr("Request Access Token") 246 )); 247 pnl.add(html, gc); 248 249 gc.gridx = 0; 250 gc.gridy = 1; 251 gc.weightx = 0.0; 252 gc.gridwidth = 1; 253 pnl.add(new JLabel(tr("Authorize URL:")), gc); 254 255 gc.gridx = 1; 256 gc.weightx = 1.0; 257 pnl.add(tfAuthoriseUrl, gc); 258 tfAuthoriseUrl.setEditable(false); 259 260 return pnl; 261 } 262 263 protected JPanel buildActionPanel() { 264 JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT)); 265 pnl.add(new JButton(new BackAction())); 266 pnl.add(new JButton(new RetrieveAccessTokenAction())); 267 return pnl; 268 } 269 270 protected final void build() { 271 setLayout(new BorderLayout()); 272 add(buildTitlePanel(), BorderLayout.NORTH); 273 add(buildContentPanel(), BorderLayout.CENTER); 274 add(buildActionPanel(), BorderLayout.SOUTH); 275 } 276 277 public void setAuthoriseUrl(String url) { 278 tfAuthoriseUrl.setText(url); 279 } 280 281 /** 282 * Action to go back to step 1 in the process 283 */ 284 class BackAction extends AbstractAction { 285 BackAction() { 286 putValue(NAME, tr("Back")); 287 putValue(SHORT_DESCRIPTION, tr("Go back to step 1/3")); 288 new ImageProvider("dialogs", "previous").getResource().attachImageIcon(this); 289 } 290 291 @Override 292 public void actionPerformed(ActionEvent arg0) { 293 transitionToRetrieveRequestToken(); 294 } 295 } 296 } 297 298 /** 299 * Displays the retrieved Access Token in step 3. 300 */ 301 class ShowAccessTokenPanel extends JPanel { 302 303 /** 304 * Constructs a new {@code ShowAccessTokenPanel}. 305 */ 306 ShowAccessTokenPanel() { 307 build(); 308 } 309 310 protected JPanel buildTitlePanel() { 311 JPanel pnl = new JPanel(new BorderLayout()); 312 pnl.add(new StepLabel(tr("<html>Step 3/3: Successfully retrieved an Access Token</html>")), BorderLayout.CENTER); 313 return pnl; 314 } 315 316 protected JPanel buildContentPanel() { 317 JPanel pnl = new JPanel(new GridBagLayout()); 318 GridBagConstraints gc = new GridBagConstraints(); 319 320 gc.anchor = GridBagConstraints.NORTHWEST; 321 gc.fill = GridBagConstraints.HORIZONTAL; 322 gc.weightx = 1.0; 323 HtmlPanel html = new HtmlPanel(); 324 html.setText(tr("<html>" 325 + "JOSM has successfully retrieved an Access Token. " 326 + "You can now accept this token. JOSM will use it in the future for authentication " 327 + "and authorization to the OSM server.<br><br>" 328 + "The access token is: </html>" 329 )); 330 pnl.add(html, gc); 331 332 gc.gridx = 0; 333 gc.gridy = 1; 334 gc.weightx = 1.0; 335 gc.gridwidth = 1; 336 pnl.add(pnlAccessTokenInfo, gc); 337 pnlAccessTokenInfo.setSaveToPreferences( 338 OAuthAccessTokenHolder.getInstance().isSaveToPreferences() 339 ); 340 return pnl; 341 } 342 343 protected JPanel buildActionPanel() { 344 JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT)); 345 pnl.add(new JButton(new RestartAction())); 346 pnl.add(new JButton(new TestAccessTokenAction())); 347 return pnl; 348 } 349 350 protected final void build() { 351 setLayout(new BorderLayout()); 352 add(buildTitlePanel(), BorderLayout.NORTH); 353 add(buildContentPanel(), BorderLayout.CENTER); 354 add(buildActionPanel(), BorderLayout.SOUTH); 355 } 356 357 /** 358 * Action to go back to step 1 in the process 359 */ 360 class RestartAction extends AbstractAction { 361 RestartAction() { 362 putValue(NAME, tr("Restart")); 363 putValue(SHORT_DESCRIPTION, tr("Go back to step 1/3")); 364 new ImageProvider("dialogs", "previous").getResource().attachImageIcon(this); 365 } 366 367 @Override 368 public void actionPerformed(ActionEvent arg0) { 369 transitionToRetrieveRequestToken(); 370 } 371 } 372 373 public void setAccessToken(OAuthToken accessToken) { 374 pnlAccessTokenInfo.setAccessToken(accessToken); 375 } 376 } 377 378 /** 379 * Action for retrieving a request token 380 */ 381 class RetrieveRequestTokenAction extends AbstractAction { 382 383 RetrieveRequestTokenAction() { 384 putValue(NAME, tr("Retrieve Request Token")); 385 new ImageProvider("oauth", "oauth-small").getResource().attachImageIcon(this); 386 putValue(SHORT_DESCRIPTION, tr("Click to retrieve a Request Token")); 387 } 388 389 @Override 390 public void actionPerformed(ActionEvent evt) { 391 final RetrieveRequestTokenTask task = new RetrieveRequestTokenTask( 392 SemiAutomaticAuthorizationUI.this, 393 getAdvancedPropertiesPanel().getAdvancedParameters() 394 ); 395 executor.execute(task); 396 Runnable r = () -> { 397 if (task.isCanceled()) return; 398 if (task.getRequestToken() == null) return; 399 requestToken = task.getRequestToken(); 400 GuiHelper.runInEDT(SemiAutomaticAuthorizationUI.this::transitionToRetrieveAccessToken); 401 }; 402 executor.execute(r); 403 } 404 } 405 406 /** 407 * Action for retrieving an Access Token 408 */ 409 class RetrieveAccessTokenAction extends AbstractAction { 410 411 RetrieveAccessTokenAction() { 412 putValue(NAME, tr("Retrieve Access Token")); 413 new ImageProvider("oauth", "oauth-small").getResource().attachImageIcon(this); 414 putValue(SHORT_DESCRIPTION, tr("Click to retrieve an Access Token")); 415 } 416 417 @Override 418 public void actionPerformed(ActionEvent evt) { 419 final RetrieveAccessTokenTask task = new RetrieveAccessTokenTask( 420 SemiAutomaticAuthorizationUI.this, 421 getAdvancedPropertiesPanel().getAdvancedParameters(), 422 requestToken 423 ); 424 executor.execute(task); 425 Runnable r = () -> { 426 if (task.isCanceled()) return; 427 if (task.getAccessToken() == null) return; 428 GuiHelper.runInEDT(() -> { 429 setAccessToken(task.getAccessToken()); 430 transitionToShowAccessToken(); 431 }); 432 }; 433 executor.execute(r); 434 } 435 } 436 437 /** 438 * Action for testing an Access Token 439 */ 440 class TestAccessTokenAction extends AbstractAction { 441 442 TestAccessTokenAction() { 443 putValue(NAME, tr("Test Access Token")); 444 new ImageProvider("oauth", "oauth-small").getResource().attachImageIcon(this); 445 putValue(SHORT_DESCRIPTION, tr("Click to test the Access Token")); 446 } 447 448 @Override 449 public void actionPerformed(ActionEvent evt) { 450 TestAccessTokenTask task = new TestAccessTokenTask( 451 SemiAutomaticAuthorizationUI.this, 452 getApiUrl(), 453 getAdvancedPropertiesPanel().getAdvancedParameters(), 454 getAccessToken() 455 ); 456 executor.execute(task); 457 } 458 } 459}