Merge pull request #1131 from pixelfed/frontend-ui-refactor

Frontend ui refactor
This commit is contained in:
daniel 2019-04-06 16:50:10 -06:00 committed by GitHub
commit fb5b41d6a2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 53 additions and 1632 deletions

View file

@ -96,4 +96,33 @@ class AvatarController extends Controller
return $avatarpath;
}
public function deleteAvatar(Request $request)
{
$user = Auth::user();
$profile = $user->profile;
$avatar = $profile->avatar;
if($avatar->media_path == 'public/avatars/default.png' || $avatar->thumb_path == 'public/avatars/default.png') {
return;
}
if(is_file(storage_path('app/' . $avatar->media_path))) {
@unlink(storage_path('app/' . $avatar->media_path));
}
if(is_file(storage_path('app/' . $avatar->thumb_path))) {
@unlink(storage_path('app/' . $avatar->thumb_path));
}
$avatar->media_path = 'public/avatars/default.png';
$avatar->thumb_path = 'public/avatars/default.png';
$avatar->change_count = $avatar->change_count + 1;
$avatar->save();
Cache::forget('avatar:' . $avatar->profile_id);
return response()->json(200);
}
}

View file

@ -2,6 +2,7 @@
namespace App\Jobs\AvatarPipeline;
use Cache;
use App\Avatar;
use App\Profile;
use Carbon\Carbon;
@ -10,6 +11,7 @@ use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Str;
use Image as Intervention;
class AvatarOptimize implements ShouldQueue
@ -60,6 +62,7 @@ class AvatarOptimize implements ShouldQueue
$avatar->change_count = ++$avatar->change_count;
$avatar->last_processed_at = Carbon::now();
$avatar->save();
Cache::forget('avatar:' . $avatar->profile_id);
$this->deleteOldAvatar($avatar->media_path, $this->current);
} catch (Exception $e) {
}
@ -67,7 +70,7 @@ class AvatarOptimize implements ShouldQueue
protected function deleteOldAvatar($new, $current)
{
if (storage_path('app/'.$new) == $current || $current == 'public/avatars/default.png') {
if (storage_path('app/'.$new) == $current || Str::endsWith($current, 'avatars/default.png')) {
return;
}
if (is_file($current)) {

View file

@ -4,10 +4,6 @@ namespace App\Jobs\AvatarPipeline;
use App\Avatar;
use App\Profile;
use App\Util\Identicon\Preprocessor\HashPreprocessor;
use Bitverse\Identicon\Color\Color;
use Bitverse\Identicon\Generator\RingsGenerator;
use Bitverse\Identicon\Identicon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
@ -45,64 +41,7 @@ class CreateAvatar implements ShouldQueue
public function handle()
{
$profile = $this->profile;
$username = $profile->username;
$generator = new RingsGenerator();
$generator->setBackgroundColor(Color::parseHex('#FFFFFF'));
$identicon = new Identicon(new HashPreprocessor('sha256'), $generator);
$hash = $username.str_random(12);
$icon = $identicon->getIcon($hash);
try {
$baseDir = storage_path('app/public/avatars');
if (!is_dir($baseDir)) {
mkdir($baseDir);
}
$prefix = $profile->id;
$padded = str_pad($prefix, 12, 0, STR_PAD_LEFT);
$parts = str_split($padded, 3);
foreach ($parts as $k => $part) {
if ($k == 0) {
$prefix = storage_path('app/public/avatars/'.$parts[0]);
if (!is_dir($prefix)) {
mkdir($prefix);
}
}
if ($k == 1) {
$prefix = storage_path('app/public/avatars/'.$parts[0].'/'.$parts[1]);
if (!is_dir($prefix)) {
mkdir($prefix);
}
}
if ($k == 2) {
$prefix = storage_path('app/public/avatars/'.$parts[0].'/'.$parts[1].'/'.$parts[2]);
if (!is_dir($prefix)) {
mkdir($prefix);
}
}
if ($k == 3) {
$avatarpath = 'public/avatars/'.$parts[0].'/'.$parts[1].'/'.$parts[2].'/'.$parts[3];
$prefix = storage_path('app/'.$avatarpath);
if (!is_dir($prefix)) {
mkdir($prefix);
}
}
}
$dir = storage_path('app/'.$avatarpath);
//$dir = storage_path('app/public/avatars/'.$prefix);
if (!is_dir($dir)) {
mkdir($dir);
}
//$path = 'public/avatars/' . $prefix . '/avatar.svg';
$path = $avatarpath.'/avatar.svg';
$basePath = storage_path('app/'.$path);
file_put_contents($basePath, $icon);
} catch (Exception $e) {
}
$path = 'public/avatars/default.png';
$avatar = new Avatar();
$avatar->profile_id = $profile->id;
$avatar->media_path = $path;

View file

@ -1,94 +0,0 @@
<?php
namespace App\Util\ActivityPub\Concern;
use Zttp\Zttp;
class HTTPSignature
{
protected $localhosts = [
'127.0.0.1', 'localhost', '::1',
];
public $profile;
public $is_url;
public function validateUrl()
{
// If the profile exists, assume its valid
if ($this->is_url === false) {
return true;
}
$url = $this->profile;
try {
$url = filter_var($url, FILTER_VALIDATE_URL);
$parsed = parse_url($url, PHP_URL_HOST);
if (!$parsed || in_array($parsed, $this->localhosts)) {
return false;
}
} catch (Exception $e) {
return false;
}
return true;
}
public function fetchPublicKey($profile, bool $is_url = true)
{
$this->profile = $profile;
$this->is_url = $is_url;
$valid = $this->validateUrl();
if (!$valid) {
throw new \Exception('Invalid URL provided');
}
if ($is_url && isset($profile->public_key) && $profile->public_key) {
return $profile->public_key;
}
try {
$url = $this->profile;
$res = Zttp::timeout(30)->withHeaders([
'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
'User-Agent' => 'PixelFedBot v0.1 - https://pixelfed.org',
])->get($url);
$actor = json_decode($res->getBody(), true);
} catch (Exception $e) {
throw new Exception('Unable to fetch public key');
}
return $actor['publicKey']['publicKeyPem'];
}
public function sendSignedObject($senderProfile, $url, $body)
{
$profile = $senderProfile;
$context = new Context([
'keys' => [$profile->keyId() => $profile->private_key],
'algorithm' => 'rsa-sha256',
'headers' => ['(request-target)', 'Date'],
]);
$handlerStack = GuzzleHttpSignatures::defaultHandlerFromContext($context);
$client = new Client(['handler' => $handlerStack]);
$headers = [
'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
'Date' => date('D, d M Y h:i:s').' GMT',
'Content-Type' => 'application/activity+json',
'User-Agent' => 'PixelFedBot - https://pixelfed.org',
];
$response = $client->post($url, [
'options' => [
'allow_redirects' => false,
'verify' => true,
'timeout' => 30,
],
'headers' => $headers,
'body' => $body,
]);
return $response->getBody();
}
}

View file

@ -1,136 +0,0 @@
<?php
namespace App\Util\ActivityPub\Writer;
use App\Util\ActivityPub\DiscoverActor;
class BaseWriter {
protected $context = 'https://www.w3.org/ns/activitystreams';
protected $activities = ['Announce','Create','Follow','Like'];
protected $audiences = ['public', 'unlisted', 'private', 'direct'];
protected $audience = 'public';
protected $actors = ['Person'];
protected $objects = ['Image','Note'];
protected $verb;
protected $sourceActivity;
protected $activity;
protected $profile;
protected $to = [];
protected $cc = [];
protected $bcc = [];
protected $response;
protected $publishedAt;
public static function build()
{
return (new self);
}
public function setContext($context)
{
$this->context = $context;
return $this;
}
public function setActor($profile)
{
$this->actor = $profile;
return $this;
}
public function setActorActivity($activity)
{
$this->activity = $activity;
$this->setPublishedAt($activity->created_at->format('Y-m-d\Th:i:s\Z'));
return $this;
}
public function setTo($audience)
{
$this->to = $audience;
return $this;
}
public function setCc($audience)
{
$this->cc = $audience;
return $this;
}
public function setBcc($audience)
{
$this->bcc = $audience;
return $this;
}
public function setPublishedAt($timestamp)
{
$this->publishedAt = $timestamp;
return $this;
}
public function audience($audience)
{
$this->setAudience($audience);
$this->buildAudience();
return $this;
}
public function setAudience($audience)
{
if(in_array($audience, $this->audience)) {
$this->audience = $audience;
}
return $this;
}
public function buildAudience()
{
switch ($this->audience) {
case 'public':
$this->to = [
$this->context . '#Public'
];
$this->cc = [
$this->actor->permalink('/followers')
];
break;
case 'unlisted':
$this->to = [
$this->actor->permalink('/followers')
];
$this->cc = [
$this->context . '#Public'
];
break;
case 'private':
$this->to = [
$this->actor->permalink('/followers')
];
break;
default:
# code...
break;
}
return $this;
}
public function get()
{
return $this->getJson();
}
public function getJson()
{
return json_encode($this->response);
}
public function getArray()
{
return $this->response;
}
}

View file

@ -1,34 +0,0 @@
<?php
namespace App\Util\HttpSignatures;
abstract class Algorithm
{
/**
* @param string $name
*
* @return HmacAlgorithm
*
* @throws Exception
*/
public static function create($name)
{
switch ($name) {
case 'hmac-sha1':
return new HmacAlgorithm('sha1');
break;
case 'hmac-sha256':
return new HmacAlgorithm('sha256');
break;
case 'rsa-sha1':
return new RsaAlgorithm('sha1');
break;
case 'rsa-sha256':
return new RsaAlgorithm('sha256');
break;
default:
throw new AlgorithmException("No algorithm named '$name'");
break;
}
}
}

View file

@ -1,7 +0,0 @@
<?php
namespace App\Util\HttpSignatures;
class AlgorithmException extends Exception
{
}

View file

@ -1,19 +0,0 @@
<?php
namespace App\Util\HttpSignatures;
interface AlgorithmInterface
{
/**
* @return string
*/
public function name();
/**
* @param string $key
* @param string $data
*
* @return string
*/
public function sign($key, $data);
}

View file

@ -1,119 +0,0 @@
<?php
namespace App\Util\HttpSignatures;
class Context
{
/** @var array */
private $headers;
/** @var KeyStoreInterface */
private $keyStore;
/** @var array */
private $keys;
/** @var string */
private $signingKeyId;
/** @var AlgorithmInterface */
private $algorithm;
/**
* @param array $args
*
* @throws Exception
*/
public function __construct($args)
{
if (isset($args['keys']) && isset($args['keyStore'])) {
throw new Exception(__CLASS__.' accepts keys or keyStore but not both');
} elseif (isset($args['keys'])) {
// array of keyId => keySecret
$this->keys = $args['keys'];
} elseif (isset($args['keyStore'])) {
$this->setKeyStore($args['keyStore']);
}
// algorithm for signing; not necessary for verifying.
if (isset($args['algorithm'])) {
$this->algorithm = Algorithm::create($args['algorithm']);
}
// headers list for signing; not necessary for verifying.
if (isset($args['headers'])) {
$this->headers = $args['headers'];
}
// signingKeyId specifies the key used for signing messages.
if (isset($args['signingKeyId'])) {
$this->signingKeyId = $args['signingKeyId'];
} elseif (isset($args['keys']) && 1 === count($args['keys'])) {
list($this->signingKeyId) = array_keys($args['keys']); // first key
}
}
/**
* @return Signer
*
* @throws Exception
*/
public function signer()
{
return new Signer(
$this->signingKey(),
$this->algorithm,
$this->headerList()
);
}
/**
* @return Verifier
*/
public function verifier()
{
return new Verifier($this->keyStore());
}
/**
* @return Key
*
* @throws Exception
* @throws KeyStoreException
*/
private function signingKey()
{
if (isset($this->signingKeyId)) {
return $this->keyStore()->fetch($this->signingKeyId);
} else {
throw new Exception('no implicit or specified signing key');
}
}
/**
* @return HeaderList
*/
private function headerList()
{
return new HeaderList($this->headers);
}
/**
* @return KeyStore
*/
private function keyStore()
{
if (empty($this->keyStore)) {
$this->keyStore = new KeyStore($this->keys);
}
return $this->keyStore;
}
/**
* @param KeyStoreInterface $keyStore
*/
private function setKeyStore(KeyStoreInterface $keyStore)
{
$this->keyStore = $keyStore;
}
}

View file

@ -1,7 +0,0 @@
<?php
namespace App\Util\HttpSignatures;
class Exception extends \Exception
{
}

View file

@ -1,41 +0,0 @@
<?php
namespace App\Util\HttpSignatures;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Request;
use App\Util\HttpSignatures\Context;
class GuzzleHttpSignatures
{
/**
* @param Context $context
* @return HandlerStack
*/
public static function defaultHandlerFromContext(Context $context)
{
$stack = HandlerStack::create();
$stack->push(self::middlewareFromContext($context));
return $stack;
}
/**
* @param Context $context
* @return \Closure
*/
public static function middlewareFromContext(Context $context)
{
return function (callable $handler) use ($context)
{
return function (
Request $request,
array $options
) use ($handler, $context)
{
$request = $context->signer()->sign($request);
return $handler($request, $options);
};
};
}
}

View file

@ -1,48 +0,0 @@
<?php
namespace App\Util\HttpSignatures;
class HeaderList
{
/** @var array */
public $names;
/**
* @param array $names
*/
public function __construct(array $names)
{
$this->names = array_map(
[$this, 'normalize'],
$names
);
}
/**
* @param $string
*
* @return HeaderList
*/
public static function fromString($string)
{
return new static(explode(' ', $string));
}
/**
* @return string
*/
public function string()
{
return implode(' ', $this->names);
}
/**
* @param $name
*
* @return string
*/
private function normalize($name)
{
return strtolower($name);
}
}

View file

@ -1,36 +0,0 @@
<?php
namespace App\Util\HttpSignatures;
class HmacAlgorithm implements AlgorithmInterface
{
/** @var string */
private $digestName;
/**
* @param string $digestName
*/
public function __construct($digestName)
{
$this->digestName = $digestName;
}
/**
* @return string
*/
public function name()
{
return sprintf('hmac-%s', $this->digestName);
}
/**
* @param string $key
* @param string $data
*
* @return string
*/
public function sign($secret, $data)
{
return hash_hmac($this->digestName, $data, $secret, true);
}
}

View file

@ -1,260 +0,0 @@
<?php
namespace App\Util\HttpSignatures;
class Key
{
/** @var string */
private $id;
/** @var string */
private $secret;
/** @var resource */
private $certificate;
/** @var resource */
private $publicKey;
/** @var resource */
private $privateKey;
/** @var string */
private $type;
/**
* @param string $id
* @param string|array $secret
*/
public function __construct($id, $item)
{
$this->id = $id;
if (Key::hasX509Certificate($item) || Key::hasPublicKey($item)) {
$publicKey = Key::getPublicKey($item);
} else {
$publicKey = null;
}
if (Key::hasPrivateKey($item)) {
$privateKey = Key::getPrivateKey($item);
} else {
$privateKey = null;
}
if (($publicKey || $privateKey)) {
$this->type = 'asymmetric';
if ($publicKey && $privateKey) {
$publicKeyPEM = openssl_pkey_get_details($publicKey)['key'];
$privateKeyPublicPEM = openssl_pkey_get_details($privateKey)['key'];
if ($privateKeyPublicPEM != $publicKeyPEM) {
throw new KeyException('Supplied Certificate and Key are not related');
}
}
$this->privateKey = $privateKey;
$this->publicKey = $publicKey;
$this->secret = null;
} else {
$this->type = 'secret';
$this->secret = $item;
$this->publicKey = null;
$this->privateKey = null;
}
}
/**
* Retrieves private key resource from a input string or
* array of strings.
*
* @param string|array $object PEM-format Private Key or file path to same
*
* @return resource|false
*/
public static function getPrivateKey($object)
{
if (is_array($object)) {
foreach ($object as $candidateKey) {
$privateKey = Key::getPrivateKey($candidateKey);
if ($privateKey) {
return $privateKey;
}
}
} else {
// OpenSSL libraries don't have detection methods, so try..catch
try {
$privateKey = openssl_get_privatekey($object);
return $privateKey;
} catch (\Exception $e) {
return null;
}
}
}
/**
* Retrieves public key resource from a input string or
* array of strings.
*
* @param string|array $object PEM-format Public Key or file path to same
*
* @return resource|false
*/
public static function getPublicKey($object)
{
if (is_array($object)) {
// If we implement key rotation in future, this should add to a collection
foreach ($object as $candidateKey) {
$publicKey = Key::getPublicKey($candidateKey);
if ($publicKey) {
return $publicKey;
}
}
} else {
// OpenSSL libraries don't have detection methods, so try..catch
try {
$publicKey = openssl_get_publickey($object);
return $publicKey;
} catch (\Exception $e) {
return null;
}
}
}
/**
* Signing HTTP Messages 'keyId' field.
*
* @return string
*
* @throws KeyException
*/
public function getId()
{
return $this->id;
}
/**
* Retrieve Verifying Key - Public Key for Asymmetric/PKI, or shared secret for HMAC.
*
* @return string Shared Secret or PEM-format Public Key
*
* @throws KeyException
*/
public function getVerifyingKey()
{
switch ($this->type) {
case 'asymmetric':
if ($this->publicKey) {
return openssl_pkey_get_details($this->publicKey)['key'];
} else {
return null;
}
break;
case 'secret':
return $this->secret;
default:
throw new KeyException("Unknown key type $this->type");
}
}
/**
* Retrieve Signing Key - Private Key for Asymmetric/PKI, or shared secret for HMAC.
*
* @return string Shared Secret or PEM-format Private Key
*
* @throws KeyException
*/
public function getSigningKey()
{
switch ($this->type) {
case 'asymmetric':
if ($this->privateKey) {
openssl_pkey_export($this->privateKey, $pem);
return $pem;
} else {
return null;
}
break;
case 'secret':
return $this->secret;
default:
throw new KeyException("Unknown key type $this->type");
}
}
/**
* @return string 'secret' for HMAC or 'asymmetric'
*/
public function getType()
{
return $this->type;
}
/**
* Test if $object is, points to or contains, X.509 PEM-format certificate.
*
* @param string|array $object PEM Format X.509 Certificate or file path to one
*
* @return bool
*/
public static function hasX509Certificate($object)
{
if (is_array($object)) {
foreach ($object as $candidateCertificate) {
$result = Key::hasX509Certificate($candidateCertificate);
if ($result) {
return $result;
}
}
} else {
// OpenSSL libraries don't have detection methods, so try..catch
try {
openssl_x509_export($object, $null);
return true;
} catch (\Exception $e) {
return false;
}
}
}
/**
* Test if $object is, points to or contains, PEM-format Public Key.
*
* @param string|array $object PEM-format Public Key or file path to one
*
* @return bool
*/
public static function hasPublicKey($object)
{
if (is_array($object)) {
foreach ($object as $candidatePublicKey) {
$result = Key::hasPublicKey($candidatePublicKey);
if ($result) {
return $result;
}
}
} else {
return false == !openssl_pkey_get_public($object);
}
}
/**
* Test if $object is, points to or contains, PEM-format Private Key.
*
* @param string|array $object PEM-format Private Key or file path to one
*
* @return bool
*/
public static function hasPrivateKey($object)
{
if (is_array($object)) {
foreach ($object as $candidatePrivateKey) {
$result = Key::hasPrivateKey($candidatePrivateKey);
if ($result) {
return $result;
}
}
} else {
return false != openssl_pkey_get_private($object);
}
}
}

View file

@ -1,7 +0,0 @@
<?php
namespace App\Util\HttpSignatures;
class KeyException extends Exception
{
}

View file

@ -1,36 +0,0 @@
<?php
namespace App\Util\HttpSignatures;
class KeyStore implements KeyStoreInterface
{
/** @var Key[] */
private $keys;
/**
* @param array $keys
*/
public function __construct($keys)
{
$this->keys = [];
foreach ($keys as $id => $key) {
$this->keys[$id] = new Key($id, $key);
}
}
/**
* @param string $keyId
*
* @return Key
*
* @throws KeyStoreException
*/
public function fetch($keyId)
{
if (isset($this->keys[$keyId])) {
return $this->keys[$keyId];
} else {
throw new KeyStoreException("Key '$keyId' not found");
}
}
}

View file

@ -1,7 +0,0 @@
<?php
namespace App\Util\HttpSignatures;
class KeyStoreException extends Exception
{
}

View file

@ -1,15 +0,0 @@
<?php
namespace App\Util\HttpSignatures;
interface KeyStoreInterface
{
/**
* return the secret for the specified $keyId.
*
* @param string $keyId
*
* @return Key
*/
public function fetch($keyId);
}

View file

@ -1,64 +0,0 @@
<?php
namespace App\Util\HttpSignatures;
class RsaAlgorithm implements AlgorithmInterface
{
/** @var string */
private $digestName;
/**
* @param string $digestName
*/
public function __construct($digestName)
{
$this->digestName = $digestName;
}
/**
* @return string
*/
public function name()
{
return sprintf('rsa-%s', $this->digestName);
}
/**
* @param string $key
* @param string $data
*
* @return string
*
* @throws \HttpSignatures\AlgorithmException
*/
public function sign($signingKey, $data)
{
$algo = $this->getRsaHashAlgo($this->digestName);
if (!openssl_get_privatekey($signingKey)) {
throw new AlgorithmException("OpenSSL doesn't understand the supplied key (not valid or not found)");
}
$signature = '';
openssl_sign($data, $signature, $signingKey, $algo);
return $signature;
}
public function verify($message, $signature, $verifyingKey)
{
$algo = $this->getRsaHashAlgo($this->digestName);
return openssl_verify($message, base64_decode($signature), $verifyingKey, $algo);
}
private function getRsaHashAlgo($digestName)
{
switch ($digestName) {
case 'sha256':
return OPENSSL_ALGO_SHA256;
case 'sha1':
return OPENSSL_ALGO_SHA1;
default:
throw new HttpSignatures\AlgorithmException($digestName.' is not a supported hash format');
}
}
}

View file

@ -1,38 +0,0 @@
<?php
namespace App\Util\HttpSignatures;
use Psr\Http\Message\RequestInterface;
class Signature
{
/** @var Key */
private $key;
/** @var AlgorithmInterface */
private $algorithm;
/** @var SigningString */
private $signingString;
/**
* @param RequestInterface $message
* @param Key $key
* @param AlgorithmInterface $algorithm
* @param HeaderList $headerList
*/
public function __construct($message, Key $key, AlgorithmInterface $algorithm, HeaderList $headerList)
{
$this->key = $key;
$this->algorithm = $algorithm;
$this->signingString = new SigningString($headerList, $message);
}
public function string()
{
return $this->algorithm->sign(
$this->key->getSigningKey(),
$this->signingString->string()
);
}
}

View file

@ -1,49 +0,0 @@
<?php
namespace App\Util\HttpSignatures;
class SignatureParameters
{
/**
* @param Key $key
* @param AlgorithmInterface $algorithm
* @param HeaderList $headerList
* @param Signature $signature
*/
public function __construct($key, $algorithm, $headerList, $signature)
{
$this->key = $key;
$this->algorithm = $algorithm;
$this->headerList = $headerList;
$this->signature = $signature;
}
/**
* @return string
*/
public function string()
{
return implode(',', $this->parameterComponents());
}
/**
* @return array
*/
private function parameterComponents()
{
return [
sprintf('keyId="%s"', $this->key->getId()),
sprintf('algorithm="%s"', $this->algorithm->name()),
sprintf('headers="%s"', $this->headerList->string()),
sprintf('signature="%s"', $this->signatureBase64()),
];
}
/**
* @return string
*/
private function signatureBase64()
{
return base64_encode($this->signature->string());
}
}

View file

@ -1,111 +0,0 @@
<?php
namespace App\Util\HttpSignatures;
class SignatureParametersParser
{
/** @var string */
private $input;
/**
* @param string $input
*/
public function __construct($input)
{
$this->input = $input;
}
/**
* @return array
*/
public function parse()
{
$result = $this->pairsToAssociative(
$this->arrayOfPairs()
);
$this->validate($result);
return $result;
}
/**
* @param array $pairs
*
* @return array
*/
private function pairsToAssociative($pairs)
{
$result = [];
foreach ($pairs as $pair) {
$result[$pair[0]] = $pair[1];
}
return $result;
}
/**
* @return array
*/
private function arrayOfPairs()
{
return array_map(
[$this, 'pair'],
$this->segments()
);
}
/**
* @return array
*/
private function segments()
{
return explode(',', $this->input);
}
/**
* @param $segment
*
* @return array
*
* @throws SignatureParseException
*/
private function pair($segment)
{
$segmentPattern = '/\A(keyId|algorithm|headers|signature)="(.*)"\z/';
$matches = [];
$result = preg_match($segmentPattern, $segment, $matches);
if (1 !== $result) {
throw new SignatureParseException("Signature parameters segment '$segment' invalid");
}
array_shift($matches);
return $matches;
}
/**
* @param $result
*
* @throws SignatureParseException
*/
private function validate($result)
{
$this->validateAllKeysArePresent($result);
}
/**
* @param $result
*
* @throws SignatureParseException
*/
private function validateAllKeysArePresent($result)
{
// Regexp in pair() ensures no unwanted keys exist.
// Ensure that all wanted keys exist.
$wanted = ['keyId', 'algorithm', 'headers', 'signature'];
$missing = array_diff($wanted, array_keys($result));
if (!empty($missing)) {
$csv = implode(', ', $missing);
throw new SignatureParseException("Missing keys $csv");
}
}
}

View file

@ -1,7 +0,0 @@
<?php
namespace App\Util\HttpSignatures;
class SignatureParseException extends Exception
{
}

View file

@ -1,7 +0,0 @@
<?php
namespace App\Util\HttpSignatures;
class SignedHeaderNotPresentException extends Exception
{
}

View file

@ -1,104 +0,0 @@
<?php
namespace App\Util\HttpSignatures;
use Psr\Http\Message\RequestInterface;
class Signer
{
/** @var Key */
private $key;
/** @var HmacAlgorithm */
private $algorithm;
/** @var HeaderList */
private $headerList;
/**
* @param Key $key
* @param HmacAlgorithm $algorithm
* @param HeaderList $headerList
*/
public function __construct($key, $algorithm, $headerList)
{
$this->key = $key;
$this->algorithm = $algorithm;
$this->headerList = $headerList;
}
/**
* @param RequestInterface $message
*
* @return RequestInterface
*/
public function sign($message)
{
$signatureParameters = $this->signatureParameters($message);
$message = $message->withAddedHeader('Signature', $signatureParameters->string());
$message = $message->withAddedHeader('Authorization', 'Signature '.$signatureParameters->string());
return $message;
}
/**
* @param RequestInterface $message
*
* @return RequestInterface
*/
public function signWithDigest($message)
{
$message = $this->addDigest($message);
return $this->sign($message);
}
/**
* @param RequestInterface $message
*
* @return RequestInterface
*/
private function addDigest($message)
{
if (!array_search('digest', $this->headerList->names)) {
$this->headerList->names[] = 'digest';
}
$message = $message->withoutHeader('Digest')
->withHeader(
'Digest',
'SHA-256='.base64_encode(hash('sha256', $message->getBody(), true))
);
return $message;
}
/**
* @param RequestInterface $message
*
* @return SignatureParameters
*/
private function signatureParameters($message)
{
return new SignatureParameters(
$this->key,
$this->algorithm,
$this->headerList,
$this->signature($message)
);
}
/**
* @param RequestInterface $message
*
* @return Signature
*/
private function signature($message)
{
return new Signature(
$message,
$this->key,
$this->algorithm,
$this->headerList
);
}
}

View file

@ -1,89 +0,0 @@
<?php
namespace App\Util\HttpSignatures;
use Psr\Http\Message\RequestInterface;
class SigningString
{
/** @var HeaderList */
private $headerList;
/** @var RequestInterface */
private $message;
/**
* @param HeaderList $headerList
* @param RequestInterface $message
*/
public function __construct(HeaderList $headerList, $message)
{
$this->headerList = $headerList;
$this->message = $message;
}
/**
* @return string
*/
public function string()
{
return implode("\n", $this->lines());
}
/**
* @return array
*/
private function lines()
{
return array_map(
[$this, 'line'],
$this->headerList->names
);
}
/**
* @param string $name
*
* @return string
*
* @throws SignedHeaderNotPresentException
*/
private function line($name)
{
if ('(request-target)' == $name) {
return $this->requestTargetLine();
} else {
return sprintf('%s: %s', $name, $this->headerValue($name));
}
}
/**
* @param string $name
*
* @return string
*
* @throws SignedHeaderNotPresentException
*/
private function headerValue($name)
{
if ($this->message->hasHeader($name)) {
$header = $this->message->getHeader($name);
return end($header);
} else {
throw new SignedHeaderNotPresentException("Header '$name' not in message");
}
}
/**
* @return string
*/
private function requestTargetLine()
{
return sprintf(
'(request-target): %s %s',
strtolower($this->message->getMethod()),
$this->message->getRequestTarget()
);
}
}

View file

@ -1,202 +0,0 @@
<?php
namespace App\Util\HttpSignatures;
use Psr\Http\Message\RequestInterface;
class Verification
{
/** @var RequestInterface */
private $message;
/** @var KeyStoreInterface */
private $keyStore;
/** @var array */
private $_parameters;
/**
* @param RequestInterface $message
* @param KeyStoreInterface $keyStore
*/
public function __construct($message, KeyStoreInterface $keyStore)
{
$this->message = $message;
$this->keyStore = $keyStore;
}
/**
* @return bool
*/
public function isValid()
{
return $this->hasSignatureHeader() && $this->signatureMatches();
}
/**
* @return bool
*/
private function signatureMatches()
{
try {
$key = $this->key();
switch ($key->getType()) {
case 'secret':
$random = random_bytes(32);
$expectedResult = hash_hmac(
'sha256', $this->expectedSignatureBase64(),
$random,
true
);
$providedResult = hash_hmac(
'sha256', $this->providedSignatureBase64(),
$random,
true
);
return $expectedResult === $providedResult;
case 'asymmetric':
$signedString = new SigningString(
$this->headerList(),
$this->message
);
$hashAlgo = explode('-', $this->parameter('algorithm'))[1];
$algorithm = new RsaAlgorithm($hashAlgo);
$result = $algorithm->verify(
$signedString->string(),
$this->parameter('signature'),
$key->getVerifyingKey());
return $result;
default:
throw new Exception("Unknown key type '".$key->getType()."', cannot verify");
}
} catch (SignatureParseException $e) {
return false;
} catch (KeyStoreException $e) {
return false;
} catch (SignedHeaderNotPresentException $e) {
return false;
}
}
/**
* @return string
*/
private function expectedSignatureBase64()
{
return base64_encode($this->expectedSignature()->string());
}
/**
* @return Signature
*/
private function expectedSignature()
{
return new Signature(
$this->message,
$this->key(),
$this->algorithm(),
$this->headerList()
);
}
/**
* @return string
*/
private function providedSignatureBase64()
{
return $this->parameter('signature');
}
/**
* @return Key
*/
private function key()
{
return $this->keyStore->fetch($this->parameter('keyId'));
}
/**
* @return HmacAlgorithm
*/
private function algorithm()
{
return Algorithm::create($this->parameter('algorithm'));
}
/**
* @return HeaderList
*/
private function headerList()
{
return HeaderList::fromString($this->parameter('headers'));
}
/**
* @param string $name
*
* @return string
*
* @throws Exception
*/
private function parameter($name)
{
$parameters = $this->parameters();
if (!isset($parameters[$name])) {
throw new Exception("Signature parameters does not contain '$name'");
}
return $parameters[$name];
}
/**
* @return array
*/
private function parameters()
{
if (!isset($this->_parameters)) {
$parser = new SignatureParametersParser($this->signatureHeader());
$this->_parameters = $parser->parse();
}
return $this->_parameters;
}
/**
* @return bool
*/
private function hasSignatureHeader()
{
return $this->message->hasHeader('Signature') || $this->message->hasHeader('Authorization');
}
/**
* @return string
*
* @throws Exception
*/
private function signatureHeader()
{
if ($signature = $this->fetchHeader('Signature')) {
return $signature;
} elseif ($authorization = $this->fetchHeader('Authorization')) {
return substr($authorization, strlen('Signature '));
} else {
throw new Exception('HTTP message has no Signature or Authorization header');
}
}
/**
* @param $name
*
* @return string|null
*/
private function fetchHeader($name)
{
// grab the most recently set header.
$header = $this->message->getHeader($name);
return end($header);
}
}

View file

@ -1,31 +0,0 @@
<?php
namespace App\Util\HttpSignatures;
use Psr\Http\Message\RequestInterface;
class Verifier
{
/** @var KeyStoreInterface */
private $keyStore;
/**
* @param KeyStoreInterface $keyStore
*/
public function __construct(KeyStoreInterface $keyStore)
{
$this->keyStore = $keyStore;
}
/**
* @param RequestInterface $message
*
* @return bool
*/
public function isValid($message)
{
$verification = new Verification($message, $this->keyStore);
return $verification->isValid();
}
}

View file

@ -12,7 +12,9 @@
</div>
<div class="col-sm-9">
<p class="lead font-weight-bold mb-0">{{Auth::user()->username}}</p>
<p><a href="#" class="font-weight-bold change-profile-photo" data-toggle="collapse" data-target="#avatarCollapse" aria-expanded="false" aria-controls="avatarCollapse">Change Profile Photo</a></p>
<p class="">
<a href="#" class="font-weight-bold change-profile-photo" data-toggle="collapse" data-target="#avatarCollapse" aria-expanded="false" aria-controls="avatarCollapse">Change Profile Photo</a>
</p>
<div class="collapse" id="avatarCollapse">
<form method="post" action="/settings/avatar" enctype="multipart/form-data">
@csrf
@ -27,6 +29,9 @@
</div>
</form>
</div>
<p class="">
<a class="font-weight-bold text-muted delete-profile-photo" href="#">Delete Profile Photo</a>
</p>
</div>
</div>
<form method="post">
@ -144,5 +149,17 @@
reader.readAsDataURL(file);
}
});
$('.delete-profile-photo').on('click', function(e) {
e.preventDefault();
if(window.confirm('Are you sure you want to delete your profile photo.') == false) {
return;
}
axios.delete('/settings/avatar').then(res => {
window.location.href = window.location.href;
}).catch(err => {
swal('Error', 'An error occured, please try again later', 'error');
});
});
</script>
@endpush

View file

@ -164,6 +164,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::post('home', 'SettingsController@homeUpdate');
Route::get('avatar', 'SettingsController@avatar')->name('settings.avatar');
Route::post('avatar', 'AvatarController@store');
Route::delete('avatar', 'AvatarController@deleteAvatar');
Route::get('password', 'SettingsController@password')->name('settings.password')->middleware('dangerzone');
Route::post('password', 'SettingsController@passwordUpdate')->middleware('dangerzone');
Route::get('email', 'SettingsController@email')->name('settings.email');

Binary file not shown.

Before

Width:  |  Height:  |  Size: 212 KiB

After

Width:  |  Height:  |  Size: 23 KiB