Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ public partial class ClientSyncComponent
[Parameter]
public DateTimeOffset EventEndTimeUtc { get; set; }

[Parameter]
public bool ReadOnly { get; set; }

[Parameter]
public bool SyncEnabled { get; set; } = true;

Expand Down Expand Up @@ -165,7 +168,7 @@ public async void OnSyncablePuzzleLoadedAsync(string mode)
[JSInvokable]
public async void OnPuzzleChangedAsync(JsPuzzleChange[] puzzleChanges)
{
if (!SyncEnabled || Paused)
if (!SyncEnabled || Paused || ReadOnly)
{
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,7 @@ private EventRole GetEventRoleFromRoute()
RouteValueDictionary route = httpContextAccessor.HttpContext.Request.RouteValues;
string eventRole = route["eventRole"] as string;

if (Enum.TryParse<EventRole>(eventRole, out EventRole role))
{
return role;
}

return EventRole.play;
return EventRole.Parse(eventRole);
}

public async Task IsPuzzleAdminCheck(AuthorizationHandlerContext authContext, IAuthorizationRequirement requirement)
Expand Down Expand Up @@ -194,7 +189,7 @@ public async Task IsPlayerOnTeamCheck(AuthorizationHandlerContext authContext, I
{
EventRole role = GetEventRoleFromRoute();

if (role != EventRole.play)
if (role != EventRole.play && !role.IsImpersonating)
{
return;
}
Expand All @@ -205,10 +200,20 @@ public async Task IsPlayerOnTeamCheck(AuthorizationHandlerContext authContext, I

if (thisEvent != null)
{
Team userTeam = await UserEventHelper.GetTeamForPlayer(dbContext, thisEvent, puzzleUser);
if (userTeam != null && userTeam.ID == team.ID)
if (role.IsImpersonating)
{
authContext.Succeed(requirement);
if (team.EventID == thisEvent.ID && await puzzleUser.IsAdminForEvent(dbContext, thisEvent))
{
authContext.Succeed(requirement);
}
}
else
{
Team userTeam = await UserEventHelper.GetTeamForPlayer(dbContext, thisEvent, puzzleUser);
if (userTeam != null && userTeam.ID == team.ID)
{
authContext.Succeed(requirement);
}
}
}
}
Expand Down Expand Up @@ -246,7 +251,19 @@ public async Task PlayerCanSeePuzzleCheck(AuthorizationHandlerContext authContex
}
else
{
Team team = await UserEventHelper.GetTeamForPlayer(dbContext, thisEvent, puzzleUser);
EventRole role = GetEventRoleFromRoute();
Team team = null;
if (role.IsImpersonating)
{
if (await puzzleUser.IsAdminForEvent(dbContext, thisEvent) || await UserEventHelper.IsAuthorOfPuzzle(dbContext, puzzle, puzzleUser))
{
team = await dbContext.Teams.Where(t => t.ID == role.ImpersonationId).FirstOrDefaultAsync();
}
}
else
{
team = await UserEventHelper.GetTeamForPlayer(dbContext, thisEvent, puzzleUser);
}

if (team != null)
{
Expand Down
74 changes: 69 additions & 5 deletions ServerCore/ModelBases/EventRole.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,78 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Diagnostics;

namespace ServerCore.ModelBases
{
public enum EventRole
public class EventRole
{
// TODO: Expand to add impersonateplayer, when there is an advantage to doing so.
// Current sticking point is whether the perf of sync is such that we can turn it on for single player pregame.
// If sync is off for single payer puzzles, there's not much to see.

public EventRoleType Type { get; private set; }
public int ImpersonationId { get; private set; }
public bool IsImpersonating => Type == EventRoleType.impersonateteam;

public override bool Equals(object obj)
{
if (!(obj is EventRole)) return false;
return this == (obj as EventRole);
}

public static bool operator ==(EventRole left, EventRole right)
{
if (Object.ReferenceEquals(left, null)) return Object.ReferenceEquals(right, null);
return !Object.ReferenceEquals(right, null) && left.Type == right.Type && left.ImpersonationId == right.ImpersonationId;
}

public static bool operator !=(EventRole left, EventRole right)
{
return !(left == right);
}

public override string ToString()
{
return (IsImpersonating) ? $"{Type}-{ImpersonationId}" : Type.ToString();
}

public override int GetHashCode()
{
// revisit if we ever get more than 200M teams or players lol
Debug.Assert((1 << 3) > (int)Type);
return (ImpersonationId << 3) + (int)Type;
}

public static EventRole Parse(string s)
{
if (string.IsNullOrEmpty(s)) return null;
s = s.ToLower();

if (s.StartsWith("impersonateteam-"))
{
bool parse = int.TryParse(s.Substring("impersonateteam-".Length), out int id);
if (!parse)
{
return new EventRole() { Type = EventRoleType.play };
}
return new EventRole() { Type = EventRoleType.impersonateteam, ImpersonationId = id };
}
else
{
EventRoleType type = Enum.IsDefined(typeof(EventRoleType), s) ? Enum.Parse<EventRoleType>(s): EventRoleType.play;
return new EventRole() { Type = type };
}
}

public static EventRole admin = new EventRole() { Type = EventRoleType.admin };
public static EventRole author = new EventRole() { Type = EventRoleType.author };
public static EventRole play = new EventRole() { Type = EventRoleType.play };
}

public enum EventRoleType
{
admin = 1,
author,
play
play,
impersonateteam
}
}
31 changes: 15 additions & 16 deletions ServerCore/ModelBases/EventSpecificPageModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ public override async Task OnPageHandlerExecutionAsync(PageHandlerExecutingConte
bool isAuthor = await IsEventAuthor();
if (((EventRole == EventRole.admin) && !isAdmin) ||
((EventRole == EventRole.author) && !isAuthor) ||
((EventRole != EventRole.admin) && (EventRole != EventRole.author) && (EventRole != EventRole.play)))
((EventRole.IsImpersonating) && !isAuthor && !isAdmin) ||
((EventRole != EventRole.admin) && (EventRole != EventRole.author) && (EventRole != EventRole.play) && !EventRole.IsImpersonating))
{
context.Result = Forbid();
return;
Expand Down Expand Up @@ -77,7 +78,7 @@ public async Task<bool> IsRegisteredUser()

if (isRegisteredUser == null)
{
isRegisteredUser = await LoggedInUser.IsRegisteredForEvent(_context, Event);
isRegisteredUser = EventRole.Type == EventRoleType.impersonateteam ? true : await LoggedInUser.IsRegisteredForEvent(_context, Event);
}

return isRegisteredUser.Value;
Expand All @@ -90,7 +91,7 @@ public async Task<bool> PlayerHasTeamForEvent()

if(playerIsOnTeam == null)
{
playerIsOnTeam = await LoggedInUser.IsPlayerOnTeam(_context, Event);
playerIsOnTeam = EventRole.Type == EventRoleType.impersonateteam ? true : await LoggedInUser.IsPlayerOnTeam(_context, Event);
}

return playerIsOnTeam.Value;
Expand Down Expand Up @@ -160,15 +161,22 @@ public async Task<Team> GetTeamAsync()
{
if (this.team == null)
{
this.team = await UserEventHelper.GetTeamForPlayer(_context, Event, LoggedInUser);
if (EventRole.Type == EventRoleType.impersonateteam)
{
this.team = await _context.Teams.Where(t => t.ID == EventRole.ImpersonationId).FirstOrDefaultAsync();
}
else
{
this.team = await UserEventHelper.GetTeamForPlayer(_context, Event, LoggedInUser);
}
}

return team;
}

public async Task<int> GetTeamId()
{
if (EventRole == ModelBases.EventRole.play)
if (EventRole == ModelBases.EventRole.play || EventRole.Type == EventRoleType.impersonateteam)
{
Team team = await this.GetTeamAsync();
return team != null ? team.ID : -1;
Expand All @@ -181,7 +189,7 @@ public async Task<int> GetTeamId()

public async Task<bool> GetShowTeamAnnouncement()
{
if (EventRole == ModelBases.EventRole.play)
if (EventRole == ModelBases.EventRole.play || EventRole.Type == EventRoleType.impersonateteam)
{
Team team = await this.GetTeamAsync();
return team != null ? team.ShowTeamAnnouncement : false;
Expand Down Expand Up @@ -241,16 +249,7 @@ public class RoleBinder : IModelBinder
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
string eventRoleAsString = bindingContext.ActionContext.RouteData.Values["eventRole"] as string;
if (eventRoleAsString == null)
{
eventRoleAsString = ModelBases.EventRole.play.ToString();
}
eventRoleAsString = eventRoleAsString.ToLower();

if (Enum.IsDefined(typeof(EventRole), eventRoleAsString))
{
bindingContext.Result = ModelBindingResult.Success(Enum.Parse(typeof(EventRole), eventRoleAsString));
}
bindingContext.Result = ModelBindingResult.Success(EventRole.Parse(eventRoleAsString));
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion ServerCore/Pages/Puzzles/Play.cshtml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public async Task OnGetAsync(
HasLiveEvents = (await LiveEventHelper.GetLiveEventsForEvent(_context, Event, false, false)).Any();
}

Team = await UserEventHelper.GetTeamForPlayer(_context, Event, LoggedInUser);
Team = await base.GetTeamAsync();

if (Team != null)
{
Expand Down
13 changes: 5 additions & 8 deletions ServerCore/Pages/Puzzles/Status.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,7 @@
</th>
}
<th>
Printed
</th>
<th>
Notes
Impersonate
</th>
<th>
IsLockedOut
Expand Down Expand Up @@ -93,10 +90,10 @@
</td>
}
<td>
@item.Printed
</td>
<td>
@item.Notes
@if (item.UnlockedTime != null)
{
<a asp-page="/Submissions/Index" asp-route-puzzleId="@Model.Puzzle.ID" asp-route-eventRole="@($"impersonateteam-{@item.Team.ID}")">Impersonate</a>
}
</td>
<td>
@item.IsLockedOut
Expand Down
4 changes: 4 additions & 0 deletions ServerCore/Pages/Submissions/AuthorIndex.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@
<a asp-page="./AuthorIndex" asp-route-puzzleId="@Model.Puzzle?.ID" asp-route-teamId="@Model.Team?.ID" asp-route-hideFreeform="@Model.HideFreeform" asp-route-favoritesOnly="false" asp-route-sort="@Model.Sort">| Clear Favorite Filter</a>
}
</div>
@if(Model.Team != null && Model.Puzzle != null)
{
<a asp-page="/Submissions/Index" asp-route-puzzleId="@Model.Puzzle.ID" asp-route-eventRole="@($"impersonateteam-{@Model.Team.ID}")">Impersonate this Puzzle</a>
}
<br/>
<div>
Submitted answers shown below are capitalized and stripped of non-alphanumeric characters
Expand Down
36 changes: 22 additions & 14 deletions ServerCore/Pages/Submissions/Index.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@
}

.puzzle-content-wrapper.unclaimed {
background: #666666;
background: #666666;
}

.puzzle-content-wrapper.unclaimed > * {
Expand Down Expand Up @@ -523,20 +523,27 @@ else

<div class="evencolumn no-print">
<div>
<div class="flexwrapper">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<label for="submissionText" class="marginright margintop answer-label-customizable">Answer:</label>
<input name="submissionText" maxlength="1000" id="submitBox" class="form-control marginright" data-val="true" data-val-required="Your answer cannot be empty" />
<input type="submit" id="submitButton" value="Submit" class="btn btn-default marginright" title="Submitted answers will automatically be capitalized and stripped of non-alphanumeric characters" />
@Html.ValidationSummary(true)
<span class="text-danger"><span class="field-validation-valid" data-valmsg-for="submissionText" data-valmsg-replace="true"></span></span>
</div>
@if (Model.Puzzle.IsFreeform)
@if (Model.EventRole.Type == ModelBases.EventRoleType.impersonateteam)
{
<label for="allowFreeformSharing" class="marginright margintop">
<input class="checkboxfix" type="checkbox" name="allowFreeformSharing" asp-for="AllowFreeformSharing" title="Allow other players to see this after the event" />
Allow other players to see this after the event
</label>
<h3>Read-only: Impersonating @Model.Team.Name</h3>
}
else
{
<div class="flexwrapper">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<label for="submissionText" class="marginright margintop answer-label-customizable">Answer:</label>
<input name="submissionText" maxlength="1000" id="submitBox" class="form-control marginright" data-val="true" data-val-required="Your answer cannot be empty" />
<input type="submit" id="submitButton" value="Submit" class="btn btn-default marginright" title="Submitted answers will automatically be capitalized and stripped of non-alphanumeric characters" />
@Html.ValidationSummary(true)
<span class="text-danger"><span class="field-validation-valid" data-valmsg-for="submissionText" data-valmsg-replace="true"></span></span>
</div>
@if (Model.Puzzle.IsFreeform)
{
<label for="allowFreeformSharing" class="marginright margintop">
<input class="checkboxfix" type="checkbox" name="allowFreeformSharing" asp-for="AllowFreeformSharing" title="Allow other players to see this after the event" />
Allow other players to see this after the event
</label>
}
}
</div>
<div class="inlinewrapper">
Expand Down Expand Up @@ -588,6 +595,7 @@ else
param-TableSASUrl="@Model.SyncTableSasUrl"
param-PuzzleUnlockTimeUtc="(DateTimeOffset?)@Model.PuzzleState.UnlockedTime"
param-EventEndTimeUtc="(DateTimeOffset)@Model.Event.AnswerSubmissionEnd"
param-ReadOnly="@(Model.EventRole != ModelBases.EventRole.play)"
/>
}
@if (!Model.IsPuzzleForSinglePlayer && Model.Event.AllowBlazor && !Model.Event.EphemeralHackKillPresence)
Expand Down
4 changes: 2 additions & 2 deletions ServerCore/Pages/Submissions/Index.cshtml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ public async Task<IActionResult> OnGetClaimPuzzleAsync(int puzzleId)

private async Task SetupContext(int puzzleId)
{
Team = await UserEventHelper.GetTeamForPlayer(_context, Event, LoggedInUser);
Team = await GetTeamAsync();

if (Event.HasPlayerClasses)
{
Expand Down Expand Up @@ -387,7 +387,7 @@ orderby submission.TimeSubmitted
SyncTableSasUrl = tableClient.GenerateSasUri(sasBuilder);
}

Team = await UserEventHelper.GetTeamForPlayer(_context, Event, LoggedInUser);
Team = await GetTeamAsync();

PuzzleState = await (PuzzleStateHelper
.GetFullReadOnlyQuery(
Expand Down
1 change: 1 addition & 0 deletions ServerCore/Pages/Teams/Index.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@
<a asp-page="/Hints/AuthorIndex" asp-route-teamId="@item.ID">Hints Taken</a><br />
}
<a asp-page="/Threads/PuzzleThreads" asp-route-teamId="@item.ID">Threads</a><br />
<a asp-page="/Puzzles/Play" asp-route-eventRole="@($"impersonateteam-{item.ID}")"> Impersonate</a><br />
------<br />
<a asp-page="./MergeInto" asp-route-teamId="@item.ID">Merge Into...</a><br/>
<a asp-page="./Delete" asp-route-teamId="@item.ID">Delete</a>
Expand Down
Loading