Skip to content

Commit

Permalink
webdav: update default credential delegation for third-party copy
Browse files Browse the repository at this point in the history
Motivation:

A third-party transfer may require authorisation; i.e., the pool may
require a credential.  If specified, the 'Credential' HTTP header
controls where this credential comes from.  If not specified then some
default policy is used.

The current default policy is based on the transfer protocol: the
protocol used by the pool to obtaining or send the file.  For 'https'
transfers, the default is to use GridSite delegation, unless the client
used some bearer token, in which case OpenID-Connect delegation is used.

If the client uses a macaroon to authorise the third-party copy then the
request has a bearer token, but OpenID-Connect delegation cannot work --
therefore the default behaviour is broken.

Modification:

Update the default policy to base the decision on how the user is
authenticated:

  OpenID-Connect -->  use OpenID-Connect delegation
  X.509          -->  use GridSite delegation
  anything else  -->  does not fetch a credential.

As before, the client may override this by specifying the Credential
header.

Result:

Requesting a third-party copy using a macaroon does not trigger a failed
attempt to OpenID-Connect delegation.

Target: master
Request: 4.2
Request: 4.1
Request: 4.0
Request: 3.2
Ticket: http://rt.dcache.org/Ticket/Display.html?id=9474
Require-notes: yes
Require-book: yes
Patch: https://rb.dcache.org/r/11093/
Acked-by: Tigran Mkrtchyan
  • Loading branch information
paulmillar committed Aug 15, 2018
1 parent 50b839d commit fd3e041
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 37 deletions.
Expand Up @@ -19,7 +19,6 @@

import com.google.common.base.CharMatcher;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.net.InternetDomainName;
import io.milton.http.Filter;
Expand All @@ -29,6 +28,7 @@
import io.milton.http.Response.Status;
import io.milton.http.exceptions.BadRequestException;
import io.milton.servlet.ServletRequest;
import org.globus.gsi.gssapi.jaas.GlobusPrincipal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
Expand Down Expand Up @@ -58,6 +58,7 @@

import org.dcache.acl.enums.AccessMask;
import org.dcache.auth.BearerTokenCredential;
import org.dcache.auth.OidcSubjectPrincipal;
import org.dcache.auth.OpenIdClientSecret;
import org.dcache.auth.Subjects;
import org.dcache.auth.attributes.Restriction;
Expand Down Expand Up @@ -110,9 +111,7 @@ public class CopyFilter implements Filter

private static final String QUERY_KEY_ASKED_TO_DELEGATE = "asked-to-delegate";
private static final String REQUEST_HEADER_CREDENTIAL = "Credential";
private static final String REQUEST_HEADER_AUTHORIZATION = "Authorization";
private static final String REQUEST_HEADER_VERIFICATION = "RequireChecksumVerification";
private static final String SCHEME_AUTHORIZATION_BEARER_WITH_SPACE = "BEARER ";

private static final Set<AccessMask> READ_ACCESS_MASK =
EnumSet.of(AccessMask.READ_DATA);
Expand Down Expand Up @@ -398,23 +397,24 @@ private CredentialSource getCredentialSource(Request request, TransferType type)

if (headerValue != null) {
source = CredentialSource.forHeaderValue(headerValue);
} else if (isAuthorizationHeaderBearer(request)) {

if (source == null) {
throw new ErrorResponseException(Status.SC_BAD_REQUEST,
"HTTP header 'Credential' has unknown value \"" +
headerValue + "\". Valid values are: " +
Joiner.on(',').join(CredentialSource.headerValues()));
}
} else if (clientAuthnUsingOidc()) {
source = CredentialSource.OIDC;
} else if (clientAuthnUsingX509()) {
source = CredentialSource.GRIDSITE;
} else {
source = type.getDefaultCredentialSource();
}

if (source == null) {
throw new ErrorResponseException(Status.SC_BAD_REQUEST,
"HTTP header 'Credential' has unknown value \"" +
headerValue + "\". Valid values are: " +
Joiner.on(',').join(CredentialSource.headerValues()));
source = CredentialSource.NONE;
}

if (!type.isSupported(source)) {
throw new ErrorResponseException(Status.SC_BAD_REQUEST,
"HTTP header 'Credential' value \"" + headerValue + "\" is not " +
"supported for transport " + type.getScheme());
"Delegation " + source + " is not supported for " + type.getScheme());
}

return source;
Expand Down Expand Up @@ -615,16 +615,16 @@ private Restriction getRestriction()
return (Restriction) servletRequest.getAttribute(AuthenticationHandler.DCACHE_RESTRICTION_ATTRIBUTE);
}

private boolean isAuthorizationHeaderBearer(Request request)
private boolean clientAuthnUsingOidc()
{
// Note that HttpSerlvetRequest#getHeader is case insensitive, as
// required by:
//
// https://tools.ietf.org/html/rfc7230#section-3.2
//
return Strings.nullToEmpty(ServletRequest.getRequest().getHeader(REQUEST_HEADER_AUTHORIZATION))
.toUpperCase()
.startsWith(SCHEME_AUTHORIZATION_BEARER_WITH_SPACE);
return Subject.getSubject(AccessController.getContext()).getPrincipals().stream()
.anyMatch(OidcSubjectPrincipal.class::isInstance);
}

private boolean clientAuthnUsingX509()
{
return Subject.getSubject(AccessController.getContext()).getPrincipals().stream()
.anyMatch(GlobusPrincipal.class::isInstance);
}

private static <T> Predicate<T> not(Predicate<T> t) {
Expand Down
Expand Up @@ -125,31 +125,22 @@ public String getHeaderName()
* The different transport schemes supported.
*/
public enum TransferType {
GSIFTP("gsiftp", GRIDSITE, EnumSet.noneOf(CredentialSource.class)),
HTTP( "http", NONE, EnumSet.noneOf(CredentialSource.class)),
HTTPS( "https", GRIDSITE, EnumSet.of(OIDC, NONE));
GSIFTP("gsiftp", EnumSet.of(GRIDSITE)),
HTTP( "http", EnumSet.of(NONE)),
HTTPS( "https", EnumSet.of(GRIDSITE, OIDC, NONE));

private static final ImmutableMap<String,TransferType> BY_SCHEME =
ImmutableMap.of("gsiftp", GSIFTP, "http", HTTP, "https", HTTPS);

private final CredentialSource _defaultCredentialSource;
private final EnumSet<CredentialSource> _supported;
private final String _scheme;

TransferType(String scheme, CredentialSource defaultSource,
EnumSet<CredentialSource> additionalSources)
TransferType(String scheme, EnumSet<CredentialSource> supportedSources)
{
_defaultCredentialSource = defaultSource;
_supported = EnumSet.copyOf(additionalSources);
_supported.add(defaultSource);
_supported = EnumSet.copyOf(supportedSources);
_scheme = scheme;
}

public CredentialSource getDefaultCredentialSource()
{
return _defaultCredentialSource;
}

public boolean isSupported(CredentialSource source)
{
return _supported.contains(source);
Expand Down

0 comments on commit fd3e041

Please sign in to comment.