Exemplo n.º 1
0
        public async Task <IActionResult> RequestWorldDownloadUrl(
            [FromRoute(Name = "id")] long worldId,
            [FromServices] IWorldEntryRepository worldEntryRepository,
            [FromServices] IStorageUrlBuilder urlBuilder,
            [FromServices] IContentDownloadAuthroizationValidator downloadAuthorizer)
        {
            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 {ClaimsReader.GetUserName(User)}:{ClaimsReader.GetUserId(User)}.");
            }

            //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.ContainsAsync(worldId).ConfigureAwait(false))
            {
                return(Json(new ContentDownloadURLResponse(ContentDownloadURLResponseCode.NoContentId)));
            }

            //TODO: Refactor this into a validation dependency
            //Now we need to do some validation to determine if they should even be downloading this world
            //we do not want people downloading a world they have no business of going to
            int userId = ClaimsReader.GetUserIdInt(User);

            if (!await downloadAuthorizer.CanUserAccessWorldContet(userId, worldId))
            {
                return(Json(new ContentDownloadURLResponse(ContentDownloadURLResponseCode.AuthorizationFailed)));
            }

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

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

                return(Json(new ContentDownloadURLResponse(ContentDownloadURLResponseCode.ContentDownloadServiceUnavailable)));
            }

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

            return(Json(new ContentDownloadURLResponse(downloadUrl)));
        }
        /// <inheritdoc />
        public async Task <HubOnConnectionState> OnConnected([JetBrains.Annotations.NotNull] Hub hubConnectedTo)
        {
            if (hubConnectedTo == null)
            {
                throw new ArgumentNullException(nameof(hubConnectedTo));
            }

            //We should never be here unless auth worked
            //so we can assume that and just try to request character session data
            //for the account.
            CharacterSessionDataResponse characterSessionDataResponse = await SocialToGameClient.GetCharacterSessionDataByAccount(ClaimsReader.GetUserIdInt(hubConnectedTo.Context.User))
                                                                        .ConfigureAwait(false);

            //TODO: To support website chat we shouldn't disconnect just because they don't have a zone session.
            //If the session data request fails we should just abort
            //and disconnect, the user shouldn't be connecting
            if (!characterSessionDataResponse.isSuccessful)
            {
                if (Logger.IsEnabled(LogLevel.Warning))
                {
                    Logger.LogWarning($"Failed to Query SessionData for AccountId: {ClaimsReader.GetUserId(hubConnectedTo.Context.User)} Reason: {characterSessionDataResponse.ResultCode}");
                }

                //TODO: Eventually we don't want to do this.
                return(HubOnConnectionState.Abort);
            }

            //This is ABSOLUTELY CRITICAL we need to validate that the character header they sent actually
            //is the character they have a session as
            //NOT CHECKING THIS IS EQUIVALENT TO LETTING USERS PRETEND THEY ARE ANYONE!
            if (hubConnectedTo.Context.UserIdentifier != characterSessionDataResponse.CharacterId.ToString())
            {
                //We can log account name and id here, because they were successfully authed.
                if (Logger.IsEnabled(LogLevel.Warning))
                {
                    Logger.LogWarning($"User with AccountId: {ClaimsReader.GetUserName(hubConnectedTo.Context.User)}:{ClaimsReader.GetUserId(hubConnectedTo.Context.User)} attempted to spoof as CharacterId: {hubConnectedTo.Context.UserIdentifier} but had session for CharacterID: {characterSessionDataResponse.CharacterId}.");
                }

                return(HubOnConnectionState.Abort);
            }

            if (Logger.IsEnabled(LogLevel.Information))
            {
                Logger.LogInformation($"Recieved SessionData: Id: {characterSessionDataResponse.CharacterId} ZoneId: {characterSessionDataResponse.ZoneId}");
            }

            //Registers for lookup so that we can tell where a connection is zone-wise.
            ZoneLookupService.Register(hubConnectedTo.Context.ConnectionId, characterSessionDataResponse.ZoneId);

            //TODO: We should have group name builders. Not hardcoded
            //Join the zoneserver's chat channel group
            await hubConnectedTo.Groups.AddToGroupAsync(hubConnectedTo.Context.ConnectionId, $"zone:{characterSessionDataResponse.ZoneId}", hubConnectedTo.Context.ConnectionAborted)
            .ConfigureAwait(false);

            return(HubOnConnectionState.Success);
        }
