def login(): form = LoginForm() if form.validate_on_submit(): username = request.form['username'] password = request.form['password'] next_path = request.form.get('next') username = username.strip() player = Player.query.filter_by(username=username).first() if player: user = player.user else: # TODO: check renames user = User.query.filter( or_(User.username == username, User.email == username) ).first() if user and user.check_password(password): if not user.session_key: user.generate_session_key(commit=False) session['user_session_key'] = user.session_key session.permanent = True if not user.last_login: session['first_login'] = True user.last_login = datetime.utcnow() user.save(commit=True) stats.incr('login.success') if user.mfa_login: session['mfa_stage'] = 'password-verified' return redirect(url_for('verify_mfa', next=next_path)) flash('Successfully logged in', 'success') return redirect(next_path or url_for('index')) else: flash('Invalid username/password combination', 'error') stats.incr('login.invalid') AuditLog.create( AuditLog.INVALID_LOGIN, username=username, matched_user_id=user.id if user else None, ip=request.remote_addr, commit=True ) return render_template('login.html', form=form), 401 return render_template('login.html', form=form)
def send_message(username): user = g.user if user.forum_ban: return jsonify({ 'err': 1, 'message': 'Oops, you are blocked from sending any messages. Awkward...' }) body = request.form.get('body') to_user = User.query.outerjoin(Player).options( joinedload(User.player) ).filter( or_(Player.username == username, User.username == username) ).first() if to_user: to_player = to_user.player else: # for cases of messages sent to players with no users created yet to_player = Player.query.filter_by(username=username).first() if not to_player: rollbar.report_message('to_player None', request=request) return jsonify({ 'err': 1 }) if libmessages.is_sender_spamming(user, to_user, to_player): rollbar.report_message('User blocked from spamming messages', request=request, extra_data={ 'to_user_id': to_user.id if to_user else None, 'to_player_id': to_player.id if to_player else None }) return jsonify({ 'err': 1, 'message': 'Whoa there, you sent too many messages recently! Try sending a bit later.' }) message = Message( from_user=user, to_user=to_user, to_player=to_player, body=body, user_ip=request.remote_addr ) message.save() notify_new_message(message) stats.incr('messages.created') return jsonify({ 'err': 0, 'message': message.to_dict() })
def login(): form = LoginForm() if form.validate_on_submit(): username = request.form['username'] password = request.form['password'] next_path = request.form.get('next') username = username.strip() player = Player.query.filter_by(username=username).first() if player: user = player.user else: # TODO: check renames user = User.query.filter( or_(User.username == username, User.email == username)).first() if user and user.check_password(password): if not user.session_key: user.generate_session_key(commit=False) session['user_session_key'] = user.session_key session.permanent = True if not user.last_login: session['first_login'] = True user.last_login = datetime.utcnow() user.save(commit=True) stats.incr('login.success') if user.mfa_login: session['mfa_stage'] = 'password-verified' return redirect(url_for('verify_mfa', next=next_path)) flash('Successfully logged in', 'success') return redirect(next_path or url_for('index')) else: flash('Invalid username/password combination', 'error') stats.incr('login.invalid') AuditLog.create(AuditLog.INVALID_LOGIN, username=username, matched_user_id=user.id if user else None, ip=request.remote_addr, commit=True) return render_template('login.html', form=form), 401 return render_template('login.html', form=form)
def create_account(token): email_token = EmailToken.query.filter_by(token=token).first() result = _check_email_token(email_token, 'creation') if result: return result if g.user: rollbar.report_message('User already logged in when verifying creation email', level='warning', request=request) session.pop('user_id', None) g.user = None form = VerifyEmailForm() player = Player.query.filter_by(uuid=email_token.uuid).first() email = email_token.email if form.validate_on_submit(): password = form.password.data confirm_password = form.confirm_password.data if password != confirm_password: flash('Passwords do not match', 'error') else: email_token.date_redeemed = datetime.utcnow() user = User.create(player, password, email) session['user_id'] = user.id session['first_login'] = True session.permanent = True flash('Account created! You are now logged in', 'success') stats.incr('account.created') rollbar.report_message('Account created', level='info', request=request, extra_data={ 'user_id': user.id, 'player_id': player.id, 'username': player.username }) return redirect(url_for('index')) return render_template('create_account.html', form=form, player=player, email_token=email_token)
def login(): form = LoginForm() if form.validate_on_submit(): username = request.form['username'] password = request.form['password'] next_path = request.form.get('next') username = username.strip() player = Player.query.filter_by(username=username).first() if player: user = player.user else: user = User.query.filter( or_(User.username == username, User.email == username) ).first() if user and user.check_password(password): session['user_id'] = user.id session.permanent = True if not user.last_login: session['first_login'] = True user.last_login = datetime.utcnow() user.save(commit=True) stats.incr('login.success') flash('Successfully logged in', 'success') return redirect(next_path or url_for('index')) else: flash('Invalid username/password combination', 'error') stats.incr('login.invalid') return render_template('login.html', form=form), 401 return render_template('login.html', form=form)
def message_reply(): api_key = app.config['MAILGUN_API_KEY'] token = request.form.get('token') timestamp = request.form.get('timestamp') signature = request.form.get('signature') if not verify_mailgun_signature(api_key, token, timestamp, signature): abort(403) body = request.form.get('stripped-text') full_body = request.form.get('body-plain') sender = request.form.get('sender') # try to find the reply token of the original message match = re.search(r'--([^\s]+)-([^\s]+)-([^\s]+)--', full_body) if match: b64_from_user_id, b64_to_user_id, signature = match.groups() try: # swap to/from in the original message since this is a reply reply_to_user_id = base64.b64decode(b64_from_user_id) reply_from_user_id = base64.b64decode(b64_to_user_id) except Exception: rollbar.report_message('Message not sent via email - cannot parse token', level='warning', extra_data={ 'b64_from_user_id': b64_from_user_id, 'b64_to_user_id': b64_to_user_id }) else: if verify_message_reply_signature(reply_to_user_id, reply_from_user_id, signature): to_user = User.query.options( joinedload(User.player) ).get(reply_to_user_id) from_user = User.query.options( joinedload(User.player) ).get(reply_from_user_id) if to_user and from_user: message = Message( from_user=from_user, to_user=to_user, to_player=to_user.player, body=body, user_ip=request.remote_addr ) message.save() stats.incr('messages.created') notify_new_message(message) rollbar.report_message('Message successfully sent via email', level='debug', extra_data={ 'from_user_id': from_user.id, 'to_user_id': to_user.id }) else: rollbar.report_message('Message not sent via email - user not found', level='warning', extra_data={ 'reply_from_user_id': reply_from_user_id, 'reply_to_user_id': reply_to_user_id }) else: rollbar.report_message('Message not sent via email - reply token signature mismatch', level='warning', extra_data={ 'reply_from_user_id': reply_from_user_id, 'reply_to_user_id': reply_to_user_id, 'signature': signature }) else: rollbar.report_message('Message not sent via email - token not found', level='warning', extra_data={ 'sender': sender, 'full_body': full_body }) return jsonify({})
def _query_server(server, mojang_status): server_status = api.get_server_status(server) or {} player_stats = [] players_to_sync_ban = Player.query.filter( Player.uuid.in_(server_status.get('banned_uuids', [])), Player.banned == False).all() if players_to_sync_ban: player_ids_to_sync_ban = [x.id for x in players_to_sync_ban] Player.query.filter(Player.id.in_(player_ids_to_sync_ban)).update( { 'banned': True, }, synchronize_session=False) for player in players_to_sync_ban: AuditLog.create(AuditLog.PLAYER_BAN, player_id=player.id, username=player.username, source='server_sync', commit=False) online_player_ids = [] for player_info in server_status.get('players', []): username = player_info['username'] uuid = player_info['uuid'] player = Player.query.options(joinedload( Player.titles)).filter_by(uuid=uuid).first() if player: if player.username != username: h.avoid_duplicate_username(username) player.set_username(username) player.save(commit=False) else: h.avoid_duplicate_username(username) player = Player(username=username, uuid=uuid) player.save(commit=False) statsd.incr('player.created') online_player_ids.append(player.id) last_activity = PlayerActivity.query.filter_by( server=server, player=player).order_by(PlayerActivity.timestamp.desc()).first() # if the last activity for this player is an 'exit' activity (or there isn't an activity), # create a new 'enter' activity since they just joined this minute if not last_activity or last_activity.activity_type == PLAYER_ACTIVITY_TYPES[ 'exit']: enter = PlayerActivity( server=server, player=player, activity_type=PLAYER_ACTIVITY_TYPES['enter']) enter.save(commit=False) if server.id == app.config['MAIN_SERVER_ID']: if player.banned: player.banned = False AuditLog.create(AuditLog.PLAYER_UNBAN, player_id=player.id, username=player.username, source='server_sync', commit=False) nickname_ansi = player_info.get('nickname_ansi') nickname = player_info.get('nickname') player.nickname_ansi = nickname_ansi player.nickname = nickname player.save(commit=False) ip = player_info.get('address') if ip: if not IPTracking.query.filter_by(ip=ip, player=player).first(): existing_player_ip = IPTracking(ip=ip, player=player) existing_player_ip.save(commit=False) if geoip.is_nok(ip): libplayer.ban_player(player, with_ip=True, source='query', ip=ip, commit=False) stats = PlayerStats.query.filter_by(server=server, player=player).first() if not stats: stats = PlayerStats(server=server, player=player) stats.last_seen = datetime.utcnow() stats.pvp_logs = player_info.get('pvp_logs') stats.time_spent = (stats.time_spent or 0) + 1 stats.save(commit=False) titles = [{ 'name': x.name, 'broadcast': x.broadcast } for x in player.titles] player_stats.append({ 'username': player.username, 'uuid': player.uuid, 'minutes': stats.time_spent, 'rank': stats.rank, 'titles': titles }) five_minutes_ago = datetime.utcnow() - timedelta(minutes=10) result = PlayerStats.query.filter(PlayerStats.server == server, PlayerStats.last_seen > five_minutes_ago) recent_player_ids = [x.player_id for x in result] # find all players that have recently left and insert an 'exit' activity for them # if their last activity was an 'enter' for player_id in set(recent_player_ids) - set(online_player_ids): latest_activity = PlayerActivity.query.filter_by(server=server, player_id=player_id)\ .order_by(PlayerActivity.timestamp.desc()).first() if latest_activity and latest_activity.activity_type == PLAYER_ACTIVITY_TYPES[ 'enter']: ex = PlayerActivity(server=server, player_id=player_id, activity_type=PLAYER_ACTIVITY_TYPES['exit']) ex.save(commit=False) player_count = server_status.get('numplayers', 0) or 0 cpu_load = server_status.get('load', 0) or 0 tps = server_status.get('tps', 0) or 0 status = ServerStatus(server=server, player_count=player_count, cpu_load=cpu_load, tps=tps) status.save(commit=True) api.send_stats( server, { 'player_stats': player_stats, 'session': mojang_status.session, 'account': mojang_status.account, 'auth': mojang_status.auth }) _handle_groups(server, server_status.get('groups', []))
def send_message(username): user = g.user player = user.player if user.forum_ban or (player and player.banned): rollbar.report_message('User blocked from sending a message', level='warning', request=request) return jsonify({ 'err': 1, 'message': 'Oops, you are blocked from sending any messages. Awkward...' }) body = request.form.get('body') to_user = User.query.outerjoin(Player).options( joinedload(User.player) ).filter( or_(Player.username == username, User.username == username) ).first() if to_user: to_player = to_user.player else: # for cases of messages sent to players with no users created yet to_player = Player.query.filter_by(username=username).first() if not to_player: rollbar.report_message('to_player None', request=request) return jsonify({ 'err': 1 }) if libmessages.is_sender_spamming(user, to_user, to_player): can_post = libforums.can_user_post(user) rollbar.report_message('User blocked from spamming messages', request=request, extra_data={ 'to_user_id': to_user.id if to_user else None, 'to_player_id': to_player.id if to_player else None, 'can-Post': can_post }) if not can_post and not user.forum_ban: player = user.player libplayer.ban_player(player, source='message_spamming', commit=False) ban = ForumBan(user_id=user.id) ban.save(commit=True) return jsonify({ 'err': 1, 'message': 'Whoa there, you sent too many messages recently! Try sending a bit later.' }) message = Message( from_user=user, to_user=to_user, to_player=to_player, body=body, user_ip=request.remote_addr ) message.save() notify_new_message(message) stats.incr('messages.created') return jsonify({ 'err': 0, 'message': message.to_dict() })
def _query_server(server, mojang_status): server_status = api.get_server_status(server) or {} player_stats = [] players_to_sync_ban = Player.query.filter( Player.uuid.in_(server_status.get('banned_uuids', [])), Player.banned == False ).all() if players_to_sync_ban: player_ids_to_sync_ban = [x.id for x in players_to_sync_ban] Player.query.filter( Player.id.in_(player_ids_to_sync_ban) ).update({ 'banned': True, }, synchronize_session=False) for player in players_to_sync_ban: AuditLog.create( AuditLog.PLAYER_BAN, player_id=player.id, username=player.username, source='server_sync', commit=False ) players = server_status.get('players', []) online_player_ids = [] players_to_nok_ban = [] for player_info in players: username = player_info['username'] uuid = player_info['uuid'] player = Player.query.options( joinedload(Player.titles) ).filter_by(uuid=uuid).first() if player: if player.username != username: h.avoid_duplicate_username(username) player.set_username(username) player.save(commit=False) else: h.avoid_duplicate_username(username) player = Player(username=username, uuid=uuid) player.save(commit=False) statsd.incr('player.created') online_player_ids.append(player.id) last_activity = PlayerActivity.query.filter_by( server=server, player=player ).order_by( PlayerActivity.timestamp.desc() ).first() # if the last activity for this player is an 'exit' activity (or there isn't an activity), # create a new 'enter' activity since they just joined this minute if not last_activity or last_activity.activity_type == PLAYER_ACTIVITY_TYPES['exit']: enter = PlayerActivity(server=server, player=player, activity_type=PLAYER_ACTIVITY_TYPES['enter']) enter.save(commit=False) if server.id == app.config['MAIN_SERVER_ID']: if player.banned: player.banned = False AuditLog.create( AuditLog.PLAYER_UNBAN, player_id=player.id, username=player.username, source='server_sync', commit=False ) nickname_ansi = player_info.get('nickname_ansi') nickname = player_info.get('nickname') player.nickname_ansi = nickname_ansi player.nickname = nickname player.save(commit=False) ip = player_info.get('address') if ip: if not IPTracking.query.filter_by(ip=ip, player=player).first(): existing_player_ip = IPTracking(ip=ip, player=player) existing_player_ip.save(commit=False) if geoip.is_nok(ip): players_to_nok_ban.append((player, ip)) stats = PlayerStats.query.filter_by(server=server, player=player).first() if not stats: stats = PlayerStats(server=server, player=player) stats.last_seen = datetime.utcnow() stats.pvp_logs = player_info.get('pvp_logs') stats.time_spent = (stats.time_spent or 0) + 1 stats.save(commit=False) titles = [{'name': x.name, 'broadcast': x.broadcast} for x in player.titles] player_stats.append({ 'username': player.username, 'uuid': player.uuid, 'minutes': stats.time_spent, 'rank': stats.rank, 'titles': titles }) if len(players) and (float(len(players_to_nok_ban)) / float(len(players))) < 0.5: for player, ip in players_to_nok_ban: libplayer.ban_player(player, with_ip=True, source='query', ip=ip, commit=False) five_minutes_ago = datetime.utcnow() - timedelta(minutes=10) result = PlayerStats.query.filter(PlayerStats.server == server, PlayerStats.last_seen > five_minutes_ago) recent_player_ids = [x.player_id for x in result] # find all players that have recently left and insert an 'exit' activity for them # if their last activity was an 'enter' for player_id in set(recent_player_ids) - set(online_player_ids): latest_activity = PlayerActivity.query.filter_by(server=server, player_id=player_id)\ .order_by(PlayerActivity.timestamp.desc()).first() if latest_activity and latest_activity.activity_type == PLAYER_ACTIVITY_TYPES['enter']: ex = PlayerActivity(server=server, player_id=player_id, activity_type=PLAYER_ACTIVITY_TYPES['exit']) ex.save(commit=False) player_count = server_status.get('numplayers', 0) or 0 cpu_load = server_status.get('load', 0) or 0 tps = server_status.get('tps', 0) or 0 status = ServerStatus(server=server, player_count=player_count, cpu_load=cpu_load, tps=tps) status.save(commit=True) api.send_stats(server, { 'player_stats': player_stats, 'session': mojang_status.session, 'account': mojang_status.account, 'auth': mojang_status.auth }) _handle_groups(server, server_status.get('groups', []))
def create_account(token): email_token = EmailToken.query.filter_by(token=token).first() result = _check_email_token(email_token, 'creation') if result: return result if g.user: if g.user.forum_ban: session['forum_ban'] = True session.permanent = True if g.user.player.banned: session['player_ban'] = True session.permanent = True rollbar.report_message( 'User already logged in when verifying creation email', level='warning', request=request, extra_data={ 'existing_forum_ban': bool(g.user.forum_ban), 'existing_player_ban': bool(g.user.player.banned) }) session.pop('user_session_key', None) g.user = None form = VerifyEmailForm() player = Player.query.filter_by(uuid=email_token.uuid).first() email = email_token.email if form.validate_on_submit(): password = form.password.data confirm_password = form.confirm_password.data if password != confirm_password: flash('Passwords do not match', 'error') else: email_token.date_redeemed = datetime.utcnow() user = User.create(player, password, email) if session.get('forum_ban'): rollbar.report_message( 'Banning user associated with another forum banned user', level='error', request=request) ban = ForumBan(user_id=user.id) ban.save(commit=True) if session.get('player_ban'): rollbar.report_message( 'Banning player associated with another banned player', level='error', request=request) player.banned = True player.save(commit=True) session['user_session_key'] = user.session_key session['first_login'] = True session.permanent = True flash('Account created! You are now logged in', 'success') stats.incr('account.created') rollbar.report_message('Account created', level='info', request=request, extra_data={ 'user_id': user.id, 'player_id': player.id, 'username': player.username }) return redirect(url_for('index')) return render_template('create_account.html', form=form, player=player, email_token=email_token)
def message_reply(): api_key = app.config['MAILGUN_API_KEY'] token = request.form.get('token') timestamp = request.form.get('timestamp') signature = request.form.get('signature') if not verify_mailgun_signature(api_key, token, timestamp, signature): abort(403) body = request.form.get('stripped-text') full_body = request.form.get('body-plain') sender = request.form.get('sender') # try to find the reply token of the original message match = re.search(r'--([^\s]+)-([^\s]+)-([^\s]+)--', full_body) if match: b64_from_user_id, b64_to_user_id, signature = match.groups() try: # swap to/from in the original message since this is a reply reply_to_user_id = base64.b64decode(b64_from_user_id) reply_from_user_id = base64.b64decode(b64_to_user_id) except Exception: rollbar.report_message( 'Message not sent via email - cannot parse token', level='warning', extra_data={ 'b64_from_user_id': b64_from_user_id, 'b64_to_user_id': b64_to_user_id }) else: if verify_message_reply_signature(reply_to_user_id, reply_from_user_id, signature): to_user = User.query.options(joinedload( User.player)).get(reply_to_user_id) from_user = User.query.options(joinedload( User.player)).get(reply_from_user_id) if to_user and from_user: message = Message(from_user=from_user, to_user=to_user, to_player=to_user.player, body=body, user_ip=request.remote_addr) message.save() stats.incr('messages.created') notify_new_message(message) rollbar.report_message( 'Message successfully sent via email', level='debug', extra_data={ 'from_user_id': from_user.id, 'to_user_id': to_user.id }) else: rollbar.report_message( 'Message not sent via email - user not found', level='warning', extra_data={ 'reply_from_user_id': reply_from_user_id, 'reply_to_user_id': reply_to_user_id }) else: rollbar.report_message( 'Message not sent via email - reply token signature mismatch', level='warning', extra_data={ 'reply_from_user_id': reply_from_user_id, 'reply_to_user_id': reply_to_user_id, 'signature': signature }) else: rollbar.report_message('Message not sent via email - token not found', level='warning', extra_data={ 'sender': sender, 'full_body': full_body }) return jsonify({})
def send_message(username): user = g.user player = user.player if user.forum_ban or (player and player.banned): rollbar.report_message('User blocked from sending a message', level='warning', request=request) return jsonify({ 'err': 1, 'message': 'Oops, you are blocked from sending any messages. Awkward...' }) body = request.form.get('body') to_user = User.query.outerjoin(Player).options(joinedload( User.player)).filter( or_(Player.username == username, User.username == username)).first() if to_user: to_player = to_user.player else: # for cases of messages sent to players with no users created yet to_player = Player.query.filter_by(username=username).first() if not to_player: rollbar.report_message('to_player None', request=request) return jsonify({'err': 1}) if libmessages.is_sender_spamming(user, to_user, to_player): can_post = libforums.can_user_post(user) rollbar.report_message('User blocked from spamming messages', request=request, extra_data={ 'to_user_id': to_user.id if to_user else None, 'to_player_id': to_player.id if to_player else None, 'can-Post': can_post }) if not can_post and not user.forum_ban: player = user.player libplayer.ban_player(player, source='message_spamming', commit=False) ban = ForumBan(user_id=user.id) ban.save(commit=True) return jsonify({ 'err': 1, 'message': 'Whoa there, you sent too many messages recently! Try sending a bit later.' }) message = Message(from_user=user, to_user=to_user, to_player=to_player, body=body, user_ip=request.remote_addr) message.save() notify_new_message(message) stats.incr('messages.created') return jsonify({'err': 0, 'message': message.to_dict()})
def create_account(token): email_token = EmailToken.query.filter_by(token=token).first() result = _check_email_token(email_token, 'creation') if result: return result if g.user: if g.user.forum_ban: session['forum_ban'] = True session.permanent = True if g.user.player.banned: session['player_ban'] = True session.permanent = True rollbar.report_message( 'User already logged in when verifying creation email', level='warning', request=request, extra_data={ 'existing_forum_ban': bool(g.user.forum_ban), 'existing_player_ban': bool(g.user.player.banned) } ) session.pop('user_session_key', None) g.user = None form = VerifyEmailForm() player = Player.query.filter_by(uuid=email_token.uuid).first() email = email_token.email if form.validate_on_submit(): password = form.password.data confirm_password = form.confirm_password.data if password != confirm_password: flash('Passwords do not match', 'error') else: email_token.date_redeemed = datetime.utcnow() user = User.create(player, password, email) if session.get('forum_ban'): rollbar.report_message( 'Banning user associated with another forum banned user', level='error', request=request ) ban = ForumBan(user_id=user.id) ban.save(commit=True) if session.get('player_ban'): rollbar.report_message( 'Banning player associated with another banned player', level='error', request=request ) player.banned = True player.save(commit=True) session['user_session_key'] = user.session_key session['first_login'] = True session.permanent = True flash('Account created! You are now logged in', 'success') stats.incr('account.created') rollbar.report_message( 'Account created', level='info', request=request, extra_data={ 'user_id': user.id, 'player_id': player.id, 'username': player.username } ) return redirect(url_for('index')) return render_template('create_account.html', form=form, player=player, email_token=email_token)