diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index dd03b0c895a0915fe53bfacff5ec3fbf27aa12d2..81dfea2114d138fd93cd264a07e59f371195d134 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -1433,13 +1433,6 @@ delete this._reloadCall; this.hideMask(); - if (status === 401) { - // TODO: append current URL to be able to get back after logging in again - OC.redirect(OC.generateUrl('apps/files')); - OC.Notification.show(result); - return false; - } - // Firewall Blocked request? if (status === 403) { // Go home diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js index ed56011866ec8b01e4e0b901509bf2107ff82d4e..a83c8c4c0bc6c56272e7e44f2b88407001b9267d 100644 --- a/apps/files/tests/js/filelistSpec.js +++ b/apps/files/tests/js/filelistSpec.js @@ -2462,13 +2462,6 @@ describe('OCA.Files.FileList tests', function() { getFolderContentsStub.restore(); fileList = undefined; }); - it('redirects to files app in case of auth error', function () { - deferredList.reject(401, 'Authentication error'); - - expect(redirectStub.calledOnce).toEqual(true); - expect(redirectStub.getCall(0).args[0]).toEqual(OC.webroot + '/index.php/apps/files'); - expect(getFolderContentsStub.calledOnce).toEqual(true); - }); it('redirects to root folder in case of forbidden access', function () { deferredList.reject(403); diff --git a/core/js/files/client.js b/core/js/files/client.js index 627630e8b0382d2a140f75307978abd135ea9e9c..a7f393d325fd56d0879e900812ef7c39eb9588a9 100644 --- a/core/js/files/client.js +++ b/core/js/files/client.js @@ -137,6 +137,8 @@ }); return result; }; + + OC.registerXHRForErrorProcessing(xhr); return xhr; }, diff --git a/core/js/js.js b/core/js/js.js index 83658a537b8fe5254cdef430a0cd2a1299b61e74..fac9c45f6687ada5303896562c6b60333161df60 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -234,6 +234,13 @@ var OC={ window.location = targetURL; }, + /** + * Reloads the current page + */ + reload: function() { + window.location.reload(); + }, + /** * Protocol that is used to access this ownCloud instance * @return {string} Used protocol @@ -727,6 +734,56 @@ var OC={ isUserAdmin: function() { return oc_isadmin; }, + + /** + * Process ajax error, redirects to main page + * if an error/auth error status was returned. + */ + _processAjaxError: function(xhr) { + // purposefully aborted request ? + if (xhr.status === 0 && (xhr.statusText === 'abort' || xhr.statusText === 'timeout')) { + return; + } + + if (_.contains([0, 302, 307, 401], xhr.status)) { + OC.reload(); + } + }, + + /** + * Registers XmlHttpRequest object for global error processing. + * + * This means that if this XHR object returns 401 or session timeout errors, + * the current page will automatically be reloaded. + * + * @param {XMLHttpRequest} xhr + */ + registerXHRForErrorProcessing: function(xhr) { + var loadCallback = function() { + if (xhr.readyState !== 4) { + return; + } + + if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) { + return; + } + + // fire jquery global ajax error handler + $(document).trigger(new $.Event('ajaxError'), xhr); + }; + + var errorCallback = function() { + // fire jquery global ajax error handler + $(document).trigger(new $.Event('ajaxError'), xhr); + }; + + // FIXME: also needs an IE8 way + if (xhr.addEventListener) { + xhr.addEventListener('load', loadCallback); + xhr.addEventListener('error', errorCallback); + } + + } }; /** @@ -1311,6 +1368,13 @@ function initCore() { $('html').addClass('edge'); } + $(document).on('ajaxError.main', function( event, request, settings ) { + if (settings && settings.allowAuthErrors) { + return; + } + OC._processAjaxError(request); + }); + /** * Calls the server periodically to ensure that session doesn't * time out diff --git a/core/js/setupchecks.js b/core/js/setupchecks.js index de41b66ec329466d39a8b871c81fc34d05e1ae33..1819b5a9c1ecd7db44158ab644454943f5b57d28 100644 --- a/core/js/setupchecks.js +++ b/core/js/setupchecks.js @@ -40,7 +40,8 @@ '<d:propfind xmlns:d="DAV:">' + '<d:prop><d:resourcetype/></d:prop>' + '</d:propfind>', - complete: afterCall + complete: afterCall, + allowAuthErrors: true }); return deferred.promise(); }, @@ -157,7 +158,8 @@ $.ajax({ type: 'GET', - url: OC.generateUrl('settings/ajax/checksetup') + url: OC.generateUrl('settings/ajax/checksetup'), + allowAuthErrors: true }).then(afterCall, afterCall); return deferred.promise(); }, @@ -181,7 +183,8 @@ $.ajax({ type: 'GET', - url: OC.generateUrl('heartbeat') + url: OC.generateUrl('heartbeat'), + allowAuthErrors: true }).then(afterCall, afterCall); return deferred.promise(); diff --git a/core/js/tests/specHelper.js b/core/js/tests/specHelper.js index d13691845a7d4ec9a0d3724d58691e7e43bfac46..f9bdeae0d64bfef35f5246d93cc58adf90e29e8c 100644 --- a/core/js/tests/specHelper.js +++ b/core/js/tests/specHelper.js @@ -116,7 +116,8 @@ window.isPhantom = /phantom/i.test(navigator.userAgent); // global setup for all tests (function setupTests() { var fakeServer = null, - $testArea = null; + $testArea = null, + ajaxErrorStub = null; /** * Utility functions for testing @@ -162,6 +163,8 @@ window.isPhantom = /phantom/i.test(navigator.userAgent); // dummy select2 (which isn't loaded during the tests) $.fn.select2 = function() { return this; }; + + ajaxErrorStub = sinon.stub(OC, '_processAjaxError'); }); afterEach(function() { @@ -172,6 +175,8 @@ window.isPhantom = /phantom/i.test(navigator.userAgent); $testArea.remove(); delete($.fn.select2); + + ajaxErrorStub.restore(); }); })(); diff --git a/core/js/tests/specs/coreSpec.js b/core/js/tests/specs/coreSpec.js index 2e970f7e7071b664e4e1690934658a18a5af458d..32eb8df32d12d6ef6c5bb99de5b30aac78c5074d 100644 --- a/core/js/tests/specs/coreSpec.js +++ b/core/js/tests/specs/coreSpec.js @@ -302,6 +302,7 @@ describe('Core base tests', function() { /* jshint camelcase: false */ window.oc_config = oldConfig; routeStub.restore(); + $(document).off('ajaxError'); }); it('sends heartbeat half the session lifetime when heartbeat enabled', function() { /* jshint camelcase: false */ @@ -473,6 +474,7 @@ describe('Core base tests', function() { }); afterEach(function() { clock.restore(); + $(document).off('ajaxError'); }); it('Sets up menu toggle', function() { window.initCore(); @@ -841,5 +843,45 @@ describe('Core base tests', function() { // verification is done in afterEach }); }); + describe('global ajax errors', function() { + var reloadStub, ajaxErrorStub; + + beforeEach(function() { + reloadStub = sinon.stub(OC, 'reload'); + // unstub the error processing method + ajaxErrorStub = OC._processAjaxError; + ajaxErrorStub.restore(); + window.initCore(); + }); + afterEach(function() { + reloadStub.restore(); + $(document).off('ajaxError'); + }); + + it('reloads current page in case of auth error', function () { + var dataProvider = [ + [200, false], + [400, false], + [401, true], + [302, true], + [307, true] + ]; + + for (var i = 0; i < dataProvider.length; i++) { + var xhr = { status: dataProvider[i][0] }; + var expectedCall = dataProvider[i][1]; + + reloadStub.reset(); + + $(document).trigger(new $.Event('ajaxError'), xhr); + + if (expectedCall) { + expect(reloadStub.calledOnce).toEqual(true); + } else { + expect(reloadStub.notCalled).toEqual(true); + } + } + }); + }) }); diff --git a/lib/private/api.php b/lib/private/api.php index 452612d4c16e90fe8e3fb46e6ad3d0b3c6f0ff01..6c6be233c9dfbcb9310543aae2ef34219a12c4a3 100644 --- a/lib/private/api.php +++ b/lib/private/api.php @@ -377,9 +377,16 @@ class OC_API { * @param string $format the format xml|json */ public static function respond($result, $format='xml') { + $request = \OC::$server->getRequest(); + // Send 401 headers if unauthorised if($result->getStatusCode() === API::RESPOND_UNAUTHORISED) { - header('WWW-Authenticate: Basic realm="Authorisation Required"'); + // If request comes from JS return dummy auth request + if($request->getHeader('X-Requested-With') === 'XMLHttpRequest') { + header('WWW-Authenticate: DummyBasic realm="Authorisation Required"'); + } else { + header('WWW-Authenticate: Basic realm="Authorisation Required"'); + } header('HTTP/1.0 401 Unauthorized'); } @@ -389,7 +396,7 @@ class OC_API { $meta = $result->getMeta(); $data = $result->getData(); - if (self::isV2(\OC::$server->getRequest())) { + if (self::isV2($request)) { $statusCode = self::mapStatusCodes($result->getStatusCode()); if (!is_null($statusCode)) { $meta['statuscode'] = $statusCode; diff --git a/lib/private/json.php b/lib/private/json.php index adee28a1593c9de9681bfad268ce396ac5b42029..74aebd476fb2e8229ef6b808428eede92c084971 100644 --- a/lib/private/json.php +++ b/lib/private/json.php @@ -66,6 +66,7 @@ class OC_JSON{ public static function checkLoggedIn() { if( !OC_User::isLoggedIn()) { $l = \OC::$server->getL10N('lib'); + http_response_code(\OCP\AppFramework\Http::STATUS_UNAUTHORIZED); self::error(array( 'data' => array( 'message' => $l->t('Authentication error'), 'error' => 'authentication_error' ))); exit(); }