source: trunk/common/security.php @ 1161

Revision 1161, 19.7 KB checked in by savin.tiberiu@…, 8 weeks ago (diff)

Bugfixes:

Fixed a bug for intern permissions

  • Fixed the reeval bug


review link:  http://reviewboard.infoarena.ro/r/179/

  • Property svn:eol-style set to native
Line 
1<?php
2
3require_once(IA_ROOT_DIR."common/db/round.php");
4
5
6// This module implements everything related to security.
7//
8// You should use security_query to determine if a certain user is allowed
9// to do a an operation. The operation is a large php array(hash), which
10// should completely describe the operation.
11//
12// NOTE: most of the time you can use identity_can or identity_require
13// instead of calling security_query directly.
14
15// Implementation:
16//
17// We distinguish between 5 types of users. Permissions only increase as you
18// go down the list.
19//  - anonymous     non-authenticated visitors.
20//  - normal        registered & authenticated users.
21//  - helper        Trusted users. They can make their own tasks, but
22//                  can't publish them. For teachers or high ratings.
23//  - interns       Trusted users, future core team members.
24//  - admin         Can do anything. For core team members.
25
26// Returns boolean whether $user can perform $action onto $object
27function security_query($user, $action, $object) {
28    list($group, $subaction) = explode('-', $action, 2);
29
30    log_assert(is_array($object) || is_null($object),
31               '$object must be an array or null');
32    // Log security checking.
33    $username = getattr($user, 'username', 'null');
34    $usersec = getattr($user, 'security_level', 'anonymous');
35    $object_id = getattr($object, 'id', getattr($object, 'name', $object));
36    if (IA_LOG_SECURITY) {
37        log_print("SECURITY QUERY: ".
38                "($username, $usersec, $action, $object_id): ".
39                "(username, level, action, object)");
40    }
41
42    // group dispatcher
43    switch ($group) {
44        case 'textblock':
45            $result = security_textblock($user, $action, $object);
46            break;
47
48        case 'user':
49            $result = security_user($user, $action, $object);
50            break;
51
52        case 'round':
53            $result = security_round($user, $action, $object);
54            break;
55
56        case 'task':
57            $result = security_task($user, $action, $object);
58            break;
59
60        case 'attach':
61            $result = security_attach($user, $action, $object);
62            break;
63
64        case 'macro':
65            $result = security_macro($user, $action, $object);
66            break;
67
68        case 'job':
69            $result = security_job($user, $action, $object);
70            break;
71
72        case 'blog':
73            $result = security_blog($user, $action, $object);
74            break;
75
76        default:
77            log_error('Invalid action group: "' . $group . '"');
78    }
79
80    log_assert(is_bool($result), "SECURITY: FAILED, didn't return a bool");
81    if (IA_LOG_SECURITY) {
82        if ($result) {
83            log_print("SECURITY: GRANTED");
84        } else {
85            log_print("SECURITY: DENIED");
86        }
87    }
88    return $result;
89}
90
91// This function simplifies $action.
92// It's not an error to pass an already simplified action.
93function security_simplify_action($action) {
94    switch ($action) {
95        // View access.
96        case 'textblock-view':
97        case 'textblock-history':
98        case 'textblock-list-attach':
99        case 'attach-download':
100        case 'user-viewinfo':
101        case 'task-view':
102        case 'round-view':
103        case 'simple-view':
104        case 'round-register-view':
105            return 'simple-view';
106
107        // View IP.
108        case 'attach-view-ip':
109        case 'textblock-view-ip':
110        case 'job-view-ip':
111        case 'grader-view-ip':
112            return 'sensitive-info';
113
114        // Reversible edits access.
115        case 'textblock-edit':
116        case 'textblock-restore':
117        case 'textblock-attach':
118        case 'textblock-create':
119        case 'textblock-change-topic':
120        case 'textblock-copy':
121        case 'simple-rev-edit':
122            return 'simple-rev-edit';
123
124        // Irreversible edits.
125        case 'textblock-move':
126        case 'attach-overwrite':
127        case 'attach-delete':
128        case 'attach-rename':
129        case 'task-edit':
130        case 'task-create':
131        case 'task-delete':
132        case 'task-tag':
133        case 'task-reeval':
134        case 'task-edit-ratings': 
135        case 'textblock-delete':
136        case 'textblock-delete-revision':
137        case 'round-tag':
138        case 'round-view-progress': 
139        case 'grader-overwrite':
140        case 'grader-delete':
141        case 'grader-rename':
142        case 'simple-edit':
143            return 'simple-edit';
144
145        // Admin stuff:
146        case 'task-change-security':
147        case 'task-change-open':
148        case 'textblock-change-security':
149        case 'textblock-tag':
150        case 'job-reeval':
151        case 'round-delete':
152        case 'task-edit-owner':
153        case 'simple-critical':
154            return 'simple-critical';
155
156        // Special actions fall through
157        // FIXME: As few as possible.
158        case 'grader-download':
159        case 'task-use-in-user-round':
160        case 'task-submit':
161        case 'round-edit':
162        case 'round-create':
163        case 'round-submit':
164        case 'round-view-tasks':
165        case 'round-view-scores':
166        case 'round-register':
167        case 'user-editprofile':
168        case 'user-change-security':
169        case 'user-tag':
170        case 'job-view':
171        case 'job-eval':
172        case 'job-view-source':
173        case 'job-view-source-size':
174        case 'job-view-score':
175        case 'job-view-partial-feedback':
176        case 'task-view-tags':
177            return $action;
178
179        default:
180            log_error('Invalid action: '.$action);
181    }
182}
183
184// Handles textblock security.
185function security_textblock($user, $action, $textblock) {
186    require_once(IA_ROOT_DIR."common/textblock.php");
187
188    $textsec = $textblock['security'];
189    $usersec = getattr($user, 'security_level', 'anonymous');
190
191    log_assert_valid(textblock_validate($textblock));
192
193    // HACK: Forward security to user.
194    // HACK: based on name
195    if (count($matches = get_page_user_name($textblock['name'])) > 0) {
196        require_once(IA_ROOT_DIR . "common/db/user.php");
197        $ouser = user_get_by_username($matches[1]);
198        if ($ouser === null) {
199            log_warn("User page for missing user");
200            return false;
201        }
202        // This is a horrible hack to prevent deleting or moving an user page.
203        // This is pure evil.
204        if ($action == 'textblock-delete' || $action == 'textblock-move') {
205            $action = 'simple-critical';
206        }
207        return security_user($user, $action, $ouser);
208    }
209
210    // Forward security to task.
211    if (($task_id = textblock_security_is_task($textsec))) {
212        require_once(IA_ROOT_DIR . "common/db/task.php");
213        $task = task_get($task_id);
214        if ($task === null) {
215            log_warn("Bad security descriptor, ask an admin.");
216            return $usersec == 'admin';
217        }
218        return security_task($user, $action, $task);
219    }
220
221    // Forward security to round.
222    if (($round_id = textblock_security_is_round($textsec))) {
223        require_once(IA_ROOT_DIR . "common/db/round.php");
224        $round = round_get($round_id);
225        if ($round === null) {
226            log_warn("Bad security descriptor, ask an admin.");
227            return $usersec == 'admin';
228        }
229        return security_round($user, $action, $round);
230    }
231
232    if (preg_match('/^ \s* (private|protected|public) \s* $/xi', $textsec, $matches)) {
233        $textsec = $matches[1];
234    } else {
235        log_warn("Bad security descriptor, ask an admin.");
236        return $usersec == 'admin';
237    }
238
239    // Log query response.
240    $action = security_simplify_action($action);
241    $objid = $textblock['name'];
242    if (IA_LOG_SECURITY) {
243        log_print("SECURITY QUERY TEXTBLOCK: ".
244                "($usersec, $action, $objid): ".
245                "(level, action, object");
246    }
247
248    switch ($action) {
249        case 'simple-view':
250            if ($textsec == 'private') {
251                return $usersec == 'admin';
252            } else {
253                return true;
254            }
255
256        case 'sensitive-info':
257            return ($usersec == 'admin' || $usersec == 'helper');
258
259        // Reversible modifications.
260        case 'simple-rev-edit':
261            if ($textsec == 'public') {
262                return $usersec != 'anonymous';
263            } else {
264                return $usersec == 'admin';
265            }
266
267        // Permanent changes. Admin only
268        case 'simple-edit':
269        case 'simple-critical':
270            return $usersec == 'admin';
271
272        default:
273            log_error('Invalid textblock action: '.$action);
274    }
275}
276
277// Jump to security_textblock.
278// FIXME: attach-grader?
279function security_attach($user, $action, $attach) {
280    $att_name = $attach['name'];
281    $att_page = normalize_page_name($attach['page']);
282    $usersec = getattr($user, 'security_level', 'anonymous');
283    $is_admin = $usersec == 'admin';
284    $is_owner = $attach['user_id'] == $user['id'];
285
286    // Log query response.
287    $level = ($is_admin ? 'admin' : ($is_owner ? 'owner' : 'other'));
288    $objid = $attach['user_id'];
289    if (IA_LOG_SECURITY) {
290        log_print("SECURITY QUERY ATTACH: ".
291                  "($level, $action, $objid): ".
292                  "(level, action, object)");
293    }
294
295    // Speed hack: avatars are always visible. This is good.
296    if ($action == 'attach-download' && $att_name == 'avatar' &&
297            starts_with($att_page, IA_USER_TEXTBLOCK_PREFIX)) {
298        return true;
299    }
300
301    // Forward to textblock.
302    $tb = textblock_get_revision($attach['page']);
303    if (!$tb) {
304        log_print_r($attach);
305    }
306    log_assert($tb, "Orphan attachment");
307
308    // Convert action into a grader action if the textblock is a task
309    // textblock and the attachment has the grader_ prefix.
310    if (textblock_security_is_task($tb['security']) &&
311        preg_match('/^grader\_/', $att_name)) {
312        $newaction = preg_replace('/^attach/', 'grader', $action);
313        if (IA_LOG_SECURITY) {
314            log_print("SECURITY: CONVERTING $action to $newaction");
315        }
316        $action = $newaction;
317    }
318
319    return security_textblock($user, $action, $tb);
320}
321
322// FIXME: more?
323function security_user($user, $action, $target_user) {
324    $usersec = getattr($user, 'security_level', 'anonymous');
325    $is_admin = $usersec == 'admin';
326    $is_self = $target_user['id'] == $user['id'];
327
328    // Log query response.
329    $action = security_simplify_action($action);
330    $level = ($is_admin ? 'admin' : ($is_self ? 'self' : 'other'));
331    $objid = $target_user['username'];
332    if (IA_LOG_SECURITY) {
333        log_print("SECURITY QUERY USER: ".
334                  "($level, $action, $objid): ".
335                  "(level, action, object)");
336    }
337
338    switch ($action) {
339        case 'simple-view':
340            return true;
341
342        case 'simple-rev-edit':
343        case 'simple-edit':
344        case 'user-editprofile':
345            // anyone can edit their own profile. admins can edit any profile
346            return $is_admin || $is_self;
347
348        // FIXME: haaaaack.
349        case 'user-change-security':
350        case 'user-tag':
351            return $is_admin;
352
353        // Nobody is allowed here. This includes moving/deleting user's own
354        // page and changing security descriptors in user pages.
355        case 'simple-critical':
356            return false;
357
358        case 'sensitive-info':
359            return ($usersec == 'admin' || $usersec == 'helper');
360
361        default:
362            log_error('Invalid user action: '.$action);
363            return false;
364    }
365}
366
367// FIXME: contest logic.
368function security_task($user, $action, $task) {
369    $usersec = getattr($user, 'security_level', 'anonymous');
370    $is_admin = $usersec == 'admin';
371    $is_intern = $usersec == 'intern';
372    $is_owner = ($task['user_id'] == $user['id'] && $usersec == 'helper');
373    $is_boss = $is_admin || $is_intern || $is_owner;
374
375    // Log query response.
376    $action = security_simplify_action($action);
377    $level = ($is_admin ? 'admin' : ($is_owner ? 'owner' : 'other'));
378    $objid = $task['id'];
379    if (IA_LOG_SECURITY) {
380        log_print("SECURITY QUERY TASK: ".
381                "($level, $action, $objid): ".
382                "(level, action, object)");
383    }
384
385    switch ($action) {
386        // Read-only access.
387        case 'simple-view':
388            return ($task['hidden'] == false) || $is_boss;
389
390        // Edit access.
391        case 'simple-rev-edit':
392            return $is_boss;
393
394        case 'simple-edit':
395            return $is_boss;
396
397        // View tags
398        case 'task-view-tags':
399            $in_archive = false;
400            $rounds = task_get_submit_rounds($task['id'], $user['id']);
401            foreach ($rounds as $round_id) {
402                if ($round_id == 'arhiva' || $round_id == 'arhiva-educationala') {
403                    $in_archive = true;
404                }
405            }
406            return $in_archive || $is_boss;
407
408        // Admin stuff:
409        case 'simple-critical':
410            return $is_admin;
411
412        case 'task-use-in-user-round':
413            if ($usersec == 'anonymous') {
414                return false;
415            }
416            if ($is_admin) {
417                return true;
418            }
419            $is_valid = true;
420            $rounds = task_get_parent_rounds($task['id']);
421            foreach ($rounds as $rid) {
422                $r = round_get($rid);
423                // You can use a task in an user defined contest ONLY IF it's
424                // not being used in a waiting or running classic contest.
425                if ($r['type'] == 'classic' && $r['state'] != 'complete') {
426                    $is_valid = false;
427                    break;
428                }
429            }
430            return ($task['hidden'] == false && $is_valid);
431
432        // Special: submit. Check for at least one registered contest for the task.
433        // FIXME: contest logic?
434        case 'task-submit':
435            //FIXME: this is ugly
436            if ($usersec == 'anonymous') {
437                return false;
438            }
439            if ($is_boss) {
440                return true;
441            }
442            $is_running = false;
443            $rounds = task_get_parent_rounds($task['id']);
444            foreach ($rounds as $round_id) {
445                $round = round_get($round_id);
446                if ($round['state'] != 'running') {
447                    continue;
448                }
449                $is_running = true;
450                break;
451            }
452            return ($task['hidden'] == false && $is_running);
453
454        case 'grader-download':
455            if ($task['open_tests']) {
456                $can_view = $task['hidden'] == false;
457            } else {
458                $can_view = false;
459            }
460            return $can_view || $is_owner || $is_admin;
461
462        case 'sensitive-info':
463            return $is_boss;
464
465        default:
466            log_error('Invalid task action: '.$action);
467    }
468}
469
470// FIXME: contest logic.
471function security_round($user, $action, $round) {
472    $usersec = getattr($user, 'security_level', 'anonymous');
473    $is_admin = $usersec == 'admin';
474    $is_intern = $usersec == 'intern';
475
476    // Log query response.
477    $action = security_simplify_action($action);
478    $level = ($is_admin ? 'admin' : 'other');
479    $objid = $round['id'];
480    if (IA_LOG_SECURITY) {
481        log_print("SECURITY QUERY ROUND: ".
482                "($level, $action, $objid): ".
483                "(level, action, object)");
484    }
485
486    switch ($action) {
487        case 'simple-view':
488          return true;
489
490        case 'round-create':
491          if ($round['type'] == 'user-defined') {
492              return $usersec != 'anonymous';
493          } else {
494              return $is_admin || $is_intern;
495          }
496
497        case 'round-edit':
498        case 'simple-rev-edit':
499          if ($usersec == 'anonymous') {
500              return false;
501          }
502          if ($round['type'] == 'user-defined') {
503              return $user['id'] == $round['user_id'] || $is_admin || $is_intern;
504          } else {
505              return $is_admin || $is_intern;
506          }
507
508        case 'round-view-tasks':
509            return $round['state'] != 'waiting' || $is_admin || $is_intern;
510        case 'round-view-scores':
511            return $round['public_eval'] == true || $is_admin || $is_intern;
512
513        case 'simple-edit':
514            return $is_admin || $is_intern;
515
516        case 'simple-critical':
517            return $is_admin;
518
519        case 'round-register':
520            if ($usersec == 'anonymous') {
521                return false;
522            }
523            // FIXME: improve round registration logic
524            $is_waiting = $round['state'] == 'waiting';
525            return $is_waiting || $is_admin;
526
527        case 'round-submit':
528            return $round["state"] == "running";
529
530        case 'sensitive-info':
531            return in_array($usersec, array('admin', 'intern', 'helper'));
532
533        default:
534            log_error('Invalid round action: '.$action);
535    }
536}
537
538// FIXME: macro security is stupid.
539function security_macro($user, $action, $args) {
540    $usersec = getattr($user, 'security_level', 'anonymous');
541
542    switch ($action) {
543        case 'macro-grep':
544            return true;
545        case 'macro-debug':
546        case 'macro-remotebox':
547        case 'macro-preoni':
548            // only administrators can execute these macros
549            return $usersec == 'admin';
550
551        default:
552            log_error('Invalid macro action: '.$action);
553    }
554}
555
556function security_blog($user, $action, $round) {
557    $usersec = getattr($user, 'security_level', 'anonymous');
558    $is_admin = $usersec == 'admin';
559
560    // Log query response.
561    $action = security_simplify_action($action);
562    $level = ($is_admin ? 'admin' : 'other');
563    $objid = $round['id'];
564    if (IA_LOG_SECURITY) {
565        log_print("SECURITY QUERY BLOG: ".
566                "($level, $action, $objid): ".
567                "(level, action, object)");
568    }
569
570    return $is_admin;
571}
572
573
574// There is no job-eval, jobs are evaluated on the spot, we check job-view instead.
575function security_job($user, $action, $job) {
576    $usersec = getattr($user, 'security_level', 'anonymous');
577    $is_admin = $usersec == 'admin';
578    $is_intern = $usersec == 'intern';
579    $is_owner = ($job['user_id'] == $user['id']);
580    $is_task_owner = ($job['task_owner_id'] == $user['id'] && 
581                      in_array($usersec, array('helper', 'intern')));
582    $can_view_job = ($job['task_hidden'] == false) || $is_task_owner || $is_admin;
583    $can_view_source = ($job['task_open_source'] == true) || $is_task_owner || 
584                       $is_owner || $is_admin || $is_intern;
585    $can_view_source_size = ($job['round_type'] == "archive") ||
586                            ($job['round_type'] != "archive" && $job['round_state'] == "complete") ||
587                            $can_view_source;
588    $can_view_score = ($job['round_public_eval'] == true) || $is_task_owner || $is_admin || $is_intern;
589    $can_view_partial_feedback = $is_owner || $is_admin || $is_intern;
590    $can_view_sensitive_info = in_array($usersec, array('admin', 'intern', 'helper'));
591
592    // Log query response.
593    $action = security_simplify_action($action);
594    $level = ($is_admin ? 'admin' : ($is_owner ? 'owner' : ($is_task_owner ? 'task-owner' : 'other')));
595    $objid = $job['id'];
596    if (IA_LOG_SECURITY) {
597        log_print("SECURITY QUERY JOB: ".
598                "($level, $action, $objid): ".
599                "(level, action, object)");
600    }
601
602    switch ($action) {
603        case 'simple-critical':
604            return $is_admin || $is_intern;
605
606        case 'job-view':
607            return $can_view_job;
608
609        case 'job-view-source':
610            return $can_view_job && $can_view_source;
611
612        case 'job-view-source-size':
613            return $can_view_job && $can_view_source_size;
614
615        case 'job-view-score':
616            return $can_view_job && $can_view_score;
617
618        case 'job-view-partial-feedback':
619            return $can_view_job && $can_view_partial_feedback;
620
621        case 'sensitive-info':
622            return $can_view_job && $can_view_sensitive_info;
623
624        default:
625            log_error('Invalid job action: '.$action);
626    }
627}
628
629?>
Note: See TracBrowser for help on using the repository browser.