com.atlassian.jira.security.auth.trustedapps.DefaultCurrentApplicationStore.java Source code

Java tutorial

Introduction

Here is the source code for com.atlassian.jira.security.auth.trustedapps.DefaultCurrentApplicationStore.java

Source

package com.atlassian.jira.security.auth.trustedapps;

import java.math.BigInteger;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;

import com.atlassian.beehive.ClusterLock;
import com.atlassian.beehive.ClusterLockService;
import com.atlassian.cache.CacheManager;
import com.atlassian.cache.CachedReference;
import com.atlassian.cache.Supplier;
import com.atlassian.event.api.EventListener;
import com.atlassian.jira.EventComponent;
import com.atlassian.jira.bc.license.JiraLicenseService;
import com.atlassian.jira.config.properties.ApplicationProperties;
import com.atlassian.jira.event.ClearCacheEvent;
import com.atlassian.jira.extension.Startable;
import com.atlassian.jira.util.dbc.Assertions;
import com.atlassian.jira.util.lang.Pair;
import com.atlassian.security.auth.trustedapps.CurrentApplication;
import com.atlassian.security.auth.trustedapps.DefaultCurrentApplication;
import com.atlassian.security.random.DefaultSecureRandomService;
import com.atlassian.security.random.SecureRandomService;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.ArrayUtils;

import static com.atlassian.jira.util.dbc.Assertions.notBlank;
import static com.atlassian.jira.util.dbc.Assertions.notNull;
import static org.apache.commons.lang.StringUtils.isBlank;

/**
 * @since v3.12
 */
@EventComponent
public class DefaultCurrentApplicationStore implements CurrentApplicationStore, Startable {

    public static final String ACCESS_LOCK = DefaultCurrentApplicationStore.class.getName() + ".accessLock";

    private static final class Keys {

        private static final String PRIVATE_KEY_DATA = "jira.trustedapp.key.private.data";
        private static final String PUBLIC_KEY_DATA = "jira.trustedapp.key.public.data";
        private static final String UID = "jira.trustedapp.uid";
    }

    private final ApplicationProperties applicationProperties;

    private final CachedReference<Pair<KeyPair, CurrentApplication>> cache;
    private final JiraLicenseService licenseService;
    private final ClusterLockService clusterLockService;
    private ClusterLock accessLock;

    public DefaultCurrentApplicationStore(final ApplicationProperties applicationProperties,
            final JiraLicenseService jiraLicenseService, final CacheManager cacheManager,
            final ClusterLockService clusterLockService) {
        this.clusterLockService = clusterLockService;
        this.applicationProperties = notNull("applicationProperties", applicationProperties);
        this.licenseService = notNull("jiraLicenseService", jiraLicenseService);
        this.cache = cacheManager.getCachedReference(getClass(), "cache",
                new Supplier<Pair<KeyPair, CurrentApplication>>() {
                    @Override
                    public Pair<KeyPair, CurrentApplication> get() {
                        return getOrCreateCurrentApplication();
                    }
                });
    }

    @Override
    public void start() throws Exception {
        accessLock = clusterLockService.getLockForName(ACCESS_LOCK);
    }

    @EventListener
    public void onClearCache(@SuppressWarnings("unused") final ClearCacheEvent event) {
        cache.reset();
    }

