diff --git a/backend/Dockerfile b/backend/Dockerfile index 056ac283..00b32041 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -9,9 +9,9 @@ WORKDIR /app/ # Install dependencies RUN apt-get update \ - && apt-get install --no-install-recommends -y python3.10-dev python3-distutils libpq-dev gcc \ - && wget -qO get-pip.py "https://raw.githubusercontent.com/pypa/get-pip/4cfa4081d27285bda1220a62a5ebf5b4bd749cdb/public/get-pip.py" \ - && python3.10 get-pip.py \ + && apt-get install --no-install-recommends -y python3.7-dev python3-distutils libpq-dev gcc \ + && wget -qO get-pip.py "https://github.com/pypa/get-pip/raw/0c72a3b4ece313faccb446a96c84770ccedc5ec5/get-pip.py" \ + && python3.7 get-pip.py \ --disable-pip-version-check \ --no-cache-dir \ && pip3 install pipenv \ diff --git a/backend/Platform/settings/base.py b/backend/Platform/settings/base.py index 72183df1..b9d3fa67 100644 --- a/backend/Platform/settings/base.py +++ b/backend/Platform/settings/base.py @@ -24,7 +24,9 @@ # See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = os.environ.get("SECRET_KEY", "o7ql0!vuk0%rgrh9p2bihq#pege$qqlm@zo#8&t==%&za33m*2") +SECRET_KEY = os.environ.get( + "SECRET_KEY", "o7ql0!vuk0%rgrh9p2bihq#pege$qqlm@zo#8&t==%&za33m*2" +) IDENTITY_RSA_PRIVATE_KEY = os.environ.get( "IDENTITY_RSA_PRIVATE_KEY", @@ -112,7 +114,9 @@ # https://docs.djangoproject.com/en/2.1/ref/settings/#databases DATABASES = { - "default": dj_database_url.config(default="sqlite:///" + os.path.join(BASE_DIR, "db.sqlite3")) + "default": dj_database_url.config( + default="sqlite:///" + os.path.join(BASE_DIR, "db.sqlite3") + ) } # Set default PK type @@ -122,7 +126,9 @@ # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ - {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"}, + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator" + }, {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, diff --git a/backend/accounts/admin.py b/backend/accounts/admin.py index a1a39dad..c7f10797 100644 --- a/backend/accounts/admin.py +++ b/backend/accounts/admin.py @@ -98,7 +98,9 @@ class LabsAdminSite(admin.AdminSite): def login(self, request, extra_context=None): if not request.user.is_authenticated: - return redirect(reverse("accounts:login") + "?next=" + request.GET.get("next")) + return redirect( + reverse("accounts:login") + "?next=" + request.GET.get("next") + ) return super().login(request, extra_context) diff --git a/backend/accounts/backends.py b/backend/accounts/backends.py index 26a52246..f444638d 100644 --- a/backend/accounts/backends.py +++ b/backend/accounts/backends.py @@ -70,7 +70,9 @@ def authenticate(self, request, remote_user, shibboleth_attributes): # Update groups with every log in user.groups.clear() for affiliation_name in shibboleth_attributes["affiliation"]: - if affiliation_name: # Some users don't have any affiliation somehow ¯\_(ツ)_/¯ + if ( + affiliation_name + ): # Some users don't have any affiliation somehow ¯\_(ツ)_/¯ group, _ = Group.objects.get_or_create(name=affiliation_name) user.groups.add(group) diff --git a/backend/accounts/migrations/0001_initial.py b/backend/accounts/migrations/0001_initial.py index 0e8065ab..d4dbdb66 100644 --- a/backend/accounts/migrations/0001_initial.py +++ b/backend/accounts/migrations/0001_initial.py @@ -22,7 +22,9 @@ class Migration(migrations.Migration): ("password", models.CharField(max_length=128, verbose_name="password")), ( "last_login", - models.DateTimeField(blank=True, null=True, verbose_name="last login"), + models.DateTimeField( + blank=True, null=True, verbose_name="last login" + ), ), ( "is_superuser", @@ -35,25 +37,35 @@ class Migration(migrations.Migration): ( "username", models.CharField( - error_messages={"unique": "A user with that username already exists."}, + error_messages={ + "unique": "A user with that username already exists." + }, help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", max_length=150, unique=True, - validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], + validators=[ + django.contrib.auth.validators.UnicodeUsernameValidator() + ], verbose_name="username", ), ), ( "first_name", - models.CharField(blank=True, max_length=30, verbose_name="first name"), + models.CharField( + blank=True, max_length=30, verbose_name="first name" + ), ), ( "last_name", - models.CharField(blank=True, max_length=150, verbose_name="last name"), + models.CharField( + blank=True, max_length=150, verbose_name="last name" + ), ), ( "email", - models.EmailField(blank=True, max_length=254, verbose_name="email address"), + models.EmailField( + blank=True, max_length=254, verbose_name="email address" + ), ), ( "is_staff", diff --git a/backend/accounts/migrations/0002_auto_20200213_1711.py b/backend/accounts/migrations/0002_auto_20200213_1711.py index 936d4574..760e59db 100644 --- a/backend/accounts/migrations/0002_auto_20200213_1711.py +++ b/backend/accounts/migrations/0002_auto_20200213_1711.py @@ -22,7 +22,9 @@ def copy_permissions(apps, schema_editor): ContentType = apps.get_model("contenttypes", "ContentType") for user in User.objects.all(): for product_permission in user.product_permission.all(): - content_type = ContentType.objects.get(app_label="accounts", model="user") + content_type = ContentType.objects.get( + app_label="accounts", model="user" + ) perm, _ = Permission.objects.get_or_create( codename=product_permission.id, name=product_permission.name, diff --git a/backend/accounts/migrations/0003_auto_20210918_2041.py b/backend/accounts/migrations/0003_auto_20210918_2041.py index bd2977a2..1016844f 100644 --- a/backend/accounts/migrations/0003_auto_20210918_2041.py +++ b/backend/accounts/migrations/0003_auto_20210918_2041.py @@ -113,7 +113,9 @@ class Migration(migrations.Migration): migrations.AlterField( model_name="user", name="first_name", - field=models.CharField(blank=True, max_length=150, verbose_name="first name"), + field=models.CharField( + blank=True, max_length=150, verbose_name="first name" + ), ), migrations.CreateModel( name="PhoneNumber", diff --git a/backend/accounts/mixins.py b/backend/accounts/mixins.py index c518eac1..51ac6ff1 100644 --- a/backend/accounts/mixins.py +++ b/backend/accounts/mixins.py @@ -23,12 +23,22 @@ def _lookup_item(self, model, field_name, item, mode=None): return model.objects.get(**item) except ObjectDoesNotExist: raise serializers.ValidationError( - {field_name: ["The object with these values does not exist: {}".format(item)]}, + { + field_name: [ + "The object with these values does not exist: {}".format( + item + ) + ] + }, code="invalid", ) except MultipleObjectsReturned: raise serializers.ValidationError( - {field_name: ["Multiple objects exist with these values: {}".format(item)]} + { + field_name: [ + "Multiple objects exist with these values: {}".format(item) + ] + } ) def save(self): @@ -65,7 +75,9 @@ def save(self): for item in items: # adds item to list - m2m_lists[field_name].append(self._lookup_item(model, field_name, item, mode)) + m2m_lists[field_name].append( + self._lookup_item(model, field_name, item, mode) + ) else: m2m["many"] = False # handles if it's a 1:1 field @@ -73,7 +85,9 @@ def save(self): model = field.Meta.model item = self.validated_data.pop(field_name, None) # creates/gets objects associaated with list and creates list of objects - m2m_lists[field_name] = self._lookup_item(model, field_name, item, mode) + m2m_lists[field_name] = self._lookup_item( + model, field_name, item, mode + ) else: # handles if it's accidentally added (e.g. Integer field, character field, etc.) ignore_fields.add(field_name) diff --git a/backend/accounts/models.py b/backend/accounts/models.py index b3a183df..e4032246 100644 --- a/backend/accounts/models.py +++ b/backend/accounts/models.py @@ -63,7 +63,9 @@ class User(AbstractUser): pennid = models.IntegerField(primary_key=True) uuid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False) preferred_name = models.CharField(max_length=225, blank=True) - profile_pic = models.ImageField(upload_to=get_user_image_filepath, blank=True, null=True) + profile_pic = models.ImageField( + upload_to=get_user_image_filepath, blank=True, null=True + ) VERIFICATION_EXPIRATION_MINUTES = 10 @@ -88,7 +90,9 @@ class Student(models.Model): ) major = models.ManyToManyField(Major, blank=True) school = models.ManyToManyField(School, blank=True) - graduation_year = models.PositiveIntegerField(validators=[MinValueValidator(1740)], null=True) + graduation_year = models.PositiveIntegerField( + validators=[MinValueValidator(1740)], null=True + ) def __str__(self): return self.user.username @@ -104,7 +108,9 @@ def ensure_student_object(sender, instance, created, **kwargs): class Email(models.Model): - user = models.ForeignKey(get_user_model(), related_name="emails", on_delete=models.CASCADE) + user = models.ForeignKey( + get_user_model(), related_name="emails", on_delete=models.CASCADE + ) value = models.EmailField(unique=True) primary = models.BooleanField(default=False) verification_code = models.CharField(max_length=6, blank=True, null=True) @@ -146,7 +152,9 @@ def add_privacy_resource(sender, instance, created, **kwargs): to true. """ users = User.objects.all() - settings = [PrivacySetting(user=user, resource=instance, enabled=True) for user in users] + settings = [ + PrivacySetting(user=user, resource=instance, enabled=True) for user in users + ] # Bulk creating for all User objects PrivacySetting.objects.bulk_create(settings, ignore_conflicts=True) @@ -155,7 +163,9 @@ class PrivacySetting(models.Model): user = models.ForeignKey( get_user_model(), related_name="privacy_setting", on_delete=models.CASCADE ) - resource = models.ForeignKey(PrivacyResource, related_name="resource", on_delete=models.CASCADE) + resource = models.ForeignKey( + PrivacyResource, related_name="resource", on_delete=models.CASCADE + ) enabled = models.BooleanField(default=True) @@ -171,6 +181,7 @@ def load_privacy_settings(sender, instance, created, **kwargs): if not instance.privacy_setting.exists(): resources = PrivacyResource.objects.all() settings = [ - PrivacySetting(user=instance, resource=resource, enabled=True) for resource in resources + PrivacySetting(user=instance, resource=resource, enabled=True) + for resource in resources ] PrivacySetting.objects.bulk_create(settings, ignore_conflicts=True) diff --git a/backend/accounts/serializers.py b/backend/accounts/serializers.py index 69c38bdb..7570cfb6 100644 --- a/backend/accounts/serializers.py +++ b/backend/accounts/serializers.py @@ -62,7 +62,9 @@ def create(self, validated_data): instance = super().create(validated_data) instance.verified = False instance.primary = False - instance.verification_code = get_random_string(length=6, allowed_chars="1234567890") + instance.verification_code = get_random_string( + length=6, allowed_chars="1234567890" + ) # timestamp is set by django instance.save() sendSMSVerification(instance.value, instance.verification_code) @@ -73,24 +75,37 @@ def update(self, instance, validated_data): elapsed_time = timezone.now() - instance.verification_timestamp if ( validated_data["verification_code"] == instance.verification_code - and elapsed_time.total_seconds() < User.VERIFICATION_EXPIRATION_MINUTES * 60 + and elapsed_time.total_seconds() + < User.VERIFICATION_EXPIRATION_MINUTES * 60 ): - if self.context["request"].user.phone_numbers.filter(verified=True).count() == 0: + if ( + self.context["request"] + .user.phone_numbers.filter(verified=True) + .count() + == 0 + ): instance.primary = True instance.verified = True - elif elapsed_time.total_seconds() >= User.VERIFICATION_EXPIRATION_MINUTES * 60: + elif ( + elapsed_time.total_seconds() + >= User.VERIFICATION_EXPIRATION_MINUTES * 60 + ): raise serializers.ValidationError( detail={"detail": "Verification code has expired"} ) else: - raise serializers.ValidationError(detail={"detail": "Incorrect verification code"}) + raise serializers.ValidationError( + detail={"detail": "Incorrect verification code"} + ) if "primary" in validated_data and validated_data["primary"]: if instance.verified: self.context["request"].user.phone_numbers.all().update(primary=False) instance.primary = True else: raise serializers.ValidationError( - detail={"detail": "Must verify point of contact before marking as primary"} + detail={ + "detail": "Must verify point of contact before marking as primary" + } ) instance.save() return instance @@ -107,7 +122,9 @@ def create(self, validated_data): instance = super().create(validated_data) instance.verified = False instance.primary = False - instance.verification_code = get_random_string(length=6, allowed_chars="1234567890") + instance.verification_code = get_random_string( + length=6, allowed_chars="1234567890" + ) # timestamp is set by django instance.save() sendEmailVerification(instance.value, instance.verification_code) @@ -118,24 +135,35 @@ def update(self, instance, validated_data): elapsed_time = timezone.now() - instance.verification_timestamp if ( validated_data["verification_code"] == instance.verification_code - and elapsed_time.total_seconds() < User.VERIFICATION_EXPIRATION_MINUTES * 60 + and elapsed_time.total_seconds() + < User.VERIFICATION_EXPIRATION_MINUTES * 60 ): - if self.context["request"].user.emails.filter(verified=True).count() == 0: + if ( + self.context["request"].user.emails.filter(verified=True).count() + == 0 + ): instance.primary = True instance.verified = True - elif elapsed_time.total_seconds() >= User.VERIFICATION_EXPIRATION_MINUTES * 60: + elif ( + elapsed_time.total_seconds() + >= User.VERIFICATION_EXPIRATION_MINUTES * 60 + ): raise serializers.ValidationError( detail={"detail": "Verification code has expired"} ) else: - raise serializers.ValidationError(detail={"detail": "Incorrect verification code"}) + raise serializers.ValidationError( + detail={"detail": "Incorrect verification code"} + ) if "primary" in validated_data and validated_data["primary"]: if instance.verified: self.context["request"].user.emails.all().update(primary=False) instance.primary = True else: raise serializers.ValidationError( - detail={"detail": "Must verify point of contact before marking as primary"} + detail={ + "detail": "Must verify point of contact before marking as primary" + } ) instance.save() return instance diff --git a/backend/accounts/update_majors.py b/backend/accounts/update_majors.py index 99aa4b0f..7d206e09 100644 --- a/backend/accounts/update_majors.py +++ b/backend/accounts/update_majors.py @@ -34,7 +34,9 @@ def update_all_majors(): listed_majors = set() # iterate through all list tags with "item" in the class (all programs) - for program in soup.find_all("li", class_=lambda value: value and value.startswith("item ")): + for program in soup.find_all( + "li", class_=lambda value: value and value.startswith("item ") + ): curr_filter_list = program.attrs["class"] # check if entry meets relevant desired and excluded filter criteria if not contains_filters( @@ -58,7 +60,9 @@ def update_all_majors(): curr_degree_type = Major.DEGREE_PROFESSIONAL # create new major entry if it does not already exist - Major.objects.update_or_create(name=major_name, defaults={"degree_type": curr_degree_type}) + Major.objects.update_or_create( + name=major_name, defaults={"degree_type": curr_degree_type} + ) # keep track of found majors listed_majors.add(major_name) diff --git a/backend/accounts/views.py b/backend/accounts/views.py index 3447e652..50f8acd4 100644 --- a/backend/accounts/views.py +++ b/backend/accounts/views.py @@ -50,7 +50,9 @@ def image_upload_helper(request, user, keyword, field): keyword: the key corresponding to the image file in the request body field: the name of the User model field to which the file is saved """ - if keyword not in request.data or not isinstance(request.data[keyword], UploadedFile): + if keyword not in request.data or not isinstance( + request.data[keyword], UploadedFile + ): return Response( {"detail": "No image file was uploaded!"}, status=status.HTTP_400_BAD_REQUEST, @@ -97,7 +99,9 @@ def get(self, request): "last_name": last_name, "affiliation": affiliation, } - user = auth.authenticate(remote_user=pennid, shibboleth_attributes=shibboleth_attributes) + user = auth.authenticate( + remote_user=pennid, shibboleth_attributes=shibboleth_attributes + ) if user: auth.login(request, user) return redirect(request.GET.get("next", "/")) @@ -112,7 +116,9 @@ class LogoutView(View): def get(self, request): auth.logout(request) - return redirect("/Shibboleth.sso/Logout?return=https://idp.pennkey.upenn.edu/logout") + return redirect( + "/Shibboleth.sso/Logout?return=https://idp.pennkey.upenn.edu/logout" + ) class DevLoginView(View): @@ -122,7 +128,9 @@ class DevLoginView(View): """ def get(self, request): - user_objects = get_user_model().objects.filter(~Q(username="admin")).order_by("pennid") + user_objects = ( + get_user_model().objects.filter(~Q(username="admin")).order_by("pennid") + ) serialized_data = UserSerializer(user_objects, many=True).data return render(request, "accounts/devlogin.html", {"user_data": serialized_data}) @@ -394,7 +402,9 @@ def resend_verification(self, request, pk=None): obj = self.get_object() elapsed_time = timezone.now() - obj.verification_timestamp if elapsed_time.total_seconds() > User.VERIFICATION_EXPIRATION_MINUTES * 60: - obj.verification_code = get_random_string(length=6, allowed_chars="1234567890") + obj.verification_code = get_random_string( + length=6, allowed_chars="1234567890" + ) obj.verification_timestamp = timezone.now() sendSMSVerification(obj.value, obj.verification_code) obj.save() @@ -434,7 +444,9 @@ def get_queryset(self): def destroy(self, request, *args, **kwargs): is_primary = self.get_object().primary if is_primary and self.get_queryset().filter(verified=True).count() < 2: - return Response({"detail": "You can't delete the only verified email"}, status=405) + return Response( + {"detail": "You can't delete the only verified email"}, status=405 + ) self.get_object().delete() next_email = self.get_queryset().filter(verified=True).first() @@ -448,7 +460,9 @@ def resend_verification(self, request, pk=None): obj = self.get_object() elapsed_time = timezone.now() - obj.verification_timestamp if elapsed_time.total_seconds() > User.VERIFICATION_EXPIRATION_MINUTES * 60: - obj.verification_code = get_random_string(length=6, allowed_chars="1234567890") + obj.verification_code = get_random_string( + length=6, allowed_chars="1234567890" + ) obj.verification_timestamp = timezone.now() sendEmailVerification(obj.value, obj.verification_code) obj.save() @@ -499,7 +513,9 @@ class ProductAdminView(APIView): def post(self, request, format=None): # Revoke all existing admin permissions content_type = ContentType.objects.get(app_label="accounts", model="user") - perms = Permission.objects.filter(content_type=content_type, codename__endswith="_admin") + perms = Permission.objects.filter( + content_type=content_type, codename__endswith="_admin" + ) for perm in perms: perm.user_set.clear() User.objects.filter(Q(is_superuser=True) | Q(is_staff=True)).update( @@ -532,7 +548,9 @@ def post(self, request, format=None): return Response({"detail": "success"}) -class PrivacySettingView(generics.GenericAPIView, mixins.ListModelMixin, mixins.UpdateModelMixin): +class PrivacySettingView( + generics.GenericAPIView, mixins.ListModelMixin, mixins.UpdateModelMixin +): serializer_class = PrivacySettingSerializer permission_classes = [IsAuthenticated] diff --git a/backend/identity/utils.py b/backend/identity/utils.py index 4e0858fa..7179266c 100644 --- a/backend/identity/utils.py +++ b/backend/identity/utils.py @@ -36,6 +36,8 @@ def mint_refresh_jwt(key: jwk.JWK, urn: str) -> jwt.JWT: - no exp claim because refresh JWTs do not expire """ now = time.time() - token = jwt.JWT(header={"alg": SIGNING_ALG}, claims={"sub": urn, "use": "refresh", "iat": now}) + token = jwt.JWT( + header={"alg": SIGNING_ALG}, claims={"sub": urn, "use": "refresh", "iat": now} + ) token.make_signed_token(key) return token diff --git a/backend/identity/views.py b/backend/identity/views.py index be9a4220..5265c318 100644 --- a/backend/identity/views.py +++ b/backend/identity/views.py @@ -68,7 +68,11 @@ class JwksInfoView(View): # building out JWKS view at init time so we don't have to recalculate it for each request def __init__(self, **kwargs): - data = {"keys": [{"alg": SIGNING_ALG, "use": "sig", "kid": ID_PRIVATE_KEY.thumbprint()}]} + data = { + "keys": [ + {"alg": SIGNING_ALG, "use": "sig", "kid": ID_PRIVATE_KEY.thumbprint()} + ] + } data["keys"][0].update(json.loads(ID_PRIVATE_KEY.export_public())) response = JsonResponse(data) response["Access-Control-Allow-Origin"] = "*" diff --git a/backend/tests/accounts/test_admin.py b/backend/tests/accounts/test_admin.py index b99c43c6..191b9c66 100644 --- a/backend/tests/accounts/test_admin.py +++ b/backend/tests/accounts/test_admin.py @@ -22,14 +22,20 @@ def test_username(self): self.assertEqual(self.student_admin.username(self.student), self.user.username) def test_first_name(self): - self.assertEqual(self.student_admin.first_name(self.student), self.user.first_name) + self.assertEqual( + self.student_admin.first_name(self.student), self.user.first_name + ) def test_last_name(self): - self.assertEqual(self.student_admin.last_name(self.student), self.user.last_name) + self.assertEqual( + self.student_admin.last_name(self.student), self.user.last_name + ) class LabsAdminTestCase(TestCase): - check = os.environ.get("DJANGO_SETTINGS_MODULE", "") == "Platform.settings.development" + check = ( + os.environ.get("DJANGO_SETTINGS_MODULE", "") == "Platform.settings.development" + ) @skipIf(check, "This test doesn't matter in development") def test_admin_not_logged_in(self): diff --git a/backend/tests/accounts/test_backends.py b/backend/tests/accounts/test_backends.py index 6078e3a3..17531c3f 100644 --- a/backend/tests/accounts/test_backends.py +++ b/backend/tests/accounts/test_backends.py @@ -19,13 +19,17 @@ def setUp(self): } def test_invalid_remote_user(self): - user = auth.authenticate(remote_user=-1, shibboleth_attributes=self.shibboleth_attributes) + user = auth.authenticate( + remote_user=-1, shibboleth_attributes=self.shibboleth_attributes + ) self.assertIsNone(user) @patch("accounts.backends.ShibbolethRemoteUserBackend.get_email") def test_empty_shibboleth_attributes(self, mock_get_email): mock_get_email.return_value = None - user = auth.authenticate(remote_user=1, shibboleth_attributes=self.shibboleth_attributes) + user = auth.authenticate( + remote_user=1, shibboleth_attributes=self.shibboleth_attributes + ) self.assertEqual(user.pennid, 1) self.assertEqual(user.first_name, "") self.assertEqual(user.emails.count(), 1) @@ -37,7 +41,9 @@ def test_empty_shibboleth_attributes(self, mock_get_email): @patch("accounts.backends.ShibbolethRemoteUserBackend.get_email") def test_create_user(self, mock_get_email): mock_get_email.return_value = None - auth.authenticate(remote_user=1, shibboleth_attributes=self.shibboleth_attributes) + auth.authenticate( + remote_user=1, shibboleth_attributes=self.shibboleth_attributes + ) self.assertEqual(len(get_user_model().objects.all()), 1) user = get_user_model().objects.all()[0] self.assertEqual(user.pennid, 1) @@ -58,7 +64,9 @@ def test_create_user_with_attributes(self, mock_get_email): self.assertEqual(user.first_name, "test") self.assertEqual(user.last_name, "user") self.assertEqual(user.groups.get(name="student"), student_affiliation) - self.assertEqual(user.groups.get(name="member"), Group.objects.get(name="member")) + self.assertEqual( + user.groups.get(name="member"), Group.objects.get(name="member") + ) self.assertEqual(len(user.groups.all()), 2) self.assertEqual(len(Group.objects.all()), 2) @@ -83,7 +91,9 @@ def test_login_user(self, mock_get_email): student = get_user_model().objects.create_user( pennid=1, username="student", password="secret" ) - user = auth.authenticate(remote_user=1, shibboleth_attributes=self.shibboleth_attributes) + user = auth.authenticate( + remote_user=1, shibboleth_attributes=self.shibboleth_attributes + ) self.assertEqual(user, student) @patch("accounts.backends.ShibbolethRemoteUserBackend.get_email") diff --git a/backend/tests/accounts/test_serializers.py b/backend/tests/accounts/test_serializers.py index ec5773c2..221280f9 100644 --- a/backend/tests/accounts/test_serializers.py +++ b/backend/tests/accounts/test_serializers.py @@ -46,7 +46,9 @@ def test_active_major(self): class MajorSerializerTestCase(TestCase): def setUp(self): - self.major_active = Major.objects.create(name="Test Active Major", is_active=True) + self.major_active = Major.objects.create( + name="Test Active Major", is_active=True + ) self.major_inactive = Major.objects.create( name="Test Inactive Major", degree_type="PHD", is_active=False ) @@ -73,7 +75,9 @@ def test_inactive_major(self): class StudentSerializerTestCase(TestCase): def setUp(self): - self.date = pytz.timezone("America/New_York").localize(datetime.datetime(2019, 1, 1)) + self.date = pytz.timezone("America/New_York").localize( + datetime.datetime(2019, 1, 1) + ) self.user = get_user_model().objects.create_user( pennid=1, username="student", @@ -82,7 +86,9 @@ def setUp(self): last_name="Last", email="test@test.com", ) - self.active_major_1 = Major.objects.create(name="Test Active Major", is_active=True) + self.active_major_1 = Major.objects.create( + name="Test Active Major", is_active=True + ) self.active_major_2 = Major.objects.create( name="Test Active Major 2", degree_type="PHD", is_active=True ) @@ -158,7 +164,9 @@ def test_remove_school(self): class UserSerializerTestCase(TestCase): def setUp(self): - self.date = pytz.timezone("America/New_York").localize(datetime.datetime(2019, 1, 1)) + self.date = pytz.timezone("America/New_York").localize( + datetime.datetime(2019, 1, 1) + ) self.user = get_user_model().objects.create_user( pennid=1, username="student", @@ -209,7 +217,9 @@ def test_preferred_same_as_first(self): class UserSearchSerializerTestCase(TestCase): def setUp(self): - self.date = pytz.timezone("America/New_York").localize(datetime.datetime(2019, 1, 1)) + self.date = pytz.timezone("America/New_York").localize( + datetime.datetime(2019, 1, 1) + ) self.user = get_user_model().objects.create_user( pennid=1, username="student", @@ -251,7 +261,9 @@ def test_str_preferred_name_provided(self): # student serializer info / test using nested serializer editing class StudentSerializerTestCaseOLD(TestCase): def setUp(self): - self.date = pytz.timezone("America/New_York").localize(datetime.datetime(2019, 1, 1)) + self.date = pytz.timezone("America/New_York").localize( + datetime.datetime(2019, 1, 1) + ) self.user = get_user_model().objects.create_user( pennid=1, username="student", @@ -325,7 +337,9 @@ def test_create(self): "verified": True, "verification_code": "000000", } - serializer = PhoneNumberSerializer(data=data, context={"request": FakeRequest(self.user)}) + serializer = PhoneNumberSerializer( + data=data, context={"request": FakeRequest(self.user)} + ) self.assertTrue(serializer.is_valid(raise_exception=True)) phone_number = serializer.save() self.assertEqual(phone_number.user, self.user) @@ -341,7 +355,9 @@ def test_create_same_phone(self): "verified": True, "verification_code": "000000", } - serializer = PhoneNumberSerializer(data=data, context={"request": FakeRequest(self.user)}) + serializer = PhoneNumberSerializer( + data=data, context={"request": FakeRequest(self.user)} + ) with self.assertRaises(serializers.ValidationError): serializer.is_valid(raise_exception=True) @@ -403,7 +419,9 @@ def test_verification_timeout(self): "verified": True, "verification_code": "000000", } - serializer = PhoneNumberSerializer(data=data, context={"request": FakeRequest(self.user)}) + serializer = PhoneNumberSerializer( + data=data, context={"request": FakeRequest(self.user)} + ) self.assertTrue(serializer.is_valid(raise_exception=True)) phone_number = serializer.save() @@ -452,7 +470,9 @@ def test_create(self): "verified": True, "verification_code": "000000", } - serializer = EmailSerializer(data=data, context={"request": FakeRequest(self.user)}) + serializer = EmailSerializer( + data=data, context={"request": FakeRequest(self.user)} + ) self.assertTrue(serializer.is_valid()) email = serializer.save() self.assertEqual(email.user, self.user) @@ -518,7 +538,9 @@ def test_verification_timeout(self): "verified": True, "verification_code": "000000", } - serializer = EmailSerializer(data=data, context={"request": FakeRequest(self.user)}) + serializer = EmailSerializer( + data=data, context={"request": FakeRequest(self.user)} + ) self.assertTrue(serializer.is_valid(raise_exception=True)) email = serializer.save() @@ -527,7 +549,9 @@ def test_verification_timeout(self): ) email.verification_code = "000000" data = {"value": "test@example.com", "verification_code": "000000"} - serializer = EmailSerializer(email, data=data, context={"request": FakeRequest(self.user)}) + serializer = EmailSerializer( + email, data=data, context={"request": FakeRequest(self.user)} + ) if serializer.is_valid(raise_exception=True): with self.assertRaises(serializers.ValidationError): serializer.save() diff --git a/backend/tests/accounts/test_verification.py b/backend/tests/accounts/test_verification.py index bde71ff3..3acd9a21 100644 --- a/backend/tests/accounts/test_verification.py +++ b/backend/tests/accounts/test_verification.py @@ -13,9 +13,13 @@ def test_(self, mock_send_email): mock_send_email.assert_called() self.assertEqual(1, len(mock_send_email.mock_calls)) expected = {"verification_code": "000000"} - self.assertEqual("emails/email_verification.html", mock_send_email.call_args[0][0]) + self.assertEqual( + "emails/email_verification.html", mock_send_email.call_args[0][0] + ) self.assertEqual(expected, mock_send_email.call_args[0][1]) - self.assertEqual("Penn Labs email verification", mock_send_email.call_args[0][2]) + self.assertEqual( + "Penn Labs email verification", mock_send_email.call_args[0][2] + ) self.assertEqual("+15555555555", mock_send_email.call_args[0][3]) @@ -31,7 +35,9 @@ def test_invalid_client(self, mock_sentry): @patch("accounts.verification.capture_message") @patch("accounts.verification.Client") def test_rest_exception(self, mock_client, mock_sentry): - mock_client.return_value.messages.create.side_effect = TwilioRestException("", "") + mock_client.return_value.messages.create.side_effect = TwilioRestException( + "", "" + ) sendSMSVerification("+15555555555", "000000") mock_sentry.assert_called() self.assertEqual(1, len(mock_sentry.mock_calls)) diff --git a/backend/tests/accounts/test_views.py b/backend/tests/accounts/test_views.py index 02d12fc9..3a1aeeb1 100644 --- a/backend/tests/accounts/test_views.py +++ b/backend/tests/accounts/test_views.py @@ -69,8 +69,13 @@ def test_valid_shibboleth(self): "HTTP_SN": "user-hyphenated", "HTTP_MAIL": "test@student.edu", } - params = reverse("accounts:authorize") + "?client_id=abc123&response_type=code&state=abc" - response = self.client.get(reverse("accounts:login") + "?next=" + quote(params), **headers) + params = ( + reverse("accounts:authorize") + + "?client_id=abc123&response_type=code&state=abc" + ) + response = self.client.get( + reverse("accounts:login") + "?next=" + quote(params), **headers + ) base_url = "/accounts/authorize/" sample_response = base_url + "?client_id=abc123&response_type=code&state=abc" self.assertRedirects(response, sample_response, fetch_redirect_response=False) @@ -86,16 +91,22 @@ def setUp(self): reload_urlconf() def test_logged_in_user(self): - get_user_model().objects.create_user(pennid=1, username="user", password="secret") + get_user_model().objects.create_user( + pennid=1, username="user", password="secret" + ) self.client.login(username="user", password="secret") response = self.client.get(reverse("accounts:logout")) self.assertNotIn("_auth_user_id", self.client.session) - sample_response = "/Shibboleth.sso/Logout?return=https://idp.pennkey.upenn.edu/logout" + sample_response = ( + "/Shibboleth.sso/Logout?return=https://idp.pennkey.upenn.edu/logout" + ) self.assertRedirects(response, sample_response, fetch_redirect_response=False) def test_guest_user(self): response = self.client.get(reverse("accounts:logout")) - sample_response = "/Shibboleth.sso/Logout?return=https://idp.pennkey.upenn.edu/logout" + sample_response = ( + "/Shibboleth.sso/Logout?return=https://idp.pennkey.upenn.edu/logout" + ) self.assertRedirects(response, sample_response, fetch_redirect_response=False) @@ -130,7 +141,9 @@ def setUp(self): reload_urlconf() def test_logout_user(self): - get_user_model().objects.create_user(pennid=1, username="user", password="secret") + get_user_model().objects.create_user( + pennid=1, username="user", password="secret" + ) self.client.login(username="user", password="secret") response = self.client.get(reverse("accounts:logout")) self.assertNotIn("_auth_user_id", self.client.session) @@ -158,7 +171,9 @@ def setUp(self): self.resource_server_user = self.UserModel.objects.create_user( pennid=1, username="resource_server" ) - self.test_user = self.UserModel.objects.create_user(pennid=2, username="bar_user") + self.test_user = self.UserModel.objects.create_user( + pennid=2, username="bar_user" + ) self.application = self.Application( name="Test Application", @@ -194,7 +209,9 @@ def setUp(self): ) def test_view_post_valid_token(self): - auth_headers = {"HTTP_AUTHORIZATION": "Bearer " + self.resource_server_token.token} + auth_headers = { + "HTTP_AUTHORIZATION": "Bearer " + self.resource_server_token.token + } response = self.client.post( reverse("accounts:introspect"), {"token": self.valid_token.token}, @@ -216,7 +233,9 @@ def test_view_post_valid_token(self): ) def test_view_post_invalid_token(self): - auth_headers = {"HTTP_AUTHORIZATION": "Bearer " + self.resource_server_token.token} + auth_headers = { + "HTTP_AUTHORIZATION": "Bearer " + self.resource_server_token.token + } response = self.client.post( reverse("accounts:introspect"), {"token": self.invalid_token.token}, @@ -229,7 +248,9 @@ def test_view_post_invalid_token(self): self.assertDictEqual(content, {"active": False}) def test_view_post_notexisting_token(self): - auth_headers = {"HTTP_AUTHORIZATION": "Bearer " + self.resource_server_token.token} + auth_headers = { + "HTTP_AUTHORIZATION": "Bearer " + self.resource_server_token.token + } response = self.client.post( reverse("accounts:introspect"), {"token": "kaudawelsch"}, **auth_headers ) @@ -262,7 +283,9 @@ def setUp(self): self.auth_headers = {"HTTP_AUTHORIZATION": f"Bearer {self.token}"} def test_short_query(self): - response = self.client.get(reverse("accounts:search") + "?q=t", **self.auth_headers) + response = self.client.get( + reverse("accounts:search") + "?q=t", **self.auth_headers + ) self.assertFalse(response.json()) self.assertNotIn(UserSearchSerializer(self.user1).data, response.json()) self.assertNotIn(UserSearchSerializer(self.user2).data, response.json()) @@ -275,17 +298,23 @@ def test_full_name(self): self.assertIn(UserSearchSerializer(self.user2).data, response.json()) def test_exact_pennkey_user1(self): - response = self.client.get(reverse("accounts:search") + "?q=test1", **self.auth_headers) + response = self.client.get( + reverse("accounts:search") + "?q=test1", **self.auth_headers + ) self.assertIn(UserSearchSerializer(self.user1).data, response.json()) self.assertNotIn(UserSearchSerializer(self.user2).data, response.json()) def test_exact_pennkey_user2(self): - response = self.client.get(reverse("accounts:search") + "?q=test2", **self.auth_headers) + response = self.client.get( + reverse("accounts:search") + "?q=test2", **self.auth_headers + ) self.assertNotIn(UserSearchSerializer(self.user1).data, response.json()) self.assertIn(UserSearchSerializer(self.user2).data, response.json()) def test_first_name(self): - response = self.client.get(reverse("accounts:search") + "?q=tes", **self.auth_headers) + response = self.client.get( + reverse("accounts:search") + "?q=tes", **self.auth_headers + ) self.assertIn(UserSearchSerializer(self.user1).data, response.json()) self.assertIn(UserSearchSerializer(self.user2).data, response.json()) @@ -301,9 +330,15 @@ def setUp(self): email="test@test.com", ) - Major.objects.create(name="Test Active Major", is_active=True, degree_type="BACHELORS") - Major.objects.create(name="Test Active Major 2", degree_type="PHD", is_active=True) - Major.objects.create(name="Test Active Major 3", degree_type="PROFESSIONAL", is_active=True) + Major.objects.create( + name="Test Active Major", is_active=True, degree_type="BACHELORS" + ) + Major.objects.create( + name="Test Active Major 2", degree_type="PHD", is_active=True + ) + Major.objects.create( + name="Test Active Major 3", degree_type="PROFESSIONAL", is_active=True + ) School.objects.create(name="Test School") School.objects.create(name="Test School 2") @@ -334,7 +369,9 @@ def test_update_major(self): def test_update_school(self): self.client.force_authenticate(user=self.user) - update_data = {"student": {"school": [{"name": "Test School"}, {"name": "Test School 2"}]}} + update_data = { + "student": {"school": [{"name": "Test School"}, {"name": "Test School 2"}]} + } response = self.client.patch(reverse("accounts:me"), update_data, format="json") @@ -411,13 +448,17 @@ def setUp(self): def test_find_user(self): self.client.force_authenticate(user=self.user1) - resp = self.client.get(reverse("accounts:user", kwargs={"username": "student2"})) + resp = self.client.get( + reverse("accounts:user", kwargs={"username": "student2"}) + ) self.assertEqual(resp.status_code, 200, resp.content) self.assertEqual(2, resp.json()["pennid"]) def test_find_user_not_found(self): self.client.force_authenticate(user=self.user1) - resp = self.client.get(reverse("accounts:user", kwargs={"username": "doesnt_exist"})) + resp = self.client.get( + reverse("accounts:user", kwargs={"username": "doesnt_exist"}) + ) self.assertEqual(resp.status_code, 404, resp.content) @@ -488,7 +529,9 @@ def test_profile_pic_upload_too_large(self): reverse("accounts:me-pfp-upload"), { "profile_pic": open( - os.path.join(os.getcwd(), "tests", "accounts", "test_pfp_large.png"), + os.path.join( + os.getcwd(), "tests", "accounts", "test_pfp_large.png" + ), "rb", ) }, @@ -538,11 +581,19 @@ def test_profile_pic_upload_no_user(self): class MajorViewTestCase(TestCase): def setUp(self): - self.major_active_1 = Major.objects.create(name="Test Active Major", is_active=True) - self.major_active_2 = Major.objects.create(name="Test Active Major 2", is_active=True) + self.major_active_1 = Major.objects.create( + name="Test Active Major", is_active=True + ) + self.major_active_2 = Major.objects.create( + name="Test Active Major 2", is_active=True + ) - self.major_inactive_1 = Major.objects.create(name="Test Inactive Major", is_active=False) - self.major_inactive_2 = Major.objects.create(name="Test Inactive Major 2", is_active=False) + self.major_inactive_1 = Major.objects.create( + name="Test Inactive Major", is_active=False + ) + self.major_inactive_2 = Major.objects.create( + name="Test Inactive Major 2", is_active=False + ) self.client = APIClient() self.serializer_active_1 = MajorSerializer(self.major_active_1) @@ -648,18 +699,24 @@ def test_destroy_only_number(self): def test_resend_verification_fail(self): self.client.force_authenticate(user=self.user) response = self.client.post( - reverse("accounts:me-phonenumber-resend-verification", args=[self.number1.id]) + reverse( + "accounts:me-phonenumber-resend-verification", args=[self.number1.id] + ) ) self.assertEqual(400, response.status_code) def test_resend_verification_success(self): # Mark verification code as expired - self.number1.verification_timestamp = timezone.now() - datetime.timedelta(days=1) + self.number1.verification_timestamp = timezone.now() - datetime.timedelta( + days=1 + ) self.number1.save() self.client.force_authenticate(user=self.user) response = self.client.post( - reverse("accounts:me-phonenumber-resend-verification", args=[self.number1.id]) + reverse( + "accounts:me-phonenumber-resend-verification", args=[self.number1.id] + ) ) self.assertEqual(200, response.status_code) @@ -704,7 +761,9 @@ def test_get_queryset(self): def test_destroy_nonprimary(self): self.client.force_authenticate(user=self.user) - response = self.client.delete(reverse("accounts:me-email-detail", args=[self.email2.id])) + response = self.client.delete( + reverse("accounts:me-email-detail", args=[self.email2.id]) + ) self.email1.refresh_from_db() self.email3.refresh_from_db() self.assertTrue(self.email1.primary) @@ -715,7 +774,9 @@ def test_destroy_nonprimary(self): def test_destroy_primary(self): self.client.force_authenticate(user=self.user) - response = self.client.delete(reverse("accounts:me-email-detail", args=[self.email1.id])) + response = self.client.delete( + reverse("accounts:me-email-detail", args=[self.email1.id]) + ) self.email2.refresh_from_db() self.email3.refresh_from_db() self.assertFalse(self.email2.primary) @@ -726,7 +787,9 @@ def test_destroy_primary(self): def test_destroy_only_verified_email(self): self.client.force_authenticate(user=self.user2) - response = self.client.delete(reverse("accounts:me-email-detail", args=[self.email4.id])) + response = self.client.delete( + reverse("accounts:me-email-detail", args=[self.email4.id]) + ) self.email4.refresh_from_db() self.email5.refresh_from_db() self.assertTrue(self.email4.primary) @@ -873,14 +936,18 @@ def test_list_settings(self): # Assert the list is exactly all of user_1's settings with correct fields self.assertEqual( json.loads(response.content), - self.serializer(PrivacySetting.objects.filter(user=self.user_1), many=True).data, + self.serializer( + PrivacySetting.objects.filter(user=self.user_1), many=True + ).data, ) # Assert the list is exactly all of user_2's settings with correct fields self.client.force_authenticate(user=self.user_2) response = self.client.get(reverse("accounts:privacy")) self.assertEqual( json.loads(response.content), - self.serializer(PrivacySetting.objects.filter(user=self.user_2), many=True).data, + self.serializer( + PrivacySetting.objects.filter(user=self.user_2), many=True + ).data, ) def test_update_settings(self): @@ -894,7 +961,9 @@ def test_update_settings(self): format="json", ) test_setting_1 = PrivacySetting.objects.get(id=test_id_1) - self.assertEqual(json.loads(response.content), self.serializer(test_setting_1).data) + self.assertEqual( + json.loads(response.content), self.serializer(test_setting_1).data + ) self.assertFalse(test_setting_1.enabled) # Assert after updating, setting remains to true (settings default to true) @@ -907,5 +976,7 @@ def test_update_settings(self): format="json", ) test_setting_2 = PrivacySetting.objects.get(id=test_id_2) - self.assertEqual(json.loads(response.content), self.serializer(test_setting_2).data) + self.assertEqual( + json.loads(response.content), self.serializer(test_setting_2).data + ) self.assertTrue(test_setting_2.enabled) diff --git a/backend/tests/identity/test_views.py b/backend/tests/identity/test_views.py index d040b23c..32c38f80 100644 --- a/backend/tests/identity/test_views.py +++ b/backend/tests/identity/test_views.py @@ -18,7 +18,9 @@ def setUp(self): self.client = Client() self.Application = get_application_model() self.UserModel = get_user_model() - self.test_user = self.UserModel.objects.create_user(pennid=2, username="bar_user") + self.test_user = self.UserModel.objects.create_user( + pennid=2, username="bar_user" + ) self.application = self.Application( name="Test Application", @@ -31,7 +33,9 @@ def setUp(self): def test_valid_attest(self): app = self.application - auth_encoded = base64.b64encode(f"{app.client_id}:{app.client_secret}".encode("utf-8")) + auth_encoded = base64.b64encode( + f"{app.client_id}:{app.client_secret}".encode("utf-8") + ) auth_headers = { "HTTP_AUTHORIZATION": f"Basic {auth_encoded.decode('utf-8')}", } @@ -137,7 +141,9 @@ def test_refresh_with_access(self): def test_refresh_no_use(self): now = time.time() - token = jwt.JWT(header={"alg": SIGNING_ALG}, claims={"sub": self.urn, "iat": now}) + token = jwt.JWT( + header={"alg": SIGNING_ALG}, claims={"sub": self.urn, "iat": now} + ) token.make_signed_token(self.key) auth_headers = { "HTTP_AUTHORIZATION": f"Bearer {token.serialize()}",