Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import java.util.ServiceLoader;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;

Expand All @@ -43,6 +44,10 @@
/**
* A class for negotiating for a supported {@link AuthenticationProvider} based on the {@code WWW-Authenticate}
* headers received from a resource server.
*
* <p>In general, any authorization mechanism loaded via the {@link ServiceLoader} will be available for use
* during the challenge-response negotiation with a server. There are, however, certain known weak mechanisms
* such as Basic auth and Digest auth that are explicitly excluded.
*/
public class ReactiveAuthorization {

Expand All @@ -59,14 +64,24 @@ public class ReactiveAuthorization {
/**
* Create a new authorization handler, loading any {@link AuthenticationProvider} implementations
* via the {@link ServiceLoader}.
*
* <p>Known weak authorization mechanisms such as {@code Basic} and {@code Digest} are explicitly omitted.
*/
public ReactiveAuthorization() {
final ServiceLoader<AuthenticationProvider> loader = ServiceLoader.load(AuthenticationProvider.class,
ReactiveAuthorization.class.getClassLoader());

final Set<String> prohibited = getProhibitedSchemes();
for (final AuthenticationProvider provider : loader) {
for (final String scheme : provider.getSchemes()) {
registry.put(scheme, provider);
if (!prohibited.contains(scheme)) {
LOGGER.debug("Registering {} scheme via {} authentication provider", scheme,
provider.getClass().getSimpleName());
registry.put(scheme, provider);
} else {
LOGGER.debug("Omitting {} scheme via {} authentication provider", scheme,
provider.getClass().getSimpleName());
}
}
}
}
Expand Down Expand Up @@ -117,5 +132,12 @@ static boolean sessionSupportsScheme(final Session session, final String scheme)
}
return session.supportedSchemes().contains(scheme);
}

static Set<String> getProhibitedSchemes() {
final Set<String> prohibited = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
prohibited.add("Basic");
prohibited.add("Digest");
return prohibited;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@

/**
* An authentication mechanism that knows how to authenticate over network connections.
*
* <p>Please note that the {@link com.inrupt.client.auth.ReactiveAuthorization} class
* explicitly prohibits the use of {@code Basic} and {@code Digest} authorization schemes.
*/
public interface AuthenticationProvider {

Expand Down
77 changes: 77 additions & 0 deletions api/src/test/java/com/inrupt/client/auth/BasicAuthProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright 2023 Inrupt Inc.
*
* 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.inrupt.client.auth;

import static java.nio.charset.StandardCharsets.UTF_8;

import com.inrupt.client.Request;
import com.inrupt.client.spi.AuthenticationProvider;

import java.net.URI;
import java.time.Instant;
import java.util.Base64;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;

public class BasicAuthProvider implements AuthenticationProvider {

private static final String BASIC = "Basic";

@Override
public String getScheme() {
return BASIC;
}

@Override
public Set<String> getSchemes() {
return Collections.singleton(BASIC);
}

@Override
public Authenticator getAuthenticator(final Challenge challenge) {
return new BasicAuthenticator();
}

public class BasicAuthenticator implements Authenticator {
@Override
public String getName() {
return "BasicAuth";
}

@Override
public int getPriority() {
return 100;
}

@Override
public CompletionStage<Credential> authenticate(final Session session, final Request request,
final Set<String> algorithms) {
final URI issuer = URI.create("https://issuer.test");
final URI agent = URI.create("https://id.test/username");
final Instant expiration = Instant.now().plusSeconds(300);
final String token = Base64.getUrlEncoder().withoutPadding()
.encodeToString("username:password".getBytes(UTF_8));
return CompletableFuture.completedFuture(new Credential("Basic", issuer, token, expiration, agent, null));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright 2023 Inrupt Inc.
*
* 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.inrupt.client.auth;

import static org.junit.jupiter.api.Assertions.*;

import com.inrupt.client.Request;

import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;

import org.junit.jupiter.api.Test;

class ReactiveAuthorizationTest {

@Test
void testProhibitedAuth() {
final ReactiveAuthorization auth = new ReactiveAuthorization();
final Session session = new BasicAuthSession();
final Request req = Request.newBuilder(URI.create("https://storage.example")).build();
final Optional<Credential> credential = auth.negotiate(session, req,
Collections.singleton(Challenge.of("Basic"))).toCompletableFuture().join();
assertFalse(credential.isPresent());
}

static class BasicAuthSession implements Session {

@Override
public String getId() {
return UUID.randomUUID().toString();
}

@Override
public Optional<URI> getPrincipal() {
return Optional.empty();
}

@Override
public Set<String> supportedSchemes() {
return Collections.singleton("Basic");
}

@Override
public Optional<Credential> getCredential(final URI name, final URI uri) {
return Optional.empty();
}

@Override
public void reset() {
/* no-op */
}

@Override
public Optional<Credential> fromCache(final Request request) {
return Optional.empty();
}

@Override
public Optional<String> generateProof(final String jkt, final Request request) {
return Optional.empty();
}

@Override
public Optional<String> selectThumbprint(final Collection<String> algorithms) {
return Optional.empty();
}

@Override
public CompletionStage<Optional<Credential>> authenticate(final Authenticator authenticator,
final Request request, final Set<String> algorithms) {
return authenticator.authenticate(this, request, algorithms).thenApply(Optional::ofNullable);
}

/* deprecated */
@Override
public CompletionStage<Optional<Credential>> authenticate(final Request request,
final Set<String> algorithms) {
return CompletableFuture.completedFuture(Optional.empty());
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
com.inrupt.client.auth.BasicAuthProvider