Skip to content

Commit

Permalink
Merge pull request #513 from navikt/dev
Browse files Browse the repository at this point in the history
🚀 Deploy: Støtte tokenX OBO token i OppfolgingV2Controller
  • Loading branch information
tu55eladd authored Jan 17, 2023
2 parents f78a768 + cbec400 commit 2bc6aec
Show file tree
Hide file tree
Showing 18 changed files with 294 additions and 48 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/deploy-feature.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
distribution: 'temurin'
cache: 'maven'
- name: Build maven artifacts
run: mvn -B package
run: mvn -B package -DskipTests
- uses: docker/login-action@v2
with:
registry: ghcr.io
Expand Down
7 changes: 6 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

<properties>
<java.version>11</java.version>
<common.version>2.2022.11.16_08.36-35c94368bc44</common.version>
<common.version>2.2023.01.10_13.49-81ddc732df3a</common.version>
<tjenestespesifikasjoner.version>1.2019.09.25-00.21-49b69f0625e0</tjenestespesifikasjoner.version>
<testcontainers.version>1.16.3</testcontainers.version>
</properties>
Expand Down Expand Up @@ -188,6 +188,11 @@
<artifactId>auth</artifactId>
<version>${common.version}</version>
</dependency>
<dependency>
<groupId>no.nav.common</groupId>
<artifactId>audit-log</artifactId>
<version>${common.version}</version>
</dependency>
<dependency>
<groupId>no.nav.common</groupId>
<artifactId>token-client</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import no.nav.common.abac.audit.AuditLogFilterUtils;
import no.nav.common.abac.audit.SpringAuditRequestInfoSupplier;
import no.nav.common.abac.constants.NavAttributter;
import no.nav.common.audit_log.log.AuditLogger;
import no.nav.common.audit_log.log.AuditLoggerImpl;
import no.nav.common.auth.context.AuthContextHolder;
import no.nav.common.auth.context.AuthContextHolderThreadLocal;
import no.nav.common.cxf.StsConfig;
Expand Down Expand Up @@ -107,4 +109,9 @@ serviceUserCredentials.password, new SpringAuditRequestInfoSupplier(),
);
}

@Bean
AuditLogger auditLogger() {
return new AuditLoggerImpl();
}

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package no.nav.veilarboppfolging.controller;

