public static void CheckAccessToCarts(ICommercePrincipal principal, IEnumerable <SalesTransaction> transactions)
            {
                if (transactions.IsNullOrEmpty())
                {
                    // Skip check if transaction does not exist.
                    return;
                }

                if (principal == null)
                {
                    throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthorizationFailed, @"Invalid principal.");
                }

                if (principal.IsAnonymous || principal.IsCustomer)
                {
                    foreach (SalesTransaction transaction in transactions)
                    {
                        // For anonymous user, check the cart belongs to anonymous user.
                        // For C2 users, make sure the cart belongs to the user or an anonymous user.
                        if ((principal.IsAnonymous || (principal.IsCustomer && !string.IsNullOrWhiteSpace(transaction.CustomerId))) &&
                            !string.Equals(transaction.CustomerId ?? string.Empty, principal.UserId ?? string.Empty, StringComparison.OrdinalIgnoreCase))
                        {
                            throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthorizationFailed, "User does not have access to cart");
                        }

                        if (!string.IsNullOrWhiteSpace(transaction.StaffId))
                        {
                            throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthorizationFailed, "Anonymous and C2 users cannot modify C1 carts.");
                        }
                    }
                }
            }
            /// <summary>
            /// Initializes the request context.
            /// </summary>
            /// <param name="request">The request.</param>
            /// <param name="context">The request context.</param>
            private static void InitializeRequestContext(Request request, RequestContext context)
            {
                ThrowIf.Null(context, "context");
                ThrowIf.Null(request, "request");

                if (context.IsInitialized)
                {
                    return;
                }

                context.SetInitialized();
                PopulateRequestContext(context);

                // We only want to authorize once per context initialization
                // Since principal cannot be changed on the context, any calls performed using same context
                // will be under the same user's principal
                ICommercePrincipal principal = context.GetPrincipal();

                if (principal != null && principal.IsInRole(CommerceRoles.Employee))
                {
                    // Some requests must not be verified for an already opened session because they are required to
                    // establish a session on the first place
                    bool enforceSessionOpened = request.NeedSessionOpened;

                    // Authorizes the staff request
                    context.Runtime.Execute <StaffAuthorizationServiceResponse>(new StaffAuthorizationServiceRequest(enforceSessionOpened), context, skipRequestTriggers: true);
                }
            }
            /// <summary>
            /// LogOff the user.
            /// </summary>
            /// <param name="request">The device activation request.</param>
            /// <returns>The device activation response.</returns>
            private static NullResponse LogOffUser(UserLogOffRealtimeRequest request)
            {
                UserAuthenticationTransactionServiceDemoMode.ThrowIfInvalidLogonConfiguration(request.LogOnConfiguration);

                ICommercePrincipal principal = request.RequestContext.GetPrincipal();

                if (string.IsNullOrEmpty(principal.UserId))
                {
                    throw new UserAuthenticationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthenticationFailed, "User is not authenticated.");
                }

                string staffId = principal.UserId;

                if (!principal.IsTerminalAgnostic)
                {
                    string terminalId = request.RequestContext.GetTerminal().TerminalId;
                    UnlockUserAtLogOffDataRequest unlockUserDataRequest = new UnlockUserAtLogOffDataRequest(
                        principal.ChannelId,
                        terminalId,
                        staffId,
                        request.RequestContext.GetChannelConfiguration().InventLocationDataAreaId);

                    bool unlocked = request.RequestContext.Execute <SingleEntityDataServiceResponse <bool> >(unlockUserDataRequest).Entity;
                    if (!unlocked)
                    {
                        throw new UserAuthenticationException(
                                  SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthenticationFailed,
                                  string.Format("User {0} was not allowed to be unlocked from terminal {1}", principal.UserId, terminalId));
                    }
                }

                return(new NullResponse());
            }
            /// <summary>
            /// Method to check if the principal has manager permission.
            /// </summary>
            /// <param name="principal">Commerce Principal.</param>
            /// <returns>True or False.</returns>
            private static bool IsManager(ICommercePrincipal principal)
            {
                if (principal == null)
                {
                    throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthorizationFailed, @"Invalid principal.");
                }

                return(principal.IsInRole(ManagerPrivilege));
            }
            /// <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);
            }
