source: josm/trunk/src/org/openstreetmap/josm/gui/JosmUserIdentityManager.java@ 12288

Last change on this file since 12288 was 11906, checked in by stoecker, 7 years ago

fix NPE caused by variable case mismatch exposed by r11901

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