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