Skip to content

Commit d56621b

Browse files
authored
Merge pull request #3114 from kobotoolbox/3113-partial-permissions-export-fix
Partial permissions export fix
2 parents 44d381d + 24c1b4e commit d56621b

File tree

4 files changed

+36
-7
lines changed

4 files changed

+36
-7
lines changed

jsapp/js/components/projectDownloads/projectExportsList.es6

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ export default class ProjectExportsList extends React.Component {
219219
<span className='animate-processing'>{t('Processing…')}</span>
220220
}
221221

222-
{mixins.permissions.userCan(PERMISSIONS_CODENAMES.manage_asset, this.props.asset) &&
222+
{mixins.permissions.userCan(PERMISSIONS_CODENAMES.view_submissions, this.props.asset) &&
223223
<bem.KoboLightButton
224224
m={['red', 'icon-only']}
225225
onClick={this.deleteExport.bind(this, exportData.uid)}

jsapp/js/constants.es6

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,14 @@ export const ROOT_URL = (() => {
1919
export const ANON_USERNAME = 'AnonymousUser';
2020

2121
/**
22-
* A hardcoded list of permissions codenames.
23-
* All of them are really defined on backend, but we need it here to be able to
24-
* build UI for handling them.
22+
* BAD CODE™ A hardcoded list of permissions codenames.
23+
*
24+
* All of them are really defined on backend, and we get them through the
25+
* permissions config endpoint, but as we need these names to reference them in
26+
* the code to build the UI it's a necessary evil.
27+
*
28+
* NOTE: to know what these permissions permit see `kpi/permissions.py` file,
29+
* where you have to match the classes with endpoints and their HTTP methods.
2530
*/
2631
export const PERMISSIONS_CODENAMES = {};
2732
new Set([

kpi/permissions.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ class SubmissionValidationStatusPermission(SubmissionPermission):
274274
}
275275

276276

277-
class AssetExportSettingsPermission(AssetNestedObjectPermission):
277+
class AssetExportSettingsPermission(SubmissionPermission):
278278
perms_map = {
279279
'GET': ['%(app_label)s.view_submissions'],
280280
'POST': ['%(app_label)s.manage_asset'],
@@ -286,7 +286,7 @@ class AssetExportSettingsPermission(AssetNestedObjectPermission):
286286
perms_map['PATCH'] = perms_map['POST']
287287
perms_map['DELETE'] = perms_map['POST']
288288

289-
class ExportTaskPermission(AssetNestedObjectPermission):
289+
class ExportTaskPermission(SubmissionPermission):
290290
perms_map = {
291291
'GET': ['%(app_label)s.view_submissions'],
292292
}

kpi/tests/api/v2/test_api_exports.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,14 @@
22
import json
33
from collections import defaultdict
44

5+
from django.contrib.auth.models import User
56
from rest_framework import status
67
from rest_framework.reverse import reverse
78

9+
from kpi.constants import (
10+
PERM_PARTIAL_SUBMISSIONS,
11+
PERM_VIEW_SUBMISSIONS,
12+
)
813
from kpi.models import Asset, ExportTask
914
from kpi.models.object_permission import get_anonymous_user
1015
from kpi.tests.base_test_case import BaseTestCase
@@ -82,14 +87,33 @@ def test_export_task_list_anotheruser(self):
8287
self._create_export_task(_type=_type)
8388

8489
self.client.logout()
85-
self.client.login(username='anotheruser', passwork='anotheruser')
90+
self.client.login(username='anotheruser', password='anotheruser')
8691
list_url = reverse(
8792
self._get_endpoint('asset-export-list'),
8893
kwargs={'format': 'json', 'parent_lookup_asset': self.asset.uid},
8994
)
9095
response = self.client.get(list_url)
9196
assert response.status_code == status.HTTP_404_NOT_FOUND
9297

98+
def test_export_task_list_partial_permissions(self):
99+
self.client.logout()
100+
self.client.login(username='anotheruser', password='anotheruser')
101+
partial_perms = {
102+
PERM_VIEW_SUBMISSIONS: [{'_submitted_by': 'someuser'}]
103+
}
104+
list_url = reverse(
105+
self._get_endpoint('asset-export-list'),
106+
kwargs={'format': 'json', 'parent_lookup_asset': self.asset.uid},
107+
)
108+
response = self.client.get(list_url)
109+
assert response.status_code == status.HTTP_404_NOT_FOUND
110+
111+
anotheruser = User.objects.get(username='anotheruser')
112+
self.asset.assign_perm(anotheruser, PERM_PARTIAL_SUBMISSIONS,
113+
partial_perms=partial_perms)
114+
response = self.client.get(list_url)
115+
assert response.status_code == status.HTTP_200_OK
116+
93117
def test_export_task_list_filtered(self):
94118
for _type in ['csv', 'csv', 'xls']:
95119
self._create_export_task(_type=_type)

0 commit comments

Comments
 (0)