Пример #1
0
        /// <inheritdoc />
        public async Task <HubOnConnectionState> OnConnected(Hub hubConnectedTo)
        {
            //TODO: Verify that the character they requested is owned by them.
            ProjectVersionStage.AssertAlpha();

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

            //We may already be able to register.
            if (await TryRegisterGuildStatus(guid, hubConnectedTo.Groups, hubConnectedTo.Context.ConnectionId).ConfigureAwait(false) == HubOnConnectionState.Success)
            {
                return(HubOnConnectionState.Success);
            }

            HubOnConnectionState state = await TryRequestCharacterGuildStatus(guid, hubConnectedTo.Context.UserIdentifier)
                                         .ConfigureAwait(false);

            if (state == HubOnConnectionState.Success)
            {
                return(await TryRegisterGuildStatus(guid, hubConnectedTo.Groups, hubConnectedTo.Context.ConnectionId)
                       .ConfigureAwait(false));
            }

            //Just error, we don't need to abort. Something didn't work right though.
            return(HubOnConnectionState.Error);
        }
        public async Task <IActionResult> GetCharacterSessionDataByAccount([FromRoute(Name = "id")] int accountId)
        {
            if (!await CharacterSessionRepository.AccountHasActiveSession(accountId)
                .ConfigureAwait(false))
            {
                return(Ok(new CharacterSessionDataResponse(CharacterSessionDataResponseCode.NoSessionAvailable)));
            }

            //TODO: There is a dangerous race condition where the zoneserver can release a session inbetween the last databse call and the characte rsessin data call
            //This is unlikely to be exploitably but it is dangerous
            ProjectVersionStage.AssertBeta();

            try
            {
                ClaimedSessionsModel claimedSessionsModel = await CharacterSessionRepository.RetrieveClaimedSessionByAccountId(accountId)
                                                            .ConfigureAwait(false);

                return(Ok(new CharacterSessionDataResponse(claimedSessionsModel.Session.ZoneId, claimedSessionsModel.CharacterId)));
            }
            catch (Exception e)
            {
                if (Logger.IsEnabled(LogLevel.Error))
                {
                    Logger.LogError($"Failed to query for character session data for active character session on AccountId: {accountId} Exception: {e.GetType().Name} - {e.Message}");
                }

                return(Ok(new CharacterSessionDataResponse(CharacterSessionDataResponseCode.GeneralServerError)));
            }
        }
Пример #3
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));
        }
Пример #4
0
        [NoResponseCache]         //we don't want to cache this, if they are removed from a guild then we want this reflected immediately or they may be taking in a guild chat they aren't apart of due to a race condition
        public async Task <IActionResult> GetCharacterMembershipGuildStatus([FromRoute(Name = "id")] int characterId, [NotNull][FromServices] IGuildCharacterMembershipRepository guildCharacterMembershipRepository)
        {
            if (guildCharacterMembershipRepository == null)
            {
                throw new ArgumentNullException(nameof(guildCharacterMembershipRepository));
            }

            //If guild membership repo doesn't have the character id as an entry then it means there is no guild associated with them.
            if (!(await guildCharacterMembershipRepository.ContainsAsync(characterId).ConfigureAwait(false)))
            {
                return(Json(new CharacterGuildMembershipStatusResponse(CharacterGuildMembershipStatusResponseCode.NoGuild)));
            }

            //TODO: There is technically a race condition here. They could have just been kicked from a guild but the cached model may say they are in a guild
            //this could result in incredibly rare cases of a kicked member joining at the perfect moment who can talk in guild chat but no longer be in the guild
            ProjectVersionStage.AssertBeta();

            //Otherwise, they are in a guild
            try
            {
                return(Json(new CharacterGuildMembershipStatusResponse((await guildCharacterMembershipRepository.RetrieveAsync(characterId).ConfigureAwait(false)).GuildId)));
            }
            catch (Exception e)
            {
                if (Logger.IsEnabled(LogLevel.Error))
                {
                    Logger.LogError($"Encountered error in expected guild membership status request. CharacterId: {characterId} Reason: {e.Message}\n\nStack: {e.StackTrace}");
                }

                return(Json(new CharacterGuildMembershipStatusResponse(CharacterGuildMembershipStatusResponseCode.GeneralServerError)));
            }
        }
Пример #5
0
        void Start()
        {
            Debug.Assert(TextObjectTransform != null, nameof(TextObjectTransform) + " != null");
            Debug.Assert(LookTransform != null, nameof(LookTransform) + " != null");

            //TODO: Camera.main is SLOW.
            ProjectVersionStage.AssertAlpha();
            LookTransform = Camera.main.transform;
        }
