JwtService.java

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

Mutations

63

1.1
Location : getPrivateKey
Killed by : edu.ucsb.cs156.frontiers.services.JwtServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.services.JwtServiceTests]/[method:testObtainingInstallationToken()]
replaced return value with null for edu/ucsb/cs156/frontiers/services/JwtService::getPrivateKey → KILLED

82

1.1
Location : getJwt
Killed by : edu.ucsb.cs156.frontiers.services.JwtServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.services.JwtServiceTests]/[method:testGettingJwt()]
replaced return value with "" for edu/ucsb/cs156/frontiers/services/JwtService::getJwt → KILLED

96

1.1
Location : getInstallationToken
Killed by : edu.ucsb.cs156.frontiers.services.JwtServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.services.JwtServiceTests]/[method:testGetInstallationToken_throwsNoLinkedOrganizationException_whenInstallationIdIsNull()]
negated conditional → KILLED

2.2
Location : getInstallationToken
Killed by : edu.ucsb.cs156.frontiers.services.JwtServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.services.JwtServiceTests]/[method:testGetInstallationToken_throwsNoLinkedOrganizationException_whenOrgNameIsNull()]
negated conditional → KILLED

105

1.1
Location : getInstallationToken
Killed by : edu.ucsb.cs156.frontiers.services.JwtServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.services.JwtServiceTests]/[method:testObtainingInstallationToken()]
removed call to org/springframework/http/HttpHeaders::add → KILLED

106

1.1
Location : getInstallationToken
Killed by : edu.ucsb.cs156.frontiers.services.JwtServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.services.JwtServiceTests]/[method:testObtainingInstallationToken()]
removed call to org/springframework/http/HttpHeaders::add → KILLED

107

1.1
Location : getInstallationToken
Killed by : edu.ucsb.cs156.frontiers.services.JwtServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.services.JwtServiceTests]/[method:testObtainingInstallationToken()]
removed call to org/springframework/http/HttpHeaders::add → KILLED

113

1.1
Location : getInstallationToken
Killed by : edu.ucsb.cs156.frontiers.services.JwtServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.services.JwtServiceTests]/[method:testObtainingInstallationToken()]
replaced return value with "" for edu/ucsb/cs156/frontiers/services/JwtService::getInstallationToken → KILLED

Active mutators

Tests examined


Report generated by PIT 1.17.0