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.FlowLayout;
008import java.awt.GridBagConstraints;
009import java.awt.GridBagLayout;
010import java.awt.Insets;
011import java.awt.event.ActionEvent;
012import java.beans.PropertyChangeEvent;
013import java.beans.PropertyChangeListener;
014import java.util.concurrent.Executor;
015
016import javax.swing.AbstractAction;
017import javax.swing.BorderFactory;
018import javax.swing.JCheckBox;
019import javax.swing.JLabel;
020import javax.swing.JPanel;
021import javax.swing.JTabbedPane;
022import javax.swing.event.DocumentEvent;
023import javax.swing.event.DocumentListener;
024import javax.swing.text.JTextComponent;
025
026import org.openstreetmap.josm.data.oauth.OAuthToken;
027import org.openstreetmap.josm.gui.SideButton;
028import org.openstreetmap.josm.gui.preferences.server.OAuthAccessTokenHolder;
029import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator;
030import org.openstreetmap.josm.gui.widgets.HtmlPanel;
031import org.openstreetmap.josm.gui.widgets.JosmTextField;
032import org.openstreetmap.josm.gui.widgets.SelectAllOnFocusGainedDecorator;
033import org.openstreetmap.josm.tools.ImageProvider;
034
035/**
036 * This is an UI which supports a JOSM user to get an OAuth Access Token in a fully
037 * manual process.
038 *
039 * @since 2746
040 */
041public class ManualAuthorizationUI extends AbstractAuthorizationUI {
042
043    private JosmTextField tfAccessTokenKey;
044    private transient AccessTokenKeyValidator valAccessTokenKey;
045    private JosmTextField tfAccessTokenSecret;
046    private transient AccessTokenSecretValidator valAccessTokenSecret;
047    private JCheckBox cbSaveToPreferences;
048    private HtmlPanel pnlMessage;
049    private final transient Executor executor;
050
051    protected JPanel buildAccessTokenPanel() {
052        JPanel pnl = new JPanel(new GridBagLayout());
053        pnl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
054        GridBagConstraints gc = new GridBagConstraints();
055        AccessTokenBuilder accessTokenBuilder = new AccessTokenBuilder();
056
057        // the access token key input field
058        gc.anchor = GridBagConstraints.NORTHWEST;
059        gc.fill = GridBagConstraints.HORIZONTAL;
060        gc.weightx = 0.0;
061        gc.gridwidth = 2;
062        gc.insets = new Insets(0, 0, 5, 0);
063        pnlMessage = new HtmlPanel();
064        pnlMessage.setText("<html><body>"
065                + tr("Please enter an OAuth Access Token which is authorized to access the OSM server "
066                        + "''{0}''.",
067                        getApiUrl()) + "</body></html>");
068        pnl.add(pnlMessage, gc);
069
070        // the access token key input field
071        gc.gridy = 1;
072        gc.weightx = 0.0;
073        gc.gridwidth = 1;
074        gc.insets = new Insets(0, 0, 0, 3);
075        pnl.add(new JLabel(tr("Access Token Key:")), gc);
076
077        gc.gridx = 1;
078        gc.weightx = 1.0;
079        pnl.add(tfAccessTokenKey = new JosmTextField(), gc);
080        SelectAllOnFocusGainedDecorator.decorate(tfAccessTokenKey);
081        valAccessTokenKey = new AccessTokenKeyValidator(tfAccessTokenKey);
082        valAccessTokenKey.validate();
083        tfAccessTokenKey.getDocument().addDocumentListener(accessTokenBuilder);
084
085        // the access token key input field
086        gc.gridy = 2;
087        gc.gridx = 0;
088        gc.weightx = 0.0;
089        pnl.add(new JLabel(tr("Access Token Secret:")), gc);
090
091        gc.gridx = 1;
092        gc.weightx = 1.0;
093        pnl.add(tfAccessTokenSecret = new JosmTextField(), gc);
094        SelectAllOnFocusGainedDecorator.decorate(tfAccessTokenSecret);
095        valAccessTokenSecret = new AccessTokenSecretValidator(tfAccessTokenSecret);
096        valAccessTokenSecret.validate();
097        tfAccessTokenSecret.getDocument().addDocumentListener(accessTokenBuilder);
098
099        // the checkbox for saving to preferences
100        gc.gridy = 3;
101        gc.gridx = 0;
102        gc.gridwidth = 2;
103        gc.weightx = 1.0;
104        pnl.add(cbSaveToPreferences = new JCheckBox(tr("Save Access Token in preferences")), gc);
105        cbSaveToPreferences.setSelected(OAuthAccessTokenHolder.getInstance().isSaveToPreferences());
106
107        // filler - grab remaining space
108        gc.gridy = 3;
109        gc.gridx = 0;
110        gc.gridwidth = 2;
111        gc.weightx = 1.0;
112        gc.weighty = 1.0;
113        gc.fill = GridBagConstraints.BOTH;
114        pnl.add(new JPanel(), gc);
115        return pnl;
116    }
117
118    protected JPanel buildTabbedPreferencesPanel() {
119        JPanel pnl = new JPanel(new BorderLayout());
120
121        JTabbedPane tp = new JTabbedPane();
122        tp.add(buildAccessTokenPanel());
123        tp.add(getAdvancedPropertiesPanel());
124
125        tp.setTitleAt(0, tr("Access Token"));
126        tp.setTitleAt(1, tr("Advanced OAuth parameters"));
127
128        tp.setToolTipTextAt(0, tr("Enter the OAuth Access Token"));
129        tp.setToolTipTextAt(1, tr("Enter advanced OAuth properties"));
130
131        pnl.add(tp, BorderLayout.CENTER);
132        return pnl;
133    }
134
135    protected JPanel buildActionsPanel() {
136        JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT));
137        TestAccessTokenAction actTestAccessToken = new TestAccessTokenAction();
138        pnl.add(new SideButton(actTestAccessToken));
139        this.addPropertyChangeListener(actTestAccessToken);
140        return pnl;
141    }
142
143    @Override
144    public void setApiUrl(String apiUrl) {
145        super.setApiUrl(apiUrl);
146        if (pnlMessage != null) {
147            pnlMessage.setText(tr("<html><body>"
148                    + "Please enter an OAuth Access Token which is authorized to access the OSM server "
149                    + "''{0}''."
150                    + "</body></html>",
151                    getApiUrl()
152            ));
153        }
154    }
155
156    protected final void build() {
157        setLayout(new BorderLayout());
158        setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
159        add(buildTabbedPreferencesPanel(), BorderLayout.CENTER);
160        add(buildActionsPanel(), BorderLayout.SOUTH);
161    }
162
163    /**
164     * Constructs a new {@code ManualAuthorizationUI} for the given API URL.
165     * @param apiUrl The OSM API URL
166     * @param executor the executor used for running the HTTP requests for the authorization
167     * @since 5422
168     */
169    public ManualAuthorizationUI(String apiUrl, Executor executor) {
170        super(apiUrl);
171        this.executor = executor;
172        build();
173    }
174
175    @Override
176    public boolean isSaveAccessTokenToPreferences() {
177        return cbSaveToPreferences.isSelected();
178    }
179
180    private static class AccessTokenKeyValidator extends AbstractTextComponentValidator {
181
182        AccessTokenKeyValidator(JTextComponent tc) {
183            super(tc);
184        }
185
186        @Override
187        public boolean isValid() {
188            return !getComponent().getText().trim().isEmpty();
189        }
190
191        @Override
192        public void validate() {
193            if (isValid()) {
194                feedbackValid(tr("Please enter an Access Token Key"));
195            } else {
196                feedbackInvalid(tr("The Access Token Key must not be empty. Please enter an Access Token Key"));
197            }
198        }
199    }
200
201    private static class AccessTokenSecretValidator extends AbstractTextComponentValidator {
202        AccessTokenSecretValidator(JTextComponent tc) {
203            super(tc);
204        }
205
206        @Override
207        public boolean isValid() {
208            return !getComponent().getText().trim().isEmpty();
209        }
210
211        @Override
212        public void validate() {
213            if (isValid()) {
214                feedbackValid(tr("Please enter an Access Token Secret"));
215            } else {
216                feedbackInvalid(tr("The Access Token Secret must not be empty. Please enter an Access Token Secret"));
217            }
218        }
219    }
220
221    class AccessTokenBuilder implements DocumentListener {
222
223        public void build() {
224            if (!valAccessTokenKey.isValid() || !valAccessTokenSecret.isValid()) {
225                setAccessToken(null);
226            } else {
227                setAccessToken(new OAuthToken(tfAccessTokenKey.getText().trim(), tfAccessTokenSecret.getText().trim()));
228            }
229        }
230
231        @Override
232        public void changedUpdate(DocumentEvent e) {
233            build();
234        }
235
236        @Override
237        public void insertUpdate(DocumentEvent e) {
238            build();
239        }
240
241        @Override
242        public void removeUpdate(DocumentEvent e) {
243            build();
244        }
245    }
246
247    /**
248     * Action for testing an Access Token
249     */
250    class TestAccessTokenAction extends AbstractAction implements PropertyChangeListener {
251        TestAccessTokenAction() {
252            putValue(NAME, tr("Test Access Token"));
253            putValue(SMALL_ICON, ImageProvider.get("oauth", "oauth-small"));
254            putValue(SHORT_DESCRIPTION, tr("Click to test the Access Token"));
255            updateEnabledState();
256        }
257
258        @Override
259        public void actionPerformed(ActionEvent evt) {
260            TestAccessTokenTask task = new TestAccessTokenTask(
261                    ManualAuthorizationUI.this,
262                    getApiUrl(),
263                    getAdvancedPropertiesPanel().getAdvancedParameters(),
264                    getAccessToken()
265            );
266            executor.execute(task);
267        }
268
269        protected final void updateEnabledState() {
270            setEnabled(hasAccessToken());
271        }
272
273        @Override
274        public void propertyChange(PropertyChangeEvent evt) {
275            if (!evt.getPropertyName().equals(AbstractAuthorizationUI.ACCESS_TOKEN_PROP))
276                return;
277            updateEnabledState();
278        }
279    }
280}