| 1 | package edu.ucsb.cs156.frontiers.services; | |
| 2 | ||
| 3 | import com.fasterxml.jackson.core.JsonProcessingException; | |
| 4 | import com.fasterxml.jackson.databind.JsonNode; | |
| 5 | import com.fasterxml.jackson.databind.ObjectMapper; | |
| 6 | import edu.ucsb.cs156.frontiers.entities.Course; | |
| 7 | import edu.ucsb.cs156.frontiers.errors.NoLinkedOrganizationException; | |
| 8 | import io.jsonwebtoken.Jwts; | |
| 9 | import java.security.KeyFactory; | |
| 10 | import java.security.NoSuchAlgorithmException; | |
| 11 | import java.security.interfaces.RSAPrivateKey; | |
| 12 | import java.security.spec.InvalidKeySpecException; | |
| 13 | import java.security.spec.PKCS8EncodedKeySpec; | |
| 14 | import java.time.Instant; | |
| 15 | import java.time.temporal.ChronoUnit; | |
| 16 | import java.util.Base64; | |
| 17 | import java.util.Date; | |
| 18 | import lombok.extern.slf4j.Slf4j; | |
| 19 | import org.springframework.beans.factory.annotation.Value; | |
| 20 | import org.springframework.boot.web.client.RestTemplateBuilder; | |
| 21 | import org.springframework.data.auditing.DateTimeProvider; | |
| 22 | import org.springframework.http.HttpEntity; | |
| 23 | import org.springframework.http.HttpHeaders; | |
| 24 | import org.springframework.http.HttpMethod; | |
| 25 | import org.springframework.http.ResponseEntity; | |
| 26 | import org.springframework.stereotype.Service; | |
| 27 | import org.springframework.web.client.RestTemplate; | |
| 28 | ||
| 29 | @Service | |
| 30 | @Slf4j | |
| 31 | public class JwtService { | |
| 32 | @Value("${app.private.key:no-key-present}") | |
| 33 | private String privateKey; | |
| 34 | ||
| 35 | @Value("${app.client.id:no-client-id}") | |
| 36 | private String clientId; | |
| 37 | ||
| 38 | private final RestTemplate restTemplate; | |
| 39 | ||
| 40 | private final ObjectMapper objectMapper; | |
| 41 | ||
| 42 | private final DateTimeProvider dateTimeProvider; | |
| 43 | ||
| 44 | public JwtService( | |
| 45 | RestTemplateBuilder restTemplateBuilder, | |
| 46 | ObjectMapper objectMapper, | |
| 47 | DateTimeProvider dateTimeProvider) { | |
| 48 | this.restTemplate = restTemplateBuilder.build(); | |
| 49 | this.objectMapper = objectMapper; | |
| 50 | this.dateTimeProvider = dateTimeProvider; | |
| 51 | } | |
| 52 | ||
| 53 | private RSAPrivateKey getPrivateKey() throws NoSuchAlgorithmException, InvalidKeySpecException { | |
| 54 | String key = privateKey; | |
| 55 | key = key.replace("-----BEGIN PRIVATE KEY-----", ""); | |
| 56 | key = key.replace("-----END PRIVATE KEY-----", ""); | |
| 57 | key = key.replaceAll(" ", ""); | |
| 58 | key = key.replaceAll(System.lineSeparator(), ""); | |
| 59 | PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(key.getBytes())); | |
| 60 | KeyFactory kf = KeyFactory.getInstance("RSA"); | |
| 61 |
1
1. getPrivateKey : replaced return value with null for edu/ucsb/cs156/frontiers/services/JwtService::getPrivateKey → KILLED |
return (RSAPrivateKey) kf.generatePrivate(spec); |
| 62 | } | |
| 63 | ||
| 64 | /** | |
| 65 | * Method to retrieve a signed JWT that a service can use to authenticate with GitHub as an app | |
| 66 | * installation without permissions to a specific organization. | |
| 67 | * | |
| 68 | * @return Signed JWT that expires in 5 minutes in the form of a String | |
| 69 | * @throws InvalidKeySpecException if the key is invalid, the exception will be thrown. | |
| 70 | */ | |
| 71 | public String getJwt() throws NoSuchAlgorithmException, InvalidKeySpecException { | |
| 72 | Instant currentTime = Instant.from(dateTimeProvider.getNow().get()); | |
| 73 | String token = | |
| 74 | Jwts.builder() | |
| 75 | .issuedAt(Date.from(currentTime.minus(30, ChronoUnit.SECONDS))) | |
| 76 | .expiration(Date.from(currentTime.plus(5, ChronoUnit.MINUTES))) | |
| 77 | .issuer(clientId) | |
| 78 | .signWith(getPrivateKey(), Jwts.SIG.RS256) | |
| 79 | .compact(); | |
| 80 |
1
1. getJwt : replaced return value with "" for edu/ucsb/cs156/frontiers/services/JwtService::getJwt → KILLED |
return token; |
| 81 | } | |
| 82 | ||
| 83 | /** | |
| 84 | * Method to retrieve a token to act as a particular app installation in a particular organization | |
| 85 | * | |
| 86 | * @param course ID of the particular app installation to act as | |
| 87 | * @return Token accepted by GitHub to act as a particular installation. | |
| 88 | */ | |
| 89 | public String getInstallationToken(Course course) | |
| 90 | throws JsonProcessingException, | |
| 91 | NoSuchAlgorithmException, | |
| 92 | InvalidKeySpecException, | |
| 93 | NoLinkedOrganizationException { | |
| 94 |
2
1. getInstallationToken : negated conditional → KILLED 2. getInstallationToken : negated conditional → KILLED |
if (course.getOrgName() == null || course.getInstallationId() == null) { |
| 95 | throw new NoLinkedOrganizationException(course.getCourseName()); | |
| 96 | } else { | |
| 97 | String token = getJwt(); | |
| 98 | String ENDPOINT = | |
| 99 | "https://api.github.com/app/installations/" | |
| 100 | + course.getInstallationId() | |
| 101 | + "/access_tokens"; | |
| 102 | HttpHeaders headers = new HttpHeaders(); | |
| 103 |
1
1. getInstallationToken : removed call to org/springframework/http/HttpHeaders::add → KILLED |
headers.add("Authorization", "Bearer " + token); |
| 104 |
1
1. getInstallationToken : removed call to org/springframework/http/HttpHeaders::add → KILLED |
headers.add("Accept", "application/vnd.github+json"); |
| 105 |
1
1. getInstallationToken : removed call to org/springframework/http/HttpHeaders::add → KILLED |
headers.add("X-GitHub-Api-Version", "2022-11-28"); |
| 106 | HttpEntity<String> entity = new HttpEntity<>(headers); | |
| 107 | ResponseEntity<String> response = | |
| 108 | restTemplate.exchange(ENDPOINT, HttpMethod.POST, entity, String.class); | |
| 109 | JsonNode responseJson = objectMapper.readTree(response.getBody()); | |
| 110 | String installationToken = responseJson.get("token").asText(); | |
| 111 |
1
1. getInstallationToken : replaced return value with "" for edu/ucsb/cs156/frontiers/services/JwtService::getInstallationToken → KILLED |
return installationToken; |
| 112 | } | |
| 113 | } | |
| 114 | } | |
Mutations | ||
| 61 |
1.1 |
|
| 80 |
1.1 |
|
| 94 |
1.1 2.2 |
|
| 103 |
1.1 |
|
| 104 |
1.1 |
|
| 105 |
1.1 |
|
| 111 |
1.1 |