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

hudson.scm.PerJobCredentialStore Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 *
 * Copyright (c) 2004-2011 Oracle Corporation.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *
 * Kohsuke Kawaguchi
 *
 *******************************************************************************/
package hudson.scm;

import hudson.Extension;
import hudson.ExtensionList;
import hudson.XmlFile;
import hudson.matrix.MatrixConfiguration;
import hudson.model.AbstractProject;
import hudson.model.Hudson;
import hudson.model.ItemGroup;
import hudson.model.Job;
import hudson.model.Saveable;
import hudson.model.listeners.SaveableListener;
import hudson.remoting.Channel;
import java.io.File;
import java.io.IOException;
import java.util.Hashtable;
import java.util.Map;
import java.util.logging.Logger;
import org.tmatesoft.svn.core.SVNURL;

import hudson.scm.subversion.Messages;

import static java.util.logging.Level.INFO;
import static hudson.scm.SubversionSCM.DescriptorImpl.Credential;

/**
 * Persists the credential per job. This object is remotable.
 *
 * @author Kohsuke Kawaguchi
 */
public final class PerJobCredentialStore implements Saveable, SubversionSCM.DescriptorImpl.RemotableSVNAuthenticationProvider {
    private static final Logger LOGGER = Logger.getLogger(PerJobCredentialStore.class.getName());

    /**
     * Used to remember the context. If we are persisting, we don't want to persist a proxy,
     * even if that happens in the context of a remote call.
     */
    private static final ThreadLocal IS_SAVING = new ThreadLocal();

    private final transient AbstractProject project;
    private final transient String url;

    private static final String credentialsFileName = "subversion.credentials";

    private transient CredentialsSaveableListener saveableListener;

    /**
     * SVN authentication realm to its associated credentials, scoped to this project.
     */
    private final Map credentials = new Hashtable();

    public PerJobCredentialStore(AbstractProject project, String url) {
        this.project = project;
        this.url = url;
        // read existing credential
        XmlFile xml = getXmlFile(project);
        try {
            if (xml.exists()) {
                xml.unmarshal(this);
            }
        } catch (IOException e) {
            // ignore the failure to unmarshal, or else we'll never get through beyond this point.
            LOGGER.log(INFO, Messages.PerJobCredentialStore_readCredentials_error(xml), e);
        }
    }

    private synchronized Credential get(String key) {
        return credentials.get(key);
    }

    public Credential getCredential(SVNURL url, String realm) {
        return get(getCredentialsKey(url.toDecodedString(), realm));
    }

    public void acknowledgeAuthentication(String realm, Credential cred) {
        try {
            acknowledge(getCredentialsKey(url, realm), cred);
        } catch (IOException e) {
            LOGGER.log(INFO, Messages.PerJobCredentialStore_acknowledgeAuthentication_error(), e);
        }
    }

    /**
     * Method retuns credentials key based on realm and url. If url is not null and if it will be processed
     * without revision number, realm is used if url is null,
     *
     * @param url svn url
     * @param realm realm
     * @return credentials key.
     * @see SubversionSCM#getUrlWithoutRevision(String)
     */
    private String getCredentialsKey(String url, String realm) {
        return null == url ? realm : url.lastIndexOf("@") > 0 ? SubversionSCM.getUrlWithoutRevision(url) : url;
    }

    private synchronized void acknowledge(String key, Credential cred) throws IOException {
        Credential old = cred == null ? credentials.remove(key) : credentials.put(key, cred);
        // save only if there was a change
        if (old == null && cred == null) {
            return;
        }
        if (old == null || cred == null || !old.equals(cred)) {
            save();
        }
    }

    public synchronized void save() throws IOException {
        IS_SAVING.set(Boolean.TRUE);
        try {
            if (!credentials.isEmpty()) {
                XmlFile xmlFile = getXmlFile(project);
                xmlFile.write(this);
                SaveableListener.fireOnChange(this, xmlFile);
            }
        } finally {
            IS_SAVING.remove();
        }
    }

    public XmlFile getXmlFile(Job prj) {
        //default behaviour
        File rootDir = prj.getRootDir();
        File credentialFile = new File(rootDir, credentialsFileName);
        if (credentialFile.exists()) {
            return new XmlFile(credentialFile);
        }
        //matrix configuration project
        if (prj instanceof MatrixConfiguration && prj.getParent() != null) {
            ItemGroup parent = prj.getParent();
            if (parent instanceof Job){
                return getXmlFile((Job)parent);
            }
        }
        if (prj.hasCascadingProject()) {
            return getXmlFile(prj.getCascadingProject());
        }
        return new XmlFile(new File(rootDir, credentialsFileName));
    }

    /*package*/
    public synchronized boolean isEmpty() {
        return credentials.isEmpty();
    }

    /**
     * When sent to the remote node, send a proxy.
     */
    private Object writeReplace() {
        if (IS_SAVING.get() != null) {
            return this;
        }

        Channel c = Channel.current();
        return c == null ? this : c.export(SubversionSCM.DescriptorImpl.RemotableSVNAuthenticationProvider.class, this);
    }

    public CredentialsSaveableListener getSaveableListener() {
        if (null == saveableListener) {
            ExtensionList extensionList = Hudson.getInstance().getExtensionList(
                SaveableListener.class);
            if (null != extensionList && !extensionList.isEmpty()) {
                for (SaveableListener listener : extensionList) {
                    if (listener instanceof CredentialsSaveableListener) {
                        saveableListener = (CredentialsSaveableListener) listener;
                        break;
                    }
                }
            }
        }
        return saveableListener;
    }

    @Extension
    public static class CredentialsSaveableListener extends SaveableListener {

        private boolean fileChanged = false;

        @Override
        public void onChange(Saveable o, XmlFile file) {
            if (o instanceof PerJobCredentialStore) {
                fileChanged = true;
            }
        }

        public boolean isFileChanged() {
            return fileChanged;
        }

        public void resetChangedStatus() {
            fileChanged = false;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy