Skip to content
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
104 changes: 54 additions & 50 deletions src/helper/class-ee-site.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
use EE\Model\Cron;
use EE\Model\Site;
use EE\Model\Option;
use EE\Model\Auth;
use EE\Model\Whitelist;
use Symfony\Component\Filesystem\Filesystem;
use function EE\Site\Cloner\Utils\check_site_access;
use function EE\Site\Cloner\Utils\copy_site_db;
Expand All @@ -28,6 +26,8 @@
use function EE\Site\Utils\get_site_info;
use function EE\Site\Utils\reload_global_nginx_proxy;
use function EE\Site\Utils\get_parent_of_alias;
use function EE\Site\Utils\split_alias_domains;
use function EE\Site\Utils\validate_alias_domains;

/**
* Base class for Site command
Expand Down Expand Up @@ -350,45 +350,18 @@ protected function delete_site( $level, $site_url, $site_fs_path, $db_data = []
if ( $level > 4 ) {
if ( $this->site_data['site_ssl'] ) {
\EE::log( 'Removing ssl certs and other config files.' );
$crt_file = EE_ROOT_DIR . "/services/nginx-proxy/certs/$site_url.crt";
$key_file = EE_ROOT_DIR . "/services/nginx-proxy/certs/$site_url.key";
$pem_file = EE_ROOT_DIR . "/services/nginx-proxy/certs/$site_url.chain.pem";
$conf_certs = EE_ROOT_DIR . "/services/nginx-proxy/acme-conf/certs/$site_url";
$conf_var = EE_ROOT_DIR . "/services/nginx-proxy/acme-conf/var/$site_url";
$htpasswd_file = EE_ROOT_DIR . "/services/nginx-proxy/htpasswd/$site_url";

$delete_files = [ $conf_certs, $conf_var, $crt_file, $key_file, $pem_file, $htpasswd_file ];
try {
$this->fs->remove( $delete_files );
} catch ( \Exception $e ) {
\EE::warning( $e );
}
}
$crt_file = EE_ROOT_DIR . "/services/nginx-proxy/certs/$site_url.crt";
$key_file = EE_ROOT_DIR . "/services/nginx-proxy/certs/$site_url.key";
$pem_file = EE_ROOT_DIR . "/services/nginx-proxy/certs/$site_url.chain.pem";
$conf_certs = EE_ROOT_DIR . "/services/nginx-proxy/acme-conf/certs/$site_url";
$conf_var = EE_ROOT_DIR . "/services/nginx-proxy/acme-conf/var/$site_url";

$site_auth_file = EE_ROOT_DIR . '/services/nginx-proxy/htpasswd/' . $site_url;
if ( $this->fs->exists( $site_auth_file ) ) {
$delete_files = [ $conf_certs, $conf_var, $crt_file, $key_file, $pem_file ];
try {
$this->fs->remove( $site_auth_file );
$this->fs->remove( $delete_files );
} catch ( \Exception $e ) {
\EE::warning( $e );
}
reload_global_nginx_proxy();
}

$whitelists = Whitelist::where( [
'site_url' => $site_url,
] );

foreach ( $whitelists as $whitelist ) {
$whitelist->delete();
}

$auths = Auth::where( [
'site_url' => $site_url,
] );

foreach ( $auths as $auth ) {
$auth->delete();
}

if ( Site::find( $site_url )->delete() ) {
Expand Down Expand Up @@ -522,32 +495,29 @@ protected function update_alias_domains( $args, $assoc_args ) {

$add_domains = get_flag_value( $assoc_args, 'add-alias-domains', false );
$delete_domains = get_flag_value( $assoc_args, 'delete-alias-domains', false );
$pre_hook_fired = false;

try {

$site = $this->site_data;
$array_data = (array) $this->site_data;
$this->site_data = reset( $array_data );

// Validate data.
$existing_alias_domains = [];
$domains_to_add = [];
$domains_to_delete = [];
// Drop blanks so that e.g. `b.com,` never stores an empty alias domain.
$existing_alias_domains = split_alias_domains( (string) $this->site_data['alias_domains'] );
$domains_to_add = split_alias_domains( $add_domains );
$domains_to_delete = split_alias_domains( $delete_domains );

if ( ! empty( $this->site_data['alias_domains'] ) ) {
$existing_alias_domains = explode( ',', $this->site_data['alias_domains'] );
}
if ( ! empty( $add_domains ) ) {
$domains_to_add = explode( ',', $add_domains );
}
if ( ! empty( $delete_domains ) ) {
$domains_to_delete = explode( ',', $delete_domains );
if ( empty( $domains_to_add ) && empty( $domains_to_delete ) ) {
EE::error( 'Please provide at least one alias domain to add or delete.' );
}

validate_alias_domains( $domains_to_add );

$already_added_domains = array_intersect( $existing_alias_domains, $domains_to_add );
$domains_to_add = array_diff( $domains_to_add, $existing_alias_domains );
$domains_to_add = array_values( array_diff( $domains_to_add, $existing_alias_domains ) );

if ( empty( $domains_to_add ) && $add_domains ) {
if ( empty( $domains_to_add ) && ! empty( $already_added_domains ) ) {
$already_added_domains = implode( ',', $already_added_domains );
EE::error( "Alias domains: $already_added_domains is/are already present on the site." );
}
Expand Down Expand Up @@ -583,6 +553,18 @@ protected function update_alias_domains( $args, $assoc_args ) {
$final_alias_domains = array_merge( $existing_alias_domains, $domains_to_add );
$final_alias_domains = array_diff( $final_alias_domains, $domains_to_delete );

// Set before firing, so a callback that throws still gets the failure hook to undo its partial work.
$pre_hook_fired = true;

/**
* Execute before the new alias domains of a site are served by the proxy.
* Note: This can be used by package commands to set up per-domain config the proxy needs from the first request.
*
* @param string $site_url Url of site whose alias domains change.
* @param array $domains_to_add Alias domains that are being added.
*/
\EE::do_hook( 'site_alias_domains_before_update', $this->site_data['site_url'], $domains_to_add );

