feat: Support Apple team Migration (#31861)

* feat: LEARNER-8790 Support Apple team migration
This commit is contained in:
Moeez Zahid
2023-03-10 13:06:31 +05:00
committed by GitHub
parent 8ac76dad35
commit 5b1eb3777e
13 changed files with 675 additions and 1 deletions

View File

@@ -14,6 +14,7 @@ from django.utils.translation import gettext_lazy as _
from .models import (
_PSA_OAUTH2_BACKENDS,
_PSA_SAML_BACKENDS,
AppleMigrationUserIdInfo,
LTIProviderConfig,
OAuth2ProviderConfig,
SAMLConfiguration,
@@ -196,3 +197,10 @@ class LTIProviderConfigAdmin(KeyedConfigurationModelAdmin):
)
admin.site.register(LTIProviderConfig, LTIProviderConfigAdmin)
class AppleMigrationUserIdInfoAdmin(admin.ModelAdmin):
""" Django Admin class for AppleMigrationUserIdInfo """
admin.site.register(AppleMigrationUserIdInfo, AppleMigrationUserIdInfoAdmin)

View File

@@ -78,8 +78,12 @@ import jwt
from jwt.algorithms import RSAAlgorithm
from jwt.exceptions import PyJWTError
from django.apps import apps
from social_core.backends.oauth import BaseOAuth2
from social_core.exceptions import AuthFailed
import social_django
from common.djangoapps.third_party_auth.toggles import is_apple_user_migration_enabled
class AppleIdAuth(BaseOAuth2):
@@ -205,6 +209,33 @@ class AppleIdAuth(BaseOAuth2):
return user_details
def get_user_id(self, details, response):
"""
If Apple team has been migrated, return the correct team_scoped apple_id that matches
existing UserSocialAuth instance. Else return apple_id as received in response.
"""
apple_id = super().get_user_id(details, response)
if is_apple_user_migration_enabled():
if social_django.models.DjangoStorage.user.get_social_auth(provider=self.name, uid=apple_id):
return apple_id
transfer_sub = response.get('transfer_sub')
if transfer_sub:
# Apple will send a transfer_sub till 60 days after the Apple Team has been migrated.
# If the team has been migrated and UserSocialAuth entries have not yet been updated
# with the new team-scoped apple-ids', use the transfer_sub to match to old apple ids'
# belonging to already signed-in users.
AppleMigrationUserIdInfo = apps.get_model('third_party_auth', 'AppleMigrationUserIdInfo')
user_apple_id_info = AppleMigrationUserIdInfo.objects.filter(transfer_id=transfer_sub).first()
old_apple_id = user_apple_id_info.old_apple_id
if social_django.models.DjangoStorage.user.get_social_auth(provider=self.name, uid=old_apple_id):
user_apple_id_info.new_apple_id = response.get(self.ID_KEY)
user_apple_id_info.save()
return user_apple_id_info.old_apple_id
return apple_id
def do_auth(self, access_token, *args, **kwargs):
response = kwargs.pop('response', None) or {}
jwt_string = response.get(self.TOKEN_KEY) or access_token

View File

@@ -0,0 +1,43 @@
Migrating Apple users while switching teams on Apple
-----------------------------------------------
This document explains how to migrate apple signed-in users in the event of
switching teams on the Apple Developer console. When a user uses Apple to sign in,
LMS receives an `id_token from apple containing user information`_, including
user's unique identifier with key `sub`. This unique identifier is unique to
Apple team this user belongs to. Upon switching teams on Apple, developers need
to migrate users from one team to another i.e. migrate users' unique
identifiers. In the LMS, users' unique apple identifiers are stored in
social_django.models.UserSocialAuth.uid. Following is an outline specifying the
migration process.
1. `Create transfer_identifiers for all apple users`_ using the current respective apple unique id.
i. Run management command generate_and_store_apple_transfer_ids to generate and store apple transfer ids.
ii. Transfer ids are stored in third_party_auth.models.AppleMigrationUserIdInfo to be used later on.
2. Transfer/Migrate teams on Apple account.
i. After the migration, `Apple continues to send the transfer identifier`_ with key `transfer_sub` in information sent after login.
ii. These transfer identifiers are available in the login information for 60 days after team transfer.
ii. The method get_user_id() in third_party_auth.appleid.AppleIdAuth enables existing users to sign in by matching the transfer_sub sent in the login information with stored records of old Apple unique identifiers in third_party_auth.models.AppleMigrationUserIdInfo.
3. Update Apple Backend credentials in third_party_auth.models.OAuth2ProviderConfig for the Apple backend.
4. Create new team-scoped apple unique ids' for users after the migration using transfer ids created in Step 1.
i. Run management command generate_and_store_new_apple_ids to generate and store new team-scoped apple ids.
5. Update apple unique identifiers in the Database with new team-scoped apple ids retrieved in step 3.
i. Run management command update_new_apple_ids_in_social_auth.
6. Apple user migration is complete!
.. _id_token from apple containing user information: https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api/authenticating_users_with_sign_in_with_apple
.. _Create transfer_identifiers for all apple users: https://developer.apple.com/documentation/sign_in_with_apple/transferring_your_apps_and_users_to_another_team
.. _Apple continues to send the transfer identifier: https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api/authenticating_users_with_sign_in_with_apple

View File

@@ -0,0 +1,118 @@
"""
Management command to generate Transfer Identifiers for users who signed in with Apple.
These transfer identifiers are used in the event of migrating an app from one team to another.
"""
import logging
import requests
import time
from django.core.management.base import BaseCommand, CommandError
from django.db import transaction
import jwt
from social_django.models import UserSocialAuth
from social_django.utils import load_strategy
from common.djangoapps.third_party_auth.models import AppleMigrationUserIdInfo
from common.djangoapps.third_party_auth.appleid import AppleIdAuth
log = logging.getLogger(__name__)
class Command(BaseCommand):
"""
Management command to generate transfer identifiers for apple users using their apple_id
stored in social_django.models.UserSocialAuth.uid.
Usage:
manage.py generate_and_store_apple_transfer_ids <target_team_id>
"""
def _generate_client_secret(self):
"""
Generate client secret for use in Apple API's
"""
now = int(time.time())
expiry = 60 * 60 * 3 # 3 hours
backend = load_strategy().get_backend(AppleIdAuth.name)
team_id = backend.setting('TEAM')
key_id = backend.setting('KEY')
private_key = backend.get_private_key()
audience = backend.TOKEN_AUDIENCE
headers = {
"alg": "ES256",
'kid': key_id
}
payload = {
'iss': team_id,
'iat': now,
'exp': now + expiry,
'aud': audience,
'sub': "org.edx.mobile",
}
return jwt.encode(payload, key=private_key, algorithm='ES256',
headers=headers)
def _generate_access_token(self, client_secret):
"""
Generate access token for use in Apple API's
"""
access_token_url = 'https://appleid.apple.com/auth/token'
app_id = "org.edx.mobile"
payload = {
"grant_type": "client_credentials",
"scope": "user.migration",
"client_id": app_id,
"client_secret": client_secret
}
headers = {
"Content-Type": "application/x-www-form-urlencoded",
"Host": "appleid.apple.com"
}
response = requests.post(access_token_url, data=payload, headers=headers)
access_token = response.json().get('access_token')
return access_token
def add_arguments(self, parser):
parser.add_argument('target_team_id', help='Team ID to which the app is to be migrated to.')
@transaction.atomic
def handle(self, *args, **options):
target_team_id = options['target_team_id']
migration_url = "https://appleid.apple.com/auth/usermigrationinfo"
app_id = "org.edx.mobile"
client_secret = self._generate_client_secret()
access_token = self._generate_access_token(client_secret)
if not access_token:
raise CommandError('Failed to create access token.')
headers = {
"Content-Type": "application/x-www-form-urlencoded",
"Host": "appleid.apple.com",
"Authorization": "Bearer " + access_token
}
payload = {
"target": target_team_id,
"client_id": app_id,
"client_secret": client_secret
}
apple_ids = UserSocialAuth.objects.filter(provider=AppleIdAuth.name).values_list('uid', flat=True)
for apple_id in apple_ids:
payload['sub'] = apple_id
response = requests.post(migration_url, data=payload, headers=headers)
transfer_id = response.json().get('transfer_sub')
if transfer_id:
apple_user_id_info, _ = AppleMigrationUserIdInfo.objects.get_or_create(old_apple_id=apple_id)
apple_user_id_info.transfer_id = transfer_id
apple_user_id_info.save()
log.info('Updated transfer_id for uid %s', apple_id)
else:
log.info('Unable to fetch transfer_id for uid %s', apple_id)

View File

@@ -0,0 +1,111 @@
"""
Management command to exchange apple transfer identifiers with Apple ID of the
user for new migrated team.
"""
import logging
import requests
import time
from django.core.management.base import BaseCommand, CommandError
from django.db import transaction
import jwt
from social_django.utils import load_strategy
from common.djangoapps.third_party_auth.models import AppleMigrationUserIdInfo
from common.djangoapps.third_party_auth.appleid import AppleIdAuth
log = logging.getLogger(__name__)
class Command(BaseCommand):
"""
Management command to exchange transfer identifiers for new team-scoped identifier for
the user in new migrated team.
Usage:
manage.py generate_and_store_apple_transfer_ids
"""
def _generate_client_secret(self):
"""
Generate client secret for use in Apple API's
"""
now = int(time.time())
expiry = 60 * 60 * 3 # 3 hours
backend = load_strategy().get_backend(AppleIdAuth.name)
team_id = backend.setting('TEAM')
key_id = backend.setting('KEY')
private_key = backend.get_private_key()
audience = backend.TOKEN_AUDIENCE
headers = {
"alg": "ES256",
'kid': key_id
}
payload = {
'iss': team_id,
'iat': now,
'exp': now + expiry,
'aud': audience,
'sub': "org.edx.mobile",
}
return jwt.encode(payload, key=private_key, algorithm='ES256',
headers=headers)
def _generate_access_token(self, client_secret):
"""
Generate access token for use in Apple API's
"""
access_token_url = 'https://appleid.apple.com/auth/token'
app_id = "org.edx.mobile"
payload = {
"grant_type": "client_credentials",
"scope": "user.migration",
"client_id": app_id,
"client_secret": client_secret
}
headers = {
"Content-Type": "application/x-www-form-urlencoded",
"Host": "appleid.apple.com"
}
response = requests.post(access_token_url, data=payload, headers=headers)
access_token = response.json().get('access_token')
return access_token
@transaction.atomic
def handle(self, *args, **options):
migration_url = "https://appleid.apple.com/auth/usermigrationinfo"
app_id = "org.edx.mobile"
client_secret = self._generate_client_secret()
access_token = self._generate_access_token(client_secret)
if not access_token:
raise CommandError('Failed to create access token.')
headers = {
"Content-Type": "application/x-www-form-urlencoded",
"Host": "appleid.apple.com",
"Authorization": "Bearer " + access_token
}
payload = {
"client_id": app_id,
"client_secret": client_secret
}
apple_user_ids_info = AppleMigrationUserIdInfo.objects.all()
for apple_user_id_info in apple_user_ids_info:
payload['transfer_sub'] = apple_user_id_info.transfer_id
response = requests.post(migration_url, data=payload, headers=headers)
new_apple_id = response.json().get('sub')
if new_apple_id:
apple_user_id_info.new_apple_id = new_apple_id
apple_user_id_info.save()
log.info('Updated new Apple ID for uid %s',
apple_user_id_info.old_apple_id)
else:
log.info('Unable to fetch new Apple ID for uid %s',
apple_user_id_info.old_apple_id)

View File

@@ -0,0 +1,85 @@
"""
Tests for `generate_and_store_apple_transfer_id` management command
"""
import json
from unittest import mock
from django.core.management import call_command
from django.core.management.base import CommandError
from django.test import TestCase
from requests.models import Response
from social_django.models import UserSocialAuth
from common.djangoapps.student.tests.factories import UserFactory
from common.djangoapps.third_party_auth.appleid import AppleIdAuth
from common.djangoapps.third_party_auth.management.commands import generate_and_store_apple_transfer_ids
from common.djangoapps.third_party_auth.models import AppleMigrationUserIdInfo
from common.djangoapps.third_party_auth.tests.factories import OAuth2ProviderConfigFactory
from openedx.core.djangolib.testing.utils import skip_unless_lms
@skip_unless_lms
class TestGenerateAndStoreTransferIds(TestCase):
"""
Test Django management command
"""
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.command = generate_and_store_apple_transfer_ids.Command()
def setUp(self):
super().setUp()
self.slug = 'garfield'
self.provider_garfield = OAuth2ProviderConfigFactory.create(slug='garfield')
self.user = UserFactory(username='fleur')
self.create_social_auth_entry(self.user, self.provider_garfield)
def create_social_auth_entry(self, user, provider):
external_id = 'sample_old_apple_id'
UserSocialAuth.objects.create(
user=user,
uid=f'{external_id}',
provider=provider.slug,
)
@mock.patch('common.djangoapps.third_party_auth.management.commands.'
'generate_and_store_apple_transfer_ids.Command._generate_access_token')
@mock.patch('common.djangoapps.third_party_auth.management.commands.'
'generate_and_store_apple_transfer_ids.Command._generate_client_secret')
def test_access_token_error(self, mock_generate_client_secret, mock_generate_access_token):
mock_generate_client_secret.return_value = 'sample_client_secret'
mock_generate_access_token.return_value = None
error_string = 'Failed to create access token.'
with self.assertRaisesRegex(CommandError, error_string):
call_command(self.command, 'sample_team_id')
@mock.patch('common.djangoapps.third_party_auth.management.commands.'
'generate_and_store_apple_transfer_ids.Command._generate_access_token')
@mock.patch('common.djangoapps.third_party_auth.management.commands.'
'generate_and_store_apple_transfer_ids.Command._generate_client_secret')
@mock.patch('requests.post')
def test_transfer_id_created(self, mock_post, mock_generate_client_secret, mock_generate_access_token):
response = Response()
response.status_code = 200
response_content = {'transfer_sub': 'sample_transfer_sub'}
response._content = json.dumps(response_content).encode('utf-8') # pylint: disable=protected-access
mock_post.return_value = response
mock_generate_client_secret.return_value = 'sample_client_secret'
mock_generate_access_token.return_value = 'sample_access_token'
self.assertEqual(0, AppleMigrationUserIdInfo.objects.all().count())
with mock.patch.object(AppleIdAuth, 'name', self.slug):
call_command(self.command, 'sample_team_id')
self.assertTrue(AppleMigrationUserIdInfo.objects.filter(
old_apple_id='sample_old_apple_id').exists())
expected_transfer_sub = 'sample_transfer_sub'
actual_transfer_sub = AppleMigrationUserIdInfo.objects.filter(
old_apple_id='sample_old_apple_id').first().transfer_id
self.assertEqual(expected_transfer_sub, actual_transfer_sub)

View File

@@ -0,0 +1,90 @@
"""
Tests for `generate_and_store_new_apple_ids` management command
"""
import json
from unittest import mock
from django.core.management import call_command
from django.core.management.base import CommandError
from django.test import TestCase
from requests.models import Response
from social_django.models import UserSocialAuth
from common.djangoapps.student.tests.factories import UserFactory
from common.djangoapps.third_party_auth.appleid import AppleIdAuth
from common.djangoapps.third_party_auth.management.commands import generate_and_store_new_apple_ids
from common.djangoapps.third_party_auth.models import AppleMigrationUserIdInfo
from common.djangoapps.third_party_auth.tests.factories import OAuth2ProviderConfigFactory
from openedx.core.djangolib.testing.utils import skip_unless_lms
@skip_unless_lms
class TestGenerateAndStoreAppleIds(TestCase):
"""
Test Django management command
"""
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.command = generate_and_store_new_apple_ids.Command()
def setUp(self):
super().setUp()
self.slug = 'garfield'
self.provider_garfield = OAuth2ProviderConfigFactory.create(slug='garfield')
self.user = UserFactory(username='fleur')
self.create_social_auth_entry(self.user, self.provider_garfield)
self.create_apple_migration_user_info_entry()
def create_social_auth_entry(self, user, provider):
external_id = 'sample_old_apple_id'
UserSocialAuth.objects.create(
user=user,
uid=f'{external_id}',
provider=provider.slug,
)
def create_apple_migration_user_info_entry(self):
AppleMigrationUserIdInfo.objects.create(
old_apple_id='sample_old_apple_id',
transfer_id='sample_transfer_sub'
)
@mock.patch('common.djangoapps.third_party_auth.management.commands.'
'generate_and_store_new_apple_ids.Command._generate_access_token')
@mock.patch('common.djangoapps.third_party_auth.management.commands.'
'generate_and_store_new_apple_ids.Command._generate_client_secret')
def test_access_token_error(self, mock_generate_client_secret, mock_generate_access_token):
mock_generate_client_secret.return_value = 'sample_client_secret'
mock_generate_access_token.return_value = None
error_string = 'Failed to create access token.'
with self.assertRaisesRegex(CommandError, error_string):
call_command(self.command)
@mock.patch('common.djangoapps.third_party_auth.management.commands.'
'generate_and_store_new_apple_ids.Command._generate_access_token')
@mock.patch('common.djangoapps.third_party_auth.management.commands.'
'generate_and_store_new_apple_ids.Command._generate_client_secret')
@mock.patch('requests.post')
def test_new_apple_id_created(self, mock_post, mock_generate_client_secret, mock_generate_access_token):
response = Response()
response.status_code = 200
response_content = {'sub': 'sample_new_apple_id'}
response._content = json.dumps(response_content).encode('utf-8') # pylint: disable=protected-access
mock_post.return_value = response
mock_generate_client_secret.return_value = 'sample_client_secret'
mock_generate_access_token.return_value = 'sample_access_token'
with mock.patch.object(AppleIdAuth, 'name', self.slug):
call_command(self.command)
self.assertTrue(AppleMigrationUserIdInfo.objects.filter(
transfer_id='sample_transfer_sub').exists())
expected_new_apple_id = 'sample_new_apple_id'
actual_new_apple_id = AppleMigrationUserIdInfo.objects.get(
transfer_id='sample_transfer_sub').new_apple_id
self.assertEqual(expected_new_apple_id, actual_new_apple_id)

View File

@@ -0,0 +1,60 @@
"""
Tests for `update_new_apple_ids_in_social_auth` management command
"""
from unittest import mock
from django.core.management import call_command
from django.test import TestCase
from social_django.models import UserSocialAuth
from common.djangoapps.student.tests.factories import UserFactory
from common.djangoapps.third_party_auth.appleid import AppleIdAuth
from common.djangoapps.third_party_auth.management.commands import update_new_apple_ids_in_social_auth
from common.djangoapps.third_party_auth.models import AppleMigrationUserIdInfo
from common.djangoapps.third_party_auth.tests.factories import OAuth2ProviderConfigFactory
from openedx.core.djangolib.testing.utils import skip_unless_lms
@skip_unless_lms
class TestGenerateAndStoreAppleIds(TestCase):
"""
Test Django management command
"""
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.command = update_new_apple_ids_in_social_auth.Command()
def setUp(self):
super().setUp()
self.slug = 'garfield'
self.provider_garfield = OAuth2ProviderConfigFactory.create(slug='garfield')
self.user = UserFactory(username='fleur')
self.create_social_auth_entry(self.user, self.provider_garfield)
self.create_apple_migration_user_info_entry()
def create_social_auth_entry(self, user, provider):
external_id = 'sample_old_apple_id'
UserSocialAuth.objects.create(
user=user,
uid=f'{external_id}',
provider=provider.slug,
)
def create_apple_migration_user_info_entry(self):
AppleMigrationUserIdInfo.objects.create(
old_apple_id='sample_old_apple_id',
transfer_id='sample_transfer_sub',
new_apple_id='sample_new_apple_id'
)
def test_new_apple_id_updated_in_social_auth(self):
self.assertTrue(UserSocialAuth.objects.filter(uid='sample_old_apple_id', provider=self.slug).exists())
self.assertFalse(UserSocialAuth.objects.filter(uid='sample_new_apple_id', provider=self.slug).exists())
with mock.patch.object(AppleIdAuth, 'name', self.slug):
call_command(self.command)
self.assertTrue(UserSocialAuth.objects.filter(uid='sample_new_apple_id', provider=self.slug).exists())
self.assertFalse(UserSocialAuth.objects.filter(uid='sample_old_apple_id', provider=self.slug).exists())

View File

@@ -0,0 +1,44 @@
"""
Management command to update new Apple ID from AppleMigrationUserIdInfo to UserSocialAuth.
"""
import logging
from django.core.management.base import BaseCommand, CommandError
from django.db import transaction
from django.db.models import Q
from social_django.models import UserSocialAuth
from common.djangoapps.third_party_auth.models import AppleMigrationUserIdInfo
from common.djangoapps.third_party_auth.appleid import AppleIdAuth
log = logging.getLogger(__name__)
class Command(BaseCommand):
"""
Management command to update new Apple ID from AppleMigrationUserIdInfo to UserSocialAuth.
Usage:
manage.py update_new_apple_ids_in_social_auth
"""
@transaction.atomic
def handle(self, *args, **options):
apple_user_ids_info = AppleMigrationUserIdInfo.objects.filter(
~Q(new_apple_id=''), new_apple_id__isnull=False
)
if not apple_user_ids_info:
raise CommandError('No Apple ID User info found.')
for apple_user_id_info in apple_user_ids_info:
user_social_auth = UserSocialAuth.objects.filter(
uid=apple_user_id_info.old_apple_id, provider=AppleIdAuth.name
).first()
if user_social_auth:
user_social_auth.uid = apple_user_id_info.new_apple_id
user_social_auth.save()
log.info(
'Replaced Apple ID %s with %s',
apple_user_id_info.old_apple_id,
apple_user_id_info.new_apple_id
)

View File

@@ -0,0 +1,26 @@
# Generated by Django 3.2.18 on 2023-02-28 11:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('third_party_auth', '0010_delete_historicalusersocialauth'),
]
operations = [
migrations.CreateModel(
name='AppleMigrationUserIdInfo',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('old_apple_id', models.CharField(max_length=255)),
('transfer_id', models.CharField(blank=True, max_length=255, null=True)),
('new_apple_id', models.CharField(blank=True, max_length=255, null=True)),
],
options={
'verbose_name': 'Apple User Id Migration Info',
'verbose_name_plural': 'Apple User Id Migration Info',
},
),
]

