示例#1
0
        public async Task <JsonResult> TryEnterGameServer([FromServices] IGameSessionRepository gameSessionRepository)
        {
            if (!ModelState.IsValid)
            {
                return(Json(new ServerEntryResponse(ServerEntryResponseCode.FailedConnectionActivelyRefused)));
            }

            int userId = int.Parse(HaloLiveUserManager.GetUserId(User));             //TODO: Should we try parse? Or change the signature for this?

            //If there is a session and we're already logged in reject the requesting user
            //It is possible for there to be an existing session yet not logged in
            //that would mean an existing session was unclaimined and hasn't been cleaned up yet or someone is connecting in the middle of the session transfer
            bool hasSessionAlready = await gameSessionRepository.HasSession(userId);

            if (hasSessionAlready)
            {
                return(Json(new ServerEntryResponse(ServerEntryResponseCode.FailedAlreadyLoggedIn)));
            }

            //This could result in a data race so we need to check the result after we create. Don't assume this won't fail or be exploited
            SessionCreationResult result = await gameSessionRepository.TryCreateSession(userId, Request.HttpContext.Connection.RemoteIpAddress.ToString());

            if (!result.isSessionCreated)
            {
                return(Json(new ServerEntryResponse(ServerEntryResponseCode.GeneralServerError)));
            }

            //TODO: Handle gameserver/zoneserver redirection based on session information.
            return(Json(new ServerEntryResponse(result.SessionGuid, new ResolvedEndpoint("127.0.0.1", 8051))));            //TODO: Obviously we want to look in the DB and provide a real token.
        }
        public async Task <IActionResult> RequestWorldDownloadUrl([FromServices] IReadOnlyWorldEntryRepository worldEntryRepository, [FromServices] IStorageUrlBuilder urlBuilder,
                                                                  [FromBody] WorldDownloadURLRequest downloadRequest)
        {
            if (worldEntryRepository == null)
            {
                throw new ArgumentNullException(nameof(worldEntryRepository));
            }

            //TODO: We want to rate limit access to this API
            //TODO: We should use both app logging but also another logging service that always gets hit

            //TODO: Consolidate this shared logic between controllers
            if (Logger.IsEnabled(LogLevel.Information))
            {
                Logger.LogInformation($"Recieved {nameof(RequestWorldDownloadUrl)} request from {HaloLiveUserManager.GetUserName(User)}:{HaloLiveUserManager.GetUserId(User)}.");
            }

            int userId;

            if (!int.TryParse(HaloLiveUserManager.GetUserId(User), out userId))
            {
                if (Logger.IsEnabled(LogLevel.Error))
                {
                    Logger.LogError($"Error: Encountered authorized user with unparsable UserId from User: {HaloLiveUserManager.GetUserName(User)}.");
                }

                return(Json(new WorldDownloadURLResponse(WorldDownloadURLResponseCode.AuthorizationFailed)));
            }

            //TODO: We should probably check the flags of world to see if it's private (IE hidden from user). Or if it's unlisted or removed.
            //It's possible a user is requesting a world that doesn't exist
            //Could be malicious or it could have been deleted for whatever reason
            if (!await worldEntryRepository.HasEntry(downloadRequest.WorldId))
            {
                return(Json(new WorldDownloadURLResponse(WorldDownloadURLResponseCode.NoWorld)));
            }

            //We can get the URL from the urlbuilder if we provide the world storage GUID
            string downloadUrl = await urlBuilder.BuildRetrivalUrl(UserContentType.World, (await worldEntryRepository.GetWorldEntry(downloadRequest.WorldId)).StorageGuid);

            //TODO: Should we both validating S3 availability?
            if (String.IsNullOrEmpty(downloadUrl))
            {
                if (Logger.IsEnabled(LogLevel.Error))
                {
                    Logger.LogError($"Failed to create world upload URL for {HaloLiveUserManager.GetUserName(User)}:{HaloLiveUserManager.GetUserId(User)} with ID: {downloadRequest.WorldId}.");
                }

                return(Json(new WorldDownloadURLResponse(WorldDownloadURLResponseCode.WorldDownloadServiceUnavailable)));
            }

            if (Logger.IsEnabled(LogLevel.Information))
            {
                Logger.LogInformation($"Success. Sending {HaloLiveUserManager.GetUserName(User)} URL: {downloadUrl}");
            }

            return(Json(new WorldDownloadURLResponse(downloadUrl)));
        }
        public async Task <IActionResult> RequestWorldUploadUrl([FromServices] IWorldEntryRepository worldEntryRepository, [FromServices] IStorageUrlBuilder urlBuilder)
        {
            if (worldEntryRepository == null)
            {
                throw new ArgumentNullException(nameof(worldEntryRepository));
            }

            //TODO: We want to rate limit access to this API
            //TODO: We should use both app logging but also another logging service that always gets hit

            if (Logger.IsEnabled(LogLevel.Information))
            {
                Logger.LogInformation($"Recieved {nameof(RequestWorldUploadUrl)} request from {HaloLiveUserManager.GetUserName(User)}:{HaloLiveUserManager.GetUserId(User)}.");
            }

            int userId;

            if (!int.TryParse(HaloLiveUserManager.GetUserId(User), out userId))
            {
                if (Logger.IsEnabled(LogLevel.Error))
                {
                    Logger.LogError($"Error: Encountered authorized user with unparsable UserId from User: {HaloLiveUserManager.GetUserName(User)}.");
                }

                return(new JsonResult(RequestedUrlResponseModel.CreateFailure("Failed to authorize action.", RequestedUrlResponseCode.AuthorizationFailed)));
            }

            //TODO: Abstract this behind an issuer
            Guid worldGuid = Guid.NewGuid();

            //TODO: Check if the result is valid? We should maybe return bool from this API
            //The idea is to create an entry which will contain a GUID. From that GUID we can then generate the upload URL
            await worldEntryRepository.AddWorldEntry(userId, this.HttpContext.Connection.RemoteIpAddress.ToString(), worldGuid);             //TODO: Ok to just provide a guid right?

            string uploadUrl = await urlBuilder.BuildUploadUrl(UserContentType.World, worldGuid);

            if (String.IsNullOrEmpty(uploadUrl))
            {
                if (Logger.IsEnabled(LogLevel.Error))
                {
                    Logger.LogError($"Failed to create world upload URL for {HaloLiveUserManager.GetUserName(User)}:{HaloLiveUserManager.GetUserId(User)} with GUID: {worldGuid}.");
                }

                return(new JsonResult(RequestedUrlResponseModel.CreateFailure("Upload service unavailable.", RequestedUrlResponseCode.ServiceUnavailable)));
            }

            if (Logger.IsEnabled(LogLevel.Information))
            {
                Logger.LogInformation($"Success. Sending {HaloLiveUserManager.GetUserName(User)} URL: {uploadUrl}");
            }

            return(new JsonResult(RequestedUrlResponseModel.CreateSuccess(uploadUrl)));
        }
        public async Task <JsonResult> GetCharacterList([FromServices] IReadonlyCharacterRepository characterRepository)
        {
            if (characterRepository == null)
            {
                throw new ArgumentNullException(nameof(characterRepository));
            }

            //The account id should be in the JWT/claim sent. It's required to load the characters from the database
            //that are associated with the account.
            int accountId = HaloLiveUserManager.GetUserIdInt(User);

            //We don't load additional uneeded information. The Ids are enough for the client to load their names, profiles and appearance if required.
            int[] characterIds = (await characterRepository.LoadAssociatedCharacterIds(accountId)).ToArray();
            characterIds = characterIds ?? Enumerable.Empty <int>().ToArray();

            //We don't need to do anything fancy. The ID of the characters is TRULY enough for the client to then request and piece together all the other missing content
            return(Json(new CharacterListResponse(characterIds)));
        }
        public async Task <JsonResult> CreateCharacter([FromBody] CharacterCreationRequest request, [FromServices] ICharacterRepository characterRepository)
        {
            if (characterRepository == null)
            {
                throw new ArgumentNullException(nameof(characterRepository));
            }

            //TODO: Return error
            if (!ModelState.IsValid)
            {
                return(Json(new CharacterCreationResponse(CharacterCreationResponseCode.GeneralServerError)));
            }

            //The account id should be in the JWT/claim sent. It's required to load the characters from the database
            //that are associated with the account.
            int accountId = HaloLiveUserManager.GetUserIdInt(User);

            //TODO: We should check banned names list
            //We must check the name first because there could be a duplicate
            bool nameIsTaken = await characterRepository.DoesNameExist(request.CharacterName);

            if (nameIsTaken)
            {
                return(Json(new CharacterCreationResponse(CharacterCreationResponseCode.NameInvalid)));
            }

            //TODO: Enforce a maximum account of characters per account
            CharacterCreationResult result = await characterRepository.TryCreateNewCharacter(accountId, Request.HttpContext.Connection.RemoteIpAddress.ToString(),
                                                                                             new CharacterCreationInformation(request.CharacterName, request.CharacterClass));

            //This means that the character was created and was assigned a section id
            if (result.IsSuccessful)
            {
                return(Json(new CharacterCreationResponse(result.CharacterSectionId)));
            }

            //We don't know what went wrong here, something did. This could be due to a race condition. It should so rarely occur that it is most likely just a major server
            //issue instead.
            return(Json(new CharacterCreationResponse(CharacterCreationResponseCode.GeneralServerError)));
        }