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
2016-10-09 17:12:18 +00:00
from common . log import logUtils as log
2018-08-12 15:18:12 +00:00
from common . sentry import sentry
2016-10-09 17:12:18 +00:00
from constants import serverPackets
2018-08-12 15:18:12 +00:00
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
2018-08-12 15:18:12 +00:00
2016-09-02 15:45:10 +00:00
class tokenList :
2016-06-10 14:22:14 +00:00
def __init__ ( self ) :
self . tokens = { }
2017-08-12 17:07:28 +00:00
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
2016-10-05 21:28:26 +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
2016-11-17 18:13:06 +00:00
: param userID : user id associated to that token
2016-12-26 09:33:05 +00:00
: param ip : ip address of the client
2016-11-17 18:13:06 +00:00
: 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
"""
2016-10-05 21:28:26 +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
2016-11-20 13:17:05 +00:00
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
2016-11-17 18:13:06 +00:00
: 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 :
2016-07-14 10:37:07 +00:00
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
2016-11-20 13:17:05 +00:00
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
2016-11-17 18:13:06 +00:00
: 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
2016-11-17 18:13:06 +00:00
: 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
2016-12-11 10:39:01 +00:00
only the first occurrence .
2016-11-17 18:13:06 +00:00
: return : False if not found , token object if found
2016-04-19 17:40:59 +00:00
"""
# Make sure the token exists
2016-12-11 10:39:01 +00:00
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 :
2016-10-01 19:19:03 +00:00
if ignoreIRC and value . irc :
continue
2016-12-26 09:33:05 +00:00
if _all :
2016-12-11 10:39:01 +00:00
ret . append ( value )
else :
return value
# Return full list or None if not found
2016-12-26 09:33:05 +00:00
if _all :
2016-12-11 10:39:01 +00:00
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
"""
2016-11-17 14:27:27 +00:00
Get an osuToken object from an username
2016-04-19 17:40:59 +00:00
2016-11-17 14:27:27 +00:00
: param username : normal username or safe username
: param ignoreIRC : if True , consider bancho clients only and skip IRC clients
2016-11-17 18:13:06 +00:00
: 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
2016-12-11 10:39:01 +00:00
only the first occurrence .
2016-11-17 14:27:27 +00:00
: return : osuToken object or None
2016-04-19 17:40:59 +00:00
"""
# lowercase
2016-11-17 14:27:27 +00:00
who = username . lower ( ) if not safe else username
2016-04-19 17:40:59 +00:00
# Make sure the token exists
2016-12-11 10:39:01 +00:00
ret = [ ]
2016-04-19 17:40:59 +00:00
for _ , value in self . tokens . items ( ) :
2016-11-17 14:27:27 +00:00
if ( not safe and value . username . lower ( ) == who ) or ( safe and value . safeUsername == who ) :
2016-10-01 19:19:03 +00:00
if ignoreIRC and value . irc :
continue
2016-12-26 09:33:05 +00:00
if _all :
2016-12-11 10:39:01 +00:00
ret . append ( value )
else :
return value
# Return full list or None if not found
2016-12-26 09:33:05 +00:00
if _all :
2016-12-11 10:39:01 +00:00
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
2016-11-17 18:13:06 +00:00
: 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 = [ ]
2016-07-14 10:37:07 +00:00
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
2016-11-17 18:13:06 +00:00
: 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
2016-11-17 18:13:06 +00:00
: 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
2018-08-12 15:18:12 +00:00
@sentry.capture ( )
2018-03-23 19:59:30 +00:00
def usersTimeoutCheckLoop ( self ) :
2016-04-19 17:40:59 +00:00
"""
2016-11-17 18:13:06 +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 !
2016-11-17 18:13:06 +00:00
: return :
2016-04-19 17:40:59 +00:00
"""
2018-08-12 15:18:12 +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 ) :
"""
2016-11-17 18:13:06 +00:00
Start spam protection reset loop .
Called every 10 seconds .
2016-06-10 14:22:14 +00:00
CALL THIS FUNCTION ONLY ONCE !
2016-11-17 18:13:06 +00:00
: return :
2016-06-10 14:22:14 +00:00
"""
2018-08-12 15:18:12 +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
2016-11-17 18:13:06 +00:00
: 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
2016-07-14 10:37:07 +00:00
2016-11-17 18:13:06 +00:00
2016-07-14 10:37:07 +00:00
def tokenExists ( self , username = " " , userID = - 1 ) :
"""
2016-11-17 18:13:06 +00:00
Check if a token exists
2016-07-14 10:37:07 +00:00
Use username or userid , not both at the same time .
2016-11-17 18:13:06 +00:00
: param username : Optional .
: param userID : Optional .
: return : True if it exists , otherwise False
2016-07-14 10:37:07 +00:00
"""
if userID > - 1 :
return True if self . getTokenFromUserID ( userID ) is not None else False
else :
2016-11-17 18:13:06 +00:00
return True if self . getTokenFromUsername ( username ) is not None else False