com.c4_soft.springaddons.security.oauth2.test.annotations.OpenIdClaims Maven / Gradle / Ivy
/*
* Copyright 2019 Jérôme Wacongne
*
* Licensed 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
*
* https://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 com.c4_soft.springaddons.security.oauth2.test.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
import org.springframework.util.StringUtils;
import com.c4_soft.springaddons.security.oauth2.test.Defaults;
import com.c4_soft.springaddons.security.oauth2.test.OpenidClaimSetBuilder;
import net.minidev.json.JSONObject;
import net.minidev.json.parser.JSONParser;
import net.minidev.json.parser.ParseException;
/**
* Configures claims defined at
* https://datatracker.ietf.org/doc/html/rfc7519#section-4.1 and
* https://openid.net/specs/openid-connect-core-1_0.html#IDToken
*
* @author Jérôme Wacongne <ch4mp@c4-soft.com>
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface OpenIdClaims {
String acr() default "";
String[] amr() default {};
String[] aud() default {};
String azp() default "";
/**
* @return authentication instant formated as {@link DateTimeFormatter#ISO_INSTANT}
*/
String authTime() default "";
/**
* @return expiration instant formated as {@link DateTimeFormatter#ISO_INSTANT}
*/
String exp() default "";
/**
* @return issue instant formated as {@link DateTimeFormatter#ISO_INSTANT}
*/
String iat() default "";
/**
* @return jti (JWT unique identifier)
*/
String jti() default "";
/**
* @return nbf (not before) instant formated as {@link DateTimeFormatter#ISO_INSTANT}
*/
String nbf() default "";
/**
* @return to be parsed as URL
*/
String iss() default "";
String nonce() default "";
String sub() default Defaults.SUBJECT;
String sessionState() default "";
String accessTokenHash() default "";
String authorizationCodeHash() default "";
OpenIdAddress address() default @OpenIdAddress();
/**
* @return End-User's birthday, represented as an ISO 8601:2004 [ISO8601‑2004] YYYY-MM-DDformat
*/
String birthdate() default "";
String email() default "";
boolean emailVerified() default false;
String familyName() default "";
String gender() default "";
String givenName() default "";
String locale() default "";
String middleName() default "";
String name() default "";
String nickName() default "";
String phoneNumber() default "";
boolean phoneNumberVerified() default false;
String picture() default "";
String preferredUsername() default "";
String profile() default "";
/**
* @return issue instant formated as {@link DateTimeFormatter#ISO_INSTANT}
*/
String updatedAt() default "";
String website() default "";
String zoneinfo() default "";
/**
* @return intended to define private claims but any claim can be defined there. In case of conflict with an OpenID standard claim, the standard claim wins.
*/
Claims otherClaims() default @Claims();
String usernameClaim() default StandardClaimNames.SUB;
/**
* @return claims from a JSON file on the classpath. In case of conflict this claims have the lowest precedence: jsonFile < json < otherClaims <
* OpenID standard claims
*/
ClasspathClaims jsonFile() default @ClasspathClaims();
/**
* @return JSON object string containing all claims. In case of conflict this claims have intermediate precedence: jsonFile < json < otherClaims <
* OpenID standard claims
*/
String json() default "";
public static class Builder {
private Builder() {}
public static OpenidClaimSetBuilder of(OpenIdClaims tokenAnnotation) {
final var token = new OpenidClaimSetBuilder();
token.putAll(ClasspathClaims.Support.parse(tokenAnnotation.jsonFile().value()));
if (StringUtils.hasText(tokenAnnotation.json())) {
try {
token.putAll(new JSONParser(JSONParser.MODE_PERMISSIVE).parse(tokenAnnotation.json(), JSONObject.class));
} catch (final ParseException e) {
throw new InvalidJsonException(e);
}
}
token.putAll(Claims.Token.of(tokenAnnotation.otherClaims()));
token.name(tokenAnnotation.usernameClaim());
if (StringUtils.hasText(tokenAnnotation.iss())) {
try {
token.issuer(new URL(tokenAnnotation.iss()));
} catch (final MalformedURLException e) {
throw new InvalidClaimException(e);
}
}
if (StringUtils.hasLength(tokenAnnotation.exp())) {
token.expiresAt(Instant.parse(tokenAnnotation.exp()));
}
if (StringUtils.hasLength(tokenAnnotation.iat())) {
token.issuedAt(Instant.parse(tokenAnnotation.iat()));
}
if (StringUtils.hasLength(tokenAnnotation.authTime())) {
token.authTime(Instant.parse(tokenAnnotation.authTime()));
}
if (StringUtils.hasLength(tokenAnnotation.sessionState())) {
token.sessionState(tokenAnnotation.sessionState());
}
if (StringUtils.hasLength(tokenAnnotation.sessionState())) {
token.accessTokenHash(tokenAnnotation.accessTokenHash());
}
if (StringUtils.hasLength(tokenAnnotation.sessionState())) {
token.authorizationCodeHash(tokenAnnotation.authorizationCodeHash());
}
token
.subject(tokenAnnotation.sub())
.audience(Arrays.asList(tokenAnnotation.aud()))
.nonce(tokenAnnotation.nonce())
.acr(tokenAnnotation.acr())
.amr(Arrays.asList(tokenAnnotation.amr()))
.azp(tokenAnnotation.azp());
if (StringUtils.hasLength(tokenAnnotation.updatedAt())) {
token.updatedAt(Instant.parse(tokenAnnotation.updatedAt()));
}
if (StringUtils.hasLength(tokenAnnotation.preferredUsername())) {
token.preferredUsername(tokenAnnotation.preferredUsername());
}
return token
.address(OpenIdAddress.Claim.of(tokenAnnotation.address()))
.birthdate(nullIfEmpty(tokenAnnotation.birthdate()))
.email(nullIfEmpty(tokenAnnotation.email()))
.emailVerified(tokenAnnotation.emailVerified())
.familyName(nullIfEmpty(tokenAnnotation.familyName()))
.gender(nullIfEmpty(tokenAnnotation.gender()))
.givenName(nullIfEmpty(tokenAnnotation.givenName()))
.jwtId(tokenAnnotation.jti())
.locale(nullIfEmpty(tokenAnnotation.locale()))
.middleName(nullIfEmpty(tokenAnnotation.middleName()))
.name(nullIfEmpty(tokenAnnotation.name()))
.nickname(nullIfEmpty(tokenAnnotation.nickName()))
.phoneNumber(nullIfEmpty(tokenAnnotation.phoneNumber()))
.phoneNumberVerified(tokenAnnotation.phoneNumberVerified())
.picture(nullIfEmpty(tokenAnnotation.picture()))
.profile(nullIfEmpty(tokenAnnotation.profile()))
.website(nullIfEmpty(tokenAnnotation.website()));
}
private static String nullIfEmpty(String str) {
return StringUtils.hasText(str) ? str : null;
}
}
}