/// <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)); } } } }
/// <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."); } } } }