Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring, examples, tests #3

Merged
merged 5 commits into from
Dec 22, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions checkkeyspresent.php
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Loading