From 6f1de65602d549ac298b3e080104a4e5da197fbb Mon Sep 17 00:00:00 2001 From: garrettmills Date: Tue, 3 Mar 2020 16:47:51 -0600 Subject: [PATCH] Add OwnerState and PermissionState --- app/classes/logical/UniversalPath.js | 18 ++++++++++ app/classes/metal/Host.js | 39 ++++++++++++++++++++++ app/classes/state/fs/OwnerState.js | 44 +++++++++++++++++++++++++ app/classes/state/fs/PermissionState.js | 39 ++++++++++++++++++++++ app/services/states.service.js | 3 +- 5 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 app/classes/state/fs/OwnerState.js create mode 100644 app/classes/state/fs/PermissionState.js diff --git a/app/classes/logical/UniversalPath.js b/app/classes/logical/UniversalPath.js index 1dc8b7d..1c3c08e 100644 --- a/app/classes/logical/UniversalPath.js +++ b/app/classes/logical/UniversalPath.js @@ -154,6 +154,24 @@ class UniversalPath extends Injectable { }) } + async permissions(set_level = false, recursive = false) { + if ( set_level ) { + await this._host.set_permissions_for_path(this, set_level, recursive) + } else { + return this._host.get_permissions_for_path(this) + } + } + + async ownership(set_owners = false, recursive = false) { + if ( set_owners ) { + const current = await this.ownership() + const target = {...current, ...set_owners} + await this._host.set_ownership_for_path(this, target, recursive) + } else { + return this._host.get_ownership_for_path(this) + } + } + async hash() { const line = await this._host.run_line_result(`sha256sum ${this._path}`) return line.split(' ')[0] diff --git a/app/classes/metal/Host.js b/app/classes/metal/Host.js index 3931caf..94a46dd 100644 --- a/app/classes/metal/Host.js +++ b/app/classes/metal/Host.js @@ -24,6 +24,12 @@ class Host extends Injectable { _resolve_path_command = `readlink -f "%%PATH%%"` _reboot_command = `reboot` _change_directory_command = `cd "%%PATH%%"` + _file_directory_permission_fetch_command = `stat -c %a "%%PATH%%"` + _file_directory_permission_set_command_flat = `chmod %%LEVEL%% "%%PATH%%"` + _file_directory_permission_set_command_recursive = `chmod -R %%LEVEL%% "%%PATH%%"` + _file_directory_ownership_fetch_command = `stat -c "%U:%G" "%%PATH%%"` + _file_directory_ownership_set_command_flat = `chown %%OWNERS%% "%%PATH%%"` + _file_directory_ownership_set_command_recursive = `chown -R %%OWNERS%% "%%PATH%%"` constructor(config) { super() @@ -117,6 +123,39 @@ class Host extends Injectable { return this.run_line_result(this._resolve_path_command.replace('%%PATH%%', resource_path)) } + async get_permissions_for_path(resource_path) { + resource_path = typeof resource_path === 'string' ? resource_path : resource_path.path + return this.run_line_result(this._file_directory_permission_fetch_command.replace('%%PATH%%', resource_path)) + } + + async set_permissions_for_path(resource_path, level, recursive = false) { + resource_path = typeof resource_path === 'string' ? resource_path : resource_path.path + let cmd + if ( recursive ) cmd = this._file_directory_permission_set_command_recursive + else cmd = this._file_directory_permission_set_command_flat + cmd = cmd.replace('%%PATH%%', resource_path) + cmd = cmd.replace('%%LEVEL%%', level) + await this.run(cmd) + } + + async get_ownership_for_path(resource_path) { + resource_path = typeof resource_path === 'string' ? resource_path : resource_path.path + const cmd = this._file_directory_ownership_fetch_command.replace('%%PATH%%', resource_path) + const result = await this.run_line_result(cmd) + const parts = result.split(':') + return {user: parts[0], group: parts[1]} + } + + async set_ownership_for_path(resource_path, { user, group }, recursive = false) { + resource_path = typeof resource_path === 'string' ? resource_path : resource_path.path + let cmd + if ( recursive ) cmd = this._file_directory_ownership_set_command_recursive + else cmd = this._file_directory_ownership_set_command_flat + cmd = cmd.replace('%%PATH%%', resource_path) + cmd = cmd.replace('%%OWNERS%%', `${user}:${group}`) + await this.run(cmd) + } + async get_mount_points() { const result = await this.execute(this._list_mount_points_command) if ( result.exit_code !== 0 ) { diff --git a/app/classes/state/fs/OwnerState.js b/app/classes/state/fs/OwnerState.js new file mode 100644 index 0000000..0db53c6 --- /dev/null +++ b/app/classes/state/fs/OwnerState.js @@ -0,0 +1,44 @@ +const State = require('../State') + +class OwnerState extends State { + static get services() { + return [...super.services, 'output'] + } + + async apply() { + if ( !(await this.check()) ) { + const path = await this._path() + await path.ownership(this._config.owners, !!this._config.recursive) + } + } + + async check() { + const path = await this._path() + const owners = await path.ownership() + + if ( this._config.owners.user && owners.user !== this._config.owners.user ) return false + else if ( this._config.owners.group && owners.group !== this._config.owners.group ) return false + return true + } + + async reverse() { + if ( await this.check() ) { + if (this._config.revert_to) { + const path = await this._path() + await path.ownership(this._config.revert_to, !!this._config.recursive) + } else { + this.output.warn(`Owner state does not support automatic reversal. Specify the revert_to config key for this functionality. (Host: ${this._host.name})`) + } + } + } + + async _path() { + const path = await this._host.get_path(this._config.path) + await path.classify() + if ( !path.is_valid() ) throw new Error(`Invalid path for OwnerState: ${path}`) + return path + } + +} + +module.exports = exports = OwnerState diff --git a/app/classes/state/fs/PermissionState.js b/app/classes/state/fs/PermissionState.js new file mode 100644 index 0000000..7ca141a --- /dev/null +++ b/app/classes/state/fs/PermissionState.js @@ -0,0 +1,39 @@ +const State = require('../State') + +class PermissionState extends State { + static get services() { + return [...super.services, 'output'] + } + + async apply() { + if ( !(await this.check()) ) { + const path = await this._path() + await path.permissions(this._config.level, !!this._config.recursive) + } + } + + async check() { + const path = await this._path() + const permissions = `${await path.permissions()}`.trim() + const target = `${this._config.level}`.trim() + return permissions === target + } + + async reverse() { + if ( this._config.revert_to ) { + const path = await this._path() + await path.permissions(this._config.revert_to, this._config.recursive) + } else { + this.output.warn(`Permission state does not support automatic reversal. Specify the revert_to config for this functionality. (Host: ${this._host.name})`) + } + } + + async _path() { + const path = await this._host.get_path(this._config.path) + await path.classify() + if ( !path.is_valid() ) throw new Error(`Invalid path for PermissionState: ${path}`) + return path + } +} + +module.exports = exports = PermissionState diff --git a/app/services/states.service.js b/app/services/states.service.js index 08ef2cd..fed46b1 100644 --- a/app/services/states.service.js +++ b/app/services/states.service.js @@ -12,7 +12,6 @@ const { Service } = require('flitter-di') class StatesService extends Service { static #state_map = { // TODO apache and nginx states - virtual host, reverse proxy - // TODO file/directory permissions state - chmod & chown // TODO file pack state - zip, tarball // TODO package repository states - import keys, install repository // TODO service manager states - service enabled, service installed @@ -21,6 +20,8 @@ class StatesService extends Service { 'fs.file': require('../classes/state/fs/FileState'), 'fs.directory': require('../classes/state/fs/DirectoryState'), 'fs.unpack': require('../classes/state/fs/UnpackState'), + 'fs.permission': require('../classes/state/fs/PermissionState'), + 'fs.ownership': require('../classes/state/fs/OwnerState'), 'package.present': require('../classes/state/os/PackageState'), 'package.updates': require('../classes/state/os/UpdateState'),