    @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "UG_SYNC_SET_UNSYNC_GET", justification = "This is a valid unsynchronized getter")
    public CurrentApplication getCurrentApplication() {
        return cache.get().second();
    }

    @Override
    public KeyPair getKeyPair() {
        return cache.get().first();
    }

    @Override
    public void setCurrentApplication(String applicationId, KeyPair pair) {
        notBlank("applicationId cannot be blank.", applicationId);
        Assertions.notNull("pair cannot be null.", pair);
        Assertions.notNull("pair.private cannot be null.", pair.getPrivate());
        Assertions.notNull("pair.public cannot be null.", pair.getPublic());

        accessLock.lock();
        try {
            applicationProperties.setText(Keys.PRIVATE_KEY_DATA, KeyFactory.encode(pair.getPrivate()));
            applicationProperties.setText(Keys.PUBLIC_KEY_DATA, KeyFactory.encode(pair.getPublic()));
            applicationProperties.setString(Keys.UID, applicationId);
            cache.reset();
        } finally {
            accessLock.unlock();
        }
    }

    @Override
    public void setCurrentApplication(String applicationId, String publicKey, String privateKey) {
        Assertions.notBlank("applicationId cannot be blank.", applicationId);
        Assertions.notNull("publicKey cannot be null.", publicKey);
        Assertions.notNull("privateKey cannot be null.", privateKey);

        PublicKey publicKeyObj = KeyFactory.getPublicKey(publicKey);
        if (publicKeyObj instanceof KeyFactory.InvalidPublicKey) {
            throw new IllegalArgumentException("publicKey is not a valid public key.",
                    ((KeyFactory.InvalidKey) publicKeyObj).getCause());
        }

        PrivateKey privateKeyObj = KeyFactory.getPrivateKey(privateKey);
        if (privateKeyObj instanceof KeyFactory.InvalidPrivateKey) {
            throw new IllegalArgumentException("privateKey is not a valid private key.",
                    ((KeyFactory.InvalidKey) privateKeyObj).getCause());
        }

        setCurrentApplication(applicationId, new KeyPair(publicKeyObj, privateKeyObj));
    }

    private Pair<KeyPair, CurrentApplication> getOrCreateCurrentApplication() {
        accessLock.lock();
        final KeyPair keyPair;
        String uid;
        try {
            final String privateKeyData = applicationProperties.getText(Keys.PRIVATE_KEY_DATA);
            final String publicKeyData = applicationProperties.getText(Keys.PUBLIC_KEY_DATA);

            if (isBlank(privateKeyData)) {
                keyPair = generateNewKeyPair();
                applicationProperties.setText(Keys.PRIVATE_KEY_DATA, KeyFactory.encode(keyPair.getPrivate()));
                applicationProperties.setText(Keys.PUBLIC_KEY_DATA, KeyFactory.encode(keyPair.getPublic()));
            } else {
                PrivateKey privateKey = KeyFactory.getPrivateKey(privateKeyData);
                PublicKey publicKey = KeyFactory.getPublicKey(publicKeyData);

                keyPair = new KeyPair(publicKey, privateKey);
            }

            uid = applicationProperties.getString(Keys.UID);
            if (isBlank(uid)) {
                uid = new UIDGenerator().generateUID(licenseService);
                applicationProperties.setString(Keys.UID, uid);
            }
        } finally {
            accessLock.unlock();
        }
        CurrentApplication application = new DefaultCurrentApplication(keyPair.getPublic(), keyPair.getPrivate(),
                uid);
        return Pair.of(keyPair, application);
    }

    private static KeyPair generateNewKeyPair() {
        try {
            return KeyFactory.getEncryptionProvider().generateNewKeyPair();
        }
        ///CLOVER:OFF
        catch (final NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        } catch (final NoSuchProviderException e) {
            throw new RuntimeException(e);
        }
        ///CLOVER:ON
    }

    private static class UIDGenerator {
        SecureRandomService secureRandom = DefaultSecureRandomService.getInstance();

        /**
         * Generate the UID. Note that this is dependent on there being a Server ID in the properties. If there isn't,
         * it'll use a random number. So the presence of the Server ID makes the return value stable and idempotent.
         * Without it it will always return something different each time.
         *
         * @param jiraLicenseService the JIRA license service
         * @return a uid.
         */
        String generateUID(final JiraLicenseService jiraLicenseService) {
            String serverId = jiraLicenseService.getServerId();
            // don't pass null into the md5 method
            serverId = (serverId != null) ? serverId : String.valueOf(secureRandom.nextLong());
            // Grab the first 4 bytes of the hashed SID and convert it to an integer
            final byte[] idHash = ArrayUtils.subarray(DigestUtils.md5(serverId), 0, 3);
            return "jira:" + new BigInteger(1, idHash).intValue();
        }
    }
}