fix(device): check privilege on Linux (#519)

* fix(device): check privilege on Linux

* device connected if also have permission

* fix rules sh path

* refactor permission detection

* fic hasPermission condition

* fix return value
This commit is contained in:
Róbert Kiss
2017-12-21 20:33:40 +01:00
committed by László Monda
parent 969c36561b
commit c11658f1fc
11 changed files with 75 additions and 74 deletions

View File

@@ -31,7 +31,7 @@ export class AppService extends MainServiceBase {
addons: this.options.addons || false,
autoWriteConfig: this.options['auto-write-config'] || false
},
deviceConnected: this.deviceService.isConnected,
deviceConnected: this.uhkHidDeviceService.deviceConnected(),
hasPermission: this.uhkHidDeviceService.hasPermission(),
agentVersionInfo: {
version: packageJson.version,

View File

@@ -1,9 +1,8 @@
import { ipcMain } from 'electron';
import { ConfigurationReply, IpcEvents, IpcResponse, LogService } from 'uhk-common';
import { Constants, snooze, UhkHidDevice, UhkOperations } from 'uhk-usb';
import { ConfigurationReply, DeviceConnectionState, IpcEvents, IpcResponse, LogService } from 'uhk-common';
import { snooze, UhkHidDevice, UhkOperations } from 'uhk-usb';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
import { Device, devices } from 'node-hid';
import { emptyDir } from 'fs-extra';
import 'rxjs/add/observable/interval';
@@ -19,13 +18,11 @@ import { QueueManager } from './queue-manager';
/**
* IpcMain pair of the UHK Communication
* Functionality:
* - Detect device is connected or not
* - Send UserConfiguration to the UHK Device
* - Read UserConfiguration from the UHK Device
*/
export class DeviceService {
private pollTimer$: Subscription;
private connected = false;
private queueManager = new QueueManager();
constructor(private logService: LogService,
@@ -66,14 +63,6 @@ export class DeviceService {
logService.debug('[DeviceService] init success');
}
/**
* Return with true is an UHK Device is connected to the computer.
* @returns {boolean}
*/
public get isConnected(): boolean {
return this.connected;
}
/**
* Return with the actual UserConfiguration from UHK Device
* @returns {Promise<Buffer>}
@@ -102,7 +91,6 @@ export class DeviceService {
}
public close(): void {
this.connected = false;
this.stopPollTimer();
this.logService.info('[DeviceService] Device connection checker stopped.');
}
@@ -153,15 +141,16 @@ export class DeviceService {
this.pollTimer$ = Observable.interval(1000)
.startWith(0)
.map(() => {
return devices().some((dev: Device) => dev.vendorId === Constants.VENDOR_ID &&
dev.productId === Constants.PRODUCT_ID);
})
.map(() => this.device.deviceConnected())
.distinctUntilChanged()
.do((connected: boolean) => {
this.connected = connected;
this.win.webContents.send(IpcEvents.device.deviceConnectionStateChanged, connected);
this.logService.info(`[DeviceService] Device connection state changed to: ${connected}`);
const response: DeviceConnectionState = {
connected,
hasPermission: this.device.hasPermission()
};
this.win.webContents.send(IpcEvents.device.deviceConnectionStateChanged, response);
this.logService.info('[DeviceService] Device connection state changed to:', response);
})
.subscribe();
}

View File

@@ -10,7 +10,7 @@ export class SudoService {
constructor(private logService: LogService) {
if (isDev) {
this.rootDir = path.join(path.join(process.cwd(), process.argv[1]), '..');
this.rootDir = path.join(path.join(process.cwd(), process.argv[1]), '../../../../');
} else {
this.rootDir = path.dirname(app.getAppPath());
}
@@ -40,11 +40,12 @@ export class SudoService {
name: 'Setting UHK access rules'
};
const command = `sh ${scriptPath}`;
console.log(command);
this.logService.debug('[SudoService] Set privilege command: ', command);
sudo.exec(command, options, (error: any) => {
const response = new IpcResponse();
if (error) {
this.logService.error('[SudoService] Error when set privilege: ', error);
response.success = false;
response.error = error;
} else {

View File

@@ -0,0 +1,4 @@
export interface DeviceConnectionState {
connected: boolean;
hasPermission: boolean;
}

View File

@@ -4,3 +4,4 @@ export * from './ipc-response';
export * from './app-start-info';
export * from './configuration-reply';
export * from './version-information';
export * from './device-connection-state';

View File

@@ -25,6 +25,7 @@ export class UhkHidDevice {
* @private
*/
private _device: HID;
private _hasPermission = false;
constructor(private logService: LogService) {
}
@@ -38,8 +39,18 @@ export class UhkHidDevice {
*/
public hasPermission(): boolean {
try {
devices();
return true;
if (this._hasPermission) {
return true;
}
if (!this.deviceConnected()) {
return true;
}
this._hasPermission = this.getDevice() !== null;
this.close();
return this._hasPermission;
} catch (err) {
this.logService.error('[UhkHidDevice] hasPermission', err);
}
@@ -47,6 +58,21 @@ export class UhkHidDevice {
return false;
}
/**
* Return with true is an UHK Device is connected to the computer.
* @returns {boolean}
*/
public deviceConnected(): boolean {
const connected = devices().some((dev: Device) => dev.vendorId === Constants.VENDOR_ID &&
dev.productId === Constants.PRODUCT_ID);
if (!connected) {
this._hasPermission = false;
}
return connected;
}
/**
* Send data to the UHK device and wait for the response.
* Throw an error when 1st byte of the response is not 0
@@ -225,7 +251,7 @@ export class UhkHidDevice {
}
}
function kbootKommandName(module: ModuleSlotToI2cAddress): string {
function kbootKommandName(module: ModuleSlotToI2cAddress): string {
switch (module) {
case ModuleSlotToI2cAddress.leftHalf:
return 'leftHalf';

View File

@@ -1,7 +1,7 @@
import { Injectable, NgZone } from '@angular/core';
import { Action, Store } from '@ngrx/store';
import { IpcEvents, IpcResponse, LogService } from 'uhk-common';
import { DeviceConnectionState, IpcEvents, IpcResponse, LogService } from 'uhk-common';
import { AppState } from '../store';
import { IpcCommonRenderer } from './ipc-common-renderer';
import {
@@ -47,7 +47,7 @@ export class DeviceRendererService {
}
private registerEvents(): void {
this.ipcRenderer.on(IpcEvents.device.deviceConnectionStateChanged, (event: string, arg: boolean) => {
this.ipcRenderer.on(IpcEvents.device.deviceConnectionStateChanged, (event: string, arg: DeviceConnectionState) => {
this.dispachStoreAction(new ConnectionStateChangedAction(arg));
});

View File

@@ -1,5 +1,5 @@
import { Action } from '@ngrx/store';
import { IpcResponse, type } from 'uhk-common';
import { DeviceConnectionState, IpcResponse, type } from 'uhk-common';
const PREFIX = '[device] ';
@@ -8,7 +8,6 @@ export const ActionTypes = {
SET_PRIVILEGE_ON_LINUX: type(PREFIX + 'set privilege on linux'),
SET_PRIVILEGE_ON_LINUX_REPLY: type(PREFIX + 'set privilege on linux reply'),
CONNECTION_STATE_CHANGED: type(PREFIX + 'connection state changed'),
PERMISSION_STATE_CHANGED: type(PREFIX + 'permission state changed'),
SAVE_CONFIGURATION: type(PREFIX + 'save configuration'),
SAVE_CONFIGURATION_REPLY: type(PREFIX + 'save configuration reply'),
SAVING_CONFIGURATION: type(PREFIX + 'saving configuration'),
@@ -39,14 +38,7 @@ export class SetPrivilegeOnLinuxReplyAction implements Action {
export class ConnectionStateChangedAction implements Action {
type = ActionTypes.CONNECTION_STATE_CHANGED;
constructor(public payload: boolean) {
}
}
export class PermissionStateChangedAction implements Action {
type = ActionTypes.PERMISSION_STATE_CHANGED;
constructor(public payload: boolean) {
constructor(public payload: DeviceConnectionState) {
}
}
@@ -121,7 +113,6 @@ export type Actions
= SetPrivilegeOnLinuxAction
| SetPrivilegeOnLinuxReplyAction
| ConnectionStateChangedAction
| PermissionStateChangedAction
| ShowSaveToKeyboardButtonAction
| SaveConfigurationAction
| SaveConfigurationReplyAction

View File

@@ -26,7 +26,6 @@ import { AppUpdateRendererService } from '../../services/app-update-renderer.ser
import {
ActionTypes as DeviceActions,
ConnectionStateChangedAction,
PermissionStateChangedAction,
SaveToKeyboardSuccessAction
} from '../actions/device';
import { AppState, autoWriteUserConfiguration } from '../index';
@@ -64,8 +63,10 @@ export class ApplicationEffects {
this.logService.debug('[AppEffect][processStartInfo] payload:', appInfo);
return [
new ApplyCommandLineArgsAction(appInfo.commandLineArgs),
new ConnectionStateChangedAction(appInfo.deviceConnected),
new PermissionStateChangedAction(appInfo.hasPermission),
new ConnectionStateChangedAction({
connected: appInfo.deviceConnected,
hasPermission: appInfo.hasPermission
}),
new UpdateAgentVersionInformationAction(appInfo.agentVersionInfo)
];
});

View File

@@ -13,12 +13,11 @@ import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/withLatestFrom';
import 'rxjs/add/operator/switchMap';
import { IpcResponse, NotificationType, UhkBuffer, UserConfiguration } from 'uhk-common';
import { DeviceConnectionState, IpcResponse, NotificationType, UhkBuffer, UserConfiguration } from 'uhk-common';
import {
ActionTypes,
ConnectionStateChangedAction,
HideSaveToKeyboardButton,
PermissionStateChangedAction,
SaveConfigurationAction,
SaveConfigurationReplyAction,
SaveToKeyboardSuccessAction,
@@ -47,35 +46,25 @@ export class DeviceEffects {
deviceConnectionStateChange$: Observable<Action> = this.actions$
.ofType<ConnectionStateChangedAction>(ActionTypes.CONNECTION_STATE_CHANGED)
.map(action => action.payload)
.do((connected: boolean) => {
if (connected) {
.do((state: DeviceConnectionState) => {
if (!state.hasPermission) {
this.router.navigate(['/privilege']);
}
else if (state.connected) {
this.router.navigate(['/']);
}
else {
this.router.navigate(['/detection']);
}
})
.switchMap((connected: boolean) => {
if (connected) {
.switchMap((state: DeviceConnectionState) => {
if (state.connected && state.hasPermission) {
return Observable.of(new LoadConfigFromDeviceAction());
}
return Observable.empty();
});
@Effect({dispatch: false})
permissionStateChange$: Observable<boolean> = this.actions$
.ofType<PermissionStateChangedAction>(ActionTypes.PERMISSION_STATE_CHANGED)
.map(action => action.payload)
.do((hasPermission: boolean) => {
if (hasPermission) {
this.router.navigate(['/detection']);
}
else {
this.router.navigate(['/privilege']);
}
});
@Effect({dispatch: false})
setPrivilegeOnLinux$: Observable<Action> = this.actions$
.ofType(ActionTypes.SET_PRIVILEGE_ON_LINUX)
@@ -90,8 +79,10 @@ export class DeviceEffects {
.mergeMap((response: any) => {
if (response.success) {
return [
new ConnectionStateChangedAction(true),
new PermissionStateChangedAction(true)
new ConnectionStateChangedAction({
connected: true,
hasPermission: true
})
];
}
return [

View File

@@ -3,8 +3,8 @@ import { Action } from '@ngrx/store';
import {
ActionTypes,
ConnectionStateChangedAction,
PermissionStateChangedAction,
SaveConfigurationAction, UpdateFirmwareFailedAction
SaveConfigurationAction,
UpdateFirmwareFailedAction
} from '../actions/device';
import { ActionTypes as AppActions, ElectronMainLogReceivedAction } from '../actions/app';
import { initProgressButtonState, ProgressButtonState } from './progress-button-state';
@@ -30,17 +30,14 @@ export const initialState: State = {
export function reducer(state = initialState, action: Action) {
switch (action.type) {
case ActionTypes.CONNECTION_STATE_CHANGED:
case ActionTypes.CONNECTION_STATE_CHANGED: {
const data = (<ConnectionStateChangedAction>action).payload;
return {
...state,
connected: (<ConnectionStateChangedAction>action).payload
};
case ActionTypes.PERMISSION_STATE_CHANGED:
return {
...state,
hasPermission: (<PermissionStateChangedAction>action).payload
connected: data.connected,
hasPermission: data.hasPermission
};
}
case ActionTypes.SAVING_CONFIGURATION: {
return {