[system] / branches / rel-2-4-patches / webwork2 / lib / WeBWorK / Authz.pm Repository:
ViewVC logotype

Diff of /branches/rel-2-4-patches/webwork2/lib/WeBWorK/Authz.pm

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

trunk/webwork2/lib/WeBWorK/Authz.pm Revision 455 branches/rel-2-4-dev/webwork2/lib/WeBWorK/Authz.pm Revision 5266
1################################################################################ 1################################################################################
2# WeBWorK mod_perl (c) 1995-2002 WeBWorK Team, Univeristy of Rochester 2# WeBWorK Online Homework Delivery System
3# $Id$ 3# Copyright © 2000-2006 The WeBWorK Project, http://openwebwork.sf.net/
4# $CVSHeader$
5#
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.
4################################################################################ 15################################################################################
5 16
6package WeBWorK::Authz; 17package WeBWorK::Authz;
7 18
8=head1 NAME 19=head1 NAME
9 20
10WeBWorK::Authz - check user permissions. 21WeBWorK::Authz - check user permissions.
22
23=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
43WeBWorK::Authen determines if a user is authorized to perform a specific
44activity, based on the user's PermissionLevel record in the WeBWorK database and
45the 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
50levels. The definitive list of activities is contained in the default version of
51%permissionLevels, in the file F<conf/global.conf.dist>.
52
53A user is able to engage in an activity if their permission level is greater
54than or equal to the level associated with the activity. If the level associated
55with an activity is undefiend, then no user is permitted to perform the
56activity, regardless of their permission level.
11 57
12=cut 58=cut
13 59
14use strict; 60use strict;
15use warnings; 61use warnings;
16use WeBWorK::DB::Auth; 62use Carp qw/croak/;
63# FIXME SET: set-level auth add
64use WeBWorK::Utils qw(before after between);
65use WeBWorK::Authen::Proctor;
66use Net::IP;
17 67
68################################################################################
69
70=head1 CONSTRUCTOR
71
72=over
73
74=item WeBWorK::Authz->new($r)
75
76Creates a new authorizer instance. $r is a WeBWorK::Request object. It must
77already have its C<ce> and C<db> fields set.
78
79=cut
80
18sub new($$$) { 81sub new {
19 my $invocant = shift; 82 my ($invocant, $r) = @_;
20 my $class = ref($invocant) || $invocant; 83 my $class = ref($invocant) || $invocant;
21 my $self = {}; 84 my $self = {
22 ($self->{r}, $self->{courseEnvironment}) = @_; 85 r => $r,
86 };
87
23 bless $self, $class; 88 bless $self, $class;
24 return $self; 89 return $self;
25} 90}
26 91
92=back
93
94=cut
95
96################################################################################
97
98=head1 METHODS
99
100=over
101
102=item setCachedUser($userID)
103
104Caches the PermissionLevel of the user $userID in an existing authorizer. If a
105user's PermissionLevel is cached, it will be used whenever hasPermissions() is
106called on the same user. Only one user can be cached at a time. This is used by
107WeBWorK to cache the "real" user.
108
109=cut
110
111sub 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 warn "setCachedUser() called with userID undefined.";
128 }
129}
130
131=item hasPermissions($userID, $activity)
132
133Checks the %permissionLevels hash in the course environment to determine if the
134user $userID has permission to engage in the activity $activity. If the user's
135permission level is greater than or equal to the level associated with $activty,
136a true value is returned. Otherwise, a false value is returned.
137
138If $userID has been cached using the setCachedUser() call, the cached data is
139used. Otherwise, the user's PermissionLevel is looked up in the WeBWorK
140database.
141
142If the user does not have a PermissionLevel record, the permission level record
143is empty, or the activity does not appear in %permissionLevels, hasPermissions()
144assumes that the user does not have permission.
145
146=cut
147
148# 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.
27sub hasPermissions { 150sub hasPermissions {
151 if (@_ != 3) {
152 shift @_; # get rid of self
153 my $nargs = @_;
154 croak "hasPermissions called with $nargs arguments instead of the expected 2: '@_'"
155 }
156
28 my ($self, $user, $activity) = @_; 157 my ($self, $userID, $activity) = @_;
29 my $r = $self->{r}; 158 my $r = $self->{r};
30 my $courseEnvironment = $self->{courseEnvironment}; 159 my $ce = $r->ce;
31 my $permission_hash = $courseEnvironment->{permission_hash}; 160 my $db = $r->db;
32 my $auth = WeBWorK::DB::Auth->new($courseEnvironment);
33 161
34 my $permissionLevel = $auth->getPermissions($user); 162 # this may need to be changed if we get other permission level data sources
35 if ($permissionLevel >= $permission_hash->{$activity}) { 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 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 #my $prettyCachedUserID = defined $cachedUserID ? "'$cachedUserID'" : "undefined";
178 #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 warn "User '$userID' has no PermissionLevel record -- assuming no permission.";
36 return 1; 189 return 0;
37 } else {return 0;} 190 }
191
192 unless (defined $permission_level and $permission_level ne "") {
193 warn "User '$userID' has empty permission level -- assuming no permission.";
194 return 0;
195 }
196
197 my $userRoles = $ce->{userRoles};
198 my $permissionLevels = $ce->{permissionLevels};
199
200 if (exists $permissionLevels->{$activity}) {
201 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 warn "Role '$activity_role' has undefined permisison level -- assuming no permission.";
209 return 0;
210 }
211 } else {
212 warn "Role '$activity_role' for activity '$activity' not found in \%userRoles -- assuming no permission.";
213 return 0;
214 }
215 } else {
216 return 0; # undefiend $activity_role, no one has permission to perform $activity
217 }
218 } else {
219 warn "Activity '$activity' not found in \%permissionLevels -- assuming no permission.";
220 return 0;
221 }
38} 222}
39 223
224#### set-level authorization routines
225
226sub 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 proctored_gateway_quiz)));
240
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 # 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 # do we have a cached set that we can use?
251 my $set = $self->{merged_set};
252
253 if ( $setName =~ /,v(\d+)$/ ) {
254 my $verNum = $1;
255 $setName =~ s/,v\d+$//;
256
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 } else {
263 if ($db->existsSetVersion($effectiveUserName,$setName,$verNum)) {
264 $set = $db->getMergedSetVersion($effectiveUserName,$setName,$verNum);
265 } else {
266 return "Requested version ($verNum) of set " .
267 "'$setName' is not assigned to user " .
268 "$effectiveUserName.";
269 }
270 }
271 if ( ! $set ) {
272 return "Requested set '$setName' could not be found " .
273 "in the database for user $effectiveUserName.";
274 }
275 } else {
276
277 if ( $set && $set->set_id eq $setName &&
278 $set->user_id eq $effectiveUserName ) {
279 # then we can just use this set, and skip the rest
280
281 } else {
282 if ( $db->existsUserSet($effectiveUserName,$setName) ) {
283 $set = $db->getMergedSet($effectiveUserName,$setName);
284 } elsif ( $setName eq 'Undefined_Set' ) {
285 # this is the weird case of the library
286 # browser, when we don't actually have
287 # a set to look at.
288 return 0;
289 } else {
290 return "Requested set '$setName' is not " .
291 "assigned to user $effectiveUserName.";
292 }
293 }
294 if ( ! $set ) {
295 return "Requested set '$setName' could not be found " .
296 "in the database for user $effectiveUserName.";
297 }
298 }
299 # cache the set for future use as needed. this should probably
300 # be more sophisticated than this
301 $self->{merged_set} = $set;
302
303 # now we know that the set is assigned to the appropriate user;
304 # check to see if we're trying to access a set that's not open
305 if ( before($set->open_date) &&
306 ! $self->hasPermissions($userName, "view_unopened_sets") ) {
307 return "Requested set '$setName' is not yet open.";
308 }
309
310 # also check to make sure that the set is published, or that we're
311 # allowed to view unpublished setes
312 # (do we need to worry about published not being set at this point?)
313 my $published = ( $set && $set->published ne '0' &&
314 $set->published ne '1' ) ? 1 : $set->published;
315 if ( ! $published &&
316 ! $self->hasPermissions($userName, "view_unpublished_sets") ) {
317 return "Requested set '$setName' is not available yet.";
318 }
319
320 # check to be sure that gateways are being sent to the correct
321 # content generator
322 if (defined($set->assignment_type) &&
323 $set->assignment_type =~ /gateway/ &&
324 ($node_name eq 'problem_list' || $node_name eq 'problem_detail')) {
325 return "Requested set '$setName' is a test/quiz assignment " .
326 "but the regular homework assignment content " .
327 "generator $node_name was called. Try re-entering " .
328 "the set from the problem sets listing page.";
329 } elsif ( (! defined($set->assignment_type) ||
330 $set->assignment_type eq 'homework') &&
331 $node_name =~ /gateway/ ) {
332 return "Requested set '$setName' is a homework assignment " .
333 "but the gateway/quiz content " .
334 "generator $node_name was called. Try re-entering " .
335 "the set from the problem sets listing page.";
336 }
337
338 # and check that if we're entering a proctored assignment that we
339 # have a valid proctor login; this is necessary to make sure that
340 # someone doesn't use the unproctored url path to obtain access
341 # to a proctored assignment.
342 if (defined($set->assignment_type) &&
343 $set->assignment_type =~ /proctored/ &&
344 ! WeBWorK::Authen::Proctor->new($r,$ce,$db)->verify() ) {
345 return "Requested set '$setName' is a proctored test/quiz " .
346 "assignment, but no valid proctor authorization " .
347 "has been obtained.";
348 }
349
350 # and whether there are ip restrictions that we need to check
351 my $badIP = $self->invalidIPAddress($set);
352 return $badIP if $badIP;
353
354 return 0;
355}
356
357sub invalidIPAddress {
358# this exists as a separate routine because we need to check multiple
359# sets in Hardcopy; having this routine to check the set allows us to do
360# that for all sets individually there.
361
362 my $self = shift;
363 my $set = shift;
364
365 my $r = $self->{r};
366 my $db = $r->db;
367 my $urlPath = $r->urlpath;
368# my $setName = $urlPath->arg("setID"); # not always defined
369 my $setName = $set->set_id;
370 my $userName = $r->param("user");
371 my $effectiveUserName = $r->param("effectiveUser");
372
373 return 0 if ($set->restrict_ip eq '' || $set->restrict_ip eq 'No' ||
374 $self->hasPermissions($userName,'view_ip_restricted_sets'));
375
376 my $clientIP = new Net::IP($r->connection->remote_ip);
377 # make sure that we're using the non-versioned set name
378 $setName =~ s/,v\d+$//;
379
380 my $restrictType = $set->restrict_ip;
381 my @restrictLocations = $db->getAllMergedSetLocations($effectiveUserName,$setName);
382 my @locationIDs = ( map {$_->location_id} @restrictLocations );
383 my @restrictAddresses = ( map {$db->listLocationAddresses($_)} @locationIDs );
384
385 # if there are no addresses in the locations, return an error that
386 # says this
387 return "Client ip address " . $clientIP->ip() . " is not allowed to " .
388 "work this assignment, because the assignment has ip address " .
389 "restrictions and there are no allowed locations associated " .
390 "with the restriction. Contact your professor to have this " .
391 "problem resolved." if ( ! @restrictAddresses );
392
393 # build a set of IP objects to match against
394 my @restrictIPs = ( map {new Net::IP($_)} @restrictAddresses );
395
396 # and check the clientAddress against these: is $clientIP
397 # in @restrictIPs?
398 my $inRestrict = 0;
399 foreach my $rIP ( @restrictIPs ) {
400 if ($rIP->overlaps($clientIP) == $IP_B_IN_A_OVERLAP ||
401 $rIP->overlaps($clientIP) == $IP_IDENTICAL) {
402 $inRestrict = $rIP->ip();
403 last;
404 }
405 }
406
407 # this is slightly complicated by having to check relax_restrict_ip
408 my $badIP = '';
409 if ( $restrictType eq 'RestrictTo' && ! $inRestrict ) {
410 $badIP = "Client ip address " . $clientIP->ip() .
411 " is not in the list of addresses from " .
412 "which this assignment may be worked.";
413 } elsif ( $restrictType eq 'DenyFrom' && $inRestrict ) {
414 $badIP = "Client ip address " . $clientIP->ip() .
415 " is in the list of addresses from " .
416 "which this assignment may not be worked.";
417 } else {
418 return 0;
419 }
420
421 # if we're here, we failed the IP check, and so need to consider
422 # if ip restrictions were relaxed. the set we were passed in
423 # is either the merged userset or the merged versioned userset,
424 # depending on whether the set is versioned or not
425
426 my $relaxRestrict = $set->relax_restrict_ip;
427 return $badIP if ( $relaxRestrict eq 'No' );
428
429 if ( $set->assignment_type =~ /gateway/ ) {
430 if ( $relaxRestrict eq 'AfterAnswerDate' ) {
431 # in this case we need to go and get the userset,
432 # not the versioned set (which we already have)
433 # drat!
434 my $userset = $db->getMergedSet($set->user_id,$setName);
435 return( ! $userset || before($userset->answer_date)
436 ? $badIP : 0 );
437 } else {
438 # this is easier; just look at the current answer date
439 return( before($set->answer_date) ? $badIP : 0 );
440 }
441 } else {
442 # the set isn't versioned, so assume that $relaxRestrict
443 # is 'AfterAnswerDate', regardless of what it actually
444 # is; 'AfterVersionAnswerDate' doesn't make sense in
445 # this case
446 return( before($set->answer_date) ? $badIP : 0 );
447 }
448}
449
450=back
451
452=cut
453
454=head1 AUTHOR
455
456Written by Dennis Lambe, malsyned at math.rochester.edu. Modified by Sam
457Hathaway, sh002i at math.rochester.edu.
458
459=cut
460
401; 4611;

Legend:
Removed from v.455  
changed lines
  Added in v.5266

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9