/* * Copyright 2017 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * 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 * * 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.keycloak.authentication.actiontoken; import org.keycloak.TokenVerifier.Predicate; import org.keycloak.common.VerificationException; import org.keycloak.common.util.Time; import org.keycloak.jose.jws.JWSBuilder; import org.keycloak.models.*; import org.keycloak.services.Urls; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.*; import javax.ws.rs.core.UriInfo; /** * Part of action token that is intended to be used e.g. in link sent in password-reset email. * The token encapsulates user, expected action and its time of expiry. * * @author hmlnarik */ public class DefaultActionToken extends DefaultActionTokenKey implements ActionTokenValueModel { public static final String JSON_FIELD_AUTHENTICATION_SESSION_ID = "asid"; public static final Predicate<DefaultActionToken> ACTION_TOKEN_BASIC_CHECKS = t -> { if (t.getActionVerificationNonce() == null) { throw new VerificationException("Nonce not present."); } return true; }; /** * Single-use random value used for verification whether the relevant action is allowed. */ public DefaultActionToken() { super(null, null, 0, null); } /** * * @param userId User ID * @param actionId Action ID * @param absoluteExpirationInSecs Absolute expiration time in seconds in timezone of Keycloak. * @param actionVerificationNonce */ protected DefaultActionToken(String userId, String actionId, int absoluteExpirationInSecs, UUID actionVerificationNonce) { super(userId, actionId, absoluteExpirationInSecs, actionVerificationNonce); } /** * * @param userId User ID * @param actionId Action ID * @param absoluteExpirationInSecs Absolute expiration time in seconds in timezone of Keycloak. * @param actionVerificationNonce */ protected DefaultActionToken(String userId, String actionId, int absoluteExpirationInSecs, UUID actionVerificationNonce, String authenticationSessionId) { super(userId, actionId, absoluteExpirationInSecs, actionVerificationNonce); setAuthenticationSessionId(authenticationSessionId); } @JsonProperty(value = JSON_FIELD_AUTHENTICATION_SESSION_ID) public String getAuthenticationSessionId() { return (String) getOtherClaims().get(JSON_FIELD_AUTHENTICATION_SESSION_ID); } @JsonProperty(value = JSON_FIELD_AUTHENTICATION_SESSION_ID) public final void setAuthenticationSessionId(String authenticationSessionId) { setOtherClaims(JSON_FIELD_AUTHENTICATION_SESSION_ID, authenticationSessionId); } @JsonIgnore @Override public Map<String, String> getNotes() { Map<String, String> res = new HashMap<>(); if (getAuthenticationSessionId() != null) { res.put(JSON_FIELD_AUTHENTICATION_SESSION_ID, getAuthenticationSessionId()); } return res; } @Override public String getNote(String name) { Object res = getOtherClaims().get(name); return res instanceof String ? (String) res : null; } /** * Sets value of the given note * @return original value (or {@code null} when no value was present) */ public final String setNote(String name, String value) { Object res = value == null ? getOtherClaims().remove(name) : getOtherClaims().put(name, value); return res instanceof String ? (String) res : null; } /** * Removes given note, and returns original value (or {@code null} when no value was present) * @return see description */ public final String removeNote(String name) { Object res = getOtherClaims().remove(name); return res instanceof String ? (String) res : null; } /** * Updates the following fields and serializes this token into a signed JWT. The list of updated fields follows: * <ul> * <li>{@code id}: random nonce</li> * <li>{@code issuedAt}: Current time</li> * <li>{@code issuer}: URI of the given realm</li> * <li>{@code audience}: URI of the given realm (same as issuer)</li> * </ul> * * @param session * @param realm * @param uri * @return */ public String serialize(KeycloakSession session, RealmModel realm, UriInfo uri) { String issuerUri = getIssuer(realm, uri); KeyManager.ActiveHmacKey keys = session.keys().getActiveHmacKey(realm); this .issuedAt(Time.currentTime()) .id(getActionVerificationNonce().toString()) .issuer(issuerUri) .audience(issuerUri); return new JWSBuilder() .kid(keys.getKid()) .jsonContent(this) .hmac512(keys.getSecretKey()); } private static String getIssuer(RealmModel realm, UriInfo uri) { return Urls.realmIssuer(uri.getBaseUri(), realm.getName()); } }