示例#1
0
        public TerminalDO RegisterOrUpdate(TerminalDO terminalDo, bool isUserInitiated)
        {
            if (terminalDo == null)
            {
                return(null);
            }

            if (!IsATandTCacheDisabled)
            {
                Initialize();
            }

            // we are going to change activityTemplateDo. It is not good to corrupt method's input parameters.
            // make a copy
            var clone = new TerminalDO();

            CopyPropertiesHelper.CopyProperties(terminalDo, clone, true);

            terminalDo = clone;

            lock (_terminals)
            {
                var        doRegisterTerminal = false;
                TerminalDO terminal, existingTerminal;
                using (var uow = ObjectFactory.GetInstance <IUnitOfWork>())
                {
                    if (terminalDo.Id == Guid.Empty)
                    {
                        terminalDo.Id = Guid.NewGuid();
                        uow.TerminalRepository.Add(terminalDo);
                        doRegisterTerminal = true;
                        existingTerminal   = terminalDo;
                    }
                    else
                    {
                        existingTerminal = uow.TerminalRepository.FindOne(x => x.Id == terminalDo.Id);
                        // this is for updating terminal
                        CopyPropertiesHelper.CopyProperties(terminalDo, existingTerminal, false, x => x.Name != "Id");
                    }

                    uow.SaveChanges();

                    terminal = Clone(existingTerminal);
                    _terminals[existingTerminal.Id] = terminal;
                }

                if (doRegisterTerminal)
                {
                    if (isUserInitiated)
                    {
                        //add ownership for this new terminal to current user
                        _securityServices.SetDefaultRecordBasedSecurityForObject(Roles.OwnerOfCurrentObject, terminal.Id, nameof(TerminalDO), new List <PermissionType>()
                        {
                            PermissionType.UseTerminal
                        });
                    }

                    //make it visible for Fr8 Admins
                    _securityServices.SetDefaultRecordBasedSecurityForObject(Roles.Admin, terminal.Id, nameof(TerminalDO), new List <PermissionType>()
                    {
                        PermissionType.UseTerminal
                    });
                }

                return(terminal);
            }
        }
 public Dictionary <string, string> GetRequestHeaders(TerminalDO terminal, string userId)
 {
     return(new Dictionary <string, string>());
 }
 public TerminalDO RegisterOrUpdate(TerminalDO terminalDo, bool isUserInitiated)
 {
     return(_terminal.RegisterOrUpdate(terminalDo, isUserInitiated));
 }
        public async Task <AuthenticateResponse> VerifyCodeAndGetAccessToken(
            Fr8AccountDO account,
            TerminalDO terminal,
            string phoneNumber,
            string verificationCode,
            string clientId,
            string clientName)
        {
            if (terminal.AuthenticationType == AuthenticationType.None)
            {
                throw new WrongAuthenticationTypeException();
            }

            var restClient = ObjectFactory.GetInstance <IRestfulServiceClient>();

            var credentialsDTO = new PhoneNumberCredentialsDTO()
            {
                PhoneNumber      = phoneNumber,
                ClientId         = clientId,
                VerificationCode = verificationCode,
                Fr8UserId        = account?.Id,
                ClientName       = clientName
            };

            var terminalResponse = await restClient.PostAsync(new Uri(terminal.Endpoint + "/authentication/token"), credentialsDTO);

            var terminalResponseAuthTokenDTO = JsonConvert.DeserializeObject <AuthorizationTokenDTO>(terminalResponse);

            if (!string.IsNullOrEmpty(terminalResponseAuthTokenDTO.Error))
            {
                return(new AuthenticateResponse()
                {
                    Error = terminalResponseAuthTokenDTO.Error
                });
            }

            if (terminalResponseAuthTokenDTO == null)
            {
                return(new AuthenticateResponse()
                {
                    Error = "An error occured while authenticating, please try again."
                });
            }

            var curTerminal = _terminalService.GetByKey(terminal.Id);

            using (var uow = ObjectFactory.GetInstance <IUnitOfWork>())
            {
                var curAccount = uow.UserRepository.GetByKey(account.Id);

                AuthorizationTokenDO authToken = null;
                if (!string.IsNullOrEmpty(terminalResponseAuthTokenDTO.ExternalAccountId))
                {
                    authToken = uow.AuthorizationTokenRepository
                                .GetPublicDataQuery()
                                .FirstOrDefault(x => x.TerminalID == curTerminal.Id &&
                                                x.UserID == curAccount.Id &&
                                                x.ExternalDomainId == terminalResponseAuthTokenDTO.ExternalDomainId &&
                                                x.ExternalAccountId == terminalResponseAuthTokenDTO.ExternalAccountId &&
                                                x.AdditionalAttributes == terminalResponseAuthTokenDTO.AdditionalAttributes);
                }
                var created = false;
                if (authToken == null)
                {
                    authToken = new AuthorizationTokenDO()
                    {
                        Token                = terminalResponseAuthTokenDTO.Token,
                        ExternalAccountId    = terminalResponseAuthTokenDTO.ExternalAccountId,
                        ExternalAccountName  = string.IsNullOrEmpty(terminalResponseAuthTokenDTO.ExternalAccountName) ? terminalResponseAuthTokenDTO.ExternalAccountId : terminalResponseAuthTokenDTO.ExternalAccountName,
                        ExternalDomainId     = terminalResponseAuthTokenDTO.ExternalDomainId,
                        ExternalDomainName   = string.IsNullOrEmpty(terminalResponseAuthTokenDTO.ExternalDomainName) ? terminalResponseAuthTokenDTO.ExternalDomainId : terminalResponseAuthTokenDTO.ExternalDomainName,
                        TerminalID           = curTerminal.Id,
                        UserID               = curAccount.Id,
                        AdditionalAttributes = terminalResponseAuthTokenDTO.AdditionalAttributes,
                    };

                    uow.AuthorizationTokenRepository.Add(authToken);
                    created = true;
                }
                else
                {
                    authToken.Token               = terminalResponseAuthTokenDTO.Token;
                    authToken.ExternalAccountId   = terminalResponseAuthTokenDTO.ExternalAccountId;
                    authToken.ExternalAccountName = string.IsNullOrEmpty(terminalResponseAuthTokenDTO.ExternalAccountName) ? terminalResponseAuthTokenDTO.ExternalDomainId : terminalResponseAuthTokenDTO.ExternalAccountName;
                    authToken.ExternalDomainId    = terminalResponseAuthTokenDTO.ExternalDomainId;
                    authToken.ExternalDomainName  = string.IsNullOrEmpty(terminalResponseAuthTokenDTO.ExternalDomainName) ? terminalResponseAuthTokenDTO.ExternalDomainId : terminalResponseAuthTokenDTO.ExternalDomainName;
                }

                uow.SaveChanges();

                if (created)
                {
                    EventManager.AuthTokenCreated(authToken);
                }

                //if terminal requires Authentication Completed Notification, follow the existing terminal event notification protocol
                //to notify the terminal about authentication completed event
                if (terminalResponseAuthTokenDTO.AuthCompletedNotificationRequired)
                {
                    //let's save id of DTO before informing related terminal
                    terminalResponseAuthTokenDTO.Id = authToken.Id.ToString();
                    EventManager.TerminalAuthenticationCompleted(curAccount.Id, curTerminal, terminalResponseAuthTokenDTO);
                }

                return(new AuthenticateResponse()
                {
                    AuthorizationToken = Mapper.Map <AuthorizationTokenDTO>(authToken),
                    Error = null
                });
            }
        }
        public async Task <PhoneNumberCredentialsDTO> SendAuthenticationCodeToMobilePhone(Fr8AccountDO account, TerminalDO terminal, string phoneNumber)
        {
            if (terminal.AuthenticationType == AuthenticationType.None)
            {
                throw new WrongAuthenticationTypeException();
            }

            if (terminal.AuthenticationType != AuthenticationType.PhoneNumberWithCode)
            {
                throw new WrongAuthenticationTypeException("Terminal support only authentication through phone number");
            }

            var restClient = ObjectFactory.GetInstance <IRestfulServiceClient>();

            var credentialsDTO = new PhoneNumberCredentialsDTO
            {
                PhoneNumber = phoneNumber,
                ClientName  = account != null ? account.UserName : "******",
                Fr8UserId   = account?.Id
            };

            var terminalResponse = await restClient.PostAsync(new Uri(terminal.Endpoint + "/authentication/send_code"), credentialsDTO);

            //response provides terminal
            var terminalResponseContent = JsonConvert.DeserializeObject <PhoneNumberCredentialsDTO>(terminalResponse);

            return(terminalResponseContent);
        }
        public async Task <AuthenticateResponse> GetOAuthToken(TerminalDO terminal, ExternalAuthenticationDTO externalAuthDTO)
        {
            var hasAuthentication = _activityTemplate.GetQuery().Any(x => x.Terminal.Id == terminal.Id);

            if (!hasAuthentication)
            {
                throw new WrongAuthenticationTypeException();
            }

            var restClient = ObjectFactory.GetInstance <IRestfulServiceClient>();
            var response   = await restClient.PostAsync(
                new Uri(terminal.Endpoint + "/authentication/token"),
                externalAuthDTO, null, _terminalService.GetRequestHeaders(terminal, null));

            var authTokenDTO = JsonConvert.DeserializeObject <AuthorizationTokenDTO>(response);

            if (!string.IsNullOrEmpty(authTokenDTO.Error))
            {
                return(new AuthenticateResponse
                {
                    Error = authTokenDTO.Error
                });
            }

            using (var uow = ObjectFactory.GetInstance <IUnitOfWork>())
            {
                var authTokenByExternalState = uow.AuthorizationTokenRepository
                                               .FindTokenByExternalState(authTokenDTO.ExternalStateToken, terminal.Id);

                if (authTokenByExternalState == null)
                {
                    throw new MissingObjectException($"Authorization token with external state '{authTokenDTO.ExternalStateToken}' doesn't exist");
                }

                var authTokenByExternalAccountId = uow.AuthorizationTokenRepository
                                                   .FindTokenByExternalAccount(
                    authTokenDTO.ExternalAccountId,
                    terminal.Id,
                    authTokenByExternalState.UserID);

                if (authTokenByExternalAccountId != null)
                {
                    authTokenByExternalAccountId.Token              = authTokenDTO.Token;
                    authTokenByExternalState.ExternalAccountId      = authTokenDTO.ExternalAccountId;
                    authTokenByExternalState.ExternalAccountName    = string.IsNullOrEmpty(authTokenDTO.ExternalAccountName) ? authTokenDTO.ExternalAccountId : authTokenDTO.ExternalAccountName;
                    authTokenByExternalState.ExternalDomainId       = authTokenDTO.ExternalDomainId;
                    authTokenByExternalState.ExternalDomainName     = string.IsNullOrEmpty(authTokenDTO.ExternalDomainName) ? authTokenDTO.ExternalDomainId : authTokenDTO.ExternalDomainName;
                    authTokenByExternalAccountId.ExternalStateToken = null;
                    authTokenByExternalState.AdditionalAttributes   = authTokenDTO.AdditionalAttributes;
                    authTokenByExternalState.ExpiresAt              = authTokenDTO.ExpiresAt;
                    if (authTokenByExternalState != null)
                    {
                        uow.AuthorizationTokenRepository.Remove(authTokenByExternalState);
                    }

                    EventManager.AuthTokenCreated(authTokenByExternalAccountId);
                }
                else
                {
                    authTokenByExternalState.Token                = authTokenDTO.Token;
                    authTokenByExternalState.ExternalAccountId    = authTokenDTO.ExternalAccountId;
                    authTokenByExternalState.ExternalAccountName  = string.IsNullOrEmpty(authTokenDTO.ExternalAccountName) ? authTokenDTO.ExternalAccountId : authTokenDTO.ExternalAccountName;
                    authTokenByExternalState.ExternalDomainId     = authTokenDTO.ExternalDomainId;
                    authTokenByExternalState.ExternalDomainName   = string.IsNullOrEmpty(authTokenDTO.ExternalDomainName) ? authTokenDTO.ExternalDomainId : authTokenDTO.ExternalDomainName;
                    authTokenByExternalState.ExternalStateToken   = null;
                    authTokenByExternalState.AdditionalAttributes = authTokenDTO.AdditionalAttributes;
                    authTokenByExternalState.ExpiresAt            = authTokenDTO.ExpiresAt;

                    EventManager.AuthTokenCreated(authTokenByExternalState);
                }

                uow.SaveChanges();

                return(new AuthenticateResponse
                {
                    AuthorizationToken = Mapper.Map <AuthorizationTokenDTO>(authTokenByExternalAccountId ?? authTokenByExternalState),
                    Error = null
                });
            }
        }
        public async Task <AuthenticateResponse> AuthenticateInternal(
            Fr8AccountDO account,
            TerminalDO terminal,
            string domain,
            string username,
            string password,
            bool isDemoAccount = false)
        {
            if (terminal.AuthenticationType == AuthenticationType.None)
            {
                throw new WrongAuthenticationTypeException();
            }
            var restClient = ObjectFactory.GetInstance <IRestfulServiceClient>();

            var credentialsDTO = new CredentialsDTO
            {
                Domain        = domain,
                Username      = username,
                Password      = password,
                IsDemoAccount = isDemoAccount,
                Fr8UserId     = account?.Id
            };

            var terminalResponse = await restClient.PostAsync(
                new Uri(terminal.Endpoint + "/authentication/token"),
                credentialsDTO,
                null,
                _terminalService.GetRequestHeaders(terminal, account?.Id));

            var terminalResponseAuthTokenDTO = JsonConvert.DeserializeObject <AuthorizationTokenDTO>(terminalResponse);

            if (!string.IsNullOrEmpty(terminalResponseAuthTokenDTO.Error))
            {
                return(new AuthenticateResponse
                {
                    Error = terminalResponseAuthTokenDTO.Error
                });
            }

            var curTerminal = _terminalService.GetByKey(terminal.Id);

            using (var uow = ObjectFactory.GetInstance <IUnitOfWork>())
            {
                var curAccount = uow.UserRepository.GetByKey(account.Id);

                AuthorizationTokenDO authToken = null;
                if (!string.IsNullOrEmpty(terminalResponseAuthTokenDTO.ExternalAccountId))
                {
                    authToken = uow.AuthorizationTokenRepository
                                .GetPublicDataQuery()
                                .FirstOrDefault(x => x.TerminalID == curTerminal.Id &&
                                                x.UserID == curAccount.Id &&
                                                x.ExternalDomainId == terminalResponseAuthTokenDTO.ExternalDomainId &&
                                                x.ExternalAccountId == terminalResponseAuthTokenDTO.ExternalAccountId &&
                                                x.AdditionalAttributes == terminalResponseAuthTokenDTO.AdditionalAttributes
                                                );
                }


                var created = false;
                if (authToken == null)
                {
                    authToken = new AuthorizationTokenDO
                    {
                        Token                = terminalResponseAuthTokenDTO.Token,
                        ExternalAccountId    = terminalResponseAuthTokenDTO.ExternalAccountId,
                        ExternalAccountName  = string.IsNullOrEmpty(terminalResponseAuthTokenDTO.ExternalAccountName) ? terminalResponseAuthTokenDTO.ExternalAccountId : terminalResponseAuthTokenDTO.ExternalAccountName,
                        ExternalDomainId     = terminalResponseAuthTokenDTO.ExternalDomainId,
                        ExternalDomainName   = string.IsNullOrEmpty(terminalResponseAuthTokenDTO.ExternalDomainName) ? terminalResponseAuthTokenDTO.ExternalDomainId : terminalResponseAuthTokenDTO.ExternalDomainName,
                        TerminalID           = curTerminal.Id,
                        UserID               = curAccount.Id,
                        AdditionalAttributes = terminalResponseAuthTokenDTO.AdditionalAttributes,
                        ExpiresAt            = terminalResponseAuthTokenDTO.ExpiresAt
                    };

                    uow.AuthorizationTokenRepository.Add(authToken);
                    created = true;
                }
                else
                {
                    authToken.Token               = terminalResponseAuthTokenDTO.Token;
                    authToken.ExternalAccountId   = terminalResponseAuthTokenDTO.ExternalAccountId;
                    authToken.ExternalAccountName = string.IsNullOrEmpty(terminalResponseAuthTokenDTO.ExternalAccountName) ? terminalResponseAuthTokenDTO.ExternalDomainId : terminalResponseAuthTokenDTO.ExternalAccountName;
                    authToken.ExternalDomainId    = terminalResponseAuthTokenDTO.ExternalDomainId;
                    authToken.ExternalDomainName  = string.IsNullOrEmpty(terminalResponseAuthTokenDTO.ExternalDomainName) ? terminalResponseAuthTokenDTO.ExternalDomainId : terminalResponseAuthTokenDTO.ExternalDomainName;
                }

                uow.SaveChanges();

                if (created)
                {
                    EventManager.AuthTokenCreated(authToken);
                }

                //if terminal requires Authentication Completed Notification, follow the existing terminal event notification protocol
                //to notify the terminal about authentication completed event
                if (terminalResponseAuthTokenDTO.AuthCompletedNotificationRequired)
                {
                    //let's save id of DTO before informing related terminal
                    terminalResponseAuthTokenDTO.Id = authToken.Id.ToString();
                    EventManager.TerminalAuthenticationCompleted(curAccount.Id, curTerminal, terminalResponseAuthTokenDTO);
                }

                return(new AuthenticateResponse
                {
                    AuthorizationToken = Mapper.Map <AuthorizationTokenDTO>(authToken),
                    Error = null
                });
            }
        }
