예제 #1
0
 def set(cls, service_name):
     try:
         ServiceStatus.set_status(service_name, request.params)
         return {'ok': True}
     except InvalidStatus:
         response.status_int = 400
         msg = 'Missing or invalid service status arguments. Must be running, discardRequests or description'
         return {'ok': False, 'msg': msg}
예제 #2
0
 def set(cls, service_name):
     try:
         ServiceStatus.set_status(service_name, request.params)
         return {'ok': True}
     except InvalidStatus:
         response.status_int = 400
         msg = 'Missing or invalid service status arguments. Must be running, discardRequests or description'
         return {'ok': False, 'msg': msg}
예제 #3
0
 def read_list(cls):
     try:
         return {'ok': True, 'data': {
             'services': ServiceStatus.get_status_list(),
             'pollInterval': ServiceStatus.get_poll_interval()
         }}
     except KeyError:
         response.status_int = 400
         return {'ok': False, 'msg': 'Missing service name'}
예제 #4
0
 def set_poll_interval(cls):
     try:
         poll_interval = float(request.params['value'])
         if poll_interval <= 0:
             raise ValueError
         ServiceStatus.set_poll_interval(poll_interval)
         return {'ok': True}
     except (KeyError, ValueError):
         response.status_int = 400
         return {'ok': False, 'msg': 'Polling interval must be a positive value'}
예제 #5
0
 def read_list(cls):
     try:
         return {
             'ok': True,
             'data': {
                 'services': ServiceStatus.get_status_list(),
                 'pollInterval': ServiceStatus.get_poll_interval()
             }
         }
     except KeyError:
         response.status_int = 400
         return {'ok': False, 'msg': 'Missing service name'}
예제 #6
0
 def set_poll_interval(cls):
     try:
         poll_interval = float(request.params['value'])
         if poll_interval <= 0:
             raise ValueError
         ServiceStatus.set_poll_interval(poll_interval)
         return {'ok': True}
     except (KeyError, ValueError):
         response.status_int = 400
         return {
             'ok': False,
             'msg': 'Polling interval must be a positive value'
         }
예제 #7
0
class ProfilesController(BaseController):
    """ ProfilesController consists of all the Profiles methods
    """

    profiles_service = ServiceStatus.check_status_decorator('profiles')

    ##
    ## FRONT CONTROLLER METHODS
    ##

    @classmethod
    @profiles_service
    @jsonify
    def user(cls):
        user = get_current_user()
        user_profile = {'username': user.username,
                        'displayname': user.username,
                        'age': user.age,
                        'language': user.language,
                        'country': user.country,
                        'avatar': user.avatar,
                        'guest': user.guest}
        return {'ok': True, 'data': user_profile}
예제 #8
0
class GamesController(BaseController):
    """
    Controller class for the 'play' branch of the URL tree.
    """

    gamesession_service = ServiceStatus.check_status_decorator('gameSessions')

    @classmethod
    @gamesession_service
    @jsonify
    def create_session(cls, slug, mode=None):
        """
        Returns application settings for local.
        """
        game = get_game_by_slug(slug)
        if not game:
            response.status_int = 404
            return {'ok': False, 'msg': 'Game does not exist: %s' % slug}

        if 'canvas' == mode:
            prefix = 'play/%s/' % slug
        else:
            prefix = ''
        mapping_table = 'mapping_table.json'
        if game:
            mapping_table = str(game.mapping_table)

        user = get_current_user()

        game_session_list = GameSessionList.get_instance()

        if (asbool(request.params.get('closeExistingSessions', False))):
            game_session_list.remove_game_sessions(user, game)

        game_session = game_session_list.create_session(user, game)

        # Reset API's (so YAML files are reloaded on a page refresh)
        StoreList.reset()
        DataShareList.reset()
        GameNotificationKeysList.reset()

        return {
            'ok': True,
            'mappingTable': {
                'mappingTableURL': prefix + mapping_table,
                'mappingTablePrefix': prefix + 'staticmax/',
                'assetPrefix': 'missing/'
            },
            'gameSessionId': game_session.gamesession_id
        }

    @classmethod
    @gamesession_service
    @jsonify
    def destroy_session(cls):
        """
        Ends a session started with create_session.
        """
        try:
            game_session_id = request.params['gameSessionId']
            user = get_current_user()
            game_session_list = GameSessionList.get_instance()
            session = game_session_list.get_session(game_session_id)
            if session is not None:
                if session.user.username == user.username:
                    game_session_list.remove_session(game_session_id)
                    return {'ok': True}
                else:
                    response.status_int = 400
                    return {
                        'ok':
                        False,
                        'msg':
                        "Attempted to end a session that is not owned by you"
                    }
            else:
                response.status_int = 400
                return {
                    'ok': False,
                    'msg': 'No session with ID "%s" exists' % game_session_id
                }

        except TypeError, e:
            response.status_int = 400
            return {'ok': False, 'msg': 'Something is missing: %s' % str(e)}
        except KeyError, e:
            response.status_int = 400
            return {'ok': False, 'msg': 'Something is missing: %s' % str(e)}
