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;
+ }
}