Skip to content

Commit

Permalink
SiteWide ACL: Milestone 1.2 (oppia#3517)
Browse files Browse the repository at this point in the history
* UI for view role added

* UI part done.

* Role graph visualization added.

* Complete functionality done with changes in config domain being reflected to roles.

* tidying up the code.

* linting issues fixed.

* backend test updated.

* requested changes done.

* Requested changes done (except for role-graph).

* replaced role graph directive with a directive for general static graphs and used it in history tab.

* directive name changed to static-graph. changes made.

* reverted to using stripped up version of state graph for role graph.

* requested changes made.

* config revert property made to sync with new role system.

* role graph further stripped and test for admin role started.

* email actions can now be performed by admins instead of super admins. so made changes in syncing config tab changes to new system.

* Test added to check view and update.

* test written and storage model made for storing query info.

* adding comment for attribute nature of directive.

* lint fixes.

* test updated.

* ADMIN_SHOW_UPDATE_ROLE turned to false.

* requested changes made.

* requested changes made.

* minor changes made.

* lint fixes.

* requested changes done.

* test added for get_by_role.

* changed an import and changed show update form to false.

* list fixes.

* requested changes made.

* test added for get_role_changes.
  • Loading branch information
1995YogeshSharma authored and seanlip committed Jun 25, 2017
1 parent e8fa9d4 commit 37d3fdd
Show file tree
Hide file tree
Showing 28 changed files with 1,198 additions and 184 deletions.
108 changes: 107 additions & 1 deletion core/controllers/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
from core.domain import exp_services
from core.domain import recommendations_services
from core.domain import rights_manager
from core.domain import role_services
from core.domain import rte_component_registry
from core.domain import user_services
from core.platform import models
import feconf
import utils
Expand All @@ -52,6 +54,18 @@ def test_super_admin(self, **kwargs):
return test_super_admin


def assign_roles(changed_user_roles):
"""Assigns roles to users based on given dict.
Args:
changed_user_roles: dict(str:str). Dict mapping usernames to roles.
These are the changes that have to be applied to roles.
"""
for username, role in changed_user_roles.iteritems():
user_services.update_user_role(
user_services.get_user_id_from_username(username), role)


class AdminPage(base.BaseHandler):
"""Admin page shown in the App Engine admin console."""
@require_super_admin
Expand Down Expand Up @@ -105,6 +119,15 @@ def get(self):
'unfinished_job_data': unfinished_job_data,
'value_generators_js': jinja2.utils.Markup(
editor.get_value_generators_js()),
'updatable_roles': {
role: role_services.HUMAN_READABLE_ROLES[role]
for role in role_services.UPDATABLE_ROLES
},
'viewable_roles': {
role: role_services.HUMAN_READABLE_ROLES[role]
for role in role_services.VIEWABLE_ROLES
},
'role_graph_data': role_services.get_role_graph_data()
})

self.render_template('pages/admin/admin.html')
Expand Down Expand Up @@ -141,14 +164,48 @@ def post(self):
'new_config_property_values')
logging.info('[ADMIN] %s saved config property values: %s' %
(self.user_id, new_config_property_values))
config_properties = (
config_domain.Registry.get_config_property_schemas())
for (name, value) in new_config_property_values.iteritems():
config_services.set_property(self.user_id, name, value)
# For maintaining sync between old and new role system when
# roles are changed from config tab.
# TODO (1995YogeshSharma): Remove this once new system takes
# over.
role_change_dict = role_services.get_role_changes(
{
name: value['value']
for name, value in config_properties.iteritems()
},
new_config_property_values)
assign_roles(role_change_dict)

elif self.payload.get('action') == 'revert_config_property':
config_property_id = self.payload.get('config_property_id')
config_properties = (
config_domain.Registry.get_config_property_schemas())
logging.info('[ADMIN] %s reverted config property: %s' %
(self.user_id, config_property_id))
config_services.revert_property(
self.user_id, config_property_id)
new_config_properties = (
config_domain.Registry.get_config_property_schemas())

# For maintaining the sync between roles in old and new
# authorization system.
# TODO (1995YogeshSharma): Remove this block of code once
# the new system takes over.
role_change_dict = role_services.get_role_changes(
{
name: value['value']
for name, value in config_properties.iteritems()
},
{
name: value['value']
for name, value in new_config_properties.iteritems()
}
)
assign_roles(role_change_dict)
elif self.payload.get('action') == 'start_new_job':
for klass in jobs_registry.ONE_OFF_JOB_MANAGERS:
if klass.__name__ == self.payload.get('job_type'):
Expand Down Expand Up @@ -176,7 +233,6 @@ def post(self):
elif self.payload.get('action') == 'upload_topic_similarities':
data = self.payload.get('data')
recommendations_services.update_topic_similarities(data)