예제 #9
0
class GameprofileController(BaseController):
    """ GameprofileController consists of all the GameProfile methods
    """

    game_session_list = GameSessionList.get_instance()

    game_profile_service = ServiceStatus.check_status_decorator('gameProfile')

    max_size = int(config.get('gameprofile.max_size', 1024))
    max_list_length = int(config.get('gameprofile.max_list_length', 64))

    @classmethod
    def __get_profile(cls, params):
        """ Get the user and game for this game session """
        try:
            session = cls.game_session_list.get_session(params['gameSessionId'])
            return GameProfile(session.user, session.game)
        except (KeyError, TypeError):
            return None

    @classmethod
    @game_profile_service
    @jsonify
    def read(cls):
        params = request.params
        try:
            usernames = loads(params['usernames'])
        except (KeyError, TypeError):
            response.status_int = 400
            return {'ok': False, 'msg': 'Missing username information'}
        except ValueError:
            response.status_int = 400
            return {'ok': False, 'msg': 'Badly formated username list'}

        if not isinstance(usernames, list):
            response.status_int = 400
            return {'ok': False, 'msg': '\'usernames\' must be a list'}
        max_list_length = cls.max_list_length
        if len(usernames) > max_list_length:
            response.status_int = 413
            return {'ok': False, 'msg': 'Cannot request game profiles ' \
                                        'for more than %d users at once' % max_list_length}

        profile = cls.__get_profile(params)
        if profile is None:
            response.status_int = 400
            return {'ok': False, 'msg': 'No session with that ID exists'}

        return {'ok': True, 'data': profile.get(usernames)}

    @classmethod
    @game_profile_service
    @secure_post
    def set(cls, params=None):
        try:
            value = str(params['value'])
        except (KeyError, TypeError):
            response.status_int = 400
            return {'ok': False, 'msg': 'No profile value provided to set'}
        except ValueError:
            response.status_int = 400
            return {'ok': False, 'msg': '\'value\' should not contain non-ascii characters'}

        value_length = len(value)
        if value_length > cls.max_size:
            response.status_int = 413
            return {'ok': False, 'msg': 'Value length should not exceed %d' % cls.max_size}

        profile = cls.__get_profile(params)
        if profile is None:
            response.status_int = 400
            return {'ok': False, 'msg': 'No session with that ID exists'}

        profile.set(value)
        return {'ok': True}

    @classmethod
    @game_profile_service
    @secure_post
    def remove(cls, params=None):
        profile = cls.__get_profile(params)
        if profile is None:
            response.status_int = 400
            return {'ok': False, 'msg': 'No session with that ID exists'}

        profile.remove()
        return {'ok': True}

    # testing only
    @classmethod
    @game_profile_service
    @jsonify
    def remove_all(cls, slug):
        game = get_game_by_slug(slug)
        if game is None:
            response.status_int = 400
            return {'ok': False, 'msg': 'No game with that slug exists'}

        GameProfile.remove_all(game)
        return {'ok': True}
예제 #10
0
class StoreController(BaseController):
    """ StoreController consists of all the store methods
    """

    store_service = ServiceStatus.check_status_decorator('store')
    game_session_list = GameSessionList.get_instance()

    @classmethod
    @store_service
    @jsonify
    def get_currency_meta(cls):
        return {'ok': True, 'data': get_currency_meta()}

    @classmethod
    @store_service
    @jsonify
    def read_meta(cls, slug):
        game = get_game_by_slug(slug)
        if game is None:
            response.status_int = 404
            return {'ok': False, 'msg': 'No game with slug %s' % slug}

        try:
            store = StoreList.get(game)
            return {
                'ok': True,
                'data': {
                    'items': store.read_meta(),
                    'resources': store.read_resources()
                }
            }

        except StoreUnsupported:
            return {'ok': True, 'data': {'items': {}, 'resources': {}}}
        except ValidationException as e:
            response.status_int = 400
            return {'ok': False, 'msg': str(e)}
        except StoreError as e:
            response.status_int = e.response_code
            return {'ok': False, 'msg': str(e)}

    @classmethod
    @store_service
    @jsonify
    def read_user_items(cls, slug):
        user = get_current_user()
        game = get_game_by_slug(slug)
        if game is None:
            response.status_int = 404
            return {'ok': False, 'msg': 'No game with slug %s' % slug}

        try:
            store = StoreList.get(game)
            store_user = store.get_store_user(user)
            return {'ok': True, 'data': {'userItems': store_user.get_items()}}

        except StoreUnsupported:
            return {'ok': True, 'data': {'userItems': {}}}
        except ValidationException as e:
            response.status_int = 400
            return {'ok': False, 'msg': str(e)}
        except StoreError as e:
            response.status_int = e.response_code
            return {'ok': False, 'msg': str(e)}

    @classmethod
    @store_service
    @secure_post
    def consume_user_items(cls, params=None):
        session = cls._get_gamesession(params)

        try:

            def get_param(param):
                value = params[param]
                if value is None:
                    raise KeyError(param)
                return value

            consume_item = get_param('key')
            consume_amount = get_param('consume')
            token = get_param('token')
            gamesession_id = get_param('gameSessionId')
        except KeyError as e:
            response.status_int = 400
            return {'ok': False, 'msg': 'Missing parameter %s' % str(e)}

        try:
            game = session.game
            user = session.user

            store = StoreList.get(game)

            transactions = UserTransactionsList.get(user)

            # check if the transaction has already been attempted
            consume_transaction = transactions.get_consume_transaction(
                gamesession_id, token)

            new_consume_transaction = ConsumeTransaction(
                user, game, consume_item, consume_amount, gamesession_id,
                token)
            if consume_transaction is None:
                consume_transaction = new_consume_transaction
            elif not consume_transaction.check_match(new_consume_transaction):
                response.status_int = 400
                return {'ok': False, 'msg': 'Reused session token'}

            if not consume_transaction.consumed:
                consume_transaction.consume()

            store_user = store.get_store_user(user)
            return {
                'ok': True,
                'data': {
                    'consumed': consume_transaction.consumed,
                    'userItems': store_user.get_items()
                }
            }

        except StoreUnsupported:
            return {
                'ok': True,
                'data': {
                    'compareAndSet': False,
                    'userItems': {}
                }
            }
        except ValidationException as e:
            response.status_int = 400
            return {'ok': False, 'msg': str(e)}
        except StoreError as e:
            response.status_int = e.response_code
            return {'ok': False, 'msg': str(e)}

    @classmethod
    @store_service
    @postonly
    @jsonify
    def remove_all(cls, slug):
        user = get_current_user()
        game = get_game_by_slug(slug)

        if game is None:
            response.status_int = 404
            return {'ok': False, 'msg': 'No game with slug %s' % slug}

        try:
            store = StoreList.get(game)
            store.get_store_user(user).remove_items()
            return {'ok': True}

        except ValidationException as e:
            response.status_int = 400
            return {'ok': False, 'msg': str(e)}
        except StoreError as e:
            response.status_int = e.response_code
            return {'ok': False, 'msg': str(e)}

    @classmethod
    @store_service
    @postonly
    @jsonify
    def checkout_transaction(cls):
        user = get_current_user()

        try:
            game_slug = request.POST['gameSlug']
            transaction_items_json = request.POST['basket']
        except KeyError as e:
            response.status_int = 400
            return {'ok': False, 'msg': 'Missing parameter %s' % str(e)}

        try:
            transaction_items = _json_decoder.decode(transaction_items_json)
        except JSONDecodeError as e:
            response.status_int = 400
            return {
                'ok': False,
                'msg': 'Basket parameter JSON error: %s' % str(e)
            }

        if not isinstance(transaction_items, dict):
            response.status_int = 400
            return {
                'ok': False,
                'msg': 'Basket parameter JSON must be a dictionary'
            }

        game = get_game_by_slug(game_slug)
        if game is None:
            response.status_int = 404
            return {'ok': False, 'msg': 'No game with slug %s' % game_slug}

        try:
            transaction = Transaction(user, game, transaction_items)
            return {'ok': True, 'data': {'transactionId': transaction.id}}
        except ValidationException as e:
            response.status_int = 400
            return {'ok': False, 'msg': str(e)}
        except StoreError as e:
            response.status_int = e.response_code
            return {'ok': False, 'msg': str(e)}

    @classmethod
    @store_service
    @postonly
    @jsonify
    def pay_transaction(cls, transaction_id):
        user = get_current_user()

        try:
            user_transactions = UserTransactionsList.get(user)
            transaction = user_transactions.get_transaction(transaction_id)
            transaction.pay()

            return {'ok': True, 'data': transaction.status()}

        except ValidationException as e:
            response.status_int = 400
            return {'ok': False, 'msg': str(e)}
        except StoreError as e:
            response.status_int = e.response_code
            return {'ok': False, 'msg': str(e)}

    @classmethod
    @store_service
    @jsonify
    def read_transaction_status(cls, transaction_id):
        user = get_current_user()

        try:
            user_transactions = UserTransactionsList.get(user)
            transaction = user_transactions.get_transaction(transaction_id)

            return {'ok': True, 'data': transaction.status()}

        except ValidationException as e:
            response.status_int = 400
            return {'ok': False, 'msg': str(e)}
        except StoreError as e:
            response.status_int = e.response_code
            return {'ok': False, 'msg': str(e)}
