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