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 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
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

80

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

94

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

103

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

104

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

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

111

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