public async Task <IActionResult> CreateNew([Required][FromBody] AccessKeyDTO newKey) { if (string.IsNullOrWhiteSpace(newKey.Description) || await database.AccessKeys .FirstOrDefaultAsync(a => a.Description == newKey.Description) != null) { return(BadRequest("Description is empty or a key with that description already exists")); } var key = new AccessKey() { Description = newKey.Description, KeyCode = Guid.NewGuid().ToString(), KeyType = newKey.KeyType }; var action = new AdminAction() { Message = $"New access key ({key.Description}) created with scope: {key.KeyType}", PerformedById = HttpContext.AuthenticatedUser() !.Id }; await database.AccessKeys.AddAsync(key); await database.AdminActions.AddAsync(action); await database.SaveChangesAsync(); return(Ok($"Created new key \"{key.Id}\" with code: {key.KeyCode}")); }
public async Task <ActionResult <string> > ForceClearTokens([FromBody] ForceClearTokensForm request) { var target = await database.Users.FindAsync(request.TargetUserId); if (target == null) { return(NotFound()); } // Early exit if already cleared if (target.LfsToken == null && target.ApiToken == null) { return(Ok("Tokens already cleared")); } var user = HttpContext.AuthenticatedUser() !; logger.LogInformation("Force clearing tokens on user {Id} by admin {Email}", target.Id, user.Email); await database.AdminActions.AddAsync(new AdminAction() { Message = "Force cleared user's tokens", TargetUserId = target.Id, PerformedById = user.Id }); // It's assumed here that the authentication also used ApplicationDbContext so that this works target.LfsToken = null; target.ApiToken = null; await database.SaveChangesAsync(); return(Ok("Tokens cleared")); }
public async Task <IActionResult> UpdateLFSProject([Required][FromBody] LFSProjectDTO request) { var item = await FindAndCheckAccess(request.Id); if (item == null || item.Deleted) { return(NotFound()); } var user = HttpContext.AuthenticatedUser() !; var(changes, description, _) = ModelUpdateApplyHelper.ApplyUpdateRequestToModel(item, request); if (!changes) { return(Ok()); } item.BumpUpdatedAt(); await database.AdminActions.AddAsync(new AdminAction() { Message = $"LFS Project {item.Id} edited", // TODO: there could be an extra info property where the description is stored PerformedById = user.Id, }); await database.SaveChangesAsync(); logger.LogInformation("LFS project {Id} edited by {Email}, changes: {Description}", item.Id, user.Email, description); return(Ok()); }
public async Task <IActionResult> UpdateSymbol([Required][FromBody] DebugSymbolDTO request) { var symbol = await database.DebugSymbols.FindAsync(request.Id); if (symbol == null) { return(NotFound()); } var user = HttpContext.AuthenticatedUser() !; var(changes, description, _) = ModelUpdateApplyHelper.ApplyUpdateRequestToModel(symbol, request); if (!changes) { return(Ok()); } symbol.BumpUpdatedAt(); await database.ActionLogEntries.AddAsync(new ActionLogEntry() { Message = $"DebugSymbol {symbol.Id} edited", // TODO: there could be an extra info property where the description of the edit is stored PerformedById = user.Id, }); await database.SaveChangesAsync(); logger.LogInformation("DebugSymbol {Id} edited by {Email}, changes: {Description}", symbol.Id, user.Email, description); return(Ok()); }
public async Task <ActionResult <PatreonCredits> > DownloadPatronCredits() { var patrons = await database.Patrons.Where(p => p.Suspended != true).ToListAsync(); var patreonSettings = await database.PatreonSettings.OrderBy(s => s.Id).FirstOrDefaultAsync(); if (patreonSettings == null) { return(Problem("Patreon settings not found")); } logger.LogInformation("Patron list for credits has been accessed by {Email}", HttpContext.AuthenticatedUser() !.Email); var groups = patrons.GroupBy(p => p.RewardId).ToList(); var vips = groups.FirstOrDefault(g => g.Key == patreonSettings.VipRewardId); var devbuilds = groups.FirstOrDefault(g => g.Key == patreonSettings.DevbuildsRewardId); var other = groups.FirstOrDefault(g => g.Key != patreonSettings.VipRewardId && g.Key != patreonSettings.DevbuildsRewardId); var result = new PatreonCredits() { VIPPatrons = PreparePatronGroup(vips), DevBuildPatrons = PreparePatronGroup(devbuilds), SupporterPatrons = PreparePatronGroup(other), }; Response.Headers.Add("Content-Disposition", "attachment; filename=\"patrons.json\""); return(result); }
public async Task <IActionResult> CreateLinkCode() { var user = HttpContext.AuthenticatedUser() !; // Fail if too many links if (await database.LauncherLinks.CountAsync(l => l.UserId == user.Id) >= AppInfo.DefaultMaxLauncherLinks) { return(BadRequest("You already have the maximum number of launchers linked")); } var modifiableUser = await database.Users.FindAsync(user.Id); if (modifiableUser == null) { throw new HttpResponseException() { Status = StatusCodes.Status500InternalServerError, Value = "Failed to find target user" }; } modifiableUser.LauncherLinkCode = Guid.NewGuid().ToString(); modifiableUser.LauncherCodeExpires = DateTime.UtcNow + AppInfo.LauncherLinkCodeExpireTime; await database.SaveChangesAsync(); logger.LogInformation("User {Email} started linking a new launcher (code created)", user.Email); return(Ok(modifiableUser.LauncherLinkCode)); }
public async Task <IActionResult> SendTestMail([FromBody][Required] EmailTestRequestForm request) { if (!mailSender.Configured) { return(BadRequest("Email is not configured")); } logger.LogInformation("Test email sent by {Email} to {Recipient}", HttpContext.AuthenticatedUser() !.Email, request.Recipient); try { await mailSender.SendEmail(new MailRequest(request.Recipient, "Test Email from ThriveDevCenter") { PlainTextBody = "This is a test email from ThriveDevCenter.\n If you received this, then things are working.", HtmlBody = "<p>This is a test email from ThriveDevCenter.</p>" + "<p>If you received this, then things are working.</p>", }, CancellationToken.None); } catch (Exception e) { logger.LogError("Error when sending test email: {@E}", e); return(Problem("Error sending test mail. See server logs for more details.")); } return(Ok()); }
public async Task <ActionResult <PagedResult <LauncherLinkDTO> > > GetLinks([Required] long userId, [Required] string sortColumn, [Required] SortDirection sortDirection, [Required][Range(1, int.MaxValue)] int page, [Required][Range(1, 50)] int pageSize) { // Only admins can view other user's info if (userId != HttpContext.AuthenticatedUser() !.Id && !HttpContext.HasAuthenticatedUserWithAccess(UserAccessLevel.Admin, AuthenticationScopeRestriction.None)) { return(Forbid()); } IQueryable <LauncherLink> query; try { query = database.LauncherLinks.Where(l => l.UserId == userId) .OrderBy(sortColumn, sortDirection); } catch (ArgumentException e) { logger.LogWarning("Invalid requested order: {@E}", e); throw new HttpResponseException() { Value = "Invalid data selection or sort" }; } var objects = await query.ToPagedResultAsync(page, pageSize); return(objects.ConvertResult(i => i.GetDTO())); }
public async Task <ActionResult> DeleteSecret([Required] long projectId, [Required] long id) { var project = await database.CiProjects.FindAsync(projectId); if (project == null) { return(NotFound()); } var item = await database.CiSecrets.FirstOrDefaultAsync(s => s.CiProjectId == project.Id && s.CiSecretId == id); if (item == null) { return(NotFound()); } var user = HttpContext.AuthenticatedUser() !; await database.AdminActions.AddAsync(new AdminAction() { Message = $"Secret \"{item.SecretName}\" ({item.CiSecretId}) deleted from project {project.Id}", PerformedById = user.Id, }); database.CiSecrets.Remove(item); await database.SaveChangesAsync(); logger.LogInformation("Secret {SecretName} ({CiSecretId}) deleted by {Email} from project {Id}", item.SecretName, item.CiSecretId, user.Email, project.Id); return(Ok()); }
public async Task <IActionResult> Activate([Required] long id) { var cla = await database.Clas.FindAsync(id); if (cla == null) { return(NotFound()); } if (cla.Active) { return(BadRequest("Already active")); } // Other active CLAs need to become inactive foreach (var otherCla in await database.Clas.Where(c => c.Active).ToListAsync()) { otherCla.Active = false; logger.LogInformation("CLA {Id} is being made inactive due to activating {Id2}", otherCla.Id, cla.Id); } cla.Active = true; await database.SaveChangesAsync(); logger.LogInformation("CLA {Id} activated by {Email}", cla.Id, HttpContext.AuthenticatedUser() !.Email); jobClient.Enqueue <InvalidatePullRequestsWithCLASignaturesJob>(x => x.Execute(CancellationToken.None)); return(Ok()); }
public async Task <ActionResult <MeetingMemberDTO> > GetMember([Required] long id, [Required] long userId) { var access = GetCurrentUserAccess(); var meeting = await GetMeetingWithReadAccess(id, access); if (meeting == null) { return(NotFound()); } // Only meeting owner sees all members, other people can only check their own info var user = HttpContext.AuthenticatedUser() !; if (meeting.OwnerId != user.Id && userId != user.Id && !user.HasAccessLevel(UserAccessLevel.Admin)) { return(this.WorkingForbid("You don't have permission to view other people's join status")); } var member = await GetMeetingMember(meeting.Id, userId); if (member == null) { return(NotFound()); } return(member.GetDTO()); }
/// <summary> /// Variant that returns also information if user login details were not provided at all /// </summary> public static AuthenticationResult HasAuthenticatedUserWithAccessExtended(this HttpContext context, UserAccessLevel requiredAccess, AuthenticationScopeRestriction?requiredRestriction) { // Non-logged in is always allowed (even if scope restrictions don't match as in that case the user could // just not authenticate at all to have access, so preventing that seems a bit silly) if (requiredAccess == UserAccessLevel.NotLoggedIn) { return(AuthenticationResult.Success); } var user = context.AuthenticatedUser(); if (user == null) { return(AuthenticationResult.NoUser); } if (!user.HasAccessLevel(requiredAccess)) { return(AuthenticationResult.NoAccess); } if (requiredRestriction != null) { if (context.AuthenticatedUserRestriction() != requiredRestriction) { return(AuthenticationResult.NoAccess); } } return(AuthenticationResult.Success); }
public async Task <ActionResult <DevBuildDownload> > DownloadBuild([Required] long buildId) { Response.ContentType = "application/json"; var build = await database.DevBuilds.Include(b => b.StorageItem) .FirstOrDefaultAsync(b => b.Id == buildId); if (build == null) { throw new HttpResponseException() { Status = StatusCodes.Status404NotFound, Value = new BasicJSONErrorResult("Build not found", "Build with specified ID not found").ToString() }; } if (build.StorageItem == null) { throw new HttpResponseException() { Status = StatusCodes.Status404NotFound, Value = new BasicJSONErrorResult("Invalid build", "The specified build doesn't have a valid download file").ToString() }; } if (!build.StorageItem.IsReadableBy(HttpContext.AuthenticatedUser())) { throw new HttpResponseException() { Status = StatusCodes.Status403Forbidden, Value = new BasicJSONErrorResult("No access", "You don't have permission to access this build's download file").ToString() }; } var version = await build.StorageItem.GetHighestUploadedVersion(database); if (version == null || version.StorageFile == null) { throw new HttpResponseException() { Status = StatusCodes.Status404NotFound, Value = new BasicJSONErrorResult("Invalid build", "The specified build's storage doesn't have a valid uploaded file").ToString() }; } build.Downloads += 1; logger.LogInformation("DevBuild {Id} downloaded from {RemoteAddress} with the launcher", build.Id, HttpContext.Connection.RemoteIpAddress); await database.SaveChangesAsync(); return(new DevBuildDownload( remoteDownloads.CreateDownloadFor(version.StorageFile, AppInfo.RemoteStorageDownloadExpireTime), build.BuildZipHash)); }
public async Task <ActionResult <ControlledServerDTO> > ForceTerminateServer(long id) { FailIfNotConfigured(); var server = await database.ControlledServers.FindAsync(id); if (server == null) { return(NotFound()); } if (server.Status == ServerStatus.Terminated) { return(Ok("Server already terminated")); } if (string.IsNullOrEmpty(server.InstanceId)) { return(BadRequest("Can't terminate a server with no instance id")); } var user = HttpContext.AuthenticatedUser() !; if (server.ReservationType != ServerReservationType.None) { await database.AdminActions.AddAsync(new AdminAction() { Message = $"Server {id} terminated by an admin while it was reserved", PerformedById = user.Id, }); } else if (server.Status == ServerStatus.Provisioning) { await database.AdminActions.AddAsync(new AdminAction() { Message = $"Server {id} terminated by an admin while it was provisioning", PerformedById = user.Id, }); } else { await database.AdminActions.AddAsync(new AdminAction() { Message = $"Server {id} terminated by an admin", PerformedById = user.Id, }); } await ec2Controller.TerminateInstance(server.InstanceId); server.Status = ServerStatus.Terminated; UpdateCommonServerStatuses(server); await database.SaveChangesAsync(); return(server.GetDTO()); }
public async Task <GithubWebhookDTO> RecreateSecret() { var existing = await GetOrCreateHook(); await database.AdminActions.AddAsync(new AdminAction() { Message = "Github webhook secret recreated", PerformedById = HttpContext.AuthenticatedUser() !.Id });
public async Task <IActionResult> JoinMeeting([Required] long id) { var access = GetCurrentUserAccess(); var meeting = await GetMeetingWithReadAccess(id, access); if (meeting == null) { return(NotFound()); } // TODO: should admins be able to always join? if (meeting.JoinAccess > access) { return(this.WorkingForbid("You don't have permission to join this meeting")); } if (meeting.EndedAt != null) { return(BadRequest("This meeting has already ended")); } var user = HttpContext.AuthenticatedUser() !; // Fail if already joined if (await GetMeetingMember(meeting.Id, user.Id) != null) { return(BadRequest("You have already joined this meeting")); } // Don't allow if already started and grace period is over if (DateTime.UtcNow > meeting.StartsAt + meeting.JoinGracePeriod) { return(BadRequest("You are too late to join this meeting")); } // Allow join await database.ActionLogEntries.AddAsync(new ActionLogEntry() { Message = $"User joined meeting {meeting.Id}", PerformedById = user.Id, }); await database.MeetingMembers.AddAsync(new MeetingMember() { MeetingId = meeting.Id, UserId = user.Id, }); await database.SaveChangesAsync(); logger.LogInformation("User {Email} joined meeting {Name} ({Id})", user.Email, meeting.Name, meeting.Id); return(Ok()); }
public async Task <IActionResult> UnVerifyBuild([Required] long id, bool siblingsAsWell = true) { var build = await database.DevBuilds.FindAsync(id); if (build == null) { return(NotFound()); } var user = HttpContext.AuthenticatedUser() !; bool didSomething = false; if (siblingsAsWell) { foreach (var sibling in await GetSiblingBuilds(build)) { if (!sibling.Verified) { continue; } logger.LogInformation("Marking sibling devbuild {Id} as unverified as well", sibling.Id); sibling.Verified = false; sibling.VerifiedById = null; didSomething = true; } } if (build.Verified) { logger.LogInformation("Marking devbuild {Id} unverified by user {Email}", build.Id, user.Email); build.Verified = false; build.VerifiedById = null; didSomething = true; } if (!didSomething) { return(Ok("Nothing needed to be unverified")); } await database.ActionLogEntries.AddAsync(new ActionLogEntry() { Message = $"Verification removed from build {id}", PerformedById = user.Id }); await database.SaveChangesAsync(); return(Ok()); }
public async Task <ActionResult <PagedResult <StorageItemInfo> > > GetFolderContents([Required] string sortColumn, [Required] SortDirection sortDirection, [Required][Range(1, int.MaxValue)] int page, [Required][Range(1, 500)] int pageSize, long?parentId = null) { // NOTE: we don't verify the parent accesses recursively, so for example if a folder has public read, but // it is contained in a private folder, the contents can be read through this if the parent id is known. StorageItem?item = null; if (parentId != null) { item = await FindAndCheckAccess(parentId.Value); if (item == null) { return(NotFound("Folder doesn't exist, or you don't have access to it")); } } else { // Everyone has read access to the root folder } IAsyncEnumerable <StorageItem> query; try { query = database.StorageItems.Where(i => i.ParentId == parentId).ToAsyncEnumerable() .OrderByDescending(p => p.Ftype).ThenBy(sortColumn, sortDirection); } catch (ArgumentException e) { logger.LogWarning("Invalid requested order: {@E}", e); throw new HttpResponseException() { Value = "Invalid data selection or sort" }; } // Filter out objects not readable by current user // NOTE: that as a special case folder owner always sees all items, even if their contents are not readable // (this is to make things consistent with the notifications hub) var reader = HttpContext.AuthenticatedUser(); if (item == null || item.OwnerId == null || item.OwnerId != reader?.Id) { query = query.Where(i => i.IsReadableBy(reader)); } // And then return the contents of this folder to the requester var objects = await query.ToPagedResultAsync(page, pageSize); return(objects.ConvertResult(i => i.GetInfo())); }
public async Task <IActionResult> EndMeeting([Required] long id) { var access = GetCurrentUserAccess(); var meeting = await GetMeetingWithReadAccess(id, access); if (meeting == null) { return(NotFound()); } var user = HttpContext.AuthenticatedUser() !; if (meeting.OwnerId != user.Id && !user.HasAccessLevel(UserAccessLevel.Admin)) { return(this.WorkingForbid("You don't have permission to end this meeting")); } if (meeting.EndedAt != null) { return(BadRequest("The meeting has already been ended")); } // Need to close still open polls var pollsToClose = await database.MeetingPolls.Where(p => p.MeetingId == meeting.Id && p.ClosedAt == null) .ToListAsync(); foreach (var poll in pollsToClose) { logger.LogInformation("Closing a poll due to meeting closing: {Id}-{PollId}", poll.MeetingId, poll.PollId); poll.ClosedAt = DateTime.UtcNow; // Queue a job to calculate the results jobClient.Schedule <ComputePollResultsJob>(x => x.Execute(poll.MeetingId, poll.PollId, CancellationToken.None), TimeSpan.FromSeconds(15)); } await database.ActionLogEntries.AddAsync(new ActionLogEntry() { Message = $"Meeting ({meeting.Id}) ended by a user", PerformedById = user.Id, }); meeting.EndedAt = DateTime.UtcNow; meeting.BumpUpdatedAt(); await database.SaveChangesAsync(); logger.LogInformation("Meeting {Id} has been ended by {Email}", meeting.Id, user.Email); return(Ok()); }
private AssociationResourceAccess GetCurrentUserAccess() { var user = HttpContext.AuthenticatedUser(); var access = AssociationResourceAccess.Public; if (user != null) { access = user.ComputeAssociationAccessLevel(); } return(access); }
public ActionResult <LauncherConnectionStatus> CheckStatus() { Response.ContentType = "application/json"; var user = HttpContext.AuthenticatedUser() !; return(new LauncherConnectionStatus() { Valid = true, Username = user.UserName ?? user.Email, Email = user.Email, Developer = user.HasAccessLevel(UserAccessLevel.Developer) }); }
public async Task <IActionResult> RefreshPollResults([Required] long id, [Required] long pollId) { var access = GetCurrentUserAccess(); var meeting = await GetMeetingWithReadAccess(id, access); if (meeting == null) { return(NotFound()); } var user = HttpContext.AuthenticatedUser() !; var poll = await database.MeetingPolls.FindAsync(id, pollId); if (poll == null) { return(NotFound()); } if (poll.ClosedAt == null) { return(BadRequest("The poll is not closed")); } if (DateTime.UtcNow - poll.PollResultsCreatedAt < TimeSpan.FromSeconds(30)) { return(BadRequest("The poll has been (re)computed in the past 30 seconds")); } if (meeting.OwnerId != user.Id && !user.HasAccessLevel(UserAccessLevel.Admin)) { return(this.WorkingForbid("You don't have permission to recompute polls in this meeting")); } await database.ActionLogEntries.AddAsync(new ActionLogEntry() { Message = $"Poll in meeting ({meeting.Id}) with title: {poll.Title} has been recomputed. " + "Note that random value used for tiebreak will be recomputed and may change the result.", PerformedById = user.Id, }); jobClient.Enqueue <ComputePollResultsJob>(x => x.Execute(poll.MeetingId, poll.PollId, CancellationToken.None)); logger.LogInformation("User {Email} has queued recompute for poll {Id} at {UtcNow}", user.Email, poll.PollId, DateTime.UtcNow); await database.SaveChangesAsync(); return(Ok()); }
public async Task <IActionResult> Create([Required][FromBody] ExternalServerDTO request) { FailIfNotConfigured(); if (request.PublicAddress == null) { return(BadRequest("Missing address")); } if (string.IsNullOrEmpty(request.SSHKeyFileName) || request.SSHKeyFileName.Contains("..") || request.SSHKeyFileName.Contains("/")) { return(BadRequest("Invalid SSH key name format")); } if (!serverSSHAccess.IsValidKey(request.SSHKeyFileName)) { return(BadRequest("Invalid SSH key specified")); } // Test connection try { serverSSHAccess.ConnectTo(request.PublicAddress.ToString(), request.SSHKeyFileName); } catch (Exception e) { logger.LogWarning("Failing to add a new external server due to connect failure: {@E}", e); return(BadRequest("Can't access the specified IP address with the specified key")); } // Don't allow duplicate IPs if (await database.ExternalServers.FirstOrDefaultAsync(s => s.PublicAddress != null && s.PublicAddress.Equals(request.PublicAddress)) != null) { return(BadRequest("There is already a server configured with that IP address")); } var server = new ExternalServer() { PublicAddress = request.PublicAddress, SSHKeyFileName = request.SSHKeyFileName, }; await database.ExternalServers.AddAsync(server); await database.AdminActions.AddAsync(new AdminAction() { Message = $"New external server with IP {request.PublicAddress} added", PerformedById = HttpContext.AuthenticatedUser() !.Id, });
public async Task <ActionResult <PagedResult <SessionDTO> > > DeleteAllUserSessions([Required] long id) { bool admin = HttpContext.HasAuthenticatedUserWithAccess(UserAccessLevel.Admin, AuthenticationScopeRestriction.None); var user = await database.Users.FindAsync(id); var actingUser = HttpContext.AuthenticatedUser() !; if (user == null) { return(NotFound()); } // Has to be an admin or performing an action on their own data if (!admin && actingUser.Id != user.Id) { return(NotFound()); } var sessions = await database.Sessions.Where(s => s.UserId == id).ToListAsync(); if (sessions.Count < 1) { return(Ok()); } if (admin && actingUser.Id != user.Id) { await database.AdminActions.AddAsync(new AdminAction() { Message = "Forced logout", PerformedById = actingUser.Id, TargetUserId = user.Id, }); } else { logger.LogInformation("User ({Email}) deleted their sessions from {RemoteIpAddress}", user.Email, HttpContext.Connection.RemoteIpAddress); } database.Sessions.RemoveRange(sessions); await database.SaveChangesAsync(); await InvalidateSessions(sessions.Select(s => s.Id)); return(Ok()); }
public async Task <IActionResult> ClosePoll([Required] long id, [Required] long pollId) { var access = GetCurrentUserAccess(); var meeting = await GetMeetingWithReadAccess(id, access); if (meeting == null) { return(NotFound()); } var user = HttpContext.AuthenticatedUser() !; var poll = await database.MeetingPolls.FindAsync(id, pollId); if (poll == null) { return(NotFound()); } if (poll.ClosedAt != null) { return(BadRequest("The poll is already closed")); } if (meeting.OwnerId != user.Id && !user.HasAccessLevel(UserAccessLevel.Admin)) { return(this.WorkingForbid("You don't have permission to close polls in this meeting")); } await database.ActionLogEntries.AddAsync(new ActionLogEntry() { Message = $"Poll ({poll.PollId}) closed early in meeting ({meeting.Id}), title: {poll.Title}", PerformedById = user.Id, }); poll.ClosedAt = DateTime.UtcNow; poll.AutoCloseAt = null; poll.ManuallyClosedById = user.Id; await database.SaveChangesAsync(); jobClient.Enqueue <ComputePollResultsJob>(x => x.Execute(poll.MeetingId, poll.PollId, CancellationToken.None)); logger.LogInformation("User {Email} has closed a poll {Id} early in meeting {Id2} at {UtcNow}", user.Email, poll.PollId, id, DateTime.UtcNow); return(Ok()); }
public async Task<IActionResult> CancelRunningJob([Required] long projectId, [Required] long buildId, [Required] long jobId) { var job = await database.CiJobs.Include(j => j.Build!).ThenInclude(b => b.CiProject) .Include(j => j.CiJobOutputSections) .FirstOrDefaultAsync(j => j.CiProjectId == projectId && j.CiBuildId == buildId && j.CiJobId == jobId); if (job == null) return NotFound(); if (job.Build?.CiProject == null) throw new NotLoadedModelNavigationException(); if (job.Build.CiProject.Deleted) return NotFound(); if (job.State == CIJobState.Finished) return BadRequest("Can't cancel a finished job"); long cancelSectionId = 0; foreach (var section in job.CiJobOutputSections) { if (section.CiJobOutputSectionId > cancelSectionId) cancelSectionId = section.CiJobOutputSectionId; } await job.CreateFailureSection(database, "Job canceled by a user", "Canceled", ++cancelSectionId); var user = HttpContext.AuthenticatedUser()!; logger.LogInformation("CI job {ProjectId}-{BuildId}-{JobId} canceled by {Email}", projectId, buildId, jobId, user.Email); // TODO: would be nice to have that non-admin action log type await database.LogEntries.AddAsync(new LogEntry() { Message = $"CI job {projectId}-{buildId}-{jobId} canceled by an user", TargetUserId = user.Id, }); await database.SaveChangesAsync(); jobClient.Enqueue<SetFinishedCIJobStatusJob>(x => x.Execute(projectId, buildId, jobId, false, CancellationToken.None)); return Ok(); }
public async Task <ActionResult <CLADTO> > GetSingle([Required] long id) { var cla = await database.Clas.FindAsync(id); if (cla == null) { return(NotFound()); } if (!cla.Active && HttpContext.AuthenticatedUser()?.Admin != true) { return(this.WorkingForbid("Only admins can view non-active CLAs")); } return(cla.GetDTO()); }
public async Task <IActionResult> RemoveBuildOfTheDay() { // Unmark all BOTD of the day builds await RemoveAllBOTDStatuses(); var user = HttpContext.AuthenticatedUser() !; await database.AdminActions.AddAsync(new AdminAction() { Message = "BOTD unset", PerformedById = user.Id, }); await database.SaveChangesAsync(); logger.LogInformation("BOTDs cleared by {Email}", user.Email); return(Ok()); }
public async Task <IActionResult> CreateNew([Required][FromBody] CLADTO request) { var newCla = new Cla() { Active = request.Active, RawMarkdown = request.RawMarkdown, }; bool inactivated = false; // Other active CLAs need to become inactive if new one is added if (newCla.Active) { foreach (var cla in await database.Clas.Where(c => c.Active).ToListAsync()) { cla.Active = false; inactivated = true; logger.LogInformation("CLA {Id} is being made inactive due to creating a new one", cla.Id); } } var user = HttpContext.AuthenticatedUser() !; await database.AdminActions.AddAsync(new AdminAction() { Message = $"New CLA with active status: {newCla.Active} created", PerformedById = user.Id, }); await database.Clas.AddAsync(newCla); await database.SaveChangesAsync(); logger.LogInformation("New CLA {Id} with active: {Active} created by {Email}", newCla.Id, newCla.Active, user.Email); if (inactivated) { jobClient.Enqueue <InvalidatePullRequestsWithCLASignaturesJob>(x => x.Execute(CancellationToken.None)); } return(Ok()); }
public async Task <IActionResult> Create([Required] long projectId, [Required][FromBody] CreateCISecretForm request) { var project = await database.CiProjects.FindAsync(projectId); if (project == null) { return(NotFound()); } if (await database.CiSecrets.FirstOrDefaultAsync(s => s.CiProjectId == project.Id && s.SecretName == request.SecretName && s.UsedForBuildTypes == request.UsedForBuildTypes) != null) { return(BadRequest("A secret with the given name and type already exists")); } var previousSecretId = await database.CiSecrets.Where(s => s.CiProjectId == project.Id) .MaxAsync(s => (long?)s.CiSecretId) ?? 0; var user = HttpContext.AuthenticatedUser() !; await database.AdminActions.AddAsync(new AdminAction() { Message = $"New secret \"{request.SecretName}\" created for project {project.Id}", PerformedById = user.Id, }); await database.CiSecrets.AddAsync(new CiSecret() { CiProjectId = project.Id, CiSecretId = previousSecretId + 1, SecretName = request.SecretName, SecretContent = request.SecretContent ?? string.Empty, UsedForBuildTypes = request.UsedForBuildTypes, }); await database.SaveChangesAsync(); logger.LogInformation("New secret {SecretName} created by {Email} for {Id}", request.SecretName, user.Email, project.Id); return(Ok()); }