Skip to content
This repository has been archived by the owner on May 16, 2023. It is now read-only.

Commit

Permalink
Feat: Add TeleTan Type (EVENT and TEST) (#239)
Browse files Browse the repository at this point in the history
* Add TeleTan Type (EVENT and TEST)

* Fix Unit Tests

* Changed Event Teletan Validity

Co-authored-by: m.schulte <[email protected]>
  • Loading branch information
f11h and mschulte-tsi authored Aug 19, 2021
1 parent b6637c1 commit 8a1cb7c
Show file tree
Hide file tree
Showing 19 changed files with 247 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ public static class Valid {
private int length = 1;
// Number of hours that teleTAN remains valid
private int hours = 1;
private int eventDays = 2;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public class ExternalTanController {
* positive.
*
* @param registrationToken generated by a hashed guid or a teleTAN. {@link RegistrationToken}
* @param fake flag for fake request
* @param fake flag for fake request
* @return A generated transaction number {@link Tan}.
*/
@Operation(
Expand Down Expand Up @@ -134,17 +134,17 @@ public DeferredResult<ResponseEntity<Tan>> generateTan(@Valid @RequestBody Regis
}
appSession.incrementTanCounter();
appSession.setUpdatedAt(LocalDateTime.now());

appSessionService.saveAppSession(appSession);
String generatedTan = tanService.generateVerificationTan(tanSourceOfTrust);
String generatedTan = tanService.generateVerificationTan(tanSourceOfTrust, appSession.getTeleTanType());

Tan returnTan = generateReturnTan(generatedTan, fake);
Tan returnTan = generateReturnTan(generatedTan, fake);
stopWatch.stop();
fakeDelayService.updateFakeTanRequestDelay(stopWatch.getTotalTimeMillis());
DeferredResult<ResponseEntity<Tan>> deferredResult = new DeferredResult<>();
scheduledExecutor.schedule(() -> deferredResult.setResult(
ResponseEntity.status(HttpStatus.CREATED).body(returnTan)),
fakeDelayService.realDelayTan(),MILLISECONDS);
scheduledExecutor.schedule(() -> deferredResult.setResult(
ResponseEntity.status(HttpStatus.CREATED).body(returnTan)),
fakeDelayService.realDelayTan(), MILLISECONDS);
log.info("Returning the successfully generated tan.");
return deferredResult;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,12 @@ public class ExternalTokenController {

private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(4);

@NonNull
private final FakeRequestService fakeRequestController;

@NonNull
private final AppSessionService appSessionService;

@NonNull
private final TanService tanService;

@NonNull
private final FakeDelayService fakeDelayService;

/**
Expand Down Expand Up @@ -102,8 +98,13 @@ public DeferredResult<ResponseEntity<RegistrationToken>> generateRegistrationTok
log.info("Returning the successfully generated RegistrationToken.");
return deferredResult;
case TELETAN:
ResponseEntity<RegistrationToken> response = appSessionService.generateRegistrationTokenByTeleTan(key, fake);
Optional<VerificationTan> optional = tanService.getEntityByTan(key);

ResponseEntity<RegistrationToken> response = appSessionService.generateRegistrationTokenByTeleTan(
key,
fake,
optional.map(VerificationTan::getTeleTanType).orElse(null));

if (optional.isPresent()) {
VerificationTan teleTan = optional.get();
teleTan.setRedeemed(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,20 @@
package app.coronawarn.verification.controller;

import app.coronawarn.verification.exception.VerificationServerException;
import app.coronawarn.verification.model.AuthorizationRole;
import app.coronawarn.verification.model.AuthorizationToken;
import app.coronawarn.verification.model.Tan;
import app.coronawarn.verification.model.TeleTan;
import app.coronawarn.verification.model.TeleTanType;
import app.coronawarn.verification.service.JwtService;
import app.coronawarn.verification.service.TanService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.headers.Header;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import javax.validation.Valid;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -66,6 +71,8 @@ public class InternalTanController {
*/
public static final String TELE_TAN_ROUTE = "/tan/teletan";

public static final String TELE_TAN_TYPE_HEADER = "X-CWA-TELETAN-TYPE";

@NonNull
private final TanService tanService;

Expand All @@ -83,7 +90,12 @@ public class InternalTanController {
description = "The provided Tan is verified to be formerly issued by the verification server"
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Tan is valid an formerly issued by the verification server"),
@ApiResponse(
responseCode = "200",
description = "Tan is valid an formerly issued by the verification server",
headers = {
@Header(name = TELE_TAN_TYPE_HEADER, description = "Type of the TeleTan (TEST or EVENT)")
}),
@ApiResponse(responseCode = "404", description = "Tan could not be verified")})
@PostMapping(value = TAN_VERIFY_ROUTE,
consumes = MediaType.APPLICATION_JSON_VALUE
Expand All @@ -96,7 +108,15 @@ public ResponseEntity<?> verifyTan(@Valid @RequestBody Tan tan) {
log.info("The Tan is valid.");
return t;
})
.map(t -> ResponseEntity.ok().build())
.map(t -> {
ResponseEntity.BodyBuilder responseBuilder = ResponseEntity.ok();

if (t.getTeleTanType() != null) {
responseBuilder.header(TELE_TAN_TYPE_HEADER, t.getTeleTanType().toString());
}

return responseBuilder.build();
})
.orElseGet(() -> {
log.info("The Tan is invalid.");
throw new VerificationServerException(HttpStatus.NOT_FOUND, "No Tan found or Tan is invalid");
Expand All @@ -119,10 +139,21 @@ public ResponseEntity<?> verifyTan(@Valid @RequestBody Tan tan) {
produces = MediaType.APPLICATION_JSON_VALUE
)
public ResponseEntity<TeleTan> createTeleTan(
@RequestHeader(JwtService.HEADER_NAME_AUTHORIZATION) @Valid AuthorizationToken authorization) {
if (jwtService.isAuthorized(authorization.getToken())) {
@RequestHeader(JwtService.HEADER_NAME_AUTHORIZATION) @Valid AuthorizationToken authorization,
@RequestHeader(value = TELE_TAN_TYPE_HEADER, required = false) @Valid TeleTanType teleTanType) {

List<AuthorizationRole> requiredRoles = new ArrayList<>();

if (teleTanType == null) {
teleTanType = TeleTanType.TEST;
requiredRoles.add(AuthorizationRole.AUTH_C19_HOTLINE);
} else if (teleTanType == TeleTanType.EVENT) {
requiredRoles.add(AuthorizationRole.AUTH_C19_HOTLINE_EVENT);
}

if (jwtService.isAuthorized(authorization.getToken(), requiredRoles)) {
if (tanService.isTeleTanRateLimitNotExceeded()) {
String teleTan = tanService.generateVerificationTeleTan();
String teleTan = tanService.generateVerificationTeleTan(teleTanType);
log.info("The teleTAN is generated.");
return ResponseEntity.status(HttpStatus.CREATED).body(new TeleTan(teleTan));
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
package app.coronawarn.verification.domain;

import app.coronawarn.verification.model.AppSessionSourceOfTrust;
import app.coronawarn.verification.model.TeleTanType;
import java.io.Serializable;
import java.time.LocalDateTime;
import javax.persistence.Column;
Expand All @@ -34,13 +35,17 @@
import javax.persistence.Table;
import javax.persistence.Version;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

/**
* This class represents the AppSession-entity.
*/
@Data
@Getter
@Setter
@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
@Entity
Expand Down Expand Up @@ -83,6 +88,10 @@ public class VerificationAppSession implements Serializable {
@Enumerated(EnumType.STRING)
private AppSessionSourceOfTrust sourceOfTrust;

@Column(name = "teletan_type")
@Enumerated(EnumType.STRING)
private TeleTanType teleTanType;

/**
* This method increments the tan counter.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import app.coronawarn.verification.model.TanSourceOfTrust;
import app.coronawarn.verification.model.TanType;
import app.coronawarn.verification.model.TeleTanType;
import java.io.Serializable;
import java.time.LocalDateTime;
import javax.persistence.Column;
Expand All @@ -35,13 +36,17 @@
import javax.persistence.Table;
import javax.persistence.Version;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

/**
* This class represents the TAN - entity.
*/
@Data
@Getter
@Setter
@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
@Entity
Expand Down Expand Up @@ -85,6 +90,10 @@ public class VerificationTan implements Serializable {
@Enumerated(EnumType.STRING)
private TanType type;

@Column(name = "teletan_type")
@Enumerated(EnumType.STRING)
private TeleTanType teleTanType;

/**
* Check if the tan can be redeemed by date.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
@Getter
public enum AuthorizationRole {
AUTH_C19_HOTLINE("c19hotline"),
AUTH_C19_HEALTHAUTHORITY("c19healthauthority");
AUTH_C19_HEALTHAUTHORITY("c19healthauthority"),
AUTH_C19_HOTLINE_EVENT("c19hotline_event");

private final String roleName;

Expand Down
36 changes: 36 additions & 0 deletions src/main/java/app/coronawarn/verification/model/TeleTanType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Corona-Warn-App / cwa-verification
*
* (C) 2020, T-Systems International GmbH
*
* Deutsche Telekom AG and all other contributors /
* copyright owners license this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package app.coronawarn.verification.model;

import io.swagger.v3.oas.annotations.media.Schema;

@Schema(
description = "The TeleTan Type model."
)
public enum TeleTanType {

@Schema(description = "TeleTan is issued because of a positive PCR Test")
TEST,

@Schema(description = "TeleTan is issued because of a confirmed infection at an event.")
EVENT
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import app.coronawarn.verification.domain.VerificationAppSession;
import app.coronawarn.verification.model.AppSessionSourceOfTrust;
import app.coronawarn.verification.model.RegistrationToken;
import app.coronawarn.verification.model.TeleTanType;
import app.coronawarn.verification.repository.VerificationAppSessionRepository;
import java.time.LocalDateTime;
import java.util.Optional;
Expand Down Expand Up @@ -116,7 +117,8 @@ public ResponseEntity<RegistrationToken> generateRegistrationTokenByGuid(
* @param teleTan the TeleTan
* @return an {@link ResponseEntity}
*/
public ResponseEntity<RegistrationToken> generateRegistrationTokenByTeleTan(String teleTan, String fake) {
public ResponseEntity<RegistrationToken> generateRegistrationTokenByTeleTan(
String teleTan, String fake, TeleTanType teleTanType) {
if (checkRegistrationTokenAlreadyExistForTeleTan(teleTan)) {
log.warn("The registration token already exists for this TeleTAN.");
return ResponseEntity.badRequest().build();
Expand All @@ -126,6 +128,7 @@ public ResponseEntity<RegistrationToken> generateRegistrationTokenByTeleTan(Stri
VerificationAppSession appSession = generateAppSession(registrationToken);
appSession.setTeleTanHash(hashingService.hash(teleTan));
appSession.setSourceOfTrust(AppSessionSourceOfTrust.TELETAN);
appSession.setTeleTanType(teleTanType);
saveAppSession(appSession);
log.info("Returning the successfully created registration token.");
return ResponseEntity.status(HttpStatus.CREATED).body(
Expand Down
17 changes: 14 additions & 3 deletions src/main/java/app/coronawarn/verification/service/JwtService.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,16 +83,17 @@ public class JwtService {
* token is valid.
*
* @param authorizationToken The authorization token to validate
* @param mandatoryRoles list of roles which are required to pass
* @return <code>true</code>, if the token is valid, otherwise <code>false</code>
*/
public boolean isAuthorized(String authorizationToken) {
public boolean isAuthorized(String authorizationToken, List<AuthorizationRole> mandatoryRoles) {
// check if the JWT is enabled
if (!verificationApplicationConfig.getJwt().getEnabled()) {
return true;
}
if (null != authorizationToken && authorizationToken.startsWith(TOKEN_PREFIX)) {
String jwtToken = authorizationToken.substring(TOKEN_PREFIX.length());
return validateToken(jwtToken, getPublicKey());
return validateToken(jwtToken, getPublicKey(), mandatoryRoles);
}
return false;
}
Expand All @@ -102,13 +103,23 @@ public boolean isAuthorized(String authorizationToken) {
*
* @param token The authorization token to validate
* @param publicKey the key from the IAM server
* @param mandatoryRoles List of roles which are required to pass.
* @return <code>true</code>, if the token is valid, otherwise <code>false</code>
*/
public boolean validateToken(final String token, final PublicKey publicKey) {
public boolean validateToken(final String token, final PublicKey publicKey, List<AuthorizationRole> mandatoryRoles) {
log.debug("process validateToken() by - token: {} PK: {}", token, publicKey);
if (null != publicKey) {
try {
List<String> roleNames = getRoles(token, publicKey);

// Return false if one of the mandatory roles are not present
for (AuthorizationRole mandatoryRole : mandatoryRoles) {
if (!roleNames.contains(mandatoryRole.getRoleName())) {
return false;
}
}

// Return true if at least one of the authorization roles are present
AuthorizationRole[] roles = AuthorizationRole.values();
for (AuthorizationRole role : roles) {
if (roleNames.contains(role.getRoleName())) {
Expand Down
Loading

0 comments on commit 8a1cb7c

Please sign in to comment.