Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions conf/permissions.dist.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ db_permissions:

Course:
getCourses:
allowed_roles: ['*']
authenticated: true
getCourse:
allowed_roles: ['*']
authenticated: true
updateCourse:
admin_required: true
addCourse:
Expand All @@ -48,7 +48,7 @@ db_permissions:
getGlobalUsers:
admin_required: true
getGlobalUser:
admin_required: true
allow_self_access: true
checkGlobalUser:
allowed_roles: ['course_admin', 'instructor']
updateGlobalUser:
Expand Down Expand Up @@ -78,6 +78,7 @@ db_permissions:
getGlobalCourseUsers:
allowed_roles: ['course_admin', 'instructor']
getCourseUser:
allow_self_access: true
allowed_roles: ['course_admin', 'instructor']
addCourseUser:
allowed_roles: ['course_admin', 'instructor']
Expand All @@ -99,6 +100,7 @@ db_permissions:
getAllUserSets:
allowed_roles: ['course_admin', 'instructor']
getUserSets:
allow_self_access: true
allowed_roles: ['course_admin', 'instructor']
addUserSet:
allowed_roles: ['course_admin', 'instructor']
Expand Down
4 changes: 2 additions & 2 deletions lib/WeBWorK3/Controller/Permission.pm
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ sub checkPermission ($c) {
} elsif ($perm_db->admin_required && !$user->{is_admin}) {
$permitted = undef;
$msg = 'This route requires admin privileges.';
} elsif (!$course_id && $perm_db->allow_self_access && defined($c->param('user_id'))) {
} elsif (!$course_id && $perm_db->allow_self_access && defined($user_id)) {
# Some routes allow self access, but the course_id is not defined.
$permitted = $user->{user_id} == $c->param('user_id');
$permitted = $user->{user_id} == $user_id;
} elsif ($course_id) {
my $course_user = $c->schema->resultset('User')->getCourseUser(
info => {
Expand Down
20 changes: 14 additions & 6 deletions src/common/models/courses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,12 @@ export class Course extends Model {
*/

export interface ParseableUserCourse {
course_id?: number;
user_id?: number;
course_name?: string;
course_id: number;
user_id: number;
course_name: string;
username?: string;
visible?: boolean;
role?: string;
role: string;
course_dates?: ParseableCourseDates;
}
export class UserCourse extends Model {
Expand All @@ -126,6 +126,13 @@ export class UserCourse extends Model {
static ALL_FIELDS = ['course_id', 'course_name', 'visible', 'course_dates',
'user_id', 'username', 'role'];

static DEFAULT_VALUES = {
course_id: 0,
user_id: 0,
course_name: 'DEFAULT_USER_COURSE',
role: 'unknown',
};

get all_field_names(): string[] {
return UserCourse.ALL_FIELDS;
}
Expand All @@ -134,7 +141,7 @@ export class UserCourse extends Model {
return ['course_dates'];
}

constructor(params: ParseableUserCourse = {}) {
constructor(params: ParseableUserCourse = UserCourse.DEFAULT_VALUES) {
super();
this.set(params);
}
Expand Down Expand Up @@ -171,7 +178,8 @@ export class UserCourse extends Model {
set role(value: string) { this._role = value; }

clone(): UserCourse {
return new UserCourse(this.toObject());
// typescript does not recognize the getters as keys when converting with .toObject()
return new UserCourse(this.toObject() as unknown as ParseableUserCourse);
}

isValid(): boolean {
Expand Down
4 changes: 2 additions & 2 deletions src/common/models/session.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { User } from 'src/common/models/users';
import type { ParseableUser, User } from 'src/common/models/users';
import { parseBoolean } from './parsers';

export interface SessionInfo {
user: User;
user: ParseableUser;
logged_in: boolean;
message: string;
}
Expand Down
7 changes: 7 additions & 0 deletions src/common/views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ export const student_views: Array<ViewInfo> = [
];

export const instructor_views: Array<ViewInfo> = [
{
name: 'Dashboard',
component_name: 'InstructorDashboard',
icon: 'speed',
route: 'dashboard',
sidebars: []
},
{
name: 'Calendar',
component_name: 'Calendar',
Expand Down
6 changes: 3 additions & 3 deletions src/components/common/Login.vue
Original file line number Diff line number Diff line change
Expand Up @@ -56,21 +56,21 @@ const login = async () => {
};
const session_info = await checkPassword(username_info);

if (!session_info.logged_in) {
if (!session_info.logged_in || !session_info.user.user_id) {
message.value = i18n.t('authentication.failure');
} else {
// success
session.updateSessionInfo(session_info);

// permissions require access to user courses and respective roles
await session.fetchUserCourses(session_info.user.user_id);
await session.fetchUserCourses();
await permission_store.fetchRoles();
await permission_store.fetchRoutePermissions();

let forward = localStorage.getItem('afterLogin');
forward ||= (session_info.user.is_admin) ?
'/admin' :
`/users/${session.user.user_id}/courses`;
`/users/${session_info.user.user_id}/courses`;
localStorage.removeItem('afterLogin');
void router.push(forward);
}
Expand Down
51 changes: 19 additions & 32 deletions src/components/common/UserCourses.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,48 +36,35 @@ import { computed } from 'vue';
import { useRouter } from 'vue-router';

import { useSessionStore } from 'src/stores/session';
import { logger } from 'src/boot/logger';

const session = useSessionStore();
const session_store = useSessionStore();
const router = useRouter();

const user = computed(() => session_store.user);

// This is used to simplify the UI.
const course_types = computed(() => [
{ name: 'Student', courses: student_courses.value },
{ name: 'Instrutor', courses: instructor_courses.value }
{ name: 'Student', courses: session_store.user_courses.filter(c => c.role === 'student') },
{ name: 'Instructor', courses: session_store.user_courses.filter(c => c.role === 'instructor') }
]);

const student_courses = computed(() =>
// for some reason on load the user_course.role is undefined.
session.user_courses.filter(user_course => user_course.role === 'student'));

const instructor_courses = computed(() =>
// For some reason on load the user_course.role is undefined.
session.user_courses.filter(user_course => user_course.role === 'instructor')
);
const user = computed(() => session.user);
const switchCourse = (course_id?: number) => {
if (!course_id) {
logger.error('[UserCourses/switchCourse]: the course_id is 0 or undefined.');
return;
}
session_store.setCourse(course_id);

const switchCourse = async (course_id: number) => {
const student_course = student_courses.value.find(c => c.course_id === course_id);
const instructor_course = instructor_courses.value.find(c => c.course_id === course_id);
if (student_course) {
session.setCourse({
course_name: student_course.course_name,
course_id: student_course.course_id,
role: 'student'
});
await router.push({
if (session_store.course.role === 'student') {
void router.push({
name: 'StudentDashboard',
params: { course_id: student_course.course_id }
});
} else if (instructor_course) {
session.setCourse({
course_name: instructor_course.course_name,
course_id: instructor_course.course_id,
role: 'instructor'
params: { course_id }
});
await router.push({
name: 'instructor',
params: { course_id: instructor_course.course_id }
} else if (session_store.course.role === 'instructor') {
void router.push({
name: 'InstructorDashboard',
params: { course_id }
});
}
};
Expand Down
19 changes: 5 additions & 14 deletions src/components/student/Student.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,31 +21,22 @@ const loadStudentSets = async () => {
// Fetch only the current user info.
await user_store.setSessionUser();

logger.debug(`[Student/loadStudenSet]: loading data for course ${session_store.course.course_id}`);
logger.debug(`[Student/loadStudentSets]: loading data for course ${session_store.course.course_id}`);

if (session_store.course.course_id > 0) {
// Fetch all problem sets and user sets
await problem_set_store.fetchProblemSets(session_store.course.course_id);
await problem_set_store.fetchUserSetsForUser({ user_id: session_store.user.user_id });

if (session_store.user.user_id) {
await problem_set_store.fetchUserSetsForUser({ user_id: session_store.user.user_id });
}
}
};

const course_id = parseRouteCourseID(route);
const course = session_store.user_courses.find(c => c.course_id === course_id);
if (course) {
session_store.setCourse({
course_id: course_id,
course_name: course.course_name
});
} else {
logger.warn(`Can't find ${course_id} in ${session_store.user_courses
.map((c) => c.course_id).join(', ')}`);
}
session_store.setCourse(course_id);
await loadStudentSets();

watch(() => session_store.course.course_id, async () => {

await loadStudentSets();
});
</script>
4 changes: 2 additions & 2 deletions src/layouts/MainLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
</q-drawer>

<q-page-container>
<Suspense>
<suspense>
<router-view />
</Suspense>
</suspense>
</q-page-container>

<!-- this only opens the first sidebar in the list
Expand Down
34 changes: 18 additions & 16 deletions src/layouts/MenuBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
<q-list>
<template v-for="course in user_courses" :key="course.course_id">
<q-item clickable v-close-popup
@click="changeCourse(course.course_id, course.course_name)">
@click="changeCourse(course.course_id)">
<q-item-section>
<q-item-label>{{course.course_name}}</q-item-label>
</q-item-section>
Expand Down Expand Up @@ -64,13 +64,16 @@
<script setup lang="ts">
import { computed, defineEmits, ref } from 'vue';
import { useRouter } from 'vue-router';
import { endSession } from 'src/common/api-requests/session';
import { useI18n } from 'vue-i18n';

import { setI18nLanguage } from 'boot/i18n';
import { logger } from 'src/boot/logger';

import { endSession } from 'src/common/api-requests/session';
import { useSessionStore } from 'src/stores/session';
import type { CourseSettingInfo } from 'src/common/models/settings';
import { useSettingsStore } from 'src/stores/settings';
import { logger } from 'src/boot/logger';

import type { CourseSettingInfo } from 'src/common/models/settings';

defineEmits(['toggle-menu', 'toggle-sidebar']);
const session = useSessionStore();
Expand All @@ -84,18 +87,17 @@ const full_name = computed(() => session.full_name);
const user_courses = computed(() =>
session.user_courses.filter(course => course.course_name !== current_course_name.value));

const changeCourse = (course_id: number, course_name: string) => {
const new_course = session.user_courses.find(course => course.course_name === course_name);
const changeCourse = (course_id: number) => {
logger.debug(`[MenuBar/changeCourse]: changing the course to #${course_id}`);
session.setCourse(course_id);

if (new_course != undefined) {
router.push(`/courses/${new_course.course_id}`).then(() => {
session.setCourse({
course_name: new_course.course_name,
course_id: new_course.course_id
});
}).catch(() => {
logger.error('[MenuBar/changeCourse]: Error occurred.');
});
// This sets the path to the instructor or student dashboard.
// This only works currently for roles of student/instructor. We'll need to think about
// the UI for other roles.
if (!session.course.role || session.course.role == 'unknown') {
logger.error(`[MenuBar/changeCourse]: the role is not defined for course #${course_id}`);
} else {
void router.push(`/courses/${course_id}/${session.course.role}`);
}
};

Expand All @@ -106,6 +108,6 @@ const availableLocales = computed(() =>
const logout = async () => {
await endSession();
void session.logout();
void router.push('/login');
void router.push({ name: 'login' });
};
</script>
8 changes: 6 additions & 2 deletions src/layouts/MenuSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,25 @@
import { defineComponent, ref, computed } from 'vue';
import { instructor_views, admin_views, student_views, ViewInfo } from 'src/common/views';
import { useRouter, useRoute } from 'vue-router';
import { useSessionStore } from 'src/stores/session';

export default defineComponent({
name: 'MenuSidebar',
setup() {
const route = useRoute();
const router = useRouter();
const session = useSessionStore();
const sidebar_open = ref<boolean>(false);
return {
sidebar_open,
views: computed(() =>
/^\/admin/.exec(route.path)
? admin_views
: /^\/courses\/\d+\/instructor/.exec(route.path)
: session.course.role === 'instructor'
? instructor_views
: student_views
: session.course.role === 'student'
? student_views
: []
),
changeView: (view: ViewInfo) => {
void router.push({ name: view.component_name, params: route.params });
Expand Down
8 changes: 8 additions & 0 deletions src/pages/instructor/Dashboard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<template>
<div class="q-pa-md">
<div class="text-h6">Instructor Dashboard</div>
<p>This is the instructor landing page/dashboard. To get started select a tool
from the list on the left.
</p>
</div>
</template>
Loading