Skip to content

Commit

Permalink
cadc-rest: use IdentityManager supported security methods
Browse files Browse the repository at this point in the history
to add www-authenticate headers
  • Loading branch information
pdowler committed Aug 28, 2024
1 parent 8e7afb6 commit 3ef5406
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 22 deletions.
2 changes: 1 addition & 1 deletion cadc-rest/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ sourceCompatibility = 1.8

group = 'org.opencadc'

version = '1.3.20'
version = '1.4.0'

description = 'OpenCADC REST server library'
def git_url = 'https://github.com/opencadc/core'
Expand Down
11 changes: 9 additions & 2 deletions cadc-rest/src/main/java/ca/nrc/cadc/rest/RestAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,13 @@ public void setSyncOutput(SyncOutput syncOutput) {
this.syncOutput = syncOutput;
}

private String getRealm() {
String str = syncInput.getRequestURI();
// remove endpoint name
str = str.replace(syncInput.getComponentPath(), "");
return str;
}

// return Object ignored; method signature from PrivilegedExceptionAction
@Override
public Object run()
Expand All @@ -365,7 +372,7 @@ public Object run()
initAction();
if (setAuthHeaders()) {
RestServlet.setAuthenticateHeaders(
AuthenticationUtil.getCurrentSubject(), syncOutput, null, new RegistryClient());
AuthenticationUtil.getCurrentSubject(), getRealm(), syncOutput, null);
authHeadersSet = true;
}
doAction();
Expand All @@ -375,7 +382,7 @@ public Object run()
logInfo.setMessage(ex.getMessage());
if (!authHeadersSet && setAuthHeaders()) {
RestServlet.setAuthenticateHeaders(
AuthenticationUtil.getCurrentSubject(), syncOutput, ex, new RegistryClient());
AuthenticationUtil.getCurrentSubject(), getRealm(), syncOutput, ex);
}
handleException(ex, 401, ex.getMessage(), false, false);
} catch (ResourceLockedException ex) {
Expand Down
59 changes: 53 additions & 6 deletions cadc-rest/src/main/java/ca/nrc/cadc/rest/RestServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
import ca.nrc.cadc.auth.AuthenticationUtil;
import ca.nrc.cadc.auth.IdentityManager;
import ca.nrc.cadc.auth.NotAuthenticatedException;
import static ca.nrc.cadc.auth.PrincipalExtractor.CERT_HEADER_ENABLE;
import ca.nrc.cadc.log.ServletLogInfo;
import ca.nrc.cadc.log.WebServiceLogInfo;
import ca.nrc.cadc.net.ResourceNotFoundException;
Expand All @@ -94,6 +95,7 @@
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.TreeMap;
import javax.security.auth.Subject;
import javax.servlet.ServletConfig;
Expand All @@ -114,6 +116,8 @@ public class RestServlet extends HttpServlet {

private static final Logger log = Logger.getLogger(RestServlet.class);

private static final Map<URI,String> SEC_METHOD_CHALLENGES = new TreeMap<>();

private static final List<String> CITEMS = new ArrayList<String>();

static final String AUGMENT_SUBJECT_PARAM = "augmentSubject";
Expand All @@ -126,6 +130,12 @@ public class RestServlet extends HttpServlet {
CITEMS.add("post");
CITEMS.add("put");
CITEMS.add("delete");

SEC_METHOD_CHALLENGES.put(Standards.SECURITY_METHOD_HTTP_BASIC, AuthenticationUtil.CHALLENGE_TYPE_BASIC);
SEC_METHOD_CHALLENGES.put(Standards.SECURITY_METHOD_CERT, AuthenticationUtil.CHALLENGE_TYPE_IVOA_X509);
SEC_METHOD_CHALLENGES.put(Standards.SECURITY_METHOD_OPENID, AuthenticationUtil.CHALLENGE_TYPE_BEARER);
SEC_METHOD_CHALLENGES.put(Standards.SECURITY_METHOD_TOKEN, AuthenticationUtil.CHALLENGE_TYPE_BEARER);
SEC_METHOD_CHALLENGES.put(Standards.SECURITY_METHOD_COOKIE, "ivoa_cookie");
}

private Class<RestAction> getAction;
Expand Down Expand Up @@ -336,7 +346,7 @@ protected void doit(HttpServletRequest request, HttpServletResponse response, C
logInfo.setSuccess(true);
logInfo.setMessage(nae.getMessage());
if (setAuthHeaders) {
setAuthenticateHeaders(null, out, nae, new RegistryClient());
setAuthenticateHeaders(null, in.getRequestURI(), out, nae);
}
handleException(out, response, nae, 401, nae.getMessage(), false);
} else {
Expand Down Expand Up @@ -453,25 +463,61 @@ private void handleUnexpected(SyncOutput out, HttpServletResponse response, Thro
* If authentication failed and a challenge was presented, add the error type and error
* description in the WWW-Authenticate header associated with the challenge.
*/
static void setAuthenticateHeaders(Subject subject, SyncOutput out, NotAuthenticatedException ex, RegistryClient rc) {
static void setAuthenticateHeaders(Subject subject, String realm, SyncOutput out, NotAuthenticatedException ex) {

if (out.isOpen()) {
log.debug("SyncOutput already open, can't set auth headers");
return;
}

final IdentityManager im = AuthenticationUtil.getIdentityManager();
if (subject != null && !subject.getPrincipals().isEmpty()) {
// Authenticated...
log.debug("Setting " + AuthenticationUtil.VO_AUTHENTICATED_HEADER + " header");
IdentityManager im = AuthenticationUtil.getIdentityManager();
String val = im.toDisplayString(subject);
if (val != null) {
out.addHeader(AuthenticationUtil.VO_AUTHENTICATED_HEADER, val);
}
return;

} else {
// Not authenticated...
log.warn("adding challenges for " + im.getClass().getName());
for (URI sm : im.getSecurityMethods()) {
String challenge = SEC_METHOD_CHALLENGES.get(sm);
log.warn(sm + " -> " + challenge);
if (challenge != null) {
// TODO: check System.getProperty(CERT_HEADER_ENABLE) aka trust ingress to do client cert validation?
// TODO: check for duplicate challenges (map does contain them)?
StringBuilder sb = new StringBuilder();
sb.append(challenge);
if (challenge.equals(AuthenticationUtil.CHALLENGE_TYPE_BASIC)) {
sb.append(" realm=\"").append(realm).append("\"");
} else if (challenge.equalsIgnoreCase(AuthenticationUtil.CHALLENGE_TYPE_BEARER)) {
// temporary hack to add ivoa_token challenge with standard_id
try {
URI loginServiceURI = getLocalServiceURI(Standards.SECURITY_METHOD_PASSWORD);
if (loginServiceURI != null) {
RegistryClient rc = new RegistryClient();
URL loginURL = rc.getServiceURL(loginServiceURI, Standards.SECURITY_METHOD_PASSWORD, AuthMethod.ANON);
if (loginURL != null) {
StringBuilder c2 = new StringBuilder();
c2.append(AuthenticationUtil.CHALLENGE_TYPE_IVOA_BEARER);
c2.append(" standard_id=\"").append(Standards.SECURITY_METHOD_PASSWORD.toString()).append("\"");
c2.append(", access_url=\"").append(loginURL).append("\"");
c2.append(", HACK=temporary");
out.addHeader(AuthenticationUtil.AUTHENTICATE_HEADER, c2.toString());
}
}
} catch (NoSuchElementException notSupported) {
log.debug("LocalAuthority -- not found: " + Standards.SECURITY_METHOD_PASSWORD, notSupported);
}
}

appendAuthenticateErrorInfo(challenge, sb, ex, true);
out.addHeader(AuthenticationUtil.AUTHENTICATE_HEADER, sb.toString());
}
}
}

/*
log.debug("Setting " + AuthenticationUtil.AUTHENTICATE_HEADER + " header");
boolean addBearerChallenge = false;
try {
Expand Down Expand Up @@ -554,6 +600,7 @@ static void setAuthenticateHeaders(Subject subject, SyncOutput out, NotAuthentic
log.debug("LocalAuthority -- not found: " + Standards.CRED_PROXY_10, notSupported);
}
}
*/
}

/**
Expand Down
27 changes: 18 additions & 9 deletions cadc-rest/src/test/java/ca/nrc/cadc/rest/RestServletTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,9 @@

import ca.nrc.cadc.auth.AuthMethod;
import ca.nrc.cadc.auth.AuthenticationUtil;
import ca.nrc.cadc.auth.AuthorizationToken;
import ca.nrc.cadc.auth.HttpPrincipal;
import ca.nrc.cadc.auth.IdentityManager;
import ca.nrc.cadc.auth.NoOpIdentityManager;
import ca.nrc.cadc.auth.NotAuthenticatedException;
import ca.nrc.cadc.auth.NotAuthenticatedException.AuthError;
import ca.nrc.cadc.reg.AccessURL;
import ca.nrc.cadc.reg.Capabilities;
import ca.nrc.cadc.reg.Capability;
Expand All @@ -92,6 +89,7 @@
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import javax.security.auth.Subject;
import javax.security.auth.x500.X500Principal;
import org.apache.log4j.Level;
Expand All @@ -110,6 +108,8 @@ public class RestServletTest {
static {
Log4jInit.setLevel("ca.nrc.cadc.rest", Level.DEBUG);
}

static final String REALM = "opencadc.org";

private class Output extends SyncOutput {

Expand Down Expand Up @@ -153,7 +153,7 @@ public void testSetAuthenticated() {
Subject s = new Subject();
s.getPrincipals().add(new HttpPrincipal("userid"));
out = new Output();
RestServlet.setAuthenticateHeaders(s, out, null, new TestRegistryClient());
RestServlet.setAuthenticateHeaders(s, REALM, out, null);
// verify
List<String> xvoauth = out.headers.get(AuthenticationUtil.VO_AUTHENTICATED_HEADER);
Assert.assertNotNull(xvoauth);
Expand All @@ -167,7 +167,7 @@ public void testSetAuthenticated() {
s = new Subject();
s.getPrincipals().add(new X500Principal("C=ca,O=people,CN=me"));
out = new Output();
RestServlet.setAuthenticateHeaders(s, out, null, new TestRegistryClient());
RestServlet.setAuthenticateHeaders(s, REALM, out, null);
// verify
xvoauth = out.headers.get(AuthenticationUtil.VO_AUTHENTICATED_HEADER);
Assert.assertNotNull(xvoauth);
Expand All @@ -182,7 +182,7 @@ public void testSetAuthenticated() {
s.getPrincipals().add(new X500Principal("C=ca,O=people,CN=me"));
s.getPrincipals().add(new HttpPrincipal("userid"));
out = new Output();
RestServlet.setAuthenticateHeaders(s, out, null, new TestRegistryClient());
RestServlet.setAuthenticateHeaders(s, REALM, out, null);
// verify
xvoauth = out.headers.get(AuthenticationUtil.VO_AUTHENTICATED_HEADER);
Assert.assertNotNull(xvoauth);
Expand Down Expand Up @@ -212,7 +212,7 @@ public void testSetChallenges() {
// username
Subject s = AuthenticationUtil.getAnonSubject();
out = new Output();
RestServlet.setAuthenticateHeaders(s, out, null, new TestRegistryClient());
RestServlet.setAuthenticateHeaders(s, REALM, out, null);
// verify
List<String> xvoauth = out.headers.get(AuthenticationUtil.VO_AUTHENTICATED_HEADER);
Assert.assertNull(xvoauth);
Expand All @@ -229,14 +229,14 @@ public void testSetChallenges() {
foundBearer++;
} else if (csLower.startsWith("ivoa_bearer standard_id=")) {
foundIvoaBearer++;
} else if (csLower.startsWith("ivoa_x509 standard_id=")) {
} else if (csLower.startsWith("ivoa_x509")) {
foundIvoaX509++;
}
}
// expectation based on cadc-registry.properties and TestRegistryClient capability output below
Assert.assertEquals("bearer", 1, foundBearer);
Assert.assertEquals("ivoa_bearer", 1, foundIvoaBearer);
Assert.assertEquals("ivoa_x509", 2, foundIvoaX509);
Assert.assertEquals("ivoa_x509", 1, foundIvoaX509);
} catch (Exception unexpected) {
log.error("unexpected exception", unexpected);
Assert.fail("unexpected exception: " + unexpected);
Expand Down Expand Up @@ -283,6 +283,15 @@ public static class TestIdentityManager extends NoOpIdentityManager {
public TestIdentityManager() {
super();
}

@Override
public Set<URI> getSecurityMethods() {
Set<URI> ret = new TreeSet<>();
ret.add(Standards.SECURITY_METHOD_CERT);
ret.add(Standards.SECURITY_METHOD_OPENID);
return ret;
}


@Override
public String toDisplayString(Subject subject) {
Expand Down
5 changes: 1 addition & 4 deletions cadc-rest/src/test/resources/cadc-registry.properties
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
# not used by tests
ca.nrc.cadc.reg.client.RegistryClient.baseURL=https://example.net/reg

ivo://ivoa.net/std/CDP#delegate-1.0 = ivo://cadc.nrc.ca/cred
ivo://ivoa.net/std/CDP#proxy-1.0 = ivo://cadc.nrc.ca/cred

ivo://ivoa.net/sso#OpenID = https://oidc.example.net/
## needed to inject ivoa_bearer challenge
ivo://ivoa.net/sso#tls-with-password = ivo://cadc.nrc.ca/gms

0 comments on commit 3ef5406

Please sign in to comment.