protected override ValueTask HandleRequirementAsync(AuthorizationHandlerContext context, AdminRequirement requirement, DiscordMember member) { if (DiscordClientProvider.HasAdminRole(member)) { context.Succeed(requirement); } return(default);
protected override async ValueTask HandleRequirementAsync(AuthorizationHandlerContext context, CharacterOwnerRequirement requirement, DiscordMember member) { if (requirement.AllowAdmin && DiscordClientProvider.HasAdminRole(member)) { context.Succeed(requirement); return; } long?characterId = context.Resource switch { long id => id, CharacterDto character => character.Id, _ => null }; if (characterId.HasValue) { var discordId = (long)member.Id; var characterIdString = characterId.Value.ToString(); if (context.User.HasClaim(AppClaimTypes.Character, characterIdString) || await _context.UserClaims.AsNoTracking().CountAsync(claim => claim.UserId == discordId && claim.ClaimType == AppClaimTypes.Character && claim.ClaimValue == characterIdString) > 0) { context.Succeed(requirement); } } } }
public LootListsController( ApplicationDbContext context, TimeZoneInfo serverTimeZoneInfo, IAuthorizationService authorizationService, TelemetryClient telemetry, DiscordClientProvider discordClientProvider) { _context = context; _serverTimeZoneInfo = serverTimeZoneInfo; _authorizationService = authorizationService; _telemetry = telemetry; _discordClientProvider = discordClientProvider; }
protected override async ValueTask HandleRequirementAsync(AuthorizationHandlerContext context, CharacterOwnerRequirement requirement, DiscordMember member) { if (requirement.AllowAdmin && DiscordClientProvider.HasAdminRole(member)) { context.Succeed(requirement); return; } long characterId; long discordId = (long)member.Id; switch (context.Resource) { case long l: characterId = l; break; case CharacterDto dto: characterId = dto.Id; break; case Character character: if (character.OwnerId == discordId) { context.Succeed(requirement); } return; default: return; } if (await _context.Characters.AsNoTracking().CountAsync(c => c.Id == characterId && c.OwnerId == discordId) > 0) { context.Succeed(requirement); } }
protected override async ValueTask HandleRequirementAsync(AuthorizationHandlerContext context, TeamLeaderRequirement requirement, DiscordMember member) { if (requirement.AllowAdmin && DiscordClientProvider.HasAdminRole(member)) { context.Succeed(requirement); return; } if ((requirement.AllowRaidLeader && DiscordClientProvider.HasRaidLeaderRole(member)) || (requirement.AllowLootMaster && DiscordClientProvider.HasLootMasterRole(member)) || (requirement.AllowRecruiter && DiscordClientProvider.HasRecruiterRole(member))) { long?teamId = context.Resource switch { long id => id, TeamDto team => team.Id, _ => null }; if (teamId.HasValue) { var discordId = (long)member.Id; var teamIdString = teamId.Value.ToString(); if (context.User.HasClaim(AppClaimTypes.RaidLeader, teamIdString) || await _context.UserClaims.AsNoTracking().CountAsync(claim => claim.UserId == discordId && claim.ClaimType == AppClaimTypes.RaidLeader && claim.ClaimValue == teamIdString) > 0) { context.Succeed(requirement); } } else { context.Succeed(requirement); } } } }
protected override async ValueTask HandleRequirementAsync(AuthorizationHandlerContext context, TeamLeaderRequirement requirement, DiscordMember member) { if (requirement.AllowAdmin && DiscordClientProvider.HasAdminRole(member)) { context.Succeed(requirement); return; } if ((requirement.AllowRaidLeader && DiscordClientProvider.HasRaidLeaderRole(member)) || (requirement.AllowLootMaster && DiscordClientProvider.HasLootMasterRole(member)) || (requirement.AllowRecruiter && DiscordClientProvider.HasRecruiterRole(member))) { long?teamId = context.Resource switch { long id => id, TeamDto team => team.Id, RaidTeam team => team.Id, _ => null }; if (teamId.HasValue) { var discordId = (long)member.Id; if (await _context.RaidTeamLeaders.AsNoTracking().CountAsync(rtl => rtl.UserId == discordId && rtl.RaidTeamId == teamId) > 0) { context.Succeed(requirement); } } else { context.Succeed(requirement); } } } }
protected DiscordAuthorizationHandler(DiscordClientProvider discordClientProvider) { DiscordClientProvider = discordClientProvider ?? throw new System.ArgumentNullException(nameof(discordClientProvider)); }
public async Task <ActionResult <EncounterDropDto> > PutAssign(long id, [FromBody] AwardDropSubmissionDto dto, [FromServices] TimeZoneInfo realmTimeZoneInfo, [FromServices] IAuthorizationService auth, [FromServices] DiscordClientProvider dcp) { var now = realmTimeZoneInfo.TimeZoneNow(); var drop = await _context.Drops.FindAsync(id); if (drop is null) { return(NotFound()); } var teamId = await _context.Raids .AsNoTracking() .Where(r => r.Id == drop.EncounterKillRaidId) .Select(r => (long?)r.RaidTeamId) .FirstOrDefaultAsync(); if (!teamId.HasValue) { return(NotFound()); } var authResult = await auth.AuthorizeAsync(User, teamId, AppPolicies.LootMaster); if (!authResult.Succeeded) { return(Unauthorized()); } if (dto.WinnerId.HasValue && drop.WinnerId.HasValue) { ModelState.AddModelError(nameof(dto.WinnerId), "Existing winner must be cleared before setting a new winner."); return(ValidationProblem()); } drop.AwardedAt = now; drop.AwardedBy = User.GetDiscordId(); var scope = await _context.GetCurrentPriorityScopeAsync(); var observedDates = await _context.Raids .AsNoTracking() .Where(r => r.RaidTeamId == teamId) .OrderByDescending(r => r.StartedAt) .Select(r => r.StartedAt.Date) .Distinct() .Take(scope.ObservedAttendances) .ToListAsync(); var presentTeamRaiders = await _context.CharacterEncounterKills .AsTracking() .Where(cek => cek.EncounterKillEncounterId == drop.EncounterKillEncounterId && cek.EncounterKillRaidId == drop.EncounterKillRaidId) .Select(c => new { Id = c.CharacterId, c.Character.TeamId, c.Character.MemberStatus, Attended = c.Character.Attendances.Where(x => !x.IgnoreAttendance && x.Raid.RaidTeamId == teamId && x.RemovalId == null && observedDates.Contains(x.Raid.StartedAt.Date)) .Select(x => x.Raid.StartedAt.Date) .Distinct() .OrderByDescending(x => x) .Take(scope.ObservedAttendances) .Count(), Entry = _context.LootListEntries.Where(e => !e.DropId.HasValue && e.LootList.CharacterId == c.CharacterId && (e.ItemId == drop.ItemId || e.Item !.RewardFromId == drop.ItemId)) .OrderByDescending(e => e.Rank) .Select(e => new { e.Id, e.Rank, e.LootList.Status, Passes = e.Passes.Count(p => p.RemovalId == null) }) .FirstOrDefault() })
public IdentityProfileService(DiscordClientProvider discordClientProvider, ILogger <DefaultProfileService> logger, ApplicationDbContext context) : base(logger) { _discordClientProvider = discordClientProvider; _context = context; }
public async Task <ActionResult <ApproveOrRejectLootListResponseDto> > PostApproveOrReject(long characterId, byte phase, [FromBody] ApproveOrRejectLootListDto dto, [FromServices] DiscordClientProvider dcp) { var character = await _context.Characters.FindAsync(characterId); if (character is null) { return(NotFound()); } var list = await _context.CharacterLootLists.FindAsync(characterId, phase); if (list is null) { return(NotFound()); } var team = await _context.RaidTeams.FindAsync(dto.TeamId); if (team is null) { ModelState.AddModelError(nameof(dto.TeamId), "Team does not exist."); return(ValidationProblem()); } var auth = await _authorizationService.AuthorizeAsync(User, team.Id, AppPolicies.RaidLeaderOrAdmin); if (!auth.Succeeded) { return(Unauthorized()); } if (!ValidateTimestamp(list, dto.Timestamp)) { return(Problem("Loot list has been changed. Refresh before trying to update the status again.")); } if (list.Status != LootListStatus.Submitted && character.TeamId.HasValue) { return(Problem("Can't approve or reject a list that isn't in the submitted state.")); } var submissions = await _context.LootListTeamSubmissions .AsTracking() .Where(s => s.LootListCharacterId == list.CharacterId && s.LootListPhase == list.Phase) .ToListAsync(); var teamSubmission = submissions.Find(s => s.TeamId == dto.TeamId); if (teamSubmission is null) { return(Unauthorized()); } MemberDto?member = null; if (dto.Approved) { _context.LootListTeamSubmissions.RemoveRange(submissions); if (character.TeamId.HasValue) { if (character.TeamId != dto.TeamId) { return(Problem("That character is not assigned to the specified raid team.")); } } else { var idString = character.Id.ToString(); var claim = await _context.UserClaims.AsNoTracking() .Where(c => c.ClaimType == AppClaimTypes.Character && c.ClaimValue == idString) .Select(c => new { c.UserId }) .FirstOrDefaultAsync(); if (claim is not null) { var otherClaims = await _context.UserClaims.AsNoTracking() .Where(c => c.ClaimType == AppClaimTypes.Character && c.UserId == claim.UserId) .Select(c => c.ClaimValue) .ToListAsync(); var characterIds = new List <long>(); foreach (var otherClaim in otherClaims) { if (long.TryParse(otherClaim, out var cid)) { characterIds.Add(cid); } } var existingCharacterName = await _context.Characters .AsNoTracking() .Where(c => characterIds.Contains(c.Id) && c.TeamId == team.Id) .Select(c => c.Name) .FirstOrDefaultAsync(); if (existingCharacterName?.Length > 0) { return(Problem($"The owner of this character is already on this team as {existingCharacterName}.")); } } character.TeamId = dto.TeamId; character.MemberStatus = RaidMemberStatus.FullTrial; character.JoinedTeamAt = _serverTimeZoneInfo.TimeZoneNow(); } list.ApprovedBy = User.GetDiscordId(); if (list.Status != LootListStatus.Locked) { list.Status = LootListStatus.Approved; } await _context.SaveChangesAsync(); var characterQuery = _context.Characters.AsNoTracking().Where(c => c.Id == character.Id); var scope = await _context.GetCurrentPriorityScopeAsync(); foreach (var m in await HelperQueries.GetMembersAsync(_context, _serverTimeZoneInfo, characterQuery, scope, team.Id, team.Name, true)) { member = m; break; } } else { _context.LootListTeamSubmissions.Remove(teamSubmission); if (character.TeamId == team.Id) { character.TeamId = null; } if (submissions.Count == 1) { if (list.Status != LootListStatus.Locked) { list.Status = LootListStatus.Editing; } list.ApprovedBy = null; } await _context.SaveChangesAsync(); } _telemetry.TrackEvent("LootListStatusChanged", User, props => { props["CharacterId"] = list.CharacterId.ToString(); props["Phase"] = list.Phase.ToString(); props["Status"] = list.Status.ToString(); props["Method"] = "ApproveOrReject"; }); var characterIdString = characterId.ToString(); var owner = await _context.UserClaims .AsNoTracking() .Where(c => c.ClaimType == AppClaimTypes.Character && c.ClaimValue == characterIdString) .Select(c => c.UserId) .FirstOrDefaultAsync(); if (owner > 0) { var sb = new StringBuilder("Your application to ") .Append(team.Name) .Append(" for ") .Append(character.Name) .Append(" was ") .Append(dto.Approved ? "approved!" : "rejected."); if (dto.Message?.Length > 0) { sb.AppendLine() .Append("<@") .Append(User.GetDiscordId()) .AppendLine("> said:") .Append("> ") .Append(dto.Message); } await dcp.SendDmAsync(owner, m => m.WithContent(sb.ToString())); } return(new ApproveOrRejectLootListResponseDto { Timestamp = list.Timestamp, Member = member, LootListStatus = list.Status }); }
public async Task <ActionResult <TimestampDto> > PostSubmit(long characterId, byte phase, [FromBody] SubmitLootListDto dto, [FromServices] DiscordClientProvider dcp) { if (dto.SubmitTo.Count == 0) { ModelState.AddModelError(nameof(dto.SubmitTo), "Loot List must be submitted to at least one raid team."); return(ValidationProblem()); } var character = await _context.Characters.FindAsync(characterId); if (character is null) { return(NotFound()); } if (character.Deactivated) { return(Problem("Character has been deactivated.")); } var list = await _context.CharacterLootLists.FindAsync(characterId, phase); if (list is null) { return(NotFound()); } var auth = await _authorizationService.AuthorizeAsync(User, list.CharacterId, AppPolicies.CharacterOwnerOrAdmin); if (!auth.Succeeded) { return(Unauthorized()); } if (!ValidateTimestamp(list, dto.Timestamp)) { return(Problem("Loot list has been changed. Refresh before trying to update the status again.")); } if (list.Status != LootListStatus.Editing && character.TeamId.HasValue) { return(Problem("Can't submit a list that is not editable.")); } var teams = await _context.RaidTeams .AsNoTracking() .Where(t => dto.SubmitTo.Contains(t.Id)) .Select(t => new { t.Id, t.Name }) .ToDictionaryAsync(t => t.Id); if (teams.Count != dto.SubmitTo.Count) { return(Problem("One or more raid teams specified do not exist.")); } var submissions = await _context.LootListTeamSubmissions .AsTracking() .Where(s => s.LootListCharacterId == characterId && s.LootListPhase == list.Phase) .ToListAsync(); foreach (var id in dto.SubmitTo) { if (submissions.Find(s => s.TeamId == id) is null) { _context.LootListTeamSubmissions.Add(new() { LootListCharacterId = list.CharacterId, LootListPhase = list.Phase, TeamId = id }); } } foreach (var submission in submissions) { if (!dto.SubmitTo.Contains(submission.TeamId)) { _context.LootListTeamSubmissions.Remove(submission); } } list.ApprovedBy = null; if (list.Status == LootListStatus.Editing) { list.Status = LootListStatus.Submitted; } await _context.SaveChangesAsync(); _telemetry.TrackEvent("LootListStatusChanged", User, props => { props["CharacterId"] = list.CharacterId.ToString(); props["Phase"] = list.Phase.ToString(); props["Status"] = list.Status.ToString(); props["Method"] = "Submit"; }); const string format = "You have a new application to {0} from {1}. ({2} {3})"; await foreach (var claim in _context.UserClaims .AsNoTracking() .Where(claim => claim.ClaimType == AppClaimTypes.RaidLeader) .Select(claim => new { claim.UserId, claim.ClaimValue }) .AsAsyncEnumerable()) { if (long.TryParse(claim.ClaimValue, out var teamId) && teams.TryGetValue(teamId, out var team) && submissions.Find(s => s.TeamId == teamId) is null) // Don't notify when submission status doesn't change. { await dcp.SendDmAsync(claim.UserId, m => m.WithContent(string.Format( format, team.Name, character.Name, character.Race.GetDisplayName(), list.MainSpec.GetDisplayName(true)))); } } return(new TimestampDto { Timestamp = list.Timestamp }); }
public TeamLeaderPolicyHandler(ApplicationDbContext context, DiscordClientProvider discordClientProvider) : base(discordClientProvider) { _context = context; }
public AdminPolicyHandler(DiscordClientProvider discordClientProvider) : base(discordClientProvider) { }
public DiscordBackgroundService(DiscordClientProvider discordClientProvider, IServiceProvider serviceProvider) { _discordClientProvider = discordClientProvider; _serviceProvider = serviceProvider; }
public IdentityProfileService(DiscordClientProvider discordClientProvider, ILogger <DefaultProfileService> logger) : base(logger) { _discordClientProvider = discordClientProvider; }
public MembersController(DiscordClientProvider discordClientProvider) { _discordClientProvider = discordClientProvider; }
public CharacterOwnerPolicyHandler(ApplicationDbContext context, DiscordClientProvider discordClientProvider) : base(discordClientProvider) { _context = context; }
public AccountController(SignInManager <AppUser> signInManager, DiscordClientProvider discordClientProvider, ILogger <AccountController> logger) { _signInManager = signInManager; _discordClientProvider = discordClientProvider; _logger = logger; }
public MemberPolicyHandler(DiscordClientProvider discordClientProvider) : base(discordClientProvider) { }
public async Task <ActionResult <EncounterDropDto> > PutAssign(long id, [FromBody] AwardDropSubmissionDto dto, [FromServices] DiscordClientProvider dcp) { var now = _realmTimeZone.TimeZoneNow(); var drop = await _context.Drops.FindAsync(id); if (drop is null) { return(NotFound()); } var raid = await _context.Raids .AsNoTracking() .Where(r => r.Id == drop.EncounterKillRaidId) .Select(r => new { r.RaidTeamId, r.LocksAt }) .FirstOrDefaultAsync(); if (raid is null) { return(NotFound()); } var authResult = await _authorizationService.AuthorizeAsync(User, raid.RaidTeamId, AppPolicies.LootMaster); if (!authResult.Succeeded) { return(Unauthorized()); } if (DateTimeOffset.UtcNow > raid.LocksAt) { return(Problem("Can't alter a locked raid.")); } if (dto.WinnerId.HasValue && drop.WinnerId.HasValue) { ModelState.AddModelError(nameof(dto.WinnerId), "Existing winner must be cleared before setting a new winner."); return(ValidationProblem()); } drop.AwardedAt = now; drop.AwardedBy = User.GetDiscordId(); var scope = await _context.GetCurrentPriorityScopeAsync(); var teamId = raid.RaidTeamId; var attendances = await _context.GetAttendanceTableAsync(teamId, scope.ObservedAttendances); var donationMatrix = await _context.GetDonationMatrixAsync(d => d.Character.Attendances.Any(a => a.RaidId == drop.EncounterKillRaidId), scope); var presentTeamRaiders = await _context.CharacterEncounterKills .AsTracking() .Where(cek => cek.EncounterKillEncounterId == drop.EncounterKillEncounterId && cek.EncounterKillRaidId == drop.EncounterKillRaidId && cek.EncounterKillTrashIndex == drop.EncounterKillTrashIndex) .Select(cek => cek.Character) .Select(ConvertToDropInfo(drop.ItemId)) .ToListAsync(); await _context.Entry(drop).Collection(drop => drop.Passes).LoadAsync(); drop.Passes.Clear(); if (dto.WinnerId.HasValue) { var winner = presentTeamRaiders.Find(e => e.Id == dto.WinnerId); if (winner is null) { ModelState.AddModelError(nameof(dto.WinnerId), "Character was not present for the kill."); return(ValidationProblem()); } int?winnerPrio = null; if (winner.Entry is not null) { winnerPrio = winner.Entry.Rank; var passes = await _context.DropPasses .AsTracking() .Where(p => p.LootListEntryId == winner.Entry.Id && p.RemovalId == null) .ToListAsync(); Debug.Assert(passes.Count == winner.Entry.Passes); var donated = donationMatrix.GetCreditForMonth(winner.Id, now); attendances.TryGetValue(winner.Id, out int attended); foreach (var bonus in PrioCalculator.GetAllBonuses(scope, attended, winner.MemberStatus, donated, passes.Count, winner.Enchanted, winner.Prepared)) { winnerPrio = winnerPrio.Value + bonus.Value; } drop.WinningEntry = await _context.LootListEntries.FindAsync(winner.Entry.Id); Debug.Assert(drop.WinningEntry is not null); drop.WinningEntry.Drop = drop; drop.WinningEntry.DropId = drop.Id; foreach (var pass in passes) { pass.WonEntryId = winner.Entry.Id; } } drop.WinnerId = winner.Id; foreach (var killer in presentTeamRaiders) { if (killer.Entry is not null && killer.TeamId == teamId && killer != winner) { var thisPrio = (int)killer.Entry.Rank; var donated = donationMatrix.GetCreditForMonth(killer.Id, now); attendances.TryGetValue(killer.Id, out int attended); foreach (var bonus in PrioCalculator.GetAllBonuses(scope, attended, killer.MemberStatus, donated, killer.Entry.Passes, killer.Enchanted, killer.Prepared)) { thisPrio += bonus.Value; } _context.DropPasses.Add(new DropPass { Drop = drop, DropId = drop.Id, CharacterId = killer.Id, RelativePriority = thisPrio - (winnerPrio ?? 0), LootListEntryId = killer.Entry.Id }); } } } else { var oldWinningEntry = await _context.LootListEntries .AsTracking() .Where(e => e.DropId == drop.Id) .SingleOrDefaultAsync(); drop.Winner = null; drop.WinnerId = null; drop.WinningEntry = null; if (oldWinningEntry is not null) { oldWinningEntry.Drop = null; oldWinningEntry.DropId = null; await foreach (var pass in _context.DropPasses .AsTracking() .Where(pass => pass.WonEntryId == oldWinningEntry.Id) .AsAsyncEnumerable()) { pass.WonEntryId = null; } } } await _context.SaveChangesAsync(); var kill = await _context.EncounterKills .AsNoTracking() .Where(kill => kill.EncounterId == drop.EncounterKillEncounterId && kill.RaidId == drop.EncounterKillRaidId && kill.TrashIndex == drop.EncounterKillTrashIndex) .Select(kill => new { kill.DiscordMessageId, kill.KilledAt, kill.RaidId, TeamName = kill.Raid.RaidTeam.Name, EncounterName = kill.Encounter.Name }) .FirstAsync(); var drops = new List <(uint, string, string?)>(); await foreach (var d in _context.Drops .AsNoTracking() .Where(d => d.EncounterKillEncounterId == drop.EncounterKillEncounterId && d.EncounterKillRaidId == drop.EncounterKillRaidId && d.EncounterKillTrashIndex == drop.EncounterKillTrashIndex) .Select(d => new { d.ItemId, ItemName = d.Item.Name, WinnerName = (string?)d.Winner !.Name })