source: josm/trunk/src/org/openstreetmap/josm/data/UserIdentityManager.java@ 17335

Last change on this file since 17335 was 15725, checked in by GerdP, 4 years ago

fix #11914: Allow to show user name in titlebar

  • implement UserIdentityListener in UserIdentityManager
  • add new checkbox "Show user name in title" in "Look and Feel" preference dialog
  • if enabled and not anonymous, show current user prefixed with "@" in titlebar of main window
  • Property svn:eol-style set to native
File size: 12.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.text.MessageFormat;
7
8import org.openstreetmap.josm.data.oauth.OAuthAccessTokenHolder;
9import org.openstreetmap.josm.data.osm.User;
10import org.openstreetmap.josm.data.osm.UserInfo;
11import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
12import org.openstreetmap.josm.io.NetworkManager;
13import org.openstreetmap.josm.io.OnlineResource;
14import org.openstreetmap.josm.io.OsmApi;
15import org.openstreetmap.josm.io.OsmServerUserInfoReader;
16import org.openstreetmap.josm.io.OsmTransferException;
17import org.openstreetmap.josm.io.auth.CredentialsManager;
18import org.openstreetmap.josm.spi.preferences.Config;
19import org.openstreetmap.josm.spi.preferences.PreferenceChangeEvent;
20import org.openstreetmap.josm.spi.preferences.PreferenceChangedListener;
21import org.openstreetmap.josm.spi.preferences.StringSetting;
22import org.openstreetmap.josm.tools.CheckParameterUtil;
23import org.openstreetmap.josm.tools.JosmRuntimeException;
24import org.openstreetmap.josm.tools.ListenerList;
25import org.openstreetmap.josm.tools.Logging;
26
27/**
28 * UserIdentityManager is a global object which keeps track of what JOSM knows about
29 * the identity of the current user.
30 *
31 * JOSM can be operated anonymously provided the current user never invokes an operation
32 * on the OSM server which required authentication. In this case JOSM neither knows
33 * the user name of the OSM account of the current user nor its unique id. Perhaps the
34 * user doesn't have one.
35 *
36 * If the current user supplies a user name and a password in the JOSM preferences JOSM
37 * can partially identify the user.
38 *
39 * The current user is fully identified if JOSM knows both the user name and the unique
40 * id of the users OSM account. The latter is retrieved from the OSM server with a
41 * <code>GET /api/0.6/user/details</code> request, submitted with the user name and password
42 * of the current user.
43 *
44 * The global UserIdentityManager listens to {@link PreferenceChangeEvent}s and keeps track
45 * of what the current JOSM instance knows about the current user. Other subsystems can
46 * let the global UserIdentityManager know in case they fully identify the current user, see
47 * {@link #setFullyIdentified}.
48 *
49 * The information kept by the UserIdentityManager can be used to
50 * <ul>
51 * <li>safely query changesets owned by the current user based on its user id, not on its user name</li>
52 * <li>safely search for objects last touched by the current user based on its user id, not on its user name</li>
53 * </ul>
54 * @since 12743 (renamed from {@code org.openstreetmap.josm.gui.JosmUserIdentityManager})
55 * @since 2689 (creation)
56 */
57public final class UserIdentityManager implements PreferenceChangedListener {
58
59 private static UserIdentityManager instance;
60 private final ListenerList<UserIdentityListener> listeners = ListenerList.create();
61
62 /**
63 * Replies the unique instance of the JOSM user identity manager
64 *
65 * @return the unique instance of the JOSM user identity manager
66 */
67 public static synchronized UserIdentityManager getInstance() {
68 if (instance == null) {
69 instance = new UserIdentityManager();
70 if (OsmApi.isUsingOAuth() && OAuthAccessTokenHolder.getInstance().containsAccessToken() &&
71 !NetworkManager.isOffline(OnlineResource.OSM_API)) {
72 try {
73 instance.initFromOAuth();
74 } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException e) {
75 Logging.error(e);
76 // Fall back to preferences if OAuth identification fails for any reason
77 instance.initFromPreferences();
78 }
79 } else {
80 instance.initFromPreferences();
81 }
82 Config.getPref().addPreferenceChangeListener(instance);
83 }
84 return instance;
85 }
86
87 private String userName;
88 private UserInfo userInfo;
89 private boolean accessTokenKeyChanged;
90 private boolean accessTokenSecretChanged;
91
92 private UserIdentityManager() {
93 }
94
95 /**
96 * Remembers the fact that the current JOSM user is anonymous.
97 */
98 public void setAnonymous() {
99 userName = null;
100 userInfo = null;
101 fireUserIdentityChanged();
102 }
103
104 /**
105 * Remembers the fact that the current JOSM user is partially identified
106 * by the user name of its OSM account.
107 *
108 * @param userName the user name. Must not be null. Must not be empty (whitespace only).
109 * @throws IllegalArgumentException if userName is null
110 * @throws IllegalArgumentException if userName is empty
111 */
112 public void setPartiallyIdentified(String userName) {
113 CheckParameterUtil.ensureParameterNotNull(userName, "userName");
114 String trimmedUserName = userName.trim();
115 if (trimmedUserName.isEmpty())
116 throw new IllegalArgumentException(
117 MessageFormat.format("Expected non-empty value for parameter ''{0}'', got ''{1}''", "userName", userName));
118 this.userName = trimmedUserName;
119 userInfo = null;
120 fireUserIdentityChanged();
121 }
122
123 /**
124 * Remembers the fact that the current JOSM user is fully identified with a
125 * verified pair of user name and user id.
126 *
127 * @param userName the user name. Must not be null. Must not be empty.
128 * @param userInfo additional information about the user, retrieved from the OSM server and including the user id
129 * @throws IllegalArgumentException if userName is null
130 * @throws IllegalArgumentException if userName is empty
131 * @throws IllegalArgumentException if userInfo is null
132 */
133 public void setFullyIdentified(String userName, UserInfo userInfo) {
134 CheckParameterUtil.ensureParameterNotNull(userName, "userName");
135 String trimmedUserName = userName.trim();
136 if (trimmedUserName.isEmpty())
137 throw new IllegalArgumentException(tr("Expected non-empty value for parameter ''{0}'', got ''{1}''", "userName", userName));
138 CheckParameterUtil.ensureParameterNotNull(userInfo, "userInfo");
139 this.userName = trimmedUserName;
140 this.userInfo = userInfo;
141 fireUserIdentityChanged();
142 }
143
144 /**
145 * Replies true if the current JOSM user is anonymous.
146 *
147 * @return {@code true} if the current user is anonymous.
148 */
149 public boolean isAnonymous() {
150 return userName == null && userInfo == null;
151 }
152
153 /**
154 * Replies true if the current JOSM user is partially identified.
155 *
156 * @return true if the current JOSM user is partially identified.
157 */
158 public boolean isPartiallyIdentified() {
159 return userName != null && userInfo == null;
160 }
161
162 /**
163 * Replies true if the current JOSM user is fully identified.
164 *
165 * @return true if the current JOSM user is fully identified.
166 */
167 public boolean isFullyIdentified() {
168 return userName != null && userInfo != null;
169 }
170
171 /**
172 * Replies the user name of the current JOSM user. null, if {@link #isAnonymous()} is true.
173 *
174 * @return the user name of the current JOSM user
175 */
176 public String getUserName() {
177 return userName;
178 }
179
180 /**
181 * Replies the user id of the current JOSM user. 0, if {@link #isAnonymous()} or
182 * {@link #isPartiallyIdentified()} is true.
183 *
184 * @return the user id of the current JOSM user
185 */
186 public int getUserId() {
187 if (userInfo == null) return 0;
188 return userInfo.getId();
189 }
190
191 /**
192 * Replies verified additional information about the current user if the user is
193 * {@link #isFullyIdentified()}.
194 *
195 * @return verified additional information about the current user
196 */
197 public UserInfo getUserInfo() {
198 return userInfo;
199 }
200
201 /**
202 * Returns the identity as a {@link User} object
203 *
204 * @return the identity as user, or {@link User#getAnonymous()} if {@link #isAnonymous()}
205 */
206 public User asUser() {
207 return isAnonymous() ? User.getAnonymous() : User.createOsmUser(userInfo != null ? userInfo.getId() : 0, userName);
208 }
209
210 /**
211 * Initializes the user identity manager from Basic Authentication values in the {@link org.openstreetmap.josm.data.Preferences}
212 * This method should be called if {@code osm-server.auth-method} is set to {@code basic}.
213 * @see #initFromOAuth
214 */
215 public void initFromPreferences() {
216 String credentialsUserName = CredentialsManager.getInstance().getUsername();
217 if (isAnonymous()) {
218 if (credentialsUserName != null && !credentialsUserName.trim().isEmpty()) {
219 setPartiallyIdentified(credentialsUserName);
220 }
221 } else {
222 if (credentialsUserName != null && !credentialsUserName.equals(this.userName)) {
223 setPartiallyIdentified(credentialsUserName);
224 }
225 // else: same name in the preferences as JOSM already knows about.
226 // keep the state, be it partially or fully identified
227 }
228 }
229
230 /**
231 * Initializes the user identity manager from OAuth request of user details.
232 * This method should be called if {@code osm-server.auth-method} is set to {@code oauth}.
233 * @see #initFromPreferences
234 * @since 5434
235 */
236 public void initFromOAuth() {
237 try {
238 UserInfo info = new OsmServerUserInfoReader().fetchUserInfo(NullProgressMonitor.INSTANCE);
239 setFullyIdentified(info.getDisplayName(), info);
240 } catch (IllegalArgumentException | OsmTransferException e) {
241 Logging.error(e);
242 }
243 }
244
245 /**
246 * Replies true if the user with name <code>username</code> is the current user
247 *
248 * @param userName the user name
249 * @return true if the user with name <code>username</code> is the current user
250 */
251 public boolean isCurrentUser(String userName) {
252 return this.userName != null && this.userName.equals(userName);
253 }
254
255 /**
256 * Replies true if the current user is {@link #isFullyIdentified() fully identified} and the {@link #getUserId() user ids} match,
257 * or if the current user is not {@link #isFullyIdentified() fully identified} and the {@link #getUserName() user names} match.
258 *
259 * @param user the user to test
260 * @return true if given user is the current user
261 */
262 public boolean isCurrentUser(User user) {
263 if (user == null) {
264 return false;
265 } else if (isFullyIdentified()) {
266 return getUserId() == user.getId();
267 } else {
268 return isCurrentUser(user.getName());
269 }
270 }
271
272 /* ------------------------------------------------------------------- */
273 /* interface PreferenceChangeListener */
274 /* ------------------------------------------------------------------- */
275 @Override
276 public void preferenceChanged(PreferenceChangeEvent evt) {
277 switch (evt.getKey()) {
278 case "osm-server.username":
279 String newUserName = null;
280 if (evt.getNewValue() instanceof StringSetting) {
281 newUserName = ((StringSetting) evt.getNewValue()).getValue();
282 }
283 if (newUserName == null || newUserName.trim().isEmpty()) {
284 setAnonymous();
285 } else {
286 if (!newUserName.equals(userName)) {
287 setPartiallyIdentified(newUserName);
288 }
289 }
290 return;
291 case "osm-server.url":
292 String newUrl = null;
293 if (evt.getNewValue() instanceof StringSetting) {
294 newUrl = ((StringSetting) evt.getNewValue()).getValue();
295 }
296 if (newUrl == null || newUrl.trim().isEmpty()) {
297 setAnonymous();
298 } else if (isFullyIdentified()) {
299 setPartiallyIdentified(getUserName());
300 }
301 break;
302 case "oauth.access-token.key":
303 accessTokenKeyChanged = true;
304 break;
305 case "oauth.access-token.secret":
306 accessTokenSecretChanged = true;
307 break;
308 default: // Do nothing
309 }
310
311 if (accessTokenKeyChanged && accessTokenSecretChanged) {
312 accessTokenKeyChanged = false;
313 accessTokenSecretChanged = false;
314 if (OsmApi.isUsingOAuth()) {
315 getInstance().initFromOAuth();
316 }
317 }
318 }
319
320 /**
321 * This listener is notified whenever the osm user is changed.
322 */
323 @FunctionalInterface
324 public interface UserIdentityListener {
325 /**
326 * The current user was changed.
327 */
328 void userIdentityChanged();
329 }
330
331 /**
332 * Add a listener that listens to changes of the current user.
333 * @param listener The listener
334 */
335 public void addListener(UserIdentityListener listener) {
336 listeners.addListener(listener);
337 }
338
339 private void fireUserIdentityChanged() {
340 listeners.fireEvent(UserIdentityListener::userIdentityChanged);
341 }
342}
Note: See TracBrowser for help on using the repository browser.