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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Collections.Generic;
using Ahk.GitHub.Monitor.Config;
using Ahk.GitHub.Monitor.Services.EventDispatch;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Moq;
Expand All @@ -13,6 +15,21 @@ internal static class FunctionBuilder
GitHubAppId = "appid", GitHubAppPrivateKey = "appprivatekey", GitHubWebhookSecret = "webhooksecret"
};


public static GitHubMonitorFunction Create(IEventDispatchService dispatchService = null)
=> new(dispatchService ?? new Mock<IEventDispatchService>().Object, Options.Create(AppConfig), new Mock<ILogger<GitHubMonitorFunction>>().Object);
{
var configValues = new Dictionary<string, string>
{
{ "GitHubMonitorConfig:test:GitHubAppId", AppConfig.GitHubAppId },
{ "GitHubMonitorConfig:test:GitHubAppPrivateKey", AppConfig.GitHubAppPrivateKey },
{ "GitHubMonitorConfig:test:GitHubWebhookSecret", AppConfig.GitHubWebhookSecret }
};

var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(configValues)
.Build();

return new GitHubMonitorFunction(
dispatchService ?? new Mock<IEventDispatchService>().Object, new Mock<ILogger<GitHubMonitorFunction>>().Object, configuration);
}
}
28 changes: 22 additions & 6 deletions github-monitor/Ahk.GitHub.Monitor/GitHubMonitorFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
using Ahk.GitHub.Monitor.EventHandlers.BaseAndUtils;
using Ahk.GitHub.Monitor.Helpers;
using Ahk.GitHub.Monitor.Services.EventDispatch;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.Functions.Worker;
Expand All @@ -26,6 +28,13 @@ public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)]
HttpRequestData request)
{
string keyVaultUrl = Environment.GetEnvironmentVariable("KEY_VAULT_URI");
if (keyVaultUrl == null)
{
return new BadRequestObjectResult(new { error = "Please set environment variable KEY_VAULT_URI" });
}
var secretClient = new SecretClient(new Uri(keyVaultUrl), new DefaultAzureCredential());

request.Headers.TryGetValues("X-GitHub-Event", out IEnumerable<string> eventNameValues);
var eventName = eventNameValues?.FirstOrDefault();
request.Headers.TryGetValues("X-GitHub-Delivery", out IEnumerable<string> deliveryIdValues);
Expand All @@ -42,6 +51,11 @@ public async Task<IActionResult> Run(
return new BadRequestObjectResult(new { error = "X-GitHub-Event header missing" });
}

if (eventName == "ping")
{
return new OkObjectResult("pong");
}

if (string.IsNullOrEmpty(receivedSignature))
{
return new BadRequestObjectResult(new { error = "X-Hub-Signature-256 header missing" });
Expand All @@ -56,19 +70,21 @@ public async Task<IActionResult> Run(

var orgName = parsedRequestBody.Repository.Owner.Login;

var orgConfig = new GitHubMonitorConfig();
configuration.GetSection(GitHubMonitorConfig.GetSectionName(orgName)).Bind(orgConfig);
var githubAppId = await secretClient.GetSecretAsync($"GitHubMonitorConfig--{orgName}--GitHubAppId");
var githubAppPrivateKey = await secretClient.GetSecretAsync($"GitHubMonitorConfig--{orgName}--GitHubAppPrivateKey");
var githubWebhookSecret = await secretClient.GetSecretAsync($"GitHubMonitorConfig--{orgName}--GitHubWebhookSecret");


if (string.IsNullOrEmpty(orgConfig.GitHubWebhookSecret))
if (string.IsNullOrEmpty(githubWebhookSecret.Value.Value))
{
return new ObjectResult(new { error = "GitHub secret not configured" })
{
StatusCode = StatusCodes.Status500InternalServerError
};
}

if (string.IsNullOrEmpty(orgConfig.GitHubAppId) ||
string.IsNullOrEmpty(orgConfig.GitHubAppPrivateKey))
if (string.IsNullOrEmpty(githubAppId.Value.Value) ||
string.IsNullOrEmpty(githubAppPrivateKey.Value.Value))
{
return new ObjectResult(new { error = "GitHub App ID/Token not configured" })
{
Expand All @@ -77,7 +93,7 @@ public async Task<IActionResult> Run(
}

if (!GitHubSignatureValidator.IsSignatureValid(requestBody, receivedSignature,
orgConfig.GitHubWebhookSecret))
githubWebhookSecret.Value.Value))
{
return new BadRequestObjectResult(new { error = "Payload signature not valid" });
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@
<ItemGroup>
<PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="AutSoft.Linq" Version="0.10.0" />
<PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.7.0" />
<PackageReference Include="BouncyCastle.Cryptography" Version="2.5.1" />
<PackageReference Include="CsvHelper" Version="32.0.3" />
<PackageReference Include="jose-jwt" Version="5.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Http.Features" Version="5.0.17" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>

<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Http.Features">
<HintPath>..\..\..\..\.dotnet\shared\Microsoft.AspNetCore.App\8.0.11\Microsoft.AspNetCore.Http.Features.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Configuration.Binder">
<HintPath>..\..\..\..\.dotnet\shared\Microsoft.AspNetCore.App\8.0.11\Microsoft.Extensions.Configuration.Binder.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

using GradeManagement.Data.Models;
using GradeManagement.Shared.Dtos;
using GradeManagement.Shared.Dtos.Request;
using GradeManagement.Shared.Dtos.Response;

using Assignment = GradeManagement.Data.Models.Assignment;
Expand All @@ -21,7 +22,7 @@ public class AutoMapperProfile : Profile
public AutoMapperProfile()
{
CreateMap<Assignment, Shared.Dtos.Assignment>();
CreateMap<Course, Shared.Dtos.Course>();
CreateMap<Course, CourseResponse>();
CreateMap<Exercise, ExerciseResponse>().ForMember(dest => dest.ScoreTypes,
opt => opt.MapFrom(src =>
src.ScoreTypeExercises.Where(ste => ste.ExerciseId == src.Id)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using GradeManagement.Bll.Services;
using GradeManagement.Bll.Services.Moodle;

using Microsoft.Extensions.DependencyInjection;

Expand All @@ -24,6 +25,8 @@ public static IServiceCollection AddBllServices(this IServiceCollection services
services.AddTransient<ExerciseService>();
services.AddTransient<UserService>();
services.AddTransient<SubjectTeacherService>();
services.AddTransient<MoodleIntegrationService>();
services.AddTransient<TokenGeneratorService>();

return services;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using GradeManagement.Shared.Dtos.AssignmentEvents;
using GradeManagement.Shared.Enums;

using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;

using Assignment = GradeManagement.Shared.Dtos.Assignment;
Expand Down Expand Up @@ -38,6 +39,20 @@ public async Task ConsumeAssignmentAcceptedEventAsync(AssignmentAccepted assignm
await using var transaction = await gradeManagementDbContext.Database.BeginTransactionAsync();
try
{
var existingAssignment = await gradeManagementDbContext.Assignment.Where(
a => a.GithubRepoUrl == assignmentAccepted.GitHubRepositoryUrl).FirstOrDefaultAsync();
if (existingAssignment != null)
{
var assignmentLogForExistingAssingnment = new AssignmentLog()
{
EventType = EventType.AssignmentAccepted,
Description = $"Assignment for github repo url {assignmentAccepted.GitHubRepositoryUrl} already exists!",
AssignmentId = existingAssignment.Id
};
await assignmentLogService.CreateAsync(assignmentLogForExistingAssingnment);
return;
}

var repositoryName = GetRepositoryNameFromUrl(assignmentAccepted.GitHubRepositoryUrl);
var exercise = await exerciseService.GetExerciseModelByGitHubRepoNameWithoutQfAsync(repositoryName);
var studentGitHubId = repositoryName.Remove(0, (exercise.GithubPrefix + "-").Length);
Expand Down Expand Up @@ -93,12 +108,10 @@ public async Task ConsumePullRequestOpenedEventAsync(PullRequestOpened pullReque
BranchName = pullRequestOpened.BranchName,
AssignmentId = assignment.Id
};
pullRequestToCreate = await pullRequestService.CreateWithoutQfAsync(pullRequestToCreate, assignment.SubjectId);
await pullRequestService.CreateWithoutQfAsync(pullRequestToCreate, assignment.SubjectId);
pullRequest = await pullRequestService.GetModelByUrlWithoutQfAsync(pullRequestOpened.PullRequestUrl);
}



var assignmentLog = new AssignmentLog()
{
EventType = EventType.PullRequestOpened,
Expand All @@ -123,15 +136,16 @@ public async Task ConsumeCiEvaluationCompletedEventAsync(CiEvaluationCompleted c
await using var transaction = await gradeManagementDbContext.Database.BeginTransactionAsync();
try
{
var pullRequest = await pullRequestService.GetModelByUrlWithoutQfAsync(ciEvaluationCompleted.PullRequestUrl);
var pullRequest =
await pullRequestService.GetModelByUrlWithoutQfAsync(ciEvaluationCompleted.PullRequestUrl);
var repositoryName = GetRepositoryNameFromUrl(ciEvaluationCompleted.GitHubRepositoryUrl);
var exercise = await exerciseService.GetExerciseModelByGitHubRepoNameWithoutQfAsync(repositoryName);
var studentGitHubId = repositoryName.Remove(0, (exercise.GithubPrefix + "-").Length);
var student = await studentService.GetStudentModelByGitHubIdAsync(studentGitHubId);
var assignment = await assignmentService.GetAssignmentModelByGitHubRepoNameWithoutQfAsync(repositoryName);
var subject = await subjectService.GetModelByIdWithoutQfAsync(exercise.SubjectId);

if(ciEvaluationCompleted.CiApiKey != subject.CiApiKey)
if (ciEvaluationCompleted.CiApiKey != subject.CiApiKey)
{
throw new SecurityTokenException("Invalid API key");
}
Expand All @@ -149,7 +163,8 @@ public async Task ConsumeCiEvaluationCompletedEventAsync(CiEvaluationCompleted c

foreach (var scoreEvent in ciEvaluationCompleted.Scores)
{
await scoreService.CreateScoreBasedOnOrderAsync(scoreEvent.Key, scoreEvent.Value, pullRequest.Id, pullRequest.SubjectId, exercise.Id);
await scoreService.CreateScoreBasedOnOrderAsync(scoreEvent.Key, scoreEvent.Value, pullRequest.Id,
pullRequest.SubjectId, exercise.Id);
}

var assignmentLog = new AssignmentLog()
Expand Down Expand Up @@ -196,7 +211,7 @@ public async Task ConsumeTeacherAssignedEventAsync(TeacherAssigned teacherAssign
pullRequest.TeacherId = null; // Unassign previous teacher
await gradeManagementDbContext.SaveChangesAsync();

if(teacherAssigned.TeacherGitHubIds == null || teacherAssigned.TeacherGitHubIds.Count == 0)
if (teacherAssigned.TeacherGitHubIds == null || teacherAssigned.TeacherGitHubIds.Count == 0)
{
await transaction.CommitAsync();
return;
Expand Down Expand Up @@ -229,7 +244,6 @@ public async Task ConsumeTeacherAssignedEventAsync(TeacherAssigned teacherAssign

await transaction.CommitAsync();
}

}
catch
{
Expand All @@ -247,24 +261,24 @@ public async Task ConsumeAssignmentGradedByTeacherEventAsync(AssignmentGradedByT
var assignment = await assignmentService.GetAssignmentModelByGitHubRepoNameWithoutQfAsync(repositoryName);
var exercise = await exerciseService.GetExerciseModelByGitHubRepoNameWithoutQfAsync(repositoryName);
var teacher = await userService.GetModelByGitHubIdAsync(assignmentGradedByTeacher.TeacherGitHubId);
var pullRequest = await pullRequestService.GetModelByUrlWithoutQfAsync(assignmentGradedByTeacher.PullRequestUrl);
var pullRequest =
await pullRequestService.GetModelByUrlWithoutQfAsync(assignmentGradedByTeacher.PullRequestUrl);

if (assignmentGradedByTeacher.Scores.IsNullOrEmpty())
{
var latestScores = await pullRequestService.GetLatestUnapprovedScoreModelsWithoutQfByIdAsync(pullRequest.Id);
var latestScores =
await pullRequestService.GetLatestUnapprovedScoreModelsWithoutQfByIdAsync(pullRequest.Id);
foreach (var scoreEntity in latestScores)
{
scoreEntity.IsApproved = true;
scoreEntity.TeacherId = teacher.Id;
await scoreService.ApproveScoreAsync(scoreEntity.Id, teacher.Id);
}

await gradeManagementDbContext.SaveChangesAsync();
}
else
{
foreach (var eventScore in assignmentGradedByTeacher.Scores)
{
await scoreService.CreateOrApprovePointsFromTeacherInputWithoutQfAsync(eventScore.Key, eventScore.Value, pullRequest.Id, teacher.Id, pullRequest.SubjectId, exercise.Id);
await scoreService.CreateOrApprovePointsFromTeacherInputWithoutQfAsync(eventScore.Key,
eventScore.Value, pullRequest.Id, teacher.Id, pullRequest.SubjectId, exercise.Id);
}
}

Expand Down Expand Up @@ -292,7 +306,8 @@ public async Task ConsumePullRequestStatusChangedEventAsync(PullRequestStatusCha
await using var transaction = await gradeManagementDbContext.Database.BeginTransactionAsync();
try
{
var pullRequest = await pullRequestService.GetModelByUrlWithoutQfAsync(pullRequestStatusChanged.PullRequestUrl);
var pullRequest =
await pullRequestService.GetModelByUrlWithoutQfAsync(pullRequestStatusChanged.PullRequestUrl);
pullRequest.Status = pullRequestStatusChanged.pullRequestStatus;
await gradeManagementDbContext.SaveChangesAsync();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,5 @@

public interface ICrudServiceBase<TDto> : IRestrictedCrudServiceBase<TDto>
{

public Task<TDto> UpdateAsync(long id, TDto requestDto);

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,5 @@

public interface ICrudServiceBase<TRequestDto, TResponseDto> : IRestrictedCrudServiceBase<TRequestDto, TResponseDto>
{

public Task<TResponseDto> UpdateAsync(long id, TRequestDto requestDto);

}
Loading
Loading