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