Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1314e2c
hide intercom widget from beta_tester users
OmarIthawi Mar 2, 2020
bfeb8c6
Merge pull request #537 from appsembler/omar/hide-intercom-for-beta
OmarIthawi Mar 5, 2020
4774de8
Fix unregistered celery task: course_structures
OmarIthawi Mar 11, 2020
80a4e1e
Merge pull request #541 from appsembler/omar/course-task
OmarIthawi Mar 12, 2020
a4db057
Django command to create devstack demo site
OmarIthawi Mar 11, 2020
b0ca4aa
Merge pull request #540 from appsembler/omar/demo-site
OmarIthawi Mar 16, 2020
a59c348
Move access control backends into courseware
OmarIthawi Mar 16, 2020
04e9f59
Merge pull request #543 from appsembler/omar/move-acb-courseware
OmarIthawi Mar 17, 2020
2cf9d68
Disable mongo debug pannel on django debug toolbar
melvinsoft Mar 19, 2020
a8853dd
Working delete site function
Jul 9, 2019
ba7aa44
Update lms/envs/devstack.py
melvinsoft Mar 19, 2020
8c81410
Merge pull request #545 from appsembler/maxi/disable-mongo-debug-panel
melvinsoft Mar 19, 2020
80f7a84
upgrade django-storages to 1.6.5
thraxil Mar 24, 2020
a9e3b3f
default to AWS v4 sigs
thraxil Mar 24, 2020
68fb350
Remove Site management command
iamjazzar Mar 19, 2020
9fc3ebc
Merge pull request #423 from appsembler/jazzar/offboard-orgs
iamjazzar Mar 24, 2020
3311d09
use default storage setting in site creation process
melvinsoft Mar 25, 2020
a0975ec
Merge pull request #549 from appsembler/maxi/use-boto3-on-site-creation
melvinsoft Mar 25, 2020
50927ef
update to boto3 in cms
melvinsoft Mar 25, 2020
855bd00
Merge pull request #550 from appsembler/maxi/more-boto3-updates
melvinsoft Mar 25, 2020
a9ae4c5
removed obsolete parameter in boto3
melvinsoft Mar 25, 2020
65db8a5
remove unused vars
melvinsoft Mar 25, 2020
d36a9de
Merge pull request #552 from appsembler/maxi/fix-course-export
melvinsoft Mar 26, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cms/djangoapps/contentstore/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@

from django.conf import settings
from django.core.files.storage import get_storage_class
from storages.backends.s3boto import S3BotoStorage
from storages.backends.s3boto3 import S3Boto3Storage
from storages.utils import setting


class ImportExportS3Storage(S3BotoStorage): # pylint: disable=abstract-method
class ImportExportS3Storage(S3Boto3Storage): # pylint: disable=abstract-method
"""
S3 backend for course import and export OLX files.
"""
Expand Down
12 changes: 3 additions & 9 deletions cms/djangoapps/contentstore/views/import_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from opaque_keys.edx.locator import LibraryLocator
from path import Path as path
from six import text_type
from storages.backends.s3boto import S3BotoStorage
from storages.backends.s3boto3 import S3Boto3Storage
from user_tasks.conf import settings as user_tasks_settings
from user_tasks.models import UserTaskArtifact, UserTaskStatus
from wsgiref.util import FileWrapper
Expand Down Expand Up @@ -373,14 +373,8 @@ def export_status_handler(request, course_key_string):
artifact = UserTaskArtifact.objects.get(status=task_status, name='Output')
if isinstance(artifact.file.storage, FileSystemStorage):
output_url = reverse_course_url('export_output_handler', course_key)
elif isinstance(artifact.file.storage, S3BotoStorage):
filename = os.path.basename(artifact.file.name).encode('utf-8')
disposition = 'attachment; filename="{}"'.format(filename)
output_url = artifact.file.storage.url(artifact.file.name, response_headers={
'response-content-disposition': disposition,
'response-content-encoding': 'application/octet-stream',
'response-content-type': 'application/x-tgz'
})
elif isinstance(artifact.file.storage, S3Boto3Storage):
output_url = artifact.file.storage.url(artifact.file.name)
else:
output_url = artifact.file.storage.url(artifact.file.name)
elif task_status.state in (UserTaskStatus.FAILED, UserTaskStatus.CANCELED):
Expand Down
3 changes: 3 additions & 0 deletions cms/envs/aws.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
from path import Path as path
from xmodule.modulestore.modulestore_settings import convert_module_store_setting_if_needed

# force S3 v4 (temporary until we can upgrade to django-storages 1.9)
S3_USE_SIGV4 = True

# SERVICE_VARIANT specifies name of the variant used, which decides what JSON
# configuration files are read during startup.
SERVICE_VARIANT = os.environ.get('SERVICE_VARIANT', None)
Expand Down
3 changes: 3 additions & 0 deletions cms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -886,6 +886,9 @@
'cms.djangoapps.contentstore.tasks',
'openedx.core.djangoapps.bookmarks.tasks',
'openedx.core.djangoapps.ccxcon.tasks',

