diff --git a/fda-metadata-db/entities/src/main/java/at/tuwien/entities/user/Role.java b/fda-metadata-db/entities/src/main/java/at/tuwien/entities/user/Role.java new file mode 100644 index 0000000000000000000000000000000000000000..f1a8b1031b43134280b1d55d503a415ed1637820 --- /dev/null +++ b/fda-metadata-db/entities/src/main/java/at/tuwien/entities/user/Role.java @@ -0,0 +1,44 @@ +package at.tuwien.entities.user; + +import lombok.*; +import org.hibernate.annotations.GenericGenerator; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import javax.persistence.*; + +@Data +@Entity +@Builder +@AllArgsConstructor +@NoArgsConstructor +@ToString +@EntityListeners(AuditingEntityListener.class) +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@Table(name = "keycloak_role") +public class Role { + + @Id + @EqualsAndHashCode.Include + @GeneratedValue(generator = "role-uuid") + @GenericGenerator(name = "role-uuid", strategy = "org.hibernate.id.UUIDGenerator") + @Column(name = "ID", nullable = false, columnDefinition = "VARCHAR(36)") + private String id; + + @Column(name = "NAME", nullable = false) + private String name; + + @Column(name = "REALM_ID", nullable = false) + private String realmId; + + @ToString.Exclude + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @JoinTable(name = "user_role_mapping", + joinColumns = { + @JoinColumn(name = "ROLE_ID", referencedColumnName = "ID", insertable = false, updatable = false), + }, + inverseJoinColumns = { + @JoinColumn(name = "USER_ID", referencedColumnName = "ID", insertable = false, updatable = false), + }) + private User user; + +} diff --git a/fda-metadata-db/entities/src/main/java/at/tuwien/entities/user/RoleMapping.java b/fda-metadata-db/entities/src/main/java/at/tuwien/entities/user/RoleMapping.java new file mode 100644 index 0000000000000000000000000000000000000000..4ef61ea2ef0e32c1b52da2f96dec404d1b153312 --- /dev/null +++ b/fda-metadata-db/entities/src/main/java/at/tuwien/entities/user/RoleMapping.java @@ -0,0 +1,31 @@ +package at.tuwien.entities.user; + +import lombok.*; +import org.hibernate.annotations.GenericGenerator; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import javax.persistence.*; + +@Data +@Entity +@Builder +@AllArgsConstructor +@NoArgsConstructor +@ToString +@IdClass(RoleMappingKey.class) +@EntityListeners(AuditingEntityListener.class) +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@Table(name = "user_role_mapping") +public class RoleMapping { + + @Id + @EqualsAndHashCode.Include + @Column(name = "USER_ID", nullable = false) + private String userId; + + @Id + @EqualsAndHashCode.Include + @Column(name = "ROLE_ID", nullable = false) + private String roleId; + +} diff --git a/fda-metadata-db/entities/src/main/java/at/tuwien/entities/user/RoleMappingKey.java b/fda-metadata-db/entities/src/main/java/at/tuwien/entities/user/RoleMappingKey.java new file mode 100644 index 0000000000000000000000000000000000000000..7e96f96b1f12ee3ca306f8526540eebcb78eea06 --- /dev/null +++ b/fda-metadata-db/entities/src/main/java/at/tuwien/entities/user/RoleMappingKey.java @@ -0,0 +1,14 @@ +package at.tuwien.entities.user; + +import lombok.EqualsAndHashCode; + +import java.io.Serializable; + +@EqualsAndHashCode +public class RoleMappingKey implements Serializable { + + private String userId; + + private String roleId; + +} diff --git a/fda-metadata-db/entities/src/main/java/at/tuwien/entities/user/User.java b/fda-metadata-db/entities/src/main/java/at/tuwien/entities/user/User.java index af7c01362072239002a8639480010b90b403932a..87d933a27d2a9bb2719ed5590c7679065a79df0c 100644 --- a/fda-metadata-db/entities/src/main/java/at/tuwien/entities/user/User.java +++ b/fda-metadata-db/entities/src/main/java/at/tuwien/entities/user/User.java @@ -58,6 +58,11 @@ public class User { @Column(nullable = false) private String databasePassword; + @Column(nullable = false) + @ToString.Exclude + @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "user") + private List<Role> roles; + @Column(nullable = false) @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "user") private List<Credential> credentials; diff --git a/fda-ui/api/user/index.js b/fda-ui/api/user/index.js index 670535c449675ff6a20af05e5bc89fedb65e6bac..feb0b974e728e86401d28db318a4e1d5abe03770 100644 --- a/fda-ui/api/user/index.js +++ b/fda-ui/api/user/index.js @@ -10,7 +10,7 @@ export function authenticate (clientSecret, username, password) { password, grant_type: 'password', client_secret: clientSecret, - scope: 'openid roles' + scope: 'openid profile roles' } return axios.post('/api/auth/realms/dbrepo/protocol/openid-connect/token', qs.stringify(payload), { headers: { ContentType: 'application/form-data' } diff --git a/fda-user-service/rest-service/src/main/java/at/tuwien/endpoint/UserEndpoint.java b/fda-user-service/rest-service/src/main/java/at/tuwien/endpoint/UserEndpoint.java index 0d5148892f22a65c92272cd841f559879203d929..0939c16efa5969b78d4b755c25cee39f9f007e34 100644 --- a/fda-user-service/rest-service/src/main/java/at/tuwien/endpoint/UserEndpoint.java +++ b/fda-user-service/rest-service/src/main/java/at/tuwien/endpoint/UserEndpoint.java @@ -3,12 +3,11 @@ package at.tuwien.endpoint; import at.tuwien.api.auth.SignupRequestDto; import at.tuwien.api.user.UserBriefDto; import at.tuwien.entities.auth.Realm; -import at.tuwien.exception.RealmNotFoundException; -import at.tuwien.exception.RemoteUnavailableException; -import at.tuwien.exception.UserAlreadyExistsException; -import at.tuwien.exception.UserNotFoundException; +import at.tuwien.entities.user.Role; +import at.tuwien.exception.*; import at.tuwien.mapper.UserMapper; import at.tuwien.service.RealmService; +import at.tuwien.service.RoleService; import at.tuwien.service.UserService; import io.micrometer.core.annotation.Timed; import io.swagger.v3.oas.annotations.Operation; @@ -33,12 +32,15 @@ public class UserEndpoint { private final UserMapper userMapper; private final UserService userService; private final RealmService realmService; + private final RoleService roleService; @Autowired - public UserEndpoint(UserMapper userMapper, UserService userService, RealmService realmService) { + public UserEndpoint(UserMapper userMapper, UserService userService, RealmService realmService, + RoleService roleService) { this.userMapper = userMapper; this.userService = userService; this.realmService = realmService; + this.roleService = roleService; } @GetMapping @@ -61,10 +63,11 @@ public class UserEndpoint { @Operation(summary = "Create a user") public ResponseEntity<UserBriefDto> create(@NotNull @Valid @RequestBody SignupRequestDto data) throws UserNotFoundException, RemoteUnavailableException, RealmNotFoundException, - UserAlreadyExistsException { + UserAlreadyExistsException, RoleNotFoundException { log.debug("endpoint create a user, data={}", data); final Realm realm = realmService.find("dbrepo"); - final UserBriefDto dto = userMapper.userToUserBriefDto(userService.create(data, realm)); + final Role role = roleService.find("default-researcher-roles"); + final UserBriefDto dto = userMapper.userToUserBriefDto(userService.create(data, realm, role)); log.trace("create user resulted in dto {}", dto); return ResponseEntity.status(HttpStatus.CREATED) .body(dto); diff --git a/fda-user-service/services/src/main/java/at/tuwien/exception/RoleNotFoundException.java b/fda-user-service/services/src/main/java/at/tuwien/exception/RoleNotFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..8430d64d76c7a422a6e17752398c491cf6f78cd3 --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/exception/RoleNotFoundException.java @@ -0,0 +1,21 @@ +package at.tuwien.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.NOT_FOUND) +public class RoleNotFoundException extends Exception { + + public RoleNotFoundException(String msg) { + super(msg); + } + + public RoleNotFoundException(String msg, Throwable thr) { + super(msg, thr); + } + + public RoleNotFoundException(Throwable thr) { + super(thr); + } + +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/repository/jpa/RoleMappingRepository.java b/fda-user-service/services/src/main/java/at/tuwien/repository/jpa/RoleMappingRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..fd7261d3a85245155a6254fee75223a5c4d97bff --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/repository/jpa/RoleMappingRepository.java @@ -0,0 +1,9 @@ +package at.tuwien.repository.jpa; + +import at.tuwien.entities.user.RoleMapping; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface RoleMappingRepository extends JpaRepository<RoleMapping, String> { +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/repository/jpa/RoleRepository.java b/fda-user-service/services/src/main/java/at/tuwien/repository/jpa/RoleRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..6efb6c207110626771b2e67234e5779a0a23e2dc --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/repository/jpa/RoleRepository.java @@ -0,0 +1,13 @@ +package at.tuwien.repository.jpa; + +import at.tuwien.entities.user.Role; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface RoleRepository extends JpaRepository<Role, String> { + + Optional<Role> findByName(String name); +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/service/RoleService.java b/fda-user-service/services/src/main/java/at/tuwien/service/RoleService.java new file mode 100644 index 0000000000000000000000000000000000000000..08c3a0cc3f693ce325eca257f4e972466f99bd6d --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/service/RoleService.java @@ -0,0 +1,8 @@ +package at.tuwien.service; + +import at.tuwien.entities.user.Role; +import at.tuwien.exception.RoleNotFoundException; + +public interface RoleService { + Role find(String name) throws RoleNotFoundException; +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/service/UserService.java b/fda-user-service/services/src/main/java/at/tuwien/service/UserService.java index 33addb5732aac74028b7b5e9ce480bed32eda19a..d60d9ab9b3931f6502e3f38692e7c65e1b5ced13 100644 --- a/fda-user-service/services/src/main/java/at/tuwien/service/UserService.java +++ b/fda-user-service/services/src/main/java/at/tuwien/service/UserService.java @@ -2,6 +2,7 @@ package at.tuwien.service; import at.tuwien.api.auth.SignupRequestDto; import at.tuwien.entities.auth.Realm; +import at.tuwien.entities.user.Role; import at.tuwien.entities.user.User; import at.tuwien.exception.RemoteUnavailableException; import at.tuwien.exception.UserAlreadyExistsException; @@ -27,7 +28,7 @@ public interface UserService { */ User findByUsername(String username) throws UserNotFoundException; - User create(SignupRequestDto data, Realm realm) throws RemoteUnavailableException, UserNotFoundException, UserAlreadyExistsException; + User create(SignupRequestDto data, Realm realm, Role role) throws RemoteUnavailableException, UserNotFoundException, UserAlreadyExistsException; User find(String id) throws UserNotFoundException; } diff --git a/fda-user-service/services/src/main/java/at/tuwien/service/impl/RoleServiceImpl.java b/fda-user-service/services/src/main/java/at/tuwien/service/impl/RoleServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..7766f31afb2972dba07689199c92d51b802a2048 --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/service/impl/RoleServiceImpl.java @@ -0,0 +1,36 @@ +package at.tuwien.service.impl; + +import at.tuwien.entities.user.Role; +import at.tuwien.exception.RoleNotFoundException; +import at.tuwien.repository.jpa.RoleRepository; +import at.tuwien.service.RoleService; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Log4j2 +@Service +public class RoleServiceImpl implements RoleService { + + private final RoleRepository roleRepository; + + @Autowired + public RoleServiceImpl(RoleRepository roleRepository) { + this.roleRepository = roleRepository; + } + + @Override + public Role find(String name) throws RoleNotFoundException { + final Optional<Role> optional = roleRepository.findByName(name); + if (optional.isEmpty()) { + log.error("Failed to find role with name {}", name); + throw new RoleNotFoundException("Failed to find role with name " + name); + } + final Role role = optional.get(); + log.trace("found role {}", role); + return role; + } + +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/service/impl/UserServiceImpl.java b/fda-user-service/services/src/main/java/at/tuwien/service/impl/UserServiceImpl.java index 514460cf5642aaf48706b9fe06324e15dc96d186..610bbc422dbded384709944741ddecd7441c408c 100644 --- a/fda-user-service/services/src/main/java/at/tuwien/service/impl/UserServiceImpl.java +++ b/fda-user-service/services/src/main/java/at/tuwien/service/impl/UserServiceImpl.java @@ -3,12 +3,15 @@ package at.tuwien.service.impl; import at.tuwien.api.auth.SignupRequestDto; import at.tuwien.entities.auth.Realm; import at.tuwien.entities.user.Credential; +import at.tuwien.entities.user.Role; +import at.tuwien.entities.user.RoleMapping; import at.tuwien.entities.user.User; import at.tuwien.exception.RemoteUnavailableException; import at.tuwien.exception.UserAlreadyExistsException; import at.tuwien.exception.UserNotFoundException; import at.tuwien.mapper.UserMapper; import at.tuwien.repository.jpa.CredentialRepository; +import at.tuwien.repository.jpa.RoleMappingRepository; import at.tuwien.repository.jpa.UserRepository; import at.tuwien.service.UserService; import lombok.extern.log4j.Log4j2; @@ -41,13 +44,15 @@ public class UserServiceImpl implements UserService { private final UserMapper userMapper; private final UserRepository userRepository; private final CredentialRepository credentialRepository; + private final RoleMappingRepository roleMappingRepository; @Autowired public UserServiceImpl(UserMapper userMapper, UserRepository userRepository, - CredentialRepository credentialRepository) { + CredentialRepository credentialRepository, RoleMappingRepository roleMappingRepository) { this.userMapper = userMapper; this.userRepository = userRepository; this.credentialRepository = credentialRepository; + this.roleMappingRepository = roleMappingRepository; } @Override @@ -66,7 +71,7 @@ public class UserServiceImpl implements UserService { } @Override - public User create(SignupRequestDto data, Realm realm) throws RemoteUnavailableException, UserNotFoundException, + public User create(SignupRequestDto data, Realm realm, Role role) throws RemoteUnavailableException, UserNotFoundException, UserAlreadyExistsException { /* check */ final Optional<User> optional = userRepository.findByUsername(data.getUsername()); @@ -75,13 +80,6 @@ public class UserServiceImpl implements UserService { throw new UserAlreadyExistsException("User with username " + data.getUsername() + " already exists"); } /* create secret */ - - /* save */ - final User tmp = userMapper.signupRequestDtoToUser(data); - tmp.setEmailVerified(false); - tmp.setEnabled(true); - tmp.setRealmId(realm.getId()); - tmp.setCreatedTimestamp(Instant.now().toEpochMilli()); final byte[] salt = getSalt(); final StringBuilder secretData = new StringBuilder("{\"value\":\"") .append(encodedCredential(data.getPassword(), DEFAULT_ITERATIONS, salt, DERIVED_KEY_SIZE)) @@ -95,10 +93,22 @@ public class UserServiceImpl implements UserService { .priority(10) .credentialData("{\"hashIterations\":" + DEFAULT_ITERATIONS + ",\"algorithm\":\"" + ID + "\",\"additionalParameters\":{}}") .build(); + /* save */ + final User tmp = userMapper.signupRequestDtoToUser(data); + tmp.setEmailVerified(false); + tmp.setEnabled(true); + tmp.setRealmId(realm.getId()); + tmp.setCreatedTimestamp(Instant.now().toEpochMilli()); final User user = userRepository.save(tmp); entity.setUserId(user.getId()); final Credential credential = credentialRepository.save(entity); user.setCredentials(List.of(credential)); + final RoleMapping tmp2 = RoleMapping.builder() + .userId(user.getId()) + .roleId(role.getId()) + .build(); + roleMappingRepository.save(tmp2); + user.setRoles(List.of(role)); log.info("Created user with id {}", user.getId()); log.debug("created user {}", user); return user; @@ -116,7 +126,6 @@ public class UserServiceImpl implements UserService { private String encodedCredential(String rawPassword, int iterations, byte[] salt, int derivedKeySize) { final String rawPasswordWithPadding = PaddingUtils.padding(rawPassword, MAX_PADDING_LENGTH); - log.trace("padding: {}", rawPasswordWithPadding); final KeySpec spec = new PBEKeySpec(rawPasswordWithPadding.toCharArray(), salt, iterations, derivedKeySize); try { byte[] key = getSecretKeyFactory().generateSecret(spec).getEncoded();