Compare commits

...

41 Commits

Author SHA1 Message Date
66fa985a33 changed the way anonymization works 2024-06-15 12:23:29 +02:00
df0db226a9 reordered some variables in Post for easier JSON readability 2024-06-13 15:01:27 +02:00
57418213f5 reversed longitude and latitude 2024-06-13 14:55:28 +02:00
9fd843da78 reversed longitude and latitude 2024-06-13 14:46:37 +02:00
5e74228959 fixed patch 2024-06-12 18:55:25 +02:00
370912a091 added reaction endpoint 2024-06-12 11:59:48 +02:00
5a74e1cb5b Merge remote-tracking branch 'origin/main' 2024-06-11 21:10:38 +02:00
c293f20559 improved anonymization 2024-06-11 21:10:27 +02:00
8943641baf fixed query error when user has not reacted to post 2024-06-11 20:35:23 +02:00
2ca997fca9 fixed docker compose 2024-06-11 18:56:23 +02:00
aa669a98a9 fixed docker compose 2024-06-11 18:51:37 +02:00
5032ce5d63 added host to env 2024-06-11 18:08:48 +02:00
07b90d1256 reverted ip 2024-06-11 17:06:37 +02:00
22bde7ef79 added direct keycloak access 2024-06-11 17:02:32 +02:00
cafde860a7 added direct keycloak access 2024-06-11 16:52:54 +02:00
ded34c5f43 added user reaction 2024-06-11 12:41:14 +02:00
018285c109 added keycloak user 2024-06-11 12:13:24 +02:00
350e1a8a43 added keycloak 2024-06-11 11:10:11 +02:00
24fd96c8b2 try to fix cors 2024-06-11 10:13:22 +02:00
696adec54d try to fix cors 2024-06-11 09:42:16 +02:00
3bca9ced56 try to fix cors 2024-06-11 09:39:16 +02:00
02e6b4ac23 try to fix cors 2024-06-11 09:35:13 +02:00
e2ba872293 try to fix cors 2024-06-11 09:31:57 +02:00
a548cd1cf4 try to fix cors 2024-06-11 09:29:59 +02:00
def5442793 try to fix cors 2024-06-11 09:28:26 +02:00
60ad5c660d fixed rounding error in coords 2024-06-10 08:02:51 +02:00
7ca41840cf added cors 2024-06-10 07:10:50 +02:00
c04b12769a fixed query 2024-06-07 19:09:05 +02:00
c151fb8320 fixed query 2024-06-07 19:06:41 +02:00
02d7facd4c fixed query 2024-06-07 10:27:13 +02:00
acb779de7e fixed query 2024-06-07 10:10:03 +02:00
614f393463 Ctrl + C / V muss gelernt sein 2024-06-05 12:13:39 +02:00
5e6e5492d9 added distance calculation from location 2024-06-05 12:09:17 +02:00
ee65a2cf60 removed all warnings 2024-06-05 11:53:30 +02:00
49254f3d87 added parent id to posts 2024-06-05 11:00:09 +02:00
d843bbd787 oopsieee 2024-06-04 11:21:40 +02:00
74d421514d fixed post and added delete 2024-06-04 10:04:47 +02:00
9070d1452b post dev 2024-05-30 18:21:17 +02:00
c6deee3da7 corrected Timestamp format 2024-05-25 16:21:19 +02:00
c7493a34b6 readded JodelPost constructor from authorID for tests 2024-05-25 15:53:53 +02:00
4c1699c314 added get endpoint for posts by id 2024-05-25 15:47:14 +02:00
14 changed files with 648 additions and 123 deletions

View File

@@ -7,7 +7,8 @@ FROM mvn as builder
WORKDIR /build
ADD . .
RUN mvn package
ARG KC_HOSTNAME
RUN mvn package -DKC_HOSTNAME=${KC_HOSTNAME}
FROM eclipse-temurin:21-jdk-alpine

19
pom.xml
View File

