Skip to content

Commit

Permalink
Merge pull request #1723 from reportportal/rc/5.9.0
Browse files Browse the repository at this point in the history
Release 5.9.0
  • Loading branch information
IvanKustau authored Jul 10, 2023
2 parents 4c3b2bf + 91810cc commit 108534e
Show file tree
Hide file tree
Showing 32 changed files with 1,223 additions and 597 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ on:

env:
GH_USER_NAME: github.actor
SCRIPTS_VERSION: 5.7.0
SCRIPTS_VERSION: 5.8.0
BOM_VERSION: 5.7.5
MIGRATIONS_VERSION: 5.8.0
RELEASE_VERSION: 5.8.0
MIGRATIONS_VERSION: 5.9.0
RELEASE_VERSION: 5.9.0
REPOSITORY_URL: 'https://maven.pkg.github.com/'

jobs:
Expand Down
8 changes: 4 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
FROM alpine:latest
LABEL version=5.8.0 description="EPAM Report portal. Main API Service" maintainer="Andrei Varabyeu <[email protected]>, Hleb Kanonik <[email protected]>"
FROM amazoncorretto:11.0.17
LABEL version=5.9.0 description="EPAM Report portal. Main API Service" maintainer="Andrei Varabyeu <[email protected]>, Hleb Kanonik <[email protected]>"
ARG GH_TOKEN
RUN echo 'exec java ${JAVA_OPTS} -jar service-api-5.8.0-exec.jar' > /start.sh && chmod +x /start.sh && \
wget --header="Authorization: Bearer ${GH_TOKEN}" -q https://maven.pkg.github.com/reportportal/service-api/com/epam/reportportal/service-api/5.8.0/service-api-5.8.0-exec.jar
RUN echo 'exec java ${JAVA_OPTS} -jar service-api-5.9.0-exec.jar' > /start.sh && chmod +x /start.sh && \
wget --header="Authorization: Bearer ${GH_TOKEN}" -q https://maven.pkg.github.com/reportportal/service-api/com/epam/reportportal/service-api/5.9.0/service-api-5.9.0-exec.jar
ENV JAVA_OPTS="-Xmx1g -XX:+UseG1GC -XX:InitiatingHeapOccupancyPercent=70 -Djava.security.egd=file:/dev/./urandom"
VOLUME ["/tmp"]
EXPOSE 8080
Expand Down
1 change: 1 addition & 0 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ node {
withEnv(["AWS_URI=${AWS_URI}"]) {
sh 'docker rmi ${AWS_URI}/service-api:SNAPSHOT-${BUILD_NUMBER}'
sh 'docker rmi reportportal-dev/service-api:latest'
sh './gradlew removeScripts'
}
}
}
42 changes: 42 additions & 0 deletions Jenkinsfile-release
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
pipeline {
agent any

environment {
JAVA_HOME = "${tool 'openjdk-11'}"
PATH = "${env.JAVA_HOME}/bin:${env.PATH}"
DOCKERHUB = credentials('dockerhub')
GITHUB = credentials('github_token')
}

stages {
stage('Assemble') {
steps {
sh './gradlew clean assemble -P buildNumber=$VERSION'
}
}

stage('Test') {
steps {
sh './gradlew test --full-stacktrace'
}
}

stage('Build Artifact'){
steps{
sh './gradlew build -PreleaseMode=true -PgithubUserName=$GITHUB_USR -PgithubToken=$GITHUB_PSW -Pscripts.version=master -Pmigrations.version=master -Pbom.version=$VERSION'
}
}

stage('Build Docker Image'){
steps{
sh './gradlew buildDocker -P dockerTag=reportportal/service-api:$VERSION'
}
}

stage('Push to DockerHub') {
steps {
sh 'echo $DOCKERHUB_PSW | docker login -u $DOCKERHUB_USR --password-stdin && docker push reportportal/service-api:$VERSION'
}
}
}
}
7 changes: 4 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,13 @@ dependencies {
compile 'com.epam.reportportal:commons-fonts'
compile 'com.epam.reportportal:plugin-api'
} else {
compile 'com.github.reportportal:commons-dao:43d03cd'
compile 'com.github.reportportal:commons-events:f130879'
compile 'com.github.reportportal:commons-dao:81abcf01'
compile 'com.github.reportportal:commons-rules:5.3.0'
compile 'com.github.reportportal:commons-model:a046458'
compile 'com.github.reportportal:commons-model:292c8af2'
compile 'com.github.reportportal:commons:7480d61'
compile 'com.github.reportportal:commons-fonts:10d1054'
compile 'com.github.reportportal:plugin-api:886ac55'
compile 'com.github.reportportal:plugin-api:2b30a961'
}

