/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses 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 org.apache.cxf.sts.token.provider.jwt; import java.security.Principal; import java.time.Duration; import java.time.Instant; import java.time.ZonedDateTime; import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.UUID; import java.util.logging.Logger; import javax.security.auth.x500.X500Principal; import org.apache.cxf.common.logging.LogUtils; import org.apache.cxf.rs.security.jose.jwt.JwtClaims; import org.apache.cxf.sts.STSPropertiesMBean; import org.apache.cxf.sts.claims.ClaimsUtils; import org.apache.cxf.sts.claims.ProcessedClaim; import org.apache.cxf.sts.claims.ProcessedClaimCollection; import org.apache.cxf.sts.request.Lifetime; import org.apache.cxf.sts.request.Participants; import org.apache.cxf.sts.request.ReceivedToken; import org.apache.cxf.sts.request.ReceivedToken.STATE; import org.apache.cxf.sts.token.provider.TokenProviderParameters; import org.apache.cxf.sts.token.provider.TokenProviderUtils; import org.apache.cxf.ws.security.sts.provider.STSException; /** * A default implementation to create a JWTClaims object. The Subject name is the name * of the current principal. */ public class DefaultJWTClaimsProvider implements JWTClaimsProvider { public static final long DEFAULT_MAX_LIFETIME = 60L * 60L * 12L; private static final Logger LOG = LogUtils.getL7dLogger(DefaultJWTClaimsProvider.class); private boolean useX500CN; private long lifetime = 60L * 30L; private long maxLifetime = DEFAULT_MAX_LIFETIME; private boolean failLifetimeExceedance = true; private boolean acceptClientLifetime; private long futureTimeToLive = 60L; /** * Get a JwtClaims object. */ public JwtClaims getJwtClaims(JWTClaimsProviderParameters jwtClaimsProviderParameters) { JwtClaims claims = new JwtClaims(); claims.setSubject(getSubjectName(jwtClaimsProviderParameters)); claims.setTokenId(UUID.randomUUID().toString()); // Set the Issuer String issuer = jwtClaimsProviderParameters.getIssuer(); if (issuer == null) { STSPropertiesMBean stsProperties = jwtClaimsProviderParameters.getProviderParameters().getStsProperties(); claims.setIssuer(stsProperties.getIssuer()); } else { claims.setIssuer(issuer); } handleWSTrustClaims(jwtClaimsProviderParameters, claims); handleConditions(jwtClaimsProviderParameters, claims); handleAudienceRestriction(jwtClaimsProviderParameters, claims); handleActAs(jwtClaimsProviderParameters, claims); return claims; } protected String getSubjectName(JWTClaimsProviderParameters jwtClaimsProviderParameters) { Principal principal = getPrincipal(jwtClaimsProviderParameters); if (principal == null) { LOG.fine("Error in getting principal"); throw new STSException("Error in getting principal", STSException.REQUEST_FAILED); } String subjectName = principal.getName(); if (principal instanceof X500Principal) { // Just use the "cn" instead of the entire DN try { String principalName = principal.getName(); int index = principalName.indexOf('='); principalName = principalName.substring(index + 1, principalName.indexOf(',', index)); subjectName = principalName; } catch (Throwable ex) { subjectName = principal.getName(); //Ignore, not X500 compliant thus use the whole string as the value } } return subjectName; } /** * Get the Principal (which is used as the Subject). By default, we check the following (in order): * - A valid OnBehalfOf principal * - A valid principal associated with a token received as ValidateTarget * - The principal associated with the request. We don't need to check to see if it is "valid" here, as it * is not parsed by the STS (but rather the WS-Security layer). */ protected Principal getPrincipal(JWTClaimsProviderParameters jwtClaimsProviderParameters) { TokenProviderParameters providerParameters = jwtClaimsProviderParameters.getProviderParameters(); Principal principal = null; //TokenValidator in IssueOperation has validated the ReceivedToken //if validation was successful, the principal was set in ReceivedToken if (providerParameters.getTokenRequirements().getOnBehalfOf() != null) { ReceivedToken receivedToken = providerParameters.getTokenRequirements().getOnBehalfOf(); if (receivedToken.getState().equals(STATE.VALID)) { principal = receivedToken.getPrincipal(); } } else if (providerParameters.getTokenRequirements().getValidateTarget() != null) { ReceivedToken receivedToken = providerParameters.getTokenRequirements().getValidateTarget(); if (receivedToken.getState().equals(STATE.VALID)) { principal = receivedToken.getPrincipal(); } } else { principal = providerParameters.getPrincipal(); } return principal; } protected void handleWSTrustClaims(JWTClaimsProviderParameters jwtClaimsProviderParameters, JwtClaims claims) { TokenProviderParameters providerParameters = jwtClaimsProviderParameters.getProviderParameters(); // Handle Claims ProcessedClaimCollection retrievedClaims = ClaimsUtils.processClaims(providerParameters); if (retrievedClaims != null) { Iterator<ProcessedClaim> claimIterator = retrievedClaims.iterator(); while (claimIterator.hasNext()) { ProcessedClaim claim = claimIterator.next(); if (claim.getClaimType() != null && claim.getValues() != null && !claim.getValues().isEmpty()) { Object claimValues = claim.getValues(); if (claim.getValues().size() == 1) { claimValues = claim.getValues().get(0); } claims.setProperty(claim.getClaimType().toString(), claimValues); } } } } protected void handleConditions(JWTClaimsProviderParameters jwtClaimsProviderParameters, JwtClaims claims) { TokenProviderParameters providerParameters = jwtClaimsProviderParameters.getProviderParameters(); Instant currentDate = Instant.now(); long currentTime = currentDate.getEpochSecond(); // Set the defaults first claims.setIssuedAt(currentTime); claims.setNotBefore(currentTime); claims.setExpiryTime(currentTime + lifetime); Lifetime tokenLifetime = providerParameters.getTokenRequirements().getLifetime(); if (lifetime > 0 && acceptClientLifetime && tokenLifetime != null && tokenLifetime.getCreated() != null && tokenLifetime.getExpires() != null) { Instant creationTime = null; Instant expirationTime = null; try { creationTime = ZonedDateTime.parse(tokenLifetime.getCreated()).toInstant(); expirationTime = ZonedDateTime.parse(tokenLifetime.getExpires()).toInstant(); } catch (DateTimeParseException ex) { LOG.fine("Error in parsing Timestamp Created or Expiration Strings"); throw new STSException( "Error in parsing Timestamp Created or Expiration Strings", STSException.INVALID_TIME ); } // Check to see if the created time is in the future Instant validCreation = Instant.now(); if (futureTimeToLive > 0) { validCreation = validCreation.plusSeconds(futureTimeToLive); } if (creationTime.isAfter(validCreation)) { LOG.fine("The Created Time is too far in the future"); throw new STSException("The Created Time is too far in the future", STSException.INVALID_TIME); } long requestedLifetime = Duration.between(creationTime, expirationTime).getSeconds(); if (requestedLifetime > getMaxLifetime()) { StringBuilder sb = new StringBuilder(); sb.append("Requested lifetime [").append(requestedLifetime); sb.append(" sec] exceed configured maximum lifetime [").append(getMaxLifetime()); sb.append(" sec]"); LOG.warning(sb.toString()); if (isFailLifetimeExceedance()) { throw new STSException("Requested lifetime exceeds maximum lifetime", STSException.INVALID_TIME); } else { expirationTime = creationTime.plusSeconds(getMaxLifetime()); } } long creationTimeInSeconds = creationTime.getEpochSecond(); claims.setIssuedAt(creationTimeInSeconds); claims.setNotBefore(creationTimeInSeconds); claims.setExpiryTime(expirationTime.getEpochSecond()); } } /** * Set the audience restriction claim. The Audiences are from an AppliesTo address, and the wst:Participants * (if either exist). */ protected void handleAudienceRestriction( JWTClaimsProviderParameters jwtClaimsProviderParameters, JwtClaims claims ) { TokenProviderParameters providerParameters = jwtClaimsProviderParameters.getProviderParameters(); List<String> audiences = new ArrayList<>(); String appliesToAddress = providerParameters.getAppliesToAddress(); if (appliesToAddress != null) { audiences.add(appliesToAddress); } Participants participants = providerParameters.getTokenRequirements().getParticipants(); if (participants != null) { String address = TokenProviderUtils.extractAddressFromParticipantsEPR(participants.getPrimaryParticipant()); if (address != null) { audiences.add(address); } if (participants.getParticipants() != null) { for (Object participant : participants.getParticipants()) { if (participant != null) { address = TokenProviderUtils.extractAddressFromParticipantsEPR(participant); if (address != null) { audiences.add(address); } } } } } if (!audiences.isEmpty()) { claims.setAudiences(audiences); } } protected void handleActAs( JWTClaimsProviderParameters jwtClaimsProviderParameters, JwtClaims claims ) { TokenProviderParameters providerParameters = jwtClaimsProviderParameters.getProviderParameters(); if (providerParameters.getTokenRequirements().getActAs() != null) { ReceivedToken receivedToken = providerParameters.getTokenRequirements().getActAs(); if (receivedToken.getState().equals(STATE.VALID)) { claims.setClaim("ActAs", receivedToken.getPrincipal().getName()); } } } public boolean isUseX500CN() { return useX500CN; } public void setUseX500CN(boolean useX500CN) { this.useX500CN = useX500CN; } /** * Get how long (in seconds) a client-supplied Created Element is allowed to be in the future. * The default is 60 seconds to avoid common problems relating to clock skew. */ public long getFutureTimeToLive() { return futureTimeToLive; } /** * Set how long (in seconds) a client-supplied Created Element is allowed to be in the future. * The default is 60 seconds to avoid common problems relating to clock skew. */ public void setFutureTimeToLive(long futureTimeToLive) { this.futureTimeToLive = futureTimeToLive; } /** * Set the default lifetime in seconds for issued JWT tokens * @param default lifetime in seconds */ public void setLifetime(long lifetime) { this.lifetime = lifetime; } /** * Get the default lifetime in seconds for issued JWT token where requestor * doesn't specify a lifetime element * @return the lifetime in seconds */ public long getLifetime() { return lifetime; } /** * Set the maximum lifetime in seconds for issued JWT tokens * @param maximum lifetime in seconds */ public void setMaxLifetime(long maxLifetime) { this.maxLifetime = maxLifetime; } /** * Get the maximum lifetime in seconds for issued JWT token * if requestor specifies lifetime element * @return the maximum lifetime in seconds */ public long getMaxLifetime() { return maxLifetime; } /** * Is client lifetime element accepted * Default: false */ public boolean isAcceptClientLifetime() { return this.acceptClientLifetime; } /** * Set whether client lifetime is accepted */ public void setAcceptClientLifetime(boolean acceptClientLifetime) { this.acceptClientLifetime = acceptClientLifetime; } /** * If requested lifetime exceeds shall it fail (default) * or overwrite with maximum lifetime */ public boolean isFailLifetimeExceedance() { return this.failLifetimeExceedance; } /** * If requested lifetime exceeds shall it fail (default) * or overwrite with maximum lifetime */ public void setFailLifetimeExceedance(boolean failLifetimeExceedance) { this.failLifetimeExceedance = failLifetimeExceedance; } }