summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNavaL <ayoyo@thoughtworks.com>2016-06-24 18:37:25 +0200
committerNavaL <ayoyo@thoughtworks.com>2016-06-24 18:37:55 +0200
commit64c54186eff000762c291758973ca8e5db28f606 (patch)
treeca90b7c74583cfa170e90838b5722b96ba7138da
parent12706c25ef02974ff54d1f42caac268f6eaa3a0c (diff)
Issue #694 add an admin restricted resource for user stats
-rw-r--r--service/pixelated/config/services.py6
-rw-r--r--service/pixelated/resources/__init__.py4
-rw-r--r--service/pixelated/resources/root_resource.py3
-rw-r--r--service/pixelated/resources/users.py30
-rw-r--r--service/test/integration/test_users_count.py47
-rw-r--r--service/test/support/integration/multi_user_client.py1
-rw-r--r--service/test/unit/bitmask_libraries/test_smtp_client_certificate.py2
-rw-r--r--service/test/unit/config/test_services.py12
-rw-r--r--service/test/unit/resources/test_users_resource.py70
9 files changed, 173 insertions, 2 deletions
diff --git a/service/pixelated/config/services.py b/service/pixelated/config/services.py
index b36eb0bb..a49e1df9 100644
--- a/service/pixelated/config/services.py
+++ b/service/pixelated/config/services.py
@@ -108,6 +108,9 @@ class ServicesFactory(object):
def add_session(self, user_id, services):
self._services_by_user[user_id] = services
+ def online_sessions(self):
+ return len(self._services_by_user.keys())
+
@defer.inlineCallbacks
def create_services_from(self, leap_session):
_services = Services(leap_session)
@@ -131,3 +134,6 @@ class SingleUserServicesFactory(object):
def destroy_session(self, user_id, using_email=False):
reactor.stop()
+
+ def online_sessions(self):
+ return 1
diff --git a/service/pixelated/resources/__init__.py b/service/pixelated/resources/__init__.py
index 0015db16..0bd34619 100644
--- a/service/pixelated/resources/__init__.py
+++ b/service/pixelated/resources/__init__.py
@@ -77,6 +77,10 @@ class BaseResource(Resource):
def get_session(self, request):
return IPixelatedSession(request.getSession())
+ def is_admin(self, request):
+ services = self._services(request)
+ return services._leap_session.user_auth.is_admin()
+
def _services(self, request):
user_id = self._get_user_id_from_request(request)
return self._services_factory.services(user_id)
diff --git a/service/pixelated/resources/root_resource.py b/service/pixelated/resources/root_resource.py
index aacf2b61..c9808a03 100644
--- a/service/pixelated/resources/root_resource.py
+++ b/service/pixelated/resources/root_resource.py
@@ -33,6 +33,8 @@ from pixelated.resources.tags_resource import TagsResource
from pixelated.resources.keys_resource import KeysResource
from twisted.web.static import File
+from pixelated.resources.users import UsersResource
+
CSRF_TOKEN_LENGTH = 32
MODE_STARTUP = 1
@@ -90,6 +92,7 @@ class RootResource(BaseResource):
self._child_resources.add('mail', MailResource(self._services_factory))
self._child_resources.add('feedback', FeedbackResource(self._services_factory))
self._child_resources.add('user-settings', UserSettingsResource(self._services_factory))
+ self._child_resources.add('users', UsersResource(self._services_factory))
self._child_resources.add(LoginResource.BASE_URL,
LoginResource(self._services_factory, portal, disclaimer_banner=disclaimer_banner))
self._child_resources.add(LogoutResource.BASE_URL, LogoutResource(self._services_factory))
diff --git a/service/pixelated/resources/users.py b/service/pixelated/resources/users.py
new file mode 100644
index 00000000..a3e6118e
--- /dev/null
+++ b/service/pixelated/resources/users.py
@@ -0,0 +1,30 @@
+#
+# Copyright (c) 2016 ThoughtWorks, Inc.
+#
+# Pixelated is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Pixelated is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
+
+from pixelated.resources import respond_json_deferred, BaseResource, respond_json, UnAuthorizedResource
+from twisted.web import server
+
+
+class UsersResource(BaseResource):
+ isLeaf = True
+
+ def __init__(self, services_factory):
+ BaseResource.__init__(self, services_factory)
+
+ def render_GET(self, request):
+ if self.is_admin(request):
+ return respond_json({"count": self._services_factory.online_sessions()}, request)
+ return UnAuthorizedResource().render_GET(request)
diff --git a/service/test/integration/test_users_count.py b/service/test/integration/test_users_count.py
new file mode 100644
index 00000000..d06ffd39
--- /dev/null
+++ b/service/test/integration/test_users_count.py
@@ -0,0 +1,47 @@
+#
+# Copyright (c) 2014 ThoughtWorks, Inc.
+#
+# Pixelated is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Pixelated is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
+import json
+
+from mockito import verify
+from mockito import when
+from twisted.internet import defer
+
+from test.support.integration.multi_user_client import MultiUserClient
+from test.support.integration.soledad_test_base import SoledadTestBase
+
+
+class UsersResourceTest(MultiUserClient, SoledadTestBase):
+
+ @defer.inlineCallbacks
+ def wait_for_session_user_id_to_finish(self):
+ yield self.adaptor.initialize_store(self.soledad)
+
+ @defer.inlineCallbacks
+ def test_online_users_count_uses_leap_auth_privileges(self):
+
+ response, login_request = yield self.login()
+ yield response
+
+ yield self.wait_for_session_user_id_to_finish()
+
+ when(self.user_auth).is_admin().thenReturn(True)
+ response, request = self.get("/users", json.dumps({'csrftoken': [login_request.getCookie('XSRF-TOKEN')]}),
+ from_request=login_request, as_json=False)
+ yield response
+
+ self.assertEqual(200, request.code) # redirected
+ self.assertEqual('{"count": 1}', request.getWrittenData()) # redirected
+ verify(self.user_auth).is_admin()
diff --git a/service/test/support/integration/multi_user_client.py b/service/test/support/integration/multi_user_client.py
index 656e0901..d6133e64 100644
--- a/service/test/support/integration/multi_user_client.py
+++ b/service/test/support/integration/multi_user_client.py
@@ -60,6 +60,7 @@ class MultiUserClient(AppTestClient):
leap_session.fresh_account = False
self.leap_session = leap_session
self.services = self._test_account.services
+ self.user_auth = user_auth
self._set_leap_srp_auth(username, password, user_auth)
when(LeapSessionFactory).create(username, password, user_auth).thenReturn(leap_session)
diff --git a/service/test/unit/bitmask_libraries/test_smtp_client_certificate.py b/service/test/unit/bitmask_libraries/test_smtp_client_certificate.py
index 58a7525f..e938d6f5 100644
--- a/service/test/unit/bitmask_libraries/test_smtp_client_certificate.py
+++ b/service/test/unit/bitmask_libraries/test_smtp_client_certificate.py
@@ -30,7 +30,7 @@ class TestSmtpClientCertificate(unittest.TestCase):
self.tmp_dir = tempdir.TempDir()
self.provider = mock()
self.provider.domain = 'some-provider.tld'
- self.auth = SRPSession('username', 'token', 'uuid', 'session_id')
+ self.auth = SRPSession('username', 'token', 'uuid', 'session_id', {})
self.pem_path = os.path.join(self.tmp_dir.name, 'providers', 'some-provider.tld', 'keys', 'client', 'smtp.pem')
self.downloader = mock()
when(session).SmtpCertDownloader(self.provider, self.auth).thenReturn(self.downloader)
diff --git a/service/test/unit/config/test_services.py b/service/test/unit/config/test_services.py
index 71765d19..8277c919 100644
--- a/service/test/unit/config/test_services.py
+++ b/service/test/unit/config/test_services.py
@@ -17,7 +17,7 @@ import unittest
from mockito import mock, verify
-from pixelated.config.services import Services
+from pixelated.config.services import Services, ServicesFactory
class ServicesTest(unittest.TestCase):
@@ -32,3 +32,13 @@ class ServicesTest(unittest.TestCase):
def test_close_services_closes_the_underlying_leap_session(self):
self.services.close()
verify(self.leap_session).close()
+
+
+class ServicesFactoryTest(unittest.TestCase):
+
+ def test_online_sessions_counts_logged_in_users(self):
+ service_factory = ServicesFactory(mock())
+ service_factory.add_session('some_id1', mock())
+ service_factory.add_session('some_id2', mock())
+
+ self.assertEqual(2, service_factory.online_sessions())
diff --git a/service/test/unit/resources/test_users_resource.py b/service/test/unit/resources/test_users_resource.py
new file mode 100644
index 00000000..bfd61022
--- /dev/null
+++ b/service/test/unit/resources/test_users_resource.py
@@ -0,0 +1,70 @@
+import os
+
+import test.support.mockito
+
+from leap.exceptions import SRPAuthenticationError
+from mock import patch
+from mockito import mock, when, any as ANY, verify, verifyZeroInteractions, verifyNoMoreInteractions
+from twisted.trial import unittest
+from twisted.web.resource import IResource
+from twisted.web.test.requesthelper import DummyRequest
+
+from pixelated.bitmask_libraries.session import LeapSession, LeapSessionFactory
+from pixelated.config.services import Services, ServicesFactory
+from pixelated.resources.login_resource import LoginResource
+from pixelated.resources.users import UsersResource
+from test.unit.resources import DummySite
+
+
+class TestUsersResource(unittest.TestCase):
+
+ def setUp(self):
+ self.services_factory = mock()
+ self.resource = UsersResource(self.services_factory)
+ self.web = DummySite(self.resource)
+
+ def test_numbers_of_users_online(self):
+ number_of_users_online = 6
+ self.services_factory.online_sessions = lambda: number_of_users_online
+ self.resource.is_admin = lambda _: True
+ request = DummyRequest([''])
+
+ d = self.web.get(request)
+
+ def assert_users_count(_):
+ self.assertEqual(200, request.code)
+ self.assertEqual('{"count": %d}' % number_of_users_online, request.written[0])
+
+ d.addCallback(assert_users_count)
+ return d
+
+ def test_numbers_of_users_online_is_only_available_only_for_admin(self):
+ self.resource.is_admin = lambda _: False
+ request = DummyRequest([''])
+ d = self.web.get(request)
+
+ def assert_is_forbidden(_):
+ self.assertEqual(401, request.responseCode)
+ self.assertEqual('Unauthorized!', request.written[0])
+
+ d.addCallback(assert_is_forbidden)
+ return d
+
+ def test_is_admin_is_queried_from_leap_auth(self):
+ leap_session = mock()
+ auth = mock()
+ auth.uuid = 'some_id1'
+ leap_session.user_auth = auth
+ leap_session.config = mock()
+ services = Services(leap_session)
+ service_factory = ServicesFactory(mock())
+ service_factory.add_session('some_id1', services)
+
+ when(auth).is_admin().thenReturn(True)
+ request = mock()
+ resource = UsersResource(service_factory)
+
+ when(resource)._get_user_id_from_request(request).thenReturn('some_id1')
+
+ self.assertTrue(resource.is_admin(request))
+ verify(auth).is_admin()