예제 #11
0
class MultiplayerController(BaseController):
    """ MultiplayerController consists of all the multiplayer methods
    """

    multiplayer_service = ServiceStatus.check_status_decorator('multiplayer')

    secret = config.get('multiplayer.secret', None)

    lock = Lock()
    last_player_id = 0
    last_session_id = 0

    sessions = {}

    servers = {}

    ##
    ## FRONT CONTROLLER METHODS
    ##

    @classmethod
    @postonly
    @multiplayer_service
    @jsonify
    def create(cls, slug):

        game = get_game_by_slug(slug)
        if not game:
            response.status_int = 404
            return {'ok': False, 'msg': 'Unknown game.'}

        try:
            num_slots = int(request.params['slots'])
            _ = request.params['gameSessionId'] # Check for compatibility with gamesite API which does use this
        except (KeyError, ValueError):
            response.status_int = 400
            return {'ok': False, 'msg': 'Missing session information.'}

        with cls.lock:

            cls.last_player_id += 1
            player_id = str(cls.last_player_id)

            sessions = cls.sessions

            cls.last_session_id += 1
            session_id = str(cls.last_session_id)

            server_address = None
            secret = None

            if cls.secret is not None:
                stale_time = time() - 80
                for ip, server in cls.servers.iteritems():
                    if stale_time < server.updated:
                        server_address = '%s:%d' % (ip, server.port)
                        secret = cls.secret
                        break

            session = MultiplayerSession(session_id, slug, num_slots, server_address, secret)

            LOG.info('Created session %s (%d slots)', session_id, num_slots)

            sessions[session_id] = session

            request_ip = get_remote_addr(request)

            session.add_player(player_id, request_ip)

            LOG.info('Player %s joins session %s', player_id, session_id)

            info = {'server': session.get_player_address(request.host, request_ip, player_id),
                    'sessionid': session_id,
                    'playerid': player_id,
                    'numplayers': session.get_num_players()}
            return {'ok': True, 'data': info}

    @classmethod
    @postonly
    @multiplayer_service
    @jsonify
    def join(cls):
        params = request.params
        try:
            session_id = params['session']
            _ = request.params['gameSessionId'] # Check for compatibility with gamesite API which does use this
        except KeyError:
            response.status_int = 400
            return {'ok': False, 'msg': 'Missing session information.'}

        try:
            session = cls.sessions[session_id]
        except KeyError:
            response.status_int = 404
            return {'ok': False, 'msg': 'Unknown session.'}

        with cls.lock:

            session.update_status()

            request_ip = get_remote_addr(request)

            player_id = params.get('player', None)
            if player_id is None:
                cls.last_player_id += 1
                player_id = str(cls.last_player_id)
            else:
                stored_ip = session.get_player_ip(player_id)
                if stored_ip is not None and request_ip != stored_ip:
                    response.status_int = 401
                    return {'ok': False}

            if session.can_join(player_id):
                session.add_player(player_id, request_ip)

                LOG.info('Player %s joins session %s', player_id, session_id)

                info = {'server': session.get_player_address(request.host, request_ip, player_id),
                        'sessionid': session_id,
                        'playerid': player_id,
                        'numplayers': session.get_num_players()}

                return {'ok': True, 'data': info}

            response.status_int = 409
            return {'ok': False, 'msg': 'No slot available.'}

    @classmethod
    @postonly
    @multiplayer_service
    @jsonify
    def join_any(cls, slug):
        params = request.params
        try:
            _ = params['gameSessionId'] # Check for compatibility with gamesite API which does use this
        except KeyError:
            response.status_int = 400
            return {'ok': False, 'msg': 'Missing game information.'}

        game = get_game_by_slug(slug)
        if not game:
            response.status_int = 404
            return {'ok': False, 'msg': 'Unknown game.'}

        with cls.lock:

            cls.last_player_id += 1
            player_id = str(cls.last_player_id)

            sessions = cls.sessions
            session = session_id = None
            for existing_session in sessions.itervalues():
                if existing_session.game == slug:
                    existing_session.update_status()
                    if existing_session.can_join(player_id):
                        session = existing_session
                        session_id = existing_session.session_id
                        break

            if session is not None:
                request_ip = get_remote_addr(request)

                session.add_player(player_id, request_ip)

                LOG.info('Player %s joins session %s', player_id, session_id)

                info = {'server': session.get_player_address(request.host, request_ip, player_id),
                        'sessionid': session_id,
                        'playerid': player_id,
                        'numplayers': session.get_num_players()}
            else:
                # No session to join
                info = {}
            return {'ok': True, 'data': info}

    @classmethod
    @postonly
    @multiplayer_service
    @jsonify
    def leave(cls):
        params = request.params
        try:
            session_id = params['session']
            player_id = params['player']
            _ = params['gameSessionId'] # Check for compatibility with gamesite API which does use this
        except KeyError:
            response.status_int = 400
            return {'ok': False, 'msg': 'Missing session information.'}

        sessions = cls.sessions
        try:
            session = sessions[session_id]
        except KeyError:
            response.status_int = 404
            return {'ok': False, 'msg': 'Unknown session.'}

        with cls.lock:

            if session.has_player(player_id):

                request_ip = get_remote_addr(request)

                stored_ip = session.get_player_ip(player_id)
                if stored_ip is not None and request_ip != stored_ip:
                    response.status_int = 401
                    return {'ok': False}

                LOG.info('Player %s leaving session %s', player_id, session_id)

                session.remove_player(player_id)

                cls._clean_empty_sessions()

        return {'ok': True}


    @classmethod
    @postonly
    @multiplayer_service
    @jsonify
    def make_public(cls):
        params = request.params
        try:
            session_id = params['session']
        except KeyError:
            response.status_int = 400
            return {'ok': False, 'msg': 'Missing session information.'}

        sessions = cls.sessions
        try:
            session = sessions[session_id]
        except KeyError:
            response.status_int = 404
            return {'ok': False, 'msg': 'Unknown session.'}

        session.public = True

        return {'ok': True}

    @classmethod
    @multiplayer_service
    @jsonify
    def list_all(cls):
        request_host = request.host

        sessions = []
        for session in cls.sessions.itervalues():
            session.update_status()
            sessions.append(session.get_info(request_host))

        return {'ok': True, 'data': sessions}

    @classmethod
    @jsonify
    def list(cls, slug):
        game = get_game_by_slug(slug)
        if not game:
            response.status_int = 404
            return {'ok': False, 'msg': 'Unknown game.'}

        request_host = request.host

        sessions = []
        for session in cls.sessions.itervalues():
            if session.game == slug:
                session.update_status()
                sessions.append(session.get_info(request_host))

        return {'ok': True, 'data': sessions}

    @classmethod
    @multiplayer_service
    @jsonify
    def read(cls):
        params = request.params
        try:
            session_id = params['session']
        except KeyError:
            response.status_int = 400
            return {'ok': False, 'msg': 'Missing session information.'}

        try:
            session = cls.sessions[session_id]
        except KeyError:
            response.status_int = 404
            return {'ok': False, 'msg': 'Unknown session.'}

        session.update_status()

        return {'ok': True, 'data': session.get_info(request.host)}

    @classmethod
    @postonly
    @multiplayer_service
    @jsonify
    def register(cls):
        remote_addr = get_remote_addr(request)

        try:
            params = request.params
            host = params.get('host', remote_addr)
            hmac = params['hmac']
            server = MultiplayerServer(params)
        except KeyError:
            response.status_int = 400
            return {'ok': False, 'msg': 'Missing server information.'}
        except ValueError:
            response.status_int = 400
            return {'ok': False, 'msg': 'Incorrect server information.'}

        calculated_hmac = _calculate_registration_hmac(cls.secret, remote_addr)
        if hmac != calculated_hmac:
            response.status_int = 400
            return {'ok': False, 'msg': 'Invalid server information.'}

        LOG.info('Multiplayer server registered from %s as %s:%d', remote_addr, host, server.port)

        cls.servers[host] = server

        return {'ok': True}

    @classmethod
    @postonly
    @multiplayer_service
    @jsonify
    def heartbeat(cls):
        remote_addr = get_remote_addr(request)
        try:
            params = request.params
            host = params.get('host', remote_addr)
            num_players = params.get('numplayers')
            hmac = params['hmac']
        except KeyError:
            response.status_int = 400
            return {'ok': False, 'msg': 'Missing server information.'}

        calculated_hmac = _calculate_heartbeat_hmac(cls.secret, remote_addr, num_players, None)
        if hmac != calculated_hmac:
            response.status_int = 400
            return {'ok': False, 'msg': 'Invalid server information.'}

        try:
            server = cls.servers[host]
        except KeyError:
            response.status_int = 404
            return {'ok': False, 'msg': 'Unknown server IP.'}

        try:
            server.update(request.params)
        except KeyError:
            response.status_int = 400
            return {'ok': False, 'msg': 'Missing server information.'}
        except ValueError:
            response.status_int = 400
            return {'ok': False, 'msg': 'Incorrect server information.'}

        #LOG.info('%s: %s', host, str(server))

        return {'ok': True}

    @classmethod
    @postonly
    @multiplayer_service
    @jsonify
    def unregister(cls):
        remote_addr = get_remote_addr(request)

        params = request.params
        host = params.get('host', remote_addr)
        try:
            hmac = params['hmac']
        except KeyError:
            response.status_int = 400
            return {'ok': False, 'msg': 'Missing session information.'}

        calculated_hmac = _calculate_registration_hmac(cls.secret, remote_addr)
        if hmac != calculated_hmac:
            response.status_int = 400
            return {'ok': False, 'msg': 'Invalid server information.'}

        try:
            server = cls.servers[host]
            del cls.servers[host]
        except KeyError:
            response.status_int = 404
            return {'ok': False, 'msg': 'Unknown server IP.'}

        LOG.info('Multiplayer server unregistered from %s:%d', host, server.port)
        return {'ok': True}

    @classmethod
    @postonly
    @multiplayer_service
    @jsonify
    def client_leave(cls):
        remote_addr = get_remote_addr(request)

        params = request.params
        try:
            session_id = params['session']
            player_id = params['client']
            hmac = params['hmac']
        except KeyError:
            response.status_int = 400
            return {'ok': False, 'msg': 'Missing session information.'}

        calculated_hmac = _calculate_client_hmac(cls.secret, remote_addr, session_id, player_id)
        if hmac != calculated_hmac:
            response.status_int = 400
            return {'ok': False, 'msg': 'Invalid server information.'}

        sessions = cls.sessions
        try:
            session = sessions[session_id]
        except KeyError:
            response.status_int = 404
            return {'ok': False, 'msg': 'Unknown session.'}

        with cls.lock:

            if session.has_player(player_id):

                request_ip = get_remote_addr(request)

                stored_ip = session.get_player_ip(player_id)
                if stored_ip is not None and request_ip != stored_ip:
                    response.status_int = 401
                    return {'ok': False}

                LOG.info('Player %s left session %s', player_id, session_id)

                session.remove_player(player_id)

                cls._clean_empty_sessions()

        return {'ok': True}

    @classmethod
    @postonly
    @multiplayer_service
    @jsonify
    def delete_session(cls):
        remote_addr = get_remote_addr(request)

        params = request.params
        try:
            session_id = params['session']
            hmac = params['hmac']
        except KeyError:
            response.status_int = 400
            return {'ok': False, 'msg': 'Missing session information.'}

        calculated_hmac = _calculate_session_hmac(cls.secret, remote_addr, session_id)
        if hmac != calculated_hmac:
            response.status_int = 400
            return {'ok': False, 'msg': 'Invalid server information.'}

        try:
            with cls.lock:
                del cls.sessions[session_id]
                LOG.info('Deleted empty session: %s', session_id)
        except KeyError:
            response.status_int = 404
            return {'ok': False, 'msg': 'Unknown session.'}

        return {'ok': True}

    @classmethod
    def _clean_empty_sessions(cls):
        # Needed because of merges
        sessions = cls.sessions
        to_delete = [session_id
                     for session_id, existing_session in sessions.iteritems()
                     if 0 == existing_session.get_num_players()]
        for session_id in to_delete:
            LOG.info('Deleting empty session: %s', session_id)
            del sessions[session_id]


    # Internal API used by internal mp server
    @classmethod
    def remove_player(cls, session_id, player_id):
        try:
            sessions = cls.sessions
            session = sessions[session_id]
            with cls.lock:
                if session.has_player(player_id):

                    LOG.info('Player %s left session %s', player_id, session_id)

                    session.remove_player(player_id)

                    cls._clean_empty_sessions()

        except KeyError:
            pass
