diff --git a/adapter/adapter-service/src/main/scala/net/shrine/adapter/service/AdapterResource.scala b/adapter/adapter-service/src/main/scala/net/shrine/adapter/service/AdapterResource.scala index d0fd7de8e..ed7adda72 100644 --- a/adapter/adapter-service/src/main/scala/net/shrine/adapter/service/AdapterResource.scala +++ b/adapter/adapter-service/src/main/scala/net/shrine/adapter/service/AdapterResource.scala @@ -1,70 +1,74 @@ package net.shrine.adapter.service import net.shrine.log.Loggable import scala.util.Try import scala.util.control.NonFatal -import javax.ws.rs.POST -import javax.ws.rs.Path -import javax.ws.rs.Produces +import javax.ws.rs.{GET, POST, Path, Produces} import javax.ws.rs.core.MediaType import javax.ws.rs.core.Response import javax.ws.rs.core.Response.ResponseBuilder -import net.shrine.protocol.{ResultOutputTypes, BroadcastMessage, ResultOutputType} + +import net.shrine.protocol.{BroadcastMessage, ResultOutputType, ResultOutputTypes} + import scala.xml.XML import net.shrine.util.StringEnrichments /** * @author clint * @since Nov 15, 2013 */ @Path("adapter") @Produces(Array(MediaType.APPLICATION_XML)) //NB: Is a case class to get apply() on the companion object for smoother testing final case class AdapterResource(service: AdapterRequestHandler) extends Loggable { @POST @Path("requests") def handleRequest(messageXml: String): Response = { import AdapterResource.StatusCodes._ def handleRequest(message: BroadcastMessage): Try[ResponseBuilder] = Try { info(s"Running request ${message.requestId} from user: ${message.networkAuthn.domain}:${message.networkAuthn.username} of type ${message.request.requestType.toString}") val adapterResult = service.handleRequest(message) val responseString = adapterResult.toXmlString Response.ok.entity(responseString) }.recover { - case NonFatal(e) => { + case NonFatal(e) => error("Error processing request: ", e) - - throw e; - } + throw e } def handleParseError(e: Throwable): Try[ResponseBuilder] = { - debug(s"Failed to unmarshal broadcast message XML: '$messageXml'") + debug(s"Failed to unmarshall broadcast message XML: '$messageXml'") error("Couldn't understand request: ", e) scala.util.Failure(e) } import StringEnrichments._ val broadcastMessageAttempt = messageXml.tryToXml.flatMap(BroadcastMessage.fromXml) val builderAttempt = broadcastMessageAttempt.transform(handleRequest, handleParseError) builderAttempt.get.build() } + + @GET + @Path("requests") + def handleGet: Response = { + Response.ok.entity("THIS IS A TEST THIS IS A TEST EHLLO").build() + } } //NB: extends Handler => Resource for smoother testing object AdapterResource extends (AdapterRequestHandler => AdapterResource) { object StatusCodes { val InternalError = 500 val BadRequest = 400 } } \ No newline at end of file diff --git a/apps/shrine-app/src/main/scala/net/shrine/status/StatusJaxrs.scala b/apps/shrine-app/src/main/scala/net/shrine/status/StatusJaxrs.scala index 39230db10..c14a03819 100644 --- a/apps/shrine-app/src/main/scala/net/shrine/status/StatusJaxrs.scala +++ b/apps/shrine-app/src/main/scala/net/shrine/status/StatusJaxrs.scala @@ -1,601 +1,602 @@ package net.shrine.status import java.security.cert.X509Certificate import javax.net.ssl.{KeyManager, SSLContext, X509TrustManager} import javax.ws.rs.core.{MediaType, Response} import javax.ws.rs.{GET, Path, Produces, WebApplicationException} import akka.actor.ActorSystem import akka.io.IO import akka.util.Timeout import com.sun.jersey.spi.container.{ContainerRequest, ContainerRequestFilter} import com.typesafe.config.{ConfigFactory, Config => TsConfig} import net.shrine.authorization.{QueryAuthorizationService, StewardQueryAuthorizationService} import net.shrine.broadcaster._ import net.shrine.client.PosterOntClient import net.shrine.config.ConfigExtensions import net.shrine.crypto._ import net.shrine.log.{Log, Loggable} import net.shrine.ont.data.OntClientOntologyMetadata import net.shrine.problem.{AbstractProblem, ProblemSources} import net.shrine.protocol._ import net.shrine.protocol.query.{OccuranceLimited, QueryDefinition, Term} import net.shrine.serialization.NodeSeqSerializer import net.shrine.spray._ import net.shrine.util.{PeerToPeerModel, SingleHubModel, Versions} import net.shrine.wiring.ShrineOrchestrator import org.json4s.native.Serialization import org.json4s.{DefaultFormats, Formats} import spray.can.Http import spray.can.Http.{HostConnectorInfo, HostConnectorSetup} import spray.client.pipelining._ import spray.http.{ContentType, HttpCharsets, HttpEntity, HttpRequest, HttpResponse, MediaTypes} import spray.io.{ClientSSLEngineProvider, PipelineContext, SSLContextProvider} import scala.collection.JavaConverters._ import scala.collection.immutable.{Map, Seq, Set} import scala.concurrent.duration.FiniteDuration import scala.concurrent.{Await, Future, duration} import scala.util.control.NonFatal import scala.util.{Failure, Success, Try} /** * A subservice that shares internal state of the shrine servlet. * * @author david * @since 12/2/15 */ @Path("/internalstatus") @Produces(Array(MediaType.APPLICATION_JSON)) case class StatusJaxrs(shrineConfig: TsConfig) extends Loggable { implicit def json4sFormats: Formats = DefaultFormats + new NodeSeqSerializer @GET @Path("version") def version: String = { val version = Version("changeMe") val versionString = Serialization.write(version) versionString } @GET @Path("config") def config: String = { //todo probably better to reach out and grab the config from ManuallyWiredShrineJaxrsResources once it is a singleton Serialization.write(Json4sConfig(shrineConfig)) } @GET @Path("summary") def summary: String = { val summary = Summary() Serialization.write(summary) } @GET @Path("i2b2") def i2b2: String = { val i2b2 = I2b2() Serialization.write(i2b2) } @GET @Path("optionalParts") def optionalParts: String = { val optionalParts = OptionalParts() Serialization.write(optionalParts) } @GET @Path("hub") def hub: String = { val hub = Hub() Serialization.write(hub) } @GET @Path("adapter") def adapter: String = { val adapter = Adapter() Serialization.write(adapter) } @GET @Path("qep") def qep: String = { val qep = Qep() Serialization.write(qep) } @GET @Path("keystore") def keystore: String = { Serialization.write(KeyStoreReport()) } } /* todo fill in later when you take the time to get the right parts in place SHRINE-1529 case class KeyStoreEntryReport( alias:String, commonName:String, md5Signature:String ) */ case class SiteStatus(siteAlias: String, theyHaveMine: Boolean, haveTheirs: Boolean, url: String, timeOutError: Boolean = false) extends DefaultJsonSupport case class AbbreviatedKeyStoreEntry(alias: String, cn: String, md5: String) extends DefaultJsonSupport case class KeyStoreReport( fileName: String, password: String = "REDACTED", privateKeyAlias: Option[String], owner: Option[String], issuer: Option[String], expires: Long, md5Signature: String, sha256Signature: String, caTrustedAlias: Option[String], caTrustedSignature: Option[String], remoteSiteStatuses: Seq[SiteStatus], isHub: Boolean, abbreviatedEntries: Seq[AbbreviatedKeyStoreEntry] // keyStoreContents:List[KeyStoreEntryReport] //todo SHRINE-1529 ) //todo build new API for the dashboard to use to check signatures object KeyStoreReport { def apply(): KeyStoreReport = { val keyStoreDescriptor: KeyStoreDescriptor = ShrineOrchestrator.keyStoreDescriptor val certCollection: BouncyKeyStoreCollection = ShrineOrchestrator.certCollection val maybeCaEntry: Option[KeyStoreEntry] = certCollection match { case DownStreamCertCollection(_, caEntry, _) => Some(caEntry) case HubCertCollection(_, caEntry, _) => Some(caEntry) case px: PeerCertCollection => None } val siteStatusesPreZip = ShaVerificationService(certCollection.remoteSites.toList) val siteStatuses = siteStatusesPreZip.zipWithIndex def sortFormat(input: String): Option[String] = { if (input.isEmpty) None else { def isLong(str: String) = str.split('=').headOption.getOrElse(str).length > 2 // Just an ugly sort for formatting purposes. I want The long key last, and otherwise just // Sort them lexicographically. Some(input.split(", ").sortBy(a => (isLong(a), a)).mkString(", ")) } } lazy val blockForSiteStatuses = siteStatuses.map(fut => Try(Await.result(fut._1, new FiniteDuration(5, duration.SECONDS))) match { case Success(Some(status)) => status case Success(None) => Log.warn("There was an issue with the verifySignature endpoint, check that we have network connectivity") SiteStatus(certCollection.remoteSites(fut._2).alias, false, false, "", true) case Failure(exc) => Log.warn("We timed out while trying to connect to the verifySignature endpoint, please check network connectivity") SiteStatus(certCollection.remoteSites(fut._2).alias, false, false, "", true) }) new KeyStoreReport( fileName = keyStoreDescriptor.file, privateKeyAlias = keyStoreDescriptor.privateKeyAlias, owner = sortFormat(certCollection.myEntry.cert.getSubjectDN.getName), issuer = sortFormat(certCollection.myEntry.cert.getIssuerDN.getName), expires = certCollection.myEntry.cert.getNotAfter.getTime, md5Signature = UtilHasher.encodeCert(certCollection.myEntry.cert, "MD5"), sha256Signature = UtilHasher.encodeCert(certCollection.myEntry.cert, "SHA-256"), //todo sha1 signature if needed caTrustedAlias = maybeCaEntry.map(_.aliases.first), caTrustedSignature = maybeCaEntry.map(entry => UtilHasher.encodeCert(entry.cert, "MD5")), remoteSiteStatuses = blockForSiteStatuses, isHub = keyStoreDescriptor.trustModel == SingleHubModel(true), abbreviatedEntries = certCollection.allEntries.map(entry => AbbreviatedKeyStoreEntry( entry.aliases.first, entry.commonName.getOrElse("No common name"), UtilHasher.encodeCert(entry.cert, "MD5"))).toList // keyStoreContents = certCollection.caCerts.zipWithIndex.map((cert: ((Principal, X509Certificate), Int)) => KeyStoreEntryReport(keyStoreDescriptor.caCertAliases(cert._2),cert._1._1.getName,toMd5(cert._1._2))).to[List] ) } } case class I2b2(pmUrl: String, crcUrl: Option[String], ontUrl: String, i2b2Domain: String, username: String, crcProject: String, ontProject: String) object I2b2 { def apply(): I2b2 = new I2b2( pmUrl = ShrineOrchestrator.pmPoster.url, crcUrl = ShrineOrchestrator.adapterComponents.map(_.i2b2AdminService.crcUrl), ontUrl = ShrineOrchestrator.ontEndpoint.url.toString, i2b2Domain = ShrineOrchestrator.crcHiveCredentials.domain, username = ShrineOrchestrator.crcHiveCredentials.username, crcProject = ShrineOrchestrator.crcHiveCredentials.projectId, ontProject = ShrineOrchestrator.ontologyMetadata.client match { case client: PosterOntClient => client.hiveCredentials.projectId case _ => "" } ) } case class DownstreamNode(name: String, url: String) // Replaces StewardQueryAuthorizationService so that we never transmit a password case class Steward(stewardBaseUrl: String, qepUsername: String, password: String = "REDACTED") case class Qep( maxQueryWaitTimeMillis: Long, create: Boolean, attachSigningCert: Boolean, authorizationType: String, includeAggregateResults: Boolean, authenticationType: String, steward: Option[Steward], broadcasterUrl: Option[String], trustModel: String, trustModelIsHub: Boolean ) object Qep { val key = "shrine.queryEntryPoint." import ShrineOrchestrator.queryEntryPointComponents def apply(): Qep = new Qep( maxQueryWaitTimeMillis = queryEntryPointComponents.fold(0L)(_.i2b2Service.queryTimeout.toMicros), create = queryEntryPointComponents.isDefined, attachSigningCert = queryEntryPointComponents.fold(false)(_.i2b2Service.broadcastAndAggregationService.attachSigningCert), authorizationType = queryEntryPointComponents.fold("")(_.i2b2Service.authorizationService.getClass.getSimpleName), includeAggregateResults = queryEntryPointComponents.fold(false)(_.i2b2Service.includeAggregateResult), authenticationType = queryEntryPointComponents.fold("")(_.i2b2Service.authenticator.getClass.getSimpleName), steward = queryEntryPointComponents.flatMap(qec => checkStewardAuthorization(qec.shrineService.authorizationService)), broadcasterUrl = queryEntryPointComponents.flatMap(_.shrineService.broadcastAndAggregationService.broadcasterUrl.map(_.toString)), trustModel = ShrineOrchestrator.keyStoreDescriptor.trustModel.description, trustModelIsHub = ShrineOrchestrator.keyStoreDescriptor.trustModel match { case sh: SingleHubModel => true case PeerToPeerModel => false }) def checkStewardAuthorization(auth: QueryAuthorizationService): Option[Steward] = auth match { case sa: StewardQueryAuthorizationService => Some(Steward(sa.stewardBaseUrl.toString, sa.qepUserName)) case _ => None } } object DownstreamNodes { def get(): Seq[DownstreamNode] = { ShrineOrchestrator.hubComponents.fold(Seq.empty[DownstreamNode])(_.broadcastDestinations.map(DownstreamNode(_)).to[Seq]) } } object DownstreamNode { def apply(nodeHandle: NodeHandle): DownstreamNode = new DownstreamNode( nodeHandle.nodeId.name, nodeHandle.client.url.map(_.toString).getOrElse("not applicable")) } case class Adapter(crcEndpointUrl: String, setSizeObfuscation: Boolean, adapterLockoutAttemptsThreshold: Int, adapterMappingsFilename: Option[String], adapterMappingsDate: Option[Long] ) object Adapter { def apply(): Adapter = { val crcEndpointUrl = ShrineOrchestrator.adapterComponents.fold("")(_.i2b2AdminService.crcUrl) val setSizeObfuscation = ShrineOrchestrator.adapterComponents.fold(false)(_.i2b2AdminService.obfuscate) val adapterLockoutAttemptsThreshold = ShrineOrchestrator.adapterComponents.fold(0)(_.i2b2AdminService.adapterLockoutAttemptsThreshold) val adapterMappingsFileName = mappingFileInfo.map(_._1) val adapterMappingsFileDate = mappingFileInfo.map(_._2) Adapter(crcEndpointUrl, setSizeObfuscation, adapterLockoutAttemptsThreshold, adapterMappingsFileName, adapterMappingsFileDate) } def mappingFileInfo: Option[(String, Long, String)] = ShrineOrchestrator.adapterComponents.map(ac => (ac.adapterMappings.source, ac.lastModified, ac.adapterMappings.version)) } case class Hub(shouldQuerySelf: Boolean, //todo don't use this field any more. Drop it when possible create: Boolean, downstreamNodes: Seq[DownstreamNode]) object Hub { def apply(): Hub = { val shouldQuerySelf = false val create = ShrineOrchestrator.hubComponents.isDefined val downstreamNodes = DownstreamNodes.get() Hub(shouldQuerySelf, create, downstreamNodes) } } case class OptionalParts(isHub: Boolean, stewardEnabled: Boolean, shouldQuerySelf: Boolean, //todo don't use this field any more. Drop it when possible downstreamNodes: Seq[DownstreamNode]) object OptionalParts { def apply(): OptionalParts = { OptionalParts( ShrineOrchestrator.hubComponents.isDefined, ShrineOrchestrator.queryEntryPointComponents.fold(false)(_.shrineService.authorizationService.isInstanceOf[StewardQueryAuthorizationService]), shouldQuerySelf = false, DownstreamNodes.get() ) } } case class Summary( isHub: Boolean, shrineVersion: String, shrineBuildDate: String, ontologyVersion: String, ontologyVersionTerm: String, ontologyTerm: String, queryResult: Option[SingleNodeResult], adapterMappingsFileName: Option[String], adapterMappingsDate: Option[Long], adapterOk: Boolean, keystoreOk: Boolean, hubOk: Boolean, qepOk: Boolean ) object Summary { val term = Term(ShrineOrchestrator.shrineConfig.getString("networkStatusQuery")) def runQueryRequest: BroadcastMessage = { val domain = "happy" val username = "happy" val networkAuthn = AuthenticationInfo(domain, username, Credential("", isToken = false)) val queryDefinition = QueryDefinition("TestQuery", OccuranceLimited(1, term)) import scala.concurrent.duration._ val req = RunQueryRequest( "happyProject", 3.minutes, networkAuthn, None, None, Set(ResultOutputType.PATIENT_COUNT_XML), queryDefinition) ShrineOrchestrator.signerVerifier.sign(BroadcastMessage(req.networkQueryId, networkAuthn, req), SigningCertStrategy.Attach) } def apply(): Summary = { val message = runQueryRequest val queryResult: Option[SingleNodeResult] = ShrineOrchestrator.adapterService.map { adapterService => import scala.concurrent.duration._ val start = System.currentTimeMillis val resultAttempt: Try[Result] = Try(adapterService.handleRequest(message)) val end = System.currentTimeMillis val elapsed = (end - start).milliseconds resultAttempt match { case scala.util.Success(result) => result case scala.util.Failure(throwable) => FailureResult(NodeId("Local"), throwable) } } val adapterOk = queryResult.fold(true) { case r: Result => true case f: FailureResult => false } val hubOk = ShrineOrchestrator.hubComponents.fold(true) { hubComponents => val maxQueryWaitTime = hubComponents.broadcasterMultiplexerService.maxQueryWaitTime val broadcaster: Broadcaster = hubComponents.broadcasterMultiplexerService.broadcaster val message = runQueryRequest val triedMultiplexer = Try(broadcaster.broadcast(message)) //todo just use fold()() in scala 2.12 triedMultiplexer.toOption.fold(false) { multiplexer => val responses = Await.result(multiplexer.responses, maxQueryWaitTime).toSeq val failures = responses.collect { case f: FailureResult => f } val timeouts = responses.collect { case t: Timeout => t } val validResults = responses.collect { case r: Result => r } failures.isEmpty && timeouts.isEmpty && (validResults.size == broadcaster.destinations.size) } } val adapterMappingInfo = Adapter.mappingFileInfo val ontologyVersion = try { ShrineOrchestrator.ontologyMetadata.ontologyVersion } catch { case NonFatal(x) => Log.info("Problem while getting ontology version", x) s"Unavailable due to: ${x.getMessage}" + // TODO: Investigate whether a Fatal exception is being thrown } Summary( isHub = ShrineOrchestrator.hubComponents.isDefined, shrineVersion = Versions.version, shrineBuildDate = Versions.buildDate, //todo in scala 2.12, do better ontologyVersion = ontologyVersion, ontologyVersionTerm = OntClientOntologyMetadata.versionContainerTerm, ontologyTerm = term.value, queryResult = queryResult, adapterMappingsFileName = adapterMappingInfo.map(_._1), adapterMappingsDate = adapterMappingInfo.map(_._2), adapterOk = adapterOk, keystoreOk = true, //todo something for this hubOk = hubOk, qepOk = true //todo something for this ) } } case class Version(version: String) //todo SortedMap when possible case class Json4sConfig(keyValues: Map[String, String]) object Json4sConfig { def isPassword(key: String): Boolean = { if (key.toLowerCase.contains("password")) true else false } def apply(config: TsConfig): Json4sConfig = { val entries: Set[(String, String)] = config.entrySet.asScala.to[Set].map(x => (x.getKey, x.getValue.render())).filterNot(x => isPassword(x._1)) val sortedMap: Map[String, String] = entries.toMap Json4sConfig(sortedMap) } } class PermittedHostOnly extends ContainerRequestFilter { //todo generalize for happy, too //todo for tomcat 8 see https://jersey.java.net/documentation/latest/filters-and-interceptors.html for a cleaner version //shell code from http://stackoverflow.com/questions/17143514/how-to-add-custom-response-and-abort-request-in-jersey-1-11-filters //how to apply in http://stackoverflow.com/questions/4358213/how-does-one-intercept-a-request-during-the-jersey-lifecycle override def filter(requestContext: ContainerRequest): ContainerRequest = { val hostOfOrigin = requestContext.getBaseUri.getHost val shrineConfig: TsConfig = ShrineOrchestrator.config val permittedHostOfOrigin: String = shrineConfig.getOption("shrine.status.permittedHostOfOrigin", _.getString).getOrElse("localhost") val path = requestContext.getPath //happy and internalstatus API calls must come from the same host as tomcat is running on (hopefully the dashboard servlet). // todo access to the happy service permitted for SHRINE 1.21 per SHRINE-1366 // restrict access to happy service when database work resumes as part of SHRINE- // if ((path.contains("happy") || path.contains("internalstatus")) && (hostOfOrigin != permittedHostOfOrigin)) { if (path.contains("internalstatus") && (hostOfOrigin != permittedHostOfOrigin)) { val response = Response.status(Response.Status.UNAUTHORIZED).entity(s"Only available from $permittedHostOfOrigin, not $hostOfOrigin, controlled by shrine.status.permittedHostOfOrigin in shrine.conf").build() throw new WebApplicationException(response) } else requestContext } } object ShaVerificationService extends Loggable with DefaultJsonSupport { //todo: remove duplication with StewardQueryAuthorizationService import akka.pattern.ask import org.json4s.native.JsonMethods.parseOpt import system.dispatcher // execution context for futures implicit val system = ActorSystem("AuthorizationServiceActors", ConfigFactory.load("shrine")) //todo use shrine's config val certCollection = ShrineOrchestrator.certCollection def sendHttpRequest(httpRequest: HttpRequest): Future[HttpResponse] = { implicit val timeout: Timeout = Timeout.durationToTimeout(new FiniteDuration(5, duration.SECONDS)) //5 seconds implicit def json4sFormats: Formats = DefaultFormats implicit def trustfulSslContext: SSLContext = { object BlindFaithX509TrustManager extends X509TrustManager { def checkClientTrusted(chain: Array[X509Certificate], authType: String) = info(s"Client asked BlindFaithX509TrustManager to check $chain for $authType") def checkServerTrusted(chain: Array[X509Certificate], authType: String) = info(s"Server asked BlindFaithX509TrustManager to check $chain for $authType") def getAcceptedIssuers = Array[X509Certificate]() } val context = SSLContext.getInstance("TLS") context.init(Array[KeyManager](), Array(BlindFaithX509TrustManager), null) context } implicit def trustfulSslContextProvider: SSLContextProvider = { SSLContextProvider.forContext(trustfulSslContext) } class CustomClientSSLEngineProvider extends ClientSSLEngineProvider { def apply(pc: PipelineContext) = ClientSSLEngineProvider.default(trustfulSslContextProvider).apply(pc) } implicit def sslEngineProvider: ClientSSLEngineProvider = new CustomClientSSLEngineProvider val responseFuture: Future[HttpResponse] = for { HostConnectorInfo(hostConnector, _) <- { val hostConnectorSetup = new HostConnectorSetup(httpRequest.uri.authority.host.address, httpRequest.uri.authority.port, sslEncryption = httpRequest.uri.scheme == "https")( sslEngineProvider = sslEngineProvider) IO(Http) ask hostConnectorSetup } response <- sendReceive(hostConnector).apply(httpRequest) _ <- hostConnector ask Http.CloseAll } yield response responseFuture } type MaybeSiteStatus = Future[Option[SiteStatus]] def apply(sites: Seq[RemoteSite]): Seq[MaybeSiteStatus] = sites.map(curl) def curl(site: RemoteSite): MaybeSiteStatus = { val shaEntry = certCollection match { case HubCertCollection(_, caEntry, _) => caEntry case PeerCertCollection(my, _, _) => my case DownStreamCertCollection(_, caEntry, _) => caEntry } val sha256 = UtilHasher.encodeCert(shaEntry.cert, "SHA-256") implicit val formats = org.json4s.DefaultFormats val request = Post(s"https://${site.url}:${site.port}/shrine-dashboard/status/verifySignature") .withEntity( // For some reason, FormData isn't producing the correct HTTP call, so we do it manually HttpEntity.apply( ContentType( MediaTypes.`application/x-www-form-urlencoded`, HttpCharsets.`UTF-8`), s"sha256=$sha256")) for {response <- sendHttpRequest(request) rawResponse = new String(response.entity.data.toByteArray) status = parseOpt(rawResponse).fold(handleError(rawResponse))(_.extractOpt[ShaResponse] match { case Some(ShaResponse(ShaResponse.badFormat, false)) => error(s"Somehow, this client is sending an incorrectly formatted SHA256 signature to the dashboard. Offending sig: $sha256") None case Some(ShaResponse(sha, true)) => Some(SiteStatus(site.alias, theyHaveMine = true, haveTheirs = doWeHaveCert(sha), site.url)) case Some(ShaResponse(sha, false)) => Some(SiteStatus(site.alias, theyHaveMine = false, haveTheirs = doWeHaveCert(sha), site.url)) case None => InvalidVerifySignatureResponse(rawResponse) None })} yield status } def doWeHaveCert(sha256: String): Boolean = UtilHasher(certCollection).handleSig(sha256).found def handleError(response: String): Option[SiteStatus] = { InvalidVerifySignatureResponse(response) None } } case class InvalidVerifySignatureResponse(response: String) extends AbstractProblem(ProblemSources.ShrineApp) { override def summary: String = "The client for handling certificate diagnostic across Dashboards in the Status Service received an invalid response from shrine-dashboard/admin/status/verifySignature" override def description: String = s"See details for incorrect response:" override def throwable: Option[Throwable] = Some(InvalidResponseException(response)) } case class InvalidResponseException(response: String) extends IllegalStateException { override def getMessage: String = s"Invalid response `$response`" } diff --git a/apps/shrine-app/src/main/scala/net/shrine/wiring/ShrineOrchestrator.scala b/apps/shrine-app/src/main/scala/net/shrine/wiring/ShrineOrchestrator.scala index fc996ac96..fc9092675 100644 --- a/apps/shrine-app/src/main/scala/net/shrine/wiring/ShrineOrchestrator.scala +++ b/apps/shrine-app/src/main/scala/net/shrine/wiring/ShrineOrchestrator.scala @@ -1,159 +1,159 @@ package net.shrine.wiring import javax.sql.DataSource import com.typesafe.config.Config import net.shrine.adapter.AdapterComponents import net.shrine.adapter.dao.AdapterDao import net.shrine.adapter.service.{AdapterRequestHandler, AdapterResource, AdapterService, I2b2AdminResource, I2b2AdminService} import net.shrine.broadcaster.dao.HubDao import net.shrine.broadcaster.dao.squeryl.SquerylHubDao import net.shrine.broadcaster.service.HubComponents import net.shrine.client.{EndpointConfig, JerseyHttpClient, OntClient, Poster, PosterOntClient} import net.shrine.config.ConfigExtensions import net.shrine.config.mappings.AdapterMappings import net.shrine.crypto.{BouncyKeyStoreCollection, KeyStoreDescriptorParser, SignerVerifierAdapter, TrustParam} import net.shrine.dao.squeryl.{DataSourceSquerylInitializer, SquerylDbAdapterSelecter, SquerylInitializer} import net.shrine.happy.{HappyShrineResource, HappyShrineService} import net.shrine.log.Loggable import net.shrine.ont.data.OntClientOntologyMetadata import net.shrine.protocol.{HiveCredentials, NodeId, ResultOutputType, ResultOutputTypes} import net.shrine.qep.{I2b2BroadcastResource, QueryEntryPointComponents, ShrineResource} import net.shrine.slick.TestableDataSourceCreator import net.shrine.source.ConfigSource import net.shrine.status.StatusJaxrs import org.squeryl.internals.DatabaseAdapter /** * @author clint * @since Jan 14, 2014 * * Application wiring for Shrine. */ object ShrineOrchestrator extends ShrineJaxrsResources with Loggable { override def resources: Iterable[AnyRef] = { Seq(happyResource,statusJaxrs) ++ shrineResource ++ i2b2BroadcastResource ++ adapterResource ++ i2b2AdminResource ++ hubComponents.map(_.broadcasterMultiplexerResource) } //todo another pass to put things only used in one place into that place's apply(Config) //Load config from file on the classpath called "shrine.conf" lazy val config: Config = ConfigSource.config val shrineConfig = config.getConfig("shrine") protected lazy val nodeId: NodeId = NodeId(shrineConfig.getString("humanReadableNodeName")) //TODO: Don't assume keystore lives on the filesystem, could come from classpath, etc lazy val keyStoreDescriptor = KeyStoreDescriptorParser(shrineConfig.getConfig("keystore"), shrineConfig.getConfigOrEmpty("hub"), shrineConfig.getConfigOrEmpty("queryEntryPoint")) lazy val certCollection: BouncyKeyStoreCollection = BouncyKeyStoreCollection.fromFileRecoverWithClassPath(keyStoreDescriptor) protected lazy val keystoreTrustParam: TrustParam = TrustParam.BouncyKeyStore(certCollection) - //todo used by the adapterServide and happyShrineService, but not by the QEP. maybe each can have its own signerVerivier + //todo used by the adapterService and happyShrineService, but not by the QEP. maybe each can have its own signerVerifier lazy val signerVerifier = SignerVerifierAdapter(certCollection) protected lazy val dataSource: DataSource = TestableDataSourceCreator.dataSource(shrineConfig.getConfig("squerylDataSource.database")) protected lazy val squerylAdapter: DatabaseAdapter = SquerylDbAdapterSelecter.determineAdapter(shrineConfig.getString("shrineDatabaseType")) protected lazy val squerylInitializer: SquerylInitializer = new DataSourceSquerylInitializer(dataSource, squerylAdapter) //todo it'd be better for the adapter and qep to each have its own connection to the pm cell. private lazy val pmEndpoint: EndpointConfig = shrineConfig.getConfigured("pmEndpoint", EndpointConfig(_)) lazy val pmPoster: Poster = Poster(certCollection,pmEndpoint) protected lazy val breakdownTypes: Set[ResultOutputType] = shrineConfig.getOptionConfigured("breakdownResultOutputTypes", ResultOutputTypes.fromConfig).getOrElse(Set.empty) //todo why does the qep need a HubDao ? protected lazy val hubDao: HubDao = new SquerylHubDao(squerylInitializer, new net.shrine.broadcaster.dao.squeryl.tables.Tables) //todo really should be part of the adapter config, but is out in shrine's part of the name space lazy val crcHiveCredentials: HiveCredentials = shrineConfig.getConfigured("hiveCredentials", HiveCredentials(_, HiveCredentials.CRC)) val adapterComponents:Option[AdapterComponents] = shrineConfig.getOptionConfiguredIf("adapter", AdapterComponents( _, certCollection, squerylInitializer, breakdownTypes, crcHiveCredentials, signerVerifier, pmPoster, nodeId )) if (adapterComponents.isEmpty) warn("Adapter Components is improperly configured, please check the adapter section in shrine.conf") //todo maybe just break demeter too use this lazy val adapterService: Option[AdapterService] = adapterComponents.map(_.adapterService) //todo maybe just break demeter too use this lazy val i2b2AdminService: Option[I2b2AdminService] = adapterComponents.map(_.i2b2AdminService) //todo this is only used by happy lazy val adapterDao: Option[AdapterDao] = adapterComponents.map(_.adapterDao) //todo this is only used by happy lazy val adapterMappings: Option[AdapterMappings] = adapterComponents.map(_.adapterMappings) val shouldQuerySelf = "hub.shouldQuerySelf" lazy val localAdapterServiceOption: Option[AdapterRequestHandler] = if(shrineConfig.getOption(shouldQuerySelf,_.getBoolean).getOrElse(false)) { //todo give this a default value (of false) in the reference.conf for the Hub, and make it part of the Hub's apply(config) require(adapterService.isDefined, s"Self-querying requested because shrine.$shouldQuerySelf is true, but this node is not configured to have an adapter") adapterService } else None //todo eventually make this just another downstream node accessed via loopback lazy val hubComponents: Option[HubComponents] = shrineConfig.getOptionConfiguredIf("hub",HubComponents(_, keystoreTrustParam, nodeId, localAdapterServiceOption, breakdownTypes, hubDao )) //todo anything that requires qepConfig should be inside QueryEntryPointComponents's apply protected lazy val qepConfig = shrineConfig.getConfig("queryEntryPoint") lazy val queryEntryPointComponents:Option[QueryEntryPointComponents] = shrineConfig.getOptionConfiguredIf("queryEntryPoint", QueryEntryPointComponents(_, certCollection, breakdownTypes, hubComponents.map(_.broadcastDestinations), hubDao, //todo the QEP should not need the hub dao squerylInitializer, //todo could really have its own pmPoster //todo could really have its own )) protected lazy val pmUrlString: String = pmEndpoint.url.toString private[shrine] lazy val ontEndpoint: EndpointConfig = shrineConfig.getConfigured("ontEndpoint", EndpointConfig(_)) protected lazy val ontPoster: Poster = Poster(certCollection,ontEndpoint) //todo only used by happy outside of here lazy val ontologyMetadata: OntClientOntologyMetadata = { import scala.concurrent.duration._ //TODO: XXX: Un-hard-code max wait time param val ontClient: OntClient = new PosterOntClient(shrineConfig.getConfigured("hiveCredentials", HiveCredentials(_, HiveCredentials.ONT)), 1.minute, ontPoster) new OntClientOntologyMetadata(ontClient) } protected lazy val happyResource: HappyShrineResource = new HappyShrineResource(HappyShrineService) protected lazy val statusJaxrs: StatusJaxrs = StatusJaxrs(config) protected lazy val shrineResource: Option[ShrineResource] = queryEntryPointComponents.map(x => ShrineResource(x.shrineService)) protected lazy val i2b2BroadcastResource: Option[I2b2BroadcastResource] = queryEntryPointComponents.map(x => new I2b2BroadcastResource(x.i2b2Service,breakdownTypes)) protected lazy val adapterResource: Option[AdapterResource] = adapterService.map(AdapterResource(_)) protected lazy val i2b2AdminResource: Option[I2b2AdminResource] = i2b2AdminService.map(I2b2AdminResource(_, breakdownTypes)) def poster(keystoreCertCollection: BouncyKeyStoreCollection)(endpoint: EndpointConfig): Poster = { val httpClient = JerseyHttpClient(keystoreCertCollection, endpoint) Poster(endpoint.url.toString, httpClient) } } diff --git a/apps/shrine-app/src/test/scala/net/shrine/wiring/NodeHandleTest.scala b/apps/shrine-app/src/test/scala/net/shrine/wiring/NodeHandleTest.scala index 5f4d37d97..4bb05e8c8 100644 --- a/apps/shrine-app/src/test/scala/net/shrine/wiring/NodeHandleTest.scala +++ b/apps/shrine-app/src/test/scala/net/shrine/wiring/NodeHandleTest.scala @@ -1,47 +1,38 @@ package net.shrine.wiring import java.net.URL -import scala.concurrent.duration.DurationInt -import org.junit.Test -import net.shrine.util.ShouldMatchersForJUnit -import javax.ws.rs.core.MediaType -import net.shrine.adapter.client.InJvmAdapterClient -import net.shrine.adapter.client.RemoteAdapterClient -import net.shrine.adapter.service.AdapterRequestHandler -import net.shrine.broadcaster.{IdAndUrl, NodeHandle} -import net.shrine.client.JerseyHttpClient -import net.shrine.client.Poster + +import net.shrine.broadcaster.IdAndUrl import net.shrine.crypto.TrustParam.AcceptAllCerts -import net.shrine.protocol.BroadcastMessage -import net.shrine.protocol.NodeId -import net.shrine.protocol.Result -import net.shrine.protocol.DefaultBreakdownResultOutputTypes +import net.shrine.protocol.{DefaultBreakdownResultOutputTypes, NodeId} +import net.shrine.util.ShouldMatchersForJUnit +import org.junit.Test /** * @author clint * @since Dec 11, 2013 */ final class NodeHandleTest extends ShouldMatchersForJUnit { @Test def testMakeNodeHandles { val trustParam = AcceptAllCerts val myId = NodeId("me") val xId = NodeId("x") val yId = NodeId("y") val xUrl = "http://example.com/x/requests" val yUrl = "http://example.com/y" val nodes = Seq( IdAndUrl(xId, new URL(xUrl)), IdAndUrl(yId, new URL(yUrl))) import scala.concurrent.duration._ val timeout = 1.minute val breakdownTypes = DefaultBreakdownResultOutputTypes.toSet } } \ No newline at end of file diff --git a/tools/scanner/src/test/scala/net/shrine/utilities/scanner/BroadcastServiceScannerClientTest.scala b/tools/scanner/src/test/scala/net/shrine/utilities/scanner/BroadcastServiceScannerClientTest.scala index 01977ffe0..fe4a8659d 100644 --- a/tools/scanner/src/test/scala/net/shrine/utilities/scanner/BroadcastServiceScannerClientTest.scala +++ b/tools/scanner/src/test/scala/net/shrine/utilities/scanner/BroadcastServiceScannerClientTest.scala @@ -1,147 +1,147 @@ package net.shrine.utilities.scanner import net.shrine.util.ShouldMatchersForJUnit import org.junit.Test import net.shrine.protocol.AuthenticationInfo import net.shrine.protocol.Credential import net.shrine.broadcaster.BroadcastAndAggregationService import net.shrine.protocol.BroadcastMessage import net.shrine.aggregation.Aggregator import scala.concurrent.Future import net.shrine.protocol.ShrineResponse import net.shrine.authentication.Authenticator import net.shrine.authentication.AuthenticationResult import net.shrine.protocol.DeleteQueryResponse import net.shrine.protocol.AggregatedRunQueryResponse import net.shrine.util.XmlDateHelper import net.shrine.protocol.query.Term import net.shrine.protocol.query.QueryDefinition import net.shrine.protocol.QueryResult import net.shrine.protocol.ResultOutputType import scala.concurrent.Await import scala.concurrent.duration.Duration import net.shrine.authentication.NotAuthenticatedException import net.shrine.protocol.AggregatedReadQueryResultResponse /** - * @author clint - * @date Jan 14, 2014 - */ + * @author clint + * @date Jan 14, 2014 + */ final class BroadcastServiceScannerClientTest extends ShouldMatchersForJUnit { private val authn = AuthenticationInfo("d", "u", Credential("p", false)) private val projectId = "SHRINE-PROJECT" import BroadcastServiceScannerClientTest._ import scala.concurrent.ExecutionContext.Implicits.{ global => executionContext } @Test def testQuery { val broadcastAndAggregationService = new MockQueryBroadcastAndAggregationService val term = "t" { val clientThatWorks: BroadcastServiceScannerClient = new BroadcastServiceScannerClient(projectId, authn, broadcastAndAggregationService, Authenticators.alwaysWorks, executionContext) {} val result = Await.result(clientThatWorks.query(term), Duration.Inf) result.count should equal(setSize) result.networkQueryId should equal(masterId) result.networkResultId should equal(resultId) result.status should equal(QueryResult.StatusType.Finished) result.term should equal(term) } { val clientThatDoesntAuthenticate: BroadcastServiceScannerClient = new BroadcastServiceScannerClient(projectId, authn, broadcastAndAggregationService, Authenticators.neverWorks, executionContext) {} intercept[NotAuthenticatedException] { clientThatDoesntAuthenticate.query(term) } } } @Test def testRetrieveResults { val broadcastAndAggregationService = new MockRetrieveResultsBroadcastAndAggregationService val term = "t" val termResult = TermResult(masterId, resultId, term, QueryResult.StatusType.Processing, setSize) { val clientThatWorks: BroadcastServiceScannerClient = new BroadcastServiceScannerClient(projectId, authn, broadcastAndAggregationService, Authenticators.alwaysWorks, executionContext) {} val result = Await.result(clientThatWorks.retrieveResults(termResult), Duration.Inf) result.count should equal(setSize) result.networkQueryId should equal(masterId) result.networkResultId should equal(resultId) result.status should equal(QueryResult.StatusType.Finished) result.term should equal(term) } { val clientThatDoesntAuthenticate: BroadcastServiceScannerClient = new BroadcastServiceScannerClient(projectId, authn, broadcastAndAggregationService, Authenticators.neverWorks, executionContext) {} intercept[NotAuthenticatedException] { clientThatDoesntAuthenticate.retrieveResults(termResult) } } } } object BroadcastServiceScannerClientTest { private val masterId = 12345L private val instanceId = 98765L private val resultId = 378256L private val setSize = 123L private final class MockQueryBroadcastAndAggregationService extends BroadcastAndAggregationService { override def sendAndAggregate(message: BroadcastMessage, aggregator: Aggregator, shouldBroadcast: Boolean): Future[ShrineResponse] = Future.successful { AggregatedRunQueryResponse( masterId, XmlDateHelper.now, "u", "d", QueryDefinition("foo", Term("t")), instanceId, Seq( QueryResult( resultId, instanceId, Some(ResultOutputType.PATIENT_COUNT_XML), setSize, Some(XmlDateHelper.now), Some(XmlDateHelper.now), None, QueryResult.StatusType.Finished, None))) } override def attachSigningCert: Boolean = false override val broadcasterUrl = None } private final class MockRetrieveResultsBroadcastAndAggregationService extends BroadcastAndAggregationService { override def sendAndAggregate(message: BroadcastMessage, aggregator: Aggregator, shouldBroadcast: Boolean): Future[ShrineResponse] = Future.successful { AggregatedReadQueryResultResponse( masterId, Seq( QueryResult( resultId, instanceId, Some(ResultOutputType.PATIENT_COUNT_XML), setSize, Some(XmlDateHelper.now), Some(XmlDateHelper.now), None, QueryResult.StatusType.Finished, None))) } override def attachSigningCert: Boolean = false override val broadcasterUrl = None } } \ No newline at end of file