[system] / trunk / webwork-modperl / lib / WeBWorK / Authz.pm Repository:
ViewVC logotype

Annotation of /trunk/webwork-modperl/lib/WeBWorK/Authz.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 5779 - (view) (download) (as text)

1 : sh002i 455 ################################################################################
2 : sh002i 1663 # WeBWorK Online Homework Delivery System
3 : sh002i 5319 # Copyright © 2000-2007 The WeBWorK Project, http://openwebwork.sf.net/
4 : glarose 5779 # $CVSHeader: webwork2/lib/WeBWorK/Authz.pm,v 1.36 2007/08/13 22:59:54 sh002i Exp $
5 : sh002i 1663 #
6 :     # This program is free software; you can redistribute it and/or modify it under
7 :     # the terms of either: (a) the GNU General Public License as published by the
8 :     # Free Software Foundation; either version 2, or (at your option) any later
9 :     # version, or (b) the "Artistic License" which comes with this package.
10 :     #
11 :     # This program is distributed in the hope that it will be useful, but WITHOUT
12 :     # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 :     # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the
14 :     # Artistic License for more details.
15 : sh002i 455 ################################################################################
16 :    
17 : malsyned 390 package WeBWorK::Authz;
18 :    
19 : sh002i 455 =head1 NAME
20 :    
21 :     WeBWorK::Authz - check user permissions.
22 :    
23 : sh002i 3058 =head1 SYNOPSIS
24 :    
25 :     # create new authorizer -- $r is a WeBWorK::Request object.
26 :     my $authz = new WeBWorK::Authz($r);
27 :    
28 :     # tell authorizer to cache permission level of user spammy.
29 :     $authz->setCachedUser("spammy");
30 :    
31 :     # this call will use the cached data.
32 :     if ($authz->hasPermissions("spammy", "eat_breakfast")) {
33 :     eat_breakfast();
34 :     }
35 :    
36 :     # this call will not use the cached data, and will cause a database lookup.
37 :     if ($authz->hasPermissions("hammy", "go_to_bed")) {
38 :     go_to_bed();
39 :     }
40 :    
41 :     =head1 DESCRIPTION
42 :    
43 :     WeBWorK::Authen determines if a user is authorized to perform a specific
44 :     activity, based on the user's PermissionLevel record in the WeBWorK database and
45 :     the contents of the %permissionLevels hash in the course environment.
46 :    
47 :     =head2 Format of the %permissionLevels hash
48 :    
49 :     %permissionLevels maps text strings describing activities to numeric permission
50 :     levels. The definitive list of activities is contained in the default version of
51 :     %permissionLevels, in the file F<conf/global.conf.dist>.
52 :    
53 :     A user is able to engage in an activity if their permission level is greater
54 :     than or equal to the level associated with the activity. If the level associated
55 :     with an activity is undefiend, then no user is permitted to perform the
56 :     activity, regardless of their permission level.
57 :    
58 : sh002i 455 =cut
59 :    
60 : malsyned 441 use strict;
61 :     use warnings;
62 : sh002i 3672 use Carp qw/croak/;
63 : glarose 4907 # FIXME SET: set-level auth add
64 :     use WeBWorK::Utils qw(before after between);
65 :     use WeBWorK::Authen::Proctor;
66 :     use Net::IP;
67 : malsyned 390
68 : sh002i 3058 ################################################################################
69 : gage 3053
70 : sh002i 3058 =head1 CONSTRUCTOR
71 :    
72 :     =over
73 :    
74 :     =item WeBWorK::Authz->new($r)
75 :    
76 :     Creates a new authorizer instance. $r is a WeBWorK::Request object. It must
77 :     already have its C<ce> and C<db> fields set.
78 :    
79 :     =cut
80 :    
81 : sh002i 1885 sub new {
82 : sh002i 3058 my ($invocant, $r) = @_;
83 : malsyned 390 my $class = ref($invocant) || $invocant;
84 : sh002i 1885 my $self = {
85 :     r => $r,
86 :     };
87 : gage 3053
88 : malsyned 390 bless $self, $class;
89 :     return $self;
90 :     }
91 :    
92 : sh002i 3058 =back
93 : gage 3053
94 : sh002i 3058 =cut
95 :    
96 :     ################################################################################
97 :    
98 :     =head1 METHODS
99 :    
100 :     =over
101 :    
102 :     =item setCachedUser($userID)
103 :    
104 :     Caches the PermissionLevel of the user $userID in an existing authorizer. If a
105 :     user's PermissionLevel is cached, it will be used whenever hasPermissions() is
106 :     called on the same user. Only one user can be cached at a time. This is used by
107 :     WeBWorK to cache the "real" user.
108 :    
109 :     =cut
110 :    
111 :     sub setCachedUser {
112 :     my ($self, $userID) = @_;
113 :     my $r = $self->{r};
114 :     my $db = $r->db;
115 :    
116 :     delete $self->{userID};
117 :     delete $self->{PermissionLevel};
118 :    
119 :     if (defined $userID) {
120 :     $self->{userID} = $userID;
121 :     my $PermissionLevel = $db->getPermissionLevel($userID); # checked
122 :     if (defined $PermissionLevel) {
123 :     # store permission level record in database to avoid later database calls
124 :     $self->{PermissionLevel} = $PermissionLevel;
125 :     }
126 :     } else {
127 : sh002i 4002 warn "setCachedUser() called with userID undefined.";
128 : sh002i 3058 }
129 :     }
130 :    
131 :     =item hasPermissions($userID, $activity)
132 :    
133 :     Checks the %permissionLevels hash in the course environment to determine if the
134 :     user $userID has permission to engage in the activity $activity. If the user's
135 :     permission level is greater than or equal to the level associated with $activty,
136 :     a true value is returned. Otherwise, a false value is returned.
137 :    
138 :     If $userID has been cached using the setCachedUser() call, the cached data is
139 :     used. Otherwise, the user's PermissionLevel is looked up in the WeBWorK
140 :     database.
141 :    
142 :     If the user does not have a PermissionLevel record, the permission level record
143 :     is empty, or the activity does not appear in %permissionLevels, hasPermissions()
144 :     assumes that the user does not have permission.
145 :    
146 :     =cut
147 :    
148 : malsyned 676 # This currently only uses two of it's arguments, but it accepts any number, in
149 :     # case in the future calculating certain permissions requires more information.
150 : malsyned 390 sub hasPermissions {
151 : sh002i 3599 if (@_ != 3) {
152 :     shift @_; # get rid of self
153 :     my $nargs = @_;
154 : sh002i 3672 croak "hasPermissions called with $nargs arguments instead of the expected 2: '@_'"
155 : sh002i 3599 }
156 : sh002i 3597
157 : sh002i 3058 my ($self, $userID, $activity) = @_;
158 :     my $r = $self->{r};
159 :     my $ce = $r->ce;
160 :     my $db = $r->db;
161 :    
162 : sh002i 3834 # this may need to be changed if we get other permission level data sources
163 :     return 0 unless defined $db;
164 :    
165 :     # this may need to be changed if we want to control what unauthenticated users
166 :     # can do with the permissions system
167 :     return 0 unless defined $userID and $userID ne "";
168 :    
169 : sh002i 3058 my $PermissionLevel;
170 :    
171 :     my $cachedUserID = $self->{userID};
172 :     if (defined $cachedUserID and $cachedUserID ne "" and $cachedUserID eq $userID) {
173 :     # this is the same user -- we can skip the database call
174 :     $PermissionLevel = $self->{PermissionLevel};
175 :     } else {
176 :     # a different user, or no user was defined before
177 : sh002i 3672 #my $prettyCachedUserID = defined $cachedUserID ? "'$cachedUserID'" : "undefined";
178 : sh002i 3058 #warn "hasPermissions called with user '$userID', but cached user is $prettyCachedUserID. Accessing database.\n";
179 :     $PermissionLevel = $db->getPermissionLevel($userID); # checked
180 :     }
181 :    
182 :     my $permission_level;
183 :    
184 :     if (defined $PermissionLevel) {
185 :     $permission_level = $PermissionLevel->permission;
186 :     } else {
187 :     # uh, oh. this user has no permission level record!
188 : sh002i 4002 warn "User '$userID' has no PermissionLevel record -- assuming no permission.";
189 : sh002i 3058 return 0;
190 :     }
191 :    
192 :     unless (defined $permission_level and $permission_level ne "") {
193 : sh002i 4002 warn "User '$userID' has empty permission level -- assuming no permission.";
194 : sh002i 3058 return 0;
195 :     }
196 :    
197 : sh002i 3672 my $userRoles = $ce->{userRoles};
198 : sh002i 3058 my $permissionLevels = $ce->{permissionLevels};
199 : sh002i 3672
200 : sh002i 2505 if (exists $permissionLevels->{$activity}) {
201 : sh002i 3672 my $activity_role = $permissionLevels->{$activity};
202 :     if (defined $activity_role) {
203 :     if (exists $userRoles->{$activity_role}) {
204 :     my $role_permlevel = $userRoles->{$activity_role};
205 :     if (defined $role_permlevel) {
206 :     return $permission_level >= $role_permlevel;
207 :     } else {
208 : sh002i 4002 warn "Role '$activity_role' has undefined permisison level -- assuming no permission.";
209 : sh002i 3672 return 0;
210 :     }
211 :     } else {
212 : sh002i 4002 warn "Role '$activity_role' for activity '$activity' not found in \%userRoles -- assuming no permission.";
213 : sh002i 3672 return 0;
214 :     }
215 : sh002i 2505 } else {
216 : sh002i 3672 return 0; # undefiend $activity_role, no one has permission to perform $activity
217 : sh002i 2505 }
218 : sh002i 817 } else {
219 : sh002i 4002 warn "Activity '$activity' not found in \%permissionLevels -- assuming no permission.";
220 : sh002i 3058 return 0;
221 : sh002i 817 }
222 : malsyned 390 }
223 :    
224 : glarose 4907 #### set-level authorization routines
225 :    
226 :     sub checkSet {
227 :     my $self = shift;
228 :     my $r = $self->{r};
229 :     my $ce = $r->ce;
230 :     my $db = $r->db;
231 :     my $urlPath = $r->urlpath;
232 :    
233 :     my $node_name = $urlPath->type;
234 :    
235 :     # first check to see if we have to worried about set-level access
236 :     # restrictions
237 :     return 0 unless (grep {/^$node_name$/}
238 :     (qw(problem_list problem_detail gateway_quiz
239 : glarose 4909 proctored_gateway_quiz)));
240 : glarose 4907
241 :     # to check set restrictions we need a set and a user
242 :     my $setName = $urlPath->arg("setID");
243 :     my $userName = $r->param("user");
244 :     my $effectiveUserName = $r->param("effectiveUser");
245 :    
246 : glarose 4909 # if there is no input userName, then the content generator will
247 :     # be forcing a login, so just bail
248 :     return 0 if ( ! $userName || ! $effectiveUserName );
249 :    
250 : glarose 4918 # do we have a cached set that we can use?
251 :     my $set = $self->{merged_set};
252 :    
253 : glarose 4907 if ( $setName =~ /,v(\d+)$/ ) {
254 :     my $verNum = $1;
255 :     $setName =~ s/,v\d+$//;
256 : glarose 4918
257 :     if ( $set && $set->set_id eq $setName &&
258 :     $set->user_id eq $effectiveUserName &&
259 :     $set->version_id eq $verNum ) {
260 :     # then we can just use this set and skip the rest
261 :    
262 : glarose 5779 } elsif ( $setName eq 'Undefined_Set' and
263 :     $self->hasPermissions($userName, "access_instructor_tools") ) {
264 :     # this is the case of previewing a problem
265 :     # from a 'try it' link
266 :     return 0;
267 : glarose 4907 } else {
268 : glarose 4918 if ($db->existsSetVersion($effectiveUserName,$setName,$verNum)) {
269 :     $set = $db->getMergedSetVersion($effectiveUserName,$setName,$verNum);
270 :     } else {
271 :     return "Requested version ($verNum) of set " .
272 :     "'$setName' is not assigned to user " .
273 :     "$effectiveUserName.";
274 :     }
275 : glarose 4907 }
276 :     if ( ! $set ) {
277 :     return "Requested set '$setName' could not be found " .
278 : glarose 4909 "in the database for user $effectiveUserName.";
279 : glarose 4907 }
280 :     } else {
281 : glarose 4918
282 :     if ( $set && $set->set_id eq $setName &&
283 :     $set->user_id eq $effectiveUserName ) {
284 :     # then we can just use this set, and skip the rest
285 :    
286 : glarose 4907 } else {
287 : glarose 4918 if ( $db->existsUserSet($effectiveUserName,$setName) ) {
288 :     $set = $db->getMergedSet($effectiveUserName,$setName);
289 : jj 5263 } elsif ( $setName eq 'Undefined_Set' and
290 :     $self->hasPermissions($userName, "access_instructor_tools") ) {
291 : glarose 5248 # this is the weird case of the library
292 :     # browser, when we don't actually have
293 : jj 5263 # a set to look at, but this only happens among
294 :     # instructor tool users.
295 : glarose 5248 return 0;
296 : glarose 4918 } else {
297 :     return "Requested set '$setName' is not " .
298 :     "assigned to user $effectiveUserName.";
299 :     }
300 : glarose 4907 }
301 :     if ( ! $set ) {
302 :     return "Requested set '$setName' could not be found " .
303 : glarose 4909 "in the database for user $effectiveUserName.";
304 : glarose 4907 }
305 :     }
306 :     # cache the set for future use as needed. this should probably
307 :     # be more sophisticated than this
308 :     $self->{merged_set} = $set;
309 :    
310 :     # now we know that the set is assigned to the appropriate user;
311 :     # check to see if we're trying to access a set that's not open
312 :     if ( before($set->open_date) &&
313 : glarose 4909 ! $self->hasPermissions($userName, "view_unopened_sets") ) {
314 : glarose 4907 return "Requested set '$setName' is not yet open.";
315 :     }
316 :    
317 :     # also check to make sure that the set is published, or that we're
318 :     # allowed to view unpublished setes
319 :     # (do we need to worry about published not being set at this point?)
320 :     my $published = ( $set && $set->published ne '0' &&
321 :     $set->published ne '1' ) ? 1 : $set->published;
322 :     if ( ! $published &&
323 : glarose 4909 ! $self->hasPermissions($userName, "view_unpublished_sets") ) {
324 : glarose 4907 return "Requested set '$setName' is not available yet.";
325 :     }
326 :    
327 :     # check to be sure that gateways are being sent to the correct
328 :     # content generator
329 :     if (defined($set->assignment_type) &&
330 :     $set->assignment_type =~ /gateway/ &&
331 :     ($node_name eq 'problem_list' || $node_name eq 'problem_detail')) {
332 :     return "Requested set '$setName' is a test/quiz assignment " .
333 :     "but the regular homework assignment content " .
334 : glarose 4909 "generator $node_name was called. Try re-entering " .
335 :     "the set from the problem sets listing page.";
336 : glarose 4911 } elsif ( (! defined($set->assignment_type) ||
337 :     $set->assignment_type eq 'homework') &&
338 :     $node_name =~ /gateway/ ) {
339 :     return "Requested set '$setName' is a homework assignment " .
340 :     "but the gateway/quiz content " .
341 :     "generator $node_name was called. Try re-entering " .
342 :     "the set from the problem sets listing page.";
343 : glarose 4907 }
344 : glarose 4911
345 : glarose 4907 # and check that if we're entering a proctored assignment that we
346 :     # have a valid proctor login; this is necessary to make sure that
347 :     # someone doesn't use the unproctored url path to obtain access
348 :     # to a proctored assignment.
349 :     if (defined($set->assignment_type) &&
350 :     $set->assignment_type =~ /proctored/ &&
351 :     ! WeBWorK::Authen::Proctor->new($r,$ce,$db)->verify() ) {
352 :     return "Requested set '$setName' is a proctored test/quiz " .
353 :     "assignment, but no valid proctor authorization " .
354 :     "has been obtained.";
355 :     }
356 :    
357 :     # and whether there are ip restrictions that we need to check
358 : glarose 4909 my $badIP = $self->invalidIPAddress($set);
359 :     return $badIP if $badIP;
360 : glarose 4907
361 : glarose 4909 return 0;
362 :     }
363 : glarose 4907
364 : glarose 4909 sub invalidIPAddress {
365 :     # this exists as a separate routine because we need to check multiple
366 :     # sets in Hardcopy; having this routine to check the set allows us to do
367 :     # that for all sets individually there.
368 : glarose 4907
369 : glarose 4909 my $self = shift;
370 :     my $set = shift;
371 : glarose 4907
372 : glarose 4909 my $r = $self->{r};
373 :     my $db = $r->db;
374 :     my $urlPath = $r->urlpath;
375 : glarose 4926 # my $setName = $urlPath->arg("setID"); # not always defined
376 :     my $setName = $set->set_id;
377 : glarose 4909 my $userName = $r->param("user");
378 :     my $effectiveUserName = $r->param("effectiveUser");
379 : glarose 4907
380 : glarose 5265 return 0 if ($set->restrict_ip eq '' || $set->restrict_ip eq 'No' ||
381 : glarose 4909 $self->hasPermissions($userName,'view_ip_restricted_sets'));
382 :    
383 :     my $clientIP = new Net::IP($r->connection->remote_ip);
384 : glarose 4916 # make sure that we're using the non-versioned set name
385 :     $setName =~ s/,v\d+$//;
386 : glarose 4909
387 :     my $restrictType = $set->restrict_ip;
388 : glarose 4916 my @restrictLocations = $db->getAllMergedSetLocations($effectiveUserName,$setName);
389 : glarose 4909 my @locationIDs = ( map {$_->location_id} @restrictLocations );
390 :     my @restrictAddresses = ( map {$db->listLocationAddresses($_)} @locationIDs );
391 :    
392 : glarose 4918 # if there are no addresses in the locations, return an error that
393 :     # says this
394 :     return "Client ip address " . $clientIP->ip() . " is not allowed to " .
395 :     "work this assignment, because the assignment has ip address " .
396 :     "restrictions and there are no allowed locations associated " .
397 :     "with the restriction. Contact your professor to have this " .
398 :     "problem resolved." if ( ! @restrictAddresses );
399 :    
400 : glarose 4909 # build a set of IP objects to match against
401 :     my @restrictIPs = ( map {new Net::IP($_)} @restrictAddresses );
402 :    
403 :     # and check the clientAddress against these: is $clientIP
404 :     # in @restrictIPs?
405 :     my $inRestrict = 0;
406 :     foreach my $rIP ( @restrictIPs ) {
407 :     if ($rIP->overlaps($clientIP) == $IP_B_IN_A_OVERLAP ||
408 :     $rIP->overlaps($clientIP) == $IP_IDENTICAL) {
409 : glarose 4916 $inRestrict = $rIP->ip();
410 : glarose 4909 last;
411 : glarose 4907 }
412 :     }
413 : glarose 4909
414 : glarose 4918 # this is slightly complicated by having to check relax_restrict_ip
415 :     my $badIP = '';
416 : glarose 4909 if ( $restrictType eq 'RestrictTo' && ! $inRestrict ) {
417 : glarose 4918 $badIP = "Client ip address " . $clientIP->ip() .
418 : glarose 4909 " is not in the list of addresses from " .
419 :     "which this assignment may be worked.";
420 :     } elsif ( $restrictType eq 'DenyFrom' && $inRestrict ) {
421 : glarose 4918 $badIP = "Client ip address " . $clientIP->ip() .
422 : glarose 4909 " is in the list of addresses from " .
423 :     "which this assignment may not be worked.";
424 :     } else {
425 :     return 0;
426 :     }
427 : glarose 4918
428 :     # if we're here, we failed the IP check, and so need to consider
429 :     # if ip restrictions were relaxed. the set we were passed in
430 :     # is either the merged userset or the merged versioned userset,
431 :     # depending on whether the set is versioned or not
432 :    
433 :     my $relaxRestrict = $set->relax_restrict_ip;
434 :     return $badIP if ( $relaxRestrict eq 'No' );
435 :    
436 :     if ( $set->assignment_type =~ /gateway/ ) {
437 :     if ( $relaxRestrict eq 'AfterAnswerDate' ) {
438 :     # in this case we need to go and get the userset,
439 :     # not the versioned set (which we already have)
440 :     # drat!
441 :     my $userset = $db->getMergedSet($set->user_id,$setName);
442 :     return( ! $userset || before($userset->answer_date)
443 :     ? $badIP : 0 );
444 :     } else {
445 :     # this is easier; just look at the current answer date
446 :     return( before($set->answer_date) ? $badIP : 0 );
447 :     }
448 :     } else {
449 :     # the set isn't versioned, so assume that $relaxRestrict
450 :     # is 'AfterAnswerDate', regardless of what it actually
451 :     # is; 'AfterVersionAnswerDate' doesn't make sense in
452 :     # this case
453 :     return( before($set->answer_date) ? $badIP : 0 );
454 :     }
455 : glarose 4907 }
456 :    
457 : sh002i 3058 =back
458 :    
459 :     =cut
460 :    
461 :     =head1 AUTHOR
462 :    
463 :     Written by Dennis Lambe, malsyned at math.rochester.edu. Modified by Sam
464 :     Hathaway, sh002i at math.rochester.edu.
465 :    
466 :     =cut
467 :    
468 : malsyned 390 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9