In #2620304: htaccess functions should be a service we attempted to move \Drupal\Component\PhpStorage\FileStorage::htaccessLines() out into \Drupal\Core\File\HtaccessWriter::htaccessLines()
However FileStorage is a component, and therefore can't call code outside of the component. It also calls self::htaccessLines() in FileStorage::ensureDirectory().
We should try to untangle this and move htaccessLines() to a separate component.
From @alexpott
@kim.pepper I think we should continue with calling \Drupal\Component\PhpStorage\FileStorage::htaccessLines() then and file a follow-up to try to resolve this. The other answer is that we should try to make the HtAccessWriter a component but that is going to be nigh on impossible. For me this points to the fact that PhpStorage doesn't really feel like a component either.
Maybe we can find some other similar stuff and make a security component that has this ¯\_(ツ)_/¯
Comment | File | Size | Author |
---|---|---|---|
#37 | 3013210-35-to-37.interdiff.txt | 914 bytes | greg.1.anderson |
#37 | 3013210-37.patch | 40.74 KB | greg.1.anderson |
Comments
Comment #3
Mile23Related: #3057094: Add Composer vendor/ hardening plugin to core
In that issue, we're using
Drupal\Component\PhpStorage\FileStorage::htaccessLines()
in the context of a Composer plugin. This replaces the same usage in the current Composer script: https://git.drupalcode.org/project/drupal/blob/8.8.x/core/lib/Drupal/Cor...This is great because the vendor hardening plugin can just require drupal/core-php-storage and automatically get the same .htaccess file it had in the script. It's a DRY win.
So basically the issue title seems wrong to me: The dependencies are not currently tangled and don't need untangling. :-)
+1 on moving the htaccess writer to be a component. That way the Composer plugin can use it with minimal dependency hell, and core's file system can wrap it up in services context all it wants.
Alternately, the htaccess template method could be in, for instance, drupal/core-utility or somewhere else that's more granular.
Comment #4
Mile23This issue is now a blocker for #3057094-101: Add Composer vendor/ hardening plugin to core and thus the Composer initiative.
+1 in favor of making an htaccess/web.config generating component that can be used in a Composer plugin context.
This might require some rearchitecting in parent issue #2620304: htaccess functions should be a service
Comment #5
kim.pepperI've created a FileSecurity component and moved FileStorage::htaccessLines() there and deprecated it.
I replaced the usages in core/lib/Drupal/Core/Composer/Composer.php
Let's see how this goes!
Comment #6
kim.pepperhttps://www.drupal.org/node/2418133 will need updating if this goes in.
Comment #8
kim.pepperFix test fails.
Comment #9
Mile23Nice, thanks.
We should return a value so that callers can know whether it happened or not.
Comment #10
kim.pepper#9: Now returns a booleean.
Comment #11
Mile23Thanks.
Right, so now that we have a return value that can be FALSE, we need a test case where that happens.
Also needs the same tests for ensureWebConfig().
I'd do it but then I can't review. :-)
Comment #12
kim.pepperThanks @Mile23. I've added some checks if the directory exists and is writeable, and refactored out the common code. Added tests for the negative return values as suggested.
I've also renamed ensureHtaccess -> writeHtaccess as 'ensure' means something different in the existing file_ensure_htaccess(). Basically it knows about the standard directories that need to be written to. I didn't want to confuse the concepts.
Comment #13
kim.pepperFWIW the existing file_save_htaccess() has a few other features which make it hard to reuse the code in here, such as:
Not sure if you think we should/must support some of those features in this component?
Comment #14
kim.pepperAdded file mode, as I think that's a hard requirement. It also allows us to replace duplicate code in \Drupal\Component\PhpStorage\FileStorage::ensureDirectory().
Comment #15
kim.pepperI also think the force overwrite feature is a hard requirement so adding it here.
This means we can replace a good chunk of what's currently in file_save_htaccess() with FileSecurity::writeHtaccess()! 🎉
This is just doing @chmod($htaccess_path, 0444) each time, so I think we are safe to replace it with the components implementation.
Comment #16
larowlancould this be one if instead of two?
Comment #19
kim.pepperFixes tests and addresses #16: moved to a single line.
Comment #20
Mile23LGTM assuming the tests pass.
Comment #21
larowlanComment #22
larowlanshouldn't we be adding a deprecation test for this?
Comment #23
kim.pepperAdds a deprecation test for
FileStorage::htaccessLines()
Comment #24
kim.pepperFeedback addressed so back to RTBC!
Comment #25
joachim CreditAttribution: joachim commentedI've read this three times and I don't understand the connection between the name of the parameter and the description.
Does it mean 'only act for private directories'?
Oh, wait reading the code further down, it's to do with what is written in the file itself.
The use of 'ensure' is what makes it unclear here, because it makes it sound like for some values of the parameter, it's not written. 'write' would be better.
While we're at it, I don't think a boolean value here is very clear. What about having class constants self::PUBLIC_ACCESS and self::PRIVATE_ACCESS?
This should be an if/else rather than a clobber.
This doesn't exist in 8.x as far as I can tell. It looks like it maybe hasn't existed since D6! Let's not bring obsolete documentation into the new class. Besides, you don't need a @see to the method that writes the files, since now it's in the same class.
This comment is not true, since we can force. Should append 'unless told to force.'
Comment #26
joachim CreditAttribution: joachim commentedAnd a couple of things for follow-ups:
This looks weird. If the file_put_contents() succeeds, but the chmod() fails, we return FALSE, even though the file has been written.
Not one to be fixed here, since this issue is about moving the existing code and keeping the same behaviour.
Also, it seems to me like most of the methods on this new class could be private. You're only going to call writeHtAccess() and writeWebConfig(), right? We need the others public for now for the deprecated methods to call, but we could deprecate them as public.
Comment #27
greg.1.anderson CreditAttribution: greg.1.anderson at Pantheon commentedI added the new
drupal/core-filesecurity
component to thereplace
section of thedrupal/core
composer.json file, like the other Components.I updated the variable name and code comment for increased clarity.
I didn't put this in, because
$deny_public_access = self::DENY_PUBLIC_ACCESS
looked redundant to me. I am not opposed to doing this if it is the consensus.This is not a clobber;
$lines
appears in the second set of lines (prepend). If this were a clobber, I would prefer early-exit (return <<<EOF
) over an else. We could move the conditional up and make$lines
always append to improve clarity a bit. I didn't do this because it's more lines, and I think the existing code is clear enough, but I wouldn't be opposed to do it that way if it was the consensus.Good catch. This method is now called
file_save_htaccess
, which still callshtaccessLines
in order to present the error message.This comment is not true, since we can force. Should append 'unless told to force.'
Updated as suggested.
Being able to write the file but not chmod it is an edge case. Perhaps this could be covered in a follow-up issue; however, if it is fixed, then the resolution should be to add a specific error message if the chmod fails. In the current behavior, if the chmod fails after the file is written, then you get an error message; the error message is wrong, but it does at least draw your attention to the file. It would be undesirable to simply return TRUE and silently fail if the chmod did not succeed.
The contents of the
htaccessLines
andwebConfigLines
are made available so that they may be included in error messages should the automatic creation of these files fail. These methods therefore should remain public.Comment #28
greg.1.anderson CreditAttribution: greg.1.anderson at Pantheon commentedSigh, accidentally posted the interdiff twice. Here's the patch that should have been #27.
Comment #29
joachim CreditAttribution: joachim commented> This is not a clobber; $lines appears in the second set of lines (prepend).
Ooops, my mistake. I don't think it's very clear, but I can't think of a way of making it clearer.
> The contents of the htaccessLines and webConfigLines are made available so that they may be included in error messages should the automatic creation of these files fail. These methods therefore should remain public.
I missed the code that calls them. Thanks for the explanation.
> I didn't put this in, because $deny_public_access = self::DENY_PUBLIC_ACCESS looked redundant to me. I am not opposed to doing this if it is the consensus.
I was thinking for the improved clarity in code that calls this:
The only place outside of tests that uses the 2nd parameter is this in file_save_htaccess():
and it's passing on a $private that it gets as its OWN parameter. But then that too could use constants FileSecurity::DENY_PUBLIC_ACCESS / FileSecurity::ALLOW_PUBLIC_ACCESS.
Comment #30
andypostMaybe it's time to clean it up?
Btw not sure constant is needed, I'd better split methods to
writeAllow()
andwriteDeny()
- this is very fragile way to bet on bool for this difference in behaviourAlso if this so much bound to specific apache + php versions this this class could detect used env and be more smart to detect IIS and not write apache for it.
I think it good time to review current webservers and their EOL
Comment #31
greg.1.anderson CreditAttribution: greg.1.anderson at Pantheon commentedLike deprecations of code, I would imagine this should be removed in Drupal 9. If that's not the case, I'd rather discuss timing as a separate issue anyway.
Yeah, I think that might work. Of course, the existing (deprecated) methods will need to maintain the boolean. I'll look at it a bit and see if it's an improvement.
Comment #32
greg.1.anderson CreditAttribution: greg.1.anderson at Pantheon commentedI split out the htaccess snippets into their own functions to make the logic easier to read in htaccessLines. I did not try to get rid of the $deny_public_access boolean, as I did not want to propagate the method bifurcation up the call stack. It didn't seem to make sense to have two
writeHtaccess
methods, etc., and I think with this minor refactor, the existing code is now clear enough.I used 3v4l.org to make sure that
return <<<EOT
is still okay in old PHP versions.Comment #33
greg.1.anderson CreditAttribution: greg.1.anderson at Pantheon commentedHere's another patch that renames
core-filesecurity
tocore-file-security
, as discussed with @mixologic in slack. Forgot that for a minute.Comment #34
kim.pepper", $variables);
Maybe we can inline $variables here?
Comment #35
greg.1.anderson CreditAttribution: greg.1.anderson at Pantheon commentedSure. The original code had these elements broken out, perhaps because the logger line is so long. However, inlining variables is the normal style for Drupal logging, so let's go with consistency.
Comment #36
kim.pepperI worked on this too much to RTBC but it LGTM.
Comment #37
greg.1.anderson CreditAttribution: greg.1.anderson at Pantheon commentedFixing code style errors from the test bot.
Comment #38
Mile23Diggit. LGTM.
Comment #39
andypostFiled follow-up for #30 #3075999: Remove php5 from htaccess writer
Comment #40
greg.1.anderson CreditAttribution: greg.1.anderson at Pantheon commentedAdd 'composer initiative' tag.
Comment #41
larowlanComment #42
larowlanCommitted 00b3ed6 and pushed to 8.8.x. Thanks!
Published change record