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

NEW replace _ss_environment.php with .env and environment vars #6337

Merged
merged 3 commits into from
Feb 1, 2017

Conversation

dhensby
Copy link
Contributor

@dhensby dhensby commented Nov 25, 2016

This PR replaces our idiosyncratic _ss_environment.php convention with support for first class environment variables with a .env poly-fill for easy testing/development.

I've also removed the dependency on the Host header ($_SERVER['HTTP_HOST']) from the bootstrap so we no longer require $_FILE_TO_URL_MAPPING global for CLI execution. Devs can now set SS_HOST environment variable or Director.alternate_host config var.

Docs and tests have been updated

@dhensby dhensby force-pushed the pulls/4/dotenv branch 5 times, most recently from 521cad4 to ee72f02 Compare November 27, 2016 21:48
@assertchris
Copy link
Contributor

assertchris commented Nov 27, 2016

Brilliant change. I'd suggest creating an env($key, $default = null) function which proxies to the underlying env function, but allows for defaults. Then things like getenv('SS_PROTECTED_ASSETS_PATH') can have sensible defaults, like env('SS_PROTECTED_ASSETS_PATH', 'assets/secure').

@tractorcow
Copy link
Contributor

Could we not support both getenv() and defined() vars using a core environment helper?

class Environment {
	public static function get($var) {
		if (defined($var)) return constant($var);
		return getenv($var);
	}
}

This allows certain vars to be fixed (and thus protected from modification). there are security concerns related to allowing core vars to be modified.

@dhensby
Copy link
Contributor Author

dhensby commented Nov 27, 2016

@assertchris good idea - I guess I'll just copy laravel ;) - though there is more a favour towards putting these methods into utility classes.

@tractorcow whilst I don't really think there's a significant issue in allowing our pseudo environment vars to be modified I think your suggestion is sound in regards to providing BC support.

One issue there is to address is how we now handle $_FILE_TO_URL_MAPPING - though we could just define a new env var for this?

@assertchris
Copy link
Contributor

I think it would be better to resolve .env variables before constants, but good suggestion @tractorcow.

@tractorcow
Copy link
Contributor

I would prefer to see $_FILE_TO_URL_MAPPING become less important and opt-in, and instead have a better default.

@dhensby
Copy link
Contributor Author

dhensby commented Nov 27, 2016

I would prefer to see $_FILE_TO_URL_MAPPING become less important and opt-in, and instead have a better default.

I agree - there's currently the ability to set default baseurl and we could easily move this to an env var itself. .env won't apply across sites, so there's less of a need for a path to domain map.

@tractorcow
Copy link
Contributor

My view is that if you declare security-sensitive vars such as SS_TRUST_* values, attempts to re-declare them via getenv() could potentially be an error condition (i.e. only if defined in multiple places AND differ).

Other values could be safely overridden, however, e.g for testing purposes. :)

I'm very security paranoid though... so I am perhaps arguing for unnecessary strictness in this case.

@tractorcow
Copy link
Contributor

tractorcow commented Nov 27, 2016

there's currently the ability to set default baseurl

Maybe just have SS_DEFAULT_BASE_URL instead as a substitute? Does that better suit the new architecture? We have BASE_URL but that's a core constant not an environment-level option.

@@ -2,33 +2,37 @@

// Bootstrap _ss_environment.php
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// Bootstrap environment.php ?

