Commit cfad9142 authored by Kertu Hiire's avatar Kertu Hiire Committed by GitHub

Merge pull request #9 from e-gov/release/v7.1

Release/v7.1
parents 49fbfad6 26d89b75
image: riha-test-env
variables:
DEPLOYMENT_DIR: "/opt/tomcat/webapps"
ARTIFACT_NAME: "ROOT.war"
stages:
......@@ -18,7 +17,7 @@ build:
tags:
- riha
deploy_development:
deploy_demo:
stage: deploy
script:
- echo "$SSH_PRIVATE_KEY" > id_rsa
......@@ -27,8 +26,7 @@ deploy_development:
- echo "$SSH_HOST_KEY" > $HOME/.ssh/known_hosts
- scp -i id_rsa backend/target/*.war deployer@$SSH_HOST:$DEPLOYMENT_DIR/$ARTIFACT_NAME
environment:
name: development
url: http://$SSH_HOST:$PORT/$CI_PROJECT_NAME
name: demo
when: manual
tags:
- riha
\ No newline at end of file
......@@ -12,7 +12,7 @@
<parent>
<groupId>ee.ria.riha</groupId>
<artifactId>browser</artifactId>
<version>0.9.0</version>
<version>7.1.0-SNAPSHOT</version>
</parent>
<dependencies>
......@@ -86,7 +86,7 @@
<dependency>
<groupId>ee.ria.riha</groupId>
<artifactId>storage-client</artifactId>
<version>0.5.0</version>
<version>0.6.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
......
......@@ -56,7 +56,7 @@ public class RihaLdapUserDetailsContextMapper extends LdapUserDetailsMapper {
UserDetails userDetails = super.mapUserFromContext(ctx, username, grantedAuthorities);
return new RihaUserDetails(userDetails, ctx.getStringAttribute(UID_ATTRIBUTE),
getUserOrganizationRoles(ctx));
getUserOrganizationRoles(ctx));
}
private Multimap<RihaOrganization, GrantedAuthority> getUserOrganizationRoles(DirContextOperations ctx) {
......@@ -71,7 +71,7 @@ public class RihaLdapUserDetailsContextMapper extends LdapUserDetailsMapper {
if (organizationRoleMapping != null) {
RihaOrganization rihaOrganization = new RihaOrganization(organizationRoleMapping.getCode(),
organizationRoleMapping.getName());
organizationRoleMapping.getName());
organizationRoles.put(rihaOrganization, organizationRoleMapping.getAuthority());
}
}
......@@ -92,17 +92,6 @@ public class RihaLdapUserDetailsContextMapper extends LdapUserDetailsMapper {
}
}
private LdapName normalizeGroupDn(String groupDnStr) {
LdapName groupDn = newLdapName(groupDnStr);
Name baseDn = getBaseDn();
if (groupDn.startsWith(baseDn)) {
return LdapUtils.removeFirst(groupDn, baseDn);
}
return groupDn;
}
private OrganizationRoleMapping getOrganizationRoleMapping(DirContextOperations groupCtx) {
OrganizationRoleMapping organizationRoleMapping = new OrganizationRoleMapping();
......@@ -115,7 +104,7 @@ public class RihaLdapUserDetailsContextMapper extends LdapUserDetailsMapper {
String[] cnTokens = commonName.split(COMMON_NAME_TOKEN_SEPARATOR);
if (cnTokens.length != 2) {
log.debug("Expecting two tokens in organization common name '{}' but found {}", commonName,
cnTokens.length);
cnTokens.length);
}
organizationRoleMapping.setCode(cnTokens[0]);
......@@ -125,6 +114,17 @@ public class RihaLdapUserDetailsContextMapper extends LdapUserDetailsMapper {
return organizationRoleMapping;
}
private LdapName normalizeGroupDn(String groupDnStr) {
LdapName groupDn = newLdapName(groupDnStr);
Name baseDn = getBaseDn();
if (groupDn.startsWith(baseDn)) {
return LdapUtils.removeFirst(groupDn, baseDn);
}
return groupDn;
}
private Name getBaseDn() {
try {
return newLdapName(ldapTemplate.getContextSource().getReadOnlyContext().getNameInNamespace());
......
......@@ -6,6 +6,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.client.RestTemplate;
......@@ -17,6 +18,7 @@ import java.io.IOException;
@Configuration
@EnableConfigurationProperties(ApplicationProperties.class)
@EnableScheduling
@EnableAsync
public class ApplicationConfiguration {
@Bean
......
......@@ -17,7 +17,9 @@ public class ApplicationProperties {
public static final String API_V1_PREFIX = "/api/v1";
private final StorageClientProperties storageClient = new StorageClientProperties();
private final AuthenticationProperties authentication = new AuthenticationProperties();
private final LdapProperties ldap = new LdapProperties();
private final LdapAuthenticationProperties ldapAuthentication = new LdapAuthenticationProperties();
private final LdapRepositoryProperties ldapRepository = new LdapRepositoryProperties();
private final ValidationProperties validation = new ValidationProperties();
private final NotificationProperties notification = new NotificationProperties();
private final CorsProperties cors = new CorsProperties();
......@@ -35,13 +37,25 @@ public class ApplicationProperties {
@Getter
@Setter
public static class AuthenticationProperties {
public static class LdapProperties {
private String url;
private String baseDn;
private String user;
private String password;
}
@Getter
@Setter
public static class LdapAuthenticationProperties {
private String userSearchBase;
private String userSearchFilter;
private String ldapUrl;
private String ldapBaseDn;
private String ldapUser;
private String ldapPassword;
}
@Getter
@Setter
public static class LdapRepositoryProperties {
private String userSearchBase;
private String groupSearchBase;
}
@Getter
......@@ -54,9 +68,23 @@ public class ApplicationProperties {
@Setter
public static class NotificationProperties {
private final CreatedInfoSystemsOverview createdInfoSystemsOverview = new CreatedInfoSystemsOverview();
private final NewIssue newIssue = new NewIssue();
private final NewIssueComment newIssueComment = new NewIssueComment();
private String from;
}
@Getter
@Setter
public static class NewIssue {
private boolean enabled;
}
@Getter
@Setter
public static class NewIssueComment {
private boolean enabled;
}
@Getter
@Setter
public static class CreatedInfoSystemsOverview {
......
......@@ -2,12 +2,15 @@ package ee.ria.riha.conf;
import ee.ria.riha.domain.InfoSystemRepository;
import ee.ria.riha.domain.RihaStorageInfoSystemRepository;
import ee.ria.riha.domain.LdapRepository;
import ee.ria.riha.storage.client.StorageClient;
import ee.ria.riha.storage.domain.CommentRepository;
import ee.ria.riha.storage.domain.FileRepository;
import ee.ria.riha.storage.domain.MainResourceRelationRepository;
import ee.ria.riha.storage.domain.MainResourceRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.web.client.RestTemplate;
@Configuration
......@@ -33,8 +36,18 @@ public class StorageConfiguration {
return new CommentRepository(storageClient);
}
@Bean
public MainResourceRelationRepository mainResourceRelationRepository(StorageClient storageClient) {
return new MainResourceRelationRepository(storageClient);
}
@Bean
public FileRepository fileRepository(RestTemplate restTemplate, ApplicationProperties applicationProperties) {
return new FileRepository(restTemplate, applicationProperties.getStorageClient().getBaseUrl());
}
@Bean
public LdapRepository ldapRepository(LdapContextSource ldapContextSource, ApplicationProperties applicationProperties) {
return new LdapRepository(ldapContextSource, applicationProperties.getLdapRepository());
}
}
......@@ -8,17 +8,20 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.CacheControl;
import org.springframework.http.HttpStatus;
import org.springframework.util.Assert;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Configuration
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {
......@@ -64,6 +67,22 @@ public class WebMvcConfiguration extends WebMvcConfigurerAdapter {
.allowedHeaders("*");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
configureFontCacheControl(registry);
}
/**
* Fixes bug when IE does not load fonts over SSL when Cache-Control header is set to "no-cache".
*
* @param registry resource handler registry for configuration
*/
private void configureFontCacheControl(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**/*.{(eot|woff|woff2|ttf|svg)}")
.addResourceLocations("classpath:/static/", "classpath:/public/")
.setCacheControl(CacheControl.maxAge(1, TimeUnit.HOURS));
}
static class UUIDConverter implements Converter<String, UUID> {
@Override
public UUID convert(String source) {
......
package ee.ria.riha.conf;
import ee.ria.riha.authentication.*;
import ee.ria.riha.conf.ApplicationProperties.AuthenticationProperties;
import ee.ria.riha.conf.ApplicationProperties.LdapAuthenticationProperties;
import ee.ria.riha.conf.ApplicationProperties.LdapProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
......@@ -33,13 +34,21 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
auth.authenticationProvider(getEsteidPreAuthenticatedAuthenticationProvider());
}
private PreAuthenticatedAuthenticationProvider getEsteidPreAuthenticatedAuthenticationProvider() {
PreAuthenticatedAuthenticationProvider authenticationProvider = new RihaPreAuthenticatedAuthenticationProvider();
authenticationProvider.setPreAuthenticatedUserDetailsService(
new RihaPreAuthenticatedUserDetailsService(ldapUserDetailsService));
return authenticationProvider;
}
@Bean
public LdapUserDetailsService ldapUserDetailsService(ApplicationProperties applicationProperties,
LdapContextSource contextSource) {
AuthenticationProperties authenticationProperties = applicationProperties.getAuthentication();
LdapAuthenticationProperties ldapAuthenticationProperties = applicationProperties.getLdapAuthentication();
RihaFilterBasedLdapUserSearch userSearch = new RihaFilterBasedLdapUserSearch(
authenticationProperties.getUserSearchBase(),
authenticationProperties.getUserSearchFilter(),
ldapAuthenticationProperties.getUserSearchBase(),
ldapAuthenticationProperties.getUserSearchFilter(),
contextSource);
LdapUserDetailsService userDetailsService = new LdapUserDetailsService(userSearch);
......@@ -52,23 +61,15 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
public LdapContextSource contextSource(ApplicationProperties applicationProperties) {
LdapContextSource contextSource = new LdapContextSource();
AuthenticationProperties authentication = applicationProperties.getAuthentication();
contextSource.setUrl(authentication.getLdapUrl());
contextSource.setBase(authentication.getLdapBaseDn());
contextSource.setUserDn(authentication.getLdapUser());
contextSource.setPassword(authentication.getLdapPassword());
LdapProperties ldapProperties = applicationProperties.getLdap();
contextSource.setUrl(ldapProperties.getUrl());
contextSource.setBase(ldapProperties.getBaseDn());
contextSource.setUserDn(ldapProperties.getUser());
contextSource.setPassword(ldapProperties.getPassword());
return contextSource;
}
private PreAuthenticatedAuthenticationProvider getEsteidPreAuthenticatedAuthenticationProvider() {
PreAuthenticatedAuthenticationProvider authenticationProvider = new RihaPreAuthenticatedAuthenticationProvider();
authenticationProvider.setPreAuthenticatedUserDetailsService(
new RihaPreAuthenticatedUserDetailsService(ldapUserDetailsService));
return authenticationProvider;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
......
package ee.ria.riha.domain;
import ee.ria.riha.conf.ApplicationProperties.LdapRepositoryProperties;
import ee.ria.riha.domain.model.LdapGroup;
import ee.ria.riha.domain.model.LdapUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.ldap.filter.AbstractFilter;
import org.springframework.ldap.filter.EqualsFilter;
import org.springframework.ldap.filter.LikeFilter;
import org.springframework.ldap.filter.OrFilter;
import org.springframework.ldap.query.LdapQuery;
import org.springframework.ldap.query.LdapQueryBuilder;
import org.springframework.ldap.support.LdapNameBuilder;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.util.Assert;
import javax.naming.Name;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@Slf4j
public class LdapRepository {
private static final String USER_ID_ATTRIBUTE = "uid";
private static final String COMMON_NAME_ATTRIBUTE = "cn";
private static final String MEMBER_OF_ATTRIBUTE = "memberOf";
private static final String APPROVER_GROUP_COMMON_NAME_PATTERN = "*-hindaja";
private static final String ALL_NON_OPERATIONAL_ATTRIBUTES = "*";
private Name baseDn;
private Name userSearchBase;
private Name groupSearchBase;
private LdapTemplate ldapTemplate;
public LdapRepository(LdapContextSource ldapContextSource, LdapRepositoryProperties ldapRepositoryProperties) {
Assert.notNull(ldapContextSource, "LDAP context source must not be null");
this.ldapTemplate = new LdapTemplate(ldapContextSource);
this.baseDn = ldapContextSource.getBaseLdapName();
Assert.hasText(ldapRepositoryProperties.getUserSearchBase(), "userSearchBase must be provided");
this.userSearchBase = LdapUtils.newLdapName(ldapRepositoryProperties.getUserSearchBase());
Assert.hasText(ldapRepositoryProperties.getGroupSearchBase(), "groupSearchBase must be provided");
this.groupSearchBase = LdapUtils.newLdapName(ldapRepositoryProperties.getGroupSearchBase());
}
public List<LdapUser> findLdapUsersByPersonalCodes(Set<String> personalCodes) {
if (personalCodes.isEmpty()) {
log.debug("Will not search for users since list of personal codes is empty");
return new ArrayList<>();
}
OrFilter filter = new OrFilter();
personalCodes.stream()
.map(personalCode -> new EqualsFilter(USER_ID_ATTRIBUTE, personalCode))
.forEach(filter::or);
return findLdapUsers(filter);
}
private List<LdapUser> findLdapUsers(AbstractFilter filter) {
LdapQuery query = LdapQueryBuilder.query()
.base(userSearchBase)
.attributes(ALL_NON_OPERATIONAL_ATTRIBUTES, MEMBER_OF_ATTRIBUTE)
.filter(filter);
return ldapTemplate.find(query, LdapUser.class);
}
public List<LdapUser> getAllApprovers() {
List<LdapGroup> approverGroups = findLdapGroups(
new LikeFilter(COMMON_NAME_ATTRIBUTE, APPROVER_GROUP_COMMON_NAME_PATTERN));
if (approverGroups.isEmpty()) {
log.debug("Would not search for approvers since no approver groups found");
return new ArrayList<>();
}
OrFilter filter = new OrFilter();
approverGroups.stream()
.map(group -> LdapNameBuilder.newInstance(baseDn)
.add(group.getDn())
.build()
.toString())
.map(groupDn -> new EqualsFilter(MEMBER_OF_ATTRIBUTE, groupDn))
.forEach(filter::or);
return findLdapUsers(filter);
}
private List<LdapGroup> findLdapGroups(AbstractFilter filter) {
LdapQuery query = LdapQueryBuilder.query()
.base(groupSearchBase)
.filter(filter);
return ldapTemplate.find(query, LdapGroup.class);
}
}
......@@ -31,6 +31,7 @@ public class Issue implements IssueEntity {
private String organizationName;
private String organizationCode;
private IssueStatus status;
private IssueType type;
public Issue() {
}
......
package ee.ria.riha.domain.model;
/**
* @author Valentin Suhnjov
*/
public enum IssueType {
/**
* Request approval to establish information system
*/
ESTABLISHMENT_REQUEST,
/**
* Request approval to take system into use
*/
TAKE_INTO_USE_REQUEST,
/**
* Request approval to modify information system data
*/
MODIFICATION_REQUEST,
/**
* Request approval to finalize information system
*/
FINALIZATION_REQUEST
}
package ee.ria.riha.domain.model;
import lombok.Data;
import org.springframework.ldap.odm.annotations.Attribute;
import org.springframework.ldap.odm.annotations.Entry;
import org.springframework.ldap.odm.annotations.Id;
import javax.naming.Name;
/**
* @author Valentin Suhnjov
*/
@Data
@Entry(objectClasses = {"groupOfNames"})
public class LdapGroup {
@Id
private Name dn;
@Attribute(name = "cn")
private String commonName;
@Attribute(name = "displayName")
private String displayName;
}
package ee.ria.riha.domain.model;
import lombok.Data;
import org.springframework.ldap.odm.annotations.Attribute;
import org.springframework.ldap.odm.annotations.Entry;
import org.springframework.ldap.odm.annotations.Id;
import javax.naming.Name;
import java.util.List;
/**
* Holds basic user data from LDAP
*/
@Data
@Entry(objectClasses = "person")
public class LdapUser {
@Id
private Name dn;
@Attribute(name = "uid")
private String personalCode;
@Attribute(name = "givenName")
private String givenName;
@Attribute(name = "sn")
private String surname;
@Attribute(name = "cn")
private String commonName;
@Attribute(name = "mail")
private String mail;
@Attribute(name = "memberOf")
private List<Name> memberOf;
}
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;
/**
* @author Valentin Suhnjov
*/
@Getter
@Setter
@Builder
@AllArgsConstructor
public class Relation {
private Long id;
private Date creationDate;
private Date modifiedDate;
private UUID infoSystemUuid;
private String infoSystemName;
private String infoSystemShortName;
private UUID relatedInfoSystemUuid;
private String relatedInfoSystemName;
private String relatedInfoSystemShortName;
private RelationType type;
private boolean reversed;
public Relation() {
}
public Relation reverse() {
return Relation.builder()
.id(this.id)
.creationDate(this.creationDate)
.modifiedDate(this.modifiedDate)
.infoSystemUuid(this.relatedInfoSystemUuid)
.infoSystemName(this.relatedInfoSystemName)
.infoSystemShortName(this.relatedInfoSystemShortName)
.relatedInfoSystemUuid(this.infoSystemUuid)
.relatedInfoSystemName(this.infoSystemName)
.relatedInfoSystemShortName(this.infoSystemShortName)
.type(this.type != null ? this.type.getOpposite() : null)
.reversed(!this.reversed)
.build();
}
}
package ee.ria.riha.domain.model;
/**
* Type of info system relationship.
*
* @author Valentin Suhnjov
*/
public enum RelationType {
SUB_SYSTEM,
SUPER_SYSTEM;
static {
SUB_SYSTEM.opposite = SUPER_SYSTEM;
SUPER_SYSTEM.opposite = SUB_SYSTEM;
}
private RelationType opposite;
public RelationType getOpposite() {
return opposite;
}
}
......@@ -3,34 +3,36 @@ package ee.ria.riha.service;
import ee.ria.riha.conf.ApplicationProperties;
import ee.ria.riha.domain.InfoSystemRepository;
import ee.ria.riha.domain.model.InfoSystem;
import ee.ria.riha.service.notifications.model.InfoSystemDataModel;
import ee.ria.riha.service.notifications.model.NewInfoSystemsEmailNotification;
import ee.ria.riha.storage.util.FilterRequest;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.mail.MailPreparationException;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.util.Assert;