Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Author versus Admin views on puzzles #148

Merged
merged 6 commits into from
Jan 13, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion ServerCore/Helpers/UserEventHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace ServerCore.Helpers
/// <summary>
/// Includes methods that connect a PuzzleUser with another part of the data model
/// </summary>
public class UserEventHelper
public static class UserEventHelper
{
/// <summary>
/// Returns a list of the author's puzzles for the given event
Expand Down
25 changes: 18 additions & 7 deletions ServerCore/ModelBases/EventSpecificPageModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using ServerCore.DataModel;
using ServerCore.Helpers;

namespace ServerCore.ModelBases
{
Expand All @@ -29,25 +30,35 @@ public PuzzleUser LoggedInUser
{
if (loggedInUser == null)
{
loggedInUser = PuzzleUser.GetPuzzleUserForCurrentUser(puzzleServerContext, User, userManager);
loggedInUser = PuzzleUser.GetPuzzleUserForCurrentUser(_context, User, userManager);
}

return loggedInUser;
}
}

private PuzzleServerContext puzzleServerContext;
protected readonly PuzzleServerContext _context;
tabascq marked this conversation as resolved.
Show resolved Hide resolved
private UserManager<IdentityUser> userManager;

public EventSpecificPageModel()
public EventSpecificPageModel(PuzzleServerContext serverContext, UserManager<IdentityUser> manager)
{
// Default constructor - note that pages that use this constructor won't know what PuzzleUser is signed in
_context = serverContext;
userManager = manager;
}

public EventSpecificPageModel(PuzzleServerContext serverContext, UserManager<IdentityUser> manager)
public async Task<bool> CanAdminPuzzle(Puzzle puzzle)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be handled through the authorization policy. Give me a little time now that I'm not visiting family and I'll get those changes merged in.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for checking in the authorization stuff - which parts of this can the authorization policy handle?

{
puzzleServerContext = serverContext;
userManager = manager;
if (EventRole != EventRole.admin && EventRole != EventRole.author)
{
return false;
}

if (puzzle == null || (EventRole == EventRole.author && !await UserEventHelper.IsAuthorOfPuzzle(_context, puzzle, LoggedInUser)))
{
return false;
}

return true;
}

public class EventBinder : IModelBinder
Expand Down
42 changes: 32 additions & 10 deletions ServerCore/ModelBases/PuzzleStatePerTeamPageModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,39 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ServerCore.DataModel;
using ServerCore.Helpers;

namespace ServerCore.ModelBases
{
public abstract class PuzzleStatePerTeamPageModel : EventSpecificPageModel
{
protected PuzzleServerContext Context { get; }

public SortOrder? Sort { get; set; }

public PuzzleStatePerTeamPageModel(PuzzleServerContext context)
public PuzzleStatePerTeamPageModel(PuzzleServerContext serverContext, UserManager<IdentityUser> userManager) : base(serverContext, userManager)
{
Context = context;
}

public IList<PuzzleStatePerTeam> PuzzleStatePerTeam { get; set; }

protected abstract SortOrder DefaultSort { get; }

public async Task InitializeModelAsync(Puzzle puzzle, Team team, SortOrder? sort)
public async Task<IActionResult> InitializeModelAsync(Puzzle puzzle, Team team, SortOrder? sort)
{
IQueryable<PuzzleStatePerTeam> statesQ = PuzzleStateHelper.GetFullReadOnlyQuery(Context, Event, puzzle, team);
if (puzzle == null && team == null)
{
return NotFound();
}

if (puzzle != null && !await CanAdminPuzzle(puzzle))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://docs.microsoft.com/en-us/aspnet/core/security/authorization/views?view=aspnetcore-2.2&tabs=aspnetcore2x

For example, in this case you'd have an attribute on the StatusModel page that's [Authorize(Policy = "IsEventAdminOrEventAuthor")]

The base class & child classes need an IAuthorizationService passed into the constructor (I believe asp.net handles getting that to them, it just needs to be requested).

Then to see if someone is the author of a puzzle you'd do a check against the policy:
bool isAuthorized = await AuthorizationService.AuthorizeAsync(User, "IsAuthorOfPuzzle"); (I don't have the exact syntax in front of me and we have people who just arrived for dinner, but if you can't find it let me know and I'll track it down after dinner)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the example of an [Authorize] attribute on event create, and I see the example of AuthorizeAsync() in the link you provided - but this comment suggests that I need both at all times, and neither example appears to use both. Do I need to use both mechanisms at the same time, or do I choose only one at a time depending on need?

{
return NotFound();
}

IQueryable<PuzzleStatePerTeam> statesQ = PuzzleStateHelper.GetFullReadOnlyQuery(_context, Event, puzzle, team, EventRole == EventRole.admin ? null : LoggedInUser);
Sort = sort;

switch(sort ?? DefaultSort)
Expand Down Expand Up @@ -58,6 +68,8 @@ public async Task InitializeModelAsync(Puzzle puzzle, Team team, SortOrder? sort
}

PuzzleStatePerTeam = await statesQ.ToListAsync();

return Page();
}

public SortOrder? SortForColumnLink(SortOrder ascendingSort, SortOrder descendingSort)
Expand All @@ -77,14 +89,24 @@ public async Task InitializeModelAsync(Puzzle puzzle, Team team, SortOrder? sort
return result;
}

public Task SetUnlockStateAsync(Puzzle puzzle, Team team, bool value)
public async Task SetUnlockStateAsync(Puzzle puzzle, Team team, bool value)
{
return PuzzleStateHelper.SetUnlockStateAsync(Context, Event, puzzle, team, value ? (DateTime?)DateTime.UtcNow : null);
if (!await CanAdminPuzzle(puzzle))
{
return;
}

await PuzzleStateHelper.SetUnlockStateAsync(_context, Event, puzzle, team, value ? (DateTime?)DateTime.UtcNow : null, EventRole == EventRole.admin ? null : LoggedInUser);
}

public Task SetSolveStateAsync(Puzzle puzzle, Team team, bool value)
public async Task SetSolveStateAsync(Puzzle puzzle, Team team, bool value)
{
return PuzzleStateHelper.SetSolveStateAsync(Context, Event, puzzle, team, value ? (DateTime?)DateTime.UtcNow : null);
if (!await CanAdminPuzzle(puzzle))
{
return;
}

await PuzzleStateHelper.SetSolveStateAsync(_context, Event, puzzle, team, value ? (DateTime?)DateTime.UtcNow : null, EventRole == EventRole.admin ? null : LoggedInUser);
}

public enum SortOrder
Expand Down
45 changes: 40 additions & 5 deletions ServerCore/Pages/Events/CreateDemo.cshtml.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using ServerCore.DataModel;
Expand All @@ -10,16 +11,18 @@ namespace ServerCore.Pages.Events
public class CreateDemoModel : PageModel
{
private readonly PuzzleServerContext _context;
private readonly UserManager<IdentityUser> _userManager;

[BindProperty]
public Event Event { get; set; }

[BindProperty]
public bool StartTheEvent { get; set; }

public CreateDemoModel(PuzzleServerContext context)
public CreateDemoModel(PuzzleServerContext context, UserManager<IdentityUser> userManager)
{
_context = context;
_userManager = userManager;
}

public IActionResult OnGet()
Expand Down Expand Up @@ -73,7 +76,8 @@ public async Task<IActionResult> OnPostAsync()
{
Name = "!!!Get Hopping!!!",
Event = Event,
IsPuzzle = false
IsPuzzle = false,
IsGloballyVisiblePrerequisite = true
};
_context.Puzzles.Add(start);

Expand Down Expand Up @@ -130,6 +134,18 @@ public async Task<IActionResult> OnPostAsync()
};
_context.Puzzles.Add(meta);

Puzzle other = new Puzzle
{
Name = "Rabbit Season",
Event = Event,
IsPuzzle = true,
SolveValue = 10,
Group = "Daffy's Delights",
OrderInGroup = 1,
MinPrerequisiteCount = 1
};
_context.Puzzles.Add(other);

await _context.SaveChangesAsync();

//
Expand All @@ -143,6 +159,8 @@ public async Task<IActionResult> OnPostAsync()
_context.Responses.Add(new Response() { Puzzle = hard, SubmittedText = "ANSWER", ResponseText = "Correct!", IsSolution = true });
_context.Responses.Add(new Response() { Puzzle = meta, SubmittedText = "PARTIAL", ResponseText = "Keep going..." });
_context.Responses.Add(new Response() { Puzzle = meta, SubmittedText = "ANSWER", ResponseText = "Correct!", IsSolution = true });
_context.Responses.Add(new Response() { Puzzle = other, SubmittedText = "PARTIAL", ResponseText = "Keep going..." });
_context.Responses.Add(new Response() { Puzzle = other, SubmittedText = "ANSWER", ResponseText = "Correct!", IsSolution = true });

await _context.SaveChangesAsync();

Expand All @@ -157,6 +175,7 @@ public async Task<IActionResult> OnPostAsync()
_context.Prerequisites.Add(new Prerequisites() { Puzzle = meta, Prerequisite = easy });
_context.Prerequisites.Add(new Prerequisites() { Puzzle = meta, Prerequisite = intermediate });
_context.Prerequisites.Add(new Prerequisites() { Puzzle = meta, Prerequisite = hard });
_context.Prerequisites.Add(new Prerequisites() { Puzzle = other, Prerequisite = start });

await _context.SaveChangesAsync();

Expand All @@ -172,14 +191,30 @@ public async Task<IActionResult> OnPostAsync()
Team team3 = new Team { Name = "Team Buster", Event = Event };
_context.Teams.Add(team3);

await _context.SaveChangesAsync();
var demoCreatorUser = PuzzleUser.GetPuzzleUserForCurrentUser(_context, User, _userManager);
if (demoCreatorUser != null)
{
//
// Event admin/author
//
_context.EventAdmins.Add(new EventAdmins() { Event = Event, Admin = demoCreatorUser });
_context.EventAuthors.Add(new EventAuthors() { Event = Event, Author = demoCreatorUser });

//
// Puzzle author (for Thumper module only)
//
_context.PuzzleAuthors.Add(new PuzzleAuthors() { Puzzle = easy, Author = demoCreatorUser });
_context.PuzzleAuthors.Add(new PuzzleAuthors() { Puzzle = intermediate, Author = demoCreatorUser });
_context.PuzzleAuthors.Add(new PuzzleAuthors() { Puzzle = hard, Author = demoCreatorUser });
_context.PuzzleAuthors.Add(new PuzzleAuthors() { Puzzle = meta, Author = demoCreatorUser });
}

// TODO: Event Owners
// TODO: Puzzle Authors
// TODO: Team Members
// TODO: Files (need to know how to detect whether local blob storage is configured)
// Is there a point to adding Feedback or is that quick/easy enough to demo by hand?

await _context.SaveChangesAsync();

//
// Mark the start puzzle as solved if we were asked to.
//
Expand Down
6 changes: 2 additions & 4 deletions ServerCore/Pages/Events/FastestSolves.cshtml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using ServerCore.DataModel;
using ServerCore.ModelBases;
Expand All @@ -10,13 +11,10 @@ namespace ServerCore.Pages.Events
{
public class FastestSolvesModel : EventSpecificPageModel
{
private readonly PuzzleServerContext _context;

public List<PuzzleStats> Puzzles { get; private set; }

public FastestSolvesModel(PuzzleServerContext context)
public FastestSolvesModel(PuzzleServerContext serverContext, UserManager<IdentityUser> userManager) : base(serverContext, userManager)
{
_context = context;
}

public async Task OnGetAsync()
Expand Down
31 changes: 25 additions & 6 deletions ServerCore/Pages/Events/Map.cshtml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ServerCore.DataModel;
using ServerCore.Helpers;
using ServerCore.ModelBases;

namespace ServerCore.Pages.Events
Expand All @@ -18,16 +21,30 @@ public class MapModel : EventSpecificPageModel

public StateStats[,] StateMap { get; private set; }

public MapModel(PuzzleServerContext context)
public MapModel(PuzzleServerContext serverContext, UserManager<IdentityUser> userManager) : base(serverContext, userManager)
{
_context = context;
_context = serverContext;
}

public async Task OnGetAsync()
public async Task<IActionResult> OnGetAsync()
{
if (EventRole != EventRole.admin && EventRole != EventRole.author)
tabascq marked this conversation as resolved.
Show resolved Hide resolved
{
return NotFound();
}

// get the puzzles and teams
// TODO: Filter puzzles if an author; no need to filter teams. Revisit when authors exist.
List<PuzzleStats> puzzles = await _context.Puzzles.Where(p => p.Event == Event).Select(p => new PuzzleStats() { Puzzle = p }).ToListAsync();
List<PuzzleStats> puzzles;

if (EventRole == EventRole.admin)
{
puzzles = await _context.Puzzles.Where(p => p.Event == Event).Select(p => new PuzzleStats() { Puzzle = p }).ToListAsync();
}
else
{
puzzles = await UserEventHelper.GetPuzzlesForAuthorAndEvent(_context, Event, LoggedInUser).Select(p => new PuzzleStats() { Puzzle = p }).ToListAsync();
}

List<TeamStats> teams = await _context.Teams.Where(t => t.Event == Event).Select(t => new TeamStats() { Team = t }).ToListAsync();

// build an ID-based lookup for puzzles and teams
Expand All @@ -38,7 +55,7 @@ public async Task OnGetAsync()
teams.ForEach(t => teamLookup[t.Team.ID] = t);

// tabulate solve counts and team scores
List<PuzzleStatePerTeam> states = await PuzzleStateHelper.GetSparseQuery(_context, Event, null, null).ToListAsync();
List<PuzzleStatePerTeam> states = await PuzzleStateHelper.GetSparseQuery(_context, Event, null, null, EventRole == EventRole.admin ? null : LoggedInUser).ToListAsync();
List<StateStats> stateList = new List<StateStats>(states.Count);
foreach (PuzzleStatePerTeam state in states)
{
Expand Down Expand Up @@ -89,6 +106,8 @@ public async Task OnGetAsync()
Puzzles = puzzles;
Teams = teams;
StateMap = stateMap;

return Page();
}

public class PuzzleStats
Expand Down
10 changes: 3 additions & 7 deletions ServerCore/Pages/Events/Players.cshtml.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using ServerCore.DataModel;
using ServerCore.ModelBases;
Expand All @@ -13,15 +12,12 @@ namespace ServerCore.Pages.Events
{
public class PlayersModel : EventSpecificPageModel
{
private readonly ServerCore.DataModel.PuzzleServerContext _context;

public IList<TeamMembers> Players { get; set; }

public string Emails { get; set; }

public PlayersModel(ServerCore.DataModel.PuzzleServerContext context)
public PlayersModel(PuzzleServerContext serverContext, UserManager<IdentityUser> userManager) : base(serverContext, userManager)
{
_context = context;
}

public async Task<IActionResult> OnGetAsync(int teamId)
Expand Down
6 changes: 2 additions & 4 deletions ServerCore/Pages/Events/Standings.cshtml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using ServerCore.DataModel;
using ServerCore.ModelBases;
Expand All @@ -10,13 +11,10 @@ namespace ServerCore.Pages.Events
{
public class StandingsModel : EventSpecificPageModel
{
private readonly PuzzleServerContext _context;

public List<TeamStats> Teams { get; private set; }

public StandingsModel(PuzzleServerContext context)
public StandingsModel(PuzzleServerContext serverContext, UserManager<IdentityUser> userManager) : base(serverContext, userManager)
{
_context = context;
}

public async Task OnGetAsync()
Expand Down
Loading