runcmf/runtracy

View on GitHub
src/RunTracy/Helpers/Console/WebConsoleRPCServer.php

Summary

Maintainability
C
1 day
Test Coverage
<?php
/**
 * Copyright 2016 1f7.wizard@gmail.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

namespace RunTracy\Helpers\Console;

use RunTracy\Exceptions\IncorrectUserOrPassword;

class WebConsoleRPCServer extends BaseJsonRpcServer
{
    protected $homeDirectory = '';

    protected $noLogin = false;
    protected $accounts = [];
    protected $passwordHashAlgorithm = '';

    // Authentication
    protected function authenticateUser($user, $password)
    {
        $user = trim((string) $user);
        $password = trim((string) $password);

        if ($user && $password) {
            if (isset($this->accounts[$user]) && !$this->isEmptyString($this->accounts[$user])) {
                if ($this->passwordHashAlgorithm) {
                    $password = $this->getHash($this->passwordHashAlgorithm, $password);
                }

                if ($this->isEqualStrings($password, $this->accounts[$user])) {
                    return $user . ':' . $this->getHash('sha256', $password);
                }
            }
        }

        throw new IncorrectUserOrPassword();
    }

    protected function authenticateToken($token)
    {
        if ($this->noLogin) {
            return true;
        }

        $token = trim((string) $token);
        $tokenParts = explode(':', $token, 2);

        if (count($tokenParts) == 2) {
            $user = trim((string) $tokenParts[0]);
            $passwordHash = trim((string) $tokenParts[1]);

            if ($user && $passwordHash) {
                if (isset($this->accounts[$user]) && !$this->isEmptyString($this->accounts[$user])) {
                    $realPasswordHash = $this->getHash('sha256', $this->accounts[$user]);
                    if ($this->isEqualStrings($passwordHash, $realPasswordHash)) {
                        return $user;
                    }
                }
            }
        }

        throw new IncorrectUserOrPassword();
    }

    protected function getHomeDirectory($user)
    {
        if (is_string($this->homeDirectory)) {
            if (!$this->isEmptyString($this->homeDirectory)) {
                return $this->homeDirectory;
            }
        } elseif (is_string($user) && !$this->isEmptyString($user)
            && isset($this->homeDirectory[$user])
            && !$this->isEmptyString($this->homeDirectory[$user])) {
            return $this->homeDirectory[$user];
        }

        return getcwd();
    }

    // Environment
    protected function getEnvironment()
    {
        $hostname = function_exists('gethostname') ? gethostname() : null;
        return ['path' => getcwd(), 'hostname' => $hostname];
    }

    protected function setEnvironment($environment)
    {
        $environment = !empty($environment) ? (array) $environment : [];
        $path = (isset($environment['path']) && !$this->isEmptyString($environment['path'])) ?
            $environment['path'] : $this->homeDirectory;

        if (!$this->isEmptyString($path)) {
            if (is_dir($path)) {
                if (!chdir($path)) {
                    return [
                    'output' => 'Unable to change directory to current working directory, updating current directory',
                    'environment' => $this->getEnvironment()
                    ];
                }
            } else {
                return [
                'output' => 'Current working directory not found, updating current directory',
                'environment' => $this->getEnvironment()
                ];
            }
        }
        return false;
    }

    // Initialization
    protected function initialize($token, $environment)
    {
        $user = $this->authenticateToken($token);
        $this->homeDirectory = $this->getHomeDirectory($user);
        $result = $this->setEnvironment($environment);

        if ($result) {
            return $result;
        }
        return false;
    }

    // Methods
    public function login($user, $password)
    {
        $result = ['token' => $this->authenticateUser($user, $password),
            'environment' => $this->getEnvironment()];

        $homeDirectory = $this->getHomeDirectory($user);
        if (!$this->isEmptyString($homeDirectory)) {
            if (is_dir($homeDirectory)) {
                $result['environment']['path'] = $homeDirectory;
            } else {
                $result['output'] = 'Home directory not found: '. $homeDirectory;
            }
        }

        return $result;
    }

    public function cd($token, $environment, $path)
    {
        $result = $this->initialize($token, $environment);
        if ($result) {
            return $result;
        }

        $path = trim((string) $path);
        if ($this->isEmptyString($path)) {
            $path = $this->homeDirectory;
        }

        if (!$this->isEmptyString($path)) {
            if (is_dir($path)) {
                if (!chdir($path)) {
                    return ['output' => 'cd: '. $path . ': Unable to change directory'];
                }
            } else {
                return ['output' => 'cd: '. $path . ': No such directory'];
            }
        }

        return ['environment' => $this->getEnvironment()];
    }

    public function completion($token, $environment, $pattern)
    {
        $result = $this->initialize($token, $environment);
        if ($result) {
            return $result;
        }

        $scanPath = '';
        $completionPrefix = '';
        $completion = [];

        if (!empty($pattern)) {
            if (!is_dir($pattern)) {
                $pattern = dirname($pattern);
                if ($pattern == '.') {
                    $pattern = '';
                }
            }

            if (!empty($pattern)) {
                if (is_dir($pattern)) {
                    $scanPath = $completionPrefix = $pattern;
                    if (substr($completionPrefix, -1) != '/') {
                        $completionPrefix .= '/';
                    }
                }
            } else {
                $scanPath = getcwd();
            }
        } else {
            $scanPath = getcwd();
        }

        if (!empty($scanPath)) {
            // Loading directory listing
            $completion = array_values(array_diff(scandir($scanPath), ['..', '.']));
            natsort($completion);

            // Prefix
            if (!empty($completionPrefix) && !empty($completion)) {
                foreach ($completion as &$value) {
                    $value = $completionPrefix . $value;
                }
            }

            // Pattern
            if (!empty($pattern) && !empty($completion)) {
                // For PHP version that does not support anonymous functions (available since PHP 5.3.0)
                function filterPattern($value, $pattern)
                {
                    return !strncmp($pattern, $value, strlen($pattern));
                }

                $completion = array_values(array_filter($completion, [$this, 'filterPattern']));
            }
        }

        return ['completion' => $completion];
    }

    public function run($token, $environment, $command)
    {
        $result = $this->initialize($token, $environment);
        if ($result) {
            return $result;
        }

        $output = ($command && !$this->isEmptyString($command)) ? $this->executeCommand($command) : '';
        if ($output && substr($output, -1) == "\n") {
            $output = substr($output, 0, -1);
        }

        return ['output' => $output];
    }

    // Command execution
    private function executeCommand($command)
    {
        $descriptors = [
            0 => ['pipe', 'r'], // STDIN
            1 => ['pipe', 'w'], // STDOUT
            2 => ['pipe', 'w']  // STDERR
        ];

        $process = proc_open($command . ' 2>&1', $descriptors, $pipes);
        if (!is_resource($process)) {
            return 'Can not execute command.';
        }

        // Nothing to push to STDIN
        fclose($pipes[0]);

        $output = stream_get_contents($pipes[1]);
        fclose($pipes[1]);

        $error = stream_get_contents($pipes[2]);
        fclose($pipes[2]);

        // All pipes must be closed before 'proc_close'
        $code = proc_close($process);

        if (!empty($error)) {
            $output .= ', exit wit error:' . $error . ', code: ' . $code;
        }
        return $output;
    }

    // Utilities
    private function isEmptyString($string)
    {
        return strlen($string) <= 0;
    }

    private function isEqualStrings($string1, $string2)
    {
        return strcmp($string1, $string2) == 0;
    }

    private function getHash($algorithm, $string)
    {
        return hash($algorithm, trim((string) $string));
    }
}