feat: 🎸Claim sources for extracting AuthenticationContextClassRef and AuthnInstant

pull/1580/head
Dominik Frantisek Bucik 2022-04-14 11:47:51 +02:00 committed by Dominik František Bučík
parent d95cd86b4f
commit d9d3034e55
No known key found for this signature in database
GPG Key ID: 73F752BEC0709845
7 changed files with 194 additions and 7 deletions

View File

@ -16,5 +16,6 @@ public class AuthenticationStatement {
private List<String> authenticatingAuthorities;
private String authnContextClassRef;
private String authnInstant;
}

View File

@ -33,6 +33,7 @@ public class SamlAuthenticationDetails {
public static final String REMOTE_ENTITY_ID = "remoteEntityId";
public static final String ATTRIBUTES = "attributes";
public static final String AUTHN_STATEMENTS = "authnStatements";
public static final String AUTHN_INSTANT = "authnInstant";
public static final String AUTHN_CONTEXT_CLASS_REF = "authnContextClassRef";
public static final String AUTHENTICATING_AUTHORITIES = "authenticatingAuthorities";
@ -73,7 +74,8 @@ public class SamlAuthenticationDetails {
{
authnContextClassRef = as.getAuthnContext().getAuthnContextClassRef().getAuthnContextClassRef();
}
authenticationStatements.add(new AuthenticationStatement(authenticatingAuthorities, authnContextClassRef));
String authnInstant = as.getAuthnInstant() != null ? as.getAuthnInstant().toString() : "";
authenticationStatements.add(new AuthenticationStatement(authenticatingAuthorities, authnContextClassRef, authnInstant));
}
}
return authenticationStatements;
@ -127,11 +129,10 @@ public class SamlAuthenticationDetails {
authorities.add(authority.getAsString());
}
String authnContextClassRef = getStringOrNull(obj.get(AUTHN_CONTEXT_CLASS_REF));
authnStatements.add(new AuthenticationStatement(authorities, authnContextClassRef));
String authnInstant = getStringOrNull(obj.get(AUTHN_INSTANT));
authnStatements.add(new AuthenticationStatement(authorities, authnContextClassRef, authnInstant));
}
details.setAuthnStatements(authnStatements);
return details;
}
@ -155,9 +156,11 @@ public class SamlAuthenticationDetails {
}
object.add(ATTRIBUTES, attrs);
JsonArray authnStatements = new JsonArray();
for (AuthenticationStatement as: o.getAuthnStatements()) {
JsonObject asJson = new JsonObject();
addStringOrNull(asJson, AUTHN_CONTEXT_CLASS_REF, as.getAuthnContextClassRef());
addStringOrNull(asJson, AUTHN_INSTANT, as.getAuthnInstant());
JsonArray authorities = new JsonArray();
for (String authAuthority: as.getAuthenticatingAuthorities()) {
if (authAuthority == null) {
@ -165,7 +168,8 @@ public class SamlAuthenticationDetails {
}
authorities.add(authAuthority);
}
asJson.add(AUTHENTICATING_AUTHORITIES, asJson);
asJson.add(AUTHENTICATING_AUTHORITIES, authorities);
authnStatements.add(asJson);
}
object.add(AUTHN_STATEMENTS, authnStatements);
return object.toString();

View File

@ -16,7 +16,6 @@
package cz.muni.ics.oauth2.model;
import cz.muni.ics.oauth2.model.convert.JsonElementStringConverter;
import cz.muni.ics.oauth2.model.convert.SamlAuthenticationDetailsStringConverter;
import cz.muni.ics.oauth2.model.convert.SimpleGrantedAuthorityStringConverter;
import cz.muni.ics.oidc.saml.SamlPrincipal;

View File

@ -0,0 +1,61 @@
package cz.muni.ics.oidc.server.claims.sources;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import cz.muni.ics.oauth2.model.AuthenticationStatement;
import cz.muni.ics.oauth2.model.SamlAuthenticationDetails;
import cz.muni.ics.oidc.server.claims.ClaimSource;
import cz.muni.ics.oidc.server.claims.ClaimSourceInitContext;
import cz.muni.ics.oidc.server.claims.ClaimSourceProduceContext;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
/**
* Claim source which extracts the AuthenticationContextClassRef value from a SAML AuthN statement.
*
* @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
*/
@Slf4j
public class SamlAuthenticationContextClassRefClaimSource extends SamlAuthnStatementExtractorBaseClaimSource {
public static final String KEY_ACR = "acr";
public SamlAuthenticationContextClassRefClaimSource(ClaimSourceInitContext ctx) {
super(ctx);
log.debug("{} - initialized", getClaimName());
}
@Override
public Set<String> getAttrIdentifiers() {
return Collections.emptySet();
}
@Override
public JsonNode produceValue(ClaimSourceProduceContext pctx) {
JsonNode res = JsonNodeFactory.instance.nullNode();
if (!hasAuthnStatements(pctx)) {
return res;
}
List<AuthenticationStatement> statements = pctx.getSamlAuthenticationDetails().getAuthnStatements();
for (AuthenticationStatement s: statements) {
if (!isValidStatement(s)) {
continue;
}
res = JsonNodeFactory.instance.textNode(s.getAuthnContextClassRef());
break;
}
log.debug("{} - produced value '{}'", getClaimName(), res);
return res;
}
private boolean isValidStatement(AuthenticationStatement s) {
return s != null && StringUtils.hasText(s.getAuthnContextClassRef());
}
}

View File

@ -0,0 +1,59 @@
package cz.muni.ics.oidc.server.claims.sources;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import cz.muni.ics.oauth2.model.AuthenticationStatement;
import cz.muni.ics.oauth2.model.SamlAuthenticationDetails;
import cz.muni.ics.oidc.server.claims.ClaimSource;
import cz.muni.ics.oidc.server.claims.ClaimSourceInitContext;
import cz.muni.ics.oidc.server.claims.ClaimSourceProduceContext;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
/**
* Claim source which extracts the AuthN instant value from a SAML AuthN statement.
*
* @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
*/
@Slf4j
public class SamlAuthnInstantClaimSource extends SamlAuthnStatementExtractorBaseClaimSource {
public SamlAuthnInstantClaimSource(ClaimSourceInitContext ctx) {
super(ctx);
log.debug("{} - initialized", getClaimName());
}
@Override
public Set<String> getAttrIdentifiers() {
return Collections.emptySet();
}
@Override
public JsonNode produceValue(ClaimSourceProduceContext pctx) {
JsonNode res = JsonNodeFactory.instance.nullNode();
if (!hasAuthnStatements(pctx)) {
return res;
}
List<AuthenticationStatement> statements = pctx.getSamlAuthenticationDetails().getAuthnStatements();
for (AuthenticationStatement s: statements) {
if (!isValidStatement(s)) {
continue;
}
res = JsonNodeFactory.instance.textNode(s.getAuthnInstant());
break;
}
log.debug("{} - produced value '{}'", getClaimName(), res);
return res;
}
private boolean isValidStatement(AuthenticationStatement s) {
return s != null && StringUtils.hasText(s.getAuthnInstant());
}
}

View File

@ -0,0 +1,37 @@
package cz.muni.ics.oidc.server.claims.sources;
import cz.muni.ics.oauth2.model.AuthenticationStatement;
import cz.muni.ics.oauth2.model.SamlAuthenticationDetails;
import cz.muni.ics.oidc.server.claims.ClaimSource;
import cz.muni.ics.oidc.server.claims.ClaimSourceInitContext;
import cz.muni.ics.oidc.server.claims.ClaimSourceProduceContext;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
/**
* Base class for a claim source which extracts value from a SAML AuthN statement.
*
* @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
*/
@Slf4j
public abstract class SamlAuthnStatementExtractorBaseClaimSource extends ClaimSource {
public SamlAuthnStatementExtractorBaseClaimSource(ClaimSourceInitContext ctx) {
super(ctx);
}
protected boolean hasAuthnStatements(ClaimSourceProduceContext pctx) {
SamlAuthenticationDetails details = pctx.getSamlAuthenticationDetails();
if (details == null || details.getAttributes() == null || details.getAttributes().isEmpty()) {
return false;
}
List<AuthenticationStatement> statements = details.getAuthnStatements();
if (statements == null || statements.isEmpty()) {
return false;
}
return true;
}
}

View File

@ -8,6 +8,7 @@ import com.google.common.collect.Sets;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import cz.muni.ics.oauth2.model.SystemScope;
import cz.muni.ics.oauth2.service.SystemScopeService;
import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
@ -26,6 +27,7 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
@ -179,7 +181,14 @@ public class ControllerUtils {
JsonArray arr = userJson.getAsJsonArray(claim);
List<String> values = new ArrayList<>();
for (int i = 0; i < arr.size(); i++) {
JsonElement el = arr.get(i);
if (el instanceof JsonObject) {
values.add(transformObject((JsonObject) el));
} else if (el instanceof JsonPrimitive) {
values.add(arr.get(i).getAsString());
} else if (el instanceof JsonArray) {
values.add(transformArray((JsonArray) el));
}
}
claimValues.put(claim, values);
}
@ -204,6 +213,23 @@ public class ControllerUtils {
model.put(SCOPES, sortedScopes);
}
private static String transformObject(JsonObject obj) {
StringJoiner sj = new StringJoiner(", ");
for (String s: obj.keySet()) {
sj.add(s + ": " + obj.get(s).getAsString());
}
return sj.toString();
}
private static String transformArray(JsonArray arr) {
StringJoiner sj = new StringJoiner(", ");
for (int i = 0; i < arr.size(); i++) {
sj.add(arr.get(i).getAsString());
}
return sj.toString();
}
/**
* Create URL form base and parameters.