View File

@@ -992,3 +992,21 @@ class LTIProviderConfig(ProviderConfig):
app_label = "third_party_auth"
verbose_name = "Provider Configuration (LTI)"
verbose_name_plural = verbose_name
class AppleMigrationUserIdInfo(models.Model):
"""
Model to store users' Apple Unique Identifier during migration
process of Apple team from edx Inc. to edx LLC.
"""
old_apple_id = models.CharField(max_length=255)
transfer_id = models.CharField(max_length=255, null=True, blank=True)
new_apple_id = models.CharField(max_length=255, null=True, blank=True)
def __str__(self):
return self.old_apple_id
class Meta:
app_label = "third_party_auth"
verbose_name = "Apple User Id Migration Info"
verbose_name_plural = verbose_name

View File

@@ -7,7 +7,9 @@ from factory import SubFactory
from factory.django import DjangoModelFactory
from faker import Factory as FakerFactory
from common.djangoapps.third_party_auth.models import SAMLConfiguration, SAMLProviderConfig
from common.djangoapps.third_party_auth.models import (
OAuth2ProviderConfig, SAMLConfiguration, SAMLProviderConfig
)
from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory
FAKER = FakerFactory.create()
@@ -40,3 +42,16 @@ class SAMLProviderConfigFactory(DjangoModelFactory):
entity_id = factory.LazyAttribute(lambda x: FAKER.uri())
metadata_source = factory.LazyAttribute(lambda x: FAKER.uri())
class OAuth2ProviderConfigFactory(DjangoModelFactory):
"""
Factory for OAuth2ProviderConfig model in third_party_auth app.
"""
class Meta:
model = OAuth2ProviderConfig
site = SubFactory(SiteFactory)
enabled = True
slug = factory.LazyAttribute(lambda x: FAKER.slug())
name = factory.LazyAttribute(lambda x: FAKER.company())

View File

@@ -0,0 +1,25 @@
"""
Togglable settings for Third Party Auth
"""
from edx_toggles.toggles import WaffleFlag
THIRD_PARTY_AUTH_NAMESPACE = 'thirdpartyauth'
# .. toggle_name: third_party_auth.apple_user_migration
# .. toggle_implementation: WaffleFlag
# .. toggle_default: False
# .. toggle_description: Enable User ID matching while apple migration is in process
# .. toggle_use_cases: temporary
# .. toggle_creation_date: 2023-02-27
# .. toggle_target_removal_date: 2023-05-01
# .. toggle_tickets: LEARNER-8790
# .. toggle_warning: None.
APPLE_USER_MIGRATION_FLAG = WaffleFlag(f'{THIRD_PARTY_AUTH_NAMESPACE}.apple_user_migration', __name__)
def is_apple_user_migration_enabled():
"""
Returns a boolean if Apple users migration is in process.
"""
return APPLE_USER_MIGRATION_FLAG.is_enabled()