CreateCharacterAsync([FromBody] RPGCharacterCreationRequest <TRaceType, TClassType> request, CancellationToken token = default)
        {
            //TODO: Fix GetAccountId API
            int accountId = ClaimsReader.GetAccountId <int>(User);

            if (!ModelState.IsValid)
            {
                return(Failure <RPGCharacterCreationResult, CharacterCreationResponseCode>(CharacterCreationResponseCode.GeneralError));
            }

            //TODO: Add validation pipeline.
            if (String.IsNullOrWhiteSpace(request.Name))
            {
                return(Failure <RPGCharacterCreationResult, CharacterCreationResponseCode>(CharacterCreationResponseCode.InvalidName));
            }

            try
            {
                DBRPGCharacter character = await CharacterRepository.CreateCharacterAsync(accountId, request.Name, request.Race, request.ClassType, token);

                return(Success <RPGCharacterCreationResult, CharacterCreationResponseCode>(new RPGCharacterCreationResult(character.Id)));
            }
            catch (Exception e)
            {
                if (Logger.IsEnabled(LogLevel.Error))
                {
                    Logger.LogError($"Failed to create Character: {request} Reason: {e}");
                }

                return(Failure <RPGCharacterCreationResult, CharacterCreationResponseCode>(CharacterCreationResponseCode.GeneralError));
            }
        }
        public async Task <RPGCharacterData <TRaceType, TClassType>[]> RetrieveCharactersDataAsync(CancellationToken token = default)
        {
            //TODO: Fix GetAccountId API
            int accountId = ClaimsReader.GetAccountId <int>(User);

            return((await CharacterRepository
                    .RetrieveOwnedCharactersAsync(accountId, token))
                   .Select(ConvertDbToTransit)
                   .ToArray());
        }
        public async Task <int[]> RetrieveCharacterBasicListAsync(CancellationToken token = default)
        {
            //TODO: Properly handle failure and return correct response codes.
            int accountId = ClaimsReader.GetAccountId <int>(User);

            return((await CharacterRepository
                    .RetrieveOwnedCharactersAsync(accountId, token))
                   .Select(d => d.Character.Id)
                   .OrderBy(i => i)
                   .ToArray());
        }
Пример #4
0
        public async Task <CharacterDataQueryResponseCode> CreateCharacterAppearanceAsync([FromRoute(Name = "id")] int characterId, [FromBody] RPGCharacterCustomizationData <TCustomizableSlotType, TColorStructureType, TProportionSlotType, TProportionStructureType> data, CancellationToken token = default)
        {
            if (!ModelState.IsValid)
            {
                return(CharacterDataQueryResponseCode.GeneralError);
            }

            int accountId = ClaimsReader.GetAccountId <int>(User);

            try
            {
                if (!await CharacterRepository.ContainsAsync(characterId, token))
                {
                    return(CharacterDataQueryResponseCode.CharacterDoesNotExist);
                }

                //Only allow users who own the character to create its appearance
                if (!await CharacterRepository.AccountOwnsCharacterAsync(accountId, characterId, token))
                {
                    return(CharacterDataQueryResponseCode.NotAuthorized);
                }

                //We scope the appearance persistence in a transaction because we don't want a HALF customized character.
                await using IDbContextTransaction transaction = await AppearanceRepository.CreateTransactionAsync(token);

                if (data.ProportionData.Count > 0)
                {
                    await AppearanceRepository.CreateSlotsAsync(data.ProportionData.Select(p => new DBRPGCharacterProportionSlot <TProportionSlotType, TProportionStructureType>(characterId, p.Key, p.Value)).ToArray(), token);
                }

                if (data.SlotData.Count > 0)
                {
                    await AppearanceRepository.CreateSlotsAsync(ConvertToCustomizableSlotData(characterId, data), token);
                }

                await transaction.CommitAsync(token);

                return(CharacterDataQueryResponseCode.Success);
            }
            catch (Exception e)
            {
                if (Logger.IsEnabled(LogLevel.Error))
                {
                    Logger.LogError($"Failed to create character appearance for Character: {characterId} Account: {accountId}. Reason: {e}");
                }

                return(CharacterDataQueryResponseCode.GeneralError);
            }
        }
 private int RetrieveOwnershipId(ConfigurationSourceType source)
 {
     return(source == ConfigurationSourceType.Account ? ClaimsReader.GetAccountId <int>(User) : ClaimsReader.GetSubAccountId <int>(User));
 }