Exemple #6
0
            /// <summary>
            /// Executes the workflow to send an e-mail to the specified customer.
            /// </summary>
            /// <param name="request">The request.</param>
            /// <returns>The response.</returns>
            protected override LinkToExistingCustomerResponse Process(InitiateLinkToExistingCustomerRequest request)
            {
                ThrowIf.Null(request, "request");

                if (string.IsNullOrWhiteSpace(request.EmailAddressOfExistingCustomer))
                {
                    throw new DataValidationException(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_RequiredValueNotFound, "An email address is required.");
                }

                ICommercePrincipal principal              = request.RequestContext.GetPrincipal();
                string             externalIdentityId     = principal.ExternalIdentityId;
                string             externalIdentityIssuer = principal.ExternalIdentityIssuer;

                if (principal.IsEmployee)
                {
                    throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthorizationFailed, "Employee user is not authorized to perform this operation.");
                }

                if (string.IsNullOrEmpty(externalIdentityId))
                {
                    throw new SecurityException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthenticationFailed, "The external identity is not set on the pricinpal");
                }

                if (string.IsNullOrEmpty(externalIdentityIssuer))
                {
                    throw new SecurityException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthenticationFailed, "The external identity issuer is not set on the pricinpal");
                }

                // Validate the customer
                GlobalCustomer customer = this.GetCustomer(request.EmailAddressOfExistingCustomer);

                if (customer == null)
                {
                    throw new DataValidationException(
                              DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_CustomerNotFound,
                              string.Format("No customer found using the specified email address. The email address is '{0}'.", request.EmailAddressOfExistingCustomer));
                }

                ICollection <NameValuePair> updatedEmailTemplateProperties = InitiateLinkToExistingCustomerRequestHandler.GetUpdatedEmailTemplateProperties(request.EmailTemplateProperties, request.ActivationToken);

                // Send email to customer
                this.SendCustomerEmail(customer.Email, request.EmailTemplateId, updatedEmailTemplateProperties);

                // Save the request to channel DB
                var serviceRequest = new InitiateLinkToExistingCustomerServiceRequest(
                    request.EmailAddressOfExistingCustomer,
                    request.ActivationToken,
                    externalIdentityId,
                    externalIdentityIssuer,
                    customer.AccountNumber);

                var serviceResponse = this.Context.Execute <LinkToExistingCustomerServiceResponse>(serviceRequest);

                return(new LinkToExistingCustomerResponse(serviceResponse.LinkToExistingCustomerResult));
            }
            /// <summary>
            /// Checks if user has been elevated for a given operation.
            /// </summary>
            /// <param name="principal">
            /// The principal.
            /// </param>
            /// <param name="operationId">
            /// The operation id.
            /// </param>
            private static void CheckUserIsElevatedForOperation(ICommercePrincipal principal, RetailOperation operationId)
            {
                if (operationId != (int)RetailOperation.None && principal.ElevatedOperation != (int)operationId)
                {
                    RetailLogger.Log.CrtServicesUserAuthenticationServiceElevatedPermissionNotApplicableToRequestedOperation(
                        ((RetailOperation)principal.ElevatedOperation).ToString(),
                        operationId.ToString());

                    throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthorizationFailed, @"Access denied; elevated to perform another operation.");
                }
            }
            /// <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>
 /// Validates the search criteria.
 /// </summary>
 /// <param name="criteria">The sales order search criteria.</param>
 /// <param name="principal">The commerce principal.</param>
 private void ValidateSearchCriteria(SalesOrderSearchCriteria criteria, ICommercePrincipal principal)
 {
     // In ecommerce scenario, we do not allow customers to search the orders by staff id.
     if (principal.IsCustomer || principal.IsAnonymous)
     {
         if (criteria.StaffId != null)
         {
             throw new DataValidationException(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_InvalidRequest, "Searching orders by staff id is not allowed in ecommerce scenario.");
         }
     }
 }
            /// <summary>
            /// Method to check if the principal has manager permission.
            /// </summary>
            /// <param name="principal">Commerce Principal.</param>
            public static void CheckAccessManager(ICommercePrincipal principal)
            {
                if (principal == null)
                {
                    throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthorizationFailed, @"Invalid principal.");
                }

                if (!IsManager(principal))
                {
                    throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthorizationFailed, @"Access denied for non-manager.");
                }
            }
