public static function isInsideContest(Contests $contest, $user_id) { if (time() > strtotime($contest->finish_time) || time() < strtotime($contest->start_time)) { return false; } if (is_null($contest->window_length)) { return true; } $contest_user = ContestsUsersDAO::getByPK($user_id, $contest->contest_id); $first_access_time = $contest_user->access_time; return time() <= strtotime($first_access_time) + $contest->window_length * 60; }
/** * Show the contest intro unless you are admin, or you * already started this contest. */ public static function showContestIntro(Request $r) { try { $r["contest"] = ContestsDAO::getByAlias($r["contest_alias"]); } catch (Exception $e) { throw new NotFoundException("contestNotFound"); } if (is_null($r['contest'])) { throw new NotFoundException("contestNotFound"); } try { // Half-authenticate, in case there is no session in place. $session = SessionController::apiCurrentSession($r); if ($session['valid'] && !is_null($session['user'])) { $r["current_user"] = $session['user']; $r["current_user_id"] = $session['user']->user_id; } self::canAccessContest($r); } catch (Exception $e) { // Could not access contest. Private contests must not be leaked, so // unless they were manually added beforehand, show them a 404 error. if (!ContestController::isInvitedToContest($r)) { throw $e; } self::$log->error("Exception while trying to verify access: " . $e); return ContestController::SHOW_INTRO; } $cs = SessionController::apiCurrentSession(); // You already started the contest. $contestOpened = ContestsUsersDAO::getByPK($r['current_user_id'], $r["contest"]->getContestId()); if (!is_null($contestOpened) && $contestOpened->access_time != "0000-00-00 00:00:00") { self::$log->debug("Not intro because you already started the contest"); return !ContestController::SHOW_INTRO; } return ContestController::SHOW_INTRO; }
/** * Create a new run * * @param Request $r * @return array * @throws Exception * @throws InvalidDatabaseOperationException * @throws InvalidFilesystemOperationException */ public static function apiCreate(Request $r) { // Init self::initializeGrader(); // Authenticate user self::authenticateRequest($r); // Validate request self::validateCreateRequest($r); self::$log->info('New run being submitted!!'); $response = array(); if (self::$practice) { if (OMEGAUP_LOCKDOWN) { throw new ForbiddenAccessException('lockdown'); } $submit_delay = 0; $contest_id = null; $test = 0; } else { //check the kind of penalty_type for this contest $penalty_type = $r['contest']->penalty_type; switch ($penalty_type) { case 'contest_start': // submit_delay is calculated from the start // of the contest $start = $r['contest']->getStartTime(); break; case 'problem_open': // submit delay is calculated from the // time the user opened the problem $opened = ContestProblemOpenedDAO::getByPK($r['contest']->getContestId(), $r['problem']->getProblemId(), $r['current_user_id']); if (is_null($opened)) { //holy moly, he is submitting a run //and he hasnt even opened the problem //what should be done here? throw new NotAllowedToSubmitException('runEvenOpened'); } $start = $opened->getOpenTime(); break; case 'none': case 'runtime': //we dont care $start = null; break; default: self::$log->error('penalty_type for this contests is not a valid option, asuming `none`.'); $start = null; } if (!is_null($start)) { //ok, what time is it now? $c_time = time(); $start = strtotime($start); //asuming submit_delay is in minutes $submit_delay = (int) (($c_time - $start) / 60); } else { $submit_delay = 0; } $contest_id = $r['contest']->getContestId(); $test = Authorization::IsContestAdmin($r['current_user_id'], $r['contest']) ? 1 : 0; } // Populate new run object $run = new Runs(array('user_id' => $r['current_user_id'], 'problem_id' => $r['problem']->getProblemId(), 'contest_id' => $contest_id, 'language' => $r['language'], 'source' => $r['source'], 'status' => 'new', 'runtime' => 0, 'penalty' => $submit_delay, 'memory' => 0, 'score' => 0, 'contest_score' => $contest_id != null ? 0 : null, 'submit_delay' => $submit_delay, 'guid' => md5(uniqid(rand(), true)), 'verdict' => 'JE', 'test' => $test)); try { // Push run into DB RunsDAO::save($run); SubmissionLogDAO::save(new SubmissionLog(array('user_id' => $run->user_id, 'run_id' => $run->run_id, 'contest_id' => $run->contest_id, 'ip' => ip2long($_SERVER['REMOTE_ADDR'])))); // Update submissions counter++ $r['problem']->setSubmissions($r['problem']->getSubmissions() + 1); ProblemsDAO::save($r['problem']); } catch (Exception $e) { // Operation failed in the data layer throw new InvalidDatabaseOperationException($e); } try { // Create file for the run $filepath = RunController::getSubmissionPath($run); FileHandler::CreateFile($filepath, $r['source']); } catch (Exception $e) { throw new InvalidFilesystemOperationException($e); } // Call Grader try { self::$grader->Grade([$run->guid], false, false); } catch (Exception $e) { self::$log->error('Call to Grader::grade() failed:'); self::$log->error($e); } if (self::$practice) { $response['submission_deadline'] = 0; } else { // Add remaining time to the response try { $contest_user = ContestsUsersDAO::getByPK($r['current_user_id'], $r['contest']->getContestId()); if ($r['contest']->getWindowLength() === null) { $response['submission_deadline'] = strtotime($r['contest']->getFinishTime()); } else { $response['submission_deadline'] = min(strtotime($r['contest']->getFinishTime()), strtotime($contest_user->getAccessTime()) + $r['contest']->getWindowLength() * 60); } } catch (Exception $e) { // Operation failed in the data layer throw new InvalidDatabaseOperationException($e); } } // Happy ending $response['guid'] = $run->getGuid(); $response['status'] = 'ok'; // Expire rank cache UserController::deleteProblemsSolvedRankCacheList(); return $response; }
/** * Validate problem Details API * * @param Request $r * @throws ApiException * @throws InvalidDatabaseOperationException * @throws NotFoundException * @throws ForbiddenAccessException */ private static function validateDetails(Request $r) { Validators::isStringNonEmpty($r['contest_alias'], 'contest_alias', false); Validators::isStringNonEmpty($r['problem_alias'], 'problem_alias'); // Lang is optional. Default is user's preferred. if (!is_null($r['lang'])) { Validators::isStringOfMaxLength($r['lang'], 'lang', 2); } else { $r['lang'] = UserController::getPreferredLanguage($r); } try { $r['problem'] = ProblemsDAO::getByAlias($r['problem_alias']); } catch (Exception $e) { throw new InvalidDatabaseOperationException($e); } if (is_null($r['problem'])) { throw new NotFoundException('problemNotFound'); } if (isset($r['statement_type']) && !in_array($r['statement_type'], array('html', 'markdown'))) { throw new NotFoundException('invalidStatementType'); } // If we request a problem inside a contest if (!is_null($r['contest_alias'])) { // Is the combination contest_id and problem_id valid? try { $r['contest'] = ContestsDAO::getByAlias($r['contest_alias']); if (is_null($r['contest'])) { throw new NotFoundException('contestNotFound'); } if (is_null(ContestProblemsDAO::getByPK($r['contest']->getContestId(), $r['problem']->getProblemId()))) { throw new NotFoundException('problemNotFoundInContest'); } } catch (ApiException $apiException) { throw $apiException; } catch (Exception $e) { throw new InvalidDatabaseOperationException($e); } // If the contest is private, verify that our user is invited $contest_admin = Authorization::IsContestAdmin($r['current_user_id'], $r['contest']); if ($r['contest']->public != '1') { if (is_null(ContestsUsersDAO::getByPK($r['current_user_id'], $r['contest']->contest_id)) && !$contest_admin) { throw new ForbiddenAccessException(); } } // If the contest has not started, user should not see it, unless // it is admin if (!ContestsDAO::hasStarted($r['contest']) && !$contest_admin) { throw new ForbiddenAccessException('contestNotStarted'); } } else { if (!Authorization::CanEditProblem($r['current_user_id'], $r['problem'])) { // If the problem is requested outside a contest, we need to // check that it is not private if ($r['problem']->public != '1') { throw new ForbiddenAccessException('problemIsPrivate'); } } } }
/** * First access time should not change */ public function testAccessTimeIsAlwaysFirstAccessForPrivate() { // Get a contest $contestData = ContestsFactory::createContest(null, 0); // Get a user for our scenario $contestant = UserFactory::createUser(); // Add user to our private contest ContestsFactory::addUser($contestData, $contestant); // Prepare our request $r = new Request(); $r['contest_alias'] = $contestData['request']['alias']; // Log in the user $r['auth_token'] = $this->login($contestant); // Call api $response = ContestController::apiDetails($r); // We need to grab the access time from the ContestUsers table $contest = ContestsDAO::getByAlias($contestData['request']['alias']); $contest_user = ContestsUsersDAO::getByPK($contestant->getUserId(), $contest->getContestId()); $firstAccessTime = $contest_user->getAccessTime(); // Call API again, access time should not change $response = ContestController::apiDetails($r); $contest_user = ContestsUsersDAO::getByPK($contestant->getUserId(), $contest->getContestId()); $this->assertEquals($firstAccessTime, $contest_user->getAccessTime()); }
/** * Show the contest intro unless you are admin, or you * already started this contest. */ public static function showContestIntro(Request $r) { try { $r["contest"] = ContestsDAO::getByAlias($r["contest_alias"]); } catch (Exception $e) { throw new NotFoundException("contestNotFound"); } try { // Half-authenticate, in case there is no session in place. $session = SessionController::apiCurrentSession($r); if ($session['valid'] && $session['user'] != null) { $r["current_user"] = $session['user']; $r["current_user_id"] = $session['user']->user_id; } self::canAccessContest($r); } catch (Exception $e) { self::$log->error("Exception while trying to verify access: " . $e); return ContestController::SHOW_INTRO; } // You are admin if (!is_null($r['current_user_id']) && Authorization::IsContestAdmin($r["current_user_id"], $r["contest"])) { self::$log->debug("Not intro because you are admin"); return !ContestController::SHOW_INTRO; } $cs = SessionController::apiCurrentSession(); // You already started the contest. $contestOpened = null; if (!is_null($clarificationEmailBody = ContestsUsersDAO::getByPK($cs["id"], $r["contest"]->getContestId())) && $contestOpened->access_time != "0000-00-00 00:00:00") { self::$log->debug("Not intro because you already started the contest"); return !ContestController::SHOW_INTRO; } return ContestController::SHOW_INTRO; }
/** * Test sending runs after the window length expired * * @expectedException NotAllowedToSubmitException */ public function testNewRunOutWindowLengthPublicContest() { // Set the context for the first contest $r = $this->setValidRequest(); // Alter Contest window length to 20 // This means: once I started the contest, I have 20 more mins // to finish it. $contest = ContestsDAO::getByAlias($r["contest_alias"]); $contest->setWindowLength(20); ContestsDAO::save($contest); // Alter first access time of our contestant such that he started // 21 minutes ago, this is, window length has expired by 1 minute $contest_user = ContestsUsersDAO::getByPK($this->contestant->getUserId(), $contest->getContestId()); $contest_user->setAccessTime(date("Y-m-d H:i:s", time() - 21 * 60)); //Window length is in minutes ContestsUsersDAO::save($contest_user); // Call API RunController::apiCreate($r); }
/** * Validate problem Details API * * @param Request $r * @throws ApiException * @throws InvalidDatabaseOperationException * @throws NotFoundException * @throws ForbiddenAccessException */ private static function validateDetails(Request $r) { Validators::isStringNonEmpty($r["contest_alias"], "contest_alias", false); Validators::isStringNonEmpty($r["problem_alias"], "problem_alias"); // Lang is optional. Default is user's preferred. if (!is_null($r["lang"])) { Validators::isStringOfMaxLength($r["lang"], "lang", 2); } else { $r['lang'] = UserController::getPreferredLanguage($r); } try { $r["problem"] = ProblemsDAO::getByAlias($r["problem_alias"]); } catch (Exception $e) { throw new InvalidDatabaseOperationException($e); } if (is_null($r["problem"])) { throw new NotFoundException("problemNotFound"); } if (isset($r["statement_type"]) && !in_array($r["statement_type"], array("html", "markdown"))) { throw new NotFoundException("invalidStatementType"); } // If we request a problem inside a contest if (!is_null($r["contest_alias"])) { // Is the combination contest_id and problem_id valid? try { $r["contest"] = ContestsDAO::getByAlias($r["contest_alias"]); if (is_null($r["contest"])) { throw new NotFoundException("contestNotFound"); } if (is_null(ContestProblemsDAO::getByPK($r["contest"]->getContestId(), $r["problem"]->getProblemId()))) { throw new NotFoundException("problemNotFoundInContest"); } } catch (ApiException $apiException) { throw $apiException; } catch (Exception $e) { throw new InvalidDatabaseOperationException($e); } // If the contest is private, verify that our user is invited if ($r["contest"]->getPublic() === 0) { if (is_null(ContestsUsersDAO::getByPK($r["current_user_id"], $r["contest"]->getContestId())) && !Authorization::IsContestAdmin($r["current_user_id"], $r["contest"])) { throw new ForbiddenAccessException(); } } // If the contest has not started, user should not see it, unless it is admin if (!ContestsDAO::hasStarted($r["contest"]) && !Authorization::IsContestAdmin($r["current_user_id"], $r["contest"])) { throw new ForbiddenAccessException("contestNotStarted"); } } else { if (!Authorization::CanEditProblem($r["current_user_id"], $r["problem"])) { // If the problem is requested outside a contest, we need to check that it is not private if ($r["problem"]->getPublic() == "0") { throw new ForbiddenAccessException("problemIsPrivate"); } } } }