/// <summary>
            /// Associates the current employee and terminal in <paramref name="context"/>.
            /// </summary>
            /// <param name="requestContext">The request context.</param>
            /// <remarks>If employee cannot hold multiple sessions on different terminals <see cref="UserAuthorizationException"/> is thrown.</remarks>
            private static void CreateStaffSession(RequestContext requestContext)
            {
                ICommercePrincipal principal = requestContext.GetPrincipal();
                string             staffId   = principal.UserId;

                // when no terminal is present, there is no need to enforce single terminal use
                if (principal.IsTerminalAgnostic)
                {
                    throw new InvalidOperationException("A new session can only be created when channel and terminal are present in the request context.");
                }

                if (string.IsNullOrWhiteSpace(staffId))
                {
                    throw new InvalidOperationException("AuthorizeMultipleTerminalUse can only be performed if user is known. Missing UserId from CommercePrincipal.");
                }

                if (!principal.IsEmployee)
                {
                    throw new InvalidOperationException("AuthorizeMultipleTerminalUse can only be performed if user is employee.");
                }

                GetEmployeeAuthorizedOnStoreDataRequest employeeDataRequest = new GetEmployeeAuthorizedOnStoreDataRequest(staffId);
                Employee employee = requestContext.Execute <SingleEntityDataServiceResponse <Employee> >(employeeDataRequest).Entity;

                // managers are not required to keep track of sessions
                if (employee.Permissions.HasManagerPrivileges)
                {
                    return;
                }

                // helper does most of the hard lifting regarding fallback logic and real time communication
                StaffRealTimeSecurityValidationHelper staffSecurityHelper = StaffRealTimeSecurityValidationHelper.Create(
                    requestContext,
                    SecurityVerificationType.Authorization,
                    staffId,
                    principal.ChannelId,
                    principal.TerminalId,
                    password: string.Empty);

                // executes the common set of steps that performs the session creation flow in either HQ, local DB, or HQ with local DB fallback
                ExecuteWorkWithLocalFallback(
                    staffSecurityHelper.GetSecurityVerificationConfiguration(),
                    staffSecurityHelper,
                    () =>
                {
                    return(CreateEmployeeSessionLocalDatabase(requestContext, employee));
                });

                // we always need to create the session on local DB so we can enforce session is open,
                // so if the configuration is set to use RealTime service, also creates the session on local DB
                if (staffSecurityHelper.GetSecurityVerificationConfiguration() == LogOnConfiguration.RealTimeService)
                {
                    CreateEmployeeSessionLocalDatabase(requestContext, employee);
                }

                RetailLogger.Log.CrtServicesStaffAuthorizationServiceUserSessionStarted(staffId, requestContext.GetTerminal().TerminalId);
            }
            /// <summary>
            /// Authorizes the retail staff.
            /// </summary>
            /// <param name="staffAuthorizationRequest">The retail staff authorization request.</param>
            /// <returns>The service response.</returns>
            private static Response AuthorizeStaff(StaffAuthorizationServiceRequest staffAuthorizationRequest)
            {
                RequestContext     context       = staffAuthorizationRequest.RequestContext;
                ICommercePrincipal principal     = context.GetPrincipal();
                string             staffId       = string.IsNullOrWhiteSpace(staffAuthorizationRequest.StaffId) ? principal.UserId : staffAuthorizationRequest.StaffId;
                long?           channelId        = principal.IsChannelAgnostic ? null : (long?)principal.ChannelId;
                long?           terminalRecordId = principal.IsTerminalAgnostic ? null : (long?)principal.TerminalId;
                RetailOperation operation        = staffAuthorizationRequest.RetailOperation;
                Employee        employee;

                if (string.IsNullOrWhiteSpace(staffId))
                {
                    throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthorizationFailed, "UserId is missing from principal.");
                }

                if (channelId.HasValue && !principal.IsTerminalAgnostic && !terminalRecordId.HasValue)
                {
                    throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthorizationFailed, "When channel identififer is provided on principal, terminal record identfier must also be.");
                }

                StaffRealTimeSecurityValidationHelper staffSecurityHelper = StaffRealTimeSecurityValidationHelper.Create(
                    context,
                    SecurityVerificationType.Authorization,
                    staffId,
                    channelId,
                    terminalRecordId,
                    password: string.Empty);

                // we can only check values against database if the principal is bound to a channel
                if (!principal.IsChannelAgnostic)
                {
                    VerifyThatOrgUnitIsPublished(context, channelId.Value);
                }

                // for authorization, we always want to go to local DB, we only go to headquarters if we don't have a channel
                // to access the DB
                LogOnConfiguration logOnConfiguration = principal.IsChannelAgnostic
                    ? LogOnConfiguration.RealTimeService
                    : LogOnConfiguration.LocalDatabase;

                // authorize employee based on configuration
                employee = ExecuteWorkWithLocalFallback(
                    logOnConfiguration,
                    staffSecurityHelper,
                    () =>
                {
                    return(AuthorizeEmployeeLocalDatabase(context, staffSecurityHelper.StaffId, staffAuthorizationRequest.EnforceSessionToBeOpened));
                });

                // Validates whether the staff can perform the requested operation
                ValidateEmployeePermissionForOperation(context, operation, employee);

                return(new StaffAuthorizationServiceResponse(employee));
            }
 /// <summary>
 /// Determines whether two helper instances are equivalent.
 /// </summary>
 /// <param name="helperFromContext">The helper available from the context.</param>
 /// <param name="newHelper">The new helper.</param>
 /// <returns>true if the specified object is equal to the current object; otherwise, false.</returns>
 private static bool AreEquivalent(StaffRealTimeSecurityValidationHelper helperFromContext, StaffRealTimeSecurityValidationHelper newHelper)
 {
     // all parameters must be same
     // passwords are same or newHelper does not care about authentication
     // verification type provided in the helper from context must contain the one required in the newHelper
     return(helperFromContext.StaffId.Equals(newHelper.StaffId, StringComparison.OrdinalIgnoreCase) &&
            helperFromContext.ChannelId == newHelper.ChannelId &&
            helperFromContext.TerminalRecordId == newHelper.TerminalRecordId &&
            helperFromContext.verificationType.HasFlag(newHelper.verificationType) &&
            (newHelper.verificationType == SecurityVerificationType.Authorization || helperFromContext.password == newHelper.password));
 }
            /// <summary>
            /// Creates a new instance of the <see cref="StaffRealTimeSecurityValidationHelper"/> class if not already available in the context.
            /// </summary>
            /// <param name="context">The request context.</param>
            /// <param name="verificationType">The verification type to be performed.</param>
            /// <param name="staffId">The staff identifier.</param>
            /// <param name="channelId">The channel identifier.</param>
            /// <param name="terminalRecordId">The terminal record identifier.</param>
            /// <param name="password">The employee password.</param>
            /// <returns>The staff security helper instance created.</returns>
            public static StaffRealTimeSecurityValidationHelper Create(RequestContext context, SecurityVerificationType verificationType, string staffId, long?channelId, long?terminalRecordId, string password)
            {
                ThrowIf.Null(context, "context");
                ThrowIf.NullOrWhiteSpace(staffId, "staffId");

                if (verificationType.HasFlag(SecurityVerificationType.Authentication) && string.IsNullOrWhiteSpace(password))
                {
                    throw new ArgumentException("Password must be provided if authentication is required.", "password");
                }

                if (channelId.HasValue && context.GetPrincipal() != null && !context.GetPrincipal().IsTerminalAgnostic&& !terminalRecordId.HasValue)
                {
                    throw new ArgumentException("Terminal record identifier must be provided whenever channel identifier is provider.", "terminalRecordId");
                }

                StaffRealTimeSecurityValidationHelper helper            = new StaffRealTimeSecurityValidationHelper(context, verificationType, staffId, channelId, terminalRecordId, password);
                StaffRealTimeSecurityValidationHelper helperFromContext = context.GetProperty(StaffSecurityHelperContextCacheKey) as StaffRealTimeSecurityValidationHelper;

                // within the same context, authentication and authorization validations might occur separatelly
                // to avoid the penalty of two transaction service calls, we store the result for the context
                // and check if we can use the stored result for subsequent calls within the same context
                if (helperFromContext != null)
                {
                    // if helper is available from context and is equivalent to this one (based on parameters)
                    if (AreEquivalent(helperFromContext, helper))
                    {
                        // then use cached results from the one from context
                        helper.realTimeStaffVerificationEmployee = helperFromContext.realTimeStaffVerificationEmployee;
                    }
                }
                else
                {
                    // first time staff helper is used, keep it on context
                    context.SetProperty(StaffSecurityHelperContextCacheKey, helper);
                }

                return(helper);
            }
            /// <summary>
            /// Executes work based against RTS or Local Database based on <paramref name="authorizationConfiguration"/>.
            /// If <paramref name="authorizationConfiguration"/> is <see cref="LogOnConfiguration.RealTimeService"/> then
            /// business logic is performed using RealTime service. If RealTime is not available and <see cref="Employee"/> is allowed to fallback to local database checks, then
            /// the delegate <paramref name="localDatabaseAction"/> will be executed. In case <paramref name="authorizationConfiguration"/> is <see cref="LogOnConfiguration.LocalDatabase"/>,
            /// then authorization is performed solely using local database.
            /// </summary>
            /// <param name="authorizationConfiguration">Indicates whether the logic should run, against local database or against the real time service.</param>
            /// <param name="staffSecurityHelper">The staff security validation helper instance.</param>
            /// <param name="localDatabaseAction">A delegate for the local execution of the authorization logic.</param>
            /// <returns>The employee.</returns>
            private static Employee ExecuteWorkWithLocalFallback(LogOnConfiguration authorizationConfiguration, StaffRealTimeSecurityValidationHelper staffSecurityHelper, Func <Employee> localDatabaseAction)
            {
                Employee employee = null;
                string   errorMessage;

                switch (authorizationConfiguration)
                {
                case LogOnConfiguration.RealTimeService:
                    try
                    {
                        employee = staffSecurityHelper.VerifyEmployeeRealTimeService(() =>
                        {
                            return(localDatabaseAction());
                        });
                    }
                    catch (HeadquarterTransactionServiceException exception)
                    {
                        throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_HeadquarterTransactionServiceMethodCallFailure, exception, exception.Message)
                              {
                                  LocalizedMessage = exception.LocalizedMessage
                              };
                    }
                    catch (CommerceException exception)
                    {
                        // The error code to be persisted
                        throw new UserAuthorizationException(
                                  SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_HeadquarterCommunicationFailure,
                                  exception,
                                  exception.Message);
                    }
                    catch (Exception exception)
                    {
                        // any exceptions that might happen will cause the authorization to fail
                        throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthorizationFailed,
                                                             exception,
                                                             exception.Message);
                    }

                    break;

                case LogOnConfiguration.LocalDatabase:
                    employee = localDatabaseAction();
                    break;

                default:
                    errorMessage = string.Format(
                        CultureInfo.InvariantCulture,
                        "The authorization configuration value '{0}' is not supported.",
                        authorizationConfiguration);
                    throw new NotSupportedException(errorMessage);
                }

                return(employee);
            }