Skip to Content.
Sympa Menu

idok-commit - [idok-commit] idok commit r175 - in branches/rest: java/ch/idok/service/server/rest java/ch/idok/service/server/search/rest misc/eclipse sites/psi/java/ch/psi/idok/common/config

idok-commit AT lists.psi.ch

Subject: Commit emails of the iDok project

List archive

[idok-commit] idok commit r175 - in branches/rest: java/ch/idok/service/server/rest java/ch/idok/service/server/search/rest misc/eclipse sites/psi/java/ch/psi/idok/common/config


Chronological Thread 
  • From: "AFS account Roman Geus" <geus AT savannah.psi.ch>
  • To: idok-commit AT lists.psi.ch
  • Subject: [idok-commit] idok commit r175 - in branches/rest: java/ch/idok/service/server/rest java/ch/idok/service/server/search/rest misc/eclipse sites/psi/java/ch/psi/idok/common/config
  • Date: Thu, 14 Aug 2008 16:24:35 +0200
  • List-archive: <https://lists.web.psi.ch/pipermail/idok-commit/>
  • List-id: Commit emails of the iDok project <idok-commit.lists.psi.ch>

Author: geus
Date: Thu Aug 14 16:24:34 2008
New Revision: 175

Log:
Implemented Negotiate/SPNEGO authentication with fallback to Basic
authentication for REST server

Added:
branches/rest/java/ch/idok/service/server/rest/SpnegoFilter.java
(contents, props changed)
Modified:
branches/rest/java/ch/idok/service/server/rest/RestServer.java

branches/rest/java/ch/idok/service/server/search/rest/RestSearchServiceResource.java
branches/rest/misc/eclipse/iDok_server.launch
branches/rest/sites/psi/java/ch/psi/idok/common/config/jaas.conf

Modified: branches/rest/java/ch/idok/service/server/rest/RestServer.java
==============================================================================
--- branches/rest/java/ch/idok/service/server/rest/RestServer.java
(original)
+++ branches/rest/java/ch/idok/service/server/rest/RestServer.java Thu
Aug 14 16:24:34 2008
@@ -4,7 +4,9 @@
import java.util.logging.Logger;

import org.restlet.Component;
-import org.restlet.Guard;
+import org.restlet.Context;
+import org.restlet.Filter;
+import org.restlet.Restlet;
import org.restlet.Server;
import org.restlet.data.Protocol;
import org.restlet.ext.jaxrs.JaxRsApplication;
@@ -16,6 +18,31 @@
import ch.idok.service.server.search.rest.RestSearchServiceResource;

