Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Regression] In 2.4 "Credentials Wrong" state does not retrigger the password-asking form #5989

Closed
SamuAlfageme opened this issue Sep 1, 2017 · 20 comments
Assignees
Labels
ReadyToTest QA, please validate the fix/enhancement sev2-high type:bug
Milestone

Comments

@SamuAlfageme
Copy link
Contributor

SamuAlfageme commented Sep 1, 2017

Expected behavior

After receiving the 401: Unauthorized from the server (<s:message>No public access to this resource., Username or password was incorrect, Username or password was incorrect</s:message>) client 2.3.3 displays again the password dialog.

Actual behavior

The status on the account tab stays Connecting to <server> as <username>... until it times out and switches to Signed out...:

[ warning sync.connectionvalidator ]:	******** Password is wrong! QNetworkReply::NetworkError(OperationCanceledError) "Operation canceled"
[ info gui.account.state ]:	AccountState connection status change:  "Credentials not ready" -> "Credentials Wrong"

Steps to reproduce

  1. Log out from any client's account and try to log in with the wrong password
@SamuAlfageme SamuAlfageme added this to the 2.4.0 milestone Sep 1, 2017
@ckamm ckamm self-assigned this Sep 5, 2017
@ckamm
Copy link
Contributor

ckamm commented Sep 5, 2017

@ogoffart I've debugged this using mitmproxy, and what seems to be happening is this: When the password is rejected, we do call HttpCredentialsGui::askFromUser again, but the GET request (to check whether the server can do oauth) stalls. After 30s the ::abort timer triggers.

