diff --git a/hapi-fhir-base/pom.xml b/hapi-fhir-base/pom.xml
index dc5be2c5942..1752cec613b 100644
--- a/hapi-fhir-base/pom.xml
+++ b/hapi-fhir-base/pom.xml
@@ -46,8 +46,15 @@
4.2.0
-
+
+
+ org.mitre
+ openid-connect-client
+ ${mitreid-connect-version}
+ true
+
+
org.thymeleaf
thymeleaf
@@ -113,13 +120,19 @@
spring-beans
${spring_version}
true
+
+
+ commons-logging
+ commons-logging
+
+
javax.servlet
javax.servlet-api
- 3.0.1
+ 3.1.0
provided
@@ -166,8 +179,14 @@
9.1.1.v20140108
test
+
+ org.eclipse.jetty
+ jetty-webapp
+ 9.1.1.v20140108
+ test
+
-
+
org.mockito
mockito-all
@@ -194,20 +213,17 @@
jdk15-sources
test
-
-
-
- commons-httpclient
- commons-httpclient
- 3.1
- test
-
-
directory-naming
naming-java
0.8
test
+
+
+ commons-logging
+ commons-logging
+
+
org.hamcrest
@@ -215,6 +231,23 @@
${hamcrest_version}
test
+
+ org.springframework.security
+ spring-security-web
+ ${spring_security_version}
+ test
+
+
+ org.springframework.security
+ spring-security-config
+ ${spring_security_version}
+ test
+
+
+ org.springframework.security
+ spring-security-jwt
+ 1.0.2.RELEASE
+
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/ISecurityManager.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/ISecurityManager.java
index 56f6e5f4d25..88d5c4fd939 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/ISecurityManager.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/ISecurityManager.java
@@ -25,8 +25,8 @@ import javax.servlet.http.HttpServletRequest;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
/**
- * Created by dsotnikov on 3/7/2014.
+ * @deprecated Use {@link ca.uhn.fhir.rest.server.security.ISecurityManager} instead
*/
public interface ISecurityManager {
- public void authenticate(HttpServletRequest request) throws AuthenticationException;
+ public void authenticate(HttpServletRequest request) throws AuthenticationException;
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java
index 4f2c9b4b14b..be47c36be2b 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java
@@ -20,7 +20,7 @@ package ca.uhn.fhir.rest.server;
* #L%
*/
-import static org.apache.commons.lang3.StringUtils.*;
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.IOException;
import java.io.OutputStreamWriter;
@@ -82,7 +82,6 @@ import ca.uhn.fhir.util.VersionUtil;
public class RestfulServer extends HttpServlet {
-
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServer.class);
private static final long serialVersionUID = 1L;
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/exceptions/AuthenticationException.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/exceptions/AuthenticationException.java
index a212592204e..c863964832e 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/exceptions/AuthenticationException.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/exceptions/AuthenticationException.java
@@ -1,5 +1,7 @@
package ca.uhn.fhir.rest.server.exceptions;
+import java.text.ParseException;
+
import ca.uhn.fhir.rest.server.Constants;
/*
@@ -22,23 +24,25 @@ import ca.uhn.fhir.rest.server.Constants;
* #L%
*/
-
/**
- * Represents an HTTP 401 Client Unauthorized response, which means that
- * the client needs to provide credentials, or has provided invalid credentials.
+ * Represents an HTTP 401 Client Unauthorized response, which means that the client needs to provide credentials, or has provided invalid credentials.
*/
public class AuthenticationException extends BaseServerResponseException {
- public static final int STATUS_CODE = Constants.STATUS_HTTP_401_CLIENT_UNAUTHORIZED;
-
+ public static final int STATUS_CODE = Constants.STATUS_HTTP_401_CLIENT_UNAUTHORIZED;
+
private static final long serialVersionUID = 1L;
- public AuthenticationException() {
- super(STATUS_CODE, "Client unauthorized");
- }
+ public AuthenticationException() {
+ super(STATUS_CODE, "Client unauthorized");
+ }
- public AuthenticationException(String theMessage) {
- super(STATUS_CODE, theMessage);
- }
+ public AuthenticationException(String theMessage) {
+ super(STATUS_CODE, theMessage);
+ }
+
+ public AuthenticationException(String theMessage, ParseException theCause) {
+ super(STATUS_CODE, theMessage, theCause);
+ }
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/security/IResourceSecurity.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/security/IResourceSecurity.java
new file mode 100644
index 00000000000..f1c01a69626
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/security/IResourceSecurity.java
@@ -0,0 +1,35 @@
+package ca.uhn.fhir.rest.server.security;
+
+/*
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import javax.servlet.http.HttpServletRequest;
+
+import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
+
+/**
+ * Implementations of this interface provide authorization to incoming service calls
+ *
+ */
+public interface IResourceSecurity {
+
+ public ISecurityOutcome authenticate(HttpServletRequest request) throws AuthenticationException;
+
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/security/ISecurityOutcome.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/security/ISecurityOutcome.java
new file mode 100644
index 00000000000..64e223cf15c
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/security/ISecurityOutcome.java
@@ -0,0 +1,5 @@
+package ca.uhn.fhir.rest.server.security;
+
+public interface ISecurityOutcome {
+
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/security/OpenIdConnectBearerTokenSecurityManager.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/security/OpenIdConnectBearerTokenSecurityManager.java
new file mode 100644
index 00000000000..9c1c971e56f
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/security/OpenIdConnectBearerTokenSecurityManager.java
@@ -0,0 +1,162 @@
+package ca.uhn.fhir.rest.server.security;
+
+import java.text.ParseException;
+import java.util.Date;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
+import org.mitre.jwt.signer.service.impl.JWKSetCacheService;
+import org.mitre.jwt.signer.service.impl.SymmetricCacheService;
+import org.mitre.oauth2.model.RegisteredClient;
+import org.mitre.openid.connect.client.service.ClientConfigurationService;
+import org.mitre.openid.connect.client.service.ServerConfigurationService;
+import org.mitre.openid.connect.config.ServerConfiguration;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import ca.uhn.fhir.rest.server.Constants;
+import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
+
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jwt.ReadOnlyJWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+
+public class OpenIdConnectBearerTokenSecurityManager implements IResourceSecurity {
+
+ private static final String BEARER_PREFIX = "Bearer ";
+
+ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(OpenIdConnectBearerTokenSecurityManager.class);
+ @Autowired
+ private ClientConfigurationService myClientConfigurationService;
+
+ @Autowired
+ private ServerConfigurationService myServerConfigurationService;
+
+ private int myTimeSkewAllowance = 300;
+
+ private SymmetricCacheService symmetricCacheService;
+ private JWKSetCacheService validationServices;
+
+ public OpenIdConnectBearerTokenSecurityManager() {
+ symmetricCacheService = new SymmetricCacheService();
+ validationServices = new JWKSetCacheService();
+ }
+
+ @Override
+ public ISecurityOutcome authenticate(HttpServletRequest theRequest) throws AuthenticationException {
+ String token = theRequest.getHeader(Constants.HEADER_AUTHORIZATION);
+ if (token == null) {
+ throw new AuthenticationException("Not authorized (no authorization header found in request)");
+ }
+ if (!token.startsWith(BEARER_PREFIX)) {
+ throw new AuthenticationException("Not authorized (authorization header does not contain a bearer token)");
+ }
+
+ token = token.substring(BEARER_PREFIX.length());
+
+ SignedJWT idToken;
+ try {
+ idToken = SignedJWT.parse(token);
+ } catch (ParseException e) {
+ throw new AuthenticationException("Not authorized (bearer token could not be validated)", e);
+ }
+
+ // validate our ID Token over a number of tests
+ ReadOnlyJWTClaimsSet idClaims;
+ try {
+ idClaims = idToken.getJWTClaimsSet();
+ } catch (ParseException e) {
+ throw new AuthenticationException("Not authorized (bearer token could not be validated)", e);
+ }
+
+ String issuer = idClaims.getIssuer();
+
+ ServerConfiguration serverConfig = myServerConfigurationService.getServerConfiguration(issuer);
+ if (serverConfig == null) {
+ ourLog.error("No server configuration found for issuer: " + issuer);
+ throw new AuthenticationException("Not authorized (no server configuration found for issuer " + issuer + ")");
+ }
+
+ RegisteredClient clientConfig = myClientConfigurationService.getClientConfiguration(serverConfig);
+ if (clientConfig == null) {
+ ourLog.error("No client configuration found for issuer: " + issuer);
+ throw new AuthenticationException("Not authorized (no client configuration found for issuer " + issuer + ")");
+ }
+
+ // check the signature
+ JwtSigningAndValidationService jwtValidator = null;
+
+ JWSAlgorithm alg = idToken.getHeader().getAlgorithm();
+ if (alg.equals(JWSAlgorithm.HS256) || alg.equals(JWSAlgorithm.HS384) || alg.equals(JWSAlgorithm.HS512)) {
+
+ // generate one based on client secret
+ jwtValidator = symmetricCacheService.getSymmetricValidtor(clientConfig.getClient());
+ } else {
+ // otherwise load from the server's public key
+ jwtValidator = validationServices.getValidator(serverConfig.getJwksUri());
+ }
+
+ if (jwtValidator != null) {
+ if (!jwtValidator.validateSignature(idToken)) {
+ throw new AuthenticationException("Not authorized (signature validation failed)");
+ }
+ } else {
+ ourLog.error("No validation service found. Skipping signature validation");
+ throw new AuthenticationException("Not authorized (can't determine signature validator)");
+ }
+
+ // check expiration
+ if (idClaims.getExpirationTime() == null) {
+ throw new AuthenticationException("Id Token does not have required expiration claim");
+ } else {
+ // it's not null, see if it's expired
+ Date now = new Date(System.currentTimeMillis() - (myTimeSkewAllowance * 1000));
+ if (now.after(idClaims.getExpirationTime())) {
+ throw new AuthenticationException("Id Token is expired: " + idClaims.getExpirationTime());
+ }
+ }
+
+ // check not before
+ if (idClaims.getNotBeforeTime() != null) {
+ Date now = new Date(System.currentTimeMillis() + (myTimeSkewAllowance * 1000));
+ if (now.before(idClaims.getNotBeforeTime())) {
+ throw new AuthenticationException("Id Token not valid untill: " + idClaims.getNotBeforeTime());
+ }
+ }
+
+ // check issued at
+ if (idClaims.getIssueTime() == null) {
+ throw new AuthenticationException("Id Token does not have required issued-at claim");
+ } else {
+ // since it's not null, see if it was issued in the future
+ Date now = new Date(System.currentTimeMillis() + (myTimeSkewAllowance * 1000));
+ if (now.before(idClaims.getIssueTime())) {
+ throw new AuthenticationException("Id Token was issued in the future: " + idClaims.getIssueTime());
+ }
+ }
+
+ return new ISecurityOutcome() {
+ };
+ }
+
+ public int getTimeSkewAllowance() {
+ return myTimeSkewAllowance;
+ }
+
+ public void setClientConfigurationService(ClientConfigurationService theClientConfigurationService) {
+ myClientConfigurationService = theClientConfigurationService;
+ }
+
+ public void setServerConfigurationService(ServerConfigurationService theServerConfigurationService) {
+ myServerConfigurationService = theServerConfigurationService;
+ }
+
+ public void setTimeSkewAllowance(int theTimeSkewAllowance) {
+ myTimeSkewAllowance = theTimeSkewAllowance;
+ }
+
+ public void setValidationServices(JWKSetCacheService theValidationServices) {
+ validationServices = theValidationServices;
+ }
+
+}
diff --git a/hapi-fhir-base/src/site/example/java/example/ClientExamples.java b/hapi-fhir-base/src/site/example/java/example/ClientExamples.java
index 95434151eac..24ce61cc761 100644
--- a/hapi-fhir-base/src/site/example/java/example/ClientExamples.java
+++ b/hapi-fhir-base/src/site/example/java/example/ClientExamples.java
@@ -65,7 +65,7 @@ IPatientClient annotationClient = ctx.newRestfulClient(IPatientClient.class, "ht
annotationClient.registerInterceptor(loggingInterceptor);
IGenericClient genericClient = ctx.newRestfulGenericClient("http://localhost:9999/fhir");
-annotationClient.registerInterceptor(loggingInterceptor);
+genericClient.registerInterceptor(loggingInterceptor);
//END SNIPPET: logging
}
diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ResfulServerMethodTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ResfulServerMethodTest.java
index 939e6bf3e0e..224ab6b3dbf 100644
--- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ResfulServerMethodTest.java
+++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ResfulServerMethodTest.java
@@ -71,8 +71,8 @@ import ca.uhn.fhir.rest.annotation.Validate;
import ca.uhn.fhir.rest.annotation.VersionIdParam;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.param.CodingListParam;
-import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.DateParam;
+import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.provider.ServerProfileProvider;
diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ServerSecurityTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ServerSecurityTest.java
new file mode 100644
index 00000000000..c7fd5d33e7d
--- /dev/null
+++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ServerSecurityTest.java
@@ -0,0 +1,183 @@
+package ca.uhn.fhir.rest.server;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.resource.ResourceFactory;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.model.api.Bundle;
+import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
+import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
+import ca.uhn.fhir.model.dstu.resource.Patient;
+import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum;
+import ca.uhn.fhir.model.primitive.IdDt;
+import ca.uhn.fhir.model.primitive.UriDt;
+import ca.uhn.fhir.rest.annotation.IdParam;
+import ca.uhn.fhir.rest.annotation.Read;
+import ca.uhn.fhir.rest.annotation.RequiredParam;
+import ca.uhn.fhir.rest.annotation.Search;
+import ca.uhn.fhir.rest.server.provider.ServerProfileProvider;
+import ca.uhn.fhir.testutil.RandomServerPortProvider;
+
+/**
+ * Created by dsotnikov on 2/25/2014.
+ */
+public class ServerSecurityTest {
+
+ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerSecurityTest.class);
+ private static CloseableHttpClient ourClient;
+ private static FhirContext ourCtx;
+
+ @BeforeClass
+ public static void beforeClass() throws Exception {
+ ourCtx = new FhirContext(Patient.class);
+
+ PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
+ HttpClientBuilder builder = HttpClientBuilder.create();
+ builder.setConnectionManager(connectionManager);
+ ourClient = builder.build();
+ }
+
+ @Test
+ public void testContextWithSpace() throws Exception {
+ if (true) return;
+
+ int port = RandomServerPortProvider.findFreePort();
+ Server server = new Server(port);
+
+ RestfulServer restServer = new RestfulServer();
+ restServer.setFhirContext(ourCtx);
+ restServer.setResourceProviders(new DummyPatientResourceProvider());
+
+ // ServletHandler proxyHandler = new ServletHandler();
+ ServletHolder servletHolder = new ServletHolder(restServer);
+
+ WebAppContext wac = new WebAppContext();
+ wac.setWar("src/test/resources/securitytest_war");
+
+// wac.addServlet(servletHolder, "/fhir/*");
+
+
+ ServletContextHandler ch = new ServletContextHandler();
+ ch.setContextPath("/");
+ ch.addServlet(servletHolder, "/fhir/*");
+// ch.add
+// ch.addFilter(org.springframework.web.filter.DelegatingFilterProxy.class, "/*", null);
+
+ ContextHandlerCollection contexts = new ContextHandlerCollection();
+ server.setHandler(wac);
+
+// server.setHandler(ch);
+ server.start();
+ try {
+
+ String baseUri = "http://localhost:" + port + "/fhir";
+ String uri = baseUri + "/Patient?identifier=urn:hapitest:mrns%7C00001";
+ HttpGet httpGet = new HttpGet(uri);
+ httpGet.addHeader("Authorization", "Basic eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0MDY4MzU3MjMsImF1ZCI6WyJjbGllbnQiXSwiaXNzIjoiaHR0cDpcL1wvdWhudmVzYjAxZC51aG4ub24uY2E6MjUxODBcL3Vobi1vcGVuaWQtY29ubmVjdC1zZXJ2ZXJcLyIsImp0aSI6IjEwZTYwYWY1LTEyZmUtNDFhYy05MWQyLTliNWY0NzBiNGM5OSIsImlhdCI6MTQwNjgzMjEyM30.LaCAOmoM0ikkaalKBfccU_YE8NBXvT5M7L9ITfR86vEp5-3_kVeSrYHI4CjVUSDafyVwQF4x5eJVSZdtQxtEk9F1L6be_JQI84crqff53EKA10z7m9Lkc5jJFs6sd9cnlayhlBgxL7BNdSf0Bjs7aS6YMEcn9IY3RNSeQj7kjhs");
+ HttpResponse status = ourClient.execute(httpGet);
+
+ String responseContent = IOUtils.toString(status.getEntity().getContent());
+ IOUtils.closeQuietly(status.getEntity().getContent());
+ ourLog.info("Response was:\n{}", responseContent);
+
+ assertEquals(200, status.getStatusLine().getStatusCode());
+ Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent);
+
+ assertEquals(1, bundle.getEntries().size());
+
+ } finally {
+ server.stop();
+ }
+
+ }
+
+
+ /**
+ * Created by dsotnikov on 2/25/2014.
+ */
+ public static class DummyPatientResourceProvider implements IResourceProvider {
+
+ public Map getIdToPatient() {
+ Map idToPatient = new HashMap();
+ {
+ Patient patient = new Patient();
+ patient.setId("1");
+ patient.addIdentifier();
+ patient.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL);
+ patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns"));
+ patient.getIdentifier().get(0).setValue("00001");
+ patient.addName();
+ patient.getName().get(0).addFamily("Test");
+ patient.getName().get(0).addGiven("PatientOne");
+ patient.getGender().setText("M");
+ idToPatient.put("1", patient);
+ }
+ {
+ Patient patient = new Patient();
+ patient.setId("2");
+ patient.getIdentifier().add(new IdentifierDt());
+ patient.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL);
+ patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns"));
+ patient.getIdentifier().get(0).setValue("00002");
+ patient.getName().add(new HumanNameDt());
+ patient.getName().get(0).addFamily("Test");
+ patient.getName().get(0).addGiven("PatientTwo");
+ patient.getGender().setText("F");
+ idToPatient.put("2", patient);
+ }
+ return idToPatient;
+ }
+
+ @Search()
+ public Patient getPatient(@RequiredParam(name = Patient.SP_IDENTIFIER) IdentifierDt theIdentifier) {
+ for (Patient next : getIdToPatient().values()) {
+ for (IdentifierDt nextId : next.getIdentifier()) {
+ if (nextId.matchesSystemAndValue(theIdentifier)) {
+ return next;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Retrieve the resource by its identifier
+ *
+ * @param theId
+ * The resource identity
+ * @return The resource
+ */
+ @Read()
+ public Patient getResourceById(@IdParam IdDt theId) {
+ return getIdToPatient().get(theId.getValue());
+ }
+
+ @Override
+ public Class getResourceType() {
+ return Patient.class;
+ }
+
+ }
+
+}
diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ServerSecurityTestRestfulServlet.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ServerSecurityTestRestfulServlet.java
new file mode 100644
index 00000000000..2f9d5af9f4f
--- /dev/null
+++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ServerSecurityTestRestfulServlet.java
@@ -0,0 +1,15 @@
+package ca.uhn.fhir.rest.server;
+
+import javax.servlet.ServletException;
+
+import ca.uhn.fhir.rest.server.ServerSecurityTest.DummyPatientResourceProvider;
+
+public class ServerSecurityTestRestfulServlet extends RestfulServer
+{
+
+ @Override
+ protected void initialize() throws ServletException {
+ setResourceProviders(new DummyPatientResourceProvider());
+ }
+
+}
\ No newline at end of file
diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/security/OpenIdConnectBearerTokenSecurityManagerTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/security/OpenIdConnectBearerTokenSecurityManagerTest.java
new file mode 100644
index 00000000000..4e83f0b1e10
--- /dev/null
+++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/security/OpenIdConnectBearerTokenSecurityManagerTest.java
@@ -0,0 +1,73 @@
+package ca.uhn.fhir.rest.server.security;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.HashMap;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.hamcrest.core.StringContains;
+import org.junit.Test;
+import org.mitre.jose.keystore.JWKSetKeyStore;
+import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
+import org.mitre.jwt.signer.service.impl.DefaultJwtSigningAndValidationService;
+import org.mitre.jwt.signer.service.impl.JWKSetCacheService;
+import org.mitre.oauth2.model.RegisteredClient;
+import org.mitre.openid.connect.client.service.impl.DynamicRegistrationClientConfigurationService;
+import org.mitre.openid.connect.client.service.impl.StaticClientConfigurationService;
+import org.mitre.openid.connect.client.service.impl.StaticServerConfigurationService;
+import org.mitre.openid.connect.config.ServerConfiguration;
+import org.springframework.core.io.ClassPathResource;
+
+import ca.uhn.fhir.rest.server.Constants;
+import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
+
+public class OpenIdConnectBearerTokenSecurityManagerTest {
+
+ @Test
+ public void testValidateToken() throws Exception {
+
+ StaticServerConfigurationService srv = new StaticServerConfigurationService();
+ srv.setServers(new HashMap());
+ ServerConfiguration srvCfg = new ServerConfiguration();
+ srvCfg.setJwksUri("AAAAAA");
+ srvCfg.setIssuer("http://localhost:8888/uhn-openid-connect/");
+ srv.getServers().put("http://localhost:8888/uhn-openid-connect/", srvCfg);
+ srv.afterPropertiesSet();
+
+ StaticClientConfigurationService cli = new StaticClientConfigurationService();
+ cli.setClients(new HashMap());
+ cli.getClients().put("http://localhost:8888/uhn-openid-connect/", new RegisteredClient());
+
+ OpenIdConnectBearerTokenSecurityManager mgr = new OpenIdConnectBearerTokenSecurityManager();
+ mgr.setClientConfigurationService(cli);
+ mgr.setServerConfigurationService(srv);
+
+ HttpServletRequest req = mock(HttpServletRequest.class);
+ when(req.getHeader(Constants.HEADER_AUTHORIZATION)).thenReturn("Bearer " + "eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0MDY4NDE4NTgsImF1ZCI6WyJjbGllbnQiXSwiaXNzIjoiaHR0cDpcL1wvbG9jYWxob3N0Ojg4ODhcL3Vobi1vcGVuaWQtY29ubmVjdFwvIiwianRpIjoiOTNiMzRjOTUtNTNiMC00YzZmLTkwYjEtYWVjODRjZTc3OGFhIiwiaWF0IjoxNDA2ODM4MjU4fQ.fYtwehPUulUYnDG_10bN6TNf7uw2FNUh_E40YagpITrVfXsV06pjU2YpNgy8nbSFmxY9IBH44UXTmMH9PLFiRn88WsPMSrUQbFCcvGIYwhqkRjGm_J1Y6oWIafUzCwZBCvk4Ne44p3DJRR6FSZRnnC850p55901DGQmNLe-rZJk3t0MHl6wySduqT3K1-Vbuq-7H6xLE10hKpLhSqBTghpQNKNjm48jm0sHcFa3ENWzyWPOmpNfzDKmJAYK2UnBtqNSJP6AJzVrJXqSu-uzasq0VOVcRU4n8b39vU1olbho1eKF0cfQlQwbrtvWipBJJSsRp_tmB9SV9BXhENxOFTw");
+
+ JWKSetCacheService val = mock(JWKSetCacheService.class);
+
+ JWKSetKeyStore keyStore = new JWKSetKeyStore();
+ keyStore.setLocation(new ClassPathResource("/svr_keystore.jwks"));
+ DefaultJwtSigningAndValidationService valSvc = new DefaultJwtSigningAndValidationService(keyStore);
+ when(val.getValidator("AAAAAA")).thenReturn(valSvc);
+
+ mgr.setValidationServices(val);
+
+ try {
+ mgr.authenticate(req);
+ fail();
+ } catch (AuthenticationException e) {
+ assertThat(e.getMessage(), StringContains.containsString("expired"));
+ }
+
+ mgr.setTimeSkewAllowance(10 * 365 * 24 * 60);
+ mgr.authenticate(req);
+
+
+ }
+
+}
diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/security/RestAuthenticationEntryPoint.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/security/RestAuthenticationEntryPoint.java
new file mode 100644
index 00000000000..60a0f262eb0
--- /dev/null
+++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/security/RestAuthenticationEntryPoint.java
@@ -0,0 +1,18 @@
+package ca.uhn.fhir.rest.server.security;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+
+public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint{
+
+ @Override
+ public void commence( HttpServletRequest request, HttpServletResponse response,
+ AuthenticationException authException ) throws IOException{
+ response.sendError( HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized" );
+ }
+}
\ No newline at end of file
diff --git a/hapi-fhir-base/src/test/resources/logback-test.xml b/hapi-fhir-base/src/test/resources/logback-test.xml
index fdd916f6e23..ba6af5fea46 100644
--- a/hapi-fhir-base/src/test/resources/logback-test.xml
+++ b/hapi-fhir-base/src/test/resources/logback-test.xml
@@ -7,8 +7,10 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ openid
+ email
+ address
+ profile
+ phone
+
+
+
+
+
+ http://localhost:8080/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/hapi-fhir-base/src/test/resources/securitytest_war/WEB-INF/web.xml b/hapi-fhir-base/src/test/resources/securitytest_war/WEB-INF/web.xml
new file mode 100644
index 00000000000..56e6a03c828
--- /dev/null
+++ b/hapi-fhir-base/src/test/resources/securitytest_war/WEB-INF/web.xml
@@ -0,0 +1,35 @@
+
+
+
+
+ springSecurityFilterChain
+ org.springframework.web.filter.DelegatingFilterProxy
+
+
+
+ springSecurityFilterChain
+ /*
+
+
+
+ org.springframework.web.context.ContextLoaderListener
+
+
+
+ contextConfigLocation
+
+ WEB-INF/app-ctx.xml
+
+
+
+
+ fhir
+ ca.uhn.fhir.rest.server.ServerSecurityTestRestfulServlet
+
+
+
+ fhir
+ /fhir/*
+
+
+
\ No newline at end of file
diff --git a/hapi-fhir-base/src/test/resources/svr_keystore.jwks b/hapi-fhir-base/src/test/resources/svr_keystore.jwks
new file mode 100644
index 00000000000..dfa8e782ded
--- /dev/null
+++ b/hapi-fhir-base/src/test/resources/svr_keystore.jwks
@@ -0,0 +1,11 @@
+{
+ "keys": [
+ {
+ "d": "S0pTrzR78vyso2ufMTAJMPNpqSgF0jggquQ2crgBw_il-QQ5zAHzlrreGWDcvr94i6PAy4xQYRAAaO_ZtjO4kMmEKdJ-bgsAJiIrUAlJdv0JCK-j3G64R0EiwrSzXqyt3E6kiA14o5wYgs86RO7TV6dpFyNuSIoZJeNG-NBviPIwY222PX07xjaMk7tSQdlTP6Vg1EZV-i0AqYGE-Ivn2eaI0KvjzPWVXVki1F6J4yQzErxcVILgicAp6Eeqy24E3enmrsQozj54Sswjy8r_MrGf2iSaf0GedLsCXWSQ-uojWF6llfjL9Q5be65ztPkvN4cPGn0o_sHzqewvFnZuAQ",
+ "e": "AQAB",
+ "n": "kmjocunUF1bw2rSnBtiA9P2LJcuUdTSpgS8e2MccFv0VLNFd50P7JMxmLt_ildr040371QPflz0wdE_O8gypJr17RyJCg7mthCRpNLxkVHS8Pd3p0fzn_xlmrm4gkR033k7aTH3bB-usjq5Z8A6GjxlUp2Of5TRJ1Tj9287m9X2w65DDCcOQYC8horObEEUkchfpyf5PGCdkq-1U46_P66RcFuLjxaO6R5OWrVK8Zd-tHOG-O3-nn0eAHxFvtGDiYOVImQ7rWEriTDQdefiIIOM_m73xbtdVM2jEgQfn76u0TvK7nLmz1w4XvddOv2TatXmAtNmTpP16629eqnKcMw",
+ "kty": "RSA",
+ "kid": "rsa1"
+ }
+ ]
+}
diff --git a/hapi-fhir-jpaserver-uhnfhirtest/.settings/org.eclipse.wst.common.component b/hapi-fhir-jpaserver-uhnfhirtest/.settings/org.eclipse.wst.common.component
index 15e9bb84a2d..c94187882b4 100644
--- a/hapi-fhir-jpaserver-uhnfhirtest/.settings/org.eclipse.wst.common.component
+++ b/hapi-fhir-jpaserver-uhnfhirtest/.settings/org.eclipse.wst.common.component
@@ -6,10 +6,10 @@
-
+
uses
-
+
uses
diff --git a/hapi-tinder-plugin/pom.xml b/hapi-tinder-plugin/pom.xml
index fb4d411f568..2d1ca1e3b96 100644
--- a/hapi-tinder-plugin/pom.xml
+++ b/hapi-tinder-plugin/pom.xml
@@ -9,7 +9,6 @@
../pom.xml
- ca.uhn.hapi.fhir
hapi-tinder-plugin
maven-plugin
diff --git a/pom.xml b/pom.xml
index 7ce8213e3db..0c14d49d111 100644
--- a/pom.xml
+++ b/pom.xml
@@ -75,16 +75,18 @@
5.1.0.Final
9.1.1.v20140108
9.1.1.v20140108
+ 4.3.1
4.11
1.1.1
2.9.1
3.3
+ 1.1.8
1.9.5
UTF-8
1.7.7
4.0.1.RELEASE
+ 3.2.4.RELEASE
2.1.3.RELEASE
- 4.3.1