Пример #6
0
        /// <inheritdoc />
        public async Task Start()
        {
            ProjectVersionStage.AssertAlpha();
            //TODO: We need this? Can we add support to GladNet3 to queue up unconnected messages?
            //await Task.Delay(1500);

            //TODO: We're sending with Bearer but there is nothing validating both sides expect that.
            await PayloadSender.SendMessage(new ClientSessionClaimRequestPayload(AuthTokenRepo.RetrieveWithType(), CharacterRepository.CharacterId));
        }
        /// <inheritdoc />
        public override async Task HandleMessage(IPeerSessionMessageContext <GameServerPacketPayload> context, ClientSessionClaimRequestPayload payload)
        {
            //TODO: We need better validation/authorization for clients trying to claim a session. Right now it's open to malicious attack
            ZoneServerTryClaimSessionResponse zoneServerTryClaimSessionResponse = null;

            try
            {
                ProjectVersionStage.AssertAlpha();
                zoneServerTryClaimSessionResponse = await GameServerClient.TryClaimSession(new ZoneServerTryClaimSessionRequest(await GameServerClient.GetAccountIdFromToken(payload.JWT), payload.CharacterId))
                                                    .ConfigureAwait(false);
            }
            catch (Exception e)            //we could get an unauthorized response
            {
                Logger.Error($"Failed to Query for AccountId: {e.Message}. AuthToken provided was: {payload.JWT}");
                throw;
            }

            if (!zoneServerTryClaimSessionResponse.isSuccessful)
            {
                //TODO: Better error code
                await context.PayloadSendService.SendMessage(new ClientSessionClaimResponsePayload(ClientSessionClaimResponseCode.SessionUnavailable))
                .ConfigureAwait(false);

                return;
            }

            NetworkEntityGuidBuilder builder = new NetworkEntityGuidBuilder();

            builder
            .WithId(payload.CharacterId)
            .WithType(EntityType.Player);

            //TODO: We assume they are authenticated, we don't check at the moment but we WILL and SHOULD. Just load their location.
            ZoneServerCharacterLocationResponse locationResponse = await GameServerClient.GetCharacterLocation(payload.CharacterId)
                                                                   .ConfigureAwait(false);

            Vector3 position = locationResponse.isSuccessful ? locationResponse.Position : Vector3.zero;

            if (Logger.IsDebugEnabled)
            {
                Logger.Debug($"Recieved player location: {position}");
            }

            //Just broadcast successful claim, let listeners figure out what to do with this information.
            OnSuccessfulSessionClaimed?.Invoke(this, new PlayerSessionClaimedEventArgs(builder.Build(), position, new PlayerEntitySessionContext(context.PayloadSendService, context.Details.ConnectionId)));

            await context.PayloadSendService.SendMessage(new ClientSessionClaimResponsePayload(ClientSessionClaimResponseCode.Success))
            .ConfigureAwait(false);

            //TODO: We shouldn't hardcode this, we should send the correct scene specified by the gameserver this zone/instance connects to to service.
            await context.PayloadSendService.SendMessage(new LoadNewSceneEventPayload(PlayableGameScene.LobbyType1))
            .ConfigureAwait(false);
        }