Do you have any ideas about why the request would not be performed? (there's no entry in mitmproxy for it)

@ogoffart
Copy link
Contributor

ogoffart commented Sep 6, 2017

@ckamm: I have no idea, if askFromUser is called, then normally the GET should be done. Some debuging might be in order.

@ckamm
Copy link
Contributor

ckamm commented Sep 6, 2017

Yeah, I'll look at it more deeply today. It's confusing that the GET request doesn't even go out. Also, I think this might deserve code sharing with DetermineAuthType - currently it doesn't deal with redirections properly.

@ckamm
Copy link
Contributor

ckamm commented Sep 6, 2017

Debugging has yielded the following (thanks to @guruz, @jturcotte, @ogoffart for suggestions):

  • When HttpCredentialsGui::askFromUseris called from user action, everything works, including the GET request to determine the auth type.
  • When the user provides an incorrect password, the PROPFIND fails and askFromUser is called again. That is good.
  • However, this time, the sendRequest("GET", ...) in there fails to do anything (there's no error, it is not destroyed, it just does nothing)
  • This seems likely to be caused by initiating a new request directly in the call stack that's processing the PROPFIND's QNetworkReply::finished. Evidence: If askFromUser is delayed to the event loop by using invokeMethod(QueuedConnection), the GET request is processed as expected.

Possibly a QNAM bug in Qt 5.7.1?

Backtrace for @guruz

Thread 1 (Thread 0x7ffff7f06d40 (LWP 770)):
#0  0x00005555556d3974 in OCC::DetermineAuthTypeJob::start() (this=0x555555e14010) at /home/kamm/dev/woboq/mirall/mirall/src/gui/owncloudsetupwizard.cpp:617
#1  0x000055555573b93d in OCC::HttpCredentialsGui::askFromUser() (this=0x555555ce4a90) at /home/kamm/dev/woboq/mirall/mirall/src/gui/creds/httpcredentialsgui.cpp:61
        qnamLock = {value = 0x555555bf2050, d = 0x5555560b6de0}
        job = 0x555555e14010
        __PRETTY_FUNCTION__ = "virtual void OCC::HttpCredentialsGui::askFromUser()"
#2  0x000055555572c7a6 in OCC::AccountState::slotInvalidCredentials() (this=0x555555ce5ca0) at /home/kamm/dev/woboq/mirall/mirall/src/gui/accountstate.cpp:320
        __PRETTY_FUNCTION__ = "void OCC::AccountState::slotInvalidCredentials()"
#3  0x0000555555763908 in OCC::AccountState::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) (_o=0x555555ce5ca0, _c=QMetaObject::InvokeMetaMethod, _id=4, _a=0x7fffffffcfa0) at /home/kamm/dev/woboq/mirall/build/src/gui/owncloud_automoc.dir/moc_accountstate_SAWUEYUUQZ3PBF.cpp:107
        _t = 0x555555ce5ca0
#4  0x00007ffff355d789 in QMetaObject::activate(QObject*, int, int, void**) (sender=0x555555ce8aa0, signalOffset=<optimized out>, local_signal_index=<optimized out>, argv=<optimized out>) at kernel/qobject.cpp:3740
        method_relative = 4
        callFunction = 0x555555763830 <OCC::AccountState::qt_static_metacall(QObject*, QMetaObject::Call, int, void**)>
        receiver = 0x555555ce5ca0
        receiverInSameThread = <optimized out>
        sw = {receiver = 0x555555ce5ca0, previousSender = 0x0, currentSender = {sender = 0x555555ce8aa0, signal = 4, ref = 1}, switched = true}
        c = 0x555555cecbb0
        last = 0x555555cecbb0
        locker = {val = 140737280210584}
        connectionLists = {connectionLists = 0x555555cec750}
        list = <optimized out>
        currentThreadId = 0x7ffff7f06d40
        signal_index = 4
        empty_argv = {0x0}
#5  0x00007ffff4dfa159 in OCC::Account::invalidCredentials() (this=0x555555ce8aa0) at /home/kamm/dev/woboq/mirall/build/src/libsync/owncloudsync_automoc.dir/moc_account_H7WYQOXRZ6GKSA.cpp:274
#6  0x00007ffff4d44a2e in OCC::Account::handleInvalidCredentials() (this=0x555555ce8aa0) at /home/kamm/dev/woboq/mirall/mirall/src/libsync/account.cpp:400
#7  0x00007ffff4d6e7db in OCC::AbstractNetworkJob::slotFinished() (this=0x555555e7d5b0) at /home/kamm/dev/woboq/mirall/mirall/src/libsync/abstractnetworkjob.cpp:202
        __PRETTY_FUNCTION__ = "void OCC::AbstractNetworkJob::slotFinished()"
        requestedUrl = {d = 0x555555ed5f90}
        redirectUrl = {d = 0x0}
        creds = 0x555555ce4a90
        discard = false
#8  0x00007ffff4df9565 in OCC::AbstractNetworkJob::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) (_o=0x555555e7d5b0, _c=QMetaObject::InvokeMetaMethod, _id=4, _a=0x7fffffffd2a0) at /home/kamm/dev/woboq/mirall/build/src/libsync/owncloudsync_automoc.dir/moc_abstractnetwor_PFI2TXGQHRE33H.cpp:98
        _t = 0x555555e7d5b0
#9  0x00007ffff355d789 in QMetaObject::activate(QObject*, int, int, void**) (sender=sender@entry=0x555555efbe60, signalOffset=<optimized out>, local_signal_index=local_signal_index@entry=1, argv=argv@entry=0x0) at kernel/qobject.cpp:3740
        method_relative = 4
        callFunction = 0x7ffff4df9494 <OCC::AbstractNetworkJob::qt_static_metacall(QObject*, QMetaObject::Call, int, void**)>
        receiver = 0x555555e7d5b0
        receiverInSameThread = <optimized out>
        sw = {receiver = 0x555555e7d5b0, previousSender = 0x0, currentSender = {sender = 0x555555efbe60, signal = 10, ref = 1}, switched = true}
        c = 0x555555ec7650
        last = 0x555555ec7650
        locker = {val = 140737280210872}
        connectionLists = {connectionLists = 0x555555f8c160}
        list = <optimized out>
        currentThreadId = 0x7ffff7f06d40
        signal_index = 10
        empty_argv = {0x0}
#10 0x00007ffff355e0a7 in QMetaObject::activate(QObject*, QMetaObject const*, int, void**) (sender=sender@entry=0x555555efbe60, m=m@entry=0x7ffff3ce91c0 <QNetworkReply::staticMetaObject>, local_signal_index=local_signal_index@entry=1, argv=argv@entry=0x0) at kernel/qobject.cpp:3602
#11 0x00007ffff3a9c883 in QNetworkReply::finished() (this=this@entry=0x555555efbe60) at .moc/moc_qnetworkreply.cpp:367
#12 0x00007ffff39feef7 in QNetworkReplyHttpImplPrivate::finished() (this=this@entry=0x555555e7d140) at access/qnetworkreplyhttpimpl.cpp:2098
        totalSize = {d = {data = {c = 0 '\000', uc = 0 '\000', s = 0, sc = 0 '\000', us = 0, i = 0, u = 0, l = 0, ul = 0, b = false, d = 0, f = 0, real = 0, ll = 0, ull = 0, o = 0x0, ptr = 0x0, shared = 0x0}, type = 0, is_shared = 0, is_null = 1}}
#13 0x00007ffff3a006e8 in QNetworkReplyHttpImpl::close() (this=<optimized out>) at access/qnetworkreplyhttpimpl.cpp:265
#14 0x00007ffff4df76ab in OCC::HttpCredentials::slotAuthentication(QNetworkReply*, QAuthenticator*) (this=0x555555ce4a90, reply=0x555555efbe60, authenticator=0x7fffa8002768) at /home/kamm/dev/woboq/mirall/mirall/src/libsync/creds/httpcredentials.cpp:459
        __PRETTY_FUNCTION__ = "void OCC::HttpCredentials::slotAuthentication(QNetworkReply*, QAuthenticator*)"
#15 0x00007ffff4dfbd23 in OCC::HttpCredentials::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) (_o=0x555555ce4a90, _c=QMetaObject::InvokeMetaMethod, _id=0, _a=0x7fffffffd580) at /home/kamm/dev/woboq/mirall/build/src/libsync/owncloudsync_automoc.dir/moc_httpcredential_RPYFXU3UDDMY3V.cpp:96
        _t = 0x555555ce4a90
#16 0x00007ffff355d789 in QMetaObject::activate(QObject*, int, int, void**) (sender=sender@entry=0x555555bf2050, signalOffset=<optimized out>, local_signal_index=local_signal_index@entry=1, argv=argv@entry=0x7fffffffd580) at kernel/qobject.cpp:3740
        method_relative = 0
        callFunction = 0x7ffff4dfbca2 <OCC::HttpCredentials::qt_static_metacall(QObject*, QMetaObject::Call, int, void**)>
        receiver = 0x555555ce4a90
        receiverInSameThread = <optimized out>
        sw = {receiver = 0x555555ce4a90, previousSender = 0x0, currentSender = {sender = 0x555555bf2050, signal = 4, ref = 1}, switched = true}
        c = 0x555555bb0170
        last = 0x555555bb0170
        locker = {val = 140737280210600}
        connectionLists = {connectionLists = 0x555555c22d60}
        list = <optimized out>
        currentThreadId = 0x7ffff7f06d40
        signal_index = 4
        empty_argv = {0x0}
#17 0x00007ffff355e0a7 in QMetaObject::activate(QObject*, QMetaObject const*, int, void**) (sender=sender@entry=0x555555bf2050, m=m@entry=0x7ffff3ce66a0 <QNetworkAccessManager::staticMetaObject>, local_signal_index=local_signal_index@entry=1, argv=argv@entry=0x7fffffffd580) at kernel/qobject.cpp:3602
#18 0x00007ffff39db6bb in QNetworkAccessManager::authenticationRequired(QNetworkReply*, QAuthenticator*) (this=this@entry=0x555555bf2050, _t1=<optimized out>, _t1@entry=0x555555efbe60, _t2=<optimized out>, _t2@entry=0x7fffa8002768) at .moc/moc_qnetworkaccessmanager.cpp:379
        _a = {0x0, 0x7fffffffd578, 0x7fffffffd570}
#19 0x00007ffff39dcc47 in QNetworkAccessManagerPrivate::authenticationRequired(QAuthenticator*, QNetworkReply*, bool, QUrl&, QUrl*, bool) (this=0x5555561b0fa0, authenticator=authenticator@entry=0x7fffa8002768, reply=0x555555efbe60, synchronous=<optimized out>, url=..., urlForLastAuthentication=urlForLastAuthentication@entry=0x555555e7d338, allowAuthenticationReuse=true) at access/qnetworkaccessmanager.cpp:1442
#20 0x00007ffff39fb404 in QNetworkReplyHttpImplPrivate::httpAuthenticationRequired(QHttpNetworkRequest const&, QAuthenticator*) (this=0x555555e7d140, request=..., auth=0x7fffa8002768) at access/qnetworkreplyhttpimpl.cpp:1302
#21 0x00007ffff3a9d74c in QNetworkReplyHttpImpl::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) (_o=<optimized out>, _c=<optimized out>, _id=<optimized out>, _a=<optimized out>) at .moc/moc_qnetworkreplyhttpimpl_p.cpp:257
        _t = <optimized out>
#22 0x00007ffff355e639 in QObject::event(QEvent*) (this=0x555555efbe60, e=<optimized out>) at kernel/qobject.cpp:1263
        mce = <optimized out>
        sw = {receiver = 0x555555efbe60, previousSender = 0x0, currentSender = {sender = 0x555555e7e290, signal = 3, ref = 1}, switched = true}
#23 0x00007ffff457635c in QApplicationPrivate::notify_helper(QObject*, QEvent*) () at /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#24 0x00007ffff457db21 in QApplication::notify(QObject*, QEvent*) () at /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#25 0x00007ffff3531c00 in QCoreApplication::notifyInternal2(QObject*, QEvent*) (receiver=0x555555efbe60, event=event@entry=0x7fffa8019ec0) at kernel/qcoreapplication.cpp:988
        selfRequired = true
        result = false
        cbdata = {0x555555efbe60, 0x7fffa8019ec0, 0x7fffffffd93f}
        d = <optimized out>
        threadData = 0x555555a93d60
        scopeLevelCounter = {threadData = 0x555555a93d60}
#26 0x00007ffff353439d in QCoreApplication::sendEvent(QObject*, QEvent*) (event=0x7fffa8019ec0, receiver=<optimized out>) at ../../include/QtCore/../../src/corelib/kernel/qcoreapplication.h:231
        e = 0x7fffa8019ec0
        pe = <optimized out>
        r = <optimized out>
        unlocker = {m = <synthetic pointer><error reading variable>}
        event_deleter = {d = 0x7fffa8019ec0}
        locker = {val = 93824997735824}
        startOffset = 0
        i = @0x555555a93d84: 16
        cleanup = {receiver = 0x0, event_type = 0, data = 0x555555a93d60, exceptionCaught = true}
#27 0x00007ffff353439d in QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*) (receiver=receiver@entry=0x0, event_type=event_type@entry=0, data=0x555555a93d60) at kernel/qcoreapplication.cpp:1649
        e = 0x7fffa8019ec0
        pe = <optimized out>
        r = <optimized out>
        unlocker = {m = <synthetic pointer><error reading variable>}
        event_deleter = {d = 0x7fffa8019ec0}
        locker = {val = 93824997735824}
        startOffset = 0
        i = @0x555555a93d84: 16
        cleanup = {receiver = 0x0, event_type = 0, data = 0x555555a93d60, exceptionCaught = true}
#28 0x00007ffff3534808 in QCoreApplication::sendPostedEvents(QObject*, int) (receiver=receiver@entry=0x0, event_type=event_type@entry=0) at kernel/qcoreapplication.cpp:1503
        data = <optimized out>
#29 0x00007ffff3585c93 in postEventSourceDispatch(GSource*, GSourceFunc, gpointer) (s=0x555555aee670) at kernel/qeventdispatcher_glib.cpp:276
        source = 0x555555aee670
#30 0x00007fffeffba377 in g_main_context_dispatch () at /lib/x86_64-linux-gnu/libglib-2.0.so.0
#31 0x00007fffeffba5e0 in  () at /lib/x86_64-linux-gnu/libglib-2.0.so.0
#32 0x00007fffeffba68c in g_main_context_iteration () at /lib/x86_64-linux-gnu/libglib-2.0.so.0
#33 0x00007ffff358609f in QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) (this=0x555555af54b0, flags=...) at kernel/qeventdispatcher_glib.cpp:423
        d = 0x555555aee5c0
        canWait = true
        savedFlags = {i = 0}
        result = <optimized out>
#34 0x00007ffff352fbea in QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) (this=this@entry=0x7fffffffdc30, flags=..., flags@entry=...) at kernel/qeventloop.cpp:212
        d = 0x555555d87910
        locker = {val = 93824997736016}
        ref = {d = 0x555555d87910, locker = @0x7fffffffdbb8, exceptionCaught = true}
#35 0x00007ffff353833c in QCoreApplication::exec() () at kernel/qcoreapplication.cpp:1261
        threadData = 0x555555a93d60
        eventLoop = {<QObject> = {_vptr.QObject = 0x7ffff39793d8 <vtable for QEventLoop+16>, static staticMetaObject = {d = {superdata = 0x0, stringdata = 0x7ffff3689c40 <qt_meta_stringdata_QObject>, data = 0x7ffff3689b20 <qt_meta_data_QObject>, static_metacall = 0x7ffff3565560 <QObject::qt_static_metacall(QObject*, QMetaObject::Call, int, void**)>, relatedMetaObjects = 0x0, extradata = 0x0}}, d_ptr = {d = 0x555555d87910}, static staticQtMetaObject = {d = {superdata = 0x0, stringdata = 0x7ffff36d3e60 <qt_meta_stringdata_Qt>, data = 0x7ffff36d13a0 <qt_meta_data_Qt>, static_metacall = 0x0, relatedMetaObjects = 0x0, extradata = 0x0}}}, static staticMetaObject = {d = {superdata = 0x7ffff39708e0 <QObject::staticMetaObject>, stringdata = 0x7ffff36eb200 <qt_meta_stringdata_QEventLoop>, data = 0x7ffff36eb1a0 <qt_meta_data_QEventLoop>, static_metacall = 0x7ffff35d6d20 <QEventLoop::qt_static_metacall(QObject*, QMetaObject::Call, int, void**)>, relatedMetaObjects = 0x0, extradata = 0x0}}}
        returnCode = <optimized out>
#36 0x00005555556525b5 in main(int, char**) (argc=6, argv=0x7fffffffdf08) at /home/kamm/dev/woboq/mirall/mirall/src/gui/main.cpp:170
        app = {<SharedTools::QtSingleApplication> = {<QApplication> = {<No data fields>}, static staticMetaObject = {d = {superdata = 0x7ffff4c4e1c0 <QApplication::staticMetaObject>, stringdata = 0x5555558004a0 <qt_meta_stringdata_SharedTools__QtSingleApplication>, data = 0x555555800640 <qt_meta_data_SharedTools__QtSingleApplication>, static_metacall = 0x5555557625c0 <SharedTools::QtSingleApplication::qt_static_metacall(QObject*, QMetaObject::Call, int, void**)>, relatedMetaObjects = 0x0, extradata = 0x0}}, firstPeer = -1, instances = 0x555555affe80, pidPeer = 0x555555b136c0, actWin = 0x0, appId = {static null = {<No data fields>}, d = 0x555555a93f00}, block = false}, static staticMetaObject = {d = {superdata = 0x555555a66e80 <SharedTools::QtSingleApplication::staticMetaObject>, stringdata = 0x555555802980 <qt_meta_stringdata_OCC__Application>, data = 0x555555802c60 <qt_meta_data_OCC__Application>, static_metacall = 0x555555764d8c <OCC::Application::qt_static_metacall(QObject*, QMetaObject::Call, int, void**)>, relatedMetaObjects = 0x0, extradata = 0x0}}, _gui = {wp = {d = 0x555555ef5e00, value = 0x555555c8b350}}, _theme = 0x555555a93100, _helpOnly = false, _versionOnly = false, _startedAt = {t1 = 21464, t2 = 241836100}, _showLogWindow = false, _logFile = {static null = {<No data fields>}, d = 0x555555b16bd0}, _logDir = {static null = {<No data fields>}, d = 0x7ffff35dc9c0 <QArrayData::shared_null>}, _logExpire = 0, _logFlush = false, _logDebug = true, _userTriggeredConnect = false, _debugMode = false, _proxy = {<QObject> = {_vptr.QObject = 0x7ffff5067990 <vtable for OCC::ClientProxy+16>, static staticMetaObject = {d = {superdata = 0x0, stringdata = 0x7ffff3689c40 <qt_meta_stringdata_QObject>, data = 0x7ffff3689b20 <qt_meta_data_QObject>, static_metacall = 0x7ffff3565560 <QObject::qt_static_metacall(QObject*, QMetaObject::Call, int, void**)>, relatedMetaObjects = 0x0, extradata = 0x0}}, d_ptr = {d = 0x555555b13480}, static staticQtMetaObject = {d = {superdata = 0x0, stringdata = 0x7ffff36d3e60 <qt_meta_stringdata_Qt>, data = 0x7ffff36d13a0 <qt_meta_data_Qt>, static_metacall = 0x0, relatedMetaObjects = 0x0, extradata = 0x0}}}, static staticMetaObject = {d = {superdata = 0x7ffff39708e0 <QObject::staticMetaObject>, stringdata = 0x7ffff4e22460 <qt_meta_stringdata_OCC__ClientProxy>, data = 0x7ffff4e224e0 <qt_meta_data_OCC__ClientProxy>, static_metacall = 0x7ffff4dfad8a <OCC::ClientProxy::qt_static_metacall(QObject*, QMetaObject::Call, int, void**)>, relatedMetaObjects = 0x0, extradata = 0x0}}}, _networkConfigurationManager = {<QObject> = {_vptr.QObject = 0x7ffff3ce7418 <vtable for QNetworkConfigurationManager+16>, static staticMetaObject = {d = {superdata = 0x0, stringdata = 0x7ffff3689c40 <qt_meta_stringdata_QObject>, data = 0x7ffff3689b20 <qt_meta_data_QObject>, static_metacall = 0x7ffff3565560 <QObject::qt_static_metacall(QObject*, QMetaObject::Call, int, void**)>, relatedMetaObjects = 0x0, extradata = 0x0}}, d_ptr = {d = 0x555555b12c50}, static staticQtMetaObject = {d = {superdata = 0x0, stringdata = 0x7ffff36d3e60 <qt_meta_stringdata_Qt>, data = 0x7ffff36d13a0 <qt_meta_data_Qt>, static_metacall = 0x0, relatedMetaObjects = 0x0, extradata = 0x0}}}, static staticMetaObject = {d = {superdata = 0x7ffff39708e0 <QObject::staticMetaObject>, stringdata = 0x7ffff3aa9060 <qt_meta_stringdata_QNetworkConfigurationManager>, data = 0x7ffff3aa8f60 <qt_meta_data_QNetworkConfigurationManager>, static_metacall = 0x7ffff3a13af0 <QNetworkConfigurationManager::qt_static_metacall(QObject*, QMetaObject::Call, int, void**)>, relatedMetaObjects = 0x0, extradata = 0x0}}}, _checkConnectionTimer = {<QObject> = {_vptr.QObject = 0x7ffff3979718 <vtable for QTimer+16>, static staticMetaObject = {d = {superdata = 0x0, stringdata = 0x7ffff3689c40 <qt_meta_stringdata_QObject>, data = 0x7ffff3689b20 <qt_meta_data_QObject>, static_metacall = 0x7ffff3565560 <QObject::qt_static_metacall(QObject*, QMetaObject::Call, int, void**)>, relatedMetaObjects = 0x0, extradata = 0x0}}, d_ptr = {d = 0x555555b1e380}, static staticQtMetaObject = {d = {superdata = 0x0, stringdata = 0x7ffff36d3e60 <qt_meta_stringdata_Qt>, data = 0x7ffff36d13a0 <qt_meta_data_Qt>, static_metacall = 0x0, relatedMetaObjects = 0x0, extradata = 0x0}}}, static staticMetaObject = {d = {superdata = 0x7ffff39708e0 <QObject::staticMetaObject>, stringdata = 0x7ffff36ed920 <qt_meta_stringdata_QTimer>, data = 0x7ffff36ed840 <qt_meta_data_QTimer>, static_metacall = 0x7ffff35d75e0 <QTimer::qt_static_metacall(QObject*, QMetaObject::Call, int, void**)>, relatedMetaObjects = 0x0, extradata = 0x0}}, id = 7, inter = 32000, del = 0, single = 0, nulltimer = 0, type = 1}, _folderManager = {d = 0x555555b47e30}}
        __PRETTY_FUNCTION__ = "int main(int, char**)"
        updater = 0x555555d4c2f0

@ckamm
Copy link
Contributor

ckamm commented Sep 6, 2017

@guruz FWIW: I get the same incorrect behavior if I don't reply->close() in HttpCredentials::slotAuthentication:

Thread 1 (Thread 0x7ffff7f06d40 (LWP 4623)):
#0  0x00005555556d3974 in OCC::DetermineAuthTypeJob::start() (this=0x555555faed60) at /home/kamm/dev/woboq/mirall/mirall/src/gui/owncloudsetupwizard.cpp:617
#1  0x000055555573b93d in OCC::HttpCredentialsGui::askFromUser() (this=0x555555c15880) at /home/kamm/dev/woboq/mirall/mirall/src/gui/creds/httpcredentialsgui.cpp:61
        qnamLock = {value = 0x555555e302e0, d = 0x555555c1ec80}
        job = 0x555555faed60
        __PRETTY_FUNCTION__ = "virtual void OCC::HttpCredentialsGui::askFromUser()"
#2  0x000055555572c7a6 in OCC::AccountState::slotInvalidCredentials() (this=0x555555cec240) at /home/kamm/dev/woboq/mirall/mirall/src/gui/accountstate.cpp:320
        __PRETTY_FUNCTION__ = "void OCC::AccountState::slotInvalidCredentials()"
#3  0x0000555555763908 in OCC::AccountState::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) (_o=0x555555cec240, _c=QMetaObject::InvokeMetaMethod, _id=4, _a=0x7fffffffd2a0) at /home/kamm/dev/woboq/mirall/build/src/gui/owncloud_automoc.dir/moc_accountstate_SAWUEYUUQZ3PBF.cpp:107
        _t = 0x555555cec240
#4  0x00007ffff355d789 in QMetaObject::activate(QObject*, int, int, void**) (sender=0x555555ce0c90, signalOffset=<optimized out>, local_signal_index=<optimized out>, argv=<optimized out>) at kernel/qobject.cpp:3740
        method_relative = 4
        callFunction = 0x555555763830 <OCC::AccountState::qt_static_metacall(QObject*, QMetaObject::Call, int, void**)>
        receiver = 0x555555cec240
        receiverInSameThread = <optimized out>
        sw = {receiver = 0x555555cec240, previousSender = 0x0, currentSender = {sender = 0x555555ce0c90, signal = 4, ref = 1}, switched = true}
        c = 0x555555cea240
        last = 0x555555cea240
        locker = {val = 140737280210800}
        connectionLists = {connectionLists = 0x555555ce1040}
        list = <optimized out>
        currentThreadId = 0x7ffff7f06d40
        signal_index = 4
        empty_argv = {0x0}
#5  0x00007ffff4dfa141 in OCC::Account::invalidCredentials() (this=0x555555ce0c90) at /home/kamm/dev/woboq/mirall/build/src/libsync/owncloudsync_automoc.dir/moc_account_H7WYQOXRZ6GKSA.cpp:274
#6  0x00007ffff4d44a2e in OCC::Account::handleInvalidCredentials() (this=0x555555ce0c90) at /home/kamm/dev/woboq/mirall/mirall/src/libsync/account.cpp:400
#7  0x00007ffff4d6e7db in OCC::AbstractNetworkJob::slotFinished() (this=0x555555efd130) at /home/kamm/dev/woboq/mirall/mirall/src/libsync/abstractnetworkjob.cpp:202
        __PRETTY_FUNCTION__ = "void OCC::AbstractNetworkJob::slotFinished()"
        requestedUrl = {d = 0x555555e30710}
        redirectUrl = {d = 0x0}
        creds = 0x555555c15880
        discard = false
#8  0x00007ffff4df954d in OCC::AbstractNetworkJob::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) (_o=0x555555efd130, _c=QMetaObject::InvokeMetaMethod, _id=4, _a=0x7fffffffd5a0) at /home/kamm/dev/woboq/mirall/build/src/libsync/owncloudsync_automoc.dir/moc_abstractnetwor_PFI2TXGQHRE33H.cpp:98
        _t = 0x555555efd130
#9  0x00007ffff355d789 in QMetaObject::activate(QObject*, int, int, void**) (sender=sender@entry=0x555555bf3e00, signalOffset=<optimized out>, local_signal_index=local_signal_index@entry=1, argv=argv@entry=0x0) at kernel/qobject.cpp:3740
        method_relative = 4
        callFunction = 0x7ffff4df947c <OCC::AbstractNetworkJob::qt_static_metacall(QObject*, QMetaObject::Call, int, void**)>
        receiver = 0x555555efd130
        receiverInSameThread = <optimized out>
        sw = {receiver = 0x555555efd130, previousSender = 0x0, currentSender = {sender = 0x555555bf3e00, signal = 10, ref = 1}, switched = true}
        c = 0x555555ef2f90
        last = 0x555555ef2f90
        locker = {val = 140737280210864}
        connectionLists = {connectionLists = 0x555555e7eb60}
        list = <optimized out>
        currentThreadId = 0x7ffff7f06d40
        signal_index = 10
        empty_argv = {0x0}
#10 0x00007ffff355e0a7 in QMetaObject::activate(QObject*, QMetaObject const*, int, void**) (sender=sender@entry=0x555555bf3e00, m=m@entry=0x7ffff3ce91c0 <QNetworkReply::staticMetaObject>, local_signal_index=local_signal_index@entry=1, argv=argv@entry=0x0) at kernel/qobject.cpp:3602
#11 0x00007ffff3a9c883 in QNetworkReply::finished() (this=this@entry=0x555555bf3e00) at .moc/moc_qnetworkreply.cpp:367
#12 0x00007ffff39feef7 in QNetworkReplyHttpImplPrivate::finished() (this=0x555555f31bd0) at access/qnetworkreplyhttpimpl.cpp:2098
        totalSize = {d = {data = {c = 14 '\016', uc = 14 '\016', s = 270, sc = 14 '\016', us = 270, i = 270, u = 270, l = 270, ul = 270, b = 14, d = 1.3339772437713657e-321, f = 3.78350585e-43, real = 1.3339772437713657e-321, ll = 270, ull = 270, o = 0x10e, ptr = 0x10e, shared = 0x10e}, type = 4, is_shared = 0, is_null = 0}}
#13 0x00007ffff39ff0f5 in QNetworkReplyHttpImplPrivate::replyFinished() (this=<optimized out>) at access/qnetworkreplyhttpimpl.cpp:1075
#14 0x00007ffff3a9d679 in QNetworkReplyHttpImpl::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) (_o=<optimized out>, _c=<optimized out>, _id=<optimized out>, _a=<optimized out>) at .moc/moc_qnetworkreplyhttpimpl_p.cpp:254
        _t = <optimized out>
#15 0x00007ffff355e639 in QObject::event(QEvent*) (this=0x555555bf3e00, e=<optimized out>) at kernel/qobject.cpp:1263
        mce = <optimized out>
        sw = {receiver = 0x555555bf3e00, previousSender = 0x0, currentSender = {sender = 0x555555e776f0, signal = 13, ref = 1}, switched = true}
#16 0x00007ffff457635c in QApplicationPrivate::notify_helper(QObject*, QEvent*) () at /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#17 0x00007ffff457db21 in QApplication::notify(QObject*, QEvent*) () at /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#18 0x00007ffff3531c00 in QCoreApplication::notifyInternal2(QObject*, QEvent*) (receiver=0x555555bf3e00, event=event@entry=0x7fffa801a710) at kernel/qcoreapplication.cpp:988
        selfRequired = true
        result = false
        cbdata = {0x555555bf3e00, 0x7fffa801a710, 0x7fffffffd93f}
        d = <optimized out>
        threadData = 0x555555a93d60
        scopeLevelCounter = {threadData = 0x555555a93d60}
#19 0x00007ffff353439d in QCoreApplication::sendEvent(QObject*, QEvent*) (event=0x7fffa801a710, receiver=<optimized out>) at ../../include/QtCore/../../src/corelib/kernel/qcoreapplication.h:231
        e = 0x7fffa801a710
        pe = <optimized out>
        r = <optimized out>
        unlocker = {m = <synthetic pointer><error reading variable>}
        event_deleter = {d = 0x7fffa801a710}
        locker = {val = 93824997735824}
        startOffset = 0
        i = @0x555555a93d84: 21
        cleanup = {receiver = 0x0, event_type = 0, data = 0x555555a93d60, exceptionCaught = true}
#20 0x00007ffff353439d in QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*) (receiver=receiver@entry=0x0, event_type=event_type@entry=0, data=0x555555a93d60) at kernel/qcoreapplication.cpp:1649
        e = 0x7fffa801a710
        pe = <optimized out>
        r = <optimized out>
        unlocker = {m = <synthetic pointer><error reading variable>}
        event_deleter = {d = 0x7fffa801a710}
        locker = {val = 93824997735824}
        startOffset = 0
        i = @0x555555a93d84: 21
        cleanup = {receiver = 0x0, event_type = 0, data = 0x555555a93d60, exceptionCaught = true}
#21 0x00007ffff3534808 in QCoreApplication::sendPostedEvents(QObject*, int) (receiver=receiver@entry=0x0, event_type=event_type@entry=0) at kernel/qcoreapplication.cpp:1503
        data = <optimized out>
#22 0x00007ffff3585c93 in postEventSourceDispatch(GSource*, GSourceFunc, gpointer) (s=0x555555aeb8d0) at kernel/qeventdispatcher_glib.cpp:276
        source = 0x555555aeb8d0
#23 0x00007fffeffba377 in g_main_context_dispatch () at /lib/x86_64-linux-gnu/libglib-2.0.so.0
#24 0x00007fffeffba5e0 in  () at /lib/x86_64-linux-gnu/libglib-2.0.so.0
#25 0x00007fffeffba68c in g_main_context_iteration () at /lib/x86_64-linux-gnu/libglib-2.0.so.0
#26 0x00007ffff358609f in QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) (this=0x555555af5c40, flags=...) at kernel/qeventdispatcher_glib.cpp:423
        d = 0x555555aee630
        canWait = true
        savedFlags = {i = 0}
        result = <optimized out>
#27 0x00007ffff352fbea in QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) (this=this@entry=0x7fffffffdc30, flags=..., flags@entry=...) at kernel/qeventloop.cpp:212
        d = 0x555555e47a90
        locker = {val = 93824997736016}
        ref = {d = 0x555555e47a90, locker = @0x7fffffffdbb8, exceptionCaught = true}
#28 0x00007ffff353833c in QCoreApplication::exec() () at kernel/qcoreapplication.cpp:1261
        threadData = 0x555555a93d60
        eventLoop = {<QObject> = {_vptr.QObject = 0x7ffff39793d8 <vtable for QEventLoop+16>, static staticMetaObject = {d = {superdata = 0x0, stringdata = 0x7ffff3689c40 <qt_meta_stringdata_QObject>, data = 0x7ffff3689b20 <qt_meta_data_QObject>, static_metacall = 0x7ffff3565560 <QObject::qt_static_metacall(QObject*, QMetaObject::Call, int, void**)>, relatedMetaObjects = 0x0, extradata = 0x0}}, d_ptr = {d = 0x555555e47a90}, static staticQtMetaObject = {d = {superdata = 0x0, stringdata = 0x7ffff36d3e60 <qt_meta_stringdata_Qt>, data = 0x7ffff36d13a0 <qt_meta_data_Qt>, static_metacall = 0x0, relatedMetaObjects = 0x0, extradata = 0x0}}}, static staticMetaObject = {d = {superdata = 0x7ffff39708e0 <QObject::staticMetaObject>, stringdata = 0x7ffff36eb200 <qt_meta_stringdata_QEventLoop>, data = 0x7ffff36eb1a0 <qt_meta_data_QEventLoop>, static_metacall = 0x7ffff35d6d20 <QEventLoop::qt_static_metacall(QObject*, QMetaObject::Call, int, void**)>, relatedMetaObjects = 0x0, extradata = 0x0}}}
        returnCode = <optimized out>
#29 0x00005555556525b5 in main(int, char**) (argc=6, argv=0x7fffffffdf08) at /home/kamm/dev/woboq/mirall/mirall/src/gui/main.cpp:170
        app = {<SharedTools::QtSingleApplication> = {<QApplication> = {<No data fields>}, static staticMetaObject = {d = {superdata = 0x7ffff4c4e1c0 <QApplication::staticMetaObject>, stringdata = 0x5555558004a0 <qt_meta_stringdata_SharedTools__QtSingleApplication>, data = 0x555555800640 <qt_meta_data_SharedTools__QtSingleApplication>, static_metacall = 0x5555557625c0 <SharedTools::QtSingleApplication::qt_static_metacall(QObject*, QMetaObject::Call, int, void**)>, relatedMetaObjects = 0x0, extradata = 0x0}}, firstPeer = -1, instances = 0x555555b034c0, pidPeer = 0x555555b13670, actWin = 0x0, appId = {static null = {<No data fields>}, d = 0x555555a93f00}, block = false}, static staticMetaObject = {d = {superdata = 0x555555a66e80 <SharedTools::QtSingleApplication::staticMetaObject>, stringdata = 0x555555802980 <qt_meta_stringdata_OCC__Application>, data = 0x555555802c60 <qt_meta_data_OCC__Application>, static_metacall = 0x555555764d8c <OCC::Application::qt_static_metacall(QObject*, QMetaObject::Call, int, void**)>, relatedMetaObjects = 0x0, extradata = 0x0}}, _gui = {wp = {d = 0x555555ef6be0, value = 0x555555ca8440}}, _theme = 0x555555a93100, _helpOnly = false, _versionOnly = false, _startedAt = {t1 = 22491, t2 = 605787670}, _showLogWindow = false, _logFile = {static null = {<No data fields>}, d = 0x555555b18ac0}, _logDir = {static null = {<No data fields>}, d = 0x7ffff35dc9c0 <QArrayData::shared_null>}, _logExpire = 0, _logFlush = false, _logDebug = true, _userTriggeredConnect = false, _debugMode = false, _proxy = {<QObject> = {_vptr.QObject = 0x7ffff5067990 <vtable for OCC::ClientProxy+16>, static staticMetaObject = {d = {superdata = 0x0, stringdata = 0x7ffff3689c40 <qt_meta_stringdata_QObject>, data = 0x7ffff3689b20 <qt_meta_data_QObject>, static_metacall = 0x7ffff3565560 <QObject::qt_static_metacall(QObject*, QMetaObject::Call, int, void**)>, relatedMetaObjects = 0x0, extradata = 0x0}}, d_ptr = {d = 0x555555b0c210}, static staticQtMetaObject = {d = {superdata = 0x0, stringdata = 0x7ffff36d3e60 <qt_meta_stringdata_Qt>, data = 0x7ffff36d13a0 <qt_meta_data_Qt>, static_metacall = 0x0, relatedMetaObjects = 0x0, extradata = 0x0}}}, static staticMetaObject = {d = {superdata = 0x7ffff39708e0 <QObject::staticMetaObject>, stringdata = 0x7ffff4e22440 <qt_meta_stringdata_OCC__ClientProxy>, data = 0x7ffff4e224c0 <qt_meta_data_OCC__ClientProxy>, static_metacall = 0x7ffff4dfad72 <OCC::ClientProxy::qt_static_metacall(QObject*, QMetaObject::Call, int, void**)>, relatedMetaObjects = 0x0, extradata = 0x0}}}, _networkConfigurationManager = {<QObject> = {_vptr.QObject = 0x7ffff3ce7418 <vtable for QNetworkConfigurationManager+16>, static staticMetaObject = {d = {superdata = 0x0, stringdata = 0x7ffff3689c40 <qt_meta_stringdata_QObject>, data = 0x7ffff3689b20 <qt_meta_data_QObject>, static_metacall = 0x7ffff3565560 <QObject::qt_static_metacall(QObject*, QMetaObject::Call, int, void**)>, relatedMetaObjects = 0x0, extradata = 0x0}}, d_ptr = {d = 0x555555b133c0}, static staticQtMetaObject = {d = {superdata = 0x0, stringdata = 0x7ffff36d3e60 <qt_meta_stringdata_Qt>, data = 0x7ffff36d13a0 <qt_meta_data_Qt>, static_metacall = 0x0, relatedMetaObjects = 0x0, extradata = 0x0}}}, static staticMetaObject = {d = {superdata = 0x7ffff39708e0 <QObject::staticMetaObject>, stringdata = 0x7ffff3aa9060 <qt_meta_stringdata_QNetworkConfigurationManager>, data = 0x7ffff3aa8f60 <qt_meta_data_QNetworkConfigurationManager>, static_metacall = 0x7ffff3a13af0 <QNetworkConfigurationManager::qt_static_metacall(QObject*, QMetaObject::Call, int, void**)>, relatedMetaObjects = 0x0, extradata = 0x0}}}, _checkConnectionTimer = {<QObject> = {_vptr.QObject = 0x7ffff3979718 <vtable for QTimer+16>, static staticMetaObject = {d = {superdata = 0x0, stringdata = 0x7ffff3689c40 <qt_meta_stringdata_QObject>, data = 0x7ffff3689b20 <qt_meta_data_QObject>, static_metacall = 0x7ffff3565560 <QObject::qt_static_metacall(QObject*, QMetaObject::Call, int, void**)>, relatedMetaObjects = 0x0, extradata = 0x0}}, d_ptr = {d = 0x555555b1e330}, static staticQtMetaObject = {d = {superdata = 0x0, stringdata = 0x7ffff36d3e60 <qt_meta_stringdata_Qt>, data = 0x7ffff36d13a0 <qt_meta_data_Qt>, static_metacall = 0x0, relatedMetaObjects = 0x0, extradata = 0x0}}}, static staticMetaObject = {d = {superdata = 0x7ffff39708e0 <QObject::staticMetaObject>, stringdata = 0x7ffff36ed920 <qt_meta_stringdata_QTimer>, data = 0x7ffff36ed840 <qt_meta_data_QTimer>, static_metacall = 0x7ffff35d75e0 <QTimer::qt_static_metacall(QObject*, QMetaObject::Call, int, void**)>, relatedMetaObjects = 0x0, extradata = 0x0}}, id = 28, inter = 32000, del = 0, single = 0, nulltimer = 0, type = 1}, _folderManager = {d = 0x555555b450e0}}
        __PRETTY_FUNCTION__ = "int main(int, char**)"
        updater = 0x555555cee8b0

@guruz
Copy link
Contributor

guruz commented Sep 6, 2017

@SamuAlfageme Is this a self-compiled client with which Qt version?

@SamuAlfageme
Copy link
Contributor Author

@guruz nope, it's a month-old nightly:

Version 2.4.0-nightly20170802 (build 8119) - Built from Git revision 416393 on Aug 2 2017, 06:37:30 using Qt 5.6.2, OpenSSL 1.0.2j 26 Sep 2016

@ckamm
Copy link
Contributor

ckamm commented Sep 6, 2017

Todo here: Apply workaround. But since this is also a somewhat scary issue: Check whether it's fixed in newer Qt versions. Try to make a small test case.

ckamm added a commit that referenced this issue Sep 7, 2017
When the GET request from askFromUser is scheduled on the QNAM inside
the slot that handles the QNetworkReply::finished signal, it seems to
not get processed at all.

This workaround moves the sending of the new GET to the event loop,
sidestepping the problem.
ckamm added a commit that referenced this issue Sep 7, 2017
When the GET request from askFromUser is scheduled on the QNAM inside
the slot that handles the QNetworkReply::finished signal, it seems to
not get processed at all.

This workaround moves the sending of the new GET to the event loop,
sidestepping the problem.
@ckamm ckamm added the ReadyToTest QA, please validate the fix/enhancement label Sep 7, 2017
@ckamm
Copy link
Contributor

ckamm commented Sep 7, 2017

Solved by workaround.

I've also tested with Qt 5.9.1 and the same bug manifests itself. This might bite us in other codepaths, so I'll spend some time trying to create a test case we can use to report and fix this problem upstream.

@ckamm
Copy link
Contributor

ckamm commented Sep 7, 2017

PR #6002

@ckamm
Copy link
Contributor

ckamm commented Sep 7, 2017

This test case doesn't reproduce the problem unfortunately:

#include <QCoreApplication>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QAuthenticator>

QUrl url("http://localhost/remote.php/dav/files/admin/");

void send(QNetworkAccessManager& nam)
{
    QNetworkRequest loginTest(url);
    loginTest.setRawHeader("Authorization", "Basic " + QByteArray("admin:incorrect").toBase64());
    auto reply1 = nam.sendCustomRequest(loginTest, "PROPFIND");
    qDebug() << "send propfind";

    QObject::connect(reply1, &QNetworkReply::finished, reply1, [&, reply1] () {
        qDebug() << "  propfind finished: " << reply1->errorString();
        auto reply2 = nam.get(QNetworkRequest(url));
        qDebug() << "send get";

        QObject::connect(reply2, &QNetworkReply::finished, reply2, [&, reply2] () {
           qDebug() << "  get finished: " << reply2->errorString();
           static int i = 0;
           ++i;
           if (i < 4) {
              send(nam);
           } else {
               qDebug() << "SUCCESS!";
           }
       });
    });
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QNetworkAccessManager nam;

    QObject::connect(&nam, &QNetworkAccessManager::authenticationRequired, &nam, [&] (QNetworkReply* reply, QAuthenticator*) {
        qDebug() << "  auth needed";
        reply->close();
    });

    send(nam);

    return a.exec();
}

@SamuAlfageme
Copy link
Contributor Author

@ckamm I'm running into the exact same behavior w/ today's nightly 😕

Trying to sign-back-in adds a temporary (just appears and disappears) entry to the keychain. Stays "Conecting..." and further login attempts preserve the wrong password on the credentials dialog.

@ckamm
Copy link
Contributor

ckamm commented Oct 6, 2017

@SamuAlfageme :(

This was pretty hard to track down. Can you reproduce and get a mitmproxy log? I'm wondering whether the workaround doesn't cover all cases, or whether you run into a different situation.

@SamuAlfageme
Copy link
Contributor Author

@ckamm sure, repro steps w/ 2.4.0alpha1 (build 8391) [macOS]:

  1. Set up an account correctly on the client / with a session initiated:
  2. Log out & try to re-login with an incorrect password

Actual Result (click to see video):

img

  • PROPFIND is replied 403 and the client still displays "Connecting" status
  • Wrong password included/deleted in the keychain

@ckamm
Copy link
Contributor

ckamm commented Oct 11, 2017

@SamuAlfageme Thanks, investigating.

@ckamm
Copy link
Contributor

ckamm commented Oct 11, 2017

For people testing this: I just was significantly confused for a bit because there's a bug in mitmproxy 0.18.2's sticky auth that makes it always on: mitmproxy/mitmproxy#1850. Use --stickyauth nonenonenone to disable it by setting a non-matching pattern.

@ckamm
Copy link
Contributor

ckamm commented Oct 11, 2017

I can reproduce the problem now. The workaround I added seems insufficient for not having that DetermineAuthType job's request eaten by QNAM. I'll attempt further debugging.

@ckamm
Copy link
Contributor

ckamm commented Oct 11, 2017

The bug is due to a badly timed QNAM::clearAccessCache. Will be able to fix.

ckamm added a commit that referenced this issue Oct 11, 2017
ckamm added a commit that referenced this issue Oct 12, 2017
ckamm added a commit that referenced this issue Oct 13, 2017
@ckamm
Copy link
Contributor

ckamm commented Oct 13, 2017

@SamuAlfageme Try again now.

@SamuAlfageme
Copy link
Contributor Author

@ckamm neat' 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ReadyToTest QA, please validate the fix/enhancement sev2-high type:bug
Projects
None yet
Development

No branches or pull requests

4 participants