From 00f5a18220c3e43b0b4d817efb317b96bbd4c883 Mon Sep 17 00:00:00 2001 From: "robinwilliam.hundt" <robinwilliam.hundt@stud.uni-goettingen.de> Date: Fri, 25 May 2018 18:38:37 +0200 Subject: [PATCH 1/5] Implemented /user/ endpoint Endpoint offers detail endpoint /change_password/ Endpoint offers list endpoint /me/ --- core/serializers/common_serializers.py | 24 ++++++++ core/tests/test_user_account_views.py | 83 ++++++++++++++++++++++++++ core/urls.py | 1 + core/views/common_views.py | 54 +++++++++++++++-- 4 files changed, 158 insertions(+), 4 deletions(-) create mode 100644 core/tests/test_user_account_views.py diff --git a/core/serializers/common_serializers.py b/core/serializers/common_serializers.py index badfb88f..305b2c6c 100644 --- a/core/serializers/common_serializers.py +++ b/core/serializers/common_serializers.py @@ -1,5 +1,9 @@ import logging +import django.contrib.auth.password_validation as validators +from django.core import exceptions +from rest_framework import serializers + from core import models from .generic import DynamicFieldsModelSerializer @@ -39,3 +43,23 @@ class SubmissionTypeSerializer(SubmissionTypeListSerializer): 'description', 'solution', 'programming_language') + + +class UserAccountSerializer(DynamicFieldsModelSerializer): + + def validate(self, data): + password = data.get('password') + + try: + if password is not None: + validators.validate_password(password=password, + user=self.instance) + except exceptions.ValidationError as err: + raise serializers.ValidationError({'password': list(err.messages)}) + return data + + class Meta: + model = models.UserAccount + fields = ('pk', 'username', 'role', 'is_admin', 'password') + read_only_fields = ('pk', 'username', 'role', 'is_admin') + extra_kwargs = {'password': {'write_only': True}} diff --git a/core/tests/test_user_account_views.py b/core/tests/test_user_account_views.py new file mode 100644 index 00000000..705c6d30 --- /dev/null +++ b/core/tests/test_user_account_views.py @@ -0,0 +1,83 @@ +from rest_framework import status +from rest_framework.test import (APIClient, APITestCase) + +from util.factories import GradyUserFactory + + +class TutorReviewerCanChangePasswordTests(APITestCase): + @classmethod + def setUpTestData(cls): + cls.user_factory = GradyUserFactory() + cls.data = { + 'old_password': 'l', + 'new_password': 'p' + } + + def setUp(self): + self.reviewer = self.user_factory.make_reviewer(password='l') + self.tutor1 = self.user_factory.make_tutor(password='l') + self.tutor2 = self.user_factory.make_tutor(password='l') + self.client = APIClient() + + def _change_password(self, changing_user, user_to_change=None, data=None): + if user_to_change is None: + user_to_change = changing_user + if data is None: + data = self.data + + self.client.force_authenticate(user=changing_user) + url = f"/api/user/{user_to_change.pk}/change_password/" + return self.client.patch(url, data=data) + + def test_tutor_needs_to_provide_current_password(self): + response = self._change_password(self.tutor1, + data={'new_password': 'p'}) + self.assertEqual(status.HTTP_401_UNAUTHORIZED, response.status_code) + ret = self.client.login(username=self.tutor1.username, + password='p') + self.assertFalse(ret) + + def test_reviewer_needs_to_provide_current_password_for_self(self): + response = self._change_password(self.reviewer, + data={'new_password': 'p'}) + self.assertEqual(status.HTTP_401_UNAUTHORIZED, response.status_code) + ret = self.client.login(username=self.tutor1.username, + password='p') + self.assertFalse(ret) + + def test_tutor_can_change_own_password(self): + response = self._change_password(self.tutor1) + self.assertEqual(status.HTTP_200_OK, response.status_code) + ret = self.client.login(username=self.tutor1.username, + password='p') + self.assertTrue(ret) + + def test_tutor_cant_change_other_password(self): + response = self._change_password(self.tutor1, self.tutor2) + self.assertEqual(status.HTTP_403_FORBIDDEN, response.status_code) + ret = self.client.login(username=self.tutor2.username, + password='p') + self.assertFalse(ret) + + def test_reviewer_can_change_own_password(self): + response = self._change_password(self.reviewer) + self.assertEqual(status.HTTP_200_OK, response.status_code) + ret = self.client.login(username=self.reviewer.username, + password='p') + self.assertTrue(ret) + + def test_reviewer_can_change_tutor_password(self): + response = self._change_password(self.reviewer, self.tutor1, + data={'new_password': 'p'}) + self.assertEqual(status.HTTP_200_OK, response.status_code) + ret = self.client.login(username=self.tutor1.username, + password='p') + self.assertTrue(ret) + + def test_student_cant_change_password(self): + student = self.user_factory.make_student(password='l') + response = self._change_password(student) + self.assertEqual(status.HTTP_403_FORBIDDEN, response.status_code) + ret = self.client.login(username=student.username, + password='p') + self.assertFalse(ret) diff --git a/core/urls.py b/core/urls.py index 715379d0..6772c660 100644 --- a/core/urls.py +++ b/core/urls.py @@ -18,6 +18,7 @@ router.register('subscription', views.SubscriptionApiViewSet, base_name='subscription') router.register('assignment', views.AssignmentApiViewSet) router.register('statistics', views.StatisticsEndpoint, base_name='statistics') +router.register('user', views.UserAccountViewSet, base_name='user') # regular views that are not viewsets regular_views_urlpatterns = [ diff --git a/core/views/common_views.py b/core/views/common_views.py index 5a28fbc8..c3201c80 100644 --- a/core/views/common_views.py +++ b/core/views/common_views.py @@ -4,9 +4,14 @@ user to be authenticated and most are only accessible by one user group """ import logging from django.conf import settings -from django.db.models import Avg +from django.contrib.auth.hashers import check_password +from django.db.models import Avg, Q +import django.contrib.auth.password_validation as validators +from django.core import exceptions + from rest_framework import generics, mixins, status, viewsets -from rest_framework.decorators import api_view, list_route, throttle_classes +from rest_framework.decorators import (api_view, list_route, throttle_classes, + detail_route) from rest_framework.exceptions import PermissionDenied from rest_framework.permissions import AllowAny from rest_framework.response import Response @@ -18,7 +23,8 @@ from core.permissions import IsReviewer, IsStudent, IsTutorOrReviewer from core.serializers import (ExamSerializer, StudentInfoSerializer, StudentInfoSerializerForListView, SubmissionNoTypeSerializer, SubmissionSerializer, - SubmissionTypeSerializer, TutorSerializer) + SubmissionTypeSerializer, TutorSerializer, + UserAccountSerializer) log = logging.getLogger(__name__) @@ -97,7 +103,7 @@ class TutorApiViewSet( mixins.DestroyModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet): - """ Api endpoint for creating, listing, viewing or deleteing tutors """ + """ Api endpoint for creating, listing, viewing or deleting tutors """ permission_classes = (IsReviewer,) queryset = models.UserAccount.tutors \ .with_feedback_count() \ @@ -165,3 +171,43 @@ class SubmissionViewSet(viewsets.ReadOnlyModelViewSet): return self.queryset.filter( assignments__subscription__owner=self.request.user ) + + +class UserAccountViewSet(viewsets.ReadOnlyModelViewSet): + serializer_class = UserAccountSerializer + queryset = models.UserAccount.objects.all() + + @detail_route(methods=['patch'], permission_classes=(IsTutorOrReviewer, )) + def change_password(self, request, *args, **kwargs): + user = self.get_object() + if request.user != user and not request.user.is_reviewer(): + return Response(status=status.HTTP_403_FORBIDDEN) + old_password = request.data.get('old_password') + + # tutors must always provide their current password + # reviewers must provide their current password when they change + # their own, not if they change the password of a tutor + if (request.user.is_tutor() or + request.user.is_reviewer and request.user == user) \ + and \ + (old_password is None or + not check_password(old_password, user.password)): + return Response(status=status.HTTP_401_UNAUTHORIZED) + + new_password = request.data.get('new_password') + # validate password + try: + if new_password is not None: + validators.validate_password(password=new_password, user=user) + except exceptions.ValidationError as err: + return Response({'new_password': list(err.messages)}, + status=status.HTTP_406_NOT_ACCEPTABLE) + user.set_password(new_password) + user.save() + log.info(f"User {request.user} changed password of {user}") + return Response(status=status.HTTP_200_OK) + + @list_route() + def me(self, request): + serializer = self.get_serializer(request.user) + return Response(serializer.data, status=status.HTTP_200_OK) -- GitLab From d3a2dcf4c0d4f6f8c3988c2ca4332c38632d2472 Mon Sep 17 00:00:00 2001 From: "robinwilliam.hundt" <robinwilliam.hundt@stud.uni-goettingen.de> Date: Fri, 25 May 2018 21:14:54 +0200 Subject: [PATCH 2/5] Users can now change their own passwords in FE --- frontend/src/PasswordChangeDialog.vue | 101 ++++++++++++++++++ frontend/src/api.js | 9 +- frontend/src/components/BaseLayout.vue | 13 ++- frontend/src/components/UserOptions.vue | 45 ++++++++ .../components/student_list/StudentList.vue | 18 +--- .../submission_notes/SubmissionCorrection.vue | 2 +- frontend/src/pages/Login.vue | 6 +- frontend/src/store/modules/authentication.js | 47 ++++---- frontend/src/store/store.js | 2 +- 9 files changed, 197 insertions(+), 46 deletions(-) create mode 100644 frontend/src/PasswordChangeDialog.vue create mode 100644 frontend/src/components/UserOptions.vue diff --git a/frontend/src/PasswordChangeDialog.vue b/frontend/src/PasswordChangeDialog.vue new file mode 100644 index 00000000..c81be2b9 --- /dev/null +++ b/frontend/src/PasswordChangeDialog.vue @@ -0,0 +1,101 @@ +<template> + <v-dialog v-model="show" width="30%"> + <v-card> + <v-card-title class="title">Change your password</v-card-title> + <v-card-text> + <v-form class="mx-4"> + <v-text-field + label="Current password" + type="password" + v-model="currentPassword" + autofocus + required + /> + <v-text-field + label="New password" + type="password" + v-model="newPassword" + required + /> + <v-text-field + label="Repeat new password" + type="password" + v-model="newPasswordRepeated" + :error-messages="errorMessageRepeat" + required + /> + </v-form> + </v-card-text> + <v-card-actions> + <v-btn @click="submitChange" :disabled="!allowChange">Change password</v-btn> + <v-btn @click="$emit('hide')" color="red">Cancel</v-btn> + </v-card-actions> + </v-card> + </v-dialog> +</template> + +<script> + import {mapState} from 'vuex' + import { changePassword } from '@/api' + + export default { + name: 'PasswordChangeDialog', + data () { + return { + show: true, + currentPassword: '', + newPassword: '', + newPasswordRepeated: '' + } + }, + computed: { + ...mapState({ + userPk: state => state.authentication.user.pk + }), + equalNewPasswords () { + return this.newPassword === this.newPasswordRepeated + }, + allowChange () { + return this.equalNewPasswords && !!this.currentPassword + }, + errorMessageRepeat () { + if (!this.equalNewPasswords) { + return 'Repeated new password is different than new one' + } + } + }, + methods: { + submitChange () { + const data = { + old_password: this.currentPassword, + new_password: this.newPassword + } + changePassword(this.userPk, data).then(() => { + this.$notify({ + title: 'Success!', + text: 'Successfully changed password!', + type: 'success' + }) + this.$emit('hide') + }).catch(() => { + this.$notify({ + title: 'Error!', + text: 'Unable to change password', + type: 'error' + }) + }) + } + }, + watch: { + show (val) { + if (!val) { + this.$emit('hide') + } + } + } + } +</script> + +<style scoped> + +</style> diff --git a/frontend/src/api.js b/frontend/src/api.js index d992c3b3..3a54904f 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -14,7 +14,6 @@ function getInstanceBaseUrl () { let ax = axios.create({ baseURL: getInstanceBaseUrl() - // headers: {'Authorization': 'JWT ' + sessionStorage.getItem('token')} }) { let token = sessionStorage.getItem('token') @@ -190,4 +189,12 @@ export async function deactivateAllStudentAccess () { return ax.post('/api/student/deactivate/') } +export async function changePassword (userPk, data) { + return ax.patch(`/api/user/${userPk}/change_password/`, data) +} + +export async function getOwnUser () { + return (await ax.get('/api/user/me/')).data +} + export default ax diff --git a/frontend/src/components/BaseLayout.vue b/frontend/src/components/BaseLayout.vue index 63723520..d49771f6 100644 --- a/frontend/src/components/BaseLayout.vue +++ b/frontend/src/components/BaseLayout.vue @@ -70,7 +70,12 @@ <v-spacer/> <slot name="toolbar-center"/> <div class="toolbar-content"> - <span>{{ userRole }} | {{ username }}</span> + <v-menu bottom offset-y> + <v-btn slot="activator" color="cyan" style="text-transform: none"> + {{ userRole }} | {{ username }} <v-icon>arrow_drop_down</v-icon> + </v-btn> + <user-options/> + </v-menu> </div> <v-btn color="blue darken-1" to="/" @click.native="logout">Logout</v-btn> <slot name="toolbar-right"></slot> @@ -82,15 +87,17 @@ import { mapGetters, mapState } from 'vuex' import {uiMut} from '@/store/modules/ui' import { createComputedGetterSetter } from '@/util/helpers' + import UserOptions from '@/components/UserOptions' export default { name: 'base-layout', + components: {UserOptions}, computed: { ...mapGetters([ 'gradySpeak' ]), ...mapState({ - username: state => state.authentication.username, - userRole: state => state.authentication.userRole + username: state => state.authentication.user.username, + userRole: state => state.authentication.user.role }), darkMode: createComputedGetterSetter({ path: 'ui.darkMode', diff --git a/frontend/src/components/UserOptions.vue b/frontend/src/components/UserOptions.vue new file mode 100644 index 00000000..fd72a71e --- /dev/null +++ b/frontend/src/components/UserOptions.vue @@ -0,0 +1,45 @@ +<template> + <div> + <v-list> + <template v-for="(opt, i) in userOptions"> + <v-list-tile + v-if="opt.condition()" + @click="opt.action" + :key="i" + > + {{opt.display}} + </v-list-tile> + </template> + </v-list> + <component v-if="displayComponent" :is="displayComponent" @hide="hideComponent"/> + </div> +</template> + +<script> + import PasswordChangeDialog from '@/PasswordChangeDialog' + export default { + name: 'UserOptions', + components: {PasswordChangeDialog}, + data () { + return { + displayComponent: null, + userOptions: [ + { + display: 'Change password', + action: () => { this.displayComponent = PasswordChangeDialog }, + condition: () => !this.$store.getters.isStudent + } + ] + } + }, + methods: { + hideComponent () { + this.displayComponent = null + } + } + } +</script> + +<style scoped> + +</style> diff --git a/frontend/src/components/student_list/StudentList.vue b/frontend/src/components/student_list/StudentList.vue index ca2e42f3..9e72c204 100644 --- a/frontend/src/components/student_list/StudentList.vue +++ b/frontend/src/components/student_list/StudentList.vue @@ -58,6 +58,7 @@ <v-btn small round outline class="submission-button" exact + v-if="props.item[type.pk]" :to="{name: 'submission-side-view', params: { studentPk: props.item.pk, submissionPk: props.item[type.pk].pk @@ -66,6 +67,7 @@ > {{props.item[type.pk].score}} </v-btn> + <span v-else>N.A</span> </td> <td style="padding: 0 15px;" @@ -76,16 +78,12 @@ <template slot="expand" slot-scope="props"> <v-card flat> <v-card-text> - <v-btn - outline class="mx-4" - @click="correctStudent(props.item)" - >Correct</v-btn> <ul class="student-info-list"> <li> - Modul: {{props.item.exam}} + <b>Modul:</b> {{props.item.exam}} </li> <li> - MatrikelNr: {{props.item.matrikel_no}} + <b>MatrikelNr:</b> {{props.item.matrikel_no}} </li> </ul> </v-card-text> @@ -165,14 +163,8 @@ }, methods: { ...mapActions([ - 'getStudents', - 'subscribeTo' + 'getStudents' ]), - correctStudent (student) { - this.subscribeTo({type: 'student', key: student.pk}).then(subscription => { - this.$router.push({name: 'subscription', params: {pk: subscription.pk}}) - }) - }, reduceArrToDict (arr, key) { return arr.reduce((acc, curr) => { const keyInDict = curr[key] diff --git a/frontend/src/components/submission_notes/SubmissionCorrection.vue b/frontend/src/components/submission_notes/SubmissionCorrection.vue index 49cdd72f..d8dc2776 100644 --- a/frontend/src/components/submission_notes/SubmissionCorrection.vue +++ b/frontend/src/components/submission_notes/SubmissionCorrection.vue @@ -98,7 +98,7 @@ }, computed: { ...mapState({ - user: state => state.authentication.username, + user: state => state.authentication.user.username, showEditorOnLine: state => state.submissionNotes.ui.showEditorOnLine, selectedComment: state => state.submissionNotes.ui.selectedCommentOnLine, origFeedback: state => state.submissionNotes.origFeedback.feedback_lines, diff --git a/frontend/src/pages/Login.vue b/frontend/src/pages/Login.vue index 13fd519f..6c34d2fd 100644 --- a/frontend/src/pages/Login.vue +++ b/frontend/src/pages/Login.vue @@ -60,7 +60,7 @@ computed: { ...mapState({ msg: state => state.authentication.message, - userRole: state => state.authentication.userRole + userRole: state => state.authentication.user.role }), production () { return process.env.NODE_ENV === 'production' @@ -72,13 +72,13 @@ methods: { ...mapActions([ 'getJWT', - 'getUserRole', + 'getUser', 'getJWTTimeDelta' ]), submit () { this.loading = true this.getJWT(this.credentials).then(() => { - this.getUserRole().then(() => { + this.getUser().then(() => { this.$router.push({name: 'home'}) }) this.getJWTTimeDelta() diff --git a/frontend/src/store/modules/authentication.js b/frontend/src/store/modules/authentication.js index db2d6c60..ce8bbb04 100644 --- a/frontend/src/store/modules/authentication.js +++ b/frontend/src/store/modules/authentication.js @@ -6,10 +6,14 @@ function initialState () { token: sessionStorage.getItem('token'), lastTokenRefreshTry: Date.now(), refreshingToken: false, - username: '', jwtTimeDelta: 0, - userRole: '', - message: '' + message: '', + user: { + pk: '', + username: '', + role: '', + is_admin: '' + } } } @@ -17,8 +21,7 @@ export const authMut = Object.freeze({ SET_MESSAGE: 'SET_MESSAGE', SET_JWT_TOKEN: 'SET_JWT_TOKEN', SET_JWT_TIME_DELTA: 'SET_JWT_TIME_DELTA', - SET_USERNAME: 'SET_USERNAME', - SET_USER_ROLE: 'SET_USER_ROLE', + SET_USER: 'SET_USER', SET_LAST_TOKEN_REFRESH_TRY: 'SET_LAST_TOKEN_REFRESH_TRY', RESET_STATE: 'RESET_STATE', SET_REFRESHING_TOKEN: 'SET_REFRESHING_TOKEN' @@ -31,13 +34,13 @@ const authentication = { return gradySays[Math.floor(Math.random() * gradySays.length)] }, isStudent: state => { - return state.userRole === 'Student' + return state.user.role === 'Student' }, isTutor: state => { - return state.userRole === 'Tutor' + return state.user.role === 'Tutor' }, isReviewer: state => { - return state.userRole === 'Reviewer' + return state.user.role === 'Reviewer' }, isTutorOrReviewer: (state, getters) => { return getters.isTutor || getters.isReviewer @@ -45,29 +48,26 @@ const authentication = { isLoggedIn: state => !!state.token }, mutations: { - [authMut.SET_MESSAGE]: function (state, message) { + [authMut.SET_MESSAGE] (state, message) { state.message = message }, - [authMut.SET_JWT_TOKEN]: function (state, token) { + [authMut.SET_JWT_TOKEN] (state, token) { sessionStorage.setItem('token', token) state.token = token }, - [authMut.SET_JWT_TIME_DELTA]: function (state, timeDelta) { + [authMut.SET_JWT_TIME_DELTA] (state, timeDelta) { state.jwtTimeDelta = timeDelta }, - [authMut.SET_USERNAME]: function (state, username) { - state.username = username - }, - [authMut.SET_USER_ROLE]: function (state, userRole) { - state.userRole = userRole + [authMut.SET_USER] (state, user) { + state.user = user }, - [authMut.SET_REFRESHING_TOKEN]: function (state, refreshing) { + [authMut.SET_REFRESHING_TOKEN] (state, refreshing) { state.refreshingToken = refreshing }, - [authMut.SET_LAST_TOKEN_REFRESH_TRY]: function (state) { + [authMut.SET_LAST_TOKEN_REFRESH_TRY] (state) { state.lastTokenRefreshTry = Date.now() }, - [authMut.RESET_STATE]: function (state) { + [authMut.RESET_STATE] (state) { sessionStorage.setItem('token', '') Object.assign(state, initialState()) } @@ -76,7 +76,6 @@ const authentication = { async getJWT (context, credentials) { try { const token = await api.fetchJWT(credentials) - context.commit(authMut.SET_USERNAME, credentials.username) context.commit(authMut.SET_JWT_TOKEN, token) } catch (error) { let errorMsg @@ -103,12 +102,12 @@ const authentication = { commit(authMut.SET_LAST_TOKEN_REFRESH_TRY) } }, - async getUserRole ({commit}) { + async getUser ({commit}) { try { - const userRole = await api.fetchUserRole() - commit(authMut.SET_USER_ROLE, userRole) + const user = await api.getOwnUser() + commit(authMut.SET_USER, user) } catch (err) { - commit(authMut.SET_MESSAGE, "You've been logged out.") + commit(authMut.SET_MESSAGE, 'Unable to fetch user.') } }, async getJWTTimeDelta ({commit}) { diff --git a/frontend/src/store/store.js b/frontend/src/store/store.js index 28fa292f..0bf79157 100644 --- a/frontend/src/store/store.js +++ b/frontend/src/store/store.js @@ -54,7 +54,7 @@ const store = new Vuex.Store({ // when manually reloading the page paths: Object.keys(initialState()).concat( ['ui', 'studentPage', 'submissionNotes', 'feedbackSearchOptions', 'subscriptions', - 'authentication.username', 'authentication.userRole', 'authentication.jwtTimeDelta', + 'authentication.user', 'authentication.jwtTimeDelta', 'authentication.tokenCreationTime']) }), lastInteraction], -- GitLab From 6b55c364f10ec9c83ebdaa8800ef06362718e597 Mon Sep 17 00:00:00 2001 From: "robinwilliam.hundt" <robinwilliam.hundt@stud.uni-goettingen.de> Date: Sat, 26 May 2018 11:20:40 +0200 Subject: [PATCH 3/5] Changed password in testcases to proper one so ci doesn't fail --- core/tests/test_user_account_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/tests/test_user_account_views.py b/core/tests/test_user_account_views.py index 705c6d30..62bdb8fc 100644 --- a/core/tests/test_user_account_views.py +++ b/core/tests/test_user_account_views.py @@ -10,7 +10,7 @@ class TutorReviewerCanChangePasswordTests(APITestCase): cls.user_factory = GradyUserFactory() cls.data = { 'old_password': 'l', - 'new_password': 'p' + 'new_password': 'chompreviver0.' } def setUp(self): -- GitLab From 33b74d91e23526c9e99a020efbe766d9caad85c9 Mon Sep 17 00:00:00 2001 From: "robinwilliam.hundt" <robinwilliam.hundt@stud.uni-goettingen.de> Date: Sat, 26 May 2018 11:23:51 +0200 Subject: [PATCH 4/5] Remove unused import... --- core/views/common_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/views/common_views.py b/core/views/common_views.py index c3201c80..b4f08200 100644 --- a/core/views/common_views.py +++ b/core/views/common_views.py @@ -5,7 +5,7 @@ import logging from django.conf import settings from django.contrib.auth.hashers import check_password -from django.db.models import Avg, Q +from django.db.models import Avg import django.contrib.auth.password_validation as validators from django.core import exceptions -- GitLab From 75a9f1bbf83d2ebf77f718b1fbac8b71a74802a8 Mon Sep 17 00:00:00 2001 From: "robinwilliam.hundt" <robinwilliam.hundt@stud.uni-goettingen.de> Date: Sat, 26 May 2018 11:31:11 +0200 Subject: [PATCH 5/5] Last minute changes before a talk are fun! Made another mistake with the tests, this commit should fix it. --- core/tests/test_user_account_views.py | 48 +++++++++++++-------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/core/tests/test_user_account_views.py b/core/tests/test_user_account_views.py index 62bdb8fc..bc9e50c0 100644 --- a/core/tests/test_user_account_views.py +++ b/core/tests/test_user_account_views.py @@ -30,54 +30,54 @@ class TutorReviewerCanChangePasswordTests(APITestCase): return self.client.patch(url, data=data) def test_tutor_needs_to_provide_current_password(self): - response = self._change_password(self.tutor1, - data={'new_password': 'p'}) - self.assertEqual(status.HTTP_401_UNAUTHORIZED, response.status_code) + res = self._change_password(self.tutor1, + data={'new_password': 'chompreviver0.'}) + self.assertEqual(status.HTTP_401_UNAUTHORIZED, res.status_code) ret = self.client.login(username=self.tutor1.username, - password='p') + password='chompreviver0.') self.assertFalse(ret) def test_reviewer_needs_to_provide_current_password_for_self(self): - response = self._change_password(self.reviewer, - data={'new_password': 'p'}) - self.assertEqual(status.HTTP_401_UNAUTHORIZED, response.status_code) + res = self._change_password(self.reviewer, + data={'new_password': 'chompreviver0.'}) + self.assertEqual(status.HTTP_401_UNAUTHORIZED, res.status_code) ret = self.client.login(username=self.tutor1.username, - password='p') + password='chompreviver0.') self.assertFalse(ret) def test_tutor_can_change_own_password(self): - response = self._change_password(self.tutor1) - self.assertEqual(status.HTTP_200_OK, response.status_code) + res = self._change_password(self.tutor1) + self.assertEqual(status.HTTP_200_OK, res.status_code) ret = self.client.login(username=self.tutor1.username, - password='p') + password='chompreviver0.') self.assertTrue(ret) def test_tutor_cant_change_other_password(self): - response = self._change_password(self.tutor1, self.tutor2) - self.assertEqual(status.HTTP_403_FORBIDDEN, response.status_code) + res = self._change_password(self.tutor1, self.tutor2) + self.assertEqual(status.HTTP_403_FORBIDDEN, res.status_code) ret = self.client.login(username=self.tutor2.username, - password='p') + password='chompreviver0.') self.assertFalse(ret) def test_reviewer_can_change_own_password(self): - response = self._change_password(self.reviewer) - self.assertEqual(status.HTTP_200_OK, response.status_code) + res = self._change_password(self.reviewer) + self.assertEqual(status.HTTP_200_OK, res.status_code) ret = self.client.login(username=self.reviewer.username, - password='p') + password='chompreviver0.') self.assertTrue(ret) def test_reviewer_can_change_tutor_password(self): - response = self._change_password(self.reviewer, self.tutor1, - data={'new_password': 'p'}) - self.assertEqual(status.HTTP_200_OK, response.status_code) + res = self._change_password(self.reviewer, self.tutor1, + data={'new_password': 'chompreviver0.'}) + self.assertEqual(status.HTTP_200_OK, res.status_code) ret = self.client.login(username=self.tutor1.username, - password='p') + password='chompreviver0.') self.assertTrue(ret) def test_student_cant_change_password(self): student = self.user_factory.make_student(password='l') - response = self._change_password(student) - self.assertEqual(status.HTTP_403_FORBIDDEN, response.status_code) + res = self._change_password(student) + self.assertEqual(status.HTTP_403_FORBIDDEN, res.status_code) ret = self.client.login(username=student.username, - password='p') + password='chompreviver0.') self.assertFalse(ret) -- GitLab