예제 #12
0
class LeaderboardsController(BaseController):
    """ LeaderboardsController consists of all the Leaderboards methods
    """

    leaderboards_service = ServiceStatus.check_status_decorator('leaderboards')

    max_top_size = 32
    max_near_size = 32
    max_page_size = 64

    @classmethod
    @leaderboards_service
    @jsonify
    def read_meta(cls, slug):
        game = get_game_by_slug(slug)
        if game is None:
            response.status_int = 404
            return {'ok': False, 'msg': 'No game with that slug'}

        try:
            leaderboards = LeaderboardsList.load(game)

            return {'ok': True, 'data': leaderboards.read_meta()}

        except ValidationException as e:
            response.status_int = 400
            return {'ok': False, 'msg': str(e)}
        except LeaderboardError as e:
            response.status_int = e.response_code
            return {'ok': False, 'msg': str(e)}


    @classmethod
    @postonly
    @leaderboards_service
    @jsonify
    def reset_meta(cls):
        LeaderboardsList.reset()
        return {'ok': True}


    @classmethod
    @leaderboards_service
    @jsonify
    def read_overview(cls, slug):
        game = get_game_by_slug(slug)
        if game is None:
            response.status_int = 404
            return {'ok': False, 'msg': 'No game with that slug'}

        try:
            leaderboards = LeaderboardsList.get(game)
            return {'ok': True, 'data': leaderboards.read_overview(get_current_user())}

        except ValidationException as e:
            response.status_int = 400
            return {'ok': False, 'msg': str(e)}
        except LeaderboardError as e:
            response.status_int = e.response_code
            return {'ok': False, 'msg': str(e)}


    @classmethod
    @leaderboards_service
    @jsonify
    def read_aggregates(cls, slug):
        game = get_game_by_slug(slug)
        if game is None:
            response.status_int = 404
            return {'ok': False, 'msg': 'No game with that slug'}

        try:
            leaderboards = LeaderboardsList.get(game)
            return {'ok': True, 'data': leaderboards.read_aggregates()}

        except ValidationException as e:
            response.status_int = 400
            return {'ok': False, 'msg': str(e)}
        except LeaderboardError as e:
            response.status_int = e.response_code
            return {'ok': False, 'msg': str(e)}


    @classmethod
    @leaderboards_service
    @jsonify
    def read_expanded(cls, slug, key):
        game = get_game_by_slug(slug)
        if game is None:
            response.status_int = 404
            return {'ok': False, 'msg': 'No game with that slug'}

        params = request.GET
        method_type = params.get('type', 'top')

        def get_size(default_size, max_size):
            try:
                size = int(params.get('size', default_size))
                if size <= 0 or size > max_size:
                    raise BadRequest('size must be a positive integer smaller than %d' % max_size)
            except ValueError:
                raise BadRequest('size must be a positive integer smaller than %d' % max_size)
            return size

        try:
            leaderboards = LeaderboardsList.get(game)

            is_above = (method_type == 'above')
            if method_type == 'below' or is_above:
                try:
                    score = float(params.get('score'))
                    score_time = float(params.get('time', 0))
                    if isinf(score) or isnan(score) or isinf(score_time) or isnan(score_time):
                        response.status_int = 400
                        return { 'ok': False, 'msg': 'Score or time are incorrectly formated' }
                except (TypeError, ValueError):
                    response.status_int = 400
                    return {'ok': False, 'msg': 'Score or time parameter missing'}

                return {'ok': True, 'data': leaderboards.get_page(key,
                                                                  get_current_user(),
                                                                  get_size(5, cls.max_page_size),
                                                                  is_above,
                                                                  score,
                                                                  score_time)}
            if method_type == 'near':
                return {'ok': True, 'data': leaderboards.get_near(key,
                                                                  get_current_user(),
                                                                  get_size(9, cls.max_near_size))}
            else:  # method_type == 'top'
                return {'ok': True, 'data': leaderboards.get_top_players(key,
                                                                         get_current_user(),
                                                                         get_size(9, cls.max_top_size))}

        except ValidationException as e:
            response.status_int = 400
            return {'ok': False, 'msg': str(e)}
        except LeaderboardError as e:
            response.status_int = e.response_code
            return {'ok': False, 'msg': str(e)}


    @classmethod
    @leaderboards_service
    @secure_post
    def set(cls, key, params=None):
        session = cls._get_gamesession(params)

        try:
            leaderboards = LeaderboardsList.get(session.game)

            score = float(params['score'])
            if isinf(score):
                response.status_int = 400
                return {'ok': False, 'msg': '"score" must be a finite number'}
            if score < 0:
                response.status_int = 400
                return {'ok': False, 'msg': '"score" cannot be a negative number'}

            return {'ok': True, 'data': leaderboards.set(key, session.user, score)}

        except (TypeError, ValueError):
            response.status_int = 400
            return {'ok': False, 'data': 'Score is missing or incorrectly formated'}
        except ValidationException as e:
            response.status_int = 400
            return {'ok': False, 'msg': str(e)}
        except LeaderboardError as e:
            response.status_int = e.response_code
            return {'ok': False, 'msg': str(e)}


    @classmethod
    @leaderboards_service
    @jsonify
    def remove_all(cls, slug):
        # This is for testing only and is not present on the Hub or Gamesite
        game = get_game_by_slug(slug)
        if not game:
            response.status_int = 404
            return {'ok': False, 'msg': 'No game with that slug exists'}
        try:
            leaderboards = LeaderboardsList.get(game)
            leaderboards.remove_all()

        except ValidationException as e:
            response.status_int = 400
            return {'ok': False, 'msg': str(e)}
        except LeaderboardError as e:
            response.status_int = e.response_code
            return {'ok': False, 'msg': str(e)}

        return {'ok': True}