import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import lombok.RequiredArgsConstructor;
import no.nav.common.auth.context.AuthContextHolder;
import no.nav.common.types.identer.AktorId;
Expand Down Expand Up @@ -37,16 +38,15 @@ public class KvpController {


@GetMapping("/{aktorId}/currentStatus")
@ApiOperation(
value = "Extract KVP status for an actor",
notes = "This API endpoint is only available for system users"
@Operation(
summary = "Extract KVP status for an actor. This API endpoint is only available for system users",
responses = {
@ApiResponse(responseCode = "200", description = "Actor is currently in a KVP period.", content = @Content(schema = @Schema(implementation = KvpDTO.class))),
@ApiResponse(responseCode = "204", description = "Actor is currently not in a KVP period."),
@ApiResponse(responseCode = "403", description = "The API endpoint is requested by a user which is not in the allowed users list."),
@ApiResponse(responseCode = "500", description = "There is a server-side bug which should be fixed.")
}
)
@ApiResponses({
@ApiResponse(code = 200, message = "Actor is currently in a KVP period.", response = KvpDTO.class),
@ApiResponse(code = 204, message = "Actor is currently not in a KVP period."),
@ApiResponse(code = 403, message = "The API endpoint is requested by a user which is not in the allowed users list."),
@ApiResponse(code = 500, message = "There is a server-side bug which should be fixed.")
})
public ResponseEntity<KvpDTO> getKvpStatus(@PathVariable("aktorId") AktorId aktorId) {
// KVP information is only available to certain system users. We trust these users here,
// so that we can avoid doing an ABAC query on each request.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package no.nav.veilarboppfolging.controller.v2;

import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import lombok.RequiredArgsConstructor;
import no.nav.common.auth.context.AuthContextHolder;
import no.nav.common.types.identer.AktorId;
Expand Down Expand Up @@ -34,10 +36,10 @@ public class KvpV2Controller {
private final AuthContextHolder authContextHolder;

@GetMapping
@ApiResponses({
@ApiResponse(code = 200, message = "Actor is currently in a KVP period.", response = KvpDTO.class),
@ApiResponse(code = 204, message = "Actor is currently not in a KVP period."),
@ApiResponse(code = 403, message = "The API endpoint is requested by a user which is not in the allowed users list.")
@Operation(responses = {
@ApiResponse(responseCode = "200", description = "Actor is currently in a KVP period.", content = @Content(schema = @Schema(implementation = KvpDTO.class))),
@ApiResponse(responseCode = "204", description = "Actor is currently not in a KVP period."),
@ApiResponse(responseCode = "403", description = "The API endpoint is requested by a user which is not in the allowed users list.")
})
public ResponseEntity<KvpDTO> getKvpStatus(@RequestParam("aktorId") AktorId aktorId) {
// KVP information is only available to certain system users. We trust these users here,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import no.nav.veilarboppfolging.service.AuthService;
import no.nav.veilarboppfolging.service.OppfolgingService;
import no.nav.veilarboppfolging.utils.DtoMappers;
import no.nav.veilarboppfolging.utils.auth.AuthorizeAktorId;
import no.nav.veilarboppfolging.utils.auth.AuthorizeFnr;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
Expand All @@ -27,17 +29,13 @@
@RequestMapping("/api/v2/oppfolging")
@RequiredArgsConstructor
public class OppfolgingV2Controller {
private final static List<String> ALLOWLIST = List.of("veilarbvedtaksstotte", "veilarbdialog", "veilarbaktivitet");
private final OppfolgingService oppfolgingService;

private final AuthService authService;

@AuthorizeFnr(allowlist = {"veilarbvedtaksstotte", "veilarbdialog", "veilarbaktivitet", "veilarbregistrering"})
@GetMapping(params = "fnr")
public UnderOppfolgingV2Response underOppfolging(@RequestParam(value = "fnr") Fnr fnr) {
boolean harTilgangSomAADSystembruker = authService.erSystemBrukerFraAzureAd();
if (!harTilgangSomAADSystembruker) {
authService.sjekkLesetilgangMedFnr(fnr);
}
return new UnderOppfolgingV2Response(oppfolgingService.erUnderOppfolging(fnr));
}

Expand All @@ -52,25 +50,21 @@ public UnderOppfolgingV2Response underOppfolging() {
if (!authService.erEksternBruker()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Som internbruker/systembruker er aktorId eller fnr påkrevd");
}

Fnr fnr = Fnr.of(authService.getInnloggetBrukerIdent());

return new UnderOppfolgingV2Response(oppfolgingService.erUnderOppfolging(fnr));
}

@PostMapping("/start")
public ResponseEntity<?> startOppfolging(@RequestParam("fnr") Fnr fnr) {
authService.skalVereInternBruker();
oppfolgingService.startOppfolging(fnr);

return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}

@PostMapping("/avslutt")
public ResponseEntity<?> avsluttOppfolging(@RequestBody AvsluttOppfolgingV2Request request) {
authService.skalVereInternBruker();
oppfolgingService.avsluttOppfolging(request.getFnr(), request.getVeilederId().get(), request.getBegrunnelse());

return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}

Expand All @@ -95,14 +89,9 @@ public OppfolgingPeriodeMinimalDTO hentOppfolgingsPeriode(@PathVariable("uuid")
return tilOppfolgingPeriodeMinimalDTO(periode);
}

@AuthorizeFnr(allowlist = {"veilarbvedtaksstotte", "veilarbdialog", "veilarbaktivitet"})
@GetMapping("/periode/gjeldende")
public ResponseEntity<OppfolgingPeriodeMinimalDTO> hentGjeldendePeriode(@RequestParam("fnr") Fnr fnr) {
if (authService.erSystemBrukerFraAzureAd()) {
authService.sjekkAtApplikasjonErIAllowList(ALLOWLIST);
} else {
authService.sjekkLesetilgangMedFnr(fnr);
}

return oppfolgingService.hentGjeldendeOppfolgingsperiode(fnr)
.map(DtoMappers::tilOppfolgingPeriodeMinimalDTO)
.map(op -> new ResponseEntity<>(op, HttpStatus.OK))
Expand All @@ -115,13 +104,9 @@ public List<OppfolgingPeriodeDTO> hentOppfolgingsperioder(@RequestParam("fnr") F
return hentOppfolgingsperioder(aktorId);
}

@AuthorizeAktorId(allowlist = {"veilarbvedtaksstotte", "veilarbdialog", "veilarbaktivitet"})
@GetMapping(value = "/perioder", params = "aktorId")
public List<OppfolgingPeriodeDTO> hentOppfolgingsperioder(@RequestParam("aktorId") AktorId aktorId) {
if (authService.erSystemBrukerFraAzureAd()) {
authService.sjekkAtApplikasjonErIAllowList(ALLOWLIST);
} else {
authService.sjekkLesetilgangMedAktorId(aktorId);
}
return oppfolgingService.hentOppfolgingsperioder(aktorId)
.stream()
.map(this::filtrerKvpPerioder)
Expand Down
38 changes: 34 additions & 4 deletions src/main/java/no/nav/veilarboppfolging/service/AuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
import no.nav.common.abac.domain.Attribute;
import no.nav.common.abac.domain.request.*;
import no.nav.common.abac.domain.response.XacmlResponse;
import no.nav.common.audit_log.cef.CefMessage;
import no.nav.common.audit_log.cef.CefMessageEvent;
import no.nav.common.audit_log.cef.CefMessageSeverity;
import no.nav.common.audit_log.log.AuditLogger;
import no.nav.common.audit_log.log.AuditLoggerImpl;
import no.nav.common.auth.context.AuthContextHolder;
import no.nav.common.auth.context.UserRole;
import no.nav.common.auth.utils.IdentUtils;
Expand Down Expand Up @@ -48,6 +53,7 @@
public class AuthService {

private final AuthContextHolder authContextHolder;
private final AuditLogger auditLogger;

private final Pep veilarbPep;

Expand All @@ -60,13 +66,14 @@ public class AuthService {
private final EnvironmentProperties environmentProperties;

@Autowired
public AuthService(AuthContextHolder authContextHolder, Pep veilarbPep, AktorOppslagClient aktorOppslagClient, Credentials serviceUserCredentials, AzureAdOnBehalfOfTokenClient aadOboTokenClient, EnvironmentProperties environmentProperties) {
public AuthService(AuthContextHolder authContextHolder, Pep veilarbPep, AktorOppslagClient aktorOppslagClient, Credentials serviceUserCredentials, AzureAdOnBehalfOfTokenClient aadOboTokenClient, EnvironmentProperties environmentProperties, AuditLogger auditLogger) {
this.authContextHolder = authContextHolder;
this.veilarbPep = veilarbPep;
this.aktorOppslagClient = aktorOppslagClient;
this.serviceUserCredentials = serviceUserCredentials;
this.aadOboTokenClient = aadOboTokenClient;
this.environmentProperties = environmentProperties;
this.auditLogger = auditLogger;
}

public void skalVereEnAv(List<UserRole> roller) {
Expand All @@ -90,7 +97,20 @@ public void skalVereInternEllerSystemBruker() {
}

public boolean harEksternBrukerTilgang(Fnr fnr) {
return getInnloggetBrukerIdent().equals(fnr.get());
// Når man ikke bruker Pep så må man gjøre auditlogging selv
var subjectUser = getInnloggetBrukerIdent();
var isAllowed = subjectUser.equals(fnr.get());
auditLogger.log(CefMessage.builder()
.timeEnded(System.currentTimeMillis())
.applicationName("veilarboppfolging")
.sourceUserId(subjectUser)
.event(CefMessageEvent.ACCESS)
.severity(CefMessageSeverity.INFO)
.name("veilarboppfolging-audit-log")
.destinationUserId(fnr.get())
.extension("msg", isAllowed ? "Ekstern bruker har gjort oppslag på seg selv" : "Ekstern bruker ble nektet innsyn")
.build());
return isAllowed;
}

public void skalVereSystemBruker() {
Expand Down Expand Up @@ -151,7 +171,13 @@ public boolean harVeilederSkriveTilgangTilFnr(String veilederId, Fnr fnr) {
}

public void sjekkLesetilgangMedFnr(Fnr fnr) {
sjekkLesetilgangMedAktorId(getAktorIdOrThrow(fnr));
if (erEksternBruker()) {
if (!harEksternBrukerTilgang(fnr)) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Ekstern bruker har ikke tilgang på andre brukere enn seg selv");
}
} else {
sjekkLesetilgangMedAktorId(getAktorIdOrThrow(fnr));
}
}

public void sjekkLesetilgangMedAktorId(AktorId aktorId) {
Expand Down Expand Up @@ -306,6 +332,10 @@ private Optional<NavIdent> getNavIdentClaimHvisTilgjengelig() {
return empty();
}


public void sjekkAtApplikasjonErIAllowList(String[] allowlist) {
sjekkAtApplikasjonErIAllowList(List.of(allowlist));
}
@SneakyThrows
public void sjekkAtApplikasjonErIAllowList(List<String> allowlist) {
String appname = hentApplikasjonFraContext();
Expand Down Expand Up @@ -335,7 +365,7 @@ private Optional<String> getFullAppName() { // "cluster:team:app"
} else if (isTokenX(maybeClaims)) {
return maybeClaims.flatMap(claims -> getStringClaimOrEmpty(claims, "client_id"));
} else {
return Optional.empty();
return authContextHolder.getSubject();
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package no.nav.veilarboppfolging.utils.auth;

import lombok.RequiredArgsConstructor;
import no.nav.common.types.identer.AktorId;
import no.nav.common.types.identer.Fnr;
import no.nav.veilarboppfolging.service.AuthService;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;

import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

@RequiredArgsConstructor
public class AuthorizationAnnotationHandler {

private final AuthService authService;

private static final List<Class<? extends Annotation>> SUPPORTED_ANNOTATIONS = List.of(
AuthorizeFnr.class,
AuthorizeAktorId.class
);

private void authorizeRequest(Annotation annotation, HttpServletRequest request) {
var idToken = authService.getInnloggetBrukerToken();
if (idToken == null || idToken.isEmpty()) {
throw new UnauthorizedException("Missing token");
}
if (annotation instanceof AuthorizeFnr) {
var fnr = Fnr.of(getFnr(request));
var allowlist = ((AuthorizeFnr) annotation).allowlist();
authorizeFnr(fnr, allowlist);
} else if (annotation instanceof AuthorizeAktorId) {
var allowlist = ((AuthorizeAktorId) annotation).allowlist();
var aktorId = AktorId.of(getAktorId(request));
authorizeAktorId(aktorId, allowlist);
}
}

private void authorizeFnr(Fnr fnr, String[] allowlist) {
if (authService.erSystemBrukerFraAzureAd()) {
authService.sjekkAtApplikasjonErIAllowList(allowlist);
} else {
authService.sjekkLesetilgangMedFnr(fnr);
}
}

private void authorizeAktorId(AktorId aktorId, String[] allowlist) {
if (authService.erInternBruker()) {
authService.sjekkLesetilgangMedAktorId(aktorId);
} else if (authService.erSystemBrukerFraAzureAd()) {
authService.sjekkAtApplikasjonErIAllowList(allowlist);
} else if (authService.erEksternBruker()) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Eksternbruker ikke tillatt");
}
}

public void doAuthorizationCheckIfTagged(Method handlerMethod, HttpServletRequest request) {
Optional.ofNullable(getAnnotation(handlerMethod, SUPPORTED_ANNOTATIONS))
// Skip if not tagged
.ifPresent((Annotation annotation) -> {
authorizeRequest(annotation, request);
});
}

protected Annotation getAnnotation(Method method, List<Class<? extends Annotation>> types) {
return Optional.ofNullable(findAnnotation(types, method.getAnnotations()))
.orElseGet(() -> findAnnotation(types, method.getDeclaringClass().getAnnotations()));
}

private static Annotation findAnnotation(List<Class<? extends Annotation>> types, Annotation... annotations) {
return Arrays.stream(annotations)
.filter(a -> types.contains(a.annotationType()))
.findFirst()
.orElse(null);
}
private String getFnr(HttpServletRequest request) {
/* Get fnr from headers instead of query when supported by clients */
return request.getParameter("fnr");
}
private String getAktorId(HttpServletRequest request) {
return request.getParameter("aktorId");
}

}
Loading

0 comments on commit 2bc6aec

Please sign in to comment.