Skip to content
Snippets Groups Projects
Commit 5bdb8d01 authored by Martin Weise's avatar Martin Weise
Browse files

Various style fixes, fixed the .env.example

parent 351b5ca0
Branches
Tags
2 merge requests!81New stable release,!80Multiple features connected with user management and ownership of databases
Showing
with 168 additions and 95 deletions
API="http://fda-gateway-service:9095"
MAIL_USERNAME="eMATRIKELNUMMER" // TU student e-mail server
MAIL_PASSWORD="PASSWORD" // TU student e-mail server
ADMIN_PASSWORD="admin"
\ No newline at end of file
MAIL_HOST="stmp.example.com"
MAIL_PORT="587"
MAIL_USERNAME="user"
MAIL_PASSWORD="pass"
\ No newline at end of file
......@@ -3,6 +3,7 @@ package at.tuwien.endpoints;
import at.tuwien.api.auth.JwtResponseDto;
import at.tuwien.api.auth.LoginRequestDto;
import at.tuwien.api.user.UserDto;
import at.tuwien.exception.OrcidMalformedException;
import at.tuwien.exception.UserEmailNotVerifiedException;
import at.tuwien.exception.UserNotFoundException;
import at.tuwien.mapper.UserMapper;
......@@ -50,7 +51,7 @@ public class AuthenticationEndpoint {
@PutMapping
@Transactional(readOnly = true)
@Operation(summary = "Validate token", security = @SecurityRequirement(name = "bearerAuth"))
public ResponseEntity<UserDto> authenticateUser(Principal principal) throws UserNotFoundException {
public ResponseEntity<UserDto> authenticateUser(Principal principal) throws UserNotFoundException, OrcidMalformedException {
final UserDto user = userMapper.userToUserDto(userService.findByUsername(principal.getName()));
return ResponseEntity.accepted()
.body(user);
......
......@@ -30,6 +30,7 @@ import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.security.Principal;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
......@@ -62,11 +63,13 @@ public class UserEndpoint {
@Transactional(readOnly = true)
@PreAuthorize("hasRole('ROLE_DATA_STEWARD') or hasRole('ROLE_DEVELOPER')")
@Operation(summary = "List users", security = @SecurityRequirement(name = "bearerAuth"))
public ResponseEntity<List<UserDto>> list() {
public ResponseEntity<List<UserDto>> list() throws OrcidMalformedException {
final List<User> users = userService.findAll();
return ResponseEntity.ok(users.stream()
.map(userMapper::userToUserDto)
.collect(Collectors.toList()));
final List<UserDto> out = new LinkedList<>();
for (User user : users) {
out.add(userMapper.userToUserDto(user));
}
return ResponseEntity.ok(out);
}
@PostMapping
......@@ -74,7 +77,7 @@ public class UserEndpoint {
@Operation(summary = "Create user")
public ResponseEntity<UserDto> register(@NotNull @Valid @RequestBody SignupRequestDto data)
throws UserEmailExistsException,
UserNameExistsException, RoleNotFoundException, UserEmailFailedException, BrokerUserCreationException {
UserNameExistsException, RoleNotFoundException, UserEmailFailedException, BrokerUserCreationException, OrcidMalformedException {
final User user = userService.create(data);
queueService.createUser(data);
final Token token = tokenService.create(user);
......@@ -90,7 +93,7 @@ public class UserEndpoint {
@Transactional
@Operation(summary = "Forgot user information")
public ResponseEntity<UserDto> forgot(@NotNull @Valid @RequestBody UserForgotDto data)
throws UserNotFoundException, UserEmailFailedException {
throws UserNotFoundException, UserEmailFailedException, OrcidMalformedException {
final User user = userService.forgot(data);
final Token token = tokenService.create(user);
final Context context = new Context();
......@@ -118,23 +121,11 @@ public class UserEndpoint {
httpServletResponse.setStatus(302);
}
@PutMapping("/token")
@Transactional
@PreAuthorize("hasRole('ROLE_RESEARCHER')")
@Operation(summary = "Update user token", security = @SecurityRequirement(name = "bearerAuth"))
public ResponseEntity<UserDto> updateTokens(@NotNull @Valid @RequestBody UserTokenModifyDto data,
@NotNull Principal principal)
throws UserNotFoundException {
final User entity = userService.updateToken(data, principal);
return ResponseEntity.status(HttpStatus.ACCEPTED)
.body(userMapper.userToUserDto(entity));
}
@GetMapping("/{id}")
@Transactional(readOnly = true)
@PreAuthorize("hasRole('ROLE_DEVELOPER') or hasPermission(#id, 'READ_USER')")
@Operation(summary = "Find some user", security = @SecurityRequirement(name = "bearerAuth"))
public ResponseEntity<UserDto> find(@NotNull @PathVariable("id") Long id) throws UserNotFoundException {
public ResponseEntity<UserDto> find(@NotNull @PathVariable("id") Long id) throws UserNotFoundException, OrcidMalformedException {
final User entity = userService.find(id);
return ResponseEntity.status(HttpStatus.OK)
.body(userMapper.userToUserDto(entity));
......@@ -146,7 +137,7 @@ public class UserEndpoint {
@Operation(summary = "Update user", security = @SecurityRequirement(name = "bearerAuth"))
public ResponseEntity<UserDto> update(@NotNull @PathVariable("id") Long id,
@NotNull @Valid @RequestBody UserUpdateDto data)
throws UserNotFoundException {
throws UserNotFoundException, OrcidMalformedException {
final User entity = userService.update(id, data);
return ResponseEntity.status(HttpStatus.ACCEPTED)
.body(userMapper.userToUserDto(entity));
......@@ -158,7 +149,7 @@ public class UserEndpoint {
@Operation(summary = "Update user roles", security = @SecurityRequirement(name = "bearerAuth"))
public ResponseEntity<UserDto> updateRoles(@NotNull @PathVariable("id") Long id,
@NotNull @Valid @RequestBody UserRolesDto data)
throws UserNotFoundException, RoleNotFoundException, RoleUniqueException {
throws UserNotFoundException, RoleNotFoundException, RoleUniqueException, OrcidMalformedException {
final User entity = userService.updateRoles(id, data);
return ResponseEntity.status(HttpStatus.ACCEPTED)
.body(userMapper.userToUserDto(entity));
......@@ -170,7 +161,7 @@ public class UserEndpoint {
@Operation(summary = "Update user password", security = @SecurityRequirement(name = "bearerAuth"))
public ResponseEntity<UserDto> updatePassword(@NotNull @PathVariable("id") Long id,
@NotNull @Valid @RequestBody UserPasswordDto data)
throws UserNotFoundException, BrokerUserCreationException {
throws UserNotFoundException, BrokerUserCreationException, OrcidMalformedException {
final User entity = userService.updatePassword(id, data);
queueService.modifyUserPassword(entity, data);
return ResponseEntity.status(HttpStatus.ACCEPTED)
......@@ -183,7 +174,7 @@ public class UserEndpoint {
@Operation(summary = "Update user email", security = @SecurityRequirement(name = "bearerAuth"))
public ResponseEntity<UserDto> updateEmail(@NotNull @PathVariable("id") Long id,
@NotNull @Valid @RequestBody UserEmailDto data)
throws UserNotFoundException {
throws UserNotFoundException, OrcidMalformedException {
final User entity = userService.updateEmail(id, data);
return ResponseEntity.status(HttpStatus.ACCEPTED)
.body(userMapper.userToUserDto(entity));
......
......@@ -52,5 +52,5 @@ fda:
replyto: "${MAIL_REPLY_TO}"
jwt:
issuer: "${JWT_ISSUER}"
secret: "${JWT_SECRET"
secret: "${JWT_SECRET}"
expiration.ms: "${JWT_EXPIRATION}" # 24 hrs
\ No newline at end of file
......@@ -22,8 +22,8 @@ spring:
loadbalancer.ribbon.enabled: false
mail:
default-encoding: UTF-8
host: "${SMTP_HOST}"
port: "${SMTP_PORT}"
host: mail.student.tuwien.ac.at
port: 993
username: "${SMTP_USERNAME}"
password: "${SMTP_PASSWORD}"
properties.mail.smtp:
......
package at.tuwien.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(code = HttpStatus.BAD_REQUEST)
public class OrcidMalformedException extends Exception {
public OrcidMalformedException(String msg) {
super(msg);
}
public OrcidMalformedException(String msg, Throwable thr) {
super(msg, thr);
}
public OrcidMalformedException(Throwable thr) {
super(thr);
}
}
......@@ -7,6 +7,7 @@ import at.tuwien.api.auth.SignupRequestDto;
import at.tuwien.api.user.*;
import at.tuwien.entities.user.RoleType;
import at.tuwien.entities.user.User;
import at.tuwien.exception.OrcidMalformedException;
import org.mapstruct.Mapper;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
......@@ -47,7 +48,7 @@ public interface UserMapper {
}
@Transactional(readOnly = true)
default UserDto userToUserDto(User data) {
default UserDto userToUserDto(User data) throws OrcidMalformedException {
return UserDto.builder()
.id(data.getId())
.username(data.getUsername())
......@@ -58,7 +59,8 @@ public interface UserMapper {
.titlesBefore(data.getTitlesBefore())
.titlesAfter(data.getTitlesAfter())
.emailVerified(data.getEmailVerified())
.hasInvenioToken(data.getInvenioToken() != null)
.affiliation(data.getAffiliation())
.orcid(userToUncompressedOrcid(data))
.authorities(data.getRoles()
.stream()
.map(this::roleTypeToGrantedAuthorityDto)
......@@ -82,6 +84,32 @@ public interface UserMapper {
.build();
}
default String userUpdateDtoToCompressedOrcid(UserUpdateDto data) {
if (data.getOrcid() == null) {
return null;
}
return data.getOrcid().replace("-", "");
}
default String userToUncompressedOrcid(User data) throws OrcidMalformedException {
if (data.getOrcid() == null) {
return null;
}
if (data.getOrcid().length() != 16) {
log.error("Provided ORCID is not compressed");
log.debug("provided orcid {} is not compressed, length is {}", data.getOrcid(), data.getOrcid().length());
throw new OrcidMalformedException("Provided ORCID is not compressed");
}
return new StringBuilder(data.getOrcid().substring(0, 4))
.append("-")
.append(data.getOrcid(), 4, 8)
.append("-")
.append(data.getOrcid(), 8, 12)
.append("-")
.append(data.getOrcid(), 12, 16)
.toString();
}
default GrantVirtualHostPermissionsDto signupRequestDtoToGrantComponentDto() {
return GrantVirtualHostPermissionsDto.builder()
.virtualHost("/")
......
......@@ -74,7 +74,7 @@ public interface UserService {
* @return The updated user.
* @throws UserNotFoundException The user was not found.
*/
User update(Long id, UserUpdateDto data) throws UserNotFoundException;
User update(Long id, UserUpdateDto data) throws UserNotFoundException, OrcidMalformedException;
/**
* Updates a user with given id and updated roles.
......@@ -98,16 +98,6 @@ public interface UserService {
*/
User updatePassword(Long id, UserPasswordDto data) throws UserNotFoundException;
/**
* Updates a user with the given id and updated Invenio tokens.
*
* @param data The updated Invenio token.
* @param principal The authentication principal.
* @return The updated user.
* @throws UserNotFoundException The user was not found.
*/
User updateToken(UserTokenModifyDto data, Principal principal) throws UserNotFoundException;
/**
* Updates a user with the given id and updated email.
*
......
......@@ -127,15 +127,23 @@ public class UserServiceImpl implements UserService {
@Override
@Transactional
public User update(Long id, UserUpdateDto data) throws UserNotFoundException {
public User update(Long id, UserUpdateDto data) throws UserNotFoundException, OrcidMalformedException {
/* check */
final User user = find(id);
/* check */
if (data.getOrcid() != null && !validateOrcid(data.getOrcid())) {
log.error("Checksum of the provided ORCID does not match");
log.debug("checksum of the provided orcid {} does not match", data.getOrcid());
throw new OrcidMalformedException(data.getOrcid());
}
/* save */
user.setTitlesBefore(data.getTitlesBefore());
user.setTitlesAfter(data.getTitlesAfter());
user.setFirstname(data.getFirstname());
user.setLastname(data.getLastname());
user.setUsername(user.getUsername());
user.setAffiliation(data.getAffiliation());
user.setOrcid(userMapper.userUpdateDtoToCompressedOrcid(data));
log.debug("mapped data {} to new user {}", data, user);
final User entity = userRepository.save(user);
log.info("Updated user with id {}", entity.getId());
......@@ -143,6 +151,37 @@ public class UserServiceImpl implements UserService {
return entity;
}
/**
* Validates a given ORCID checksum (ISO 7064 11,2)
* Source: https://support.orcid.org/hc/en-us/articles/360006897674-Structure-of-the-ORCID-Identifier
*
* @param orcid The ORCID.
* @return True if the ORCID provided is valid, false otherwise.
*/
protected static Boolean validateOrcid(String orcid) {
if (orcid == null) {
return true;
}
if (orcid.length() != 19) {
log.error("Provided ORCID has an invalid length");
log.debug("provided orcid {} has an invalid length {}, is not 19", orcid, orcid.length());
return false;
}
int total = 0;
for (int i = 0; i < orcid.length() - 1; i++) {
if (orcid.charAt(i) == '-') {
continue;
}
int digit = Character.getNumericValue(orcid.charAt(i));
total = (total + digit) * 2;
}
int remainder = total % 11;
int result = (12 - remainder) % 11;
final String check = result == 10 ? "X" : String.valueOf(result);
log.trace("orcid checksum is '{}'", check);
return orcid.substring(18).equals(check);
}
@Override
@Transactional
public User updateRoles(Long id, UserRolesDto data)
......@@ -187,19 +226,6 @@ public class UserServiceImpl implements UserService {
return entity;
}
@Override
@Transactional
public User updateToken(UserTokenModifyDto data, Principal principal) throws UserNotFoundException {
/* check */
final User user = findByUsername(principal.getName());
/* save */
user.setInvenioToken(data.getInvenioToken());
final User entity = userRepository.save(user);
log.info("Updated user with id {}", entity.getId());
log.debug("updated user {}", entity);
return entity;
}
@Override
@Transactional
public User updateEmail(Long id, UserEmailDto data) throws UserNotFoundException {
......
......@@ -22,10 +22,6 @@ public class ImageBriefDto {
@Parameter(required = true, example = "mariadb")
private String repository;
@ToString.Exclude
@Parameter(required = true, example = "base64:aaaa")
private String logo;
@NotBlank
@Parameter(required = true, example = "10.5")
private String tag;
......
......@@ -36,10 +36,6 @@ public class ImageDto {
@Parameter(required = true, example = "org.postgresql.Driver")
private String driverClass;
@ToString.Exclude
@Parameter(required = true)
private String logo;
@JsonProperty("date_formats")
private List<ImageDateDto> dateFormats;
......
......@@ -42,9 +42,11 @@ public class UserBriefDto {
@Parameter(name = "last name")
private String lastname;
@ToString.Exclude
@JsonIgnore
private String invenioToken;
@Parameter(name = "affiliation")
private String affiliation;
@Parameter(name = "orcid")
private String orcid;
@NotNull
@Parameter(name = "mail address")
......
......@@ -43,6 +43,12 @@ public class UserDto {
@Parameter(name = "last name")
private String lastname;
@Parameter(name = "affiliation")
private String affiliation;
@Parameter(name = "orcid")
private String orcid;
@Parameter(name = "list of containers")
private List<ContainerDto> containers;
......@@ -52,13 +58,6 @@ public class UserDto {
@Parameter(name = "list of identifiers")
private List<ContainerDto> identifiers;
@ToString.Exclude
@JsonIgnore
private String invenioToken;
@JsonProperty("has_invenio_token")
private Boolean hasInvenioToken;
@ToString.Exclude
@JsonIgnore
@Parameter(name = "password hash")
......
......@@ -31,4 +31,10 @@ public class UserUpdateDto {
@Parameter(name = "last name")
private String lastname;
@Parameter(name = "affiliation")
private String affiliation;
@Parameter(name = "orcid")
private String orcid;
}
......@@ -51,9 +51,11 @@ public class User {
@Column(name = "main_email", unique = true, nullable = false)
private String email;
@ToString.Exclude
@Column(name = "invenio_token")
private String invenioToken;
@Column
private String affiliation;
@Column
private String orcid;
@Column(name = "main_email_verified", nullable = false)
private Boolean emailVerified;
......
......@@ -149,12 +149,13 @@ CREATE TABLE IF NOT EXISTS mdb_users
First_name VARCHAR(50),
Last_name VARCHAR(50),
Gender gender,
Preceding_titles VARCHAR(50),
Postpositioned_title VARCHAR(50),
Preceding_titles VARCHAR(255),
Postpositioned_title VARCHAR(255),
orcid VARCHAR(16),
affiliation VARCHAR(255),
Main_Email VARCHAR(255) not null,
main_email_verified bool not null default false,
password VARCHAR(255) not null,
invenio_token VARCHAR(255),
created timestamp without time zone NOT NULL DEFAULT NOW(),
last_modified timestamp without time zone,
PRIMARY KEY (UserID),
......
......@@ -96,7 +96,7 @@ public class QueryEndpoint extends AbstractEndpoint {
@NotNull @PathVariable("queryId") Long queryId,
@NotNull Principal principal)
throws QueryStoreException, QueryNotFoundException, DatabaseNotFoundException, ImageNotSupportedException,
ContainerNotFoundException, TableMalformedException, FileStorageException, NotAllowedException {
ContainerNotFoundException, TableMalformedException, FileStorageException, NotAllowedException, QueryMalformedException {
if (!hasQueryPermission(databaseId, queryId, "QUERY_EXPORT", principal)) {
log.error("Missing export query permission");
throw new NotAllowedException("Missing export query permission");
......
......@@ -14,7 +14,6 @@ import at.tuwien.querystore.Query;
import at.tuwien.entities.database.table.Table;
import at.tuwien.entities.database.table.columns.TableColumn;
import at.tuwien.exception.ImageNotSupportedException;
import net.sf.jsqlparser.statement.select.FromItem;
import net.sf.jsqlparser.statement.select.SelectItem;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
......@@ -29,8 +28,8 @@ import java.text.Normalizer;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
......@@ -40,6 +39,9 @@ public interface QueryMapper {
org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(QueryMapper.class);
DateTimeFormatter mariaDbFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
.withZone(ZoneId.of("UTC"));
@Deprecated
@Mappings({
@Mapping(source = "query", target = "statement")
......@@ -297,27 +299,39 @@ public interface QueryMapper {
});
query.append("FROM `")
.append(table.getInternalName())
.append("` INTO OUTFILE '/tmp/")
.append(filename)
.append("' CHARACTER SET utf8");
.append("`");
if (timestamp != null) {
query.append(" FOR SYSTEM_TIME AS OF TIMESTAMP'")
.append(LocalDateTime.ofInstant(timestamp, ZoneId.of("Europe/Vienna")))
.append(mariaDbFormatter.format(timestamp))
.append("'");
}
query.append(" INTO OUTFILE '/tmp/")
.append(filename)
.append("' CHARACTER SET utf8");
query.append(";");
return query.toString();
}
default String queryToRawExportQuery(Query query, String filename) {
default String queryToRawExportQuery(Query query, String filename) throws QueryMalformedException {
if (query.getQuery().contains(";")) {
log.trace("Remove ending ; from statement [{}]", query.getQuery());
query.setQuery(query.getQuery().substring(0, query.getQuery().indexOf(";")));
}
final StringBuilder statement = new StringBuilder(query.getQuery())
.append(" FOR SYSTEM_TIME AS OF TIMESTAMP'")
.append(LocalDateTime.ofInstant(query.getExecution(), ZoneId.of("Europe/Vienna")))
.append("' INTO OUTFILE '/tmp/")
/* insert the FOR SYSTEM_TIME ... part after the FROM in the query */
final StringBuilder versionPart = new StringBuilder(" FOR SYSTEM_TIME AS OF TIMESTAMP'")
.append(mariaDbFormatter.format(query.getExecution()))
.append("' ");
final Pattern pattern = Pattern.compile("from `?[a-zA-Z0-9_]+`?", Pattern.CASE_INSENSITIVE) /* https://mariadb.com/kb/en/columnstore-naming-conventions/ */;
final Matcher matcher = pattern.matcher(query.getQuery());
if (!matcher.find()) {
log.error("Failed to find 'from' clause in query");
throw new QueryMalformedException("Failed to find from clause");
}
log.debug("found group from {} to {} in '{}'", matcher.start(), matcher.end(), query.getQuery());
final StringBuilder statement = new StringBuilder(query.getQuery().substring(0, matcher.end(0)))
.append(versionPart)
.append(query.getQuery().substring(matcher.end(0)))
.append(" INTO OUTFILE '/tmp/")
.append(filename)
.append("' CHARACTER SET utf8 FIELDS TERMINATED BY ',';");
log.trace("raw export query: [{}]", statement);
......
......@@ -127,7 +127,7 @@ public interface QueryService {
*/
ExportResource findOne(Long containerId, Long databaseId, Long queryId)
throws DatabaseNotFoundException, ImageNotSupportedException, TableMalformedException,
ContainerNotFoundException, FileStorageException, QueryStoreException, QueryNotFoundException;
ContainerNotFoundException, FileStorageException, QueryStoreException, QueryNotFoundException, QueryMalformedException;
/**
* Count the total tuples for a given table id within a container-database id tuple at a given time.
......
......@@ -206,7 +206,7 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService
@Transactional(readOnly = true)
public ExportResource findOne(Long containerId, Long databaseId, Long queryId)
throws DatabaseNotFoundException, ImageNotSupportedException, TableMalformedException,
ContainerNotFoundException, FileStorageException, QueryStoreException, QueryNotFoundException {
ContainerNotFoundException, FileStorageException, QueryStoreException, QueryNotFoundException, QueryMalformedException {
/* find */
final Database database = databaseService.find(databaseId);
final Query query = storeService.findOne(containerId, databaseId, queryId);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment