Skip to content

Commit

Permalink
Merge pull request #3 from Bit-Wasp/bump2
Browse files Browse the repository at this point in the history
Refactoring, examples, tests
  • Loading branch information
afk11 committed Dec 22, 2018
2 parents c5e2a6b + 3464aa6 commit 55323d3
Show file tree
Hide file tree
Showing 20 changed files with 556 additions and 174 deletions.
14 changes: 14 additions & 0 deletions checkkeyspresent.php
@@ -0,0 +1,14 @@
<?php
$required = [1=>0, 2=>0];

$tests = [
[0=>0, 1=>0, 2=>0],
[1=>0, 2=>0, 3=>0],
];

foreach ($tests as $test) {
$missingKeys = array_diff_key($required, $test);
if (count($missingKeys) > 0) {
echo "Missing keys " . implode(" ", $missingKeys) . PHP_EOL;
}
}
3 changes: 0 additions & 3 deletions composer.json
Expand Up @@ -19,9 +19,6 @@
"BitWasp\\Test\\PinEntry\\": "test/unit"
}
},
"require": {
"guzzlehttp/streams": "^3.0"
},
"require-dev": {
"phpunit/phpunit": "^6.0.0",
"squizlabs/php_codesniffer": "^3.2",
Expand Down
9 changes: 9 additions & 0 deletions examples/dummy.php
@@ -0,0 +1,9 @@
<?php
require __DIR__ . "/../vendor/autoload.php";
use BitWasp\PinEntry\PinEntry;
use BitWasp\PinEntry\Process\DebugDecorator;
use BitWasp\PinEntry\Process\Process;

$pinEntry = new PinEntry(new DebugDecorator(new Process("/usr/bin/pinentry")));
$pin = $pinEntry->getInfo("dummy");
echo "got pin {$pin}\n";
13 changes: 9 additions & 4 deletions examples/getpid.php
@@ -1,7 +1,12 @@
<?php
require "vendor/autoload.php";
require __DIR__ . "/../vendor/autoload.php";
use BitWasp\PinEntry\PinEntry;
use BitWasp\PinEntry\Process\Process;
use BitWasp\PinEntry\Process\DebugDecorator;

$pinEntry = new PinEntry("/usr/bin/pinentry");
$pin = $pinEntry->getInfo("pid");
echo "got pin {$pin}\n";
$pinEntry = new PinEntry(new DebugDecorator(new Process("/usr/bin/pinentry")));
$pin = $pinEntry->getPID();
echo "got PID {$pin}\n";

$pin = $pinEntry->getVersion();
echo "got version {$pin}\n";
37 changes: 37 additions & 0 deletions examples/getpin.php
@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
require __DIR__ . "/../vendor/autoload.php";
use BitWasp\PinEntry\PinEntry;
use BitWasp\PinEntry\PinValidation\PinValidatorInterface;
use BitWasp\PinEntry\Process\DebugDecorator;
use BitWasp\PinEntry\Process\Process;
use BitWasp\PinEntry\PinRequest;


$request = new PinRequest();
$request->withTitle("Enter that secure pin of yours");
$request->withDesc("Your pin is required in order to proceed");
$request->withPrompt("promptpromptprompt");

$pinEntry = new PinEntry(new DebugDecorator(new Process("/usr/bin/pinentry")));

class SixDigitPinValidator implements PinValidatorInterface
{
public function validate(string $input, string &$error = null): bool
{
if ($input !== (string)(int)$input) {
$error = "Pin should be an integer, with no special characters";
return false;
}
if (strlen($input) != 6) {
$error = "PIN must be 6 digits";
return false;
}
return true;
}
}

$validator = new SixDigitPinValidator();
echo "call getpin\n";
$pin = $pinEntry->getPin($request, $validator);
echo "got pin {$pin}\n";
6 changes: 3 additions & 3 deletions examples/getversion.php
@@ -1,8 +1,8 @@
<?php
require "vendor/autoload.php";
require __DIR__ . "/../vendor/autoload.php";
use BitWasp\PinEntry\PinEntry;
use BitWasp\PinEntry\PinRequest;
use BitWasp\PinEntry\Process\Process;

$pinEntry = new PinEntry("/usr/bin/pinentry");
$pinEntry = new PinEntry(new Process("/usr/bin/pinentry"));
$pin = $pinEntry->getInfo("version");
echo "got pin {$pin}\n";
63 changes: 63 additions & 0 deletions src/Assuan/Assuan.php
@@ -0,0 +1,63 @@
<?php

declare(strict_types=1);

namespace BitWasp\PinEntry\Assuan;

use BitWasp\PinEntry\Exception\RemotePinEntryException;
use BitWasp\PinEntry\Process\ProcessInterface;
use BitWasp\PinEntry\Response;

class Assuan
{
/**
* @param ProcessInterface $process
* @return Response
* @throws RemotePinEntryException
*/
public function parseResponse(ProcessInterface $process): Response
{
$data = [];
$statuses = [];
$comments = [];
$okMsg = null;

for (;;) {
$buffer = $process->recv();
foreach (explode("\n", $buffer) as $piece) {
if (substr($piece, 0, 4) === "ERR ") {
$c = explode(" ", $piece, 3);
// don't change state, it's only a single line
throw new RemotePinEntryException($c[2], (int)$c[1]);
}

$prefix = substr($piece, 0, 2);
if ($prefix === "D ") {
$data[] = substr($piece, 2);
} else if ($prefix === "S ") {
$statuses[] = substr($piece, 2);
} else if ($prefix === "# ") {
// don't change state, it's only a single line
$comments[] = substr($piece, 2);
} else if ($prefix === "OK") {
if (strlen($piece) > 2) {
$okMsg = substr($piece, 3);
}
break 2;
}
}
}

return new Response($data, $statuses, $comments, $okMsg);
}

/**
* @param ProcessInterface $process
* @param string $command
* @param string|null $params
*/
public function send(ProcessInterface $process, string $command, string $params = null)
{
$process->send(sprintf("%s%s\n", $command, $params ? " {$params}" : ""));
}
}
4 changes: 4 additions & 0 deletions src/Exception/RemotePinEntryException.php
Expand Up @@ -4,6 +4,10 @@

namespace BitWasp\PinEntry\Exception;

/**
* This class captures a protocol error returned by
* the pinetry program
*/
class RemotePinEntryException extends PinEntryException
{

Expand Down
78 changes: 62 additions & 16 deletions src/PinEntry.php
Expand Up @@ -4,7 +4,9 @@

namespace BitWasp\PinEntry;

use BitWasp\PinEntry\Assuan\Assuan;
use BitWasp\PinEntry\Exception\PinEntryException;
use BitWasp\PinEntry\PinValidation\PinValidatorInterface;
use BitWasp\PinEntry\Process\ProcessInterface;

class PinEntry
Expand All @@ -14,37 +16,81 @@ class PinEntry
*/
private $process;

public function __construct(
ProcessInterface $process
) {
$msg = $process->waitFor("OK");
if ($msg !== "OK Pleased to meet you\n") {
/**
* @var Assuan
*/
private $assuan;

public function __construct(ProcessInterface $process, Assuan $assuan = null)
{
$response = $process->recv();
if ($response !== "OK Pleased to meet you\n") {
throw new PinEntryException("First message from pinentry did not match expected value");
}
$this->process = $process;
$this->assuan = $assuan ?: new Assuan();
}

/**
* @param string $key
* @param string $value
* @return Response
* @throws Exception\RemotePinEntryException
*/
public function setOption(string $key, string $value): Response
{
$this->assuan->send($this->process, Command::OPTION, "{$key} {$value}");
$msg = $this->assuan->parseResponse($this->process);
return $msg;
}

public function getPID(): int
{
return (int) $this->getInfo('pid');
}

public function getVersion(): string
{
return $this->getInfo('version');
}

public function getInfo(string $type): string
{
$this->process->send(Command::GETINFO . " {$type}\n");
$msg = $this->process->waitFor("D");
return $msg;
$response = $this->assuan->parseResponse($this->process);

if (empty($response->getData())) {
throw new \RuntimeException("expecting info in response");
}
list ($result) = $response->getData();

return $result;
}

public function getPin(PinRequest $request): string
public function getPin(PinRequest $request, PinValidatorInterface $pinValidator): string
{
foreach ($request->getCommands() as $command => $param) {
$this->process->send("{$command} {$param}\n");
$this->process->waitFor("OK");
$this->assuan->send($this->process, $command, $param);
$this->assuan->parseResponse($this->process);
}

foreach ($request->getOptions() as $option => $value) {
$this->process->send(Command::OPTION . " {$option} {$value}\n");
$this->process->waitFor("OK");
$error = null;
$pin = '';
while (!$pinValidator->validate($pin, $error)) {
if ($pin !== '') {
$this->assuan->send($this->process, Command::SETERROR, $error);
$this->assuan->parseResponse($this->process);
}

$this->assuan->send($this->process, Command::GETPIN);
$response = $this->assuan->parseResponse($this->process);
if (empty($response->getData())) {
throw new \RuntimeException("expecting pin in response");
}

list ($pin) = $response->getData();
}

$this->process->send(Command::GETPIN . "\n");
$msg = $this->process->waitFor("D");
return $msg;
return $pin;
}
}
45 changes: 5 additions & 40 deletions src/PinRequest.php
Expand Up @@ -7,51 +7,20 @@
class PinRequest
{
/**
* @var string[]|int[]
* keyed by the command, here we store a command => param map
* @var string[]
*/
private $commands = [];

/**
* @var string[]|int[]
*/
private $options = [];

public function withOption(string $key, $value)
{
$this->options[$key] = $value;
return $this;
}

public function hasOption(string $key): bool
{
return array_key_exists($key, $this->options);
}

public function getOption(string $key)
{
if ($this->hasOption($key)) {
return $this->options[$key];
}
return null;
}

/**
* @return string[]|int[]
*/
public function getOptions(): array
{
return $this->options;
}

/**
* @return string[]|int[]
* @return string[]
*/
public function getCommands(): array
{
return $this->commands;
}

private function withCommand(string $command, $param)
private function withCommand(string $command, string $param)
{
$this->commands[$command] = $param;
}
Expand Down Expand Up @@ -133,10 +102,6 @@ public function getRepeat()
return $this->getCommand(Command::SETREPEAT);
}

/**
* @param string $repeatError
* @return $this
*/
public function withRepeatError(string $repeatError)
{
$this->withCommand(Command::SETREPEATERROR, $repeatError);
Expand Down Expand Up @@ -267,7 +232,7 @@ public function getQualityBarTooltip()

public function withTimeout(int $timeout)
{
$this->withCommand(Command::SETTIMEOUT, $timeout);
$this->withCommand(Command::SETTIMEOUT, (string) $timeout);
return $this;
}

Expand Down
15 changes: 15 additions & 0 deletions src/PinValidation/PinValidatorInterface.php
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace BitWasp\PinEntry\PinValidation;

/**
* This interface must be implemented for the desired type of PIN.
* It uses the return value of validate to indicate a positive/negative
* outcome, and upon failure, error message will be written to $errorMsg
*/
interface PinValidatorInterface
{
public function validate(string $input, string &$errorMsg = null): bool;
}
9 changes: 1 addition & 8 deletions src/Process/DebugDecorator.php
Expand Up @@ -24,18 +24,11 @@ public function close(): bool

public function recv(): string
{
echo sprintf("%s()\n", __METHOD__);
$recv = $this->process->recv();
return $recv;
}

public function waitFor(string $text)
{
echo sprintf("%s(%s)\n", __METHOD__, trim($text));
$recv = $this->process->waitFor($text);
echo sprintf("%s(%s) received\n%s\n", __METHOD__, trim($text), $recv);
return $recv;
}

public function send(string $data)
{
echo sprintf("%s(%s)\n", __METHOD__, trim($data));
Expand Down

0 comments on commit 55323d3

Please sign in to comment.