Bugfixes and nick filter.
Add a configurable nickname ban filter, similar to the chat filter except this is defined in the properties file since it doesn't need unicode. Any user that attempts to use a nick that contains anything on this list will be denied. Fix a bug with /kick and /ban that caused it to only work against users with entirely lower-case nicknames. Add a check that the user's IP address hasn't changed. This probably isn't as needed now that /kick actually works against everybody.
This commit is contained in:
parent
67c0e0e954
commit
d8740258d7
|
@ -11,6 +11,8 @@ pyx.id_code_salt=
|
||||||
# comma-separated listed of IP addresses (v4 or v6) from which users are considered admins.
|
# comma-separated listed of IP addresses (v4 or v6) from which users are considered admins.
|
||||||
# IPv6 addresses must be fully spelt out without omitting groups of 0s with ::
|
# IPv6 addresses must be fully spelt out without omitting groups of 0s with ::
|
||||||
pyx.admin_addrs=127.0.0.1,0:0:0:0:0:0:0:1
|
pyx.admin_addrs=127.0.0.1,0:0:0:0:0:0:0:1
|
||||||
|
# comma-separated list of strings banned from appearing in nicks.
|
||||||
|
pyx.banned_nicks=xyzzy
|
||||||
|
|
||||||
# The name of a class that implements net.socialgamer.cah.util.ChatFilter.ShadowBannedStringProvider
|
# The name of a class that implements net.socialgamer.cah.util.ChatFilter.ShadowBannedStringProvider
|
||||||
# which will then be called to get the shadowbanned strings.
|
# which will then be called to get the shadowbanned strings.
|
||||||
|
|
|
@ -21,6 +21,7 @@ pyx.chat.global.repeated_words_unique_ratio=${pyx.global.repeated_words_unique_r
|
||||||
pyx.chat.game.flood_count=${pyx.game.flood_count}
|
pyx.chat.game.flood_count=${pyx.game.flood_count}
|
||||||
pyx.chat.game.flood_time=${pyx.game.flood_time}
|
pyx.chat.game.flood_time=${pyx.game.flood_time}
|
||||||
pyx.build=${buildNumber}
|
pyx.build=${buildNumber}
|
||||||
|
pyx.banned_nicks=${pyx.banned_nicks}
|
||||||
|
|
||||||
pyx.metrics.game.enabled=${pyx.metrics.game.enabled}
|
pyx.metrics.game.enabled=${pyx.metrics.game.enabled}
|
||||||
pyx.metrics.game.url_format=${pyx.metrics.game.url_format}
|
pyx.metrics.game.url_format=${pyx.metrics.game.url_format}
|
||||||
|
|
|
@ -289,6 +289,14 @@ public class CahModule extends AbstractModule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@BannedNicks
|
||||||
|
Set<String> provideBannedNicks() {
|
||||||
|
synchronized (properties) {
|
||||||
|
return ImmutableSet.copyOf(properties.getProperty("pyx.banned_nicks", "").split(","));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@BindingAnnotation
|
@BindingAnnotation
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface BanList {
|
public @interface BanList {
|
||||||
|
@ -388,4 +396,9 @@ public class CahModule extends AbstractModule {
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface Admins {
|
public @interface Admins {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@BindingAnnotation
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface BannedNicks {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -167,7 +167,7 @@ public class ConnectedUsers {
|
||||||
*/
|
*/
|
||||||
public void removeUser(final User user, final DisconnectReason reason) {
|
public void removeUser(final User user, final DisconnectReason reason) {
|
||||||
synchronized (users) {
|
synchronized (users) {
|
||||||
if (users.containsKey(user.getNickname())) {
|
if (users.containsKey(user.getNickname().toLowerCase())) {
|
||||||
logger.info(String.format("Removing user %s because %s", user.toString(), reason));
|
logger.info(String.format("Removing user %s because %s", user.toString(), reason));
|
||||||
user.noLongerValid();
|
user.noLongerValid();
|
||||||
users.remove(user.getNickname().toLowerCase());
|
users.remove(user.getNickname().toLowerCase());
|
||||||
|
|
|
@ -30,6 +30,8 @@ import java.util.concurrent.PriorityBlockingQueue;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.assistedinject.Assisted;
|
import com.google.inject.assistedinject.Assisted;
|
||||||
|
|
||||||
|
@ -46,6 +48,8 @@ import net.socialgamer.cah.Constants.Sigil;
|
||||||
*/
|
*/
|
||||||
public class User {
|
public class User {
|
||||||
|
|
||||||
|
private static final Logger LOG = Logger.getLogger(User.class);
|
||||||
|
|
||||||
private final String nickname;
|
private final String nickname;
|
||||||
|
|
||||||
private final String idCode;
|
private final String idCode;
|
||||||
|
@ -289,6 +293,15 @@ public class User {
|
||||||
return valid;
|
return valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isValidFromHost(final String currentHostname) {
|
||||||
|
final boolean addrValid = hostname.equals(currentHostname);
|
||||||
|
if (!addrValid) {
|
||||||
|
LOG.warn(String.format("User %s used to be from %s but is now from %s", nickname, hostname,
|
||||||
|
currentHostname));
|
||||||
|
}
|
||||||
|
return isValid() && addrValid;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark this user as no longer valid, probably because they pinged out.
|
* Mark this user as no longer valid, probably because they pinged out.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
package net.socialgamer.cah.handlers;
|
package net.socialgamer.cah.handlers;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
@ -39,6 +40,7 @@ import com.google.inject.Provider;
|
||||||
|
|
||||||
import net.socialgamer.cah.CahModule.Admins;
|
import net.socialgamer.cah.CahModule.Admins;
|
||||||
import net.socialgamer.cah.CahModule.BanList;
|
import net.socialgamer.cah.CahModule.BanList;
|
||||||
|
import net.socialgamer.cah.CahModule.BannedNicks;
|
||||||
import net.socialgamer.cah.CahModule.SessionPermalinkUrlFormat;
|
import net.socialgamer.cah.CahModule.SessionPermalinkUrlFormat;
|
||||||
import net.socialgamer.cah.CahModule.ShowSessionPermalink;
|
import net.socialgamer.cah.CahModule.ShowSessionPermalink;
|
||||||
import net.socialgamer.cah.CahModule.ShowUserPermalink;
|
import net.socialgamer.cah.CahModule.ShowUserPermalink;
|
||||||
|
@ -73,6 +75,7 @@ public class RegisterHandler extends Handler {
|
||||||
private final ConnectedUsers users;
|
private final ConnectedUsers users;
|
||||||
private final Set<String> adminList;
|
private final Set<String> adminList;
|
||||||
private final Set<String> banList;
|
private final Set<String> banList;
|
||||||
|
private final Set<String> bannedNickList;
|
||||||
private final User.Factory userFactory;
|
private final User.Factory userFactory;
|
||||||
private final Provider<String> persistentIdProvider;
|
private final Provider<String> persistentIdProvider;
|
||||||
private final IdCodeMangler idCodeMangler;
|
private final IdCodeMangler idCodeMangler;
|
||||||
|
@ -89,7 +92,8 @@ public class RegisterHandler extends Handler {
|
||||||
@ShowSessionPermalink final boolean showSessionPermalink,
|
@ShowSessionPermalink final boolean showSessionPermalink,
|
||||||
@SessionPermalinkUrlFormat final String sessionPermalinkFormatString,
|
@SessionPermalinkUrlFormat final String sessionPermalinkFormatString,
|
||||||
@ShowUserPermalink final boolean showUserPermalink,
|
@ShowUserPermalink final boolean showUserPermalink,
|
||||||
@UserPermalinkUrlFormat final String userPermalinkFormatString) {
|
@UserPermalinkUrlFormat final String userPermalinkFormatString,
|
||||||
|
@BannedNicks final Set<String> bannedNickList) {
|
||||||
this.users = users;
|
this.users = users;
|
||||||
this.banList = banList;
|
this.banList = banList;
|
||||||
this.userFactory = userFactory;
|
this.userFactory = userFactory;
|
||||||
|
@ -100,6 +104,7 @@ public class RegisterHandler extends Handler {
|
||||||
this.sessionPermalinkFormatString = sessionPermalinkFormatString;
|
this.sessionPermalinkFormatString = sessionPermalinkFormatString;
|
||||||
this.showUserPermalink = showUserPermalink;
|
this.showUserPermalink = showUserPermalink;
|
||||||
this.userPermalinkFormatString = userPermalinkFormatString;
|
this.userPermalinkFormatString = userPermalinkFormatString;
|
||||||
|
this.bannedNickList = bannedNickList;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -121,10 +126,14 @@ public class RegisterHandler extends Handler {
|
||||||
return error(ErrorCode.INVALID_ID_CODE);
|
return error(ErrorCode.INVALID_ID_CODE);
|
||||||
} else {
|
} else {
|
||||||
final String nick = request.getParameter(AjaxRequest.NICKNAME).trim();
|
final String nick = request.getParameter(AjaxRequest.NICKNAME).trim();
|
||||||
|
final String nickLower = nick.toLowerCase(Locale.ENGLISH);
|
||||||
|
for (final String banned : bannedNickList) {
|
||||||
|
if (nickLower.contains(banned)) {
|
||||||
|
return error(ErrorCode.RESERVED_NICK);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!VALID_NAME.matcher(nick).matches()) {
|
if (!VALID_NAME.matcher(nick).matches()) {
|
||||||
return error(ErrorCode.INVALID_NICK);
|
return error(ErrorCode.INVALID_NICK);
|
||||||
} else if ("xyzzy".equalsIgnoreCase(nick)) {
|
|
||||||
return error(ErrorCode.RESERVED_NICK);
|
|
||||||
} else {
|
} else {
|
||||||
String persistentId = request.getParameter(AjaxRequest.PERSISTENT_ID);
|
String persistentId = request.getParameter(AjaxRequest.PERSISTENT_ID);
|
||||||
if (StringUtils.isBlank(persistentId)) {
|
if (StringUtils.isBlank(persistentId)) {
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
/**
|
/**
|
||||||
* Copyright (c) 2012-2018, Andy Janata
|
* Copyright (c) 2012-2018, Andy Janata
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without modification, are permitted
|
* Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||||
* provided that the following conditions are met:
|
* provided that the following conditions are met:
|
||||||
*
|
*
|
||||||
* * Redistributions of source code must retain the above copyright notice, this list of conditions
|
* * Redistributions of source code must retain the above copyright notice, this list of conditions
|
||||||
* and the following disclaimer.
|
* and the following disclaimer.
|
||||||
* * Redistributions in binary form must reproduce the above copyright notice, this list of
|
* * Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||||
* conditions and the following disclaimer in the documentation and/or other materials provided
|
* conditions and the following disclaimer in the documentation and/or other materials provided
|
||||||
* with the distribution.
|
* with the distribution.
|
||||||
*
|
*
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||||
|
@ -37,6 +37,11 @@ import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
|
|
||||||
|
import org.json.simple.JSONObject;
|
||||||
|
import org.json.simple.JSONValue;
|
||||||
|
|
||||||
|
import com.google.inject.Injector;
|
||||||
|
|
||||||
import net.socialgamer.cah.Constants.AjaxOperation;
|
import net.socialgamer.cah.Constants.AjaxOperation;
|
||||||
import net.socialgamer.cah.Constants.AjaxRequest;
|
import net.socialgamer.cah.Constants.AjaxRequest;
|
||||||
import net.socialgamer.cah.Constants.AjaxResponse;
|
import net.socialgamer.cah.Constants.AjaxResponse;
|
||||||
|
@ -46,17 +51,12 @@ import net.socialgamer.cah.Constants.SessionAttribute;
|
||||||
import net.socialgamer.cah.StartupUtils;
|
import net.socialgamer.cah.StartupUtils;
|
||||||
import net.socialgamer.cah.data.User;
|
import net.socialgamer.cah.data.User;
|
||||||
|
|
||||||
import org.json.simple.JSONObject;
|
|
||||||
import org.json.simple.JSONValue;
|
|
||||||
|
|
||||||
import com.google.inject.Injector;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Servlet implementation class CahServlet.
|
* Servlet implementation class CahServlet.
|
||||||
*
|
*
|
||||||
* Superclass for all CAH servlets. Provides utility methods to return errors and data, and to log.
|
* Superclass for all CAH servlets. Provides utility methods to return errors and data, and to log.
|
||||||
*
|
*
|
||||||
* @author Andy Janata (ajanata@socialgamer.net)
|
* @author Andy Janata (ajanata@socialgamer.net)
|
||||||
*/
|
*/
|
||||||
public abstract class CahServlet extends HttpServlet {
|
public abstract class CahServlet extends HttpServlet {
|
||||||
|
@ -107,8 +107,9 @@ public abstract class CahServlet extends HttpServlet {
|
||||||
|| op.equals(AjaxOperation.FIRST_LOAD.toString()));
|
|| op.equals(AjaxOperation.FIRST_LOAD.toString()));
|
||||||
if (!skipSessionUserCheck && hSession.getAttribute(SessionAttribute.USER) == null) {
|
if (!skipSessionUserCheck && hSession.getAttribute(SessionAttribute.USER) == null) {
|
||||||
returnError(user, response.getWriter(), ErrorCode.NOT_REGISTERED, serial);
|
returnError(user, response.getWriter(), ErrorCode.NOT_REGISTERED, serial);
|
||||||
} else if (user != null && !user.isValid()) {
|
} else if (user != null && !user.isValidFromHost(request.getRemoteAddr())) {
|
||||||
// user probably pinged out, or possibly kicked by admin
|
// user probably pinged out, or possibly kicked by admin
|
||||||
|
// or their IP address magically changed (working around a ban?)
|
||||||
hSession.invalidate();
|
hSession.invalidate();
|
||||||
returnError(user, response.getWriter(), ErrorCode.SESSION_EXPIRED, serial);
|
returnError(user, response.getWriter(), ErrorCode.SESSION_EXPIRED, serial);
|
||||||
} else {
|
} else {
|
||||||
|
@ -137,7 +138,7 @@ public abstract class CahServlet extends HttpServlet {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles a request from a CAH client. A session is guaranteed to exist at this point.
|
* Handles a request from a CAH client. A session is guaranteed to exist at this point.
|
||||||
*
|
*
|
||||||
* @param request
|
* @param request
|
||||||
* The request data.
|
* The request data.
|
||||||
* @param response
|
* @param response
|
||||||
|
@ -153,7 +154,7 @@ public abstract class CahServlet extends HttpServlet {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an error to the client.
|
* Return an error to the client.
|
||||||
*
|
*
|
||||||
* @param user
|
* @param user
|
||||||
* User that caused the error.
|
* User that caused the error.
|
||||||
* @param writer
|
* @param writer
|
||||||
|
@ -175,7 +176,7 @@ public abstract class CahServlet extends HttpServlet {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return response data to the client.
|
* Return response data to the client.
|
||||||
*
|
*
|
||||||
* @param user
|
* @param user
|
||||||
* User this response is for.
|
* User this response is for.
|
||||||
* @param writer
|
* @param writer
|
||||||
|
@ -190,7 +191,7 @@ public abstract class CahServlet extends HttpServlet {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return multiple response data to the client.
|
* Return multiple response data to the client.
|
||||||
*
|
*
|
||||||
* @param user
|
* @param user
|
||||||
* User this response is for.
|
* User this response is for.
|
||||||
* @param writer
|
* @param writer
|
||||||
|
@ -205,7 +206,7 @@ public abstract class CahServlet extends HttpServlet {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return any response data to the client.
|
* Return any response data to the client.
|
||||||
*
|
*
|
||||||
* @param user
|
* @param user
|
||||||
* User this response is for.
|
* User this response is for.
|
||||||
* @param writer
|
* @param writer
|
||||||
|
@ -231,7 +232,7 @@ public abstract class CahServlet extends HttpServlet {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log a message, with the user's name if {@code user} is not null.
|
* Log a message, with the user's name if {@code user} is not null.
|
||||||
*
|
*
|
||||||
* @param user
|
* @param user
|
||||||
* The user this log message is about, or {@code null} if unknown.
|
* The user this log message is about, or {@code null} if unknown.
|
||||||
* @param message
|
* @param message
|
||||||
|
|
Loading…
Reference in New Issue