self.render_json({})
except Exception as e:
self.render_json({'error': unicode(e)})
Expand Down Expand Up @@ -205,6 +261,56 @@ def _reload_collection(self, collection_id):
raise Exception('Cannot reload a collection in production.')


class AdminRoleHandler(base.BaseHandler):
"""Handler for roles tab of admin page. Used to view and update roles."""

GET_HANDLER_ERROR_RETURN_TYPE = feconf.HANDLER_TYPE_JSON

@require_super_admin
def get(self):
view_method = self.request.get('method')

if view_method == feconf.VIEW_METHOD_ROLE:
role = self.request.get(feconf.VIEW_METHOD_ROLE)
users_by_role = {
username: role
for username in user_services.get_usernames_by_role(role)
}
role_services.log_role_query(
self.user_id, feconf.ROLE_ACTION_VIEW_BY_ROLE,
role=role)
self.render_json(users_by_role)
elif view_method == feconf.VIEW_METHOD_USERNAME:
username = self.request.get(feconf.VIEW_METHOD_USERNAME)
user_id = user_services.get_user_id_from_username(username)
role_services.log_role_query(
self.user_id, feconf.ROLE_ACTION_VIEW_BY_USERNAME,
username=username)
if user_id is None:
raise self.InvalidInputException(
'User with given username does not exist.')
user_role_dict = {
username: user_services.get_user_role_from_id(user_id)
}
self.render_json(user_role_dict)
else:
raise self.InvalidInputException('Invalid method to view roles.')

@require_super_admin
def post(self):
username = self.payload.get('username')
role = self.payload.get('role')
user_id = user_services.get_user_id_from_username(username)
if user_id is None:
raise self.InvalidInputException(
'User with given username does not exist.')
user_services.update_user_role(user_id, role)
role_services.log_role_query(
self.user_id, feconf.ROLE_ACTION_UPDATE, role=role,
username=username)
self.render_json({})


class AdminJobOutput(base.BaseHandler):
"""Retrieves job output to show on the admin page."""

Expand Down
63 changes: 63 additions & 0 deletions core/controllers/admin_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

from core.controllers import base
from core.tests import test_utils
import feconf


BOTH_MODERATOR_AND_ADMIN_EMAIL = '[email protected]'
Expand Down Expand Up @@ -98,3 +99,65 @@ def test_change_about_page_config_property(self):

response = self.testapp.get('/about')
self.assertIn(new_config_value, response.body)


class AdminRoleHandlerTest(test_utils.GenericTestBase):
"""Checks the user role handling on the admin page."""

def setUp(self):
"""Complete the signup process for self.ADMIN_EMAIL."""
super(AdminRoleHandlerTest, self).setUp()
self.signup(self.ADMIN_EMAIL, self.ADMIN_USERNAME)
self.set_admins([self.ADMIN_USERNAME])

def test_view_and_update_role(self):
user_email = '[email protected]'
user_name = 'user1'

self.signup(user_email, user_name)

self.login(self.ADMIN_EMAIL, is_super_admin=True)
# Check normal user has expected role. Viewing by username.
response_dict = self.get_json(
feconf.ADMIN_ROLE_HANDLER_URL,
{'method': 'username', 'username': 'user1'})
self.assertEqual(
response_dict, {'user1': feconf.ROLE_ID_EXPLORATION_EDITOR})

# Check role correctly gets updated.
response = self.testapp.get(feconf.ADMIN_URL)
csrf_token = self.get_csrf_token_from_response(response)
response_dict = self.post_json(
feconf.ADMIN_ROLE_HANDLER_URL,
{'role': feconf.ROLE_ID_MODERATOR, 'username': user_name},
csrf_token=csrf_token, expect_errors=False,
expected_status_int=200)
self.assertEqual(response_dict, {})

# Viewing by role.
response_dict = self.get_json(
feconf.ADMIN_ROLE_HANDLER_URL,
{'method': 'role', 'role': feconf.ROLE_ID_MODERATOR})
self.assertEqual(response_dict, {'user1': feconf.ROLE_ID_MODERATOR})
self.logout()

def test_invalid_username_in_view_and_update_role(self):
username = 'myinvaliduser'

self.login(self.ADMIN_EMAIL, is_super_admin=True)

# Trying to view role of non-existent user.
response = self.get_json(
feconf.ADMIN_ROLE_HANDLER_URL,
{'method': 'username', 'username': username},
expect_errors=True)
self.assertEqual(response['code'], 400)

# Trying to update role of non-existent user.
response = self.testapp.get(feconf.ADMIN_URL)
csrf_token = self.get_csrf_token_from_response(response)
response = self.post_json(
feconf.ADMIN_ROLE_HANDLER_URL,
{'role': feconf.ROLE_ID_MODERATOR, 'username': username},
csrf_token=csrf_token, expect_errors=True,
expected_status_int=400)
Loading

0 comments on commit 37d3fdd

Please sign in to comment.