@@ -37,7 +37,24 @@
<artifactId>postgresql</artifactId>
<version>42.7.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@@ -1,14 +1,22 @@
package de.anxietyprime.swajodel;
import java.util.Date;
import java.util.Objects;
import java.util.Optional;
import java.util.Vector;
import java.sql.*;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
public class JodelPost {
// id of the post
public Long id;
// Parent id
public Optional<Long> parent = Optional.empty();
// id of the author in db
private Long authorID;
private final Long authorID;
// anonymized authorID
public Long anonymousID;
// title of the post
@@ -16,32 +24,27 @@ public class JodelPost {
// content of the post
public String content;
// date of the post
public Date date;
public Timestamp date;
// location if the post
public Location location;
// list of all comments for the post
public Vector<JodelPost> comments = new Vector<>();
// the own reaction (null = none, true = positive, false = negative)
public Optional<Boolean> reaction;
// all other reactions
public Reaction reactions;
public Reactions reactions;
// list of all comments for the post
public Vector<JodelPost> comments = new Vector<>();
// anonymize function to recursively anonymize the posts
public void anonymize(Optional<Vector<Long>> idCache) {
// check if this is the first post in this process
if (idCache.isEmpty()) {
// create a new Vector as cache
idCache = Optional.of(new Vector());
}
public void anonymize(Vector<Long> idCache) {
// get the anonymized id as index in cached authorIDs
int i = idCache.get().indexOf(this.authorID);
int i = idCache.indexOf(this.authorID);
// if the index is -1 the authorID has not been cached jet
if (i == -1) {
// set the current anonymousID as length of the cache (== next index)
this.anonymousID = (long) idCache.get().size();
this.anonymousID = (long) idCache.size();
// push the current authorID to to cache
idCache.get().add(this.authorID);
idCache.add(this.authorID);
}
// the authorID has been anonymized once before, so we can get it from cache
else this.anonymousID = (long) i;
@@ -53,9 +56,10 @@ public class JodelPost {
}
// add a comment if it is really a comment
public boolean addComment(JodelPost post, long parent) {
public boolean addComment(JodelPost post) {
if (post.parent.isEmpty()) return true;
// check if the post is a direct comment
if (parent == this.id) {
if (Objects.equals(post.parent.get(), this.id)) {
// add the comment
this.comments.add(post);
// return success
@@ -64,14 +68,52 @@ public class JodelPost {
// recursively repeat this for all comments
for (JodelPost comment : this.comments) {
// return success if the post is a comment on a child
if (comment.addComment(post, parent)) return true;
if (comment.addComment(post)) return true;
}
// return no success
return false;
}
// constructor with private authorID
public JodelPost(long authorID) {
this.authorID = authorID;
// constructor from
public JodelPost(ResultSet rs) throws SQLException {
// add all other information to the post
this.authorID = rs.getLong("author");
this.id = rs.getLong("id");
this.title = rs.getString("title");
this.content = rs.getString("content");
this.date = rs.getTimestamp("postdate");
this.location = new Location(rs.getFloat("longitude"), rs.getFloat("latitude"));
this.reaction = rs.getString("reaction") == null ? Optional.empty() : Optional.of(rs.getBoolean("reaction"));
this.reactions = new Reactions(rs.getLong("positive"), rs.getLong("negative"));
this.parent = Optional.of(rs.getLong("parent"));
}
// constructor from authorID for tests
public JodelPost(int authorID) {
this.authorID = (long) authorID;
}
@JsonCreator
public JodelPost(@JsonProperty("title") String title,
@JsonProperty("content") String content,
@JsonProperty("date") Timestamp date,
@JsonProperty("location") Location location,
@JsonProperty("parent") Optional<Long> parent) {
this.authorID = User.getID(); // TODO: getter from Keycloak
this.title = title;
this.content = content;
this.date = date;
this.location = location;
this.parent = parent;
}
@JsonIgnore
public long getAuthorID() {
return authorID;
}
@JsonIgnore
public boolean isComment() {
return parent.filter(aLong -> !Objects.equals(id, aLong)).isPresent();
}
}

View File

@@ -1,10 +1,14 @@
package de.anxietyprime.swajodel;
public class Location {
public float longitude;
public float latitude;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
public Location(float longitude, float latitude) {
public class Location {
public float latitude;
public float longitude;
@JsonCreator
public Location(@JsonProperty("longitude") float longitude, @JsonProperty("latitude") float latitude ) {
this.longitude = longitude;
this.latitude = latitude;
}

View File

@@ -1,6 +0,0 @@
package de.anxietyprime.swajodel;
public class Reaction {
private long positive;
private long negative;
}

View File

@@ -0,0 +1,11 @@
package de.anxietyprime.swajodel;
public class Reactions {
public long positive;
public long negative;
Reactions(long positive, long negative) {
this.positive = positive;
this.negative = negative;
}
}

View File

@@ -1,8 +1,8 @@
package de.anxietyprime.swajodel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;
import java.sql.*;
@@ -10,16 +10,20 @@ import java.util.Optional;
import java.util.Vector;
@RestController
@CrossOrigin(origins = "*", allowedHeaders = "*", maxAge = 3600)
public class Routes {
@GetMapping("/posts/{longitude}/{latitude}")
public Vector<JodelPost> getPosts(@PathVariable("longitude") float longitude, @PathVariable("latitude") float latitude) {
@GetMapping("/posts/{latitude}/{longitude}")
public Vector<JodelPost> getPostsByLocation(@PathVariable("longitude") float longitude, @PathVariable("latitude") float latitude) {
// list of all posts (not comments) in range
Vector<JodelPost> posts = new Vector<>();
// get UserID
Long userID = User.getID();
// DB connection and statement
Connection c = null;
PreparedStatement stmt = null;
Connection c;
PreparedStatement stmt;
// try to get data from db
try {
@@ -34,40 +38,49 @@ public class Routes {
c.setAutoCommit(false);
// create a new statement
stmt = c.prepareStatement("WITH RECURSIVE targets AS (\n" +
" SELECT\n" +
" id,\n" +
" id AS parent,\n" +
" author,\n" +
" title,\n" +
" content,\n" +
" postdate,\n" +
" postlocation[0] AS longitude,\n" +
" postlocation[1] AS latitude\n" +
" FROM\n" +
" posts\n" +
" WHERE\n" +
" deleted IS NULL\n" +
" AND sqrt(power(postlocation[0] - (?), 2) + power(postlocation[1] - (?), 2)) <= 10\n" +
" AND id NOT IN (SELECT child FROM comments)\n" +
" UNION\n" +
" SELECT\n" +
" com.child,\n" +
" com.parent,\n" +
" com.author,\n" +
" com.title,\n" +
" com.content,\n" +
" com.postdate,\n" +
" com.postlocation[0],\n" +
" com.postlocation[1]\n" +
" FROM\n" +
" (SELECT * FROM comments inner join posts ON comments.child = posts.id) com\n" +
" inner join targets ON targets.id = com.parent\n" +
")\n" +
"SELECT * FROM targets;");
stmt = c.prepareStatement("""
WITH RECURSIVE targets AS (
SELECT
posts.id,
posts.id AS parent,
posts.author,
posts.title,
posts.content,
posts.postdate,
posts.postlocation[0] AS latitude,
posts.postlocation[1] AS longitude,
(SELECT count(*) FROM reactions WHERE reactions.post = posts.id AND reactions.positive = TRUE) AS positive,
(SELECT count(*) FROM reactions WHERE reactions.post = posts.id AND reactions.positive = FALSE) AS negative
FROM
posts
WHERE
deleted IS NULL
AND earth_distance(
ll_to_earth(posts.postlocation[0], posts.postlocation[1]),
ll_to_earth((?), (?))
) <= 10000
AND posts.id NOT IN (SELECT child FROM comments)
UNION
SELECT
com.child,
com.parent,
com.author,
com.title,
com.content,
com.postdate,
com.postlocation[0],
com.postlocation[1],
(SELECT count(*) FROM reactions WHERE reactions.post = com.child AND reactions.positive = TRUE) AS positive,
(SELECT count(*) FROM reactions WHERE reactions.post = com.child AND reactions.positive = FALSE) AS negative
FROM
(SELECT * FROM comments inner join posts ON comments.child = posts.id) com
inner join targets ON targets.id = com.parent
)
SELECT * FROM targets LEFT JOIN (SELECT post, positive as reaction FROM reactions WHERE userid = (?)) ON post = id;""");
stmt.setObject(1, longitude);
stmt.setObject(2, latitude);
stmt.setObject(1, latitude);
stmt.setObject(2, longitude);
stmt.setObject(3, userID);
// query recursively for posts inside a 10km radius
ResultSet rs = stmt.executeQuery();
@@ -75,20 +88,11 @@ public class Routes {
// for all the posts found
while ( rs.next() ) {
// create a post from the author id
JodelPost post = new JodelPost(rs.getLong("author"));
// add all other information to the post
post.id = rs.getLong("id");
post.title = rs.getString("title");
post.content = rs.getString("content");
post.date = rs.getDate("postdate");
post.location = new Location(rs.getLong("longitude"), rs.getLong("latitude"));
// get the posts parent
long parent = rs.getLong("parent");
JodelPost post = new JodelPost(rs);
// check if the parent is the own id
// if it is, it is a post
if (parent == post.id) {
if (!post.isComment()) {
// add the post to the posts
posts.add(post);
}
@@ -97,7 +101,7 @@ public class Routes {
// iterate over all posts
for (JodelPost p : posts) {
// try to add the post to a parent
p.addComment(post, parent);
p.addComment(post);
}
}
}
@@ -111,13 +115,304 @@ public class Routes {
// else log the error
catch ( Exception e ) {
System.err.println( e.getClass().getName()+": "+ e.getMessage() );
throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, "Database is offline");
}
// calculate anonymous IDs for the posts
Vector<Long> anonymousIDs = new Vector<Long>();
anonymousIDs.add(userID);
posts.forEach(post -> {
post.anonymize(Optional.empty());
post.anonymize(anonymousIDs);
});
// return the posts
return posts;
}
@GetMapping("/post/{id}")
public JodelPost getPostByID(@PathVariable("id") long id) {
// list of all posts (not comments) in range
Optional<JodelPost> root_post = Optional.empty();
// get UserID
Long userID = User.getID();
// DB connection and statement
Connection c;
PreparedStatement stmt;
// try to get data from db
try {
// check for the driver
Class.forName("org.postgresql.Driver");
// get the connection with credentials from env variables
c = DriverManager
.getConnection("jdbc:postgresql://"+
System.getenv("POSTGRES_IP")+"/"+System.getenv("POSTGRES_DB"),
System.getenv("POSTGRES_USER"), System.getenv("POSTGRES_PASSWORD"));
// disable auto commits
c.setAutoCommit(false);
// create a new statement
stmt = c.prepareStatement("""
WITH RECURSIVE targets AS (
SELECT
posts.id,
posts.id AS parent,
posts.author,
posts.title,
posts.content,
posts.postdate,
posts.postlocation[0] AS latitude,
posts.postlocation[1] AS longitude,
(SELECT count(*) FROM reactions WHERE reactions.post = posts.id AND reactions.positive = TRUE) AS positive,
(SELECT count(*) FROM reactions WHERE reactions.post = posts.id AND reactions.positive = FALSE) AS negative
FROM
posts
WHERE
deleted IS NULL
AND posts.id = (?)
AND posts.id NOT IN (SELECT child FROM comments)
UNION
SELECT
com.child,
com.parent,
com.author,
com.title,
com.content,
com.postdate,
com.postlocation[0],
com.postlocation[1],
(SELECT count(*) FROM reactions WHERE reactions.post = com.child AND reactions.positive = TRUE) AS positive,
(SELECT count(*) FROM reactions WHERE reactions.post = com.child AND reactions.positive = FALSE) AS negative
FROM
(SELECT * FROM comments inner join posts ON comments.child = posts.id) com
inner join targets ON targets.id = com.parent
)
SELECT * FROM targets LEFT JOIN (SELECT post, positive as reaction FROM reactions WHERE userid = (?)) ON post = id;""");
stmt.setObject(1, id);
stmt.setObject(2, userID);
// query recursively for posts inside a 10km radius
ResultSet rs = stmt.executeQuery();
// for all the posts found
while ( rs.next() ) {
// create a post from the author id
JodelPost post = new JodelPost(rs);
// get the posts parent
long parent = rs.getLong("parent");
// check if the parent is the own id
// if it is, it is a post
if (parent == post.id) {
// add the post to the posts
root_post = Optional.of(post);
}
// else it is a comment
else {
// try to add the post to parent
root_post.ifPresent(root -> root.addComment(post));
}
}
// close all connections to db
rs.close();
stmt.close();
c.close();
}
// else log the error
catch ( Exception e ) {
System.err.println( e.getClass().getName()+": "+ e.getMessage() );
throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, "Database is offline");
}
// calculate anonymous IDs for the post
Vector<Long> anonymousIDs = new Vector<Long>();
anonymousIDs.add(userID);
root_post.ifPresent(root -> root.anonymize(anonymousIDs));
// return the posts
if (root_post.isEmpty()) throw new ResponseStatusException(HttpStatus.NOT_FOUND, "No post found");
return root_post.get();
}
@PostMapping("/posts")
public JodelPost postPost(@RequestBody JodelPost post) {
// DB connection and statement
Connection c;
PreparedStatement stmt;
// try to get data from db
try {
// check for the driver
Class.forName("org.postgresql.Driver");
// get the connection with credentials from env variables
c = DriverManager
.getConnection("jdbc:postgresql://"+
System.getenv("POSTGRES_IP")+"/"+System.getenv("POSTGRES_DB"),
System.getenv("POSTGRES_USER"), System.getenv("POSTGRES_PASSWORD"));
// disable auto commits
c.setAutoCommit(false);
// create a new statement
stmt = c.prepareStatement("INSERT INTO Posts(author, title, content, postdate, postlocation) " +
"VALUES (?, ?, ?, ?, Point(?, ?)) RETURNING id");
stmt.setObject(1, post.getAuthorID());
stmt.setObject(2, post.title);
stmt.setObject(3, post.content);
stmt.setObject(4, post.date);
stmt.setObject(5, post.location.latitude);
stmt.setObject(6, post.location.longitude);
// insert post and get its id
ResultSet rs = stmt.executeQuery();
// get the id of the new post
rs.next();
post.id = rs.getLong("id");
// check if there is a parent
if (post.parent.isPresent()) {
// create a new statement
stmt = c.prepareStatement("INSERT INTO comments(parent, child) VALUES (?, ?)");
// fill statement
stmt.setObject(1, post.parent.get());
stmt.setObject(2, post.id);
// execute statement
stmt.execute();
}
// commit the changes
c.commit();
// close all connections to db
stmt.close();
c.close();
}
// else log the error
catch ( Exception e ) {
System.err.println( e.getClass().getName()+": "+ e.getMessage() );
throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, "Database is offline");
}
// fill missing post values
if (post.parent.isEmpty()) post.parent = Optional.of(post.id);
post.reactions = new Reactions(0, 0);
post.reaction = Optional.empty();
// anonymize code
Vector<Long> anonymousIDs = new Vector<Long>();
anonymousIDs.add(0L);
post.anonymize(anonymousIDs);
return post;
}
@PatchMapping("/post/{id}")
public void reactToPost(@PathVariable long id, @RequestBody JodelPost post) {
System.out.println(post.reaction);
long userID = User.getID();
// DB connection and statement
Connection c;
PreparedStatement stmt;
// try to get data from db
try {
// check for the driver
Class.forName("org.postgresql.Driver");
// get the connection with credentials from env variables
c = DriverManager
.getConnection("jdbc:postgresql://"+
System.getenv("POSTGRES_IP")+"/"+System.getenv("POSTGRES_DB"),
System.getenv("POSTGRES_USER"), System.getenv("POSTGRES_PASSWORD"));
// disable auto commits
c.setAutoCommit(false);
// create a new statement
stmt = c.prepareStatement("UPDATE Reactions SET positive = (?) WHERE post = (?) AND userid = (?) RETURNING id, positive");
stmt.setObject(1, post.reaction.orElse(null));
stmt.setObject(2, id);
stmt.setObject(3, userID);
// insert post and get its id
ResultSet rs = stmt.executeQuery();
// check if there is a parent
if (!rs.next()) {
// create a new statement
stmt = c.prepareStatement("INSERT INTO Reactions(userid, post, positive) VALUES ((?), (?), (?))");
// fill statement
stmt.setObject(1, userID);
stmt.setObject(2, id);
stmt.setObject(3, post.reaction.orElse(null));
// execute statement
stmt.execute();
}
// commit the changes
c.commit();
// close all connections to db
stmt.close();
c.close();
}
// else log the error
catch ( Exception e ) {
System.err.println( e.getClass().getName()+": "+ e.getMessage() );
throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, "Database is offline");
}
}
@DeleteMapping("/post/{id}")
public void deletePost(@PathVariable long id) {
// DB connection and statement
Connection c;
PreparedStatement stmt;
// try to get data from db
try {
// check for the driver
Class.forName("org.postgresql.Driver");
// get the connection with credentials from env variables
c = DriverManager
.getConnection("jdbc:postgresql://"+
System.getenv("POSTGRES_IP")+"/"+System.getenv("POSTGRES_DB"),
System.getenv("POSTGRES_USER"), System.getenv("POSTGRES_PASSWORD"));
// disable auto commits
c.setAutoCommit(false);
// create a new statement
stmt = c.prepareStatement("UPDATE Posts SET deleted = now() WHERE id = ? AND author = (?) AND deleted IS NULL");
stmt.setObject(1, id);
stmt.setObject(2, User.getID());
// insert delete time
stmt.execute();
// close all connections to db
stmt.close();
c.commit();
c.close();
}
// else log the error
catch ( Exception e ) {
System.err.println( e.getClass().getName()+": "+ e.getMessage() );
throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, "Database is offline");
}
}
}

View File

@@ -2,6 +2,9 @@ package de.anxietyprime.swajodel;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@SpringBootApplication
public class SwaJodelApplication {

View File

@@ -0,0 +1,77 @@
package de.anxietyprime.swajodel;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.server.ResponseStatusException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class User {
static public String getUUID () {
return SecurityContextHolder.getContext().getAuthentication().getName();
}
static public Long getID() {
String uuid = SecurityContextHolder.getContext().getAuthentication().getName();
long id;
// DB connection and statement
Connection c;
PreparedStatement stmt;
// try to get data from db
try {
// check for the driver
Class.forName("org.postgresql.Driver");
// get the connection with credentials from env variables
c = DriverManager
.getConnection("jdbc:postgresql://"+
System.getenv("POSTGRES_IP")+"/"+System.getenv("POSTGRES_DB"),
System.getenv("POSTGRES_USER"), System.getenv("POSTGRES_PASSWORD"));
// disable auto commits
c.setAutoCommit(false);
// create a new statement
stmt = c.prepareStatement("SELECT id FROM users WHERE username = (?)");
stmt.setObject(1, uuid);
// insert post and get its id
ResultSet rs = stmt.executeQuery();
// check if there is a parent
if (!rs.next()) {
// create a new statement
stmt = c.prepareStatement("INSERT INTO users(username, password) VALUES (?, 'unused') RETURNING id");
// fill statement
stmt.setObject(1, uuid);
// execute statement
rs = stmt.executeQuery();
// commit the changes
c.commit();
rs.next();
}
id = rs.getLong("id");
// close all connections to db
stmt.close();
c.close();
}
// else log the error
catch ( Exception e ) {
System.err.println( e.getClass().getName()+": "+ e.getMessage() );
throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, "Database is offline");
}
return id;
}
}

View File

@@ -0,0 +1,60 @@
package de.anxietyprime.swajodel.security;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimNames;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Component
public class JwtConverter implements Converter<Jwt, AbstractAuthenticationToken> {
private final JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
private final JwtConverterProperties properties;
public JwtConverter(JwtConverterProperties properties) {
this.properties = properties;
}
@Override
public AbstractAuthenticationToken convert(Jwt jwt) {
Collection<GrantedAuthority> authorities = Stream.concat(
jwtGrantedAuthoritiesConverter.convert(jwt).stream(),
extractResourceRoles(jwt).stream()).collect(Collectors.toSet());
return new JwtAuthenticationToken(jwt, authorities, getPrincipalClaimName(jwt));
}
private String getPrincipalClaimName(Jwt jwt) {
String claimName = JwtClaimNames.SUB;
if (properties.getPrincipalAttribute() != null) {
claimName = properties.getPrincipalAttribute();
}
return jwt.getClaim(claimName);
}
private Collection<? extends GrantedAuthority> extractResourceRoles(Jwt jwt) {
Map<String, Object> resourceAccess = jwt.getClaim("resource_access");
Map<String, Object> resource;
Collection<String> resourceRoles;
if (resourceAccess == null
|| (resource = (Map<String, Object>) resourceAccess.get(properties.getResourceId())) == null
|| (resourceRoles = (Collection<String>) resource.get("roles")) == null) {
return Set.of();
}
return resourceRoles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toSet());
}
}

View File

@@ -0,0 +1,19 @@
package de.anxietyprime.swajodel.security;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.annotation.Validated;
@Data
@Validated
@Configuration
@ConfigurationProperties(prefix = "jwt.auth.converter")
public class JwtConverterProperties {
private String resourceId;
private String principalAttribute;
}

View File

@@ -0,0 +1,33 @@
package de.anxietyprime.swajodel.security;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
public class SecurityConfig {
public static final String ADMIN = "admin";
public static final String USER = "user";
private final JwtConverter jwtConverter;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((authz) ->
authz.requestMatchers(HttpMethod.GET, "/messages/").permitAll()
.anyRequest().authenticated());
http.sessionManagement(sess -> sess.sessionCreationPolicy(
SessionCreationPolicy.STATELESS));
http.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtConverter)));
return http.build();
}
}

View File

@@ -1 +1,5 @@
spring.application.name=SWA-Jodel
# Security Configuration
spring.security.oauth2.resourceserver.jwt.issuer-uri=https://${KC_HOSTNAME}/realms/Jodel
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=${spring.security.oauth2.resourceserver.jwt.issuer-uri}/protocol/openid-connect/certs

View File

@@ -3,44 +3,9 @@ package de.anxietyprime.swajodel;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Optional;
import java.util.Vector;
@SpringBootTest
class SwaJodelApplicationTests {
@Test
void anonymousTest() {
JodelPost post = new JodelPost(1000);
post.comments.add(new JodelPost(1001));
post.comments.add(new JodelPost(1002));
post.comments.get(0).comments.add(new JodelPost(1000));
post.comments.get(0).comments.add(new JodelPost(1001));
post.comments.get(0).comments.add(new JodelPost(1000));
post.comments.add(new JodelPost(1003));
post.comments.get(2).comments.add(new JodelPost(1001));
post.comments.get(2).comments.add(new JodelPost(1002));
post.comments.get(2).comments.add(new JodelPost(1003));
post.comments.get(2).comments.add(new JodelPost(1000));
post.comments.add(new JodelPost(1000));
post.comments.add(new JodelPost(1001));
post.comments.add(new JodelPost(1001));
post.anonymize(Optional.empty());
assert (post.anonymousID == 0);
assert (post.comments.get(0).anonymousID == 1);
assert (post.comments.get(1).anonymousID == 2);
assert (post.comments.get(2).anonymousID == 3);
assert (post.comments.get(3).anonymousID == 0);
assert (post.comments.get(4).anonymousID == 1);
assert (post.comments.get(5).anonymousID == 1);
assert (post.comments.get(0).comments.get(0).anonymousID == 0);
assert (post.comments.get(0).comments.get(1).anonymousID == 1);
assert (post.comments.get(0).comments.get(2).anonymousID == 0);
assert (post.comments.get(2).comments.get(0).anonymousID == 1);
assert (post.comments.get(2).comments.get(1).anonymousID == 2);
assert (post.comments.get(2).comments.get(2).anonymousID == 3);
assert (post.comments.get(2).comments.get(3).anonymousID == 0);
}
}