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