Exemplo n.º 3
0
        /// <inheritdoc />
        public override async Task OnConnectedAsync()
        {
            await base.OnConnectedAsync()
            .ConfigureAwait(false);

            if (Logger.IsEnabled(LogLevel.Information))
            {
                Logger.LogInformation($"Account Connected: {ClaimsReader.GetUserName(Context.User)}:{ClaimsReader.GetUserId(Context.User)}");
            }

            NetworkEntityGuid guid = new NetworkEntityGuidBuilder()
                                     .WithId(int.Parse(Context.UserIdentifier))
                                     .WithType(EntityType.Player)
                                     .Build();

            //Register interest and then lock
            //We need to lock on the entity so only 1 connection for the entity can go through this process at a time.
            await EntityLockService.RegisterEntityInterestAsync(guid)
            .ConfigureAwait(false);

            using (await EntityLockService.AquireEntityLockAsync(guid).ConfigureAwait(false))
            {
                try
                {
                    foreach (var listener in OnConnectionHubListeners)
                    {
                        HubOnConnectionState connectionState = await listener.OnConnected(this).ConfigureAwait(false);

                        //if the listener indicated we need to abort for whatever reason we
                        //should believe it and just abort.
                        if (connectionState == HubOnConnectionState.Abort)
                        {
                            Context.Abort();
                            break;
                        }
                    }
                }
                catch (Exception e)
                {
                    if (Logger.IsEnabled(LogLevel.Error))
                    {
                        Logger.LogInformation($"Account: {ClaimsReader.GetUserName(Context.User)}:{ClaimsReader.GetUserId(Context.User)} failed to properly connect to hub. Error: {e.Message}\n\nStack: {e.StackTrace}");
                    }

                    Context.Abort();
                }
            }
        }
Exemplo n.º 4
0
        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 {ClaimsReader.GetUserName(User)}:{ClaimsReader.GetUserId(User)}.");
            }

            int userId = ClaimsReader.GetUserIdInt(User);

            //TODO: We should send this if we can't get a user id
            //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 (we do return bool from this API now)
            //The idea is to create an entry which will contain a GUID. From that GUID we can then generate the upload URL
            WorldEntryModel world  = new WorldEntryModel(userId, this.HttpContext.Connection.RemoteIpAddress.ToString(), worldGuid);
            bool            result = await worldEntryRepository.TryCreateAsync(world);  //TODO: Ok to just provide a guid right?

            //TODO: Check world's worldid has been set

            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 {ClaimsReader.GetUserName(User)}:{ClaimsReader.GetUserId(User)} with GUID: {worldGuid}.");
                }

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

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

            return(new JsonResult(RequestedUrlResponseModel.CreateSuccess(uploadUrl, world.WorldId)));
        }
Exemplo n.º 5
0
        public async Task <CharacterListResponse> GetCharacters()
        {
            int accountId = ClaimsReader.GetUserIdInt(User);

            //So to check characters we just need to query for the
            //characters with this account id
            int[] characterIds = await CharacterRepository.CharacterIdsForAccountId(accountId);

#warning This is just for the test build, we need to change this
            ProjectVersionStage.AssertInternalTesting();
            if (characterIds.Length == 0)
            {
                //We just create a new one for testing.
                bool result = await CharacterRepository.TryCreateAsync(new CharacterEntryModel(accountId, ClaimsReader.GetUserName(User)))
                              .ConfigureAwait(false);

                if (result)
                {
                    //Just return the get, a character should now exist.
                    return(await GetCharacters());
                }
                else
                {
                    return(new CharacterListResponse(CharacterListResponseCode.NoCharactersFoundError));
                }
            }

            /*if(characterIds.Length == 0)
             *      return new CharacterListResponse(CharacterListResponseCode.NoCharactersFoundError);*/

            //The reason we only provide the IDs is all other character data can be looked up
            //by the client when it needs it. Like name query, visible/character details/look stuff.
            //No reason to send all this data when they may only need names. Which can be queried through the known API
            return(new CharacterListResponse(characterIds));
        }