This issue is part of #2371629: [meta] Finalize Session and User Authentication API and #2047951: [META] Remove calls to deprecated global $user and $GLOBALS['user']
Problem/Motivation
The coupling of the 'current_user' service to AuthenticationManager creates a circular dependency that makes it difficult to implement alternative authentication schemes like basic_auth (see #2283637: Provide test coverage to prove that an AuthenticationProvider can initiate a session). The current_user
service originally was a synthetic service that caused problems. #2180109: Change the current_user service to a proxy fixed these problems by
a) introducing a transparent proxy around the actual user object in order to remove the requirement to declare it synthetic and
b)introducing on-demand authentication (i.e. user authentication is triggered as soon as a method is called on the current_user
object).
However, in order to solve the problems a) is sufficient. b), however, introduces the possibility of user authentication with different authentication methods at multiple points in the request/response cycle e.g. when the container is rebuilt during tests or when enabling/disabling modules. If there are credentials for multiple authentication methods on one request (e.g. a developer logged into a site, testing some requests with token authentication), the behavior of the authentication system is hard to predict (see #104).
Authentication must only be performed once during the request / response cycle. User authentication requires checking the credentials sent along with the initial HTTP request based on a pre-selected authentication method.
The current system allows for a mix of multiple authentication methods. Because not all of them are equally secure, the system needs to support the restriction of authentication methods to only a subset of the site. This is especially true for HTTP basic authentication or URL token based authentication which are commonly used for machine-to-machine communication.
On a 403 (access denied), if there are no credentials on the request, some authentication methods (e.g. basic auth) require that a challenge is sent to the client. For obvious reasons this functionality also needs to be restricted to those routes where such providers are active.
Currently this dilemma is resolved by resetting authentication from within AuthenticationEnhancer
if there is a mismatch between the allowed authentication methods and the chosen one. In order to minimize the opportunity for security problems, it would be better to simply bail out and deny access in this case.
Proposed resolution
- Remove the on-demand authentication of the current user in
AccountProxy::setAccount()
and implement authentication in anEventSubscriber
that runs before language processing, routing and any other consumer of thecurrent_user
service. This provides a single point of entry for authentication which is easier to audit and control. This effectively removes the dependency of thecurrent_user
service on the authentication manager and the request object. - Remove the
AuthenticationEnhancer
and replace it with an additional request listener on theAuthenticationSubscriber
. The new listener runs after routing and filters the selected authentication provider according to the list of allowed providers specified in the_auth
option of the current route. ThrowsAccessDeniedHttpException
exception if it detects a mismatch. - Make sure that all implementers of
AuthenticationProviderInterface
respect the liskov substitution principle, i.e.AuthenticationManager
should not be a special flower, neither shouldBasicAuth
or theCookie
provider. Also ensure thatAuthenticationSubscriber
accepts anyAuthenticationProviderInterface
.
Remaining tasks
Reviews and commit.
User interface changes
None
API changes
AuthenticationManagerInterface
is completely removedAuthenticationProviderInterface
methods removed:cleanup()
(dead code)handleException() (moved to new <code>AuthenticationProviderChallengeInterface
)
AuthenticationEnhancer
replaced by implementors ofAuthenticationProviderFilterInterface
, called fromAuthenticationSubscriber
Beta phase evaluation
Issue priority | Critical |
---|---|
Prioritized changes | The main goal of this issue is security and reducing brittleness. |
Disruption | Mildly disruptive for core/contributed/custom modules |
Comments
Comment #1
znerol CreditAttribution: znerol commentedPoC. Note that this patch declares
current_user
service aspersist
. This is necessary because there is no reauthentication anymore.Comment #2
znerol CreditAttribution: znerol commentedThis is clearly a copy-paste fail.
Comment #3
znerol CreditAttribution: znerol commentedComment #6
znerol CreditAttribution: znerol commentedTest fails in session-tests are fixable by #2265099: Cleanup SessionHttpsTest and fix redirect to non-existing URL after POST requests.
Comment #7
znerol CreditAttribution: znerol commentedTest fails in
MenuRouterTest
are a bit tricky. CurrentlyMaintenanceModeSubscriber
runs before routing and always triggers a user authentication. Moving it after routing and the authentication subscriber results in a whole lot more (contrib-)code being executed in maintenance mode. It would also be possible to trigger authentication from withinMaintenanceModeSubscriber
if maint-mode is enabled, but that would result in inconsistent behavior when it is on vs. when it is off.Comment #8
damiankloip CreditAttribution: damiankloip commentedCould we do something like this? Not tested.
EDIT: Meant to actually remove that onKernelRequestDetermineSiteStatus method.
Comment #9
shivanshuag CreditAttribution: shivanshuag commentedComment #10
znerol CreditAttribution: znerol commentedFiled #2288665: Remove _maintenance request attribute and replace it with a maintenance mode service, should help with disentangling the maintenance mode subscriber.
Comment #11
znerol CreditAttribution: znerol commentedFiled #2288911: Use route name instead of system path in user maintenance mode subscriber on the maintenance mode subscriber in the user module.
Comment #12
znerol CreditAttribution: znerol commentedReroll. Due to the fact that access checking has been moved inside the
AccessAwareRouter
, authentication also needs to be performed there.Comment #14
znerol CreditAttribution: znerol commentedComment #16
catchThis really looks critical to me, bumping priority.
Comment #17
znerol CreditAttribution: znerol commentedReroll after #287292: Add functionality to impersonate a user. Failures in maintenance mode tests are due to #2288911: Use route name instead of system path in user maintenance mode subscriber, rest still needs work.
Comment #18
znerol CreditAttribution: znerol commentedThis time with the rerolled patch.
Comment #20
znerol CreditAttribution: znerol commentedReroll after #2288911: Use route name instead of system path in user maintenance mode subscriber.
Comment #22
znerol CreditAttribution: znerol commentedNote to self: Test-fails from #20
Detailed results are currently not available on qa.drupal.org due to #2376039: Undefined property ContainerAwareEventDispatcherTest::results in run-tests.sh.
Comment #23
znerol CreditAttribution: znerol commentedI'm a bit stuck with
BookTest
which I believe is caused by a rather nasty architecture problem. First let me repeat the problems with the current on-demand authentication by the authentication proxy._auth
option on the route object) is not respected. In order to circumvent that, there isAuthenticationEnhancer
which simply reverts authentication. The enhancer hack is known to cause #2283637: Provide test coverage to prove that an AuthenticationProvider can initiate a sessionglobal $user
which is to be replaced with the authentication proxy).This patch aims to resolve both problems by removing the on-demand authentication of the proxy but instead perform authentication immediately after routing (when the
_auth
option is available) and before access is checked.The failures in
BookTest
are a result of parameter conversion happening before authentication. When a book node is loaded before authentication happened, then node access grants are evaluated for the anonymous user. In the test this results in the node being loaded without book links.It looks like authentication needs to happen after the route was loaded (because of the
_auth
option) but before theParamConversionEnhancer
. Hence it looks like we need to keepAuthenticationEnhancer
in order to authenticate from there.Comment #24
catchJust looked at book_node_load(), which got me to https://api.drupal.org/api/drupal/core%21modules%21book%21src%21BookMana...
Since we have entity caching in core now, book module should absolutely not be doing anything access-dependent in book_node_load() - so I think the test fail is actually exposing a bug in book module and we should change book module instead.
Probably the quickest place to move that logic would be hook_entity_prepare_view().
Comment #25
pwolanin CreditAttribution: pwolanin commentedSo, the book code should be using the node access system to optimize these checks and never be calling access on a single object, normally.
But I guess this is using the really old and crappy node links API. Can we defer the access check to render time? Maybe a question for Wim?
Not sure how to make sure we don't see this general problem again in the future, or it will just break so devs will fix it?
Comment #26
pwolanin CreditAttribution: pwolanin commentedSo, I think the approach here is wrong, or we need to re-think when upcasting happens.
In general, we don't want to go to the trouble of loading objects (upcasting) if authentication is going to fail.
Comment #27
pwolanin CreditAttribution: pwolanin commentedThe bug in this case seems to be that's it's using hook_node_load() to essentially attach render information.
Maybe should be using hook_entity_prepare_view() instead?
Comment #28
pwolanin CreditAttribution: pwolanin commentedOops - no - I take all that back.
Book module is basically trying to load a data field. We should not be checking access at all during that - if access is denied for the node, you won't see the attached field anyhow.
Comment #29
znerol CreditAttribution: znerol commentedI likely confused everybody (including myself) yesterday in IRC when I said that authentication happens before upcasting with that patch. The contrary is true:
The sequence of events with #20 is as follows:
Comment #30
znerol CreditAttribution: znerol commentedThe
MaintenanceModeTest
somehow manages to trigger a 404 which resulted in an exception thrown by the router. We also want to authenticate a request when routing fails, therefore lets usetry ... catch ... finally
. Oh, there is nofinally
, bummer.Comment #32
znerol CreditAttribution: znerol commentedThe basic authentication provider gets activated under the following condition (
BasicAuth::applies()
):The basic authentication provider is supposed to return a 401 Unauthorized response under the following condition
BasicAuth::handleException()
:However, currently
BasicAuth::handleException()
tries to determine whether or not to emit a 401 by checking!$this->applies()
. Frankly I do not really understand how this is working at all inHEAD
.Comment #34
znerol CreditAttribution: znerol commentedWith this patch
LanguageRequestSubscriber
is now running way before authentication. I wonder whetherAccountProxy::setAccount
should emit an event to notify everybody. This also might help with #2054203: HttpBasic authentication tests failing when date.timezone is not set.Do we have to expect any drawbacks when moving language negotiation after routing (and upcasting)?
Comment #35
dawehnerI tried to write a beta evaluation for this issue:
This itself sounds not like a justification for this being a critical? Is the current way of doing it (on the fly) so brittle at the moment?
Comment #36
catchThe patch here has exposed some very dodgy assumptions elsewhere like #2380615: Result of book_node_load() should not vary depending on user permissions. I think we need to evaluate all of the issues on #2371629: [meta] Finalize Session and User Authentication API to figure out which if any are release blocking etc. not sure if this one is or how much of the other work it blocks.
I do think #2286591: [meta] Security audit the Authentication component needs to be done before release - authentication providers are pretty broken at the moment and it's a very security sensitive API. Tthat can only happen once the session and auth APIs are reviewable, which doesn't feel like the case without some of this range of issues being fixed.
Comment #37
znerol CreditAttribution: znerol commented@dawehner, see #2286591: [meta] Security audit the Authentication component
Comment #38
alexpottComment #39
xjmComment #40
dawehnerYou can't do it after upcasting, as upcasting behaves different depending on the language.
Comment #41
znerol CreditAttribution: znerol commentedThe attached patch is a reroll.
Proper language detection only can be performed after the user was authenticated. If language detection needs to be performed before upcasting, this essentially means that authentication needs to be moved in between routing and upcasting. Since upcasting is done in a route enhancer, it follows that we have the following options to resolve this problem:
AccessAwareRouter::matchRequest()
.Comment #43
effulgentsia CreditAttribution: effulgentsia commentedComment #44
cilefen CreditAttribution: cilefen commentedreroll
Comment #48
almaudoh CreditAttribution: almaudoh commentedPer #41
#41.2 looks like the the only option that can be accomodated in the current issue scope, so I will try out that option.
Comment #49
znerol CreditAttribution: znerol commentedI think #41.1 should be explored also. This is comparable to the book bug (#2380615: Result of book_node_load() should not vary depending on user permissions). The primary reason for introducing language-dependent upcasting is convenience, and therefore it might be okay to roll back #2160965: Content entities are not upcast in the page language, inconsistent with config entities.
@almaudoh: I'm willing to continue the coding work here. It would be awesome if you'd assume the reviewer/manager role here, this likely increases the chances that the patch eventually gets to a commitable state.
Comment #50
almaudoh CreditAttribution: almaudoh commentedAwesome @znerol :) You've already done great work in this area, and I'd be happy to review.
Comment #51
znerol CreditAttribution: znerol commentedLet's test the waters with #2420523: Upcasting should not depend on the current user (make upcasting language agnostic)
Comment #52
dawehnerIt would be still great to outline in the issue summary the following points. Its hard to understand what this issue is exactly about, beside of the state of HEAD
a) What is problematic in triggering authentication multiple times. The only thing I can read in the summary is: "in my opinion its wrong"
b) How can you ever resolve the problem that you need the current user to do path inbound processing which comes before routing
c) What is the problem in running authentication again once you have more information due to basic auth?
Comment #53
znerol CreditAttribution: znerol commentedThis is a child issue of #2371629: [meta] Finalize Session and User Authentication API, please read/comment over there concerning the architectural problems we are trying to resolve here.
Comment #54
dawehner@znerol
... this is for everyone trying to understand the issue, please be fair and give people an easy life when they want to participate in any kind of way in this issue.
Comment #55
almaudoh CreditAttribution: almaudoh commented@dawehner: #52.b: Good question. We need to figure this one out. Updated IS.
Comment #56
almaudoh CreditAttribution: almaudoh commentedComment #57
znerol CreditAttribution: znerol commented@dawehner: #52.b: I cannot find any inbound path processors which depend on the users language.
Comment #58
almaudoh CreditAttribution: almaudoh commented@znerol, when I was stepping through the code, I found the
PathProcessorLanguage
ran before theRouterListener
. Maybe you could check?Comment #59
znerol CreditAttribution: znerol commentedI'm postponing this on #2420523: Upcasting should not depend on the current user (make upcasting language agnostic), we need a decision over there before we can continue here.
Comment #60
Gábor HojtsyComment #61
znerol CreditAttribution: znerol commentedReroll.
Comment #62
znerol CreditAttribution: znerol commentedAdd back the
AuthenticationEnhancer
, but instead use it to actually trigger authentication there.Comment #63
znerol CreditAttribution: znerol commentedSome cleanup: Reuse $account variable in authorize.php, revert unnecessary changes to AuthenticationProvider.
Comment #64
znerol CreditAttribution: znerol commentedI hate the try..catch in the
AccessAwareRouter
, maybe this could be avoided by registeringAuthenticationEnhancer
as an event subscriber for theKernelEvents::EXCEPTION
, analogous to howParamConvertsionEnhancer
works.Comment #68
almaudoh CreditAttribution: almaudoh commentedStraight re-roll
Comment #70
almaudoh CreditAttribution: almaudoh commentedAuthentication providers need some of the $request attributes to be injected early. Let's see how many fails we have now.
Comment #72
almaudoh CreditAttribution: almaudoh commentedOkay. Looks like the
Cookie
auth provider is not doing that much in this patch - it's definitely not authenticating and this patch expects it to. Drupal cookie auth is being handled inSessionHandler
.This shouldn't be the case and should be moved to
\Drupal\Core\Authentication\Provider\Cookie
. #2228393: Decouple session from cookie based user authentication may need to go in first before we continue on this issue.Comment #73
almaudoh CreditAttribution: almaudoh commentedPulled in some ideas from #2228393: Decouple session from cookie based user authentication (nice work @znerol) to separate out cookie auth from the
SessionHandler
into theCookie
auth provider. Also applied some fixes from #2328645: Remove remaining global $user (btw, it would help to commit that issue before this one).This makes the patch much larger, but it's good to test that it will work. Afterwards the
Cookie
auth part (the interdiff mostly) can be pulled out to #2228393: Decouple session from cookie based user authentication and finished there.Let's see how many tests fail now.
Comment #75
andypostsuppose better to query sessions first, then join user data, also please left only required columns
Not good for Core to know storage related details of user module.
probably better to use entity query service (properly injected)
Also entity manager should be injected
now this will execute a module handler for alter query hook
Comment #76
andypostComment #77
eojthebraveIt looks like these tests are failing because when you login with
\Drupal\simpletest\WebTestBase::drupalLogin
that it's not sticking. The login works, but then when the browser requests node/add/book, the session isn't found so you can't access the page, can't fill out the form, can't create a new node, and the tests start failing.Also curious. After you run tests, with this patch applied, you're logged out of your session.
Not quite sure what is broken yet, but posting these notes in case it helps someone else see what is broken.
Comment #78
eojthebraveCouple more things.
This patch adds a parameter to the authorize_access_allowed() function, but doesn't add the corresponding @docblock
Typo, "incomming" should be "incoming"
"incomming" should be "incoming"
Comment #79
almaudoh CreditAttribution: almaudoh commentedThanks for the reviews @andypost and @eojthebrave. Will tackle them after fixing the test fails.
#77: Found the same thing too - still trying to figure out why the session gets lost. If you do:
The first one will work, the second one will fail with access denied. That's why
::drupalPostForm('node/add/book', ...)
fails because the post is done on the second call.Interestingly, this doesn't happen for all tests. I wrote a spanking new custom module with a test and copied
BookTest::createBookNode()
into it. And that module's version of::createBookNode()
worked,BookTest
's version failed. Crazy...Still working through the code...
Comment #80
almaudoh CreditAttribution: almaudoh commentedSo I think I found the offending line of code:
Attempting to catch and re-authenticate after an exception in the
AccessAwareRouter::matchRequest()
fails, leading to the current user (as read from the session) being effectively logged out again. This is the cause of most of the test failures - sending to testbot to confirm.The exceptions being thrown are of the nature:
None of the routers in the chain matched this request GET /node HTTP/1.1 Accept: text/html Accept-Charset: ISO-8859-1,...
I'm now drilling down to understand why authentication fails after these exceptions are thrown in the
AccessAwareRouter::matchRequest()
(and perhaps also why the route matching fails in the first place)Is there any particular reason why we need to re-authenticate after the matcher fails to match? If there is a session already and the user is authenticated from the session, need we bother to re-authenticate after the route match fails?
Comment #81
cpj CreditAttribution: cpj commentedI think the nested try/catch was added in #62, so perhaps the reason is explained in the comments and changes around #60 - #70 ?
Comment #83
almaudoh CreditAttribution: almaudoh commentedDrilled a bit more into the exceptions thrown by
AccessAwareRouter
and why authentication fails afterwards. Found that thePathBasedBreadcrumbBuilder
is using theAccessAwareRouter
to find matching routes as it is building the breadcrumb off of the current path. Unfortunately, the$request
object passed in is incomplete (no route parameters) resulting in the exceptions as well as the auth failures.Another undesirable side effect of this is that authentication is re-run multiple times as the breadcrumb links are being built.
A way to fix this would be to changing the
PathBasedBreadcrumbBuilder
to use a route matcher (e.g.NestedMatcher
) instead of a router to build its links.Comment #84
almaudoh CreditAttribution: almaudoh commentedRemoved authentication from
AccessAwareRouter
. Now auth is only on theAuthenticationEnhancer
.Comment #85
almaudoh CreditAttribution: almaudoh commentedMeh
Comment #87
andypostI think this needs own issue to solve.
#84 shows same failures are not caused by that
Comment #88
znerol CreditAttribution: znerol commentedI do not think #73 was a good idea. Can we please go back to #70 and explore the idea in #64, i.e. get rid of the ugly try catch and instead implement an exception subscriber.
Comment #89
almaudoh CreditAttribution: almaudoh commentedThe really clean way to do authentication would have been to do it in a kernel event subscriber that runs before routing. We would avoid all of this route enhancer messiness. AFAICT the only thing making this hard is the
_auth
routing option. Maybe we can figure out a different approach to specifying the authentication provider that doesn't rely on the routing system.Raised #2428609: Refactor PathBasedBreadcrumbBuilder to not use AccessAwareRouter for building links
Can u explain why? I was just trying out what could be possible causes of the test fails. We can move that change back to #2228393: Decouple session from cookie based user authentication and finish it there, I have no problem with that. But we need that change for authentication to work as it should.
Comment #90
znerol CreditAttribution: znerol commentedBecause #44 only had 7 fails. We need to find the minimal amount of changes necessary to get from there to something like #63.
Comment #91
dawehnerJust in general I'm wondering whether we can decouple the routing system from the authentication system by using a path
level override.
Comment #92
znerol CreditAttribution: znerol commented@dawehner This will probably not work with the current basic auth implementation when using the
rest
module, because that still insists on doing content negotiation on the same paths that browsers use. If you accessnode/1
on a rest enabled site, it is impossible to tell whether basic auth or cookie should be used to authenticate. If we want to move authentication before routing, then we need to implement it in a way that different auth providers can coexist throughout the whole site and will not interfere with each other.Yet I think there is a technical solution which allows us to use the same list of authentication providers on the whole site (cookie and basic auth) if we slightly change the way basic auth is implemented. The reason for restricting basic auth to rest-routes is because it is necessary to send a
401 Unauthorized
response if the request lacks credentials and the route is restricted to authenticated users. However, on pages accessed by web users we want to send403 Access Denied
. I think that we may resolve this dilemma by simply restricting the basic auth exception listener to rest-routes (the thing which issues 401).As a consequence it would be possible to use basic auth on all routes on the site. Only if the request lacks credentials, the behavior is different for REST routes than it is for other ones.
Assuming the following authentication providers ordered according to their priority:
We get the following result:
8.0.x
restricted non-rest route, correct credentials
restricted rest route, correct credentials
proposal
restricted non-rest route, correct credentials
restricted rest route, correct credentials
Comment #93
andypostlooks as solid idea, +1
Comment #94
almaudoh CreditAttribution: almaudoh commented+1 to this proposal. Some questions @znerol:
Will basic auth trump over cookie auth in this case if the order of priority was 1) Basic Auth, 2) Cookie Auth?
Comment #95
znerol CreditAttribution: znerol commentedYes.
Same as in
HEAD
, however we only need the information in the exception subscriber and not already upon authentication. Access denied exceptions are thrown by when checking access which is after routing.Comment #96
znerol CreditAttribution: znerol commentedOk, let's try. This does not instantly blow up on my machine, see how far the bot comes.
No interdiff, this is a new approach.
Comment #97
znerol CreditAttribution: znerol commentedComment #99
znerol CreditAttribution: znerol commented$this->account->id();
inAccessAwareRouter.php
is now a noop, remove that.Should not have any effect on test results though.
Comment #100
znerol CreditAttribution: znerol commentedComment #102
almaudoh CreditAttribution: almaudoh commentedThis is a much more elegant approach than what we were doing before. Thanks.
+1
These two look like they are doing the same thing, it's only after checking
SessionConfiguration::hasSession()
docs that we see that sessionConfiguration is actually checking for a session cookie. Not related, but it would be clearer ifSessionConfiguration::hasSession()
was called::hasSessionCookie()
<3 Now that's what I'm talking 'bout.
+1. Bye-bye authentication enhancer.
We shouldn't be using global $user anymore after this issue considering #2328645: Remove remaining global $user will remove the last remaining uses that are not in this issue.
Looks like BasicAuth tests need to be updated to reflect the expectations in #92. Maybe in a follow up.
Is there any use case where we would want to disable Cookie Authentication completely?
Comment #103
cpj CreditAttribution: cpj commentedI agree with @almaudoh, apart from the need to remove the call to
$GLOBALS[user]
, this looks great, so +1 from me too. I will test it as soon as it gets to Needs Review again.Don't know if it's relevant here but I don't use it when we authenticate our REST calls.
Comment #104
znerol CreditAttribution: znerol commentedFYI, I opened a security issue about this a couple of days ago in order to get assurance that we can discuss any level of detail of the security implications of the current design. It was decided that this should be public.
Re #102: Maybe when some site wishes to use TLS client certificates exclusively.
Comment #105
znerol CreditAttribution: znerol commentedThe test failures in
UpdateScriptTest
are due to the fact thatdrupal_flush_all_caches
triggers a container rebuild. After that thecurrent_user
service will point to an anonymous user. This then leads to the batch failing to redirect toupdate.php/results
because anonymous does not have access to itExtract from
function _batch_finished()
incore/includes/batch.inc
around line number 456:The problem is that
$redirect
is set toFALSE
because the anonymous user has no access to the update script. As a result the batch triggers a redirect to the current path (update.php/finished
but without the (batch)id
parameter. On the next page load,DbUpdateController::handle()
is called again, the request is passed to_batch_page
which returnsFALSE
because there is noid
parameter on the request. That leads to the$output
variable set to a boolean instead of an array and that blows up when passed toBareHtmlPageRenderer::renderBarePage()
.Long story short, we need to restore
current_user
after a container rebuild.Comment #106
znerol CreditAttribution: znerol commentedComment #108
znerol CreditAttribution: znerol commentedRegrettably that is not as easy as it seems. This is mainly because the
entity.manager
service needs some additional setup/cache-clearing before it can be used after modules are updated. CurrentlyHEAD
relies on the lazyness ofAccountProxy
, therefore the best way of resolving this issue is to retain some of it.This patch adds a
setInitialAccountId()
method to theAccountProxy
which is called from withinDrupalKernel::initializeContainer
if appropriate.Comment #109
znerol CreditAttribution: znerol commentedComment #111
znerol CreditAttribution: znerol commentedFun, apparently there is existing code which serializes the
AccountProxy
. Now that it is container aware, this obviously blows up. So we need to either remove the container awareness ofAccountProxy
and instead use a static call toDrupal::entityManager()
or figure out is trying to serialize the proxy and whether this is justifiable (i bet not).The failures in
BlockLanguageTest
are interesting too: #2430133: BlockLanguageTest tests non-existing pages. However that indicates that maybe there is a problem with subrequests in this patch because exceptions are rendered in a subrequest.Comment #112
almaudoh CreditAttribution: almaudoh commentedWe could use the
UserSession
to restore the user account instead of trying to load theUser
entity and also creating an additional dependency on entity manager.FWIW I'm not comfortable with making current_user container aware in #108.
Comment #113
almaudoh CreditAttribution: almaudoh commentedActually, I believe the preferred approach to setting the current user should be to use either
new AnonymousUserSession()
ornew UserSession($user_id)
, so we don't have to load the hugeUser
entity if we don't need to.Comment #114
znerol CreditAttribution: znerol commentedThis is at odds with the efforts in #2345611: Load user entity in Cookie AuthenticationProvider instead of using manual queries. According to @Berdir this should actually be faster because entity storage cache..
Comment #115
znerol CreditAttribution: znerol commentedThe test failures in
BlockLanguageTest
actually expose two bugs: one in the test case and one in a weird interaction between the session system andLanguageNegotiationSession
(#2430379: Add explicit test for session based language negotiation) which is fixed by this patch (and that's why the buggyBlockLanguageTest
now fails).Therefore let's fix
BlockLanguageTest
here.AccountProxy
serialization issue is still present.Comment #117
almaudoh CreditAttribution: almaudoh commentedRE #111:
Can we do both?
Comment #118
znerol CreditAttribution: znerol commentedFiled #2430447: AccountProxy should not be serialized.
Comment #119
znerol CreditAttribution: znerol commentedRollback container awareness of account proxy.
Comment #120
znerol CreditAttribution: znerol commentedAddresses #102.5, properly inject current user into provider for basic authentication.
Comment #121
znerol CreditAttribution: znerol commentedKill the
_authentication_provider
request attribute.Comment #122
znerol CreditAttribution: znerol commentedRemove
AuthenticationManagerInterface
anddefaultProviderId()
, it was only ever used byAuthenticationEnhancer
.Comment #123
znerol CreditAttribution: znerol commentedRemove
cleanup()
, this was basically a noop since #2229145: Register symfony session components in the DIC and inject the session service into the request object. That also makes it possible to get rid oftriggeredProvider
.Comment #130
znerol CreditAttribution: znerol commented#122 had a spurious invocation of
defaultProviderId()
removed by #123 but that has one tocleanup()
.After removing the invocation of
cleanup()
fromAuthenticationSubscriber
this should actually turn green again.Comment #131
almaudoh CreditAttribution: almaudoh commentedThis is looking much better now. Just a few nits:
Maybe we can expand on the comment to explain why we are saving the currently logged in user.
This is the only not-so-nice piece of this otherwise great patch. In a follow-up, we may want to look at how this can be improved.
s/manager/provider/. Match docs to function signature. Also the constructor first line doc needs to be updated.
Comment #132
almaudoh CreditAttribution: almaudoh commentedUpdated IS and added related issue #2228393: Decouple session from cookie based user authentication because cookie auth is still being done in
SessionHandler
Comment #133
znerol CreditAttribution: znerol commentedAlso the meaning/behavior of the
_auth
route parameter changed, and I think we should probably replace it with something sensible. It is only used in order to restrict the basic auth exception subscriber to rest routes.Maybe we could replace it with something specific to basic auth or more generic HTTP authentication as defined by RFC 2617 (e.g. a pair of route parameters like
_http_auth_challenge: 'TRUE'
,_http_auth_scheme: 'basic')
. In my opinion it would be legit to get rid ofAuthenticationProviderInterface::handleException
completely and instead implement the basic auth case as standard exception event subscriber.Comment #134
almaudoh CreditAttribution: almaudoh commentedComment #135
znerol CreditAttribution: znerol commentedRegarding #2228393: Decouple session from cookie based user authentication, I think the best approach would be if
AuthenticationProviderInterface::authenticate()
simply would return a user-id (or NULL), that also might help with #2345611: Load user entity in Cookie AuthenticationProvider instead of using manual queries because the entity might be loaded in theAuthenticationSubscriber
or theAuthenticationManager
. Also the check for a blocked user neither should be performed by the session handler nor by the authentication provider (see basic auth).Comment #136
znerol CreditAttribution: znerol commented#135 can easily be a follow-up. For #133 we need feedback from
rest
people, because theirsupported_auth
parameter doesn't make sense either with the new approach.Comment #137
BerdirHm, I'm not sure it can be this easy.
This makes every authentication method available on every part of Drupal. Take something like https://www.drupal.org/project/services_token_access, you don't want that to allow you to access every page?
I also think it is wrong to hardcode basic auth to rest routes, and you're not actually doing that yet, you're still relying on the _auth option. custom services/API's will want to use basic auth as well, and want to get a login prompt, somehow?
I'm not sure why the following is not an option, I've not read every comment/patch here:
1. Do the authentication like done here, the first one that matches wins, don't worry about routes.
2. When matching the route, compare the method that was used with the requirements. Possibly improve improve the fallback to a configurable list of default methods instead of that hardcoded last-in-list-wins assumption. (This should be able to deal with the "but I want to allow client cert and cookie, or only client cert" requirement)
3. If it doesn't match, access denied. No re-trying or anything, just bail out.
So then you try to use basic auth to access /user. With HEAD, you get a normal page, as anonymous, now you get a 403, so what? IMHO, that's even better. What worse can happen, how is that a problem?
Comment #138
znerol CreditAttribution: znerol commentedTrying to rephrase @Berdirs idea: Introduce an access-checker which we can use to restrict some authentication methods to some routes. Would that be sufficient?
In order to do something like this we probably should store the authentication method in the current user (i.e. introduce
AccountInterface::getAuthenticationMethod()
.Comment #139
znerol CreditAttribution: znerol commentedTo be clear: that's supported with the current approach (using
_auth
parameter, or something better like proposed in #133).Comment #140
BerdirI don't know if access checker or built-in, but as written, we whould have to keep the current behavior more or less that by default, only the default method(s) are allowed?
A method on that interface might work, but we also need a way to set it, we can say that we only have that on actual implementations, but user entity is tricky, there we need it on the interface, which would actually be a bit weird?)
Comment #141
cpj CreditAttribution: cpj commentedFor our REST interfaces we use our own custom provider, which passes an OAuth2 token to an external authentication server. For "normal" access we use Cookie Authentication.
I am not sure what is being suggested here, but it would make sense to me that I can configure a route to use whatever provider I want.
Comment #142
BerdirThe latest patch removes that option, and I agree with you that is not an option, including the part that not specifying anything on a route needs to use a (limited, somehow configurable) list, not all.
HEAD is doing some strange things right now and tries to re-authenticate if you e.g. use basic auth for a route that does not allow it, so it switches to the cookie authentication, which then provides the default anon user. My suggestion was to simply drop that part and in that case, return a 403 and be done with it.
Comment #143
znerol CreditAttribution: znerol commentedI think that makes sense, and I think that functionality belongs into the domain of access checking. I'm not yet sure whether it should be implemented as a *real* access checker automatically added to each route or just be plugged directly into the access manager.
Regarding the
getAuthenticationMethod()
on the user entity, that could be implemented to just returnNULL
(orFALSE
or whatever we want to return for unauthenticated). Only theUserSession
andAccountProxy
actually need to return something useful. That said, it is also weird to have all those*session*
methods on the user entity.Comment #144
BerdirI'm not sure, access checking is also applied to all displayed links, and that's a pretty pointless thing to run those?
Uhm, no? basic auth already returns loaded entities, and so would #2345611: Load user entity in Cookie AuthenticationProvider instead of using manual queries? And those need to return something too?
That's also not my point, I've no issue with adding getAuthenticationMethod(). I'm asking how basic_auth would tell the loaded $user entity that it was loaded through basic_auth. For that, we need "setAuthenticationMethod()" on UserInterface, and that's the weird part :)
Comment #145
Berdir- Removed, wrong issue -
Comment #146
znerol CreditAttribution: znerol commentedNot if you declare that the check in question needs the request, then it is only applied to incoming requests.
Not really because you only ever access them through the request proxy (at least as soon as #2328645: Remove remaining global $user is in). I think it would be acceptable if the authentication method is passed to the account proxy upon
setAccount
and stored there.Comment #147
BerdirRight, we could possibly do AccountProxyInterface::getAccountMethod(), but that's not what you wrote :)
Ignore comment #145, wrong issue o.0
Comment #148
almaudoh CreditAttribution: almaudoh commentedMaybe I'm not following the discussions well here, but there is still the first question: how do you associate
_auth
method with specific routes? And how isAuthenticationSubscriber
aware of this since it is running before routing? Will AuthenticationManager build and maintain it's own mapping ofroute -> _auth
when routes are being built?Also, I don't think it makes sense to authenticate successfully with cookie auth, for example, only to 403 again just because the route specified basic auth since cookie auth never gave basic auth the chance to authenticate the user (cookie auth applied first).
Comment #149
BerdirNo mapping, no knowing.
We authenticate first, and know user X, with authentication method Y.
When we check access to the route, we look for the _auth option and deny access if we have a mismatch.
Maybe I'm missing something, because it almost seems too easy ;)
No, basic_auth runs first. It looks like it doesn't in HEAD, but that is because we filter it out when we don't know yet what will be allowed. This is exactly what would change, removing that filtering.
It might not allow for every imaginable scenario, but I can currently not think of any that would be an actual problem.
I think what we should do is convert the comparison from #92 into a table with HEAD | znerol's idea | my idea and extend it with at least one more use case: trying to access /user with basic authentication.
I don't have time to do that right now, if someone can start that, that would be great, I'm happy to complete/review it later.
Comment #150
znerol CreditAttribution: znerol commentedWe only make it possible to customize the behavior on a per-route basis in those two cases:
The second situation is what we are talking about at the moment. The use case for this is mentioned in #137: in many cases it is desirable to restrict some authentication methods to some routes.
Both of these cases can be handled with the current design of the patch. Authentication is performed in an early request subscriber, but the effective behavior is determined on a per-route basis (but without resetting the current user and reauthentication, like HEAD currently does).
Comment #151
znerol CreditAttribution: znerol commentedAdd very naive
NoBasicAuthCheck
.Comment #153
znerol CreditAttribution: znerol commentedConfigImportAllTest
throwsUncaught PHP Exception InvalidArgumentException: "No check has been registered for access_check.no_basic_auth" at core/lib/Drupal/Core/Access/CheckProvider.php line 99
.Comment #154
larowlanCould go in a trait to avoid duplication?
Comment #155
znerol CreditAttribution: znerol commentedThe failures in
ConfigImportAllTest
is due to the fact that the routes are not immediately rebuilt by the uninstall() on line 114, instead the module-installer just sets a flag that a rebuild is needed. Regrettably that rebuild then never happens. Because of that the subsequent call todrupalPostForm
blows up during the access check because a theNoBasicAuthCheck
is still registered on routes. This can be fixed by callingWebTestBase::resetAll()
before attempting to load the next page from the SUT.Comment #157
znerol CreditAttribution: znerol commentedFix rest auth test:
Comment #159
znerol CreditAttribution: znerol commentedComment #160
BerdirI'm confused why you add a custom check for basic auth? We want a generic solution, that is also applied if there is no _auth option, which means it is applied to all routes. And that would imho just cause unexpected troubles with ANY, so it should not be an access check?
Comment #161
znerol CreditAttribution: znerol commentedBecause I'm just exploring the simplest solution. We can generalize later (especially after verifying the behavior on larger scale).
I definitely do not agree here, it should be an access check if possible.
Comment #162
BerdirBut it isn't possible :)
It will apply to every route and return TRUE or FALSE. This will completely break the ANY/or mode, because those will always allow access.
Comment #163
znerol CreditAttribution: znerol commentedOk, perhaps its clearer when the logic is written the other way around (see interdiff).
Comment #164
znerol CreditAttribution: znerol commentedIn
HEAD
if there is an_auth
option on the route, then only those authentication methods are performed which are explicitly enabled. This is currently not the case in the patch. However, that behavior could also be expressed by an access check for cookie authentication analogous to the one for basic authentication.If this works, the difference between cookie auth and basic auth is that the former is allowed on routes by default (i.e. even if no
_auth
option is set).Comment #165
znerol CreditAttribution: znerol commentedReally? If this is true then this means that the access-check system is screwed up completely (at least the any-mode). How are we supposed to compose access checks if the checker function needs to have information on whether it is part of an or vs. and check?
Comment #166
BerdirNot sure what you mean. ANY means that at least one check needs to return TRUE. If we add an access check that applies to all routes, and always returns TRUE, then the routes that use ANY always result in TRUE, because at least one check is always going to do so.
That's not screwed? That's ANY, by design. And the access checker knowing about it or not doesn't change anything?
Comment #167
znerol CreditAttribution: znerol commentedIf it is not possible to add an additional access checker function to whatever route without first inspecting the access check configuration and either add one that returns deny, allow (on AND checks) or deny, neutral (on ANY checks), then in my opinion that access check system is not so usable. It means for example that you cannot add CSRF protection to your route if you use the ANY access mode.
Comment #168
dawehnerMh? For CSRF you always want to KILL. Other cases might want to consider other access checkers as well.
Comment #169
znerol CreditAttribution: znerol commented@dawehner: There is no problem when using
forbidden()
to deny access, but there is a problem when usingallowed()
in context of an access check in ANY mode.The problem is that an access checker has no means to say I don't care, other checkers should decide in a way which works in ANY as well as in ALL mode. If you return
allowed()
then you allow too much in ANY mode and if you returnneutral()
than that evaluates toforbidden()
in ALL mode.Comment #170
znerol CreditAttribution: znerol commentedFiled #2431281: Drop support for _access_mode routes and always assume ALL.
Comment #171
znerol CreditAttribution: znerol commentedIntroduces
AuthenticationMethodCheck
replacing theNoCookieAuthCheck
and NoBasicAuthCheck. This is very WiP and has some stupid design problems, especially the second optional pass-by-reference parameter inAuthenticationManager::authenticate()
.From a conceptual pov, the result of authentication probably should be a tuple (user-id, provider-id). In fact
AnonymousSession
/UserSession
is supposed to serve that purpose, as @almaudoh already mentioned in #113. This however thatgetAuthenticationMethod
/setAuthenticationMethod
needs to be added toAccountInterface
.Comment #173
znerol CreditAttribution: znerol commentedSo, the failure in
RouterPermissionTest
(Access denied by default if no access specified) confirms @Berdirs concerns. I see two options to move forward:AuthenticationMethodCheck
into a request subscriber which runs after the router.AuthenticationEnhancer
but with the logic ofAuthenticationMethodCheck
.Comment #174
znerol CreditAttribution: znerol commentedImplementation of option 1 (event subscriber), I've opted for a new subscriber instead of moving the logic into the existing
AuthenticationSubscriber
in order to allow for site-specific optimizations. If only ever one authentication method is used, theAuthenticationCheckMethodSubscriber
could be removed while leaving the other in place.Comment #175
znerol CreditAttribution: znerol commentedFiled #2431357: Extract a reusable UserAuthFlood service and reuse it in UserLoginForm and BasicAuth and UserAuthenticationController
Comment #176
vijaycs85Few review comments/questions:
so, we are trying to set account and auth_method only for authenticated user here?
Minor: typo
Do we need a follow up for this todo? if there is one, can we update the d.o URL?
More than 80 chars?
Why isn't this part of applies()? do we really need a new method here?
Comment #177
almaudoh CreditAttribution: almaudoh commentedRe #171: I still think the
AnonymousUserSession
/UserSession
should be used for authentication instead of the User entity. This decouples the authentication system from the entity system and keeps code less complex.We could re-purpose the
(Anonymous)UserSession
into value objects to represent sessions as the name implies. We don't have to pollute theAccountInterface
withgetAuthenticationMethod
/setAuthenticationMethod
since it's not needed there, just extend aUserSessionInterface
from it to hold those methods, thenAuthenticationProviderInterface::authenticate()
would return an instance ofUserSessionInterface
Comment #178
znerol CreditAttribution: znerol commentedIntroduces
AuthenticationProviderAccessInterface
which is used byAuthenticationProviderCheckSubscriber
and implemented inAuthenticationManager
. That makes it possible to get rid of the weirdsetAuthenticationMethod
/getAuthenticationMethod
onAccountProxy
.That looks now very close to how I'd like it to be - except for the
setInitialAccountId
weirdness inAccountProxy
.Does not yet address any of #176.
Comment #179
znerol CreditAttribution: znerol commentedTidy up the
BasicAuth
methods as suggested by #176. So it looks likeappliesToRoute
does in fact the same asAuthenticationProviderAccessInterface::access()
and if we use that inAuthenticationManager::access
, then there is no need anymore forAuthenticationManager::$defaultProviders
. Bam, two birds with one stone!Comment #180
klausiThis should be @param.
I'm not sure if "access" is a good name here. Maybe "isAllowed()" instead?
@return should say "TRUE if this authentication method is allowed ..."
Could you add a comment here why we cannot inject the user storage here and must call the global \Drupal?
Otherwise looks like an improvement to me and the changes to the REST tests look fine.
Comment #181
znerol CreditAttribution: znerol commentedAuthenticationManager
as well asBasicAuth
still smell, especially because of the weird exception handling due to the basic auth challenge. Also theAuthenticationSubscriber
cannot really take anyAuthenticationProviderInterface
but relies onAuthenticationManager
deviating from its interface.BasicAuth
also has the provider id hard-coded in itsaccess()
method, which is really a bad thing. The provider id is a concept which is actually private to theAuthenticationManager
. There is no means on how we could pass around provider ids in the current interfaces.Both of these problems can be fixed by moving some of the basic auth exception logic to the authentication subscriber and rewrite the interface according to the special needs of basic auth - i.e. it desires to send a special response on a 403 (access denied), if there are no credentials on the request.
As a result any implementer of
AuthenticationProviderInterface
now can be used withoutAuthenticationManager
directly inAuthenticationSubscriber
, evenBasicAuth
. On the other hand the only entity which has knowledge about the_auth
route option is nowAuthenticationManager
.#180 not yet addressed.
Comment #182
znerol CreditAttribution: znerol commentedAddresses #180.3, also removes current user from basic auth again.
Comment #183
znerol CreditAttribution: znerol commentedRename
AuthenticationProviderAccessInterface
toAuthenticationProviderFilterInterface
andaccess()
toappliesToRoutedRequest()
, addresses the remaining comments in #180.Comment #184
Berdirtypo.
you should be able to get this from the request context, getCompleteBaseUrl()
Comment #185
znerol CreditAttribution: znerol commentedWhen implementing an authentication provider collection (like authentication manager basically does), it is important that the filter-method (a.k.a.
appliesToRoutedRequest
) knows whether it is evaluated for authenticated requests (before throwing eventually throwing the 403) or for unauthenticated requests (before eventually sending the 401).Addressed #184. Replaced the
$base_url
(the fallback for when the site name is not configured, which is very unlikely) with a well known static string. The realm can be chosen freely, so let's make use of that fact.Service construction / global providers still need some work. At least sorting of the providers should be done in the compiler pass.
Comment #186
znerol CreditAttribution: znerol commentedRewriting service construction requires the introduction of a new compiler pass. Filed a follow-up: #2432585: Improve authentication manager service construction to support custom global service providers.
Comment #187
znerol CreditAttribution: znerol commentedComment #188
znerol CreditAttribution: znerol commentedAdd a module and a script to help exercising before/after scenarios. Test numbers can be specified on command line. If none are given, all requests are performed.
Comment #189
znerol CreditAttribution: znerol commentedThe
at
module from the previous comment has a bug, look fors/both_auth/basic_auth/g
inat.routing.yml
.Results so far:Table removed, too much visual clutter.
Comment #190
znerol CreditAttribution: znerol commentedThis is the same table but ordered authentication credentials on the request.Table removed, too much visual clutter.
Comment #192
cosmicdreams CreditAttribution: cosmicdreams commentedThis is hard to follow. The tables seem to have different labels and structure.
Can you bold the items that demonstrate the differences / bugs?
Comment #193
BerdirHere's my attempt at displaying the results.
Note, I misunderstood first, so explanation of the columns:
access: allow = anyone, auth = only when logged in, deny = nobody is allowed to access
_auth: what the route defines as the _auth option
cred: credentials the request sends, (both means both cookie and basic auth in the same request)
response: the response status code
Output is a "diff -up head.txt patch.txt", so the changes are as the patch changes it:
The differences are easy to explain:
4: sends both, basic auth is checked first, authenticated, then denied (because non means the default aka cookie). This is exactly the change that I mentioned above, IMHO OK and actually expected.
8: same except it explicitly wants cookie
10: This is an interesting change, I think this makes sense, wrong auth method should not allow, even if the route allows anyone access (that combination does not make sense, but it's also not relevant that it would allow anyone, just that it does if you're authenticated). No idea why it allowed that before?
20 & 24 are the same as 4 & 8, 26 is the same as 10 (it makes no difference for all of those if only auth or all are allowed)
42. is the same as 10 really, we would have allowed access but then basic auth would have kicked in with the challenge I think. Now we deny access.
So all those changes are IMHO valid, and I like the new Authentication subscriber :)
Comment #194
znerol CreditAttribution: znerol commentedThis is strange, my results also indicate that 3 and 7 change. I'd also expect that because of the same reason like 4 and 8.
Comment #196
znerol CreditAttribution: znerol commentedComment #197
BerdirConfirmed, sorry. Looks like the page cache interfered locally, I think this is the security issue that was recently closed and has yet to be fixed in 8.x (aka, basic auth requests must not be page cached, ever).
Comment #198
znerol CreditAttribution: znerol commentedOpened #2432657: BasicAuth challenge never sent to browser, #2432911: Provide test coverage to prove that a third party authentication provider does not automatically start a session
Comment #199
znerol CreditAttribution: znerol commentedRemove dead code and stale methods.
Comment #200
almaudoh CreditAttribution: almaudoh commented@znerol uploading patches at superspeed - like The Flash :)
Some reviews and some comments:
Doc should be
AuthenticationProviderFilterInterface
.The name $challengers threw me off at first, and I was wondering "what are they challenging". Couldn't figure out from the name until I read the code - challenging the
AccessDeniedException
? Maybe a more descriptive name?Hard-coding cookie auth, what if there's a use case where cookie auth is not wanted? Guess we can discuss that more at #2432585: Improve authentication manager service construction to support custom global service providers
Wonder if we couldn't just inject these as separate arguments since we have separate interfaces, this makes the dependencies more explicit. Of course, this implies @authentication would be repeated three times in the service definition arguments. But this just makes it clearer that
AuthenticationManager
is playing a triple role.@return doc doesn't match method.
Overall, these are great changes.
Re #193 & #194: The behavior in 4 & 8 is not satisfactory. Without specifying an
_auth
method,basic
authenticates and then denies. Moreover, the route / path isallow
ed for all users. That is not proper behavior IMHOIf
basic
is not among the default auth providers, it shouldn't be the one to authenticate in the first place, even though it is higher in priority.This is my concern and will cause problems in a use case I have right now.
[Edit: completed sentence]
Comment #201
znerol CreditAttribution: znerol commentedComment #202
znerol CreditAttribution: znerol commentedComment #203
znerol CreditAttribution: znerol commentedComment #204
BerdirI respectfully disagree ;)
Note that it is *not* possible authenticate with basic auth on HEAD if no _auth option is provided either. What happens instead is that basic_auth fails to authenticate in the first place*, we fall back to session, "authenticate" as anon user and are granted access.
* To clarify even more: basic auth *always* fails at that point. We throw away the result and repeat it after we get the route, and only then basic auth can authenticate.
The fact that all users would be allowed to access the page is a detail that we don't know yet at that point.
This is also visible in the fact that those cases are no longer different in second group of checks, where we actually require a user. There both deny access, if for a different reason.
Care to share your use case for this? I've trouble imaging a use case that relies on this.
Also, you +1'd the suggestion from @znerol in #92, which would have removed the concept of a limited list of default auth providers completely, so basic auth would have allowed access to all routes.
We just can't have it both ways. We can't require the authenticated user before routing and make authentication depending on the matched route.
See also #104, which explains possible issues with the current behavior. This issue IMHO solves that, because we only have one authentication process and we can rely on the result, even if we end up denying access because of the used method.
Comment #205
cpj CreditAttribution: cpj commentedIn the hope that the discussion has completed, I reviewed & tested the code, and using the test module from #188, I believe I have been able to reproduced the results in #193.
But I don't feel confident enough about what is going on to mark this RTBC. I wonder if some sort of flow chart, maybe something like
https://www.drupal.org/node/2212069#comment-9651765
would be helpful ?Comment #206
znerol CreditAttribution: znerol commentedI do not think that this is RTBC yet. There are still some code style issues pointed out in #200. I also do neither like the
challengeException
method nor thechallenger
instance variable. I think it might be worth extracting the creation of theUnauthorizedHttpException
to theAuthenticationManager
and only leave generation of the$challenge
up to the implementing provider. The interface could be renamed toAuthenticationChallangeGeneratorInterface
, and the method togenerateChallenge()
.Comment #207
almaudoh CreditAttribution: almaudoh commentedRe #204: We want to see the new authentication system as something that should be flexible enough to apply to other (possibly not yet envisioned) use cases. In this patch we are basing it on the specific use cases that assume basic auth would run before cookie auth - I realize this is based on what we already have in HEAD.
While this patch is a big improvement on what we have in HEAD, we do have to acknowledge that the use of the
_auth
parameter in routing is the key limitation in implementing a more flexible and robust authentication system. Because we should only do authentication once, with the appropriately selected auth method, prior to routing.In order not to hold up this patch, which I would not want to do, we can continue this line of reasoning in a separate issue.
I actually saw that as an intermediate step, after which we could figure out how to limit auth methods to routes without using the
_auth
routing parameter (mentioned in #89). But the ideas took a different direction, which is why I got confused thereafter.Will experiment with other different possibilities in a separate issue.
Re #205: A flow chart would be nice. Let's see what I can come up with.
Comment #208
almaudoh CreditAttribution: almaudoh commentedEdit: removed the comment
Comment #209
cpj CreditAttribution: cpj commented@almaudoh, #207
I think flexibility & robustness are essential. I find the current system & proposed changes difficult to understand. The 45 step manual check in #193 illustrates how complex the system is. So some kind of picture would be very helpful - thanks for offering.
Comment #211
BerdirRe #206: Are those things really blocking this (a critical which I think is blocking further work on ~3 related/meta criticals)? Can we put some things like anything that's not a public API to follow-ups?
Updated patch with some documentation fixes.
1. Fixed, also a few other documentation improvements, verified by going through the class, PhpStorm can now identify each method call as valid based on the documentation.
2. See http://en.wikipedia.org/wiki/Challenge%E2%80%93response_authentication, but I agree that the name isn't perfect, one thing is that I'm wondering is whether it is too specific/narrow.. could there be other things an authentication provider might do that's not a challenge? Maybe we can improve the documentation on the interface/property as a start?
3. Yeah, I think we should look into that in a follow-up issue. In the meantime, you could write a ServiceModifier implementation that would pass in a different global provider. It's not really hardcoded, just the default constructor value :)
4. No we can't, not without writing a custom service collector compiler pass, that only supports a single method/interface. I'm not sure where you mean to repeat @authentication thrice, this method is called implicitly due to service tags. Doesn't seem like a big problem to me, it's essentially just a internal optimization to store them differently, we could also loop over all providers and check if they implement a certain interface when we access it. There's no need for multiple public methods and someone outside of AuthenticationManager to know about this.
Comment #212
almaudoh CreditAttribution: almaudoh commentedThanks @Berdir. I think we can move forward with this - the other issues can be follow-ups. Was hoping to have some time to review and test this but I can't
git pull
now (been behind a firewall for the last 3 weeks). Hope to look at the patch in some detail before the weekend.Comment #213
jibranThis is not according to doc standards.
Comment #214
BerdirFixed the @todo's.
Comment #215
klausiThe patch makes lots and lots of sense to me.
Can we have test coverage for this? Do we already have an AuthenticationManager (unit?)test somewhere that we can expand?
so noone except the authentication manager implements this interface in core? Do we have test coverage for this?
Before we RTBC this we also need a change record draft.
Comment #216
dawehnerThis looks great! I'm working now on a change record.
So this means we could move that onto a middleware at some point.
Comment #217
dawehnerAlright posted. https://www.drupal.org/node/2453449
Comment #218
jibranSorry @Berdir for such a use less review. I hoped that by bumping it someone would actually review it. Let's see what @alexpott thinks about it.
Comment #219
jibranSorry old browser tab. NW as per #215
Comment #220
BerdirThanks for the reviews.
1. I had a quick look at unit tests, but mocking RouteMatch::createFromRequest($request)->getRouteObject(); would be pretty painful I think, lots of object and methods to set up even when creating real Request object.
2. We have no other implementation, but we're testing the one we do I think.
What we could do to improve test coverage is convert the script from #188 into a web test, and hardcode and document the expected results.
Comment #221
BerdirOk, writing a unit test for that method is actually not that hard.
I've also refactored that method, I think it is much easier to understand that way, and it is actually by far not as complex as it looked before.
The unit tests I think actually cover both points, at least the relevant implementation in AuthenticationManager (that you can implement the filter interface, although I'm not quite sure what the use case for that is)
Comment #222
BerdirGrml, had some left-overs in the unit test class.
Comment #224
jibranThanks for the test @Berdir. I think it is ready now.
Comment #225
BerdirUpdated the change record a bit with a more detailed before/after description of what the change means.
Comment #226
dawehnerberdir++
Comment #227
cosmicdreams CreditAttribution: cosmicdreams commentedIn reading the code last night I found a @todo that mentions this issue. It reads:
This global also exists in:
Drupal/Core/Session/SessionHandler.php
Drupal/Core/Session/SessionManager.php
As far as I can tell, this patch does not remove global $_session_user. Should it?
Removing it was the focus of my efforts in this area last year and I wasn't able to remove it because of the problem this issue outlines.
Should we simply create a follow up and update the todo to point to a fresh issue where we remove the globals?
Comment #228
Berdir#2228393: Decouple session from cookie based user authentication will take care of that, I think. This issue temporarily merged that in but that was reverted again. We should update the @todo to point to that.
Comment #229
alexpottMissing @param documentation - yes we have a todo but in case this remains...
$authenticated is not used - do we need it?
Missing leading slash
Missing leading slash
Challenge is misspelt.
Why is this required? At the very least there should be a comment.
Missing param documentation.
@todo
mentioned in #227/#228Comment #230
znerol CreditAttribution: znerol commentedSee #155 regarding the changes in
ConfigImportAllTest
. This hunk might be not necessary anymore though because we do not rely on access checks in the latest patch.Comment #231
BerdirThanks for finding all that stuff.
1; Added documentation.
2: Removed it.
3-5: Fixed.
6: Removed, let's see.
7: Added.
8: Updated all @todo's.
Comment #232
cpj CreditAttribution: cpj commented@Berdir - Is the manual testing from #193 still needed ?
Comment #233
BerdirI don't think so.. as mentioned above, we might want to convert that test script into a test to verify the expected behavior, but I think we can do that in a follow-up, we have enough existing coverage IMHO.
Comment #234
almaudoh CreditAttribution: almaudoh commentedNice work @Berdir. Back to RTBC after addressing Alex's comments. I have uploaded the interdiff from #73 at #2228393: Decouple session from cookie based user authentication to move cookie auth from
SessionHandler
to theCookie
auth provider.Comment #235
cpj CreditAttribution: cpj commentedI have also reviewed the code again, so +1 for RTBC
Comment #236
alexpottThis issue addresses a critical bug and is allowed per https://www.drupal.org/core/beta-changes. Committed 531f95e and pushed to 8.0.x. Thanks!
Comment #238
clemens.tolboomhttps://www.drupal.org/project/restui is now broken due to #2456303: AuthenticationManager needs interface and ::getProviderKeys as reported in Rest UI #2456283: AuthenticationManager::getSortedProviders is protected
Comment #240
xjmComment #241
znerol CreditAttribution: znerol commentedFollow-up: #2471443: Improve test coverage when multiple authentication providers are in use