예제 #13
0
def __init_controllers():
    ServiceStatus.set_ok('userdata')
    ServiceStatus.set_ok('gameProfile')
    ServiceStatus.set_ok('leaderboards')
    ServiceStatus.set_ok('gameSessions')
    ServiceStatus.set_ok('badges')
    ServiceStatus.set_ok('profiles')
    ServiceStatus.set_ok('multiplayer')
    ServiceStatus.set_ok('customMetrics')
    ServiceStatus.set_ok('store')
    ServiceStatus.set_ok('datashare')
    ServiceStatus.set_ok('notifications')

    GameSessionList.get_instance().purge_sessions()
예제 #14
0
class DatashareController(BaseController):
    """ DataShareController consists of all the datashare methods
    """

    datashare_service = ServiceStatus.check_status_decorator('datashare')
    game_session_list = GameSessionList.get_instance()

    # Testing only - Not available on the Gamesite
    @classmethod
    @postonly
    @datashare_service
    @jsonify
    def remove_all(cls, slug):
        game = get_game_by_slug(slug)
        if game is None:
            raise NotFound('No game with slug %s' % slug)
        DataShareList.get(game).remove_all()
        return {'ok': True}

    @classmethod
    @postonly
    @datashare_service
    @jsonify
    def create(cls, slug):
        game = get_game_by_slug(slug)
        datashare = DataShareList.get(game).create_datashare(
            get_current_user())
        return {'ok': True, 'data': {'datashare': datashare.summary_dict()}}

    @classmethod
    @postonly
    @datashare_service
    @jsonify
    def join(cls, slug, datashare_id):
        game = get_game_by_slug(slug)
        datashare = DataShareList.get(game).get(datashare_id)
        datashare.join(get_current_user())
        return {'ok': True, 'data': {'users': datashare.users}}

    @classmethod
    @postonly
    @datashare_service
    @jsonify
    def leave(cls, slug, datashare_id):
        game = get_game_by_slug(slug)
        DataShareList.get(game).leave_datashare(get_current_user(),
                                                datashare_id)
        return {'ok': True}

    @classmethod
    @datashare_service
    @secure_post
    def set_properties(cls, slug, datashare_id, params=None):
        game = get_game_by_slug(slug)
        datashare = DataShareList.get(game).get(datashare_id)
        if 'joinable' in params:
            try:
                joinable = asbool(params['joinable'])
            except ValueError:
                raise BadRequest('Joinable must be a boolean value')
            datashare.set_joinable(get_current_user(), joinable)
        return {'ok': True}

    @classmethod
    @datashare_service
    @jsonify
    def find(cls, slug):
        game = get_game_by_slug(slug)
        username = request.params.get('username')
        datashares = DataShareList.get(game).find(get_current_user(),
                                                  username_to_find=username)
        return {
            'ok': True,
            'data': {
                'datashares':
                [datashare.summary_dict() for datashare in datashares]
            }
        }

    @classmethod
    @datashare_service
    @secure_get
    def read(cls, datashare_id, params=None):
        session = cls._get_gamesession(params)
        datashare = DataShareList.get(session.game).get(datashare_id)
        datashare_keys = datashare.get_keys(session.user)
        return {'ok': True, 'data': {'keys': datashare_keys}}

    @classmethod
    @datashare_service
    @secure_get
    def read_key(cls, datashare_id, key, params=None):
        session = cls._get_gamesession(params)
        datashare = DataShareList.get(session.game).get(datashare_id)
        datashare_key = datashare.get(session.user, key)
        return {'ok': True, 'data': datashare_key}

    @classmethod
    @datashare_service
    @secure_post
    def set_key(cls, datashare_id, key, params=None):
        session = cls._get_gamesession(params)
        datashare = DataShareList.get(session.game).get(datashare_id)
        value = params.get('value')
        new_token = datashare.set(session.user, key, value)
        return {'ok': True, 'data': {'token': new_token}}

    @classmethod
    @datashare_service
    @secure_post
    def compare_and_set_key(cls, datashare_id, key, params=None):
        session = cls._get_gamesession(params)
        datashare = DataShareList.get(session.game).get(datashare_id)

        value = params.get('value')
        token = params.get('token')
        try:
            new_token = datashare.compare_and_set(session.user, key, value,
                                                  token)
            return {'ok': True, 'data': {'wasSet': True, 'token': new_token}}
        except CompareAndSetInvalidToken:
            return {'ok': True, 'data': {'wasSet': False}}
