pep.py/objects/tokenList.py

262 lines
7.3 KiB
Python
Raw Normal View History

2016-04-19 17:40:59 +00:00
import threading
2016-10-02 20:48:14 +00:00
import time
2016-12-26 09:33:05 +00:00
import redis
2016-10-02 20:48:14 +00:00
from common.ripple import userUtils
from common.log import logUtils as log
from common.sentry import sentry
from constants import serverPackets
from constants.exceptions import periodicLoopException
2016-05-18 17:12:46 +00:00
from events import logoutEvent
2016-10-02 20:48:14 +00:00
from objects import glob
from objects import osuToken
2016-09-02 15:45:10 +00:00
class tokenList:
2016-06-10 14:22:14 +00:00
def __init__(self):
self.tokens = {}
self._lock = threading.Lock()
def __enter__(self):
self._lock.acquire()
def __exit__(self, exc_type, exc_val, exc_tb):
self._lock.release()
2016-04-19 17:40:59 +00:00
def addToken(self, userID, ip = "", irc = False, timeOffset=0, tournament=False):
2016-04-19 17:40:59 +00:00
"""
Add a token object to tokens list
:param userID: user id associated to that token
2016-12-26 09:33:05 +00:00
:param ip: ip address of the client
:param irc: if True, set this token as IRC client
:param timeOffset: the time offset from UTC for this user. Default: 0.
:param tournament: if True, flag this client as a tournement client. Default: True.
:return: token object
2016-04-19 17:40:59 +00:00
"""
newToken = osuToken.token(userID, ip=ip, irc=irc, timeOffset=timeOffset, tournament=tournament)
2016-04-19 17:40:59 +00:00
self.tokens[newToken.token] = newToken
glob.redis.incr("ripple:online_users")
2016-04-19 17:40:59 +00:00
return newToken
2016-06-10 11:15:42 +00:00
def deleteToken(self, token):
2016-04-19 17:40:59 +00:00
"""
Delete a token from token list if it exists
:param token: token string
:return:
2016-04-19 17:40:59 +00:00
"""
2016-06-10 11:15:42 +00:00
if token in self.tokens:
if self.tokens[token].ip != "":
2016-10-02 20:48:14 +00:00
userUtils.deleteBanchoSessions(self.tokens[token].userID, self.tokens[token].ip)
2017-07-04 21:16:10 +00:00
t = self.tokens.pop(token)
del t
glob.redis.decr("ripple:online_users")
2016-04-19 17:40:59 +00:00
2016-06-10 11:15:42 +00:00
def getUserIDFromToken(self, token):
2016-04-19 17:40:59 +00:00
"""
Get user ID from a token
:param token: token to find
:return: False if not found, userID if found
2016-04-19 17:40:59 +00:00
"""
# Make sure the token exists
2016-06-10 11:15:42 +00:00
if token not in self.tokens:
2016-04-19 17:40:59 +00:00
return False
# Get userID associated to that token
2016-06-10 11:15:42 +00:00
return self.tokens[token].userID
2016-04-19 17:40:59 +00:00
2016-12-26 09:33:05 +00:00
def getTokenFromUserID(self, userID, ignoreIRC=False, _all=False):
2016-04-19 17:40:59 +00:00
"""
Get token from a user ID
:param userID: user ID to find
:param ignoreIRC: if True, consider bancho clients only and skip IRC clients
2016-12-26 09:33:05 +00:00
:param _all: if True, return a list with all clients that match given username, otherwise return
only the first occurrence.
:return: False if not found, token object if found
2016-04-19 17:40:59 +00:00
"""
# Make sure the token exists
ret = []
2016-04-19 17:40:59 +00:00
for _, value in self.tokens.items():
2016-06-10 11:15:42 +00:00
if value.userID == userID:
if ignoreIRC and value.irc:
continue
2016-12-26 09:33:05 +00:00
if _all:
ret.append(value)
else:
return value
# Return full list or None if not found
2016-12-26 09:33:05 +00:00
if _all:
return ret
else:
return None
2016-12-26 09:33:05 +00:00
def getTokenFromUsername(self, username, ignoreIRC=False, safe=False, _all=False):
2016-04-19 17:40:59 +00:00
"""
Get an osuToken object from an username
2016-04-19 17:40:59 +00:00
:param username: normal username or safe username
:param ignoreIRC: if True, consider bancho clients only and skip IRC clients
:param safe: if True, username is a safe username,
compare it with token's safe username rather than normal username
2016-12-26 09:33:05 +00:00
:param _all: if True, return a list with all clients that match given username, otherwise return
only the first occurrence.
:return: osuToken object or None
2016-04-19 17:40:59 +00:00
"""
# lowercase
who = username.lower() if not safe else username
2016-04-19 17:40:59 +00:00
# Make sure the token exists
ret = []
2016-04-19 17:40:59 +00:00
for _, value in self.tokens.items():
if (not safe and value.username.lower() == who) or (safe and value.safeUsername == who):
if ignoreIRC and value.irc:
continue
2016-12-26 09:33:05 +00:00
if _all:
ret.append(value)
else:
return value
# Return full list or None if not found
2016-12-26 09:33:05 +00:00
if _all:
return ret
else:
return None
2016-04-19 17:40:59 +00:00
2016-06-10 11:15:42 +00:00
def deleteOldTokens(self, userID):
2016-04-19 17:40:59 +00:00
"""
Delete old userID's tokens if found
:param userID: tokens associated to this user will be deleted
:return:
2016-04-19 17:40:59 +00:00
"""
# Delete older tokens
2016-11-30 22:33:56 +00:00
delete = []
for key, value in list(self.tokens.items()):
2016-06-10 11:15:42 +00:00
if value.userID == userID:
2016-04-19 17:40:59 +00:00
# Delete this token from the dictionary
2016-11-30 22:33:56 +00:00
#self.tokens[key].kick("You have logged in from somewhere else. You can't connect to Bancho/IRC from more than one device at the same time.", "kicked, multiple clients")
delete.append(self.tokens[key])
for i in delete:
logoutEvent.handle(i)
2016-04-19 17:40:59 +00:00
2016-06-10 11:15:42 +00:00
def multipleEnqueue(self, packet, who, but = False):
2016-04-19 17:40:59 +00:00
"""
Enqueue a packet to multiple users
:param packet: packet bytes to enqueue
:param who: userIDs array
:param but: if True, enqueue to everyone but users in `who` array
:return:
2016-04-19 17:40:59 +00:00
"""
for _, value in self.tokens.items():
shouldEnqueue = False
2016-06-10 11:15:42 +00:00
if value.userID in who and not but:
2016-04-19 17:40:59 +00:00
shouldEnqueue = True
2016-06-10 11:15:42 +00:00
elif value.userID not in who and but:
2016-04-19 17:40:59 +00:00
shouldEnqueue = True
if shouldEnqueue:
2016-06-10 11:15:42 +00:00
value.enqueue(packet)
2016-04-19 17:40:59 +00:00
2016-06-10 11:15:42 +00:00
def enqueueAll(self, packet):
2016-04-19 17:40:59 +00:00
"""
Enqueue packet(s) to every connected user
:param packet: packet bytes to enqueue
:return:
2016-04-19 17:40:59 +00:00
"""
for _, value in self.tokens.items():
2016-06-10 11:15:42 +00:00
value.enqueue(packet)
2016-04-19 17:40:59 +00:00
@sentry.capture()
def usersTimeoutCheckLoop(self):
2016-04-19 17:40:59 +00:00
"""
Start timed out users disconnect loop.
This function will be called every `checkTime` seconds and so on, forever.
2016-04-19 17:40:59 +00:00
CALL THIS FUNCTION ONLY ONCE!
:return:
2016-04-19 17:40:59 +00:00
"""
try:
log.debug("Checking timed out clients")
exceptions = []
timedOutTokens = [] # timed out users
timeoutLimit = int(time.time()) - 100
for key, value in self.tokens.items():
# Check timeout (fokabot is ignored)
if value.pingTime < timeoutLimit and value.userID != 999 and not value.irc and not value.tournament:
# That user has timed out, add to disconnected tokens
# We can't delete it while iterating or items() throws an error
timedOutTokens.append(key)
# Delete timed out users from self.tokens
# i is token string (dictionary key)
for i in timedOutTokens:
log.debug("{} timed out!!".format(self.tokens[i].username))
self.tokens[i].enqueue(serverPackets.notification("Your connection to the server timed out."))
try:
logoutEvent.handle(self.tokens[i], None)
except Exception as e:
exceptions.append(e)
log.error(
"Something wrong happened while disconnecting a timed out client. Reporting to Sentry "
"when the loop ends."
)
del timedOutTokens
# Re-raise exceptions if needed
if exceptions:
raise periodicLoopException(exceptions)
finally:
# Schedule a new check (endless loop)
threading.Timer(100, self.usersTimeoutCheckLoop).start()
@sentry.capture()
2016-06-10 14:22:14 +00:00
def spamProtectionResetLoop(self):
"""
Start spam protection reset loop.
Called every 10 seconds.
2016-06-10 14:22:14 +00:00
CALL THIS FUNCTION ONLY ONCE!
:return:
2016-06-10 14:22:14 +00:00
"""
try:
# Reset spamRate for every token
for _, value in self.tokens.items():
value.spamRate = 0
finally:
# Schedule a new check (endless loop)
threading.Timer(10, self.spamProtectionResetLoop).start()
2016-06-10 11:15:42 +00:00
def deleteBanchoSessions(self):
"""
2016-11-20 12:03:07 +00:00
Remove all `peppy:sessions:*` redis keys.
2016-06-10 11:15:42 +00:00
Call at bancho startup to delete old cached sessions
:return:
2016-06-10 11:15:42 +00:00
"""
2016-11-20 12:03:07 +00:00
try:
# TODO: Make function or some redis meme
glob.redis.eval("return redis.call('del', unpack(redis.call('keys', ARGV[1])))", 0, "peppy:sessions:*")
2016-12-26 09:33:05 +00:00
except redis.RedisError:
2016-11-20 12:03:07 +00:00
pass
def tokenExists(self, username = "", userID = -1):
"""
Check if a token exists
Use username or userid, not both at the same time.
:param username: Optional.
:param userID: Optional.
:return: True if it exists, otherwise False
"""
if userID > -1:
return True if self.getTokenFromUserID(userID) is not None else False
else:
return True if self.getTokenFromUsername(username) is not None else False