/// <summary> /// Initializes a new instance of the <see cref="TransactionServiceClientFactory"/> class. /// </summary> /// <param name="context">The request context.</param> public TransactionServiceClientFactory(Microsoft.Dynamics.Commerce.Runtime.RequestContext context) { ThrowIf.Null(context, "context"); this.dataAreaId = context.GetChannelConfiguration() != null ? context.GetChannelConfiguration().InventLocationDataAreaId : string.Empty; var getTransactionServiceProfileDataRequest = new GetTransactionServiceProfileDataRequest(); this.transactionServiceProfile = context.Runtime.Execute <SingleEntityDataServiceResponse <TransactionServiceProfile> >(getTransactionServiceProfileDataRequest, context).Entity; this.transactionServiceProfile = GetTransactionServiceProfile(context); this.SetRealTimeServiceProfileConfigSettings(context); this.requestLanguageId = this.transactionServiceProfile.LanguageId; this.ValidateTransactionProfile(); }
/// <summary> /// Change password for the user. /// </summary> /// <param name="request">The device activation request.</param> /// <returns>The device activation response.</returns> private static Response ChangePassword(UserChangePasswordRealtimeRequest request) { ThrowIf.Null(request, "request"); long channelId = request.RequestContext.GetPrincipal().ChannelId; string staffId = request.StaffId; // Get employee salt and password hash algorithm. GetEmployeePasswordCryptoInfoDataRequest employeePasswordCryptoInfoRequest = new GetEmployeePasswordCryptoInfoDataRequest(channelId, staffId); var passwordCryptoInfo = request.RequestContext.Execute <SingleEntityDataServiceResponse <EmployeePasswordCryptoInfo> >(employeePasswordCryptoInfoRequest).Entity; string salt = passwordCryptoInfo.PasswordSalt ?? string.Empty; string passwordHashAlgorithm = passwordCryptoInfo.PasswordHashAlgorithm; 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; } // hash old password HashDataServiceRequest hashDataServiceRequest = new HashDataServiceRequest(request.OldPassword, passwordHashAlgorithm, request.StaffId, salt); string oldPasswordHash = request.RequestContext.Execute <HashDataServiceResponse>(hashDataServiceRequest).Data; // check if old password is correct EmployeeLogOnStoreDataRequest employeeDataRequest = new EmployeeLogOnStoreDataRequest( channelId, staffId, oldPasswordHash, new ColumnSet()); if (request.RequestContext.Execute <SingleEntityDataServiceResponse <Employee> >(employeeDataRequest).Entity == null) { // SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_InvalidPassword is used internally to identify when user provides incorrect password // here we make sure this is not surfaced outside of the runtime, so there is no information disclosure throw new UserAuthenticationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_InvalidAuthenticationCredentials, "Incorrect user name or password."); } // hash new password hashDataServiceRequest = new HashDataServiceRequest(request.NewPassword, passwordHashAlgorithm, request.StaffId, salt); string newPasswordHash = request.RequestContext.Execute <HashDataServiceResponse>(hashDataServiceRequest).Data; // return new password hash + salt return(new StaffChangePasswordRealtimeResponse(newPasswordHash, salt, passwordHashAlgorithm, DateTime.UtcNow, AuthenticationOperation.ChangePassword)); }
/// <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> /// Validate Device token. /// </summary> /// <param name="deviceId">The device identifier.</param> /// <param name="deviceTokenData">The device token data.</param> /// <param name="deviceToken">The device token.</param> /// <param name="requestContext">Request context.</param> /// <returns>Device response.</returns> private static Device ValidateDeviceTokenLocally(string deviceId, string deviceTokenData, string deviceToken, RequestContext requestContext) { var getTransactionServiceProfileDataRequest = new GetTransactionServiceProfileDataRequest(); TransactionServiceProfile transactionServiceProfile = requestContext.Runtime.Execute <SingleEntityDataServiceResponse <TransactionServiceProfile> >(getTransactionServiceProfileDataRequest, requestContext).Entity; int deviceTokenExpirationInDays = transactionServiceProfile.DeviceTokenExpirationInDays; // only validate device if connecting against master database bool mustValidateActiveDevice = requestContext.Runtime.Configuration.IsMasterDatabaseConnectionString; // Get the device GetDeviceDataRequest getDeviceRequest = new GetDeviceDataRequest(deviceId, mustValidateActiveDevice); Device localDevice = requestContext.Execute <SingleEntityDataServiceResponse <Device> >(getDeviceRequest).Entity; if (localDevice == null) { throw new DeviceAuthenticationException( SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_LocalDeviceAuthenticationFailed, string.Format("Device not found for device '{0}'.", deviceId)); } localDevice.Token = deviceToken; string algorithm = localDevice.TokenAlgorithm; if (string.IsNullOrEmpty(algorithm)) { // string algorithm = transactionServiceProfile.DeviceTokenAlgorithm; algorithm = "SHA256"; } if (mustValidateActiveDevice) { if (string.IsNullOrEmpty(localDevice.TokenSalt)) { throw new DeviceAuthenticationException( SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_LocalDeviceAuthenticationFailed, string.Format("TokenSalt not available for device '{0}'.", deviceId)); } if (localDevice.ActivationStatus != DeviceActivationStatus.Activated) { throw new DeviceAuthenticationException( SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_LocalDeviceAuthenticationFailed, string.Format("Device is not activated, actual state is '{0}'.", localDevice.ActivationStatus)); } // Get hashed value for device token HashDataServiceRequest hashDataServiceRequest = new HashDataServiceRequest(deviceTokenData, algorithm, localDevice.DeviceNumber, localDevice.TokenSalt); string deviceTokenHash = requestContext.Execute <HashDataServiceResponse>(hashDataServiceRequest).Data; // Validate the hashed device token with value in the database if (!string.Equals(deviceTokenHash, localDevice.TokenData)) { throw new DeviceAuthenticationException( SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_LocalDeviceAuthenticationFailed, string.Format("Device token for '{0}' record was not found.", deviceId)); } if (!localDevice.TokenIssueTime.HasValue) { throw new ConfigurationException( ConfigurationErrors.Microsoft_Dynamics_Commerce_Runtime_ActivatedDeviceMissingTokenIssueDatetime, string.Format("Activated device '{0}' does not have an associated token issue datetime.", deviceId)); } if (DateTimeOffset.Compare(DateTimeOffset.Now, localDevice.TokenIssueTime.Value.AddDays(deviceTokenExpirationInDays)) > 0) { throw new DeviceAuthenticationException( SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_DeviceTokenExpired, string.Format("Device Token for device '{0}' expired.", deviceId)); } } else { // when device comes from non-master database, it might have stale data localDevice = ConstructDeviceFromToken(deviceToken); } return(localDevice); }