Compare commits
13 Commits
develop
...
remove-for
Author | SHA1 | Date | |
---|---|---|---|
488723425c | |||
10eb7f8fb2 | |||
c3cbf57c19 | |||
23295d3875 | |||
2983e13af6 | |||
2dc71119cc | |||
cd77921029 | |||
79cc561e44 | |||
e29e9561db | |||
dbcf0b603f | |||
b772b71061 | |||
83044e5072 | |||
2c7ec3f2fc |
GetToken.javacompose-branch-deploy.yamldocker-compose.yml
core/src
main
java/se/su/dsv/scipro
forum
BasicForumService.javaBasicForumServiceImpl.javaGroupForumService.javaGroupForumServiceImpl.javaProjectForumPostDeletedEvent.javaProjectForumService.javaProjectForumServiceImpl.javaProjectForumThreadDeletedEvent.java
dataobjects
notifications
notifications
resources/db/migration
test/java/se/su/dsv/scipro/forum
test-data/src/main/java/se/su/dsv/scipro/testdata/populators
view/src
main/java/se/su/dsv/scipro
finalseminar
forum
group
notifications/pages
reviewer
supervisor/pages
wicket-package.utf8.propertiestest/java/se/su/dsv/scipro
forum/pages/threaded
test
war/src/main
java/se/su/dsv/scipro/war
resources
@ -5,7 +5,9 @@ import java.awt.Toolkit;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Authenticator;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.PasswordAuthentication;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
@ -13,7 +15,6 @@ import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
@ -26,9 +27,16 @@ public class GetToken {
|
||||
String clientId = "get-token";
|
||||
String clientSecret = "get-token-secret";
|
||||
|
||||
System.out.println("Browse to " + baseUri.resolve("oauth2/authorize?response_type=code&client_id=" + clientId));
|
||||
System.out.println("Browse to " + baseUri.resolve("authorize?response_type=code&client_id=" + clientId));
|
||||
|
||||
HttpClient httpClient = HttpClient.newBuilder().build();
|
||||
HttpClient httpClient = HttpClient.newBuilder()
|
||||
.authenticator(new Authenticator() {
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
return new PasswordAuthentication(clientId, clientSecret.toCharArray());
|
||||
}
|
||||
})
|
||||
.build();
|
||||
|
||||
HttpServer httpServer = HttpServer.create();
|
||||
httpServer.bind(new InetSocketAddress(59732), 0);
|
||||
@ -44,11 +52,8 @@ public class GetToken {
|
||||
Map<String, List<String>> queryParams = getQueryParams(exchange);
|
||||
String code = queryParams.get("code").get(0);
|
||||
HttpRequest httpRequest = HttpRequest.newBuilder()
|
||||
.uri(baseUri.resolve("oauth2/token"))
|
||||
.uri(baseUri.resolve("exchange"))
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
// Have to preemptively set the authorization header
|
||||
// Spring Authorization server does not send a WWW-Authenticate header which would trigger the above Authenticator
|
||||
.header("Authorization", "Basic " + Base64.getEncoder().encodeToString((clientId + ":" + clientSecret).getBytes(StandardCharsets.UTF_8)))
|
||||
.POST(HttpRequest.BodyPublishers.ofString("grant_type=authorization_code&code=" + code))
|
||||
.build();
|
||||
try {
|
||||
|
@ -13,9 +13,14 @@ services:
|
||||
- JDBC_DATABASE_URL=jdbc:mariadb://db:3306/scipro
|
||||
- JDBC_DATABASE_USERNAME=scipro
|
||||
- JDBC_DATABASE_PASSWORD=scipro
|
||||
- OAUTH2_ISSUER_URI=https://oauth2-${VHOST}
|
||||
- OAUTH2_AUTHORIZATION_URI=https://oauth2-${VHOST}/authorize
|
||||
- OAUTH2_TOKEN_URI=https://oauth2-${VHOST}/exchange
|
||||
- OAUTH2_USER_INFO_URI=https://oauth2-${VHOST}/introspect
|
||||
- OAUTH2_CLIENT_ID=scipro_client
|
||||
- OAUTH2_CLIENT_SECRET=scipro_secret
|
||||
- OAUTH2_RESOURCE_SERVER_ID=scipro_api_client
|
||||
- OAUTH2_RESOURCE_SERVER_SECRET=scipro_api_secret
|
||||
- OAUTH2_RESOURCE_SERVER_INTROSPECTION_URI=https://oauth2-${VHOST}/introspect
|
||||
- OAUTH2_GS_AUTHORIZATION_URI=https://oauth2-gs-${VHOST}
|
||||
- OAUTH2_GS_CLIENT_REDIRECT_URI=https://${VHOST}/oauth/callback
|
||||
networks:
|
||||
@ -44,13 +49,16 @@ services:
|
||||
retries: 6
|
||||
|
||||
oauth2:
|
||||
build: https://gitea.dsv.su.se/DMC/oauth2-authorization-server.git#1d469c73468d00be5430dac01a7ab84f11ed471a
|
||||
build:
|
||||
context: https://github.com/dsv-su/toker.git
|
||||
dockerfile: embedded.Dockerfile
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- CLIENT_ID=scipro_client
|
||||
- CLIENT_SECRET=scipro_secret
|
||||
- CLIENT_REDIRECT_URI=https://${VHOST}/login/oauth2/code/scipro
|
||||
- CLIENT_SCOPES=openid
|
||||
- RESOURCE_SERVER_ID=scipro_api_client
|
||||
- RESOURCE_SERVER_SECRET=scipro_api_secret
|
||||
networks:
|
||||
- traefik
|
||||
labels:
|
||||
@ -59,12 +67,16 @@ services:
|
||||
- "traefik.http.routers.oauth2-${COMPOSE_PROJECT_NAME}.tls.certresolver=letsencrypt"
|
||||
|
||||
oauth2-gs:
|
||||
build: https://gitea.dsv.su.se/DMC/oauth2-authorization-server.git#1d469c73468d00be5430dac01a7ab84f11ed471a
|
||||
build:
|
||||
context: https://github.com/dsv-su/toker.git
|
||||
dockerfile: embedded.Dockerfile
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- CLIENT_ID=scipro_client
|
||||
- CLIENT_SECRET=scipro_secret
|
||||
- CLIENT_REDIRECT_URI=https://${VHOST}/oauth/callback
|
||||
- RESOURCE_SERVER_ID=scipro_api_client
|
||||
- RESOURCE_SERVER_SECRET=scipro_api_secret
|
||||
- CLIENT_SCOPES=grade:read grade:write
|
||||
networks:
|
||||
- traefik
|
||||
|
@ -22,6 +22,8 @@ public interface BasicForumService extends Serializable {
|
||||
|
||||
ForumThread createThread(String subject);
|
||||
|
||||
ForumThread findThreadById(Long id);
|
||||
|
||||
long countUnreadThreads(List<ForumThread> forumThreadList, User user);
|
||||
|
||||
ForumPost getLastPost(ForumThread forumThread);
|
||||
@ -31,4 +33,8 @@ public interface BasicForumService extends Serializable {
|
||||
boolean canDelete(ForumPost forumPost);
|
||||
|
||||
void deletePost(ForumPost post);
|
||||
|
||||
boolean canDelete(ForumThread forumThread);
|
||||
|
||||
void deleteThread(ForumThread forumThread);
|
||||
}
|
||||
|
@ -91,6 +91,11 @@ public class BasicForumServiceImpl implements BasicForumService {
|
||||
return threadRepository.save(forumThread);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ForumThread findThreadById(Long id) {
|
||||
return threadRepository.findOne(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countUnreadThreads(List<ForumThread> forumThreadList, User user) {
|
||||
return postRepository.countUnreadThreads(forumThreadList, user);
|
||||
@ -179,8 +184,35 @@ public class BasicForumServiceImpl implements BasicForumService {
|
||||
if (!canDelete(post)) {
|
||||
throw new PostCantBeDeletedException();
|
||||
}
|
||||
post.setDeleted(true);
|
||||
postRepository.save(post);
|
||||
ForumThread forumThread = post.getForumThread();
|
||||
forumThread.getPosts().remove(post);
|
||||
threadRepository.save(forumThread);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDelete(ForumThread forumThread) {
|
||||
boolean hasReplies = getPosts(forumThread).size() > 1;
|
||||
if (hasReplies) {
|
||||
return false;
|
||||
}
|
||||
|
||||
User currentUser = currentUserProvider.get();
|
||||
if (currentUser == null) {
|
||||
// Allow the system to delete any thread
|
||||
return true;
|
||||
}
|
||||
|
||||
return Objects.equals(currentUser, forumThread.getCreatedBy());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteThread(ForumThread forumThread) {
|
||||
if (!canDelete(forumThread)) {
|
||||
throw new IllegalArgumentException("Not allowed to delete thread");
|
||||
}
|
||||
|
||||
threadRepository.delete(forumThread);
|
||||
}
|
||||
|
||||
private static final class PostCantBeDeletedException extends IllegalArgumentException {
|
||||
|
@ -18,4 +18,12 @@ public interface GroupForumService {
|
||||
ForumPost createReply(GroupThread groupThread, User poster, String content, Set<Attachment> attachments);
|
||||
|
||||
List<ForumPost> getPosts(GroupThread groupThread);
|
||||
|
||||
boolean canDelete(GroupThread groupThread, ForumPost post);
|
||||
|
||||
void deletePost(GroupThread groupThread, ForumPost post);
|
||||
|
||||
boolean canDeleteThread(GroupThread groupThread);
|
||||
|
||||
void deleteThread(GroupThread groupThread);
|
||||
}
|
||||
|
@ -81,4 +81,25 @@ public class GroupForumServiceImpl implements GroupForumService {
|
||||
public List<ForumPost> getPosts(final GroupThread groupThread) {
|
||||
return basicForumService.getPosts(groupThread.getForumThread());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDelete(GroupThread groupThread, ForumPost post) {
|
||||
return basicForumService.canDelete(post);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePost(GroupThread groupThread, ForumPost post) {
|
||||
basicForumService.deletePost(post);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDeleteThread(GroupThread groupThread) {
|
||||
return basicForumService.canDelete(groupThread.getForumThread());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteThread(GroupThread groupThread) {
|
||||
groupThreadRepository.delete(groupThread);
|
||||
basicForumService.deleteThread(groupThread.getForumThread());
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,5 @@
|
||||
package se.su.dsv.scipro.forum;
|
||||
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
|
||||
public record ProjectForumPostDeletedEvent(Project project, String subject) {}
|
@ -24,4 +24,12 @@ public interface ProjectForumService {
|
||||
List<Pair<ProjectThread, ForumPost>> latestPost(Project a, int amount);
|
||||
|
||||
long getUnreadThreadsCount(Project project, User user);
|
||||
|
||||
boolean canDelete(ProjectThread projectThread, ForumPost post);
|
||||
|
||||
void deletePost(ProjectThread projectThread, ForumPost post);
|
||||
|
||||
boolean canDelete(ProjectThread projectThread);
|
||||
|
||||
void deleteThread(ProjectThread projectThread);
|
||||
}
|
||||
|
@ -121,6 +121,34 @@ public class ProjectForumServiceImpl implements ProjectForumService {
|
||||
return basicForumService.countUnreadThreads(list, user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDelete(ProjectThread projectThread, ForumPost post) {
|
||||
return basicForumService.canDelete(post);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePost(ProjectThread projectThread, ForumPost post) {
|
||||
basicForumService.deletePost(post);
|
||||
eventBus.post(
|
||||
new ProjectForumPostDeletedEvent(projectThread.getProject(), projectThread.getForumThread().getSubject())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDelete(ProjectThread projectThread) {
|
||||
return basicForumService.canDelete(projectThread.getForumThread());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteThread(ProjectThread projectThread) {
|
||||
Project project = projectThread.getProject();
|
||||
String subject = projectThread.getForumThread().getSubject();
|
||||
projectThreadRepository.delete(projectThread);
|
||||
basicForumService.deleteThread(projectThread.getForumThread());
|
||||
eventBus.post(new ProjectForumThreadDeletedEvent(project, subject));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProjectThread findOne(long threadId) {
|
||||
return projectThreadRepository.findOne(threadId);
|
||||
|
@ -0,0 +1,5 @@
|
||||
package se.su.dsv.scipro.forum;
|
||||
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
|
||||
public record ProjectForumThreadDeletedEvent(Project project, String subject) {}
|
@ -11,11 +11,11 @@ import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Inheritance;
|
||||
import jakarta.persistence.InheritanceType;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.PostLoad;
|
||||
import jakarta.persistence.Table;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
import se.su.dsv.scipro.system.LazyDeletableDomainObject;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
|
||||
@ -38,17 +38,9 @@ public class ForumThread extends LazyDeletableDomainObject {
|
||||
// ----------------------------------------------------------------------------------
|
||||
// JPA-mappings of other tables referencing to this table "thread"
|
||||
// ----------------------------------------------------------------------------------
|
||||
@OneToMany(mappedBy = "forumThread", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
||||
@OneToMany(mappedBy = "forumThread", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
|
||||
private List<ForumPost> posts = new ArrayList<>();
|
||||
|
||||
// ----------------------------------------------------------------------------------
|
||||
// JPA-lifecycle method
|
||||
// ----------------------------------------------------------------------------------
|
||||
@PostLoad
|
||||
void lazyDeletion() {
|
||||
posts.removeIf(LazyDeletableDomainObject::isDeleted);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------
|
||||
// Properties (Getters and Setters)
|
||||
// ----------------------------------------------------------------------------------
|
||||
@ -109,8 +101,8 @@ public class ForumThread extends LazyDeletableDomainObject {
|
||||
posts.add(post);
|
||||
}
|
||||
|
||||
public int getPostCount() {
|
||||
return posts.size();
|
||||
public long getPostCount() {
|
||||
return posts.stream().filter(Predicate.not(ForumPost::isDeleted)).count();
|
||||
}
|
||||
|
||||
public User getCreatedBy() {
|
||||
|
@ -9,6 +9,8 @@ import java.util.function.Function;
|
||||
import se.su.dsv.scipro.forum.ForumPostReadEvent;
|
||||
import se.su.dsv.scipro.forum.NewGroupForumReplyEvent;
|
||||
import se.su.dsv.scipro.forum.NewProjectForumReplyEvent;
|
||||
import se.su.dsv.scipro.forum.ProjectForumPostDeletedEvent;
|
||||
import se.su.dsv.scipro.forum.ProjectForumThreadDeletedEvent;
|
||||
import se.su.dsv.scipro.forum.dataobjects.ForumPost;
|
||||
import se.su.dsv.scipro.notifications.NotificationController;
|
||||
import se.su.dsv.scipro.notifications.NotificationService;
|
||||
@ -95,6 +97,24 @@ public class ForumNotifications {
|
||||
});
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void projectThreadDeleted(ProjectForumThreadDeletedEvent event) {
|
||||
NotificationSource source = new NotificationSource();
|
||||
source.setMessage(event.subject());
|
||||
notificationController.notifyProjectForum(
|
||||
ProjectForumEvent.Event.FORUM_THREAD_DELETED,
|
||||
source,
|
||||
event.project()
|
||||
);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void projectForumPostDeleted(ProjectForumPostDeletedEvent event) {
|
||||
NotificationSource source = new NotificationSource();
|
||||
source.setMessage(event.subject());
|
||||
notificationController.notifyProjectForum(ProjectForumEvent.Event.FORUM_POST_DELETED, source, event.project());
|
||||
}
|
||||
|
||||
private void sendAndConnect(ForumPost post, Function<NotificationSource, Set<Notification>> send) {
|
||||
NotificationSource notificationSource = new NotificationSource();
|
||||
notificationSource.setMessage(String.format("Posted by %s\n\n%s", getPostedBy(post), post.getContent()));
|
||||
|
@ -23,6 +23,8 @@ FORUM.NEW_FORUM_POST_COMMENT.body = {0}
|
||||
FORUM.NEW_REVIEWER_INTERACTION.body = {0}
|
||||
GROUP.MESSAGE_THREAD_CREATED.body = {0}
|
||||
GROUP.MESSAGE_THREAD_REPLY.body = {0}
|
||||
FORUM.FORUM_THREAD_DELETED.title = Forum thread {1} deleted in project {0}
|
||||
FORUM.FORUM_POST_DELETED.title = Reply deleted in thread {1} in project {0}
|
||||
|
||||
PROJECT = \
|
||||
***********************************\n\n\
|
||||
|
@ -17,6 +17,8 @@ public class ProjectForumEvent extends NotificationEvent {
|
||||
NEW_FORUM_POST,
|
||||
NEW_FORUM_POST_COMMENT,
|
||||
NEW_REVIEWER_INTERACTION,
|
||||
FORUM_THREAD_DELETED,
|
||||
FORUM_POST_DELETED,
|
||||
}
|
||||
|
||||
@Basic
|
||||
|
@ -99,6 +99,10 @@ FORUM.NEW_FORUM_POST_COMMENT.title = Forum reply: {2}
|
||||
FORUM.NEW_FORUM_POST_COMMENT.body = New forum reply: {1}<br /><br />{0}
|
||||
FORUM.NEW_REVIEWER_INTERACTION.title = Reviewer interaction updated in project: {0}
|
||||
FORUM.NEW_REVIEWER_INTERACTION.body = New message in reviewer interaction: {1}<br /><br />{0}
|
||||
FORUM.FORUM_THREAD_DELETED.title = Forum thread {1} deleted
|
||||
FORUM.FORUM_THREAD_DELETED.body = The forum thread {0} was deleted.
|
||||
FORUM.FORUM_POST_DELETED.title = Reply deleted in thread {1}
|
||||
FORUM.FORUM_POST_DELETED.body = A reply was deleted in the thread {0}.
|
||||
FORUM.compilationSuffix =
|
||||
|
||||
GROUP.MESSAGE_THREAD_CREATED.title = Group forum: {2}
|
||||
|
@ -1,4 +0,0 @@
|
||||
alter table idea_student
|
||||
add constraint fk_idea_student_program_id
|
||||
foreign key (program_id) references program (id)
|
||||
on update cascade on delete set null;
|
@ -1,198 +0,0 @@
|
||||
package se.su.dsv.scipro.forum;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.AdditionalAnswers;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import se.su.dsv.scipro.forum.dataobjects.ForumPost;
|
||||
import se.su.dsv.scipro.forum.dataobjects.ForumPostReadState;
|
||||
import se.su.dsv.scipro.forum.dataobjects.ForumThread;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
import se.su.dsv.scipro.test.ForumBuilder;
|
||||
import se.su.dsv.scipro.test.UserBuilder;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class BasicForumServiceImplTest {
|
||||
|
||||
@Mock
|
||||
private AbstractThreadRepository threadRepository;
|
||||
|
||||
@Mock
|
||||
private ForumPostReadStateRepository readStateRepository;
|
||||
|
||||
@Mock
|
||||
private ForumPostRepository postRepository;
|
||||
|
||||
@Mock
|
||||
private EventBus eventBus;
|
||||
|
||||
@InjectMocks
|
||||
private BasicForumServiceImpl basicForumService;
|
||||
|
||||
@Test
|
||||
public void testGetPostPageByForumThread() {
|
||||
List<ForumPost> posts = Collections.singletonList(new ForumBuilder().createPost());
|
||||
when(postRepository.findByThread(isA(ForumThread.class))).thenReturn(posts);
|
||||
|
||||
List<ForumPost> servicePage = basicForumService.getPosts(mock(ForumThread.class));
|
||||
|
||||
assertEquals(posts, servicePage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMarkRead() {
|
||||
when(readStateRepository.find(any(User.class), any(ForumPost.class))).thenReturn(new ForumPostReadState());
|
||||
when(readStateRepository.save(isA(ForumPostReadState.class))).thenAnswer(AdditionalAnswers.returnsFirstArg());
|
||||
|
||||
User user = new User();
|
||||
ForumPost post = new ForumPost();
|
||||
|
||||
boolean read = basicForumService.setRead(user, post, true);
|
||||
|
||||
assertTrue(read, "Did not return proper read state");
|
||||
|
||||
ArgumentCaptor<ForumPostReadState> captor = ArgumentCaptor.forClass(ForumPostReadState.class);
|
||||
verify(readStateRepository, times(1)).save(captor.capture());
|
||||
|
||||
assertTrue(captor.getValue().isRead(), "Did not save correct read state");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMarkUnread() {
|
||||
when(readStateRepository.find(any(User.class), any(ForumPost.class))).thenReturn(new ForumPostReadState());
|
||||
when(readStateRepository.save(isA(ForumPostReadState.class))).thenAnswer(AdditionalAnswers.returnsFirstArg());
|
||||
|
||||
// when
|
||||
User user = new User();
|
||||
ForumPost post = new ForumPost();
|
||||
|
||||
// when
|
||||
boolean read = basicForumService.setRead(user, post, false);
|
||||
|
||||
// then
|
||||
assertFalse(read, "Did not return proper read state");
|
||||
|
||||
ArgumentCaptor<ForumPostReadState> captor = ArgumentCaptor.forClass(ForumPostReadState.class);
|
||||
verify(readStateRepository, times(1)).save(captor.capture());
|
||||
|
||||
assertFalse(captor.getValue().isRead(), "Did not save correct read state");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMarkThreadReadPostsEvent() {
|
||||
User user = new User();
|
||||
ForumPost post = new ForumPost();
|
||||
post.setContent("post 1");
|
||||
ForumPost post2 = new ForumPost();
|
||||
post2.setContent("post 2");
|
||||
ForumThread forumThread = new ForumThread();
|
||||
forumThread.addPost(post);
|
||||
forumThread.addPost(post2);
|
||||
|
||||
basicForumService.setThreadRead(user, forumThread, true);
|
||||
|
||||
verify(eventBus).post(new ForumPostReadEvent(post, user));
|
||||
verify(eventBus).post(new ForumPostReadEvent(post2, user));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsThreadRead() {
|
||||
User goodUser = new UserBuilder().setFirstName("Reads").setLastName("Forum").create();
|
||||
User badUser = new UserBuilder().setFirstName("Does not read").setLastName("Forum").create();
|
||||
|
||||
ForumPost post = new ForumPost();
|
||||
ForumPostReadState readState = new ForumPostReadState(goodUser, post);
|
||||
readState.setRead(true);
|
||||
|
||||
ForumPostReadState notReadState = new ForumPostReadState(badUser, post);
|
||||
notReadState.setRead(false);
|
||||
|
||||
ForumThread forumThread = new ForumThread();
|
||||
forumThread.addPost(post);
|
||||
|
||||
when(postRepository.findByThread(forumThread)).thenReturn(List.of(post));
|
||||
|
||||
when(readStateRepository.find(eq(goodUser), isA(ForumPost.class))).thenReturn(readState);
|
||||
when(readStateRepository.find(eq(badUser), isA(ForumPost.class))).thenReturn(notReadState);
|
||||
|
||||
boolean goodUserState = basicForumService.isThreadRead(goodUser, forumThread);
|
||||
boolean badUserState = basicForumService.isThreadRead(badUser, forumThread);
|
||||
|
||||
assertTrue(goodUserState, "Good user has not read all thread posts");
|
||||
assertFalse(badUserState, "Bad user has read all thread posts");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void create_forum_thread() {
|
||||
when(threadRepository.save(any(ForumThread.class))).thenAnswer(AdditionalAnswers.returnsFirstArg());
|
||||
final String subject = "Subject";
|
||||
|
||||
ForumThread thread = basicForumService.createThread(subject);
|
||||
|
||||
assertThat(thread.getSubject(), is(subject));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void reply_to_thread() {
|
||||
when(readStateRepository.find(any(User.class), any(ForumPost.class))).thenReturn(new ForumPostReadState());
|
||||
when(readStateRepository.save(isA(ForumPostReadState.class))).thenAnswer(AdditionalAnswers.returnsFirstArg());
|
||||
when(threadRepository.save(any(ForumThread.class))).thenAnswer(AdditionalAnswers.returnsFirstArg());
|
||||
when(postRepository.save(any(ForumPost.class))).thenAnswer(AdditionalAnswers.returnsFirstArg());
|
||||
|
||||
final ForumThread forumThread = new ForumThread();
|
||||
final User poster = User.builder().firstName("Bob").lastName("Example").emailAddress("bob@example.com").build();
|
||||
final String content = "content";
|
||||
|
||||
ForumPost forumPost = basicForumService.createReply(forumThread, poster, content, Collections.emptySet());
|
||||
|
||||
assertThat(forumPost.getContent(), is(content));
|
||||
assertThat(forumPost.getPostedBy(), is(poster));
|
||||
assertThat(forumPost.getForumThread(), is(forumThread));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mark_post_read_posts_event() {
|
||||
when(readStateRepository.find(any(User.class), any(ForumPost.class))).thenReturn(new ForumPostReadState());
|
||||
when(readStateRepository.save(isA(ForumPostReadState.class))).thenAnswer(AdditionalAnswers.returnsFirstArg());
|
||||
|
||||
final ForumPost post = new ForumPost();
|
||||
post.setId(235235L);
|
||||
final User user = new User();
|
||||
user.setId(2378924L);
|
||||
|
||||
basicForumService.setRead(user, post, true);
|
||||
|
||||
ArgumentCaptor<ForumPostReadEvent> captor = ArgumentCaptor.forClass(ForumPostReadEvent.class);
|
||||
verify(eventBus).post(captor.capture());
|
||||
|
||||
ForumPostReadEvent event = captor.getValue();
|
||||
assertEquals(event.post(), post);
|
||||
assertEquals(event.user(), user);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mark_post_unread_does_not_post_read_event() {
|
||||
when(readStateRepository.find(any(User.class), any(ForumPost.class))).thenReturn(new ForumPostReadState());
|
||||
when(readStateRepository.save(isA(ForumPostReadState.class))).thenAnswer(AdditionalAnswers.returnsFirstArg());
|
||||
|
||||
final ForumPost post = new ForumPost();
|
||||
post.setId(235235L);
|
||||
final User user = new User();
|
||||
user.setId(2378924L);
|
||||
|
||||
basicForumService.setRead(user, post, false);
|
||||
|
||||
verify(eventBus, never()).post(isA(ForumPostReadEvent.class));
|
||||
}
|
||||
}
|
@ -1,11 +1,17 @@
|
||||
package se.su.dsv.scipro.forum;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.hasItem;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@ -80,4 +86,157 @@ public class BasicForumServiceIntegrationTest extends IntegrationTest {
|
||||
assertTrue(basicForumService.canDelete(secondReply));
|
||||
assertDoesNotThrow(() -> basicForumService.deletePost(secondReply));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPostPageByForumThread() {
|
||||
ForumThread thread = basicForumService.createThread("Test thread");
|
||||
ForumPost post1 = basicForumService.createReply(thread, op, "Test post 1", Set.of());
|
||||
ForumPost post2 = basicForumService.createReply(thread, commenter, "Test post 2", Set.of());
|
||||
|
||||
List<ForumPost> posts = basicForumService.getPosts(thread);
|
||||
|
||||
assertThat(posts, contains(post1, post2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMarkRead() {
|
||||
ForumThread thread = basicForumService.createThread("Test thread");
|
||||
ForumPost post = basicForumService.createReply(thread, op, "Test post 1", Set.of());
|
||||
|
||||
boolean read = basicForumService.setRead(commenter, post, true);
|
||||
|
||||
assertTrue(read, "Did not return proper read state");
|
||||
|
||||
boolean isRead = basicForumService.isRead(commenter, post);
|
||||
|
||||
assertTrue(isRead, "Did not save correct read state");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMarkUnread() {
|
||||
ForumThread thread = basicForumService.createThread("Test thread");
|
||||
ForumPost post = basicForumService.createReply(thread, op, "Test post 1", Set.of());
|
||||
|
||||
boolean read = basicForumService.setRead(op, post, false);
|
||||
|
||||
assertFalse(read, "Did not return proper read state");
|
||||
|
||||
boolean isRead = basicForumService.isRead(commenter, post);
|
||||
|
||||
assertFalse(isRead, "Did not save correct read state");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMarkThreadReadPostsEvent() {
|
||||
ForumThread thread = basicForumService.createThread("Test thread");
|
||||
ForumPost post1 = basicForumService.createReply(thread, op, "Test post 1", Set.of());
|
||||
ForumPost post2 = basicForumService.createReply(thread, op, "Test post 2", Set.of());
|
||||
|
||||
basicForumService.setThreadRead(commenter, thread, true);
|
||||
|
||||
assertThat(getPublishedEvents(), hasItem(new ForumPostReadEvent(post1, commenter)));
|
||||
assertThat(getPublishedEvents(), hasItem(new ForumPostReadEvent(post2, commenter)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsThreadRead() {
|
||||
ForumThread thread = basicForumService.createThread("Test thread");
|
||||
basicForumService.createReply(thread, op, "Test post 1", Set.of());
|
||||
|
||||
basicForumService.setThreadRead(commenter, thread, true);
|
||||
|
||||
basicForumService.createReply(thread, commenter, "Test post 2", Set.of());
|
||||
|
||||
boolean goodUserState = basicForumService.isThreadRead(commenter, thread);
|
||||
boolean badUserState = basicForumService.isThreadRead(op, thread);
|
||||
|
||||
assertTrue(goodUserState, "Good user has not read all thread posts");
|
||||
assertFalse(badUserState, "Bad user has read all thread posts");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mark_post_read_posts_event() {
|
||||
ForumThread thread = basicForumService.createThread("Test thread");
|
||||
ForumPost post = basicForumService.createReply(thread, op, "Test post 1", Set.of());
|
||||
|
||||
basicForumService.setRead(commenter, post, true);
|
||||
|
||||
assertThat(getPublishedEvents(), hasItem(new ForumPostReadEvent(post, commenter)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mark_post_unread_does_not_post_read_event() {
|
||||
ForumThread thread = basicForumService.createThread("Test thread");
|
||||
ForumPost post = basicForumService.createReply(thread, op, "Test post 1", Set.of());
|
||||
|
||||
basicForumService.setRead(commenter, post, false);
|
||||
|
||||
assertThat(getPublishedEvents(), not(hasItem(new ForumPostReadEvent(post, commenter))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void can_delete_own_thread_without_replies() {
|
||||
ForumThread thread = basicForumService.createThread("Test thread");
|
||||
basicForumService.createReply(thread, op, "Test post 1", Set.of());
|
||||
|
||||
setLoggedInAs(op);
|
||||
|
||||
assertTrue(basicForumService.canDelete(thread));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cant_delete_others_threads_without_replies() {
|
||||
ForumThread thread = basicForumService.createThread("Test thread");
|
||||
basicForumService.createReply(thread, op, "Test post 1", Set.of());
|
||||
|
||||
setLoggedInAs(commenter);
|
||||
|
||||
assertFalse(basicForumService.canDelete(thread));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void system_can_delete_all_threads() {
|
||||
ForumThread thread = basicForumService.createThread("Test thread");
|
||||
basicForumService.createReply(thread, op, "Test post 1", Set.of());
|
||||
|
||||
setLoggedInAs(null);
|
||||
|
||||
assertTrue(basicForumService.canDelete(thread));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void can_not_delete_thread_with_replies() {
|
||||
ForumThread thread = basicForumService.createThread("Test thread");
|
||||
basicForumService.createReply(thread, op, "Test post 1", Set.of());
|
||||
basicForumService.createReply(thread, commenter, "Test post 2", Set.of());
|
||||
|
||||
setLoggedInAs(op);
|
||||
|
||||
assertFalse(basicForumService.canDelete(thread));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleting_thread_removes_it() {
|
||||
ForumThread thread = basicForumService.createThread("Test thread");
|
||||
basicForumService.createReply(thread, op, "Test post 1", Set.of());
|
||||
|
||||
setLoggedInAs(op);
|
||||
|
||||
assertTrue(basicForumService.canDelete(thread));
|
||||
assertDoesNotThrow(() -> basicForumService.deleteThread(thread));
|
||||
|
||||
ForumThread threadById = basicForumService.findThreadById(thread.getId());
|
||||
assertNull(threadById, "Thread still exists");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void trying_to_delete_thread_with_replies_throws() {
|
||||
ForumThread thread = basicForumService.createThread("Test thread");
|
||||
basicForumService.createReply(thread, op, "Test post 1", Set.of());
|
||||
basicForumService.createReply(thread, commenter, "Test post 2", Set.of());
|
||||
|
||||
setLoggedInAs(op);
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> basicForumService.deleteThread(thread));
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
services:
|
||||
oauth2:
|
||||
container_name: scipro-dev-oauth2
|
||||
build: https://gitea.dsv.su.se/DMC/oauth2-authorization-server.git#1d469c73468d00be5430dac01a7ab84f11ed471a
|
||||
build:
|
||||
context: https://github.com/dsv-su/toker.git
|
||||
dockerfile: embedded.Dockerfile
|
||||
restart: on-failure
|
||||
ports:
|
||||
- '59733:8080'
|
||||
@ -9,8 +11,12 @@ services:
|
||||
- CLIENT_ID=get-token
|
||||
- CLIENT_SECRET=get-token-secret
|
||||
- CLIENT_REDIRECT_URI=http://localhost:59732/
|
||||
- RESOURCE_SERVER_ID=scipro-api-client
|
||||
- RESOURCE_SERVER_SECRET=scipro-api-secret
|
||||
oauth2-wicket:
|
||||
build: https://gitea.dsv.su.se/DMC/oauth2-authorization-server.git#1d469c73468d00be5430dac01a7ab84f11ed471a
|
||||
build:
|
||||
context: https://github.com/dsv-su/toker.git
|
||||
dockerfile: embedded.Dockerfile
|
||||
restart: on-failure
|
||||
ports:
|
||||
- '59734:8080'
|
||||
@ -18,4 +24,3 @@ services:
|
||||
- CLIENT_ID=scipro
|
||||
- CLIENT_SECRET=s3cr3t
|
||||
- CLIENT_REDIRECT_URI=http://localhost:8080/login/oauth2/code/scipro
|
||||
- CLIENT_SCOPES=openid
|
||||
|
53
test-data/src/main/java/se/su/dsv/scipro/testdata/populators/ProjectLevelTitleCredits.java
vendored
53
test-data/src/main/java/se/su/dsv/scipro/testdata/populators/ProjectLevelTitleCredits.java
vendored
@ -1,53 +0,0 @@
|
||||
package se.su.dsv.scipro.testdata.populators;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.Date;
|
||||
import org.springframework.stereotype.Service;
|
||||
import se.su.dsv.scipro.finalseminar.FinalSeminar;
|
||||
import se.su.dsv.scipro.finalseminar.FinalSeminarService;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.project.ProjectService;
|
||||
import se.su.dsv.scipro.system.Language;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
import se.su.dsv.scipro.testdata.BaseData;
|
||||
import se.su.dsv.scipro.testdata.Factory;
|
||||
import se.su.dsv.scipro.testdata.TestDataPopulator;
|
||||
|
||||
@Service
|
||||
public class ProjectLevelTitleCredits implements TestDataPopulator {
|
||||
|
||||
private final ProjectService projectService;
|
||||
private final FinalSeminarService finalSeminarService;
|
||||
|
||||
public ProjectLevelTitleCredits(ProjectService projectService, FinalSeminarService finalSeminarService) {
|
||||
this.projectService = projectService;
|
||||
this.finalSeminarService = finalSeminarService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void populate(BaseData baseData, Factory factory) {
|
||||
// Participants
|
||||
User author = factory.createAuthor("Beata");
|
||||
User headSupervisor = factory.createSupervisor("Elsabet");
|
||||
|
||||
// Project
|
||||
Project project = new Project();
|
||||
project.setProjectType(baseData.bachelor());
|
||||
project.setTitle("A bachelor thesis");
|
||||
project.setCredits(15);
|
||||
project.setResearchArea(baseData.researchArea().researchArea());
|
||||
project.setHeadSupervisor(headSupervisor);
|
||||
project.addProjectParticipant(author);
|
||||
project.setStartDate(LocalDate.now().minusDays(1));
|
||||
|
||||
projectService.save(project);
|
||||
|
||||
// Seminar
|
||||
FinalSeminar finalSeminar = new FinalSeminar();
|
||||
finalSeminar.setProject(project);
|
||||
finalSeminar.setStartDate(new Date());
|
||||
finalSeminar.setRoom("Cyber Space");
|
||||
finalSeminar.setPresentationLanguage(Language.SWEDISH);
|
||||
finalSeminarService.save(finalSeminar);
|
||||
}
|
||||
}
|
@ -2,11 +2,7 @@
|
||||
<html xmlns:wicket="http://wicket.apache.org">
|
||||
<body>
|
||||
<wicket:panel>
|
||||
<h3>Final seminar for project:
|
||||
<span wicket:id="projectTitle">[Title]</span> (<span wicket:id="projectType"></span><wicket:enclosure>,
|
||||
<span wicket:id="credits">30</span> hec</wicket:enclosure>)
|
||||
</h3>
|
||||
|
||||
<h3>Final seminar for project: <span wicket:id="projectTitle">[Title]</span></h3>
|
||||
<br>
|
||||
|
||||
<div class="row">
|
||||
|
@ -1,7 +1,7 @@
|
||||
package se.su.dsv.scipro.finalseminar;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Date;
|
||||
import java.util.*;
|
||||
import org.apache.wicket.markup.html.WebMarkupContainer;
|
||||
import org.apache.wicket.markup.html.basic.Label;
|
||||
import org.apache.wicket.markup.html.link.ExternalLink;
|
||||
@ -18,8 +18,6 @@ import se.su.dsv.scipro.system.User;
|
||||
public class SeminarPanel extends Panel {
|
||||
|
||||
public static final String PROJECT_TITLE = "projectTitle";
|
||||
static final String PROJECT_TYPE = "projectType";
|
||||
static final String CREDITS = "credits";
|
||||
static final String CANCELLED = "cancelled";
|
||||
static final String CRUD = "crud";
|
||||
static final String CRUD_NOT_ALLOWED = "noCrud";
|
||||
@ -71,16 +69,6 @@ public class SeminarPanel extends Panel {
|
||||
);
|
||||
|
||||
add(new Label(PROJECT_TITLE, seminar.map(FinalSeminar::getProject).map(Project::getTitle)));
|
||||
add(new Label(PROJECT_TYPE, seminar.map(FinalSeminar::getProject).map(Project::getProjectTypeName)));
|
||||
add(
|
||||
new Label(CREDITS, seminar.map(FinalSeminar::getProject).map(Project::getCredits)) {
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
setVisible(seminar.getObject().getProject().getCredits() > 0);
|
||||
}
|
||||
}
|
||||
);
|
||||
add(new ScheduleFinalSeminarPanel("schedule", getProject()));
|
||||
add(new SeminarCRUDPanel(CRUD, seminar));
|
||||
|
||||
|
@ -6,8 +6,23 @@ import se.su.dsv.scipro.forum.dataobjects.ForumPost;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
|
||||
public interface ForumThread<A> extends IClusterable {
|
||||
List<ForumPost> getPosts(A a);
|
||||
/**
|
||||
* @param a the context of the thread
|
||||
* @return the initial post of the thread
|
||||
*/
|
||||
Optional<ForumPost> getInitialPost(A a);
|
||||
|
||||
List<ForumPost> getReplies(A a);
|
||||
|
||||
ForumPost reply(A a, User poster, String content, Set<Attachment> attachments);
|
||||
String getSubject(A a);
|
||||
void setRead(User user, A a);
|
||||
|
||||
boolean canDelete(A a, ForumPost post);
|
||||
|
||||
void deletePost(A a, ForumPost post);
|
||||
|
||||
boolean canDeleteThread(A a);
|
||||
|
||||
void deleteThread(A a);
|
||||
}
|
||||
|
@ -40,6 +40,8 @@ public class ProjectViewForumThreadPage
|
||||
@Inject
|
||||
private ProjectFileService projectFileService;
|
||||
|
||||
private final IModel<ProjectThread> threadModel;
|
||||
|
||||
public ProjectViewForumThreadPage(PageParameters pp) {
|
||||
super(pp);
|
||||
StringValue value = pp.get(PageParameterKeys.MAP.get(ForumThread.class));
|
||||
@ -63,7 +65,7 @@ public class ProjectViewForumThreadPage
|
||||
basicForumService.setThreadRead(SciProSession.get().getUser(), thread.getForumThread(), true);
|
||||
|
||||
final Long finalThreadId = threadId;
|
||||
IModel<ProjectThread> threadModel = new LoadableDetachableModel<>() {
|
||||
threadModel = new LoadableDetachableModel<>() {
|
||||
@Override
|
||||
protected ProjectThread load() {
|
||||
return projectForumService.findOne(finalThreadId);
|
||||
@ -79,5 +81,16 @@ public class ProjectViewForumThreadPage
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
|
||||
// Check for thread deletion
|
||||
if (threadModel.getObject() == null) {
|
||||
PageParameters pageParameters = ProjectThreadedForumPage.getPageParameters(getActiveProject());
|
||||
throw new RestartResponseException(ProjectThreadedForumPage.class, pageParameters);
|
||||
}
|
||||
}
|
||||
|
||||
static final String FORUM_THREAD = "forumThread";
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package se.su.dsv.scipro.forum.pages.threaded;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import org.apache.wicket.Page;
|
||||
import org.apache.wicket.RestartResponseException;
|
||||
import org.apache.wicket.model.IModel;
|
||||
import org.apache.wicket.model.LoadableDetachableModel;
|
||||
import org.apache.wicket.request.mapper.parameter.PageParameters;
|
||||
@ -40,6 +41,8 @@ public class SupervisorViewForumThreadPage
|
||||
@Inject
|
||||
private ProjectFileService projectFileService;
|
||||
|
||||
private final IModel<ProjectThread> threadModel;
|
||||
|
||||
public SupervisorViewForumThreadPage(PageParameters pp) {
|
||||
super(pp);
|
||||
StringValue value = pp.get(PageParameterKeys.MAP.get(ForumThread.class));
|
||||
@ -59,7 +62,7 @@ public class SupervisorViewForumThreadPage
|
||||
basicForumService.setThreadRead(SciProSession.get().getUser(), thread.getForumThread(), true);
|
||||
|
||||
final Long finalThreadId = threadId;
|
||||
final IModel<ProjectThread> threadModel = new LoadableDetachableModel<>() {
|
||||
threadModel = new LoadableDetachableModel<>() {
|
||||
@Override
|
||||
protected ProjectThread load() {
|
||||
return projectForumService.findOne(finalThreadId);
|
||||
@ -72,6 +75,14 @@ public class SupervisorViewForumThreadPage
|
||||
new ProjectForumThread(projectForumService),
|
||||
projectModel
|
||||
) {
|
||||
@Override
|
||||
protected void onThreadDeleted() {
|
||||
setResponsePage(
|
||||
SupervisorForumBasePage.class,
|
||||
SupervisorForumBasePage.getPageParameters(projectModel.getObject())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<? extends Page> getForumPage() {
|
||||
return SupervisorForumBasePage.class;
|
||||
@ -80,5 +91,16 @@ public class SupervisorViewForumThreadPage
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
|
||||
// Check for thread deletion
|
||||
if (threadModel.getObject() == null) {
|
||||
PageParameters pageParameters = SupervisorThreadedForumPage.getPageParameters(projectModel.getObject());
|
||||
throw new RestartResponseException(SupervisorThreadedForumPage.class, pageParameters);
|
||||
}
|
||||
}
|
||||
|
||||
static final String FORUM_THREAD = "forumThread";
|
||||
}
|
||||
|
@ -5,12 +5,12 @@
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
<wicket:panel>
|
||||
<wicket:border>
|
||||
<div class="messageWrap">
|
||||
<div class="forumBlueBackground d-flex justify-content-between">
|
||||
<!-- DATE ROW-->
|
||||
<span wicket:id="dateCreated"></span>
|
||||
<button wicket:id="delete" class="btn btn-sm btn-outline-danger ms-auto">Delete</button>
|
||||
<wicket:body/>
|
||||
</div>
|
||||
<div class="forumGrayBackground">
|
||||
<div class="vertAlign">
|
||||
@ -29,6 +29,6 @@
|
||||
</wicket:enclosure>
|
||||
</div>
|
||||
</div>
|
||||
</wicket:panel>
|
||||
</wicket:border>
|
||||
</body>
|
||||
</html>
|
@ -1,10 +1,8 @@
|
||||
package se.su.dsv.scipro.forum.panels.threaded;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import org.apache.wicket.Component;
|
||||
import org.apache.wicket.markup.html.WebMarkupContainer;
|
||||
import org.apache.wicket.markup.html.link.Link;
|
||||
import org.apache.wicket.markup.html.panel.Panel;
|
||||
import org.apache.wicket.markup.html.border.Border;
|
||||
import org.apache.wicket.model.IModel;
|
||||
import org.apache.wicket.model.LambdaModel;
|
||||
import se.su.dsv.scipro.components.DateLabel;
|
||||
@ -13,26 +11,23 @@ import se.su.dsv.scipro.components.ListAdapterModel;
|
||||
import se.su.dsv.scipro.components.SmarterLinkMultiLineLabel;
|
||||
import se.su.dsv.scipro.data.enums.DateStyle;
|
||||
import se.su.dsv.scipro.file.FileReference;
|
||||
import se.su.dsv.scipro.forum.BasicForumService;
|
||||
import se.su.dsv.scipro.forum.dataobjects.ForumPost;
|
||||
import se.su.dsv.scipro.profile.UserLinkPanel;
|
||||
import se.su.dsv.scipro.repository.panels.ViewAttachmentPanel;
|
||||
import se.su.dsv.scipro.session.SciProSession;
|
||||
|
||||
public class ForumPostPanel extends Panel {
|
||||
public class ForumPostPanel extends Border {
|
||||
|
||||
public static final String POSTED_BY = "postedBy";
|
||||
public static final String DATE_CREATED = "dateCreated";
|
||||
public static final String CONTENT = "content";
|
||||
public static final String ATTACHMENT = "attachment";
|
||||
|
||||
@Inject
|
||||
private BasicForumService basicForumService;
|
||||
|
||||
public ForumPostPanel(String id, final IModel<ForumPost> model) {
|
||||
super(id);
|
||||
add(new UserLinkPanel(POSTED_BY, LambdaModel.of(model, ForumPost::getPostedBy, ForumPost::setPostedBy)));
|
||||
add(
|
||||
super(id, model);
|
||||
addToBorder(
|
||||
new UserLinkPanel(POSTED_BY, LambdaModel.of(model, ForumPost::getPostedBy, ForumPost::setPostedBy))
|
||||
);
|
||||
addToBorder(
|
||||
new WebMarkupContainer("postedBySystem") {
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
@ -41,18 +36,18 @@ public class ForumPostPanel extends Panel {
|
||||
}
|
||||
}
|
||||
);
|
||||
add(
|
||||
addToBorder(
|
||||
new DateLabel(
|
||||
DATE_CREATED,
|
||||
LambdaModel.of(model, ForumPost::getDateCreated, ForumPost::setDateCreated),
|
||||
DateStyle.DATETIME
|
||||
)
|
||||
);
|
||||
add(
|
||||
addToBorder(
|
||||
new SmarterLinkMultiLineLabel(CONTENT, LambdaModel.of(model, ForumPost::getContent, ForumPost::setContent))
|
||||
);
|
||||
|
||||
add(
|
||||
addToBorder(
|
||||
new DisplayMultiplesPanel<>(
|
||||
ATTACHMENT,
|
||||
new ListAdapterModel<>(LambdaModel.of(model, ForumPost::getAttachments, ForumPost::setAttachments))
|
||||
@ -69,28 +64,5 @@ public class ForumPostPanel extends Panel {
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
add(
|
||||
new Link<>("delete", model) {
|
||||
@Override
|
||||
public void onClick() {
|
||||
ForumPost post = getModelObject();
|
||||
basicForumService.deletePost(post);
|
||||
onPostDeleted();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
setVisible(allowDeletion() && basicForumService.canDelete(getModelObject()));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected boolean allowDeletion() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void onPostDeleted() {}
|
||||
}
|
||||
|
@ -17,8 +17,17 @@ public class ProjectForumThread implements ForumThread<ProjectThread> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ForumPost> getPosts(final ProjectThread projectThread) {
|
||||
return projectForumService.getPosts(projectThread);
|
||||
public Optional<ForumPost> getInitialPost(ProjectThread projectThread) {
|
||||
return projectForumService.getPosts(projectThread).stream().findFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ForumPost> getReplies(final ProjectThread projectThread) {
|
||||
List<ForumPost> posts = projectForumService.getPosts(projectThread);
|
||||
if (posts.size() <= 1) {
|
||||
return List.of();
|
||||
}
|
||||
return posts.subList(1, posts.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -40,4 +49,24 @@ public class ProjectForumThread implements ForumThread<ProjectThread> {
|
||||
public void setRead(User user, ProjectThread thread) {
|
||||
projectForumService.markRead(user, thread);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDelete(ProjectThread projectThread, ForumPost post) {
|
||||
return projectForumService.canDelete(projectThread, post);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePost(ProjectThread projectThread, ForumPost post) {
|
||||
projectForumService.deletePost(projectThread, post);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDeleteThread(ProjectThread projectThread) {
|
||||
return projectForumService.canDelete(projectThread);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteThread(ProjectThread projectThread) {
|
||||
projectForumService.deleteThread(projectThread);
|
||||
}
|
||||
}
|
||||
|
@ -19,8 +19,13 @@
|
||||
<div wicket:id="topReplyPanel"></div>
|
||||
<!-- POST LIST-->
|
||||
<div class="d-flex flex-column-reverse">
|
||||
<wicket:container wicket:id="initialPost">
|
||||
<button wicket:id="delete" class="btn btn-sm btn-outline-danger ms-auto">Delete</button>
|
||||
</wicket:container>
|
||||
<wicket:container wicket:id="postList">
|
||||
<wicket:container wicket:id="post"/>
|
||||
<wicket:container wicket:id="post">
|
||||
<button wicket:id="delete" class="btn btn-sm btn-outline-danger ms-auto">Delete</button>
|
||||
</wicket:container>
|
||||
</wicket:container>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,5 +1,6 @@
|
||||
package se.su.dsv.scipro.forum.panels.threaded;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import org.apache.wicket.Component;
|
||||
@ -7,11 +8,13 @@ import org.apache.wicket.ajax.AjaxRequestTarget;
|
||||
import org.apache.wicket.ajax.markup.html.AjaxLink;
|
||||
import org.apache.wicket.markup.html.WebMarkupContainer;
|
||||
import org.apache.wicket.markup.html.basic.Label;
|
||||
import org.apache.wicket.markup.html.link.Link;
|
||||
import org.apache.wicket.markup.html.list.ListItem;
|
||||
import org.apache.wicket.markup.html.list.ListView;
|
||||
import org.apache.wicket.markup.html.panel.GenericPanel;
|
||||
import org.apache.wicket.model.IModel;
|
||||
import org.apache.wicket.model.LoadableDetachableModel;
|
||||
import se.su.dsv.scipro.components.OrNullModel;
|
||||
import se.su.dsv.scipro.forum.ForumThread;
|
||||
import se.su.dsv.scipro.forum.dataobjects.ForumPost;
|
||||
import se.su.dsv.scipro.session.SciProSession;
|
||||
@ -20,10 +23,9 @@ public class ViewForumThreadPanel<A> extends GenericPanel<A> {
|
||||
|
||||
private final ForumThread<A> forumThread;
|
||||
private final WebMarkupContainer wrapper;
|
||||
private SubmitForumReplyPanel topReplyPanel;
|
||||
private SubmitForumReplyPanel<A> topReplyPanel;
|
||||
private AjaxLink<Void> topReplyLink;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public ViewForumThreadPanel(String id, final IModel<A> model, final ForumThread<A> forumThread) {
|
||||
super(id, model);
|
||||
this.forumThread = forumThread;
|
||||
@ -54,30 +56,66 @@ public class ViewForumThreadPanel<A> extends GenericPanel<A> {
|
||||
}
|
||||
|
||||
private void addPostList() {
|
||||
ForumPostPanel initialPostPanel = new ForumPostPanel(
|
||||
"initialPost",
|
||||
new OrNullModel<>(getModel().map(forumThread::getInitialPost))
|
||||
) {
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
setVisible(getDefaultModelObject() != null);
|
||||
}
|
||||
};
|
||||
initialPostPanel.add(
|
||||
new Link<>("delete", getModel()) {
|
||||
@Override
|
||||
public void onClick() {
|
||||
forumThread.deleteThread(getModelObject());
|
||||
onThreadDeleted();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
setVisible(forumThread.canDeleteThread(getModelObject()));
|
||||
}
|
||||
}
|
||||
);
|
||||
wrapper.add(initialPostPanel);
|
||||
wrapper.add(
|
||||
new ListView<>(POST_LIST, new PostProvider()) {
|
||||
@Override
|
||||
protected void populateItem(ListItem<ForumPost> item) {
|
||||
ListView<ForumPost> listView = this;
|
||||
item.add(
|
||||
new ForumPostPanel(POST, item.getModel()) {
|
||||
ForumPostPanel forumPostPanel = new ForumPostPanel(POST, item.getModel());
|
||||
forumPostPanel.add(
|
||||
new Link<>("delete", item.getModel()) {
|
||||
@Override
|
||||
protected boolean allowDeletion() {
|
||||
return true;
|
||||
public void onClick() {
|
||||
ForumPost post = getModelObject();
|
||||
forumThread.deletePost(ViewForumThreadPanel.this.getModelObject(), post);
|
||||
listView.detach();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostDeleted() {
|
||||
// Refresh the list of posts
|
||||
listView.detach();
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
setVisible(
|
||||
forumThread.canDelete(ViewForumThreadPanel.this.getModelObject(), getModelObject())
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
item.add(forumPostPanel);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected void onThreadDeleted() {
|
||||
// callback when a thread is deleted
|
||||
}
|
||||
|
||||
private void addReplyButtons() {
|
||||
topReplyLink = new AjaxLink<>(TOP_REPLY_LINK) {
|
||||
@Override
|
||||
@ -118,9 +156,10 @@ public class ViewForumThreadPanel<A> extends GenericPanel<A> {
|
||||
|
||||
@Override
|
||||
protected List<ForumPost> load() {
|
||||
List<ForumPost> posts = forumThread.getPosts(getModelObject());
|
||||
posts.sort(Comparator.comparing(ForumPost::getDateCreated));
|
||||
return posts;
|
||||
List<ForumPost> posts = forumThread.getReplies(getModelObject());
|
||||
ArrayList<ForumPost> sortedPosts = new ArrayList<>(posts);
|
||||
sortedPosts.sort(Comparator.comparing(ForumPost::getDateCreated));
|
||||
return sortedPosts;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,11 @@ public class ViewProjectForumThreadPanel extends ViewForumThreadPanel<ProjectThr
|
||||
return new BookmarkablePageLink<Void>(id, getForumPage(), getProjectParameters(projectModel.getObject()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onThreadDeleted() {
|
||||
setResponsePage(getForumPage(), getProjectParameters(projectModel.getObject()));
|
||||
}
|
||||
|
||||
protected Class<? extends Page> getForumPage() {
|
||||
return ProjectForumBasePage.class;
|
||||
}
|
||||
|
@ -17,8 +17,17 @@ public class GroupForumThread implements ForumThread<GroupThread> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ForumPost> getPosts(final GroupThread groupThread) {
|
||||
return groupForumService.getPosts(groupThread);
|
||||
public Optional<ForumPost> getInitialPost(GroupThread groupThread) {
|
||||
return groupForumService.getPosts(groupThread).stream().findFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ForumPost> getReplies(final GroupThread groupThread) {
|
||||
List<ForumPost> posts = groupForumService.getPosts(groupThread);
|
||||
if (posts.size() <= 1) {
|
||||
return List.of();
|
||||
}
|
||||
return posts.subList(1, posts.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -40,4 +49,24 @@ public class GroupForumThread implements ForumThread<GroupThread> {
|
||||
public void setRead(User user, GroupThread groupThread) {
|
||||
groupForumService.markRead(user, groupThread);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDelete(GroupThread groupThread, ForumPost post) {
|
||||
return groupForumService.canDelete(groupThread, post);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePost(GroupThread groupThread, ForumPost post) {
|
||||
groupForumService.deletePost(groupThread, post);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDeleteThread(GroupThread groupThread) {
|
||||
return groupForumService.canDeleteThread(groupThread);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteThread(GroupThread groupThread) {
|
||||
groupForumService.deleteThread(groupThread);
|
||||
}
|
||||
}
|
||||
|
@ -56,6 +56,13 @@ public class ViewThreadPage extends AbstractAuthorGroupPage implements MenuHighl
|
||||
);
|
||||
return new BookmarkablePageLink<Void>(id, AuthorGroupPage.class, pageParameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onThreadDeleted() {
|
||||
PageParameters pageParameters = new PageParameters();
|
||||
pageParameters.set(PageParameterKeys.MAP.get(Group.class), getGroup().getObject().getId());
|
||||
setResponsePage(AuthorGroupPage.class, pageParameters);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -340,7 +340,7 @@ public class NotificationLandingPage extends WebPage {
|
||||
pp.set(PageParameterKeys.MAP.get(Project.class), project.getId());
|
||||
|
||||
switch (projectForumEvent.getEvent()) {
|
||||
case NEW_FORUM_POST, NEW_FORUM_POST_COMMENT:
|
||||
case NEW_FORUM_POST, NEW_FORUM_POST_COMMENT, FORUM_POST_DELETED, FORUM_THREAD_DELETED:
|
||||
roleSplit(currentUser, project, ProjectForumBasePage.class, SupervisorForumBasePage.class, pp);
|
||||
break;
|
||||
case NEW_REVIEWER_INTERACTION:
|
||||
|
@ -18,7 +18,12 @@ public class ReviewerInteractionForumThread implements ForumThread<Project>, ICl
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ForumPost> getPosts(final Project project) {
|
||||
public Optional<ForumPost> getInitialPost(Project project) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ForumPost> getReplies(final Project project) {
|
||||
return reviewerInteractionService.getPosts(project);
|
||||
}
|
||||
|
||||
@ -41,4 +46,24 @@ public class ReviewerInteractionForumThread implements ForumThread<Project>, ICl
|
||||
public void setRead(User user, Project project) {
|
||||
reviewerInteractionService.markAllRead(user, project);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDelete(Project project, ForumPost post) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePost(Project project, ForumPost post) {
|
||||
throw new UnsupportedOperationException("Can not delete posts in reviewer interaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDeleteThread(Project project) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteThread(Project project) {
|
||||
throw new UnsupportedOperationException("Can not delete the entire reviewer interaction");
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
<wicket:panel>
|
||||
<div class="d-flex flex-column-reverse pt-1">
|
||||
<wicket:container wicket:id="events">
|
||||
<wicket:container wicket:id="event"/>
|
||||
<wicket:container wicket:id="event"></wicket:container>
|
||||
</wicket:container>
|
||||
</div>
|
||||
</wicket:panel>
|
||||
|
@ -49,6 +49,14 @@ public class SupervisorViewGroupThreadPage
|
||||
);
|
||||
return new BookmarkablePageLink<Void>(id, SupervisorGroupPage.class, pageParameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onThreadDeleted() {
|
||||
setResponsePage(
|
||||
SupervisorGroupPage.class,
|
||||
SupervisorGroupPage.getPageParameters(groupModel.getObject())
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -71,6 +71,8 @@ ProjectEvent.REFLECTION_IMPROVEMENTS_SUBMITTED = Reflection improvements submitt
|
||||
ProjectForumEvent.NEW_FORUM_POST = Forum thread created.
|
||||
ProjectForumEvent.NEW_FORUM_POST_COMMENT = Comment posted in forum thread.
|
||||
ProjectForumEvent.NEW_REVIEWER_INTERACTION = Comment posted in supervisor - reviewer communication.
|
||||
ProjectForumEvent.FORUM_THREAD_DELETED = Forum thread deleted.
|
||||
ProjectForumEvent.FORUM_POST_DELETED = Forum post deleted.
|
||||
|
||||
SeminarEvent.CREATED = Final seminar created. (with date, time, place, room specified by head supervisor; authors can now upload final seminar thesis; other authors can now oppose and be active participants)
|
||||
SeminarEvent.ROOM_CHANGED = Room changed.
|
||||
|
@ -81,4 +81,16 @@ public class ProjectViewForumThreadPageTest extends PageTest {
|
||||
page = tester.startPage(ProjectViewForumThreadPage.class, getPageParameters());
|
||||
tester.assertRenderedPage(NotFoundPage.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handle_deleted_thread_while_on_page() {
|
||||
// First call there is a thread, gets deleted while on the page
|
||||
when(projectForumService.findOne(isA(Long.class))).thenReturn(thread);
|
||||
|
||||
ProjectViewForumThreadPage page = tester.startPage(ProjectViewForumThreadPage.class, getPageParameters());
|
||||
|
||||
when(projectForumService.findOne(isA(Long.class))).thenReturn(null);
|
||||
tester.startPage(page);
|
||||
tester.assertRenderedPage(ProjectThreadedForumPage.class);
|
||||
}
|
||||
}
|
||||
|
@ -69,4 +69,16 @@ public class SupervisorViewForumThreadPageTest extends PageTest {
|
||||
page = tester.startPage(SupervisorViewForumThreadPage.class, getPageParameters());
|
||||
tester.assertRenderedPage(NotFoundPage.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handle_deleted_thread_while_on_page() {
|
||||
// First call there is a thread, gets deleted while on the page
|
||||
when(projectForumService.findOne(isA(Long.class))).thenReturn(thread);
|
||||
|
||||
SupervisorViewForumThreadPage page = tester.startPage(SupervisorViewForumThreadPage.class, getPageParameters());
|
||||
|
||||
when(projectForumService.findOne(isA(Long.class))).thenReturn(null);
|
||||
tester.startPage(page);
|
||||
tester.assertRenderedPage(SupervisorThreadedForumPage.class);
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ public class ApiConfig {
|
||||
return http
|
||||
.securityMatcher(mvc.pattern("/**"))
|
||||
.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated())
|
||||
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
|
||||
.oauth2ResourceServer(oauth2 -> oauth2.opaqueToken(Customizer.withDefaults()))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,42 @@
|
||||
package se.su.dsv.scipro.war;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
public class TokenIntrospectionRequestEntityConverter implements Converter<OAuth2UserRequest, RequestEntity<?>> {
|
||||
|
||||
private static final MediaType FORM_URL_ENCODED = MediaType.valueOf(
|
||||
MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"
|
||||
);
|
||||
|
||||
@Override
|
||||
public RequestEntity<?> convert(OAuth2UserRequest userRequest) {
|
||||
ClientRegistration clientRegistration = userRequest.getClientRegistration();
|
||||
|
||||
URI uri = UriComponentsBuilder.fromUriString(
|
||||
clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri()
|
||||
)
|
||||
.build()
|
||||
.toUri();
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret());
|
||||
headers.setAccept(Collections.singletonList(MediaType.ALL));
|
||||
headers.setContentType(FORM_URL_ENCODED);
|
||||
|
||||
MultiValueMap<String, String> formParameters = new LinkedMultiValueMap<>();
|
||||
formParameters.add(OAuth2ParameterNames.TOKEN, userRequest.getAccessToken().getTokenValue());
|
||||
return new RequestEntity<>(formParameters, headers, HttpMethod.POST, uri);
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ 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.oauth2.client.userinfo.DefaultOAuth2UserService;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import se.su.dsv.scipro.SciProApplication;
|
||||
import se.su.dsv.scipro.crosscutting.ForwardPhase2Feedback;
|
||||
@ -67,6 +68,21 @@ public class WicketConfiguration {
|
||||
return http.build();
|
||||
}
|
||||
|
||||
// Stop gap measure to switch to Token Introspection instead of OIDC UserInfo
|
||||
// endpoint. This is necessary because the UserInfo endpoint will in soon require
|
||||
// the "openid" scope, which is not granted to our clients. Unfortunately we can't
|
||||
// request the scope because that makes Spring require an id token in the token
|
||||
// exchange which is not granted at the moment.
|
||||
//
|
||||
// Once a new authorization server is in place we can remove this bean and use
|
||||
// straight up id tokens with "openid" scope.
|
||||
@Bean
|
||||
public DefaultOAuth2UserService defaultOAuth2UserService() {
|
||||
DefaultOAuth2UserService defaultOAuth2UserService = new DefaultOAuth2UserService();
|
||||
defaultOAuth2UserService.setRequestEntityConverter(new TokenIntrospectionRequestEntityConverter());
|
||||
return defaultOAuth2UserService;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CurrentUserFromSpringSecurity currentUserFromSpringSecurity(
|
||||
UserService userService,
|
||||
|
@ -17,13 +17,17 @@ springdoc.swagger-ui.path=/swagger
|
||||
springdoc.swagger-ui.persist-authorization=true
|
||||
|
||||
# These will be overwritten by configuration in the environment of servers it is deployed to
|
||||
spring.security.oauth2.resourceserver.jwt.issuer-uri=${OAUTH2_ISSUER_URI:http://localhost:59733}
|
||||
spring.security.oauth2.resourceserver.opaquetoken.client-id=${OAUTH2_RESOURCE_SERVER_ID:scipro-api-client}
|
||||
spring.security.oauth2.resourceserver.opaquetoken.client-secret=${OAUTH2_RESOURCE_SERVER_SECRET:scipro-api-secret}
|
||||
spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=${OAUTH2_RESOURCE_SERVER_INTROSPECTION_URI:http://localhost:59733/introspect}
|
||||
|
||||
# Log in via local OAuth 2 authorization server
|
||||
spring.security.oauth2.client.provider.docker.issuer-uri=${OAUTH2_ISSUER_URI:http://localhost:59734}
|
||||
spring.security.oauth2.client.provider.docker.user-info-uri=${OAUTH2_USER_INFO_URI:http://localhost:59734/introspect}
|
||||
spring.security.oauth2.client.provider.docker.user-name-attribute=sub
|
||||
spring.security.oauth2.client.provider.docker.token-uri=${OAUTH2_TOKEN_URI:http://localhost:59734/exchange}
|
||||
spring.security.oauth2.client.provider.docker.authorization-uri=${OAUTH2_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=${OAUTH2_CLIENT_ID:scipro}
|
||||
spring.security.oauth2.client.registration.scipro.client-secret=${OAUTH2_CLIENT_SECRET:s3cr3t}
|
||||
spring.security.oauth2.client.registration.scipro.authorization-grant-type=authorization_code
|
||||
spring.security.oauth2.client.registration.scipro.scope=openid
|
||||
|
Loading…
x
Reference in New Issue
Block a user