From 7d747b60a8607b88811cf913aec05c18312a5ee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Wo=C5=BAniak?= Date: Wed, 2 Dec 2020 12:38:17 +0100 Subject: [PATCH 01/20] Renamed CasValidator -> DefaultUAMCasValidator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcin Woźniak --- .../java/com/plannaplan/controllers/TokenController.java | 4 ++-- .../{CasValidator.java => DefaultUAMCasValidator.java} | 6 +++--- ...asValidatorTest.java => DefaultUAMCasValidatorTest.java} | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) rename restservice/src/main/java/com/plannaplan/security/{CasValidator.java => DefaultUAMCasValidator.java} (87%) rename restservice/src/test/java/com/plannaplan/security/{CasValidatorTest.java => DefaultUAMCasValidatorTest.java} (77%) diff --git a/restservice/src/main/java/com/plannaplan/controllers/TokenController.java b/restservice/src/main/java/com/plannaplan/controllers/TokenController.java index 29df59d..f53181f 100755 --- a/restservice/src/main/java/com/plannaplan/controllers/TokenController.java +++ b/restservice/src/main/java/com/plannaplan/controllers/TokenController.java @@ -2,7 +2,7 @@ package com.plannaplan.controllers; import com.plannaplan.exceptions.UserNotFoundException; import com.plannaplan.security.CasValidationExcepiton; -import com.plannaplan.security.CasValidator; +import com.plannaplan.security.DefaultUAMCasValidator; import com.plannaplan.services.UserService; import org.springframework.beans.factory.annotation.Autowired; @@ -31,7 +31,7 @@ public class TokenController { @ApiOperation(value = "Endpoint to access token required to call secured endpoints. In order to access token we need to provide access token comming from unviersity CAS system") public ResponseEntity getToken( @RequestParam("ticket") @ApiParam(value = "Ticket get from CAS system. It should look like ST-1376572-wo41gty5R0JCZFKMMie2-cas.amu.edu.pl") final String ticket) { - final CasValidator validator = new CasValidator(SERVICE_URL, ticket); + final DefaultUAMCasValidator validator = new DefaultUAMCasValidator(SERVICE_URL, ticket); try { String authority = validator.validate(); diff --git a/restservice/src/main/java/com/plannaplan/security/CasValidator.java b/restservice/src/main/java/com/plannaplan/security/DefaultUAMCasValidator.java similarity index 87% rename from restservice/src/main/java/com/plannaplan/security/CasValidator.java rename to restservice/src/main/java/com/plannaplan/security/DefaultUAMCasValidator.java index 5ad718c..22f514d 100755 --- a/restservice/src/main/java/com/plannaplan/security/CasValidator.java +++ b/restservice/src/main/java/com/plannaplan/security/DefaultUAMCasValidator.java @@ -9,19 +9,19 @@ import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; -public class CasValidator { +public class DefaultUAMCasValidator { private static String CAS_URL = "https://cas.amu.edu.pl/cas"; private final CloseableHttpClient httpClient = HttpClients.createDefault(); private String service; private String ticket; - public CasValidator(String service, String ticket) { + public DefaultUAMCasValidator(String service, String ticket) { this.service = service; this.ticket = ticket; } public String validate() throws Exception, CasValidationExcepiton { - HttpGet request = new HttpGet(CasValidator.CAS_URL + "/validate?service=" + HttpGet request = new HttpGet(DefaultUAMCasValidator.CAS_URL + "/validate?service=" + URLEncoder.encode(this.service, "UTF-8") + "&ticket=" + URLEncoder.encode(this.ticket, "UTF-8")); try (CloseableHttpResponse response = httpClient.execute(request)) { diff --git a/restservice/src/test/java/com/plannaplan/security/CasValidatorTest.java b/restservice/src/test/java/com/plannaplan/security/DefaultUAMCasValidatorTest.java similarity index 77% rename from restservice/src/test/java/com/plannaplan/security/CasValidatorTest.java rename to restservice/src/test/java/com/plannaplan/security/DefaultUAMCasValidatorTest.java index 9772b08..3823c03 100755 --- a/restservice/src/test/java/com/plannaplan/security/CasValidatorTest.java +++ b/restservice/src/test/java/com/plannaplan/security/DefaultUAMCasValidatorTest.java @@ -5,14 +5,14 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.Ignore; import org.junit.Test; -public class CasValidatorTest { +public class DefaultUAMCasValidatorTest { @Test @Ignore public void shouldValidateTicket() { // you need to privide fresh ticket to make this test pass that's why it is // marked as ignored - final CasValidator validator = new CasValidator("http://localhost:3000", + final DefaultUAMCasValidator validator = new DefaultUAMCasValidator("http://localhost:3000", "ST-572267-cbgKrcJLd0tdCubeLqdW-cas.amu.edu.pl"); try { System.out.println(validator.validate()); @@ -24,7 +24,7 @@ public class CasValidatorTest { @Test public void shouldNotValidateTicket() { - final CasValidator validator = new CasValidator("http://localhost:3000", "notticket"); + final DefaultUAMCasValidator validator = new DefaultUAMCasValidator("http://localhost:3000", "notticket"); try { assertTrue(validator.validate().trim().equals("")); } catch (CasValidationExcepiton e) { From cd96e74c29e6712cce310eacaf0186a6dc50e729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Wo=C5=BAniak?= Date: Wed, 2 Dec 2020 13:31:18 +0100 Subject: [PATCH 02/20] Added folder cas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcin Woźniak --- .../main/java/com/plannaplan/controllers/TokenController.java | 4 ++-- .../plannaplan/security/{ => cas}/CasValidationExcepiton.java | 2 +- .../plannaplan/security/{ => cas}/DefaultUAMCasValidator.java | 2 +- .../security/{ => cas}/DefaultUAMCasValidatorTest.java | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) rename restservice/src/main/java/com/plannaplan/security/{ => cas}/CasValidationExcepiton.java (85%) rename restservice/src/main/java/com/plannaplan/security/{ => cas}/DefaultUAMCasValidator.java (97%) rename restservice/src/test/java/com/plannaplan/security/{ => cas}/DefaultUAMCasValidatorTest.java (96%) diff --git a/restservice/src/main/java/com/plannaplan/controllers/TokenController.java b/restservice/src/main/java/com/plannaplan/controllers/TokenController.java index f53181f..f6e35e3 100755 --- a/restservice/src/main/java/com/plannaplan/controllers/TokenController.java +++ b/restservice/src/main/java/com/plannaplan/controllers/TokenController.java @@ -1,8 +1,8 @@ package com.plannaplan.controllers; import com.plannaplan.exceptions.UserNotFoundException; -import com.plannaplan.security.CasValidationExcepiton; -import com.plannaplan.security.DefaultUAMCasValidator; +import com.plannaplan.security.cas.CasValidationExcepiton; +import com.plannaplan.security.cas.DefaultUAMCasValidator; import com.plannaplan.services.UserService; import org.springframework.beans.factory.annotation.Autowired; diff --git a/restservice/src/main/java/com/plannaplan/security/CasValidationExcepiton.java b/restservice/src/main/java/com/plannaplan/security/cas/CasValidationExcepiton.java similarity index 85% rename from restservice/src/main/java/com/plannaplan/security/CasValidationExcepiton.java rename to restservice/src/main/java/com/plannaplan/security/cas/CasValidationExcepiton.java index 88f3f5b..3e6fec3 100755 --- a/restservice/src/main/java/com/plannaplan/security/CasValidationExcepiton.java +++ b/restservice/src/main/java/com/plannaplan/security/cas/CasValidationExcepiton.java @@ -1,4 +1,4 @@ -package com.plannaplan.security; +package com.plannaplan.security.cas; public class CasValidationExcepiton extends RuntimeException { /** diff --git a/restservice/src/main/java/com/plannaplan/security/DefaultUAMCasValidator.java b/restservice/src/main/java/com/plannaplan/security/cas/DefaultUAMCasValidator.java similarity index 97% rename from restservice/src/main/java/com/plannaplan/security/DefaultUAMCasValidator.java rename to restservice/src/main/java/com/plannaplan/security/cas/DefaultUAMCasValidator.java index 22f514d..6f69b3a 100755 --- a/restservice/src/main/java/com/plannaplan/security/DefaultUAMCasValidator.java +++ b/restservice/src/main/java/com/plannaplan/security/cas/DefaultUAMCasValidator.java @@ -1,4 +1,4 @@ -package com.plannaplan.security; +package com.plannaplan.security.cas; import java.net.URLEncoder; diff --git a/restservice/src/test/java/com/plannaplan/security/DefaultUAMCasValidatorTest.java b/restservice/src/test/java/com/plannaplan/security/cas/DefaultUAMCasValidatorTest.java similarity index 96% rename from restservice/src/test/java/com/plannaplan/security/DefaultUAMCasValidatorTest.java rename to restservice/src/test/java/com/plannaplan/security/cas/DefaultUAMCasValidatorTest.java index 3823c03..7ebb5c3 100755 --- a/restservice/src/test/java/com/plannaplan/security/DefaultUAMCasValidatorTest.java +++ b/restservice/src/test/java/com/plannaplan/security/cas/DefaultUAMCasValidatorTest.java @@ -1,4 +1,4 @@ -package com.plannaplan.security; +package com.plannaplan.security.cas; import static org.junit.jupiter.api.Assertions.assertTrue; From 1e3cccc9919f8ead38f3371f19c25c375ba29e0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Wo=C5=BAniak?= Date: Wed, 2 Dec 2020 13:48:28 +0100 Subject: [PATCH 03/20] Added CasValidator.java; Update DefaultUAMCasValidator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcin Woźniak --- .../plannaplan/security/cas/CasValidator.java | 7 ++++ .../security/cas/DefaultUAMCasValidator.java | 38 +++++++++++-------- 2 files changed, 29 insertions(+), 16 deletions(-) create mode 100644 restservice/src/main/java/com/plannaplan/security/cas/CasValidator.java diff --git a/restservice/src/main/java/com/plannaplan/security/cas/CasValidator.java b/restservice/src/main/java/com/plannaplan/security/cas/CasValidator.java new file mode 100644 index 0000000..8cde61c --- /dev/null +++ b/restservice/src/main/java/com/plannaplan/security/cas/CasValidator.java @@ -0,0 +1,7 @@ +package com.plannaplan.security.cas; + +public interface CasValidator { + + String validate(); + +} diff --git a/restservice/src/main/java/com/plannaplan/security/cas/DefaultUAMCasValidator.java b/restservice/src/main/java/com/plannaplan/security/cas/DefaultUAMCasValidator.java index 6f69b3a..b5c2ad6 100755 --- a/restservice/src/main/java/com/plannaplan/security/cas/DefaultUAMCasValidator.java +++ b/restservice/src/main/java/com/plannaplan/security/cas/DefaultUAMCasValidator.java @@ -9,7 +9,7 @@ import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; -public class DefaultUAMCasValidator { +public class DefaultUAMCasValidator implements CasValidator { private static String CAS_URL = "https://cas.amu.edu.pl/cas"; private final CloseableHttpClient httpClient = HttpClients.createDefault(); private String service; @@ -20,26 +20,32 @@ public class DefaultUAMCasValidator { this.ticket = ticket; } - public String validate() throws Exception, CasValidationExcepiton { - HttpGet request = new HttpGet(DefaultUAMCasValidator.CAS_URL + "/validate?service=" - + URLEncoder.encode(this.service, "UTF-8") + "&ticket=" + URLEncoder.encode(this.ticket, "UTF-8")); - try (CloseableHttpResponse response = httpClient.execute(request)) { + @Override + public String validate() { + try { + HttpGet request = new HttpGet(DefaultUAMCasValidator.CAS_URL + "/validate?service=" + + URLEncoder.encode(this.service, "UTF-8") + "&ticket=" + URLEncoder.encode(this.ticket, "UTF-8")); + try (CloseableHttpResponse response = httpClient.execute(request)) { - HttpEntity entity = response.getEntity(); + HttpEntity entity = response.getEntity(); - String result = null; - if (entity != null) { - // return it as a String - result = EntityUtils.toString(entity); - if (result.replace("\n", "").trim().equals("no")) { - throw new CasValidationExcepiton("Validation failed"); + String result = null; + if (entity != null) { + // return it as a String + result = EntityUtils.toString(entity); + if (result.replace("\n", "").trim().equals("no")) { + throw new CasValidationExcepiton("Validation failed"); + } } + + String res = result.substring(result.indexOf('\n') + 1); + return res; + } - - String res = result.substring(result.indexOf('\n') + 1); - return res; - } + catch (Exception e) { + throw new CasValidationExcepiton("Cas Validation has failed."); + } } } From 20d261635b0035ef9137f8bc4b92203ea460e2f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Wo=C5=BAniak?= Date: Wed, 2 Dec 2020 13:51:07 +0100 Subject: [PATCH 04/20] Added CustomUAMCasValidator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcin Woźniak --- .../security/cas/CustomUAMCasValidator.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 restservice/src/main/java/com/plannaplan/security/cas/CustomUAMCasValidator.java diff --git a/restservice/src/main/java/com/plannaplan/security/cas/CustomUAMCasValidator.java b/restservice/src/main/java/com/plannaplan/security/cas/CustomUAMCasValidator.java new file mode 100644 index 0000000..67b8885 --- /dev/null +++ b/restservice/src/main/java/com/plannaplan/security/cas/CustomUAMCasValidator.java @@ -0,0 +1,12 @@ +package com.plannaplan.security.cas; + +public class CustomUAMCasValidator implements CasValidator { + + public CustomUAMCasValidator(){} + + @Override + public String validate() { + + return null; + } +} From c24aa73bb02f7f7cfe45081088fb17284ca40db9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Wo=C5=BAniak?= Date: Wed, 2 Dec 2020 14:37:44 +0100 Subject: [PATCH 05/20] CustomUAMCasValidatorTest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcin Woźniak --- .../cas/CustomUAMCasValidatorTest.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 restservice/src/test/java/com/plannaplan/security/cas/CustomUAMCasValidatorTest.java diff --git a/restservice/src/test/java/com/plannaplan/security/cas/CustomUAMCasValidatorTest.java b/restservice/src/test/java/com/plannaplan/security/cas/CustomUAMCasValidatorTest.java new file mode 100644 index 0000000..2abcd48 --- /dev/null +++ b/restservice/src/test/java/com/plannaplan/security/cas/CustomUAMCasValidatorTest.java @@ -0,0 +1,28 @@ +package com.plannaplan.security.cas; + +import org.junit.Test; +import org.jasig.cas.client.validation.Assertion; +import org.jasig.cas.client.validation.Cas20ServiceTicketValidator; +import org.jasig.cas.client.validation.TicketValidationException; + +public class CustomUAMCasValidatorTest { + @Test + public void shouldValidateWithDomain() { + /* + * TO DO + * Dodać case z CAS10/CAS20/CAS30 + */ + Cas20ServiceTicketValidator validator = new Cas20ServiceTicketValidator("https://cas.amu.edu.pl/cas"); + + try { + Assertion assertion = validator.validate("ST-53723-d0gcC3qovlJhhnKZBhTN-cas.amu.edu.pl","https://wmi.plannaplan.pl"); + if (assertion == null) { + throw new CasValidationExcepiton("Validation failed. Assertion could not be retrieved for ticket " + ""); + } + + } catch (TicketValidationException e) { + e.printStackTrace(); + } + + } +} From 6298bb47652c8097cf617cc460c01fe36765e6d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Wo=C5=BAniak?= Date: Wed, 2 Dec 2020 15:00:18 +0100 Subject: [PATCH 06/20] Updated MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcin Woźniak --- .../security/cas/CustomUAMCasValidator.java | 38 +++++++++++++++++-- .../cas/CustomUAMCasValidatorTest.java | 20 ++-------- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/restservice/src/main/java/com/plannaplan/security/cas/CustomUAMCasValidator.java b/restservice/src/main/java/com/plannaplan/security/cas/CustomUAMCasValidator.java index 67b8885..764be01 100644 --- a/restservice/src/main/java/com/plannaplan/security/cas/CustomUAMCasValidator.java +++ b/restservice/src/main/java/com/plannaplan/security/cas/CustomUAMCasValidator.java @@ -1,12 +1,44 @@ package com.plannaplan.security.cas; -public class CustomUAMCasValidator implements CasValidator { +import org.jasig.cas.client.validation.Assertion; +import org.jasig.cas.client.validation.Cas20ServiceTicketValidator; +import org.jasig.cas.client.validation.TicketValidationException; - public CustomUAMCasValidator(){} +public class CustomUAMCasValidator implements CasValidator { + private static String CAS_URL = "https://cas.amu.edu.pl/cas"; + private static String EMAIL_FIELD = "mail"; + private static String USOS_ID = "usos_id"; + private String service; + private String ticket; + + public CustomUAMCasValidator(String service, String ticket){ + this.service = service; + this.ticket = ticket; + } @Override public String validate() { - + /* + * TO DO + * Dodać case z CAS10/CAS20/CAS30 + */ + Cas20ServiceTicketValidator validator = new Cas20ServiceTicketValidator(CustomUAMCasValidator.CAS_URL); + + try { + Assertion assertion = validator.validate(this.ticket, this.service); + if (assertion == null) { + throw new CasValidationExcepiton("Validation failed. Assertion could not be retrieved for ticket " + ""); + } + String usosid = assertion.getPrincipal().getAttributes().get(CustomUAMCasValidator.USOS_ID).toString(); + String mail = assertion.getPrincipal().getAttributes().get(CustomUAMCasValidator.EMAIL_FIELD).toString(); + + System.out.println(usosid); + System.out.println(mail); + + } catch (TicketValidationException e) { + e.printStackTrace(); + } + return null; } } diff --git a/restservice/src/test/java/com/plannaplan/security/cas/CustomUAMCasValidatorTest.java b/restservice/src/test/java/com/plannaplan/security/cas/CustomUAMCasValidatorTest.java index 2abcd48..8069d0e 100644 --- a/restservice/src/test/java/com/plannaplan/security/cas/CustomUAMCasValidatorTest.java +++ b/restservice/src/test/java/com/plannaplan/security/cas/CustomUAMCasValidatorTest.java @@ -1,28 +1,14 @@ package com.plannaplan.security.cas; import org.junit.Test; -import org.jasig.cas.client.validation.Assertion; -import org.jasig.cas.client.validation.Cas20ServiceTicketValidator; -import org.jasig.cas.client.validation.TicketValidationException; public class CustomUAMCasValidatorTest { @Test public void shouldValidateWithDomain() { - /* - * TO DO - * Dodać case z CAS10/CAS20/CAS30 - */ - Cas20ServiceTicketValidator validator = new Cas20ServiceTicketValidator("https://cas.amu.edu.pl/cas"); + CustomUAMCasValidator validator = new CustomUAMCasValidator("https://wmi.plannaplan.pl", "ST-54649-5x4h09vzUpEIyAGmf1sz-cas.amu.edu.pl"); + + validator.validate(); - try { - Assertion assertion = validator.validate("ST-53723-d0gcC3qovlJhhnKZBhTN-cas.amu.edu.pl","https://wmi.plannaplan.pl"); - if (assertion == null) { - throw new CasValidationExcepiton("Validation failed. Assertion could not be retrieved for ticket " + ""); - } - - } catch (TicketValidationException e) { - e.printStackTrace(); - } } } From b6c2e43975ee6ec4bb526911de08b5eec2b007df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Wo=C5=BAniak?= Date: Thu, 3 Dec 2020 15:11:23 +0100 Subject: [PATCH 07/20] Added cas module + add customuamcasvalidator + test passed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcin Woźniak --- restservice/pom.xml | 7 +++++++ .../controllers/TokenController.java | 4 +++- .../security/cas/CasUserIdentity.java | 19 +++++++++++++++++++ .../plannaplan/security/cas/CasValidator.java | 3 +-- .../security/cas/CustomUAMCasValidator.java | 16 +++++++++------- .../security/cas/DefaultUAMCasValidator.java | 4 ++-- .../cas/DefaultUAMCasValidatorTest.java | 2 +- 7 files changed, 42 insertions(+), 13 deletions(-) create mode 100644 restservice/src/main/java/com/plannaplan/security/cas/CasUserIdentity.java diff --git a/restservice/pom.xml b/restservice/pom.xml index 60714c1..773be7a 100755 --- a/restservice/pom.xml +++ b/restservice/pom.xml @@ -57,6 +57,13 @@ test + + + org.jasig.cas.client + cas-client-core + 3.6.2 + + org.springframework.boot spring-boot-starter-security diff --git a/restservice/src/main/java/com/plannaplan/controllers/TokenController.java b/restservice/src/main/java/com/plannaplan/controllers/TokenController.java index f6e35e3..8e06c2e 100755 --- a/restservice/src/main/java/com/plannaplan/controllers/TokenController.java +++ b/restservice/src/main/java/com/plannaplan/controllers/TokenController.java @@ -1,6 +1,7 @@ package com.plannaplan.controllers; import com.plannaplan.exceptions.UserNotFoundException; +import com.plannaplan.security.cas.CasUserIdentity; import com.plannaplan.security.cas.CasValidationExcepiton; import com.plannaplan.security.cas.DefaultUAMCasValidator; import com.plannaplan.services.UserService; @@ -34,7 +35,8 @@ public class TokenController { final DefaultUAMCasValidator validator = new DefaultUAMCasValidator(SERVICE_URL, ticket); try { - String authority = validator.validate(); + CasUserIdentity casUserIdentity = validator.validate(); + String authority = casUserIdentity.getEmail(); String token = this.userService.login(authority); return new ResponseEntity<>(token, HttpStatus.OK); } catch (CasValidationExcepiton e) { diff --git a/restservice/src/main/java/com/plannaplan/security/cas/CasUserIdentity.java b/restservice/src/main/java/com/plannaplan/security/cas/CasUserIdentity.java new file mode 100644 index 0000000..90b1f5e --- /dev/null +++ b/restservice/src/main/java/com/plannaplan/security/cas/CasUserIdentity.java @@ -0,0 +1,19 @@ +package com.plannaplan.security.cas; + +public class CasUserIdentity { + private String usosId; + private String email; + + public CasUserIdentity(String usosId, String email){ + this.usosId = usosId; + this.email = email; + } + + public String getUsosId() { + return usosId; + } + + public String getEmail() { + return email; + } +} \ No newline at end of file diff --git a/restservice/src/main/java/com/plannaplan/security/cas/CasValidator.java b/restservice/src/main/java/com/plannaplan/security/cas/CasValidator.java index 8cde61c..b091e42 100644 --- a/restservice/src/main/java/com/plannaplan/security/cas/CasValidator.java +++ b/restservice/src/main/java/com/plannaplan/security/cas/CasValidator.java @@ -2,6 +2,5 @@ package com.plannaplan.security.cas; public interface CasValidator { - String validate(); - + CasUserIdentity validate(); } diff --git a/restservice/src/main/java/com/plannaplan/security/cas/CustomUAMCasValidator.java b/restservice/src/main/java/com/plannaplan/security/cas/CustomUAMCasValidator.java index 764be01..9a845df 100644 --- a/restservice/src/main/java/com/plannaplan/security/cas/CustomUAMCasValidator.java +++ b/restservice/src/main/java/com/plannaplan/security/cas/CustomUAMCasValidator.java @@ -17,23 +17,25 @@ public class CustomUAMCasValidator implements CasValidator { } @Override - public String validate() { + public CasUserIdentity validate() { /* * TO DO * Dodać case z CAS10/CAS20/CAS30 */ - Cas20ServiceTicketValidator validator = new Cas20ServiceTicketValidator(CustomUAMCasValidator.CAS_URL); + final Cas20ServiceTicketValidator validator = new Cas20ServiceTicketValidator(CustomUAMCasValidator.CAS_URL); try { - Assertion assertion = validator.validate(this.ticket, this.service); + final Assertion assertion = validator.validate(this.ticket, this.service); + if (assertion == null) { throw new CasValidationExcepiton("Validation failed. Assertion could not be retrieved for ticket " + ""); } - String usosid = assertion.getPrincipal().getAttributes().get(CustomUAMCasValidator.USOS_ID).toString(); - String mail = assertion.getPrincipal().getAttributes().get(CustomUAMCasValidator.EMAIL_FIELD).toString(); - System.out.println(usosid); - System.out.println(mail); + final String usosid = assertion.getPrincipal().getAttributes().get(CustomUAMCasValidator.USOS_ID).toString(); + + final String mail = assertion.getPrincipal().getAttributes().get(CustomUAMCasValidator.EMAIL_FIELD).toString(); + + return new CasUserIdentity(usosid,mail); } catch (TicketValidationException e) { e.printStackTrace(); diff --git a/restservice/src/main/java/com/plannaplan/security/cas/DefaultUAMCasValidator.java b/restservice/src/main/java/com/plannaplan/security/cas/DefaultUAMCasValidator.java index b5c2ad6..e79a410 100755 --- a/restservice/src/main/java/com/plannaplan/security/cas/DefaultUAMCasValidator.java +++ b/restservice/src/main/java/com/plannaplan/security/cas/DefaultUAMCasValidator.java @@ -21,7 +21,7 @@ public class DefaultUAMCasValidator implements CasValidator { } @Override - public String validate() { + public CasUserIdentity validate() { try { HttpGet request = new HttpGet(DefaultUAMCasValidator.CAS_URL + "/validate?service=" + URLEncoder.encode(this.service, "UTF-8") + "&ticket=" + URLEncoder.encode(this.ticket, "UTF-8")); @@ -39,7 +39,7 @@ public class DefaultUAMCasValidator implements CasValidator { } String res = result.substring(result.indexOf('\n') + 1); - return res; + return new CasUserIdentity(null,res); } } diff --git a/restservice/src/test/java/com/plannaplan/security/cas/DefaultUAMCasValidatorTest.java b/restservice/src/test/java/com/plannaplan/security/cas/DefaultUAMCasValidatorTest.java index 7ebb5c3..1373e93 100755 --- a/restservice/src/test/java/com/plannaplan/security/cas/DefaultUAMCasValidatorTest.java +++ b/restservice/src/test/java/com/plannaplan/security/cas/DefaultUAMCasValidatorTest.java @@ -26,7 +26,7 @@ public class DefaultUAMCasValidatorTest { public void shouldNotValidateTicket() { final DefaultUAMCasValidator validator = new DefaultUAMCasValidator("http://localhost:3000", "notticket"); try { - assertTrue(validator.validate().trim().equals("")); + assertTrue(validator.validate().getEmail().trim().equals("")); } catch (CasValidationExcepiton e) { assertTrue(true); } catch (Exception e) { From 3ebfda5316264726f8a051e39519802ed1865d54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Wo=C5=BAniak?= Date: Thu, 3 Dec 2020 16:23:39 +0100 Subject: [PATCH 08/20] CAS Part 1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcin Woźniak --- .../java/com/plannaplan/entities/User.java | 25 +++++++++++++++++++ .../repositories/UserRepository.java | 3 +++ .../com/plannaplan/services/UserService.java | 25 +++++++++++++++++++ .../src/main/java/com/plannaplan/App.java | 8 ------ .../controllers/TokenController.java | 4 ++- 5 files changed, 56 insertions(+), 9 deletions(-) diff --git a/buisnesslogic/src/main/java/com/plannaplan/entities/User.java b/buisnesslogic/src/main/java/com/plannaplan/entities/User.java index bb4bd4c..9bf955d 100755 --- a/buisnesslogic/src/main/java/com/plannaplan/entities/User.java +++ b/buisnesslogic/src/main/java/com/plannaplan/entities/User.java @@ -24,9 +24,11 @@ public class User { private String name; private String surname; private String email; + private String usosId; private UserRoles role; private String token; private Timestamp tokenUsageDate; + public User() { } @@ -46,6 +48,29 @@ public class User { this.role = role; } + /* + * User + * + * @param name name given to the user + * @param surname surname given to the user + * @param email mail given to the user + * @param usosId id in the USOS system + * @param role role given to the user + */ + public User(String name, String surname, String mail, String usosId, UserRoles role){ + this(name,surname,mail,role); + this.usosId = usosId; + } + + /* + * getusosId + * + * @return usosId + */ + public String getUsosId() { + return usosId; + } + /* * getEmail * diff --git a/buisnesslogic/src/main/java/com/plannaplan/repositories/UserRepository.java b/buisnesslogic/src/main/java/com/plannaplan/repositories/UserRepository.java index c2cc8e8..206fa34 100755 --- a/buisnesslogic/src/main/java/com/plannaplan/repositories/UserRepository.java +++ b/buisnesslogic/src/main/java/com/plannaplan/repositories/UserRepository.java @@ -50,4 +50,7 @@ public interface UserRepository extends JpaRepository { @Query("FROM User WHERE (name LIKE %?1% OR surname LIKE %?1%) AND role=?2") List searchForUsers(@Param("query") String query, @Param("role") UserRoles role); + + @Query("FROM User WHERE usosId = ?1") + Optional getByUsosId(@Param("usosId") String usosId); } \ No newline at end of file diff --git a/buisnesslogic/src/main/java/com/plannaplan/services/UserService.java b/buisnesslogic/src/main/java/com/plannaplan/services/UserService.java index a9a7c3f..2a6ea14 100755 --- a/buisnesslogic/src/main/java/com/plannaplan/services/UserService.java +++ b/buisnesslogic/src/main/java/com/plannaplan/services/UserService.java @@ -24,6 +24,31 @@ public class UserService { super(); } + public User checkForUser(String email, String usosId) { + if (usosId == null) { + Optional user = this.repo.getByAuthority(email.replace("\n", "").trim()); + if (user.isPresent()){ + return user.get(); + } + else { + final User newUser = new User(null,null,email.replace("\n", "").trim(),UserRoles.STUDENT); + this.repo.save(newUser); + return newUser; + } + } + else { + Optional user = this.repo.getByUsosId(usosId.replace("\n", "").trim()); + if (user.isPresent()){ + return user.get(); + } + else { + final User newUser = new User(null,null,email.replace("\n", "").trim(),usosId,UserRoles.STUDENT); + this.repo.save(newUser); + return newUser; + } + } + } + public String login(String authority) throws UserNotFoundException { User user = this.repo.getByAuthority(authority.replace("\n", "").trim()) .orElseThrow(() -> new UserNotFoundException("Can not find user with given authority")); diff --git a/restservice/src/main/java/com/plannaplan/App.java b/restservice/src/main/java/com/plannaplan/App.java index af81457..33561b9 100755 --- a/restservice/src/main/java/com/plannaplan/App.java +++ b/restservice/src/main/java/com/plannaplan/App.java @@ -49,13 +49,5 @@ public class App { mac.setSurname("Głowacki"); mac.setRole(UserRoles.STUDENT); this.userService.save(mac); - - User mar = new User(); - mar.setEmail("marwoz16@st.amu.edu.pl"); - mar.setName("Marcin"); - mar.setSurname("Woźniak"); - mar.setRole(UserRoles.ADMIN); - this.userService.save(mar); - } } diff --git a/restservice/src/main/java/com/plannaplan/controllers/TokenController.java b/restservice/src/main/java/com/plannaplan/controllers/TokenController.java index 8e06c2e..8500298 100755 --- a/restservice/src/main/java/com/plannaplan/controllers/TokenController.java +++ b/restservice/src/main/java/com/plannaplan/controllers/TokenController.java @@ -36,7 +36,9 @@ public class TokenController { try { CasUserIdentity casUserIdentity = validator.validate(); - String authority = casUserIdentity.getEmail(); + String usosId = casUserIdentity.getUsosId(); + String authority = casUserIdentity.getEmail(); + this.userService.checkForUser(authority, usosId); String token = this.userService.login(authority); return new ResponseEntity<>(token, HttpStatus.OK); } catch (CasValidationExcepiton e) { From 453907782a279977ac1b9e081f2358feb595b4f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Wo=C5=BAniak?= Date: Thu, 3 Dec 2020 16:57:53 +0100 Subject: [PATCH 09/20] CAS Part 2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcin Woźniak --- .../com/plannaplan/services/UserService.java | 20 +++++------ .../plannaplan/services/UserServiceTest.java | 15 ++------ .../controllers/TokenController.java | 11 +++--- .../AssignmentsControllerTest.java | 4 +-- .../controllers/CommisionControllerTest.java | 36 ++++++++++++------- .../controllers/ConfigControllerTest.java | 10 +++--- .../controllers/UsersControllerTest.java | 8 ++--- 7 files changed, 51 insertions(+), 53 deletions(-) diff --git a/buisnesslogic/src/main/java/com/plannaplan/services/UserService.java b/buisnesslogic/src/main/java/com/plannaplan/services/UserService.java index 2a6ea14..88426a6 100755 --- a/buisnesslogic/src/main/java/com/plannaplan/services/UserService.java +++ b/buisnesslogic/src/main/java/com/plannaplan/services/UserService.java @@ -32,8 +32,7 @@ public class UserService { } else { final User newUser = new User(null,null,email.replace("\n", "").trim(),UserRoles.STUDENT); - this.repo.save(newUser); - return newUser; + return this.repo.save(newUser); } } else { @@ -43,19 +42,20 @@ public class UserService { } else { final User newUser = new User(null,null,email.replace("\n", "").trim(),usosId,UserRoles.STUDENT); - this.repo.save(newUser); - return newUser; + return this.repo.save(newUser); } } } - public String login(String authority) throws UserNotFoundException { - User user = this.repo.getByAuthority(authority.replace("\n", "").trim()) - .orElseThrow(() -> new UserNotFoundException("Can not find user with given authority")); - + public String login(User authority) throws UserNotFoundException { final String token = UUID.randomUUID().toString(); - user.setToken(token); - this.repo.save(user); + try{ + authority.setToken(token); + this.repo.save(authority); + } + catch (Exception e){ + throw new UserNotFoundException(e.getMessage()); + } return token; } diff --git a/buisnesslogic/src/test/java/com/plannaplan/services/UserServiceTest.java b/buisnesslogic/src/test/java/com/plannaplan/services/UserServiceTest.java index 256ec90..265daae 100755 --- a/buisnesslogic/src/test/java/com/plannaplan/services/UserServiceTest.java +++ b/buisnesslogic/src/test/java/com/plannaplan/services/UserServiceTest.java @@ -32,10 +32,9 @@ public class UserServiceTest { @Test public void shouldReturnToken() { - final User testUser = new User(TEST_USER_NAME, TEST_USER_SUERNAME, TEST_USER_MAIL, UserRoles.TEST_USER); - this.userService.save(testUser); + final User testUser = this.userService.save(new User(TEST_USER_NAME, TEST_USER_SUERNAME, TEST_USER_MAIL, UserRoles.TEST_USER)); try { - final String token = this.userService.login(TEST_USER_MAIL); + final String token = this.userService.login(testUser); System.out.println("Returned token: " + token); assertTrue(token != null); assertTrue(this.userService.getUserByEmail(TEST_USER_MAIL).getToken() != null); @@ -45,16 +44,6 @@ public class UserServiceTest { } } - @Test - public void shouldThrowException() { - try { - this.userService.login("thiseamilisnotindatabase@gmail.com"); - assertTrue(false); - } catch (UserNotFoundException e) { - assertTrue(true); - } - } - @Test public void shouldFindStudents() { this.userService.save(new User("Nemo", "TheFish", "Nemo@shouldFindStudents.test", UserRoles.STUDENT)); diff --git a/restservice/src/main/java/com/plannaplan/controllers/TokenController.java b/restservice/src/main/java/com/plannaplan/controllers/TokenController.java index 8500298..5bf40f7 100755 --- a/restservice/src/main/java/com/plannaplan/controllers/TokenController.java +++ b/restservice/src/main/java/com/plannaplan/controllers/TokenController.java @@ -1,5 +1,6 @@ package com.plannaplan.controllers; +import com.plannaplan.entities.User; import com.plannaplan.exceptions.UserNotFoundException; import com.plannaplan.security.cas.CasUserIdentity; import com.plannaplan.security.cas.CasValidationExcepiton; @@ -35,11 +36,11 @@ public class TokenController { final DefaultUAMCasValidator validator = new DefaultUAMCasValidator(SERVICE_URL, ticket); try { - CasUserIdentity casUserIdentity = validator.validate(); - String usosId = casUserIdentity.getUsosId(); - String authority = casUserIdentity.getEmail(); - this.userService.checkForUser(authority, usosId); - String token = this.userService.login(authority); + final CasUserIdentity casUserIdentity = validator.validate(); + final String usosId = casUserIdentity.getUsosId(); + final String authority = casUserIdentity.getEmail(); + final User user = this.userService.checkForUser(authority, usosId); + String token = this.userService.login(user); return new ResponseEntity<>(token, HttpStatus.OK); } catch (CasValidationExcepiton e) { return new ResponseEntity<>("Wrong ticket", HttpStatus.UNAUTHORIZED); diff --git a/restservice/src/test/java/com/plannaplan/controllers/AssignmentsControllerTest.java b/restservice/src/test/java/com/plannaplan/controllers/AssignmentsControllerTest.java index e5de853..eb04229 100755 --- a/restservice/src/test/java/com/plannaplan/controllers/AssignmentsControllerTest.java +++ b/restservice/src/test/java/com/plannaplan/controllers/AssignmentsControllerTest.java @@ -36,8 +36,8 @@ public class AssignmentsControllerTest extends AbstractControllerTest { @Test public void shouldReturnOk() throws Exception { - this.service.save(new User(null, null, TEST_MAIL, UserRoles.TEST_USER)); - final String token = this.service.login(TEST_MAIL); + final User newuser = this.service.save(new User(null, null, TEST_MAIL, UserRoles.TEST_USER)); + final String token = this.service.login(newuser); MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); mockMvc.perform(get(ASSIGFNMENTS_ENDPOINT).header("Authorization", "Bearer " + token)) diff --git a/restservice/src/test/java/com/plannaplan/controllers/CommisionControllerTest.java b/restservice/src/test/java/com/plannaplan/controllers/CommisionControllerTest.java index 1ee756b..1408ca6 100755 --- a/restservice/src/test/java/com/plannaplan/controllers/CommisionControllerTest.java +++ b/restservice/src/test/java/com/plannaplan/controllers/CommisionControllerTest.java @@ -57,7 +57,8 @@ public class CommisionControllerTest extends AbstractControllerTest { @Test public void shouldFailedAddingCommisionDueToNoArgs() throws Exception { this.checkUsers(); - final String token = this.service.login(TEST_COMMISIONS_STUDENT_EMAIL); + final User user = this.service.checkForUser(TEST_COMMISIONS_STUDENT_EMAIL, null); + final String token = this.service.login(user); MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); mockMvc.perform(post(ADD_COMMISION_ENDPOINT).header("Authorization", "Bearer " + token)) @@ -67,7 +68,8 @@ public class CommisionControllerTest extends AbstractControllerTest { @Test public void shouldReturnOkAddingCommision() throws Exception { this.checkUsers(); - final String token = this.service.login(TEST_COMMISIONS_STUDENT_EMAIL); + final User user = this.service.checkForUser(TEST_COMMISIONS_STUDENT_EMAIL, null); + final String token = this.service.login(user); MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); mockMvc.perform(post(ADD_COMMISION_ENDPOINT).header("Authorization", "Bearer " + token) @@ -83,7 +85,8 @@ public class CommisionControllerTest extends AbstractControllerTest { @Test public void shouldReturnOkGettingAllCommisions() throws Exception { this.checkUsers(); - final String token = this.service.login(TEST_COMMISIONS_STUDENT_EMAIL); + final User user = this.service.checkForUser(TEST_COMMISIONS_STUDENT_EMAIL, null); + final String token = this.service.login(user); MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); mockMvc.perform(get(GET_COMMISIONS_ENDPOINT).header("Authorization", "Bearer " + token)) @@ -93,7 +96,8 @@ public class CommisionControllerTest extends AbstractControllerTest { @Test public void shouldAddCommisionWithSelfIdPrivided() throws Exception { this.checkUsers(); - final String token = this.service.login(TEST_COMMISIONS_STUDENT_EMAIL); + final User user = this.service.checkForUser(TEST_COMMISIONS_STUDENT_EMAIL, null); + final String token = this.service.login(user); MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); mockMvc.perform(post(ADD_COMMISION_ENDPOINT + "/" + CommisionControllerTest.user.getId().toString()) @@ -105,7 +109,8 @@ public class CommisionControllerTest extends AbstractControllerTest { public void shouldFailCommisionWithSomeoneIdPrividedAsStudent() throws Exception { this.checkUsers(); - final String token = this.service.login(TEST_COMMISIONS_STUDENT_EMAIL); + final User user = this.service.checkForUser(TEST_COMMISIONS_STUDENT_EMAIL, null); + final String token = this.service.login(user); MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); mockMvc.perform(post(ADD_COMMISION_ENDPOINT + "/" + CommisionControllerTest.otherUser.getId().toString()) @@ -116,7 +121,8 @@ public class CommisionControllerTest extends AbstractControllerTest { @Test public void shouldFailCommisionAsDeanaryWithNoId() throws Exception { this.checkUsers(); - final String token = this.service.login(TEST_COMMISIONS_DEANERY_EMAIL); + final User user = this.service.checkForUser(TEST_COMMISIONS_DEANERY_EMAIL, null); + final String token = this.service.login(user); MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); mockMvc.perform(post(ADD_COMMISION_ENDPOINT).header("Authorization", "Bearer " + token) @@ -125,8 +131,8 @@ public class CommisionControllerTest extends AbstractControllerTest { @Test public void shouldFailCommisionWithSelfIdPrividedAsDeanary() throws Exception { - this.checkUsers(); - final String token = this.service.login(TEST_COMMISIONS_DEANERY_EMAIL); + final User user = this.service.checkForUser(TEST_COMMISIONS_DEANERY_EMAIL, null); + final String token = this.service.login(user); MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); mockMvc.perform(post(ADD_COMMISION_ENDPOINT + "/" + CommisionControllerTest.asker.getId().toString()) @@ -138,7 +144,8 @@ public class CommisionControllerTest extends AbstractControllerTest { public void shouldAddCommisionWithSomeoneIdPrividedAsDeanary() throws Exception { this.checkUsers(); - final String token = this.service.login(TEST_COMMISIONS_DEANERY_EMAIL); + final User user = this.service.checkForUser(TEST_COMMISIONS_DEANERY_EMAIL, null); + final String token = this.service.login(user); MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); mockMvc.perform(post(ADD_COMMISION_ENDPOINT + "/" + CommisionControllerTest.otherUser.getId().toString()) @@ -150,7 +157,8 @@ public class CommisionControllerTest extends AbstractControllerTest { public void shouldFailCommisionWithOtherDeanaryIdPrividedAsDeanary() throws Exception { this.checkUsers(); - final String token = this.service.login(TEST_COMMISIONS_DEANERY_EMAIL); + final User user = this.service.checkForUser(TEST_COMMISIONS_DEANERY_EMAIL, null); + final String token = this.service.login(user); MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); mockMvc.perform(post(ADD_COMMISION_ENDPOINT + "/" + CommisionControllerTest.otherAsker.getId().toString()) @@ -162,7 +170,8 @@ public class CommisionControllerTest extends AbstractControllerTest { public void shouldGetStudentCommisionsListByDeanary() throws Exception { this.checkUsers(); - final String token = this.service.login(TEST_COMMISIONS_DEANERY_EMAIL); + final User user = this.service.checkForUser(TEST_COMMISIONS_DEANERY_EMAIL, null); + final String token = this.service.login(user); MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); mockMvc.perform(get(GET_SOMEONE_COMMISIONS_ENDPOINT + "/" + CommisionControllerTest.user.getId().toString()) @@ -172,8 +181,9 @@ public class CommisionControllerTest extends AbstractControllerTest { @Test public void shouldFailStudentCommisionsListByOtherStudent() throws Exception { this.checkUsers(); - - final String token = this.service.login(TEST_COMMISIONS_STUDENT_EMAIL); + + final User user = this.service.checkForUser(TEST_COMMISIONS_STUDENT_EMAIL, null); + final String token = this.service.login(user); MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); mockMvc.perform(get(GET_SOMEONE_COMMISIONS_ENDPOINT + "/" + CommisionControllerTest.user.getId().toString()) diff --git a/restservice/src/test/java/com/plannaplan/controllers/ConfigControllerTest.java b/restservice/src/test/java/com/plannaplan/controllers/ConfigControllerTest.java index 9c56c69..b6e510b 100755 --- a/restservice/src/test/java/com/plannaplan/controllers/ConfigControllerTest.java +++ b/restservice/src/test/java/com/plannaplan/controllers/ConfigControllerTest.java @@ -45,12 +45,11 @@ public class ConfigControllerTest extends AbstractControllerTest { @Test public void shouldReturnOKAuthorized() throws Exception { final String mail = "shouldReturnOKAuthorized@ConfigController.test"; - final User usr = new User(null, null, mail, UserRoles.ADMIN); - this.service.save(usr); + final User usr = this.service.save(new User(null, null, mail, UserRoles.ADMIN)); final InputStream inputStream = getClass().getClassLoader().getResourceAsStream(FILE_NAME); final MockMultipartFile file = new MockMultipartFile("file", inputStream); - final String token = this.service.login(mail); + final String token = this.service.login(usr); MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); mockMvc.perform(multipart(CONFIG_ENDPOINT).file(file).header("Authorization", "Bearer " + token)) @@ -61,12 +60,11 @@ public class ConfigControllerTest extends AbstractControllerTest { @Test public void shouldReturnDenyNoAdminAuthorized() throws Exception { final String mail = "shouldReturnDenyNoAdminAuthorized@ConfigController.test"; - final User usr = new User(null, null, mail, UserRoles.TEST_USER); - this.service.save(usr); + final User usr = this.service.save(new User(null, null, mail, UserRoles.TEST_USER)); final InputStream inputStream = getClass().getClassLoader().getResourceAsStream(FILE_NAME); final MockMultipartFile file = new MockMultipartFile("file", inputStream); - final String token = this.service.login(mail); + final String token = this.service.login(usr); MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); mockMvc.perform(multipart(CONFIG_ENDPOINT).file(file).header("Authorization", "Bearer " + token)) diff --git a/restservice/src/test/java/com/plannaplan/controllers/UsersControllerTest.java b/restservice/src/test/java/com/plannaplan/controllers/UsersControllerTest.java index 9d4120e..ceaa738 100755 --- a/restservice/src/test/java/com/plannaplan/controllers/UsersControllerTest.java +++ b/restservice/src/test/java/com/plannaplan/controllers/UsersControllerTest.java @@ -30,8 +30,8 @@ public class UsersControllerTest extends AbstractControllerTest { @Test public void shouldRestrun200OK() throws Exception { final String email = "notexistingassignmentuser@shouldRestrun200OK.test"; - this.service.save(new User(null, null, email, UserRoles.DEANERY)); - final String token = this.service.login(email); + final User user = this.service.save(new User(null, null, email, UserRoles.DEANERY)); + final String token = this.service.login(user); MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); mockMvc.perform(get(ENDPOINT).param("query", "").header("Authorization", "Bearer " + token)) @@ -47,8 +47,8 @@ public class UsersControllerTest extends AbstractControllerTest { @Test public void shouldFailedDueToMissingParam() throws Exception { final String email = "notexistingassignmentuser@shouldFailedDueToMissingParam.test"; - this.service.save(new User(null, null, email, UserRoles.DEANERY)); - final String token = this.service.login(email); + final User user = this.service.save(new User(null, null, email, UserRoles.DEANERY)); + final String token = this.service.login(user); MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); mockMvc.perform(get(ENDPOINT).header("Authorization", "Bearer " + token)) From f4db74b8999c1d6798fa6214052cb06fe327bc21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Wo=C5=BAniak?= Date: Thu, 3 Dec 2020 17:07:41 +0100 Subject: [PATCH 10/20] Added test UserServiceTest - shouldCreateUser shouldReturnExistingUser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcin Woźniak --- .../plannaplan/services/UserServiceTest.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/buisnesslogic/src/test/java/com/plannaplan/services/UserServiceTest.java b/buisnesslogic/src/test/java/com/plannaplan/services/UserServiceTest.java index 265daae..46db025 100755 --- a/buisnesslogic/src/test/java/com/plannaplan/services/UserServiceTest.java +++ b/buisnesslogic/src/test/java/com/plannaplan/services/UserServiceTest.java @@ -82,4 +82,21 @@ public class UserServiceTest { private boolean containsName(final List list, final String name) { return list.stream().map(User::getName).filter(name::equals).findFirst().isPresent(); } + + @Test + public void shouldCreateUser(){ + + final User user = this.userService.checkForUser("shouldCreateUser@UserService.test", null); + + assertTrue(user.getId() != null); + } + + @Test + public void shouldReturnExistingUser(){ + final String email = "shouldReturnExistingUser@UserService.test"; + this.userService.save(new User("Tom","Smieszne",email,UserRoles.TEST_USER)); + final User user = this.userService.checkForUser(email, null); + + assertTrue(user.getName() != "Tom"); + } } From b897fce416a5fbcdaa18994a3b833f322da9d524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Wo=C5=BAniak?= Date: Thu, 3 Dec 2020 17:14:39 +0100 Subject: [PATCH 11/20] Fixed TokenController MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcin Woźniak --- .../java/com/plannaplan/controllers/TokenController.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/restservice/src/main/java/com/plannaplan/controllers/TokenController.java b/restservice/src/main/java/com/plannaplan/controllers/TokenController.java index 5bf40f7..ffe7143 100755 --- a/restservice/src/main/java/com/plannaplan/controllers/TokenController.java +++ b/restservice/src/main/java/com/plannaplan/controllers/TokenController.java @@ -4,7 +4,7 @@ import com.plannaplan.entities.User; import com.plannaplan.exceptions.UserNotFoundException; import com.plannaplan.security.cas.CasUserIdentity; import com.plannaplan.security.cas.CasValidationExcepiton; -import com.plannaplan.security.cas.DefaultUAMCasValidator; +import com.plannaplan.security.cas.CustomUAMCasValidator; import com.plannaplan.services.UserService; import org.springframework.beans.factory.annotation.Autowired; @@ -24,7 +24,7 @@ import io.swagger.annotations.ApiParam; @Api(tags = { "Token" }, value = "Token", description = "Enpoints to get authorization.") public class TokenController { - private final static String SERVICE_URL = "http://localhost:3000"; + private final static String SERVICE_URL = "https://wmi.plannaplan.pl"; @Autowired private UserService userService; @@ -32,8 +32,8 @@ public class TokenController { @GetMapping("/token") @ApiOperation(value = "Endpoint to access token required to call secured endpoints. In order to access token we need to provide access token comming from unviersity CAS system") public ResponseEntity getToken( - @RequestParam("ticket") @ApiParam(value = "Ticket get from CAS system. It should look like ST-1376572-wo41gty5R0JCZFKMMie2-cas.amu.edu.pl") final String ticket) { - final DefaultUAMCasValidator validator = new DefaultUAMCasValidator(SERVICE_URL, ticket); + @RequestParam("ticket") @ApiParam(value = "Ticket get from CAS system. It should look like ST-1376572-wo41gty5R0JCZFKMMie2-cas.amu.edu.psl") final String ticket) { + final CustomUAMCasValidator validator = new CustomUAMCasValidator(SERVICE_URL, ticket); try { final CasUserIdentity casUserIdentity = validator.validate(); From 6dfb99f253e2c3adea223c113f07f505a0149b0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Wo=C5=BAniak?= Date: Fri, 4 Dec 2020 15:42:28 +0100 Subject: [PATCH 12/20] Updated MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcin Woźniak --- restservice/src/main/java/com/plannaplan/App.java | 15 +-------------- .../plannaplan/controllers/TokenController.java | 4 +++- .../src/main/resources/application.properties | 4 +++- .../security/cas/CustomUAMCasValidatorTest.java | 8 +++++++- .../security/cas/DefaultUAMCasValidatorTest.java | 9 +++++++-- 5 files changed, 21 insertions(+), 19 deletions(-) diff --git a/restservice/src/main/java/com/plannaplan/App.java b/restservice/src/main/java/com/plannaplan/App.java index 33561b9..8d7580b 100755 --- a/restservice/src/main/java/com/plannaplan/App.java +++ b/restservice/src/main/java/com/plannaplan/App.java @@ -29,20 +29,7 @@ public class App { @EventListener(ApplicationReadyEvent.class) public void importData() { - User filip = new User(); - filip.setEmail("filizy@st.amu.edu.pl"); - filip.setName("Filip"); - filip.setSurname("Izydorczyk"); - filip.setRole(UserRoles.STUDENT); - this.userService.save(filip); - - User hub = new User(); - hub.setEmail("hubwrz1@st.amu.edu.pl"); - hub.setName("Hubert"); - hub.setSurname("Wrzesiński"); - hub.setRole(UserRoles.STUDENT); - this.userService.save(hub); - + User mac = new User(); mac.setEmail("macglo2@st.amu.edu.pl"); mac.setName("Maciej"); diff --git a/restservice/src/main/java/com/plannaplan/controllers/TokenController.java b/restservice/src/main/java/com/plannaplan/controllers/TokenController.java index ffe7143..8de674a 100755 --- a/restservice/src/main/java/com/plannaplan/controllers/TokenController.java +++ b/restservice/src/main/java/com/plannaplan/controllers/TokenController.java @@ -8,6 +8,7 @@ import com.plannaplan.security.cas.CustomUAMCasValidator; import com.plannaplan.services.UserService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.CrossOrigin; @@ -24,7 +25,8 @@ import io.swagger.annotations.ApiParam; @Api(tags = { "Token" }, value = "Token", description = "Enpoints to get authorization.") public class TokenController { - private final static String SERVICE_URL = "https://wmi.plannaplan.pl"; + @Value("${plannaplan.frontendUrl}") + private String SERVICE_URL; @Autowired private UserService userService; diff --git a/restservice/src/main/resources/application.properties b/restservice/src/main/resources/application.properties index 55e1449..4ec6d84 100755 --- a/restservice/src/main/resources/application.properties +++ b/restservice/src/main/resources/application.properties @@ -9,4 +9,6 @@ spring.jackson.serialization.fail-on-empty-beans=false spring.main.allow-bean-definition-overriding=true spring.jackson.default-property-inclusion = NON_NULL -server.port=1285 \ No newline at end of file +server.port=1285 + +plannaplan.frontendUrl = http://localhost:3000 \ No newline at end of file diff --git a/restservice/src/test/java/com/plannaplan/security/cas/CustomUAMCasValidatorTest.java b/restservice/src/test/java/com/plannaplan/security/cas/CustomUAMCasValidatorTest.java index 8069d0e..cab037e 100644 --- a/restservice/src/test/java/com/plannaplan/security/cas/CustomUAMCasValidatorTest.java +++ b/restservice/src/test/java/com/plannaplan/security/cas/CustomUAMCasValidatorTest.java @@ -1,11 +1,17 @@ package com.plannaplan.security.cas; import org.junit.Test; +import org.springframework.beans.factory.annotation.Value; public class CustomUAMCasValidatorTest { + + @Value("${plannaplan.frontendUrl}") + private String serviceUrl; + @Test public void shouldValidateWithDomain() { - CustomUAMCasValidator validator = new CustomUAMCasValidator("https://wmi.plannaplan.pl", "ST-54649-5x4h09vzUpEIyAGmf1sz-cas.amu.edu.pl"); + + CustomUAMCasValidator validator = new CustomUAMCasValidator(serviceUrl, "ST-54649-5x4h09vzUpEIyAGmf1sz-cas.amu.edu.pl"); validator.validate(); diff --git a/restservice/src/test/java/com/plannaplan/security/cas/DefaultUAMCasValidatorTest.java b/restservice/src/test/java/com/plannaplan/security/cas/DefaultUAMCasValidatorTest.java index 1373e93..bd5bd6f 100755 --- a/restservice/src/test/java/com/plannaplan/security/cas/DefaultUAMCasValidatorTest.java +++ b/restservice/src/test/java/com/plannaplan/security/cas/DefaultUAMCasValidatorTest.java @@ -4,15 +4,20 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.Ignore; import org.junit.Test; +import org.springframework.beans.factory.annotation.Value; public class DefaultUAMCasValidatorTest { + @Value("${plannaplan.frontendUrl}") + private String serviceUrl; + @Test @Ignore public void shouldValidateTicket() { // you need to privide fresh ticket to make this test pass that's why it is // marked as ignored - final DefaultUAMCasValidator validator = new DefaultUAMCasValidator("http://localhost:3000", + + final DefaultUAMCasValidator validator = new DefaultUAMCasValidator(serviceUrl, "ST-572267-cbgKrcJLd0tdCubeLqdW-cas.amu.edu.pl"); try { System.out.println(validator.validate()); @@ -24,7 +29,7 @@ public class DefaultUAMCasValidatorTest { @Test public void shouldNotValidateTicket() { - final DefaultUAMCasValidator validator = new DefaultUAMCasValidator("http://localhost:3000", "notticket"); + final DefaultUAMCasValidator validator = new DefaultUAMCasValidator(serviceUrl, "notticket"); try { assertTrue(validator.validate().getEmail().trim().equals("")); } catch (CasValidationExcepiton e) { From 616f782155041cbe1312175e0569311a34148d22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Wo=C5=BAniak?= Date: Fri, 4 Dec 2020 15:54:49 +0100 Subject: [PATCH 13/20] Updated MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcin Woźniak --- .../java/com/plannaplan/controllers/TokenController.java | 9 +++++++-- restservice/src/main/resources/application.properties | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/restservice/src/main/java/com/plannaplan/controllers/TokenController.java b/restservice/src/main/java/com/plannaplan/controllers/TokenController.java index 8de674a..77272e6 100755 --- a/restservice/src/main/java/com/plannaplan/controllers/TokenController.java +++ b/restservice/src/main/java/com/plannaplan/controllers/TokenController.java @@ -4,7 +4,9 @@ import com.plannaplan.entities.User; import com.plannaplan.exceptions.UserNotFoundException; import com.plannaplan.security.cas.CasUserIdentity; import com.plannaplan.security.cas.CasValidationExcepiton; +import com.plannaplan.security.cas.CasValidator; import com.plannaplan.security.cas.CustomUAMCasValidator; +import com.plannaplan.security.cas.DefaultUAMCasValidator; import com.plannaplan.services.UserService; import org.springframework.beans.factory.annotation.Autowired; @@ -26,7 +28,10 @@ import io.swagger.annotations.ApiParam; public class TokenController { @Value("${plannaplan.frontendUrl}") - private String SERVICE_URL; + private String serviceUrl; + + @Value("${plannaplan.dev}") + private boolean isDev; @Autowired private UserService userService; @@ -35,7 +40,7 @@ public class TokenController { @ApiOperation(value = "Endpoint to access token required to call secured endpoints. In order to access token we need to provide access token comming from unviersity CAS system") public ResponseEntity getToken( @RequestParam("ticket") @ApiParam(value = "Ticket get from CAS system. It should look like ST-1376572-wo41gty5R0JCZFKMMie2-cas.amu.edu.psl") final String ticket) { - final CustomUAMCasValidator validator = new CustomUAMCasValidator(SERVICE_URL, ticket); + final CasValidator validator = isDev ? new DefaultUAMCasValidator(serviceUrl, ticket) : new CustomUAMCasValidator(serviceUrl, ticket); try { final CasUserIdentity casUserIdentity = validator.validate(); diff --git a/restservice/src/main/resources/application.properties b/restservice/src/main/resources/application.properties index 4ec6d84..27bae32 100755 --- a/restservice/src/main/resources/application.properties +++ b/restservice/src/main/resources/application.properties @@ -11,4 +11,5 @@ spring.jackson.default-property-inclusion = NON_NULL server.port=1285 +plannaplan.dev = true plannaplan.frontendUrl = http://localhost:3000 \ No newline at end of file From c70cac58b2d5d4cd18d71740b37da604fb88257c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Wo=C5=BAniak?= Date: Fri, 4 Dec 2020 16:16:10 +0100 Subject: [PATCH 14/20] Updated MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcin Woźniak --- restservice/pom.xml | 18 +++++++++++++++++ .../main/resources/application-dev.properties | 13 ++++++++++++ .../resources/application-prod.properties | 20 +++++++++++++++++++ .../src/main/resources/application.properties | 16 +-------------- 4 files changed, 52 insertions(+), 15 deletions(-) create mode 100644 restservice/src/main/resources/application-dev.properties create mode 100644 restservice/src/main/resources/application-prod.properties diff --git a/restservice/pom.xml b/restservice/pom.xml index 773be7a..7c82417 100755 --- a/restservice/pom.xml +++ b/restservice/pom.xml @@ -16,6 +16,24 @@ http://www.example.com + + + dev + + dev + + + true + + + + prod + + prod + + + + UTF-8 14 diff --git a/restservice/src/main/resources/application-dev.properties b/restservice/src/main/resources/application-dev.properties new file mode 100644 index 0000000..2a28294 --- /dev/null +++ b/restservice/src/main/resources/application-dev.properties @@ -0,0 +1,13 @@ +spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect +spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=yes&characterEncoding=UTF-8 +spring.datasource.username=root +spring.datasource.password=example +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.jpa.open-in-view=true +spring.jpa.hibernate.ddl-auto=create-drop +spring.jackson.serialization.fail-on-empty-beans=false +spring.main.allow-bean-definition-overriding=true +spring.jackson.default-property-inclusion = NON_NULL +server.port=1285 +plannaplan.dev = true +plannaplan.frontendUrl = http://localhost:3000 \ No newline at end of file diff --git a/restservice/src/main/resources/application-prod.properties b/restservice/src/main/resources/application-prod.properties new file mode 100644 index 0000000..8ea6d44 --- /dev/null +++ b/restservice/src/main/resources/application-prod.properties @@ -0,0 +1,20 @@ +spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect +spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=yes&characterEncoding=UTF-8 +spring.datasource.username=root +spring.datasource.password=example +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.jpa.open-in-view=true +spring.jpa.hibernate.ddl-auto=create-drop +spring.jackson.serialization.fail-on-empty-beans=false +spring.main.allow-bean-definition-overriding=true +spring.jackson.default-property-inclusion = NON_NULL + +server.port=1285 + +plannaplan.dev = false +plannaplan.frontendUrl = https://wmi.plannaplan.pl +security.require-ssl=true +server.ssl.key-store=/keys/keystore.p12 +server.ssl.key-store-password= +server.ssl.keyStoreType=PKCS12 +server.ssl.keyAlias=tomcat \ No newline at end of file diff --git a/restservice/src/main/resources/application.properties b/restservice/src/main/resources/application.properties index 27bae32..257b306 100755 --- a/restservice/src/main/resources/application.properties +++ b/restservice/src/main/resources/application.properties @@ -1,15 +1 @@ -spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect -spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=yes&characterEncoding=UTF-8 -spring.datasource.username=root -spring.datasource.password=example -spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver -spring.jpa.open-in-view=true -spring.jpa.hibernate.ddl-auto=create-drop -spring.jackson.serialization.fail-on-empty-beans=false -spring.main.allow-bean-definition-overriding=true -spring.jackson.default-property-inclusion = NON_NULL - -server.port=1285 - -plannaplan.dev = true -plannaplan.frontendUrl = http://localhost:3000 \ No newline at end of file +spring.profiles.active=dev \ No newline at end of file From 2dd1f70eeec137162a476e31d9345069977130e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Wo=C5=BAniak?= Date: Fri, 4 Dec 2020 17:15:08 +0100 Subject: [PATCH 15/20] Updated --- restservice/src/main/resources/application-prod.properties | 6 +++--- restservice/src/main/resources/application.properties | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/restservice/src/main/resources/application-prod.properties b/restservice/src/main/resources/application-prod.properties index 8ea6d44..b944224 100644 --- a/restservice/src/main/resources/application-prod.properties +++ b/restservice/src/main/resources/application-prod.properties @@ -1,7 +1,7 @@ spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect -spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=yes&characterEncoding=UTF-8 -spring.datasource.username=root -spring.datasource.password=example +spring.datasource.url = jdbc:mysql://${PLANNAPLAN_MYSQL_DB_HOST}:${PLANNAPLAN_MYSQL_DB_PORT}/${PLANNAPLAN_MYSQL_DB}?useUnicode=yes&characterEncoding=UTF-8 +spring.datasource.username = ${PLANNAPLAN_MYSQL_DB_USERNAME} +spring.datasource.password = ${PLANNAPLAN_MYSQL_DB_PASSWORD} spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.jpa.open-in-view=true spring.jpa.hibernate.ddl-auto=create-drop diff --git a/restservice/src/main/resources/application.properties b/restservice/src/main/resources/application.properties index 257b306..a015c68 100755 --- a/restservice/src/main/resources/application.properties +++ b/restservice/src/main/resources/application.properties @@ -1 +1 @@ -spring.profiles.active=dev \ No newline at end of file +spring.profiles.active=prod \ No newline at end of file From cbe8f96700e427488a0f6ff56f20c6e291c78373 Mon Sep 17 00:00:00 2001 From: BuildTools Date: Sat, 5 Dec 2020 15:10:03 +0100 Subject: [PATCH 16/20] Getch chanmges --- .../main/java/com/plannaplan/security/cas/CasUserIdentity.java | 0 .../src/main/java/com/plannaplan/security/cas/CasValidator.java | 0 .../java/com/plannaplan/security/cas/CustomUAMCasValidator.java | 0 restservice/src/main/resources/application-dev.properties | 0 restservice/src/main/resources/application-prod.properties | 0 .../com/plannaplan/security/cas/CustomUAMCasValidatorTest.java | 0 6 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 restservice/src/main/java/com/plannaplan/security/cas/CasUserIdentity.java mode change 100644 => 100755 restservice/src/main/java/com/plannaplan/security/cas/CasValidator.java mode change 100644 => 100755 restservice/src/main/java/com/plannaplan/security/cas/CustomUAMCasValidator.java mode change 100644 => 100755 restservice/src/main/resources/application-dev.properties mode change 100644 => 100755 restservice/src/main/resources/application-prod.properties mode change 100644 => 100755 restservice/src/test/java/com/plannaplan/security/cas/CustomUAMCasValidatorTest.java diff --git a/restservice/src/main/java/com/plannaplan/security/cas/CasUserIdentity.java b/restservice/src/main/java/com/plannaplan/security/cas/CasUserIdentity.java old mode 100644 new mode 100755 diff --git a/restservice/src/main/java/com/plannaplan/security/cas/CasValidator.java b/restservice/src/main/java/com/plannaplan/security/cas/CasValidator.java old mode 100644 new mode 100755 diff --git a/restservice/src/main/java/com/plannaplan/security/cas/CustomUAMCasValidator.java b/restservice/src/main/java/com/plannaplan/security/cas/CustomUAMCasValidator.java old mode 100644 new mode 100755 diff --git a/restservice/src/main/resources/application-dev.properties b/restservice/src/main/resources/application-dev.properties old mode 100644 new mode 100755 diff --git a/restservice/src/main/resources/application-prod.properties b/restservice/src/main/resources/application-prod.properties old mode 100644 new mode 100755 diff --git a/restservice/src/test/java/com/plannaplan/security/cas/CustomUAMCasValidatorTest.java b/restservice/src/test/java/com/plannaplan/security/cas/CustomUAMCasValidatorTest.java old mode 100644 new mode 100755 From 751d4b1744e9d4187bc8b88f15e2593a43da96a3 Mon Sep 17 00:00:00 2001 From: BuildTools Date: Sat, 5 Dec 2020 15:59:47 +0100 Subject: [PATCH 17/20] Readme update --- README.md | 47 +++++++++++-------- .../resources/application-prod.properties | 2 +- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 0be4755..4947eff 100755 --- a/README.md +++ b/README.md @@ -3,20 +3,7 @@ Zeby wystartowac aplikacje backendu najpierw nalezy postawic testowa baze danych na naszym komputerze za pomoca dockera. Jesli raz juz go odpalimy przy nastepnym razem bardzo mozliwe, ze wlaczy sie sam. AAby sprawdzic czy docker jesty wystartowany mozna uzyc `docker ps` ``` -docker-compose -f stack.yml up -``` - -Nastepnie w `restservice/src/main/resources/application.properties` nalezy podac ip naszego kontenera. - -``` -spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect -spring.datasource.url=jdbc:mysql://localhost:3306/test -spring.datasource.username=root -spring.datasource.password=example -spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver -spring.jpa.hibernate.ddl-auto=create - -server.port=1285 +docker-compose -f stack.yml up -d ``` Następnym krokiem jest odpalenie poniższych komend w terminalu. @@ -26,7 +13,7 @@ cd restservice mvn spring-boot:run ``` -## Token +## Token obtaining Żeby tesotwać API wpełni potrzebny nam jest token który otrzymujemy na podstawie ticketa z systemu autoryzacyjnego **CAS**. Z tego powodu system autoryzacji działa inaczej niż w "książkowych" restowych aplikacjach i np Postman za nas jej nie dokona. Musimy mu podać już uzyskany token. Aby łatwo go uzyskać odpal skrypt @@ -36,13 +23,23 @@ python gettoken.py Na koniec w przęglądarce dostaniesz w odpowiedzi token. W samym pliku można zmienić porty aplikacji jeśli to potrzebne. -## Api docs +## Profiles -Żeby zobaczyć dokumentację api trzeba wejść w przeglądarce na `http://localhost:1285/swagger-ui.html` po odpaleniu aplikacji. +W aplikacji posiadamy dwa profile. `dev` i `prod`. **Dev** używamy do testowania aplikacji lokalnie. **Pord** służy do stworzenia builda na produkcję. +Profil wybieramy w pliku `restservice/src/main/resources/application.properties` wpisując odpowiednią nazwę -### Nazewnictwo odpowiedzi +``` +spring.profiles.active=prod +``` -Każdą odp zaczynamy od modelu, który opisuje np. `Courses` a kończymy na `Response`. Między tymi dwoma członami możemy dodawać modyfikatory opisujące dokładniej odpowiedź np. `Default`. W ten sposób możemy otrzymać nazwę `CoursesDefaultResponse.java` +Jeżeli chcemy zmienić jakieś opcję dla pordukcji to robimy to w tym sammym katalogi w pliku `application-prod.properties` i dla dev analogicznie w `application-dev.properties`. +W paczce dla proda w protpertiesach poufne dane odczytywane są ze zmiennych środowiskowych systemu na którym odpalana jest aplikacja. Ustawić trzeba następujące zmienne: + +- PLANNAPLAN_MYSQL_DB_HOST - host bazy danych np `localhost` +- PLANNAPLAN_MYSQL_DB_PORT - port na którym działa baza +- PLANNAPLAN_MYSQL_DB - nazwa bazy dancyh. W profilu **dev** jest to np test +- PLANNAPLAN_MYSQL_DB_USERNAME - nazwa użytkownika bazy +- PLANNAPLAN_MYSQL_DB_PASSWORD - hasło użytkownika bazy ## Packaging @@ -57,12 +54,22 @@ mvn clean package spring-boot:repackage Utworzony zostanie jar w `restservice/target/restservice-1.0-SNAPSHOT.jar`. Oczywiscie zeby jar zadzialal kontenery dockerowe musza byc odpalone (lub baza danych na serwerze jesli zmienialismy propertisy z localhost) -## Generowanie dokumentacji - javadoc +## Generowanie dokumentacji + +### Javadocs ```bash mvn javadoc:javadoc ``` +### Api docs + +Żeby zobaczyć dokumentację api trzeba wejść w przeglądarce na `http://localhost:1285/swagger-ui.html` po odpaleniu aplikacji. + +#### Nazewnictwo odpowiedzi + +Każdą odp zaczynamy od modelu, który opisuje np. `Courses` a kończymy na `Response`. Między tymi dwoma członami możemy dodawać modyfikatory opisujące dokładniej odpowiedź np. `Default`. W ten sposób możemy otrzymać nazwę `CoursesDefaultResponse.java` + ## Troubleshooting Spring chyba cacheuje jakies dane dotyczace polaczenia wiec jesli spring wywali Ci blad `Connection Refused`, a wiesz, ze ta baza stoi na podanym ip i porcie to sprobuj diff --git a/restservice/src/main/resources/application-prod.properties b/restservice/src/main/resources/application-prod.properties index b944224..b8e7a83 100755 --- a/restservice/src/main/resources/application-prod.properties +++ b/restservice/src/main/resources/application-prod.properties @@ -4,7 +4,7 @@ spring.datasource.username = ${PLANNAPLAN_MYSQL_DB_USERNAME} spring.datasource.password = ${PLANNAPLAN_MYSQL_DB_PASSWORD} spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.jpa.open-in-view=true -spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.hibernate.ddl-auto=update spring.jackson.serialization.fail-on-empty-beans=false spring.main.allow-bean-definition-overriding=true spring.jackson.default-property-inclusion = NON_NULL From 495098eeed87ad13e918407f3552211657a18043 Mon Sep 17 00:00:00 2001 From: BuildTools Date: Sat, 5 Dec 2020 16:28:02 +0100 Subject: [PATCH 18/20] Added init test data on dev profile --- .../src/main/java/com/plannaplan/App.java | 119 ++++++++++++++++-- .../src/main/java/com/plannaplan/Logo.java | 26 +++- restservice/src/main/resources/Zajecia.xlsx | Bin 0 -> 68931 bytes .../src/main/resources/application.properties | 2 +- 4 files changed, 138 insertions(+), 9 deletions(-) create mode 100755 restservice/src/main/resources/Zajecia.xlsx diff --git a/restservice/src/main/java/com/plannaplan/App.java b/restservice/src/main/java/com/plannaplan/App.java index 8d7580b..d44ab7a 100755 --- a/restservice/src/main/java/com/plannaplan/App.java +++ b/restservice/src/main/java/com/plannaplan/App.java @@ -1,24 +1,35 @@ package com.plannaplan; +import java.io.InputStream; + +import com.plannaplan.models.ConfigData; import com.plannaplan.entities.User; import com.plannaplan.services.UserService; import com.plannaplan.types.UserRoles; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; +import com.plannaplan.services.ConfiguratorService; @SpringBootApplication public class App { public final static String API_VERSION = "v1"; + @Autowired + private ConfiguratorService contrl; + @Autowired UserService userService; + @Value("${plannaplan.dev}") + private boolean isDev; + public static void main(String[] args) { Logo logo = new Logo("beta"); System.out.println(logo.getLogo()); @@ -29,12 +40,106 @@ public class App { @EventListener(ApplicationReadyEvent.class) public void importData() { - - User mac = new User(); - mac.setEmail("macglo2@st.amu.edu.pl"); - mac.setName("Maciej"); - mac.setSurname("Głowacki"); - mac.setRole(UserRoles.STUDENT); - this.userService.save(mac); + System.out.println(Logo.getInitInfo(isDev)); + + if (this.isDev) { + InputStream inputStream = getClass().getClassLoader().getResourceAsStream("Zajecia.xlsx"); + ConfigData data = new ConfigData(null, null, inputStream); + this.contrl.config(data); + + User filip = new User(); + filip.setEmail("filizy@st.amu.edu.pl"); + filip.setName("Filip"); + filip.setSurname("Izydorczyk"); + filip.setRole(UserRoles.ADMIN); + this.userService.save(filip); + + User hub = new User(); + hub.setEmail("hubwrz1@st.amu.edu.pl"); + hub.setName("Hubert"); + hub.setSurname("Wrzesiński"); + hub.setRole(UserRoles.DEANERY); + this.userService.save(hub); + + User mac = new User(); + mac.setEmail("macglo2@st.amu.edu.pl"); + mac.setName("Maciej"); + mac.setSurname("Głowacki"); + mac.setRole(UserRoles.STUDENT); + this.userService.save(mac); + + User mar = new User(); + mar.setEmail("marwoz16@st.amu.edu.pl"); + mar.setName("Marcin"); + mar.setSurname("Woźniak"); + mar.setRole(UserRoles.ADMIN); + this.userService.save(mar); + + User newuser = new User(); + newuser.setEmail("tommy@st.amu.edu.pl"); + newuser.setName("Tomek"); + newuser.setSurname("Atomek"); + newuser.setRole(UserRoles.STUDENT); + this.userService.save(newuser); + + newuser = new User(); + newuser.setEmail("robercik@st.amu.edu.pl"); + newuser.setName("Robert"); + newuser.setSurname("Głowacki"); + newuser.setRole(UserRoles.STUDENT); + this.userService.save(newuser); + + newuser = new User(); + newuser.setEmail("mewa@st.amu.edu.pl"); + newuser.setName("Poznanska"); + newuser.setSurname("Mewa"); + newuser.setRole(UserRoles.STUDENT); + this.userService.save(newuser); + + newuser = new User(); + newuser.setEmail("tkul2@st.amu.edu.pl"); + newuser.setName("Tomasz"); + newuser.setSurname("Kula"); + newuser.setRole(UserRoles.STUDENT); + this.userService.save(newuser); + + newuser = new User(); + newuser.setEmail("annana@st.amu.edu.pl"); + newuser.setName("Anna"); + newuser.setSurname("Na"); + newuser.setRole(UserRoles.STUDENT); + this.userService.save(newuser); + + newuser = new User(); + newuser.setEmail("mnart@st.amu.edu.pl"); + newuser.setName("Marta"); + newuser.setSurname("Narta"); + newuser.setRole(UserRoles.STUDENT); + this.userService.save(newuser); + + newuser = new User(); + newuser.setEmail("zmineniane@st.amu.edu.pl"); + newuser.setName("Tutaj"); + newuser.setSurname("Koncza"); + newuser.setRole(UserRoles.STUDENT); + this.userService.save(newuser); + + newuser = new User(); + newuser.setEmail("mi@st.amu.edu.pl"); + newuser.setName("Mi"); + newuser.setSurname("Sie"); + newuser.setRole(UserRoles.STUDENT); + this.userService.save(newuser); + + newuser = new User(); + newuser.setEmail("pms@st.amu.edu.pl"); + newuser.setName("Pomysly"); + newuser.setSurname("Sad"); + newuser.setRole(UserRoles.STUDENT); + this.userService.save(newuser); + } + + System.out.println(Logo.getStartedInfo(isDev)); + } } diff --git a/restservice/src/main/java/com/plannaplan/Logo.java b/restservice/src/main/java/com/plannaplan/Logo.java index 048744a..3573082 100755 --- a/restservice/src/main/java/com/plannaplan/Logo.java +++ b/restservice/src/main/java/com/plannaplan/Logo.java @@ -1,11 +1,14 @@ package com.plannaplan; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + public class Logo { public static final String ANSI_RESET = "\u001B[0m"; public static final String ANSI_YELLOW = "\u001B[33m"; public static final String ANSI_BLUE = "\u001B[34m"; - public static final String ANSI_BLACK = "\u001B[30m"; + public static final String ANSI_BLACK = "\u001B[37m"; private String version; public Logo(String version){ @@ -30,4 +33,25 @@ public class Logo { ANSI_RESET; return result; } + + + public static String getInitInfo(boolean isDev){ + DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"); + LocalDateTime now = LocalDateTime.now(); + + if(isDev){ + return ANSI_BLACK + dtf.format(now) + ANSI_YELLOW + " plannaplan" + ANSI_RESET + " initializing [" +ANSI_BLUE + "dev" + ANSI_RESET +"]"; + } + return ANSI_BLACK + dtf.format(now) + ANSI_YELLOW + " plannaplan" + ANSI_RESET + " initializing [" +ANSI_BLUE + "prod" + ANSI_RESET +"]"; + } + + public static String getStartedInfo(boolean isDev){ + DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"); + LocalDateTime now = LocalDateTime.now(); + + if(isDev){ + return ANSI_BLACK + dtf.format(now) +ANSI_YELLOW + " plannaplan" + ANSI_RESET + " started [" +ANSI_BLUE + "dev" + ANSI_RESET +"]"; + } + return ANSI_BLACK + dtf.format(now) + ANSI_YELLOW + " plannaplan" + ANSI_RESET + " started [" +ANSI_BLUE + "prod" + ANSI_RESET +"]"; + } } \ No newline at end of file diff --git a/restservice/src/main/resources/Zajecia.xlsx b/restservice/src/main/resources/Zajecia.xlsx new file mode 100755 index 0000000000000000000000000000000000000000..57c8e9c1eda18f3f5b4f55f0c8f1f142d5845f40 GIT binary patch literal 68931 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�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 Date: Sat, 5 Dec 2020 16:38:22 +0100 Subject: [PATCH 19/20] Added metadata to plannaplan propterties + we should create sssl bean instead inerting properties --- .../additional-spring-configuration-metadata.json | 14 ++++++++++++++ .../src/main/resources/application-dev.properties | 2 +- .../src/main/resources/application-prod.properties | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100755 restservice/src/main/resources/META-INF/additional-spring-configuration-metadata.json diff --git a/restservice/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/restservice/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100755 index 0000000..e5a729a --- /dev/null +++ b/restservice/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,14 @@ +{ + "properties": [ + { + "name": "plannaplan.dev", + "type": "java.lang.String", + "description": "Should application create resorces for development or not" + }, + { + "name": "plannaplan.frontendUrl", + "type": "java.lang.String", + "description": "Url where frontend app is located" + } + ] +} diff --git a/restservice/src/main/resources/application-dev.properties b/restservice/src/main/resources/application-dev.properties index 2a28294..7b49197 100755 --- a/restservice/src/main/resources/application-dev.properties +++ b/restservice/src/main/resources/application-dev.properties @@ -10,4 +10,4 @@ spring.main.allow-bean-definition-overriding=true spring.jackson.default-property-inclusion = NON_NULL server.port=1285 plannaplan.dev = true -plannaplan.frontendUrl = http://localhost:3000 \ No newline at end of file +plannaplan.frontendUrl= http://localhost:3000 \ No newline at end of file diff --git a/restservice/src/main/resources/application-prod.properties b/restservice/src/main/resources/application-prod.properties index b8e7a83..46e3b11 100755 --- a/restservice/src/main/resources/application-prod.properties +++ b/restservice/src/main/resources/application-prod.properties @@ -12,7 +12,7 @@ spring.jackson.default-property-inclusion = NON_NULL server.port=1285 plannaplan.dev = false -plannaplan.frontendUrl = https://wmi.plannaplan.pl +plannaplan.frontendUrl= https://wmi.plannaplan.pl security.require-ssl=true server.ssl.key-store=/keys/keystore.p12 server.ssl.key-store-password= From 2037f0bf07141cf7550560a68256b70b0b8346a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Wo=C5=BAniak?= Date: Mon, 7 Dec 2020 21:09:54 +0100 Subject: [PATCH 20/20] Added MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcin Woźniak --- README.md | 2 +- .../src/main/java/com/plannaplan/App.java | 28 ------------------- 2 files changed, 1 insertion(+), 29 deletions(-) diff --git a/README.md b/README.md index 4947eff..281203b 100755 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ mvn javadoc:javadoc #### Nazewnictwo odpowiedzi -Każdą odp zaczynamy od modelu, który opisuje np. `Courses` a kończymy na `Response`. Między tymi dwoma członami możemy dodawać modyfikatory opisujące dokładniej odpowiedź np. `Default`. W ten sposób możemy otrzymać nazwę `CoursesDefaultResponse.java` +Każdą odpowiedź zaczynamy od modelu, który opisuje np. `Courses` a kończymy na `Response`. Między tymi dwoma członami możemy dodawać modyfikatory opisujące dokładniej odpowiedź np. `Default`. W ten sposób możemy otrzymać nazwę `CoursesDefaultResponse.java` ## Troubleshooting diff --git a/restservice/src/main/java/com/plannaplan/App.java b/restservice/src/main/java/com/plannaplan/App.java index d44ab7a..1e114ba 100755 --- a/restservice/src/main/java/com/plannaplan/App.java +++ b/restservice/src/main/java/com/plannaplan/App.java @@ -47,34 +47,6 @@ public class App { ConfigData data = new ConfigData(null, null, inputStream); this.contrl.config(data); - User filip = new User(); - filip.setEmail("filizy@st.amu.edu.pl"); - filip.setName("Filip"); - filip.setSurname("Izydorczyk"); - filip.setRole(UserRoles.ADMIN); - this.userService.save(filip); - - User hub = new User(); - hub.setEmail("hubwrz1@st.amu.edu.pl"); - hub.setName("Hubert"); - hub.setSurname("Wrzesiński"); - hub.setRole(UserRoles.DEANERY); - this.userService.save(hub); - - User mac = new User(); - mac.setEmail("macglo2@st.amu.edu.pl"); - mac.setName("Maciej"); - mac.setSurname("Głowacki"); - mac.setRole(UserRoles.STUDENT); - this.userService.save(mac); - - User mar = new User(); - mar.setEmail("marwoz16@st.amu.edu.pl"); - mar.setName("Marcin"); - mar.setSurname("Woźniak"); - mar.setRole(UserRoles.ADMIN); - this.userService.save(mar); - User newuser = new User(); newuser.setEmail("tommy@st.amu.edu.pl"); newuser.setName("Tomek");