Exemple #11
0
            /// <summary>
            /// The data service method to execute the data manager to get the device configuration.
            /// </summary>
            /// <param name="request">The data service request.</param>
            /// <returns>The data service response.</returns>
            private SingleEntityDataServiceResponse <DeviceConfiguration> GetDeviceConfiguration(GetDeviceConfigurationDataRequest request)
            {
                ThrowIf.Null(request, "request");
                ThrowIf.Null(request.RequestContext, "request.RequestContext");
                ThrowIf.Null(request.QueryResultSettings, "request.QueryResultSettings");

                ICommercePrincipal principal = request.RequestContext.GetPrincipal();

                if (principal.IsChannelAgnostic || principal.IsTerminalAgnostic || string.IsNullOrWhiteSpace(principal.DeviceNumber))
                {
                    throw new InvalidOperationException("Current request context is not associated to a device.");
                }

                Terminal terminal = request.RequestContext.GetTerminal();

                ParameterSet parameters = new ParameterSet();
                Tuple <PagedResult <DeviceConfiguration>,
                       ReadOnlyCollection <HardwareConfiguration>,
                       ReadOnlyCollection <HardwareConfiguration>,
                       ReadOnlyCollection <HardwareConfiguration> > dataSets = null;

                parameters[DatabaseAccessor.ChannelIdVariableName] = terminal.ChannelId;
                parameters[TerminalIdVariableName]    = terminal.TerminalId;
                parameters[IncludeImagesVariableName] = request.IncludeImages;

                using (SqlServerDatabaseContext databaseContext = new SqlServerDatabaseContext(request.RequestContext, request.QueryResultSettings))
                {
                    dataSets = databaseContext.ExecuteStoredProcedure <DeviceConfiguration, HardwareConfiguration, HardwareConfiguration, HardwareConfiguration>(GetDeviceConfigurationSprocName, parameters);
                }

                DeviceConfiguration deviceConfiguration             = dataSets.Item1.SingleOrDefault();
                ReadOnlyCollection <HardwareConfiguration> drawers  = dataSets.Item2;
                ReadOnlyCollection <HardwareConfiguration> printers = dataSets.Item3;
                ReadOnlyCollection <HardwareConfiguration> pinpads  = dataSets.Item4;

                if (deviceConfiguration != null)
                {
                    deviceConfiguration.HardwareConfigurations = new HardwareConfigurations();
                    deviceConfiguration.HardwareConfigurations.CashDrawerConfigurations.AddRange(drawers);
                    deviceConfiguration.HardwareConfigurations.PrinterConfigurations.AddRange(printers);
                    deviceConfiguration.HardwareConfigurations.PinPadConfiguration = pinpads.SingleOrDefault();
                }

                GetDeviceDataRequest getDeviceRequest = new GetDeviceDataRequest(principal.DeviceNumber);
                Device device = request.RequestContext.Execute <SingleEntityDataServiceResponse <Device> >(getDeviceRequest).Entity;

                if (deviceConfiguration != null && device != null)
                {
                    deviceConfiguration.UseInMemoryDeviceDataStorage = device.UseInMemoryDeviceDataStorage;
                }

                return(new SingleEntityDataServiceResponse <DeviceConfiguration>(deviceConfiguration));
            }
            /// <summary>
            /// Verifies whether local authorization must be used as a fallback mode in case of error on remote authorization.
            /// </summary>
            /// <returns>A value indicating whether local authorization must be used as a fallback mode in case of error on remote authorization.</returns>
            private bool MustFallbackToLocalAuthorization()
            {
                // we can only fallback to local database if we have a channel id provided
                if (this.CanUseLocalDatabase)
                {
                    EmployeePermissions employeePermissions = null;

                    try
                    {
                        // Create a temporary context with the employee for getting employee permissions.
                        RequestContext tempContext = new RequestContext(this.context.Runtime);
                        var            employee    = new Employee()
                        {
                            StaffId = this.StaffId
                        };
                        ICommercePrincipal principal = this.context.GetPrincipal();
                        CommerceIdentity   identity  = new CommerceIdentity(
                            employee,
                            new Device()
                        {
                            DeviceNumber     = principal.DeviceNumber,
                            Token            = principal.DeviceToken,
                            ChannelId        = principal.ChannelId,
                            TerminalRecordId = principal.TerminalId
                        });
                        tempContext.SetPrincipal(new CommercePrincipal(identity));

                        // Get employee permissions
                        GetEmployeePermissionsDataRequest getEmployeePermissionsDataRequest = new GetEmployeePermissionsDataRequest(
                            this.StaffId,
                            new ColumnSet());

                        employeePermissions = tempContext.Execute <SingleEntityDataServiceResponse <EmployeePermissions> >(
                            getEmployeePermissionsDataRequest).Entity;
                    }
                    catch (Exception exception)
                    {
                        // this method occurs in an error handling scenario
                        // if this fails, we do not want to break the flow
                        // so we just log the exception internally
                        RetailLogger.Instance.CrtServicesStaffAuthorizationServiceGetEmployeePermissionsFailure(exception);
                    }

                    // we can fallback if it is allowed by user permissions
                    return(employeePermissions != null && employeePermissions.ContinueOnTSErrors);
                }

                return(false);
            }
            /// <summary>
            /// LogOff the user.
            /// </summary>
            /// <param name="request">The device activation request.</param>
            /// <returns>The device activation response.</returns>
            private static NullResponse LogOffUser(UserLogOffRealtimeRequest request)
            {
                UserAuthenticationTransactionService.ThrowIfInvalidLogonConfiguration(request.LogOnConfiguration);
                TransactionService.TransactionServiceClient transactionService = new TransactionService.TransactionServiceClient(request.RequestContext);

                ICommercePrincipal principal = request.RequestContext.GetPrincipal();

                if (string.IsNullOrEmpty(principal.UserId))
                {
                    throw new UserAuthenticationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthenticationFailed, "User is not authenticated.");
                }

                transactionService.RetailServerStaffLogOff(principal.UserId, principal.ChannelId, principal.TerminalId, !principal.IsTerminalAgnostic);
                return(new NullResponse());
            }
            /// <summary>
            /// Populates the request context.
            /// </summary>
            /// <param name="context">The request context.</param>
            /// <returns>The populated request context.</returns>
            public static RequestContext PopulateRequestContext(RequestContext context)
            {
                ThrowIf.Null(context, "context");
                ICommercePrincipal principal = context.GetPrincipal();

                if (principal != null && !principal.IsChannelAgnostic)
                {
                    var getChannelConfigurationDataServiceRequest = new GetChannelConfigurationDataRequest(context.GetPrincipal().ChannelId);
                    ChannelConfiguration channelConfiguration     = context.Runtime.Execute <SingleEntityDataServiceResponse <ChannelConfiguration> >(getChannelConfigurationDataServiceRequest, context, skipRequestTriggers: true).Entity;

                    context.SetChannelConfiguration(channelConfiguration);

                    if (context.GetChannelConfiguration().ChannelType == RetailChannelType.RetailStore)
                    {
                        PopulateContextWithOrgUnit(context);
                    }

                    if (!principal.IsTerminalAgnostic)
                    {
                        PopulateContextWithTerminal(context);

                        if (!string.IsNullOrWhiteSpace(principal.DeviceNumber))
                        {
                            PopulateContextWithDeviceConfiguration(context);
                        }
                    }

                    // Use the channel's default language if no language was set on the request.
                    if (string.IsNullOrWhiteSpace(context.LanguageId))
                    {
                        if (!string.IsNullOrWhiteSpace(context.Runtime.Locale))
                        {
                            context.LanguageId = context.Runtime.Locale;
                        }
                        else
                        {
                            context.LanguageId = context.GetChannelConfiguration().DefaultLanguageId;
                        }
                    }

                    if (context.GetTerminal() != null && !string.IsNullOrWhiteSpace(principal.UserId) && principal.ShiftId == 0)
                    {
                        PopulateContextWithShiftInformation(context);
                    }
                }

                return(context);
            }