Пример #6
0
        public async Task <IActionResult> CreateZoneServerAccount([FromBody][JetBrains.Annotations.NotNull] ZoneServerAccountRegistrationRequest request)
        {
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }

            //We want to log this out for information purposes whenever an auth request begins
            if (Logger.IsEnabled(LogLevel.Information))
            {
                Logger.LogInformation($"Zone Register Request by UserAccount: {ClaimsReader.GetAccountName(this.User)}:{ClaimsReader.GetAccountIdInt(this.User)} {HttpContext.Connection.RemoteIpAddress}:{HttpContext.Connection.RemotePort}");
            }

            //TODO: Create cryptographically secure random bytes for password and user.
            string zoneUserName = Guid.NewGuid().ToString();
            string zonePassword = Guid.NewGuid().ToString();

            ZoneServerApplicationUser user = new ZoneServerApplicationUser(ClaimsReader.GetAccountIdInt(this.User))
            {
                UserName = zoneUserName,
                Email    = "*****@*****.**",              //TODO: Real email???
            };

            IdentityResult identityResult = await UserManager.CreateAsync(user, zonePassword);

            if (identityResult.Succeeded)
            {
                //Adds the vrgid account owner claim.
                await UserManager.AddClaimAsync(user, new Claim(GladMMOAuthConstants.ACCOUNT_ID_OWNER_CLAIM_NAME, ClaimsReader.GetAccountId(this.User)));

                //Here we inherit each role the user account has to the zoneserver account
                foreach (var claim in User.Claims)
                {
                    if (claim.Type == "role")                                                               //TODO: Is there a constant for this??
                    {
                        if (claim.Value.ToLower() != GladMMOAuthConstants.PLAYERACCOUNT_AUTHORIZATION_ROLE) //DO NOT add the player role.
                        {
                            await UserManager.AddToRoleAsync(user, claim.Value);
                        }
                    }
                }

                //Also add the ZoneServer role
                await UserManager.AddToRoleAsync(user, GladMMOAuthConstants.ZONESERVER_AUTHORIZATION_ROLE);

                //At this point, the account has the PlayFab id claim so it's ready for use.
                return(Json(new ZoneServerAccountRegistrationResponse(user.Id, zoneUserName, zonePassword)));
            }
            else
            {
                return(BadRequest(identityResult.Errors.Aggregate("", (s, error) => $"{s} {error.Code}:{error.Description}")));
            }
        }
Пример #7
0
        public async Task <IActionResult> TryRegisterZoneServer([FromBody] ZoneServerRegistrationRequest request,
                                                                [FromServices][NotNull] IWorldDataServiceClient worldDataClient)
        {
            if (worldDataClient == null)
            {
                throw new ArgumentNullException(nameof(worldDataClient));
            }

            if (!ModelState.IsValid)
            {
                return(BuildFailedResponseModel(ZoneServerRegistrationResponseCode.GeneralServerError));
            }

            //This should really ever happen in normal circumstances.
            if (await ZoneRepository.ContainsAsync(ClaimsReader.GetAccountIdInt(User)))
            {
                if (Logger.IsEnabled(LogLevel.Warning))
                {
                    Logger.LogWarning($"UserId: {ClaimsReader.GetPlayerAccountId(User)} attempted to register ZoneId: {ClaimsReader.GetAccountId(User)} multiple times.");
                }

                return(BuildFailedResponseModel(ZoneServerRegistrationResponseCode.ZoneAlreadyRegistered));
            }

            //Check world exists
            if (!await worldDataClient.CheckWorldExistsAsync(request.WorldId))
            {
                return(BuildFailedResponseModel(ZoneServerRegistrationResponseCode.WorldRequestedNotFound));
            }

            //TODO: Should we check world rights ownership here?
            bool registerResult = await ZoneRepository.TryCreateAsync(new ZoneInstanceEntryModel(ClaimsReader.GetAccountIdInt(User), HttpContext.Connection.RemoteIpAddress.ToString(), request.NetworkPort, request.WorldId));

            if (!registerResult)
            {
                return(BuildFailedResponseModel(ZoneServerRegistrationResponseCode.GeneralServerError));
            }

            return(BuildSuccessfulResponseModel(new ZoneServerRegistrationResponse(ClaimsReader.GetAccountIdInt(User))));
        }
Пример #8
0
        public async Task <IActionResult> RegisteredZoneCheckin([FromBody] ZoneServerCheckinRequestModel zoneCheckinRequest)
        {
            //This is ok, it means the zone was unregistered before the checkin message was recieved.
            //It doesn't alone indicate a failure.
            if (!await ZoneRepository.ContainsAsync(ClaimsReader.GetAccountIdInt(User)))
            {
                if (Logger.IsEnabled(LogLevel.Warning))
                {
                    Logger.LogWarning($"UserId: {ClaimsReader.GetPlayerAccountId(User)} attempted to checkin ZoneId: {ClaimsReader.GetAccountId(User)} but the zone was not registered.");
                }

                return(Ok());
            }

            ZoneInstanceEntryModel model = await ZoneRepository.RetrieveAsync(ClaimsReader.GetAccountIdInt(User));

            //We don't REMOVE if expired. This should only update checkin time
            //something else should be responsible for removal if expired at some point.
            model.UpdateCheckinTime();
            await ZoneRepository.UpdateAsync(model.ZoneId, model);

            //We don't really have anything to reply to, this shouldn't be called externally.
            //It's basically handling a proxied message queue message.
            return(Ok());
        }
