Skip to content

Commit

Permalink
Merge pull request #8 from fsinfopassau/dev
Browse files Browse the repository at this point in the history
Added various fixes & updates
  • Loading branch information
FDHoho007 authored Jun 10, 2024
2 parents a6c26ec + 38404fc commit 0ce94d8
Show file tree
Hide file tree
Showing 52 changed files with 939 additions and 593 deletions.
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
Ein digitales Kaffeekassen-System
der [Fachschaft für Informatik und Mathematik der Universität Passau](https://fsinfo.fim.uni-passau.de/).

## Deploy
![Shop-Ansicht](img/buymenu.png)

## Features

Eine [Feature-Liste](https://github.com/fsinfopassau/PRoST/wiki/Features) mit Nutzungsanleitung ist im [Wiki](https://github.com/fsinfopassau/PRoST/wiki) zu finden

Docker-Compose-Environment:
## Deploy

- VITE_API_URL: URL für die Backend-API
- /data : Ordner für Datenbank und Item-Bilder
Details stehen unter [Setup im Wiki](https://github.com/fsinfopassau/PRoST/wiki/Setup)

**Build & Run Compose**:

Expand All @@ -22,6 +25,8 @@ docker compose build
docker compose up
```

Die Seite ist unter [localhost/prost](http://localhost/prost) zu finden

## Development

**Frontend**:
Expand All @@ -34,6 +39,8 @@ npm install
npm run dev
```

Frontend ist unter [localhost:8080](http://localhost:8080) zu finden

**Backend**:

- [Google-Java-Codestyle](https://github.com/google/styleguide/blob/gh-pages/intellij-java-google-style.xml)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public static boolean isValidString(String value, String name) {
return false;
}
if (value.length() > MAX_NAME_LENGTH) {
System.out.println("[DF] :: " + name + " size to large");
System.out.println("[DF] :: " + name + " size too large");
return false;
}
return true;
Expand Down Expand Up @@ -61,4 +61,11 @@ public static String formatMoney(BigDecimal amount) {
return df.format(amount);
}

public static boolean isValidMoney(BigDecimal amount) {
if (amount == null) {
return false;
}
return amount.scale() <= 2;
}

}
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package de.unipassau.fim.fsinfo.prost.security;

import java.util.Arrays;
import java.util.Collection;
import lombok.Getter;
import lombok.Setter;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.ldap.userdetails.UserDetailsContextMapper;
import org.springframework.security.ldap.userdetails.LdapUserDetailsMapper;

public class CustomUserDetailsContextMapper implements UserDetailsContextMapper {
public class CustomUserDetailsContextMapper extends LdapUserDetailsMapper {

@Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
Expand All @@ -30,6 +31,14 @@ public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
userDetails.setEmail(mails[0]);
}

// Additional handling for service accounts if needed
if (ctx.attributeExists("objectClass")) {
String[] objectClasses = ctx.getStringAttributes("objectClass");
if (objectClasses != null && Arrays.asList(objectClasses).contains("simpleSecurityObject")) {
userDetails.setServiceAccount(true);
}
}

return userDetails;
}

Expand All @@ -47,6 +56,7 @@ public static final class CustomUserDetails implements UserDetails {
private Collection<? extends GrantedAuthority> authorities;
private String displayName;
private String email;
private boolean serviceAccount;

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public LdapAuthoritiesPopulator ldapAuthoritiesPopulator() {
authorities.setGroupSearchFilter("(member={0})");
authorities.setConvertToUpperCase(true);
authorities.setRolePrefix("");
authorities.setIgnorePartialResultException(true); // Handle service accounts without groups
return authorities;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
package de.unipassau.fim.fsinfo.prost.security;

import de.unipassau.fim.fsinfo.prost.data.UserAccessRole;
import de.unipassau.fim.fsinfo.prost.security.CustomUserDetailsContextMapper.CustomUserDetails;
import java.util.Arrays;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
Expand Down Expand Up @@ -76,10 +79,15 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.authorizeHttpRequests(auth -> auth
.requestMatchers(AUTH_WHITELIST).permitAll()
.requestMatchers(USER_SPACE)
.hasAnyAuthority(UserAccessRole.FSINFO.name(), UserAccessRole.KIOSK.name(),
UserAccessRole.KAFFEEKASSE.name())
.access((authentication, object) ->
new AuthorizationDecision(isServiceAccountOr(authentication.get(),
UserAccessRole.FSINFO, UserAccessRole.KIOSK, UserAccessRole.KAFFEEKASSE))
)
.requestMatchers(KIOSK_SPACE)
.hasAnyAuthority(UserAccessRole.KIOSK.name(), UserAccessRole.KAFFEEKASSE.name())
.access((authentication, object) -> new AuthorizationDecision(
isServiceAccountOr(authentication.get(), UserAccessRole.KIOSK,
UserAccessRole.KAFFEEKASSE))
)
.requestMatchers(ADMIN_SPACE).hasAnyAuthority(UserAccessRole.KAFFEEKASSE.name())
.anyRequest().authenticated() // Require authentication for all other requests
)
Expand All @@ -89,6 +97,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.build();
}


@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
Expand All @@ -101,4 +110,21 @@ public void configure(AuthenticationManagerBuilder auth) throws Exception {
.userDetailsContextMapper(userDetailsContextMapper);
}

private boolean isServiceAccountOr(Authentication authentication, UserAccessRole... roles) {
if (authentication.getPrincipal() instanceof CustomUserDetails userDetails) {
boolean isServiceAccount = userDetails.isServiceAccount();
boolean hasKioskRole = userDetails.getAuthorities().stream()
.anyMatch(grantedAuthority -> {
for (UserAccessRole role : roles) {
if (grantedAuthority.getAuthority().equals(role.name())) {
return true;
}
}
return false;
});
return isServiceAccount || hasKioskRole;
}
return false;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,22 @@ public Collection<UserAccessRole> getRoles(Authentication authentication) {

List<UserAccessRole> userRoles = new ArrayList<>();

if (userDetails != null && userDetails.isServiceAccount()) {
userRoles.add(UserAccessRole.KIOSK);
}

authentication.getAuthorities().forEach(authority -> {
if (authority != null) {
try {
UserAccessRole role = UserAccessRole.valueOf(authority.getAuthority());
userRoles.add(role);
if (!userRoles.contains(role)) {
userRoles.add(role);
}
} catch (IllegalArgumentException e) {
System.err.println(
"[AC] :: Could not map authority " + authority.getAuthority()
+ " to UserAccessRole of User " + userDetails.getUsername() + "!");
+ " to UserAccessRole of User " + (userDetails == null ? "[No UserDetails]"
: userDetails.getUsername()) + "!");
}
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,21 @@
@Service
public class MailService {

@Value("${MAIL_USER_NAME:Username}")
@Value("${MAIL_SENDER_ADDR:[email protected]}")
private String mailSenderAddr;

@Value("${MAIL_USER_NAME:}")
private String mailUserName;

@Value("${MAIL_USER_PASSWORD:password}")
@Value("${MAIL_USER_PASSWORD:}")
private String mailUserPassword;

@Value("${MAIL_HOST_NAME:smtp.test.com}")
private String mailHostName;

@Value("${MAIL_USE_SSL:true}")
private boolean useSsl;

@Value("${MAIL_HOST_PORT:587}")
private int mailHostPort;

Expand Down Expand Up @@ -123,12 +129,16 @@ private boolean sendMail(String address, String subject, String text) {
try {
email.setHostName(mailHostName);
email.setSmtpPort(mailHostPort);
email.setAuthentication(mailUserName, mailUserPassword);
email.setFrom(mailUserName);
if (mailUserName != null && !mailUserName.isEmpty()) {
email.setAuthentication(mailUserName, mailUserPassword == null ? "" : mailUserPassword);
}
email.setFrom(mailSenderAddr);
email.addTo(address);
email.setMsg(text);
email.setSSLOnConnect(true);
email.setStartTLSEnabled(true);
if (useSsl) {
email.setSSLOnConnect(true);
email.setStartTLSEnabled(true);
}
email.setSubject(subject);

email.send();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public Optional<ShopItem> createItem(String identifier, String displayName, Stri
return Optional.empty();
}

if (price == null) {
if (!DataFilter.isValidMoney(price)) {
System.out.println("[SS] :: The price is invalid or null");
return Optional.empty();
}
Expand Down Expand Up @@ -150,13 +150,20 @@ public Optional<ShopItem> changePrice(String identifier, String value) {
try {
if (item.isPresent()) {
BigDecimal price = new BigDecimal(value);

if (!DataFilter.isValidMoney(price)) {
System.out.println("[SS] :: Price-value with " + price + " not valid!");
return Optional.empty();
}

if (price.compareTo(MAX_PRICE) > 0) {
System.out.println("[SS] :: Price is with " + price + " to high");
System.out.println("[SS] :: Price is with " + price + " too high!");
return Optional.empty();
} else if (price.compareTo(MIN_PRICE) < 0) {
System.out.println("[SS] :: Price is with " + price + " to low");
System.out.println("[SS] :: Price is with " + price + " too low!");
return Optional.empty();
}

item.get().setPrice(price);
itemRepository.save(item.get());
return item;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.unipassau.fim.fsinfo.prost.service;

import de.unipassau.fim.fsinfo.prost.data.DataFilter;
import de.unipassau.fim.fsinfo.prost.data.TransactionType;
import de.unipassau.fim.fsinfo.prost.data.dao.ProstUser;
import de.unipassau.fim.fsinfo.prost.data.dao.TransactionEntry;
Expand Down Expand Up @@ -44,7 +45,7 @@ public Optional<TransactionEntry> moneyTransfer(Optional<ProstUser> sender,
Optional<ProstUser> receiver,
Optional<ProstUser> bearer, BigDecimal amount, TransactionType type) {

if (amount.scale() > 2) {
if (!DataFilter.isValidMoney(amount)) {
System.err.println("[TS] :: " + amount + " has not the right money-precision!");
return Optional.empty();
}
Expand Down Expand Up @@ -100,7 +101,7 @@ private Optional<TransactionEntry> buy(ProstUser receiver, ProstUser bearer,
BigDecimal amount) {

if (amount.compareTo(BigDecimal.ZERO) < 0) { // Only Positive Values
System.out.println("[TS] :: Buy is with " + amount + " to low");
System.out.println("[TS] :: Buy is with " + amount + " too low");
return Optional.empty();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,13 @@ public boolean setMoneySpent(String id, String amountString) {
try {
BigDecimal amount = new BigDecimal(amountString);

if (!DataFilter.isValidMoney(amount)) {
System.out.println("[US] :: Price-value with " + amount + " not valid!");
return false;
}

if (amount.compareTo(BigDecimal.ZERO) < 0) { // Only Positive Values
System.out.println("[US] :: Money Spent is with " + amount + " to low");
System.out.println("[US] :: Money Spent is with " + amount + " too low");
return false;
}

Expand Down
7 changes: 7 additions & 0 deletions backend/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ server:
port: ${PORT:8081}
error:
include-stacktrace: never
logging:
level:
org:
springframework:
security: ${LOGGING_LEVEL:INFO}
web: ${LOGGING_LEVEL:INFO}
data: ${LOGGING_LEVEL:INFO}
prost:
save-location: ${DATA_LOCATION:prost-data}
ldap-uri: ${LDAP_URI:ldap://ldap:1389/dc=fsinfo,dc=fim,dc=uni-passau,dc=de}
Original file line number Diff line number Diff line change
Expand Up @@ -99,5 +99,25 @@ public void testFormatMoney_ZeroAmount() {
public void testFormatMoney_NegativeAmount() {
assertEquals("-1.234,56 €", DataFilter.formatMoney(new BigDecimal("-1234.56")));
}

@Test
public void testValidMoney_RightDecimalAmount() {
assertTrue(DataFilter.isValidMoney(new BigDecimal("-1234.56")));
}

@Test
public void testValidMoney_RightDecimalAmount2() {
assertTrue(DataFilter.isValidMoney(new BigDecimal("-1234")));
}

@Test
public void testValidMoney_WrongDecimalAmount() {
assertFalse(DataFilter.isValidMoney(new BigDecimal("-1234.563")));
}

@Test
public void testValidMoney_NullAmount() {
assertFalse(DataFilter.isValidMoney(null));
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,13 @@ public void testCreateItem_InvalidData_ReturnsEmpty() {
assertTrue(result.isEmpty());
}

@Test
public void testCreateItem_WrongMoneyPrecision_ReturnsEmpty() {
Optional<ShopItem> result = shopService.createItem("moneyTest", "Money?", "Category 1",
new BigDecimal("10.001"));
assertTrue(result.isEmpty());
}

@Test
public void testCreateItem_DuplicateIdentifier_ReturnsEmpty() {
when(itemRepository.existsById(shopItem.getId())).thenReturn(true);
Expand Down Expand Up @@ -195,6 +202,14 @@ public void testChangePrice_InvalidItem_ReturnsEmpty() {
assertTrue(result.isEmpty());
}

@Test
public void testChangePrice_PricePrecisionError_ReturnsEmpty() {
when(itemRepository.findById(shopItem.getId())).thenReturn(Optional.of(shopItem));

Optional<ShopItem> result = shopService.changePrice(shopItem.getId(), "10.001");
assertTrue(result.isEmpty());
}

@Test
public void testChangePrice_PriceToHigh_ReturnsEmpty() {
when(itemRepository.findById(shopItem.getId())).thenReturn(Optional.of(shopItem));
Expand Down
Loading

0 comments on commit 0ce94d8

Please sign in to comment.