예제 #15
0
class UserdataController(BaseController):
    """ UserdataController consists of all the Userdata methods
    """

    game_session_list = GameSessionList.get_instance()

    userdata_service = ServiceStatus.check_status_decorator('userdata')

    @classmethod
    @userdata_service
    @secure_get
    def read_keys(cls, params=None):
        _set_json_headers(response.headers)
        userdata = UserData(cls._get_gamesession(params))

        return {'ok': True, 'keys': userdata.get_keys()}

    @classmethod
    @userdata_service
    @secure_get
    def exists(cls, key, params=None):
        _set_json_headers(response.headers)
        userdata = UserData(cls._get_gamesession(params))

        return {'ok': True, 'exists': userdata.exists(key)}

    @classmethod
    @userdata_service
    @secure_get
    def read(cls, key, params=None):
        _set_json_headers(response.headers)
        userdata = UserData(cls._get_gamesession(params))

        try:
            value = userdata.get(key)
        except UserDataKeyError:
            response.status_int = 404
            return {'ok': False, 'msg': 'Key does not exist'}
        else:
            return {'ok': True, 'value': value}

    @classmethod
    @userdata_service
    @secure_post
    def set(cls, key, params=None):
        userdata = UserData(cls._get_gamesession(params))

        value = params['value']

        userdata.set(key, value)
        return {'ok': True}

    @classmethod
    @userdata_service
    @secure_post
    def remove(cls, key, params=None):
        userdata = UserData(cls._get_gamesession(params))

        try:
            userdata.remove(key)
        except UserDataKeyError:
            response.status_int = 404
            return {'ok': False, 'msg': 'Key does not exist'}
        else:
            return {'ok': True}

    @classmethod
    @userdata_service
    @secure_post
    def remove_all(cls, params=None):
        userdata = UserData(cls._get_gamesession(params))

        userdata.remove_all()
        return {'ok': True}
