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

74

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

84

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

90

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

91

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

92

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

97

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