compile 'org.springframework.boot:spring-boot-starter-aop'
Expand Down
8 changes: 4 additions & 4 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ services:
- minio
environment:
- JAVA_OPTS=-Xmx1g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp
- RP_BINARYSTORE_TYPE=minio
- RP_BINARYSTORE_MINIO_ENDPOINT=http://minio:9000
- RP_BINARYSTORE_MINIO_ACCESSKEY=minio
- RP_BINARYSTORE_MINIO_SECRETKEY=minio123
- DATASTORE_TYPE=minio
- DATASTORE_ENDPOINT=http://minio:9000
- DATASTORE_ACCESSKEY=minio
- DATASTORE_SECRETKEY=minio123
restart: always
ports:
- "8585:8585"
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version=5.8.1
version=5.9.0
description=EPAM Report portal. Main API Service
dockerPrepareEnvironment=
dockerJavaOpts=-Xmx1g -XX:+UseG1GC -XX:InitiatingHeapOccupancyPercent=70 -Djava.security.egd=file:/dev/./urandom
Expand Down
9 changes: 5 additions & 4 deletions project-properties.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ project.ext {
limits = [
'instruction': 70,
'branch' : 53,
'line' : 75,
'line' : 74,
'complexity' : 60,
'method' : 65,
'class' : 90
]
isDebugMode = System.getProperty("DEBUG", "false") == "true"
releaseMode = project.hasProperty("releaseMode")
scriptsUrl = commonScriptsUrl + (releaseMode ? getProperty('scripts.version') : 'EPMRPP-81358-fix-vulnerabilities')
migrationsUrl = migrationsScriptsUrl + (releaseMode ? getProperty('migrations.version') : 'hotfix/5.8.0')
scriptsUrl = commonScriptsUrl + (releaseMode ? getProperty('scripts.version') : 'master')
migrationsUrl = migrationsScriptsUrl + (releaseMode ? getProperty('migrations.version') : 'master')
//TODO refactor with archive download
testScriptsSrc = [
(migrationsUrl + '/migrations/0_extensions.up.sql') : 'V001__extensions.sql',
Expand Down Expand Up @@ -58,7 +58,8 @@ project.ext {
(migrationsUrl + '/migrations/59_stale_materialized_view.up.sql') : 'V059__stale_materialized_view.sql',
(migrationsUrl + '/migrations/60_sender_case_operator.up.sql') : 'V060__sender_case_operator.sql',
(migrationsUrl + '/migrations/61_remove_acl.up.sql') : 'V061__remove_acl.sql',
(migrationsUrl + '/migrations/62_remove_dashboard_cascade_drop.up.sql') : 'V062_remove_dashboard_cascade_drop.sql',
(migrationsUrl + '/migrations/62_remove_dashboard_cascade_drop.up.sql') : 'V062__remove_dashboard_cascade_drop.sql',
(migrationsUrl + '/migrations/67_api_keys.up.sql') : 'V067__api_keys.sql',
]
excludeTests = ['**/entity/**',
'**/aop/**',
Expand Down
67 changes: 67 additions & 0 deletions src/main/java/com/epam/ta/reportportal/auth/ApiKeyUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright 2023 EPAM Systems
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.epam.ta.reportportal.auth;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.codec.digest.DigestUtils;

public class ApiKeyUtils {

private static final String UUID_PATTERN =
"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$";

private ApiKeyUtils() {
}

/**
* Validate token sign
*/
public static boolean validateToken(String apiKey) {
if (isUUID(apiKey) || (apiKey.length() == 27 && Base64.getUrlDecoder().decode(apiKey.getBytes(
StandardCharsets.US_ASCII)).length == 20)) {
return true;
}
String[] nameChecksum = apiKey.split("_", 2);
try {
byte[] checksumBytes = Base64.getUrlDecoder().decode(nameChecksum[1]);
byte[] actualUuid = Arrays.copyOf(checksumBytes, 16);
byte[] actualHash = Arrays.copyOfRange(checksumBytes, 16, checksumBytes.length);

byte[] nameBytes = nameChecksum[0].getBytes(StandardCharsets.UTF_8);
ByteBuffer nameUuidBb = ByteBuffer.wrap(new byte[nameBytes.length + actualUuid.length]);
nameUuidBb.put(nameBytes);
nameUuidBb.put(actualUuid);

byte[] expected = DigestUtils.sha3_256(nameUuidBb.array());
return Arrays.equals(actualHash, expected);
} catch (Exception e) {
return false;
}
}

private static boolean isUUID(String uuidStr) {
Pattern pattern = Pattern.compile(UUID_PATTERN);
Matcher matcher = pattern.matcher(uuidStr);
return matcher.matches();
}

}
163 changes: 105 additions & 58 deletions src/main/java/com/epam/ta/reportportal/auth/CombinedTokenStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,27 @@

import com.epam.ta.reportportal.auth.util.AuthUtils;
import com.epam.ta.reportportal.commons.ReportPortalUser;
import com.epam.ta.reportportal.dao.OAuth2AccessTokenRepository;
import com.epam.ta.reportportal.entity.user.StoredAccessToken;
import com.epam.ta.reportportal.dao.ApiKeyRepository;
import com.epam.ta.reportportal.dao.UserRepository;
import com.epam.ta.reportportal.entity.user.ApiKey;
import com.google.common.collect.Maps;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import javax.xml.bind.DatatypeConverter;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.common.util.SerializationUtils;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.stereotype.Component;
Expand All @@ -38,58 +51,92 @@
@Transactional(readOnly = true)
public class CombinedTokenStore extends JwtTokenStore {

@Autowired
private OAuth2AccessTokenRepository oAuth2AccessTokenRepository;

@Autowired
private UserDetailsService userDetailsService;

@Autowired
public CombinedTokenStore(JwtAccessTokenConverter jwtTokenEnhancer) {
super(jwtTokenEnhancer);
}

@Override
public OAuth2Authentication readAuthentication(OAuth2AccessToken token) {
try {
return super.readAuthentication(token);
} catch (InvalidTokenException e) {
return this.readAuthentication(token.getValue());
}
}

@Override
public OAuth2Authentication readAuthentication(String tokenId) {
try {
return super.readAuthentication(tokenId);
} catch (InvalidTokenException e) {
StoredAccessToken accessToken = oAuth2AccessTokenRepository.findByTokenId(tokenId);
ReportPortalUser userDetails = (ReportPortalUser) userDetailsService.loadUserByUsername(accessToken.getUserName());
OAuth2Authentication authentication = AuthUtils.deserializeSafely(accessToken.getAuthentication(), auth -> {
// if we are at the place, there was InvalidClassException,
// and we successfully recovered auth object
// let's save it back to DB then, since now it has correct version UUID
accessToken.setAuthentication(SerializationUtils.serialize(auth));
oAuth2AccessTokenRepository.save(accessToken);
});

ReportPortalUser reportPortalUser = (ReportPortalUser) authentication.getPrincipal();
reportPortalUser.setProjectDetails(userDetails.getProjectDetails());
reportPortalUser.setUserRole(userDetails.getUserRole());
return authentication;
}
}

@Override
public OAuth2AccessToken readAccessToken(String tokenValue) {
try {
return super.readAccessToken(tokenValue);
} catch (InvalidTokenException e) {
StoredAccessToken token = oAuth2AccessTokenRepository.findByTokenId(tokenValue);
if (token == null) {
return null; //let spring security handle the invalid token
}
return SerializationUtils.deserialize(token.getToken());
}
}
@Autowired
private ApiKeyRepository apiKeyRepository;

@Autowired
private UserRepository userRepository;

@Autowired
public CombinedTokenStore(JwtAccessTokenConverter jwtTokenEnhancer) {
super(jwtTokenEnhancer);
}

@Override
public OAuth2Authentication readAuthentication(OAuth2AccessToken token) {
try {
return super.readAuthentication(token);
} catch (InvalidTokenException e) {
return this.readAuthentication(token.getValue());
}
}

@Override
public OAuth2Authentication readAuthentication(String tokenId) {
try {
return super.readAuthentication(tokenId);
} catch (InvalidTokenException e) {
String hashedKey = DatatypeConverter.printHexBinary(DigestUtils.sha3_256(tokenId));
ApiKey apiKey = apiKeyRepository.findByHash(hashedKey);
if (apiKey != null) {
Optional<ReportPortalUser> user = userRepository.findReportPortalUser(apiKey.getUserId());
if (user.isPresent()) {
return getAuthentication(getUserWithAuthorities(user.get()));
}
}
return null;
}
}

@Override
public OAuth2AccessToken readAccessToken(String tokenValue) {
try {
return super.readAccessToken(tokenValue);
} catch (InvalidTokenException e) {
if (ApiKeyUtils.validateToken(tokenValue)) {
DefaultOAuth2AccessToken defaultOAuth2AccessToken = new DefaultOAuth2AccessToken(
tokenValue);
defaultOAuth2AccessToken.setExpiration(new Date(System.currentTimeMillis() + 60 * 1000L));
return defaultOAuth2AccessToken;
}
return null; //let spring security handle the invalid token
}
}

private OAuth2Authentication getAuthentication(ReportPortalUser user) {
HashMap<String, String> requestParameters = new HashMap<>();
requestParameters.put("username", user.getUsername());
requestParameters.put("client_id", ReportPortalClient.api.name());

Set<GrantedAuthority> authorities = new HashSet<>();
authorities.add(new SimpleGrantedAuthority(user.getUserRole().getAuthority()));

Set<String> scopes = Collections.singleton(ReportPortalClient.api.name());

OAuth2Request authorizationRequest = new OAuth2Request(
requestParameters, ReportPortalClient.api.name(),
authorities, true, scopes, Collections.emptySet(), null,
Collections.emptySet(), null);

UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
user, null, authorities);

OAuth2Authentication authenticationRequest = new OAuth2Authentication(
authorizationRequest, authenticationToken);
authenticationRequest.setAuthenticated(true);

return authenticationRequest;
}

private ReportPortalUser getUserWithAuthorities(ReportPortalUser user) {
return ReportPortalUser.userBuilder()
.withUserName(user.getUsername())
.withPassword(user.getPassword())
.withAuthorities(AuthUtils.AS_AUTHORITIES.apply(user.getUserRole()))
.withUserId(user.getUserId())
.withUserRole(user.getUserRole())
.withProjectDetails(Maps.newHashMapWithExpectedSize(1))
.withEmail(user.getEmail())
.build();
}
}
Loading

0 comments on commit 108534e

Please sign in to comment.