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); }
/// <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(); } } }
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))); }
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)); }