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 anEventSubscriberthat runs before language processing, routing and any other consumer of thecurrent_userservice. This provides a single point of entry for authentication which is easier to audit and control. This effectively removes the dependency of thecurrent_userservice on the authentication manager and the request object. - Remove the
AuthenticationEnhancerand 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_authoption of the current route. ThrowsAccessDeniedHttpExceptionexception if it detects a mismatch. - Make sure that all implementers of
AuthenticationProviderInterfacerespect the liskov substitution principle, i.e.AuthenticationManagershould not be a special flower, neither shouldBasicAuthor theCookieprovider. Also ensure thatAuthenticationSubscriberaccepts anyAuthenticationProviderInterface.
Remaining tasks
Reviews and commit.
User interface changes
None
API changes
AuthenticationManagerInterfaceis completely removedAuthenticationProviderInterfacemethods removed:cleanup()(dead code)handleException() (moved to new <code>AuthenticationProviderChallengeInterface)
AuthenticationEnhancerreplaced 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 commentedPoC. Note that this patch declares
current_userservice aspersist. This is necessary because there is no reauthentication anymore.Comment #2
znerol commentedThis is clearly a copy-paste fail.
Comment #3
znerol commentedComment #6
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 commentedTest fails in
MenuRouterTestare a bit tricky. CurrentlyMaintenanceModeSubscriberruns 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 withinMaintenanceModeSubscriberif maint-mode is enabled, but that would result in inconsistent behavior when it is on vs. when it is off.Comment #8
damiankloip commentedCould we do something like this? Not tested.
EDIT: Meant to actually remove that onKernelRequestDetermineSiteStatus method.
Comment #9
shivanshuag commentedComment #10
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 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 commentedReroll. Due to the fact that access checking has been moved inside the
AccessAwareRouter, authentication also needs to be performed there.Comment #14
znerol commentedComment #16
catchThis really looks critical to me, bumping priority.
Comment #17
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 commentedThis time with the rerolled patch.
Comment #20
znerol commentedReroll after #2288911: Use route name instead of system path in user maintenance mode subscriber.
Comment #22
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 commentedI'm a bit stuck with
BookTestwhich 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._authoption on the route object) is not respected. In order to circumvent that, there isAuthenticationEnhancerwhich simply reverts authentication. The enhancer hack is known to cause #2283637: Provide test coverage to prove that an AuthenticationProvider can initiate a sessionglobal $userwhich 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
_authoption is available) and before access is checked.The failures in
BookTestare 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
_authoption) but before theParamConversionEnhancer. Hence it looks like we need to keepAuthenticationEnhancerin 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 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 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 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 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 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 commentedThe
MaintenanceModeTestsomehow 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 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 commentedWith this patch
LanguageRequestSubscriberis now running way before authentication. I wonder whetherAccountProxy::setAccountshould 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 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 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 commentedComment #44
cilefen commentedreroll
Comment #48
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 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 commentedAwesome @znerol :) You've already done great work in this area, and I'd be happy to review.
Comment #51
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 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 commented@dawehner: #52.b: Good question. We need to figure this one out. Updated IS.
Comment #56
almaudoh commentedComment #57
znerol commented@dawehner: #52.b: I cannot find any inbound path processors which depend on the users language.
Comment #58
almaudoh commented@znerol, when I was stepping through the code, I found the
PathProcessorLanguageran before theRouterListener. Maybe you could check?Comment #59
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 commentedReroll.
Comment #62
znerol commentedAdd back the
AuthenticationEnhancer, but instead use it to actually trigger authentication there.Comment #63
znerol commentedSome cleanup: Reuse $account variable in authorize.php, revert unnecessary changes to AuthenticationProvider.
Comment #64
znerol commentedI hate the try..catch in the
AccessAwareRouter, maybe this could be avoided by registeringAuthenticationEnhanceras an event subscriber for theKernelEvents::EXCEPTION, analogous to howParamConvertsionEnhancerworks.Comment #68
almaudoh commentedStraight re-roll
Comment #70
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 commentedOkay. Looks like the
Cookieauth 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 commentedPulled in some ideas from #2228393: Decouple session from cookie based user authentication (nice work @znerol) to separate out cookie auth from the
SessionHandlerinto theCookieauth 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
Cookieauth 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::drupalLoginthat 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 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 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 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 commentedDrilled a bit more into the exceptions thrown by
AccessAwareRouterand why authentication fails afterwards. Found that thePathBasedBreadcrumbBuilderis using theAccessAwareRouterto find matching routes as it is building the breadcrumb off of the current path. Unfortunately, the$requestobject 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
PathBasedBreadcrumbBuilderto use a route matcher (e.g.NestedMatcher) instead of a router to build its links.Comment #84
almaudoh commentedRemoved authentication from
AccessAwareRouter. Now auth is only on theAuthenticationEnhancer.Comment #85
almaudoh commentedMeh
Comment #87
andypostI think this needs own issue to solve.
#84 shows same failures are not caused by that
Comment #88
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 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
_authrouting 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 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 commented@dawehner This will probably not work with the current basic auth implementation when using the
restmodule, because that still insists on doing content negotiation on the same paths that browsers use. If you accessnode/1on 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 Unauthorizedresponse 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 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 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 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 commentedComment #99
znerol commented$this->account->id();inAccessAwareRouter.phpis now a noop, remove that.Should not have any effect on test results though.
Comment #100
znerol commentedComment #102
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 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 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 commentedThe test failures in
UpdateScriptTestare due to the fact thatdrupal_flush_all_cachestriggers a container rebuild. After that thecurrent_userservice will point to an anonymous user. This then leads to the batch failing to redirect toupdate.php/resultsbecause anonymous does not have access to itExtract from
function _batch_finished()incore/includes/batch.incaround line number 456:The problem is that
$redirectis set toFALSEbecause the anonymous user has no access to the update script. As a result the batch triggers a redirect to the current path (update.php/finishedbut without the (batch)idparameter. On the next page load,DbUpdateController::handle()is called again, the request is passed to_batch_pagewhich returnsFALSEbecause there is noidparameter on the request. That leads to the$outputvariable set to a boolean instead of an array and that blows up when passed toBareHtmlPageRenderer::renderBarePage().Long story short, we need to restore
current_userafter a container rebuild.Comment #106
znerol commentedComment #108
znerol commentedRegrettably that is not as easy as it seems. This is mainly because the
entity.managerservice needs some additional setup/cache-clearing before it can be used after modules are updated. CurrentlyHEADrelies 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 theAccountProxywhich is called from withinDrupalKernel::initializeContainerif appropriate.Comment #109
znerol commentedComment #111
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 ofAccountProxyand 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
BlockLanguageTestare 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 commentedWe could use the
UserSessionto restore the user account instead of trying to load theUserentity 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 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 hugeUserentity if we don't need to.Comment #114
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 commentedThe test failures in
BlockLanguageTestactually 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 buggyBlockLanguageTestnow fails).Therefore let's fix
BlockLanguageTesthere.AccountProxyserialization issue is still present.Comment #117
almaudoh commentedRE #111:
Can we do both?
Comment #118
znerol commentedFiled #2430447: AccountProxy should not be serialized.
Comment #119
znerol commentedRollback container awareness of account proxy.
Comment #120
znerol commentedAddresses #102.5, properly inject current user into provider for basic authentication.
Comment #121
znerol commentedKill the
_authentication_providerrequest attribute.Comment #122
znerol commentedRemove
AuthenticationManagerInterfaceanddefaultProviderId(), it was only ever used byAuthenticationEnhancer.Comment #123
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 commented#122 had a spurious invocation of
defaultProviderId()removed by #123 but that has one tocleanup().After removing the invocation of
cleanup()fromAuthenticationSubscriberthis should actually turn green again.Comment #131
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 commentedUpdated IS and added related issue #2228393: Decouple session from cookie based user authentication because cookie auth is still being done in
SessionHandlerComment #133
znerol commentedAlso the meaning/behavior of the
_authroute 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::handleExceptioncompletely and instead implement the basic auth case as standard exception event subscriber.Comment #134
almaudoh commentedComment #135
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 theAuthenticationSubscriberor 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 commented#135 can easily be a follow-up. For #133 we need feedback from
restpeople, because theirsupported_authparameter 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 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 commentedTo be clear: that's supported with the current approach (using
_authparameter, 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 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 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(orFALSEor whatever we want to return for unauthenticated). Only theUserSessionandAccountProxyactually 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 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
setAccountand 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 commentedMaybe I'm not following the discussions well here, but there is still the first question: how do you associate
_authmethod with specific routes? And how isAuthenticationSubscriberaware of this since it is running before routing? Will AuthenticationManager build and maintain it's own mapping ofroute -> _authwhen 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 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 commentedAdd very naive
NoBasicAuthCheck.Comment #153
znerol commentedConfigImportAllTestthrowsUncaught 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 commentedThe failures in
ConfigImportAllTestis 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 todrupalPostFormblows up during the access check because a theNoBasicAuthCheckis still registered on routes. This can be fixed by callingWebTestBase::resetAll()before attempting to load the next page from the SUT.Comment #157
znerol commentedFix rest auth test:
Comment #159
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 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 commentedOk, perhaps its clearer when the logic is written the other way around (see interdiff).
Comment #164
znerol commentedIn
HEADif there is an_authoption 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
_authoption is set).Comment #165
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 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 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 commentedFiled #2431281: Drop support for _access_mode routes and always assume ALL.
Comment #171
znerol commentedIntroduces
AuthenticationMethodCheckreplacing theNoCookieAuthCheckand 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/UserSessionis supposed to serve that purpose, as @almaudoh already mentioned in #113. This however thatgetAuthenticationMethod/setAuthenticationMethodneeds to be added toAccountInterface.Comment #173
znerol commentedSo, the failure in
RouterPermissionTest(Access denied by default if no access specified) confirms @Berdirs concerns. I see two options to move forward:AuthenticationMethodCheckinto a request subscriber which runs after the router.AuthenticationEnhancerbut with the logic ofAuthenticationMethodCheck.Comment #174
znerol commentedImplementation of option 1 (event subscriber), I've opted for a new subscriber instead of moving the logic into the existing
AuthenticationSubscriberin order to allow for site-specific optimizations. If only ever one authentication method is used, theAuthenticationCheckMethodSubscribercould be removed while leaving the other in place.Comment #175
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 commentedRe #171: I still think the
AnonymousUserSession/UserSessionshould 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)UserSessioninto value objects to represent sessions as the name implies. We don't have to pollute theAccountInterfacewithgetAuthenticationMethod/setAuthenticationMethodsince it's not needed there, just extend aUserSessionInterfacefrom it to hold those methods, thenAuthenticationProviderInterface::authenticate()would return an instance ofUserSessionInterfaceComment #178
znerol commentedIntroduces
AuthenticationProviderAccessInterfacewhich is used byAuthenticationProviderCheckSubscriberand implemented inAuthenticationManager. That makes it possible to get rid of the weirdsetAuthenticationMethod/getAuthenticationMethodonAccountProxy.That looks now very close to how I'd like it to be - except for the
setInitialAccountIdweirdness inAccountProxy.Does not yet address any of #176.
Comment #179
znerol commentedTidy up the
BasicAuthmethods as suggested by #176. So it looks likeappliesToRoutedoes 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 commentedAuthenticationManageras well asBasicAuthstill smell, especially because of the weird exception handling due to the basic auth challenge. Also theAuthenticationSubscribercannot really take anyAuthenticationProviderInterfacebut relies onAuthenticationManagerdeviating from its interface.BasicAuthalso 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
AuthenticationProviderInterfacenow can be used withoutAuthenticationManagerdirectly inAuthenticationSubscriber, evenBasicAuth. On the other hand the only entity which has knowledge about the_authroute option is nowAuthenticationManager.#180 not yet addressed.
Comment #182
znerol commentedAddresses #180.3, also removes current user from basic auth again.
Comment #183
znerol commentedRename
AuthenticationProviderAccessInterfacetoAuthenticationProviderFilterInterfaceandaccess()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 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 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 commentedComment #188
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 commentedThe
atmodule from the previous comment has a bug, look fors/both_auth/basic_auth/ginat.routing.yml.Results so far:Table removed, too much visual clutter.
Comment #190
znerol commentedThis is the same table but ordered authentication credentials on the request.Table removed, too much visual clutter.
Comment #192
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 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 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 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 commentedRemove dead code and stale methods.
Comment #200
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
AuthenticationManageris 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
_authmethod,basicauthenticates and then denies. Moreover, the route / path isallowed for all users. That is not proper behavior IMHOIf
basicis 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 commentedComment #202
znerol commentedComment #203
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 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-9651765would be helpful ?Comment #206
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
challengeExceptionmethod nor thechallengerinstance variable. I think it might be worth extracting the creation of theUnauthorizedHttpExceptionto theAuthenticationManagerand only leave generation of the$challengeup to the implementing provider. The interface could be renamed toAuthenticationChallangeGeneratorInterface, and the method togenerateChallenge().Comment #207
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
_authparameter 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
_authrouting 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 commentedEdit: removed the comment
Comment #209
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 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 pullnow (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 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.
@todomentioned in #227/#228Comment #230
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 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 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
SessionHandlerto theCookieauth provider.Comment #235
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 commentedFollow-up: #2471443: Improve test coverage when multiple authentication providers are in use