diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 5ec8a3f..22c5dfb 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -4,6 +4,7 @@ on: push: branches: - main + - main-who jobs: build: @@ -32,9 +33,45 @@ jobs: server-id: docker.io server-username: DOCKER_USERNAME server-password: DOCKER_PASSWORD + - name: Get current version + id: get-version + run: | + current_version=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) + echo "Current version: $current_version" + echo "::set-output name=current_version::$current_version" - name: Bump version id: bump - uses: mickem/gh-action-bump-maven-version@v1 + run: | + current_version=${{ steps.get-version.outputs.current_version }} + branch=${GITHUB_REF##*/} + echo "Current branch: $branch" + + # Extract the base version without suffix + base_version=$(echo $current_version | sed -E 's/(-.*)?$//') + + # Increment the base version (assuming semantic versioning) + IFS='.' read -r -a version_parts <<< "$base_version" + version_parts[2]=$((version_parts[2] + 1)) + new_base_version="${version_parts[0]}.${version_parts[1]}.${version_parts[2]}" + + if [[ "$branch" == "main-who" ]]; then + new_version="${new_base_version}-WHO" + else + new_version="$new_base_version" + fi + + echo "New version: $new_version" + mvn versions:set -DnewVersion=$new_version -DgenerateBackupPoms=false + echo ":: set-output name=new_version::$new_version" + - name: Commit new version + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git add pom.xml + git commit -m "Bump version to ${{ steps.bump.outputs.new_version }}" + git tag ${{ steps.bump.outputs.new_version }} + git push origin HEAD:${GITHUB_REF##*/} + git push origin ${{ steps.bump.outputs.new_version }} - name: Build and Publish package run: mvn --batch-mode -Prelease package dockerfile:push - name: Release @@ -42,9 +79,9 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - tag_name: ${{ steps.bump.outputs.tag }} + tag_name: ${{ steps.bump.outputs.new_version }} generate_release_notes: true env: DOCKER_USERNAME: ${{secrets.DOCKER_USERNAME}} - DOCKER_TOKEN: ${{secrets.DOCKER_PASSWORD}} + DOCKER_TOKEN: ${{secrets.DOCKER_PASSWORD}} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 2f0fd85..2ae5915 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ edu.stanford.protege webprotege-gwt-api-gateway - 1.0.11 + 1.0.12-WHO webprotege-gwt-api-gateway The API Gateway for the WebProtégé GWT User Interface @@ -77,7 +77,7 @@ edu.stanford.protege webprotege-ipc - 1.0.4 + 1.0.5 @@ -153,7 +153,7 @@ edu.stanford.protege webprotege-backend-api - 1.0.23 + 1.0.24-WHO diff --git a/src/main/java/edu/stanford/protege/webprotege/gateway/FileStorageService.java b/src/main/java/edu/stanford/protege/webprotege/gateway/FileStorageService.java index ee92be8..d0602ae 100644 --- a/src/main/java/edu/stanford/protege/webprotege/gateway/FileStorageService.java +++ b/src/main/java/edu/stanford/protege/webprotege/gateway/FileStorageService.java @@ -43,6 +43,7 @@ public FileSubmissionId storeFile(Path tempFile) { logger.info("Storing file ({}) in {} bucket with an object id of {}", getFileSizeInMB(tempFile), minioProperties.getBucketName(), fileIdentifier); createBucketIfNecessary(); uploadObject(tempFile, fileIdentifier); + logger.info("File {} uploaded successfully !", fileIdentifier); return new FileSubmissionId(fileIdentifier); } diff --git a/src/main/java/edu/stanford/protege/webprotege/gateway/GatewayController.java b/src/main/java/edu/stanford/protege/webprotege/gateway/GatewayController.java index 7df42d2..e1f40e4 100644 --- a/src/main/java/edu/stanford/protege/webprotege/gateway/GatewayController.java +++ b/src/main/java/edu/stanford/protege/webprotege/gateway/GatewayController.java @@ -1,15 +1,19 @@ package edu.stanford.protege.webprotege.gateway; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import edu.stanford.protege.webprotege.common.UserId; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.http.HttpStatus; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.web.authentication.logout.LogoutHandler; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @@ -34,8 +38,12 @@ public class GatewayController { private final RpcRequestProcessor rpcRequestProcessor; - public GatewayController(RpcRequestProcessor rpcRequestProcessor) { + private final LogoutHandler logoutHandler; + + + public GatewayController(RpcRequestProcessor rpcRequestProcessor, LogoutHandler logoutHandler) { this.rpcRequestProcessor = rpcRequestProcessor; + this.logoutHandler = logoutHandler; } @PostMapping(path = "/api/execute", consumes = "application/json") @@ -64,4 +72,10 @@ public RpcResponse execute(@RequestBody RpcRequest request, return RpcResponse.forError(request.methodName(), HttpStatus.GATEWAY_TIMEOUT); } } + + @GetMapping(path = "/logout") + public HttpServletResponse logout(HttpServletRequest request, HttpServletResponse response) { + logoutHandler.logout(request, response, SecurityContextHolder.getContext().getAuthentication()); + return response; + } } diff --git a/src/main/java/edu/stanford/protege/webprotege/gateway/KeycloakLogoutHandler.java b/src/main/java/edu/stanford/protege/webprotege/gateway/KeycloakLogoutHandler.java index 5dbc9f6..ae4ab64 100644 --- a/src/main/java/edu/stanford/protege/webprotege/gateway/KeycloakLogoutHandler.java +++ b/src/main/java/edu/stanford/protege/webprotege/gateway/KeycloakLogoutHandler.java @@ -8,13 +8,18 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.security.web.authentication.logout.LogoutHandler; +import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; +import java.net.URI; + @Component -public class KeycloakLogoutHandler implements LogoutHandler { +public class KeycloakLogoutHandler extends SecurityContextLogoutHandler { private static final Logger logger = LoggerFactory.getLogger(KeycloakLogoutHandler.class); private final RestTemplate restTemplate; @@ -26,21 +31,25 @@ public KeycloakLogoutHandler() { @Override public void logout(HttpServletRequest request, HttpServletResponse response, Authentication auth) { - logoutFromKeycloak((OidcUser) auth.getPrincipal()); + logoutFromKeycloak((Jwt) auth.getPrincipal()); + super.logout(request, response,auth); } - private void logoutFromKeycloak(OidcUser user) { - String endSessionEndpoint = user.getIssuer() + "/protocol/openid-connect/logout"; - UriComponentsBuilder builder = UriComponentsBuilder - .fromUriString(endSessionEndpoint) - .queryParam("id_token_hint", user.getIdToken().getTokenValue()); - - ResponseEntity logoutResponse = restTemplate.getForEntity( - builder.toUriString(), String.class); - if (logoutResponse.getStatusCode().is2xxSuccessful()) { - logger.info("Successfulley logged out from Keycloak"); - } else { - logger.error("Could not propagate logout to Keycloak"); + private void logoutFromKeycloak(Jwt token) { + String issuer = token.getClaimAsString("iss"); + String endSessionEndpoint = issuer + "/protocol/openid-connect/logout"; + + String accessToken = token.getTokenValue(); + try { + URI logoutUri = new URI(endSessionEndpoint + "?token=" + accessToken); + ResponseEntity logoutResponse = restTemplate.getForEntity(logoutUri, String.class); + if (logoutResponse.getStatusCode().is2xxSuccessful()) { + logger.info("Successfulley logged out from Keycloak"); + } else { + logger.error("Could not propagate logout to Keycloak"); + } + } catch (Exception e) { + e.printStackTrace(); } } diff --git a/src/main/java/edu/stanford/protege/webprotege/gateway/SecurityConfig.java b/src/main/java/edu/stanford/protege/webprotege/gateway/SecurityConfig.java index 6f76cfc..4f19efa 100644 --- a/src/main/java/edu/stanford/protege/webprotege/gateway/SecurityConfig.java +++ b/src/main/java/edu/stanford/protege/webprotege/gateway/SecurityConfig.java @@ -1,5 +1,6 @@ package edu.stanford.protege.webprotege.gateway; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.Customizer; @@ -10,9 +11,14 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.core.session.SessionRegistryImpl; +import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter; import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority; import org.springframework.security.oauth2.core.user.OAuth2UserAuthority; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.access.intercept.AuthorizationFilter; +import org.springframework.security.web.authentication.logout.LogoutFilter; +import org.springframework.security.web.authentication.logout.LogoutHandler; +import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy; import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; @@ -39,16 +45,19 @@ public class SecurityConfig { SecurityConfig(KeycloakLogoutHandler keycloakLogoutHandler) { this.keycloakLogoutHandler = keycloakLogoutHandler; } - + @Bean + public LogoutFilter customLogoutFilter() { + return new LogoutFilter("/login?logout", keycloakLogoutHandler); + } @Bean protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl()); } - @Bean +/* @Bean public WebSecurityCustomizer webSecurityCustomizer() { return (web) -> web.debug(true); - } + }*/ @Bean public SecurityFilterChain resourceServerFilterChain(HttpSecurity http) throws Exception { http.csrf(AbstractHttpConfigurer::disable) @@ -57,10 +66,12 @@ public SecurityFilterChain resourceServerFilterChain(HttpSecurity http) throws E .anyRequest() .authenticated() ); + http.addFilterAfter(customLogoutFilter(), AuthorizationFilter.class); http.oauth2ResourceServer((oauth2) -> oauth2 .jwt(Customizer.withDefaults())); http.oauth2Login(Customizer.withDefaults()) - .logout(logout -> logout.addLogoutHandler(keycloakLogoutHandler).logoutSuccessUrl("/")); + .logout(AbstractHttpConfigurer::disable); + return http.build(); } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 1c62e34..e72233d 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,6 +1,11 @@ server: port: 7777 +logging: + level: + org: + springframework: + amqp: ERROR spring: application: name: webprotege-gwt-api-gateway @@ -11,15 +16,26 @@ spring: password: guest publisher-confirm-type: correlated publisher-returns: true + servlet: + multipart: + max-file-size: 200MB + max-request-size: 200MB + webprotege: rabbitmq: requestqueue: webprotege-gwt-api-gateway-queue responsequeue: webprotege-gwt-api-gateway-response-queue eventsqueue: webprotege-gwt-api-gateway-event-queue - timeout: 60000 + timeout: 120000 event-subscribe: true allowedOrigin: webprotege-local.edu + minio: + accessKey: webprotege + endPoint: http://webprotege-local.edu:9000 + secretKey: webprotege + bucketName: webprotege-uploads + spring.security.oauth2: client: