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"));
        }
Example #3
0
        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());
        }
Example #4
0
        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());
        }
Example #5
0
        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());
        }
Example #11
0
        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());
        }
Example #15
0
        public async Task <GithubWebhookDTO> RecreateSecret()
        {
            var existing = await GetOrCreateHook();

            await database.AdminActions.AddAsync(new AdminAction()
            {
                Message       = "Github webhook secret recreated",
                PerformedById = HttpContext.AuthenticatedUser() !.Id
            });
Example #16
0
        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());
        }
Example #17
0
        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()));
        }
Example #19
0
        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());
        }
Example #20
0
        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)
            });
        }
Example #22
0
        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());
        }
Example #23
0
        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());
        }
Example #25
0
        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());
        }
Example #26
0
        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());
        }
Example #28
0
        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());
        }