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