All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tngtech.keycloakmock.api.TokenConfig Maven / Gradle / Ivy

There is a newer version: 0.16.0
Show newest version
package com.tngtech.keycloakmock.api;

import io.jsonwebtoken.ClaimJwtException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/**
 * The configuration from which to generate an access token.
 *
 * 

Example usage: * *

{@code
 * TokenConfig config = TokenConfig.aTokenConfig().withSubject("subject).build();
 * }
*/ public class TokenConfig { @Nonnull private final Set audience; @Nonnull private final String authorizedParty; @Nonnull private final String subject; @Nonnull private final String scope; @Nonnull private final Map claims; @Nonnull private final Access realmAccess; @Nonnull private final Map resourceAccess; @Nonnull private final Instant issuedAt; @Nonnull private final Instant authenticationTime; @Nonnull private final Instant expiration; @Nullable private final Instant notBefore; @Nullable private final String name; @Nullable private final String givenName; @Nullable private final String familyName; @Nullable private final String email; @Nullable private final String preferredUsername; private TokenConfig(@Nonnull final Builder builder) { if (builder.audience.isEmpty()) { audience = Collections.singleton("server"); } else { audience = builder.audience; } authorizedParty = builder.authorizedParty; subject = builder.subject; scope = String.join(" ", builder.scope); claims = builder.claims; realmAccess = builder.realmRoles; resourceAccess = builder.resourceAccess; issuedAt = builder.issuedAt; authenticationTime = builder.authenticationTime; expiration = builder.expiration; notBefore = builder.notBefore; givenName = builder.givenName; familyName = builder.familyName; if (builder.name != null) { name = builder.name; } else if (givenName != null) { if (familyName != null) { name = givenName + " " + familyName; } else { name = givenName; } } else { name = familyName; } email = builder.email; preferredUsername = builder.preferredUsername; } /** * Get a new builder. * * @return a token config builder */ @Nonnull public static Builder aTokenConfig() { return new Builder(); } @Nonnull public Set getAudience() { return Collections.unmodifiableSet(audience); } @Nonnull public String getAuthorizedParty() { return authorizedParty; } @Nonnull public String getSubject() { return subject; } @Nonnull public String getScope() { return scope; } @Nonnull public Map getClaims() { return Collections.unmodifiableMap(claims); } @Nonnull public Access getRealmAccess() { return realmAccess; } @Nonnull public Map getResourceAccess() { return Collections.unmodifiableMap(resourceAccess); } @Nonnull public Instant getIssuedAt() { return issuedAt; } @Nonnull public Instant getAuthenticationTime() { return authenticationTime; } @Nonnull public Instant getExpiration() { return expiration; } @Nullable public Instant getNotBefore() { return notBefore; } @Nullable public String getName() { return name; } @Nullable public String getGivenName() { return givenName; } @Nullable public String getFamilyName() { return familyName; } @Nullable public String getEmail() { return email; } @Nullable public String getPreferredUsername() { return preferredUsername; } /** * Builder for {@link TokenConfig}. * *

Use this to generate a token configuration to your needs. */ public static final class Builder { @Nonnull private final Set audience = new HashSet<>(); @Nonnull private String authorizedParty = "client"; @Nonnull private String subject = "user"; @Nonnull private final Set scope = new HashSet<>(); @Nonnull private final Map claims = new HashMap<>(); @Nonnull private final Access realmRoles = new Access(); @Nonnull private final Map resourceAccess = new HashMap<>(); @Nonnull private Instant issuedAt = Instant.now(); @Nonnull private Instant expiration = issuedAt.plus(10, ChronoUnit.HOURS); @Nonnull private Instant authenticationTime = Instant.now(); @Nullable private Instant notBefore; @Nullable private String givenName; @Nullable private String familyName; @Nullable private String name; @Nullable private String email; @Nullable private String preferredUsername; private Builder() { scope.add("openid"); } /** * Add the contents of a real token. * *

The content of the token is read into this token config. Signature, issuer as well as * start and end time of the original token are ignored. * * @param originalToken token to read from * @return builder */ @Nonnull @SuppressWarnings("unchecked") public Builder withSourceToken(@Nonnull final String originalToken) { int i = originalToken.lastIndexOf('.'); String untrustedJwtString = originalToken.substring(0, i + 1); Claims untrustedClaims; try { untrustedClaims = Jwts.parserBuilder().build().parseClaimsJwt(untrustedJwtString).getBody(); } catch (ClaimJwtException e) { // ignoring expiry exceptions untrustedClaims = e.getClaims(); } for (Map.Entry entry : untrustedClaims.entrySet()) { switch (entry.getKey()) { case "aud": Object aud = entry.getValue(); if (aud instanceof String) { withAudience((String) aud); } else if (aud instanceof Collection) { withAudiences((Collection) aud); } break; case "azp": withAuthorizedParty(getTypedValue(entry, String.class)); break; case "sub": withSubject(getTypedValue(entry, String.class)); break; case "name": withName(getTypedValue(entry, String.class)); break; case "given_name": withGivenName(getTypedValue(entry, String.class)); break; case "family_name": withFamilyName(getTypedValue(entry, String.class)); break; case "email": withEmail(getTypedValue(entry, String.class)); break; case "preferred_username": withPreferredUsername(getTypedValue(entry, String.class)); break; case "realm_access": Map> sourceRealmAccess = getTypedValue(entry, Map.class); withRealmRoles(sourceRealmAccess.get("roles")); break; case "resource_access": Map>> sourceResourceAccess = getTypedValue(entry, Map.class); sourceResourceAccess.forEach( (key, value) -> withResourceRoles(key, value.get("roles"))); break; case "scope": withScopes(Arrays.asList(getTypedValue(entry, String.class).split(" "))); break; case "typ": if (!"Bearer".equals(getTypedValue(entry, String.class))) { throw new IllegalArgumentException("Only bearer tokens are allowed here!"); } break; case "iss": case "iat": case "nbf": case "exp": case "auth_time": // ignoring issuer and date information break; default: withClaim(entry.getKey(), entry.getValue()); break; } } return this; } @SuppressWarnings("unchecked") @Nonnull private T getTypedValue( @Nonnull final Map.Entry entry, @Nonnull final Class clazz) { if (clazz.isInstance(entry.getValue())) { return (T) entry.getValue(); } throw new IllegalArgumentException( String.format( "Expected %s for key %s, but found %s", clazz, entry.getKey(), entry.getValue().getClass())); } /** * Add an audience. * *

An audience is an identifier of a recipient of the token. * * @param audience the audience to add * @return builder * @see ID token */ @Nonnull public Builder withAudience(@Nonnull final String audience) { this.audience.add(Objects.requireNonNull(audience)); return this; } /** * Add a collection of audiences. * *

An audience is an identifier of a recipient of the token. * * @param audiences the audiences to add * @return builder * @see ID token */ @Nonnull public Builder withAudiences(@Nonnull final Collection audiences) { this.audience.addAll(Objects.requireNonNull(audiences)); return this; } /** * Set authorized party. * *

The authorized party identifies the party for which the token was issued. * * @param authorizedParty the authorized party to set * @return builder * @see ID token */ @Nonnull public Builder withAuthorizedParty(@Nonnull final String authorizedParty) { this.authorizedParty = Objects.requireNonNull(authorizedParty); return this; } /** * Set subject. * *

The subject identifies the end-user for which the token was issued. * * @param subject the subject to set * @return builder * @see ID token */ @Nonnull public Builder withSubject(@Nonnull final String subject) { this.subject = Objects.requireNonNull(subject); return this; } /** * Add scope. * *

The scope for which this token has been requested. Always contains "openid". * * @param scope the scope to add * @return builder * @see scope * claims */ @Nonnull public Builder withScope(@Nonnull final String scope) { this.scope.add(scope); return this; } /** * Add scopes. * *

The scope for which this token has been requested. Always contains "openid". * * @param scopes the scopes to add * @return builder * @see scope * claims */ @Nonnull public Builder withScopes(@Nonnull final Collection scopes) { this.scope.addAll(scopes); return this; } /** * Add realm roles. * *

Realm roles apply to all clients within a realm. * * @param roles the roles to add * @return builder * @see realm * roles */ @Nonnull public Builder withRealmRoles(@Nonnull final Collection roles) { this.realmRoles.addRoles(Objects.requireNonNull(roles)); return this; } /** * Add a realm role. * *

Realm roles apply to all clients within a realm. * * @param role the role to add * @return builder * @see realm * roles */ @Nonnull public Builder withRealmRole(@Nonnull final String role) { this.realmRoles.addRole(Objects.requireNonNull(role)); return this; } /** * Add resource roles. * *

Resource roles only apply to a specific client or resource. * * @param resource the resource or client for which to add the roles * @param roles the roles to add * @return builder * @see client * roles */ @Nonnull public Builder withResourceRoles( @Nonnull final String resource, @Nonnull final Collection roles) { this.resourceAccess .computeIfAbsent(Objects.requireNonNull(resource), k -> new Access()) .addRoles(Objects.requireNonNull(roles)); return this; } /** * Add a resource role. * *

Resource roles only apply to a specific client or resource. * * @param resource the resource or client for which to add the roles * @param role the role to add * @return builder * @see client * roles */ @Nonnull public Builder withResourceRole(@Nonnull final String resource, @Nonnull final String role) { this.resourceAccess .computeIfAbsent(Objects.requireNonNull(resource), k -> new Access()) .addRole(Objects.requireNonNull(role)); return this; } /** * Add generic claims. * *

Use this method to add elements to the token cannot be set using more specialized methods. * The underlying library uses Jackson * data-binding, so getters will be used to generate JSON objects. * * @param claims the claims to add (map from claim name to claim value) * @return builder */ @Nonnull public Builder withClaims(@Nonnull final Map claims) { this.claims.putAll(Objects.requireNonNull(claims)); return this; } /** * Add generic claim. * *

Use this method to add elements to the token cannot be set using more specialized methods. * The underlying library uses Jackson * data-binding, so getters will be used to generate JSON objects. * * @param key the claim name * @param value the claim value * @return builder */ @Nonnull public Builder withClaim(@Nonnull final String key, @Nonnull final Object value) { this.claims.put(Objects.requireNonNull(key), Objects.requireNonNull(value)); return this; } /** * Set issued at date. * * @param issuedAt the instant when the token was generated * @return builder * @see ID token */ @Nonnull public Builder withIssuedAt(@Nonnull final Instant issuedAt) { this.issuedAt = Objects.requireNonNull(issuedAt); return this; } /** * Set authentication time. * * @param authenticationTime the instant when user was authenticated at the SSO server * @return builder * @see ID token */ @Nonnull public Builder withAuthenticationTime(@Nonnull final Instant authenticationTime) { this.authenticationTime = Objects.requireNonNull(authenticationTime); return this; } /** * Set expiration date. * * @param expiration the instant when the token expires * @return builder * @see ID token */ @Nonnull public Builder withExpiration(@Nonnull final Instant expiration) { this.expiration = Objects.requireNonNull(expiration); return this; } /** * Set not before date. * * @param notBefore the instant when the token starts being valid * @return builder * @see Not Before Claim */ @Nonnull public Builder withNotBefore(@Nullable final Instant notBefore) { this.notBefore = notBefore; return this; } /** * Set given name. * * @param givenName the given name of the user * @return builder * @see create * new user */ @Nonnull public Builder withGivenName(@Nullable final String givenName) { this.givenName = givenName; return this; } /** * Set family name. * * @param familyName the family name of the user * @return builder * @see create * new user */ @Nonnull public Builder withFamilyName(@Nullable final String familyName) { this.familyName = familyName; return this; } /** * Set full name. * *

If not set, this will automatically be filled using given name and family name. * * @param name the full name of the user * @return builder */ @Nonnull public Builder withName(@Nullable final String name) { this.name = name; return this; } /** * Set email address. * * @param email the email address of the user * @return builder * @see create * new user */ @Nonnull public Builder withEmail(@Nullable final String email) { this.email = email; return this; } /** * Set preferred username. * * @param preferredUsername the preferred username of the user * @return builder * @see create * new user */ @Nonnull public Builder withPreferredUsername(@Nullable final String preferredUsername) { this.preferredUsername = preferredUsername; return this; } @Nonnull public TokenConfig build() { return new TokenConfig(this); } } static class Access { @Nonnull private final Set roles = new HashSet<>(); @Nonnull public Set getRoles() { return Collections.unmodifiableSet(roles); } void addRole(@Nonnull final String role) { roles.add(Objects.requireNonNull(role)); } void addRoles(@Nonnull final Collection newRoles) { roles.addAll(newRoles); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy