diff --git a/dev-tools/kind/config/structures-server/values.yaml b/dev-tools/kind/config/structures-server/values.yaml index 4de3cb57..a2cb9dbd 100644 --- a/dev-tools/kind/config/structures-server/values.yaml +++ b/dev-tools/kind/config/structures-server/values.yaml @@ -33,7 +33,7 @@ image: # Repository matches bootBuildImage output repository: mindsignited/structures-server # Tag from gradle.properties version (structuresVersion=3.5.3-SNAPSHOT) - tag: 3.5.4 + tag: 3.5.5 # Never pull - use images loaded into KinD cluster pullPolicy: Never sha: "" @@ -46,7 +46,7 @@ migration: activeDeadlineSeconds: 300 image: repository: mindsignited/structures-migration - tag: 3.5.4 + tag: 3.5.5 # Never pull - use images loaded into KinD cluster pullPolicy: Never sha: "" diff --git a/docker-compose/compose.yml b/docker-compose/compose.yml index 9ea1efad..09d203e5 100644 --- a/docker-compose/compose.yml +++ b/docker-compose/compose.yml @@ -6,7 +6,7 @@ services: structures-migration: container_name: structures-migration pull_policy: always - image: mindsignited/structures-migration:${structuresVersion:-3.5.4} + image: mindsignited/structures-migration:${structuresVersion:-3.5.5} depends_on: structures-elasticsearch: condition: service_healthy @@ -17,7 +17,7 @@ services: structures-server: container_name: structures-server pull_policy: always - image: mindsignited/structures-server:${structuresVersion:-3.5.4} + image: mindsignited/structures-server:${structuresVersion:-3.5.5} depends_on: structures-elasticsearch: condition: service_healthy diff --git a/gradle.properties b/gradle.properties index f512db58..9334be34 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -structuresVersion=3.5.4 +structuresVersion=3.5.5 allureVersion=2.32.0 antlrVersion=4.13.1 diff --git a/helm/structures/values.yaml b/helm/structures/values.yaml index 09912632..93c24426 100644 --- a/helm/structures/values.yaml +++ b/helm/structures/values.yaml @@ -4,7 +4,7 @@ nameOverride: "" image: repository: mindsignited/structures-server pullPolicy: Always - tag: "3.5.4" + tag: "3.5.5" sha: "" # Migration configuration @@ -15,7 +15,7 @@ migration: activeDeadlineSeconds: 300 image: repository: mindsignited/structures-migration - tag: "3.5.4" + tag: "3.5.5" pullPolicy: Always sha: "" diff --git a/structures-auth/src/main/java/org/kinotic/structures/auth/api/domain/OidcProvider.java b/structures-auth/src/main/java/org/kinotic/structures/auth/api/domain/OidcProvider.java index 43388f21..6c273e40 100644 --- a/structures-auth/src/main/java/org/kinotic/structures/auth/api/domain/OidcProvider.java +++ b/structures-auth/src/main/java/org/kinotic/structures/auth/api/domain/OidcProvider.java @@ -63,6 +63,15 @@ public class OidcProvider { */ private List domains; + /** + * If true, this provider matches on issuer alone and skips the email-domain check. + * Required for M2M tokens (no email claim, sub is a client id) and useful when the + * IDP (e.g. Okta) issues tokens for users from arbitrary email domains. + * A provider whose {@link #domains} explicitly matches a token's email domain is + * preferred over an allowAnyDomain provider with the same issuer. + */ + private boolean allowAnyDomain; + /** * The audience this service will expect for this OIDC provider. */ diff --git a/structures-auth/src/main/java/org/kinotic/structures/auth/internal/services/OidcSecurityService.java b/structures-auth/src/main/java/org/kinotic/structures/auth/internal/services/OidcSecurityService.java index 418682b7..8f54a863 100644 --- a/structures-auth/src/main/java/org/kinotic/structures/auth/internal/services/OidcSecurityService.java +++ b/structures-auth/src/main/java/org/kinotic/structures/auth/internal/services/OidcSecurityService.java @@ -28,7 +28,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; @Slf4j @@ -90,28 +89,25 @@ private CompletableFuture validateTokenWithKey(String token, Jwk validateTokenWithKey(String token, Jwk issuer.equals(provider.getAuthority()) && provider.getDomains().contains(email.split("@")[1])) - .findFirst() - .orElse(null); + List candidates = properties.getOidcProviders().stream() + .filter(p -> issuer.equals(p.getAuthority())) + .filter(OidcProvider::isEnabled) + .toList(); - if (oidcProvider == null) { - log.warn("No allowed Oidc Providers configured for issuer: {}", issuer); + if (candidates.isEmpty()) { + log.warn("No enabled Oidc Providers configured for issuer: {}", issuer); return null; } - if(!oidcProvider.isEnabled()) { - log.warn("Oidc Provider found but is not enabled: {}", issuer); - return null; + String emailDomain = email != null && email.contains("@") ? email.split("@")[1] : null; + + if (emailDomain != null) { + OidcProvider domainMatch = candidates.stream() + .filter(p -> p.getDomains() != null && p.getDomains().contains(emailDomain)) + .findFirst() + .orElse(null); + if (domainMatch != null) { + return domainMatch; + } } - return oidcProvider; + OidcProvider anyDomainMatch = candidates.stream() + .filter(OidcProvider::isAllowAnyDomain) + .findFirst() + .orElse(null); + if (anyDomainMatch != null) { + return anyDomainMatch; + } + + if (emailDomain != null) { + log.warn("Issuer {} matched but no provider allows domain {} and no allowAnyDomain provider configured", + issuer, emailDomain); + } else { + log.warn("Issuer {} matched but token has no email and no allowAnyDomain provider configured", issuer); + } + return null; } private boolean isValidAudience(OidcProvider oidcProvider, Set audiences) { @@ -224,15 +249,14 @@ private Participant createParticipantFromClaims(OidcProvider oidcProvider, Claim // and can add other users as admins/users. If that is how we manage that, how do we handle the enterprise // cases, automatically configure those users as we validate the tokens? - // Create metadata - HashMap metadata = new HashMap<>(Map.of( - ParticipantConstants.PARTICIPANT_TYPE_METADATA_KEY, - ParticipantConstants.PARTICIPANT_TYPE_USER, - "email", email != null ? email : "", - "name", name != null ? name : (preferredUsername != null ? preferredUsername : subject), - "iss", claims.getIssuer(), - "aud", claims.getAudience().stream().collect(Collectors.joining(", ")) - )); + HashMap metadata = new HashMap<>(); + metadata.put(ParticipantConstants.PARTICIPANT_TYPE_METADATA_KEY, ParticipantConstants.PARTICIPANT_TYPE_USER); + metadata.put("name", name != null ? name : (preferredUsername != null ? preferredUsername : subject)); + metadata.put("iss", claims.getIssuer()); + metadata.put("aud", String.join(", ", claims.getAudience())); + if (email != null) { + metadata.put("email", email); + } if(oidcProvider.getMetadata() != null && !oidcProvider.getMetadata().isEmpty()) { metadata.putAll(oidcProvider.getMetadata());