From 3bc4e1cd03e4f4b9fb63d9df91e213a8dc5a096c Mon Sep 17 00:00:00 2001 From: MaxchilKH Date: Mon, 7 Sep 2020 17:41:09 +0200 Subject: [PATCH 1/3] dummy --- .../Controllers/EditionController.cs | 1 - .../InternshipRegistrationController.cs | 24 +++++++++-- .../Controllers/RegistrationController.cs | 2 +- .../Queries/RegistrationFormQuery.cs | 15 ------- .../Commands/UpdateRegistrationForm.cs | 33 +++++++++++++++ src/InternshipSystem.Core/Entity/Company.cs | 13 +++--- src/InternshipSystem.Core/Entity/Edition.cs | 13 ++++++ .../Entity/Internship/Internship.cs | 40 ++++++++++++++++++- .../Internship/InternshipRegistration.cs | 7 ++++ .../Entity/Internship/InternshipType.cs | 13 +++--- src/InternshipSystem.Core/Entity/Report.cs | 6 ++- .../DatabaseFiller.cs | 10 +---- 12 files changed, 136 insertions(+), 41 deletions(-) delete mode 100644 src/InternshipSystem.Api/Queries/RegistrationFormQuery.cs create mode 100644 src/InternshipSystem.Core/Commands/UpdateRegistrationForm.cs diff --git a/src/InternshipSystem.Api/Controllers/EditionController.cs b/src/InternshipSystem.Api/Controllers/EditionController.cs index 67c2f81..b592984 100644 --- a/src/InternshipSystem.Api/Controllers/EditionController.cs +++ b/src/InternshipSystem.Api/Controllers/EditionController.cs @@ -88,6 +88,5 @@ namespace InternshipSystem.Api.Controllers return Ok(edition); } - } } \ No newline at end of file diff --git a/src/InternshipSystem.Api/Controllers/InternshipRegistrationController.cs b/src/InternshipSystem.Api/Controllers/InternshipRegistrationController.cs index cb48ea4..1d06be1 100644 --- a/src/InternshipSystem.Api/Controllers/InternshipRegistrationController.cs +++ b/src/InternshipSystem.Api/Controllers/InternshipRegistrationController.cs @@ -2,9 +2,13 @@ using System.Threading; using System.Threading.Tasks; using InternshipSystem.Api.Queries; +using InternshipSystem.Api.Security; +using InternshipSystem.Core.Commands; using InternshipSystem.Repository; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; namespace InternshipSystem.Api.Controllers { @@ -21,7 +25,7 @@ namespace InternshipSystem.Api.Controllers /// /// Validate and add filled internship registration form /// - /// Internship registration data + /// Internship registration data /// If registration form was successfully added /// If the provided registration query was malformed /// This action is only available for authorized student registered for current edition @@ -29,7 +33,21 @@ namespace InternshipSystem.Api.Controllers [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] - public async Task SubmitRegistrationForm([FromBody] RegistrationFormQuery registrationQuery, CancellationToken cancellationToken) => - throw new NotImplementedException(); + [Authorize(Policy = Policies.RegisteredOnly)] + public async Task SubmitRegistrationForm( + [FromBody] UpdateRegistrationForm updateRegistration, + User user, + CancellationToken cancellationToken) + { + var edition = await Context.Editions.FindAsync(user.EditionId.Value); + + var internship = await Context + .Entry(edition) + .Collection(e => e.Internships) + .Query() + .FirstAsync(i => i.Student.Id == user.PersonNumber, cancellationToken); + + internship.UpdateInternshipRegistration(updateRegistration); + } } } \ No newline at end of file diff --git a/src/InternshipSystem.Api/Controllers/RegistrationController.cs b/src/InternshipSystem.Api/Controllers/RegistrationController.cs index 26931fc..3991b51 100644 --- a/src/InternshipSystem.Api/Controllers/RegistrationController.cs +++ b/src/InternshipSystem.Api/Controllers/RegistrationController.cs @@ -47,7 +47,7 @@ namespace InternshipSystem.Api.Controllers var student = await _context.Students.FindAsync(user.PersonNumber); edition.RegisterInternship(student); - await _context.SaveChangesAsync(); + await _context.SaveChangesAsync(token); return Ok(); } diff --git a/src/InternshipSystem.Api/Queries/RegistrationFormQuery.cs b/src/InternshipSystem.Api/Queries/RegistrationFormQuery.cs deleted file mode 100644 index fe30ded..0000000 --- a/src/InternshipSystem.Api/Queries/RegistrationFormQuery.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using InternshipSystem.Core; -using InternshipSystem.Core.Entity.Internship; - -namespace InternshipSystem.Api.Queries -{ - public class RegistrationFormQuery - { - public Company Company { get; set; } - public BranchOffice BranchAddress { get; set; } - public DateTime Start { get; set; } - public DateTime End { get; set; } - public InternshipType Type { get; set; } - } -} \ No newline at end of file diff --git a/src/InternshipSystem.Core/Commands/UpdateRegistrationForm.cs b/src/InternshipSystem.Core/Commands/UpdateRegistrationForm.cs new file mode 100644 index 0000000..4ff3a8d --- /dev/null +++ b/src/InternshipSystem.Core/Commands/UpdateRegistrationForm.cs @@ -0,0 +1,33 @@ +using System; +using InternshipSystem.Core.Entity.Internship; + +namespace InternshipSystem.Core.Commands +{ + public class UpdateRegistrationForm + { + public UpdateCompany? Company { get; set; } + public DateTime? Start { get; set; } + public DateTime? End { get; set; } + public InternshipType? Type { get; set; } + } + + public struct UpdateCompany + { + public long? Id { get; set; } + public string Nip { get; set; } + public string Name { get; set; } + public UpdateBranchOffice? BranchOffice { get; set; } + + public bool IsUpdate => Id.HasValue; + } + + public struct UpdateBranchOffice + { + public long? Id { get; set; } + public string Street { get; set; } + public string Building { get; set; } + public string City { get; set; } + public string PostalCode { get; set; } + public string Country { get; set; } + } +} \ No newline at end of file diff --git a/src/InternshipSystem.Core/Entity/Company.cs b/src/InternshipSystem.Core/Entity/Company.cs index ddbeeb4..12a9168 100644 --- a/src/InternshipSystem.Core/Entity/Company.cs +++ b/src/InternshipSystem.Core/Entity/Company.cs @@ -1,5 +1,5 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; +using InternshipSystem.Core.Commands; namespace InternshipSystem.Core { @@ -8,16 +8,13 @@ namespace InternshipSystem.Core public long Id { get; set; } public Nip Nip { get; set; } public string Name { get; set; } - public RangeOfActivity Range { get; set; } public List Branches { get; set; } - public Uri SiteAddress { get; set; } - public Company CreateCompany(string nip, RangeOfActivity range, string name) + public Company CreateCompany(string nip, string name) { return new Company { Nip = nip, - Range = range, Name = name }; } @@ -25,5 +22,9 @@ namespace InternshipSystem.Core public void AddBranchAddress(BranchAddress branch) { } + + public static Company CreateCompany(UpdateCompany updateCompany) + { + } } } \ No newline at end of file diff --git a/src/InternshipSystem.Core/Entity/Edition.cs b/src/InternshipSystem.Core/Entity/Edition.cs index 64172af..3c28a4b 100644 --- a/src/InternshipSystem.Core/Entity/Edition.cs +++ b/src/InternshipSystem.Core/Entity/Edition.cs @@ -13,6 +13,9 @@ namespace InternshipSystem.Core public DateTime ReportingStart { get; set; } public Course Course { get; set; } public List Internships { get; set; } + + public InternshipType AllowedInternshipTypes { get; set; } + public List AvailableSubjects { get; set; } public bool IsOpen => EditionFinish < DateTime.Today; @@ -33,5 +36,15 @@ namespace InternshipSystem.Core Internships.Add(internship); } + + public bool IsDateDuringEdition(DateTime start, DateTime end) + { + return start >= EditionStart && end <= EditionFinish; + } + + public bool IsInternshiptypeAllowed(InternshipType internshipType) + { + return AllowedInternshipTypes.HasFlag(internshipType); + } } } \ No newline at end of file diff --git a/src/InternshipSystem.Core/Entity/Internship/Internship.cs b/src/InternshipSystem.Core/Entity/Internship/Internship.cs index b9981d8..15c5272 100644 --- a/src/InternshipSystem.Core/Entity/Internship/Internship.cs +++ b/src/InternshipSystem.Core/Entity/Internship/Internship.cs @@ -1,5 +1,7 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; +using InternshipSystem.Core.Commands; using InternshipSystem.Core.ValueObject; namespace InternshipSystem.Core @@ -13,6 +15,9 @@ namespace InternshipSystem.Core public Report Report { get; set; } public List Approvals { get; set; } public List Documentation { get; set; } + + public Edition Edition { get; set; } + public float? Grade { get; set; } public void UpdateDocument(Document document) @@ -46,5 +51,38 @@ namespace InternshipSystem.Core return internship; } + + public void UpdateInternshipRegistration(UpdateRegistrationForm updateRegistration) + { + var start = updateRegistration.Start ?? InternshipRegistration.Start; + var end = updateRegistration.End ?? InternshipRegistration.End; + + if (!Edition.IsDateDuringEdition(start, end)) + { + throw new ArgumentOutOfRangeException(nameof(InternshipRegistration.Start) + nameof(InternshipRegistration.End),"Date outside of edition boundaries"); + } + + var internshipType = updateRegistration.Type ?? InternshipRegistration.Type; + + if (!Edition.IsInternshiptypeAllowed(internshipType)) + { + throw new ArgumentException("Internship type not allowed for this edition", nameof(updateRegistration.Type)); + } + + var company = InternshipRegistration.Company; + if (company == null) + { + if (!updateRegistration.Company.HasValue) + { + throw new ArgumentException("Company"); + } + + company = Company.CreateCompany(updateRegistration.Company.Value); + + + } + + InternshipRegistration.Update(start, end, internshipType); + } } } \ No newline at end of file diff --git a/src/InternshipSystem.Core/Entity/Internship/InternshipRegistration.cs b/src/InternshipSystem.Core/Entity/Internship/InternshipRegistration.cs index e12679a..b0b447a 100644 --- a/src/InternshipSystem.Core/Entity/Internship/InternshipRegistration.cs +++ b/src/InternshipSystem.Core/Entity/Internship/InternshipRegistration.cs @@ -17,5 +17,12 @@ namespace InternshipSystem.Core { return new InternshipRegistration(); } + + public void Update(DateTime start, DateTime end, InternshipType internshipType) + { + Start = start; + End = end; + Type = internshipType; + } } } \ No newline at end of file diff --git a/src/InternshipSystem.Core/Entity/Internship/InternshipType.cs b/src/InternshipSystem.Core/Entity/Internship/InternshipType.cs index d9fb97f..5e85e23 100644 --- a/src/InternshipSystem.Core/Entity/Internship/InternshipType.cs +++ b/src/InternshipSystem.Core/Entity/Internship/InternshipType.cs @@ -1,9 +1,12 @@ -namespace InternshipSystem.Core.Entity.Internship +using System; + +namespace InternshipSystem.Core.Entity.Internship { - public class InternshipType + [Flags] + public enum InternshipType : long { - public long Id { get; set; } - public string Type { get; set; } - public string Description { get; set; } + None, + UOP, + UOZ } } \ No newline at end of file diff --git a/src/InternshipSystem.Core/Entity/Report.cs b/src/InternshipSystem.Core/Entity/Report.cs index 35f60a5..ab5df11 100644 --- a/src/InternshipSystem.Core/Entity/Report.cs +++ b/src/InternshipSystem.Core/Entity/Report.cs @@ -1,9 +1,13 @@ -namespace InternshipSystem.Core +using System; + +namespace InternshipSystem.Core { public class Report { public long Id { get; set; } public DocumentState State { get; set; } + public RangeOfActivity Range { get; set; } + public Uri SiteAddress { get; set; } public static Report Create() { diff --git a/src/InternshipSystem.Repository/DatabaseFiller.cs b/src/InternshipSystem.Repository/DatabaseFiller.cs index bee6e4b..99a270e 100644 --- a/src/InternshipSystem.Repository/DatabaseFiller.cs +++ b/src/InternshipSystem.Repository/DatabaseFiller.cs @@ -162,10 +162,7 @@ namespace InternshipSystem.Repository InternshipRegistration = new InternshipRegistration { Company = Context.Companies.First(c => c.Id.Equals(1)), //Intel - Type = new InternshipType - { - Type = "UOP" - }, + Type = InternshipType.UOP, Start = new DateTime(2000, 7, 1), End = new DateTime(2000, 8, 30), State = DocumentState.Submitted, @@ -215,10 +212,7 @@ namespace InternshipSystem.Repository InternshipRegistration = new InternshipRegistration { Company = Context.Companies.First(c => c.Id.Equals(2)), //Asseco - Type = new InternshipType - { - Type = "UZ" - }, + Type = InternshipType.UOZ, Start = new DateTime(2000, 7, 1), End = new DateTime(2000, 8, 30), State = DocumentState.Submitted, -- 2.45.2 From 46d178a72251ecd6054593504763748ccbe666a7 Mon Sep 17 00:00:00 2001 From: MaxchilKH Date: Sat, 12 Sep 2020 23:01:10 +0200 Subject: [PATCH 2/3] add Gut authentication and authorization --- .docker/docker-compose.yaml | 7 +- .../AspNet.Security.OAuth.MyGut.csproj | 11 -- .../AuthenticationBuilderExtension.cs | 20 --- .../MyGutAuthenticationConstants.cs | 20 --- .../MyGutAuthenticationDefaults.cs | 19 --- .../MyGutAuthenticationHandler.cs | 19 --- .../MyGutAuthenticationOptions.cs | 16 --- .../Controllers/AccessController.cs | 127 +++++++++++++++++- .../InternshipSystem.Api.csproj | 1 - .../Options/SecurityOptions.cs | 15 ++- .../Result/CasUserData.cs | 14 ++ .../Result/CasUserProfile.cs | 10 ++ src/InternshipSystem.Api/Startup.cs | 45 ++++--- src/InternshipSystem.Core/Entity/Company.cs | 4 +- src/InternshipSystem.Core/Entity/Student.cs | 13 +- .../DatabaseFiller.cs | 4 +- .../InternshipSystem.Api.Test.csproj | 4 + .../InternshipSystem.cs | 53 ++++++++ 18 files changed, 266 insertions(+), 136 deletions(-) delete mode 100644 src/AspNet.Security.OAuth.MyGut/AspNet.Security.OAuth.MyGut.csproj delete mode 100644 src/AspNet.Security.OAuth.MyGut/AuthenticationBuilderExtension.cs delete mode 100644 src/AspNet.Security.OAuth.MyGut/MyGutAuthenticationConstants.cs delete mode 100644 src/AspNet.Security.OAuth.MyGut/MyGutAuthenticationDefaults.cs delete mode 100644 src/AspNet.Security.OAuth.MyGut/MyGutAuthenticationHandler.cs delete mode 100644 src/AspNet.Security.OAuth.MyGut/MyGutAuthenticationOptions.cs create mode 100644 src/InternshipSystem.Api/Result/CasUserData.cs create mode 100644 src/InternshipSystem.Api/Result/CasUserProfile.cs diff --git a/.docker/docker-compose.yaml b/.docker/docker-compose.yaml index 5e6599f..73c579b 100644 --- a/.docker/docker-compose.yaml +++ b/.docker/docker-compose.yaml @@ -7,8 +7,13 @@ services: CONNECTIONSTRINGS__INTERNSHIPDATABASE: "Host=db.postgres;Port=5432;Database=postgres;Username=postgres;Password=szwoniu" ASPNETCORE_ENVIRONMENT: Development ASPNETCORE_URLS: http://+:80 - SECURITYOPTIONS__SECRET: PDv7DrqznYL6nv7DrqzjnQYO9JxIsWdcjnQYL6nu0f + SECURITYOPTIONS__SECRET: iewaiwie3aig9wi3chieBai9eephai SECURITYOPTIONS__EXPIRATION: 20 + SECURITYOPTIONS__BASEURL: https://logowanie.pg.edu.pl + SECURITYOPTIONS__TOKENPATH: /oauth2.0/accessToken + SECURITYOPTIONS__PROFILEPATH: /oauth2.0/profile + SECURITYOPTIONS__CLIENTID: PraktykiClientId + SECURITYOPTIONS__REDIRECTURL: https://system-praktyk.stg.kadet.net/user/login/check/pg depends_on: - db.postgres ports: diff --git a/src/AspNet.Security.OAuth.MyGut/AspNet.Security.OAuth.MyGut.csproj b/src/AspNet.Security.OAuth.MyGut/AspNet.Security.OAuth.MyGut.csproj deleted file mode 100644 index e5cfdb1..0000000 --- a/src/AspNet.Security.OAuth.MyGut/AspNet.Security.OAuth.MyGut.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - - netcoreapp3.1 - - - - - - - diff --git a/src/AspNet.Security.OAuth.MyGut/AuthenticationBuilderExtension.cs b/src/AspNet.Security.OAuth.MyGut/AuthenticationBuilderExtension.cs deleted file mode 100644 index b442239..0000000 --- a/src/AspNet.Security.OAuth.MyGut/AuthenticationBuilderExtension.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using Microsoft.AspNetCore.Authentication; -using Microsoft.Extensions.DependencyInjection; - -namespace AspNet.Security.OAuth.MyGut -{ - public static class AuthenticationBuilderExtension - { - public static AuthenticationBuilder AddMyGut(this AuthenticationBuilder builder) => - builder.AddMyGut(MyGutAuthenticationDefaults.AuthenticationScheme, MyGutAuthenticationDefaults.DisplayName, options => { }); - - public static AuthenticationBuilder AddMyGut( - this AuthenticationBuilder builder, - string scheme, - string caption, - Action configuration - ) => - builder.AddOAuth(scheme, caption, configuration); - } -} \ No newline at end of file diff --git a/src/AspNet.Security.OAuth.MyGut/MyGutAuthenticationConstants.cs b/src/AspNet.Security.OAuth.MyGut/MyGutAuthenticationConstants.cs deleted file mode 100644 index 7edb2e5..0000000 --- a/src/AspNet.Security.OAuth.MyGut/MyGutAuthenticationConstants.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace AspNet.Security.OAuth.MyGut -{ - public static class MyGutAuthenticationConstants - { - public static class Urls - { - } - - public static class Claims - { - public const string AlbumNumber = "urn:mygut:albumnumber"; - } - - public static class UrlQueryParameterValues - { - public const string Consent = "consent"; - public const string None = "none"; - } - } -} \ No newline at end of file diff --git a/src/AspNet.Security.OAuth.MyGut/MyGutAuthenticationDefaults.cs b/src/AspNet.Security.OAuth.MyGut/MyGutAuthenticationDefaults.cs deleted file mode 100644 index 5e07b0d..0000000 --- a/src/AspNet.Security.OAuth.MyGut/MyGutAuthenticationDefaults.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace AspNet.Security.OAuth.MyGut -{ - public static class MyGutAuthenticationDefaults - { - public const string AuthenticationScheme = "MyGut"; - - public const string DisplayName = "MyGut"; - - public const string Issuer = "MyGut"; - - public const string CallbackPath = "/signin-mygut"; - - public const string AuthorizationEndpoint = "https://logowanie.pg.edu.pl/login"; - - public const string TokenEndpoint = "https://logowanie.pg.edu.pl/login"; - - public const string UserInformationEndpoint = "https://logowanie.pg.edu.pl/login"; - } -} \ No newline at end of file diff --git a/src/AspNet.Security.OAuth.MyGut/MyGutAuthenticationHandler.cs b/src/AspNet.Security.OAuth.MyGut/MyGutAuthenticationHandler.cs deleted file mode 100644 index 6782093..0000000 --- a/src/AspNet.Security.OAuth.MyGut/MyGutAuthenticationHandler.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Text.Encodings.Web; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.OAuth; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -namespace AspNet.Security.OAuth.MyGut -{ - public class MyGutAuthenticationHandler : OAuthHandler - { - public MyGutAuthenticationHandler( - IOptionsMonitor options, - ILoggerFactory logger, - UrlEncoder encoder, - ISystemClock clock) : base(options, logger, encoder, clock) - { - } - } -} \ No newline at end of file diff --git a/src/AspNet.Security.OAuth.MyGut/MyGutAuthenticationOptions.cs b/src/AspNet.Security.OAuth.MyGut/MyGutAuthenticationOptions.cs deleted file mode 100644 index c3f027d..0000000 --- a/src/AspNet.Security.OAuth.MyGut/MyGutAuthenticationOptions.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.AspNetCore.Authentication.OAuth; - -namespace AspNet.Security.OAuth.MyGut -{ - public class MyGutAuthenticationOptions : OAuthOptions - { - public MyGutAuthenticationOptions() - { - ClaimsIssuer = MyGutAuthenticationDefaults.Issuer; - CallbackPath = MyGutAuthenticationDefaults.CallbackPath; - AuthorizationEndpoint = MyGutAuthenticationDefaults.AuthorizationEndpoint; - TokenEndpoint = MyGutAuthenticationDefaults.TokenEndpoint; - UserInformationEndpoint = MyGutAuthenticationDefaults.UserInformationEndpoint; - } - } -} \ No newline at end of file diff --git a/src/InternshipSystem.Api/Controllers/AccessController.cs b/src/InternshipSystem.Api/Controllers/AccessController.cs index 44d1b95..d30c6fa 100644 --- a/src/InternshipSystem.Api/Controllers/AccessController.cs +++ b/src/InternshipSystem.Api/Controllers/AccessController.cs @@ -1,15 +1,22 @@ using System; +using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; using System.Security.Claims; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using InternshipSystem.Api.Options; using InternshipSystem.Api.Security; +using InternshipSystem.Core; using InternshipSystem.Repository; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; +using Serilog; namespace InternshipSystem.Api.Controllers { @@ -19,12 +26,18 @@ namespace InternshipSystem.Api.Controllers { private readonly InternshipDbContext _context; private readonly JwtTokenService _tokenService; + private readonly GutCasClient _loginClient; private readonly SecurityOptions _securityOptions; - public AccessController(IOptions options, InternshipDbContext context, JwtTokenService tokenService) + public AccessController( + IOptions options, + InternshipDbContext context, + JwtTokenService tokenService, + GutCasClient loginClient) { _context = context; _tokenService = tokenService; + _loginClient = loginClient; _securityOptions = options.Value; } @@ -32,16 +45,47 @@ namespace InternshipSystem.Api.Controllers [HttpGet("login")] public async Task Authenticate(string code, CancellationToken cancellationToken) { + var token = await _loginClient.GetCasTokenAsync(code, cancellationToken); + + var casData = await _loginClient.GetProfileAsync(token, cancellationToken); + + if (!long.TryParse(casData.PersonNumber, out var id)) + { + return BadRequest(); + } + + var student = await _context.Students.FirstOrDefaultAsync(s => s.Id == id); + + if (student == null) + { + student = CreateStudentWithCasData(casData); + await _context.Students.AddAsync(student, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + } + var identity = new ClaimsIdentity(new[] { - new Claim(ClaimTypes.Name, "Jan"), - new Claim(ClaimTypes.Surname, "Kowalski"), - new Claim(InternshipClaims.PersonNumber, "1") + new Claim(ClaimTypes.Name, student.FirstName), + new Claim(ClaimTypes.Surname, student.LastName), + new Claim(InternshipClaims.PersonNumber, student.Id.ToString()) }); return Ok(_tokenService.generateToken(identity)); } + [HttpGet("/dev/login")] + public async Task Authenticate(CancellationToken cancellationToken) + { + var identity = new ClaimsIdentity(new[] + { + new Claim(ClaimTypes.Name, "firstname"), + new Claim(ClaimTypes.Surname, "lastname"), + new Claim(InternshipClaims.PersonNumber, "1") + }); + + return Ok(_tokenService.generateToken(identity)); + } + [HttpGet("loginEdition")] [Authorize] public async Task LoginIntoEdition(Guid editionId, User user, CancellationToken token) @@ -64,5 +108,80 @@ namespace InternshipSystem.Api.Controllers return Ok(_tokenService.generateToken(newIdentity)); } + + private Student CreateStudentWithCasData(CasUserData casData) + { + var id = long.Parse(casData.PersonNumber); + var firstName = casData.FirstName; + var lastName = casData.LastName; + var email = casData.Mail.First(s => s.EndsWith("@student.pg.edu.pl")); + var albumNumber = int.Parse(casData.AlbumNumber); + + return Student.CreateStudent(id, firstName, lastName, email, albumNumber); + } + } + + public class GutCasClient + { + private readonly HttpClient _client; + private readonly SecurityOptions _securityOptions; + + public GutCasClient(HttpClient client, IOptions options) + { + _securityOptions = options.Value; + + client.BaseAddress = _securityOptions.BaseUrl; + _client = client; + } + + public async Task GetCasTokenAsync(string code, CancellationToken cancellationToken) + { + var request = new HttpRequestMessage + { + Method = HttpMethod.Post, + Content = new FormUrlEncodedContent(new Dictionary + { + { "grant_type", "authorization_code" }, + { "client_id", _securityOptions.ClientId }, + { "client_secret", _securityOptions.Secret }, + { "redirect_uri", _securityOptions.RedirectUrl.ToString() }, + { "code", code } + }), + RequestUri = _securityOptions.TokenPath + }; + + var response = await _client.SendAsync(request, cancellationToken); + await using var stream = await response.Content.ReadAsStreamAsync(); + + var value = await JsonSerializer.DeserializeAsync>(stream); + + return value["access_token"].ToString(); + } + + + public async Task GetProfileAsync(string token, CancellationToken cancellationToken) + { + var request = new HttpRequestMessage + { + Method = HttpMethod.Get, + Content = new StringContent(string.Empty), + RequestUri = _securityOptions.ProfilePath + }; + + request.Headers.Authorization = AuthenticationHeaderValue.Parse($"Bearer {token}"); + request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json"); + + var response = await _client.SendAsync(request, cancellationToken); + await using var stream = await response.Content.ReadAsStreamAsync(); + + var result = await JsonSerializer.DeserializeAsync( + stream, + new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }); + + return result.Attributes; + } } } \ No newline at end of file diff --git a/src/InternshipSystem.Api/InternshipSystem.Api.csproj b/src/InternshipSystem.Api/InternshipSystem.Api.csproj index 133fa45..d5c17c0 100644 --- a/src/InternshipSystem.Api/InternshipSystem.Api.csproj +++ b/src/InternshipSystem.Api/InternshipSystem.Api.csproj @@ -23,7 +23,6 @@ - diff --git a/src/InternshipSystem.Api/Options/SecurityOptions.cs b/src/InternshipSystem.Api/Options/SecurityOptions.cs index 2fcf5fe..499b604 100644 --- a/src/InternshipSystem.Api/Options/SecurityOptions.cs +++ b/src/InternshipSystem.Api/Options/SecurityOptions.cs @@ -1,8 +1,21 @@ -namespace InternshipSystem.Api.Options +using System; +using System.Security.Policy; + +namespace InternshipSystem.Api.Options { public class SecurityOptions { public string Secret { get; set; } public double Expiration { get; set; } + + public Uri BaseUrl { get; set; } + + public Uri TokenPath { get; set; } + + public Uri ProfilePath { get; set; } + + public Uri RedirectUrl { get; set; } + + public string ClientId { get; set; } } } \ No newline at end of file diff --git a/src/InternshipSystem.Api/Result/CasUserData.cs b/src/InternshipSystem.Api/Result/CasUserData.cs new file mode 100644 index 0000000..560a3ac --- /dev/null +++ b/src/InternshipSystem.Api/Result/CasUserData.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace InternshipSystem.Api.Controllers +{ + public class CasUserData + { + public string AlbumNumber { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public List Mail { get; set; } + public string PersonNumber { get; set; } + public List Pg_Cui_Portalroles { get; set; } + } +} \ No newline at end of file diff --git a/src/InternshipSystem.Api/Result/CasUserProfile.cs b/src/InternshipSystem.Api/Result/CasUserProfile.cs new file mode 100644 index 0000000..f051b2d --- /dev/null +++ b/src/InternshipSystem.Api/Result/CasUserProfile.cs @@ -0,0 +1,10 @@ +namespace InternshipSystem.Api.Controllers +{ + public class CasUserProfile + { + public string Service { get; set; } + public CasUserData Attributes { get; set; } + public string Id { get; set; } + public string Client_Id { get; set; } + } +} \ No newline at end of file diff --git a/src/InternshipSystem.Api/Startup.cs b/src/InternshipSystem.Api/Startup.cs index ab34720..ccdc27d 100644 --- a/src/InternshipSystem.Api/Startup.cs +++ b/src/InternshipSystem.Api/Startup.cs @@ -2,6 +2,7 @@ using System; using System.IO; using System.Reflection; using AutoMapper; +using InternshipSystem.Api.Controllers; using InternshipSystem.Api.Extensions; using InternshipSystem.Api.ModelBinders; using InternshipSystem.Api.Options; @@ -26,32 +27,38 @@ namespace InternshipSystem.Api public Startup(IConfiguration configuration) => Configuration = configuration; - public void ConfigureServices(IServiceCollection services) => + public void ConfigureServices(IServiceCollection services) + { services - .Configure(Configuration.GetSection("SecurityOptions")) - .AddDbContext(o => o.UseNpgsql(Configuration.GetConnectionString("InternshipDatabase"))) - .AddSwaggerGen(options => + .Configure(Configuration.GetSection("SecurityOptions")); + + services + .AddStudentAuthentication() + .AddAuthorization(o => + { + o.AddPolicy(Policies.RegisteredOnly, policy => policy.RequireClaim("Edition")); + }) + .AddHttpClient(); + + services + .AddDbContext(o => + o.UseNpgsql(Configuration.GetConnectionString("InternshipDatabase"))) + .AddScoped() + .AddScoped() + .AddScoped() + .AddAutoMapper(cfg => cfg.AddProfile()); + + services + .AddSwaggerGen(options => { options.SwaggerDoc("v1", new OpenApiInfo {Title = "InternshipSystem Api - TEST", Version = "v1"}); var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); options.IncludeXmlComments(xmlPath); }) - .AddScoped() - .AddScoped() - .AddScoped() - .AddAutoMapper(cfg => cfg.AddProfile()) - .AddStudentAuthentication() - .AddAuthorization(o => - { - o.AddPolicy(Policies.RegisteredOnly, policy => policy.RequireClaim("Edition")); - }) - .AddControllers(o => - { - o.ModelBinderProviders.Insert(0, new UserBinderProvider()); - }) - ; - + .AddControllers(o => { o.ModelBinderProviders.Insert(0, new UserBinderProvider()); }); + } + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) diff --git a/src/InternshipSystem.Core/Entity/Company.cs b/src/InternshipSystem.Core/Entity/Company.cs index 15c3843..48201fd 100644 --- a/src/InternshipSystem.Core/Entity/Company.cs +++ b/src/InternshipSystem.Core/Entity/Company.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using InternshipSystem.Core.Commands; namespace InternshipSystem.Core @@ -28,6 +29,7 @@ namespace InternshipSystem.Core public static Company CreateCompany(UpdateCompany updateCompany) { + throw new NotImplementedException(); } } } \ No newline at end of file diff --git a/src/InternshipSystem.Core/Entity/Student.cs b/src/InternshipSystem.Core/Entity/Student.cs index b3e7fab..04b7f1b 100644 --- a/src/InternshipSystem.Core/Entity/Student.cs +++ b/src/InternshipSystem.Core/Entity/Student.cs @@ -9,6 +9,17 @@ namespace InternshipSystem.Core public string FirstName { get; set; } public string LastName { get; set; } public string Email { get; set; } - public int Semester { get; set; } + + public static Student CreateStudent(long id, string firstName, string lastName, string email, in int albumNumber) + { + return new Student + { + Id = id, + AlbumNumber = albumNumber, + FirstName = firstName, + LastName = lastName, + Email = email + }; + } } } \ No newline at end of file diff --git a/src/InternshipSystem.Repository/DatabaseFiller.cs b/src/InternshipSystem.Repository/DatabaseFiller.cs index 4751c2d..6936d86 100644 --- a/src/InternshipSystem.Repository/DatabaseFiller.cs +++ b/src/InternshipSystem.Repository/DatabaseFiller.cs @@ -162,7 +162,6 @@ namespace InternshipSystem.Repository LastName = "Kowalski", AlbumNumber = 123456, Email = "s123456@student.pg.edu.pl", - Semester = 4, }, InternshipRegistration = new InternshipRegistration { @@ -212,12 +211,11 @@ namespace InternshipSystem.Repository LastName = "Kołek", AlbumNumber = 102137, Email = "s102137@student.pg.edu.pl", - Semester = 6, }, InternshipRegistration = new InternshipRegistration { Company = Context.Companies.First(c => c.Id.Equals(2)), //Asseco - Type = InternshipType.UOZ, + Type = InternshipType.UZ, Start = new DateTime(2000, 7, 1), End = new DateTime(2000, 8, 30), State = DocumentState.Submitted, diff --git a/test/InternshipSystem.Api.Test/InternshipSystem.Api.Test.csproj b/test/InternshipSystem.Api.Test/InternshipSystem.Api.Test.csproj index 80e5e31..b18b329 100644 --- a/test/InternshipSystem.Api.Test/InternshipSystem.Api.Test.csproj +++ b/test/InternshipSystem.Api.Test/InternshipSystem.Api.Test.csproj @@ -6,6 +6,10 @@ false + + + + diff --git a/test/InternshipSystem.Api.Test/InternshipSystem.cs b/test/InternshipSystem.Api.Test/InternshipSystem.cs index b490e1d..2033085 100644 --- a/test/InternshipSystem.Api.Test/InternshipSystem.cs +++ b/test/InternshipSystem.Api.Test/InternshipSystem.cs @@ -1,6 +1,59 @@ using System; +using System.Collections.Generic; +using System.Text.Json; +using InternshipSystem.Api.Controllers; using Machine.Specifications; namespace InternshipSystem.Api.Test { + [Subject(typeof(JsonSerializer))] + class When_deserializing_cas + { + private static string json; + + + private Establish context = () => + { + // { + // "service": "https://system-praktyk.stg.kadet.net/user/login/code", + // "attributes": { + // "albumNumber": "165581", + // "firstName": "Kacper", + // "lastName": "Donat", + // "mail": [ + // "kacdonat@pg.edu.pl", + // "kacper.donat@pg.edu.pl", + // "s165581@student.pg.edu.pl" + // ], + // "personNumber": "1101074", + // "PG_CUI_PORTALROLES": [ + // "ROLE_EKONTAKT_PROD", + // "RP_STUDENT", + // "RP_USER", + // "ROLE_TRAC", + // "ROLE_HUDSON", + // "ROLE_WWW_ADMIN", + // "RP_PRACOWNIK" + // ] + // }, + // "id": "1101074", + // "client_id": "PraktykiClientId" + // } + + json = + " {\r\n \"service\": \"https://system-praktyk.stg.kadet.net/user/login/code\",\r\n \"attributes\": {\r\n \"albumNumber\": \"165581\",\r\n \"firstName\": \"Kacper\",\r\n \"lastName\": \"Donat\",\r\n \"mail\": [\r\n \"kacdonat@pg.edu.pl\",\r\n \"kacper.donat@pg.edu.pl\",\r\n \"s165581@student.pg.edu.pl\"\r\n ],\r\n \"personNumber\": \"1101074\",\r\n \"PG_CUI_PORTALROLES\": [\r\n \"ROLE_EKONTAKT_PROD\",\r\n \"RP_STUDENT\",\r\n \"RP_USER\",\r\n \"ROLE_TRAC\",\r\n \"ROLE_HUDSON\",\r\n \"ROLE_WWW_ADMIN\",\r\n \"RP_PRACOWNIK\"\r\n ]\r\n },\r\n \"id\": \"1101074\",\r\n \"client_id\": \"PraktykiClientId\"\r\n }"; + + options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + }; + }; + + private Because of = () => result = JsonSerializer.Deserialize(json, options); + + private It should_nop = () => true.ShouldBeTrue(); + + private static JsonSerializerOptions options; + private static CasUserProfile result; + } } -- 2.45.2 From c7be8bf61ac9cd3c12830f92371b6eea87fd4868 Mon Sep 17 00:00:00 2001 From: MaxchilKH Date: Sun, 13 Sep 2020 00:59:10 +0200 Subject: [PATCH 3/3] move client --- .../Controllers/AccessController.cs | 68 ----------------- .../Controllers/GutCasClient.cs | 75 +++++++++++++++++++ 2 files changed, 75 insertions(+), 68 deletions(-) create mode 100644 src/InternshipSystem.Api/Controllers/GutCasClient.cs diff --git a/src/InternshipSystem.Api/Controllers/AccessController.cs b/src/InternshipSystem.Api/Controllers/AccessController.cs index 3aa02bb..69164e3 100644 --- a/src/InternshipSystem.Api/Controllers/AccessController.cs +++ b/src/InternshipSystem.Api/Controllers/AccessController.cs @@ -1,11 +1,7 @@ using System; -using System.Collections.Generic; using System.IO; using System.Linq; -using System.Net.Http; -using System.Net.Http.Headers; using System.Security.Claims; -using System.Text.Json; using System.Threading; using System.Threading.Tasks; using InternshipSystem.Api.Options; @@ -120,68 +116,4 @@ namespace InternshipSystem.Api.Controllers return Student.CreateStudent(id, firstName, lastName, email, albumNumber); } } - - public class GutCasClient - { - private readonly HttpClient _client; - private readonly SecurityOptions _securityOptions; - - public GutCasClient(HttpClient client, IOptions options) - { - _securityOptions = options.Value; - - client.BaseAddress = _securityOptions.BaseUrl; - _client = client; - } - - public async Task GetCasTokenAsync(string code, CancellationToken cancellationToken) - { - var request = new HttpRequestMessage - { - Method = HttpMethod.Post, - Content = new FormUrlEncodedContent(new Dictionary - { - { "grant_type", "authorization_code" }, - { "client_id", _securityOptions.ClientId }, - { "client_secret", _securityOptions.Secret }, - { "redirect_uri", _securityOptions.RedirectUrl.ToString() }, - { "code", code } - }), - RequestUri = _securityOptions.TokenPath - }; - - var response = await _client.SendAsync(request, cancellationToken); - await using var stream = await response.Content.ReadAsStreamAsync(); - - var value = await JsonSerializer.DeserializeAsync>(stream); - - return value["access_token"].ToString(); - } - - - public async Task GetProfileAsync(string token, CancellationToken cancellationToken) - { - var request = new HttpRequestMessage - { - Method = HttpMethod.Get, - Content = new StringContent(string.Empty), - RequestUri = _securityOptions.ProfilePath - }; - - request.Headers.Authorization = AuthenticationHeaderValue.Parse($"Bearer {token}"); - request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json"); - - var response = await _client.SendAsync(request, cancellationToken); - await using var stream = await response.Content.ReadAsStreamAsync(); - - var result = await JsonSerializer.DeserializeAsync( - stream, - new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }); - - return result.Attributes; - } - } } \ No newline at end of file diff --git a/src/InternshipSystem.Api/Controllers/GutCasClient.cs b/src/InternshipSystem.Api/Controllers/GutCasClient.cs new file mode 100644 index 0000000..08f0714 --- /dev/null +++ b/src/InternshipSystem.Api/Controllers/GutCasClient.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using InternshipSystem.Api.Options; +using Microsoft.Extensions.Options; + +namespace InternshipSystem.Api.Controllers +{ + public class GutCasClient + { + private readonly HttpClient _client; + private readonly SecurityOptions _securityOptions; + + public GutCasClient(HttpClient client, IOptions options) + { + _securityOptions = options.Value; + + client.BaseAddress = _securityOptions.BaseUrl; + _client = client; + } + + public async Task GetCasTokenAsync(string code, CancellationToken cancellationToken) + { + var request = new HttpRequestMessage + { + Method = HttpMethod.Post, + Content = new FormUrlEncodedContent(new Dictionary + { + { "grant_type", "authorization_code" }, + { "client_id", _securityOptions.ClientId }, + { "client_secret", _securityOptions.Secret }, + { "redirect_uri", _securityOptions.RedirectUrl.ToString() }, + { "code", code } + }), + RequestUri = _securityOptions.TokenPath + }; + + var response = await _client.SendAsync(request, cancellationToken); + await using var stream = await response.Content.ReadAsStreamAsync(); + + var value = await JsonSerializer.DeserializeAsync>(stream); + + return value["access_token"].ToString(); + } + + + public async Task GetProfileAsync(string token, CancellationToken cancellationToken) + { + var request = new HttpRequestMessage + { + Method = HttpMethod.Get, + Content = new StringContent(string.Empty), + RequestUri = _securityOptions.ProfilePath + }; + + request.Headers.Authorization = AuthenticationHeaderValue.Parse($"Bearer {token}"); + request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json"); + + var response = await _client.SendAsync(request, cancellationToken); + await using var stream = await response.Content.ReadAsStreamAsync(); + + var result = await JsonSerializer.DeserializeAsync( + stream, + new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }); + + return result.Attributes; + } + } +} \ No newline at end of file -- 2.45.2