Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Støtte for azure-grupper og gjenkjenning av disse #1264

Merged
merged 7 commits into from
Mar 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;

import org.jose4j.jwt.MalformedClaimException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import no.nav.vedtak.exception.TekniskException;
import no.nav.vedtak.log.mdc.MDCOperations;
import no.nav.vedtak.sikkerhet.abac.BeskyttetRessurs;
import no.nav.vedtak.sikkerhet.abac.ÅpenRessurs;
Expand All @@ -28,6 +28,19 @@
import no.nav.vedtak.sikkerhet.oidc.validator.OidcTokenValidator;
import no.nav.vedtak.sikkerhet.oidc.validator.OidcTokenValidatorConfig;
import no.nav.vedtak.sikkerhet.oidc.validator.OidcTokenValidatorResult;
import org.jose4j.jwt.MalformedClaimException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import java.lang.reflect.Method;
import java.time.Instant;
import java.util.Optional;

/**
* Bruksanvisning inntil alle er over og det evt samles her:
Expand Down Expand Up @@ -67,10 +80,12 @@ public static void validerSettKontekst(ResourceInfo resourceInfo, ContainerReque
} else if (beskyttetRessurs == null) {
throw new WebApplicationException(metodenavn + " mangler annotering", Response.Status.INTERNAL_SERVER_ERROR);
} else {
var tokenString = getTokenFromHeader(ctx).or(() -> getCookie(ctx, cookiePath)).orElseThrow(() -> new TokenFeil("Mangler token"));
var tokenString = getTokenFromHeader(ctx)
.or(() -> getCookie(ctx, cookiePath))
.orElseThrow(() -> new TokenFeil("Mangler token"));
validerToken(tokenString);
}
} catch (MalformedClaimException | TokenFeil e) {
} catch (TekniskException | TokenFeil e) {
throw new WebApplicationException(e, Response.Status.FORBIDDEN);
} catch (WebApplicationException e) {
throw e;
Expand All @@ -91,40 +106,39 @@ private static void setCallAndConsumerId(ContainerRequestContext request) {
.orElseGet(MDCOperations::generateCallId);
MDCOperations.putCallId(callId);

Optional.ofNullable(request.getHeaderString(MDCOperations.HTTP_HEADER_CONSUMER_ID)).ifPresent(MDCOperations::putConsumerId);
Optional.ofNullable(request.getHeaderString(MDCOperations.HTTP_HEADER_CONSUMER_ID))
.ifPresent(MDCOperations::putConsumerId);
}

private static Optional<TokenString> getTokenFromHeader(ContainerRequestContext request) {
String headerValue = request.getHeaderString(AUTHORIZATION_HEADER);
return headerValue != null && headerValue.startsWith(OpenIDToken.OIDC_DEFAULT_TOKEN_TYPE) ? Optional.of(
new TokenString(headerValue.substring(OpenIDToken.OIDC_DEFAULT_TOKEN_TYPE.length()))) : Optional.empty();
return headerValue != null && headerValue.startsWith(OpenIDToken.OIDC_DEFAULT_TOKEN_TYPE)
? Optional.of(new TokenString(headerValue.substring(OpenIDToken.OIDC_DEFAULT_TOKEN_TYPE.length())))
: Optional.empty();
}

private static Optional<TokenString> getCookie(ContainerRequestContext request, String cookiePath) {
if (cookiePath == null || request.getCookies() == null) {
return Optional.empty();
}
return request.getCookies()
.values()
.stream()
return request.getCookies().values().stream()
.filter(c -> c.getValue() != null)
.filter(c -> ID_TOKEN_COOKIE_NAME.equalsIgnoreCase(c.getName()))
.filter(c -> cookiePath.equalsIgnoreCase(c.getPath()))
.findFirst()
.or(() -> request.getCookies()
.values()
.stream()
.or(() -> request.getCookies().values().stream()
.filter(c -> c.getValue() != null)
.filter(c -> ID_TOKEN_COOKIE_NAME.equalsIgnoreCase(c.getName()))
.findFirst())
.map(Cookie::getValue)
.map(TokenString::new);
}

public static void validerToken(TokenString tokenString) throws MalformedClaimException {
public static void validerToken(TokenString tokenString) {
// Sett opp OpenIDToken
var claims = JwtUtil.getClaims(tokenString.token());
var configuration = ConfigProvider.getOpenIDConfiguration(claims.getIssuer()).orElseThrow(() -> new TokenFeil("Token mangler issuer claim"));
var configuration = ConfigProvider.getOpenIDConfiguration(JwtUtil.getIssuer(claims))
.orElseThrow(() -> new TokenFeil("Token mangler issuer claim"));
var expiresAt = Optional.ofNullable(JwtUtil.getExpirationTime(claims)).orElseGet(() -> Instant.now().plusSeconds(300));
var token = new OpenIDToken(configuration.type(), OpenIDToken.OIDC_DEFAULT_TOKEN_TYPE, tokenString, null, expiresAt.toEpochMilli());

Expand All @@ -137,8 +151,8 @@ public static void validerToken(TokenString tokenString) throws MalformedClaimEx
throw new ValideringsFeil("Token expired");
}
if (validateResult.isValid()) {
KontekstHolder.setKontekst(
RequestKontekst.forRequest(validateResult.subject(), validateResult.compactSubject(), validateResult.identType(), token));
KontekstHolder.setKontekst(RequestKontekst.forRequest(validateResult.subject(), validateResult.compactSubject(),
validateResult.identType(), token, validateResult.getGrupper()));
LOG.trace("token validert");
} else {
throw new ValideringsFeil("Ugyldig token");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package no.nav.vedtak.sikkerhet.kontekst;

public enum Groups {
SAKSBEHANDLER,
BESLUTTER,
OVERSTYRER,
OPPGAVESTYRER,
VEILEDER,
DRIFT
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package no.nav.vedtak.sikkerhet.kontekst;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import no.nav.foreldrepenger.konfig.Environment;

public class GroupsProvider {

private static final Logger LOG = LoggerFactory.getLogger(GroupsProvider.class);
private static final String SUFFIX = ".properties";
private static final String PREFIX = "groups";

private static GroupsProvider INSTANCE;

private final Properties properties;
private final Map<String, Groups> reverse;


private GroupsProvider() {
this(init());
}

private GroupsProvider(Properties properties) {
this.properties = properties;
this.reverse = new HashMap<>();
properties.forEach((k,v) -> reverse.put((String) v, Groups.valueOf(((String) k).toUpperCase())));
}

public static synchronized GroupsProvider instance() {
var inst = INSTANCE;
if (inst == null) {
inst = new GroupsProvider();
INSTANCE = inst;
}
return INSTANCE;
}

public String getGroupValue(Groups group) {
return (String) properties.get(group.name().toLowerCase());
}

public Groups getGroupFrom(String value) {
return Optional.ofNullable(value).map(reverse::get).orElse(null);
}

public Set<Groups> getGroupsFrom(List<String> values) {
return values.stream().map(reverse::get).filter(Objects::nonNull).collect(Collectors.toSet());
}

private static Properties init() {
var c = new Properties();
lesFra(getInfix(), new Properties())
.forEach((k, v) -> c.put(k.toString().toLowerCase(), v.toString()));
return c;
}

private static Properties lesFra(String infix, Properties p) {
if (infix == null) {
return p;
}
String navn = GroupsProvider.class.getSimpleName().toLowerCase() + infix + SUFFIX;
try (var is = GroupsProvider.class.getResourceAsStream(navn)) {
if (is != null) {
LOG.info("Laster groups fra {}", navn);
p.load(is);
return p;
}
} catch (IOException e) {
LOG.info("Propertyfil {} ikke lesbar", navn);
}
LOG.info("Propertyfil {} ikke funnet", navn);
return p;
}

private static String getInfix() {
var cluster = Environment.current().getCluster();
if (cluster.isLocal()) {
return "-vtp";
}
return "-" + (cluster.isProd() ? "prod" : "dev");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
saksbehandler=27e77109-fef2-48ce-a174-269074490353
beslutter=6e31f9db-7e46-409d-809c-143d3863e4f6
overstyrer=542269ee-090b-4017-bbcc-6791580290ac
oppgavestyrer=e6508a2a-2e74-450e-ad24-eb1b2b4625c6
veileder=8cddda87-0a22-4d35-9186-a2c32a6ab450
drift=f1b82579-c5b5-4617-9673-8ace5ff67f63
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
saksbehandler=73107205-17ec-4a07-a56e-e0a8542f90c9
beslutter=237ec744-da1f-455b-a794-b2d59c7ce7ee
overstyrer=8200240e-d76d-4d48-a655-728d1d8c3641
oppgavestyrer=1a59da27-4c55-4a9d-8480-6abd1a856cd2
veileder=77f05833-ebfd-45fb-8be7-88eca8e7418f
drift=0d226374-4748-4367-a38a-062dcad70034
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
saksbehandler=saksbehandler
beslutter=beslutter
overstyrer=overstyrer
oppgavestyrer=oppgavestyrer
veileder=veileder
drift=drift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package no.nav.vedtak.sikkerhet.kontekst;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.List;
import java.util.Set;

import org.junit.jupiter.api.Test;

class GroupsProviderTest {

@Test
void testGroupsForLocal() {
var provider = GroupsProvider.instance();
assertThat(provider.getGroupValue(Groups.BESLUTTER)).isEqualTo("beslutter");
assertThat(provider.getGroupFrom("drift")).isEqualTo(Groups.DRIFT);
var grupper = List.of("saksbehandler", "overstyrer");
assertThat(provider.getGroupsFrom(grupper)).containsAll(Set.of(Groups.SAKSBEHANDLER, Groups.OVERSTYRER));

assertThat(provider.getGroupFrom(null)).isNull();
assertThat(provider.getGroupFrom("forsvarer")).isNull();
assertThat(provider.getGroupsFrom(List.of("forsvarer"))).isEmpty();
}


}
Original file line number Diff line number Diff line change
@@ -1,34 +1,37 @@
package no.nav.vedtak.sikkerhet.kontekst;

import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

import no.nav.vedtak.log.mdc.MDCOperations;
import no.nav.vedtak.sikkerhet.oidc.token.OpenIDToken;

public final class RequestKontekst extends BasisKontekst {

private final OpenIDToken token;
private final Set<Groups> grupper;

private RequestKontekst(String uid, String kompaktUid, IdentType identType, String consumerId, OpenIDToken token) {
private RequestKontekst(String uid, String kompaktUid, IdentType identType, String consumerId, OpenIDToken token, Set<Groups> grupper) {
super(SikkerhetContext.REQUEST, uid, kompaktUid, identType, consumerId);
this.token = token;
this.grupper = new HashSet<>(grupper);
}

public static RequestKontekst forRequest(String uid, IdentType identType, String consumerId, OpenIDToken token) {
return new RequestKontekst(uid, uid, identType, consumerId, token);
public static RequestKontekst forRequest(String uid, String kompaktUid, IdentType identType, OpenIDToken token, Set<Groups> grupper) {
var konsumentId = Optional.ofNullable(MDCOperations.getConsumerId()).orElse(uid);
return new RequestKontekst(uid, kompaktUid, identType, konsumentId, token, grupper);
}

public static RequestKontekst forRequest(String uid, IdentType identType, OpenIDToken token) {
var konsumentId = Optional.ofNullable(MDCOperations.getConsumerId()).orElse(uid);
return new RequestKontekst(uid, uid, identType, konsumentId, token);
public OpenIDToken getToken() {
return token;
}

public static RequestKontekst forRequest(String uid, String kompaktUid, IdentType identType, OpenIDToken token) {
var konsumentId = Optional.ofNullable(MDCOperations.getConsumerId()).orElse(uid);
return new RequestKontekst(uid, kompaktUid, identType, konsumentId, token);
public Set<Groups> getGrupper() {
return grupper;
}

public OpenIDToken getToken() {
return token;
public boolean harGruppe(Groups gruppe) {
return grupper.contains(gruppe);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ public enum AzureProperty {

public static final String NAV_IDENT = "NAVident";
public static final String AZP_NAME = "azp_name";
public static final String GRUPPER = "groups";

}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,22 @@ public static String getIssuer(JwtClaims claims) {
}
}

public static String getSubject(JwtClaims claims) {
try {
return claims.getSubject();
} catch (MalformedClaimException e) {
throw ugyldigJwt(e);
}
}

public static List<String> getAudience(JwtClaims claims) {
try {
return claims.getAudience();
} catch (MalformedClaimException e) {
throw ugyldigJwt(e);
}
}

public static Instant getExpirationTime(JwtClaims claims) {
try {
long expirationTime = claims.getExpirationTime().getValue();
Expand All @@ -58,6 +74,14 @@ public static Instant getExpirationTime(JwtClaims claims) {
}
}

public static long getExpirationTimeRaw(JwtClaims claims) {
try {
return claims.getExpirationTime().getValue();
} catch (MalformedClaimException e) {
throw ugyldigJwt(e);
}
}

public static String getClientName(JwtClaims claims) {
try {
String azp = claims.getStringClaimValue("azp");
Expand All @@ -75,6 +99,22 @@ public static String getClientName(JwtClaims claims) {

}

public static String getStringClaim(JwtClaims claims, String key) {
try {
return claims.getStringClaimValue(key);
} catch (MalformedClaimException e) {
throw ugyldigJwt(e);
}
}

public static List<String> getStringListClaim(JwtClaims claims, String key) {
try {
return claims.getStringListClaimValue(key);
} catch (MalformedClaimException e) {
throw ugyldigJwt(e);
}
}

private static TekniskException ugyldigJwt(Exception e) {
return new TekniskException("F-026968", "Feil ved parsing av JWT", e);
}
Expand Down
Loading