Unverified Commit 193dd20c authored by Kristjan Kruus's avatar Kristjan Kruus Committed by GitHub

Merge pull request #72 from e-gov/release/v7.5

Release/v7.5
parents 8ef2b7c9 6b9c4de1
......@@ -8,3 +8,10 @@ Software application that allows a human user to browse descriptions and approva
## Installation
Installation manual is divided into two: [build process](https://github.com/e-gov/RIHA-Browser/blob/master/docs/build.md) and [deployment process](https://github.com/e-gov/RIHA-Browser/blob/master/docs/deploy.md)
## Development profile configuration
Development profile allows to run the application without external dependencies to LDAP or TARA.
In order to user the development profile, the maven profile "dev" must be selected for packaging and necessary user attribute values must be provided under the application-dev.properties configuration file.
\ No newline at end of file
......@@ -12,7 +12,7 @@
<parent>
<groupId>ee.ria.riha</groupId>
<artifactId>browser</artifactId>
<version>7.4.1</version>
<version>7.5</version>
</parent>
<dependencies>
......@@ -120,7 +120,7 @@
<dependency>
<groupId>ee.ria.riha</groupId>
<artifactId>storage-client</artifactId>
<version>0.12.1</version>
<version>0.13.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
......@@ -133,6 +133,7 @@
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
<resource>
<directory>../frontend/dist</directory>
......@@ -203,5 +204,22 @@
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>dev</id>
<properties>
<spring.profiles.active>dev</spring.profiles.active>
</properties>
</profile>
<profile>
<id>prod</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<spring.profiles.active>prod</spring.profiles.active>
</properties>
</profile>
</profiles>
</project>
package ee.ria.riha.conf;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.github.fge.jackson.JsonLoader;
import ee.ria.riha.domain.model.NationalHolidays;
import ee.ria.riha.service.JsonValidationService;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.task.TaskExecutorBuilder;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.client.RestTemplate;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
/**
* @author Valentin Suhnjov
......@@ -26,9 +34,21 @@ public class ApplicationConfiguration {
return restTemplateBuilder.build();
}
@Bean
public TaskExecutor taskExecutor(TaskExecutorBuilder taskExecutorBuilder) {
return taskExecutorBuilder.build();
}
@Bean
public JsonValidationService jsonValidationService(ApplicationProperties applicationProperties) throws IOException {
return new JsonValidationService(
JsonLoader.fromResource(applicationProperties.getValidation().getJsonSchemaUrl()));
}
@Bean
public NationalHolidays nationalHolidays(ApplicationProperties applicationProperties) throws IOException, URISyntaxException {
ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule());
File holidaysFile = new File(getClass().getClassLoader().getResource(applicationProperties.getNationalHolidaysFile()).toURI());
return mapper.readValue(holidaysFile, NationalHolidays.class);
}
}
package ee.ria.riha.conf;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.validator.constraints.NotEmpty;
......@@ -25,10 +27,14 @@ public class ApplicationProperties {
private final CorsProperties cors = new CorsProperties();
private final TrackingProperties tracking = new TrackingProperties();
private final TaraProperties tara = new TaraProperties();
private final DeveloperUser developerUser = new DeveloperUser();
@Setter
private String baseUrl;
@Setter
private String nationalHolidaysFile;
@Getter
@Setter
public static class StorageClientProperties {
......@@ -71,6 +77,7 @@ public class ApplicationProperties {
private final CreatedInfoSystemsOverview createdInfoSystemsOverview = new CreatedInfoSystemsOverview();
private final NewIssue newIssue = new NewIssue();
private final NewIssueComment newIssueComment = new NewIssueComment();
private final NewIssueDecision newIssueDecision = new NewIssueDecision();
private final IssueStatusUpdate issueStatusUpdate = new IssueStatusUpdate();
private String from;
private String recipientPattern;
......@@ -88,6 +95,12 @@ public class ApplicationProperties {
private boolean enabled;
}
@Getter
@Setter
public static class NewIssueDecision {
private boolean enabled;
}
@Getter
@Setter
public static class CreatedInfoSystemsOverview {
......@@ -142,5 +155,20 @@ public class ApplicationProperties {
private String jwkKeySetUri;
}
@Getter
@Setter
public static class Organization {
private String name;
private String code;
private String[] roles;
}
@Getter
@Setter
public static class DeveloperUser {
private String name;
private String code;
private List<Organization> organizations;
}
}
package ee.ria.riha.conf;
import java.util.Arrays;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.ldap.userdetails.LdapUserDetails;
import org.springframework.security.ldap.userdetails.LdapUserDetailsImpl;
import org.springframework.security.ldap.userdetails.LdapUserDetailsImpl.Essence;
import org.springframework.stereotype.Component;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import ee.ria.riha.authentication.RihaLdapUserDetailsContextMapper;
import ee.ria.riha.authentication.RihaOrganization;
import ee.ria.riha.authentication.RihaUserDetails;
import ee.ria.riha.conf.ApplicationProperties.DeveloperUser;
import ee.ria.riha.conf.ApplicationProperties.Organization;
@Component
public class DeveloperAuthenticationManager implements AuthenticationManager {
@Autowired
ApplicationProperties applicationProperties;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
DeveloperUser developerUser = applicationProperties.getDeveloperUser();
String name = developerUser.getName();
String code = developerUser.getCode();
Essence essence = new LdapUserDetailsImpl.Essence();
essence.setUsername(code);
essence.setDn(name);
essence.addAuthority(new SimpleGrantedAuthority(RihaLdapUserDetailsContextMapper.DEFAULT_RIHA_USER_ROLE));
LdapUserDetails ldapUserDetails = essence.createUserDetails();
Multimap<RihaOrganization, GrantedAuthority> organizationRoles = developerUser.getOrganizations().stream()
.collect(
ArrayListMultimap::create,
(map, org) -> map.putAll(
new RihaOrganization(org.getCode(), org.getName()),
Arrays.stream(org.getRoles())
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toSet())),
(map, u) -> {});
RihaUserDetails userDetails = new RihaUserDetails(ldapUserDetails, code, organizationRoles);
userDetails.setFirstName(name);
userDetails.setLastName(name);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
userDetails,
authentication.getCredentials(),
developerUser.getOrganizations().stream()
.map(Organization::getRoles)
.flatMap(Arrays::stream)
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toSet())
);
SecurityContextHolder.getContext().setAuthentication(token);
return token;
}
}
......@@ -10,6 +10,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.http.HttpStatus;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.support.LdapContextSource;
......@@ -35,9 +36,13 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
import org.springframework.web.util.UriUtils;
import javax.servlet.Filter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.util.Map;
......@@ -46,6 +51,7 @@ import java.util.Map;
* @author Valentin Suhnjov
*/
@Configuration
@Profile("!dev")
@EnableWebSecurity
@EnableOAuth2Client
@EnableGlobalMethodSecurity(prePostEnabled = true)
......@@ -55,6 +61,8 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
// parameter passed from frontend. Contains the URL from where the login button was clicked.
private static final String REDIRECT_URL_PARAMETER_MARKER = "fromUrl";
static final String TARA_AUTH_ENDPOINT = "/oauth2/authorization/tara";
@Autowired
private LdapUserDetailsService ldapUserDetailsService;
......@@ -65,7 +73,7 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
private JwtDecoder jwtDecoder;
@Autowired
private ApplicationProperties applicationProperties;
protected ApplicationProperties applicationProperties;
@Bean
public LdapUserDetailsService ldapUserDetailsService(ApplicationProperties applicationProperties,
......@@ -109,40 +117,10 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
.logoutUrl("/logout")
.logoutSuccessHandler((new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK)))
.and()
.addFilterBefore((request, response, chain) -> {
if (request instanceof HttpServletRequest && ((HttpServletRequest) request).getRequestURI().contains("/oauth2/authorization/tara")) {
String fromUrlParameter = request.getParameter(REDIRECT_URL_PARAMETER_MARKER);
log.info("authenticate request detected, fromUrl param ({}) is saved to session ", fromUrlParameter);
((HttpServletRequest) request).getSession().setAttribute("fromUrl", fromUrlParameter);
}
chain.doFilter(request, response);
},
ChannelProcessingFilter.class)
.addFilterBefore(createFromUrlSessionFilter(), ChannelProcessingFilter.class)
.oauth2Login()
.loginPage(applicationProperties.getBaseUrl())
.successHandler((request, response, authentication) -> {
log.info("Kasutaja {} ID koodiga {} logis sisse kasutades amr: {} ",
((RihaUserDetails) authentication.getPrincipal()).getFullName(),
((RihaUserDetails) authentication.getPrincipal()).getPersonalCode(),
((RihaUserDetails) authentication.getPrincipal()).getTaraAmr()
);
String fromUrl = (String) request.getSession(false).getAttribute("fromUrl");
if (fromUrl != null) {
// fromUrl param has the following format:
// /url?param1=paramValue&param2=param2Value...
// should transform question marks into param delimiters
fromUrl = fromUrl.replaceAll("\\?", "&");
response.sendRedirect("/Login?" + UriUtils.encodePath("fromUrl=" + fromUrl, "UTF-8"));
} else {
response.sendRedirect("/Login");
}
})
.successHandler(successHandler())
.redirectionEndpoint()
.baseUri("/authenticate")
.and()
......@@ -178,6 +156,40 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
.addObjectPostProcessor(new CustomPostProcessor());
}
protected AuthenticationSuccessHandler successHandler() {
return (request, response, authentication) -> {
log.info("Kasutaja {} ID koodiga {} logis sisse kasutades amr: {} ",
((RihaUserDetails) authentication.getPrincipal()).getFullName(),
((RihaUserDetails) authentication.getPrincipal()).getPersonalCode(),
((RihaUserDetails) authentication.getPrincipal()).getTaraAmr()
);
String fromUrl = (String) request.getSession(false).getAttribute("fromUrl");
if (fromUrl != null) {
// fromUrl param has the following format:
// /url?param1=paramValue&param2=param2Value...
// should transform question marks into param delimiters
fromUrl = fromUrl.replaceAll("\\?", "&");
response.sendRedirect("/Login?" + UriUtils.encodePath("fromUrl=" + fromUrl, "UTF-8"));
} else {
response.sendRedirect("/Login");
}
};
}
protected Filter createFromUrlSessionFilter() {
return (request, response, chain) -> {
if (request instanceof HttpServletRequest && ((HttpServletRequest) request).getRequestURI().contains(TARA_AUTH_ENDPOINT)) {
String fromUrlParameter = request.getParameter(REDIRECT_URL_PARAMETER_MARKER);
log.info("authenticate request detected, fromUrl param ({}) is saved to session ", fromUrlParameter);
((HttpServletRequest) request).getSession().setAttribute("fromUrl", fromUrlParameter);
}
chain.doFilter(request, response);
};
}
private RihaUserDetails getDefaultRihaUserWithDefaultRole(String personalCode) {
RihaUserDetails rihaUserDetails;
LdapUserDetailsImpl.Essence userDetailsEssence = new LdapUserDetailsImpl.Essence(new DirContextAdapter());
......@@ -189,6 +201,7 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
}
@Bean
@Profile("!dev")
ClientRegistrationRepository clientRegistrationRepository(ApplicationProperties applicationProperties) {
ApplicationProperties.TaraProperties taraConfig = applicationProperties.getTara();
return new InMemoryClientRegistrationRepository(
......
package ee.ria.riha.conf;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@Configuration
@Profile("dev")
public class WebSecurityDevConfiguration extends WebSecurityConfiguration {
@Autowired
DeveloperAuthenticationManager authenticationManager;
@Bean
protected UsernamePasswordAuthenticationFilter authenticationFilter() {
UsernamePasswordAuthenticationFilter authenticationFilter = new UsernamePasswordAuthenticationFilter();
authenticationFilter.setAuthenticationManager(authenticationManager);
authenticationFilter.setAuthenticationSuccessHandler(successHandler());
authenticationFilter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(TARA_AUTH_ENDPOINT, HttpMethod.GET.name()));
authenticationFilter.setPostOnly(Boolean.FALSE);
return authenticationFilter;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.cors().disable()
.authorizeRequests()
.anyRequest()
.permitAll()
.and()
.addFilterBefore(createFromUrlSessionFilter(), ChannelProcessingFilter.class)
.logout()
.logoutUrl("/logout")
.logoutSuccessHandler((new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK)))
.and()
.formLogin()
.permitAll()
.loginPage("/oauth2/authorization/tara")
.loginProcessingUrl("/oauth2/authorization/tara");
}
}
package ee.ria.riha.domain;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import ee.ria.riha.domain.model.Classifier;
import ee.ria.riha.storage.client.StorageClient;
import ee.ria.riha.storage.util.Filterable;
import ee.ria.riha.storage.util.Pageable;
import ee.ria.riha.storage.util.PagedResponse;
@Service
public class ClassifierRepository {
private static final String CLASSIFIER_PATH = "db/classifier";
@Autowired
private StorageClient storageClient;
public List<Classifier> findAll() {
return storageClient.find(CLASSIFIER_PATH, Classifier.class);
}
public PagedResponse<Classifier> list(Pageable pageable, Filterable filterable) {
return storageClient.list(CLASSIFIER_PATH, pageable, filterable, Classifier.class);
}
}
package ee.ria.riha.domain;
import ee.ria.riha.domain.model.InfoSystemDataObject;
import ee.ria.riha.storage.client.StorageClient;
import ee.ria.riha.storage.util.Filterable;
import ee.ria.riha.storage.util.Pageable;
import ee.ria.riha.storage.util.PagedResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class InfoSystemDataObjectRepository {
private static final String DATA_OBJECT_PATH = "db/data_object_search_view";
@Autowired
private StorageClient storageClient;
public PagedResponse<InfoSystemDataObject> list(Pageable pageable, Filterable filterable) {
return storageClient.list(DATA_OBJECT_PATH, pageable, filterable, InfoSystemDataObject.class);
}
}
......@@ -71,17 +71,30 @@ public class LdapRepository {
return ldapTemplate.find(query, LdapUser.class);
}
public List<LdapUser> getApproversByOrganization(String organizationCode) {
String approverGroupName = APPROVER_GROUP_COMMON_NAME_PATTERN.replace("*", organizationCode);
LdapGroup group = findLdapGroup(new EqualsFilter(COMMON_NAME_ATTRIBUTE, approverGroupName));
String groupDn = LdapNameBuilder.newInstance(baseDn).add(group.getDn()).build().toString();
return findLdapUsers(new EqualsFilter(MEMBER_OF_ATTRIBUTE, groupDn));
}
public List<LdapUser> getUsersByOrganization(String organizationCode) {
return getLdapUsersByGroups(organizationCode + ALL_NON_OPERATIONAL_ATTRIBUTES);
}
public List<LdapUser> getAllApprovers() {
List<LdapGroup> approverGroups = findLdapGroups(
new LikeFilter(COMMON_NAME_ATTRIBUTE, APPROVER_GROUP_COMMON_NAME_PATTERN));
return getLdapUsersByGroups(APPROVER_GROUP_COMMON_NAME_PATTERN);
}
if (approverGroups.isEmpty()) {
private List<LdapUser> getLdapUsersByGroups(String groupNamePattern) {
List<LdapGroup> groups = findLdapGroups(new LikeFilter(COMMON_NAME_ATTRIBUTE, groupNamePattern));
if (groups.isEmpty()) {
log.debug("Would not search for approvers since no approver groups found");
return new ArrayList<>();
}
OrFilter filter = new OrFilter();
approverGroups.stream()
groups.stream()
.map(group -> LdapNameBuilder.newInstance(baseDn)
.add(group.getDn())
.build()
......@@ -92,11 +105,17 @@ public class LdapRepository {
return findLdapUsers(filter);
}
private LdapGroup findLdapGroup(AbstractFilter filter) {
return ldapTemplate.findOne(getGroupQuery(filter), LdapGroup.class);
}
private List<LdapGroup> findLdapGroups(AbstractFilter filter) {
LdapQuery query = LdapQueryBuilder.query()
return ldapTemplate.find(getGroupQuery(filter), LdapGroup.class);
}
private LdapQuery getGroupQuery(AbstractFilter filter) {
return LdapQueryBuilder.query()
.base(groupSearchBase)
.filter(filter);
return ldapTemplate.find(query, LdapGroup.class);
}
}
......@@ -37,6 +37,7 @@ public class RihaStorageInfoSystemRepository implements InfoSystemRepository {
infoSystem.setLastPositiveEstablishmentRequestDate(mainResource.getLast_positive_establishment_request_date());
infoSystem.setLastPositiveTakeIntoUseRequestDate(mainResource.getLast_positive_take_into_use_request_date());
infoSystem.setLastPositiveFinalizationRequestDate(mainResource.getLast_positive_finalization_request_date());
infoSystem.setHasUsedSystemTypeRelations(mainResource.isHasUsedSystemTypeRelations());
return infoSystem;
};
......
package ee.ria.riha.domain.model;
import java.util.Date;
import lombok.Data;
@Data