diff --git a/README.md b/README.md index dc2b0cb977..22533e79fc 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,12 @@ +## Working with the web GUI (Wicket) +The web GUI is protected by OAuth 2 log in. Run the Docker Compose containers with +`docker compose up` to start the authorization server to be able to log in. + +If you run SciPro in development mode (DEV profile) you will be able to log in +as the "default" OAuth 2 user populated in the upper form. If you have other +data in your database you will have to use the lower form and specify a valid +username in the principal field. + ## Working with the API The API is protected by OAuth 2 acting as a [resource server](https://www.oauth.com/oauth2-servers/the-resource-server/) verifying tokens using [token introspection](https://datatracker.ietf.org/doc/html/rfc7662). @@ -19,9 +28,14 @@ to format all Java code. To reformat the code run Yes it's a mouthful but unfortunately the [prettier-maven-plugin](https://github.com/HubSpot/prettier-maven-plugin) does not work due to an [outstanding issue](https://github.com/HubSpot/prettier-maven-plugin/issues/79). -An easier way to reformat code is to set IntelliJ to do it on save. Go to -`Settings -> Language & Frameworks -> JavaScript -> Prettier` and then check +The formatting is validated by CI, but you should do it beforehand with a simple `./mvnw verify -pl .`. + +### Making IntelliJ format for you +For this to work you also need to have [Node.js](https://nodejs.org) +installed and configured under `Settings -> Language & Frameworks -> Node.js` +and the file you're saving *must* be able to compile otherwise no formatting +can be performed. + +Go to `Settings -> Language & Frameworks -> JavaScript -> Prettier` and then check `Automatic Prettier Configuration`, set `Run for files` to `**/*.{java}`, and finally check `Run on save`. - -The formatting is validated by CI, but you should do it beforehand with a simple `./mvnw verify -pl .`. diff --git a/core/pom.xml b/core/pom.xml index a3c2a1a1e9..e34cb44596 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -86,8 +86,23 @@ <scope>test</scope> </dependency> <dependency> - <groupId>org.hsqldb</groupId> - <artifactId>hsqldb</artifactId> + <groupId>org.testcontainers</groupId> + <artifactId>junit-jupiter</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.testcontainers</groupId> + <artifactId>mariadb</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mariadb.jdbc</groupId> + <artifactId>mariadb-java-client</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.flywaydb</groupId> + <artifactId>flyway-mysql</artifactId> <scope>test</scope> </dependency> diff --git a/core/src/main/java/se/su/dsv/scipro/CoreConfig.java b/core/src/main/java/se/su/dsv/scipro/CoreConfig.java index 2dc8f54929..43c4d7ed10 100644 --- a/core/src/main/java/se/su/dsv/scipro/CoreConfig.java +++ b/core/src/main/java/se/su/dsv/scipro/CoreConfig.java @@ -96,6 +96,7 @@ import se.su.dsv.scipro.match.TargetRepository; import se.su.dsv.scipro.match.TargetServiceImpl; import se.su.dsv.scipro.milestones.MilestoneActivityTemplateRepository; import se.su.dsv.scipro.milestones.service.ActivateCompletedMilestonesOnNewProjects; +import se.su.dsv.scipro.milestones.service.MilestoneActivator; import se.su.dsv.scipro.milestones.service.MilestoneActivityTemplateService; import se.su.dsv.scipro.milestones.service.impl.MilestoneActivityTemplateServiceImpl; import se.su.dsv.scipro.milestones.service.impl.MilestonePhaseTemplateServiceImpl; @@ -1129,4 +1130,21 @@ public class CoreConfig { public GroupFacadeImpl groupFacade() { return new GroupFacadeImpl(); } + + @Bean + public MilestoneActivator milestoneActivator( + EventBus eventBus, + MilestoneServiceImpl milestoneService, + MilestoneActivityTemplateService milestoneActivityTemplateService, + FinalSeminarService finalSeminarService, + NotificationController notificationController + ) { + return new MilestoneActivator( + milestoneActivityTemplateService, + milestoneService, + eventBus, + finalSeminarService, + notificationController + ); + } } diff --git a/core/src/main/java/se/su/dsv/scipro/DataInitializer.java b/core/src/main/java/se/su/dsv/scipro/DataInitializer.java index aee6d0b488..7ab805cd7a 100644 --- a/core/src/main/java/se/su/dsv/scipro/DataInitializer.java +++ b/core/src/main/java/se/su/dsv/scipro/DataInitializer.java @@ -206,6 +206,10 @@ public class DataInitializer implements Lifecycle { admin.addRole(Roles.SYSADMIN); createBeta(admin); passwordService.updatePassword(admin, "aey7ru8aefei0jaW2wo9eX8EiShi0aan"); + Username defaultOAuth2Principal = new Username(); + defaultOAuth2Principal.setUsername("dev@localhost"); + defaultOAuth2Principal.setUser(admin); + save(defaultOAuth2Principal); } private void createBeta(User user) { diff --git a/core/src/main/java/se/su/dsv/scipro/report/GradeCalculator.java b/core/src/main/java/se/su/dsv/scipro/report/GradeCalculator.java index 55cd0c3343..67abd527b3 100644 --- a/core/src/main/java/se/su/dsv/scipro/report/GradeCalculator.java +++ b/core/src/main/java/se/su/dsv/scipro/report/GradeCalculator.java @@ -1,8 +1,6 @@ package se.su.dsv.scipro.report; -import java.io.Serializable; - -public interface GradeCalculator extends Serializable { +public interface GradeCalculator { GradingReport.Grade getGrade(GradingReport gradingReport); long getPoints(GradingReport gradingReport); diff --git a/core/src/main/java/se/su/dsv/scipro/system/AuthenticationContext.java b/core/src/main/java/se/su/dsv/scipro/system/AuthenticationContext.java new file mode 100644 index 0000000000..9f46604358 --- /dev/null +++ b/core/src/main/java/se/su/dsv/scipro/system/AuthenticationContext.java @@ -0,0 +1,15 @@ +package se.su.dsv.scipro.system; + +/** + * Information about the current authentication context. + * <p> + * The difference between this and {@link CurrentUser} is that a user can be + * authenticated without being a user in the system. This can happen when a + * user logs in for the first time via SSO. The {@link #set(User)} method can + * be used if the user can be imported based on the {@link #getPrincipalName()}. + */ +public interface AuthenticationContext extends CurrentUser { + void set(User user); + + String getPrincipalName(); +} diff --git a/core/src/main/resources/META-INF/persistence.xml b/core/src/main/resources/META-INF/persistence.xml index fc5f0a5977..902cc7aeae 100755 --- a/core/src/main/resources/META-INF/persistence.xml +++ b/core/src/main/resources/META-INF/persistence.xml @@ -4,9 +4,6 @@ xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd" version="3.0"> - <!-- NOTE THAT THERE ARE TWO PERSISTENCE UNITS, one default and one test - used for either running or unit-tests --> - <!-- A JPA Persistence Unit --> <persistence-unit name="defaultPersistenceUnit" transaction-type="RESOURCE_LOCAL"> @@ -17,13 +14,4 @@ </properties> </persistence-unit> - <!-- A JPA Persistence Unit used for tests --> - <persistence-unit name="testPersistenceUnit" - transaction-type="RESOURCE_LOCAL"> - <properties> - <property name="jakarta.persistence.jdbc.driver" value="org.hsqldb.jdbc.JDBCDriver"/> - <property name="jakarta.persistence.jdbc.url" value="jdbc:hsqldb:mem:test"/> - <property name="hibernate.hbm2ddl.auto" value="create"/> - </properties> - </persistence-unit> </persistence> diff --git a/core/src/test/java/se/su/dsv/scipro/finalseminar/FinalSeminarSchedulingTest.java b/core/src/test/java/se/su/dsv/scipro/finalseminar/FinalSeminarSchedulingTest.java index 23ebd8113c..cca047aed5 100644 --- a/core/src/test/java/se/su/dsv/scipro/finalseminar/FinalSeminarSchedulingTest.java +++ b/core/src/test/java/se/su/dsv/scipro/finalseminar/FinalSeminarSchedulingTest.java @@ -197,6 +197,7 @@ public class FinalSeminarSchedulingTest extends IntegrationTest { NonWorkDayPeriod nonWorkDayPeriod = new NonWorkDayPeriod(); nonWorkDayPeriod.setStartDate(date); nonWorkDayPeriod.setEndDate(date); + nonWorkDayPeriod.setComment("test non work day"); save(nonWorkDayPeriod); } diff --git a/core/src/test/java/se/su/dsv/scipro/peer/CommentThreadServiceImplTest.java b/core/src/test/java/se/su/dsv/scipro/peer/CommentThreadServiceImplTest.java index cd037f3ee5..625162f167 100644 --- a/core/src/test/java/se/su/dsv/scipro/peer/CommentThreadServiceImplTest.java +++ b/core/src/test/java/se/su/dsv/scipro/peer/CommentThreadServiceImplTest.java @@ -12,6 +12,7 @@ import se.su.dsv.scipro.file.FileDescription; import se.su.dsv.scipro.file.FileReference; import se.su.dsv.scipro.project.Project; import se.su.dsv.scipro.system.DegreeType; +import se.su.dsv.scipro.system.Language; import se.su.dsv.scipro.system.ProjectType; import se.su.dsv.scipro.system.User; import se.su.dsv.scipro.test.Dates; @@ -66,6 +67,7 @@ public class CommentThreadServiceImplTest extends IntegrationTest { PeerRequest peerRequest = new PeerRequest(); peerRequest.setProject(project); peerRequest.setRequester(createUser()); + peerRequest.setLanguage(Language.ENGLISH); final FileDescription fileDescription = save(new FileDescription()); final FileReference fileReference = new FileReference(); fileReference.setFileDescription(fileDescription); diff --git a/core/src/test/java/se/su/dsv/scipro/peer/PeerRequestServiceImplTest.java b/core/src/test/java/se/su/dsv/scipro/peer/PeerRequestServiceImplTest.java index 215e6c5f45..fc98ba6f7b 100644 --- a/core/src/test/java/se/su/dsv/scipro/peer/PeerRequestServiceImplTest.java +++ b/core/src/test/java/se/su/dsv/scipro/peer/PeerRequestServiceImplTest.java @@ -13,6 +13,7 @@ import se.su.dsv.scipro.file.FileDescription; import se.su.dsv.scipro.file.FileReference; import se.su.dsv.scipro.project.Project; import se.su.dsv.scipro.system.DegreeType; +import se.su.dsv.scipro.system.Language; import se.su.dsv.scipro.system.PageRequest; import se.su.dsv.scipro.system.ProjectType; import se.su.dsv.scipro.system.User; @@ -158,6 +159,7 @@ public class PeerRequestServiceImplTest extends IntegrationTest { peerRequest.setProject(project); peerRequest.setRequester(requester); peerRequest.setStatus(status); + peerRequest.setLanguage(Language.ENGLISH); final FileDescription fileDescription = save(new FileDescription()); final FileReference fileReference = new FileReference(); fileReference.setFileDescription(fileDescription); diff --git a/core/src/test/java/se/su/dsv/scipro/peer/PeerReviewServiceImplIntegrationTest.java b/core/src/test/java/se/su/dsv/scipro/peer/PeerReviewServiceImplIntegrationTest.java index 15a004fd89..a954f455be 100644 --- a/core/src/test/java/se/su/dsv/scipro/peer/PeerReviewServiceImplIntegrationTest.java +++ b/core/src/test/java/se/su/dsv/scipro/peer/PeerReviewServiceImplIntegrationTest.java @@ -14,6 +14,7 @@ import se.su.dsv.scipro.file.FileDescription; import se.su.dsv.scipro.file.FileReference; import se.su.dsv.scipro.project.Project; import se.su.dsv.scipro.system.DegreeType; +import se.su.dsv.scipro.system.Language; import se.su.dsv.scipro.system.PageRequest; import se.su.dsv.scipro.system.ProjectType; import se.su.dsv.scipro.system.User; @@ -102,6 +103,7 @@ public class PeerReviewServiceImplIntegrationTest extends IntegrationTest { PeerRequest peerRequest = new PeerRequest(); peerRequest.setProject(createProject()); peerRequest.setRequester(createUser()); + peerRequest.setLanguage(Language.ENGLISH); final FileDescription fileDescription = save(new FileDescription()); final FileReference fileReference = new FileReference(); fileReference.setFileDescription(fileDescription); diff --git a/core/src/test/java/se/su/dsv/scipro/peer/PeerReviewServiceImplTest.java b/core/src/test/java/se/su/dsv/scipro/peer/PeerReviewServiceImplTest.java index 9c6a5dd3da..6495faaf7a 100644 --- a/core/src/test/java/se/su/dsv/scipro/peer/PeerReviewServiceImplTest.java +++ b/core/src/test/java/se/su/dsv/scipro/peer/PeerReviewServiceImplTest.java @@ -14,6 +14,7 @@ import se.su.dsv.scipro.file.FileDescription; import se.su.dsv.scipro.file.FileReference; import se.su.dsv.scipro.project.Project; import se.su.dsv.scipro.system.DegreeType; +import se.su.dsv.scipro.system.Language; import se.su.dsv.scipro.system.PageRequest; import se.su.dsv.scipro.system.ProjectType; import se.su.dsv.scipro.system.User; @@ -95,6 +96,7 @@ public class PeerReviewServiceImplTest extends IntegrationTest { peerRequest.setRequester( save(User.builder().firstName("Bob").lastName("Sponge").emailAddress("bob@example.com").build()) ); + peerRequest.setLanguage(Language.ENGLISH); final FileDescription fileDescription = save(new FileDescription()); final FileReference fileReference = new FileReference(); fileReference.setFileDescription(fileDescription); diff --git a/core/src/test/java/se/su/dsv/scipro/peer/TestPeerReview.java b/core/src/test/java/se/su/dsv/scipro/peer/TestPeerReview.java index 34e5e5c7e2..fe5e2be992 100755 --- a/core/src/test/java/se/su/dsv/scipro/peer/TestPeerReview.java +++ b/core/src/test/java/se/su/dsv/scipro/peer/TestPeerReview.java @@ -19,6 +19,7 @@ import se.su.dsv.scipro.file.FileDescription; import se.su.dsv.scipro.file.FileReference; import se.su.dsv.scipro.project.Project; import se.su.dsv.scipro.project.ProjectStatus; +import se.su.dsv.scipro.system.Language; import se.su.dsv.scipro.system.PageRequest; import se.su.dsv.scipro.system.ProjectType; import se.su.dsv.scipro.system.User; @@ -196,6 +197,7 @@ public class TestPeerReview extends IntegrationTest { request.setComment(comment); request.setRequester(requester); request.setProject(project); + request.setLanguage(Language.ENGLISH); final FileDescription fileDescription = save(new FileDescription()); final FileReference fileReference = new FileReference(); fileReference.setFileDescription(fileDescription); diff --git a/core/src/test/java/se/su/dsv/scipro/plagiarism/urkund/UrkundSubmissionRepositoryTest.java b/core/src/test/java/se/su/dsv/scipro/plagiarism/urkund/UrkundSubmissionRepositoryTest.java index c09f3ea67e..a3b40ed7ae 100644 --- a/core/src/test/java/se/su/dsv/scipro/plagiarism/urkund/UrkundSubmissionRepositoryTest.java +++ b/core/src/test/java/se/su/dsv/scipro/plagiarism/urkund/UrkundSubmissionRepositoryTest.java @@ -13,6 +13,7 @@ import org.hamcrest.TypeSafeMatcher; import org.junit.jupiter.api.Test; import se.su.dsv.scipro.file.FileDescription; import se.su.dsv.scipro.file.FileReference; +import se.su.dsv.scipro.system.User; import se.su.dsv.scipro.test.SpringTest; public class UrkundSubmissionRepositoryTest extends SpringTest { @@ -23,11 +24,14 @@ public class UrkundSubmissionRepositoryTest extends SpringTest { @Test public void save() { final Instant submitted = Instant.now(); + User bob = User.builder().firstName("Bob").lastName("Sponge").emailAddress("bob@example.com").build(); + save(bob); final UrkundSubmission submission = new UrkundSubmission(); submission.setState(UrkundSubmission.State.SUBMITTED); submission.setMessage("Hi"); submission.setSubmitted(submitted); submission.setNextPoll(submitted); + submission.setReceiver(bob); final FileDescription file = save(new FileDescription()); final FileReference fileReference = new FileReference(); fileReference.setFileDescription(file); diff --git a/core/src/test/java/se/su/dsv/scipro/test/BeanDefinitionsTest.java b/core/src/test/java/se/su/dsv/scipro/test/BeanDefinitionsTest.java new file mode 100644 index 0000000000..8dd6ff6130 --- /dev/null +++ b/core/src/test/java/se/su/dsv/scipro/test/BeanDefinitionsTest.java @@ -0,0 +1,18 @@ +package se.su.dsv.scipro.test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; +import se.su.dsv.scipro.milestones.service.MilestoneActivator; + +public class BeanDefinitionsTest extends IntegrationTest { + + @Inject + MilestoneActivator milestoneActivator; + + @Test + public void milestone_activator() { + assertNotNull(milestoneActivator); + } +} diff --git a/core/src/test/java/se/su/dsv/scipro/test/SpringTest.java b/core/src/test/java/se/su/dsv/scipro/test/SpringTest.java index 2b68b55008..04d0f70da7 100644 --- a/core/src/test/java/se/su/dsv/scipro/test/SpringTest.java +++ b/core/src/test/java/se/su/dsv/scipro/test/SpringTest.java @@ -4,28 +4,47 @@ import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManagerFactory; import jakarta.persistence.EntityTransaction; import jakarta.persistence.Persistence; +import java.sql.SQLException; import java.time.Clock; +import java.util.Map; import java.util.Optional; +import org.flywaydb.core.Flyway; +import org.hibernate.cfg.Environment; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.mariadb.jdbc.MariaDbDataSource; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; import se.su.dsv.scipro.CoreConfig; import se.su.dsv.scipro.RepositoryConfiguration; import se.su.dsv.scipro.profiles.CurrentProfile; import se.su.dsv.scipro.sukat.Sukat; import se.su.dsv.scipro.system.CurrentUser; +@Testcontainers public abstract class SpringTest { private EntityManager entityManager; private EntityManagerFactory entityManagerFactory; + @Container + static MariaDBContainer<?> mariaDBContainer = new MariaDBContainer<>("mariadb:10.11"); + @BeforeEach - public final void prepareSpring() { - entityManagerFactory = Persistence.createEntityManagerFactory("testPersistenceUnit"); + public final void prepareSpring() throws SQLException { + MariaDbDataSource dataSource = new MariaDbDataSource(mariaDBContainer.getJdbcUrl()); + dataSource.setUser(mariaDBContainer.getUsername()); + dataSource.setPassword(mariaDBContainer.getPassword()); + + Flyway.configure().dataSource(dataSource).load().migrate(); + + Map<String, Object> jpaProperties = Map.of(Environment.JAKARTA_JTA_DATASOURCE, dataSource); + entityManagerFactory = Persistence.createEntityManagerFactory("defaultPersistenceUnit", jpaProperties); this.entityManager = entityManagerFactory.createEntityManager(); EntityTransaction transaction = entityManager.getTransaction(); transaction.begin(); diff --git a/daisy-integration/src/main/java/se/su/dsv/scipro/daisyExternal/impl/ImporterTransactionsImpl.java b/daisy-integration/src/main/java/se/su/dsv/scipro/daisyExternal/impl/ImporterTransactionsImpl.java index 0eca54eadf..395f5485ef 100644 --- a/daisy-integration/src/main/java/se/su/dsv/scipro/daisyExternal/impl/ImporterTransactionsImpl.java +++ b/daisy-integration/src/main/java/se/su/dsv/scipro/daisyExternal/impl/ImporterTransactionsImpl.java @@ -171,6 +171,7 @@ public class ImporterTransactionsImpl implements ImporterTransactions { username.setUsername(completeUsername); username.setUser(local); userNameService.save(username); + local.getUsernames().add(username); } } } diff --git a/docker-compose.yml b/docker-compose.yml index 637455a39e..aac221e274 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,3 +13,14 @@ services: - CLIENT_REDIRECT_URI=http://localhost:59732/ - RESOURCE_SERVER_ID=scipro-api-client - RESOURCE_SERVER_SECRET=scipro-api-secret + oauth2-wicket: + build: + context: https://github.com/dsv-su/toker.git + dockerfile: embedded.Dockerfile + restart: on-failure + ports: + - '59734:8080' + environment: + - CLIENT_ID=scipro + - CLIENT_SECRET=s3cr3t + - CLIENT_REDIRECT_URI=http://localhost:8080/login/oauth2/code/scipro diff --git a/pom.xml b/pom.xml index e69b87c989..a4b4f57961 100755 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,6 @@ <querydsl.version>5.0.0</querydsl.version> <jakarta.servlet.version>5.0.0</jakarta.servlet.version> <junit.version>5.9.3</junit.version> - <hsqldb.version>2.7.1</hsqldb.version> <mockito.version>5.3.1</mockito.version> <flyway.version>9.19.1</flyway.version> <jersey.version>3.1.6</jersey.version> @@ -101,7 +100,7 @@ <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> - <version>3.2.5</version> + <version>3.2.12</version> <scope>import</scope> <type>pom</type> </dependency> @@ -133,14 +132,6 @@ <scope>import</scope> </dependency> - <!-- Database stuff --> - <dependency> - <groupId>org.hsqldb</groupId> - <artifactId>hsqldb</artifactId> - <version>${hsqldb.version}</version> - <scope>test</scope> - </dependency> - <dependency> <groupId>org.mariadb.jdbc</groupId> <artifactId>mariadb-java-client</artifactId> diff --git a/view/src/main/java/se/su/dsv/scipro/CurrentUserFromWicketSession.java b/view/src/main/java/se/su/dsv/scipro/CurrentUserFromWicketSession.java deleted file mode 100644 index a13b465884..0000000000 --- a/view/src/main/java/se/su/dsv/scipro/CurrentUserFromWicketSession.java +++ /dev/null @@ -1,14 +0,0 @@ -package se.su.dsv.scipro; - -import org.apache.wicket.Session; -import se.su.dsv.scipro.session.SciProSession; -import se.su.dsv.scipro.system.CurrentUser; -import se.su.dsv.scipro.system.User; - -public class CurrentUserFromWicketSession implements CurrentUser { - - @Override - public User get() { - return Session.exists() ? SciProSession.get().getUser() : null; - } -} diff --git a/view/src/main/java/se/su/dsv/scipro/applicationperiod/AdminEditApplicationPeriodPage.html b/view/src/main/java/se/su/dsv/scipro/applicationperiod/AdminEditApplicationPeriodPage.html index 04de12622c..8c5597de96 100644 --- a/view/src/main/java/se/su/dsv/scipro/applicationperiod/AdminEditApplicationPeriodPage.html +++ b/view/src/main/java/se/su/dsv/scipro/applicationperiod/AdminEditApplicationPeriodPage.html @@ -16,10 +16,8 @@ <fieldset class="mb-3"> <legend><wicket:message key="projectTypes"/></legend> - <div class="form-check" wicket:id="projectTypes"> - <input type="checkbox" wicket:id="checkbox" class="form-check-input"/> - <label class="form-check-label" wicket:for="checkbox"><span wicket:id="name"></span></label> - </div> + <div wicket:id="projectTypes"></div> + <div wicket:id="projectTypesFeedback"></div> </fieldset> <div class="form-row"> diff --git a/view/src/main/java/se/su/dsv/scipro/applicationperiod/AdminEditApplicationPeriodPage.java b/view/src/main/java/se/su/dsv/scipro/applicationperiod/AdminEditApplicationPeriodPage.java index 2409d13d83..8781578a73 100644 --- a/view/src/main/java/se/su/dsv/scipro/applicationperiod/AdminEditApplicationPeriodPage.java +++ b/view/src/main/java/se/su/dsv/scipro/applicationperiod/AdminEditApplicationPeriodPage.java @@ -4,17 +4,14 @@ import jakarta.inject.Inject; import java.time.LocalDate; import java.time.LocalTime; import java.util.List; -import org.apache.wicket.extensions.model.AbstractCheckBoxModel; import org.apache.wicket.feedback.FencedFeedbackPanel; -import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.*; -import org.apache.wicket.markup.html.list.ListItem; -import org.apache.wicket.markup.html.list.ListView; import org.apache.wicket.markup.html.panel.ComponentFeedbackPanel; import org.apache.wicket.model.IModel; import org.apache.wicket.model.LambdaModel; import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.request.mapper.parameter.PageParameters; +import se.su.dsv.scipro.components.BootstrapCheckBoxMultipleChoice; import se.su.dsv.scipro.components.BootstrapDatePicker; import se.su.dsv.scipro.components.BootstrapTimePicker; import se.su.dsv.scipro.components.DatesValidator; @@ -39,7 +36,6 @@ public class AdminEditApplicationPeriodPage public static final String TITLE = "title"; public static final String FEEDBACK = "Feedback"; public static final String TITLE_FEEDBACK = "titleFeedback"; - public static final String CHECKBOX = "checkbox"; @Inject private ProjectTypeService projectTypeService; @@ -62,19 +58,15 @@ public class AdminEditApplicationPeriodPage ); add(new ComponentFeedbackPanel(TITLE_FEEDBACK, title)); add(title); - add( - new ListView<>(PROJECT_TYPES, availableProjectTypes()) { - @Override - protected void populateItem(ListItem<ProjectType> item) { - item.add( - new CheckBox(CHECKBOX, new ProjectTypeSelectionModel(item.getModel())).setOutputMarkupId( - true - ) - ); - item.add(new Label("name", item.getModel().map(ProjectType::getName))); - } - } + BootstrapCheckBoxMultipleChoice<ProjectType> projectTypeChoice = new BootstrapCheckBoxMultipleChoice<>( + PROJECT_TYPES, + LambdaModel.of(getModel(), ApplicationPeriod::getProjectTypes, ApplicationPeriod::setProjectTypes), + availableProjectTypes(), + new LambdaChoiceRenderer<>(ProjectType::getName, ProjectType::getId) ); + projectTypeChoice.setRequired(true); + add(projectTypeChoice); + add(new FencedFeedbackPanel("projectTypesFeedback", projectTypeChoice)); final FormComponent<LocalDate> startDate = addDateField( START_DATE, LambdaModel.of(getModel(), ApplicationPeriod::getStartDate, ApplicationPeriod::setStartDate) @@ -139,30 +131,6 @@ public class AdminEditApplicationPeriodPage getRootForm().error(getString("overlapping")); } } - - private class ProjectTypeSelectionModel extends AbstractCheckBoxModel { - - private final IModel<ProjectType> model; - - public ProjectTypeSelectionModel(IModel<ProjectType> model) { - this.model = model; - } - - @Override - public boolean isSelected() { - return getModelObject().getProjectTypes().contains(model.getObject()); - } - - @Override - public void select() { - getModelObject().addProjectType(model.getObject()); - } - - @Override - public void unselect() { - getModelObject().removeProjectType(model.getObject()); - } - } } private LoadableDetachableModel<ApplicationPeriod> getLoaded(final PageParameters pp) { diff --git a/view/src/main/java/se/su/dsv/scipro/applicationperiod/AdminEditApplicationPeriodPage.properties b/view/src/main/java/se/su/dsv/scipro/applicationperiod/AdminEditApplicationPeriodPage.properties index f5095052d4..dc945a5788 100644 --- a/view/src/main/java/se/su/dsv/scipro/applicationperiod/AdminEditApplicationPeriodPage.properties +++ b/view/src/main/java/se/su/dsv/scipro/applicationperiod/AdminEditApplicationPeriodPage.properties @@ -14,4 +14,5 @@ success= Application period saved. overlapping= Overlapping application period already exists. date.Required= You need to specify a valid date. hours.Required= Hours field is required. -minutes.Required= Minutes field is required. \ No newline at end of file +minutes.Required= Minutes field is required. +projectTypes.Required=You must select at least one project type. diff --git a/view/src/main/java/se/su/dsv/scipro/grading/GradingReportPointsPanel.java b/view/src/main/java/se/su/dsv/scipro/grading/GradingReportPointsPanel.java index 593ee532c5..4f95d80881 100644 --- a/view/src/main/java/se/su/dsv/scipro/grading/GradingReportPointsPanel.java +++ b/view/src/main/java/se/su/dsv/scipro/grading/GradingReportPointsPanel.java @@ -4,7 +4,6 @@ import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.model.IModel; -import org.apache.wicket.model.LoadableDetachableModel; import se.su.dsv.scipro.components.OppositeVisibility; import se.su.dsv.scipro.report.GradeCalculator; import se.su.dsv.scipro.report.GradingReport; @@ -18,15 +17,13 @@ public class GradingReportPointsPanel extends Panel { public GradingReportPointsPanel( String id, final IModel<? extends GradingReport> gradingReportIModel, - final GradeCalculator gradeCalculator + final IModel<GradeCalculator> gradeCalculator ) { super(id, gradingReportIModel); - final IModel<GradingReport.Grade> gradeModel = new LoadableDetachableModel<>() { - @Override - protected GradingReport.Grade load() { - return gradingReportIModel.getObject().getGrade(gradeCalculator); - } - }; + final IModel<GradingReport.Grade> gradeModel = gradingReportIModel.combineWith( + gradeCalculator, + GradingReport::getGrade + ); final Label grade = new Label(GRADE, gradeModel.map(GradingReport.Grade::name)) { @Override protected void onConfigure() { @@ -36,12 +33,7 @@ public class GradingReportPointsPanel extends Panel { }; add(grade); - final IModel<Long> points = new LoadableDetachableModel<>() { - @Override - protected Long load() { - return gradingReportIModel.getObject().getPoints(gradeCalculator); - } - }; + final IModel<Long> points = gradingReportIModel.combineWith(gradeCalculator, GradingReport::getPoints); add(new Label(POINTS_LABEL, points)); add(new WebMarkupContainer(NO_GRADE_EXPLANATION).add(new OppositeVisibility(grade))); diff --git a/view/src/main/java/se/su/dsv/scipro/grading/IndividualAuthorAssessmentPanel.java b/view/src/main/java/se/su/dsv/scipro/grading/IndividualAuthorAssessmentPanel.java index 6d4ba8e0d6..c9c0938a58 100644 --- a/view/src/main/java/se/su/dsv/scipro/grading/IndividualAuthorAssessmentPanel.java +++ b/view/src/main/java/se/su/dsv/scipro/grading/IndividualAuthorAssessmentPanel.java @@ -271,8 +271,8 @@ public class IndividualAuthorAssessmentPanel extends GenericPanel<User> { new TemplatePanel("points_to_grade_conversion", gradingReport.map(SupervisorGradingReport::getProject)) ); - GradeCalculator supervisorCalculator = gradeCalculatorService.getSupervisorCalculator( - gradingReport.getObject().getProject() + IModel<GradeCalculator> supervisorCalculator = LoadableDetachableModel.of(() -> + gradeCalculatorService.getSupervisorCalculator(gradingReport.getObject().getProject()) ); add(new GradingReportPointsPanel("points", gradingReport, supervisorCalculator)); diff --git a/view/src/main/java/se/su/dsv/scipro/loginlogout/pages/SSOPage.java b/view/src/main/java/se/su/dsv/scipro/loginlogout/pages/SSOPage.java index 8df12a7cce..cb9a802c52 100644 --- a/view/src/main/java/se/su/dsv/scipro/loginlogout/pages/SSOPage.java +++ b/view/src/main/java/se/su/dsv/scipro/loginlogout/pages/SSOPage.java @@ -1,12 +1,12 @@ package se.su.dsv.scipro.loginlogout.pages; import jakarta.inject.Inject; -import jakarta.servlet.http.HttpServletRequest; import java.util.Optional; import java.util.Set; import se.su.dsv.scipro.basepages.PublicPage; import se.su.dsv.scipro.security.auth.Authorization; import se.su.dsv.scipro.session.SciProSession; +import se.su.dsv.scipro.system.AuthenticationContext; import se.su.dsv.scipro.system.User; import se.su.dsv.scipro.system.UserImportService; import se.su.dsv.scipro.system.UserService; @@ -20,8 +20,11 @@ public class SSOPage extends PublicPage { @Inject private UserService userService; + @Inject + private AuthenticationContext authenticationContext; + public SSOPage() { - String remoteUserName = ((HttpServletRequest) getRequest().getContainerRequest()).getRemoteUser(); + String remoteUserName = authenticationContext.getPrincipalName(); User user = userService.findByUsername(remoteUserName); if (user != null) { diff --git a/view/src/main/java/se/su/dsv/scipro/security/auth/MockRemoteUserFilter.java b/view/src/main/java/se/su/dsv/scipro/security/auth/MockRemoteUserFilter.java deleted file mode 100755 index c8ab375393..0000000000 --- a/view/src/main/java/se/su/dsv/scipro/security/auth/MockRemoteUserFilter.java +++ /dev/null @@ -1,81 +0,0 @@ -package se.su.dsv.scipro.security.auth; - -import jakarta.servlet.*; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletRequestWrapper; -import java.io.IOException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Throw-away implementation of a servlet filter, main task is to fake the getRemoteUser() call for the request chain. - */ -public final class MockRemoteUserFilter implements Filter { - - private static final Logger LOGGER = LoggerFactory.getLogger(MockRemoteUserFilter.class); - //Default value unless supplied via init parameter - private String fakedUser = "SOME_GUY"; - private FilterConfig cfg = null; - - /** - * Default constructor. - */ - public MockRemoteUserFilter() {} - - /** - * @see Filter#destroy() - */ - @Override - public void destroy() { - cfg = null; - } - - /** - * Wraps the passed request and alters the behavior of getRemoteUser() for later links of the chain. - * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain) - */ - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { - LOGGER.debug("Faking external authentication user: " + fakedUser); - if (cfg != null) { - HttpServletRequestWrapper wrapper = new ModifiedRemoteUserRequestWrapper( - (HttpServletRequest) request, - fakedUser - ); - // pass the request along the filter chain - chain.doFilter(wrapper, response); - return; - } - chain.doFilter(request, response); - } - - /** - * @see Filter#init(FilterConfig) - */ - @Override - public void init(FilterConfig fConfig) { - cfg = fConfig; - if (cfg != null) { - fakedUser = cfg.getInitParameter("fakedUser"); - } - } - - /** - * Private RequestWrapper, of no interest to anyone outside of this class. - */ - static class ModifiedRemoteUserRequestWrapper extends HttpServletRequestWrapper { - - private final String fakedUser; - - ModifiedRemoteUserRequestWrapper(final HttpServletRequest request, final String fakedUser) { - super(request); - this.fakedUser = fakedUser; - } - - @Override - public String getRemoteUser() { - return fakedUser; - } - } -} diff --git a/view/src/main/java/se/su/dsv/scipro/session/SciProSession.java b/view/src/main/java/se/su/dsv/scipro/session/SciProSession.java index 75af2b3bff..202e061242 100755 --- a/view/src/main/java/se/su/dsv/scipro/session/SciProSession.java +++ b/view/src/main/java/se/su/dsv/scipro/session/SciProSession.java @@ -5,24 +5,21 @@ import java.util.Collections; import java.util.HashSet; import java.util.Locale; import java.util.Set; -import org.apache.wicket.MetaDataKey; import org.apache.wicket.Session; import org.apache.wicket.injection.Injector; import org.apache.wicket.protocol.http.WebSession; import org.apache.wicket.request.Request; import se.su.dsv.scipro.security.auth.roles.IRole; import se.su.dsv.scipro.security.auth.roles.Roles; +import se.su.dsv.scipro.system.AuthenticationContext; import se.su.dsv.scipro.system.ProjectModule; import se.su.dsv.scipro.system.SystemModule; import se.su.dsv.scipro.system.User; -import se.su.dsv.scipro.system.UserService; public class SciProSession extends WebSession { - private static final MetaDataKey<Long> LOGGED_IN_USER_ID = new MetaDataKey<>() {}; - @Inject - private UserService userService; + private AuthenticationContext authenticationContext; private Set<ProjectModule> projectModules = new HashSet<>(); private Set<SystemModule> systemModules = new HashSet<>(); @@ -37,15 +34,15 @@ public class SciProSession extends WebSession { } public synchronized void setUser(User user) { - setMetaData(LOGGED_IN_USER_ID, user.getId()); + authenticationContext.set(user); } public synchronized User getUser() { - return isLoggedIn() ? userService.findOne(getMetaData(LOGGED_IN_USER_ID)) : null; + return authenticationContext.get(); } public synchronized boolean isLoggedIn() { - return getMetaData(LOGGED_IN_USER_ID) != null; + return authenticationContext.get() != null; } public synchronized boolean authorizedForRole(Roles role) { diff --git a/view/src/test/java/se/su/dsv/scipro/SciProTest.java b/view/src/test/java/se/su/dsv/scipro/SciProTest.java index ceec7ebadb..768b84e0df 100755 --- a/view/src/test/java/se/su/dsv/scipro/SciProTest.java +++ b/view/src/test/java/se/su/dsv/scipro/SciProTest.java @@ -3,7 +3,6 @@ package se.su.dsv.scipro; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import com.google.common.eventbus.EventBus; import java.lang.reflect.Field; @@ -119,12 +118,11 @@ import se.su.dsv.scipro.springdata.services.UnitService; import se.su.dsv.scipro.springdata.services.UserProfileService; import se.su.dsv.scipro.supervisor.pages.SupervisorStartPage; import se.su.dsv.scipro.survey.SurveyService; -import se.su.dsv.scipro.system.CurrentUser; +import se.su.dsv.scipro.system.AuthenticationContext; import se.su.dsv.scipro.system.ExternalResourceService; import se.su.dsv.scipro.system.FooterAddressRepo; import se.su.dsv.scipro.system.FooterLinkService; import se.su.dsv.scipro.system.GenericService; -import se.su.dsv.scipro.system.Lifecycle; import se.su.dsv.scipro.system.PasswordRepo; import se.su.dsv.scipro.system.PasswordService; import se.su.dsv.scipro.system.ProjectModule; @@ -369,7 +367,7 @@ public abstract class SciProTest { protected ChecklistAnswerService checklistAnswerService; @Mock - protected CurrentUser currentUser; + protected AuthenticationContext authenticationContext; @Mock private Scheduler scheduler; diff --git a/view/src/test/java/se/su/dsv/scipro/applicationperiod/AdminEditApplicationPeriodPageTest.java b/view/src/test/java/se/su/dsv/scipro/applicationperiod/AdminEditApplicationPeriodPageTest.java index 7b68730909..a6bc599bd9 100644 --- a/view/src/test/java/se/su/dsv/scipro/applicationperiod/AdminEditApplicationPeriodPageTest.java +++ b/view/src/test/java/se/su/dsv/scipro/applicationperiod/AdminEditApplicationPeriodPageTest.java @@ -7,8 +7,10 @@ import java.io.Serializable; import java.time.LocalDate; import java.util.Collections; import java.util.List; +import org.apache.wicket.Component; import org.apache.wicket.Page; import org.apache.wicket.feedback.FeedbackMessage; +import org.apache.wicket.markup.html.form.CheckBoxMultipleChoice; import org.apache.wicket.markup.html.form.RequiredTextField; import org.apache.wicket.request.mapper.parameter.PageParameters; import org.apache.wicket.util.tester.FormTester; @@ -36,6 +38,7 @@ public class AdminEditApplicationPeriodPageTest extends SciProTest { @BeforeEach public void setUp() throws Exception { bachelor = new ProjectType(DegreeType.BACHELOR, "Bachelor", "Bachelor"); + bachelor.setId(8L); bachelor.addModule(ProjectModule.MATCH); when(projectTypeService.findWithModule(ProjectModule.MATCH)).thenReturn(Collections.singletonList(bachelor)); startPage(); @@ -47,8 +50,12 @@ public class AdminEditApplicationPeriodPageTest extends SciProTest { } @Test + @SuppressWarnings("unchecked") public void contains_project_type_selection() { - tester.assertModelValue(path(FORM, PROJECT_TYPES), projectTypeService.findWithModule(ProjectModule.MATCH)); + tester.assertComponent(path(FORM, PROJECT_TYPES), CheckBoxMultipleChoice.class); + Component component = tester.getComponentFromLastRenderedPage(path(FORM, PROJECT_TYPES)); + CheckBoxMultipleChoice<ProjectType> choice = (CheckBoxMultipleChoice<ProjectType>) component; + Assertions.assertEquals(projectTypeService.findWithModule(ProjectModule.MATCH), choice.getChoices()); } @Test @@ -105,7 +112,7 @@ public class AdminEditApplicationPeriodPageTest extends SciProTest { ); FormTester formTester = tester.newFormTester(FORM); fillInForm("Title", 0, 1, 2, formTester); - formTester.setValue(path(PROJECT_TYPES, 0, CHECKBOX), true); + formTester.setValue(path(PROJECT_TYPES), String.valueOf(bachelor.getId())); formTester.submit(); ArgumentCaptor<ApplicationPeriod> captor = ArgumentCaptor.forClass(ApplicationPeriod.class); @@ -113,12 +120,25 @@ public class AdminEditApplicationPeriodPageTest extends SciProTest { MatcherAssert.assertThat(captor.getValue().getProjectTypes(), Matchers.hasItem(bachelor)); } + @Test + public void requires_at_least_one_project_type_to_be_selected() { + FormTester formTester = tester.newFormTester(FORM); + fillInFormWithValidValues(formTester); + formTester.setValue(path(PROJECT_TYPES), ""); + formTester.submit(); + tester.assertErrorMessages(tester.getLastRenderedPage().getString("projectTypes.Required")); + } + private void submitForm(String title, int startDate, int endDate, int courseStartDate) { FormTester formTester = tester.newFormTester(FORM); fillInForm(title, startDate, endDate, courseStartDate, formTester); formTester.submit(); } + private void fillInFormWithValidValues(FormTester formTester) { + fillInForm("Title", 0, 1, 2, formTester); + } + private void fillInForm(String title, int startDate, int endDate, int courseStartDate, FormTester formTester) { formTester.setValue(TITLE, title); final LocalDate now = LocalDate.now(); @@ -126,6 +146,7 @@ public class AdminEditApplicationPeriodPageTest extends SciProTest { formTester.setValue(END_DATE, now.plusDays(endDate).toString()); formTester.setValue(COURSE_START_DATE, now.plusDays(courseStartDate).toString()); formTester.setValue("courseStartTime", "08:00"); + formTester.setValue(PROJECT_TYPES, String.valueOf(bachelor.getId())); } private void startPage() { diff --git a/view/src/test/java/se/su/dsv/scipro/grading/GradingReportPointsPanelTest.java b/view/src/test/java/se/su/dsv/scipro/grading/GradingReportPointsPanelTest.java index caa5835e3f..f358d419e6 100644 --- a/view/src/test/java/se/su/dsv/scipro/grading/GradingReportPointsPanelTest.java +++ b/view/src/test/java/se/su/dsv/scipro/grading/GradingReportPointsPanelTest.java @@ -62,7 +62,7 @@ public class GradingReportPointsPanelTest extends SciProTest { private void startPanel() { panel = tester.startComponentInPage( - new GradingReportPointsPanel("id", Model.of(gradingReport), gradeCalculator) + new GradingReportPointsPanel("id", Model.of(gradingReport), () -> gradeCalculator) ); } diff --git a/war/pom.xml b/war/pom.xml index 2c4ad17242..456eeae393 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -41,6 +41,10 @@ <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-oauth2-client</artifactId> + </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> diff --git a/war/src/main/java/se/su/dsv/scipro/war/CurrentUserFromSpringSecurity.java b/war/src/main/java/se/su/dsv/scipro/war/CurrentUserFromSpringSecurity.java new file mode 100644 index 0000000000..6f209f38aa --- /dev/null +++ b/war/src/main/java/se/su/dsv/scipro/war/CurrentUserFromSpringSecurity.java @@ -0,0 +1,98 @@ +package se.su.dsv.scipro.war; + +import jakarta.inject.Inject; +import jakarta.inject.Provider; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.security.Principal; +import java.util.Collections; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; +import org.springframework.security.web.context.HttpSessionSecurityContextRepository; +import org.springframework.security.web.context.SecurityContextRepository; +import se.su.dsv.scipro.system.AuthenticationContext; +import se.su.dsv.scipro.system.User; +import se.su.dsv.scipro.system.UserService; +import se.su.dsv.scipro.system.Username; + +public class CurrentUserFromSpringSecurity implements AuthenticationContext { + + private final UserService userService; + + // injecting providers since this is a singleton and the request and response are not + private final Provider<HttpServletRequest> currentRequest; + private final Provider<HttpServletResponse> currentResponse; + + // hardcoded since that is what Spring Security does (see SwitchUserFilter) + private final SecurityContextRepository securityContextRepository = new HttpSessionSecurityContextRepository(); + + @Inject + public CurrentUserFromSpringSecurity( + UserService userService, + Provider<HttpServletRequest> currentRequest, + Provider<HttpServletResponse> currentResponse + ) { + this.userService = userService; + this.currentRequest = currentRequest; + this.currentResponse = currentResponse; + } + + @Override + public User get() { + SecurityContext context = SecurityContextHolder.getContext(); + Authentication authentication = context.getAuthentication(); + if (authentication == null) { + return null; + } + String username = authentication.getName(); + return userService.findByUsername(username); + } + + // Implementing switch user manually rather than using the built-in Spring Security switch user feature + // due to compatibility with Wicket. + // Wicket does not supply a form with a username field since it has some JavaScript based auto-complete + // person finder. + // See Spring's SwitchUserFilter for the built-in switch user feature from where most of the code is copied. + @Override + public void set(User user) { + SecurityContextHolderStrategy strategy = SecurityContextHolder.getContextHolderStrategy(); + SecurityContext context = strategy.createEmptyContext(); + WicketControlledPrincipal principal = new WicketControlledPrincipal(user); + UsernamePasswordAuthenticationToken targetUser = UsernamePasswordAuthenticationToken.authenticated( + principal, + null, + Collections.emptyList() + ); + context.setAuthentication(targetUser); + strategy.setContext(context); + this.securityContextRepository.saveContext(context, currentRequest.get(), currentResponse.get()); + } + + @Override + public String getPrincipalName() { + SecurityContext context = SecurityContextHolder.getContext(); + Authentication authentication = context.getAuthentication(); + if (authentication == null) { + return null; + } + return authentication.getName(); + } + + private static final class WicketControlledPrincipal implements Principal { + + private final String username; + + public WicketControlledPrincipal(User user) { + // extract any username so that we can look it up later + this.username = user.getUsernames().stream().findAny().map(Username::getUsername).orElse("<unknown>"); + } + + @Override + public String getName() { + return username; + } + } +} diff --git a/war/src/main/java/se/su/dsv/scipro/war/Main.java b/war/src/main/java/se/su/dsv/scipro/war/Main.java index 9669f121d6..2a14e64915 100644 --- a/war/src/main/java/se/su/dsv/scipro/war/Main.java +++ b/war/src/main/java/se/su/dsv/scipro/war/Main.java @@ -23,7 +23,6 @@ import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.orm.jpa.SharedEntityManagerCreator; import org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter; import se.su.dsv.scipro.CoreConfig; -import se.su.dsv.scipro.CurrentUserFromWicketSession; import se.su.dsv.scipro.FileSystemStore; import se.su.dsv.scipro.RepositoryConfiguration; import se.su.dsv.scipro.file.FileStore; @@ -85,11 +84,6 @@ public class Main extends SpringBootServletInitializer implements ServletContain return currentProfile; } - @Bean - public CurrentUserFromWicketSession currentUserFromWicketSession() { - return new CurrentUserFromWicketSession(); - } - @Bean public FileStore fileStore() { return new FileSystemStore(); diff --git a/war/src/main/java/se/su/dsv/scipro/war/WicketConfiguration.java b/war/src/main/java/se/su/dsv/scipro/war/WicketConfiguration.java index db84241566..90b0b70a61 100644 --- a/war/src/main/java/se/su/dsv/scipro/war/WicketConfiguration.java +++ b/war/src/main/java/se/su/dsv/scipro/war/WicketConfiguration.java @@ -1,6 +1,9 @@ package se.su.dsv.scipro.war; import com.google.common.eventbus.EventBus; +import jakarta.inject.Provider; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.apache.wicket.protocol.http.WebApplication; import org.apache.wicket.protocol.http.WicketFilter; import org.apache.wicket.spring.injection.annot.SpringComponentInjector; @@ -8,6 +11,10 @@ import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.SecurityFilterChain; import se.su.dsv.scipro.SciProApplication; import se.su.dsv.scipro.crosscutting.ForwardPhase2Feedback; import se.su.dsv.scipro.crosscutting.NotifyFailedReflection; @@ -21,6 +28,7 @@ import se.su.dsv.scipro.notifications.NotificationController; import se.su.dsv.scipro.profiles.CurrentProfile; import se.su.dsv.scipro.reviewing.FinalSeminarApprovalService; import se.su.dsv.scipro.reviewing.RoughDraftApprovalService; +import se.su.dsv.scipro.system.UserService; @Configuration public class WicketConfiguration { @@ -49,6 +57,25 @@ public class WicketConfiguration { return new SciProApplication(currentProfile); } + @Bean + @Order(3) // make sure it's after the API security filters + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http.authorizeHttpRequests(requests -> requests.anyRequest().authenticated()); + http.oauth2Login(Customizer.withDefaults()); + http.csrf(csrf -> csrf.disable()); // Wicket has its own CSRF protection + http.logout(logout -> logout.logoutUrl("/logout").logoutSuccessUrl("/")); + return http.build(); + } + + @Bean + public CurrentUserFromSpringSecurity currentUserFromSpringSecurity( + UserService userService, + Provider<HttpServletRequest> httpServletRequestProvider, + Provider<HttpServletResponse> httpServletResponseProvider + ) { + return new CurrentUserFromSpringSecurity(userService, httpServletRequestProvider, httpServletResponseProvider); + } + @Bean public ReviewingNotifications reviewingNotifications( EventBus eventBus, diff --git a/war/src/main/resources/application.properties b/war/src/main/resources/application.properties index 7754344e7d..f405136272 100644 --- a/war/src/main/resources/application.properties +++ b/war/src/main/resources/application.properties @@ -19,3 +19,14 @@ springdoc.swagger-ui.persist-authorization=true spring.security.oauth2.resourceserver.opaquetoken.client-id=scipro-api-client spring.security.oauth2.resourceserver.opaquetoken.client-secret=scipro-api-secret spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=http://localhost:59733/introspect + +# Log in via local OAuth 2 authorization server +spring.security.oauth2.client.provider.docker.user-info-uri=http://localhost:59734/verify +spring.security.oauth2.client.provider.docker.user-name-attribute=sub +spring.security.oauth2.client.provider.docker.token-uri=http://localhost:59734/exchange +spring.security.oauth2.client.provider.docker.authorization-uri=http://localhost:59734/authorize +spring.security.oauth2.client.registration.scipro.redirect-uri={baseUrl}/login/oauth2/code/{registrationId} +spring.security.oauth2.client.registration.scipro.provider=docker +spring.security.oauth2.client.registration.scipro.client-id=scipro +spring.security.oauth2.client.registration.scipro.client-secret=s3cr3t +spring.security.oauth2.client.registration.scipro.authorization-grant-type=authorization_code