From 2f8268d5a0eaf7844743de1e73d08816a7bc62d4 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Tue, 18 Aug 2015 15:32:18 +0100 Subject: [PATCH] [JENKINS-29370] Need the ability to convert the best match of a selection of credentials --- .../tokens/api/AuthenticationTokens.java | 81 ++++++++++++++++++- .../api/AuthenticationTokenContextTest.java | 12 ++- 2 files changed, 89 insertions(+), 4 deletions(-) diff --git a/src/main/java/jenkins/authentication/tokens/api/AuthenticationTokens.java b/src/main/java/jenkins/authentication/tokens/api/AuthenticationTokens.java index d23a462..ac7292d 100644 --- a/src/main/java/jenkins/authentication/tokens/api/AuthenticationTokens.java +++ b/src/main/java/jenkins/authentication/tokens/api/AuthenticationTokens.java @@ -28,16 +28,18 @@ import com.cloudbees.plugins.credentials.CredentialsMatchers; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; -import jenkins.model.Jenkins; - +import java.util.AbstractMap; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; +import jenkins.model.Jenkins; /** * Utility class for manipulating authentication tokens. @@ -160,4 +162,79 @@ public static T convert(@NonNull AuthenticationTokenC return null; } + /** + * Converts the best match of the supplied credentials into the specified token. + * + * @param context the context that an authentication token is required in. + * @param credentials the credentials instances to try and convert. + * @param the type of token to convert to. + * @param the type of credentials to convert, + * @return the token or {@code null} if the credentials could not be converted. + * @since 1.2 + */ + @SuppressWarnings("unchecked") + @CheckForNull + public static T convert(@NonNull AuthenticationTokenContext context, + @NonNull C... credentials) { + return convert(context, Arrays.asList(credentials)); + } + + /** + * Converts the best match of the supplied credentials into the specified token. + * + * @param context the context that an authentication token is required in. + * @param credentials the credentials instances to try and convert. + * @param the type of token to convert to. + * @param the type of credentials to convert, + * @return the token or {@code null} if the credentials could not be converted. + * @since 1.2 + */ + @SuppressWarnings("unchecked") + @CheckForNull + public static T convert(@NonNull AuthenticationTokenContext context, + @NonNull List credentials) { + // we want the best match first + SortedMap> matches = + new TreeMap>( + Collections.reverseOrder()); + for (C credential : credentials) { + for (AuthenticationTokenSource source : Jenkins.getInstance() + .getExtensionList(AuthenticationTokenSource.class)) { + Integer score = source.score(context, credential); + if (score != null && !matches.containsKey(score)) { + // if there are two extensions with the same score, + // then the first (i.e. highest Extension.ordinal should win) + // if there are two credentials with the same scoe, + // then the first in the list should win. + matches.put(score, new AbstractMap.SimpleEntry(credential, source)); + } + } + } + // now try all the matches (form best to worst) until we get a conversion + for (Map.Entry entry : matches.values()) { + C credential = entry.getKey(); + AuthenticationTokenSource source = entry.getValue(); + if (source.produces(context.getTokenClass()) && source + .consumes(credential)) { // redundant test, but for safety + AuthenticationTokenSource s = + (AuthenticationTokenSource) source; + T token = null; + try { + token = s.convert(credential); + } catch (AuthenticationTokenException e) { + LogRecord lr = new LogRecord(Level.FINE, + "Could not convert credentials {0} into token of type {1} using source {2}: {3}"); + lr.setThrown(e); + lr.setParameters(new Object[]{credentials, context.getTokenClass(), s, e.getMessage()}); + LOGGER.log(lr); + } + if (token != null) { + return token; + } + } + } + + return null; + } + } diff --git a/src/test/java/jenkins/authentication/tokens/api/AuthenticationTokenContextTest.java b/src/test/java/jenkins/authentication/tokens/api/AuthenticationTokenContextTest.java index 1d670e6..8edb13c 100644 --- a/src/test/java/jenkins/authentication/tokens/api/AuthenticationTokenContextTest.java +++ b/src/test/java/jenkins/authentication/tokens/api/AuthenticationTokenContextTest.java @@ -33,7 +33,10 @@ public void smokes() { UsernamePasswordCredentials p = new UsernamePasswordCredentialsImpl(CredentialsScope.GLOBAL, "test", null, "bob", "secret"); assertThat(AuthenticationTokens.matcher(context).matches(p), is(true)); - assertThat(AuthenticationTokens.matcher(context).matches(new CertificateCredentialsImpl(CredentialsScope.GLOBAL, "test2", null, null, new CertificateCredentialsImpl.UploadedKeyStoreSource(null))), is( + CertificateCredentialsImpl q = new CertificateCredentialsImpl(CredentialsScope.GLOBAL, "test2", null, null, + new CertificateCredentialsImpl.UploadedKeyStoreSource(null)); + assertThat(AuthenticationTokens.matcher(context).matches( + q), is( false)); HttpAuthenticator authenticator = AuthenticationTokens.convert(context, p); @@ -63,7 +66,12 @@ public void smokes() { assertThat(authenticator, notNullValue()); assertThat(authenticator, instanceOf(DigestAuthenticator.class)); assertThat(authenticator.getHeader("foo:"), is(Util.getDigestOf("foo:bob:secret"))); - + + authenticator = AuthenticationTokens.convert(context, q, p); + assertThat(authenticator, notNullValue()); + assertThat(authenticator, instanceOf(DigestAuthenticator.class)); + assertThat(authenticator.getHeader("foo:"), is(Util.getDigestOf("foo:bob:secret"))); + context = AuthenticationTokenContext.builder(HttpAuthenticator.class) .with(HttpAuthenticator.class, "certificate") .build();