# TODO: Remove after Hawthorn because the `course_structures` app will no longer exist.
'openedx.core.djangoapps.content.course_structures.tasks',
)

# Message configuration
Expand Down
2 changes: 1 addition & 1 deletion lms/djangoapps/courseware/access.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
from courseware.masquerade import get_masquerade_role, is_masquerading_as_student
from lms.djangoapps.ccx.custom_exception import CCXLocatorValidationException
from lms.djangoapps.ccx.models import CustomCourseForEdX
from lms.lib.access_control_backends import access_control_backends
from lms.djangoapps.courseware.access_control_backends import access_control_backends
from mobile_api.models import IgnoreMobileAvailableFlagConfig
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.external_auth.models import ExternalAuthMap
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,9 @@ def query(self, action, user, resource, default_has_access):
:param action: currently supporting the course access actions in SUPPORTED_ACTIONS.
:param user: The User model object.
:param resource: The course/resource ID.
:param default_has_access: True/False What's the default Open edX access control.
:return: True/False whether the `user` can perform the `action` on the `resource` or not.
:param default_has_access: AccessResponse The default access response object by Open edX.
:return: AccessResponse: ACCESS_GRANTED or ACCESS_DENIED whether the
`user` can perform the `action` on the `resource` or not.
"""
if action not in self.SUPPORTED_ACTIONS:
raise NotImplementedError(self.UNSUPPORTED_ERROR_FMT(action=action))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
"""
Test cases for the pluggable access control system.
"""
import datetime
import ddt
from mock import patch, Mock
import pytest
import pytz

from django.conf import settings
from django.test.utils import override_settings
from django.test import TestCase
from mock import patch, Mock
from opaque_keys.edx.locator import CourseLocator

from lms.lib.access_control_backends import AccessControlBackends
from lms.djangoapps.courseware.access_utils import (
ACCESS_DENIED,
ACCESS_GRANTED,
)
import lms.djangoapps.courseware.access as access
from lms.djangoapps.courseware.access_control_backends import (
access_control_backends,
AccessControlBackends,
)
from student.tests.factories import CourseEnrollmentAllowedFactory, UserFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase


@ddt.ddt
Expand Down Expand Up @@ -93,7 +106,7 @@ def test_settings_with_options(self, mock_backend):
'NAME': 'lms.lib:see_in_catalog_backend',
}
})
@patch('lms.lib.access_control_backends.log')
@patch('lms.djangoapps.courseware.access_control_backends.log')
def test_settings_with_missing_function(self, mock_log):
"""
Check that the system fails explicitly on a missing function.
Expand Down Expand Up @@ -159,11 +172,14 @@ def test_query_missing_backend(self, default_has_access):

@override_settings(ACCESS_CONTROL_BACKENDS={
'course.load': {
'NAME': 'lms.lib:load_backend',
'NAME': 'lms.djangoapps.courseware.access_control_backends:load_backend',
}
})
@patch('lms.lib.load_backend', Mock(side_effect=ArithmeticError('Dividing by zero!')), create=True)
@patch('lms.lib.access_control_backends.log')
@patch('lms.djangoapps.courseware.access_control_backends.load_backend', Mock(
side_effect=ArithmeticError('Dividing by zero!')),
create=True,
)
@patch('lms.djangoapps.courseware.access_control_backends.log')
def test_query_broken_backend(self, mock_log):
"""
Ensure a broken backend fails explicitly.
Expand All @@ -175,3 +191,38 @@ def test_query_broken_backend(self, mock_log):
mock_log.exception.assert_called_once_with(
'Something went wrong in querying the access control backend for `course.load`.'
)


@ddt.ddt
class AccessWithACLBackendsTestCase(ModuleStoreTestCase):
"""
Integration tests for `access._has_access_course`.
"""

def setUp(self):
"""
Set up tests environment.
"""
tomorrow = datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1)
self.user = UserFactory.create()
self.course = Mock(
enrollment_domain='',
enrollment_end=tomorrow,
enrollment_start=tomorrow,
id=CourseLocator('edX', 'test', '2012_Fall'),
)
CourseEnrollmentAllowedFactory(email=self.user.email, course_id=self.course.id)

def test_has_access_with_no_acl_backends(self):
"""
Ensure that the `access._has_access_course` queries the Access Control Backends.
"""
assert access._has_access_course(self.user, 'enroll', self.course).has_access

@ddt.data(ACCESS_GRANTED, ACCESS_DENIED)
def test_has_access_with_acl_backends(self, backend_access):
"""
Ensure that the `access._has_access_course` queries the Access Control Backends.
"""
with patch.object(access_control_backends, 'query', Mock(return_value=backend_access)):
assert access._has_access_course(self.user, 'enroll', self.course) == backend_access

This file was deleted.

3 changes: 3 additions & 0 deletions lms/envs/aws.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
from path import Path as path
from xmodule.modulestore.modulestore_settings import convert_module_store_setting_if_needed

# force S3 v4 (temporary until we can upgrade to django-storages 1.9)
S3_USE_SIGV4 = True

# SERVICE_VARIANT specifies name of the variant used, which decides what JSON
# configuration files are read during startup.
SERVICE_VARIANT = os.environ.get('SERVICE_VARIANT', None)
Expand Down
6 changes: 5 additions & 1 deletion lms/envs/devstack.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@
'debug_toolbar.panels.sql.SQLPanel',
'debug_toolbar.panels.signals.SignalsPanel',
'debug_toolbar.panels.logging.LoggingPanel',
'debug_toolbar_mongo.panel.MongoDebugPanel',

# Appsembler: MongoDebugPanel has been intentionally disabled by maxi@appsembler.com
# since it was breaking the mongo connections. Probably it's because we
# upgraded pymongo and DjangoDebugToolbar is some versions behind.

# ProfilingPanel has been intentionally removed for default devstack.py
# runtimes for performance reasons. If you wish to re-enable it in your
# local development environment, please create a new settings file
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from student.models import CourseAccessRole
from student.roles import CourseCreatorRole

from openedx.core.djangoapps.appsembler.intercom_integration.helpers import should_show_intercom_widget


def intercom(request):
data = {'show_intercom_widget': False}
Expand All @@ -15,10 +17,7 @@ def intercom(request):
return data

user = request.user
if user.is_authenticated() and (
user_has_role(user, CourseCreatorRole()) # Course authors, which is given by default for AMC site admins
or CourseAccessRole.objects.filter(user=user).exists() # Course staff, of any type
):
if should_show_intercom_widget(user):
data['show_intercom_widget'] = True
user_hash = hmac.new(
str(settings.INTERCOM_APP_SECRET),
Expand Down
31 changes: 31 additions & 0 deletions openedx/core/djangoapps/appsembler/intercom_integration/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""
Helpers for Intercom integration.
"""

