/// <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();
                RetailOperation operation          = RequestTypeToOperationMap[requestType];
                var             checkAccessRequest = new CheckAccessServiceRequest(operation);

                request.RequestContext.Execute <NullResponse>(checkAccessRequest);
            }
            /// <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>
            /// This method performs validation for non drawer operation permissions.
            /// </summary>
            /// <param name="operationId">Retail operation Id.</param>
            /// <param name="context">The request context.</param>
            private static void CheckNonDrawerOperationPermission(RetailOperation operationId, RequestContext context)
            {
                // If the shift Id is not set then it is non-drawer operation performed by user.
                // Then we validate against the white list of retail operations that can be performed by user.
                // The terminal should be always set to support non-drawer operation.
                if (context.GetPrincipal() != null && context.GetPrincipal().ShiftId == 0 && operationId != RetailOperation.None && !context.GetPrincipal().IsTerminalAgnostic)
                {
                    // Few operations are added to the white list which are not related to non-drawer operations like:
                    // ActivateDevice, Logon, DeactivateDevice, Logoff and CloseShift.
                    switch (operationId)
                    {
                    case RetailOperation.ActivateDevice:
                    case RetailOperation.ChangePassword:
                    case RetailOperation.ResetPassword:
                    case RetailOperation.LogOn:
                    case RetailOperation.DeactivateDevice:
                    case RetailOperation.LogOff:
                    case RetailOperation.CustomerClear:
                    case RetailOperation.CustomerSearch:
                    case RetailOperation.CustomerAdd:
                    case RetailOperation.CustomerEdit:
                    case RetailOperation.CustomerTransactions:
                    case RetailOperation.CustomerTransactionsReport:
                    case RetailOperation.DatabaseConnectionStatus:
                    case RetailOperation.GiftCardBalance:
                    case RetailOperation.InventoryLookup:
                    case RetailOperation.ItemSearch:
                    case RetailOperation.MinimizePOSWindow:
                    case RetailOperation.PairHardwareStation:
                    case RetailOperation.PriceCheck:
                    case RetailOperation.ShippingAddressSearch:
                    case RetailOperation.ShowJournal:
                    case RetailOperation.ShowBlindClosedShifts:
                    case RetailOperation.Search:
                    case RetailOperation.ExtendedLogOn:
                    case RetailOperation.TimeRegistration:
                    case RetailOperation.ViewReport:
                    case RetailOperation.PrintZ:
                    case RetailOperation.ChangeHardwareStation:
                        break;

                    default:
                    {
                        throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_NonDrawerOperationsOnly, string.Format(@"Operation {0} cannot be executed; Shift is not open.", operationId));
                    }
                    }
                }
            }
예제 #4
0
            /// <summary>
            /// Executes the workflow to recalculate a sales transaction and return a cart representing the transaction.
            /// </summary>
            /// <param name="request">Instance of <see cref="RecalculateOrderRequest"/>.</param>
            /// <returns>Instance of <see cref="RecalculateOrderResponse"/>.</returns>
            protected override RecalculateOrderResponse Process(RecalculateOrderRequest request)
            {
                ThrowIf.Null(request, "request");

                // Recovers transaction from database
                SalesTransaction salesTransaction = CartWorkflowHelper.LoadSalesTransaction(this.Context, request.CartId);

                if (salesTransaction == null)
                {
                    throw new DataValidationException(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_CartNotFound, "Cart does not exist.");
                }

                // Check permissions.
                RetailOperation operation = salesTransaction.CartType == CartType.CustomerOrder ? RetailOperation.RecalculateCustomerOrder : RetailOperation.CalculateFullDiscounts;

                request.RequestContext.Execute <NullResponse>(new CheckAccessServiceRequest(operation));

                // When recalcalculating order, unlock prices so new prices and discounts are applied to the entire order.
                foreach (SalesLine salesLine in salesTransaction.SalesLines)
                {
                    salesLine.IsPriceLocked = false;
                }

                // Recalculate transaction
                CartWorkflowHelper.Calculate(this.Context, salesTransaction, requestedMode: null, discountCalculationMode: DiscountCalculationMode.CalculateAll);

                // Update order on database
                CartWorkflowHelper.SaveSalesTransaction(this.Context, salesTransaction);

                // Convert the SalesOrder into a cart object for the client
                Cart cart = CartWorkflowHelper.ConvertToCart(this.Context, salesTransaction);

                CartWorkflowHelper.RemoveHistoricalTenderLines(cart);

                // Return cart
                return(new RecalculateOrderResponse(cart));
            }
            /// <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);
                            }
                        }
                    }
                }
            }
            /// <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>
            /// Validates whether the employee has enough permission to execute the requested operation.
            /// </summary>
            /// <param name="context">The request context.</param>
            /// <param name="retailOperation">The retail operation.</param>
            /// <param name="employee">The employee.</param>
            private static void ValidateEmployeePermissionForOperation(RequestContext context, RetailOperation retailOperation, Employee employee)
            {
                // If the request is for logon with specific operation, check if the employee can execute requested operation.
                if (retailOperation != RetailOperation.None && !employee.Permissions.HasManagerPrivileges)
                {
                    if (context.GetPrincipal().IsChannelAgnostic)
                    {
                        throw new UserAuthorizationException(
                                  SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthorizationFailed,
                                  "Operation are only allowed when CommercePrincipal has a channel defined.");
                    }

                    GetOperationPermissionsDataRequest dataRequest = new GetOperationPermissionsDataRequest(
                        retailOperation,
                        QueryResultSettings.SingleRecord);

                    OperationPermission operationPermission = context.Execute <EntityDataServiceResponse <OperationPermission> >(dataRequest)
                                                              .PagedEntityCollection.FirstOrDefault();

                    if (operationPermission == null)
                    {
                        throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthorizationFailed, "The user does not have permissions to execute the operation requested.");
                    }

                    // Check if principal has all required permissions for the Operation or have manager privilege.
                    foreach (string permission in operationPermission.Permissions)
                    {
                        if (!employee.Permissions.Roles.Contains(permission) && !employee.Permissions.HasManagerPrivileges)
                        {
                            throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthorizationFailed, "The user does not have permissions to execute the operation requested.");
                        }
                    }
                }
            }