Пример #8
0
        private async Task <string> GetSocialServiceAuthorizationToken([JetBrains.Annotations.NotNull] IAuthenticationService authService)
        {
            if (authService == null)
            {
                throw new ArgumentNullException(nameof(authService));
            }

            //TODO: Don't hardcode the authentication details
            ProjectVersionStage.AssertBeta();
            //TODO: Handle errors
            return((await authService.TryAuthenticate(new AuthenticationRequestModel("SocialService", "Test69!"))).AccessToken);
        }
        /// <inheritdoc />
        public DefaultLoadableContentResourceManager(
            [NotNull] IContentServerServiceClient contentClient,
            [NotNull] IReadonlyAuthTokenRepository authTokenRepo,
            [NotNull] ILog logger)
        {
            //TODO: We haven't implemented the refcounted cleanup. We ref count, but don't yet dispose.
            ProjectVersionStage.AssertAlpha();

            ContentClient = contentClient ?? throw new ArgumentNullException(nameof(contentClient));
            AuthTokenRepo = authTokenRepo ?? throw new ArgumentNullException(nameof(authTokenRepo));
            Logger        = logger ?? throw new ArgumentNullException(nameof(logger));

            ResourceHandleCache = new Dictionary <long, ReferenceCountedPrefabContentResourceHandle>();

            ReleaseUnmanagedResources();
        }
        //[AuthorizeJwt(GuardianApplicationRole.ZoneServer)] //TODO: Eventually we'll need to auth these zoneservers.
        public async Task <IActionResult> RegisterZoneServer([FromBody] ZoneServerRegisterationRequest registerationRequest,
                                                             [FromServices] IZoneInstanceWorkQueue instanceWorkQueue, [FromServices] IZoneServerRepository zoneRepo)
        {
            //TODO: JSON
            if (!ModelState.IsValid)
            {
                return(BadRequest());
            }

            //Conceptually, just because there is no work doesn't mean this is an error
            //Requesting users who were trying to make an instance could have abandoned that request.
            //Instances may eventually free themselves after inactivity and attempt to reregister instead of shutting down at first
            //So we just want to say "Nothing to do right now" so they can sleep and maybe manually shutdown after a timeout period.
            if (instanceWorkQueue.isEmpty)
            {
                return(NoWorkForInstanceResponse());
            }

            //The specification says this could complete immediately
            //with null if no works exists, there is technically a data race condition between checking isEmpty
            //and trying to dequeue so the result may not be predictible.
            ZoneInstanceWorkEntry zoneInstanceWorkEntry = await instanceWorkQueue.DequeueAsync()
                                                          .ConfigureAwait(false);

            //TODO: If anything here fails after dequeueing we could lose CRITICAL data to keep things running
            //We need VERY good failure handling, and to reenter this work request into the queue somehow.
            //Otherwise the request for the instance will be lost and unhandled forever.
            ProjectVersionStage.AssertAlpha();

            if (zoneInstanceWorkEntry == null)
            {
                return(NoWorkForInstanceResponse());
            }

            //TODO: Validate endpoint
            //Since there IS work to do, we can't just tell the zone instance
            //We must register it into the zone server repo
            if (!await zoneRepo.TryCreateAsync(new ZoneInstanceEntryModel(registerationRequest.ZoneServerEndpoint.EndpointAddress, (short)registerationRequest.ZoneServerEndpoint.EndpointPort, zoneInstanceWorkEntry.WorldId)))
            {
                //As stated above, we need good handling for this else
                //we will encounter MAJOR issues.
                return(NoWorkForInstanceResponse());
            }

            //Success
            return(Ok(new ZoneServerRegisterationResponse(zoneInstanceWorkEntry.WorldId)));
        }
Пример #11
0
        /// <inheritdoc />
        public bool Destroy(NetworkEntityGuid obj)
        {
            //This removes the world entity from it's special collection AND removes it from the relevant map
            GameObject rootEntityGameObject = GuidToGameObjectMappable[obj];

            GameObjectToEntityMap.ObjectToEntityMap.Remove(rootEntityGameObject);
            GameObject.Destroy(rootEntityGameObject);

            foreach (var removable in RemovableCollections)
            {
                ProjectVersionStage.AssertBeta();
                //TODO: Should we check this?
                removable.RemoveEntityEntry(obj);
            }

            return(true);
        }
        //[AuthorizeJwt(GuardianApplicationRole.ZoneServer)] //only zone servers should EVER be able to release the active session. They should also likely only be able to release an active session if it's on them.
        public async Task <IActionResult> ReleaseActiveSession([FromRoute(Name = "id")] int characterId)
        {
            //We NEED to AUTH for zoneserver JWT.
            ProjectVersionStage.AssertAlpha();

            //If an active session does NOT exist we have a BIG problem.
            if (!await CharacterSessionRepository.CharacterHasActiveSession(characterId))
            {
                if (Logger.IsEnabled(LogLevel.Error))
                {
                    Logger.LogError($"ZoneServer requested ActiveSession for Player: {characterId} be removed. Session DOES NOT EXIST. This should NOT HAPPEN.");
                }

                return(NotFound());
            }

            //We should try to remove the active sesison.
            //One this active session is revoked the character/account is free to claim any existing session
            //including the same one that was just freed.
            if (!await CharacterSessionRepository.TryDeleteClaimedSession(characterId))
            {
                if (Logger.IsEnabled(LogLevel.Error))
                {
                    Logger.LogError($"ZoneServer requested ActiveSession for Player: {characterId} be removed. Session DOES NOT EXIST. This should NOT HAPPEN.");
                }

                return(BadRequest());
            }
            else
            {
                if (Logger.IsEnabled(LogLevel.Information))
                {
                    Logger.LogInformation($"Removed ActiveSession for Player: {characterId}");
                }

                return(Ok());
            }
        }
        public async Task <IActionResult> TryClaimSession([FromBody] ZoneServerTryClaimSessionRequest request)
        {
            //TODO: Renable auth for session claiming
            ProjectVersionStage.AssertAlpha();

            if (!this.ModelState.IsValid)
            {
                return(BadRequest());                //TODO: Send JSON back too.
            }
            //TODO: We should validate a lot things. One, that the character has a session on this zoneserver.
            //We should also validate that the account owns the character. We need a new auth process for entering users.
            //We have to do this validation, somehow. Or malicious players could spoof this.
            ProjectVersionStage.AssertAlpha();

            //TODO: Verify that the zone id is correct. Right now we aren't providing it and the query doesn't enforce it.
            //We don't validate characterid/accountid association manually. It is implemented in the tryclaim SQL instead.
            //It additionally also checks the zone relation for the session so it will fail if it's invalid for the provided zone.
            //Therefore we don't need to make 3/4 database calls/queries to claim a session. Just one stored procedure call.
            //This is preferable. A result code will be used to indicate the exact error in the future. For now it just fails if it fails.
            bool sessionClaimed = await CharacterSessionRepository.TryClaimUnclaimedSession(request.PlayerAccountId, request.CharacterId);

            return(Ok(new ZoneServerTryClaimSessionResponse(sessionClaimed ? ZoneServerTryClaimSessionResponseCode.Success : ZoneServerTryClaimSessionResponseCode.GeneralServerError)));            //TODO
        }