예제 #16
0
class CustommetricsController(BaseController):

    custommetrics_service = ServiceStatus.check_status_decorator('customMetrics')

    @classmethod
    @custommetrics_service
    @secure_post
    def add_event(cls, slug, params=None):
        # Only a validation simulation! Custom events are only tracked on the game site.
        try:
            session = cls.game_session_list.get_session(params['gameSessionId'])
        except (KeyError, TypeError):
            raise BadRequest('Invalid game session id')

        game = session.game
        if game is None:
            raise ApiException('No game with that slug')

        if slug != game.slug:
            raise BadRequest('Slug and game session do not match')

        try:
            event_key = params['key']
        except (TypeError, KeyError):
            raise BadRequest('Event key missing')

        try:
            event_value = params['value']
        except (TypeError, KeyError):
            raise BadRequest('Event value missing')
        del params['value']

        try:
            event_key, event_value = _validate_event(event_key, event_value)
        except ValueError as e:
            raise BadRequest(e.message)

        # If reaches this point, assume success
        return  {'ok': True, 'data': {'msg': 'Added "' + str(event_value) + '" for "' + event_key + '" ' \
                                             '(Simulation only - Custom events are only tracked on the game site)'}}


    @classmethod
    @custommetrics_service
    @secure_post
    def add_event_batch(cls, slug, params=None):
        # Only a validation simulation! Custom events are only tracked on the game site.
        try:
            session = cls.game_session_list.get_session(params['gameSessionId'])
        except (KeyError, TypeError):
            raise BadRequest('Invalid game session id')

        game = session.game
        if game is None:
            raise ApiException('No game with that slug')

        if slug != game.slug:
            raise BadRequest('Slug and game session do not match')

        try:
            event_batch = params['batch']
        except (TypeError, KeyError):
            raise BadRequest('Event batch missing')
        del params['batch']

        if not isinstance(event_batch, list):
            raise BadRequest('Event batch must be an array of events')

        for event in event_batch:
            try:
                event_key = event['key']
            except (TypeError, KeyError):
                raise BadRequest('Event key missing')

            try:
                event_value = event['value']
            except (TypeError, KeyError):
                raise BadRequest('Event value missing')

            try:
                event_key, event_value = _validate_event(event_key, event_value)
            except ValueError as e:
                raise BadRequest(e.message)

            try:
                event_time = float(event['timeOffset'])
            except (TypeError, KeyError):
                raise BadRequest('Event time offset missing')
            except ValueError:
                raise BadRequest('Event time offset should be a float')

            if event_time > 0:
                raise BadRequest('Event time offsets should be <= 0 to represent older events')

        # If reaches this point, assume success
        return  {'ok': True, 'data': {'msg': 'Added %d events ' \
                                             '(Simulation only - Custom events are only tracked on the game site)' %
                                             len(event_batch)}}
