/// <summary> /// Executes the workflow to do user authentication. /// </summary> /// <param name="request">The request.</param> /// <returns>The response.</returns> protected override GetEmployeePermissionsResponse Process(GetEmployeePermissionsRequest request) { ThrowIf.Null(request, "request"); GetEmployeesServiceRequest getEmployeeRequest = new GetEmployeesServiceRequest(request.StaffId, QueryResultSettings.SingleRecord); GetEmployeesServiceResponse employeeResponse = this.Context.Execute <GetEmployeesServiceResponse>(getEmployeeRequest); Employee employee = employeeResponse.Employees.SingleOrDefault(); if (employee == null) { return(new GetEmployeePermissionsResponse(employee)); } // Check if the requested Employee object is same as logged-on user. // If not, check staff have manager permission. // If the staff is not manager, do not return permissions if (!string.Equals(request.StaffId, this.Context.GetPrincipal().UserId)) { try { this.Context.Execute <Response>(new CheckAccessIsManagerServiceRequest()); } catch (UserAuthorizationException) { return(new GetEmployeePermissionsResponse(employee)); } } GetEmployeePermissionsDataRequest permissionsDataRequest = new GetEmployeePermissionsDataRequest(request.StaffId, new ColumnSet()); employee.Permissions = this.Context.Execute <SingleEntityDataServiceResponse <EmployeePermissions> >(permissionsDataRequest).Entity; return(new GetEmployeePermissionsResponse(employee)); }
/// <summary> /// Gets the employee permission details. /// </summary> /// <param name="context">The request context.</param> /// <param name="staffId">The staff identifier.</param> /// <returns> /// The employee permission request. /// </returns> /// <exception cref="UserAuthenticationException">When the employee does not exists.</exception> public static EmployeePermissions GetEmployeePermissions(RequestContext context, string staffId) { ThrowIf.Null(context, "context"); GetEmployeeDataRequest dataRequest = new GetEmployeeDataRequest(staffId, QueryResultSettings.SingleRecord); Employee employee = context.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); } // Check if the requested Employee object is same as logged-on user. // If not, check staff have manager permission. if (!string.Equals(staffId, context.GetPrincipal().UserId)) { var checkAccessRequest = new CheckAccessIsManagerServiceRequest(); context.Execute <NullResponse>(checkAccessRequest); } GetEmployeePermissionsDataRequest permissionsDataRequest = new GetEmployeePermissionsDataRequest(staffId, new ColumnSet()); employee.Permissions = context.Execute <SingleEntityDataServiceResponse <EmployeePermissions> >(permissionsDataRequest).Entity; return(employee.Permissions); }
private static void OnGetEmployeePermissionsExecuting(GetEmployeePermissionsDataRequest request) { // Check if the requested Employee object is same as logged-on user. // If not, check staff have manager permission. if (!string.Equals(request.StaffId, request.RequestContext.GetPrincipal().UserId)) { var checkAccessRequest = new CheckAccessIsManagerServiceRequest(); request.RequestContext.Execute <Response>(checkAccessRequest); } }
/// <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> /// Gets the current shift and sets it on the context. /// </summary> /// <param name="context">The context.</param> private static void PopulateContextWithShiftInformation(RequestContext context) { ShiftDataQueryCriteria criteria = new ShiftDataQueryCriteria { ChannelId = context.GetPrincipal().ChannelId, TerminalId = context.GetTerminal().TerminalId, StaffId = string.IsNullOrWhiteSpace(context.GetPrincipal().OriginalUserId) ? context.GetPrincipal().UserId : context.GetPrincipal().OriginalUserId, Status = (int)ShiftStatus.Open, SearchByStaffId = true, SearchByCurrentStaffId = true, SearchByTerminalId = true, SearchByCurrentTerminalId = true }; // Get original staff id (not a manager's) during elevated permission operations. GetEmployeePermissionsDataRequest permissionsDataRequest = new GetEmployeePermissionsDataRequest(criteria.StaffId, new ColumnSet()); EmployeePermissions employeePermissions = context.Runtime.Execute <SingleEntityDataServiceResponse <EmployeePermissions> >( permissionsDataRequest, context, skipRequestTriggers: true).Entity; if (employeePermissions != null) { criteria.IncludeSharedShifts = employeePermissions.HasManagerPrivileges || employeePermissions.AllowManageSharedShift || employeePermissions.AllowUseSharedShift || employeePermissions.AllowMultipleShiftLogOn; } GetShiftDataDataRequest dataServiceRequest = new GetShiftDataDataRequest(criteria, QueryResultSettings.SingleRecord); Shift shift = context.Runtime.Execute <EntityDataServiceResponse <Shift> >(dataServiceRequest, context, skipRequestTriggers: true).PagedEntityCollection.Results.FirstOrDefault(); if (shift != null) { // TerminalId is the identifier of the terminal which creates the shift context.GetPrincipal().ShiftId = shift.ShiftId; context.GetPrincipal().ShiftTerminalId = shift.TerminalId; } }
/// <summary> /// Gets the employee permission group for the staff identifier. /// </summary> /// <param name="request">The data service request.</param> /// <returns>The data service response.</returns> private SingleEntityDataServiceResponse <EmployeePermissions> GetEmployeePermissions(GetEmployeePermissionsDataRequest request) { EmployeeDataManager dataManager = this.GetDataManagerInstance(request.RequestContext); var result = dataManager.GetEmployeePermissions(request.StaffId, request.ColumnSet); return(new SingleEntityDataServiceResponse <EmployeePermissions>(result)); }
/// <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> /// Authenticates and authorizes the user. /// </summary> /// <param name="request">UserAuthenticationRequest request object.</param> /// <param name="device">The device.</param> /// <returns>Employee object.</returns> internal static Employee AuthenticateAndAuthorizeUser(UserAuthenticationRequest request, Device device) { // Authenticate Employee employee; string staffId; // in extended authentication cases, this value might be empty RequestContext executionContext = request.RequestContext; if (device != null && request.RequestContext.GetPrincipal().IsChannelAgnostic) { executionContext = CreateRequestContext(string.Empty, device, request.RequestContext.Runtime); } UserLogOnServiceRequest authenticateServiceRequest = new UserLogOnServiceRequest( request.StaffId, request.Password, request.Credential, request.GrantType, request.AdditionalAuthenticationData); UserLogOnServiceResponse response = executionContext.Execute <UserLogOnServiceResponse>(authenticateServiceRequest); staffId = response.StaffId; // Authorize only if this was for elevate user if (request.RetailOperation != RetailOperation.None) { var authorizeStaffRequest = new StaffAuthorizationServiceRequest( request.RequestContext.GetPrincipal().UserId, request.RetailOperation); bool currentUserAlreadyAuthorizedForOperation = true; try { executionContext.Execute <StaffAuthorizationServiceResponse>(authorizeStaffRequest); } catch (UserAuthorizationException authorizationException) { if (string.Equals(authorizationException.ErrorResourceId, SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthorizationFailed.ToString(), StringComparison.Ordinal)) { currentUserAlreadyAuthorizedForOperation = false; } } if (currentUserAlreadyAuthorizedForOperation) { RetailLogger.Log.CrtWorkflowUserAuthenticationUserAlreadyHasAccessToTheTargetedOperation(request.RequestContext.GetPrincipal().UserId, request.RetailOperation.ToString()); throw new UserAuthorizationException(SecurityErrors.Microsoft_Dynamics_Commerce_Runtime_AuthorizationFailed, "The current user is already authorized to perform the targeted operation."); } authorizeStaffRequest = new StaffAuthorizationServiceRequest( staffId, request.RetailOperation); StaffAuthorizationServiceResponse authorizationResponse = executionContext.Execute <StaffAuthorizationServiceResponse>(authorizeStaffRequest); employee = authorizationResponse.Employee; } else { // The employee has already been authenticated above. employee = new Employee() { StaffId = staffId }; // if we created a new context, then we need to update it here with the staff id executionContext = CreateRequestContext(staffId, device, request.RequestContext.Runtime); // This is needed for offline scenarios/logon because as opposed to online/RS in CRT there is no user look up when the identity is presented on API calls. GetEmployeePermissionsDataRequest permissionsDataRequest = new GetEmployeePermissionsDataRequest(staffId, new ColumnSet()); employee.Permissions = executionContext.Execute <SingleEntityDataServiceResponse <EmployeePermissions> >(permissionsDataRequest).Entity; } LogAuthenticationRequest(executionContext, employee.StaffId, AuthenticationStatus.Success, AuthenticationOperation.CreateToken); return(employee); }