/*
* 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.TaskListener;
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.SECRETS_REDACTED;
/**
* 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 {
SECRETS_REDACTED.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 {
SECRETS_REDACTED.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));
}
}
}