ResetCredentialsActionToken.java

138 lines | 5.323 kB Blame History Raw Download
/*
 * 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;

import org.keycloak.TokenVerifier;
import org.keycloak.TokenVerifier.Predicate;
import org.keycloak.common.VerificationException;
import org.keycloak.common.util.Time;
import org.keycloak.jose.jws.*;
import org.keycloak.models.*;
import org.keycloak.services.Urls;
import org.keycloak.sessions.AuthenticationSessionModel;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Map;
import java.util.UUID;
import javax.ws.rs.core.UriInfo;
import org.jboss.logging.Logger;

/**
 * Representation of a token that represents a time-limited reset credentials action.
 * <p>
 * This implementation handles signature.
 *
 * @author hmlnarik
 */
public class ResetCredentialsActionToken extends DefaultActionToken {

    private static final Logger LOG = Logger.getLogger(ResetCredentialsActionToken.class);

    public static final String RESET_CREDENTIALS_TYPE = "reset-credentials";
    public static final String NOTE_AUTHENTICATION_SESSION_ID = "clientSessionId";
    private static final String JSON_FIELD_AUTHENTICATION_SESSION_ID = "asid";
    private static final String JSON_FIELD_LAST_CHANGE_PASSWORD_TIMESTAMP = "lcpt";

    @JsonIgnore
    private AuthenticationSessionModel authenticationSession;

    @JsonProperty(value = JSON_FIELD_LAST_CHANGE_PASSWORD_TIMESTAMP)
    private Long lastChangedPasswordTimestamp;

    public ResetCredentialsActionToken(String userId, int absoluteExpirationInSecs, UUID actionVerificationNonce, Long lastChangedPasswordTimestamp, String authenticationSessionId) {
        super(userId, RESET_CREDENTIALS_TYPE, absoluteExpirationInSecs, actionVerificationNonce);
        setNote(NOTE_AUTHENTICATION_SESSION_ID, authenticationSessionId);
        this.lastChangedPasswordTimestamp = lastChangedPasswordTimestamp;
    }

    public ResetCredentialsActionToken(String userId, int absoluteExpirationInSecs, UUID actionVerificationNonce, Long lastChangedPasswordTimestamp, AuthenticationSessionModel authenticationSession) {
        this(userId, absoluteExpirationInSecs, actionVerificationNonce, lastChangedPasswordTimestamp, authenticationSession == null ? null : authenticationSession.getId());
        this.authenticationSession = authenticationSession;
    }

    private ResetCredentialsActionToken() {
        super(null, null, -1, null);
    }

    public AuthenticationSessionModel getAuthenticationSession() {
        return this.authenticationSession;
    }

    public void setAuthenticationSession(AuthenticationSessionModel authenticationSession) {
        this.authenticationSession = authenticationSession;
        setAuthenticationSessionId(authenticationSession == null ? null : authenticationSession.getId());
    }

    @JsonProperty(value = JSON_FIELD_AUTHENTICATION_SESSION_ID)
    public String getAuthenticationSessionId() {
        return getNote(NOTE_AUTHENTICATION_SESSION_ID);
    }

    public void setAuthenticationSessionId(String authenticationSessionId) {
        setNote(NOTE_AUTHENTICATION_SESSION_ID, authenticationSessionId);
    }

    public Long getLastChangedPasswordTimestamp() {
        return lastChangedPasswordTimestamp;
    }

    public void setLastChangedPasswordTimestamp(Long lastChangedPasswordTimestamp) {
        this.lastChangedPasswordTimestamp = lastChangedPasswordTimestamp;
    }

    @Override
    @JsonIgnore
    public Map<String, String> getNotes() {
        Map<String, String> res = super.getNotes();
        if (this.authenticationSession != null) {
            res.put(NOTE_AUTHENTICATION_SESSION_ID, getNote(NOTE_AUTHENTICATION_SESSION_ID));
        }
        return res;
    }

    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());
    }

    /**
     * Returns a {@code DefaultActionToken} instance decoded from the given string. If decoding fails, returns {@code null}
     *
     * @param session
     * @param actionTokenString
     * @return
     */
    public static ResetCredentialsActionToken deserialize(String token) throws VerificationException {
        return TokenVerifier.create(token, ResetCredentialsActionToken.class).getToken();
    }
}