Exemple #15
0
            /// <summary>
            /// Executes the workflow to do user log off.
            /// </summary>
            /// <param name="request">The request.</param>
            /// <returns>The response.</returns>
            protected override NullResponse Process(UserLogOffRequest request)
            {
                ThrowIf.Null(request, "request");

                ICommercePrincipal principal = this.Context.GetPrincipal();

                request.DeviceNumber       = principal.DeviceNumber;
                request.DeviceToken        = principal.DeviceToken;
                request.LogOnConfiguration = principal.LogOnConfiguration;
                request.StaffId            = principal.UserId;

                // User logs off.
                AuthenticationHelper.LogOff(this.Context, request);

                return(new NullResponse());
            }
            /// <summary>
            /// Method to check if the principal has a shift.
            /// </summary>
            /// <param name="principal">Commerce Principal.</param>
            public static void CheckAccessHasShift(ICommercePrincipal principal)
            {
                if (principal == null)
                {
                    throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthorizationFailed, @"Invalid principal.");
                }

                // Access check not enabled for storefront operations.
                if (principal.IsInRole(CommerceRoles.Storefront))
                {
                    return;
                }

                if (principal.ShiftId == 0)
                {
                    throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_NonDrawerOperationsOnly, "Shift is not open.");
                }
            }
            /// <summary>
            /// Invoked before request has been processed by <see cref="IRequestHandler"/>.
            /// </summary>
            /// <param name="request">The incoming request message.</param>
            public void OnExecuting(Request request)
            {
                ThrowIf.Null(request, "request");
                Type requestType             = request.GetType();
                ICommercePrincipal principal = request.RequestContext.GetPrincipal();

                if (request.NeedChannelIdFromPrincipal && principal.IsChannelAgnostic)
                {
                    throw new CommerceException(
                              SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_InvalidChannel.ToString(),
                              ExceptionSeverity.Warning,
                              null,
                              string.Format("Invalid channel id {0} in current principal. Request type is {1}.", principal.ChannelId, requestType));
                }

                AdjustRequestTimeZone(request);
                InitializeRequestContext(request, request.RequestContext);
            }
            /// <summary>
            /// Executes the workflow to unlock a register.
            /// </summary>
            /// <param name="request">The request.</param>
            /// <returns>The response.</returns>
            protected override NullResponse Process(UnlockRegisterRequest request)
            {
                ThrowIf.Null(request, "request");
                Device device = null;

                ICommercePrincipal principal = this.Context.GetPrincipal();
                string             userId    = principal.UserId;

                try
                {
                    if (userId != request.StaffId)
                    {
                        throw new UserAuthenticationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_UnlockRegisterFailed);
                    }

                    if (request.DeviceId != null && request.DeviceToken != null)
                    {
                        // Authenticate device only when DeviceId is specified
                        device = AuthenticationHelper.AuthenticateDevice(
                            this.Context,
                            request.DeviceToken);
                    }

                    // Unlock the terminal
                    AuthenticationHelper.UnlockRegister(this.Context, device, request);
                    return(new NullResponse());
                }
                catch (DeviceAuthenticationException ex)
                {
                    RetailLogger.Log.CrtWorkflowUnlockRegisterRequestHandlerFailure(request.StaffId, request.DeviceId, ex);
                    throw;
                }
                catch (UserAuthenticationException ex)
                {
                    RetailLogger.Log.CrtWorkflowUnlockRegisterRequestHandlerFailure(request.StaffId, request.DeviceId, ex);
                    throw new UserAuthenticationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_UnlockRegisterFailed);
                }
                catch (Exception ex)
                {
                    RetailLogger.Log.CrtWorkflowUnlockRegisterRequestHandlerFailure(request.StaffId, request.DeviceId, ex);
                    throw new UserAuthenticationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthenticationFailed);
                }
            }
            /// <summary>
            /// Method to check if the principal has access to the customer account.
            /// </summary>
            /// <param name="principal">Commerce Principal.</param>
            /// <param name="customerAccount">Customer Account.</param>
            public static void CheckAccessToCustomerAccount(ICommercePrincipal principal, string customerAccount)
            {
                if (principal == null)
                {
                    throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthorizationFailed, @"Invalid principal.");
                }

                if (principal.IsAnonymous)
                {
                    throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthorizationFailed, "User does not have access to customer account");
                }
                else if (principal.IsCustomer)
                {
                    if (!string.Equals(principal.UserId, customerAccount))
                    {
                        throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthorizationFailed, "User does not have access to customer account");
                    }
                }
            }
            /// <summary>
            /// Gets the commerce lists specified by the request.
            /// </summary>
            /// <param name="request">The request.</param>
            /// <returns><see cref="GetCommerceListResponse"/>Object containing the commerce lists.</returns>
            protected override GetCommerceListResponse Process(GetCommerceListRequest request)
            {
                ThrowIf.Null(request, "request");
                ThrowIf.Null(request.RequestContext, "request.RequestContext");

                // Async customers do not have commerce lists.
                if (this.IsAsyncCustomer(request.RequestContext, request.CustomerAccountNumber))
                {
                    return(new GetCommerceListResponse(PagedResult <CommerceList> .Empty()));
                }

                // Employee can read any commerce lists.
                // Customer can only read lists owned by him or shared to him.
                // Anonymous is not allowed to read commerce lists.
                ICommercePrincipal principal = request.RequestContext.GetPrincipal();

                if (principal.IsAnonymous)
                {
                    throw new NotSupportedException("Anonymous is not allowed to read commerce lists.");
                }
                else if (principal.IsCustomer)
                {
                    request.CustomerAccountNumber = principal.UserId;
                }

                // Form a request and get the response
                GetCommerceListRealtimeRequest serviceRequest = new GetCommerceListRealtimeRequest
                {
                    Id = request.Id,
                    CustomerAccountNumber = request.CustomerAccountNumber,
                    FavoriteFilter        = request.FavoriteFilter,
                    PublicFilter          = request.PublicFilter,
                    QueryResultSettings   = request.QueryResultSettings
                };

                GetCommerceListRealtimeResponse serviceResponse = this.Context.Execute <GetCommerceListRealtimeResponse>(serviceRequest);

                return(new GetCommerceListResponse(serviceResponse.Clists));
            }
            /// <summary>
            /// Logon the user on local store.
            /// </summary>
            /// <param name="request">The device activation request.</param>
            /// <param name="channelConfiguration">The channel configuration.</param>
            /// <returns>The device activation response.</returns>
            private static Employee EmployeeLogOnStore(StaffLogOnRealtimeRequest request, ChannelConfiguration channelConfiguration)
            {
                if (request == null)
                {
                    throw new ArgumentNullException("request");
                }

                Employee employee = null, localEmployee = null;
                string   passwordHash = string.Empty;
                string   staffId      = request.StaffId;

                if (request.ChannelId.HasValue && request.RequestContext.GetPrincipal().ChannelId == request.ChannelId.Value)
                {
                    // Get employee salt and password hash algorithm.
                    GetEmployeePasswordCryptoInfoDataRequest employeePasswordCryptoInfoRequest = new GetEmployeePasswordCryptoInfoDataRequest(request.ChannelId.GetValueOrDefault(), staffId);
                    var    passwordCryptoInfo = request.RequestContext.Execute <SingleEntityDataServiceResponse <EmployeePasswordCryptoInfo> >(employeePasswordCryptoInfoRequest).Entity;
                    string salt = passwordCryptoInfo.PasswordSalt ?? string.Empty;
                    string passwordHashAlgorithm = passwordCryptoInfo.PasswordHashAlgorithm;

                    if (!string.IsNullOrEmpty(request.StaffPassword))
                    {
                        if (string.IsNullOrEmpty(passwordHashAlgorithm))
                        {
                            // get hash algorithm from the transaction service profile.
                            var getTransactionServiceProfileDataRequest         = new GetTransactionServiceProfileDataRequest();
                            TransactionServiceProfile transactionServiceProfile = request.RequestContext.Execute <SingleEntityDataServiceResponse <TransactionServiceProfile> >(
                                getTransactionServiceProfileDataRequest).Entity;
                            passwordHashAlgorithm = transactionServiceProfile.StaffPasswordHash;
                        }

                        HashDataServiceRequest hashDataServiceRequest = new HashDataServiceRequest(request.StaffPassword, passwordHashAlgorithm, staffId, salt);
                        passwordHash = request.RequestContext.Execute <HashDataServiceResponse>(hashDataServiceRequest).Data;
                    }

                    // Logon to the store
                    EmployeeLogOnStoreDataRequest dataRequest = new EmployeeLogOnStoreDataRequest(
                        request.ChannelId.Value,
                        staffId,
                        passwordHash,
                        new ColumnSet());

                    localEmployee = request.RequestContext.Execute <SingleEntityDataServiceResponse <Employee> >(dataRequest).Entity;
                    if (localEmployee == null || localEmployee.Permissions == null)
                    {
                        throw new UserAuthenticationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthenticationFailed);
                    }

                    // Return new object for the logged-on employee with only the required fields to ensure not returning any sensitive data.
                    employee               = new Employee();
                    employee.Permissions   = new EmployeePermissions();
                    employee.Name          = localEmployee.Name;
                    employee.StaffId       = localEmployee.StaffId;
                    employee.NameOnReceipt = localEmployee.NameOnReceipt;
                    employee.Permissions   = localEmployee.Permissions;
                    employee.Images        = localEmployee.Images;
                    employee.PasswordLastChangedDateTime = localEmployee.PasswordLastChangedDateTime;

                    // Lock the user if multiple logins are not allowed.
                    if (!employee.Permissions.AllowMultipleLogins && channelConfiguration != null)
                    {
                        LockUserAtLogOnDataRequest lockDataRequest = new LockUserAtLogOnDataRequest(
                            request.ChannelId.Value,
                            request.RequestContext.GetTerminal().TerminalId,
                            staffId,
                            channelConfiguration.InventLocationDataAreaId);
                        bool locked = request.RequestContext.Execute <SingleEntityDataServiceResponse <bool> >(lockDataRequest).Entity;
                        if (!locked)
                        {
                            throw new UserAuthenticationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_UserLogonAnotherTerminal);
                        }
                    }
                }
                else
                {
                    RequestContext getEmployeeRequestContext;

                    // Employee record is associated with channel
                    // Decide what context is to be used to retrieve it
                    if (request.RequestContext.GetPrincipal().IsChannelAgnostic)
                    {
                        // If the context is channel agnostic (no channel information), then use current (first published channel in this case)
                        GetChannelIdServiceRequest  getChannelRequest  = new GetChannelIdServiceRequest();
                        GetChannelIdServiceResponse getChannelResponse = request.RequestContext.Execute <GetChannelIdServiceResponse>(getChannelRequest);

                        var anonymousIdentity = new CommerceIdentity()
                        {
                            ChannelId = getChannelResponse.ChannelId
                        };

                        getEmployeeRequestContext = new RequestContext(request.RequestContext.Runtime);
                        getEmployeeRequestContext.SetPrincipal(new CommercePrincipal(anonymousIdentity));
                    }
                    else
                    {
                        // If the request has channel information, then use current context
                        getEmployeeRequestContext = request.RequestContext;
                    }

                    GetEmployeeDataRequest dataRequest = new GetEmployeeDataRequest(staffId, QueryResultSettings.SingleRecord);
                    employee = getEmployeeRequestContext.Execute <SingleEntityDataServiceResponse <Employee> >(dataRequest).Entity;
                    if (employee == null)
                    {
                        string message = string.Format(CultureInfo.InvariantCulture, "The specified employee ({0}) was not found.", staffId);
                        throw new UserAuthenticationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthenticationFailed, message);
                    }

                    ICommercePrincipal principal = request.RequestContext.GetPrincipal();
                    RequestContext     contextForOperationAuthorization;
                    if (principal.IsAnonymous)
                    {
                        // create an authenticated context that can be used for specific operation authorization
                        RequestContext   authenticatedContext = new RequestContext(request.RequestContext.Runtime);
                        CommerceIdentity identity             = new CommerceIdentity(
                            employee,
                            new Device()
                        {
                            DeviceNumber     = principal.DeviceNumber,
                            Token            = principal.DeviceToken,
                            ChannelId        = principal.ChannelId,
                            TerminalRecordId = principal.TerminalId
                        });
                        authenticatedContext.SetPrincipal(new CommercePrincipal(identity));

                        contextForOperationAuthorization = authenticatedContext;
                    }
                    else
                    {
                        contextForOperationAuthorization = request.RequestContext;
                    }

                    GetEmployeePermissionsDataRequest permissionsDataRequest = new GetEmployeePermissionsDataRequest(staffId, new ColumnSet());
                    employee.Permissions = contextForOperationAuthorization.Execute <SingleEntityDataServiceResponse <EmployeePermissions> >(permissionsDataRequest).Entity;
                }

                return(employee);
            }
            /// <summary>
            /// Checks if the principal has permission to do the operation. If not, throws UserAuthorizationException.
            /// </summary>
            /// <param name="principal">The request.</param>
            /// <param name="operationId">Operation Id.</param>
            /// <param name="context">Request Context.</param>
            /// <param name="allowedRoles">Allowed roles.</param>
            /// <param name="deviceTokenRequired">Device token required.</param>
            /// <param name="nonDrawerOperationCheckRequired">Is non-drawer mode operation check required.</param>
            public static void CheckAccess(ICommercePrincipal principal, RetailOperation operationId, RequestContext context, string[] allowedRoles, bool deviceTokenRequired, bool nonDrawerOperationCheckRequired)
            {
                if (principal == null)
                {
                    throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthorizationFailed, @"Invalid principal.");
                }

                if (context == null)
                {
                    throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthorizationFailed, @"Invalid context.");
                }

                // Access check not enabled for storefront operations.
                if (principal.IsInRole(CommerceRoles.Storefront))
                {
                    return;
                }

                // Check device token for Employee role.
                if (principal.IsInRole(CommerceRoles.Employee) && deviceTokenRequired)
                {
                    if (string.IsNullOrEmpty(principal.DeviceToken))
                    {
                        throw new DeviceAuthenticationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_DeviceTokenNotPresent, @"Device token was expected, but not provided.");
                    }
                }

                bool isAnonymousUser = principal.IsInRole(CommerceRoles.Anonymous);

                // Check if the principal is one of the allowed roles.
                if (allowedRoles != null)
                {
                    bool allowedRole = false;
                    foreach (string role in allowedRoles)
                    {
                        if (principal.IsInRole(role))
                        {
                            allowedRole = true;
                            break;
                        }
                    }

                    // If the user is not in the allowed roles, throw unauthorized exception.
                    if (!allowedRole)
                    {
                        if (isAnonymousUser)
                        {
                            // this means that if the user authenticates with the system and retry the request
                            // s(he) might get a different result (user authentication maps to HTTP 401 whereas Authorization maps to HTTP 403)
                            throw new UserAuthenticationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthenticationFailed, @"Assigned role is not allowed to perform this operation.");
                        }
                        else
                        {
                            throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthorizationFailed, @"Assigned role is not allowed to perform this operation.");
                        }
                    }
                }

                // Access check for Anonymous principal.
                if (isAnonymousUser)
                {
                    if (operationId != RetailOperation.None)
                    {
                        GetOperationPermissionsDataRequest dataRequest = new GetOperationPermissionsDataRequest(operationId, QueryResultSettings.SingleRecord);
                        OperationPermission operationPermission        = context.Execute <EntityDataServiceResponse <OperationPermission> >(dataRequest).PagedEntityCollection.FirstOrDefault();
                        if (operationPermission == null)
                        {
                            throw new UserAuthenticationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthorizationFailed, @"Access denied for employee to perform this operation.");
                        }

                        if (!operationPermission.AllowAnonymousAccess)
                        {
                            throw new UserAuthenticationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthorizationFailed, @"Access denied to perform this operation.");
                        }
                    }
                }

                // Access check for authenticated customer.
                if (principal.IsInRole(CommerceRoles.Customer))
                {
                    if (operationId != RetailOperation.None)
                    {
                        GetOperationPermissionsDataRequest dataRequest = new GetOperationPermissionsDataRequest(operationId, QueryResultSettings.SingleRecord);
                        OperationPermission operationPermission        = context.Execute <EntityDataServiceResponse <OperationPermission> >(dataRequest).PagedEntityCollection.FirstOrDefault();
                        if (operationPermission == null)
                        {
                            throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthorizationFailed, @"Access denied  to perform this operation.");
                        }

                        if (!operationPermission.AllowCustomerAccess)
                        {
                            throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthorizationFailed, @"Access denied to perform this operation.");
                        }

                        if (!string.IsNullOrWhiteSpace(operationPermission.PermissionsStringV2))
                        {
                            if (!principal.IsInRole(operationPermission.PermissionsStringV2.ToUpperInvariant()))
                            {
                                throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthorizationFailed, @"Access denied for employee to perform this operation (permissions).");
                            }
                        }
                    }
                }

                // Access check for Staff principal.
                if (principal.IsInRole(CommerceRoles.Employee))
                {
                    // Validates the non drawer operation permission.
                    if (nonDrawerOperationCheckRequired)
                    {
                        CheckNonDrawerOperationPermission(operationId, context);
                    }

                    // If the principal has Manager privilege, always allow operation.
                    if (IsManager(principal) && principal.ElevatedOperation == (int)RetailOperation.None)
                    {
                        return;
                    }

                    // Only employees users have access to the retail operation specified in the attribute.
                    if (operationId != RetailOperation.None)
                    {
                        GetOperationPermissionsDataRequest dataRequest = new GetOperationPermissionsDataRequest(operationId, QueryResultSettings.SingleRecord);
                        OperationPermission operationPermission        = context.Execute <EntityDataServiceResponse <OperationPermission> >(dataRequest).PagedEntityCollection.FirstOrDefault();
                        if (operationPermission == null)
                        {
                            if (operationId == RetailOperation.ActivateDevice)
                            {
                                throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_NoDeviceManagementPermission, "Access denied for employee to perform device activation operation.");
                            }

                            throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthorizationFailed, "Access denied for employee to perform this operation.");
                        }

                        // If CheckUserAccess flag is not enabled, return.
                        if (operationPermission.CheckUserAccess == false)
                        {
                            return;
                        }

                        // Enumerate the permissions for the operation and ensure user have access.
                        foreach (string permission in operationPermission.Permissions)
                        {
                            if (!principal.IsInRole(permission.ToUpperInvariant()))
                            {
                                if (operationId == RetailOperation.ActivateDevice)
                                {
                                    throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_NoDeviceManagementPermission, "Access denied for employee to perform device activation operation.");
                                }

                                if (string.IsNullOrWhiteSpace(principal.OriginalUserId))
                                {
                                    throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthorizationFailed, "Access denied for employee to perform this operation (permissions).");
                                }

                                // Checkin for elevated operation only the if the user is not already in role.
                                CheckUserIsElevatedForOperation(principal, operationId);
                            }
                        }
                    }
                }
            }