Commit b802ab9e authored by Valentin Suhnjov's avatar Valentin Suhnjov

Merge branch 'releases/release-0.7.0' into master

parents 9092e457 cb17e377
image: riha-test-env
variables:
DEPLOYMENT_DIR: "/opt/riha/$CI_PROJECT_NAME"
ARTIFACT_NAME: "$CI_PROJECT_NAME.jar"
DEPLOYMENT_DIR: "/opt/tomcat/webapps"
ARTIFACT_NAME: "ROOT.war"
stages:
- test
......@@ -25,7 +25,7 @@ build:
- ./build.sh
artifacts:
paths:
- backend/target/*.jar
- backend/target/*.war
tags:
- riha
......@@ -36,12 +36,10 @@ deploy_development:
- chmod 700 id_rsa
- mkdir $HOME/.ssh
- echo "$SSH_HOST_KEY" > $HOME/.ssh/known_hosts
- scp -i id_rsa backend/target/*.jar deployer@$SSH_HOST:$DEPLOYMENT_DIR/$ARTIFACT_NAME
- ssh -i id_rsa deployer@$SSH_HOST "/bin/chmod ug+x $DEPLOYMENT_DIR/$ARTIFACT_NAME"
- ssh -i id_rsa deployer@$SSH_HOST "/sbin/service $CI_PROJECT_NAME restart"
- scp -i id_rsa backend/target/*.war deployer@$SSH_HOST:$DEPLOYMENT_DIR/$ARTIFACT_NAME
environment:
name: development
url: http://$SSH_HOST:$PORT/
url: http://$SSH_HOST:$PORT/$CI_PROJECT_NAME
when: manual
tags:
- riha
\ No newline at end of file
......@@ -7,10 +7,12 @@
<name>Backend</name>
<description>backend</description>
<packaging>war</packaging>
<parent>
<groupId>ee.ria.riha</groupId>
<artifactId>browser</artifactId>
<version>0.6.0</version>
<version>0.7.0</version>
</parent>
<dependencies>
......@@ -72,6 +74,11 @@
<artifactId>storage-client</artifactId>
<version>0.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
......@@ -88,9 +95,6 @@
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<executable>true</executable>
</configuration>
</plugin>
<plugin>
<groupId>com.github.kongchen</groupId>
......
......@@ -2,11 +2,18 @@ package ee.ria.riha;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;
@SpringBootApplication
public class BrowserApplication {
public class BrowserApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(BrowserApplication.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(BrowserApplication.class);
}
}
......@@ -12,6 +12,7 @@ import java.security.Principal;
@ToString
public class EstEIDPrincipal implements Principal {
private static final String PREFIX = "EE";
private String serialNumber;
private String givenName;
private String surname;
......@@ -22,7 +23,7 @@ public class EstEIDPrincipal implements Principal {
@Override
public String getName() {
return serialNumber;
return PREFIX + serialNumber;
}
public String getSerialNumber() {
......
......@@ -37,7 +37,7 @@ public class RihaLdapUserDetailsContextMapper extends LdapUserDetailsMapper {
private static final String COMMON_NAME_ATTRIBUTE = "cn";
private static final String DISPLAY_NAME_ATTRIBUTE = "displayname";
private static final String ROLE_PREFIX = "ROLE_";
private static final String DEFAULT_RIHA_USER_ROLE = "ROLE_RIHA_USER";
private static final String DEFAULT_RIHA_USER_ROLE = ROLE_PREFIX + "RIHA_USER";
private LdapTemplate ldapTemplate;
......
......@@ -57,7 +57,7 @@ public class RihaOrganizationAwareAuthenticationToken extends PreAuthenticatedAu
combinedAuthorities.addAll(organizationAuthorities.get(this.activeOrganization));
}
return combinedAuthorities.isEmpty() ? null : ImmutableSet.copyOf(combinedAuthorities);
return ImmutableSet.copyOf(combinedAuthorities);
}
@Override
......
......@@ -9,6 +9,9 @@ import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.Assert;
import java.util.Collection;
import java.util.StringJoiner;
import static org.springframework.util.StringUtils.hasText;
/**
* RIHA user details including additional information like personal code and name.
......@@ -61,6 +64,19 @@ public class RihaUserDetails implements UserDetails {
this.lastName = lastName;
}
public String getFullName() {
StringJoiner stringJoiner = new StringJoiner(" ");
if (hasText(firstName)) {
stringJoiner.add(firstName);
}
if (hasText(lastName)) {
stringJoiner.add(lastName);
}
return stringJoiner.toString();
}
public Multimap<RihaOrganization, GrantedAuthority> getOrganizationAuthorities() {
return organizationAuthorities;
}
......
......@@ -5,6 +5,7 @@ import ee.ria.riha.domain.InfoSystemRepository;
import ee.ria.riha.domain.RihaStorageInfoSystemRepository;
import ee.ria.riha.service.JsonValidationService;
import ee.ria.riha.storage.client.StorageClient;
import ee.ria.riha.storage.domain.CommentRepository;
import ee.ria.riha.storage.domain.MainResourceRepository;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
......@@ -35,6 +36,11 @@ public class ApplicationConfiguration {
return new RihaStorageInfoSystemRepository(mainResourceRepository);
}
@Bean
public CommentRepository commentRepository(ApplicationProperties applicationProperties) {
return new CommentRepository(getStorageClient(applicationProperties));
}
@Bean
public JsonValidationService jsonValidationService(ApplicationProperties applicationProperties) throws IOException {
return new JsonValidationService(
......
......@@ -16,7 +16,6 @@ public class ApplicationProperties {
public static final String API_V1_PREFIX = "/api/v1";
private final RemoteApi remoteApi = new RemoteApi();
private final StorageClientProperties storageClient = new StorageClientProperties();
private final AuthenticationProperties authentication = new AuthenticationProperties();
private final ValidationProperties validation = new ValidationProperties();
......@@ -28,12 +27,6 @@ public class ApplicationProperties {
private String baseUrl;
}
@Getter
@Setter
public static class RemoteApi {
private String approverUrl;
}
@Getter
@Setter
public static class AuthenticationProperties {
......
......@@ -22,7 +22,7 @@ import org.springframework.security.web.authentication.preauth.PreAuthenticatedA
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
......
......@@ -6,6 +6,7 @@ import ee.ria.riha.storage.util.Pageable;
import ee.ria.riha.storage.util.PagedResponse;
import java.util.List;
import java.util.UUID;
/**
* Interface for repositories that persist InfoSystem entities.
......@@ -18,6 +19,8 @@ public interface InfoSystemRepository {
InfoSystem load(String shortName);
InfoSystem load(UUID uuid);
void update(String shortName, InfoSystem infoSystem);
void remove(String shortName);
......
......@@ -7,6 +7,7 @@ import ee.ria.riha.storage.domain.model.MainResource;
import ee.ria.riha.storage.util.*;
import java.util.List;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
......@@ -60,6 +61,18 @@ public class RihaStorageInfoSystemRepository implements InfoSystemRepository {
return infoSystems.get(0);
}
@Override
public InfoSystem load(UUID uuid) {
FilterRequest filter = new FilterRequest("uuid,=," + uuid.toString(), null, null);
List<InfoSystem> infoSystems = find(filter);
if (infoSystems.isEmpty()) {
throw new ObjectNotFoundException("Could not resolve info system by uuid " + uuid.toString());
}
return infoSystems.get(0);
}
@Override
public void update(String shortName, InfoSystem infoSystem) {
throw new RuntimeException(NOT_IMPLEMENTED);
......
package ee.ria.riha.domain.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
import java.util.UUID;
import static ee.ria.riha.domain.model.IssueEntityType.ISSUE;
/**
* Issue entity model
*
* @author Valentin Suhnjov
*/
@Getter
@Setter
@Builder
@AllArgsConstructor
public class Issue implements IssueEntity {
private Long id;
private UUID infoSystemUuid;
private Date dateCreated;
private String title;
private String comment;
private String authorName;
private String authorPersonalCode;
private String organizationName;
private String organizationCode;
private IssueStatus status;
public Issue() {
}
@Override
public IssueEntityType getEntityType() {
return ISSUE;
}
}
package ee.ria.riha.domain.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
import static ee.ria.riha.domain.model.IssueEntityType.ISSUE_COMMENT;
/**
* Issue comment entity model
*
* @author Valentin Suhnjov
*/
@Getter
@Setter
@Builder
@AllArgsConstructor
public class IssueComment implements IssueEntity {
private Long id;
private Long issueId;
private String comment;
private Date dateCreated;
private String authorName;
private String authorPersonalCode;
private String organizationName;
private String organizationCode;
public IssueComment() {
}
@Override
public IssueEntityType getEntityType() {
return ISSUE_COMMENT;
}
}
package ee.ria.riha.domain.model;
/**
* @author Valentin Suhnjov
*/
public interface IssueEntity {
IssueEntityType getEntityType();
}
package ee.ria.riha.domain.model;
/**
* @author Valentin Suhnjov
*/
public enum IssueEntityType {
/**
* Issue entity discriminator
*/
ISSUE,
/**
* Issue comment entity discriminator
*/
ISSUE_COMMENT,
/**
* Issue event entity discriminator
*/
ISSUE_EVENT;
}
package ee.ria.riha.domain.model;
import lombok.Builder;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import java.util.Date;
import static ee.ria.riha.domain.model.IssueEntityType.ISSUE_EVENT;
/**
* Issue status update event
*
* @author Valentin Suhnjov
*/
@Getter
@Setter
@Builder
public class IssueEvent implements IssueEntity {
private Long id;
private Long issueId;
@NonNull
private IssueEventType type;
private Date dateCreated;
private String authorName;
private String authorPersonalCode;
private String organizationName;
private String organizationCode;
@Override
public IssueEntityType getEntityType() {
return ISSUE_EVENT;
}
}
package ee.ria.riha.domain.model;
/**
* @author Valentin Suhnjov
*/
public enum IssueEventType {
CLOSED;
}
package ee.ria.riha.domain.model;
/**
* Status of an issue.
*
* @author Valentin Suhnjov
*/
public enum IssueStatus {
OPEN,
CLOSED;
}
......@@ -4,9 +4,6 @@ import ee.ria.riha.conf.ApplicationProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
/**
* @author Valentin Suhnjov
*/
......@@ -19,14 +16,6 @@ public class EnvironmentService {
@Autowired
private UserService userService;
public Map<String, Object> getEnvironment() {
HashMap<String, Object> environment = new HashMap<>();
environment.put("remotes", applicationProperties.getRemoteApi());
return environment;
}
/**
* @deprecated use user service instead
*/
......
......@@ -9,4 +9,15 @@ public class IllegalBrowserStateException extends BrowserException {
super(message);
}
public IllegalBrowserStateException() {
super();
}
public IllegalBrowserStateException(String message, Throwable cause) {
super(message, cause);
}
public IllegalBrowserStateException(Throwable cause) {
super(cause);
}
}
\ No newline at end of file
package ee.ria.riha.service;
import ee.ria.riha.authentication.RihaOrganization;
import ee.ria.riha.authentication.RihaUserDetails;
import ee.ria.riha.domain.InfoSystemRepository;
import ee.ria.riha.domain.model.InfoSystem;
import ee.ria.riha.storage.util.FilterRequest;
import ee.ria.riha.storage.util.Filterable;
import ee.ria.riha.storage.util.Pageable;
import ee.ria.riha.storage.util.PagedResponse;
......@@ -11,10 +11,11 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.UUID;
import static ee.ria.riha.service.SecurityContextUtil.getActiveOrganization;
import static ee.ria.riha.service.SecurityContextUtil.getRihaUserDetails;
import static ee.ria.riha.service.SecurityContextUtil.getRihaUserPersonalCode;
/**
* @author Valentin Suhnjov
......@@ -41,24 +42,21 @@ public class InfoSystemService {
* @return created {@link InfoSystem}
*/
public InfoSystem create(InfoSystem model) {
RihaUserDetails rihaUserDetails = getRihaUserDetails();
if (rihaUserDetails == null) {
throw new IllegalBrowserStateException("User must be valid RIHA logged in user");
}
RihaOrganization organization = getActiveOrganization();
if (organization == null) {
throw new IllegalBrowserStateException("Active organization must be set");
throw new ValidationException("validation.generic.activeOrganization.notSet");
}
InfoSystem infoSystem = new InfoSystem(model.getJsonObject());
log.info("User '{}' with active organization '{}'" +
" is creating new info system with short name '{}'",
getRihaUserPersonalCode(), organization, infoSystem.getShortName());
validateInfoSystemShortName(infoSystem.getShortName());
infoSystem.setUuid(UUID.randomUUID());
infoSystem.setOwnerCode(organization.getCode());
infoSystem.setOwnerName(organization.getName());
infoSystemValidationService.validate(infoSystem.asJson());
log.info("User with ID {} is going to create a new IS with short name {} for next organization: {}",
rihaUserDetails.getPersonalCode(), infoSystem.getShortName(), infoSystem.getOwnerName());
return infoSystemRepository.add(infoSystem);
}
......@@ -73,6 +71,16 @@ public class InfoSystemService {
return infoSystemRepository.load(shortName);
}
/**
* Retrieves {@link InfoSystem} by its short name
*
* @param uuid info system uuid
* @return retrieved {@link InfoSystem}
*/
public InfoSystem get(UUID uuid) {
return infoSystemRepository.load(uuid);
}
/**
* Creates new record with the same UUID and owner. Other parts of {@link InfoSystem} are updated from model.
*
......@@ -81,23 +89,32 @@ public class InfoSystemService {
* @return new {@link InfoSystem}
*/
public InfoSystem update(String shortName, InfoSystem model) {
RihaUserDetails rihaUserDetails = getRihaUserDetails();
if (rihaUserDetails == null) {
throw new IllegalBrowserStateException("User must be valid RIHA logged in user");
}
InfoSystem existingInfoSystem = get(shortName);
log.info("User '{}' with active organization '{}'" +
" is updating info system with id {}, owner code '{}' and short name '{}'",
getRihaUserPersonalCode(), getActiveOrganization(), existingInfoSystem.getId(),
existingInfoSystem.getOwnerCode(), existingInfoSystem.getShortName());
InfoSystem updatedInfoSystem = new InfoSystem(model.getJsonObject());
if (!shortName.equals(updatedInfoSystem.getShortName())) {
validateInfoSystemShortName(updatedInfoSystem.getShortName());
}
updatedInfoSystem.setUuid(existingInfoSystem.getUuid());
updatedInfoSystem.setOwnerCode(existingInfoSystem.getOwnerCode());
updatedInfoSystem.setOwnerName(existingInfoSystem.getOwnerName());
infoSystemValidationService.validate(updatedInfoSystem.asJson());
log.info("User with ID {} is going to update an IS with ID {} and short name {} for next organization: {}",
rihaUserDetails.getPersonalCode(), existingInfoSystem.getId(), shortName,
existingInfoSystem.getOwnerName());
return infoSystemRepository.add(updatedInfoSystem);
}
private void validateInfoSystemShortName(String shortName) {
log.debug("Checking info system '{}' existence", shortName);
FilterRequest filter = new FilterRequest("short_name,=," + shortName, null, null);
List<InfoSystem> infoSystems = infoSystemRepository.find(filter);
if (!infoSystems.isEmpty()) {
throw new ValidationException("validation.system.shortName.alreadyTaken", shortName);
}
}
}
package ee.ria.riha.service;
import ee.ria.riha.authentication.RihaOrganization;
import ee.ria.riha.authentication.RihaUserDetails;
import ee.ria.riha.domain.model.IssueComment;
import ee.ria.riha.domain.model.IssueEntityType;
import ee.ria.riha.storage.domain.CommentRepository;
import ee.ria.riha.storage.domain.model.Comment;
import ee.ria.riha.storage.util.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.function.Function;
import static ee.ria.riha.service.SecurityContextUtil.getActiveOrganization;
import static ee.ria.riha.service.SecurityContextUtil.getRihaUserDetails;
import static java.util.stream.Collectors.toList;
/**
* Info system issue comment service.
*
* @author Valentin Suhnjov
*/
@Service
public class IssueCommentService {
public static final Function<Comment, IssueComment> COMMENT_TO_ISSUE_COMMENT = comment -> {
if (comment == null) {
return null;
}
return IssueComment.builder()
.id(comment.getComment_id())
.dateCreated(comment.getCreation_date())
.issueId(comment.getComment_parent_id())
.comment(comment.getComment())
.authorName(comment.getAuthor_name())
.authorPersonalCode(comment.getAuthor_personal_code())
.organizationName(comment.getOrganization_name())
.organizationCode(comment.getOrganization_code())
.build();
};
public static final Function<IssueComment, Comment> ISSUE_COMMENT_TO_COMMENT = issueComment -> {
if (issueComment == null) {
return null;
}
Comment comment = new Comment();
comment.setType(IssueEntityType.ISSUE_COMMENT.name());
comment.setComment_id(issueComment.getId());
comment.setComment_parent_id(issueComment.getIssueId());
comment.setComment(issueComment.getComment());
comment.setAuthor_name(issueComment.getAuthorName());
comment.setAuthor_personal_code(issueComment.getAuthorPersonalCode());
comment.setOrganization_name(issueComment.getOrganizationName());
comment.setOrganization_code(issueComment.getOrganizationCode());
return comment;
};
@Autowired
private CommentRepository commentRepository;
/**
* List concrete info system concrete issue comments.
*
* @param issueId issue id
* @param pageable paging definition
* @param filterable filter definition
* @return paginated list of issue comments
*/
public PagedResponse<IssueComment> listIssueComments(Long issueId, Pageable pageable, Filterable filterable) {
Filterable filter = new FilterRequest(filterable.getFilter(), filterable.getSort(), filterable.getFields())
.addFilter(getIssueCommentTypeFilter())
.addFilter(getIssueIdEqFilter(issueId));
PagedResponse<Comment> response = commentRepository.list(pageable, filter);
return new PagedResponse<>(new PageRequest(response.getPage(), response.getSize()),
response.getTotalElements(),
response.getContent().stream()