$this->site_data['alias_domains'] = implode( ',', $final_alias_domains );
$is_ssl = $this->site_data['site_ssl'] ? true : false;
$preferred_ssl_challenge = get_preferred_ssl_challenge( get_domains_of_site( $this->site_data['site_url'] ) );
Expand All @@ -591,6 +573,16 @@ protected function update_alias_domains( $args, $assoc_args ) {
$this->dump_docker_compose_yml( [ 'nohttps' => $nohttps ] );
\EE_DOCKER::docker_compose_up( $this->site_data['site_fs_path'], [ 'nginx' ] );
} catch ( \Exception $e ) {
if ( $pre_hook_fired ) {
/**
* Execute when an alias domains update is aborted after `site_alias_domains_before_update`.
* Note: The site keeps its old alias domains, so this can be used to undo what was set up for the new ones.
*
* @param string $site_url Url of site whose alias domains update failed.
* @param array $domains_to_add Alias domains that were not added after all.
*/
\EE::do_hook( 'site_alias_domains_update_failed', $site->site_url, $domains_to_add );
}
EE::error( $e->getMessage() );
}

Expand Down Expand Up @@ -620,6 +612,7 @@ protected function update_alias_domains( $args, $assoc_args ) {
} catch ( \Exception $e ) {
EE::warning( 'Certificate could not be issued. Reverting back to original state.' );
$this->enable( [ $this->site_data['site_url'] ], [ 'refresh' => 'true' ] );
\EE::do_hook( 'site_alias_domains_update_failed', $site->site_url, $domains_to_add );
EE::error( $e->getMessage() );
}
} elseif ( 'custom' === $this->site_data['site_ssl'] ) {
Expand Down Expand Up @@ -653,6 +646,17 @@ protected function update_alias_domains( $args, $assoc_args ) {
];
$this->update_proxy_cache( $args, $assoc_args );
}

/**
* Execute after the alias domains of a site have been updated.
* Note: This can be used by package commands to sync their per-domain config.
*
* @param string $site_url Url of site whose alias domains changed.
* @param array $domains_to_add Alias domains that were added.
* @param array $domains_to_delete Alias domains that were removed.
*/
\EE::do_hook( 'site_alias_domains_updated', $this->site_data['site_url'], $domains_to_add, $domains_to_delete );

delem_log( 'site alias domains update end' );
}

Expand Down
65 changes: 65 additions & 0 deletions src/helper/site-utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,71 @@ function check_alias_in_db( $domains ) {
}
}

