diff --git a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/MainApplication.java b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/MainApplication.java index e1968ca4..d6fe2cac 100644 --- a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/MainApplication.java +++ b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/MainApplication.java @@ -23,9 +23,9 @@ public class MainApplication extends AbstractDnetApp { @Bean public GroupedOpenApi publicApi() { return GroupedOpenApi.builder() - .group("D-Net Organizations Service APIs") - .pathsToMatch("/api/**", "/oa_api/**") - .build(); + .group("D-Net Organizations Service APIs") + .pathsToMatch("/api/**", "/oa_api/**", "/public-api/**") + .build(); } @Override diff --git a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/MockSecurityConfig.java b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/MockSecurityConfig.java index 4e5a34e1..405cf34a 100644 --- a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/MockSecurityConfig.java +++ b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/MockSecurityConfig.java @@ -44,28 +44,28 @@ public class MockSecurityConfig extends WebSecurityConfigurerAdapter { http.headers().frameOptions().sameOrigin(); http.csrf() - .disable() - .authorizeRequests() - .antMatchers("/", "/api/**") - .hasAnyRole(OpenOrgsConstants.VALID_ROLES) - .antMatchers("/registration_api/**") - .hasRole(OpenOrgsConstants.NOT_AUTORIZED_ROLE) - .antMatchers("/common/**", "/resources/**", "/webjars/**", "/metrics", "/health", "/kpis", "/dbmodel/**") - .permitAll() - .antMatchers("/oa_api/**") - .permitAll() - .anyRequest() - .authenticated() - .and() - .formLogin() - .loginPage("/login") - .permitAll() - .and() - .logout() - .permitAll() - .and() - .exceptionHandling() - .accessDeniedHandler(accessDeniedHandler()); + .disable() + .authorizeRequests() + .antMatchers("/", "/api/**") + .hasAnyRole(OpenOrgsConstants.VALID_ROLES) + .antMatchers("/registration_api/**") + .hasRole(OpenOrgsConstants.NOT_AUTORIZED_ROLE) + .antMatchers("/common/**", "/resources/**", "/webjars/**", "/metrics", "/health", "/kpis", "/dbmodel/**", "/public-api/**") + .permitAll() + .antMatchers("/oa_api/**") + .permitAll() + .anyRequest() + .authenticated() + .and() + .formLogin() + .loginPage("/login") + .permitAll() + .and() + .logout() + .permitAll() + .and() + .exceptionHandling() + .accessDeniedHandler(accessDeniedHandler()); } private AccessDeniedHandler accessDeniedHandler() { @@ -74,7 +74,8 @@ public class MockSecurityConfig extends WebSecurityConfigurerAdapter { if (auth != null) { logger - .warn(String.format("User '%s' (%s) attempted to access the protected URL: %s", auth.getName(), req.getRemoteAddr(), req.getRequestURI())); + .warn(String + .format("User '%s' (%s) attempted to access the protected URL: %s", auth.getName(), req.getRemoteAddr(), req.getRequestURI())); } if (UserInfo.isNotAuthorized(auth)) { @@ -89,12 +90,12 @@ public class MockSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired public void configureGlobal(final AuthenticationManagerBuilder auth) throws Exception { auth.jdbcAuthentication() - .dataSource(dataSource) - .usersByUsernameQuery("select ?, '{MD5}" + DigestUtils.md5Hex(DEFAULT_PASSWORD) + "', true") - .authoritiesByUsernameQuery("with const as (SELECT ? as email) " - + "select c.email, 'ROLE_" + OpenOrgsConstants.OPENORGS_ROLE_PREFIX + "'||coalesce(u.role, '" - + UserRole.NOT_AUTHORIZED - + "') from const c left outer join users u on (u.email = c.email)"); + .dataSource(dataSource) + .usersByUsernameQuery("select ?, '{MD5}" + DigestUtils.md5Hex(DEFAULT_PASSWORD) + "', true") + .authoritiesByUsernameQuery("with const as (SELECT ? as email) " + + "select c.email, 'ROLE_" + OpenOrgsConstants.OPENORGS_ROLE_PREFIX + "'||coalesce(u.role, '" + + UserRole.NOT_AUTHORIZED + + "') from const c left outer join users u on (u.email = c.email)"); } @Bean diff --git a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/OAuth2WebSecurityConfig.java b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/OAuth2WebSecurityConfig.java index c34e4c43..fef9ac8f 100644 --- a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/OAuth2WebSecurityConfig.java +++ b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/OAuth2WebSecurityConfig.java @@ -57,29 +57,29 @@ public class OAuth2WebSecurityConfig extends WebSecurityConfigurerAdapter { http.headers().frameOptions().sameOrigin(); http.csrf() - .disable() - .authorizeRequests() - .antMatchers("/main", "/api/**") - .hasAnyRole(OpenOrgsConstants.VALID_ROLES) - .antMatchers("/registration_api/**") - .hasRole(OpenOrgsConstants.NOT_AUTORIZED_ROLE) - .antMatchers("/", "/common/**", "/resources/**", "/webjars/**", "/metrics", "/health", "/kpis", "/dbmodel/**") - .permitAll() - .antMatchers("/oa_api/**") - .hasIpAddress(openaireApiValidSubnet) - .anyRequest() - .authenticated() - .and() - .exceptionHandling() - .accessDeniedHandler(accessDeniedHandler()) - .and() - .logout() - .logoutSuccessHandler(oidcLogoutSuccessHandler()) - .invalidateHttpSession(true) - .clearAuthentication(true) - .deleteCookies("JSESSIONID") - .and() - .oauth2Login(oauth2 -> oauth2.userInfoEndpoint(userInfo -> userInfo.oidcUserService(this.oidcUserService()))); + .disable() + .authorizeRequests() + .antMatchers("/main", "/api/**") + .hasAnyRole(OpenOrgsConstants.VALID_ROLES) + .antMatchers("/registration_api/**") + .hasRole(OpenOrgsConstants.NOT_AUTORIZED_ROLE) + .antMatchers("/", "/common/**", "/resources/**", "/webjars/**", "/metrics", "/health", "/kpis", "/dbmodel/**", "/public-api/**") + .permitAll() + .antMatchers("/oa_api/**") + .hasIpAddress(openaireApiValidSubnet) + .anyRequest() + .authenticated() + .and() + .exceptionHandling() + .accessDeniedHandler(accessDeniedHandler()) + .and() + .logout() + .logoutSuccessHandler(oidcLogoutSuccessHandler()) + .invalidateHttpSession(true) + .clearAuthentication(true) + .deleteCookies("JSESSIONID") + .and() + .oauth2Login(oauth2 -> oauth2.userInfoEndpoint(userInfo -> userInfo.oidcUserService(oidcUserService()))); } private AccessDeniedHandler accessDeniedHandler() { @@ -87,8 +87,8 @@ public class OAuth2WebSecurityConfig extends WebSecurityConfigurerAdapter { final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null) { log.warn(String - .format("User '%s' (%s) attempted to access the protected URL: %s", UserInfo.getEmail(authentication), req - .getRemoteAddr(), req.getRequestURI())); + .format("User '%s' (%s) attempted to access the protected URL: %s", UserInfo.getEmail(authentication), req + .getRemoteAddr(), req.getRequestURI())); } if (UserInfo.isNotAuthorized(authentication)) { @@ -122,7 +122,7 @@ public class OAuth2WebSecurityConfig extends WebSecurityConfigurerAdapter { private OAuth2UserService oidcUserService() { final OidcUserService delegate = new OidcUserService(); - return (userRequest) -> { + return userRequest -> { final OidcUser oidcUser = delegate.loadUser(userRequest); log.debug("User attributes:"); @@ -137,9 +137,9 @@ public class OAuth2WebSecurityConfig extends WebSecurityConfigurerAdapter { } final String role = "ROLE_" + OpenOrgsConstants.OPENORGS_ROLE_PREFIX + user - .map(User::getRole) - .filter(StringUtils::isNotBlank) - .orElse(UserRole.NOT_AUTHORIZED.toString()); + .map(User::getRole) + .filter(StringUtils::isNotBlank) + .orElse(UserRole.NOT_AUTHORIZED.toString()); final Set mappedAuthorities = new HashSet<>(); mappedAuthorities.add(new SimpleGrantedAuthority(role)); diff --git a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/controller/PublicApiController.java b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/controller/PublicApiController.java new file mode 100644 index 00000000..a7787c0f --- /dev/null +++ b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/controller/PublicApiController.java @@ -0,0 +1,35 @@ +package eu.dnetlib.organizations.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import eu.dnetlib.organizations.model.view.ApiJournalView; +import eu.dnetlib.organizations.repository.OrganizationRepository; +import eu.dnetlib.organizations.repository.readonly.ApiJournalViewRepository; + +@RestController +@RequestMapping("/public-api") +public class PublicApiController { + + @Autowired + private ApiJournalViewRepository apiJournalViewRepository; + + @Autowired + private OrganizationRepository organizationRepository; + + @GetMapping("/logs") + public ApiJournalView findJournalByDsId(@RequestParam final String id) { + return apiJournalViewRepository.findById(id).orElse(organizationRepository.findById(id).map(ApiJournalView::new).orElse(new ApiJournalView())); + } + + @GetMapping("/logs/{country}/{page}/{size}") + public Page findJournalByCountry(@PathVariable final String country, @PathVariable final int page, @PathVariable final int size) { + return apiJournalViewRepository.findByCountry(country, PageRequest.of(page, size)); + } +} diff --git a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/model/utils/ApiOperation.java b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/model/utils/ApiOperation.java new file mode 100644 index 00000000..cc8a9a63 --- /dev/null +++ b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/model/utils/ApiOperation.java @@ -0,0 +1,37 @@ +package eu.dnetlib.organizations.model.utils; + +import java.io.Serializable; +import java.time.LocalDateTime; + +public class ApiOperation implements Serializable { + + private static final long serialVersionUID = -7111524441502889608L; + + private String operation; + private String description; + private LocalDateTime date; + + public String getOperation() { + return operation; + } + + public void setOperation(final String operation) { + this.operation = operation; + } + + public String getDescription() { + return description; + } + + public void setDescription(final String description) { + this.description = description; + } + + public LocalDateTime getDate() { + return date; + } + + public void setDate(final LocalDateTime date) { + this.date = date; + } +} diff --git a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/model/view/ApiJournalView.java b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/model/view/ApiJournalView.java new file mode 100644 index 00000000..b33eb2b8 --- /dev/null +++ b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/model/view/ApiJournalView.java @@ -0,0 +1,87 @@ +package eu.dnetlib.organizations.model.view; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.hibernate.annotations.TypeDefs; + +import com.vladmihalcea.hibernate.type.json.JsonBinaryType; +import com.vladmihalcea.hibernate.type.json.JsonStringType; + +import eu.dnetlib.organizations.model.Organization; +import eu.dnetlib.organizations.model.utils.ApiOperation; + +@Entity +@Table(name = "api_journal_view") +@TypeDefs({ + @TypeDef(name = "json", typeClass = JsonStringType.class), + @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class) +}) +public class ApiJournalView implements Serializable { + + private static final long serialVersionUID = 1270660185726854334L; + + @Id + @Column(name = "id") + private String id; + + @Column(name = "name") + private String name; + + @Column(name = "country") + private String country; + + @Type(type = "json") + @Column(name = "logs", columnDefinition = "json") + private List logs; + + public ApiJournalView() {} + + public ApiJournalView(final Organization o) { + id = o.getId(); + name = o.getName(); + country = o.getCountry(); + logs = new ArrayList<>(); + } + + public String getId() { + return id; + } + + public void setId(final String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public String getCountry() { + return country; + } + + public void setCountry(final String country) { + this.country = country; + } + + public List getLogs() { + return logs; + } + + public void setLogs(final List logs) { + this.logs = logs; + } + +} diff --git a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/repository/readonly/ApiJournalViewRepository.java b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/repository/readonly/ApiJournalViewRepository.java new file mode 100644 index 00000000..9c8eba6c --- /dev/null +++ b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/repository/readonly/ApiJournalViewRepository.java @@ -0,0 +1,14 @@ +package eu.dnetlib.organizations.repository.readonly; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Repository; + +import eu.dnetlib.organizations.model.view.ApiJournalView; + +@Repository +public interface ApiJournalViewRepository extends ReadOnlyRepository { + + Page findByCountry(String country, Pageable page); + +} diff --git a/apps/dnet-orgs-database-application/src/main/resources/sql/schema.sql b/apps/dnet-orgs-database-application/src/main/resources/sql/schema.sql index 7ff4a386..78a69025 100644 --- a/apps/dnet-orgs-database-application/src/main/resources/sql/schema.sql +++ b/apps/dnet-orgs-database-application/src/main/resources/sql/schema.sql @@ -6,6 +6,7 @@ DROP VIEW IF EXISTS conflict_groups_view; DROP VIEW IF EXISTS suggestions_info_by_country_view; DROP VIEW IF EXISTS duplicate_groups_view; DROP VIEW IF EXISTS persistent_orgs_view; +DROP VIEW IF EXISTS api_journal_view; DROP TABLE IF EXISTS sysconf; DROP TABLE IF EXISTS other_ids; @@ -748,3 +749,4 @@ $$; CREATE TRIGGER insert_or_update_index_search_trigger AFTER INSERT OR UPDATE ON organizations FOR EACH ROW EXECUTE PROCEDURE insert_or_update_index_search_trigger(); CREATE TRIGGER delete_index_search_trigger BEFORE DELETE ON organizations FOR EACH ROW EXECUTE PROCEDURE delete_index_search(); +