diff --git a/docker/README.md b/docker/README.md index da8299413287a703b38d12c233e8abb0c32babfa..9636ff324e43b29c67fa367165370bbbf5c3a5f7 100644 --- a/docker/README.md +++ b/docker/README.md @@ -7,71 +7,33 @@ To use this image, you need a running instance of PostgreSQL and to configure the software using the following environment variables. -### DATABASES - -* PORTAL_DB_URL: JDBC URL to connect to the portal database, default value is "jdbc:postgresql://172.22.0.1:5432/portal". -* PORTAL_DB_SCHEMA: Database schema, default value is "public". -* PORTAL_DB_USER: User to use when connecting to the portal database, default value is "postgres". -* PORTAL_DB_PASSWORD: Password to use when connecting to the portal database. - -* META_DB_URL: JDBC URL to connect to the metadata database, default value is "jdbc:postgresql://172.22.0.1:5432/meta". -* META_DB_SCHEMA: Database schema, default value is "public". -* META_DB_USER: User to use when connecting to the metadata database. -* META_DB_PASSWORD: Password to use when connecting to the metadata database. - -* FEATURES_DB_URL: JDBC URL to connect to the science database, default value is "jdbc:postgresql://172.22.0.1:5433/features". -* FEATURES_DB_SCHEMA: Database schema, default value is "public". -* FEATURES_DB_USER: User to use when connecting to the science database, default value is "postgres". -* FEATURES_DB_PASSWORD: Password to use when connecting to the science database. -* FEATURES_DB_MAIN_TABLE: Table that contains the scientific data to use, default value is "features". - - -### OAUTH2 LOGIN - -* AUTHENTICATION: "0" to disable authentication or "1" to enable authentication, default value is "1". -* CLIENT_ID: required when authentication is turned on, client ID for the [OpenID server of HBP](https://services.humanbrainproject.eu/oidc/). -* CLIENT_SECRET: required when authentication is turned on, client secret for the [OpenID server of HBP](https://services.humanbrainproject.eu/oidc/). -* TOKEN_URI: default to "https://services.humanbrainproject.eu/oidc/token". -* AUTH_URI: default to "https://services.humanbrainproject.eu/oidc/authorize". -* USER_INFO_URI: default to "https://services.humanbrainproject.eu/oidc/userInfo". -* REVOKE_TOKEN_URI "https://services.humanbrainproject.eu/oidc/slo". - - -### WEB FRONTEND - -* FRONTEND_LOGIN_URL: URL to redirect to when login is required. Default to "http://frontend/services/login/hbp". -* FRONTEND_AFTER_LOGIN_URL: URL to redirect after login. Default to "http://frontend/home". -* FRONTEND_AFTER_LOGOUT_URL: URL to redirect to after logout. Default to "http://frontend/services/login/hbp". - - -### LOGGING - +### LOG LEVELS ### * LOG_LEVEL: log level for the developer added logs. Default is "ERROR". * LOG_LEVEL_FRAMEWORK: log level for all the framework logs. Default is "ERROR". -### ENDPOINTS +### AUTHENTICATION ### +* AUTHENTICATION: true for production, false for development. -* EXAREME_URL: URL to Exareme server, default value is "http://hbps2.chuv.ch:9090". -* WORKFLOW_URL: URL to Workflow server -* JWT_SECRET: "secret" -### EMBEDDED SERVER CONFIGURATION - -* CONTEXT_PATH: context path appended to all services running in this container. Default to "/services". -* SESSION_TIMEOUT: Timeout in milliseconds for session expiration. Default to 2592000. - -### PROXY +### DATABASE CONFIGURATION ### +* PORTAL_DB_URL: JDBC URL to connect to the portal database, default value is "jdbc:postgresql://127.0.0.1:5432/portal". +* PORTAL_DB_SCHEMA: Database schema, default value is "public". +* PORTAL_DB_USER: User to use when connecting to the portal database, default value is "postgres". +* PORTAL_DB_PASSWORD: Password to use when connecting to the portal database. -* HTTP_PROXY_HOST: HTTP proxy host -* HTTP_PROXY_PORT: HTTP proxy port -* HTTPS_PROXY_HOST: HTTPS proxy host -* HTTPS_PROXY_PORT: HTTPS proxy port -## ERROR REPORTING +### EXTERNAL SERVICES ### +* EXAREME_URL: URL to Exareme server. Default is "http://localhost:9090" . -* RELEASE_STAGE: Release stage used when reporting errors to Bugsnag. Values are dev, staging, production -* DATA_CENTER_LOCATION: Location of the datacenter, used when reporting errors to Bugsnag +* GALAXY_URL: URL to Workflow server. Default is "http://localhost:8090/" . +* GALAXY_API_KEY: The api key to authorize galaxy requests. +* GALAXY_USERNAME: The username of galaxy user to be able to embed the frame. +* GALAXY_PASSWORD: The password of galaxy user. -# TODO Refactor variables \ No newline at end of file +### KEYCLOAK ### +* KEYCLOAK_AUTH_URL: Keycloak authentication URL. +* KEYCLOAK_REALM: Keycloak realm user for authentication. +* KEYCLOAK_CLIENT_ID: The keycloak client id. +* KEYCLOAK_CLIENT_SECRET: The keycloak secret to be able to authenticate. \ No newline at end of file diff --git a/docker/config/application.tmpl b/docker/config/application.tmpl index 257bfa0871747ff23d288c5cdf53be4086ce2827..5859f78a8e9bb52fb8dae1c4083ca3a6b2485334 100644 --- a/docker/config/application.tmpl +++ b/docker/config/application.tmpl @@ -1,5 +1,19 @@ # Configuration template for the portal running inside a Docker container +### LOG LEVELS ### +logging: + level: + root: {{ default .Env.LOG_LEVEL_FRAMEWORK "ERROR" }} + org: {{ default .Env.LOG_LEVEL_FRAMEWORK "ERROR" }} + eu: + hbp: {{ default .Env.LOG_LEVEL "INFO" }} + + +### AUTHENTICATION ### +authentication: + enabled: {{ default .Env.AUTHENTICATION "true" }} + + ### DATABASE CONFIGURATION ### spring: portal-datasource: @@ -17,28 +31,6 @@ spring: dialect: org.hibernate.dialect.PostgreSQL9Dialect ddl-auto: validate -### LOG LEVELS ### -logging: - level: - root: {{ default .Env.LOG_LEVEL_FRAMEWORK "ERROR" }} - org: {{ default .Env.LOG_LEVEL_FRAMEWORK "ERROR" }} - eu: - hbp: {{ default .Env.LOG_LEVEL "INFO" }} - -### EMBEDDED SERVER CONFIGURATION ### -server: - servlet: - contextPath: "/services" - port: 8080 - forward-headers-strategy: native - -### ENDPOINTS ### -endpoints: - enabled: true - health: - enabled: true - endpoint: /health - sensitive: false ### EXTERNAL SERVICES ### services: @@ -48,24 +40,21 @@ services: galaxy: galaxyUrl: {{ default .Env.GALAXY_URL "http://localhost:8090/" }} - galaxyContext: {{ default .Env.GALAXY_CONTEXT "nativeGalaxy/workflows/list" }} + galaxyContext: "nativeGalaxy/workflows/list" galaxyApiKey: {{ .Env.GALAXY_API_KEY }} galaxyUsername: {{ default .Env.GALAXY_USERNAME "admin" }} galaxyPassword: {{ default .Env.GALAXY_PASSWORD "password" }} -### Authentication ### -authentication: - enabled: {{ default .Env.AUTHENTICATION "1" }} -### Keycloak ### +### KEYCLOAK ### keycloak: enabled: true auth-server-url: {{ .Env.KEYCLOAK_AUTH_URL }} realm: {{ .Env.KEYCLOAK_REALM }} - resource: {{ .Env.CLIENT_ID }} + resource: {{ .Env.KEYCLOAK_CLIENT_ID }} enable-basic-auth: true credentials: - secret: {{ .Env.CLIENT_SECRET }} + secret: {{ .Env.KEYCLOAK_CLIENT_SECRET }} principal-attribute: "preferred_username" ### EXTERNAL FILES ### @@ -73,3 +62,20 @@ keycloak: files: pathologies_json: "file:/opt/portal/api/pathologies.json" disabledAlgorithms_json: "file:/opt/portal/api/disabledAlgorithms.json" + + +### EMBEDDED SERVER CONFIGURATION ### +server: + servlet: + contextPath: "/services" + port: 8080 + forward-headers-strategy: native + + +### ENDPOINTS ### +endpoints: + enabled: true + health: + enabled: true + endpoint: /health + sensitive: false \ No newline at end of file diff --git a/pom.xml b/pom.xml index a9f07d9c2789b89324fc970488c255c2ee2eccde..b9da794804d1ddd3669e7277d451a63cbc03f606 100644 --- a/pom.xml +++ b/pom.xml @@ -189,7 +189,6 @@ <artifactId>aspectjweaver</artifactId> <version>${aspectjweaver.version}</version> </dependency> - <dependency> <groupId>javax.inject</groupId> <artifactId>javax.inject</artifactId> @@ -221,42 +220,32 @@ <artifactId>blend4j</artifactId> <version>0.2.0</version> </dependency> - <dependency> <groupId>com.squareup.retrofit2</groupId> <artifactId>retrofit</artifactId> <version>2.9.0</version> </dependency> - <!-- https://mvnrepository.com/artifact/com.squareup.retrofit2/converter-gson --> <dependency> <groupId>com.squareup.retrofit2</groupId> <artifactId>converter-gson</artifactId> <version>2.9.0</version> </dependency> - <!-- https://mvnrepository.com/artifact/com.squareup.okhttp3/logging-interceptor --> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>logging-interceptor</artifactId> </dependency> - <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.11.0</version> </dependency> - <dependency> <groupId>com.google.code.svenson</groupId> <artifactId>svenson</artifactId> <version>1.5.8</version> </dependency> - <!-- <dependency> - <groupId>eu.hbp.mip</groupId> - <artifactId>portal-backend</artifactId> - <version>4.0.0</version> - </dependency>--> </dependencies> <build> diff --git a/src/main/java/eu/hbp/mip/configurations/SecurityConfiguration.java b/src/main/java/eu/hbp/mip/configurations/SecurityConfiguration.java index dfc5d4e475634dfc3bf8754bb3ea6584953313c7..59929212b6b18d9a377c18fa9adc2fd78e19dfa7 100644 --- a/src/main/java/eu/hbp/mip/configurations/SecurityConfiguration.java +++ b/src/main/java/eu/hbp/mip/configurations/SecurityConfiguration.java @@ -14,18 +14,29 @@ import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper import org.springframework.security.core.session.SessionRegistryImpl; import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy; import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; +import org.springframework.security.web.csrf.CsrfFilter; +import org.springframework.security.web.csrf.CsrfToken; +import org.springframework.security.web.csrf.CsrfTokenRepository; +import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.util.WebUtils; +import javax.servlet.Filter; +import javax.servlet.FilterChain; import javax.servlet.ServletException; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; @Controller @KeycloakConfiguration public class SecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter { - // Redirect to login page url + // Upon logout, redirect to login page url private static final String logoutRedirectURL = "/sso/login"; @Value("#{'${authentication.enabled}'}") @@ -42,7 +53,9 @@ public class SecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter "/v2/api-docs", "/swagger-ui/**", "/swagger-resources/**" // Swagger URLs ).permitAll() .antMatchers("/galaxy*", "/galaxy/*").hasRole("DATA MANAGER") - .anyRequest().hasRole("RESEARCHER"); + .anyRequest().hasRole("RESEARCHER") + .and().csrf().ignoringAntMatchers("/logout").csrfTokenRepository(csrfTokenRepository()) + .and().addFilterAfter(csrfHeaderFilter(), CsrfFilter.class); } else { http.antMatcher("/**") .authorizeRequests() @@ -51,6 +64,32 @@ public class SecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter } } + private Filter csrfHeaderFilter() { + return new OncePerRequestFilter() { + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); + if (csrf != null) { + Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN"); + String token = csrf.getToken(); + if (cookie == null || token != null && !token.equals(cookie.getValue())) { + cookie = new Cookie("XSRF-TOKEN", token); + cookie.setPath("/"); + response.addCookie(cookie); + } + } + filterChain.doFilter(request, response); + } + }; + } + + private CsrfTokenRepository csrfTokenRepository() { + HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository(); + repository.setHeaderName("X-XSRF-TOKEN"); + return repository; + } + @Autowired private HttpServletRequest request; diff --git a/src/main/java/eu/hbp/mip/controllers/ExperimentAPI.java b/src/main/java/eu/hbp/mip/controllers/ExperimentAPI.java index 886c59232cf16442d64010fb6e934e93cff2e69d..5c002c0b8dc1fc800706e5f4508b308a37ad0f0f 100644 --- a/src/main/java/eu/hbp/mip/controllers/ExperimentAPI.java +++ b/src/main/java/eu/hbp/mip/controllers/ExperimentAPI.java @@ -2,7 +2,6 @@ package eu.hbp.mip.controllers; import eu.hbp.mip.models.DTOs.ExperimentDTO; -import eu.hbp.mip.services.ActiveUserService; import eu.hbp.mip.services.ExperimentService; import eu.hbp.mip.utils.JsonConverters; import io.swagger.annotations.Api; @@ -78,15 +77,6 @@ public class ExperimentAPI { } - @ApiOperation(value = "Create a transient experiment", response = ExperimentDTO.class) - @RequestMapping(value = "/transient", method = RequestMethod.POST) - public ResponseEntity<String> createTransientExperiment(Authentication authentication, @RequestBody ExperimentDTO experimentDTO) { - experimentDTO = experimentService.createTransientExperiment(authentication, experimentDTO, "(POST) /experiments/transient"); - - return new ResponseEntity<>(JsonConverters.convertObjectToJsonString(experimentDTO), HttpStatus.OK); - } - - @ApiOperation(value = "Update an experiment", response = ExperimentDTO.class) @RequestMapping(value = "/{uuid}", method = RequestMethod.PATCH) public ResponseEntity<String> updateExperiment(@RequestBody ExperimentDTO experimentDTO, @ApiParam(value = "uuid", required = true) @PathVariable("uuid") String uuid) { @@ -102,4 +92,13 @@ public class ExperimentAPI { experimentService.deleteExperiment(uuid, "(DELETE) /experiments/{uuid}"); return new ResponseEntity<>(HttpStatus.OK); } + + + @ApiOperation(value = "Create a transient experiment", response = ExperimentDTO.class) + @RequestMapping(value = "/transient", method = RequestMethod.POST) + public ResponseEntity<String> createTransientExperiment(Authentication authentication, @RequestBody ExperimentDTO experimentDTO) { + experimentDTO = experimentService.createTransientExperiment(authentication, experimentDTO, "(POST) /experiments/transient"); + + return new ResponseEntity<>(JsonConverters.convertObjectToJsonString(experimentDTO), HttpStatus.OK); + } } \ No newline at end of file diff --git a/src/main/java/eu/hbp/mip/services/ActiveUserService.java b/src/main/java/eu/hbp/mip/services/ActiveUserService.java index 5004e1f7f92dfc96e7f4decbf007c7dc442ae370..61bad973c94cc04da2adccf5952949378030ce92 100644 --- a/src/main/java/eu/hbp/mip/services/ActiveUserService.java +++ b/src/main/java/eu/hbp/mip/services/ActiveUserService.java @@ -46,19 +46,15 @@ public class ActiveUserService { return user; } - - // TODO Update user if new values are providedTO // If authentication is ON get user info from Token KeycloakPrincipal keycloakPrincipal = (KeycloakPrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); IDToken idToken = keycloakPrincipal.getKeycloakSecurityContext().getIdToken(); - UserDAO userInDatabase = userRepository.findByUsername(idToken.getPreferredUsername()); - if (userInDatabase != null) { - user = userInDatabase; - } else { - UserDAO newUser = new UserDAO(idToken.getPreferredUsername(), idToken.getName(), idToken.getEmail()); - userRepository.save(newUser); - user = newUser; + user = new UserDAO(idToken.getPreferredUsername(), idToken.getName(), idToken.getEmail()); + + UserDAO userInDatabase = userRepository.findByUsername(user.getUsername()); + if (userInDatabase == null || !userInDatabase.equals(user)) { + userRepository.save(user); } return user; } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 366f1c340ea0802001df9493415072ef41fb1376..84053f9128a3a2c724ae1aa3ecab8ad0ff9b2e39 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,5 +1,19 @@ # Configuration for development purposes +### LOG LEVELS ### +logging: + level: + root: "ERROR" + org: "ERROR" + eu: + hbp: "DEBUG" + + +### AUTHENTICATION ### +authentication: + enabled: true + + ### DATABASE CONFIGURATION ### spring: portal-datasource: @@ -17,28 +31,6 @@ spring: dialect: org.hibernate.dialect.PostgreSQL9Dialect ddl-auto: validate -### LOG LEVELS ### -logging: - level: - root: "ERROR" - org: "ERROR" - eu: - hbp: "DEBUG" - -### EMBEDDED SERVER CONFIGURATION ### -server: - servlet: - contextPath: "/services" - port: 8080 - forward-headers-strategy: native - -### ENDPOINTS ### -endpoints: - enabled: true - health: - enabled: true - endpoint: "/health" - sensitive: false ### EXTERNAL SERVICES ### services: @@ -52,11 +44,8 @@ services: galaxyUsername: "admin" galaxyPassword: "password" -### Authentication ### -authentication: - enabled: false -### Keycloak ### +### KEYCLOAK ### keycloak: enabled: true auth-server-url: "http://127.0.0.1/auth" @@ -67,8 +56,26 @@ keycloak: secret: "dae83a6b-c769-4186-8383-f0984c6edf05" principal-attribute: "preferred_username" + ### EXTERNAL FILES ### # Files are loaded from the resources files: pathologies_json: "classPath:/pathologies.json" disabledAlgorithms_json: "classPath:/disabledAlgorithms.json" + + +### EMBEDDED SERVER CONFIGURATION ### +server: + servlet: + contextPath: "/services" + port: 8080 + forward-headers-strategy: native + + +### ENDPOINTS ### +endpoints: + enabled: true + health: + enabled: true + endpoint: "/health" + sensitive: false \ No newline at end of file