mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-10-17 01:31:01 +00:00
added preferences support to user store; see bug 2429
This commit is contained in:
1 parent
8b45395fcc
commit
2052941172
15 files changed
+207
-19
No files matched your search
@@ -50,7 +50,7 @@
|
||||
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-war "${project_loc:com.sap.sailing.gwt.ui}" -noserver -remoteUI "${gwt_remote_ui_server_port}:${unique_id}" -logLevel INFO -codeServerPort 9997 -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/UserManagement.html com.sap.sailing.gwt.ui.UserManagement -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/TvView.html com.sap.sailing.gwt.ui.TvView -startupUrl /gwt/PolarSheets.html com.sap.sailing.gwt.ui.PolarSheets -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.ui.RegattaOverview -startupUrl /gwt/DataMining.html com.sap.sailing.gwt.ui.DataMining -startupUrl /gwt/Simulator.html com.sap.sailing.gwt.ui.Simulator -startupUrl /gwt/VideoPopup.html com.sap.sailing.gwt.ui.VideoPopup -startupUrl /gwt/Search.html com.sap.sailing.gwt.ui.Search -startupUrl /security/ui/Login.html com.sap.sse.security.ui.Login -startupUrl /security/ui/EditProfile.html com.sap.sse.security.ui.EditProfile -startupUrl /security/ui/EmailValidation.html com.sap.sse.security.ui.EmailValidation"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-war "${project_loc:com.sap.sailing.gwt.ui}" -noserver -remoteUI "${gwt_remote_ui_server_port}:${unique_id}" -logLevel INFO -codeServerPort 9997 -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/UserManagement.html com.sap.sailing.gwt.ui.UserManagement -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/TvView.html com.sap.sailing.gwt.ui.TvView -startupUrl /gwt/PolarSheets.html com.sap.sailing.gwt.ui.PolarSheets -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.ui.RegattaOverview -startupUrl /gwt/DataMining.html com.sap.sailing.gwt.ui.DataMining -startupUrl /gwt/Simulator.html com.sap.sailing.gwt.ui.Simulator -startupUrl /gwt/VideoPopup.html com.sap.sailing.gwt.ui.VideoPopup -startupUrl /gwt/Search.html com.sap.sailing.gwt.ui.Search -startupUrl /security/ui/Login.html com.sap.sse.security.ui.Login -startupUrl /security/ui/EditProfile.html com.sap.sse.security.ui.EditProfile -startupUrl /security/ui/EmailValidation.html com.sap.sse.security.ui.EmailValidation com.sap.sailing.gwt.ui.Simulator com.sap.sailing.gwt.ui.Leaderboard com.sap.sailing.gwt.ui.PolarSheets com.sap.sailing.gwt.ui.VideoPopup com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.ui.RegattaOverview com.sap.sailing.gwt.ui.Spectator com.sap.sailing.gwt.ui.TvView com.sap.sailing.gwt.ui.UserManagement com.sap.sailing.gwt.ui.Search com.sap.sailing.gwt.ui.AdminConsole com.sap.sailing.gwt.ui.LeaderboardEditing com.sap.sailing.gwt.ui.YoutubePopup com.sap.sailing.gwt.ui.DataMining com.sap.sailing.gwt.Home com.sap.sailing.gwt.ui.RaceBoard"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.gwt.ui"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx2048m -XX:MaxPermSize=512m"/>
|
||||
</launchConfiguration>
|
||||
+3
-3
@@ -45,11 +45,11 @@ import com.sap.sse.common.Util;
|
||||
|
||||
public class StoreGPSFixTrackTest extends AbstractMongoDBTest {
|
||||
public StoreGPSFixTrackTest() throws UnknownHostException, MongoException {
|
||||
super();
|
||||
}
|
||||
super();
|
||||
}
|
||||
|
||||
@After
|
||||
public void dropCollection() {
|
||||
public void dropCollection() {
|
||||
DB db = getMongoService().getDB();
|
||||
db.getCollection(CollectionNames.GPS_FIXES.name()).drop();
|
||||
}
|
||||
|
||||
@@ -7,4 +7,6 @@ Bundle-Vendor: SAP
|
||||
Fragment-Host: com.sap.sse.security
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
|
||||
Require-Bundle: org.junit4;bundle-version="4.8.2",
|
||||
com.sap.sse.security.userstore.mongodb
|
||||
com.sap.sse.security.userstore.mongodb,
|
||||
com.mongodb.driver,
|
||||
com.sap.sailing.mongodb
|
||||
@@ -1,23 +1,38 @@
|
||||
package com.sap.sse.security.test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.mongodb.DB;
|
||||
import com.mongodb.MongoException;
|
||||
import com.sap.sailing.mongodb.MongoDBConfiguration;
|
||||
import com.sap.sailing.mongodb.MongoDBService;
|
||||
import com.sap.sse.security.SecurityServiceImpl;
|
||||
import com.sap.sse.security.UserStore;
|
||||
import com.sap.sse.security.UsernamePasswordRealm;
|
||||
import com.sap.sse.security.impl.Activator;
|
||||
import com.sap.sse.security.userstore.mongodb.UserStoreImpl;
|
||||
import com.sap.sse.security.userstore.mongodb.impl.CollectionNames;
|
||||
|
||||
public class LoginTest {
|
||||
@BeforeClass
|
||||
public static void setUp() {
|
||||
UserStore store = new UserStoreImpl();
|
||||
private UserStoreImpl store;
|
||||
|
||||
@Before
|
||||
public void setUp() throws UnknownHostException, MongoException {
|
||||
final MongoDBConfiguration dbConfiguration = MongoDBConfiguration.getDefaultTestConfiguration();
|
||||
final MongoDBService service = dbConfiguration.getService();
|
||||
DB db = service.getDB();
|
||||
db.getCollection(CollectionNames.SETTINGS.name()).drop();
|
||||
db.getCollection(CollectionNames.PREFERENCES.name()).drop();
|
||||
db.getCollection(CollectionNames.PREFERENCES.name()).drop();
|
||||
store = new UserStoreImpl();
|
||||
UsernamePasswordRealm.setTestUserStore(store);
|
||||
Activator.setTestUserStore(store);
|
||||
new SecurityServiceImpl(store, /* mailProperties */ new Properties());
|
||||
@@ -27,4 +42,19 @@ public class LoginTest {
|
||||
public void testGetUser() {
|
||||
assertNotNull("Subject should not be null: ", SecurityUtils.getSubject());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPreferencesTest() {
|
||||
store.setPreference("me", "key", "value");
|
||||
UserStoreImpl store2 = new UserStoreImpl();
|
||||
assertEquals("value", store2.getPreference("me", "key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAndUnsetPreferencesTest() {
|
||||
store.setPreference("me", "key", "value");
|
||||
store.unsetPreference("me", "key");
|
||||
UserStoreImpl store2 = new UserStoreImpl();
|
||||
assertNull(store2.getPreference("me", "key"));
|
||||
}
|
||||
}
|
||||
@@ -12,4 +12,5 @@ Bundle-ClassPath: .
|
||||
Require-Bundle: com.sap.sse.security;bundle-version="1.0.0",
|
||||
com.mongodb.driver;bundle-version="2.6.2",
|
||||
com.sap.sailing.mongodb
|
||||
Export-Package: com.sap.sse.security.userstore.mongodb
|
||||
Export-Package: com.sap.sse.security.userstore.mongodb,
|
||||
com.sap.sse.security.userstore.mongodb.impl;x-friends:="com.sap.sse.security.test"
|
||||
+2
@@ -13,4 +13,6 @@ public interface DomainObjectFactory {
|
||||
Map<String, Class<?>> loadSettingTypes();
|
||||
|
||||
User loadUser(String name);
|
||||
|
||||
Map<String, Map<String, String>> loadPreferences();
|
||||
}
|
||||
+2
@@ -13,4 +13,6 @@ public interface MongoObjectFactory {
|
||||
public void storeSettings(Map<String, Object> settings);
|
||||
|
||||
public void storeSettingTypes(Map<String, Class<?>> settingTypes);
|
||||
|
||||
public void storePreferences(String username, Map<String, String> userMap);
|
||||
}
|
||||
+43
@@ -22,6 +22,7 @@ public class UserStoreImpl implements UserStore {
|
||||
private final ConcurrentHashMap<String, User> users;
|
||||
private final ConcurrentHashMap<String, Object> settings;
|
||||
private final ConcurrentHashMap<String, Class<?>> settingTypes;
|
||||
private final ConcurrentHashMap<String, Map<String, String>> preferences;
|
||||
private final DomainObjectFactory domainObjectFactory;
|
||||
private final MongoObjectFactory mongoObjectFactory;
|
||||
|
||||
@@ -29,6 +30,7 @@ public class UserStoreImpl implements UserStore {
|
||||
users = new ConcurrentHashMap<>();
|
||||
settings = new ConcurrentHashMap<>();
|
||||
settingTypes = new ConcurrentHashMap<String, Class<?>>();
|
||||
preferences = new ConcurrentHashMap<>();
|
||||
domainObjectFactory = PersistenceFactory.INSTANCE.getDefaultDomainObjectFactory();
|
||||
mongoObjectFactory = PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory();
|
||||
for (Entry<String, Class<?>> e : domainObjectFactory.loadSettingTypes().entrySet()) {
|
||||
@@ -37,6 +39,9 @@ public class UserStoreImpl implements UserStore {
|
||||
for (Entry<String, Object> e : domainObjectFactory.loadSettings().entrySet()) {
|
||||
settings.put(e.getKey(), e.getValue());
|
||||
}
|
||||
for (Entry<String, Map<String, String>> e : domainObjectFactory.loadPreferences().entrySet()) {
|
||||
preferences.put(e.getKey(), e.getValue());
|
||||
}
|
||||
boolean changed = false;
|
||||
changed = changed || initSocialSettingsIfEmpty();
|
||||
if (changed) {
|
||||
@@ -165,6 +170,44 @@ public class UserStoreImpl implements UserStore {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPreference(String username, String key, String value) {
|
||||
Map<String, String> userMap = preferences.get(username);
|
||||
if (userMap == null) {
|
||||
synchronized (preferences) {
|
||||
// only synchronize when necessary
|
||||
userMap = preferences.get(username);
|
||||
if (userMap == null) {
|
||||
userMap = new ConcurrentHashMap<>();
|
||||
preferences.put(username, userMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
userMap.put(key, value);
|
||||
mongoObjectFactory.storePreferences(username, userMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsetPreference(String username, String key) {
|
||||
Map<String, String> userMap = preferences.get(username);
|
||||
if (userMap != null) {
|
||||
userMap.remove(key);
|
||||
mongoObjectFactory.storePreferences(username, userMap);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPreference(String username, String key) {
|
||||
final String result;
|
||||
Map<String, String> userMap = preferences.get(username);
|
||||
if (userMap != null) {
|
||||
result = userMap.get(key);
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getAllSettings() {
|
||||
return settings;
|
||||
|
||||
+30
-1
@@ -118,7 +118,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
||||
|
||||
@Override
|
||||
public Map<String, Object> loadSettings() {
|
||||
Map<String, Object> result = new HashMap<String, Object>();
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
DBCollection settingsCollection = db.getCollection(CollectionNames.SETTINGS.name());
|
||||
|
||||
try {
|
||||
@@ -139,6 +139,35 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Map<String, String>> loadPreferences() {
|
||||
Map<String, Map<String, String>> result = new HashMap<>();
|
||||
DBCollection settingsCollection = db.getCollection(CollectionNames.PREFERENCES.name());
|
||||
try {
|
||||
for (Object o : settingsCollection.find()) {
|
||||
DBObject usernameAndPreferencesMap = (DBObject) o;
|
||||
Map<String, String> userMap = loadPreferencesMap((BasicDBList) usernameAndPreferencesMap.get(FieldNames.Preferences.KEYS_AND_VALUES.name()));
|
||||
String username = (String) usernameAndPreferencesMap.get(FieldNames.Preferences.USERNAME.name());
|
||||
result.put(username, userMap);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load settings.");
|
||||
logger.log(Level.SEVERE, "loadSettings", e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<String, String> loadPreferencesMap(BasicDBList preferencesDBObject) {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
for (Object o : preferencesDBObject) {
|
||||
DBObject keyValue = (DBObject) o;
|
||||
String key = (String) keyValue.get(FieldNames.Preferences.KEY.name());
|
||||
String value = (String) keyValue.get(FieldNames.Preferences.VALUE.name());
|
||||
result.put(key, value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<String, Object> loadSettingMap(DBObject settingDBObject) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
Map<?, ?> map = ((DBObject) settingDBObject.get(FieldNames.Settings.MAP.name())).toMap();
|
||||
|
||||
+7
@@ -18,6 +18,13 @@ public class FieldNames {
|
||||
VALUES;
|
||||
}
|
||||
|
||||
public static enum Preferences {
|
||||
USERNAME,
|
||||
KEYS_AND_VALUES,
|
||||
KEY,
|
||||
VALUE;
|
||||
}
|
||||
|
||||
public static enum Account {
|
||||
NAME;
|
||||
}
|
||||
|
||||
+19
-1
@@ -5,6 +5,7 @@ import java.util.Map.Entry;
|
||||
|
||||
import org.apache.shiro.util.SimpleByteSource;
|
||||
|
||||
import com.mongodb.BasicDBList;
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DB;
|
||||
import com.mongodb.DBCollection;
|
||||
@@ -13,9 +14,9 @@ import com.mongodb.WriteConcern;
|
||||
import com.sap.sse.security.Social;
|
||||
import com.sap.sse.security.User;
|
||||
import com.sap.sse.security.shared.Account;
|
||||
import com.sap.sse.security.shared.Account.AccountType;
|
||||
import com.sap.sse.security.shared.SocialUserAccount;
|
||||
import com.sap.sse.security.shared.UsernamePasswordAccount;
|
||||
import com.sap.sse.security.shared.Account.AccountType;
|
||||
import com.sap.sse.security.userstore.mongodb.MongoObjectFactory;
|
||||
|
||||
public class MongoObjectFactoryImpl implements MongoObjectFactory {
|
||||
@@ -87,6 +88,23 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
|
||||
settingCollection.update(query, dbSettings, /* upsrt */true, /* multi */false, WriteConcern.SAFE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storePreferences(String username, Map<String, String> userMap) {
|
||||
DBCollection settingCollection = db.getCollection(CollectionNames.PREFERENCES.name());
|
||||
settingCollection.ensureIndex(FieldNames.Preferences.USERNAME.name());
|
||||
BasicDBList dbSettings = new BasicDBList();
|
||||
for (Entry<String, String> e : userMap.entrySet()) {
|
||||
DBObject entry = new BasicDBObject();
|
||||
entry.put(FieldNames.Preferences.KEY.name(), e.getKey());
|
||||
entry.put(FieldNames.Preferences.VALUE.name(), e.getValue());
|
||||
dbSettings.add(entry);
|
||||
}
|
||||
DBObject query = new BasicDBObject(FieldNames.Preferences.USERNAME.name(), username);
|
||||
DBObject update = new BasicDBObject(FieldNames.Preferences.KEYS_AND_VALUES.name(), dbSettings);
|
||||
update.put(FieldNames.Preferences.USERNAME.name(), username);
|
||||
settingCollection.update(query, update, /* upsrt */true, /* multi */false, WriteConcern.SAFE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeSettingTypes(Map<String, Class<?>> settingTypes) {
|
||||
DBCollection settingCollection = db.getCollection(CollectionNames.SETTINGS.name());
|
||||
|
||||
@@ -10,6 +10,7 @@ import javax.servlet.ServletContext;
|
||||
import org.apache.shiro.cache.CacheManager;
|
||||
import org.apache.shiro.mgt.SecurityManager;
|
||||
|
||||
import com.sap.sse.security.shared.DefaultRoles;
|
||||
import com.sap.sse.security.shared.MailException;
|
||||
import com.sap.sse.security.shared.SocialUserAccount;
|
||||
import com.sap.sse.security.shared.UserManagementException;
|
||||
@@ -107,4 +108,17 @@ public interface SecurityService {
|
||||
|
||||
boolean validateEmail(String username, String validationSecret) throws UserManagementException;
|
||||
|
||||
/**
|
||||
* Permitted only for users with role {@link DefaultRoles#ADMIN} or when the subject's user name matches
|
||||
* <code>username</code>.
|
||||
*/
|
||||
void setPreference(String username, String key, String value);
|
||||
|
||||
/**
|
||||
* Permitted only for users with role {@link DefaultRoles#ADMIN} or when the subject's user name matches
|
||||
* <code>username</code>.
|
||||
*/
|
||||
void unsetPreference(String username, String key);
|
||||
|
||||
String getPreference(String username, String key);
|
||||
}
|
||||
@@ -718,4 +718,38 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Securit
|
||||
public CacheManager getCacheManager() {
|
||||
return cacheManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPreference(String username, String key, String value) {
|
||||
Subject subject = SecurityUtils.getSubject();
|
||||
if (subject.hasRole(DefaultRoles.ADMIN.name()) || username.equals(SessionUtils.loadUsername())) {
|
||||
store.setPreference(username, key, value);
|
||||
} else {
|
||||
throw new SecurityException("User " + SessionUtils.loadUsername()
|
||||
+ " does not have permission to set preference for user " + username);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsetPreference(String username, String key) {
|
||||
Subject subject = SecurityUtils.getSubject();
|
||||
if (subject.hasRole(DefaultRoles.ADMIN.name()) || username.equals(SessionUtils.loadUsername())) {
|
||||
store.unsetPreference(username, key);
|
||||
} else {
|
||||
throw new SecurityException("User " + SessionUtils.loadUsername()
|
||||
+ " does not have permission to unset preference for user " + username);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPreference(String username, String key) {
|
||||
Subject subject = SecurityUtils.getSubject();
|
||||
if (subject.hasRole(DefaultRoles.ADMIN.name()) || username.equals(SessionUtils.loadUsername())) {
|
||||
return store.getPreference(username, key);
|
||||
} else {
|
||||
throw new SecurityException("User " + SessionUtils.loadUsername()
|
||||
+ " does not have permission to read preferences of user " + username);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -34,7 +34,13 @@ public interface UserStore {
|
||||
* is compatible with <code>type</code>. Note that the store implementation may impose constraints on the types supported.
|
||||
* All store implementations are required to support at least {@link String} and {@link UUID} as types.
|
||||
*/
|
||||
public void addSetting(String key, Class<?> type);
|
||||
void addSetting(String key, Class<?> type);
|
||||
|
||||
void setPreference(String username, String key, String value);
|
||||
|
||||
void unsetPreference(String username, String key);
|
||||
|
||||
String getPreference(String username, String key);
|
||||
|
||||
/**
|
||||
* Sets a value for a key if that key was previously added to this store using {@link #addSetting(String, Class)}.
|
||||
@@ -51,12 +57,12 @@ public interface UserStore {
|
||||
* setting because either the key was not registered before by {@link #addSetting(String, Class)} or the type of the
|
||||
* <code>setting</code> object does not conform to the type used in {@link #addSetting(String, Class)}
|
||||
*/
|
||||
public boolean setSetting(String key, Object setting);
|
||||
boolean setSetting(String key, Object setting);
|
||||
|
||||
public <T> T getSetting(String key, Class<T> clazz);
|
||||
<T> T getSetting(String key, Class<T> clazz);
|
||||
|
||||
public Map<String, Object> getAllSettings();
|
||||
Map<String, Object> getAllSettings();
|
||||
|
||||
public Map<String, Class<?>> getAllSettingTypes();
|
||||
Map<String, Class<?>> getAllSettingTypes();
|
||||
|
||||
}
|
||||
@@ -47,7 +47,7 @@ public class SecurityResource extends AbstractSecurityResource {
|
||||
@Path("/logout")
|
||||
public Response logout() {
|
||||
getService().logout();
|
||||
return Response.ok("Logger out", MediaType.TEXT_PLAIN).build();
|
||||
return Response.ok("Logged out", MediaType.TEXT_PLAIN).build();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user