Skip to content

Commit

Permalink
Fixed some issues when using Participants as AuthorizedUsers
Browse files Browse the repository at this point in the history
  • Loading branch information
softwaremagico committed Mar 27, 2024
1 parent dcdb10c commit c127224
Show file tree
Hide file tree
Showing 10 changed files with 85 additions and 31 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
[![GitHub commit activity](https://img.shields.io/github/commit-activity/y/softwaremagico/KendoTournamentManager)](https://github.com/softwaremagico/KendoTournamentManager)
[![GitHub last commit](https://img.shields.io/github/last-commit/softwaremagico/KendoTournamentManager)](https://github.com/softwaremagico/KendoTournamentManager)
[![CircleCI](https://circleci.com/gh/softwaremagico/KendoTournamentManager.svg?style=shield)](https://circleci.com/gh/softwaremagico/KendoTournamentManager)
[![Time](https://img.shields.io/badge/development-589.5h-blueviolet.svg)]()
[![Time](https://img.shields.io/badge/development-591h-blueviolet.svg)]()

[![Powered by](https://img.shields.io/badge/powered%20by%20java-orange.svg?logo=OpenJDK&logoColor=white)]()
[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=kendo-tournament-backend&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=kendo-tournament-backend)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ public TemporalToken generateTemporalToken(ParticipantDTO participant) {
* Participants contains the id in the autogenerated username in JWT.
*/
public ParticipantDTO getByUserName(String userName) {
if (userName.contains("_")) {
final String[] fields = userName.split("_");
if (userName.contains(ParticipantProvider.TOKEN_NAME_SEPARATOR)) {
final String[] fields = userName.split(ParticipantProvider.TOKEN_NAME_SEPARATOR);
try {
return convert(getProvider().get(Integer.parseInt(fields[0])).orElseThrow(() ->
new UserNotFoundException(this.getClass(), "No user found for the provided token!")));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@

import com.softwaremagico.kt.core.exceptions.DuplicatedUserException;
import com.softwaremagico.kt.persistence.entities.AuthenticatedUser;
import com.softwaremagico.kt.persistence.entities.IAuthenticatedUser;
import com.softwaremagico.kt.persistence.entities.Participant;
import com.softwaremagico.kt.persistence.repositories.AuthenticatedUserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
Expand All @@ -44,31 +46,43 @@ public class AuthenticatedUserProvider {

private final AuthenticatedUserRepository authenticatedUserRepository;

private final ParticipantProvider participantProvider;


private final boolean guestEnabled;

@Autowired
public AuthenticatedUserProvider(AuthenticatedUserRepository authenticatedUserRepository, @Value("${enable.guest.user:false}") String guestUsersEnabled) {
public AuthenticatedUserProvider(AuthenticatedUserRepository authenticatedUserRepository, ParticipantProvider participantProvider,
@Value("${enable.guest.user:false}") String guestUsersEnabled) {
this.authenticatedUserRepository = authenticatedUserRepository;
this.participantProvider = participantProvider;
guestEnabled = Boolean.parseBoolean(guestUsersEnabled);
}


public Optional<AuthenticatedUser> findByUsername(String username) {
public Optional<IAuthenticatedUser> findByUsername(String username) {
//Create guest user on the fly
if (Objects.equals(username, GUEST_USER) && guestEnabled) {
final AuthenticatedUser guest = new AuthenticatedUser(GUEST_USER);
guest.setRoles(Collections.singleton(GUEST_ROLE));
return Optional.of(guest);
}
return authenticatedUserRepository.findByUsername(username);
final Optional<AuthenticatedUser> authenticatedUser = authenticatedUserRepository.findByUsername(username);
if (authenticatedUser.isPresent()) {
return Optional.of(authenticatedUser.get());
}
final Optional<Participant> participant = participantProvider.findByTokenUsername(username);
if (participant.isPresent()) {
return Optional.of(participant.get());
}
return Optional.empty();
}

public long count() {
return authenticatedUserRepository.count();
}

public Optional<AuthenticatedUser> findByUniqueId(String uniqueId) {
public Optional<IAuthenticatedUser> findByUniqueId(String uniqueId) {
return findByUsername(uniqueId);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
*/

import com.softwaremagico.kt.core.controller.models.TemporalToken;
import com.softwaremagico.kt.logger.KendoTournamentLogger;
import com.softwaremagico.kt.persistence.entities.Club;
import com.softwaremagico.kt.persistence.entities.Participant;
import com.softwaremagico.kt.persistence.entities.Tournament;
Expand All @@ -43,6 +44,8 @@
@Service
public class ParticipantProvider extends CrudProvider<Participant, Integer, ParticipantRepository> {

public static final String TOKEN_NAME_SEPARATOR = "_";

@Autowired
public ParticipantProvider(ParticipantRepository repository) {
super(repository);
Expand Down Expand Up @@ -113,7 +116,15 @@ public Optional<Participant> findByTemporalToken(String token) {
return getRepository().findByTemporalToken(token);
}

public Optional<Participant> findByToken(String token) {
return getRepository().findByToken(token);
public Optional<Participant> findByTokenUsername(String tokenUsername) {
if (tokenUsername.contains(ParticipantProvider.TOKEN_NAME_SEPARATOR)) {
final String[] fields = tokenUsername.split(ParticipantProvider.TOKEN_NAME_SEPARATOR);
try {
return getRepository().findById(Integer.parseInt(fields[0]));
} catch (NumberFormatException ignore) {
}
}
KendoTournamentLogger.warning(this.getClass(), "Invalid id obtained from '{}'.", tokenUsername);
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import com.softwaremagico.kt.core.providers.AuthenticatedUserProvider;
import com.softwaremagico.kt.logger.KendoTournamentLogger;
import com.softwaremagico.kt.persistence.entities.AuthenticatedUser;
import com.softwaremagico.kt.persistence.entities.IAuthenticatedUser;
import com.softwaremagico.kt.persistence.entities.Participant;
import com.softwaremagico.kt.rest.exceptions.BadRequestException;
import com.softwaremagico.kt.rest.exceptions.InvalidPasswordException;
import com.softwaremagico.kt.rest.exceptions.UserNotFoundException;
Expand Down Expand Up @@ -75,22 +77,28 @@ public AuthenticatedUser createUser(String creator, String username, String firs
}

public void updatePassword(String username, String oldPassword, String newPassword) {
final AuthenticatedUser user = authenticatedUserProvider.findByUsername(username).orElseThrow(() ->
final IAuthenticatedUser user = authenticatedUserProvider.findByUsername(username).orElseThrow(() ->
new UserNotFoundException(this.getClass(), "User with username '" + username + "' does not exists"));

if (user instanceof Participant) {
throw new UserNotFoundException(this.getClass(), "User with username '" + username + "' is not a registered user");
}

final AuthenticatedUser authenticatedUser = (AuthenticatedUser) user;

//Check old password.
if (!BCrypt.checkpw(oldPassword, user.getPassword())) {
if (!BCrypt.checkpw(oldPassword, authenticatedUser.getPassword())) {
throw new InvalidPasswordException(this.getClass(), "Provided password is incorrect!");
}

//Update new password.
user.setPassword(newPassword);
authenticatedUserProvider.save(user);
authenticatedUser.setPassword(newPassword);
authenticatedUserProvider.save(authenticatedUser);
KendoTournamentLogger.info(this.getClass(), "Password updated correctly by '{}'!", username);
}

public AuthenticatedUser updateUser(String updater, CreateUserRequest createUserRequest) {
final AuthenticatedUser user = authenticatedUserProvider.findByUsername(createUserRequest.getUsername()).orElseThrow(() ->
final AuthenticatedUser user = (AuthenticatedUser) authenticatedUserProvider.findByUsername(createUserRequest.getUsername()).orElseThrow(() ->
new UserNotFoundException(this.getClass(), "User with username '" + createUserRequest.getUsername() + "' does not exists"));
user.setName(createUserRequest.getName() != null ? createUserRequest.getName().replaceAll("[\n\r\t]", "_") : "");
user.setLastname(createUserRequest.getLastname() != null ? createUserRequest.getLastname().replaceAll("[\n\r\t]", "_") : "");
Expand All @@ -108,17 +116,23 @@ public AuthenticatedUser updateUser(String updater, CreateUserRequest createUser
}

public void deleteUser(String actioner, String username) {
final AuthenticatedUser user = authenticatedUserProvider.findByUsername(username).orElseThrow(() ->
//Can only be AuthenticatedUsers and not Participants
final IAuthenticatedUser user = authenticatedUserProvider.findByUsername(username).orElseThrow(() ->
new UserNotFoundException(this.getClass(), "User with username '" + username + "' does not exists"));
if (user instanceof Participant) {
throw new UserNotFoundException(this.getClass(), "User with username '" + username + "' is not a registered user");
}
//Ensure that at least, one user remain.
if (authenticatedUserProvider.count() > 1) {
authenticatedUserProvider.delete(user);
KendoTournamentLogger.info(this.getClass(), "User '{}' deleted by '{}'.", username, actioner);
if (user instanceof AuthenticatedUser) {
authenticatedUserProvider.delete((AuthenticatedUser) user);
KendoTournamentLogger.info(this.getClass(), "User '{}' deleted by '{}'.", username, actioner);
}
}
}

public Set<String> getRoles(String username) {
final AuthenticatedUser user = authenticatedUserProvider.findByUsername(username).orElseThrow(() ->
final IAuthenticatedUser user = authenticatedUserProvider.findByUsername(username).orElseThrow(() ->
new UserNotFoundException(this.getClass(), "User with username '" + username + "' does not exists"));
return user.getRoles();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,10 @@ public ResponseEntity<IAuthenticatedUser> login(@RequestBody AuthRequest request
RestServerLogger.debug(this.getClass().getName(), "User '" + request.getUsername().replaceAll("[\n\r\t]", "_") + "' authenticated.");

try {
final AuthenticatedUser user = authenticatedUserProvider.findByUsername(authenticate.getName()).orElseThrow(() ->
final IAuthenticatedUser user = authenticatedUserProvider.findByUsername(authenticate.getName()).orElseThrow(() ->
new UsernameNotFoundException(String.format("User '%s' not found!", authenticate.getName())));
final long jwtExpiration = jwtTokenUtil.getJwtExpirationTime();
final String jwtToken = jwtTokenUtil.generateAccessToken(user, ip);
user.setPassword(jwtToken);
bruteForceService.loginSucceeded(ip);

//We generate the JWT token and return it as a response header along with the user identity information in the response body.
Expand Down Expand Up @@ -192,12 +191,11 @@ public ResponseEntity<IAuthenticatedUser> loginAsGuest(@RequestBody AuthGuestReq
}
try {
try {
final AuthenticatedUser user = authenticatedUserProvider.findByUsername(AuthenticatedUserProvider.GUEST_USER)
final IAuthenticatedUser user = authenticatedUserProvider.findByUsername(AuthenticatedUserProvider.GUEST_USER)
.orElseThrow(() -> new GuestDisabledException(this.getClass(),
String.format("User '%s' not found!", AuthenticatedUserProvider.GUEST_USER)));
final long jwtExpiration = jwtTokenUtil.getJwtGuestExpirationTime();
final String jwtToken = jwtTokenUtil.generateAccessToken(user, ip);
user.setPassword(jwtToken);

//Guest user can only access to non-locked tournaments.
final Tournament tournament = tournamentProvider.get(request.getTournamentId()).orElseThrow(() ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.softwaremagico.kt.core.providers.AuthenticatedUserProvider;
import com.softwaremagico.kt.core.providers.ParticipantProvider;
import com.softwaremagico.kt.logger.JwtFilterLogger;
import com.softwaremagico.kt.persistence.entities.IAuthenticatedUser;
import com.softwaremagico.kt.rest.exceptions.InvalidIpException;
import com.softwaremagico.kt.rest.exceptions.InvalidJwtException;
import com.softwaremagico.kt.rest.exceptions.InvalidMacException;
Expand Down Expand Up @@ -77,7 +78,7 @@ public class JwtTokenFilter extends OncePerRequestFilter {
private final NetworkController networkController;

@Autowired
public JwtTokenFilter(@Value("${jwt.ip.check:false}") String ipCheck, @Value("${enable.participant.access}:false") String participantAccess,
public JwtTokenFilter(@Value("${jwt.ip.check:false}") String ipCheck, @Value("${enable.participant.access:false}") String participantAccess,
JwtTokenUtil jwtTokenUtil, AuthenticatedUserProvider authenticatedUserProvider,
ParticipantProvider participantProvider,
NetworkController networkController) {
Expand Down Expand Up @@ -125,14 +126,17 @@ public void doFilterInternal(HttpServletRequest request,
}

// Get user identity and set it on the spring security context
UserDetails userDetails;
userDetails = authenticatedUserProvider.findByUsername(jwtTokenUtil.getUsername(token)).orElse(null);
final IAuthenticatedUser user = authenticatedUserProvider.findByUsername(jwtTokenUtil.getUsername(token)).orElse(null);

//Check if is a participant access.
boolean participantUser = false;
if (userDetails == null && participantAccess) {
userDetails = participantProvider.findByToken(jwtTokenUtil.getUsername(token)).orElse(null);
final UserDetails userDetails;
if (user == null && participantAccess) {
userDetails = participantProvider.findByTokenUsername(jwtTokenUtil.getUsername(token)).orElse(null);
participantUser = true;
} else {
//It is a standard user
userDetails = (UserDetails) user;
}

final UsernamePasswordAuthenticationToken
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@
*/

import com.softwaremagico.kt.core.providers.AuthenticatedUserProvider;
import com.softwaremagico.kt.core.providers.ParticipantProvider;
import com.softwaremagico.kt.persistence.entities.AuthenticatedUser;
import com.softwaremagico.kt.persistence.entities.IAuthenticatedUser;
import com.softwaremagico.kt.persistence.entities.Participant;
import com.softwaremagico.kt.rest.exceptions.UserNotFoundException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
Expand All @@ -38,16 +42,25 @@
public class KendoUserDetailsService implements UserDetailsService {

private final AuthenticatedUserProvider authenticatedUserProvider;
private final ParticipantProvider participantProvider;

public KendoUserDetailsService(AuthenticatedUserProvider authenticatedUserProvider) {
public KendoUserDetailsService(AuthenticatedUserProvider authenticatedUserProvider,
ParticipantProvider participantProvider) {
this.authenticatedUserProvider = authenticatedUserProvider;
this.participantProvider = participantProvider;
}

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
final AuthenticatedUser authenticatedUser = authenticatedUserProvider.findByUsername(username).orElseThrow(() ->
final IAuthenticatedUser user = authenticatedUserProvider.findByUsername(username).orElseThrow(() ->
new UsernameNotFoundException(String.format("User '%s' not found!", username)));

if (user instanceof Participant) {
throw new UserNotFoundException(this.getClass(), "User with username '" + username + "' is not a registered user");
}

final AuthenticatedUser authenticatedUser = (AuthenticatedUser) user;

return new UserDetails() {
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ public ParticipantStatisticsDTO getStatisticsFromParticipant(@Parameter(descript
Authentication authentication,
HttpServletRequest request) {
final ParticipantStatisticsDTO participantStatisticsDTO = participantStatisticsController.get(participantController.get(participantId));
participantStatisticsDTO.setCreatedBy(authentication.getName());
participantStatisticsDTO.setCreatedBy(authentication != null ? authentication.getName() : null);
participantStatisticsDTO.setCreatedAt(LocalDateTime.now());
return participantStatisticsDTO;
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/app/interceptors/logged-in.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {Tournament} from "../models/tournament";
export class LoggedInService implements CanActivate {

//Pages that will not force a login to access.
whiteListedPages: string[] = ["/tournaments/fights"];
whiteListedPages: string[] = ["/tournaments/fights", "/participants/statistics"];

public isUserLoggedIn: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);

Expand Down

0 comments on commit c127224

Please sign in to comment.