예제 #17
0
def __init_controllers():
    ServiceStatus.set_ok('userdata')
    ServiceStatus.set_ok('gameProfile')
    ServiceStatus.set_ok('leaderboards')
    ServiceStatus.set_ok('gameSessions')
    ServiceStatus.set_ok('badges')
    ServiceStatus.set_ok('profiles')
    ServiceStatus.set_ok('multiplayer')
    ServiceStatus.set_ok('customMetrics')
    ServiceStatus.set_ok('store')

    GameSessionList.get_instance().purge_sessions()
예제 #18
0
class BadgesController(BaseController):
    """ BadgesController consists of all the badges methods
    """

    badges_service = ServiceStatus.check_status_decorator('badges')

    #list badges for a given user (the username is taken from the environment if it's not passed as a parameter)
    @classmethod
    @jsonify
    def badges_user_list(cls, slug=None):
        try:
            game = get_game_by_slug(slug)
            if game is None:
                raise ApiException('No game with that slug')
            # get the user from the environment
            # get a user model (simulation)
            user = get_current_user()
            # try to get a user_id from the context

            badges_obj = Badges.get_singleton(game)
            badges = badges_obj.badges
            badges_total_dict = dict((b['key'], b.get('total')) for b in badges)

            userbadges = badges_obj.find_userbadges_by_user(user.username)

            for key, userbadge in userbadges.iteritems():
                del userbadge['username']
                try:
                    total = badges_total_dict[key]
                except KeyError:
                    # the badge has been deleted or its key renamed so we just skip it
                    continue

                userbadge['total'] = total
                userbadge['achieved'] = (userbadge['current'] >= total)

            response.status_int = 200
            return {'ok': True, 'data': userbadges.values()}

        except BadgesUnsupportedException:
            return {'ok': False, 'data': []}
        except ApiException as message:
            response.status_int = 404
            return {'ok': False, 'msg': str(message)}

    @classmethod
    @badges_service
    @jsonify
    def badges_list(cls, slug):
        try:
            game = get_game_by_slug(slug)
            if game is None:
                raise ApiException('No game with that slug')

            badges = Badges.get_singleton(game).badges

            # Patch any unset total values in the response (to be consistent with the hub and game site)
            for badge in badges:
                if 'total' not in badge:
                    badge['total'] = None
                if 'predescription' not in badge:
                    badge['predescription'] = None

            return {'ok': True, 'data': badges}

        except BadgesUnsupportedException:
            return {'ok': False, 'data': []}
        except ApiException as message:
            response.status_int = 404
            return {'ok': False, 'msg': str(message)}
        except ScannerError as message:
            response.status_int = 404
            return {'ok': False, 'msg': 'Could not parse YAML file. %s' % (message)}

    @classmethod
    @badges_service
    @secure_post
    # add a badge to a user (gets passed
    # a badge and a current level over POST,
    # the username is taken from the environment)
    def badges_user_add(cls, slug, params=None):
        try:
            session = cls._get_gamesession(params)
            game = session.game
            if game is None:
                raise ApiException('No game with that slug')

            badge_key = params['badge_key']
            if not badge_key:
                raise ApiException('Must specify a badge_key to add.')

            # we have a badge_key now try to see if that badge exists
            badges_obj = Badges.get_singleton(game)
            badge = badges_obj.get_badge(badge_key)
            if not badge:
                raise ApiException('Badge name %s was not found.' % badge_key)
            if not ('image' in badge) or not badge['image']:
                badge['image'] = '/static/img/badge-46x46.png'

            # Use the badge['key'] property because badge_key is unicode
            ub = {'username': session.user.username,
                  'badge_key': badge['key']}

            badge_total = badge.get('total')
            total = badge_total or 1.0

            current = 0
            if 'current' in params:
                try:
                    current = float(int(params['current']))
                except (ValueError, TypeError):
                    response.status_int = 400
                    return {'ok': False, 'msg': '\'current\' must be a integer'}
            if not current:
                current = total
            ub['current'] = current

            userbadge = badges_obj.get_userbadge(session.user.username, badge_key)
            Badges.get_singleton(game).upsert_badge(ub)

            if current >= total and (not userbadge or userbadge.get('current', 0) < total):
                achieved = True
            else:
                achieved = False

            response.status_int = 200
            return {'ok': True, 'data': {
                'current': current,
                'total': badge_total,
                'badge_key': badge_key,
                'achieved': achieved
            }}

        except BadgesUnsupportedException:
            response.status_int = 404
            return {'ok': False, 'msg': 'Badges are unsupported for this game'}
        except ApiException as message:
            response.status_int = 404
            return {'ok': False, 'msg': str(message)}