Skip to content

Commit 079e673

Browse files
author
Ronald Holshausen
authoredOct 27, 2019
Merge pull request #951 from tinexw/925-bearer-token
Support bearer token with JUnit annotations - Fixes gh-925
2 parents 3175a14 + c89ecfb commit 079e673

File tree

6 files changed

+219
-21
lines changed

6 files changed

+219
-21
lines changed
 

‎provider/pact-jvm-provider-junit/README.md

+12-5
Original file line numberDiff line numberDiff line change
@@ -227,9 +227,9 @@ The following keys may be managed through the environment
227227
* `pactbroker.port`
228228
* `pactbroker.scheme`
229229
* `pactbroker.tags` (comma separated)
230-
* `pactbroker.auth.scheme`
231-
* `pactbroker.auth.username`
232-
* `pactbroker.auth.password`
230+
* `pactbroker.auth.username` (for basic auth)
231+
* `pactbroker.auth.password` (for basic auth)
232+
* `pactbroker.auth.token` (for bearer auth)
233233

234234

235235
#### Using tags with the pact broker
@@ -246,7 +246,7 @@ For any other value the latest pact tagged with the specified tag is loaded.
246246

247247
Specifying multiple tags is an OR operation. For example if you specify `tags = {"dev", "prod"}` then both the latest pact file tagged with `dev` and the latest pact file taggged with `prod` is loaded.
248248

249-
#### Using basic auth with the with the pact broker
249+
#### Using authentication with the with the pact broker
250250