Пример #9
0
        public async Task <IActionResult> RequestContentDownloadURL(
            [FromRoute(Name = "id")] long contentId,
            [FromServices] ICustomContentRepository <TContentType> contentEntryRepository,
            [FromServices] IStorageUrlBuilder urlBuilder,
            [FromServices] IContentDownloadAuthroizationValidator downloadAuthorizer)
        {
            if (contentEntryRepository == null)
            {
                throw new ArgumentNullException(nameof(contentEntryRepository));
            }

            //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(RequestContentDownloadURL)} request from {ClaimsReader.GetAccountName(User)}:{ClaimsReader.GetAccountId(User)}.");
            }

            //TODO: We should probably check the flags of content 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 content that doesn't exist
            //Could be malicious or it could have been deleted for whatever reason
            if (!await contentEntryRepository.ContainsAsync(contentId).ConfigureAwaitFalse())
            {
                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 content
            //we do not want people downloading a content they have no business of going to
            int userId = ClaimsReader.GetAccountIdInt(User);

            //TODO: Need to NOT call it world content
            if (!await downloadAuthorizer.CanUserAccessWorldContet(userId, contentId))
            {
                return(Json(new ContentDownloadURLResponse(ContentDownloadURLResponseCode.AuthorizationFailed)));
            }

            TContentType contentEntry = await contentEntryRepository.RetrieveAsync(contentId);

            //We can get the URL from the urlbuilder if we provide the content storage GUID
            string downloadUrl = await urlBuilder.BuildRetrivalUrl(ContentType, contentEntry.StorageGuid);

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

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

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

            return(Json(new ContentDownloadURLResponse(downloadUrl, contentEntry.Version)));
        }
Пример #10
0
        private async Task <IActionResult> GenerateUploadTokenResponse(IStorageUrlBuilder urlBuilder, TContentType content)
        {
            string uploadUrl = await urlBuilder.BuildUploadUrl(ContentType, content.StorageGuid);

            if (String.IsNullOrEmpty(uploadUrl))
            {
                if (Logger.IsEnabled(LogLevel.Error))
                {
                    Logger.LogError($"Failed to create content upload URL for {ClaimsReader.GetAccountName(User)}:{ClaimsReader.GetAccountId(User)} with GUID: {content.StorageGuid}.");
                }

                return(BuildFailedResponseModel(ContentUploadResponseCode.ServiceUnavailable));
            }

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

            return(BuildSuccessfulResponseModel(new ContentUploadToken(uploadUrl, content.ContentId, content.StorageGuid)));
        }
Пример #11
0
        public async Task <IActionResult> RequestContentUploadUrl(
            [FromServices] ICustomContentRepository <TContentType> contentEntryRepository,
            [FromServices] IStorageUrlBuilder urlBuilder)
        {
            if (contentEntryRepository == null)
            {
                throw new ArgumentNullException(nameof(contentEntryRepository));
            }

            //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(RequestContentUploadUrl)} request from {ClaimsReader.GetAccountName(User)}:{ClaimsReader.GetAccountId(User)}.");
            }

            //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
            TContentType content = GenerateNewModel();
            bool         result  = await contentEntryRepository.TryCreateAsync(content);

            return(await GenerateUploadTokenResponse(urlBuilder, content));
        }
Пример #12
0
        /// <inheritdoc />
        public override async Task OnConnectedAsync()
        {
            await base.OnConnectedAsync()
            .ConfigureAwaitFalseVoid();

            if (Logger.IsEnabled(LogLevel.Information))
            {
                Logger.LogInformation($"Account Connected: {ClaimsReader.GetAccountName(Context.User)}:{ClaimsReader.GetAccountId(Context.User)} with SignalR UserId: {Context.UserIdentifier}");
            }

            try
            {
                foreach (var listener in OnConnectionHubListeners)
                {
                    HubOnConnectionState connectionState = await listener.OnConnected(this).ConfigureAwaitFalse();

                    //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.LogError($"Account: {ClaimsReader.GetAccountName(Context.User)}:{ClaimsReader.GetAccountId(Context.User)} failed to properly connect to hub. Error: {e.ToString()}\n\nStack: {e.StackTrace}");
                }

                Context.Abort();
            }
        }