core/controllers/acl_decorators.py
# coding: utf-8
#
# Copyright 2017 The Oppia Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS-IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Decorators to provide authorization across the site."""
from __future__ import annotations
import functools
import logging
import re
from core import android_validation_constants
from core import feature_flag_list
from core import feconf
from core import utils
from core.constants import constants
from core.controllers import base
from core.domain import android_services
from core.domain import blog_services
from core.domain import classifier_services
from core.domain import classroom_config_services
from core.domain import email_manager
from core.domain import feature_flag_services
from core.domain import feedback_services
from core.domain import question_services
from core.domain import rights_manager
from core.domain import role_services
from core.domain import skill_domain
from core.domain import skill_fetchers
from core.domain import story_domain
from core.domain import story_fetchers
from core.domain import subtopic_page_services
from core.domain import suggestion_services
from core.domain import topic_domain
from core.domain import topic_fetchers
from core.domain import topic_services
from core.domain import user_services
from typing import Any, Callable, Dict, List, Optional, Type, TypeVar
# Note: '_SelfBaseHandlerType' is a private type variable because it is only
# supposed to denote the 'self' argument of the handler function that the
# decorator is decorating. So, do not make it public type variable in future.
_SelfBaseHandlerType = Type[base.BaseHandler]
# Note: '_GenericHandlerFunctionReturnType' is a private type variable because
# it is only supposed to denote the return type of handler function that the
# decorator is decorating. So, do not make it public type variable in future.
_GenericHandlerFunctionReturnType = TypeVar('_GenericHandlerFunctionReturnType')
def _redirect_based_on_return_type(
handler: _SelfBaseHandlerType,
redirection_url: str,
expected_return_type: str
) -> None:
"""Redirects to the provided URL if the handler type is not JSON.
Args:
handler: function. The function to be decorated.
redirection_url: str. The URL to redirect to.
expected_return_type: str. The type of the response to be returned
in case of errors eg. html, json.
Raises:
NotFoundException. The page is not found.
"""
if expected_return_type == feconf.HANDLER_TYPE_JSON:
raise handler.NotFoundException
handler.redirect(redirection_url)
def open_access(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to give access to everyone.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that can also give access to
everyone.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_access(
self: _SelfBaseHandlerType,
*args: Any,
**kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Gives access to everyone.
Args:
*args: list(*). A list of arguments.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
"""
return handler(self, *args, **kwargs)
return test_can_access
def is_source_mailchimp(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the request was generated from Mailchimp.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_is_source_mailchimp(
self: _SelfBaseHandlerType, secret: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks whether the request was generated from Mailchimp.
Args:
secret: str. The key that is used to authenticate that the request
has originated from Mailchimp.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
"""
if not email_manager.verify_mailchimp_secret(secret):
logging.error('Received invalid Mailchimp webhook secret')
raise self.NotFoundException
return handler(self, secret, **kwargs)
return test_is_source_mailchimp
def does_classroom_exist(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether classroom exists.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_does_classroom_exist(
self: _SelfBaseHandlerType,
classroom_url_fragment: str,
**kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if classroom url fragment provided is valid. If so, return
handler or else redirect to the correct classroom.
Args:
classroom_url_fragment: str. The classroom url fragment.
**kwargs: *. Keyword arguments.
Returns:
handler. function. The newly decorated function.
Raises:
Exception. This decorator is not expected to be used with other
handler types.
"""
classroom = classroom_config_services.get_classroom_by_url_fragment(
classroom_url_fragment)
if not classroom:
# This decorator should only be used for JSON handlers, since all
# HTML page handlers are expected to be migrated to the Angular
# router and access validation for such pages should be done using
# the access validation handler endpoint.
if self.GET_HANDLER_ERROR_RETURN_TYPE == feconf.HANDLER_TYPE_JSON:
raise self.NotFoundException
# As this decorator is not expected to be used with other
# handler types, raising an error here.
raise Exception(
'does_classroom_exist decorator is only expected to '
'be used with json return type handlers.')
return handler(self, classroom_url_fragment, **kwargs)
return test_does_classroom_exist
def can_play_exploration(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether user can play given exploration.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now can check if users can
play a given exploration.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_play(
self: _SelfBaseHandlerType, exploration_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can play the exploration.
Args:
exploration_id: str. The exploration id.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotFoundException. The page is not found.
"""
if exploration_id in feconf.DISABLED_EXPLORATION_IDS:
raise self.NotFoundException
exploration_rights = rights_manager.get_exploration_rights(
exploration_id, strict=False)
if exploration_rights is None:
raise self.NotFoundException
if rights_manager.check_can_access_activity(
self.user, exploration_rights):
return handler(self, exploration_id, **kwargs)
else:
raise self.NotFoundException
return test_can_play
def can_play_exploration_as_logged_in_user(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether user can play given exploration if the user
is logged in.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now can check if users can
play a given exploration if the user is logged in.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_play(
self: _SelfBaseHandlerType, exploration_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can play the exploration.
Args:
exploration_id: str. The exploration id.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotFoundException. The page is not found.
NotLoggedInException. The user is not logged in.
"""
if self.user_id is None:
raise self.NotLoggedInException
if exploration_id in feconf.DISABLED_EXPLORATION_IDS:
raise self.NotFoundException
exploration_rights = rights_manager.get_exploration_rights(
exploration_id, strict=False)
if exploration_rights is None:
raise self.NotFoundException
if rights_manager.check_can_access_activity(
self.user, exploration_rights):
return handler(self, exploration_id, **kwargs)
else:
raise self.NotFoundException
return test_can_play
def can_view_skills(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether user can view multiple given skills.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that can also check if the user
can view multiple given skills.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_view(
self: _SelfBaseHandlerType,
selected_skill_ids: List[str],
**kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can view the skills.
Args:
selected_skill_ids: list(str). List of skill ids.
**kwargs: *. Keyword arguments.
Returns:
bool. Whether the user can view the given skills.
Raises:
NotFoundException. The page is not found.
"""
# This is a temporary check, since a decorator is required for every
# method. Once skill publishing is done, whether given skill is
# published should be checked here.
try:
for skill_id in selected_skill_ids:
skill_domain.Skill.require_valid_skill_id(skill_id)
except utils.ValidationError as e:
raise self.InvalidInputException(e)
try:
skill_fetchers.get_multi_skills(selected_skill_ids)
except Exception as e:
raise self.NotFoundException(e)
return handler(self, selected_skill_ids, **kwargs)
return test_can_view
def can_play_collection(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether user can play given collection.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that can also check if a user can
play a given collection.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_play(
self: _SelfBaseHandlerType, collection_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can play the collection.
Args:
collection_id: str. The collection id.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotFoundException. The page is not found.
"""
collection_rights = rights_manager.get_collection_rights(
collection_id, strict=False)
if collection_rights is None:
raise self.NotFoundException
if rights_manager.check_can_access_activity(
self.user, collection_rights):
return handler(self, collection_id, **kwargs)
else:
raise self.NotFoundException
return test_can_play
def can_download_exploration(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether user can download given exploration.
If a user is authorized to play given exploration, they can download it.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that can also check if the user
has permission to download a given exploration.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_download(
self: _SelfBaseHandlerType, exploration_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can download the exploration.
Args:
exploration_id: str. The exploration id.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotFoundException. The page is not found.
"""
if exploration_id in feconf.DISABLED_EXPLORATION_IDS:
raise base.UserFacingExceptions.NotFoundException
exploration_rights = rights_manager.get_exploration_rights(
exploration_id, strict=False)
if exploration_rights is None:
raise self.NotFoundException
if rights_manager.check_can_access_activity(
self.user, exploration_rights):
return handler(self, exploration_id, **kwargs)
else:
raise self.NotFoundException
return test_can_download
def can_view_exploration_stats(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether user can view exploration stats.
If a user is authorized to play given exploration, they can view its stats.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that checks if the user
has permission to view exploration stats.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_view_stats(
self: _SelfBaseHandlerType, exploration_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can view the exploration stats.
Args:
exploration_id: str. The exploration id.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotFoundException. The page is not found.
"""
if exploration_id in feconf.DISABLED_EXPLORATION_IDS:
raise base.UserFacingExceptions.NotFoundException
exploration_rights = rights_manager.get_exploration_rights(
exploration_id, strict=False)
if exploration_rights is None:
raise self.NotFoundException
if rights_manager.check_can_access_activity(
self.user, exploration_rights):
return handler(self, exploration_id, **kwargs)
else:
raise base.UserFacingExceptions.NotFoundException
return test_can_view_stats
def can_edit_collection(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can edit collection.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that checks if the user has
permission to edit a given collection.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_edit(
self: _SelfBaseHandlerType, collection_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user is logged in and can edit the collection.
Args:
collection_id: str. The collection id.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotFoundException. The page is not found.
UnauthorizedUserException. The user does not have
credentials to edit the collection.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
collection_rights = rights_manager.get_collection_rights(
collection_id, strict=False)
if collection_rights is None:
raise base.UserFacingExceptions.NotFoundException
if rights_manager.check_can_edit_activity(
self.user, collection_rights):
return handler(self, collection_id, **kwargs)
else:
raise base.UserFacingExceptions.UnauthorizedUserException(
'You do not have credentials to edit this collection.')
return test_can_edit
def can_manage_email_dashboard(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether user can access email dashboard.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now checks if the user has
permission to access the email dashboard.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_manage_emails(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user is logged in and can access email dashboard.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have credentials to
access the email dashboard.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
if self.current_user_is_super_admin:
return handler(self, **kwargs)
raise self.UnauthorizedUserException(
'You do not have credentials to access email dashboard.')
return test_can_manage_emails
def can_access_blog_admin_page(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether user can access blog admin page.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now checks if the user has
permission to access the blog admin page.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_access_blog_admin_page(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user is logged in and can access blog admin page.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have credentials to
access the blog admin page.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
if role_services.ACTION_ACCESS_BLOG_ADMIN_PAGE in self.user.actions:
return handler(self, **kwargs)
raise self.UnauthorizedUserException(
'You do not have credentials to access blog admin page.')
return test_can_access_blog_admin_page
def can_manage_blog_post_editors(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether user can add and remove users as blog
post editors.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now checks if the user has
permission to manage blog post editors.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_manage_blog_post_editors(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user is logged in and can add and remove users as blog
post editors.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have credentials to
manage blog post editors..
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
if role_services.ACTION_MANAGE_BLOG_POST_EDITORS in self.user.actions:
return handler(self, **kwargs)
raise self.UnauthorizedUserException(
'You do not have credentials to add or remove blog post editors.')
return test_can_manage_blog_post_editors
def can_access_blog_dashboard(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether user can access blog dashboard.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now checks if the user has
permission to access the blog dashboard.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_access_blog_dashboard(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user is logged in and can access blog dashboard.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have credentials to
access the blog dashboard.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
if role_services.ACTION_ACCESS_BLOG_DASHBOARD in self.user.actions:
return handler(self, **kwargs)
raise self.UnauthorizedUserException(
'You do not have credentials to access blog dashboard page.')
return test_can_access_blog_dashboard
def can_delete_blog_post(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether user can delete blog post.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that checks if a user has
permission to delete a given blog post.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_delete(
self: _SelfBaseHandlerType, blog_post_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can delete the blog post.
Args:
blog_post_id: str. The blog post id.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have permissions to
delete this blog post.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
blog_post_rights = blog_services.get_blog_post_rights(
blog_post_id, strict=False)
if not blog_post_rights:
raise self.NotFoundException(
Exception('The given blog post id is invalid.'))
if role_services.ACTION_DELETE_ANY_BLOG_POST in self.user.actions:
return handler(self, blog_post_id, **kwargs)
if self.user_id in blog_post_rights.editor_ids:
return handler(self, blog_post_id, **kwargs)
else:
raise base.UserFacingExceptions.UnauthorizedUserException(
'User %s does not have permissions to delete blog post %s' %
(self.user_id, blog_post_id))
return test_can_delete
def can_edit_blog_post(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether user can edit blog post.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that checks if a user has
permission to edit a given blog post.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_edit(
self: _SelfBaseHandlerType, blog_post_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can edit the blog post.
Args:
blog_post_id: str. The blog post id.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have permissions to
edit this blog post.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
blog_post_rights = blog_services.get_blog_post_rights(
blog_post_id, strict=False)
if not blog_post_rights:
raise self.NotFoundException(
Exception('The given blog post id is invalid.'))
if role_services.ACTION_EDIT_ANY_BLOG_POST in self.user.actions:
return handler(self, blog_post_id, **kwargs)
if self.user_id in blog_post_rights.editor_ids:
return handler(self, blog_post_id, **kwargs)
else:
raise base.UserFacingExceptions.UnauthorizedUserException(
'User %s does not have permissions to edit blog post %s' %
(self.user_id, blog_post_id))
return test_can_edit
def can_access_moderator_page(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether user can access moderator page.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now checks if the user has
permission to access the moderator page.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_access_moderator_page(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user is logged in and can access moderator page.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have credentials to
access the moderator page.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
if role_services.ACTION_ACCESS_MODERATOR_PAGE in self.user.actions:
return handler(self, **kwargs)
raise self.UnauthorizedUserException(
'You do not have credentials to access moderator page.')
return test_can_access_moderator_page
def can_access_release_coordinator_page(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether user can access release coordinator page.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now checks if the user has
permission to access the release coordinator page.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_access_release_coordinator_page(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user is logged in and can access release coordinator
page.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have credentials to
access the release coordinator page.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
if role_services.ACTION_ACCESS_RELEASE_COORDINATOR_PAGE in (
self.user.actions):
return handler(self, **kwargs)
raise self.UnauthorizedUserException(
'You do not have credentials to access release coordinator page.')
return test_can_access_release_coordinator_page
def can_access_translation_stats(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether user can access translation stats.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now checks if the user has
permission to access translation stats.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_access_translation_stats(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can access translation stats.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have credentials to
access translation stats.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
if role_services.ACTION_MANAGE_TRANSLATION_CONTRIBUTOR_ROLES in (
self.user.actions):
return handler(self, **kwargs)
raise self.UnauthorizedUserException(
'You do not have credentials to access translation stats.')
return test_can_access_translation_stats
def can_manage_memcache(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether user can can manage memcache.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now checks if the user has
permission to manage memcache.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_manage_memcache(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user is logged in and can manage memcache.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have credentials manage
memcache.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
if role_services.ACTION_MANAGE_MEMCACHE in self.user.actions:
return handler(self, **kwargs)
raise self.UnauthorizedUserException(
'You do not have credentials to manage memcache.')
return test_can_manage_memcache
def can_run_any_job(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether user can can run any job.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now checks if the user has
permission to run any job.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_run_any_job(
self: _SelfBaseHandlerType, *args: Any, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user is logged in and can run any job.
Args:
*args: list(*). Positional arguments.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have credentials run
any job.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
if role_services.ACTION_RUN_ANY_JOB in self.user.actions:
return handler(self, *args, **kwargs)
raise self.UnauthorizedUserException(
'You do not have credentials to run jobs.')
return test_can_run_any_job
def can_send_moderator_emails(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether user can send moderator emails.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if the user
has permission to send moderator emails.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_send_moderator_emails(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user is logged in and can send moderator emails.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have credentials to
send moderator emails.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
if role_services.ACTION_SEND_MODERATOR_EMAILS in self.user.actions:
return handler(self, **kwargs)
raise self.UnauthorizedUserException(
'You do not have credentials to send moderator emails.')
return test_can_send_moderator_emails
def can_manage_own_account(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether user can manage their account.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if the user
has permission to manage their account.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_manage_account(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user is logged in and can manage their account.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have credentials to
manage account or preferences.
"""
if not self.user_id:
raise self.NotLoggedInException
if role_services.ACTION_MANAGE_ACCOUNT in self.user.actions:
return handler(self, **kwargs)
raise self.UnauthorizedUserException(
'You do not have credentials to manage account or preferences.')
return test_can_manage_account
def can_access_admin_page(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator that checks if the current user is a super admin.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if the user
is a super admin.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_super_admin(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user is logged in and is a super admin.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user is not a super admin of the
application.
"""
if not self.user_id:
raise self.NotLoggedInException
if not self.current_user_is_super_admin:
raise self.UnauthorizedUserException(
'%s is not a super admin of this application' % self.user_id)
return handler(self, **kwargs)
return test_super_admin
def can_access_contributor_dashboard_admin_page(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator that checks if the user can access the contributor dashboard
admin page.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks user can
access the contributor dashboard admin page.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_access_contributor_dashboard_admin_page(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can access the contributor dashboard admin page.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user cannot access the contributor
dashboard admin page.
"""
if not self.user_id:
raise self.NotLoggedInException
new_dashboard_enabled = feature_flag_services.is_feature_flag_enabled(
feature_flag_list.FeatureNames.CD_ADMIN_DASHBOARD_NEW_UI.value,
self.user_id)
if new_dashboard_enabled and (
role_services
.ACTION_ACCESS_NEW_CONTRIBUTOR_DASHBOARD_ADMIN_PAGE
in self.user.actions
):
return handler(self, **kwargs)
if not new_dashboard_enabled and (
role_services.ACTION_ACCESS_CONTRIBUTOR_DASHBOARD_ADMIN_PAGE in (
self.user.actions)):
return handler(self, **kwargs)
raise self.UnauthorizedUserException(
'You do not have credentials to access contributor dashboard '
'admin page.')
return test_can_access_contributor_dashboard_admin_page
def can_manage_contributors_role(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator that checks if the current user can modify contributor's role
for the contributor dashboard page.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if the user
can modify contributor's role for the contributor dashboard page.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_manage_contributors_role(
self: _SelfBaseHandlerType, category: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can modify contributor's role for the contributor
dashboard page.
Args:
category: str. The category of contribution.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user cannnot modify contributor's
role for the contributor dashboard page.
"""
if not self.user_id:
raise self.NotLoggedInException
if category in [
constants.CD_USER_RIGHTS_CATEGORY_REVIEW_QUESTION,
constants.CD_USER_RIGHTS_CATEGORY_SUBMIT_QUESTION]:
if role_services.ACTION_MANAGE_QUESTION_CONTRIBUTOR_ROLES in (
self.user.actions):
return handler(self, category, **kwargs)
elif category == (
constants.CD_USER_RIGHTS_CATEGORY_REVIEW_TRANSLATION):
if role_services.ACTION_MANAGE_TRANSLATION_CONTRIBUTOR_ROLES in (
self.user.actions):
return handler(self, category, **kwargs)
else:
raise self.InvalidInputException(
'Invalid category: %s' % category)
raise self.UnauthorizedUserException(
'You do not have credentials to modify contributor\'s role.')
return test_can_manage_contributors_role
def can_delete_any_user(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator that checks if the current user can delete any user.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if the user
can delete any user.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_primary_admin(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user is logged in and is a primary admin e.g. user with
email address equal to feconf.SYSTEM_EMAIL_ADDRESS.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user is not a primary admin of the
application.
"""
if not self.user_id:
raise self.NotLoggedInException
email = user_services.get_email_from_user_id(self.user_id)
if email != feconf.SYSTEM_EMAIL_ADDRESS:
raise self.UnauthorizedUserException(
'%s cannot delete any user.' % self.user_id)
return handler(self, **kwargs)
return test_primary_admin
def can_upload_exploration(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator that checks if the current user can upload exploration.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if a user
has permission to upload an exploration.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_upload(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can upload exploration.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have credentials to
upload an exploration.
"""
if not self.user_id:
raise self.NotLoggedInException
if not self.current_user_is_super_admin:
raise self.UnauthorizedUserException(
'You do not have credentials to upload explorations.')
return handler(self, **kwargs)
return test_can_upload
def can_create_exploration(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can create an exploration.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if a user
has permission to create an exploration.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_create(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can create an exploration.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have credentials to
create an exploration.
"""
if self.user_id is None:
raise self.NotLoggedInException
if role_services.ACTION_CREATE_EXPLORATION in self.user.actions:
return handler(self, **kwargs)
else:
raise base.UserFacingExceptions.UnauthorizedUserException(
'You do not have credentials to create an exploration.')
return test_can_create
def can_create_collection(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can create a collection.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if a user
has permission to create a collection.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_create(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can create a collection.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have credentials to
create a collection.
"""
if self.user_id is None:
raise self.NotLoggedInException
if role_services.ACTION_CREATE_COLLECTION in self.user.actions:
return handler(self, **kwargs)
else:
raise base.UserFacingExceptions.UnauthorizedUserException(
'You do not have credentials to create a collection.')
return test_can_create
def can_access_creator_dashboard(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can access creator dashboard page.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if a
user has permission to access the creator dashboard page.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_access(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can access the creator dashboard page.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have credentials to
access creator dashboard.
"""
if self.user_id is None:
raise self.NotLoggedInException
if role_services.ACTION_ACCESS_CREATOR_DASHBOARD in self.user.actions:
return handler(self, **kwargs)
else:
raise base.UserFacingExceptions.UnauthorizedUserException(
'You do not have credentials to access creator dashboard.')
return test_can_access
def can_create_feedback_thread(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can create a feedback thread.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if a user
has permission to create a feedback thread.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_access(
self: _SelfBaseHandlerType, exploration_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can create a feedback thread.
Args:
exploration_id: str. The ID of the exploration where the thread will
be created.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotFoundException. The page is not found.
UnauthorizedUserException. The user does not have credentials to
create an exploration feedback.
"""
if exploration_id in feconf.DISABLED_EXPLORATION_IDS:
raise base.UserFacingExceptions.NotFoundException
exploration_rights = rights_manager.get_exploration_rights(
exploration_id, strict=False)
if rights_manager.check_can_access_activity(
self.user, exploration_rights):
return handler(self, exploration_id, **kwargs)
else:
raise self.UnauthorizedUserException(
'You do not have credentials to create exploration feedback.')
return test_can_access
def can_view_feedback_thread(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can view a feedback thread.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if a user
has permission to view a feedback thread.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_access(
self: _SelfBaseHandlerType, thread_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can view a feedback thread.
Args:
thread_id: str. The feedback thread id.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
InvalidInputException. The thread ID is not valid.
NotFoundException. The page is not found.
UnauthorizedUserException. The user does not have credentials to
view an exploration feedback.
"""
# This should already be checked by the controller handler
# argument schemas, but we are adding it here for additional safety.
regex_pattern = constants.VALID_THREAD_ID_REGEX
regex_matched = bool(re.match(regex_pattern, thread_id))
if not regex_matched:
raise self.InvalidInputException('Not a valid thread id.')
entity_type = feedback_services.get_thread(thread_id).entity_type
entity_types_with_unrestricted_view_suggestion_access = (
feconf.ENTITY_TYPES_WITH_UNRESTRICTED_VIEW_SUGGESTION_ACCESS)
if entity_type in entity_types_with_unrestricted_view_suggestion_access:
return handler(self, thread_id, **kwargs)
exploration_id = feedback_services.get_exp_id_from_thread_id(thread_id)
if exploration_id in feconf.DISABLED_EXPLORATION_IDS:
raise base.UserFacingExceptions.NotFoundException
exploration_rights = rights_manager.get_exploration_rights(
exploration_id, strict=False)
if rights_manager.check_can_access_activity(
self.user, exploration_rights):
return handler(self, thread_id, **kwargs)
else:
raise self.UnauthorizedUserException(
'You do not have credentials to view exploration feedback.')
return test_can_access
def can_comment_on_feedback_thread(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can comment on feedback thread.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if the user
has permission to comment on a given feedback thread.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_access(
self: _SelfBaseHandlerType, thread_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can comment on the feedback thread.
Args:
thread_id: str. The feedback thread id.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
InvalidInputException. The thread ID is not valid.
NotFoundException. The page is not found.
UnauthorizedUserException. The user does not have credentials to
comment on an exploration feedback.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
# This should already be checked by the controller handler
# argument schemas, but we are adding it here for additional safety.
regex_pattern = constants.VALID_THREAD_ID_REGEX
regex_matched = bool(re.match(regex_pattern, thread_id))
if not regex_matched:
raise self.InvalidInputException('Not a valid thread id.')
exploration_id = feedback_services.get_exp_id_from_thread_id(thread_id)
if exploration_id in feconf.DISABLED_EXPLORATION_IDS:
raise base.UserFacingExceptions.NotFoundException
exploration_rights = rights_manager.get_exploration_rights(
exploration_id, strict=False)
if rights_manager.check_can_access_activity(
self.user, exploration_rights):
return handler(self, thread_id, **kwargs)
else:
raise self.UnauthorizedUserException(
'You do not have credentials to comment on exploration'
' feedback.')
return test_can_access
def can_rate_exploration(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can give rating to given
exploration.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if the user
has permission to rate a given exploration.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_rate(
self: _SelfBaseHandlerType, exploration_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can rate the exploration.
Args:
exploration_id: str. The exploration id.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
UnauthorizedUserException. The user does not have credentials to
rate an exploration.
"""
if (role_services.ACTION_RATE_ANY_PUBLIC_EXPLORATION in
self.user.actions):
return handler(self, exploration_id, **kwargs)
else:
raise base.UserFacingExceptions.UnauthorizedUserException(
'You do not have credentials to give ratings to explorations.')
return test_can_rate
def can_flag_exploration(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether user can flag given exploration.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if
a user can flag a given exploration.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_flag(
self: _SelfBaseHandlerType, exploration_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can flag the exploration.
Args:
exploration_id: str. The exploration id.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
UnauthorizedUserException. The user does not have credentials to
flag an exploration.
"""
if role_services.ACTION_FLAG_EXPLORATION in self.user.actions:
return handler(self, exploration_id, **kwargs)
else:
raise base.UserFacingExceptions.UnauthorizedUserException(
'You do not have credentials to flag explorations.')
return test_can_flag
def can_subscribe_to_users(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether user can subscribe/unsubscribe a creator.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if a user
has permission to subscribe/unsubscribe a creator.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_subscribe(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can subscribe/unsubscribe a creator.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
UnauthorizedUserException. The user does not have credentials to
manage subscriptions.
"""
if role_services.ACTION_SUBSCRIBE_TO_USERS in self.user.actions:
return handler(self, **kwargs)
else:
raise base.UserFacingExceptions.UnauthorizedUserException(
'You do not have credentials to manage subscriptions.')
return test_can_subscribe
def can_edit_exploration(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can edit given exploration.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if
a user has permission to edit a given exploration.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_edit(
self: _SelfBaseHandlerType,
exploration_id: str,
*args: Any,
**kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can edit the exploration.
Args:
exploration_id: str. The exploration id.
*args: list(*). A list of arguments.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
NotFoundException. The page is not found.
UnauthorizedUserException. The user does not have credentials to
edit an exploration.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
exploration_rights = rights_manager.get_exploration_rights(
exploration_id, strict=False)
if exploration_rights is None:
raise base.UserFacingExceptions.NotFoundException
if rights_manager.check_can_edit_activity(
self.user, exploration_rights):
return handler(self, exploration_id, *args, **kwargs)
else:
raise base.UserFacingExceptions.UnauthorizedUserException(
'You do not have credentials to edit this exploration.')
return test_can_edit
def can_voiceover_exploration(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can voiceover given exploration.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if a user
has permission to voiceover a given exploration.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_voiceover(
self: _SelfBaseHandlerType, exploration_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can voiceover the exploration.
Args:
exploration_id: str. The exploration id.
**kwargs: dict(str: *). Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
NotFoundException. The page is not found.
UnauthorizedUserException. The user does not have credentials to
voiceover an exploration.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
exploration_rights = rights_manager.get_exploration_rights(
exploration_id, strict=False)
if exploration_rights is None:
raise base.UserFacingExceptions.NotFoundException
if rights_manager.check_can_voiceover_activity(
self.user, exploration_rights):
return handler(self, exploration_id, **kwargs)
else:
raise base.UserFacingExceptions.UnauthorizedUserException(
'You do not have credentials to voiceover this exploration.')
return test_can_voiceover
def can_add_voice_artist(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can add voice artist to
the given activity.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if a user
has permission to add voice artist.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_add_voice_artist(
self: _SelfBaseHandlerType,
entity_type: str,
entity_id: str,
**kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can add a voice artist for the given entity.
Args:
entity_type: str. The type of entity.
entity_id: str. The Id of the entity.
**kwargs: dict(str: *). Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
InvalidInputException. The given entity type is not supported.
NotFoundException. The page is not found.
InvalidInputException. The given exploration is private.
UnauthorizedUserException. The user does not have the credentials
to manage voice artist.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
if entity_type != feconf.ENTITY_TYPE_EXPLORATION:
raise self.InvalidInputException(
'Unsupported entity_type: %s' % entity_type)
exploration_rights = rights_manager.get_exploration_rights(
entity_id, strict=False)
if exploration_rights is None:
raise base.UserFacingExceptions.NotFoundException
if exploration_rights.is_private():
raise base.UserFacingExceptions.InvalidInputException(
'Could not assign voice artist to private activity.')
if rights_manager.check_can_manage_voice_artist_in_activity(
self.user, exploration_rights):
return handler(self, entity_type, entity_id, **kwargs)
else:
raise base.UserFacingExceptions.UnauthorizedUserException(
'You do not have credentials to manage voice artists.')
return test_can_add_voice_artist
def can_remove_voice_artist(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can remove voice artist
from the given activity.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if a user
has permission to remove voice artist.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_remove_voice_artist(
self: _SelfBaseHandlerType,
entity_type: str,
entity_id: str,
**kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can remove a voice artist for the given entity.
Args:
entity_type: str. The type of entity.
entity_id: str. The Id of the entity.
**kwargs: dict(str: *). Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
InvalidInputException. The given entity type is not supported.
NotFoundException. The page is not found.
UnauthorizedUserException. The user does not have the credentials
to manage voice artist.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
if entity_type != feconf.ENTITY_TYPE_EXPLORATION:
raise self.InvalidInputException(
'Unsupported entity_type: %s' % entity_type)
exploration_rights = rights_manager.get_exploration_rights(
entity_id, strict=False)
if exploration_rights is None:
raise base.UserFacingExceptions.NotFoundException
if rights_manager.check_can_manage_voice_artist_in_activity(
self.user, exploration_rights):
return handler(self, entity_type, entity_id, **kwargs)
else:
raise base.UserFacingExceptions.UnauthorizedUserException(
'You do not have credentials to manage voice artists.')
return test_can_remove_voice_artist
def can_save_exploration(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether user can save exploration.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that checks if
a user has permission to save a given exploration.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_save(
self: _SelfBaseHandlerType,
exploration_id: str,
**kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can save the exploration.
Args:
exploration_id: str. The exploration id.
**kwargs: dict(str: *). Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
NotFoundException. The page is not found.
UnauthorizedUserException. The user does not have credentials to
save changes to this exploration.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
exploration_rights = rights_manager.get_exploration_rights(
exploration_id, strict=False)
if exploration_rights is None:
raise base.UserFacingExceptions.NotFoundException
if rights_manager.check_can_save_activity(
self.user, exploration_rights):
return handler(self, exploration_id, **kwargs)
else:
raise base.UserFacingExceptions.UnauthorizedUserException(
'You do not have permissions to save this exploration.')
return test_can_save
def can_delete_exploration(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether user can delete exploration.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that checks if a user has
permission to delete a given exploration.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_delete(
self: _SelfBaseHandlerType, exploration_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can delete the exploration.
Args:
exploration_id: str. The exploration id.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have permissions to
delete an exploration.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
exploration_rights = rights_manager.get_exploration_rights(
exploration_id, strict=False)
if rights_manager.check_can_delete_activity(
self.user, exploration_rights):
return handler(self, exploration_id, **kwargs)
else:
raise base.UserFacingExceptions.UnauthorizedUserException(
'User %s does not have permissions to delete exploration %s' %
(self.user_id, exploration_id))
return test_can_delete
def can_suggest_changes_to_exploration(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether a user can make suggestions to an
exploration.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if a user
has permission to make suggestions to an exploration.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_suggest(
self: _SelfBaseHandlerType, exploration_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can make suggestions to an exploration.
Args:
exploration_id: str. The exploration id.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
UnauthorizedUserException. The user does not have credentials to
give suggestions to an exploration.
"""
if role_services.ACTION_SUGGEST_CHANGES in self.user.actions:
return handler(self, exploration_id, **kwargs)
else:
raise base.UserFacingExceptions.UnauthorizedUserException(
'You do not have credentials to give suggestions to this '
'exploration.')
return test_can_suggest
def can_suggest_changes(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether a user can make suggestions.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if the user
has permission to make suggestions.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_suggest(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can make suggestions to an exploration.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
UnauthorizedUserException. The user does not have credentials to
make suggestions.
"""
if role_services.ACTION_SUGGEST_CHANGES in self.user.actions:
return handler(self, **kwargs)
else:
raise base.UserFacingExceptions.UnauthorizedUserException(
'You do not have credentials to make suggestions.')
return test_can_suggest
def can_resubmit_suggestion(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether a user can resubmit a suggestion."""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_resubmit_suggestion(
self: _SelfBaseHandlerType, suggestion_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can edit the given suggestion.
Args:
suggestion_id: str. The ID of the suggestion.
**kwargs: *. The keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
UnauthorizedUserException. The user does not have credentials to
edit this suggestion.
"""
suggestion = suggestion_services.get_suggestion_by_id(
suggestion_id, strict=False
)
if suggestion is None:
raise self.InvalidInputException(
'No suggestion found with given suggestion id')
if self.user_id and suggestion_services.check_can_resubmit_suggestion(
suggestion_id, self.user_id):
return handler(self, suggestion_id, **kwargs)
else:
raise base.UserFacingExceptions.UnauthorizedUserException(
'You do not have credentials to resubmit this suggestion.')
return test_can_resubmit_suggestion
def can_publish_exploration(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether user can publish exploration.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if the user
has permission to publish an exploration.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_publish(
self: _SelfBaseHandlerType,
exploration_id: str,
*args: Any,
**kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can publish the exploration.
Args:
exploration_id: str. The exploration id.
*args: list(*). A list of arguments.
**kwargs: *. Keyword arguments present in kwargs.
Returns:
*. The return value of the decorated function.
Raises:
NotFoundException. The page is not found.
UnauthorizedUserException. The user does not have credentials to
publish an exploration.
"""
exploration_rights = rights_manager.get_exploration_rights(
exploration_id, strict=False)
if exploration_rights is None:
raise base.UserFacingExceptions.NotFoundException
if rights_manager.check_can_publish_activity(
self.user, exploration_rights):
return handler(self, exploration_id, *args, **kwargs)
raise base.UserFacingExceptions.UnauthorizedUserException(
'You do not have credentials to publish this exploration.')
return test_can_publish
def can_publish_collection(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether user can publish collection.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if a user
has permission to publish a collection.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_publish_collection(
self: _SelfBaseHandlerType, collection_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can publish the collection.
Args:
collection_id: str. The collection id.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotFoundException. The page is not found.
UnauthorizedUserException. The user does not have credentials to
publish a collection.
"""
collection_rights = rights_manager.get_collection_rights(
collection_id, strict=False)
if collection_rights is None:
raise base.UserFacingExceptions.NotFoundException
if rights_manager.check_can_publish_activity(
self.user, collection_rights):
return handler(self, collection_id, **kwargs)
raise self.UnauthorizedUserException(
'You do not have credentials to publish this collection.')
return test_can_publish_collection
def can_unpublish_collection(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether user can unpublish a given
collection.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that also checks if
the user has permission to unpublish a collection.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_unpublish_collection(
self: _SelfBaseHandlerType, collection_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can unpublish the collection.
Args:
collection_id: str. The collection id.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotFoundException. The page is not found.
UnauthorizedUserException. The user does not have credentials
to unpublish a collection.
"""
collection_rights = rights_manager.get_collection_rights(
collection_id, strict=False)
if collection_rights is None:
raise base.UserFacingExceptions.NotFoundException
if rights_manager.check_can_unpublish_activity(
self.user, collection_rights):
return handler(self, collection_id, **kwargs)
raise self.UnauthorizedUserException(
'You do not have credentials to unpublish this collection.')
return test_can_unpublish_collection
def can_modify_exploration_roles(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorators to check whether user can manage rights related to an
exploration.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if
the user has permission to manage rights related to an
exploration.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_modify(
self: _SelfBaseHandlerType, exploration_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can modify the rights related to an exploration.
Args:
exploration_id: str. The exploration id.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
UnauthorizedUserException. The user does not have credentials to
change the rights for an exploration.
"""
exploration_rights = rights_manager.get_exploration_rights(
exploration_id, strict=False)
if rights_manager.check_can_modify_core_activity_roles(
self.user, exploration_rights):
return handler(self, exploration_id, **kwargs)
else:
raise base.UserFacingExceptions.UnauthorizedUserException(
'You do not have credentials to change rights for this '
'exploration.')
return test_can_modify
def can_perform_tasks_in_taskqueue(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to ensure that the handler is being called by task scheduler or
by a superadmin of the application.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also ensures that
the handler can only be executed if it is called by task scheduler or by
a superadmin of the application.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_perform(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the handler is called by task scheduler or by a superadmin
of the application.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
UnauthorizedUserException. The user does not have
credentials to access the page.
"""
# The X-AppEngine-QueueName header is set inside AppEngine and if
# a request from outside comes with this header AppEngine will get
# rid of it.
# https://cloud.google.com/tasks/docs/creating-appengine-handlers#reading_app_engine_task_request_headers
if (self.request.headers.get('X-AppEngine-QueueName') is None and
not self.current_user_is_super_admin):
raise self.UnauthorizedUserException(
'You do not have the credentials to access this page.')
return handler(self, **kwargs)
return test_can_perform
def can_perform_cron_tasks(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to ensure that the handler is being called by cron or by a
superadmin of the application.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also ensures that
the handler can only be executed if it is called by cron or by
a superadmin of the application.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_perform(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the handler is called by cron or by a superadmin of the
application.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
UnauthorizedUserException. The user does not have
credentials to access the page.
"""
# The X-AppEngine-Cron header is set inside AppEngine and if a request
# from outside comes with this header AppEngine will get rid of it.
# https://cloud.google.com/appengine/docs/flexible/python/scheduling-jobs-with-cron-yaml#validating_cron_requests
if (self.request.headers.get('X-AppEngine-Cron') is None and
not self.current_user_is_super_admin):
raise self.UnauthorizedUserException(
'You do not have the credentials to access this page.')
return handler(self, **kwargs)
return test_can_perform
def can_access_learner_dashboard(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check access to learner dashboard.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if
one can access the learner dashboard.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_access(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can access the learner dashboard.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have
credentials to access the page.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
if role_services.ACTION_ACCESS_LEARNER_DASHBOARD in self.user.actions:
return handler(self, **kwargs)
else:
raise self.UnauthorizedUserException(
'You do not have the credentials to access this page.')
return test_can_access
def can_access_feedback_updates(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check access to feedback updates.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if
one can access the feedback updates.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_access(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can access the feedback updates.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have
credentials to access the page.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
if role_services.ACTION_ACCESS_FEEDBACK_UPDATES in self.user.actions:
return handler(self, **kwargs)
else:
raise self.UnauthorizedUserException(
'You do not have the credentials to access this page.')
return test_can_access
def can_access_learner_groups(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check access to learner groups.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if
one can access the learner groups.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_access(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can access the learner groups.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have
credentials to access the page.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
if role_services.ACTION_ACCESS_LEARNER_GROUPS in self.user.actions:
return handler(self, **kwargs)
else:
raise self.UnauthorizedUserException(
'You do not have the credentials to access this page.')
return test_can_access
def can_manage_question_skill_status(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can publish a question and link it
to a skill.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if the
given user has permission to publish a question and link it
to a skill.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_manage_question_skill_status(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can publish a question directly.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have
credentials to publish a question.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
if (
role_services.ACTION_MANAGE_QUESTION_SKILL_STATUS in
self.user.actions):
return handler(self, **kwargs)
else:
raise self.UnauthorizedUserException(
'You do not have credentials to publish a question.')
return test_can_manage_question_skill_status
def require_user_id_else_redirect_to_homepage(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., Optional[_GenericHandlerFunctionReturnType]]:
"""Decorator that checks if a user_id is associated with the current
session. If not, the user is redirected to the main page.
Note that the user may not yet have registered.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks
if a given user_id is associated with the current
session.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_login(
self: _SelfBaseHandlerType, **kwargs: Any
) -> Optional[_GenericHandlerFunctionReturnType]:
"""Checks if the user for the current session is logged in.
If not, redirects the user to the home page.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
"""
if not self.user_id:
self.redirect('/')
return None
return handler(self, **kwargs)
return test_login
def can_edit_topic(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can edit given topic."""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_edit(
self: _SelfBaseHandlerType, topic_id: str, *args: Any, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks whether the user can edit a given topic.
Args:
topic_id: str. The topic id.
*args: list(*). The arguments from the calling function.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
NotFoundException. The page is not found.
UnauthorizedUserException. The user does not have
credentials to edit a topic.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
try:
topic_domain.Topic.require_valid_topic_id(topic_id)
except utils.ValidationError as e:
raise self.NotFoundException(e)
topic = topic_fetchers.get_topic_by_id(topic_id, strict=False)
topic_rights = topic_fetchers.get_topic_rights(topic_id, strict=False)
if topic_rights is None or topic is None:
raise base.UserFacingExceptions.NotFoundException
if topic_services.check_can_edit_topic(self.user, topic_rights):
return handler(self, topic_id, *args, **kwargs)
else:
raise self.UnauthorizedUserException(
'You do not have credentials to edit this topic.')
return test_can_edit
def can_edit_question(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can edit given question.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks
whether the user has permission to edit a given question.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_edit(
self: _SelfBaseHandlerType, question_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks whether the user can edit the given question.
Args:
question_id: str. The question id.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
NotFoundException. The page is not found.
UnauthorizedUserException. The user does not have
credentials to edit a question.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
question = question_services.get_question_by_id(
question_id, strict=False)
if question is None:
raise self.NotFoundException
if role_services.ACTION_EDIT_ANY_QUESTION in self.user.actions:
return handler(self, question_id, **kwargs)
else:
raise self.UnauthorizedUserException(
'You do not have credentials to edit this question.')
return test_can_edit
def can_play_question(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can play given question.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks
whether the user can play a given question.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_play_question(
self: _SelfBaseHandlerType, question_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks whether the user can play the given question.
Args:
question_id: str. The question id.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotFoundException. The page is not found.
"""
question = question_services.get_question_by_id(
question_id, strict=False)
if question is None:
raise self.NotFoundException
return handler(self, question_id, **kwargs)
return test_can_play_question
def can_view_question_editor(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can view any question editor.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks
if the user has permission to view any question editor.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_view_question_editor(
self: _SelfBaseHandlerType, question_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks whether the user can view the question editor.
Args:
question_id: str. The question id.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
NotFoundException. The page is not found.
UnauthorizedUserException. The user does not have
enough rights to access the question editor.
"""
if not self.user_id:
raise self.NotLoggedInException
question = question_services.get_question_by_id(
question_id, strict=False)
if question is None:
raise self.NotFoundException
if role_services.ACTION_VISIT_ANY_QUESTION_EDITOR_PAGE in (
self.user.actions):
return handler(self, question_id, **kwargs)
else:
raise self.UnauthorizedUserException(
'%s does not have enough rights to access the questions editor'
% self.user_id)
return test_can_view_question_editor
def can_delete_question(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can delete a question.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks
if the user has permission to delete a question.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_delete_question(
self: _SelfBaseHandlerType, question_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks whether the user can delete a given question.
Args:
question_id: str. The question id.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have
enough rights to delete the question.
"""
if not self.user_id:
raise self.NotLoggedInException
user_actions_info = user_services.get_user_actions_info(self.user_id)
if (role_services.ACTION_DELETE_ANY_QUESTION in
user_actions_info.actions):
return handler(self, question_id, **kwargs)
else:
raise self.UnauthorizedUserException(
'%s does not have enough rights to delete the'
' question.' % self.user_id)
return test_can_delete_question
def can_add_new_story_to_topic(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can add a story to a given topic.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks
if the user has permission to add a story to a given topic.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_add_story(
self: _SelfBaseHandlerType, topic_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks whether the user can add a story to
a given topic.
Args:
topic_id: str. The topic id.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
NotFoundException. The page is not found.
UnauthorizedUserException. The user does not have
credentials to add a story to a given topic.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
try:
topic_domain.Topic.require_valid_topic_id(topic_id)
except utils.ValidationError as e:
raise self.NotFoundException(e)
topic = topic_fetchers.get_topic_by_id(topic_id, strict=False)
topic_rights = topic_fetchers.get_topic_rights(topic_id, strict=False)
if topic_rights is None or topic is None:
raise base.UserFacingExceptions.NotFoundException
if topic_services.check_can_edit_topic(self.user, topic_rights):
return handler(self, topic_id, **kwargs)
else:
raise self.UnauthorizedUserException(
'You do not have credentials to add a story to this topic.')
return test_can_add_story
def can_edit_story(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can edit a story belonging to a given
topic.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if
a user has permission to edit a story for a given topic.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_edit_story(
self: _SelfBaseHandlerType, story_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks whether the user can edit a story belonging to
a given topic.
Args:
story_id: str. The story id.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
NotFoundException. The page is not found.
UnauthorizedUserException. The user does not have
credentials to edit a story belonging to a
given topic.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
story_domain.Story.require_valid_story_id(story_id)
story = story_fetchers.get_story_by_id(story_id, strict=False)
if story is None:
raise base.UserFacingExceptions.NotFoundException
topic_id = story.corresponding_topic_id
topic_rights = topic_fetchers.get_topic_rights(topic_id, strict=False)
topic = topic_fetchers.get_topic_by_id(topic_id, strict=False)
if topic_rights is None or topic is None:
raise base.UserFacingExceptions.NotFoundException
canonical_story_ids = topic.get_canonical_story_ids()
if story_id not in canonical_story_ids:
raise base.UserFacingExceptions.NotFoundException
if topic_services.check_can_edit_topic(self.user, topic_rights):
return handler(self, story_id, **kwargs)
else:
raise self.UnauthorizedUserException(
'You do not have credentials to edit this story.')
return test_can_edit_story
def can_edit_skill(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can edit a skill, which can be
independent or belong to a topic.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if
the user has permission to edit a skill.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_edit_skill(
self: _SelfBaseHandlerType, skill_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Test to see if user can edit a given skill by checking if
logged in and using can_user_edit_skill.
Args:
skill_id: str. The skill ID.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
NotFoundException. The given page cannot be found.
UnauthorizedUserException. The user does not have the
credentials to edit the given skill.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
if role_services.ACTION_EDIT_SKILL in self.user.actions:
return handler(self, skill_id, **kwargs)
else:
raise self.UnauthorizedUserException(
'You do not have credentials to edit this skill.')
return test_can_edit_skill
def can_submit_images_to_questions(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can submit images to questions.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if
the user has permission to submit a question.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_submit_images_to_questions(
self: _SelfBaseHandlerType, skill_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Test to see if user can submit images to questions.
Args:
skill_id: str. The skill ID.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
NotFoundException. The given page cannot be found.
UnauthorizedUserException. The user does not have the
credentials to edit the given skill.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
if any(action in self.user.actions for action in [
role_services.ACTION_SUGGEST_CHANGES,
role_services.ACTION_EDIT_ANY_QUESTION
]):
return handler(self, skill_id, **kwargs)
else:
raise self.UnauthorizedUserException(
'You do not have credentials to submit images to questions.')
return test_can_submit_images_to_questions
def can_submit_images_to_explorations(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can submit images to explorations.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if
the user has permission to submit images to an exploration.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_submit_images_to_explorations(
self: _SelfBaseHandlerType, target_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Test to see if user can submit images to explorations.
Args:
target_id: str. The target exploration ID.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
NotFoundException. The given page cannot be found.
UnauthorizedUserException. The user does not have the
credentials to edit the target exploration.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
if role_services.ACTION_SUGGEST_CHANGES in self.user.actions:
return handler(self, target_id, **kwargs)
else:
raise self.UnauthorizedUserException(
'You do not have credentials to submit images to explorations.')
return test_can_submit_images_to_explorations
def can_delete_skill(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can delete a skill.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks
if the user can delete a skill.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_delete_skill(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks whether the user can delete a skill.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have
credentials to delete a skill.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
user_actions_info = user_services.get_user_actions_info(self.user_id)
if role_services.ACTION_DELETE_ANY_SKILL in user_actions_info.actions:
return handler(self, **kwargs)
else:
raise self.UnauthorizedUserException(
'You do not have credentials to delete the skill.')
return test_can_delete_skill
def can_create_skill(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can create a skill, which can be
independent or added to a topic.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks if
the user has permission to create a skill.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_create_skill(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks whether the user can create a skill, which can be
independent or belong to a topic.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have
credentials to create a skill.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
user_actions_info = user_services.get_user_actions_info(self.user_id)
if role_services.ACTION_CREATE_NEW_SKILL in user_actions_info.actions:
return handler(self, **kwargs)
else:
raise self.UnauthorizedUserException(
'You do not have credentials to create a skill.')
return test_can_create_skill
def can_delete_story(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can delete a story in a given
topic.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also checks
whether the user has permission to delete a story in a
given topic.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_delete_story(
self: _SelfBaseHandlerType, story_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks whether the user can delete a story in
a given topic.
Args:
story_id: str. The story ID.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
NotFoundException. The page is not found.
UnauthorizedUserException. The user does not have
credentials to delete a story.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
story = story_fetchers.get_story_by_id(story_id, strict=False)
if story is None:
raise base.UserFacingExceptions.NotFoundException
topic_id = story.corresponding_topic_id
topic = topic_fetchers.get_topic_by_id(topic_id, strict=False)
topic_rights = topic_fetchers.get_topic_rights(topic_id, strict=False)
if topic_rights is None or topic is None:
raise base.UserFacingExceptions.NotFoundException
if topic_services.check_can_edit_topic(self.user, topic_rights):
return handler(self, story_id, **kwargs)
else:
raise self.UnauthorizedUserException(
'You do not have credentials to delete this story.')
return test_can_delete_story
def can_delete_topic(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can delete a topic.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now also
checks if the user can delete a given topic.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_delete_topic(
self: _SelfBaseHandlerType, topic_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks whether the user can delete a given topic.
Args:
topic_id: str. The topic id.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have
enough rights to delete a given topic.
"""
if not self.user_id:
raise self.NotLoggedInException
try:
topic_domain.Topic.require_valid_topic_id(topic_id)
except utils.ValidationError as e:
raise self.NotFoundException(e)
user_actions_info = user_services.get_user_actions_info(self.user_id)
if role_services.ACTION_DELETE_TOPIC in user_actions_info.actions:
return handler(self, topic_id, **kwargs)
else:
raise self.UnauthorizedUserException(
'%s does not have enough rights to delete the'
' topic.' % self.user_id)
return test_can_delete_topic
def can_create_topic(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can create a topic.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that also checks
if the user can create a topic.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_create_topic(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks whether the user can create a topic.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have
enough rights to create a topic.
"""
if not self.user_id:
raise self.NotLoggedInException
user_actions_info = user_services.get_user_actions_info(self.user_id)
if role_services.ACTION_CREATE_NEW_TOPIC in user_actions_info.actions:
return handler(self, **kwargs)
else:
raise self.UnauthorizedUserException(
'%s does not have enough rights to create a'
' topic.' % self.user_id)
return test_can_create_topic
def can_access_topics_and_skills_dashboard(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can access the topics and skills
dashboard.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that also checks if
the user can access the topics and skills dashboard.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_access_topics_and_skills_dashboard(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks whether the user can access the topics and skills
dashboard.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have
enough rights to access the topics and skills
dashboard.
"""
if not self.user_id:
raise self.NotLoggedInException
user_actions_info = user_services.get_user_actions_info(self.user_id)
if (
role_services.ACTION_ACCESS_TOPICS_AND_SKILLS_DASHBOARD in
user_actions_info.actions):
return handler(self, **kwargs)
else:
raise self.UnauthorizedUserException(
'%s does not have enough rights to access the topics and skills'
' dashboard.' % self.user_id)
return test_can_access_topics_and_skills_dashboard
def can_view_any_topic_editor(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can view any topic editor.
Args:
handler: function. The newly decorated function.
Returns:
function. The newly decorated function that also checks
if the user can view any topic editor.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_view_any_topic_editor(
self: _SelfBaseHandlerType, topic_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks whether the user can view any topic editor.
Args:
topic_id: str. The topic id.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have
enough rights to view any topic editor.
"""
if not self.user_id:
raise self.NotLoggedInException
try:
topic_domain.Topic.require_valid_topic_id(topic_id)
except utils.ValidationError as e:
raise self.NotFoundException(e)
user_actions_info = user_services.get_user_actions_info(self.user_id)
if (
role_services.ACTION_VISIT_ANY_TOPIC_EDITOR_PAGE in
user_actions_info.actions):
return handler(self, topic_id, **kwargs)
else:
raise self.UnauthorizedUserException(
'%s does not have enough rights to view any topic editor.'
% self.user_id)
return test_can_view_any_topic_editor
def can_manage_rights_for_topic(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can manage a topic's rights.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that also checks
if the user can manage a given topic's rights.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_manage_topic_rights(
self: _SelfBaseHandlerType, topic_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks whether the user can manage a topic's rights.
Args:
topic_id: str. The topic id.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have
enough rights to assign roles for a given topic.
"""
if not self.user_id:
raise self.NotLoggedInException
user_actions_info = user_services.get_user_actions_info(self.user_id)
if (
role_services.ACTION_MANAGE_TOPIC_RIGHTS in
user_actions_info.actions):
return handler(self, topic_id, **kwargs)
else:
raise self.UnauthorizedUserException(
'%s does not have enough rights to assign roles for the '
'topic.' % self.user_id)
return test_can_manage_topic_rights
def can_access_classroom_admin_page(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether user can access classroom admin page.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now checks if the user has
permission to access the classroom admin page.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_access_classroom_admin_page(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user is logged in and can access classroom admin page.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have credentials to
access the classroom admin page.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
if (
role_services.ACTION_ACCESS_CLASSROOM_ADMIN_PAGE in
self.user.actions
):
return handler(self, **kwargs)
raise self.UnauthorizedUserException(
'You do not have credentials to access classroom admin page.')
return test_can_access_classroom_admin_page
def can_access_voiceover_admin_page(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether user can access voiceover admin page.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now checks if the user has
permission to access the voiceover admin page.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_access_voiceover_admin_page(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user is logged in and can access voiceover admin page.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have credentials to
access the voiceover admin page.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
if (
role_services.ACTION_ACCESS_VOICEOVER_ADMIN_PAGE in
self.user.actions
):
return handler(self, **kwargs)
raise self.UnauthorizedUserException(
'You do not have credentials to access voiceover admin page.')
return test_can_access_voiceover_admin_page
def can_change_topic_publication_status(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the user can publish or unpublish a topic.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now checks
if the user can publish or unpublish a topic.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_change_topic_publication_status(
self: _SelfBaseHandlerType, topic_id: str, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks whether the user can can publish or unpublish a topic.
Args:
topic_id: str. The topic id.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have
enough rights to publish or unpublish the topic..
"""
if not self.user_id:
raise self.NotLoggedInException
try:
topic_domain.Topic.require_valid_topic_id(topic_id)
except utils.ValidationError as e:
raise self.NotFoundException(e)
user_actions_info = user_services.get_user_actions_info(self.user_id)
if (
role_services.ACTION_CHANGE_TOPIC_STATUS in
user_actions_info.actions):
return handler(self, topic_id, **kwargs)
else:
raise self.UnauthorizedUserException(
'%s does not have enough rights to publish or unpublish the '
'topic.' % self.user_id)
return test_can_change_topic_publication_status
def can_access_topic_viewer_page(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., Optional[_GenericHandlerFunctionReturnType]]:
"""Decorator to check whether user can access topic viewer page.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now checks
if the user can access the given topic viewer page.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_access(
self: _SelfBaseHandlerType,
classroom_url_fragment: str,
topic_url_fragment: str,
**kwargs: Any
) -> Optional[_GenericHandlerFunctionReturnType]:
"""Checks if the user can access topic viewer page.
Args:
topic_url_fragment: str. The url fragment of the topic.
classroom_url_fragment: str. The classroom url fragment.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotFoundException. The given page cannot be found.
EntityNotFoundError. The TopicRights with ID topic_id was not
found in the datastore.
"""
if topic_url_fragment != topic_url_fragment.lower():
_redirect_based_on_return_type(
self, '/learn/%s/%s' % (
classroom_url_fragment,
topic_url_fragment.lower()),
self.GET_HANDLER_ERROR_RETURN_TYPE)
return None
topic = topic_fetchers.get_topic_by_url_fragment(
topic_url_fragment)
if topic is None:
_redirect_based_on_return_type(
self, '/learn/%s' % classroom_url_fragment,
self.GET_HANDLER_ERROR_RETURN_TYPE)
return None
verified_classroom_url_fragment = (
classroom_config_services.get_classroom_url_fragment_for_topic_id(
topic.id))
if classroom_url_fragment != verified_classroom_url_fragment:
url_substring = topic_url_fragment
_redirect_based_on_return_type(
self, '/learn/%s/%s' % (
verified_classroom_url_fragment,
url_substring),
self.GET_HANDLER_ERROR_RETURN_TYPE)
return None
topic_id = topic.id
topic_rights = topic_fetchers.get_topic_rights(
topic_id, strict=True)
user_actions_info = user_services.get_user_actions_info(self.user_id)
if (
topic_rights.topic_is_published or
role_services.ACTION_VISIT_ANY_TOPIC_EDITOR_PAGE in
user_actions_info.actions):
return handler(self, topic.name, **kwargs)
else:
raise self.NotFoundException
return test_can_access
def can_access_story_viewer_page(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., Optional[_GenericHandlerFunctionReturnType]]:
"""Decorator to check whether user can access story viewer page.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now checks
if the user can access the given story viewer page.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_access(
self: _SelfBaseHandlerType,
classroom_url_fragment: str,
topic_url_fragment: str,
story_url_fragment: str,
*args: Any,
**kwargs: Any
) -> Optional[_GenericHandlerFunctionReturnType]:
"""Checks if the user can access story viewer page.
Args:
classroom_url_fragment: str. The classroom url fragment.
topic_url_fragment: str. The url fragment of the topic
associated with the story.
story_url_fragment: str. The story url fragment.
*args: list(*). A list of arguments from the calling function.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotFoundException. The given page cannot be found.
"""
if story_url_fragment != story_url_fragment.lower():
_redirect_based_on_return_type(
self, '/learn/%s/%s/story/%s' % (
classroom_url_fragment,
topic_url_fragment,
story_url_fragment.lower()),
self.GET_HANDLER_ERROR_RETURN_TYPE)
return None
story = story_fetchers.get_story_by_url_fragment(story_url_fragment)
if story is None:
_redirect_based_on_return_type(
self,
'/learn/%s/%s/story' %
(classroom_url_fragment, topic_url_fragment),
self.GET_HANDLER_ERROR_RETURN_TYPE)
return None
story_is_published = False
topic_is_published = False
topic_id = story.corresponding_topic_id
story_id = story.id
user_actions_info = user_services.get_user_actions_info(self.user_id)
if topic_id:
topic = topic_fetchers.get_topic_by_id(topic_id)
if topic.url_fragment != topic_url_fragment:
_redirect_based_on_return_type(
self,
'/learn/%s/%s/story/%s' % (
classroom_url_fragment,
topic.url_fragment,
story_url_fragment),
self.GET_HANDLER_ERROR_RETURN_TYPE)
return None
verified_classroom_url_fragment = (
classroom_config_services
.get_classroom_url_fragment_for_topic_id(topic.id))
if classroom_url_fragment != verified_classroom_url_fragment:
url_substring = '%s/story/%s' % (
topic_url_fragment, story_url_fragment)
_redirect_based_on_return_type(
self, '/learn/%s/%s' % (
verified_classroom_url_fragment,
url_substring),
self.GET_HANDLER_ERROR_RETURN_TYPE)
return None
topic_rights = topic_fetchers.get_topic_rights(topic_id)
topic_is_published = topic_rights.topic_is_published
all_story_references = topic.get_all_story_references()
for reference in all_story_references:
if reference.story_id == story_id:
story_is_published = reference.story_is_published
if (
(story_is_published and topic_is_published) or
role_services.ACTION_VISIT_ANY_TOPIC_EDITOR_PAGE in
user_actions_info.actions):
return handler(self, story_id, *args, **kwargs)
else:
raise self.NotFoundException
return test_can_access
def can_access_story_viewer_page_as_logged_in_user(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., Optional[_GenericHandlerFunctionReturnType]]:
"""Decorator to check whether the user can access story viewer page
if the user is logged in.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now checks
if the user can access the given story viewer page if the
user is logged in.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_access(
self: _SelfBaseHandlerType,
classroom_url_fragment: str,
topic_url_fragment: str,
story_url_fragment: str,
*args: Any,
**kwargs: Any
) -> Optional[_GenericHandlerFunctionReturnType]:
"""Checks if the user can access story viewer page.
Args:
classroom_url_fragment: str. The classroom url fragment.
topic_url_fragment: str. The url fragment of the topic
associated with the story.
story_url_fragment: str. The story url fragment.
*args: list(*). A list of arguments from the calling function.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
NotFoundException. The given page cannot be found.
"""
if self.user_id is None:
raise self.NotLoggedInException
if story_url_fragment != story_url_fragment.lower():
_redirect_based_on_return_type(
self, '/learn/%s/%s/story/%s' % (
classroom_url_fragment,
topic_url_fragment,
story_url_fragment.lower()),
self.GET_HANDLER_ERROR_RETURN_TYPE)
return None
story = story_fetchers.get_story_by_url_fragment(story_url_fragment)
if story is None:
_redirect_based_on_return_type(
self,
'/learn/%s/%s/story' %
(classroom_url_fragment, topic_url_fragment),
self.GET_HANDLER_ERROR_RETURN_TYPE)
return None
story_is_published = False
topic_is_published = False
topic_id = story.corresponding_topic_id
story_id = story.id
user_actions_info = user_services.get_user_actions_info(self.user_id)
if topic_id:
topic = topic_fetchers.get_topic_by_id(topic_id)
if topic.url_fragment != topic_url_fragment:
_redirect_based_on_return_type(
self,
'/learn/%s/%s/story/%s' % (
classroom_url_fragment,
topic.url_fragment,
story_url_fragment),
self.GET_HANDLER_ERROR_RETURN_TYPE)
return None
verified_classroom_url_fragment = (
classroom_config_services
.get_classroom_url_fragment_for_topic_id(topic.id))
if classroom_url_fragment != verified_classroom_url_fragment:
url_substring = '%s/story/%s' % (
topic_url_fragment, story_url_fragment)
_redirect_based_on_return_type(
self, '/learn/%s/%s' % (
verified_classroom_url_fragment,
url_substring),
self.GET_HANDLER_ERROR_RETURN_TYPE)
return None
topic_rights = topic_fetchers.get_topic_rights(topic_id)
topic_is_published = topic_rights.topic_is_published
all_story_references = topic.get_all_story_references()
for reference in all_story_references:
if reference.story_id == story_id:
story_is_published = reference.story_is_published
if (
(story_is_published and topic_is_published) or
role_services.ACTION_VISIT_ANY_TOPIC_EDITOR_PAGE in
user_actions_info.actions
):
return handler(self, story_id, *args, **kwargs)
else:
raise self.NotFoundException
return test_can_access
def can_access_subtopic_viewer_page(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., Optional[_GenericHandlerFunctionReturnType]]:
"""Decorator to check whether user can access subtopic page viewer.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now checks
if the user can access the given subtopic viewer page.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_access( # pylint: disable=too-many-return-statements
self: _SelfBaseHandlerType,
classroom_url_fragment: str,
topic_url_fragment: str,
subtopic_url_fragment: str,
**kwargs: Any
) -> Optional[_GenericHandlerFunctionReturnType]:
"""Checks if the user can access subtopic viewer page.
Args:
classroom_url_fragment: str. The classroom url fragment.
topic_url_fragment: str. The url fragment of the topic
associated with the subtopic.
subtopic_url_fragment: str. The url fragment of the Subtopic.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of decorated function.
Raises:
NotFoundException. The given page cannot be found.
"""
if subtopic_url_fragment != subtopic_url_fragment.lower():
_redirect_based_on_return_type(
self, '/learn/%s/%s/revision/%s' % (
classroom_url_fragment,
topic_url_fragment,
subtopic_url_fragment.lower()),
self.GET_HANDLER_ERROR_RETURN_TYPE)
return None
topic = topic_fetchers.get_topic_by_url_fragment(topic_url_fragment)
subtopic_id = None
if topic is None:
_redirect_based_on_return_type(
self, '/learn/%s' % classroom_url_fragment,
self.GET_HANDLER_ERROR_RETURN_TYPE)
return None
user_actions_info = user_services.get_user_actions_info(self.user_id)
topic_rights = topic_fetchers.get_topic_rights(topic.id)
if (
(topic_rights is None or not topic_rights.topic_is_published)
and role_services.ACTION_VISIT_ANY_TOPIC_EDITOR_PAGE not in
user_actions_info.actions):
_redirect_based_on_return_type(
self, '/learn/%s' % classroom_url_fragment,
self.GET_HANDLER_ERROR_RETURN_TYPE)
return None
for subtopic in topic.subtopics:
if subtopic.url_fragment == subtopic_url_fragment:
subtopic_id = subtopic.id
if not subtopic_id:
_redirect_based_on_return_type(
self,
'/learn/%s/%s/revision' %
(classroom_url_fragment, topic_url_fragment),
self.GET_HANDLER_ERROR_RETURN_TYPE)
return None
verified_classroom_url_fragment = (
classroom_config_services.get_classroom_url_fragment_for_topic_id(
topic.id))
if classroom_url_fragment != verified_classroom_url_fragment:
url_substring = '%s/revision/%s' % (
topic_url_fragment, subtopic_url_fragment)
_redirect_based_on_return_type(
self, '/learn/%s/%s' % (
verified_classroom_url_fragment,
url_substring),
self.GET_HANDLER_ERROR_RETURN_TYPE)
return None
subtopic_page = subtopic_page_services.get_subtopic_page_by_id(
topic.id, subtopic_id, strict=False)
if subtopic_page is None:
_redirect_based_on_return_type(
self,
'/learn/%s/%s/revision' % (
classroom_url_fragment, topic_url_fragment),
self.GET_HANDLER_ERROR_RETURN_TYPE)
return None
else:
return handler(self, topic.name, subtopic_id, **kwargs)
return test_can_access
def get_decorator_for_accepting_suggestion(
decorator: Callable[[Callable[..., None]], Callable[..., None]]
) -> Callable[[Callable[..., None]], Callable[..., None]]:
"""Function that takes a decorator as an argument and then applies some
common checks and then checks the permissions specified by the passed in
decorator.
Args:
decorator: function. The decorator to be used to verify permissions
for accepting/rejecting suggestions.
Returns:
function. The new decorator which includes all the permission checks for
accepting/rejecting suggestions. These permissions include:
- Admins can accept/reject any suggestion.
- Users with scores above threshold can accept/reject any suggestion
in that category.
- Any user with edit permissions to the target entity can
accept/reject suggestions for that entity.
"""
def generate_decorator_for_handler(
handler: Callable[..., None]
) -> Callable[..., None]:
"""Function that generates a decorator for a given handler.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that has common checks and
permissions specified by passed in decorator.
Raises:
NotLoggedInException. The user is not logged in.
"""
# Here we use type Any because this method can accept arbitrary number
# of arguments with different types.
@functools.wraps(handler)
def test_can_accept_suggestion(
self: _SelfBaseHandlerType,
target_id: str,
suggestion_id: str,
**kwargs: Any
) -> None:
"""Returns a (possibly-decorated) handler to test whether a
suggestion can be accepted based on the user actions and roles.
Args:
target_id: str. The target id.
suggestion_id: str. The suggestion id.
**kwargs: *. Keyword arguments.
Returns:
function. The (possibly-decorated) handler for accepting a
suggestion.
Raises:
NotLoggedInException. The user is not logged in.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
user_actions = user_services.get_user_actions_info(
self.user_id
).actions
if role_services.ACTION_ACCEPT_ANY_SUGGESTION in user_actions:
return handler(self, target_id, suggestion_id, **kwargs)
if len(suggestion_id.split('.')) != 3:
raise self.InvalidInputException(
'Invalid format for suggestion_id.'
' It must contain 3 parts separated by \'.\'')
suggestion = suggestion_services.get_suggestion_by_id(
suggestion_id, strict=False
)
if suggestion is None:
raise self.NotFoundException
# TODO(#6671): Currently, the can_user_review_category is
# not in use as the suggestion scoring system is not enabled.
# Remove this check once the new scoring structure gets implemented.
if suggestion_services.can_user_review_category(
self.user_id, suggestion.score_category):
return handler(self, target_id, suggestion_id, **kwargs)
if suggestion.suggestion_type == (
feconf.SUGGESTION_TYPE_TRANSLATE_CONTENT):
if user_services.can_review_translation_suggestions(
self.user_id,
language_code=suggestion.change_cmd.language_code):
return handler(self, target_id, suggestion_id, **kwargs)
elif suggestion.suggestion_type == (
feconf.SUGGESTION_TYPE_ADD_QUESTION):
if user_services.can_review_question_suggestions(self.user_id):
return handler(self, target_id, suggestion_id, **kwargs)
return decorator(handler)(self, target_id, suggestion_id, **kwargs)
return test_can_accept_suggestion
return generate_decorator_for_handler
def can_view_reviewable_suggestions(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., Optional[_GenericHandlerFunctionReturnType]]:
"""Decorator to check whether user can view the list of suggestions that
they are allowed to review.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now checks
if the user can view reviewable suggestions.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_view_reviewable_suggestions(
self: _SelfBaseHandlerType,
target_type: str,
suggestion_type: str,
**kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks whether the user can view reviewable suggestions.
Args:
target_type: str. The entity type of the target of the suggestion.
suggestion_type: str. The type of the suggestion.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotFoundException. The given page cannot be found.
Exception. User is not allowed to review translation suggestions.
Exception. User is not allowed to review question suggestions.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
if suggestion_type == (
feconf.SUGGESTION_TYPE_TRANSLATE_CONTENT):
if user_services.can_review_translation_suggestions(self.user_id):
return handler(self, target_type, suggestion_type, **kwargs)
else:
raise Exception(
'User with user_id: %s is not allowed to review '
'translation suggestions.' % self.user_id
)
elif suggestion_type == (
feconf.SUGGESTION_TYPE_ADD_QUESTION):
if user_services.can_review_question_suggestions(self.user_id):
return handler(self, target_type, suggestion_type, **kwargs)
else:
raise Exception(
'User with user_id: %s is not allowed to review question '
'suggestions.' % self.user_id
)
else:
raise self.NotFoundException
return test_can_view_reviewable_suggestions
def can_edit_entity(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether user can edit entity.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now checks
if the user can edit the entity.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_edit_entity(
self: _SelfBaseHandlerType,
entity_type: str,
entity_id: str,
**kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can edit entity.
Args:
entity_type: str. The type of entity i.e. exploration, question etc.
entity_id: str. The ID of the entity.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotFoundException. The given page cannot be found.
"""
arg_swapped_handler = lambda x, y, z: handler(y, x, z)
# This swaps the first two arguments (self and entity_type), so
# that functools.partial can then be applied to the leftmost one to
# create a modified handler function that has the correct signature
# for the corresponding decorators.
reduced_handler = functools.partial(
arg_swapped_handler, entity_type)
functions: (
Dict[str, Callable[[str], _GenericHandlerFunctionReturnType]]
) = {
feconf.ENTITY_TYPE_EXPLORATION: lambda entity_id: (
can_edit_exploration(reduced_handler)(
self, entity_id, **kwargs)),
feconf.ENTITY_TYPE_QUESTION: lambda entity_id: (
can_edit_question(reduced_handler)(
self, entity_id, **kwargs)),
feconf.ENTITY_TYPE_TOPIC: lambda entity_id: (
can_edit_topic(reduced_handler)(
self, entity_id, **kwargs)),
feconf.ENTITY_TYPE_SKILL: lambda entity_id: (
can_edit_skill(reduced_handler)(
self, entity_id, **kwargs)),
feconf.IMAGE_CONTEXT_QUESTION_SUGGESTIONS: lambda entity_id: (
can_submit_images_to_questions(reduced_handler)(
self, entity_id, **kwargs)),
feconf.IMAGE_CONTEXT_EXPLORATION_SUGGESTIONS: lambda entity_id: (
can_submit_images_to_explorations(reduced_handler)(
self, entity_id, **kwargs)),
feconf.ENTITY_TYPE_STORY: lambda entity_id: (
can_edit_story(reduced_handler)(
self, entity_id, **kwargs)),
feconf.ENTITY_TYPE_BLOG_POST: lambda entity_id: (
can_edit_blog_post(reduced_handler)(
self, entity_id, **kwargs))
}
if entity_type not in dict.keys(functions):
raise self.NotFoundException
return functions[entity_type](entity_id)
return test_can_edit_entity
def can_play_entity(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether user can play entity.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now checks
if the user can play the entity.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_play_entity(
self: _SelfBaseHandlerType,
entity_type: str,
entity_id: str,
**kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user can play entity.
Args:
entity_type: str. The type of entity i.e. exploration, question etc.
entity_id: str. The ID of the entity.
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotFoundException. The given page cannot be found.
"""
arg_swapped_handler = lambda x, y, z: handler(y, x, z)
if entity_type == feconf.ENTITY_TYPE_EXPLORATION:
# This swaps the first two arguments (self and entity_type), so
# that functools.partial can then be applied to the leftmost one to
# create a modified handler function that has the correct signature
# for can_edit_question().
reduced_handler = functools.partial(
arg_swapped_handler, feconf.ENTITY_TYPE_EXPLORATION)
# This raises an error if the question checks fail.
return can_play_exploration(reduced_handler)(
self, entity_id, **kwargs)
elif entity_type == feconf.ENTITY_TYPE_QUESTION:
reduced_handler = functools.partial(
arg_swapped_handler, feconf.ENTITY_TYPE_QUESTION)
return can_play_question(reduced_handler)(
self, entity_id, **kwargs)
else:
raise self.NotFoundException
return test_can_play_entity
def is_from_oppia_ml(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the incoming request is from a valid Oppia-ML
VM instance.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now can check if incoming
request is from a valid VM instance.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_request_originates_from_valid_oppia_ml_instance(
self: base.OppiaMLVMHandler[Dict[str, str], Dict[str, str]],
**kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the incoming request is from a valid Oppia-ML VM
instance.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
UnauthorizedUserException. If incoming request is not from a valid
Oppia-ML VM instance.
"""
oppia_ml_auth_info = (
self.extract_request_message_vm_id_and_signature())
if (oppia_ml_auth_info.vm_id == feconf.DEFAULT_VM_ID and
not constants.DEV_MODE):
raise self.UnauthorizedUserException
if not classifier_services.verify_signature(oppia_ml_auth_info):
raise self.UnauthorizedUserException
return handler(self, **kwargs)
return test_request_originates_from_valid_oppia_ml_instance
def can_update_suggestion(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the current user can update suggestions.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now checks
if the user can update a given suggestion.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have credentials to
edit this suggestion.
InvalidInputException. The submitted suggestion id is not valid.
NotFoundException. A suggestion is not found with the given
suggestion id.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_update_suggestion(
self: _SelfBaseHandlerType,
suggestion_id: str,
**kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Returns a handler to test whether a suggestion can be updated based
on the user's roles.
Args:
suggestion_id: str. The suggestion id.
**kwargs: *. Keyword arguments.
Returns:
function. The handler for updating a suggestion.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have credentials to
edit this suggestion.
InvalidInputException. The submitted suggestion id is not valid.
NotFoundException. A suggestion is not found with the given
suggestion id.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
user_actions = self.user.actions
if len(suggestion_id.split('.')) != 3:
raise self.InvalidInputException(
'Invalid format for suggestion_id.' +
' It must contain 3 parts separated by \'.\'')
suggestion = suggestion_services.get_suggestion_by_id(
suggestion_id, strict=False
)
if suggestion is None:
raise self.NotFoundException
if role_services.ACTION_ACCEPT_ANY_SUGGESTION in user_actions:
return handler(self, suggestion_id, **kwargs)
if suggestion.author_id == self.user_id:
raise base.UserFacingExceptions.UnauthorizedUserException(
'The user, %s is not allowed to update self-created'
'suggestions.' % (user_services.get_username(self.user_id)))
if suggestion.suggestion_type not in (
feconf.CONTRIBUTOR_DASHBOARD_SUGGESTION_TYPES):
raise self.InvalidInputException('Invalid suggestion type.')
if suggestion.suggestion_type == (
feconf.SUGGESTION_TYPE_TRANSLATE_CONTENT):
if user_services.can_review_translation_suggestions(
self.user_id,
language_code=suggestion.change_cmd.language_code):
return handler(self, suggestion_id, **kwargs)
elif suggestion.suggestion_type == (
feconf.SUGGESTION_TYPE_ADD_QUESTION):
if user_services.can_review_question_suggestions(self.user_id):
return handler(self, suggestion_id, **kwargs)
raise base.UserFacingExceptions.UnauthorizedUserException(
'You are not allowed to update the suggestion.')
return test_can_update_suggestion
def can_fetch_contributor_dashboard_stats(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the current user can fetch contributor
dashboard stats.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now checks
if the user can fetch stats.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have credentials to
fetch stats for the given username.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_fetch_contributor_dashboard_stats(
self: _SelfBaseHandlerType,
contribution_type: str,
contribution_subtype: str,
username: str,
**kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Returns a handler to test whether stats can be fetched based
on the logged in user.
Args:
contribution_type: str. The type of the contribution that the stats
are requested.
contribution_subtype: str. The subtype of the contribution that the
stats are requested.
username: str. The provided username.
**kwargs: *. Keyword arguments.
Returns:
function. The handler for fetching stats.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have credentials to
fetch stats for the given username.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
if user_services.get_username(self.user_id) != username:
raise base.UserFacingExceptions.UnauthorizedUserException(
'The user %s is not allowed to fetch the stats of other '
'users.' % (user_services.get_username(self.user_id)))
return handler(
self, contribution_type, contribution_subtype, username, **kwargs)
return test_can_fetch_contributor_dashboard_stats
def can_fetch_all_contributor_dashboard_stats(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the current user can fetch contributor
dashboard stats.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now checks
if the user can fetch stats.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have credentials to
fetch stats for the given username.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_fetch_all_contributor_dashboard_stats(
self: _SelfBaseHandlerType,
username: str,
**kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Returns a handler to test whether stats can be fetched based
on the logged in user.
Args:
username: str. The provided username.
**kwargs: *. Keyword arguments.
Returns:
function. The handler for fetching stats.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have credentials to
fetch stats for the given username.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException
if user_services.get_username(self.user_id) != username:
raise base.UserFacingExceptions.UnauthorizedUserException(
'The user %s is not allowed to fetch the stats of other '
'users.' % (user_services.get_username(self.user_id)))
return handler(self, username, **kwargs)
return test_can_fetch_all_contributor_dashboard_stats
def is_from_oppia_android(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the request was sent from Oppia Android.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_is_from_oppia_android(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks whether the request was sent from Oppia Android.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
UnauthorizedUserException. If incoming request is not from a valid
Oppia Android request.
"""
headers = self.request.headers
api_key = headers['api_key']
app_package_name = headers['app_package_name']
app_version_name = headers['app_version_name']
app_version_code = headers['app_version_code']
version_name_matches = (
android_validation_constants.APP_VERSION_WITH_HASH_REGEXP.match(
app_version_name))
version_code_is_positive_int = app_version_code.isdigit() and (
int(app_version_code) > 0)
if (
api_key != android_validation_constants.ANDROID_API_KEY or
app_package_name != (
android_validation_constants.ANDROID_APP_PACKAGE_NAME) or
not version_name_matches or
not version_code_is_positive_int):
raise self.UnauthorizedUserException(
'The incoming request is not a valid Oppia Android request.')
return handler(self, **kwargs)
return test_is_from_oppia_android
def is_from_oppia_android_build(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether the request was sent from Oppia Android build
process.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function.
"""
# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_is_from_oppia_android_build(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks whether the request was sent from Oppia Android build process.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
Exception. If the secret API key is not set.
UnauthorizedUserException. If incoming request is not from a valid
Oppia Android build request.
"""
if (
self.request.headers.get('X-ApiKey') is None or
not android_services.verify_android_build_secret(
self.request.headers['X-ApiKey']
)
):
raise self.UnauthorizedUserException(
'The incoming request is not a valid '
'Oppia Android build request.'
)
return handler(self, **kwargs)
return test_is_from_oppia_android_build