251251
You can use basic authentication with the `@PactBroker` annotation by setting the `authentication` value to a `@PactBrokerAuth`
252252
annotation. For example:
@@ -256,7 +256,14 @@ annotation. For example:
256256
authentication = @PactBrokerAuth(username = "test", password = "test"))
257257
```
258258

259-
The `username` and `password` values also take Java system property expressions.
259+
Bearer tokens are also supported. For example:
260+
261+
```java
262+
@PactBroker(host = "${pactbroker.url:localhost}", port = "1234", tags = {"latest", "prod", "dev"},
263+
authentication = @PactBrokerAuth(token = "test"))
264+
```
265+
266+
The `token`, `username` and `password` values also take Java system property expressions.
260267

261268
Preemptive Authentication can be enabled by setting the `pact.pactbroker.httpclient.usePreemptiveAuthentication` Java
262269
system property to `true`.

‎provider/pact-jvm-provider-junit/src/test/groovy/au/com/dius/pact/provider/junit/loader/PactBrokerLoaderSpec.groovy

+140
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import au.com.dius.pact.core.pactbroker.IHalClient
55
import au.com.dius.pact.core.pactbroker.InvalidHalResponse
66
import au.com.dius.pact.core.pactbroker.PactBrokerClient
77
import au.com.dius.pact.core.pactbroker.PactBrokerConsumer
8+
import au.com.dius.pact.core.support.expressions.SystemPropertyResolver
89
import au.com.dius.pact.core.support.expressions.ValueResolver
910
import au.com.dius.pact.provider.ConsumerInfo
1011
import spock.lang.Specification
@@ -408,6 +409,104 @@ class PactBrokerLoaderSpec extends Specification {
408409
1 * brokerClient.fetchConsumers('test') >> []
409410
}
410411

412+
def 'Auth: Uses basic auth if no auth is provided'() {
413+
given:
414+
pactBrokerLoader = {
415+
new PactBrokerLoader(PactBrokerAnnotationAuthNotSet.getAnnotation(PactBroker))
416+
}
417+
418+
when:
419+
def pactBrokerClient = pactBrokerLoader()
420+
.newPactBrokerClient(new URI('http://localhost'), new SystemPropertyResolver())
421+
422+
then:
423+
pactBrokerClient.options == ['authentication': ['basic', '', '']]
424+
}
425+
426+
def 'Auth: Uses provided auth scheme if it is provided'() {
427+
given:
428+
pactBrokerLoader = {
429+
new PactBrokerLoader(PactBrokerAnnotationAuthWithSchemeSet.getAnnotation(PactBroker))
430+
}
431+
432+
when:
433+
def pactBrokerClient = pactBrokerLoader()
434+
.newPactBrokerClient(new URI('http://localhost'), new SystemPropertyResolver())
435+
436+
then:
437+
pactBrokerClient.options == ['authentication': ['foobar', 'user', 'pw']]
438+
}
439+
440+
def 'Auth: Uses basic auth if username and password are provided'() {
441+
given:
442+
pactBrokerLoader = {
443+
new PactBrokerLoader(PactBrokerAnnotationWithUsernameAndPassword.getAnnotation(PactBroker))
444+
}
445+
446+
when:
447+
def pactBrokerClient = pactBrokerLoader()
448+
.newPactBrokerClient(new URI('http://localhost'), new SystemPropertyResolver())
449+
450+
then:
451+
pactBrokerClient.options == ['authentication': ['basic', 'user', 'pw']]
452+
}
453+
454+
def 'Auth: Uses basic auth if username and token are provided'() {
455+
given:
456+
pactBrokerLoader = {
457+
new PactBrokerLoader(PactBrokerAnnotationAuthWithUsernameAndToken.getAnnotation(PactBroker))
458+
}
459+
460+
when:
461+
def pactBrokerClient = pactBrokerLoader()
462+
.newPactBrokerClient(new URI('http://localhost'), new SystemPropertyResolver())
463+
464+
then:
465+
pactBrokerClient.options == ['authentication': ['basic', 'user', '']]
466+
}
467+
468+
def 'Auth: Uses bearer auth if token is provided'() {
469+
given:
470+
pactBrokerLoader = {
471+
new PactBrokerLoader(PactBrokerAnnotationWithOnlyToken.getAnnotation(PactBroker))
472+
}
473+
474+
when:
475+
def pactBrokerClient = pactBrokerLoader()
476+
.newPactBrokerClient(new URI('http://localhost'), new SystemPropertyResolver())
477+
478+
then:
479+
pactBrokerClient.options == ['authentication': ['bearer', 'token-value']]
480+
}
481+
482+
def 'Auth: Uses bearer auth if token and password are provided'() {
483+
given:
484+
pactBrokerLoader = {
485+
new PactBrokerLoader(PactBrokerAnnotationWithPasswordAndToken.getAnnotation(PactBroker))
486+
}
487+
488+
when:
489+
def pactBrokerClient = pactBrokerLoader()
490+
.newPactBrokerClient(new URI('http://localhost'), new SystemPropertyResolver())
491+
492+
then:
493+
pactBrokerClient.options == ['authentication': ['bearer', 'token-value']]
494+
}
495+
496+
def 'Auth: Fails if neither token nor username is provided'() {
497+
given:
498+
pactBrokerLoader = {
499+
new PactBrokerLoader(PactBrokerAnnotationEmptyAuth.getAnnotation(PactBroker))
500+
}
501+
502+
when:
503+
pactBrokerLoader().newPactBrokerClient(new URI('http://localhost'), new SystemPropertyResolver())
504+
505+
then:
506+
IllegalArgumentException exception = thrown(IllegalArgumentException)
507+
exception.message == 'Invalid pact authentication specified. Either username or token must be set.'
508+
}
509+
411510
@PactBroker(host = 'pactbroker.host', port = '1000')
412511
static class FullPactBrokerAnnotation {
413512

@@ -428,4 +527,45 @@ class PactBrokerLoaderSpec extends Specification {
428527

429528
}
430529

530+
@PactBroker(host = 'pactbroker.host')
531+
static class PactBrokerAnnotationAuthNotSet {
532+
533+
}
534+
535+
@PactBroker(host = 'pactbroker.host',
536+
authentication = @PactBrokerAuth(scheme = 'foobar', username = 'user', password = 'pw'))
537+
static class PactBrokerAnnotationAuthWithSchemeSet {
538+
539+
}
540+
541+
@PactBroker(host = 'pactbroker.host',
542+
authentication = @PactBrokerAuth(username = 'user', password = 'pw'))
543+
static class PactBrokerAnnotationWithUsernameAndPassword {
544+
545+
}
546+
547+
@PactBroker(host = 'pactbroker.host',
548+
authentication = @PactBrokerAuth(username = 'user', token = 'ignored'))
549+
static class PactBrokerAnnotationAuthWithUsernameAndToken {
550+
551+
}
552+
553+
@PactBroker(host = 'pactbroker.host',
554+
authentication = @PactBrokerAuth(password = 'pw', token = 'token-value'))
555+
static class PactBrokerAnnotationWithPasswordAndToken {
556+
557+
}
558+
559+
@PactBroker(host = 'pactbroker.host',
560+
authentication = @PactBrokerAuth(token = 'token-value'))
561+
static class PactBrokerAnnotationWithOnlyToken {
562+
563+
}
564+
565+
@PactBroker(host = 'pactbroker.host',
566+
authentication = @PactBrokerAuth)
567+
static class PactBrokerAnnotationEmptyAuth {
568+
569+
}
570+
431571
}

‎provider/pact-jvm-provider-junit/src/test/kotlin/au/com/dius/pact/provider/junit/PactBrokerAnnotationDefaultsTest.kt

+13-2
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ class PactBrokerAnnotationDefaultsTest {
100100
}
101101

102102
@Test
103-
fun `default auth scheme is basic`() {
104-
assertThat(parseExpression(annotation.authentication.scheme), `is`("basic"))
103+
fun `default auth scheme is legacy`() {
104+
assertThat(parseExpression(annotation.authentication.scheme), `is`("legacy"))
105105
}
106106

107107
@Test
@@ -132,6 +132,17 @@ class PactBrokerAnnotationDefaultsTest {
132132
assertThat(parseListExpression(annotation.authentication.password), contains("myPass"))
133133
}
134134

135+
@Test
136+
fun `default auth token is empty`() {
137+
assertThat(parseExpression(annotation.authentication.token), `is`(""))
138+
}
139+
140+
@Test
141+
fun `can set auth token`() {
142+
props.setProperty("pactbroker.auth.token", "myToken")
143+
assertThat(parseListExpression(annotation.authentication.token), contains("myToken"))
144+
}
145+
135146
@PactBroker
136147
class SampleBrokerClass
137148
}

‎provider/pact-jvm-provider/src/main/java/au/com/dius/pact/provider/junit/loader/PactBroker.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@
5050
/**
5151
* Authentication to use with the pact broker, by default no authentication is used
5252
*/
53-
PactBrokerAuth authentication() default @PactBrokerAuth(scheme = "${pactbroker.auth.scheme:basic}",
54-
username = "${pactbroker.auth.username:}", password = "${pactbroker.auth.password:}");
53+
PactBrokerAuth authentication() default @PactBrokerAuth(scheme = "${pactbroker.auth.scheme:legacy}",
54+
username = "${pactbroker.auth.username:}", password = "${pactbroker.auth.password:}", token = "${pactbroker.auth.token:}");
5555

5656
/**
5757
* Override the default value resolver for resolving the values in the expressions

‎provider/pact-jvm-provider/src/main/java/au/com/dius/pact/provider/junit/loader/PactBrokerAuth.java

+18-6
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,29 @@
1515
public @interface PactBrokerAuth {
1616

1717
/**
18-
* Authentication scheme to use. The default is basic.
18+
* Authentication scheme to use.
19+
*
20+
* @deprecated Does no longer need to be set explicitly. It is now automatically set to
21+
* <ul>
22+
* <li><b>Basic</b> if username is set.</li>
23+
* <li><b>Bearer</b> if token is set.</li>
24+
* </ul>
1925
*/
20-
String scheme() default "Basic";
26+
@Deprecated
27+
String scheme() default "";
2128

2229
/**
23-
* Username to use for authentication
30+
* Username to use for basic authentication
2431
*/
25-
String username();
32+
String username() default "";
2633

2734
/**
28-
* Password to use for authentication
35+
* Password to use for basic authentication
2936
*/
30-
String password();
37+
String password() default "";
38+
39+
/**
40+
* Token to use for bearer token authentication
41+
*/
42+
String token() default "";
3143
}

‎provider/pact-jvm-provider/src/main/java/au/com/dius/pact/provider/junit/loader/PactBrokerLoader.java

+34-6
Original file line numberDiff line numberDiff line change
@@ -179,13 +179,41 @@ Pact loadPact(ConsumerInfo consumer, Map options) {
179179
}
180180

181181
PactBrokerClient newPactBrokerClient(URI url, ValueResolver resolver) {
182-
HashMap options = new HashMap();
183-
if (this.authentication != null && !this.authentication.scheme().equalsIgnoreCase("none")) {
184-
options.put("authentication", Arrays.asList(parseExpression(this.authentication.scheme(), resolver),
185-
parseExpression(this.authentication.username(), resolver),
186-
parseExpression(this.authentication.password(), resolver)));
182+
if (this.authentication == null || this.authentication.scheme().equalsIgnoreCase("none")) {
183+
LOGGER.debug("Authentication: None");
184+
return new PactBrokerClient(url.toString(), Collections.emptyMap());
187185
}
188-
return new PactBrokerClient(url.toString(), options);
186+
187+
String scheme = parseExpression(this.authentication.scheme(), resolver);
188+
if (StringUtils.isNotEmpty(scheme)) {
189+
// Legacy behavior (before support for bearer token was added):
190+
// If scheme was not explicitly set, basic was always used.
191+
// If it was explicitly set, the given value was used together with username and password
192+
String schemeToUse = scheme.equals("legacy") ? "basic": scheme;
193+
LOGGER.debug("Authentication: {}", schemeToUse);
194+
Map<String, List<String>> options = Collections.singletonMap("authentication", Arrays.asList(schemeToUse,
195+
parseExpression(this.authentication.username(), resolver),
196+
parseExpression(this.authentication.password(), resolver)));
197+
return new PactBrokerClient(url.toString(), options);
198+
}
199+
200+
// Check if username is set. If yes, use basic auth.
201+
String username = parseExpression(this.authentication.username(), resolver);
202+
if (StringUtils.isNotEmpty(username)) {
203+
LOGGER.debug("Authentication: Basic");
204+
Map<String, List<String>> options = Collections.singletonMap("authentication", Arrays.asList("basic", username, parseExpression(this.authentication.password(), resolver)));
205+
return new PactBrokerClient(url.toString(), options);
206+
}
207+
208+
// Check if token is set. If yes, use bearer auth.
209+
String token = parseExpression(this.authentication.token(), resolver);
210+
if (StringUtils.isNotEmpty(token)) {
211+
LOGGER.debug("Authentication: Bearer");
212+
Map<String, List<String>> options = Collections.singletonMap("authentication", Arrays.asList("bearer", token));
213+
return new PactBrokerClient(url.toString(), options);
214+
}
215+
216+
throw new IllegalArgumentException("Invalid pact authentication specified. Either username or token must be set.");
189217
}
190218

191219
public boolean isFailIfNoPactsFound() {

0 commit comments

Comments
 (0)
Please sign in to comment.