示例#8
0
        public async Task <ActionResult> ProcessSuccessfulOAuthResponse(
            string terminalName,
            string terminalVersion,
            string state = null)
        {
            //Here state is optional and is designed to pass auth token external state (to identify token in database) in case 3rd party service doesn't return unknown parameters contained in URL back
            if (string.IsNullOrEmpty(terminalName) || string.IsNullOrEmpty(terminalVersion))
            {
                throw new ApplicationException("TerminalName or TerminalVersion is not specified.");
            }

            var requestQueryString = Request.Url.Query;

            if (!string.IsNullOrEmpty(requestQueryString) && requestQueryString[0] == '?')
            {
                requestQueryString = requestQueryString.Substring(1);
                var queryDictionary = System.Web.HttpUtility.ParseQueryString(requestQueryString);
                if (queryDictionary["error"] != null && queryDictionary["error"] == "access_denied")
                {
                    //user has denied access for our app, so close the window
                    dynamic model = new ExpandoObject();
                    model.AuthorizationTokenId = string.Empty;
                    model.TerminalId           = 0;
                    model.TerminalName         = string.Empty;

                    return(View(model));
                }
                if (!string.IsNullOrEmpty(state) && queryDictionary["state"] == null)
                {
                    requestQueryString = $"{requestQueryString}&state={state}";
                }
            }

            TerminalDO terminal = _terminal.GetAll().FirstOrDefault(x => x.Name == terminalName && x.Version == terminalVersion);

            if (terminal == null)
            {
                throw new ApplicationException("Could not find terminal.");
            }
            string userId = null;

            // It is unlikely that ASP cookies/headers/other stuff that is used to track current session will be send within auth callback from external service
            // We need a smarter approach

            /*using (var uow = ObjectFactory.GetInstance<IUnitOfWork>())
             * {
             *  userId = _security.GetCurrentAccount(uow).Id;
             * }*/

            var externalAuthenticationDTO = new ExternalAuthenticationDTO()
            {
                RequestQueryString = requestQueryString,
                Fr8UserId          = userId
            };

            var response = await _authorization.GetOAuthToken(terminal, externalAuthenticationDTO);

            if (string.IsNullOrEmpty(response.Error))
            {
                dynamic model = new ExpandoObject();
                model.AuthorizationTokenId = response.AuthorizationToken.Id;
                model.TerminalId           = response.AuthorizationToken.TerminalID;
                model.TerminalName         = terminal.Name;

                if (response.AuthorizationToken.ExternalAccountId == "*****@*****.**")
                {
                    EventManager.TerminalAuthenticationCompleted(response.AuthorizationToken.UserId, terminal, response.AuthorizationToken);
                }
                return(View(model));
            }
            else
            {
                EventManager.OAuthAuthenticationFailed(requestQueryString, response.Error);
                return(View("Error", new AuthenticationErrorVM()
                {
                    Error = response.Error
                }));
            }
        }
        public async Task SaveOrRegister(TerminalDTO terminal)
        {
            string curEndpoint = string.Empty;

            // Get the value of TerminalDTO.ParticipationState which we can trust
            int safeParticipationState;

            if (UserHasTerminalAdministratorPermission())
            {
                // We can trust Fr8 administrator (and he/she can change ParticipationState) so just get it from DTO
                safeParticipationState = terminal.ParticipationState;
            }
            else
            {
                if (terminal.InternalId == Guid.Empty)
                {
                    // For new terminals the value is 0 (Unapproved)
                    safeParticipationState = ParticipationState.Unapproved;
                }
                else
                {
                    // We cannot trust user so get the value from the DB
                    using (var uow = ObjectFactory.GetInstance <IUnitOfWork>())
                    {
                        if (_securityService.AuthorizeActivity(PermissionType.UseTerminal, terminal.InternalId, nameof(TerminalDO)))
                        {
                            var terminalTempDo = uow.TerminalRepository.GetByKey(terminal.InternalId);
                            if (terminalTempDo == null)
                            {
                                throw new Fr8NotFoundException(nameof(terminal.InternalId), $"Terminal with the id {terminal.InternalId} is not found.");
                            }
                            safeParticipationState = terminalTempDo.ParticipationState;
                        }
                        else
                        {
                            throw new HttpException(403, $"You are not authorized to use Terminal {terminal.Name}!");
                        }
                    }
                }
            }
            terminal.ParticipationState = safeParticipationState;

            // Validate data
            if (terminal.ParticipationState == ParticipationState.Approved)
            {
                if (string.IsNullOrWhiteSpace(terminal.ProdUrl))
                {
                    throw new Fr8ArgumentNullException(nameof(terminal.ProdUrl), "Production endpoint must be specified for the terminals in the Approved state.");
                }
                curEndpoint = NormalizeUrl(terminal.ProdUrl);
            }
            else
            {
                if (string.IsNullOrWhiteSpace(terminal.DevUrl))
                {
                    throw new Fr8ArgumentNullException(nameof(terminal.DevUrl), "Development endpoint must be specified for the terminals in the Unapproved, Blocked or Deleted state.");
                }
                curEndpoint = NormalizeUrl(terminal.DevUrl);
            }

#if DEBUG
            // Use local URL for Fr8 own terminals when in the local environment or during FBB tests.
            if (terminal.IsFr8OwnTerminal)
            {
                curEndpoint = NormalizeUrl(terminal.DevUrl);
            }
#endif

            if (curEndpoint.Contains("/discover", StringComparison.OrdinalIgnoreCase))
            {
                throw new Fr8ArgumentException(nameof(terminal.Endpoint), "Invalid terminal URL", "Terminal URL should not contain 'discover'. Please correct the URL and try again.");
            }

            if (!UserHasTerminalAdministratorPermission())
            {
                // Developer cannot add terminals with the "localhost" endpoint,
                // or set it while editing, or assign a terminal the Fr8OwnTerminal flag,
                // or edit the terminal with such a flag.
                // User must be an administrator to add or edit a Fr8 own terminal.
                if ((new Uri(curEndpoint).Host == "localhost"))
                {
                    throw new Fr8InsifficientPermissionsException("Insufficient permissions to add a 'localhost' endpoint.",
                                                                  "Terminal URL cannot contain the string 'localhost'. Please correct your terminal URL and try again.");
                }

                if (terminal.IsFr8OwnTerminal)
                {
                    throw new Fr8InsifficientPermissionsException("Insufficient permissions to manage a Fr8Own terminal.",
                                                                  "Terminal URL cannot contain the string 'localhost'. Please correct your terminal URL and try again.");
                }
            }

            // Validating discovery response
            if (terminal.ParticipationState == ParticipationState.Approved ||
                terminal.ParticipationState == ParticipationState.Unapproved)
            {
                if (!curEndpoint.Contains("://localhost"))
                {
                    string errorMessage = "Terminal at the specified URL did not return a valid response to the discovery request.";
                    try
                    {
                        var terminalRegistrationInfo = await SendDiscoveryRequest(curEndpoint, null);

                        if (terminalRegistrationInfo == null)
                        {
                            Logger.Info($"Terminal at '{curEndpoint}' returned an invalid response.");
                            throw new Fr8ArgumentException(nameof(terminal.Endpoint), errorMessage, errorMessage);
                        }
                        if (string.IsNullOrEmpty(terminalRegistrationInfo.Definition.Name))
                        {
                            string validationErrorMessage = $"Validation of terminal at '{curEndpoint}' failed: Terminal Name is empty.";
                            Logger.Info(validationErrorMessage);
                            throw new Fr8ArgumentException(nameof(terminal.Endpoint), validationErrorMessage, validationErrorMessage);
                        }
                    }
                    catch (TaskCanceledException ex)
                    {
                        string errorMessase = $"Terminal at '{curEndpoint}' did not respond to a /discovery request within 10 sec.";
                        Logger.Info(errorMessase);
                        throw new Fr8ArgumentException(nameof(terminal.Endpoint), errorMessase, "The terminal did not respond to a discovery request within 10 seconds.");
                    }
                    catch (Exception ex)
                    {
                        Logger.Info($"Terminal at '{curEndpoint}' returned an invalid response.");
                        throw new Fr8ArgumentException(nameof(terminal.Endpoint), ex.ToString(), errorMessage);
                    }
                }
            }


            var terminalDo = new TerminalDO();
            terminalDo.Endpoint = terminal.Endpoint = curEndpoint;

            //Check whether we save an existing terminal or register a new one
            if (terminal.InternalId == Guid.Empty)
            {
                Logger.Info($"Registration of terminal at '{curEndpoint}' is requested. ");

                // New terminal
                if (IsExistingTerminal(curEndpoint))
                {
                    Logger.Error($"Terminal with endpoint '{curEndpoint}' was already registered");
                    throw new Fr8ConflictException(nameof(TerminalDO), nameof(TerminalDO.Endpoint), curEndpoint);
                }

                terminalDo.TerminalStatus = TerminalStatus.Undiscovered;

                // The 'Endpoint' property contains the currently active endpoint which may be changed
                // by deployment scripts or by promoting the terminal from Dev to Production
                // while ProdUrl/DevUrl contains  whatever user or administrator have supplied.

                // Set properties which can be safely set by any user
                terminalDo.DevUrl = terminal.DevUrl;

                if (UserHasTerminalAdministratorPermission())
                {
                    // Set properties which can only be set by Administrator
                    terminalDo.ParticipationState = terminal.ParticipationState;
                    terminalDo.IsFr8OwnTerminal   = terminal.IsFr8OwnTerminal;
                    terminalDo.ProdUrl            = terminal.ProdUrl;
                }
                else
                {
                    // If a Developer adds a terminal, it has to be approved by Fr8 Administrator
                    terminalDo.ParticipationState = ParticipationState.Unapproved;
                }

                terminalDo.UserId = Thread.CurrentPrincipal.Identity.GetUserId();
            }
            else
            {
                // An existing terminal
                terminalDo.Id     = terminal.InternalId;
                terminalDo.DevUrl = terminal.DevUrl;

                //Administrator can update production URL and ParticipationState
                if (UserHasTerminalAdministratorPermission())
                {
                    terminalDo.ProdUrl            = terminal.ProdUrl;
                    terminalDo.ParticipationState = terminal.ParticipationState;
                    terminalDo.IsFr8OwnTerminal   = terminal.IsFr8OwnTerminal;
                }
            }

            if (terminal.InternalId == Guid.Empty)
            {
                Logger.Info($"Proceeding to registering a new terminal: " + JsonConvert.SerializeObject(terminalDo));
            }
            else
            {
                Logger.Info($"Proceeding to update of an existing terminal: " + JsonConvert.SerializeObject(terminalDo));
            }

            terminalDo = _terminal.RegisterOrUpdate(terminalDo, true);

            Logger.Info($"Terminal at '{terminalDo.Endpoint}' (id: {terminalDo.Id}) was successfully saved.");

            if (terminalDo.ParticipationState == ParticipationState.Approved ||
                terminalDo.ParticipationState == ParticipationState.Unapproved)
            {
                bool discoveryResult = (await DiscoverInternal(terminalDo, true)).IsSucceed;
                if (!discoveryResult)
                {
                    Logger.Info($"The terminal at {curEndpoint} has been registered but an error has occurred while carrying out discovery.");
                }
            }
        }
        //private async Task<StandardFr8TerminalCM> SendDiscoveryRequest(string terminalUrl, Dictionary<string, string> headers = null)
        //{
        //    // Use a custom HttpClient to query the terminal with a low timeout (10 sec)
        //    var innerClient = new HttpClient();
        //    innerClient.Timeout = new TimeSpan(0, 0, 10);
        //    try
        //    {
        //        var response = await innerClient.GetAsync(new Uri(terminalUrl + "/discover", UriKind.Absolute));
        //        response.EnsureSuccessStatusCode();
        //        var responseContent = await response.Content.ReadAsStringAsync();
        //        return JsonConvert.DeserializeObject<StandardFr8TerminalCM>(responseContent);
        //    }
        //    catch (Exception) // Expect an exception if the request failed
        //    {
        //        throw;
        //    }
        //}

        private async Task <DiscoveryResult> DiscoverInternal(TerminalDO terminalDo, bool isUserInitiated)
        {
            var terminalUrl = terminalDo.Endpoint;

            Logger.Info($"Starting discovering terminal at '{terminalUrl}'. Reporting about self as the Hub at '{_serverUrl}'.");


            string secret = null;

            if (terminalDo.ParticipationState == ParticipationState.Blocked)
            {
                var message = $"Discovery for terminal '{terminalUrl}' will not happen because the terminal is blocked.";

                Logger.Info(message);
                return(DiscoveryResult.Error(message));
            }

            if (terminalDo.ParticipationState == ParticipationState.Deleted)
            {
                var message = $"Discovery for terminal '{terminalUrl}' will not happen because the terminal is deleted.";
                Logger.Info(message);
                return(DiscoveryResult.Error(message));
            }

            if (!string.IsNullOrWhiteSpace(terminalDo?.Secret))
            {
                secret = terminalDo.Secret;
            }

            if (secret == null)
            {
                Logger.Info($"Generating new secret for terminal at '{terminalUrl}'");
                secret = Guid.NewGuid().ToString("N");
            }

            var headers = new Dictionary <string, string>
            {
                { "Fr8HubCallbackSecret", secret },
                { "Fr8HubCallBackUrl", _serverUrl }
            };

            StandardFr8TerminalCM terminalRegistrationInfo = null;

            try
            {
                terminalRegistrationInfo = await SendDiscoveryRequest(terminalUrl, headers);
            }
            catch (Exception ex)
            {
                Logger.Warn("Failed to call terminal discovery endpoint", ex);

                _eventReporter.ActivityTemplateTerminalRegistrationError($"Failed terminal service: {terminalUrl}. Error Message: {ex.Message} ", ex.GetType().Name);
                terminalRegistrationInfo = null;
            }

            if (terminalRegistrationInfo == null || terminalRegistrationInfo.Definition == null || terminalRegistrationInfo.Activities == null)
            {
                // Discovery failed
                var message = $"Terminal at '{terminalUrl}'  didn't return a valid response to the discovery request.";

                Logger.Warn(message);
                // Set terminal status inactive
                terminalDo.OperationalState = OperationalState.Inactive;

                try
                {
                    _terminal.RegisterOrUpdate(terminalDo, isUserInitiated);
                }
                catch (Exception ex)
                {
                    Logger.Error("Failed to update information about the terminal.", ex);
                }

                return(DiscoveryResult.Error(message));
            }

            terminalDo.Secret             = secret;
            terminalDo.OperationalState   = OperationalState.Active;
            terminalDo.AuthenticationType = terminalRegistrationInfo.Definition.AuthenticationType;
            terminalDo.Description        = terminalRegistrationInfo.Definition.Description;
            terminalDo.Label          = terminalRegistrationInfo.Definition.Label;
            terminalDo.Name           = terminalRegistrationInfo.Definition.Name;
            terminalDo.Version        = terminalRegistrationInfo.Definition.Version;
            terminalDo.TerminalStatus = terminalRegistrationInfo.Definition.TerminalStatus;

            if (string.IsNullOrWhiteSpace(terminalDo.Label))
            {
                terminalDo.Label = terminalDo.Name;
            }

            try
            {
                terminalDo = _terminal.RegisterOrUpdate(terminalDo, isUserInitiated);
            }
            catch (Exception ex)
            {
                Logger.Error("Failed to update information about the terminal.", ex);
                return(DiscoveryResult.Error($"Internal error updating the information about the terminal at '{terminalUrl}'"));
            }

            var activityTemplates = terminalRegistrationInfo.Activities.Select(Mapper.Map <ActivityTemplateDO>).ToList();
            var result            = new DiscoveryResult(true, null);

            foreach (var curItem in activityTemplates)
            {
                Logger.Info($"Registering activity '{curItem.Name}' from terminal at '{terminalUrl}'");

                try
                {
                    curItem.Terminal   = terminalDo;
                    curItem.TerminalId = terminalDo.Id;

                    _activityTemplateService.RegisterOrUpdate(curItem);
                    result.SucceededTemplates.Add(curItem);
                }
                catch (Exception ex)
                {
                    _eventReporter.ActivityTemplateTerminalRegistrationError($"Failed to register activity {curItem.Name} of version {curItem.Version} for terminal {terminalDo.Name}. Error Message: {ex.Message}", ex.GetType().Name);

                    Logger.Warn($"Failed to register activity {curItem.Name} of version {curItem.Version} for terminal {terminalDo.Name}", ex);
                    result.FailedTemplates.Add(curItem);
                }
            }

            _activityTemplateService.RemoveInactiveActivities(terminalDo, activityTemplates);

            Logger.Info($"Successfully discovered terminal at '{terminalUrl}'.");

            return(result);
        }