if(defined('SS_PROTECTED_ASSETS_PATH')) {
return SS_PROTECTED_ASSETS_PATH;
if(getenv('SS_PROTECTED_ASSETS_PATH')) {
return getenv('SS_PROTECTED_ASSETS_PATH');
Copy link
Contributor

@SpiritLevel SpiritLevel Nov 28, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it more efficient to avoid calling getenv twice and instead do something like below?

if($protetedAssetsPath=getenv('SS_PROTECTED_ASSETS_PATH')) {
   return $protectedAssetsPath;
}

If so then there are a number of other places one reduce the calls to getenv :)

@@ -585,7 +585,7 @@ public static function is_https() {
// See https://support.microsoft.com/en-us/kb/307347
$headerOverride = false;
if (TRUSTED_PROXY) {
$headers = (defined('SS_TRUSTED_PROXY_PROTOCOL_HEADER')) ? array(SS_TRUSTED_PROXY_PROTOCOL_HEADER) : null;
$headers = (getenv('SS_TRUSTED_PROXY_PROTOCOL_HEADER')) ? array(getenv('SS_TRUSTED_PROXY_PROTOCOL_HEADER')) : null;
if (!$headers) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra call to getenv ?

@@ -708,7 +708,7 @@ public function allParsed() {
public function getIP() {
$headerOverrideIP = null;
if(TRUSTED_PROXY) {
$headers = (defined('SS_TRUSTED_PROXY_IP_HEADER')) ? array(SS_TRUSTED_PROXY_IP_HEADER) : null;
$headers = (getenv('SS_TRUSTED_PROXY_IP_HEADER')) ? array(getenv('SS_TRUSTED_PROXY_IP_HEADER')) : null;
if(!$headers) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra call to getenv ?

if(defined('SS_TRUSTED_PROXY_IPS') && SS_TRUSTED_PROXY_IPS !== 'none') {
if(SS_TRUSTED_PROXY_IPS === '*') {
if(getenv('SS_TRUSTED_PROXY_IPS') !== 'none') {
if(getenv('SS_TRUSTED_PROXY_IPS') === '*') {
$trusted = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra call to getenv ?

if (defined('SS_ALLOWED_HOSTS') && php_sapi_name() !== "cli") {
$all_allowed_hosts = explode(',', SS_ALLOWED_HOSTS);
if (getenv('SS_ALLOWED_HOSTS') && php_sapi_name() !== "cli") {
$all_allowed_hosts = explode(',', getenv('SS_ALLOWED_HOSTS'));
if (!isset($_SERVER['HTTP_HOST']) || !in_array($_SERVER['HTTP_HOST'], $all_allowed_hosts)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra call to getenv ?

@@ -167,7 +167,7 @@ protected function currentAbsoluteURL() {
// See https://support.microsoft.com/en-us/kb/307347
$headerOverride = false;
if(TRUSTED_PROXY) {
$headers = (defined('SS_TRUSTED_PROXY_PROTOCOL_HEADER')) ? array(SS_TRUSTED_PROXY_PROTOCOL_HEADER) : null;
$headers = (getenv('SS_TRUSTED_PROXY_PROTOCOL_HEADER')) ? array(getenv('SS_TRUSTED_PROXY_PROTOCOL_HEADER')) : null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra call to getenv ?

if(defined('SS_DEPRECATION_ENABLED')) {
return SS_DEPRECATION_ENABLED;
if(getenv('SS_DEPRECATION_ENABLED')) {
return getenv('SS_DEPRECATION_ENABLED');
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra call to getenv ?

if($usingEnv && isset($fieldSpec['envVar']) && defined($fieldSpec['envVar'])) {
$value = constant($fieldSpec['envVar']);
if($usingEnv && isset($fieldSpec['envVar']) && getenv($fieldSpec['envVar'])) {
$value = getenv($fieldSpec['envVar']);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra call to getenv ?

if( defined('SS_DATABASE_CLASS') ){
$type = $_REQUEST['db']['type'] = SS_DATABASE_CLASS;
if( getenv('SS_DATABASE_CLASS') ){
$type = $_REQUEST['db']['type'] = getenv('SS_DATABASE_CLASS');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra call to getenv ?

if( defined('SS_DATABASE_CLASS') ){
$type = $_REQUEST['db']['type'] = SS_DATABASE_CLASS;
if( getenv('SS_DATABASE_CLASS') ){
$type = $_REQUEST['db']['type'] = getenv('SS_DATABASE_CLASS');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra call to getenv ?

} else if(defined('SS_DATABASE_CHOOSE_NAME') && SS_DATABASE_CHOOSE_NAME) {
$loopCount = (int)SS_DATABASE_CHOOSE_NAME;
if(getenv('SS_DATABASE_NAME')) {
$database = getenv('SS_DATABASE_NAME');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra call to getenv ?

if(getenv('SS_DATABASE_NAME')) {
$database = getenv('SS_DATABASE_NAME');
} else if(getenv('SS_DATABASE_CHOOSE_NAME')) {
$loopCount = (int)getenv('SS_DATABASE_CHOOSE_NAME');
$databaseDir = BASE_PATH;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra call to getenv ?

if(defined('SS_DATABASE_PORT')) {
$databaseConfig['port'] = SS_DATABASE_PORT;
if(getenv('SS_DATABASE_PORT')) {
$databaseConfig['port'] = getenv('SS_DATABASE_PORT');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra call to getenv ?

if (defined('SS_DATABASE_TIMEZONE')) {
$databaseConfig['timezone'] = SS_DATABASE_TIMEZONE;
if (getenv('SS_DATABASE_TIMEZONE')) {
$databaseConfig['timezone'] = getenv('SS_DATABASE_TIMEZONE');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra call to getenv ?

if(defined('SS_DATABASE_SCHEMA'))
$databaseConfig["schema"] = SS_DATABASE_SCHEMA;
if(getenv('SS_DATABASE_SCHEMA'))
$databaseConfig["schema"] = getenv('SS_DATABASE_SCHEMA');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra call to getenv ?

@dhensby dhensby force-pushed the pulls/4/dotenv branch 2 times, most recently from 20de2fa to 7edf25a Compare January 31, 2017 13:25
@dhensby
Copy link
Contributor Author

dhensby commented Jan 31, 2017

Needs to link to more detailed docs, and an example on how to rewrite the old format to the new one.

I've added docs under environment management that show the new format. I've added to the changelog to link to this.

Should mention that devs need to check if their webservers are configured to deny requests to dot-prefixed files if the environment file is located in the webroot (or move it out of the webroot). This wasn't a problem before with PHP files, since they would execute on webserver requests, but not leak any information.

Added security issues section to environment management docs

Needs to point out conditional logic as bad practice (no longer possible), and suggest a better alternative

I'm not sure where to put this, it doesn't really make sense to put it in environment management docs ("Don't do this thing you can't do any more coz it's bad but if you have to do it this way").

Mention the removal of $_FILE_TO_URL_MAPPING (and how to achieve this now)

Added to change log

@chillu
Copy link
Member

chillu commented Jan 31, 2017

I'm not sure where to put this, it doesn't really make sense to put it in environment management docs ("Don't do this thing you can't do any more coz it's bad but if you have to do it this way").

In the 4.0.0.md upgrading guide - doesn't make sense as permanent documentation, but at the point of an upgrade it's relevant.

* Removed support for _ss_environment.php in favour of .env and first class environment variables
* Environment variables now can be set in `.env` file placed in webroot or one level above
* Environment variables will be read from the environment as well
* `$_FILE_TO_URL_MAPPING` has been removed and replaced with using `Director.aleternate_host` or `SS_HOST` env var
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling ("aleternate")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's wrong with my aleternate spelling :)

Copy link
Member

@sminnee sminnee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few more points. However, I'd be open to merging as-is and raising an alpha5 ticket for the subsequent changes if we want to just get the thing over the line.


## Managing environment variables with `.env` files

By default the `.env` must be placed in your webroot. If this file exists, it will be automatically loaded by the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Let's start saying "project root" rather than webroot. If, one blessed day, we allow a flexible webroot location, then these docs won't need updating.
  • We should note in these docs that the .env

Eg: "By default, the .env file must be placed either in your project root folder (i.e. next to your composer.json), or in its parent folder"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

| `SS_ALLOWED_HOSTS` | A comma deliminated list of hostnames the site is allowed to respond to |
| `SS_MANIFESTCACHE` | The manifest cache to use (defaults to file based caching) |
| `SS_IGNORE_DOT_ENV` | If set the .env file will be ignored. This is good for live to mitigate any performance implications of loading the .env file |
| `SS_HOST` | The hostname to use when it isn't determinable by other means (eg: for CLI commands) |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't appear to let us force the generation of https:// URLs from CLI scripts. Perhaps we want to allow SS_HOST to be a URL fragment as well as a hostname?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a future PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've also lost the ability to indicate that a SilverStripe site is running in a non-root URL.

Was there a reason that we didn't make this setting SS_BASE_URL instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm - good point


// Basic CLI hostname
# Basic CLI hostname
global $_FILE_TO_URL_MAPPING;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is out of date.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

BASE_PATH,
dirname(BASE_PATH),
) as $path) {
try {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a blocker, but I feel that an if(file_exists()) check would be better than throwing and swallowing an exception.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exception is a bit of overhead, we can fix in separate ticket.

@dhensby
Copy link
Contributor Author

dhensby commented Jan 31, 2017

OK - I've addressed all the feedback apart from the URL issues

@sminnee
Copy link
Member

sminnee commented Jan 31, 2017

I'm happy for us to merge as-is and pick up SS_HOST vs SS_BASE_URL as a separate ticket.

@tractorcow tractorcow merged commit b4536bf into silverstripe:master Feb 1, 2017
@dhensby dhensby deleted the pulls/4/dotenv branch February 1, 2017 00:29
@dhensby
Copy link
Contributor Author

dhensby commented Feb 1, 2017

:') Can't believe it - whoop

@dhensby
Copy link
Contributor Author

dhensby commented Feb 1, 2017

I've opened the follow-up issue

@chillu
Copy link
Member

chillu commented Feb 1, 2017

Awesome to see this merged! Buuuuut: It broke all travis builds apart from framework, since that's the only one that no longer relies on travis-support: silverstripe/silverstripe-travis-support#41. And funnily enough, we're using conditional logic in the _ss_env there ;) @dhensby, do you have time to look at this?

@dhensby
Copy link
Contributor Author

dhensby commented Feb 1, 2017

I'll look at it today.

@GrahamCampbell
Copy link

The current version of phpdotenv is robust and thread-safe. The following example will work in a multithreaded environment. This avoids using the adapter that would have called putenv and getenv, which are not threadsafe.

<?php

use Dotenv\Environment\Adapter\EnvConstAdapter;
use Dotenv\Environment\Adapter\ServerConstAdapter;
use Dotenv\Environment\DotenvFactory;
use Dotenv\Dotenv;

$factory = new DotenvFactory([new EnvConstAdapter(), new ServerConstAdapter()]);

Dotenv::create($path, null, $factory)->load();

@robbieaverill
Copy link
Contributor

Thanks for the update @GrahamCampbell!

@silverstripe/core-team do we want to do an RFC to re-implement the latest version of phpdotenv for SS5?

@dhensby
Copy link
Contributor Author

dhensby commented Jan 28, 2019

New issue / thread please :)

@maxime-rainville
Copy link
Contributor

I second Dan. Going through dozen of messages from 2 years ago is not conductive to a useful discussion.

@GrahamCampbell
Copy link

See #8764. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.