mirror of
https://github.com/laravel/valet.git
synced 2026-02-05 16:40:05 +01:00
450 lines
12 KiB
PHP
450 lines
12 KiB
PHP
<?php
|
|
|
|
namespace Valet;
|
|
|
|
use DomainException;
|
|
|
|
class Site
|
|
{
|
|
var $config, $cli, $files;
|
|
|
|
/**
|
|
* Create a new Site instance.
|
|
*
|
|
* @param Configuration $config
|
|
* @param CommandLine $cli
|
|
* @param Filesystem $files
|
|
*/
|
|
function __construct(Configuration $config, CommandLine $cli, Filesystem $files)
|
|
{
|
|
$this->cli = $cli;
|
|
$this->files = $files;
|
|
$this->config = $config;
|
|
}
|
|
|
|
/**
|
|
* Get the name of the site.
|
|
*
|
|
* @param string|null $name
|
|
* @return string
|
|
*/
|
|
private function getRealSiteName($name)
|
|
{
|
|
if (! is_null($name)) {
|
|
return $name;
|
|
}
|
|
|
|
if (is_string($link = $this->getLinkNameByCurrentDir())) {
|
|
return $link;
|
|
}
|
|
|
|
return basename(getcwd());
|
|
}
|
|
|
|
/**
|
|
* Get link name based on the current directory.
|
|
*
|
|
* @return null|string
|
|
*/
|
|
private function getLinkNameByCurrentDir()
|
|
{
|
|
$count = count($links = $this->links()->where('path', getcwd()));
|
|
|
|
if ($count == 1) {
|
|
return $links->shift()['site'];
|
|
}
|
|
|
|
if ($count > 1) {
|
|
throw new DomainException("There are {$count} links related to the current directory, please specify the name: valet unlink <name>.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the real hostname for the given path, checking links.
|
|
*
|
|
* @param string $path
|
|
* @return string|null
|
|
*/
|
|
function host($path)
|
|
{
|
|
foreach ($this->files->scandir($this->sitesPath()) as $link) {
|
|
if ($resolved = realpath($this->sitesPath().'/'.$link) === $path) {
|
|
return $link;
|
|
}
|
|
}
|
|
|
|
return basename($path);
|
|
}
|
|
|
|
/**
|
|
* Link the current working directory with the given name.
|
|
*
|
|
* @param string $target
|
|
* @param string $link
|
|
* @return string
|
|
*/
|
|
function link($target, $link)
|
|
{
|
|
$this->files->ensureDirExists(
|
|
$linkPath = $this->sitesPath(), user()
|
|
);
|
|
|
|
$this->config->prependPath($linkPath);
|
|
|
|
$this->files->symlinkAsUser($target, $linkPath.'/'.$link);
|
|
|
|
return $linkPath.'/'.$link;
|
|
}
|
|
|
|
/**
|
|
* Pretty print out all links in Valet.
|
|
*
|
|
* @return \Illuminate\Support\Collection
|
|
*/
|
|
function links()
|
|
{
|
|
$certsPath = VALET_HOME_PATH.'/Certificates';
|
|
|
|
$this->files->ensureDirExists($certsPath, user());
|
|
|
|
$certs = $this->getCertificates($certsPath);
|
|
|
|
return $this->getLinks($this->sitesPath(), $certs);
|
|
}
|
|
|
|
/**
|
|
* Get all certificates from config folder.
|
|
*
|
|
* @param string $path
|
|
* @return \Illuminate\Support\Collection
|
|
*/
|
|
function getCertificates($path)
|
|
{
|
|
return collect($this->files->scandir($path))->filter(function ($value, $key) {
|
|
return ends_with($value, '.crt');
|
|
})->map(function ($cert) {
|
|
$certWithoutSuffix = substr($cert, 0, -4);
|
|
return substr($certWithoutSuffix, 0, strrpos($certWithoutSuffix, '.'));
|
|
})->flip();
|
|
}
|
|
|
|
/**
|
|
* Get list of links and present them formatted.
|
|
*
|
|
* @param string $path
|
|
* @param \Illuminate\Support\Collection $certs
|
|
* @return \Illuminate\Support\Collection
|
|
*/
|
|
function getLinks($path, $certs)
|
|
{
|
|
$config = $this->config->read();
|
|
|
|
return collect($this->files->scandir($path))->mapWithKeys(function ($site) use ($path) {
|
|
return [$site => $this->files->readLink($path.'/'.$site)];
|
|
})->map(function ($path, $site) use ($certs, $config) {
|
|
$secured = $certs->has($site);
|
|
$url = ($secured ? 'https': 'http').'://'.$site.'.'.$config['tld'];
|
|
|
|
return [
|
|
'site' => $site,
|
|
'secured' => $secured ? ' X': '',
|
|
'url' => $url,
|
|
'path' => $path,
|
|
];
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Unlink the given symbolic link.
|
|
*
|
|
* @param string $name
|
|
* @return void
|
|
*/
|
|
function unlink($name)
|
|
{
|
|
$name = $this->getRealSiteName($name);
|
|
|
|
if ($this->files->exists($path = $this->sitesPath().'/'.$name)) {
|
|
$this->files->unlink($path);
|
|
}
|
|
|
|
return $name;
|
|
}
|
|
|
|
/**
|
|
* Remove all broken symbolic links.
|
|
*
|
|
* @return void
|
|
*/
|
|
function pruneLinks()
|
|
{
|
|
$this->files->ensureDirExists($this->sitesPath(), user());
|
|
|
|
$this->files->removeBrokenLinksAt($this->sitesPath());
|
|
}
|
|
|
|
/**
|
|
* Resecure all currently secured sites with a fresh tld.
|
|
*
|
|
* @param string $oldTld
|
|
* @param string $tld
|
|
* @return void
|
|
*/
|
|
function resecureForNewTld($oldTld, $tld)
|
|
{
|
|
if (! $this->files->exists($this->certificatesPath())) {
|
|
return;
|
|
}
|
|
|
|
$secured = $this->secured();
|
|
|
|
foreach ($secured as $url) {
|
|
$this->unsecure($url);
|
|
}
|
|
|
|
foreach ($secured as $url) {
|
|
$this->secure(str_replace('.'.$oldTld, '.'.$tld, $url));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all of the URLs that are currently secured.
|
|
*
|
|
* @return array
|
|
*/
|
|
function secured()
|
|
{
|
|
return collect($this->files->scandir($this->certificatesPath()))
|
|
->map(function ($file) {
|
|
return str_replace(['.key', '.csr', '.crt', '.conf'], '', $file);
|
|
})->unique()->values()->all();
|
|
}
|
|
|
|
/**
|
|
* Secure the given host with TLS.
|
|
*
|
|
* @param string $url
|
|
* @return void
|
|
*/
|
|
function secure($url)
|
|
{
|
|
$this->unsecure($url);
|
|
|
|
$this->files->ensureDirExists($this->caPath(), user());
|
|
|
|
$this->files->ensureDirExists($this->certificatesPath(), user());
|
|
|
|
$this->createCa();
|
|
|
|
$this->createCertificate($url);
|
|
|
|
$this->files->putAsUser(
|
|
VALET_HOME_PATH.'/Nginx/'.$url, $this->buildSecureNginxServer($url)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* If CA and root certificates are nonexistent, crete them and trust the root cert.
|
|
*
|
|
* @return void
|
|
*/
|
|
function createCa()
|
|
{
|
|
$caPemPath = $this->caPath().'/LaravelValetCASelfSigned.pem';
|
|
$caKeyPath = $this->caPath().'/LaravelValetCASelfSigned.key';
|
|
|
|
if ($this->files->exists($caKeyPath) && $this->files->exists($caPemPath)) {
|
|
return;
|
|
}
|
|
|
|
$oName = 'Laravel Valet CA Self Signed Organization';
|
|
$cName = 'Laravel Valet CA Self Signed CN';
|
|
|
|
if ($this->files->exists($caKeyPath)) {
|
|
$this->files->unlink($caKeyPath);
|
|
}
|
|
if ($this->files->exists($caPemPath)) {
|
|
$this->files->unlink($caPemPath);
|
|
}
|
|
|
|
$this->cli->run(sprintf(
|
|
'sudo security delete-certificate -c "%s" /Library/Keychains/System.keychain',
|
|
$cName
|
|
));
|
|
|
|
$this->cli->runAsUser(sprintf(
|
|
'openssl req -new -newkey rsa:2048 -days 730 -nodes -x509 -subj "/C=/ST=/O=%s/localityName=/commonName=%s/organizationalUnitName=Developers/emailAddress=%s/" -keyout %s -out %s',
|
|
$oName, $cName, 'rootcertificate@laravel.valet', $caKeyPath, $caPemPath
|
|
));
|
|
$this->trustCa($caPemPath);
|
|
}
|
|
|
|
/**
|
|
* Create and trust a certificate for the given URL.
|
|
*
|
|
* @param string $url
|
|
* @return void
|
|
*/
|
|
function createCertificate($url)
|
|
{
|
|
$caPemPath = $this->caPath().'/LaravelValetCASelfSigned.pem';
|
|
$caKeyPath = $this->caPath().'/LaravelValetCASelfSigned.key';
|
|
$caSrlPath = $this->caPath().'/LaravelValetCASelfSigned.srl';
|
|
$keyPath = $this->certificatesPath().'/'.$url.'.key';
|
|
$csrPath = $this->certificatesPath().'/'.$url.'.csr';
|
|
$crtPath = $this->certificatesPath().'/'.$url.'.crt';
|
|
$confPath = $this->certificatesPath().'/'.$url.'.conf';
|
|
|
|
$this->buildCertificateConf($confPath, $url);
|
|
$this->createPrivateKey($keyPath);
|
|
$this->createSigningRequest($url, $keyPath, $csrPath, $confPath);
|
|
|
|
$caSrlParam = ' -CAcreateserial';
|
|
if ($this->files->exists($caSrlPath)) {
|
|
$caSrlParam = ' -CAserial ' . $caSrlPath;
|
|
}
|
|
|
|
$this->cli->runAsUser(sprintf(
|
|
'openssl x509 -req -sha256 -days 730 -CA %s -CAkey %s%s -in %s -out %s -extensions v3_req -extfile %s',
|
|
$caPemPath, $caKeyPath, $caSrlParam, $csrPath, $crtPath, $confPath
|
|
));
|
|
|
|
$this->trustCertificate($crtPath);
|
|
}
|
|
|
|
/**
|
|
* Create the private key for the TLS certificate.
|
|
*
|
|
* @param string $keyPath
|
|
* @return void
|
|
*/
|
|
function createPrivateKey($keyPath)
|
|
{
|
|
$this->cli->runAsUser(sprintf('openssl genrsa -out %s 2048', $keyPath));
|
|
}
|
|
|
|
/**
|
|
* Create the signing request for the TLS certificate.
|
|
*
|
|
* @param string $keyPath
|
|
* @return void
|
|
*/
|
|
function createSigningRequest($url, $keyPath, $csrPath, $confPath)
|
|
{
|
|
$this->cli->runAsUser(sprintf(
|
|
'openssl req -new -key %s -out %s -subj "/C=/ST=/O=/localityName=/commonName=%s/organizationalUnitName=/emailAddress=%s%s/" -config %s',
|
|
$keyPath, $csrPath, $url, $url, '@laravel.valet', $confPath
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Trust the given root certificate file in the Mac Keychain.
|
|
*
|
|
* @param string $pemPath
|
|
* @return void
|
|
*/
|
|
function trustCa($caPemPath)
|
|
{
|
|
$this->cli->run(sprintf(
|
|
'sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain %s', $caPemPath
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Trust the given certificate file in the Mac Keychain.
|
|
*
|
|
* @param string $crtPath
|
|
* @return void
|
|
*/
|
|
function trustCertificate($crtPath)
|
|
{
|
|
$this->cli->run(sprintf(
|
|
'sudo security add-trusted-cert -d -r trustAsRoot -k /Library/Keychains/System.keychain %s', $crtPath
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Build the SSL config for the given URL.
|
|
*
|
|
* @param string $url
|
|
* @return string
|
|
*/
|
|
function buildCertificateConf($path, $url)
|
|
{
|
|
$config = str_replace('VALET_DOMAIN', $url, $this->files->get(__DIR__.'/../stubs/openssl.conf'));
|
|
$this->files->putAsUser($path, $config);
|
|
}
|
|
|
|
/**
|
|
* Build the TLS secured Nginx server for the given URL.
|
|
*
|
|
* @param string $url
|
|
* @return string
|
|
*/
|
|
function buildSecureNginxServer($url)
|
|
{
|
|
$path = $this->certificatesPath();
|
|
|
|
return str_replace(
|
|
['VALET_HOME_PATH', 'VALET_SERVER_PATH', 'VALET_STATIC_PREFIX', 'VALET_SITE', 'VALET_CERT', 'VALET_KEY'],
|
|
[VALET_HOME_PATH, VALET_SERVER_PATH, VALET_STATIC_PREFIX, $url, $path.'/'.$url.'.crt', $path.'/'.$url.'.key'],
|
|
$this->files->get(__DIR__.'/../stubs/secure.valet.conf')
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Unsecure the given URL so that it will use HTTP again.
|
|
*
|
|
* @param string $url
|
|
* @return void
|
|
*/
|
|
function unsecure($url)
|
|
{
|
|
if ($this->files->exists($this->certificatesPath().'/'.$url.'.crt')) {
|
|
$this->files->unlink(VALET_HOME_PATH.'/Nginx/'.$url);
|
|
|
|
$this->files->unlink($this->certificatesPath().'/'.$url.'.conf');
|
|
$this->files->unlink($this->certificatesPath().'/'.$url.'.key');
|
|
$this->files->unlink($this->certificatesPath().'/'.$url.'.csr');
|
|
$this->files->unlink($this->certificatesPath().'/'.$url.'.crt');
|
|
}
|
|
|
|
$this->cli->run(sprintf('sudo security delete-certificate -c "%s" /Library/Keychains/System.keychain', $url));
|
|
$this->cli->run(sprintf('sudo security delete-certificate -c "*.%s" /Library/Keychains/System.keychain', $url));
|
|
$this->cli->run(sprintf(
|
|
'sudo security find-certificate -e "%s%s" -a -Z | grep SHA-1 | sudo awk \'{system("security delete-certificate -Z "$NF" /Library/Keychains/System.keychain")}\'',
|
|
$url, '@laravel.valet'
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Get the path to the linked Valet sites.
|
|
*
|
|
* @return string
|
|
*/
|
|
function sitesPath()
|
|
{
|
|
return VALET_HOME_PATH.'/Sites';
|
|
}
|
|
|
|
/**
|
|
* Get the path to the Valet CA certificates.
|
|
*
|
|
* @return string
|
|
*/
|
|
function caPath()
|
|
{
|
|
return VALET_HOME_PATH.'/CA';
|
|
}
|
|
|
|
/**
|
|
* Get the path to the Valet TLS certificates.
|
|
*
|
|
* @return string
|
|
*/
|
|
function certificatesPath()
|
|
{
|
|
return VALET_HOME_PATH.'/Certificates';
|
|
}
|
|
}
|