from student.auth import user_has_role
from student.models import CourseAccessRole
from student.roles import CourseCreatorRole, CourseInstructorRole, CourseStaffRole


def should_show_intercom_widget(user):
"""
Show or hide the Intercom chat widget to different users.

The widget should be displayed only for course creators, course staff and course admins.

:param user: User object to check for.
:return: bool
"""
if not user.is_authenticated:
return False

if user.is_superuser:
return False

if user_has_role(user, CourseCreatorRole()):
return True

return CourseAccessRole.objects.filter(user=user, role__in=[
CourseStaffRole.ROLE,
CourseInstructorRole.ROLE,
]).exists()
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""
Tests for the intercom should_show_intercom_widget helper.
"""

from mock import patch, Mock
import ddt
from django.test import TestCase
from django.conf import settings
from opaque_keys.edx.keys import CourseKey
from student.roles import CourseCreatorRole, CourseInstructorRole, CourseStaffRole, CourseBetaTesterRole
from student.tests.factories import UserFactory

from openedx.core.djangoapps.appsembler.intercom_integration.helpers import should_show_intercom_widget


@ddt.ddt
@patch.dict(settings.FEATURES, ENABLE_CREATOR_GROUP=True)
class TestShouldShowIntercomWidgetHelperTestCase(TestCase):
"""
Tests for the `should_show_intercom_widget` helper.
"""

def test_should_hide_for_non_authenticated(self):
anonymous_user = Mock(is_authenticated=False)
assert not should_show_intercom_widget(anonymous_user)

def test_should_hide_for_superusers(self):
superuser = UserFactory.create(is_superuser=True)
assert not should_show_intercom_widget(superuser)

def test_should_show_for_site_wide_staff(self):
staff = UserFactory.create(is_staff=True)
assert should_show_intercom_widget(staff)

def test_should_show_for_course_creators(self):
course_creator = UserFactory.create()
CourseCreatorRole().add_users(course_creator)
assert should_show_intercom_widget(course_creator)

@ddt.unpack
@ddt.data({
'course_role_class': CourseStaffRole,
'should_show': True,
}, {
'course_role_class': CourseInstructorRole,
'should_show': True,
}, {
'course_role_class': CourseBetaTesterRole,
'should_show': False,
})
def test_should_show_for_course_staff(self, course_role_class, should_show):
course_staff = UserFactory.create(is_staff=False, is_superuser=False)
course_key = CourseKey.from_string('course-v1:Demo+Course+2017')
course_role_class(course_key).add_users(course_staff)
assert should_show_intercom_widget(course_staff) == should_show
2 changes: 1 addition & 1 deletion openedx/core/djangoapps/appsembler/sites/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def get_serializer_class(self):
return super(SiteConfigurationViewSet, self).get_serializer_class()

def perform_destroy(self, instance):
delete_site(instance)
delete_site(instance.site)


class FileUploadView(views.APIView):
Expand Down
Loading