Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Taxonomy import templates [FC-0036] #593

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
db25297
feat: receiver for verified exam event (#33390)
alangsto Oct 5, 2023
b4cb93e
feat: add grades event bus event handler for rejected special exam
MichaelRoytman Oct 3, 2023
2ae0738
feat: receiver for invalidate certificate (#33319)
ilee2u Oct 5, 2023
a313b3f
Merge pull request #33412 from openedx/MichaelRoytman/MST-2107-EXAM_A…
MichaelRoytman Oct 5, 2023
a59d7e5
chore: ORA bump to 5.5.4 (#33426)
jszewczulak Oct 5, 2023
29c83ca
feat: notifications and preferences will be created in batches (#33418)
muhammadadeeltajamul Oct 6, 2023
e3df004
feat: Upgrade Python dependency edx-enterprise
saleem-latif Oct 6, 2023
46776e9
chore: using `edx-proctoring-proctortrack` hash for django42 compatib…
awais786 Oct 6, 2023
9f16b0f
feat: handle exam submission and reset (#33323)
zacharis278 Oct 6, 2023
4d3ef54
fix: library ref mgmt cmd task params (#33427)
connorhaugh Oct 6, 2023
c376661
chore(i18n): update translations
Oct 8, 2023
8eb236d
Merge pull request #33430 from openedx/saleem-latif/upgrade-edx-enter…
saleem-latif Oct 9, 2023
b353019
chore: Adding condition to pick values in case of django42. (#33440)
awais786 Oct 9, 2023
c6f4ea7
feat: Upgrade Python dependency edx-enterprise
jajjibhai008 Oct 9, 2023
b617969
chore: Updating Python Requirements (#33452)
edx-requirements-bot Oct 10, 2023
427c98b
Merge pull request #33444 from openedx/jajjibhai008/upgrade-edx-enter…
jajjibhai008 Oct 10, 2023
1db6867
fix: replace py2neo with forked package (#33453)
UsamaSadiq Oct 10, 2023
565b34e
feat: allow oauth configuration per site and backend (#32656)
navinkarkera Oct 10, 2023
d6e21a1
feat: remove Taxonomy.required, squash taxonomy migrations (#33438)
bradenmacdonald Oct 10, 2023
6825702
feat: adds endpoint for downloading the taxonomy templates
pomegranited Oct 9, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,15 @@ def replace_all_library_source_blocks_ids(self, v1_to_v2_lib_map):
"""A method to replace 'source_library_id' in all relevant blocks."""

courses = CourseOverview.get_all_courses()
course_id_strings = [str(course.id) for course in courses]

# Use Celery to distribute the workload
tasks = group(
replace_all_library_source_blocks_ids_for_course.s(
course,
course_id_string,
v1_to_v2_lib_map
)
for course in courses
for course_id_string in course_id_strings
)
results = tasks.apply_async()

Expand All @@ -58,7 +59,8 @@ def replace_all_library_source_blocks_ids(self, v1_to_v2_lib_map):
def validate(self, v1_to_v2_lib_map):
""" Validate that replace_all_library_source_blocks_ids was successful"""
courses = CourseOverview.get_all_courses()
tasks = group(validate_all_library_source_blocks_ids_for_course.s(course, v1_to_v2_lib_map) for course in courses) # lint-amnesty, pylint: disable=line-too-long
course_id_strings = [str(course.id) for course in courses]
tasks = group(validate_all_library_source_blocks_ids_for_course.s(course_id, v1_to_v2_lib_map) for course_id in course_id_strings) # lint-amnesty, pylint: disable=line-too-long
results = tasks.apply_async()

validation = set()
Expand All @@ -80,9 +82,16 @@ def validate(self, v1_to_v2_lib_map):
def undo(self, v1_to_v2_lib_map):
""" undo the changes made by replace_all_library_source_blocks_ids"""
courses = CourseOverview.get_all_courses()
course_id_strings = [str(course.id) for course in courses]

# Use Celery to distribute the workload
tasks = group(undo_all_library_source_blocks_ids_for_course.s(course, v1_to_v2_lib_map) for course in courses)
tasks = group(
undo_all_library_source_blocks_ids_for_course.s(
course_id,
v1_to_v2_lib_map
)
for course_id in course_id_strings
)
results = tasks.apply_async()

for result in results.get():
Expand Down
26 changes: 15 additions & 11 deletions cms/djangoapps/contentstore/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -1006,23 +1006,24 @@ def delete_v1_library(v1_library_key_string):

@shared_task(time_limit=30)
@set_code_owner_attribute
def validate_all_library_source_blocks_ids_for_course(course, v1_to_v2_lib_map):
def validate_all_library_source_blocks_ids_for_course(course_key_string, v1_to_v2_lib_map):
"""Search a Modulestore for all library source blocks in a course by querying mongo.
replace all source_library_ids with the corresponding v2 value from the map
"""
course_id = CourseKey.from_string(course_key_string)
store = modulestore()
with store.bulk_operations(course.id):
with store.bulk_operations(course_id):
visited = []
for branch in [ModuleStoreEnum.BranchName.draft, ModuleStoreEnum.BranchName.published]:
blocks = store.get_items(
course.id.for_branch(branch),
course_id.for_branch(branch),
settings={'source_library_id': {'$exists': True}}
)
for xblock in blocks:
if xblock.source_library_id not in v1_to_v2_lib_map.values():
# lint-amnesty, pylint: disable=broad-except
raise Exception(
f'{xblock.source_library_id} in {course.id} is not found in mapping. Validation failed'
f'{xblock.source_library_id} in {course_id} is not found in mapping. Validation failed'
)
visited.append(xblock.source_library_id)
# return sucess
Expand All @@ -1031,18 +1032,20 @@ def validate_all_library_source_blocks_ids_for_course(course, v1_to_v2_lib_map):

@shared_task(time_limit=30)
@set_code_owner_attribute
def replace_all_library_source_blocks_ids_for_course(course, v1_to_v2_lib_map): # lint-amnesty, pylint: disable=useless-return
def replace_all_library_source_blocks_ids_for_course(course_key_string, v1_to_v2_lib_map): # lint-amnesty, pylint: disable=useless-return
"""Search a Modulestore for all library source blocks in a course by querying mongo.
replace all source_library_ids with the corresponding v2 value from the map.

This will trigger a publish on the course for every published library source block.
"""
store = modulestore()
with store.bulk_operations(course.id):
course_id = CourseKey.from_string(course_key_string)

with store.bulk_operations(course_id):
#for branch in [ModuleStoreEnum.BranchName.draft, ModuleStoreEnum.BranchName.published]:
draft_blocks, published_blocks = [
store.get_items(
course.id.for_branch(branch),
course_id.for_branch(branch),
settings={'source_library_id': {'$exists': True}}
)
for branch in [ModuleStoreEnum.BranchName.draft, ModuleStoreEnum.BranchName.published]
Expand All @@ -1058,7 +1061,7 @@ def replace_all_library_source_blocks_ids_for_course(course, v1_to_v2_lib_map):
LOGGER.error(
'Key %s not found in mapping. Skipping block for course %s',
str({draft_library_source_block.source_library_id}),
str(course.id)
str(course_id)
)
continue

Expand Down Expand Up @@ -1088,18 +1091,19 @@ def replace_all_library_source_blocks_ids_for_course(course, v1_to_v2_lib_map):

@shared_task(time_limit=30)
@set_code_owner_attribute
def undo_all_library_source_blocks_ids_for_course(course, v1_to_v2_lib_map): # lint-amnesty, pylint: disable=useless-return
def undo_all_library_source_blocks_ids_for_course(course_key_string, v1_to_v2_lib_map): # lint-amnesty, pylint: disable=useless-return
"""Search a Modulestore for all library source blocks in a course by querying mongo.
replace all source_library_ids with the corresponding v1 value from the inverted map.
This is exists to undo changes made previously.
"""
course_id = CourseKey.from_string(course_key_string)

v2_to_v1_lib_map = {v: k for k, v in v1_to_v2_lib_map.items()}

store = modulestore()
draft_blocks, published_blocks = [
store.get_items(
course.id.for_branch(branch),
course_id.for_branch(branch),
settings={'source_library_id': {'$exists': True}}
)
for branch in [ModuleStoreEnum.BranchName.draft, ModuleStoreEnum.BranchName.published]
Expand All @@ -1115,7 +1119,7 @@ def undo_all_library_source_blocks_ids_for_course(course, v1_to_v2_lib_map): #
LOGGER.error(
'Key %s not found in mapping. Skipping block for course %s',
str({draft_library_source_block.source_library_id}),
str(course.id)
str(course_id)
)
continue

Expand Down
1 change: 1 addition & 0 deletions cms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,7 @@
CROSS_DOMAIN_CSRF_COOKIE_DOMAIN = ''
CROSS_DOMAIN_CSRF_COOKIE_NAME = ''
CSRF_TRUSTED_ORIGINS = []
CSRF_TRUSTED_ORIGINS_WITH_SCHEME = []

#################### CAPA External Code Evaluation #############################
XQUEUE_WAITTIME_BETWEEN_REQUESTS = 5 # seconds
Expand Down
6 changes: 6 additions & 0 deletions cms/envs/production.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import yaml

from corsheaders.defaults import default_headers as corsheaders_default_headers
import django
from django.core.exceptions import ImproperlyConfigured
from django.urls import reverse_lazy
from edx_django_utils.plugins import add_plugins
Expand Down Expand Up @@ -236,6 +237,11 @@ def get_env_setting(setting):
# by end users.
CSRF_COOKIE_SECURE = ENV_TOKENS.get('CSRF_COOKIE_SECURE', False)

# values are already updated above with default CSRF_TRUSTED_ORIGINS values but in
# case of new django version these values will override.
if django.VERSION[0] >= 4: # for greater than django 3.2 use schemes.
CSRF_TRUSTED_ORIGINS = ENV_TOKENS.get('CSRF_TRUSTED_ORIGINS_WITH_SCHEME', [])

#Email overrides
MKTG_URL_LINK_MAP.update(ENV_TOKENS.get('MKTG_URL_LINK_MAP', {}))
MKTG_URL_OVERRIDES.update(ENV_TOKENS.get('MKTG_URL_OVERRIDES', MKTG_URL_OVERRIDES))
Expand Down
30 changes: 26 additions & 4 deletions common/djangoapps/third_party_auth/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,13 +366,12 @@ class OAuth2ProviderConfig(ProviderConfig):

.. no_pii:
"""
# We are keying the provider config by backend_name here as suggested in the python social
# auth documentation. In order to reuse a backend for a second provider, a subclass can be
# created with seperate name.
# We are keying the provider config by backend_name and site_id to support configuration per site.
# In order to reuse a backend for a second provider, a subclass can be created with seperate name.
# example:
# class SecondOpenIDProvider(OpenIDAuth):
# name = "second-openId-provider"
KEY_FIELDS = ('backend_name',)
KEY_FIELDS = ('site_id', 'backend_name')
prefix = 'oa2'
backend_name = models.CharField(
max_length=50, blank=False, db_index=True,
Expand Down Expand Up @@ -401,6 +400,29 @@ class Meta:
verbose_name = "Provider Configuration (OAuth)"
verbose_name_plural = verbose_name

@classmethod
def current(cls, *args):
"""
Get the current config model for the provider according to the given backend and the current
site.
"""
site_id = Site.objects.get_current(get_current_request()).id
return super(OAuth2ProviderConfig, cls).current(site_id, *args)

@property
def provider_id(self):
"""
Unique string key identifying this provider. Must be URL and css class friendly.
Ignoring site_id as the config is filtered using current method which fetches the configuration for the current
site_id.
"""
assert self.prefix is not None
return "-".join((self.prefix, ) + tuple(
str(getattr(self, field))
for field in self.KEY_FIELDS
if field != 'site_id'
))

def clean(self):
""" Standardize and validate fields """
super().clean()
Expand Down
4 changes: 2 additions & 2 deletions common/djangoapps/third_party_auth/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -854,7 +854,7 @@ def user_details_force_sync(auth_entry, strategy, details, user=None, *args, **k
This step is controlled by the `sync_learner_profile_data` flag on the provider's configuration.
"""
current_provider = provider.Registry.get_from_pipeline({'backend': strategy.request.backend.name, 'kwargs': kwargs})
if user and current_provider.sync_learner_profile_data:
if user and current_provider and current_provider.sync_learner_profile_data:
# Keep track of which incoming values get applied.
changed = {}

Expand Down Expand Up @@ -931,7 +931,7 @@ def set_id_verification_status(auth_entry, strategy, details, user=None, *args,
Use the user's authentication with the provider, if configured, as evidence of their identity being verified.
"""
current_provider = provider.Registry.get_from_pipeline({'backend': strategy.request.backend.name, 'kwargs': kwargs})
if user and current_provider.enable_sso_id_verification:
if user and current_provider and current_provider.enable_sso_id_verification:
# Get previous valid, non expired verification attempts for this SSO Provider and user
verifications = SSOVerification.objects.filter(
user=user,
Expand Down
62 changes: 53 additions & 9 deletions common/djangoapps/third_party_auth/tests/test_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
from common.djangoapps.third_party_auth import provider
from common.djangoapps.third_party_auth.tests import testutil
from common.djangoapps.third_party_auth.tests.utils import skip_unless_thirdpartyauth
from openedx.core.djangoapps.site_configuration.tests.test_util import with_site_configuration
from openedx.core.djangoapps.site_configuration.tests.test_util import (
with_site_configuration, with_site_configuration_context
)

SITE_DOMAIN_A = 'professionalx.example.com'
SITE_DOMAIN_B = 'somethingelse.example.com'
Expand Down Expand Up @@ -114,45 +116,81 @@ def test_providers_displayed_for_login(self):
assert no_log_in_provider.provider_id not in provider_ids
assert normal_provider.provider_id in provider_ids

def test_tpa_hint_provider_displayed_for_login(self):
def test_tpa_hint_exp_hidden_provider_displayed_for_login(self):
"""
Tests to ensure that an enabled-but-not-visible provider is presented
Test to ensure that an explicitly enabled-but-not-visible provider is presented
for use in the UI when the "tpa_hint" parameter is specified
A hidden provider should be accessible with tpa_hint (this is the main case)
"""

# A hidden provider should be accessible with tpa_hint (this is the main case)
hidden_provider = self.configure_google_provider(visible=False, enabled=True)
provider_ids = [
idp.provider_id
for idp in provider.Registry.displayed_for_login(tpa_hint=hidden_provider.provider_id)
]
assert hidden_provider.provider_id in provider_ids

# New providers are hidden (ie, not flagged as 'visible') by default
# The tpa_hint parameter should work for these providers as well
def test_tpa_hint_hidden_provider_displayed_for_login(self):
"""
Tests to ensure that an implicitly enabled-but-not-visible provider is presented
for use in the UI when the "tpa_hint" parameter is specified.
New providers are hidden (ie, not flagged as 'visible') by default
The tpa_hint parameter should work for these providers as well.
"""

implicitly_hidden_provider = self.configure_linkedin_provider(enabled=True)
provider_ids = [
idp.provider_id
for idp in provider.Registry.displayed_for_login(tpa_hint=implicitly_hidden_provider.provider_id)
]
assert implicitly_hidden_provider.provider_id in provider_ids

# Disabled providers should not be matched in tpa_hint scenarios
def test_tpa_hint_disabled_hidden_provider_displayed_for_login(self):
"""
Disabled providers should not be matched in tpa_hint scenarios
"""
disabled_provider = self.configure_twitter_provider(visible=True, enabled=False)
provider_ids = [
idp.provider_id
for idp in provider.Registry.displayed_for_login(tpa_hint=disabled_provider.provider_id)
]
assert disabled_provider.provider_id not in provider_ids

# Providers not utilized for learner authentication should not match tpa_hint
def test_tpa_hint_no_log_hidden_provider_displayed_for_login(self):
"""
Providers not utilized for learner authentication should not match tpa_hint
"""
no_log_in_provider = self.configure_lti_provider()
provider_ids = [
idp.provider_id
for idp in provider.Registry.displayed_for_login(tpa_hint=no_log_in_provider.provider_id)
]
assert no_log_in_provider.provider_id not in provider_ids

def test_get_current_site_oauth_provider(self):
"""
Verify that correct provider for current site is returned even if same backend is used for multiple sites.
"""
site_a = Site.objects.get_or_create(domain=SITE_DOMAIN_A, name=SITE_DOMAIN_A)[0]
site_b = Site.objects.get_or_create(domain=SITE_DOMAIN_B, name=SITE_DOMAIN_B)[0]
site_a_provider = self.configure_google_provider(visible=True, enabled=True, site=site_a)
site_b_provider = self.configure_google_provider(visible=True, enabled=True, site=site_b)
with with_site_configuration_context(domain=SITE_DOMAIN_A):
assert site_a_provider.enabled_for_current_site is True

# Registry.displayed_for_login gets providers enabled for current site
provider_ids = provider.Registry.displayed_for_login()
# Google oauth provider for current site should be displayed
assert site_a_provider in provider_ids
assert site_b_provider not in provider_ids

# Similarly, the other site should only see its own providers
with with_site_configuration_context(domain=SITE_DOMAIN_B):
assert site_b_provider.enabled_for_current_site is True
provider_ids = provider.Registry.displayed_for_login()
assert site_b_provider in provider_ids
assert site_a_provider not in provider_ids

def test_provider_enabled_for_current_site(self):
"""
Verify that enabled_for_current_site returns True when the provider matches the current site.
Expand Down Expand Up @@ -201,14 +239,20 @@ def test_oauth2_enabled_only_for_supplied_backend(self):
def test_get_returns_none_if_provider_id_is_none(self):
assert provider.Registry.get(None) is None

def test_get_returns_none_if_provider_not_enabled(self):
def test_get_returns_none_if_provider_not_enabled_change(self):
linkedin_provider_id = "oa2-linkedin-oauth2"
# At this point there should be no configuration entries at all so no providers should be enabled
assert provider.Registry.enabled() == []
assert provider.Registry.get(linkedin_provider_id) is None
# Now explicitly disabled this provider:
self.configure_linkedin_provider(enabled=False)
assert provider.Registry.get(linkedin_provider_id) is None

def test_get_returns_provider_if_provider_enabled(self):
"""
Test to ensure that Registry gets enabled providers.
"""
linkedin_provider_id = "oa2-linkedin-oauth2"
self.configure_linkedin_provider(enabled=True)
assert provider.Registry.get(linkedin_provider_id).provider_id == linkedin_provider_id

Expand Down
2 changes: 1 addition & 1 deletion common/djangoapps/third_party_auth/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def inactive_user_view(request):
if third_party_auth.is_enabled() and pipeline.running(request):
running_pipeline = pipeline.get(request)
third_party_provider = provider.Registry.get_from_pipeline(running_pipeline)
if third_party_provider.skip_email_verification and not activated:
if third_party_provider and third_party_provider.skip_email_verification and not activated:
user.is_active = True
user.save()
activated = True
Expand Down
10 changes: 9 additions & 1 deletion conf/locale/ar/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 0.1a\n"
"Report-Msgid-Bugs-To: [email protected]\n"
"POT-Creation-Date: 2023-09-10 20:42+0000\n"
"POT-Creation-Date: 2023-10-01 20:43+0000\n"
"PO-Revision-Date: 2019-01-20 20:43+0000\n"
"Last-Translator: NELC Open edX Translation <[email protected]>, 2020\n"
"Language-Team: Arabic (https://app.transifex.com/open-edx/teams/6205/ar/)\n"
Expand Down Expand Up @@ -7601,6 +7601,14 @@ msgstr "أردج {country} على اللائحة السوداء للمساق {co
msgid "Learner Pathways"
msgstr ""

#: openedx/core/djangoapps/notifications/admin.py
msgid "Notification App"
msgstr ""

#: openedx/core/djangoapps/notifications/admin.py
msgid "Notification Type"
msgstr ""

#: openedx/core/djangoapps/notifications/base_notification.py
#, python-brace-format
msgid ""
Expand Down
Loading
Loading