Пример #14
0
        /// <inheritdoc />
        public string GetUserId(HubConnectionContext connection)
        {
            //TODO: This could fail if they don't put the header in
            ProjectVersionStage.AssertBeta();

            //We trust the client to send us a header that contains the character id
            //You may be freaking out, but we aren't taking the client at face value here.
            //This is and MUST be verified in the Hub's OnConnected method to prevent
            //malicious uses from spoofing.
            int characterId = connection.GetHttpContext().Request.GetTypedHeaders().Get <int>(SocialNetworkConstants.CharacterIdRequestHeaderName);

            if (characterId <= 0)
            {
                if (Logger.IsEnabled(LogLevel.Warning))
                {
                    Logger.LogWarning($"Encountered client: {ClaimsReader.GetUserName(connection.User)}:{ClaimsReader.GetUserId(connection.User)} with invalid characterId {characterId}");
                }

                connection.Abort();
                return(String.Empty);
            }

            return(characterId.ToString());
        }
Пример #15
0
 /// <inheritdoc />
 public Task <bool> CanUserAccessWorldContet(int userId, long worldId)
 {
     //TODO: When this project leaves alpha, we should probably handle the download authorization validator.
     ProjectVersionStage.AssertAlpha();
     return(Task.FromResult <bool>(true));
 }
        /// <inheritdoc />
        protected override void HandleEvent(SessionStatusChangeEventArgs args)
        {
            //The reason this system is so simple is basically the other threads just need a way to queue up
            //cleanup on the main thread. The reasoning being that GameObject destruction needs to occur
            //as well as collection modification needs to happen, and the main thread is where the majority if collection
            //iteration should be taking place.

            //it's possible that we're attempting to clean up an entity for a connection
            //that doesn't have one. This can happen if they disconnect during claim or before claim.
            if (!ConnectionToEntityMap.ContainsKey(args.Details.ConnectionId))
            {
                if (Logger.IsInfoEnabled)
                {
                    Logger.Info($"ConnectionId: {args.Details.ConnectionId} had entity exit cleanup but contained no entity. This is not an error.");
                }

                //We may be in this method, handling cleanup for an entity that has a claim going on
                //in the claim handler, but is still awaiting a response for character data from the gameserver. MEANING we could end up with hanging entites.
                //This is not mitgated here, but inside the player entery factory
                //which SHOULD make a check for the connection still being valid AFTER creation
                //Not before because we still want to create, and then deconstruct. Reasoning being that gameserver session
                //will still be claimed unless we go through th cleanup process.
                return;
            }

            NetworkEntityGuid entityGuid = ConnectionToEntityMap[args.Details.ConnectionId];

            //First we need to save the entity data since it DOES own an entity.
            UnityExtended.UnityMainThreadContext.PostAsync(async() =>
            {
                //Nothing should really be taking a write lock, except session entry I guess.
                //This could be bad
                //We no longer do a read lock because Player Entry is actually trying to
                //aquire a write lock and this could block if for a LONG time. KILLING the server basically
                //So, we just assume it's safe to save entity data. Even if it's changing mid save like this.
                //using(await LockingPolicy.ReaderLockAsync(null, CancellationToken.None))
                {
                    //We know that an entity exists, so we must save it. Before we even queue it up for removal.
                    await EntitySaver.SaveAsync(entityGuid)
                    .ConfigureAwait(true);
                }

                //At this point we MUST write lock, since we are actually modifying entity collections and entries
                using (await LockingPolicy.WriterLockAsync(null, CancellationToken.None))                //we can use async await since we're in a async context too!! Which is good.
                {
                    SessionDestructor.Destroy(new PlayerSessionDeconstructionContext(args.Details.ConnectionId));
                }

                //TODO: We have a big problem if this fails, we need to handle it properly. Otherwise the player cannot log in again.\
                ProjectVersionStage.AssertBeta();

                //We need to async send the release request, a very important part of session cleanup.
                //if this failes we have BIG problems. BIG BIG BIG.
                await ZoneClientGameService.ReleaseActiveSession(entityGuid.EntityId)
                .ConfigureAwait(false);

                if (Logger.IsInfoEnabled)
                {
                    Logger.Info($"Cleaned up Entity Player Session for ConnectionId: {args.Details.ConnectionId} Guid: {entityGuid} {entityGuid.EntityType}:{entityGuid.EntityId}");
                }
            });
        }