/**
+ * A JaxRsApplication that installs a Filter for all incoming requests
+ */
+class FilteringJaxRsApplication extends JaxRsApplication {
+ Filter filter;
+
+ public FilteringJaxRsApplication(Filter filter) {
+ this.filter = filter;
+ }
+
+ /**
+ * @see org.restlet.ext.jaxrs.JaxRsApplication#createRoot()
+ */
+ @Override
+ public Restlet createRoot() {
+ if (filter != null) {
+ filter.setNext(super.createRoot());
+ return filter;
+ } else
+ return super.createRoot();
+ }
+
+}
+
+
+/**
* This class initializes and runs the REST server
*/
public class RestServer implements ch.idok.service.server.Server {
@@ -50,16 +77,18 @@
comp = new Component();
Server server = comp.getServers().add(Protocol.HTTP, 8183);

- // create JAX-RS runtime environment
- JaxRsApplication application = new
JaxRsApplication(comp.getContext());
+ Context appContext = new Context();

- // create a custom Guard that authenticates using JAAS
- Guard guard = new JaasBasicAuthGuard(application.getContext(),
- "JAX-RS example", "DmsNoCache");
+ // Create authentication filter
+ // TODO make realm configurable (should match the Subversion realm)
+ SpnegoFilter filter = new SpnegoFilter("iDok login", "DmsNoCache");
+
+ // create JAX-RS runtime environment
+ JaxRsApplication application = new FilteringJaxRsApplication(filter);
+ application.setContext(appContext);

// attach ApplicationConfig and Guard
- application.attach(new IdokAppConfig());
- application.setGuard(guard);
+ application.add(new IdokAppConfig());

// attach the application to the component and start it
comp.getDefaultHost().attach("/v1", application);

Added: branches/rest/java/ch/idok/service/server/rest/SpnegoFilter.java
==============================================================================
--- (empty file)
+++ branches/rest/java/ch/idok/service/server/rest/SpnegoFilter.java Thu
Aug 14 16:24:34 2008
@@ -0,0 +1,296 @@
+package ch.idok.service.server.rest;
+
+import java.io.UnsupportedEncodingException;
+import java.math.BigInteger;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSCredential;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import org.ietf.jgss.Oid;
+import org.restlet.Filter;
+import org.restlet.data.ChallengeRequest;
+import org.restlet.data.ChallengeResponse;
+import org.restlet.data.ChallengeScheme;
+import org.restlet.data.Form;
+import org.restlet.data.Parameter;
+import org.restlet.data.Request;
+import org.restlet.data.Response;
+import org.restlet.data.Status;
+import org.restlet.util.Series;
+
+import ch.idok.common.errorhandling.DmsException;
+import ch.idok.common.util.AuthUtil;
+import ch.idok.common.util.DmsCredentials;
+import ch.idok.common.util.DummyCallbackHandler;
+import ch.idok.common.util.Krb5DmsCredentials;
+
+import com.noelios.restlet.Engine;
+import com.noelios.restlet.authentication.AuthenticationHelper;
+import com.noelios.restlet.util.Base64;
+import com.sun.security.jgss.GSSUtil;
+
+/**
+ * This is a small test filter to test SPNEGO authentication. Treat as
+ * experimental!
+ *
+ * This filter is based on code by Bruno Harbulot
+ */
+public class SpnegoFilter extends Filter {
+
+ private static final String subjectAttributeName =
"ch.idok.service.server.rest.Subject";
+
+ private GSSManager gssManager;
+ private GSSCredential gssServerCreds;
+
+ /**
+ * The HTTP authentication realm
+ */
+ private String realm;
+
+ /**
+ * The login configuration used for checking username and password for
BASIC
+ * authentication scheme
+ */
+ private String jaasLoginConfig;
+
+ public static final ChallengeScheme HTTP_SPNEGO = new ChallengeScheme(
+ "HTTP_Negotiate", "Negotiate");
+
+ public SpnegoFilter(String realm, String jaasLoginConfig) {
+ this.realm = realm;
+ this.jaasLoginConfig = jaasLoginConfig;
+ Engine.getInstance().getRegisteredAuthentications().add(0,
+ new SpnegoAuthenticationHelper());
+ }
+
+ private GSSContext gssInit() throws Exception {
+ gssManager = GSSManager.getInstance();
+
+ // Accept both SPNEGO and Kerberos v5 tokens
+ Oid[] spnegoOid = new Oid[] { new Oid("1.3.6.1.5.5.2"),
+ new Oid("1.2.840.113554.1.2.2") };
+
+ gssServerCreds = gssManager.createCredential(null,
+ GSSCredential.DEFAULT_LIFETIME, spnegoOid,
+ GSSCredential.ACCEPT_ONLY);
+ return gssManager.createContext((GSSCredential) gssServerCreds);
+ }
+
+ public static class SpnegoAuthenticationHelper extends
AuthenticationHelper {
+ public static final String SPNEGO_TOKEN_PARAM_NAME = "spnego-token";
+
+ public SpnegoAuthenticationHelper() {
+ super(HTTP_SPNEGO, true, true);
+ }
+
+ @Override
+ public void formatCredentials(StringBuilder arg0,
+ ChallengeResponse arg1, Request arg2, Series<Parameter>
arg3) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void formatParameters(StringBuilder sb,
+ Series<Parameter> parameters, ChallengeRequest request) {
+ String challengeString = parameters
+ .getFirstValue(SPNEGO_TOKEN_PARAM_NAME);
+ if (challengeString != null) {
+ sb.append(challengeString);
+ }
+ }
+ }
+
+ @Override
+ protected int doHandle(Request request, Response response) {
+ Logger logger = getContext().getLogger();
+
+ ChallengeResponse cr = request.getChallengeResponse();
+
+ Subject authenticatedSubject = null;
+
+ int result = Filter.STOP;
+ @SuppressWarnings("unchecked")
+ Series<Parameter> reqHeaders = (Series<Parameter>) request
+ .getAttributes().get("org.restlet.http.headers");
+
+ String authorizationHeader =
reqHeaders.getFirstValue("Authorization");
+
+ try {
+ // Initialises the GSSContext
+ GSSContext gssContext = gssInit();
+
+ logger.log(Level.FINEST, "Received this authorization header:
{0}",
+ authorizationHeader);
+
+ Form spnegoParams = new Form();
+
+ // Reads the autorisation header
+ if (authorizationHeader != null) {
+ if (authorizationHeader.startsWith("Negotiate ")) {
+ /*
+ * If the request contains a Negotiate auth header, the
+ * token is passed to the GSS context. The token
obtained in
+ * return (from the GSS API) will be sent back in the
+ * response.
+ */
+ String spnegoOutputTokenString = "";
+
+ String spnegoInputTokenString = authorizationHeader
+ .substring("Negotiate ".length());
+ byte[] spnegoToken;
+ try {
+ BigInteger integerToken = new BigInteger(
+ spnegoInputTokenString, 16);
+ spnegoToken = integerToken.toByteArray();
+ } catch (NumberFormatException e) {
+ spnegoToken = Base64.decode(spnegoInputTokenString);
+ }
+
+ if (spnegoToken.length != 0) {
+ spnegoToken =
gssContext.acceptSecContext(spnegoToken,
+ 0, spnegoToken.length);
+ spnegoOutputTokenString = Base64.encode(spnegoToken,
+ false);
+ }
+
+ spnegoParams.add(
+
SpnegoAuthenticationHelper.SPNEGO_TOKEN_PARAM_NAME,
+ spnegoOutputTokenString);
+ logger.log(Level.FINEST,
+ "Sending this Negotiate challenge: {0}",
+ spnegoOutputTokenString);
+ } else if (authorizationHeader.startsWith("Basic ")) {
+ try {
+ final byte[] credentialsEncoded = Base64.decode(cr
+ .getCredentials());
+ if (credentialsEncoded == null) {
+ logger.warning("Cannot decode credentials: "
+ + cr.getCredentials());
+ }
+
+ final String credentials = new String(
+ credentialsEncoded, "US-ASCII");
+ final int separator = credentials.indexOf(':');
+
+ if (separator == -1) {
+ // Log the blocking
+ logger
+ .warning("Invalid credentials given by
client with IP: "
+ + ((request != null) ? request
+ .getClientInfo()
+ .getAddress() : "?"));
+ } else {
+ cr.setIdentifier(credentials
+ .substring(0, separator));
+ cr.setSecret(credentials.substring(separator +
1));
+ authenticatedSubject = checkSecret(logger,
request,
+ cr.getIdentifier(), cr.getSecret());
+ }
+
+ } catch (final UnsupportedEncodingException e) {
+ logger.log(Level.WARNING, "Unsupported encoding
error",
+ e);
+ }
+
+ } else {
+ response.setStatus(Status.CLIENT_ERROR_UNAUTHORIZED);
+ }
+ }
+
+ /*
+ * Adds two challenges.
+ */
+ ChallengeRequest challengeReq = new ChallengeRequest(HTTP_SPNEGO,
+ null);
+ challengeReq.setParameters(spnegoParams);
+ response.getChallengeRequests().add(challengeReq);
+
+ ChallengeRequest basicChallengeReq = new ChallengeRequest(
+ ChallengeScheme.HTTP_BASIC, realm);
+ response.getChallengeRequests().add(basicChallengeReq);
+
+ if (gssContext.isEstablished()) {
+ try {
+ final GSSName srcName = gssContext.getSrcName();
+ logger
+ .log(Level.FINER,
+ "Authenticated via GSS/SPNEGO: {0}({1})",
+ new Object[] { srcName,
+ srcName.getStringNameType() });
+
+ // Create Subject from GSS-API token (including TGT)
+ authenticatedSubject = GSSUtil.createSubject(gssContext
+ .getSrcName(), gssContext.getDelegCred());
+ // Set identifier, so ChallengeResponse.getPrincipal()
works
+ cr.setIdentifier(srcName.toString());
+ } catch (GSSException e) {
+ logger.log(Level.FINER,
+ "An exception occurred in SpnegoFilter", e);
+ response.setStatus(Status.SERVER_ERROR_INTERNAL);
+ }
+ }
+
+ if (authenticatedSubject != null) {
+ // Make Subject available through request attributes
+ request.getAttributes().put(subjectAttributeName,
+ authenticatedSubject);
+ cr.setAuthenticated(true);
+ result = super.doHandle(request, response);
+ } else {
+ response.setStatus(Status.CLIENT_ERROR_UNAUTHORIZED);
+ }
+
+ } catch (Exception e) {
+ logger.log(Level.FINER, "An exception occurred in SpnegoFilter",
e);
+ response.setStatus(Status.SERVER_ERROR_INTERNAL);
+ }
+
+ return result;
+ }
+
+ /**
+ * Check the username/password combination using a JAAS login
+ *
+ * @return the authenticated Subject if successful, null otherwise
+ */
+ private Subject checkSecret(Logger logger, Request request,
+ String identifier, char[] secret) {
+ CallbackHandler handler = new DummyCallbackHandler(identifier,
secret);
+
+ try {
+ LoginContext lc = new LoginContext(jaasLoginConfig, handler);
+ lc.login();
+ logger.finer("Authentication successful for user " + identifier);
+ return lc.getSubject();
+ } catch (LoginException e) {
+ logger.log(Level.FINER, "Authentication failed for user "
+ + identifier, e);
+ return null;
+ }
+ }
+
+ /**
+ * Return a DmsCredentials object created from the current Restlet
request.
+ *
+ * @throws DmsException
+ */
+ static public DmsCredentials getDmsCredentials() throws DmsException {
+ Request request = Request.getCurrent();
+ Subject subject = (Subject) request.getAttributes().get(
+ subjectAttributeName);
+ char[] password = (char[])
request.getChallengeResponse().getSecret();
+
+ return new Krb5DmsCredentials(AuthUtil.getUserPrincipal(subject)
+ .getName(), password, subject);
+ }
+
+}

Modified:
branches/rest/java/ch/idok/service/server/search/rest/RestSearchServiceResource.java
==============================================================================
---
branches/rest/java/ch/idok/service/server/search/rest/RestSearchServiceResource.java
(original)
+++
branches/rest/java/ch/idok/service/server/search/rest/RestSearchServiceResource.java
Thu Aug 14 16:24:34 2008
@@ -9,7 +9,7 @@
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
-import javax.ws.rs.ProduceMime;
+import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
@@ -29,8 +29,8 @@
import ch.idok.common.util.Pair;
import ch.idok.service.common.search.QueryMatch;
import ch.idok.service.common.search.SearchService;
-import ch.idok.service.server.rest.JaasBasicAuthGuard;
import ch.idok.service.server.rest.RestServer;
+import ch.idok.service.server.rest.SpnegoFilter;

/**
* JAX-RS resource class for the iDok search service
@@ -59,7 +59,7 @@

@GET
@Path("{project}/{repository}/")
- @ProduceMime("text/xml")
+ @Produces("text/xml")
public Response query(@PathParam("project") String project,
@PathParam("repository") String repository,
@QueryParam("q") String query,
@@ -100,10 +100,9 @@
metaFields = "";

try {
- // TODO JAAS login using HTTP Negotiate authentication
DmsCredentials cred = null;
if (securityContext.getUserPrincipal() != null) {
- cred = JaasBasicAuthGuard.getDmsCredentials();
+ cred = SpnegoFilter.getDmsCredentials();
}

// build Lucene query string

Modified: branches/rest/misc/eclipse/iDok_server.launch
==============================================================================
--- branches/rest/misc/eclipse/iDok_server.launch (original)
+++ branches/rest/misc/eclipse/iDok_server.launch Thu Aug 14 16:24:34
2008
@@ -13,5 +13,5 @@
</listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE"
value="ch.idok.service.server.Daemon"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="idok"/>
-<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-server
-Xbootclasspath/p:lib/jacorb.jar:lib/logkit.jar:lib/avalon-framework.jar
-Djava.util.logging.config.file=/tmp/idok_server/log/logging.conf
-Dch.idok.server.projectRoot=/tmp/idok_server/projects
-Dderby.system.home=/tmp/idok_server/derby
-Dch.idok.service.admin.backup.dir=/tmp/idok_server/derby_backup
-Dch.idok.server.httpdConfDir=/tmp/idok_server/httpd/conf.d
-Dch.idok.service.server.search.lucene.indexdir=lucene-index
-Dch.idok.service.server.search.lucene.repodir=/tmp/idok_server/indices
-DprivateKeyPassword=VgxfhGau7WL1T4qlD1NE
-Dch.idok.service.servers=ch.idok.service.server.rest.RestServer,ch.idok.service.server.corba.CorbaServer
-Dch.idok.service.provider=ch.idok.service.server.ServerProvider
-Dch.idok.service.client.provider=ch.idok.service.client.corba.ClientProvider
-Dorg.omg.CORBA.ORBClass=org.jacorb.orb.ORB
-Dorg.omg.CORBA.ORBSingletonClass=org.jacorb.orb.ORBSingleton
-Dorg.omg.PortableInterceptor.ORBInitializerClass.bidir_init=org.jacorb.orb.giop.BiDirConnectionInitializer
-DORBInitRef.NameService=corbaloc::localhost:1050/NameService
-Dch.idok.service.common.keypass=P7GXtm6rPr5pT988N4Gm
-Dch.idok.privateKeyPassword=90fd3498jw03riqdgs4f
-Dch.idok.service.server.admin.masterAdminName=idokadmin"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-server
-Xbootclasspath/p:lib/jacorb.jar:lib/logkit.jar:lib/avalon-framework.jar
-Djava.util.logging.config.file=/tmp/idok_server/log/logging.conf
-Dch.idok.server.projectRoot=/tmp/idok_server/projects
-Dderby.system.home=/tmp/idok_server/derby
-Dch.idok.service.admin.backup.dir=/tmp/idok_server/derby_backup
-Dch.idok.server.httpdConfDir=/tmp/idok_server/httpd/conf.d
-Dch.idok.service.server.search.lucene.indexdir=lucene-index
-Dch.idok.service.server.search.lucene.repodir=/tmp/idok_server/indices
-DprivateKeyPassword=VgxfhGau7WL1T4qlD1NE
-Dch.idok.service.servers=ch.idok.service.server.rest.RestServer,ch.idok.service.server.corba.CorbaServer
-Dch.idok.service.provider=ch.idok.service.server.ServerProvider
-Dch.idok.service.client.provider=ch.idok.service.client.corba.ClientProvider
-Dorg.omg.CORBA.ORBClass=org.jacorb.orb.ORB
-Dorg.omg.CORBA.ORBSingletonClass=org.jacorb.orb.ORBSingleton
-Dorg.omg.PortableInterceptor.ORBInitializerClass.bidir_init=org.jacorb.orb.giop.BiDirConnectionInitializer
-DORBInitRef.NameService=corbaloc::localhost:1050/NameService
-Dch.idok.service.common.keypass=P7GXtm6rPr5pT988N4Gm
-Dch.idok.privateKeyPassword=90fd3498jw03riqdgs4f
-Dch.idok.service.server.admin.masterAdminName=idokadmin
-Dch.idok.service.server.login.config=DmsServerMpc1127
-Dch.idok.setupClass=ch.psi.idok.common.config.Setup"/>
</launchConfiguration>

Modified: branches/rest/sites/psi/java/ch/psi/idok/common/config/jaas.conf
==============================================================================
--- branches/rest/sites/psi/java/ch/psi/idok/common/config/jaas.conf
(original)
+++ branches/rest/sites/psi/java/ch/psi/idok/common/config/jaas.conf Thu
Aug 14 16:24:34 2008
@@ -51,3 +51,13 @@
useTicketCache=false
debug=false;
};
+
+DmsServerMpc1127 {
+ com.sun.security.auth.module.Krb5LoginModule required
+ storeKey=true
+ principal="HTTP/mpc1127.psi.ch AT D.PSI.CH"
+ useKeyTab=true
+ keyTab="/etc/mpc1127.keytab"
+ useTicketCache=false
+ debug=false;
+};



  • [idok-commit] idok commit r175 - in branches/rest: java/ch/idok/service/server/rest java/ch/idok/service/server/search/rest misc/eclipse sites/psi/java/ch/psi/idok/common/config, AFS account Roman Geus, 08/14/2008

Archive powered by MHonArc 2.6.19.

Top of Page