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