public async Task <IActionResult> InitializeModelAsync(Puzzle puzzle, Team team, SortOrder?sort) { if (puzzle == null && team == null) { return(NotFound()); } IQueryable <PuzzleStatePerTeam> statesQ = PuzzleStateHelper.GetFullReadOnlyQuery( _context, Event, puzzle, team, EventRole == EventRole.admin ? null : LoggedInUser); Sort = sort; switch (sort ?? DefaultSort) { case SortOrder.PuzzleAscending: statesQ = statesQ.OrderBy(state => state.Puzzle.Name); break; case SortOrder.PuzzleDescending: statesQ = statesQ.OrderByDescending(state => state.Puzzle.Name); break; case SortOrder.TeamAscending: statesQ = statesQ.OrderBy(state => state.Team.Name); break; case SortOrder.TeamDescending: statesQ = statesQ.OrderByDescending(state => state.Team.Name); break; case SortOrder.UnlockAscending: statesQ = statesQ.OrderBy(state => state.UnlockedTime ?? DateTime.MaxValue); break; case SortOrder.UnlockDescending: statesQ = statesQ.OrderByDescending(state => state.UnlockedTime ?? DateTime.MaxValue); break; case SortOrder.SolveAscending: statesQ = statesQ.OrderBy(state => state.SolvedTime ?? DateTime.MaxValue); break; case SortOrder.SolveDescending: statesQ = statesQ.OrderByDescending(state => state.SolvedTime ?? DateTime.MaxValue); break; default: throw new ArgumentException($"unknown sort: {sort}"); } PuzzleStatePerTeam = await statesQ.Include(pspt => pspt.Team).Include(pspt => pspt.Puzzle).ToListAsync(); return(Page()); }
private async Task SetupContext(int puzzleId) { Team = await UserEventHelper.GetTeamForPlayer(_context, Event, LoggedInUser); Puzzle = await _context.Puzzles.Where( (p) => p.ID == puzzleId).FirstOrDefaultAsync(); PuzzleState = await(PuzzleStateHelper .GetFullReadOnlyQuery( _context, Event, Puzzle, Team)) .FirstAsync(); SubmissionViews = await(from submission in _context.Submissions join user in _context.PuzzleUsers on submission.Submitter equals user join r in _context.Responses on submission.Response equals r into responses from response in responses.DefaultIfEmpty() where submission.Team == Team && submission.Puzzle == Puzzle orderby submission.TimeSubmitted select new SubmissionView() { Submission = submission, Response = response, SubmitterName = user.Name, FreeformReponse = submission.FreeformResponse, IsFreeform = Puzzle.IsFreeform }).ToListAsync(); Submissions = new List <Submission>(SubmissionViews.Count); foreach (SubmissionView submissionView in SubmissionViews) { Submissions.Add(submissionView.Submission); } PuzzlesCausingGlobalLockout = await PuzzleStateHelper.PuzzlesCausingGlobalLockout(_context, Event, Team).ToListAsync(); if (PuzzleState.SolvedTime != null) { if (!Puzzle.IsFreeform && Submissions?.Count > 0) { AnswerToken = Submissions.Last().SubmissionText; } else { AnswerToken = "(marked as solved by admin or author)"; } } }
public async Task <IActionResult> OnGet(int puzzleId) { PrereqPuzzleId = puzzleId; Puzzle puzzle = await(from puzz in _context.Puzzles where puzz.ID == puzzleId select puzz).FirstOrDefaultAsync(); if (puzzle == null) { return(NotFound()); } // Restrict this page to whistle stop non-puzzles if (puzzle.MinutesOfEventLockout == 0 || puzzle.IsPuzzle) { return(NotFound()); } Team team = await UserEventHelper.GetTeamForPlayer(_context, Event, LoggedInUser); var puzzleStateQuery = PuzzleStateHelper.GetFullReadOnlyQuery(_context, Event, null, team); PuzzleStatePerTeam state = await(from pspt in puzzleStateQuery where pspt.PuzzleID == puzzle.ID select pspt).FirstOrDefaultAsync(); if (state == null) { return(NotFound()); } // Only move forward if the puzzle is open and unsolved if (state.UnlockedTime == null || state.SolvedTime != null) { return(NotFound()); } CurrentPuzzle = puzzle; // Treat all locked puzzles where this is a prerequisite as puzzles that can be unlocked PuzzleOptions = await(from pspt in puzzleStateQuery join prereq in _context.Prerequisites on pspt.PuzzleID equals prereq.PuzzleID where prereq.PrerequisiteID == puzzle.ID && pspt.SolvedTime == null && pspt.UnlockedTime == null select prereq.Puzzle).ToListAsync(); return(Page()); }
public async Task InitializeModelAsync(Puzzle puzzle, Team team, SortOrder?sort) { IQueryable <PuzzleStatePerTeam> statesQ = PuzzleStateHelper.GetFullReadOnlyQuery(this.Context, this.Event, puzzle, team); this.Sort = sort; switch (sort ?? this.DefaultSort) { case SortOrder.PuzzleAscending: statesQ = statesQ.OrderBy(state => state.Puzzle.Name); break; case SortOrder.PuzzleDescending: statesQ = statesQ.OrderByDescending(state => state.Puzzle.Name); break; case SortOrder.TeamAscending: statesQ = statesQ.OrderBy(state => state.Team.Name); break; case SortOrder.TeamDescending: statesQ = statesQ.OrderByDescending(state => state.Team.Name); break; case SortOrder.UnlockAscending: statesQ = statesQ.OrderBy(state => state.UnlockedTime ?? DateTime.MaxValue); break; case SortOrder.UnlockDescending: statesQ = statesQ.OrderByDescending(state => state.UnlockedTime ?? DateTime.MaxValue); break; case SortOrder.SolveAscending: statesQ = statesQ.OrderBy(state => state.SolvedTime ?? DateTime.MaxValue); break; case SortOrder.SolveDescending: statesQ = statesQ.OrderByDescending(state => state.SolvedTime ?? DateTime.MaxValue); break; default: throw new ArgumentException($"unknown sort: {sort}"); } PuzzleStatePerTeam = await statesQ.ToListAsync(); }
public async Task <IActionResult> OnGet(int puzzleId) { Puzzle puzzle = await(from puzz in _context.Puzzles where puzz.ID == puzzleId select puzz).FirstOrDefaultAsync(); if (puzzle == null) { return(NotFound()); } // Restrict to the Backstage metapuzzle if (puzzle.Group != "Backstage") { return(NotFound()); } if (puzzle.SolveValue != 50) { return(NotFound()); } Team team = await UserEventHelper.GetTeamForPlayer(_context, Event, LoggedInUser); var puzzleQuery = PuzzleStateHelper.GetFullReadOnlyQuery(_context, Event, null, team, null); var solvedBackstagePuzzles = await(from PuzzleStatePerTeam pspt in puzzleQuery where pspt.SolvedTime != null && pspt.UnlockedTime != null && pspt.Puzzle.IsPuzzle && pspt.Puzzle.Group == "Backstage" select new BackstageSolveStatus() { PuzzleName = pspt.Puzzle.Name, SolveTime = (int)(pspt.SolvedTime.Value - pspt.UnlockedTime.Value).TotalSeconds }).ToListAsync(); string json = JsonConvert.SerializeObject(solvedBackstagePuzzles); string escapedJson = Uri.EscapeDataString(json); byte[] macKey = Encoding.ASCII.GetBytes(puzzle.Description); HMACSHA1 hasher = new HMACSHA1(macKey); byte[] jsonBytes = Encoding.ASCII.GetBytes(escapedJson); byte[] mac = hasher.ComputeHash(jsonBytes); string macString = Convert.ToBase64String(mac).Replace('/', '_').Replace('+', '.'); return(Redirect($"https://ph20backstage.azurewebsites.net/openingnight/{macString}/{escapedJson}")); }
public async Task PlayerCanSeePuzzleCheck(AuthorizationHandlerContext authContext, IAuthorizationRequirement requirement) { PuzzleUser puzzleUser = await PuzzleUser.GetPuzzleUserForCurrentUser(dbContext, authContext.User, userManager); Puzzle puzzle = await GetPuzzleFromRoute(); Event thisEvent = await GetEventFromRoute(); if (thisEvent != null && puzzle != null) { Team team = await UserEventHelper.GetTeamForPlayer(dbContext, thisEvent, puzzleUser); if (team != null) { IQueryable <PuzzleStatePerTeam> statesQ = PuzzleStateHelper.GetFullReadOnlyQuery(dbContext, thisEvent, puzzle, team); if (statesQ.FirstOrDefault().UnlockedTime != null || thisEvent.AreAnswersAvailableNow) { authContext.Succeed(requirement); } } } }
private async Task SetupContext(int puzzleId) { Team = await UserEventHelper.GetTeamForPlayer(_context, Event, LoggedInUser); Puzzle = await _context.Puzzles.Where( (p) => p.ID == puzzleId).FirstOrDefaultAsync(); PuzzleState = await(PuzzleStateHelper .GetFullReadOnlyQuery( _context, Event, Puzzle, Team)) .FirstOrDefaultAsync(); Submissions = await _context.Submissions.Where( (s) => s.Team == Team && s.Puzzle == Puzzle) .OrderBy(submission => submission.TimeSubmitted) .ToListAsync(); PuzzlesCausingGlobalLockout = await PuzzleStateHelper.PuzzlesCausingGlobalLockout(_context, Event, Team).ToListAsync(); }
public async Task <IActionResult> OnPostUnlock(int puzzleId, int unlockId) { Puzzle puzzle = await(from puzz in _context.Puzzles where puzz.ID == puzzleId select puzz).FirstOrDefaultAsync(); if (puzzle == null) { return(NotFound()); } // Restrict this page to whistle stop non-puzzles if (puzzle.MinutesOfEventLockout == 0 || puzzle.IsPuzzle) { return(NotFound()); } Team team = await UserEventHelper.GetTeamForPlayer(_context, Event, LoggedInUser); using (var transaction = _context.Database.BeginTransaction()) { var puzzleStateQuery = PuzzleStateHelper.GetFullReadOnlyQuery(_context, Event, null, team); PuzzleStatePerTeam prereqState = await(from pspt in puzzleStateQuery where pspt.PuzzleID == puzzle.ID select pspt).FirstOrDefaultAsync(); if (prereqState == null) { return(NotFound()); } // Only move forward if the prereq is open and unsolved if (prereqState.UnlockedTime == null || prereqState.SolvedTime != null) { return(NotFound("Your team has already chosen and can't choose again")); } PuzzleStatePerTeam unlockState = await(from pspt in puzzleStateQuery where pspt.PuzzleID == unlockId select pspt).FirstOrDefaultAsync(); if (unlockState == null) { return(NotFound()); } // The chosen puzzle must be locked (and unsolved) if (unlockState.UnlockedTime != null || unlockState.SolvedTime != null) { return(NotFound("You've already chosen this puzzle")); } // Ensure the puzzle is actually one of the unlock options Prerequisites prereq = await(from pre in _context.Prerequisites where pre.PrerequisiteID == puzzleId && pre.PuzzleID == unlockId select pre).FirstOrDefaultAsync(); if (prereq == null) { return(NotFound()); } await PuzzleStateHelper.SetSolveStateAsync(_context, Event, puzzle, team, DateTime.UtcNow); await PuzzleStateHelper.SetUnlockStateAsync(_context, Event, unlockState.Puzzle, team, DateTime.UtcNow); transaction.Commit(); } Puzzle puzzleToUnlock = await(from p in _context.Puzzles where p.ID == unlockId select p).FirstOrDefaultAsync(); string puzzleUrl; if (puzzleToUnlock.CustomURL != null) { puzzleUrl = PuzzleHelper.GetFormattedUrl(puzzleToUnlock, Event.ID); } else { puzzleUrl = puzzleToUnlock.PuzzleFile.UrlString; } return(Redirect(puzzleUrl)); }
/// <summary> /// Evaulates player submissions then either saves them to the database or returns an error to the caller /// </summary> public static async Task <SubmissionResponse> EvaluateSubmission(PuzzleServerContext context, PuzzleUser loggedInUser, Event thisEvent, int puzzleId, string submissionText, bool allowFreeformSharing) { //Query data needed to process submission Team team = await UserEventHelper.GetTeamForPlayer(context, thisEvent, loggedInUser); Puzzle puzzle = await context.Puzzles.Where( (p) => p.ID == puzzleId).FirstOrDefaultAsync(); PuzzleStatePerTeam puzzleState = await(PuzzleStateHelper .GetFullReadOnlyQuery( context, thisEvent, puzzle, team)) .FirstAsync(); List <Puzzle> puzzlesCausingGlobalLockout = await PuzzleStateHelper.PuzzlesCausingGlobalLockout(context, thisEvent, team).ToListAsync(); // Return early for cases when there's obviously nothing we can do with the submission // The submission text is empty if (String.IsNullOrWhiteSpace(submissionText)) { return(new SubmissionResponse() { ResponseCode = SubmissionResponseCode.EmptySubmission }); } // The puzzle is locked if (puzzle == null || puzzleState.UnlockedTime == null) { return(new SubmissionResponse() { ResponseCode = SubmissionResponseCode.PuzzleLocked }); } // The user or team isn't known if (loggedInUser == null || team == null) { return(new SubmissionResponse() { ResponseCode = SubmissionResponseCode.Unauthorized }); } // The event hasn't started yet if (DateTime.UtcNow < thisEvent.EventBegin) { return(new SubmissionResponse() { ResponseCode = SubmissionResponseCode.Unauthorized }); } // The team is locked out if (puzzleState.IsTeamLockedOut || puzzleState.IsEmailOnlyMode) { return(new SubmissionResponse() { ResponseCode = SubmissionResponseCode.TeamLockedOut }); } // The puzzle has already been solved if (puzzleState.SolvedTime != null) { return(new SubmissionResponse() { ResponseCode = SubmissionResponseCode.AlreadySolved }); } // The team is under a global lockout if (puzzlesCausingGlobalLockout.Count != 0 && !puzzlesCausingGlobalLockout.Contains(puzzle)) { return(new SubmissionResponse() { ResponseCode = SubmissionResponseCode.TeamLockedOut }); } List <SubmissionView> submissionViews = await(from sub in context.Submissions join user in context.PuzzleUsers on sub.Submitter equals user join r in context.Responses on sub.Response equals r into responses from response in responses.DefaultIfEmpty() where sub.Team == team && sub.Puzzle == puzzle orderby sub.TimeSubmitted select new SubmissionView() { Submission = sub, Response = response, SubmitterName = user.Name, FreeformReponse = sub.FreeformResponse, IsFreeform = puzzle.IsFreeform }).ToListAsync(); List <Submission> submissions = new List <Submission>(submissionViews.Count); foreach (SubmissionView submissionView in submissionViews) { submissions.Add(submissionView.Submission); } // The submission is a duplicate bool duplicateSubmission = (from sub in submissions where sub.SubmissionText == Response.FormatSubmission(submissionText) select sub).Any(); if (duplicateSubmission) { return(new SubmissionResponse() { ResponseCode = SubmissionResponseCode.DuplicateSubmission }); } // Create submission and add it to list Submission submission = new Submission { TimeSubmitted = DateTime.UtcNow, Puzzle = puzzleState.Puzzle, Team = puzzleState.Team, Submitter = loggedInUser, AllowFreeformSharing = allowFreeformSharing }; string submissionTextToCheck = Response.FormatSubmission(submissionText); if (puzzle.IsFreeform) { submission.UnformattedSubmissionText = submissionText; } else { submission.SubmissionText = submissionText; } submission.Response = await context.Responses.Where( r => r.Puzzle.ID == puzzleId && submissionTextToCheck == r.SubmittedText) .FirstOrDefaultAsync(); submissions.Add(submission); // Update puzzle state if submission was correct if (submission.Response != null && submission.Response.IsSolution) { await PuzzleStateHelper.SetSolveStateAsync(context, thisEvent, submission.Puzzle, submission.Team, submission.TimeSubmitted); } else if (!puzzle.IsFreeform && submission.Response == null && thisEvent.IsAnswerSubmissionActive) { // We also determine if the puzzle should be set to email-only mode. if (IsPuzzleSubmissionLimitReached( thisEvent, submissions, puzzleState)) { await PuzzleStateHelper.SetEmailOnlyModeAsync(context, thisEvent, submission.Puzzle, submission.Team, true); var authors = await context.PuzzleAuthors.Where((pa) => pa.Puzzle == submission.Puzzle).Select((pa) => pa.Author.Email).ToListAsync(); MailHelper.Singleton.SendPlaintextBcc(authors, $"{thisEvent.Name}: Team {submission.Team.Name} is in email mode for {submission.Puzzle.Name}", ""); } else { // If the submission was incorrect and not a partial solution, // we will do the lockout computations now. DateTime?lockoutExpiryTime = ComputeLockoutExpiryTime( thisEvent, submissions, puzzleState); if (lockoutExpiryTime != null) { await PuzzleStateHelper.SetLockoutExpiryTimeAsync(context, thisEvent, submission.Puzzle, submission.Team, lockoutExpiryTime); } } } context.Submissions.Add(submission); await context.SaveChangesAsync(); // Send back responses for cases where the database has been updated // Correct response if (submission.Response != null && submission.Response.IsSolution) { return(new SubmissionResponse() { ResponseCode = SubmissionResponseCode.Correct, CompleteResponse = submission.Response }); } // Freeform response if (puzzle.IsFreeform) { return(new SubmissionResponse() { ResponseCode = SubmissionResponseCode.Freeform, FreeformResponse = submission.FreeformResponse }); } // Partial response if (submission.Response != null && !submission.Response.IsSolution) { return(new SubmissionResponse() { ResponseCode = SubmissionResponseCode.Partial, CompleteResponse = submission.Response }); } // Default to incorrect return(new SubmissionResponse() { ResponseCode = SubmissionResponseCode.Incorrect }); }
/// <summary> /// Returns whether the user is authorized to view the file /// </summary> /// <param name="eventId">The current event</param> /// <param name="puzzle">The puzzle the file belongs to</param> /// <param name="content">The file</param> private async Task <bool> IsAuthorized(int eventId, Puzzle puzzle, ContentFile content) { Event currentEvent = await(from ev in context.Events where ev.ID == eventId select ev).SingleAsync(); PuzzleUser user = await PuzzleUser.GetPuzzleUserForCurrentUser(context, User, userManager); // Admins can see all files if (await user.IsAdminForEvent(context, currentEvent)) { return(true); } // Authors can see all files attached to their puzzles if (await UserEventHelper.IsAuthorOfPuzzle(context, puzzle, user)) { return(true); } Team team = await UserEventHelper.GetTeamForPlayer(context, currentEvent, user); if (team == null) { return(false); } // Once answers are available, so are all other files if (currentEvent.AreAnswersAvailableNow) { return(true); } PuzzleStatePerTeam puzzleState = await PuzzleStateHelper.GetFullReadOnlyQuery(context, currentEvent, puzzle, team).SingleAsync(); switch (content.FileType) { case ContentFileType.Answer: // The positive case is already handled above by checking AreAnswersAvailableNow return(false); case ContentFileType.Puzzle: case ContentFileType.PuzzleMaterial: if (puzzleState.UnlockedTime != null) { return(true); } else { return(false); } case ContentFileType.SolveToken: if (puzzleState.SolvedTime != null) { return(true); } else { return(false); } default: throw new NotImplementedException(); } }