Пример #17
0
        //TODO: Refactor this behind its own object to provide download URL for character.
        /// <inheritdoc />
        public async Task OnGameInitialized()
        {
            if (Logger.IsInfoEnabled)
            {
                Logger.Info("About to start downloading map data.");
            }

            //When we start the loading screen for the game
            //To know what world we should load we should
            CharacterSessionDataResponse characterSessionData = await CharacterService.GetCharacterSessionData(LocalCharacterData.CharacterId, AuthTokenRepo.RetrieveWithType())
                                                                .ConfigureAwait(false);

            if (!characterSessionData.isSuccessful)
            {
                Logger.Error($"Failed to query Character Session Data: {characterSessionData.ResultCode}:{(int)characterSessionData.ResultCode}");
                return;
            }

            //TODO: Handle failure
            ProjectVersionStage.AssertAlpha();
            //TODO: Handle throwing/error
            //We need to know the world the zone is it, so we can request a download URL for it.
            long worldId = await ZoneDataService.GetZoneWorld(characterSessionData.ZoneId)
                           .ConfigureAwait(false);

            //With the worldid we can get the download URL.
            ContentDownloadURLResponse urlDownloadResponse = await ContentService.RequestWorldDownloadUrl(worldId, AuthTokenRepo.RetrieveWithType())
                                                             .ConfigureAwait(false);

            //TODO: Handle failure
            if (urlDownloadResponse.isSuccessful)
            {
                if (Logger.IsInfoEnabled)
                {
                    Logger.Info($"Download URL: {urlDownloadResponse.DownloadURL}");
                }

                //Can't do web request not on the main thread, sadly.
                await new UnityYieldAwaitable();

                //TODO: Do we need to be on the main unity3d thread
                UnityWebRequestAsyncOperation asyncOperation = UnityWebRequestAssetBundle.GetAssetBundle(urlDownloadResponse.DownloadURL, 0).SendWebRequest();

                //TODO: We should render these operations to the loading screen UI.
                asyncOperation.completed += operation =>
                {
                    AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(asyncOperation.webRequest);

                    string[] paths = bundle.GetAllScenePaths();

                    foreach (string p in paths)
                    {
                        Debug.Log($"Found Scene in Bundle: {p}");
                    }

                    AsyncOperation sceneAsync = SceneManager.LoadSceneAsync(System.IO.Path.GetFileNameWithoutExtension(paths.First()));

                    sceneAsync.completed += operation1 =>
                    {
                        //When the scene is finished loading we should cleanup the asset bundle
                        //Don't clean up the WHOLE BUNDLE, just the compressed downloaded data
                        bundle.Unload(false);

                        //TODO: We need a way/system to reference the bundle later so it can be cleaned up inbetween scene loads.
                    };

                    sceneAsync.allowSceneActivation = true;
                };
            }
        }
 static GlobalEntityResourceLockingPolicy()
 {
     //TODO: There is kinda data race here, we need to a global for this collection too to prevent it being modidified before the lock occurs inbetween lookup
     ProjectVersionStage.AssertBeta();
 }