def validate(self, attrs): data = super().validate(attrs) refresh = self.get_token(self.user) data['refresh'] = str(refresh) data['access'] = str(refresh.access_token) #@Ole Add the logging now = timezone.now() dt_string = now.strftime("%Y-%m-%d %H:%M:%S") # f = open("/apps/Klaverjassen/log/klaverjas_login.txt", "a") # log_text = "*** " + dt_string + ', Username : '******'authentication').info(f'[{str(self.user)}] has logged in') # logger('debug').debug('This is a debug test') return data
async def connect(self): #@ Currently there is no validation on user, so everybody can connect to a channel #@ Needs to add an authentication self.gameID = self.scope['url_route']['kwargs']['gameID'] self.group_name = 'game_%s' % self.gameID # Join room group await self.channel_layer.group_add( self.group_name, self.channel_name ) # can add here a validation before the accept await self.accept() # On connect send a confirmation with channelname to the client # the client can then send a back message with channel, gameid and position # to be stored in the database message = { 'type': 'confirm_connected', 'channel': self.channel_name } # Only send the the client (not to the group) await self.send(text_data=json.dumps(message)) print('Connection created : ', self.channel_name) logger('debug').debug(f'Connection created : , {self.channel_name}')
def post(self, request): """ Based on token in the header get the user_id Put the user_id and token on the Blacklist, so that this token is not allowed anymore. """ try: user_id = JWTAuthenticationBlacklist().authenticate(request)[0] access_token = JWTAuthenticationBlacklist().authenticate(request)[1] # Add user + token to the Blacklist user_obj = User.objects.get(username=user_id) item = BlackListedToken( token = access_token, user = user_obj ) item.save() #Log the logout logger('authentication').info(f'[{user_id}] has logged out') # see: https://www.django-rest-framework.org/api-guide/status-codes/ content = {'logout': 'success'} return Response(content, status=status.HTTP_200_OK) except: logger('errors').error(f'User {user_id} failed to logout') content = {'logout': 'not succeeded'} return Response(content, status=status.HTTP_400_BAD_REQUEST)
def is_not_on_blacklist(self, raw_token, validated_token): is_allowed = True user_id = self.get_user(validated_token) try: on_list = BlackListedToken.objects.get(user=user_id, token=validated_token) if on_list: logger('authentication').info(f'User {user_id} tried to login with token on Blacklist') is_allowed = False except BlackListedToken.DoesNotExist: is_allowed = True return is_allowed
def set_connected_status(gameID, position, channel): ''' Update the status of a player at a position at the table at a game. Use this function when a player connects to the game. ''' # first check that an entry for that game/position exists # if not, create an entry qs = WSConnectedStatus.objects.filter(gameID=gameID, position=position) if len(qs) > 0: obj = WSConnectedStatus.objects.get(gameID=gameID, position=position) obj.connected = True obj.channel = channel obj.save() else: entry = WSConnectedStatus() entry.gameID = gameID entry.position = position entry.connected = True entry.channel = channel entry.save() # print('++ Entry created ', gameID, position, channel) logger('debug').debug(f'++ Entry created, {gameID}, {position}, {channel}')
def get_raw_token(self, header): """ Extracts an unvalidated JSON web token from the given "Authorization" header value. """ parts = header.split() if len(parts) == 0: # Empty AUTHORIZATION header sent return None if parts[0] not in AUTH_HEADER_TYPE_BYTES: # Assume the header does not contain a JSON web token return None if len(parts) != 2: logger('errors').error(f'Authenticaion failed due to header {header}') raise AuthenticationFailed( _('Authorization header must contain two space-delimited values'), code='bad_authorization_header', ) return parts[1]
def save(self): # Define what needs to be saved # use validated_data as the input from the API # Create a User with the variable from validated data user = User( username = self.validated_data['username'], first_name = self.validated_data['first_name'], last_name = self.validated_data['last_name'], # It is possible to change the data. Then make sure to also update the validated_data # otherwise the old value will be shown in the API reponse # last_name = 'XXX', email = self.validated_data['email'] ) # validate the password using the default password validators password = self.validated_data['password'] # password = data.get('password') # When validating the password using the validate_password then # the error must be catched, otherwise this whole serializer will result in an error errors = dict() try: # validate the password and catch the exception validate_password(password=password) # the exception raised here is different than serializers.ValidationError except exceptions.ValidationError as e: errors['password'] = list(e.messages) if errors: raise serializers.ValidationError(errors) else: # Add the password to the user and save to the database user.set_password(password) user.save() # Log the registration of a new user now = timezone.now() dt_string = now.strftime("%Y-%m-%d %H:%M:%S") # f = open("/apps/Klaverjassen/log/klaverjas_registration.txt", "a") # log_text = '*** ' + dt_string + ', Username: '******'username'] \ # + ', Naam: ' + self.validated_data['first_name'] + ' ' + self.validated_data['last_name'] \ # + ', Email: ' + self.validated_data['email'] + "\n" # f.write(log_text) # f.close() # log the registration logger_text = 'Username: '******'username'] \ + ', Naam: ' + self.validated_data['first_name'] + ' ' + self.validated_data['last_name'] \ + ', Email: ' + self.validated_data['email'] logger('registration').info(f'{logger_text}') try: # send a mail subject='New user registered' message_text = '*** ' + dt_string + ', Username: '******'username'] \ + ', Naam: ' + self.validated_data['first_name'] + ' ' + self.validated_data['last_name'] \ + ', Email: ' + self.validated_data['email'] mailfrom='*****@*****.**' mailto='*****@*****.**' send_mail( subject, message_text, mailfrom, [mailto], fail_silently=False, ) except: logger('errors').exception('Mail could not be sent')
def post(self, request): """ Receive username, reset_code and password. These will be validated. When Ok then the new password will be set """ try: username = request.data['username'] reset_code = request.data['reset_code'] password = request.data['password'] # Validate username # - exists, is unique, is active, try: # Get the user, when this fails this will go to except user_obj = User.objects.get(username=username) # validate that user is active if user_obj.is_active == False: content = {'message': 'Wachtwoord reset niet gelukt','username': ['Opgegeven gebruikersnaaam is niet meer actief.']} logger('authentication').warning(f'Reset password failed. User [{username}] is not active anymore') return Response(content, status=status.HTTP_404_NOT_FOUND) except: logger('authentication').warning(f'Reset password failed. Used incorrect username [{username}]') content = {'message': 'Wachtwoord reset niet gelukt','username': ['Incorrecte gebruikersnaam opgegeven.']} return Response(content, status=status.HTTP_404_NOT_FOUND) # When there is a correct user next validate the reset_code # - is not expired, belongs to given username # print('DUMMY1',user_obj.reset_code_valid_until.strftime('%Y-%m-%d %H:%M:%S'), timezone.now().strftime('%Y-%m-%d %H:%M:%S')) # print(user_obj.reset_code_valid_until.strftime('%Y-%m-%d %H:%M:%S') < timezone.now().strftime('%Y-%m-%d %H:%M:%S')) if reset_code != user_obj.reset_code: logger('authentication').warning(f'Reset password failed. User [{username}] used incorrect reset code') content = {'message': 'Wachtwoord reset niet gelukt','reset_code': ['Opgegeven resetcode is niet correct.']} return Response(content, status=status.HTTP_404_NOT_FOUND) else: # code is correct, now validate the expiration of code if user_obj.reset_code_valid_until.strftime('%Y-%m-%d %H:%M:%S') < timezone.now().strftime('%Y-%m-%d %H:%M:%S'): logger('authentication').warning(f'Reset password failed. User [{username}] used expired reset code') logger('authentication').warning(f"{user_obj.reset_code_valid_until.strftime('%Y-%m-%d %H:%M:%S')} versus {timezone.now().strftime('%Y-%m-%d %H:%M:%S')}") content = {'message': 'Wachtwoord reset niet gelukt','reset_code': ['De reset_code is niet meer geldig']} return Response(content, status=status.HTTP_404_NOT_FOUND) # Now the reset_code is valid, so the password can be set. # First validate the password try: # validate the password and catch the exception validate_password(password=password) # the exception raised here is different than serializers.ValidationError except exceptions.ValidationError as e: content = {'message': 'Wachtwoord reset niet gelukt','password': list(e.messages)} return Response(content, status=status.HTTP_400_BAD_REQUEST) user_obj.set_password(password) # Also set the reset_code to exipred user_obj.reset_code_valid_until = timezone.now() - timezone.timedelta(minutes=1) # Save the user object user_obj.save() logger('authentication').info(f'Reset password completed for user [{username}]') content = {'message': 'Wachtwoord van gebruiker is aangepast'} return Response(content, status=status.HTTP_201_CREATED) except: logger('authentication').error(f'Reset password for user [{username}] failed due to internal error.') content = {'message': 'Interne fout opgetreden bij het afhandelen van dit verzoek'} return Response(content, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def post(self, request): """ Receive username and/or email address. When all validated, return a reset code """ # Set the length to the reset code code_length = 8 try: username = request.data['username'] email = request.data['email'] ip_address = request.META.get("REMOTE_ADDR") if username != '': # When username is given, only use username to check the existence of this user qs = User.objects.filter(username=username) if len(qs) != 1: # When there is not a single result, then create then error content = {'message': 'Deze gebruikersnaam is niet in gebruik'} logger('authentication').warning(f'Non existent user [{username}] requested password reset from IP [{ip_address}]') return Response(content, status=status.HTTP_404_NOT_FOUND) else: # When no username is given check the email if email != '': qs = User.objects.filter(email=email).order_by('username') if len(qs) == 1: # When there is a single account with this email address this can be used # Do nothing, and create the code and email at the end pass elif len(qs) == 0: # No email adresses are using this email address, # so so unique user can be determined content = {'message': 'Geen gebruikersnamen gevonden die horen bij dit email adres.'} logger('authentication').warning(f'Non existent mail address [{email}] requested password reset from IP [{ip_address}]') return Response(content, status=status.HTTP_404_NOT_FOUND) else: # multiple accounts have been found for the email address # Put the usernames in a list list_names = list() for item in qs: list_names.append(item.username) # send a mail with these usernames mailVars = { "users": list_names, "email": email } subject = 'Klaverjasfun.nl, gebruikersnamen voor email adres' from_email = '*****@*****.**' to = [email] bcc = ['*****@*****.**'] cc = [] html_message = render_to_string('mail_template_multiple_usernames.html', mailVars ) plain_message = strip_tags(html_message) send_mail(subject, plain_message, from_email, to, html_message=html_message) #Log sending the reset code logger('authentication').info(f'Send reset code mail for multiple users for mail [{email}] from IP [{ip_address}]') content = {'message': 'Meerdere gebruikersnamen horen bij dit email adres. Er is een mail verstuurd met de verschillende gebruikersnamen.'} return Response(content, status=status.HTTP_404_NOT_FOUND) else: # No username and no email content = {'message': 'Gebruikersnaam of email adres moet worden gebruikt'} return Response(content, status=status.HTTP_400_BAD_REQUEST) ############################################################################# # When a unique user has been found, create the reset_code user_obj = User.objects.get(username=qs[0].username) reset_code = createResetCode(code_length) user_obj.reset_code = reset_code # Set the Valid until to now + 15 minutes for the reset code user_obj.reset_code_valid_until = timezone.now() + timezone.timedelta(minutes=15) user_obj.save() # Send a mail to the user mailVars = { "username": user_obj.username, "email": user_obj.email, "reset_code": reset_code } subject = 'Klaverjasfun.nl, code voor reset wachtwoord' from_email = '*****@*****.**' to = [user_obj.email] bcc = ['*****@*****.**'] cc = [] html_message = render_to_string('mail_template_reset_code.html', mailVars ) plain_message = strip_tags(html_message) send_mail(subject, plain_message, from_email, to, html_message=html_message) #Log sending the reset code logger('authentication').info(f'Send reset code mail for user [{user_obj.username}] and mail [{user_obj.email}] on IP [{ip_address}]') content = {'reset_code': reset_code} return Response(content, status=status.HTTP_200_OK) except: logger('authentication').exception(f'Error creating resetcode for user [{username}] and mail [{email}] from IP [{ip_address}]') content = {'reset_code': 'XXXXXXX'} return Response(content, status=status.HTTP_400_BAD_REQUEST)
async def receive(self, text_data): message = json.loads(text_data) # converts the data to Python ###### Functions to handle the different request types ##################################################################################################### #### TYPE: connected_update ## register this player as connected and send all connection statusses of that game to all players if message['type'] == 'connected_update': # print('@@@ CONNECTED_UPDATE') await self.connected_update(message) ##################################################################################################### #### TYPE: handle_verzaken ## Show to all players that a player has noted verzaakt if message['type'] == 'notify_verzaken': # print('@@@ HANDLE_VERZAKEN') await self.notify_verzaken(message) ##################################################################################################### #### TYPE: process_verzaken ## Show to all players that a player has noted verzaakt # if message['type'] == 'process_verzaken': # print('@@@ PROCESS_VERZAKEN') # await self.process_verzaken(message) ##################################################################################################### #### TYPE: get_players !NOT USED ANYMORE !!! elif message['type'] == 'get_players': # Get the players for a gameID # print('@@@ GET_PLAYERS') message = json.loads(text_data) # converts the data to Python # print('**', message) players = await get_players(message['gameID']) # send this info to all players in the group. message = { 'type' : 'send_players', 'players' : players } # Send message to room group await self.channel_layer.group_send( self.group_name, { 'type': 'send_to_group', ## Based on this name a function to handle is created 'message': message } ) # print('GET_PLAYERS') ##################################################################################################### #### TYPE: request_new_round ### Send to info to the player to play a new round ### Sends back 8 cards, but the cards that have been played in the previous rounds ### will be set to no card. This ensures that the cards will be shown on the same position as ### in the previous round. ### Also a signal will be send to all other player to load the round. ### The troef is klaver for every first round. For the next rounds the troef will be the samen ### as the first round ### elif message['type'] == 'request_new_round': logger('debug').debug('REQUEST_NEW_ROUND') message = json.loads(text_data) # converts the data to Python # print('**', message) # Set the variables to be used. matchID = message['matchID'] gameID = message['gameID'] position = message['position'] troef = message['troef'] # Needs to be included when troef is changed in first round button_pressed = message['button_pressed'] # print('DUMMY20', matchID, gameID, position, troef ) ### FIRST: get the information of the game game = await get_game(message['gameID']) current_leg = game.legs_completed # when 0 are completed get leg 0 for the new leg current_round = game.rounds_completed ##@@@@@ Only do this when current_leg < n_leg (get from match) if current_leg < message['n_legs']: ### NEXT Determine the troef that needs to be send ### In the first round the default troef is clubs. This can be changed. ### In the next rounds in the leg the same troef will be used as in round 0 troef_choices = ['clubs', 'hearts', 'spades', 'diamonds'] if current_round != 0: # get the troef from round = 0 slag = await get_slag(gameID, current_leg, 0 ) for i in range(len(troef_choices)): if troef_choices[i] == slag.troef_id: troef = i ### NEXT: get all cards of the leg and sort this for the troef cards_all = await get_player_cards(matchID, gameID, current_leg, position, troef) ### NEXT: get all played cards in the previous rounds of this leg for a position (player) played_cards = await get_played_cards_of_leg(gameID, current_leg, position) # print('played cards :', played_cards) ### NEXT: Remove the playeds cards from the hand, by setting the card to blank ### that is, set color and rank to 'no-card' cards = cards_all for i in range(len(cards)): # check all cards in the hand to match a played card. # When they match then the card will be set to no-card # If played cards is empty the for loop will be skipped # In that case assign all cards to the cards list if len(played_cards) != 0: for played in played_cards: if cards[i] == played: cards[i] = {'color': 'no-card', 'rank': 'no-card' } # print('position ', position, cards) ### NEXT: Determine which player has to start this round. if current_round == 0: next_to_play = current_leg % 4 else: # When cards in the round have been played, # then the next person to start depends on the winner of the previous round slag = await get_slag(gameID, current_leg, current_round - 1) next_to_play = slag.player_won ## Determine the total roem thusfar in the leg for both parties ## roem is a list of two values for team A and teamB ## Note: roem for teamA is : roem[0]['roem__sum'] roem = await get_roem(gameID, current_leg) # do not need current_round ### Send the information to only the player that requested this new round. # Only send to the client (not to the group) message = { 'type' : 'send_new_round', 'cards' : cards, 'next_to_play' : next_to_play, 'current_leg' : current_leg, 'current_round' : current_round, 'troef' : troef, 'roem' : roem # is a list with the values of roemA and roemB } # if not button pressed it only needs to be send to a single player if button_pressed == False: await self.send(text_data=json.dumps(message)) ### Send a message to the other players to if buttonpressed = true if button_pressed == True: message = { 'type' : 'signal_to_load_new_round' } # Send message to room group await self.channel_layer.group_send( self.group_name, { 'type': 'send_to_group', ## Based on this name a function to handle is created 'message': message } ) ### end if ##################################################################################################### #### TYPE: request_player_cards ### This sends the cards to only the player that reqeusted the cards elif message['type'] == 'request_player_cards': # print('@@@ REQUEST_PLAYER_CARDS') message = json.loads(text_data) # converts the data to Python # print('**', message) cards = await get_player_cards(message['matchID'], message['gameID'], message['leg'], message['position'], message['troef']) # Only send the the client (not to the group) message = { 'type' : 'send_player_cards', 'cards' : cards } await self.send(text_data=json.dumps(message)) # print('Cards send : ', self.channel_name) ##################################################################################################### #### When a player plays a card, this card needs to be communicated to the other players #### Django will also update who is next to play and the count on how many cards have been played in this round elif message['type'] == 'play_card': message = json.loads(text_data) # converts the data to Python # print('@@@ PLAY_CARD') # print('Card played by :', message['position'], message['color'], message['rank']) # set the next player to throw a card message['next_to_play'] = ( message['next_to_play'] + 1 ) % 4 message['count_cards_played'] = ( message['count_cards_played'] + 1 ) # Send message to room group await self.channel_layer.group_send( self.group_name, { 'type': 'send_to_group', ## Based on this name a function to handle is created 'message': message } ) ##################################################################################################### #### TYPE: check_round #### the played round is send from player with my_position = 0 to the websocket. #### The following needs to be done #### - check who won the round and send this to the group #### receive : 'gameID', 'leg', 'round', 'cards', 'troef' elif message['type'] == 'check_round': message = json.loads(text_data) # converts the data to Python print('@@@ CHECK_ROUND') # print(message) # FIRST: get the game information # We need the info from some fields game = await get_game(message['gameID']) # NEXT: determine the troef played troef_choices = ['clubs', 'hearts', 'spades', 'diamonds'] troef = troef_choices[message['troef']] # NEXT: Determine which position started this round. # if the round = 0 then determine based on current leg # if round > 0 then look at the winner of the previous round if message['round'] == 0: position_start = (game.legs_completed ) % 4 # print('Position that started this round: ', position_start) else: # Get the winner of the previous round (slag) slag = await get_slag(message['gameID'], message['leg'], message['round']-1 ) position_start = slag.player_won # Determine which player and team won the round # print('AAAAA',troef, position_start,message['cards']) winner = evaluateSlag(message['cards'], troef, position_start) message = { 'type' : 'winner_round', 'winner' : winner } # Send message to room group await self.channel_layer.group_send( self.group_name, { 'type': 'send_to_group', ## Based on this name a function to handle is created 'message': message } ) ##################################################################################################### #### TYPE: log_round #### the played round is send from player with my_position = 0 to the websocket. #### Or verzaakt is reported by a player. #### #### The following needs to be done #### - store the result of this round in the database #### - Update this game with the correct leg and round numbers #### #### A variable is used to incidate that the game has ended #### #### when a slag already exists, then first remove it and then log the new slag ### ### This procedure needs to determine whether or not a new round needs to be played ### There are tree possibilies ### - Play new round ### - Signal end of leg ### - Signal the end of game #### #### receive : 'gameID', 'leg', 'round', 'cards', 'troef', 'roem' elif message['type'] == 'log_round': message = json.loads(text_data) # converts the data to Python # print('@@@ LOG_ROUND') # print('***', message) await self.process_log_round(message) ##################################################################################################### #### TYPE: request_scores #### For a game get all the leg scores and total score for the game elif message['type'] == 'request_scores': message = json.loads(text_data) # converts the data to Python # print('@@@ REQUEST SCORES') # print(message) scores_per_leg , totalscores = await get_current_scores(message['gameID']) ### Create the message based on state message = { 'type' : 'send_scores', 'scores_per_leg' : scores_per_leg, 'totalscores' : totalscores } # Only send the the client that did this request(not to the group) await self.send(text_data=json.dumps(message)) # # Send message to room group # await self.channel_layer.group_send( # self.group_name, # { # 'type': 'send_to_group', ## Based on this name a function to handle is created # 'message': message # } # ) ##################################################################################################### else: # Types that do not need to be processed can directly be forwarded to the group # like status updates # Send message to room group # print('@@@@ Other messages') await self.channel_layer.group_send( self.group_name, { 'type': 'send_to_group', ## Based on this name a function to handle is created 'message': message } )
def evaluate_leg(gameID, leg): ''' Determine the winner and scores for a completed leg ''' qs = Slag.objects.filter(gameID=gameID, leg=leg) if len(qs) != 8: # print('WARNING: Leg not properly completed') logger('debug').debug('WARNING: Leg not properly completed') return 'Leg not properly completed' else: # Determine who took om this leg. (heeft aangenomen) player_aangenomen = leg % 4 # In the game variant 'verplicht aannemen' total_score_A = 0 total_score_B = 0 total_roem_A = 0 total_roem_B = 0 # determine the total score and roem for both teams for slag in qs: if slag.teamA_won == True: total_score_A = total_score_A + slag.score total_roem_A = total_roem_A + slag.roem else: total_score_B = total_score_B + slag.score total_roem_B = total_roem_B + slag.roem # Check that the player of Team A succeeded in playing the leg (is 'door') if player_aangenomen == 0 or player_aangenomen == 2: team = 'team A' succeeded = (total_score_A + total_roem_A ) > (total_score_B + total_roem_B ) if not succeeded: # als 'nat' total_score_A = 0 total_roem_A = 0 total_score_B = 162 total_roem_B = (total_roem_A + total_roem_B) if succeeded and total_score_A == 162: total_roem_A = total_roem_A + 100 pit = True else: pit = False # Check that the player of Team B succeeded in playing the leg (is 'door') if player_aangenomen == 1 or player_aangenomen == 3: team = 'team B' succeeded = (total_score_B + total_roem_B ) > (total_score_A + total_roem_A ) if not succeeded: # als 'nat' total_score_B = 0 total_roem_B = 0 total_score_A = 162 total_roem_A = (total_roem_A + total_roem_B) if succeeded and total_score_B == 162: total_roem_B = total_roem_B + 100 pit = True else: pit = False return [player_aangenomen, succeeded, pit, team, total_score_A, total_roem_A, total_score_B, total_roem_B]