/// <summary> /// Authenticate the user. /// </summary> /// <param name="request">The device activation request.</param> /// <returns>The device activation response.</returns> private static StaffLogOnRealtimeResponse LogOnUser(StaffLogOnRealtimeRequest request) { // Mimic the real time TS logon Employee employee = EmployeeLogOnStore(request, request.RequestContext.GetChannelConfiguration()); return(new StaffLogOnRealtimeResponse(employee)); }
/// <summary> /// Authenticate the user. /// </summary> /// <param name="request">The device activation request.</param> /// <returns>The device activation response.</returns> private static StaffLogOnRealtimeResponse LogOnUser(StaffLogOnRealtimeRequest request) { Employee employee; var transactionService = new TransactionService.TransactionServiceClient(request.RequestContext); long channelId = request.ChannelId.GetValueOrDefault(0); long terminalId = request.TerminalRecordId.GetValueOrDefault(0); bool logOntoStore = request.ChannelId.HasValue; bool skipPasswordVerification = string.IsNullOrWhiteSpace(request.StaffPassword); employee = transactionService.RetailServerStaffLogOn(request.StaffId, channelId, terminalId, request.StaffPassword, logOntoStore, skipPasswordVerification); return(new StaffLogOnRealtimeResponse(employee)); }
/// <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> /// Authorizes the staff using the real time service. /// </summary> /// <param name="verifyEmployeeLocalDatabaseFallbackDelegate">A delegate that performs the verification checks against the local database that is used in case local fallback is required.</param> /// <returns>The verified employee.</returns> /// <exception cref="InvalidOperationException">Due to invalid verification type.</exception> /// <exception cref="HeadquarterTransactionServiceException">When the real time service process the request but returns an error.</exception> /// <exception cref="CommunicationException">When it was not possible to communicate with the real time service.</exception> public Employee VerifyEmployeeRealTimeService(Func <Employee> verifyEmployeeLocalDatabaseFallbackDelegate) { ThrowIf.Null(verifyEmployeeLocalDatabaseFallbackDelegate, "verifyEmployeeLocalDatabaseFallbackDelegate"); Employee employee = null; if (this.verificationType == SecurityVerificationType.Authentication) { throw new InvalidOperationException("Real time service does not support authentication only. Please use Authentication and Authorization for real time calls."); } // if we don't have a stored result, or the stored result is for a call with different paramters, then call TS if (this.realTimeStaffVerificationEmployee == null) { // retail operation, logon key, extra data and all values from device object but channelid and terminal are not used by transaction service StaffLogOnRealtimeRequest request = new StaffLogOnRealtimeRequest( this.ChannelId, this.TerminalRecordId, this.StaffId, this.password); bool mustFallbackToLocal = false; try { employee = this.realTimeStaffVerificationEmployee = this.context.Execute <StaffLogOnRealtimeResponse>(request).Employee; } catch (UserAuthenticationException) { throw; } catch (UserAuthorizationException) { throw; } catch (Exception exception) { // if we could not make the real time call, check if we can validate user locally if (this.MustFallbackToLocalAuthorization()) { RetailLogger.Instance.CrtServicesStaffAuthorizationServiceRetalTimeServiceFailure(exception); // and fallback to local verification mustFallbackToLocal = true; } else { RetailLogger.Log.CrtServicesStaffAuthorizationServiceUserNotAuthorizedToFallbackToLocalDatabase(this.StaffId, exception); // because user does not have permission to perform a local authentication/authorization flow // we need to fail authorization throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthorizationFailed, exception, "User not authorized"); } } // if we fallback, do not cache result since it does not come from transaction service if (mustFallbackToLocal) { employee = verifyEmployeeLocalDatabaseFallbackDelegate(); } } else { employee = this.realTimeStaffVerificationEmployee; } return(employee); }