-
-
Notifications
You must be signed in to change notification settings - Fork 168
easy entry to a course from admin course #3103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -40,6 +40,8 @@ use Scalar::Util qw(weaken); | |||||||||||||
| use Mojo::Util qw(b64_encode b64_decode); | ||||||||||||||
| use Math::Random::Secure qw(irand); | ||||||||||||||
|
|
||||||||||||||
| use WeBWorK::CourseEnvironment; | ||||||||||||||
| use WeBWorK::DB; | ||||||||||||||
| use WeBWorK::Debug; | ||||||||||||||
| use WeBWorK::Utils qw(x runtime_use utf8Crypt cryptPassword); | ||||||||||||||
| use WeBWorK::Utils::Logs qw(writeCourseLog); | ||||||||||||||
|
|
@@ -199,7 +201,9 @@ sub verify { | |||||||||||||
| } else { | ||||||||||||||
| $self->write_log_entry("LOGIN FAILED $self->{log_error}") if defined $self->{log_error}; | ||||||||||||||
| $self->maybe_kill_cookie; | ||||||||||||||
| $c->stash(authen_error => $self->{error}) if $self->{error} && $self->{error} =~ /\S/; | ||||||||||||||
| # A failed admin cross-course login attempt should not surface an error to the user. | ||||||||||||||
| $c->stash(authen_error => $self->{error}) | ||||||||||||||
| if $self->{error} && $self->{error} =~ /\S/ && ($self->{credential_source} // '') ne 'admin_cross_course'; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| my $caliper_sensor = Caliper::Sensor->new($c->ce); | ||||||||||||||
|
|
@@ -261,6 +265,8 @@ sub do_verify { | |||||||||||||
|
|
||||||||||||||
| if (defined $self->{login_type} && $self->{login_type} eq 'guest') { | ||||||||||||||
| return $self->verify_practice_user; | ||||||||||||||
| } elsif (($self->{credential_source} // '') eq 'admin_cross_course') { | ||||||||||||||
| return $self->verify_admin_cross_course_user; | ||||||||||||||
| } else { | ||||||||||||||
| return $self->verify_normal_user; | ||||||||||||||
| } | ||||||||||||||
|
|
@@ -378,7 +384,89 @@ sub get_credentials { | |||||||||||||
| return 1; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| return 0; | ||||||||||||||
| return $self->try_admin_cross_course_credentials; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| =head2 try_admin_cross_course_credentials | ||||||||||||||
|
|
||||||||||||||
| This is a last resort used by C<get_credentials> when a request for this course has no cookie or | ||||||||||||||
| C<user> parameter. If C<< $ce->{authen}{admin_cross_course_login} >> is enabled, the admin course | ||||||||||||||
| uses C<session_cookie> session management, and the browser still holds an active session cookie | ||||||||||||||
| for the admin course, this decodes that cookie, confirms the session is still valid, and confirms | ||||||||||||||
| the admin course user has the C<create_and_delete_courses> permission. If all of that succeeds, | ||||||||||||||
| C<user_id> and C<credential_source> are set so that C<verify_admin_cross_course_user> can finish | ||||||||||||||
| authenticating the user into this course. | ||||||||||||||
|
|
||||||||||||||
| =cut | ||||||||||||||
|
|
||||||||||||||
| sub try_admin_cross_course_credentials { | ||||||||||||||
| my $self = shift; | ||||||||||||||
| my $c = $self->{c}; | ||||||||||||||
| my $ce = $c->ce; | ||||||||||||||
|
|
||||||||||||||
| return 0 | ||||||||||||||
| unless $ce->{authen}{admin_cross_course_login} | ||||||||||||||
| && defined $ce->{admin_course_id} | ||||||||||||||
| && $ce->{courseName} ne $ce->{admin_course_id}; | ||||||||||||||
|
|
||||||||||||||
| my ($ce_admin, $db_admin); | ||||||||||||||
| eval { | ||||||||||||||
| $ce_admin = WeBWorK::CourseEnvironment->new({ courseName => $ce->{admin_course_id} }); | ||||||||||||||
| $db_admin = WeBWorK::DB->new($ce_admin); | ||||||||||||||
| }; | ||||||||||||||
| return 0 if $@ || !$db_admin || ($ce_admin->{session_management_via} // '') ne 'session_cookie'; | ||||||||||||||
|
|
||||||||||||||
| # Decode the admin course's session cookie | ||||||||||||||
| my $sessions = $c->app->sessions; | ||||||||||||||
| my $adminCookieName = 'WeBWorKCourseSession.' . $ce->{admin_course_id}; | ||||||||||||||
| my $cookieMethod = $sessions->encrypted ? 'encrypted_cookie' : 'signed_cookie'; | ||||||||||||||
| my $rawValue = $c->$cookieMethod($adminCookieName); | ||||||||||||||
|
Comment on lines
+420
to
+423
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be
Suggested change
Webwork uses |
||||||||||||||
| return 0 unless $rawValue; | ||||||||||||||
| $rawValue =~ y/-/=/; | ||||||||||||||
| my $adminSession = eval { $sessions->deserialize->(b64_decode($rawValue)) }; | ||||||||||||||
| return 0 unless $adminSession; | ||||||||||||||
|
Comment on lines
+426
to
+427
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no need for the
Suggested change
|
||||||||||||||
|
|
||||||||||||||
| my $expiration = $adminSession->{expiration} // $sessions->default_expiration; | ||||||||||||||
| my $expires = delete $adminSession->{expires}; | ||||||||||||||
| return 0 if !$expires && $expiration || defined $expires && $expires <= time; | ||||||||||||||
|
Comment on lines
+429
to
+431
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I forgot to mention this. I included it in the pull request. Most of this code seems to be copied from the
Suggested change
|
||||||||||||||
|
|
||||||||||||||
| my ($adminUserID, $adminKey, $adminTimestamp) = @{$adminSession}{qw(user_id key timestamp)}; | ||||||||||||||
| return 0 unless $adminUserID && $adminKey; | ||||||||||||||
|
|
||||||||||||||
| # Confirm the admin session is still valid | ||||||||||||||
| my $AdminKey = $db_admin->getKey($adminUserID); | ||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do not use Pascal case for variable names. Use camel case. I have been working on removing that sort of thing in the webwork2 code. I recommend using the variable |
||||||||||||||
| return 0 | ||||||||||||||
| unless defined $AdminKey | ||||||||||||||
| && $AdminKey->key eq $adminKey | ||||||||||||||
| && time <= ($adminTimestamp // $AdminKey->timestamp) + $ce_admin->{sessionTimeout}; | ||||||||||||||
|
|
||||||||||||||
| return 0 unless _admin_course_has_create_delete_permission($ce_admin, $db_admin, $adminUserID); | ||||||||||||||
|
|
||||||||||||||
| my $AdminPassword = $db_admin->getPassword($adminUserID); | ||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This also should not be Pascal case. I recommend using |
||||||||||||||
| return 0 unless defined $AdminPassword && $AdminPassword->password =~ /\S/; | ||||||||||||||
|
|
||||||||||||||
| $self->{user_id} = $adminUserID; | ||||||||||||||
| $self->{admin_cross_course_password} = $AdminPassword->password; | ||||||||||||||
| $self->{login_type} = 'normal'; | ||||||||||||||
| $self->{credential_source} = 'admin_cross_course'; | ||||||||||||||
| debug('credential source: "admin_cross_course", user: "', $self->{user_id}, '"'); | ||||||||||||||
| return 1; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| # Mirrors the relevant part of WeBWorK::Authz::hasPermissions for the admin course, since that method requires a | ||||||||||||||
| # full Mojolicious controller bound to the admin course, and here the controller is bound to the course being | ||||||||||||||
| # entered instead. | ||||||||||||||
| sub _admin_course_has_create_delete_permission { | ||||||||||||||
| my ($ce_admin, $db_admin, $user) = @_; | ||||||||||||||
|
|
||||||||||||||
| my $activity_role = $ce_admin->{permissionLevels}{create_and_delete_courses}; | ||||||||||||||
| return 0 unless defined $activity_role && exists $ce_admin->{userRoles}{$activity_role}; | ||||||||||||||
| my $role_permlevel = $ce_admin->{userRoles}{$activity_role}; | ||||||||||||||
|
|
||||||||||||||
| my $PermissionLevel = $db_admin->getPermissionLevel($user); | ||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I realize this variable was copied from |
||||||||||||||
| return 0 unless defined $PermissionLevel && defined $PermissionLevel->permission; | ||||||||||||||
|
|
||||||||||||||
| return $PermissionLevel->permission >= $role_permlevel; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| sub check_user { | ||||||||||||||
|
|
@@ -594,6 +682,37 @@ sub verify_normal_user { | |||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| =head2 verify_admin_cross_course_user | ||||||||||||||
|
|
||||||||||||||
| Finishes authenticating a user identified by C<try_admin_cross_course_credentials>. | ||||||||||||||
| Compares this course's password hash for the user against the admin course's hash | ||||||||||||||
| captured by C<try_admin_cross_course_credentials>. | ||||||||||||||
|
|
||||||||||||||
| =cut | ||||||||||||||
|
|
||||||||||||||
| sub verify_admin_cross_course_user { | ||||||||||||||
| my $self = shift; | ||||||||||||||
| my $c = $self->{c}; | ||||||||||||||
|
|
||||||||||||||
| my $user_id = $self->{user_id}; | ||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no need for a local |
||||||||||||||
|
|
||||||||||||||
| my $coursePassword = $c->db->getPassword($user_id); | ||||||||||||||
| unless (defined $coursePassword | ||||||||||||||
| && $coursePassword->password =~ /\S/ | ||||||||||||||
| && $coursePassword->password eq $self->{admin_cross_course_password}) | ||||||||||||||
| { | ||||||||||||||
| $self->{log_error} = 'admin cross-course login: no matching password for this user in this course'; | ||||||||||||||
| $self->{error} = $c->maketext(GENERIC_ERROR_MESSAGE); | ||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Delete this line. The only case in which this method is called is if the |
||||||||||||||
| return 0; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| return 0 unless $self->validate_user; | ||||||||||||||
|
|
||||||||||||||
| $self->{session_key} = $self->create_session($user_id); | ||||||||||||||
| $self->{initial_login} = 1; | ||||||||||||||
| return 1; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| # Returns 1 if authentication succeeded, returns 0 if required data was present but authentication failed, | ||||||||||||||
| # and returns -1 if the password is missing. | ||||||||||||||
| sub authenticate { | ||||||||||||||
|
|
||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be
and is the reason for the conflict.