001 // License: GPL. For details, see LICENSE file. 002 package org.openstreetmap.josm.gui; 003 004 import static org.openstreetmap.josm.tools.I18n.tr; 005 006 import java.awt.Component; 007 import java.text.MessageFormat; 008 009 import org.openstreetmap.josm.Main; 010 import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent; 011 import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener; 012 import org.openstreetmap.josm.data.Preferences.StringSetting; 013 import org.openstreetmap.josm.data.osm.UserInfo; 014 import org.openstreetmap.josm.gui.preferences.server.OAuthAccessTokenHolder; 015 import org.openstreetmap.josm.gui.progress.NullProgressMonitor; 016 import org.openstreetmap.josm.io.OsmServerUserInfoReader; 017 import org.openstreetmap.josm.io.OsmTransferException; 018 import org.openstreetmap.josm.io.auth.CredentialsManager; 019 import org.openstreetmap.josm.tools.CheckParameterUtil; 020 021 /** 022 * JosmUserStateManager is a global object which keeps track of what JOSM knows about 023 * the identity of the current user. 024 * 025 * JOSM can be operated anonymously provided the current user never invokes an operation 026 * on the OSM server which required authentication. In this case JOSM neither knows 027 * the user name of the OSM account of the current user nor its unique id. Perhaps the 028 * user doesn't have one. 029 * 030 * If the current user supplies a user name and a password in the JOSM preferences JOSM 031 * can partially identify the user. 032 * 033 * The current user is fully identified if JOSM knows both the user name and the unique 034 * id of the users OSM account. The latter is retrieved from the OSM server with a 035 * <tt>GET /api/0.6/user/details</tt> request, submitted with the user name and password 036 * of the current user. 037 * 038 * The global JosmUserStateManager listens to {@link PreferenceChangeEvent}s and keeps track 039 * of what the current JOSM instance knows about the current user. Other subsystems can 040 * let the global JosmUserStateManager know in case they fully identify the current user, see 041 * {@link #setFullyIdentified}. 042 * 043 * The information kept by the JosmUserStateManager can be used to 044 * <ul> 045 * <li>safely query changesets owned by the current user based on its user id, not on its user name</li> 046 * <li>safely search for objects last touched by the current user based on its user id, not on its user name</li> 047 * </ul> 048 * 049 */ 050 public class JosmUserIdentityManager implements PreferenceChangedListener{ 051 052 static private JosmUserIdentityManager instance; 053 054 /** 055 * Replies the unique instance of the JOSM user identity manager 056 * 057 * @return the unique instance of the JOSM user identity manager 058 */ 059 static public JosmUserIdentityManager getInstance() { 060 if (instance == null) { 061 instance = new JosmUserIdentityManager(); 062 if (Main.pref.get("osm-server.auth-method").equals("oauth") && OAuthAccessTokenHolder.getInstance().containsAccessToken()) { 063 try { 064 instance.initFromOAuth(Main.parent); 065 } catch (Throwable t) { 066 t.printStackTrace(); 067 // Fall back to preferences if OAuth identification fails for any reason 068 instance.initFromPreferences(); 069 } 070 } else { 071 instance.initFromPreferences(); 072 } 073 Main.pref.addPreferenceChangeListener(instance); 074 } 075 return instance; 076 } 077 078 private String userName; 079 private UserInfo userInfo; 080 private boolean accessTokenKeyChanged; 081 private boolean accessTokenSecretChanged; 082 083 private JosmUserIdentityManager() { 084 } 085 086 /** 087 * Remembers the fact that the current JOSM user is anonymous. 088 */ 089 public void setAnonymous() { 090 userName = null; 091 userInfo = null; 092 } 093 094 /** 095 * Remebers the fact that the current JOSM user is partially identified 096 * by the user name of its OSM account. 097 * 098 * @param userName the user name. Must not be null. Must not be empty (whitespace only). 099 * @throws IllegalArgumentException thrown if userName is null 100 * @throws IllegalArgumentException thrown if userName is empty 101 */ 102 public void setPartiallyIdentified(String userName) throws IllegalArgumentException { 103 CheckParameterUtil.ensureParameterNotNull(userName, "userName"); 104 if (userName.trim().equals("")) 105 throw new IllegalArgumentException(MessageFormat.format("Expected non-empty value for parameter ''{0}'', got ''{1}''", "userName", userName)); 106 this.userName = userName; 107 userInfo = null; 108 } 109 110 /** 111 * Remembers the fact that the current JOSM user is fully identified with a 112 * verified pair of user name and user id. 113 * 114 * @param userName the user name. Must not be null. Must not be empty. 115 * @param userinfo additional information about the user, retrieved from the OSM server and including the user id 116 * @throws IllegalArgumentException thrown if userName is null 117 * @throws IllegalArgumentException thrown if userName is empty 118 * @throws IllegalArgumentException thrown if userinfo is null 119 */ 120 public void setFullyIdentified(String username, UserInfo userinfo) throws IllegalArgumentException { 121 CheckParameterUtil.ensureParameterNotNull(username, "username"); 122 if (username.trim().equals("")) 123 throw new IllegalArgumentException(tr("Expected non-empty value for parameter ''{0}'', got ''{1}''", "userName", userName)); 124 CheckParameterUtil.ensureParameterNotNull(userinfo, "userinfo"); 125 this.userName = username; 126 this.userInfo = userinfo; 127 } 128 129 /** 130 * Replies true if the current JOSM user is anonymous. 131 * 132 * @return true if the current user is anonymous. 133 */ 134 public boolean isAnonymous() { 135 return userName == null && userInfo == null; 136 } 137 138 /** 139 * Replies true if the current JOSM user is partially identified. 140 * 141 * @return true if the current JOSM user is partially identified. 142 */ 143 public boolean isPartiallyIdentified() { 144 return userName != null && userInfo == null; 145 } 146 147 /** 148 * Replies true if the current JOSM user is fully identified. 149 * 150 * @return true if the current JOSM user is fully identified. 151 */ 152 public boolean isFullyIdentified() { 153 return userName != null && userInfo != null; 154 } 155 156 /** 157 * Replies the user name of the current JOSM user. null, if {@link #isAnonymous()} is true. 158 * 159 * @return the user name of the current JOSM user 160 */ 161 public String getUserName() { 162 return userName; 163 } 164 165 /** 166 * Replies the user id of the current JOSM user. 0, if {@link #isAnonymous()} or 167 * {@link #isPartiallyIdentified()} is true. 168 * 169 * @return the user id of the current JOSM user 170 */ 171 public int getUserId() { 172 if (userInfo == null) return 0; 173 return userInfo.getId(); 174 } 175 176 /** 177 * Replies verified additional information about the current user if the user is 178 * {@link #isFullyIdentified()}. 179 * 180 * @return verified additional information about the current user 181 */ 182 public UserInfo getUserInfo() { 183 return userInfo; 184 } 185 186 /** 187 * Initializes the user identity manager from Basic Authentication values in the {@link org.openstreetmap.josm.data.Preferences} 188 * This method should be called if {@code osm-server.auth-method} is set to {@code basic}. 189 * @see #initFromOAuth 190 */ 191 public void initFromPreferences() { 192 String userName = CredentialsManager.getInstance().getUsername(); 193 if (isAnonymous()) { 194 if (userName != null && ! userName.trim().equals("")) { 195 setPartiallyIdentified(userName); 196 } 197 } else { 198 if (userName != null && !userName.equals(this.userName)) { 199 setPartiallyIdentified(userName); 200 } else { 201 // same name in the preferences as JOSM already knows about; 202 // keep the state, be it partially or fully identified 203 } 204 } 205 } 206 207 /** 208 * Initializes the user identity manager from OAuth request of user details. 209 * This method should be called if {@code osm-server.auth-method} is set to {@code oauth}. 210 * @param parent component relative to which the {@link PleaseWaitDialog} is displayed. 211 * @see #initFromPreferences 212 * @since 5434 213 */ 214 public void initFromOAuth(Component parent) { 215 try { 216 UserInfo info = new OsmServerUserInfoReader().fetchUserInfo(NullProgressMonitor.INSTANCE); 217 setFullyIdentified(info.getDisplayName(), info); 218 } catch (IllegalArgumentException e) { 219 e.printStackTrace(); 220 } catch (OsmTransferException e) { 221 e.printStackTrace(); 222 } 223 } 224 225 /** 226 * Replies true if the user with name <code>username</code> is the current 227 * user 228 * 229 * @param username the user name 230 * @return true if the user with name <code>username</code> is the current 231 * user 232 */ 233 public boolean isCurrentUser(String username) { 234 if (username == null || this.userName == null) return false; 235 return this.userName.equals(username); 236 } 237 238 /* ------------------------------------------------------------------- */ 239 /* interface PreferenceChangeListener */ 240 /* ------------------------------------------------------------------- */ 241 public void preferenceChanged(PreferenceChangeEvent evt) { 242 if (evt.getKey().equals("osm-server.username")) { 243 if (!(evt.getNewValue() instanceof StringSetting)) return; 244 String newValue = ((StringSetting) evt.getNewValue()).getValue(); 245 if (newValue == null || newValue.trim().length() == 0) { 246 setAnonymous(); 247 } else { 248 if (! newValue.equals(userName)) { 249 setPartiallyIdentified(newValue); 250 } 251 } 252 return; 253 254 } else if (evt.getKey().equals("osm-server.url")) { 255 if (!(evt.getNewValue() instanceof StringSetting)) return; 256 String newValue = ((StringSetting) evt.getNewValue()).getValue(); 257 if (newValue == null || newValue.trim().equals("")) { 258 setAnonymous(); 259 } else if (isFullyIdentified()) { 260 setPartiallyIdentified(getUserName()); 261 } 262 263 } else if (evt.getKey().equals("oauth.access-token.key")) { 264 accessTokenKeyChanged = true; 265 266 } else if (evt.getKey().equals("oauth.access-token.secret")) { 267 accessTokenSecretChanged = true; 268 } 269 270 if (accessTokenKeyChanged && accessTokenSecretChanged) { 271 accessTokenKeyChanged = false; 272 accessTokenSecretChanged = false; 273 if (Main.pref.get("osm-server.auth-method").equals("oauth")) { 274 try { 275 instance.initFromOAuth(Main.parent); 276 } catch (Throwable t) { 277 t.printStackTrace(); 278 } 279 } 280 } 281 } 282 }