/**
* Splits a comma separated list of alias domains, trimming them and dropping blank entries.
*
* @param string|bool $domains Comma separated alias domains, as passed to the alias domain flags.
*
* @return array
*/
function split_alias_domains( $domains ) {

// A flag passed without a value is `true`, which would otherwise become the alias domain `1`.
if ( ! is_string( $domains ) ) {
return [];
}

return array_values( array_filter( array_map( 'trim', explode( ',', $domains ) ), 'strlen' ) );
}

/**
* Checks whether a name is one of the global proxy file names (e.g. auth-command's htpasswd and ACL files), in any case.
*
* @param string $name File name.
*
* @return bool
*/
function is_reserved_proxy_file_name( $name ) {

return in_array( strtolower( (string) $name ), [ 'default', 'default_admin_tools' ], true );
}

/**
* Checks whether an alias domain is a plain hostname or `*.hostname` that is safe to use as a proxy file name.
*
* @param string $domain Alias domain.
*
* @return bool
*/
function is_valid_alias_domain( $domain ) {

// No leading `_`, so an alias can't take over the `_wildcard.<site>` files of another site.
$label = '[A-Za-z0-9](?:[A-Za-z0-9_-]*[A-Za-z0-9_])?';

return is_string( $domain )
&& 1 === preg_match( '/^(?:\*\.)?' . $label . '(?:\.' . $label . ')*$/D', $domain )
&& ! is_reserved_proxy_file_name( $domain );
}

/**
* Exits with an error listing the alias domains that are not a plain hostname or `*.hostname`.
*
* @param array $domains Alias domains.
*/
function validate_alias_domains( $domains ) {

$invalid = array_filter(
$domains,
function ( $domain ) {
return ! is_valid_alias_domain( $domain );
}
);

if ( ! empty( $invalid ) ) {
\EE::error( sprintf( 'Invalid alias domain(s): %s. An alias domain must be a hostname or `*.hostname` whose labels use letters, digits, `-` and `_`, do not start with `-` or `_` (a leading `_` is reserved for proxy files like `_wildcard.<site>`) and do not end with `-`. It can not be `default` or `default_admin_tools`.', implode( ', ', $invalid ) ) );
}
}

/**
* 'sysctl' parameters for docker-compose file.
*
Expand Down
19 changes: 6 additions & 13 deletions src/site-type/HTML.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
use function EE\Site\Utils\get_public_dir;
use function EE\Site\Utils\get_webroot;
use function EE\Site\Utils\check_alias_in_db;
use function EE\Site\Utils\split_alias_domains;
use function EE\Site\Utils\validate_alias_domains;
use function EE\Utils\get_flag_value;

/**
Expand Down Expand Up @@ -116,9 +118,10 @@ public function create( $args, $assoc_args ) {
\EE::error( sprintf( "Site %1\$s already exists. If you want to re-create it please delete the older one using:\n`ee site delete %1\$s`", $this->site_data['site_url'] ) );
}

$alias_domains = \EE\Utils\get_flag_value( $assoc_args, 'alias-domains', '' );
$alias_domains = split_alias_domains( \EE\Utils\get_flag_value( $assoc_args, 'alias-domains', '' ) );
validate_alias_domains( $alias_domains );

$alias_domain_to_check = explode( ',', $alias_domains );
$alias_domain_to_check = $alias_domains;
$alias_domain_to_check[] = $this->site_data['site_url'];
check_alias_in_db( $alias_domain_to_check );

Expand All @@ -127,17 +130,7 @@ public function create( $args, $assoc_args ) {
$this->skip_status_check = \EE\Utils\get_flag_value( $assoc_args, 'skip-status-check' );
$this->site_data['site_container_fs_path'] = get_public_dir( $assoc_args );

$this->site_data['alias_domains'] = $this->site_data['site_url'];
$this->site_data['alias_domains'] .= ',';
if ( ! empty( $alias_domains ) ) {
$comma_seprated_domains = explode( ',', $alias_domains );
foreach ( $comma_seprated_domains as $domain ) {
$trimmed_domain = trim( $domain );
$this->site_data['alias_domains'] .= $trimmed_domain . ',';
}
}

$this->site_data['alias_domains'] = substr( $this->site_data['alias_domains'], 0, - 1 );
$this->site_data['alias_domains'] = implode( ',', array_merge( [ $this->site_data['site_url'] ], $alias_domains ) );
$this->site_data['site_ssl'] = get_value_if_flag_isset( $assoc_args, 'ssl', 'le' );

if ( 'custom' === $this->site_data['site_ssl'] ) {
Expand Down
Loading