Java tutorial
/* * The MIT License * * Copyright (c) 2011-2016, CloudBees, Inc., Stephen Connolly. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.cloudbees.plugins.credentials; import com.cloudbees.plugins.credentials.common.IdCredentials; import com.cloudbees.plugins.credentials.domains.DomainRequirement; import com.cloudbees.plugins.credentials.fingerprints.ItemCredentialsFingerprintFacet; import com.cloudbees.plugins.credentials.fingerprints.NodeCredentialsFingerprintFacet; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import hudson.BulkChange; import hudson.DescriptorExtensionList; import hudson.ExtensionList; import hudson.ExtensionPoint; import hudson.Util; import hudson.init.InitMilestone; import hudson.model.Cause; import hudson.model.Computer; import hudson.model.ComputerSet; import hudson.model.Describable; import hudson.model.Descriptor; import hudson.model.DescriptorVisibilityFilter; import hudson.model.Fingerprint; import hudson.model.Item; import hudson.model.ItemGroup; import hudson.model.Job; import hudson.model.ModelObject; import hudson.model.Node; import hudson.model.ParameterValue; import hudson.model.ParametersAction; import hudson.model.Queue; import hudson.model.Run; import hudson.model.User; import hudson.model.queue.Tasks; import hudson.security.ACL; import hudson.security.Permission; import hudson.security.PermissionGroup; import hudson.security.PermissionScope; import hudson.security.SecurityRealm; import hudson.util.ListBoxModel; import java.io.IOException; import java.io.OutputStreamWriter; import java.nio.charset.Charset; import java.security.DigestOutputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.Collator; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import jenkins.model.FingerprintFacet; import jenkins.model.Jenkins; import jenkins.util.Timer; import org.acegisecurity.Authentication; import org.acegisecurity.GrantedAuthority; import org.acegisecurity.context.SecurityContext; import org.acegisecurity.context.SecurityContextHolder; import org.acegisecurity.providers.UsernamePasswordAuthenticationToken; import org.acegisecurity.userdetails.UsernameNotFoundException; import org.apache.commons.io.IOUtils; import org.apache.commons.io.output.NullOutputStream; import org.apache.commons.lang.StringUtils; import org.jenkins.ui.icon.IconSpec; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.DoNotUse; import org.kohsuke.stapler.Stapler; import org.kohsuke.stapler.StaplerRequest; import static com.cloudbees.plugins.credentials.CredentialsStoreAction.FINGERPRINT_XML; /** * An extension point for providing {@link Credentials}. */ public abstract class CredentialsProvider extends Descriptor<CredentialsProvider> implements ExtensionPoint, Describable<CredentialsProvider>, IconSpec { /** * A {@link CredentialsProvider} that does nothing for use as a marker * * @since 2.1.1 */ public static final CredentialsProvider NONE = new CredentialsProvider() { /** * {@inheritDoc} */ @NonNull @Override public <C extends Credentials> List<C> getCredentials(@NonNull Class<C> type, @Nullable ItemGroup itemGroup, @Nullable Authentication authentication) { return Collections.emptyList(); } }; /** * The permission group for credentials. * * @since 1.8 */ public static final PermissionGroup GROUP = new PermissionGroup(CredentialsProvider.class, Messages._CredentialsProvider_PermissionGroupTitle()); /** * Where an immediate action against a job requires that a credential be selected by the user triggering the * action, this permission allows the user to select a credential from their private credential store. Immediate * actions could include: building with parameters, tagging a build, deploying artifacts, etc. * * @since 1.16 */ public static final Permission USE_OWN = new Permission(GROUP, "UseOwn", Messages._CredentialsProvider_UseOwnPermissionDescription(), Boolean.getBoolean("com.cloudbees.plugins.credentials.UseOwnPermission") ? Jenkins.ADMINISTER : Job.BUILD, Boolean.getBoolean("com.cloudbees.plugins.credentials.UseOwnPermission"), new PermissionScope[] { PermissionScope.ITEM }); /** * Where an immediate action against a job requires that a credential be selected by the user triggering the * action, this permission allows the user to select a credential from those credentials available within the * scope of the job. Immediate actions could include: building with parameters, tagging a build, * deploying artifacts, etc. * * This permission is implied by {@link Job#CONFIGURE} as anyone who can configure the job can configure the * job to use credentials within the item scope anyway. * * @since 1.16 */ public static final Permission USE_ITEM = new Permission(GROUP, "UseItem", Messages._CredentialsProvider_UseItemPermissionDescription(), Job.CONFIGURE, Boolean.getBoolean("com.cloudbees.plugins.credentials.UseItemPermission"), new PermissionScope[] { PermissionScope.ITEM }); /** * Our logger. * * @since 1.6 */ private static final Logger LOGGER = Logger.getLogger(CredentialsProvider.class.getName()); /** * The scopes that we allow credential permissions on. * * @since 1.12. */ private static final PermissionScope[] SCOPES = new PermissionScope[] { PermissionScope.ITEM, PermissionScope.ITEM_GROUP, PermissionScope.JENKINS }; /** * The permission for adding credentials to a {@link CredentialsStore}. * * @since 1.8 */ public static final Permission CREATE = new Permission(GROUP, "Create", Messages._CredentialsProvider_CreatePermissionDescription(), Permission.CREATE, true, SCOPES); /** * The permission for updating credentials in a {@link CredentialsStore}. * * @since 1.8 */ public static final Permission UPDATE = new Permission(GROUP, "Update", Messages._CredentialsProvider_UpdatePermissionDescription(), Permission.UPDATE, true, SCOPES); /** * The permission for viewing credentials in a {@link CredentialsStore}. * * @since 1.8 */ public static final Permission VIEW = new Permission(GROUP, "View", Messages._CredentialsProvider_ViewPermissionDescription(), Permission.READ, true, SCOPES); /** * The permission for removing credentials from a {@link CredentialsStore}. * * @since 1.8 */ public static final Permission DELETE = new Permission(GROUP, "Delete", Messages._CredentialsProvider_DeletePermissionDescription(), Permission.DELETE, true, SCOPES); /** * The permission for managing credential domains in a {@link CredentialsStore}. * * @since 1.8 */ public static final Permission MANAGE_DOMAINS = new Permission(GROUP, "ManageDomains", Messages._CredentialsProvider_ManageDomainsPermissionDescription(), Permission.CONFIGURE, true, SCOPES); /** * Default constructor. */ @SuppressWarnings("unchecked") public CredentialsProvider() { super(Descriptor.self()); } /** * Returns all the registered {@link com.cloudbees.plugins.credentials.Credentials} descriptors. * * @return all the registered {@link com.cloudbees.plugins.credentials.Credentials} descriptors. */ public static DescriptorExtensionList<Credentials, CredentialsDescriptor> allCredentialsDescriptors() { // TODO switch to Jenkins.getInstance() once 2.0+ is the baseline return Jenkins.getActiveInstance().getDescriptorList(Credentials.class); } /** * Returns all credentials which are available to the {@link ACL#SYSTEM} {@link Authentication} * within the {@link jenkins.model.Jenkins#getInstance()}. * * @param type the type of credentials to get. * @param <C> the credentials type. * @return the list of credentials. * @deprecated use {@link #lookupCredentials(Class, Item, Authentication, List)}, * {@link #lookupCredentials(Class, Item, Authentication, DomainRequirement...)}, * {@link #lookupCredentials(Class, ItemGroup, Authentication, List)} * or {@link #lookupCredentials(Class, ItemGroup, Authentication, DomainRequirement...)} */ @Deprecated @NonNull @SuppressWarnings("unused") // API entry point for consumers public static <C extends Credentials> List<C> lookupCredentials(@NonNull Class<C> type) { return lookupCredentials(type, (Item) null, ACL.SYSTEM); } /** * Returns all credentials which are available to the specified {@link Authentication} * within the {@link jenkins.model.Jenkins#getInstance()}. * * @param type the type of credentials to get. * @param authentication the authentication. * @param <C> the credentials type. * @return the list of credentials. * @deprecated use {@link #lookupCredentials(Class, Item, Authentication, List)}, * {@link #lookupCredentials(Class, Item, Authentication, DomainRequirement...)}, * {@link #lookupCredentials(Class, ItemGroup, Authentication, List)} * or {@link #lookupCredentials(Class, ItemGroup, Authentication, DomainRequirement...)} */ @Deprecated @NonNull @SuppressWarnings("unused") // API entry point for consumers public static <C extends Credentials> List<C> lookupCredentials(@NonNull Class<C> type, @Nullable Authentication authentication) { return lookupCredentials(type, Jenkins.getInstance(), authentication); } /** * Returns all credentials which are available to the {@link ACL#SYSTEM} {@link Authentication} * for use by the specified {@link Item}. * * @param type the type of credentials to get. * @param item the item. * @param <C> the credentials type. * @return the list of credentials. * @deprecated use {@link #lookupCredentials(Class, Item, Authentication, List)} * or {@link #lookupCredentials(Class, Item, Authentication, DomainRequirement...)} */ @Deprecated @NonNull @SuppressWarnings("unused") // API entry point for consumers public static <C extends Credentials> List<C> lookupCredentials(@NonNull Class<C> type, @Nullable Item item) { return item == null ? lookupCredentials(type, Jenkins.getInstance(), ACL.SYSTEM) : lookupCredentials(type, item, ACL.SYSTEM); } /** * Returns all credentials which are available to the {@link ACL#SYSTEM} {@link Authentication} * for use by the {@link Item}s in the specified {@link ItemGroup}. * * @param type the type of credentials to get. * @param itemGroup the item group. * @param <C> the credentials type. * @return the list of credentials. * @deprecated use {@link #lookupCredentials(Class, ItemGroup, Authentication, List)} * or {@link #lookupCredentials(Class, ItemGroup, Authentication, DomainRequirement...)} */ @Deprecated @NonNull @SuppressWarnings("unused") // API entry point for consumers public static <C extends Credentials> List<C> lookupCredentials(@NonNull Class<C> type, @Nullable ItemGroup itemGroup) { return lookupCredentials(type, itemGroup, ACL.SYSTEM); } /** * Returns all credentials which are available to the specified {@link Authentication} * for use by the {@link Item}s in the specified {@link ItemGroup}. * * @param type the type of credentials to get. * @param itemGroup the item group. * @param authentication the authentication. * @param <C> the credentials type. * @return the list of credentials. * @deprecated use {@link #lookupCredentials(Class, ItemGroup, Authentication, List)} * or {@link #lookupCredentials(Class, ItemGroup, Authentication, DomainRequirement...)} */ @Deprecated @NonNull @SuppressWarnings({ "unchecked", "unused" }) // API entry point for consumers public static <C extends Credentials> List<C> lookupCredentials(@NonNull Class<C> type, @Nullable ItemGroup itemGroup, @Nullable Authentication authentication) { return lookupCredentials(type, itemGroup, authentication, Collections.<DomainRequirement>emptyList()); } /** * Returns all credentials which are available to the specified {@link Authentication} * for use by the specified {@link Item}. * * @param type the type of credentials to get. * @param authentication the authentication. * @param item the item. * @param <C> the credentials type. * @return the list of credentials. * @deprecated use {@link #lookupCredentials(Class, Item, Authentication, List)} * or {@link #lookupCredentials(Class, Item, Authentication, DomainRequirement...)} */ @Deprecated @NonNull @SuppressWarnings("unused") // API entry point for consumers public static <C extends Credentials> List<C> lookupCredentials(@NonNull Class<C> type, @Nullable Item item, @Nullable Authentication authentication) { return lookupCredentials(type, item, authentication, Collections.<DomainRequirement>emptyList()); } /** * Returns all credentials which are available to the specified {@link Authentication} * for use by the {@link Item}s in the specified {@link ItemGroup}. * * @param type the type of credentials to get. * @param itemGroup the item group. * @param authentication the authentication. * @param domainRequirements the credential domains to match. * @param <C> the credentials type. * @return the list of credentials. * @since 1.5 */ @NonNull @SuppressWarnings({ "unchecked", "unused" }) // API entry point for consumers public static <C extends Credentials> List<C> lookupCredentials(@NonNull Class<C> type, @Nullable ItemGroup itemGroup, @Nullable Authentication authentication, @Nullable DomainRequirement... domainRequirements) { return lookupCredentials(type, itemGroup, authentication, Arrays.asList(domainRequirements)); } /** * Returns all credentials which are available to the specified {@link Authentication} * for use by the {@link Item}s in the specified {@link ItemGroup}. * * @param type the type of credentials to get. * @param itemGroup the item group. * @param authentication the authentication. * @param domainRequirements the credential domains to match. * @param <C> the credentials type. * @return the list of credentials. * @since 1.5 */ @NonNull @SuppressWarnings({ "unchecked", "unused" }) // API entry point for consumers public static <C extends Credentials> List<C> lookupCredentials(@NonNull Class<C> type, @Nullable ItemGroup itemGroup, @Nullable Authentication authentication, @Nullable List<DomainRequirement> domainRequirements) { type.getClass(); // throw NPE if null // TODO switch to Jenkins.getInstance() once 2.0+ is the baseline Jenkins jenkins = Jenkins.getActiveInstance(); itemGroup = itemGroup == null ? jenkins : itemGroup; authentication = authentication == null ? ACL.SYSTEM : authentication; domainRequirements = domainRequirements == null ? Collections.<DomainRequirement>emptyList() : domainRequirements; CredentialsResolver<Credentials, C> resolver = CredentialsResolver.getResolver(type); if (resolver != null) { LOGGER.log(Level.FINE, "Resolving legacy credentials of type {0} with resolver {1}", new Object[] { type, resolver }); final List<Credentials> originals = lookupCredentials(resolver.getFromClass(), itemGroup, authentication, domainRequirements); LOGGER.log(Level.FINE, "Original credentials for resolving: {0}", originals); return resolver.resolve(originals); } List<C> result = new ArrayList<C>(); Set<String> ids = new HashSet<String>(); for (CredentialsProvider provider : all()) { if (provider.isEnabled(itemGroup) && provider.isApplicable(type)) { try { for (C c : provider.getCredentials(type, itemGroup, authentication, domainRequirements)) { if (!(c instanceof IdCredentials) || ids.add(((IdCredentials) c).getId())) { // if IdCredentials, only add if we havent added already // if not IdCredentials, always add result.add(c); } } } catch (NoClassDefFoundError e) { LOGGER.log(Level.FINE, "Could not retrieve provider credentials from " + provider + " likely due to missing optional dependency", e); } } } Collections.sort(result, new CredentialsNameComparator()); return result; } /** * Returns a {@link ListBoxModel} of all credentials which are available to the specified {@link Authentication} * for use by the {@link Item}s in the specified {@link ItemGroup}. * * @param type the type of credentials to get. * @param authentication the authentication. * @param itemGroup the item group. * @param domainRequirements the credential domains to match. * @param matcher the additional filtering to apply to the credentials * @param <C> the credentials type. * @return the {@link ListBoxModel} of {@link IdCredentials#getId()} with the corresponding display names as * provided by {@link CredentialsNameProvider}. * @since 2.1.0 */ public static <C extends IdCredentials> ListBoxModel listCredentials(@NonNull Class<C> type, @Nullable ItemGroup itemGroup, @Nullable Authentication authentication, @Nullable List<DomainRequirement> domainRequirements, @Nullable CredentialsMatcher matcher) { type.getClass(); // throw NPE if null // TODO switch to Jenkins.getInstance() once 2.0+ is the baseline Jenkins jenkins = Jenkins.getActiveInstance(); itemGroup = itemGroup == null ? jenkins : itemGroup; authentication = authentication == null ? ACL.SYSTEM : authentication; domainRequirements = domainRequirements == null ? Collections.<DomainRequirement>emptyList() : domainRequirements; matcher = matcher == null ? CredentialsMatchers.always() : matcher; CredentialsResolver<Credentials, C> resolver = CredentialsResolver.getResolver(type); if (resolver != null && IdCredentials.class.isAssignableFrom(resolver.getFromClass())) { LOGGER.log(Level.FINE, "Listing legacy credentials of type {0} identified by resolver {1}", new Object[] { type, resolver }); return listCredentials((Class) resolver.getFromClass(), itemGroup, authentication, domainRequirements, matcher); } ListBoxModel result = new ListBoxModel(); Set<String> ids = new HashSet<String>(); for (CredentialsProvider provider : all()) { if (provider.isEnabled(itemGroup) && provider.isApplicable(type)) { try { for (ListBoxModel.Option option : provider.getCredentialIds(type, itemGroup, authentication, domainRequirements, matcher)) { if (ids.add(option.value)) { result.add(option); } } } catch (NoClassDefFoundError e) { LOGGER.log(Level.FINE, "Could not retrieve provider credentials from " + provider + " likely due to missing optional dependency", e); } } } Collections.sort(result, new ListBoxModelOptionComparator()); return result; } /** * Returns all credentials which are available to the specified {@link Authentication} * for use by the specified {@link Item}. * * @param type the type of credentials to get. * @param authentication the authentication. * @param item the item. * @param domainRequirements the credential domains to match. * @param <C> the credentials type. * @return the list of credentials. * @since 1.5 */ @NonNull @SuppressWarnings("unused") // API entry point for consumers public static <C extends Credentials> List<C> lookupCredentials(@NonNull Class<C> type, @Nullable Item item, @Nullable Authentication authentication, DomainRequirement... domainRequirements) { return lookupCredentials(type, item, authentication, Arrays.asList(domainRequirements)); } /** * Returns all credentials which are available to the specified {@link Authentication} * for use by the specified {@link Item}. * * @param type the type of credentials to get. * @param authentication the authentication. * @param item the item. * @param domainRequirements the credential domains to match. * @param <C> the credentials type. * @return the list of credentials. * @since 1.5 */ @NonNull @SuppressWarnings("unused") // API entry point for consumers public static <C extends Credentials> List<C> lookupCredentials(@NonNull Class<C> type, @Nullable Item item, @Nullable Authentication authentication, @Nullable List<DomainRequirement> domainRequirements) { type.getClass(); // throw NPE if null if (item == null) { return lookupCredentials(type, Jenkins.getInstance(), authentication, domainRequirements); } if (item instanceof ItemGroup) { return lookupCredentials(type, (ItemGroup) item, authentication, domainRequirements); } authentication = authentication == null ? ACL.SYSTEM : authentication; domainRequirements = domainRequirements == null ? Collections.<DomainRequirement>emptyList() : domainRequirements; CredentialsResolver<Credentials, C> resolver = CredentialsResolver.getResolver(type); if (resolver != null) { LOGGER.log(Level.FINE, "Resolving legacy credentials of type {0} with resolver {1}", new Object[] { type, resolver }); final List<Credentials> originals = lookupCredentials(resolver.getFromClass(), item, authentication, domainRequirements); LOGGER.log(Level.FINE, "Original credentials for resolving: {0}", originals); return resolver.resolve(originals); } List<C> result = new ArrayList<C>(); Set<String> ids = new HashSet<String>(); for (CredentialsProvider provider : all()) { if (provider.isEnabled(item) && provider.isApplicable(type)) { try { for (C c : provider.getCredentials(type, item, authentication, domainRequirements)) { if (!(c instanceof IdCredentials) || ids.add(((IdCredentials) c).getId())) { // if IdCredentials, only add if we havent added already // if not IdCredentials, always add result.add(c); } } } catch (NoClassDefFoundError e) { LOGGER.log(Level.FINE, "Could not retrieve provider credentials from " + provider + " likely due to missing optional dependency", e); } } } Collections.sort(result, new CredentialsNameComparator()); return result; } /** * Returns a {@link ListBoxModel} of all credentials which are available to the specified {@link Authentication} * for use by the specified {@link Item}. * * @param type the type of credentials to get. * @param authentication the authentication. * @param item the item. * @param domainRequirements the credential domains to match. * @param matcher the additional filtering to apply to the credentials * @param <C> the credentials type. * @return the {@link ListBoxModel} of {@link IdCredentials#getId()} with the corresponding display names as * provided by {@link CredentialsNameProvider}. * @since 2.1.0 */ @NonNull public static <C extends IdCredentials> ListBoxModel listCredentials(@NonNull Class<C> type, @Nullable Item item, @Nullable Authentication authentication, @Nullable List<DomainRequirement> domainRequirements, @Nullable CredentialsMatcher matcher) { type.getClass(); // throw NPE if null if (item == null) { return listCredentials(type, Jenkins.getInstance(), authentication, domainRequirements, matcher); } if (item instanceof ItemGroup) { return listCredentials(type, (ItemGroup) item, authentication, domainRequirements, matcher); } authentication = authentication == null ? ACL.SYSTEM : authentication; domainRequirements = domainRequirements == null ? Collections.<DomainRequirement>emptyList() : domainRequirements; CredentialsResolver<Credentials, C> resolver = CredentialsResolver.getResolver(type); if (resolver != null && IdCredentials.class.isAssignableFrom(resolver.getFromClass())) { LOGGER.log(Level.FINE, "Listing legacy credentials of type {0} identified by resolver {1}", new Object[] { type, resolver }); return listCredentials((Class) resolver.getFromClass(), item, authentication, domainRequirements, matcher); } ListBoxModel result = new ListBoxModel(); Set<String> ids = new HashSet<String>(); for (CredentialsProvider provider : all()) { if (provider.isEnabled(item) && provider.isApplicable(type)) { try { for (ListBoxModel.Option option : provider.getCredentialIds(type, item, authentication, domainRequirements, matcher)) { if (ids.add(option.value)) { result.add(option); } } } catch (NoClassDefFoundError e) { LOGGER.log(Level.FINE, "Could not retrieve provider credentials from " + provider + " likely due to missing optional dependency", e); } } } Collections.sort(result, new ListBoxModelOptionComparator()); return result; } /** * Returns the scopes allowed for credentials stored within the specified object or {@code null} if the * object is not relevant for scopes and the object's container should be considered instead. * * @param object the object. * @return the set of scopes that are relevant for the object or {@code null} if the object is not a credentials * container. */ @CheckForNull public static Set<CredentialsScope> lookupScopes(ModelObject object) { object = CredentialsDescriptor.unwrapContext(object); Set<CredentialsScope> result = null; for (CredentialsProvider provider : all()) { if (provider.isEnabled(object)) { try { Set<CredentialsScope> scopes = provider.getScopes(object); if (scopes != null) { // if multiple providers for the same object, then combine scopes if (result == null) { result = new LinkedHashSet<CredentialsScope>(); } result.addAll(scopes); } } catch (NoClassDefFoundError e) { // ignore optional dependency } } } return result; } /** * Tests if the supplied context has any credentials stores associated with it. * * @param context the context object. * @return {@code true} if and only if the supplied context has at least one {@link CredentialsStore} associated * with it. * @since 2.1.5 */ public static boolean hasStores(final ModelObject context) { for (CredentialsProvider p : all()) { if (p.isEnabled(context) && p.getStore(context) != null) { return true; } } return false; } /** * Returns a lazy {@link Iterable} of all the {@link CredentialsStore} instances contributing credentials to the * supplied object. * * @param context the {@link Item} or {@link ItemGroup} or {@link User} to get the {@link CredentialsStore}s of. * @return a lazy {@link Iterable} of all {@link CredentialsStore} instances. * @since 1.8 */ public static Iterable<CredentialsStore> lookupStores(final ModelObject context) { final ExtensionList<CredentialsProvider> providers = all(); return new Iterable<CredentialsStore>() { public Iterator<CredentialsStore> iterator() { return new Iterator<CredentialsStore>() { private ModelObject current = context; private Iterator<CredentialsProvider> iterator = providers.iterator(); private CredentialsStore next; public boolean hasNext() { if (next != null) { return true; } while (current != null) { while (iterator.hasNext()) { CredentialsProvider p = iterator.next(); if (!p.isEnabled(context)) { continue; } next = p.getStore(current); if (next != null) { return true; } } // now walk up the model object tree // TODO make this an extension point perhaps ContextResolver could help if (current instanceof Item) { current = ((Item) current).getParent(); iterator = providers.iterator(); } else if (current instanceof User) { Jenkins jenkins = Jenkins.getActiveInstance(); Authentication a; if (jenkins.hasPermission(USE_ITEM) && current == User.current()) { // this is the fast path for the 99% of cases a = Jenkins.getAuthentication(); } else { try { a = ((User) current).impersonate(); } catch (UsernameNotFoundException e) { a = null; } } if (current == User.current() && jenkins.getACL().hasPermission(a, USE_ITEM)) { current = jenkins; iterator = providers.iterator(); } else { current = null; } } else if (current instanceof Jenkins) { // escape current = null; } else if (current instanceof ComputerSet) { current = Jenkins.getActiveInstance(); iterator = providers.iterator(); } else if (current instanceof Computer) { current = Jenkins.getActiveInstance(); iterator = providers.iterator(); } else if (current instanceof Node) { current = Jenkins.getActiveInstance(); iterator = providers.iterator(); } else { // fall back to Jenkins as the ultimate parent of everything else current = Jenkins.getActiveInstance(); iterator = providers.iterator(); } } return false; } public CredentialsStore next() { if (!hasNext()) { throw new NoSuchElementException(); } try { return next; } finally { next = null; } } public void remove() { throw new UnsupportedOperationException(); } }; } }; } /** * Make a best effort to ensure that the supplied credential is a snapshot credential (i.e. self-contained and * does not reference any external stores). <b>WARNING:</b> May produce unusual results if presented an exotic * credential that implements multiple distinct credential types at the same time, e.g. a credential that is * simultaneously a TLS certificate and a SSH key pair and a GPG key pair all at the same time... unless the * author of that credential type also provides a {@link CredentialsSnapshotTaker} that can handle such a * tripple play. * * @param credential the credential. * @param <C> the type of credential. * @return the credential or a snapshot of the credential. * @since 1.14 */ @SuppressWarnings("unchecked") public static <C extends Credentials> C snapshot(C credential) { return (C) snapshot(Credentials.class, credential); } /** * Make a best effort to ensure that the supplied credential is a snapshot credential (i.e. self-contained and * does not reference any external stores) * * @param clazz the type of credential that we are trying to snapshot (specified so that if there is more than * one type of snapshot able credential interface implemented by the credentials, * then they can be separated out. * @param credential the credential. * @param <C> the type of credential. * @return the credential or a snapshot of the credential. * @since 1.14 */ @SuppressWarnings("unchecked") public static <C extends Credentials> C snapshot(Class<C> clazz, C credential) { Class bestType = null; CredentialsSnapshotTaker bestTaker = null; for (CredentialsSnapshotTaker taker : ExtensionList.lookup(CredentialsSnapshotTaker.class)) { if (clazz.isAssignableFrom(taker.type()) && taker.type().isInstance(credential)) { if (bestTaker == null || bestType.isAssignableFrom(taker.type())) { bestTaker = taker; bestType = taker.type(); } } } if (bestTaker == null) { return credential; } return clazz.cast(bestTaker.snapshot(credential)); } /** * Helper method to get the default authentication to use for an {@link Item}. */ @NonNull /*package*/ static Authentication getDefaultAuthenticationOf(Item item) { if (item instanceof Queue.Task) { return Tasks.getAuthenticationOf((Queue.Task) item); } else { return ACL.SYSTEM; } } /** * A common requirement for plugins is to resolve a specific credential by id in the context of a specific run. * Given that the credential itself could be resulting from a build parameter expression and the complexities of * determining the scope of items from which the credential should be resolved in a chain of builds, this method * provides the correct answer. * * @param id either the id of the credential to find or a parameter expression for the id. * @param type the type of credential to find. * @param run the {@link Run} defining the context within which to find the credential. * @param domainRequirements the domain requirements of the credential. * @param <C> the credentials type. * @return the credential or {@code null} if either the credential cannot be found or the user triggering the run * is not permitted to use the credential in the context of the run. * @since 1.16 */ @CheckForNull public static <C extends IdCredentials> C findCredentialById(@NonNull String id, @NonNull Class<C> type, @NonNull Run<?, ?> run, DomainRequirement... domainRequirements) { return findCredentialById(id, type, run, Arrays.asList(domainRequirements)); } /** * A common requirement for plugins is to resolve a specific credential by id in the context of a specific run. * Given that the credential itself could be resulting from a build parameter expression and the complexities of * determining the scope of items from which the credential should be resolved in a chain of builds, this method * provides the correct answer. * * @param id either the id of the credential to find or a parameter expression for the id. * @param type the type of credential to find. * @param run the {@link Run} defining the context within which to find the credential. * @param domainRequirements the domain requirements of the credential. * @param <C> the credentials type. * @return the credential or {@code null} if either the credential cannot be found or the user triggering the run * is not permitted to use the credential in the context of the run. * @since 1.16 */ @CheckForNull public static <C extends IdCredentials> C findCredentialById(@NonNull String id, @NonNull Class<C> type, @NonNull Run<?, ?> run, @Nullable List<DomainRequirement> domainRequirements) { id.getClass(); // throw NPE if null; type.getClass(); // throw NPE if null; run.getClass(); // throw NPE if null; // first we need to find out if this id is pre-selected or a parameter id = id.trim(); boolean isParameter = false; boolean isDefaultValue = false; if (id.startsWith("${") && id.endsWith("}")) { final ParametersAction action = run.getAction(ParametersAction.class); if (action != null) { final ParameterValue parameter = action.getParameter(id.substring(2, id.length() - 1)); if (parameter instanceof CredentialsParameterValue) { isParameter = true; isDefaultValue = ((CredentialsParameterValue) parameter).isDefaultValue(); id = ((CredentialsParameterValue) parameter).getValue(); } } } // non parameters or default parameter values can only come from the job's context if (!isParameter || isDefaultValue) { // we use the default authentication of the job as those are the only ones that can be configured // if a different strategy is in play it doesn't make sense to consider the run-time authentication // as you would have no way to configure it Authentication runAuth = CredentialsProvider.getDefaultAuthenticationOf(run.getParent()); List<C> candidates = new ArrayList<C>(); // we want the credentials available to the user the build is running as candidates.addAll( CredentialsProvider.lookupCredentials(type, run.getParent(), runAuth, domainRequirements)); // if that user can use the item's credentials, add those in too if (runAuth != ACL.SYSTEM && run.getACL().hasPermission(runAuth, CredentialsProvider.USE_ITEM)) { candidates.addAll(CredentialsProvider.lookupCredentials(type, run.getParent(), ACL.SYSTEM, domainRequirements)); } return CredentialsMatchers.firstOrNull(candidates, CredentialsMatchers.withId(id)); } // this is a parameter and not the default value, we need to determine who triggered the build final Map.Entry<User, Run<?, ?>> triggeredBy = triggeredBy(run); final Authentication a = triggeredBy == null ? Jenkins.ANONYMOUS : triggeredBy.getKey().impersonate(); List<C> candidates = new ArrayList<C>(); if (triggeredBy != null && run == triggeredBy.getValue() && run.getACL().hasPermission(a, CredentialsProvider.USE_OWN)) { // the user triggered this job directly and they are allowed to supply their own credentials, so // add those into the list. We do not want to follow the chain for the user's authentication // though, as there is no way to limit how far the passed-through parameters can be used candidates.addAll(CredentialsProvider.lookupCredentials(type, run.getParent(), a, domainRequirements)); } if (run.getACL().hasPermission(a, CredentialsProvider.USE_ITEM)) { // the triggering user is allowed to use the item's credentials, so add those into the list // we use the default authentication of the job as those are the only ones that can be configured // if a different strategy is in play it doesn't make sense to consider the run-time authentication // as you would have no way to configure it Authentication runAuth = CredentialsProvider.getDefaultAuthenticationOf(run.getParent()); // we want the credentials available to the user the build is running as candidates.addAll( CredentialsProvider.lookupCredentials(type, run.getParent(), runAuth, domainRequirements)); // if that user can use the item's credentials, add those in too if (runAuth != ACL.SYSTEM && run.getACL().hasPermission(runAuth, CredentialsProvider.USE_ITEM)) { candidates.addAll(CredentialsProvider.lookupCredentials(type, run.getParent(), ACL.SYSTEM, domainRequirements)); } } C result = CredentialsMatchers.firstOrNull(candidates, CredentialsMatchers.withId(id)); // if the run has not completed yet then we can safely assume that the credential is being used for this run // so we will track it's usage. We use isLogUpdated() as it could be used during post production return run.isLogUpdated() ? track(run, result) : result; } /** * Identifies the {@link User} and {@link Run} that triggered the supplied {@link Run}. * * @param run the {@link Run} to find the trigger of. * @return the trigger of the supplied run or {@code null} if this could not be determined. */ @CheckForNull private static Map.Entry<User, Run<?, ?>> triggeredBy(Run<?, ?> run) { Cause.UserIdCause cause = run.getCause(Cause.UserIdCause.class); if (cause != null) { User u = User.get(cause.getUserId(), false, Collections.emptyMap()); return u == null ? null : new AbstractMap.SimpleImmutableEntry<User, Run<?, ?>>(u, run); } Cause.UpstreamCause c = run.getCause(Cause.UpstreamCause.class); run = (c != null) ? c.getUpstreamRun() : null; while (run != null) { cause = run.getCause(Cause.UserIdCause.class); if (cause != null) { User u = User.get(cause.getUserId(), false, Collections.emptyMap()); return u == null ? null : new AbstractMap.SimpleImmutableEntry<User, Run<?, ?>>(u, run); } c = run.getCause(Cause.UpstreamCause.class); run = (c != null) ? c.getUpstreamRun() : null; } return null; } /** * Returns the list of all {@link CredentialsProvider}. * * @return the list of all {@link CredentialsProvider}. */ public static ExtensionList<CredentialsProvider> all() { return ExtensionList.lookup(CredentialsProvider.class); } /** * Returns only those {@link CredentialsProvider} that are {@link #isEnabled()}. * * @return a list of {@link CredentialsProvider} that are {@link #isEnabled()}. * @since 2.0 */ public static List<CredentialsProvider> enabled() { List<CredentialsProvider> providers = new ArrayList<CredentialsProvider>( ExtensionList.lookup(CredentialsProvider.class)); for (Iterator<CredentialsProvider> iterator = providers.iterator(); iterator.hasNext();) { CredentialsProvider p = iterator.next(); if (!p.isEnabled()) { iterator.remove(); } } return providers; } /** * Returns only those {@link CredentialsProvider} that are {@link #isEnabled()} within a specific context. * * @param context the context in which to get the list. * @return a list of {@link CredentialsProvider} that are {@link #isEnabled()}. * @since 2.0 */ public static List<CredentialsProvider> enabled(Object context) { List<CredentialsProvider> providers = new ArrayList<CredentialsProvider>( ExtensionList.lookup(CredentialsProvider.class)); for (Iterator<CredentialsProvider> iterator = providers.iterator(); iterator.hasNext();) { CredentialsProvider p = iterator.next(); if (!p.isEnabled(context)) { iterator.remove(); } } return providers; } /** * {@inheritDoc} */ @Override public Descriptor<CredentialsProvider> getDescriptor() { return this; } /** * Returns {@code true} if this {@link CredentialsProvider} is enabled. * * @return {@code true} if this {@link CredentialsProvider} is enabled. * @since 2.0 */ public final boolean isEnabled() { return CredentialsProviderManager.isEnabled(this); } /** * Returns {@code true} if this {@link CredentialsProvider} is enabled in the specified context. * * @param context the context. * @return {@code true} if this {@link CredentialsProvider} is enabled in the specified context. * @since 2.0 */ public boolean isEnabled(Object context) { if (!isEnabled()) { return false; } for (DescriptorVisibilityFilter filter : DescriptorVisibilityFilter.all()) { if (!filter.filter(context, this)) { return false; } } return true; } /** * {@inheritDoc} */ @Override public String getDisplayName() { return StringUtils.join(StringUtils.splitByCharacterTypeCamelCase(getClass().getSimpleName()), ' '); } /** * {@inheritDoc} */ @Override public String getIconClassName() { return "icon-credentials-credentials"; } /** * Returns the scopes allowed for credentials stored within the specified object or {@code null} if the * object is not relevant for scopes and the object's container should be considered instead. * * @param object the object. * @return the set of scopes that are relevant for the object or {@code null} if the object is not a credentials * container. */ public Set<CredentialsScope> getScopes(ModelObject object) { return null; } /** * Returns the {@link CredentialsStore} that this {@link CredentialsProvider} maintains specifically for this * {@link ModelObject} or {@code null} if either the object is not a credentials container or this * {@link CredentialsProvider} does not maintain a store specifically bound to this {@link ModelObject}. * * @param object the {@link Item} or {@link ItemGroup} or {@link User} that the store is being requested of. * @return either {@code null} or a scoped {@link CredentialsStore} where * {@link com.cloudbees.plugins.credentials.CredentialsStore#getContext()} {@code == object}. * @since 1.8 */ @CheckForNull public CredentialsStore getStore(@CheckForNull ModelObject object) { return null; } /** * Returns the credentials provided by this provider which are available to the specified {@link Authentication} * for items in the specified {@link ItemGroup} * * @param type the type of credentials to return. * @param itemGroup the item group (if {@code null} assume {@link hudson.model.Hudson#getInstance()}. * @param authentication the authentication (if {@code null} assume {@link hudson.security.ACL#SYSTEM}. * @param <C> the credentials type. * @return the list of credentials. */ @NonNull public abstract <C extends Credentials> List<C> getCredentials(@NonNull Class<C> type, @Nullable ItemGroup itemGroup, @Nullable Authentication authentication); /** * Returns the credentials provided by this provider which are available to the specified {@link Authentication} * for items in the specified {@link ItemGroup} and are appropriate for the specified {@link com.cloudbees * .plugins.credentials.domains.DomainRequirement}s. * * @param type the type of credentials to return. * @param itemGroup the item group (if {@code null} assume {@link hudson.model.Hudson#getInstance()}. * @param authentication the authentication (if {@code null} assume {@link hudson.security.ACL#SYSTEM}. * @param domainRequirements the credential domains to match (if the {@link CredentialsProvider} does not support * {@link DomainRequirement}s then it should * assume the match is true). * @param <C> the credentials type. * @return the list of credentials. * @since 1.5 */ @NonNull public <C extends Credentials> List<C> getCredentials(@NonNull Class<C> type, @Nullable ItemGroup itemGroup, @Nullable Authentication authentication, @NonNull List<DomainRequirement> domainRequirements) { return getCredentials(type, itemGroup, authentication); } /** * Returns a {@link ListBoxModel} of the credentials provided by this provider which are available to the * specified {@link Authentication} for items in the specified {@link ItemGroup} and are appropriate for the * specified {@link DomainRequirement}s. * <strong>NOTE:</strong> implementations are recommended to override this method if the actual secret information * is being stored external from Jenkins and the non-secret information can be accessed with lesser tracability * requirements. The default implementation just uses {@link #getCredentials(Class, Item, Authentication, List)} * to build the {@link ListBoxModel}. Handling the {@link CredentialsMatcher} may require standing up a proxy * instance to apply the matcher against if {@link CredentialsMatchers#describe(CredentialsMatcher)} returns * {@code null} * * @param <C> the credentials type. * @param type the type of credentials to return. * @param itemGroup the item group (if {@code null} assume {@link hudson.model.Hudson#getInstance()}. * @param authentication the authentication (if {@code null} assume {@link ACL#SYSTEM}. * @param domainRequirements the credential domain to match. * @param matcher the additional filtering to apply to the credentials * @return the {@link ListBoxModel} of {@link IdCredentials#getId()} with names provided by * {@link CredentialsNameProvider}. * @since 2.1.0 */ @NonNull public <C extends IdCredentials> ListBoxModel getCredentialIds(@NonNull Class<C> type, @Nullable ItemGroup itemGroup, @Nullable Authentication authentication, @NonNull List<DomainRequirement> domainRequirements, @NonNull CredentialsMatcher matcher) { ListBoxModel result = new ListBoxModel(); for (IdCredentials c : getCredentials(type, itemGroup, authentication, domainRequirements)) { if (matcher.matches(c)) { result.add(CredentialsNameProvider.name(c), c.getId()); } } return result; } /** * Returns the credentials provided by this provider which are available to the specified {@link Authentication} * for the specified {@link Item} * * @param type the type of credentials to return. * @param item the item. * @param authentication the authentication (if {@code null} assume {@link hudson.security.ACL#SYSTEM}. * @param <C> the credentials type. * @return the list of credentials. */ @NonNull public <C extends Credentials> List<C> getCredentials(@NonNull Class<C> type, @NonNull Item item, @Nullable Authentication authentication) { item.getClass(); return getCredentials(type, item.getParent(), authentication); } /** * Returns the credentials provided by this provider which are available to the specified {@link Authentication} * for the specified {@link Item} and are appropriate for the specified {@link DomainRequirement}s. * * @param type the type of credentials to return. * @param item the item. * @param authentication the authentication (if {@code null} assume {@link hudson.security.ACL#SYSTEM}. * @param domainRequirements the credential domain to match. * @param <C> the credentials type. * @return the list of credentials. * @since 1.5 */ @NonNull public <C extends Credentials> List<C> getCredentials(@NonNull Class<C> type, @NonNull Item item, @Nullable Authentication authentication, @NonNull List<DomainRequirement> domainRequirements) { return getCredentials(type, item instanceof ItemGroup ? (ItemGroup) item : item.getParent(), authentication, domainRequirements); } /** * Returns a {@link ListBoxModel} of the credentials provided by this provider which are available to the * specified {@link Authentication} for the specified {@link Item} and are appropriate for the * specified {@link DomainRequirement}s. * <strong>NOTE:</strong> implementations are recommended to override this method if the actual secret information * is being stored external from Jenkins and the non-secret information can be accessed with lesser tracability * requirements. The default implementation just uses {@link #getCredentials(Class, Item, Authentication, List)} * to build the {@link ListBoxModel}. Handling the {@link CredentialsMatcher} may require standing up a proxy * instance to apply the matcher against. * * @param type the type of credentials to return. * @param item the item. * @param authentication the authentication (if {@code null} assume {@link hudson.security.ACL#SYSTEM}. * @param domainRequirements the credential domain to match. * @param matcher the additional filtering to apply to the credentials * @param <C> the credentials type. * @return the {@link ListBoxModel} of {@link IdCredentials#getId()} with names provided by * {@link CredentialsNameProvider}. * @since 2.1.0 */ @NonNull public <C extends IdCredentials> ListBoxModel getCredentialIds(@NonNull Class<C> type, @NonNull Item item, @Nullable Authentication authentication, @NonNull List<DomainRequirement> domainRequirements, @NonNull CredentialsMatcher matcher) { if (item instanceof ItemGroup) { return getCredentialIds(type, (ItemGroup) item, authentication, domainRequirements, matcher); } ListBoxModel result = new ListBoxModel(); for (IdCredentials c : getCredentials(type, item, authentication, domainRequirements)) { if (matcher.matches(c)) { result.add(CredentialsNameProvider.name(c), c.getId()); } } return result; } /** * Returns {@code true} if this {@link CredentialsProvider} can provide credentials of the supplied type. * * @param clazz the base type of {@link Credentials} to check. * @return {@code true} if and only if there is at least one {@link CredentialsDescriptor} matching the required * {@link Credentials} interface that {@link #isApplicable(Descriptor)}. * @since 2.0 */ public final boolean isApplicable(Class<? extends Credentials> clazz) { if (!isEnabled()) { return false; } for (CredentialsDescriptor d : ExtensionList.lookup(CredentialsDescriptor.class)) { if (clazz.isAssignableFrom(d.clazz) && isApplicable(d)) { return true; } } return false; } /** * Returns {@code true} if the supplied {@link Descriptor} is applicable to this {@link CredentialsProvider}. * * @param descriptor the {@link Descriptor} to check. * @return {@code true} if and only if the supplied {@link Descriptor} is applicable in this * {@link CredentialsProvider}. * @since 2.0 */ public final boolean isApplicable(Descriptor<?> descriptor) { if (!isEnabled()) { return false; } if (descriptor instanceof CredentialsDescriptor) { if (!((CredentialsDescriptor) descriptor).isApplicable(this)) { return false; } } for (DescriptorVisibilityFilter filter : DescriptorVisibilityFilter.all()) { if (!filter.filter(this, descriptor)) { return false; } } return _isApplicable(descriptor); } /** * {@link CredentialsProvider} subtypes can override this method to veto some {@link Descriptor}s * from being available from their store. This is often useful when you are building * a custom store that holds a specific type of credentials or where you want to limit the * number of choices given to the users. * * @param descriptor the {@link Descriptor} to check. * @return {@code true} if the supplied {@link Descriptor} is applicable in this {@link CredentialsProvider} * @since 2.0 */ protected boolean _isApplicable(Descriptor<?> descriptor) { return true; } /** * Returns the list of {@link CredentialsDescriptor} instances that are applicable within this * {@link CredentialsProvider}. * * @return the list of {@link CredentialsDescriptor} instances that are applicable within this * {@link CredentialsProvider}. * @since 2.0 */ public final List<CredentialsDescriptor> getCredentialsDescriptors() { List<CredentialsDescriptor> result = DescriptorVisibilityFilter.apply(this, ExtensionList.lookup(CredentialsDescriptor.class)); if (!(result instanceof ArrayList)) { // should never happen, but let's be defensive in case the DescriptorVisibilityFilter contract changes result = new ArrayList<CredentialsDescriptor>(result); } for (Iterator<CredentialsDescriptor> iterator = result.iterator(); iterator.hasNext();) { CredentialsDescriptor d = iterator.next(); if (!_isApplicable(d)) { iterator.remove(); } } return result; } /** * Checks if there is at least one {@link CredentialsDescriptor} applicable within this {@link CredentialsProvider}. * * @return {@code true} if and ony if there is at least one {@link CredentialsDescriptor} applicable within this * {@link CredentialsProvider}. * @since 2.0 */ public final boolean hasCredentialsDescriptors() { ExtensionList<DescriptorVisibilityFilter> filters = DescriptorVisibilityFilter.all(); OUTER: for (CredentialsDescriptor d : ExtensionList.lookup(CredentialsDescriptor.class)) { for (DescriptorVisibilityFilter f : filters) { if (!f.filter(this, d)) { // not visible, let's try the next descriptor continue OUTER; } } if (_isApplicable(d)) { return true; } } return false; } /** * Retrieves the {@link Fingerprint} for a specific credential. * * @param c the credential. * @return the {@link Fingerprint} or {@code null} if the credential has no fingerprint associated with it. * @throws IOException if the credential's fingerprint hash could not be computed. * @since 2.1.1 */ @CheckForNull public static Fingerprint getFingerprintOf(@NonNull Credentials c) throws IOException { try { MessageDigest md5 = MessageDigest.getInstance("MD5"); DigestOutputStream out = new DigestOutputStream(new NullOutputStream(), md5); try { FINGERPRINT_XML.toXML(c, new OutputStreamWriter(out, Charset.forName("UTF-8"))); } finally { IOUtils.closeQuietly(out); } return Jenkins.getActiveInstance().getFingerprintMap().get(Util.toHexString(md5.digest())); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("JLS mandates MD5 as a supported digest algorithm"); } } /** * Creates a fingerprint that can be used to track the usage of a specific credential. * * @param c the credential to fingerprint. * @return the Fingerprint. * @throws IOException if the credential's fingerprint hash could not be computed. * @since 2.1.1 */ @NonNull public static Fingerprint getOrCreateFingerprintOf(@NonNull Credentials c) throws IOException { String pseudoFilename = String.format("Credential id=%s name=%s", c instanceof IdCredentials ? ((IdCredentials) c).getId() : "unknown", CredentialsNameProvider.name(c)); try { MessageDigest md5 = MessageDigest.getInstance("MD5"); DigestOutputStream out = new DigestOutputStream(new NullOutputStream(), md5); try { FINGERPRINT_XML.toXML(c, new OutputStreamWriter(out, Charset.forName("UTF-8"))); } finally { IOUtils.closeQuietly(out); } return Jenkins.getActiveInstance().getFingerprintMap().getOrCreate(null, pseudoFilename, md5.digest()); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("JLS mandates MD5 as a supported digest algorithm"); } } /** * Track the usage of credentials in a specific build. * * @param build the run to tag the fingerprint * @param credentials the credentials to fingerprint. * @param <C> the credentials type. * @return the supplied credentials for method chaining. * @since 2.1.1 */ @CheckForNull public static <C extends Credentials> C track(@NonNull Run build, @CheckForNull C credentials) { if (credentials != null) { trackAll(build, Collections.singletonList(credentials)); } return credentials; } /** * Track the usage of credentials in a specific build. * * @param build the run to tag the fingerprint * @param credentials the credentials to fingerprint. * @param <C> the credentials type. * @return the supplied credentials for method chaining. * @since 2.1.1 */ @NonNull public static <C extends Credentials> List<C> trackAll(@NonNull Run build, C... credentials) { if (credentials != null) { return trackAll(build, Arrays.asList(credentials)); } return Collections.emptyList(); } /** * Track the usage of credentials in a specific build. * * @param build the run to tag the fingerprint * @param credentials the credentials to fingerprint. * @param <C> the credentials type. * @return the supplied credentials for method chaining. * @since 2.1.1 */ @NonNull public static <C extends Credentials> List<C> trackAll(@NonNull Run build, @NonNull List<C> credentials) { for (Credentials c : credentials) { if (c != null) { try { getOrCreateFingerprintOf(c).addFor(build); } catch (IOException e) { LOGGER.log(Level.FINEST, "Could not track usage of " + c, e); } } } return credentials; } /** * Track the usage of credentials in a specific node. * Would be used for example when launching an agent. * @param node the node to tag the fingerprint * @param credentials the credentials to fingerprint. * @param <C> the credentials type. * @return the supplied credentials for method chaining. * @since 2.1.1 */ @CheckForNull public static <C extends Credentials> C track(@NonNull Node node, @CheckForNull C credentials) { if (credentials != null) { trackAll(node, Collections.singletonList(credentials)); } return credentials; } /** * Track the usage of credentials in a specific node. * Would be used for example when launching an agent. * @param node the node to tag the fingerprint * @param credentials the credentials to fingerprint. * @param <C> the credentials type. * @return the supplied credentials for method chaining. * @since 2.1.1 */ @NonNull public static <C extends Credentials> List<C> trackAll(@NonNull Node node, C... credentials) { if (credentials != null) { return trackAll(node, Arrays.asList(credentials)); } return Collections.emptyList(); } /** * Track the usage of credentials in a specific node. * Would be used for example when launching an agent. * @param node the node to tag the fingerprint * @param credentials the credentials to fingerprint. * @param <C> the credentials type. * @return the supplied credentials for method chaining. * @since 2.1.1 */ @NonNull public static <C extends Credentials> List<C> trackAll(@NonNull Node node, @NonNull List<C> credentials) { long timestamp = System.currentTimeMillis(); String nodeName = node.getNodeName(); for (Credentials c : credentials) { if (c != null) { try { Fingerprint fingerprint = getOrCreateFingerprintOf(c); BulkChange change = new BulkChange(fingerprint); try { Collection<FingerprintFacet> facets = fingerprint.getFacets(); // purge any old facets long start = timestamp; for (Iterator<FingerprintFacet> iterator = facets.iterator(); iterator.hasNext();) { FingerprintFacet f = iterator.next(); if (f instanceof NodeCredentialsFingerprintFacet && StringUtils.equals(nodeName, ((NodeCredentialsFingerprintFacet) f).getNodeName())) { start = Math.min(start, f.getTimestamp()); iterator.remove(); } } // add in the new one facets.add(new NodeCredentialsFingerprintFacet(node, fingerprint, start, timestamp)); } finally { change.commit(); } } catch (IOException e) { LOGGER.log(Level.FINEST, "Could not track usage of " + c, e); } } } return credentials; } /** * Track the usage of credentials in a specific item but not associated with a specific build, for example SCM * polling. * * @param item the item to tag the fingerprint against * @param credentials the credentials to fingerprint. * @param <C> the credentials type. * @return the supplied credentials for method chaining. * @since 2.1.1 */ @CheckForNull public static <C extends Credentials> C track(@NonNull Item item, @CheckForNull C credentials) { if (credentials != null) { trackAll(item, Collections.singletonList(credentials)); } return credentials; } /** * Track the usage of credentials in a specific item but not associated with a specific build, for example SCM * polling. * * @param item the item to tag the fingerprint against * @param credentials the credentials to fingerprint. * @param <C> the credentials type. * @return the supplied credentials for method chaining. * @since 2.1.1 */ @NonNull public static <C extends Credentials> List<C> trackAll(@NonNull Item item, C... credentials) { if (credentials != null) { return trackAll(item, Arrays.asList(credentials)); } return Collections.emptyList(); } /** * Track the usage of credentials in a specific item but not associated with a specific build, for example SCM * polling. * * @param item the item to tag the fingerprint against * @param credentials the credentials to fingerprint. * @param <C> the credentials type. * @return the supplied credentials for method chaining. * @since 2.1.1 */ @NonNull public static <C extends Credentials> List<C> trackAll(@NonNull Item item, @NonNull List<C> credentials) { long timestamp = System.currentTimeMillis(); String fullName = item.getFullName(); for (Credentials c : credentials) { if (c != null) { try { Fingerprint fingerprint = getOrCreateFingerprintOf(c); BulkChange change = new BulkChange(fingerprint); try { Collection<FingerprintFacet> facets = fingerprint.getFacets(); // purge any old facets long start = timestamp; for (Iterator<FingerprintFacet> iterator = facets.iterator(); iterator.hasNext();) { FingerprintFacet f = iterator.next(); if (f instanceof ItemCredentialsFingerprintFacet && StringUtils.equals(fullName, ((ItemCredentialsFingerprintFacet) f).getItemFullName())) { start = Math.min(start, f.getTimestamp()); iterator.remove(); } } // add in the new one facets.add(new ItemCredentialsFingerprintFacet(item, fingerprint, start, timestamp)); } finally { change.commit(); } } catch (IOException e) { LOGGER.log(Level.FINEST, "Could not track usage of " + c, e); } } } return credentials; } /** * A helper method for Groovy Scripting to address use cases such as JENKINS-39317 where all credential stores * need to be resaved. As this is a potentially very expensive operation the method has been marked * {@link DoNotUse} in order to ensure that no plugin attempts to call this method. If invoking this method * from an {@code init.d} Groovy script, ensure that the call is guarded by a marker file such that * * @throws IOException if things go wrong. */ @Restricted(DoNotUse.class) // Do not use from plugins public static void saveAll() throws IOException { LOGGER.entering(CredentialsProvider.class.getName(), "saveAll"); try { final Jenkins jenkins = Jenkins.getActiveInstance(); jenkins.checkPermission(Jenkins.ADMINISTER); LOGGER.log(Level.INFO, "Forced save credentials stores: Requested by {0}", StringUtils.defaultIfBlank(Jenkins.getAuthentication().getName(), "anonymous")); Timer.get().execute(new Runnable() { @Override public void run() { SecurityContext ctx = ACL.impersonate(ACL.SYSTEM); try { if (jenkins.getInitLevel().compareTo(InitMilestone.JOB_LOADED) < 0) { LOGGER.log(Level.INFO, "Forced save credentials stores: Initialization has not completed"); while (jenkins.getInitLevel().compareTo(InitMilestone.JOB_LOADED) < 0) { LOGGER.log(Level.INFO, "Forced save credentials stores: Sleeping for 1 second"); try { Thread.sleep(1000); } catch (InterruptedException e) { LOGGER.log(Level.WARNING, "Forced save credentials stores: Aborting due to interrupt", e); return; } } LOGGER.log(Level.INFO, "Forced save credentials stores: Initialization has completed"); } LOGGER.log(Level.INFO, "Forced save credentials stores: Processing Jenkins"); for (CredentialsStore s : lookupStores(jenkins)) { try { s.save(); } catch (IOException e) { LOGGER.log(Level.WARNING, "Forced save credentials stores: Could not save " + s, e); } } LOGGER.log(Level.INFO, "Forced save credentials stores: Processing Items..."); int count = 0; for (Item item : jenkins.getAllItems(Item.class)) { count++; if (count % 100 == 0) { LOGGER.log(Level.INFO, "Forced save credentials stores: Processing Items ({0} processed)", count); } for (CredentialsStore s : lookupStores(item)) { if (item == s.getContext()) { // only save if the store is associated with this context item as otherwise will // have been saved already / later try { s.save(); } catch (IOException e) { LOGGER.log(Level.WARNING, "Forced save credentials stores: Could not save " + s, e); } } } } LOGGER.log(Level.INFO, "Forced save credentials stores: Processing Users..."); count = 0; for (User user : User.getAll()) { count++; if (count % 100 == 0) { LOGGER.log(Level.INFO, "Forced save credentials stores: Processing Users ({0} processed)", count); } // HACK ALERT we just want to access the user's stores so we do just enough impersonation // to ensure that User.current() == user // while we could use User.impersonate() that would force a query against the backing // SecurityRealm to revalidate ACL.impersonate(new UsernamePasswordAuthenticationToken(user.getId(), "", new GrantedAuthority[] { SecurityRealm.AUTHENTICATED_AUTHORITY })); for (CredentialsStore s : lookupStores(user)) { if (user == s.getContext()) { // only save if the store is associated with this context item as otherwise will // have been saved already / later try { s.save(); } catch (IOException e) { LOGGER.log(Level.WARNING, "Forced save credentials stores: Could not save " + s, e); } } } } } finally { LOGGER.log(Level.INFO, "Forced save credentials stores: Completed"); SecurityContextHolder.setContext(ctx); } } }); } finally { LOGGER.exiting(CredentialsProvider.class.getName(), "saveAll"); } } /** * A {@link Comparator} for {@link ListBoxModel.Option} instances. * * @since 2.1.0 */ private static class ListBoxModelOptionComparator implements Comparator<ListBoxModel.Option> { /** * The locale to compare with. */ private final Locale locale; /** * The {@link Collator} */ private transient Collator collator; public ListBoxModelOptionComparator() { StaplerRequest req = Stapler.getCurrentRequest(); if (req != null) { locale = req.getLocale(); } else { locale = Locale.getDefault(); } collator = Collator.getInstance(locale); } /** * {@inheritDoc} */ @Override public int compare(ListBoxModel.Option o1, ListBoxModel.Option o2) { return collator.compare(o1.name.toLowerCase(locale), o2.name.toLowerCase(locale)); } } }