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

de.adorsys.keycloak.mapper.SecretTokenMapper Maven / Gradle / Ivy

There is a newer version: 2.2.3
Show newest version
package de.adorsys.keycloak.mapper;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.adorsys.envutils.EnvProperties;
import org.adorsys.jjwk.selector.JWEEncryptedSelector;
import org.adorsys.jjwk.selector.KeyExtractionException;
import org.adorsys.jjwk.selector.UnsupportedEncAlgorithmException;
import org.adorsys.jjwk.selector.UnsupportedKeyLengthException;
import org.apache.commons.lang3.RandomStringUtils;
import org.keycloak.broker.oidc.util.JsonSimpleHttp;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.oidc.mappers.AbstractOIDCProtocolMapper;
import org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper;
import org.keycloak.protocol.oidc.mappers.OIDCIDTokenMapper;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.AccessToken;

import com.fasterxml.jackson.databind.JsonNode;
import com.nimbusds.jose.EncryptionMethod;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWEAlgorithm;
import com.nimbusds.jose.JWEDecrypter;
import com.nimbusds.jose.JWEEncrypter;
import com.nimbusds.jose.JWEHeader;
import com.nimbusds.jose.JWEHeader.Builder;
import com.nimbusds.jose.JWEObject;
import com.nimbusds.jose.Payload;
import com.nimbusds.jose.crypto.DirectDecrypter;
import com.nimbusds.jose.crypto.DirectEncrypter;
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;

/**
 * Created by alexg on 23.05.17.
 */
public class SecretTokenMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper {

    private static final List configProperties = new ArrayList();

    public static final String PROVIDER_ID = "secret-token-mapper";
    private byte[] secretEncryptionPassword;
    private static final String CREDENTIAL_TYPE = "custom_secret";


    @Override
    public void postInit(KeycloakSessionFactory factory) {
        String prop = EnvProperties.getEnvOrSysProp("SECRET_ENCRYPTION_PASSWORD", false);
        try {
            secretEncryptionPassword = prop.getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new IllegalStateException(e);
        }
        super.postInit(factory);
    }

    @Override
    public String getDisplayCategory() {
        return TOKEN_MAPPER_CATEGORY;
    }

    @Override
    public String getDisplayType() {
        return "User Attribute";
    }

    @Override
    public String getHelpText() {
        return "Map a db user sercret attribute to token.";
    }

    @Override
    public List getConfigProperties() {
        return configProperties;
    }

    @Override
    public String getId() {
        return PROVIDER_ID;
    }

    @Override
    public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
                                            UserSessionModel userSession, ClientSessionModel clientSession) {

        String customSecretAttr;
        List attribute = userSession.getUser().getAttribute(CREDENTIAL_TYPE);
        if (attribute == null || attribute.isEmpty()) {
            customSecretAttr = generateUserSecret(userSession);
        } else {
            customSecretAttr = attribute.iterator().next();
        }
        String serializedSecret = wrapSecretForResourceServer(customSecretAttr, userSession, session);
        if (serializedSecret != null) {
            token.getOtherClaims().put("custom_secret", serializedSecret);
        }

        return token;
    }

    /**
     * TODO implement caching.
     *
     * @param customSecretAttr
     * @param userSession
     * @param session
     * @return
     */
    private String wrapSecretForResourceServer(String customSecretAttr, UserSessionModel userSession, KeycloakSession session) {
        try {
            //decrypt with database encryption pass
            JWEObject jweObject = JWEObject.parse(customSecretAttr);
            JWEDecrypter decrypter = new DirectDecrypter(secretEncryptionPassword);
            jweObject.decrypt(decrypter);
            Payload payload = jweObject.getPayload();

            //encrypt with remote server public key
            String url = EnvProperties.getEnvOrSysProp(userSession.getRealm().getName().toUpperCase() + "_PUBLIC_KEY_URL", true);
            if (url == null) {
                return null;
            }
            JsonNode publicKeyResponse = JsonSimpleHttp.asJson(JsonSimpleHttp.doGet(url, session));
            JWKSet jwkSet = JWKSet.parse(publicKeyResponse.toString());
            JWK jwk = jwkSet.getKeys().iterator().next();
            JWEEncrypter jweEncrypter = JWEEncryptedSelector.geEncrypter(jwk, null, null);
            // JWE encrypt secret.
            JWEObject jweObj = new JWEObject(getHeader(jwk), payload);
            jweObj.encrypt(jweEncrypter);
            return jweObj.serialize();
        } catch (ParseException | JOSEException | UnsupportedEncAlgorithmException | IOException | KeyExtractionException | UnsupportedKeyLengthException e) {
            throw new IllegalStateException(e);
        }
    }

    private JWEHeader getHeader(JWK jwk) throws JOSEException {
        JWEHeader header;
        if (jwk instanceof RSAKey) {
            header = new JWEHeader(JWEAlgorithm.RSA_OAEP, EncryptionMethod.A128GCM);
        } else if (jwk instanceof ECKey) {
            header = new JWEHeader(JWEAlgorithm.ECDH_ES_A128KW, EncryptionMethod.A192GCM);
        } else {
            return null;
        }
        return new JWEHeader.Builder(header).keyID(jwk.getKeyID()).build();
    }

    /**
     * TODO implement caching
     */
    private String generateUserSecret(UserSessionModel userSession) {
        String randomGraph = RandomStringUtils.randomGraph(16);
        Builder headerBuilder = new JWEHeader.Builder(JWEAlgorithm.DIR, EncryptionMethod.A128GCM);
        JWEObject jweObj = new JWEObject(headerBuilder.build(), new Payload(randomGraph));
        try {
            DirectEncrypter encrypter = new DirectEncrypter(secretEncryptionPassword);
            // You can now use the encrypter on one or more JWE objects
            // that you wish to secure
            jweObj.encrypt(encrypter);
        } catch (JOSEException e) {
            throw new IllegalStateException(e);
        }
        String customSecretAttr = jweObj.serialize();

        userSession.getUser().setAttribute(CREDENTIAL_TYPE, Arrays.asList(customSecretAttr));
        return customSecretAttr;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy