diff --git a/adapter/adapter-api/pom.xml b/adapter/adapter-api/pom.xml index a55d5a7dd..350123729 100644 --- a/adapter/adapter-api/pom.xml +++ b/adapter/adapter-api/pom.xml @@ -1,74 +1,74 @@ 4.0.0 SHRINE Adapter Client API shrine-adapter-client-api net.shrine shrine-base - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT ../../pom.xml src/main/scala src/test/scala net.alchim31.maven scala-maven-plugin net.shrine shrine-crypto ${project.version} net.shrine shrine-client ${project.version} net.shrine shrine-protocol ${project.version} com.sun.jersey.jersey-test-framework jersey-test-framework-http ${jersey-version} test com.sun.jersey.contribs jersey-simple-server ${jersey-version} test net.shrine shrine-test-commons ${project.version} test-jar test net.shrine shrine-client ${project.version} test-jar test net.shrine shrine-config ${project.version} test-jar test diff --git a/adapter/adapter-service/pom.xml b/adapter/adapter-service/pom.xml index 2fac60faf..2400b94e0 100644 --- a/adapter/adapter-service/pom.xml +++ b/adapter/adapter-service/pom.xml @@ -1,180 +1,180 @@ 4.0.0 SHRINE Adapter shrine-adapter-service jar net.shrine shrine-base - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT ../../pom.xml src/main/scala src/test/scala net.alchim31.maven scala-maven-plugin org.apache.maven.plugins maven-jar-plugin test-jar org.squeryl squeryl_${scala-major-version} org.scala-lang scalap org.scala-lang scalap ${scala-version} net.shrine shrine-auth ${project.version} net.shrine shrine-adapter-client-api ${project.version} net.shrine shrine-data-commons ${project.version} net.shrine shrine-crypto ${project.version} net.shrine shrine-crypto ${project.version} test-jar test net.shrine shrine-client ${project.version} net.shrine shrine-protocol ${project.version} net.shrine shrine-config ${project.version} com.typesafe.slick slick_2.11 ${slick-version} org.suecarter freeslick_2.11 ${freeslick-version} org.slf4j slf4j-log4j12 ${slf4j-version} mysql mysql-connector-java com.sun.jersey.jersey-test-framework jersey-test-framework-http ${jersey-version} test com.sun.jersey.contribs jersey-simple-server ${jersey-version} test com.h2database h2 test org.easymock easymock test org.springframework spring-jdbc test net.shrine shrine-util ${project.version} test-jar test net.shrine shrine-test-commons ${project.version} test-jar test net.shrine shrine-data-commons ${project.version} test-jar test diff --git a/apps/dashboard-app/pom.xml b/apps/dashboard-app/pom.xml index 05933047a..e624af4ea 100644 --- a/apps/dashboard-app/pom.xml +++ b/apps/dashboard-app/pom.xml @@ -1,200 +1,200 @@ shrine-base net.shrine - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT ../../pom.xml 4.0.0 dashboard-app - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT Dashboard App jar net.alchim31.maven scala-maven-plugin com.github.eirslett frontend-maven-plugin 0.0.23 src/main/js install node and npm install-node-and-npm v0.10.33 2.7.4 npm install npm generate-resources install bower install bower install grunt default grunt --no-color log4j log4j io.spray spray-routing_2.11 ${spray-version} io.spray spray-servlet_2.11 ${spray-version} io.spray spray-util_2.11 ${spray-version} io.spray spray-testkit_2.11 ${spray-version} test com.typesafe.akka akka-actor_2.11 ${akka-version} com.typesafe.akka akka-slf4j_2.11 ${akka-version} com.typesafe.akka akka-testkit_2.11 ${akka-testkit-version} test org.json4s json4s-native_2.11 ${json4s-version} com.typesafe.slick slick_2.11 ${slick-version} org.slf4j slf4j-log4j12 ${slf4j-version} com.h2database h2 ${h2-version} test net.shrine shrine-protocol ${project.version} net.shrine shrine-utility-commons ${project.version} net.shrine shrine-crypto ${project.version} test-jar test net.shrine shrine-auth ${project.version} net.shrine shrine-data-commons ${project.version} mysql mysql-connector-java ${mysql-version} io.jsonwebtoken jjwt 0.6.0 net.sourceforge.jtds jtds 1.3.1 net.shrine shrine-adapter-client-api ${project.version} com.typesafe config ${typesafe-config-version} diff --git a/apps/dashboard-app/src/main/js/pom.xml b/apps/dashboard-app/src/main/js/pom.xml index df5d5b303..17acb9804 100644 --- a/apps/dashboard-app/src/main/js/pom.xml +++ b/apps/dashboard-app/src/main/js/pom.xml @@ -1,94 +1,94 @@ shrine-base net.shrine 1.22.2.4-SNAPSHOT ../../../../../pom.xml 4.0.0 dashboard-app-client - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT Dashboard JS App pom com.github.eirslett frontend-maven-plugin 0.0.23 install node and npm install-node-and-npm v0.10.33 2.7.4 npm install npm generate-resources install bower install bower install grunt default grunt --no-color diff --git a/apps/dashboard-app/src/main/js/src/app/diagnostic/diagnostic.model.js b/apps/dashboard-app/src/main/js/src/app/diagnostic/diagnostic.model.js index 5285dc6e4..e027520b5 100644 --- a/apps/dashboard-app/src/main/js/src/app/diagnostic/diagnostic.model.js +++ b/apps/dashboard-app/src/main/js/src/app/diagnostic/diagnostic.model.js @@ -1,247 +1,247 @@ (function (){ 'use strict'; // -- angular module -- // angular.module('shrine-tools') .factory('DiagnosticModel', DiagnosticModel); DiagnosticModel.$inject = ['$http', '$q', 'UrlGetter', '$location']; function DiagnosticModel (h, q, urlGetter, $location) { var toDashboard = {url:''}; var cache = {}; // used solely for remote dashboard persistence var m = {}; m.remoteSiteStatuses = []; m.siteAlias = ''; // -- private const -- // var Config = { AdapterEndpoint: 'admin/status/adapter', ConfigEndpoint: 'admin/status/config', HubEndpoint: 'admin/status/hub', I2B2Endpoint: 'admin/status/i2b2', KeystoreEndpoint: 'admin/status/keystore', OptionsEndpoint: 'admin/status/optionalParts', ProblemEndpoint: 'admin/status/problems', QepEndpoint: 'admin/status/qep', SummaryEndpoint: 'admin/status/summary' }; // -- public -- // return { getAdapter: getJsonMaker(Config.AdapterEndpoint, 'adapter'), getConfig: getJsonMaker(Config.ConfigEndpoint, 'config', parseConfig), getHub: getJsonMaker(Config.HubEndpoint, 'hub'), getI2B2: getJsonMaker(Config.I2B2Endpoint, 'i2b2'), getKeystore: getJsonMaker(Config.KeystoreEndpoint, 'keystore', storeRemoteSites), getOptionalParts: getJsonMaker(Config.OptionsEndpoint, 'optionalParts'), getProblems: getProblemsMaker(), getQep: getJsonMaker(Config.QepEndpoint, 'qep'), getSummary: getJsonMaker(Config.SummaryEndpoint, 'summary'), safeLogout: safeLogout, clearCache: clearCache, map: map, formatDate: formatDate, cache: cache, toDashboard: toDashboard, m: m }; function map(func, list) { var result = []; for(var i = 0; i < list.length; i++) { result.push(func(list[i])) } return result; } function formatDate(dateObject) { return [dateObject.getUTCFullYear(), "-", - pad2(dateObject.getUTCMonth()), "-", + pad2(dateObject.getUTCMonth() + 1), "-", pad2(dateObject.getUTCDate()), " ", pad2(dateObject.getUTCHours()), ":", pad2(dateObject.getUTCMinutes()), ":", pad2(dateObject.getUTCSeconds())].join(""); } function pad2(stringLikeThing) { // Does javascript provide a string format thing? Would love to write %02d here. var stringed = "" + stringLikeThing; if (stringed.length > 2) { return stringed; } return ("00" + stringed).slice(-2); } /** * Clears the current remote dashboard before logging out. */ function safeLogout() { clearCache(); toDashboard.url = ''; m.siteAlias = ''; $location.path('/login'); } function clearCache() { for (var member in cache) { if(cache.hasOwnProperty(member)) delete cache[member]; } } /** * Method for Handling a failed rest call. * @param failedResult * @returns {*} */ function onFail(failedResult) { return q.reject(failedResult); } /*** * Method for handling a successful rest call. Simply caches it and returns it. * @param result * @param cacheKey * @returns {*} */ function parseJsonResult(result, cacheKey) { cache[cacheKey] = result.data; return result.data; } /** * Still cache and return the result, however, save the RemoteSites outside of the cache, * as we don't want these values to change between cache resets (which occur when switching sites) * @param result * @param cacheKey */ function storeRemoteSites(result, cacheKey) { cache[cacheKey] = result.data; if (m.remoteSiteStatuses.length == 0) { m.remoteSiteStatuses = result.data.remoteSiteStatuses; } return result.data } /** * Parses the json config map and turns it into a nested json object * @param json the flat config map * @param cacheKey a unique identifier for the function */ function parseConfig (json, cacheKey) { var configMap = json.data.configMap; var processed = preProcessJson(configMap); cache[cacheKey] = processed; return processed; } // IE11 doesn't support string includes // This only searchers for characters, not arbitrary strings function stringIncludes(haystack, needle) { var arr = haystack.split(""); for (var i = 0; i < arr.length; i++) { if (arr[i] == needle) { return true; } } return false; } // "explodes" and merges the flag config map. // e.g., {"key.foo": 10, "key.baz": 5} -> {"key": {"foo": 10, "baz": 5}} function preProcessJson (object) { var result = {}; for (var key in object) { if (object.hasOwnProperty(key)) { if (!stringIncludes(key, ".")) { result[key] = object[key] } else { var split = key.split("."); var prev = result; for (var i = 0; i < split.length; i++) { var cur = split[i]; if (!(cur in prev)) { prev[cur] = {} } if (i == split.length - 1) { prev[cur] = object[key]; } else { prev = prev[cur] } } } } } return result; } /** * There's a lot going on here. Essentially, this is a function factory that allows one to * define backend calls just through the path. It also implements a simple caching * strategy. * Essentially the get function only needs to be called once, and from then on it will spit * back a cached promise. This lets you write the code and not care whether it's cached or * not, but also get the caching performance anyways. For this function to work, the * resolver function has to take in the http response and the cache key to set, and make * sure that it caches what it returns (see parseJsonResult or parseConfig). * @param endpoint * @param cacheKey * @param resolverDefault * @returns {Function} */ function getJsonMaker(endpoint, cacheKey, resolverDefault) { var resolver = (typeof resolverDefault !== 'undefined')? function (response) { return resolverDefault(response, cacheKey) }: function (response) { return parseJsonResult(response, cacheKey) }; return function() { var cachedValue = cache[cacheKey]; if (cachedValue === undefined) { var url = urlGetter(endpoint, undefined, toDashboard.url); return h.get(url) .then(resolver, onFail) } else { return q(function(resolver) { resolver(cachedValue)}); } } } function getProblemsMaker() { // Caches the last offset and page size to hold onto it between different views var prevOffset = 0; var prevN = 20; /** * ProblemEndpoint: 'admin/status/problems', * @returns {*} */ return function(offset, n, epoch) { if (offset != null) { prevOffset = offset; } else { offset = prevOffset; } if (n != null) { prevN = n; } else { n = prevN; } var epochString = epoch && isFinite(epoch) ? '&epoch=' + epoch : ''; var url = urlGetter( Config.ProblemEndpoint + '?offset=' + offset + '&n=' + n + epochString, undefined, toDashboard.url ); return h.get(url) .then(parseJsonResult, onFail); } } } })(); diff --git a/apps/dashboard-app/src/main/js/src/app/diagnostic/views/dashboard.controller.js b/apps/dashboard-app/src/main/js/src/app/diagnostic/views/dashboard.controller.js index 7c97bb946..ccdbe9d71 100644 --- a/apps/dashboard-app/src/main/js/src/app/diagnostic/views/dashboard.controller.js +++ b/apps/dashboard-app/src/main/js/src/app/diagnostic/views/dashboard.controller.js @@ -1,92 +1,92 @@ /** * Controller for the Remote Dashboards panel. * Parses the keystore get call, and has to handle * some tricky caching logic with the model backend * (namely we don't want to reset the dashboard links themselves * between remoteDashboard visits) */ (function () { 'use strict'; // -- register controller with angular -- // angular.module('shrine-tools') .controller('DashboardController', DashboardController); /** * * @type {string[]} */ DashboardController.$inject = ['$app', '$log', '$location']; function DashboardController ($app, $log, $location) { var vm = this; var map = $app.model.map; vm.keyStoreError = false; init(); /** * */ function init () { $app.model.getKeystore() .then(setDashboard, handleFailure); } function handleFailure (failure) { vm.keyStoreError = failure; } /** * * @param keystore */ function setDashboard (keystore) { var modelStatuses = $app.model.m.remoteSiteStatuses; var tempList = []; for (var i = 0; i < modelStatuses.length; i++) { var abbreviatedEntry = modelStatuses[i]; if (abbreviatedEntry.url != "") // ignore self tempList.push(abbreviatedEntry) } - vm.otherDashboards = [['Hub', '']].concat(map(entryToPair, tempList)); + vm.otherDashboards = [['Hub', '', true]].concat(map(entryToPair, tempList)); vm.otherDashboards.sort(comparator); vm.clearCache = clearCache; vm.switchDashboard = switchDashboard; } /** * Lexicographic sort where Hub is always first. I'm sure there's a more * golf-way of writing this. */ function comparator(first, second) { if (first[0] == 'Hub') { return -2; } else if (second[0] == 'Hub') { return 2; } else { var less = first[0].toLowerCase() < second[0].toLowerCase(); var eq = first[0].toLowerCase() == second[0].toLowerCase(); return less? -1: eq? 0 : 1 } } //todo remove duplication with header.js function switchDashboard(url, alias) { $app.model.toDashboard.url = url; $app.model.m.siteAlias = alias == 'Hub'? '': alias; clearCache(); $location.url("/diagnostic/summary"); } function clearCache() { $app.model.clearCache(); } function entryToPair(entry){ - return [entry.siteAlias, entry.url]; + return [entry.siteAlias, entry.url, entry.theyHaveMine && entry.haveTheirs && !entry.timeOutError]; } } })(); diff --git a/apps/dashboard-app/src/main/js/src/app/diagnostic/views/dashboard.tpl.html b/apps/dashboard-app/src/main/js/src/app/diagnostic/views/dashboard.tpl.html index 87770fc49..5bd8c8af0 100644 --- a/apps/dashboard-app/src/main/js/src/app/diagnostic/views/dashboard.tpl.html +++ b/apps/dashboard-app/src/main/js/src/app/diagnostic/views/dashboard.tpl.html @@ -1,18 +1,19 @@
-
\ No newline at end of file diff --git a/apps/dashboard-app/src/main/scala/net/shrine/dashboard/DashboardService.scala b/apps/dashboard-app/src/main/scala/net/shrine/dashboard/DashboardService.scala index 36f83c3f9..d69f27359 100644 --- a/apps/dashboard-app/src/main/scala/net/shrine/dashboard/DashboardService.scala +++ b/apps/dashboard-app/src/main/scala/net/shrine/dashboard/DashboardService.scala @@ -1,527 +1,528 @@ package net.shrine.dashboard import akka.actor.Actor import akka.event.Logging import net.shrine.authentication.UserAuthenticator import net.shrine.authorization.steward.OutboundUser import net.shrine.config.ConfigExtensions import net.shrine.crypto.{BouncyKeyStoreCollection, KeyStoreDescriptorParser, UtilHasher} import net.shrine.dashboard.httpclient.HttpClientDirectives.{forwardUnmatchedPath, requestUriThenRoute} import net.shrine.dashboard.jwtauth.ShrineJwtAuthenticator import net.shrine.i2b2.protocol.pm.User import net.shrine.log.Loggable import net.shrine.problem.{ProblemDigest, Problems} import net.shrine.serialization.NodeSeqSerializer import net.shrine.source.ConfigSource import net.shrine.spray._ import net.shrine.status.protocol.{Config => StatusProtocolConfig} import net.shrine.util.SingleHubModel import org.json4s.native.JsonMethods.{parse => json4sParse} import org.json4s.{DefaultFormats, Formats} import shapeless.HNil import spray.http.{HttpRequest, HttpResponse, StatusCodes, Uri} import spray.httpx.Json4sSupport import spray.routing._ import spray.routing.directives.LogEntry import scala.collection.immutable.Iterable import scala.concurrent.ExecutionContext.Implicits.global /** * Mixes the DashboardService trait with an Akka Actor to provide the actual service. */ class DashboardServiceActor extends Actor with DashboardService { // the HttpService trait defines only one abstract member, which // connects the services environment to the enclosing actor or test def actorRefFactory = context // this actor only runs our route, but you could add // other things here, like request stream processing // or timeout handling def receive = runRoute(route) } /** * A web service that provides the Dashboard endpoints. It is a trait to support testing independent of Akka. */ trait DashboardService extends HttpService with Loggable { val userAuthenticator = UserAuthenticator(ConfigSource.config) //don't need to do anything special for unauthorized users, but they do need access to a static form. lazy val route:Route = gruntWatchCorsSupport { redirectToIndex ~ staticResources ~ makeTrouble ~ about ~ authenticatedInBrowser ~ authenticatedDashboard ~ post { // Chicken and egg problem; Can't check status of certs validation between sites if you need valid certs to exchange messages pathPrefix("status") pathPrefix("verifySignature") verifySignature } } /** logs the request method, uri and response at info level */ def logEntryForRequestResponse(req: HttpRequest): Any => Option[LogEntry] = { case res: HttpResponse => Some(LogEntry(s"\n Request: $req\n Response: $res", Logging.InfoLevel)) case _ => None // other kind of responses } /** logs just the request method, uri and response status at info level */ def logEntryForRequest(req: HttpRequest): Any => Option[LogEntry] = { case res: HttpResponse => Some(LogEntry(s"\n Request: $req\n Response status: ${res.status}", Logging.InfoLevel)) case _ => None // other kind of responses } def authenticatedInBrowser: Route = pathPrefixTest("user"|"admin"|"toDashboard") { logRequestResponse(logEntryForRequestResponse _) { //logging is controlled by Akka's config, slf4j, and log4j config reportIfFailedToAuthenticate { authenticate(userAuthenticator.basicUserAuthenticator) { user => pathPrefix("user") { userRoute(user) } ~ pathPrefix("admin") { adminRoute(user) } ~ pathPrefix("toDashboard") { toDashboardRoute(user) } } } } } val reportIfFailedToAuthenticate = routeRouteResponse { case Rejected(List(AuthenticationFailedRejection(_,_))) => complete("AuthenticationFailed") } def authenticatedDashboard:Route = pathPrefix("fromDashboard") { logRequestResponse(logEntryForRequestResponse _) { //logging is controlled by Akka's config, slf4j, and log4j config get { //all remote dashboard calls are gets. authenticate(ShrineJwtAuthenticator.authenticate) { user => info(s"Sucessfully authenticated user `$user`") adminRoute(user) } } } } def makeTrouble = pathPrefix("makeTrouble") { complete(throw new IllegalStateException("fake trouble")) } lazy val redirectToIndex = pathEnd { redirect("shrine-dashboard/client/index.html", StatusCodes.PermanentRedirect) //todo pick up "shrine-dashboard" programatically } ~ ( path("index.html") | pathSingleSlash) { redirect("client/index.html", StatusCodes.PermanentRedirect) } lazy val staticResources = pathPrefix("client") { pathEnd { redirect("client/index.html", StatusCodes.PermanentRedirect) } ~ pathSingleSlash { redirect("index.html", StatusCodes.PermanentRedirect) } ~ { getFromResourceDirectory("client") } } lazy val about = pathPrefix("about") { complete("Nothing here yet") //todo } def userRoute(user:User):Route = get { pathPrefix("whoami") { complete(OutboundUser.createFromUser(user)) } } //todo check that this an admin. def adminRoute(user:User):Route = get { pathPrefix("happy") { val happyBaseUrl: String = ConfigSource.config.getString("shrine.dashboard.happyBaseUrl") forwardUnmatchedPath(happyBaseUrl) } ~ pathPrefix("messWithHappyVersion") { //todo is this used? val happyBaseUrl: String = ConfigSource.config.getString("shrine.dashboard.happyBaseUrl") def pullClasspathFromConfig(httpResponse:HttpResponse,uri:Uri):Route = { ctx => { val result = httpResponse.entity.asString ctx.complete(s"Got '$result' from $uri") } } requestUriThenRoute(happyBaseUrl+"/version",pullClasspathFromConfig) } ~ pathPrefix("ping") { complete("pong") }~ pathPrefix("status") { statusRoute(user) } } //Manually test this by running a curl command //curl -k -w "\n%{response_code}\n" -u dave:kablam "https://shrine-dev1.catalyst:6443/shrine-dashboard/toDashboard/shrine-dev2.catalyst/ping" /** * Forward a request from this dashboard to a remote dashboard */ def toDashboardRoute(user:User):Route = get { pathPrefix(Segment) { dnsName => import scala.collection.JavaConversions._ val urlToParse: String = KeyStoreInfo.keyStoreDescriptor.trustModel match { case SingleHubModel(false) => ConfigSource.config.getString("shrine.queryEntryPoint.broadcasterServiceEndpoint.url") case _ => ConfigSource.config.getObject("shrine.hub.downstreamNodes").values.head.unwrapped.toString } val remoteDashboardPort = urlToParse.split(':')(2).split('/')(0) // TODO: Do ports vary between sites? val remoteDashboardProtocol = urlToParse.split("://")(0) val remoteDashboardPathPrefix = "shrine-dashboard/fromDashboard" // I don't think this needs to be configurable val baseUrl = s"$remoteDashboardProtocol://$dnsName:$remoteDashboardPort/$remoteDashboardPathPrefix" + info(s"toDashboardRoute: BaseURL: $baseUrl") forwardUnmatchedPath(baseUrl,Some(ShrineJwtAuthenticator.createOAuthCredentials(user, dnsName))) } } def statusRoute(user:User):Route = get { val( adapter , hub , i2b2 , keystore , optionalParts , qep , summary ) = ("adapter", "hub", "i2b2", "keystore", "optionalParts", "qep", "summary") pathPrefix("classpath") { getClasspath }~ pathPrefix("config") { getConfig }~ pathPrefix("problems") { getProblems }~ pathPrefix(adapter) { getFromSubService(adapter) }~ pathPrefix(hub) { getFromSubService(hub) }~ pathPrefix(i2b2) { getFromSubService(i2b2) }~ pathPrefix(keystore) { getFromSubService(keystore) }~ pathPrefix(optionalParts) { getFromSubService(optionalParts) }~ pathPrefix(qep) { getFromSubService(qep) }~ pathPrefix(summary) { getFromSubService(summary) } } val statusBaseUrl = ConfigSource.config.getString("shrine.dashboard.statusBaseUrl") // TODO: Move this over to Status API? lazy val verifySignature:Route = { formField("sha256".as[String].?) { sha256: Option[String] => val response = sha256.map(s => KeyStoreInfo.hasher.handleSig(s)) implicit val format = ShaResponse.json4sFormats response match { case None => complete(StatusCodes.BadRequest) case Some(sh@ShaResponse(ShaResponse.badFormat, _)) => complete(StatusCodes.BadRequest -> sh) case Some(sh@ShaResponse(_, false)) => complete(StatusCodes.NotFound -> sh) case Some(sh@ShaResponse(_, true)) => complete(StatusCodes.OK -> sh) } } } lazy val getConfig:Route = { def completeConfigRoute(httpResponse:HttpResponse,uri:Uri):Route = { ctx => { val config = ParsedConfig(httpResponse.entity.asString) ctx.complete( ShrineConfig(config) ) } } requestUriThenRoute(statusBaseUrl + "/config", completeConfigRoute) } lazy val getClasspath:Route = { def pullClasspathFromConfig(httpResponse:HttpResponse,uri:Uri):Route = { ctx => { val result = httpResponse.entity.asString val shrineConfig = ShrineConfig(ParsedConfig(result)) ctx.complete(shrineConfig) } } requestUriThenRoute(statusBaseUrl + "/config",pullClasspathFromConfig) } def getFromSubService(key: String):Route = { requestUriThenRoute(s"$statusBaseUrl/$key") } // table based view, can see N problems at a time. Front end sends how many problems that they want // to skip, and it will take N the 'nearest N' ie with n = 20, 0-19 -> 20, 20-39 -> 20-40 lazy val getProblems:Route = { def floorMod(x: Int, y: Int) = { x - (x % y) } val db = Problems.DatabaseConnector // Intellij loudly complains if you use parameters instead of chained parameter calls. // ¯\_(ツ)_/¯ parameter("offset".as[Int].?(0)) {(offsetPreMod:Int) => { parameter("n".as[Int].?(20)) {(nPreMax:Int) => parameter("epoch".as[Long].?) {(epoch:Option[Long]) => val n = Math.max(0, nPreMax) val moddedOffset = floorMod(Math.max(0, offsetPreMod), n) val query = for { result <- db.IO.sizeAndProblemDigest(n, moddedOffset) } yield (result._2, floorMod(Math.max(0, moddedOffset), n), n, result._1) val query2 = for { dateOffset <- db.IO.findIndexOfDate(epoch.getOrElse(0)) moddedOffset = floorMod(dateOffset, n) result <- db.IO.sizeAndProblemDigest(n, moddedOffset) } yield (result._2, moddedOffset, n, result._1) val queryReal = if (epoch.isEmpty) query else query2 val tupled = db.runBlocking(queryReal) val response: ProblemResponse = ProblemResponse(tupled._1, tupled._2, tupled._3, tupled._4) implicit val formats = response.json4sMarshaller complete(response) }}}} } } case class ProblemResponse(size: Int, offset: Int, n: Int, problems: Seq[ProblemDigest]) extends Json4sSupport { override implicit def json4sFormats: Formats = DefaultFormats + new NodeSeqSerializer } object KeyStoreInfo { val config = ConfigSource.config val keyStoreDescriptor = KeyStoreDescriptorParser( config.getConfig("shrine.keystore"), config.getConfigOrEmpty("shrine.hub"), config.getConfigOrEmpty("shrine.queryEntryPoint")) val certCollection = BouncyKeyStoreCollection.fromFileRecoverWithClassPath(keyStoreDescriptor) val hasher = UtilHasher(certCollection) } /** * Centralized parsing logic for map of shrine.conf * the class literal `T.class` in Java. */ //todo most of this info should come directly from the status service in Shrine, not from reading the config case class ParsedConfig(configMap:Map[String, String]){ private val trueVal = "true" private val rootKey = "shrine" def isHub = getOrElse(rootKey + ".hub.create", "") .toLowerCase == trueVal def stewardEnabled = configMap.keySet .contains(rootKey + ".queryEntryPoint.shrineSteward") def shouldQuerySelf = getOrElse(rootKey + ".hub.shouldQuerySelf", "") .toLowerCase == trueVal def fromJsonString(jsonString:String): String = jsonString.split("\"").mkString("") def get(key:String): Option[String] = configMap.get(key).map(fromJsonString) def getOrElse(key:String, elseVal:String = ""): String = get(key).getOrElse(elseVal) } object ParsedConfig { def apply(jsonString:String):ParsedConfig = { implicit def json4sFormats: Formats = DefaultFormats ParsedConfig(json4sParse(jsonString).extract[StatusProtocolConfig].keyValues)//.filterKeys(_.toLowerCase.startsWith("shrine"))) } } case class DownstreamNode(name:String, url:String) object DownstreamNode { def create(configMap:Map[String,String]):Iterable[DownstreamNode] = { for ((k, v) <- configMap.filterKeys(_.toLowerCase.startsWith ("shrine.hub.downstreamnodes"))) yield DownstreamNode(k.split('.').last,v.split("\"").mkString("")) } } //todo replace with the actual config, scrubbed of passwords case class ShrineConfig(isHub:Boolean, hub:Hub, pmEndpoint:Endpoint, ontEndpoint:Endpoint, hiveCredentials: HiveCredentials, adapter: Adapter, queryEntryPoint:QEP, networkStatusQuery:String, configMap:Map[String, String] ) extends DefaultJsonSupport object ShrineConfig extends DefaultJsonSupport { def apply(config:ParsedConfig):ShrineConfig = { val hub = Hub(config) val isHub = config.isHub val pmEndpoint = Endpoint("pm",config) val ontEndpoint = Endpoint("ont",config) val hiveCredentials = HiveCredentials(config) val adapter = Adapter(config) val queryEntryPoint = QEP(config) val networkStatusQuery = config.configMap("shrine.networkStatusQuery") ShrineConfig(isHub, hub, pmEndpoint, ontEndpoint, hiveCredentials, adapter, queryEntryPoint, networkStatusQuery, config.configMap) } } case class Endpoint(acceptAllCerts:Boolean, url:String, timeoutSeconds:Int) object Endpoint{ def apply(endpointType:String,parsedConfig:ParsedConfig):Endpoint = { val prefix = "shrine." + endpointType.toLowerCase + "Endpoint." val acceptAllCerts = parsedConfig.configMap.getOrElse(prefix + "acceptAllCerts", "") == "true" val url = parsedConfig.configMap.getOrElse(prefix + "url","") val timeoutSeconds = parsedConfig.configMap.getOrElse(prefix + "timeout.seconds", "0").toInt Endpoint(acceptAllCerts, url, timeoutSeconds) } } case class HiveCredentials(domain:String, username:String, password:String, crcProjectId:String, ontProjectId:String) object HiveCredentials{ def apply(parsedConfig:ParsedConfig):HiveCredentials = { val key = "shrine.hiveCredentials." val domain = parsedConfig.configMap.getOrElse(key + "domain","") val username = parsedConfig.configMap.getOrElse(key + "username","") val password = "REDACTED" val crcProjectId = parsedConfig.configMap.getOrElse(key + "crcProjectId","") val ontProjectId = parsedConfig.configMap.getOrElse(key + "ontProjectId","") HiveCredentials(domain, username, password, crcProjectId, ontProjectId) } } // -- hub only -- // //todo delete when the Dashboard front end can use the status service's hub method case class Hub(shouldQuerySelf:Boolean, create:Boolean, downstreamNodes:Iterable[DownstreamNode]) object Hub{ def apply(parsedConfig:ParsedConfig):Hub = { val shouldQuerySelf = parsedConfig.shouldQuerySelf val create = parsedConfig.isHub val downstreamNodes = DownstreamNode.create(parsedConfig.configMap) Hub(shouldQuerySelf, create, downstreamNodes) } } // -- adapter info -- // case class Adapter(crcEndpointUrl:String, setSizeObfuscation:Boolean, adapterLockoutAttemptsThreshold:Int, adapterMappingsFilename:String) object Adapter{ def apply(parsedConfig:ParsedConfig):Adapter = { val key = "shrine.adapter." val crcEndpointUrl = parsedConfig.configMap.getOrElse(key + "crcEndpoint.url","") val setSizeObfuscation = parsedConfig.configMap.getOrElse(key + "setSizeObfuscation","").toLowerCase == "true" val adapterLockoutAttemptsThreshold = parsedConfig.configMap.getOrElse(key + "adapterLockoutAttemptsThreshold", "0").toInt val adapterMappingsFileName = parsedConfig.configMap.getOrElse(key + "adapterMappingsFileName","") Adapter(crcEndpointUrl, setSizeObfuscation, adapterLockoutAttemptsThreshold, adapterMappingsFileName) } } case class Steward(qepUserName:String, stewardBaseUrl:String) object Steward { def apply (parsedConfig:ParsedConfig):Steward = { val key = "shrine.queryEntryPoint.shrineSteward." val qepUserName = parsedConfig.configMap.getOrElse(key + "qepUserName","") val stewardBaseUrl = parsedConfig.configMap.getOrElse(key + "stewardBaseUrl","") Steward(qepUserName, stewardBaseUrl) } } // -- if needed -- // case class TimeoutInfo (timeUnit:String, description:String) case class DatabaseInfo(createTablesOnStart:Boolean, dataSourceFrom:String, jndiDataSourceName:String, slickProfileClassName:String) case class Audit(database:DatabaseInfo, collectQepAudit:Boolean) object Audit{ def apply(parsedConfig:ParsedConfig):Audit = { val key = "shrine.queryEntryPoint.audit." val createTablesOnStart = parsedConfig.configMap.getOrElse(key + "database.createTablesOnStart","") == "true" val dataSourceFrom = parsedConfig.configMap.getOrElse(key + "database.dataSourceFrom","") val jndiDataSourceName = parsedConfig.configMap.getOrElse(key + "database.jndiDataSourceName","") val slickProfileClassName = parsedConfig.configMap.getOrElse(key + "database.slickProfileClassName","") val collectQepAudit = parsedConfig.configMap.getOrElse(key + "collectQepAudit","") == "true" val database = DatabaseInfo(createTablesOnStart, dataSourceFrom, jndiDataSourceName, slickProfileClassName) Audit(database, collectQepAudit) } } case class QEP( maxQueryWaitTimeMinutes:Int, create:Boolean, attachSigningCert:Boolean, authorizationType:String, includeAggregateResults:Boolean, authenticationType:String, audit:Audit, shrineSteward:Steward, broadcasterServiceEndpointUrl:Option[String] ) object QEP{ val key = "shrine.queryEntryPoint." def apply(parsedConfig:ParsedConfig):QEP = QEP( maxQueryWaitTimeMinutes = parsedConfig.configMap.getOrElse(key + "maxQueryWaitTime.minutes", "0").toInt, create = parsedConfig.configMap.getOrElse(key + "create","") == "true", attachSigningCert = parsedConfig.configMap.getOrElse(key + "attachSigningCert","") == "true", authorizationType = parsedConfig.configMap.getOrElse(key + "authorizationType",""), includeAggregateResults = parsedConfig.configMap.getOrElse(key + "includeAggregateResults","") == "true", authenticationType = parsedConfig.configMap.getOrElse(key + "authenticationType", ""), audit = Audit(parsedConfig), shrineSteward = Steward(parsedConfig), broadcasterServiceEndpointUrl = parsedConfig.configMap.get(key + "broadcasterServiceEndpoint.url") ) } //adapted from https://gist.github.com/joseraya/176821d856b43b1cfe19 object gruntWatchCorsSupport extends Directive0 with RouteConcatenation { import spray.http.AllOrigins import spray.http.HttpHeaders.{`Access-Control-Allow-Headers`, `Access-Control-Allow-Methods`, `Access-Control-Allow-Origin`, `Access-Control-Max-Age`} import spray.http.HttpMethods.{GET, OPTIONS, POST} import spray.routing.directives.MethodDirectives.options import spray.routing.directives.RespondWithDirectives.respondWithHeaders import spray.routing.directives.RouteDirectives.complete private val allowOriginHeader = `Access-Control-Allow-Origin`(AllOrigins) private val optionsCorsHeaders = List( `Access-Control-Allow-Headers`("Origin, X-Requested-With, Content-Type, Accept, Accept-Encoding, Accept-Language, Host, Referer, User-Agent, Authorization"), `Access-Control-Max-Age`(1728000)) //20 days val gruntWatch:Boolean = ConfigSource.config.getBoolean("shrine.dashboard.gruntWatch") override def happly(f: (HNil) => Route): Route = { if(gruntWatch) { options { respondWithHeaders(`Access-Control-Allow-Methods`(OPTIONS, GET, POST) :: allowOriginHeader :: optionsCorsHeaders){ complete(StatusCodes.OK) } } ~ f(HNil) } else f(HNil) } } \ No newline at end of file diff --git a/apps/dashboard-app/src/main/scala/net/shrine/dashboard/httpclient/HttpClientDirectives.scala b/apps/dashboard-app/src/main/scala/net/shrine/dashboard/httpclient/HttpClientDirectives.scala index da1755a57..e40f2e7cf 100644 --- a/apps/dashboard-app/src/main/scala/net/shrine/dashboard/httpclient/HttpClientDirectives.scala +++ b/apps/dashboard-app/src/main/scala/net/shrine/dashboard/httpclient/HttpClientDirectives.scala @@ -1,206 +1,207 @@ package net.shrine.dashboard.httpclient import java.io.InputStream import java.security.cert.X509Certificate import javax.net.ssl.{SSLContext, X509TrustManager} import net.shrine.log.Loggable import spray.can.Http import akka.io.IO import akka.actor.{ActorRef, ActorSystem} import spray.can.Http.{ConnectionAttemptFailedException, HostConnectorSetup} import spray.http.{HttpCredentials, HttpEntity, HttpHeader, HttpHeaders, HttpRequest, HttpResponse, StatusCodes, Uri} import spray.io.ClientSSLEngineProvider import spray.routing.{RequestContext, Route} import akka.pattern.ask import net.shrine.source.ConfigSource import scala.concurrent.{Await, Future, TimeoutException, blocking} import scala.concurrent.duration.DurationInt import scala.language.postfixOps import scala.concurrent.ExecutionContext.Implicits.global import scala.util.control.NonFatal /** * From https://github.com/bthuillier/spray/commit/d31fc1b5e1415e1b908fe7d1f01f364a727e2593 with extra bits from http://www.cakesolutions.net/teamblogs/http-proxy-with-spray . * Replace when Spray has its own version. * * @author david * @since 9/14/15 */ trait HttpClientDirectives extends Loggable { implicit val system = ActorSystem("dashboardServer",ConfigSource.config) /** * Proxy the request to the specified base uri appended with the unmatched path. * */ def forwardUnmatchedPath(baseUri: Uri,maybeCredentials:Option[HttpCredentials] = None): Route = { def completeWithEntityAsString(httpResponse:HttpResponse,uri:Uri):Route = { ctx => { ctx.complete(httpResponse.entity.asString) } } requestWithUnmatchedPath(baseUri,completeWithEntityAsString,maybeCredentials) } /** * Make the request to the specified base uri appended with the unmatched path, then use the returned entity (as a string) to complete the route. * */ def requestWithUnmatchedPath(baseUri:Uri, route:(HttpResponse,Uri) => Route,maybeCredentials:Option[HttpCredentials] = None): Route = { ctx => { - val resourceUri = baseUri.withPath(baseUri.path.++(ctx.unmatchedPath)) + + val resourceUri = baseUri.withPath(baseUri.path.++(ctx.unmatchedPath)).withQuery(ctx.request.uri.query) requestUriThenRoute(resourceUri,route,maybeCredentials)(ctx) } } /** * Just pass the result through */ def passThrough(httpResponse: HttpResponse,uri: Uri):Route = ctx => ctx.complete(httpResponse.entity.asString) /** * proxy the request to the specified uri with the unmatched path, then use the returned entity (as a string) to complete the route. * */ def requestUriThenRoute( resourceUri:Uri, route:(HttpResponse,Uri) => Route = passThrough, maybeCredentials:Option[HttpCredentials] = None ): Route = { ctx => { val httpResponse = httpResponseForUri(resourceUri,ctx,maybeCredentials) info(s"Got $httpResponse for $resourceUri") handleCommonErrorsOrRoute(route)(httpResponse,resourceUri)(ctx) } } private def httpResponseForUri(resourceUri:Uri,ctx: RequestContext,maybeCredentials:Option[HttpCredentials] = None):HttpResponse = { info(s"Requesting $resourceUri") if(resourceUri.scheme == "classpath") ClasspathResourceHttpClient.loadFromResource(resourceUri.path.toString()) else { val basicRequest = HttpRequest(ctx.request.method,resourceUri) val request = maybeCredentials.fold(basicRequest){ (credentials: HttpCredentials) => val headers: List[HttpHeader] = basicRequest.headers :+ HttpHeaders.Authorization(credentials) basicRequest.copy(headers = headers) } HttpClient.webApiCall(request) } } def handleCommonErrorsOrRoute(route:(HttpResponse,Uri) => Route)(httpResponse: HttpResponse,uri:Uri): Route = { ctx => { if(httpResponse.status != StatusCodes.OK) { //todo create and report a problem val ctxCopy: RequestContext = ctx.withHttpResponseMapped(_.copy(status = httpResponse.status)) ctxCopy.complete(s"$uri replied with $httpResponse") } else route(httpResponse,uri)(ctx) } } } object HttpClientDirectives extends HttpClientDirectives /** * A simple HttpClient to use inside the HttpDirectives */ object HttpClient extends Loggable { //todo hand back a Try, Failures with custom exceptions instead of a crappy response def webApiCall(request:HttpRequest)(implicit system: ActorSystem): HttpResponse = { val transport: ActorRef = IO(Http)(system) debug(s"Requesting $request uri is ${request.uri} path is ${request.uri.path}") blocking { val future:Future[HttpResponse] = for { Http.HostConnectorInfo(connector, _) <- transport.ask(createConnector(request))(10 seconds) //todo make this timeout configurable response <- connector.ask(request)(10 seconds).mapTo[HttpResponse] //todo make this timeout configurable } yield response try { Await.result(future, 10 seconds) //todo make this timeout configurable } catch { case x:TimeoutException => HttpResponse(status = StatusCodes.RequestTimeout,entity = HttpEntity(s"${request.uri} timed out after 10 seconds. ${x.getMessage}")) //todo is there a better message? What comes up in real life? case x:ConnectionAttemptFailedException => { //no web service is there to respond info(s"${request.uri} failed with ${x.getMessage}",x) HttpResponse(status = StatusCodes.NotFound,entity = HttpEntity(s"${request.uri} failed with ${x.getMessage}")) } case NonFatal(x) => { info(s"${request.uri} failed with ${x.getMessage}",x) HttpResponse(status = StatusCodes.InternalServerError,entity = HttpEntity(s"${request.uri} failed with ${x.getMessage}")) } } } } //from https://github.com/TimothyKlim/spray-ssl-poc/blob/master/src/main/scala/Main.scala //trust all SSL contexts. We just want encrypted comms. implicit val trustfulSslContext: SSLContext = { class IgnoreX509TrustManager extends X509TrustManager { def checkClientTrusted(chain: Array[X509Certificate], authType: String) {} def checkServerTrusted(chain: Array[X509Certificate], authType: String) {} def getAcceptedIssuers = null } val context = SSLContext.getInstance("TLS") context.init(null, Array(new IgnoreX509TrustManager), null) info("trustfulSslContex initialized") context } implicit val clientSSLEngineProvider = //todo lookup this constructor ClientSSLEngineProvider { _ => val engine = trustfulSslContext.createSSLEngine() engine.setUseClientMode(true) engine } def createConnector(request: HttpRequest) = { val connector = new HostConnectorSetup(host = request.uri.authority.host.toString, port = request.uri.effectivePort, sslEncryption = request.uri.scheme == "https", defaultHeaders = request.headers) connector } } /** * For testing, get an HttpResponse for a classpath resource */ object ClasspathResourceHttpClient extends Loggable { def loadFromResource(resourceName:String):HttpResponse = { blocking { val cleanResourceName = if (resourceName.startsWith ("/") ) resourceName.drop(1) else resourceName val classLoader = getClass.getClassLoader try { val is: InputStream = classLoader.getResourceAsStream (cleanResourceName) val string:String = scala.io.Source.fromInputStream (is).mkString HttpResponse(entity = HttpEntity(string)) } catch{ case NonFatal(x) => { info(s"Could not load $resourceName",x) HttpResponse(status = StatusCodes.NotFound,entity = HttpEntity(s"Could not load $resourceName due to ${x.getMessage}")) } } } } } \ No newline at end of file diff --git a/apps/dashboard-app/src/test/scala/net/shrine/dashboard/DashboardServiceTest.scala b/apps/dashboard-app/src/test/scala/net/shrine/dashboard/DashboardServiceTest.scala index 584f9eecb..8f73f9119 100644 --- a/apps/dashboard-app/src/test/scala/net/shrine/dashboard/DashboardServiceTest.scala +++ b/apps/dashboard-app/src/test/scala/net/shrine/dashboard/DashboardServiceTest.scala @@ -1,434 +1,434 @@ package net.shrine.dashboard import java.security.PrivateKey import java.util.Date import io.jsonwebtoken.impl.TextCodec import io.jsonwebtoken.{Jwts, SignatureAlgorithm} import net.shrine.authorization.steward.OutboundUser import net.shrine.config.ConfigExtensions import net.shrine.crypto.{BouncyKeyStoreCollection, KeyStoreDescriptorParser} import net.shrine.dashboard.jwtauth.ShrineJwtAuthenticator import net.shrine.i2b2.protocol.pm.User import net.shrine.protocol.Credential import net.shrine.source.ConfigSource import net.shrine.spray.ShaResponse import org.json4s.native.JsonMethods.parse import org.junit.runner.RunWith import org.scalatest.FlatSpec import org.scalatest.junit.JUnitRunner import spray.http.StatusCodes.{NotFound, OK, PermanentRedirect, Unauthorized} import spray.http.{BasicHttpCredentials, FormData, OAuth2BearerToken, StatusCodes} import spray.testkit.ScalatestRouteTest import scala.language.postfixOps @RunWith(classOf[JUnitRunner]) class DashboardServiceTest extends FlatSpec with ScalatestRouteTest with DashboardService { def actorRefFactory = system import scala.concurrent.duration._ implicit val routeTestTimeout = RouteTestTimeout(10 seconds) val adminUserName = "keith" val adminFullName = adminUserName /** * to run these tests with I2B2 * add a user named keith, to be the admin * add a Boolean parameter for keith, Admin, true * add all this user to the i2b2 project */ val adminCredentials = BasicHttpCredentials(adminUserName,"shh!") val brokenCredentials = BasicHttpCredentials(adminUserName,"wrong password") val adminUser = User( fullName = adminUserName, username = adminFullName, domain = "domain", credential = new Credential("admin's password",false), params = Map(), rolesByProject = Map() ) val adminOutboundUser = OutboundUser.createFromUser(adminUser) "DashboardService" should "return an OK and a valid outbound user for a user/whoami request" in { Get(s"/user/whoami") ~> addCredentials(adminCredentials) ~> route ~> check { assertResult(OK)(status) implicit val formats = OutboundUser.json4sFormats val userJson = new String(body.data.toByteArray) val outboundUser = parse(userJson).extract[OutboundUser] assertResult(adminOutboundUser)(outboundUser) } } "DashboardService" should "return an OK and a valid outbound user for a user/whoami request and an '' " in { Get(s"/user/whoami") ~> addCredentials(brokenCredentials) ~> route ~> check { assertResult(OK)(status) val response = new String(body.data.toByteArray) assertResult("""AuthenticationFailed""")(response) } } "DashboardService" should "redirect several urls to client/index.html" in { Get() ~> route ~> check { status === PermanentRedirect header("Location") === "client/index.html" } Get("/") ~> route ~> check { status === PermanentRedirect header("Location") === "client/index.html" } Get("/index.html") ~> route ~> check { status === PermanentRedirect header("Location") === "client/index.html" } Get("/client") ~> route ~> check { status === PermanentRedirect header("Location") === "client/index.html" } Get("/client/") ~> route ~> check { status === PermanentRedirect header("Location") === "client/index.html" } } - "DashboardService" should "return an OK and the right version string for an admin/happy/all test" in { + "DashboardService" should "return an OK and the right version string for an admin/happy/all?extra=true test" in { - Get(s"/admin/happy/all") ~> + Get(s"/admin/happy/all?extra=true") ~> addCredentials(adminCredentials) ~> route ~> check { assertResult(OK)(status) val allString = new String(body.data.toByteArray) //todo test it to see if it's right } } "DashboardService" should "return an OK and mess with the right version string for an admin/messWithHappyVersion test" in { Get(s"/admin/messWithHappyVersion") ~> addCredentials(adminCredentials) ~> route ~> check { assertResult(OK)(status) val versionString = new String(body.data.toByteArray) //todo test it to see if it's right } } "DashboardService" should "return an OK for admin/status/config" in { Get(s"/admin/status/config") ~> addCredentials(adminCredentials) ~> route ~> check { assertResult(OK)(status) val configString = new String(body.data.toByteArray) } } "DashboardService" should "return an OK for admin/status/classpath" in { Get(s"/admin/status/classpath") ~> addCredentials(adminCredentials) ~> route ~> check { assertResult(OK)(status) val classpathString = new String(body.data.toByteArray) } } "DashboardService" should "return an OK for admin/status/optionalParts" in { Get(s"/admin/status/optionalParts") ~> addCredentials(adminCredentials) ~> route ~> check { assertResult(OK)(status) val options = new String(body.data.toByteArray) } } "DashboardService" should "return an OK for admin/status/summary" in { Get(s"/admin/status/summary") ~> addCredentials(adminCredentials) ~> route ~> check { assertResult(OK)(status) val summary = new String(body.data.toByteArray) } } "DashboardService" should "return an OK for admin/status/adapter" in { Get(s"/admin/status/adapter") ~> addCredentials(adminCredentials) ~> route ~> check { assertResult(OK)(status) val adapter = new String(body.data.toByteArray) } } "DashboardService" should "return an OK for admin/status/hub" in { Get(s"/admin/status/hub") ~> addCredentials(adminCredentials) ~> route ~> check { assertResult(OK)(status) val hub = new String(body.data.toByteArray) } } "DashboardService" should "return an OK for admin/status/i2b2" in { Get(s"/admin/status/i2b2") ~> addCredentials(adminCredentials) ~> route ~> check { assertResult(OK)(status) val i2b2 = new String(body.data.toByteArray) } } "DashboardService" should "return an OK for admin/status/keystore" in { Get(s"/admin/status/keystore") ~> addCredentials(adminCredentials) ~> route ~> check { assertResult(OK)(status) val keystore = new String(body.data.toByteArray) } } "DashboardService" should "return an OK for admin/status/qep" in { Get(s"/admin/status/qep") ~> addCredentials(adminCredentials) ~> route ~> check { assertResult(OK)(status) val qep = new String(body.data.toByteArray) } } "DashboardService" should "return an OK for admin/status/problems" in { Get("/admin/status/problems") ~> addCredentials(adminCredentials) ~> route ~> check { assertResult(OK)(status) val problems = new String(body.data.toByteArray) } } "DashboardService" should "return an OK for admin/status/problems with queries" in { Get("/admin/status/problems?offset=2&n=1") ~> addCredentials(adminCredentials) ~> route ~> check { assertResult(OK)(status) val problems = new String(body.data.toByteArray) } } "DashboardService" should "return an OK for admin/status/problems with queries and an epoch filter" in { Get("/admin/status/problems?offset=2&n=3&epoch=3") ~> addCredentials(adminCredentials) ~> route ~> check { assertResult(OK)(status) val problems = new String(body.data.toByteArray) } } "DashboardService" should "return a BadRequest for admin/status/signature with no signature parameter" in { Post("/status/verifySignature") ~> addCredentials(adminCredentials) ~> route ~> check { assertResult(StatusCodes.BadRequest)(status) } } "DashboardService" should "return a BadRequest for admin/status/signature with a malformatted signature parameter" in { Post("/status/verifySignature", FormData(Seq("sha256" -> "foo"))) ~> addCredentials(adminCredentials) ~> route ~> check { assertResult(StatusCodes.BadRequest)(status) implicit val formats = ShaResponse.json4sFormats assertResult(ShaResponse(ShaResponse.badFormat, false))(parse(new String(body.data.toByteArray)).extract[ShaResponse]) } } "DashboardService" should "return a NotFound for admin/status/signature with a correctly formatted parameter that is not in the keystore" in { Post("/status/verifySignature", FormData(Seq("sha256" -> "00:00:00:00:00:00:00:7C:4B:FD:8D:A8:0A:C7:A4:AA:13:3E:22:B3:57:A7:C6:B0:95:15:1B:22:C0:E5:15:9A"))) ~> addCredentials(adminCredentials) ~> route ~> check { assertResult(NotFound)(status) implicit val formats = ShaResponse.json4sFormats assertResult(ShaResponse("0E:5D:D1:10:68:2B:63:F4:66:E2:50:41:EA:13:AF:1A:F9:99:DB:40:6A:F7:EE:39:F2:1A:0D:51:7A:44:09:7A", false)) ( parse(new String(body.data.toByteArray)).extract[ShaResponse]) } } "DashboardService" should "return an OK for admin/status/signature with a valid sha256 hash" in { val post = Post("/status/verifySignature", FormData(Seq("sha256" -> "0E:5D:D1:10:68:2B:63:F4:66:E2:50:41:EA:13:AF:1A:F9:99:DB:40:6A:F7:EE:39:F2:1A:0D:51:7A:44:09:7A"))) post ~> addCredentials(adminCredentials) ~> route ~> check { assertResult(OK)(status) implicit val formats = ShaResponse.json4sFormats assertResult(ShaResponse("0E:5D:D1:10:68:2B:63:F4:66:E2:50:41:EA:13:AF:1A:F9:99:DB:40:6A:F7:EE:39:F2:1A:0D:51:7A:44:09:7A", true))( parse(new String(body.data.toByteArray)).extract[ShaResponse] ) } } val dashboardCredentials = BasicHttpCredentials(adminUserName,"shh!") "DashboardService" should "return an OK and pong for fromDashboard/ping" in { Get(s"/fromDashboard/ping") ~> addCredentials(ShrineJwtAuthenticator.createOAuthCredentials(adminUser, "")) ~> route ~> check { assertResult(OK)(status) val string = new String(body.data.toByteArray) assertResult("pong")(string) } } "DashboardService" should "reject a fromDashboard/ping with an expired jwts header" in { val config = ConfigSource.config val shrineCertCollection: BouncyKeyStoreCollection = BouncyKeyStoreCollection.fromFileRecoverWithClassPath(KeyStoreDescriptorParser( config.getConfig("shrine.keystore"), config.getConfigOrEmpty("shrine.hub"), config.getConfigOrEmpty("shrine.queryEntryPoint"))) val base64Cert = new String(TextCodec.BASE64URL.encode(shrineCertCollection.myEntry.cert.getEncoded)) val key: PrivateKey = shrineCertCollection.myEntry.privateKey.get val expiration: Date = new Date(System.currentTimeMillis() - 300 * 1000) //bad for 5 minutes val jwtsString = Jwts.builder(). setHeaderParam("kid", base64Cert). setSubject(java.net.InetAddress.getLocalHost.getHostName). setExpiration(expiration). signWith(SignatureAlgorithm.RS512, key). compact() Get(s"/fromDashboard/ping") ~> addCredentials(OAuth2BearerToken(jwtsString)) ~> sealRoute(route) ~> check { assertResult(Unauthorized)(status) } } "DashboardService" should "reject a fromDashboard/ping with no subject" in { val config = ConfigSource.config val shrineCertCollection: BouncyKeyStoreCollection = BouncyKeyStoreCollection.fromFileRecoverWithClassPath(KeyStoreDescriptorParser( config.getConfig("shrine.keystore"), config.getConfigOrEmpty("shrine.hub"), config.getConfigOrEmpty("shrine.queryEntryPoint"))) val base64Cert = new String(TextCodec.BASE64URL.encode(shrineCertCollection.myEntry.cert.getEncoded)) val key: PrivateKey = shrineCertCollection.myEntry.privateKey.get val expiration: Date = new Date(System.currentTimeMillis() + 30 * 1000) val jwtsString = Jwts.builder(). setHeaderParam("kid", base64Cert). setExpiration(expiration). signWith(SignatureAlgorithm.RS512, key). compact() Get(s"/fromDashboard/ping") ~> addCredentials(OAuth2BearerToken(jwtsString)) ~> sealRoute(route) ~> check { assertResult(Unauthorized)(status) } } "DashboardService" should "reject a fromDashboard/ping with no Authorization header" in { Get(s"/fromDashboard/ping") ~> sealRoute(route) ~> check { assertResult(Unauthorized)(status) } } "DashboardService" should "reject a fromDashboard/ping with an Authorization header for the wrong authorization spec" in { Get(s"/fromDashboard/ping") ~> addCredentials(adminCredentials) ~> sealRoute(route) ~> check { assertResult(Unauthorized)(status) } } "DashboardService" should "not find a bogus web service to talk to" in { Get(s"/toDashboard/bogus.harvard.edu/ping") ~> addCredentials(adminCredentials) ~> sealRoute(route) ~> check { val string = new String(body.data.toByteArray) assertResult(NotFound)(status) } } } diff --git a/apps/dashboard-war/pom.xml b/apps/dashboard-war/pom.xml index ea0661845..2f6c48c94 100644 --- a/apps/dashboard-war/pom.xml +++ b/apps/dashboard-war/pom.xml @@ -1,37 +1,37 @@ shrine-base net.shrine - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT ../../pom.xml 4.0.0 dashboard-war Dashboard War war org.apache.maven.plugins maven-war-plugin shrine-dashboard net.shrine dashboard-app ${project.version} org.slf4j slf4j-log4j12 ${slf4j-version} runtime diff --git a/apps/meta-app/pom.xml b/apps/meta-app/pom.xml index b2390f38f..b20c554a2 100644 --- a/apps/meta-app/pom.xml +++ b/apps/meta-app/pom.xml @@ -1,92 +1,92 @@ shrine-base net.shrine - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT ../../pom.xml 4.0.0 meta-app MetaData App jar net.alchim31.maven scala-maven-plugin net.shrine shrine-utility-commons ${project.version} net.shrine shrine-config ${project.version} log4j log4j io.spray spray-routing_2.11 ${spray-version} io.spray spray-servlet_2.11 ${spray-version} io.spray spray-util_2.11 ${spray-version} io.spray spray-testkit_2.11 ${spray-version} test com.typesafe.akka akka-actor_2.11 ${akka-version} com.typesafe.akka akka-slf4j_2.11 ${akka-version} com.typesafe.akka akka-testkit_2.11 ${akka-testkit-version} test org.json4s json4s-native_2.11 ${json4s-version} com.typesafe config ${typesafe-config-version} mysql mysql-connector-java ${mysql-version} diff --git a/apps/meta-war/pom.xml b/apps/meta-war/pom.xml index 44fe1f52c..cdaaba0c7 100644 --- a/apps/meta-war/pom.xml +++ b/apps/meta-war/pom.xml @@ -1,37 +1,37 @@ shrine-base net.shrine - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT ../../pom.xml 4.0.0 meta-war MetaData War war org.apache.maven.plugins maven-war-plugin shrine-metadata net.shrine meta-app ${project.version} org.slf4j slf4j-log4j12 ${slf4j-version} runtime diff --git a/apps/proxy/pom.xml b/apps/proxy/pom.xml index 822266785..ff588f203 100644 --- a/apps/proxy/pom.xml +++ b/apps/proxy/pom.xml @@ -1,52 +1,52 @@ 4.0.0 SHRINE Proxy shrine-proxy war net.shrine shrine-base - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT ../../pom.xml net.shrine shrine-test-commons ${project.version} test-jar test net.shrine shrine-crypto ${project.version} net.shrine shrine-client ${project.version} net.shrine shrine-protocol ${project.version} javax.servlet javax.servlet-api provided src/main/scala src/test/scala shrine-proxy net.alchim31.maven scala-maven-plugin diff --git a/apps/shrine-app/pom.xml b/apps/shrine-app/pom.xml index e7fa59e8a..f985cb58a 100644 --- a/apps/shrine-app/pom.xml +++ b/apps/shrine-app/pom.xml @@ -1,118 +1,118 @@ shrine-base net.shrine - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT ../../pom.xml 4.0.0 shrine-app SHRINE App jar src/main/scala src/test/scala net.alchim31.maven scala-maven-plugin net.shrine shrine-test-commons ${project.version} test-jar test net.shrine shrine-config ${project.version} net.shrine shrine-hms-core ${project.version} net.shrine shrine-qep ${project.version} net.shrine shrine-broadcaster-aggregator ${project.version} net.shrine shrine-broadcaster-service ${project.version} net.shrine shrine-adapter-service ${project.version} net.shrine shrine-crypto ${project.version} net.shrine shrine-ont-support ${project.version} net.shrine shrine-crypto ${project.version} test-jar test net.shrine shrine-protocol ${project.version} test-jar test net.shrine shrine-config ${project.version} test-jar test net.shrine shrine-broadcaster-aggregator ${project.version} test-jar test net.shrine shrine-client ${project.version} test-jar test com.h2database h2 ${h2-version} test org.json4s json4s-native_2.11 ${json4s-version} 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 a10379ea0..2d6bb0f4b 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,601 @@ 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"), 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, //todo: delete attatchSingingCert 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 = nodeHandle.client.url.fold(new DownstreamNode("self", "not applicable"))(url => new DownstreamNode(nodeHandle.nodeId.name, url.toString)) } 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 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/steward-app/pom.xml b/apps/steward-app/pom.xml index c087e48c9..48e121264 100644 --- a/apps/steward-app/pom.xml +++ b/apps/steward-app/pom.xml @@ -1,177 +1,177 @@ shrine-base net.shrine - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT ../../pom.xml 4.0.0 steward-app Steward App jar src/main/scala src/test/scala net.alchim31.maven scala-maven-plugin com.github.eirslett frontend-maven-plugin 1.0 src/main/js install node and npm install-node-and-npm v6.2.2 3.10.3 npm install npm generate-resources bower install bower gulp test and build gulp --no-color io.spray spray-routing_2.11 ${spray-version} io.spray spray-servlet_2.11 ${spray-version} io.spray spray-util_2.11 ${spray-version} io.spray spray-testkit_2.11 ${spray-version} test com.typesafe.akka akka-actor_2.11 ${akka-version} com.typesafe.akka akka-slf4j_2.11 ${akka-version} com.typesafe.akka akka-testkit_2.11 ${akka-testkit-version} test org.json4s json4s-native_2.11 ${json4s-version} com.typesafe.slick slick_2.11 ${slick-version} org.suecarter freeslick_2.11 ${freeslick-version} org.slf4j slf4j-log4j12 ${slf4j-version} com.h2database h2 ${h2-version} test net.shrine shrine-protocol ${project.version} net.shrine shrine-email ${project.version} net.shrine shrine-data-commons ${project.version} net.shrine shrine-client ${project.version} net.shrine shrine-crypto ${project.version} test-jar test net.shrine shrine-auth ${project.version} mysql mysql-connector-java ${mysql-version} diff --git a/apps/steward-app/src/main/js/app/assets/css/shrine.css b/apps/steward-app/src/main/js/app/assets/css/shrine.css index 2bbab94a2..8fd6793af 100644 --- a/apps/steward-app/src/main/js/app/assets/css/shrine.css +++ b/apps/steward-app/src/main/js/app/assets/css/shrine.css @@ -1,1148 +1,1149 @@ @charset "UTF-8"; /* import timeline.scss */ @font-face { font-family: 'Nexa'; src: url("../fnt/nexa/nexa-light-webfont.eot"); src: url("../fnt/nexa/nexa-light-webfont.eot?#iefix") format("embedded-opentype"), url("../fnt/nexa/nexa-light-webfont.woff2") format("woff2"), url("../fnt/nexa/nexa-light-webfont.woff") format("woff"), url("../fnt/nexa/nexa-light-webfont.ttf") format("truetype"), url("../fnt/nexa/nexa-light-webfont.svg#nexa_lightregular") format("svg"); font-weight: normal; font-style: normal; } @font-face { font-family: 'Nexa Bold'; src: url("../fnt/nexa/nexa-bold-webfont.eot"); src: url("../fnt/nexa/nexa-bold-webfont.eot?#iefix") format("embedded-opentype"), url("../fnt/nexa/nexa-bold-webfont.woff2") format("woff2"), url("../fnt/nexa/nexa-bold-webfont.woff") format("woff"), url("../fnt/nexa/nexa-bold-webfont.ttf") format("truetype"), url("../fnt/nexa/nexa-bold-webfont.svg#nexa_boldregular") format("svg"); font-weight: normal; font-style: normal; } @font-face { font-family: 'Open Sans Regular'; src: url("../fnt/open-sans/opensans-regular-webfont.eot"); src: url("../fnt/open-sans/opensans-regular-webfont.eot?#iefix") format("embedded-opentype"), url("../fnt/open-sans/opensans-regular-webfont.woff2") format("woff2"), url("../fnt/open-sans/opensans-regular-webfont.woff") format("woff"), url("../fnt/open-sans/opensans-regular-webfont.ttf") format("truetype"), url("../fnt/open-sans/opensans-regular-webfont.svg#open_sansregular") format("svg"); font-weight: normal; font-style: normal; } @font-face { font-family: 'Open Sans Semibold'; src: url("../fnt/open-sans/opensans-semibold-webfont.eot"); src: url("../fnt/open-sans/opensans-semibold-webfont.eot?#iefix") format("embedded-opentype"), url("../fnt/open-sans/opensans-semibold-webfont.woff2") format("woff2"), url("../fnt/open-sans/opensans-semibold-webfont.woff") format("woff"), url("../fnt/open-sans/opensans-semibold-webfont.ttf") format("truetype"), url("../fnt/open-sans/opensans-semibold-webfont.svg#open_sanssemibold") format("svg"); font-weight: normal; font-style: normal; } @font-face { font-family: 'Roboto Bold'; src: url("../fnt/roboto/roboto-bold-webfont.eot"); src: url("../fnt/roboto/roboto-bold-webfont.eot?#iefix") format("embedded-opentype"), url("../fnt/roboto/roboto-bold-webfont.woff2") format("woff2"), url("../fnt/roboto/roboto-bold-webfont.woff") format("woff"), url("../fnt/roboto/roboto-bold-webfont.ttf") format("truetype"), url("../fnt/roboto/roboto-bold-webfont.svg#robotobold") format("svg"); font-weight: normal; font-style: normal; } @font-face { font-family: 'Roboto Regular'; src: url("../fnt/roboto/roboto-regular-webfont.eot"); src: url("../fnt/roboto/roboto-regular-webfont.eot?#iefix") format("embedded-opentype"), url("../fnt/roboto/roboto-regular-webfont.woff2") format("woff2"), url("../fnt/roboto/roboto-regular-webfont.woff") format("woff"), url("../fnt/roboto/roboto-regular-webfont.ttf") format("truetype"), url("../fnt/roboto/roboto-regular-webfont.svg#robotoregular") format("svg"); font-weight: normal; font-style: normal; } .main-app { background-image: url("../img/Background_Shrine.jpg"); background-size: cover; background-color: transparent; background-attachment: fixed; width: 100%; height: 100%; width: calc(100vw); height: calc(100vh); min-width: 100%; min-height: 100%; } .shrine-navbar { background-color: rgba(255, 255, 255, 0.62); border-color: transparent; font-family: "Nexa"; color: #5d5d5d; min-height: 60px; width: 100%; height: 4.8em; } .shrine-brand { float: left; padding: 22px 15px; font-size: 30px; line-height: 30px; height: 30px; } .shrine-brand strong { font-family: "Nexa Bold"; color: #2c5566; } .shrine-navbar .shrine-institution-logo { background-image: url("/static/logo.png"); background-size: contain; background-color: transparent; background-color: rgba(255, 255, 255, 0.1); background-repeat: no-repeat; background-position: right top; margin-right: 2%; margin-top: 5px; width: 4em; height: 4em; max-height: 4em; max-width: 4em; } .shrine-button { cursor: pointer !important; background-color: transparent; border: none; } .shrine-button span { position: relative; bottom: 5px; } .shrine-btn-default { margin-right: 6px; border: none; } .shrine-btn-on { padding: 12px 12px; border-radius: 0; font-family: "Roboto Bold" !important; color: #FFFFFF !important; background: linear-gradient(rgba(4, 141, 190, 0.8), rgba(2, 89, 120, 0.8)), url("../img/bckg_diagonal_lines_no_border.png") !important; } .shrine-btn-off { padding: 6px 12px !important; border-radius: 4px !important; font-family: "Open Sans Semibold" !important; background-color: #8896A4 !important; color: #FFFFFF !important; } .shrine-on, .shrine-on a { font-family: "Roboto Bold" !important; color: #FFFFFF !important; background: linear-gradient(#048DBE, #025978) !important; } .shrine-off { font-family: "Roboto Regular" !important; color: #2C5566 !important; background-color: #ECEEEE !important; } .shrine-button.disabled, .shrine-button[disabled] { cursor: default !important; opacity: 0.2 !important; } .shrine-copy-bold { font-family: "Nexa Bold"; color: #64818e; } .shrine-copy { font-family: "Nexa"; color: #000000; } .row { margin-right: 0; margin-left: 0; } td.error, span.error { color: red; } td.error a, td.error a:hover, span.error a, span.error a:hover { color: inherit; text-decoration: underline !important; cursor: pointer; } td.ok, span.ok { color: green; } .form-group span { font-family: "Open Sans Semibold"; color: #2c5566; } fieldset button { color: #2E5366; } fieldset button:hover, form a:hover { color: #008CBA; text-decoration: none; cursor: pointer; } form a { font-family: "Open Sans Regular"; color: #647d8d; text-decoration: none; } footer img { margin-left: 10px; margin-top: 2px; } footer { background-color: rgba(50, 62, 74, 0.48); position: fixed; bottom: 0; left: 0; width: 100%; height: 83px; min-height: 83px; max-width: 100%; } table { background-image: url("../img/bckg_diagonal_lines_no_border.png"); border: 1px solid #CCD8DF; } .table tr > td:first-child { width: 20%; min-width: 140px; } .table tr > td.thin-col { width: 6%; min-width: 35px; } .table-striped > tbody > tr:nth-of-type(odd) { background-color: #EFF6F9; } .table-striped > tbody > tr:nth-of-type(even) { background-color: #FFFFFF; } thead tr { border: 1px solid #CCD8DF; } td { border-right: 1px solid #CCD8DF; overflow: hidden; max-width: 450px; word-break: break-all; } thead tr td, thead tr td label, tfoot tr td span { font-family: "Open Sans Semibold"; color: #003153; } td a, td a:hover { text-decoration: none !important; cursor: pointer; font-family: "Open Sans Semibold"; color: #003153; } .shrine-panel { background-image: url("../img/bckg_diagonal_lines.png"); background-size: 100% 100%; padding-right: 20px; padding-left: 20px; padding-top: 30px; padding-bottom: 30px; } /*! * Start Bootstrap - SB Admin 2 Bootstrap Admin Theme (http://startbootstrap.com) * Code licensed under the Apache License v2.0. * For details, see http://www.apache.org/licenses/LICENSE-2.0. */ body { background-image: url("../img/Background_Shrine.jpg"); background-repeat: no-repeat; background-size: 100% 100%; } #wrapper { width: 100%; padding-bottom: 83px; /* clearance space for footer at bottom of long page */ } .login-wrapper { margin-top: 51px; margin-right: 20px; } #page-wrapper { background-color: transparent; margin-top: 54px; margin-right: 20px; } .navbar-top-links li { display: inline-block; } .navbar-top-links li:last-child { margin-right: 15px; } .navbar-top-links li a { padding: 15px; min-height: 50px; font-family: "Open Sans Semibold"; color: #2c5566; } .navbar-top-links .dropdown-menu li { font-family: "Open Sans Semibold"; color: #2c5566; display: block; } .navbar-top-links .dropdown-menu li:last-child { margin-right: 0; } .navbar-top-links .dropdown-menu li a { padding: 3px 20px; min-height: 0; } .navbar-top-links .dropdown-menu li a div { white-space: normal; } .navbar-top-links .dropdown-messages, .navbar-top-links .dropdown-tasks, .navbar-top-links .dropdown-alerts { width: 310px; min-width: 0; } .navbar-top-links .dropdown-messages { margin-left: 5px; } .navbar-top-links .dropdown-tasks { margin-left: -59px; } .navbar-top-links .dropdown-alerts { margin-left: -123px; } .navbar-top-links { right: 0; left: auto; } .sidebar .sidebar-nav.navbar-collapse { padding-right: 0; padding-left: 0; } .sidebar .sidebar-search { padding: 15px; } .sidebar ul li { border-bottom: 1px solid #e7e7e7; } .sidebar ul li a.active { background-color: #eee; } .sidebar .arrow { float: right; } .sidebar .fa.arrow:before { content: "\f104"; } .sidebar .active > a > .fa.arrow:before { content: "\f107"; } .sidebar .nav-second-level li, .sidebar .nav-third-level li { border-bottom: 0 !important; } .sidebar .nav-second-level li a { padding-left: 37px; } .sidebar .nav-third-level li a { padding-left: 52px; } @media (min-width: 768px) { .sidebar { z-index: 1; margin-top: 51px; } .navbar-top-links .dropdown-messages, .navbar-top-links .dropdown-tasks, .navbar-top-links .dropdown-alerts { margin-left: auto; } } .btn-outline { color: inherit; background-color: transparent; transition: all .5s; } .btn-primary.btn-outline { color: #428bca; } .btn-success.btn-outline { color: #5cb85c; } .btn-info.btn-outline { color: #5bc0de; } .btn-warning.btn-outline { color: #f0ad4e; } .btn-danger.btn-outline { color: #d9534f; } .btn-primary.btn-outline:hover, .btn-success.btn-outline:hover, .btn-info.btn-outline:hover, .btn-warning.btn-outline:hover, .btn-danger.btn-outline:hover { color: #fff; } .chat { margin: 0; padding: 0; list-style: none; } .chat li { margin-bottom: 10px; padding-bottom: 5px; border-bottom: 1px dotted #999; } .chat li.left .chat-body { margin-left: 60px; } .chat li.right .chat-body { margin-right: 60px; } .chat li .chat-body p { margin: 0; } .panel .slidedown .glyphicon, .chat .glyphicon { margin-right: 5px; } .chat-panel .panel-body { height: 350px; overflow-y: scroll; } .login-panel { margin-top: 25%; } .flot-chart { display: block; height: 400px; } .flot-chart-content { width: 100%; height: 100%; } .dataTables_wrapper { position: relative; clear: both; } table.dataTable thead .sorting, table.dataTable thead .sorting_asc, table.dataTable thead .sorting_desc, table.dataTable thead .sorting_asc_disabled, table.dataTable thead .sorting_desc_disabled { background: 0 0; } table.dataTable thead .sorting_asc:after { content: "\f0de"; float: right; font-family: fontawesome; } table.dataTable thead .sorting_desc:after { content: "\f0dd"; float: right; font-family: fontawesome; } table.dataTable thead .sorting:after { content: "\f0dc"; float: right; font-family: fontawesome; color: rgba(50, 50, 50, 0.5); } .btn-circle { width: 30px; height: 30px; padding: 6px 0; border-radius: 15px; text-align: center; font-size: 12px; line-height: 1.428571429; } .btn-circle.btn-lg { width: 50px; height: 50px; padding: 10px 16px; border-radius: 25px; font-size: 18px; line-height: 1.33; } .btn-circle.btn-xl { width: 70px; height: 70px; padding: 10px 16px; border-radius: 35px; font-size: 24px; line-height: 1.33; } .show-grid [class^=col-] { padding-top: 10px; padding-bottom: 10px; border: 1px solid #ddd; background-color: #eee !important; } .show-grid { margin: 15px 0; } .huge { font-size: 40px; } .panel-green { border-color: #5cb85c; } .panel-green .panel-heading { border-color: #5cb85c; color: #fff; background-color: #5cb85c; } .panel-green a { color: #5cb85c; } .panel-green a:hover { color: #3d8b3d; } .panel-red { border-color: #d9534f; } .panel-red .panel-heading { border-color: #d9534f; color: #fff; background-color: #d9534f; } .panel-red a { color: #d9534f; } .panel-red a:hover { color: #b52b27; } .panel-yellow { border-color: #f0ad4e; } .panel-yellow .panel-heading { border-color: #f0ad4e; color: #fff; background-color: #f0ad4e; } .panel-yellow a { color: #f0ad4e; } .panel-yellow a:hover { color: #df8a13; } .modal-content { border: none; } .shrine-modal { background-color: white; border: 1px solid #2c5566; font-family: "Open Sans Semibold"; color: #2e5366; padding: 15px; } .shrine-modal form div.col-sm-12 { border: 1px solid rgba(2, 89, 120, 0.8); } .shrine-modal input, .shrine-modal textarea { border-radius: 0px; border: 1px solid #2c5566; } .shrine-modal span { font-family: "Nexa Bold"; color: #2e5366; } .shrine-modal span:hover { font-family: "Nexa Bold"; color: #008CBA; } .shrine-modal button { background-color: white; border: none; font-family: "Nexa Bold"; color: #2e5366; } .shrine-modal button span { position: relative; bottom: 6px; } .shrine-modal button:hover, .btn-success { font-family: "Nexa Bold"; color: #008CBA; background-color: transparent; border: none; } .shrine-login { margin-top: 8%; margin-left: 1%; } .shrine-content { overflow: auto; } /*Fix for resizeable text area.*/ textarea { resize: none; } @media (min-width: 768px) { .shrine-content { padding: 0; } } .shrine-calendar-input { margin-right: 1px; max-width: 50%; } i.shrine-close { float: right; margin-top: -40px; margin-right: -40px; cursor: pointer; color: #fff; border: 2px solid #C8CED1; border-radius: 30px; background: #8896a4; font-size: 31px; font-weight: normal; display: inline-block; line-height: 0px; padding: 11px 3px; font-style: normal; } i.shrine-close:hover { background: #008cba; } .shrine-close:before { content: "×"; } .timeline { position: relative; padding: 20px 0 20px; list-style: none; } .timeline:before { content: " "; position: absolute; top: 0; bottom: 0; left: 50%; width: 3px; margin-left: -1.5px; background-color: #eeeeee; } .timeline > li { position: relative; margin-bottom: 20px; } .timeline > li:before, .timeline > li:after { content: " "; display: table; } .timeline > li:after { clear: both; } .timeline > li:before, .timeline > li:after { content: " "; display: table; } .timeline > li:after { clear: both; } .timeline > li > .timeline-panel { float: left; position: relative; width: 46%; padding: 20px; border: 1px solid #d4d4d4; border-radius: 2px; -webkit-box-shadow: 0 1px 6px rgba(0, 0, 0, 0.175); box-shadow: 0 1px 6px rgba(0, 0, 0, 0.175); } .timeline > li > .timeline-panel:before { content: " "; display: inline-block; position: absolute; top: 26px; right: -15px; border-top: 15px solid transparent; border-right: 0 solid #ccc; border-bottom: 15px solid transparent; border-left: 15px solid #ccc; } .timeline > li > .timeline-panel:after { content: " "; display: inline-block; position: absolute; top: 27px; right: -14px; border-top: 14px solid transparent; border-right: 0 solid #fff; border-bottom: 14px solid transparent; border-left: 14px solid #fff; } .timeline > li > .timeline-badge { z-index: 100; position: absolute; top: 16px; left: 50%; width: 50px; height: 50px; margin-left: -25px; border-radius: 50% 50% 50% 50%; text-align: center; font-size: 1.4em; line-height: 50px; color: #fff; background-color: #999999; } .timeline > li.timeline-inverted > .timeline-panel { float: right; } .timeline > li.timeline-inverted > .timeline-panel:before { right: auto; left: -15px; border-right-width: 15px; border-left-width: 0; } .timeline > li.timeline-inverted > .timeline-panel:after { right: auto; left: -14px; border-right-width: 14px; border-left-width: 0; } .timeline-badge.primary { background-color: #2e6da4 !important; } .timeline-badge.success { background-color: #3f903f !important; } .timeline-badge.warning { background-color: #f0ad4e !important; } .timeline-badge.danger { background-color: #d9534f !important; } .timeline-badge.info { background-color: #5bc0de !important; } .timeline-title { margin-top: 0; color: inherit; } .timeline-body > p, .timeline-body > ul { margin-bottom: 0; } .timeline-body > p + p { margin-top: 5px; } @media (max-width: 767px) { ul.timeline:before { left: 40px; } ul.timeline > li > .timeline-panel { width: calc(100% - 90px); width: -moz-calc(100% - 90px); width: -webkit-calc(100% - 90px); } ul.timeline > li > .timeline-badge { top: 16px; left: 15px; margin-left: 0; } ul.timeline > li > .timeline-panel { float: right; } ul.timeline > li > .timeline-panel:before { right: auto; left: -15px; border-right-width: 15px; border-left-width: 0; } ul.timeline > li > .timeline-panel:after { right: auto; left: -14px; border-right-width: 14px; border-left-width: 0; } } /* -- stats-graph styles -- */ .stats-minimized, .stats-graph-minimized, .stats-ontology-minimized { cursor: pointer; opacity: .7; } .stats-minimized:hover, .stats-graph-minimized:hover, .stats-ontology-minimized:hover { border: 1px solid white; opacity: 1; } .stats-graph-minimized { transform: translate(60%, -20%) scale(0.1, 0.25); } .stats-ontology-minimized { transform: translate(60%, 0%) scale(0.1, 0.25); } .stats-stage-base, .stats-graph-container, .stats-ontology-container { padding-bottom: 0px; padding-top: 0px; color: white; margin: 0 auto; width: 100%; } .stats-graph-container { border-bottom: none; /*border-left: 1px solid #125c8a;;*/ overflow: auto; padding-top: .6em; /*background: linear-gradient(#048DBE, #025978) !important;*/ } .stats-ontology-container { overflow: auto; height: 100vh; } .stats-graph-container .bar-bg { background-color: white; height: 30px; margin: 0 auto 10px auto; line-height: 30px; font-size: 16px; font-family: Roboto, sans-serif; font-weight: 100; color: white; padding: 0 0 0 10px; position: relative; } .stats-graph-container .bar-bg .user { position: absolute; z-index: 4; left: .2em; } .stats-graph-container .bar-bg .bar { opacity: .8; z-index: 1; background-color: #125c8a; border: 1px solid #1c8ed6; height: 30px; transition: 0.7s; width: 100%; display: block; animation: stats-bar-before 1 1.8s; position: absolute; top: 0; left: 0; z-index: 2; padding-right: 8px; text-align: right; } .stats-graph-container .bar-bg .bar:hover { background: rgba(0, 0, 0, 0.5); cursor: pointer; } @keyframes stats-bar-before { 0% { width: 0px; } 100% { width: 100%; } } .digest ul { top: 15rem; left: 5rem; float: left; clear: left; margin: .25em; padding: 0; } .digest ul::before { display: none; } .digest ul li { display: block; position: relative; float: left; clear: both; right: auto; padding-left: 1em; width: auto; text-align: center; color: white; animation: showterm .25s; } .digest ul li a { display: block; position: relative; float: left; z-index: 4; margin: .25em; padding: .25em; background: white; color: #C0C0C0; border: 2px solid #94a0b4; border-radius: .5rem; transition: all .75s; font-weight: 300; font-family: sans-serif; } .digest .expandable { position: absolute; z-index: 4; top: 0; left: -.5em; margin: .65em; padding: 0; width: .8em; height: .8em; text-align: center; line-height: .6em; font-size: 1em; background-color: white; color: #C0C0C0; border: 1px solid #94a0b4; border-radius: .25rem; } .digest > ul { position: relative; font-family: "Georgia"; } .digest > ul:before { left: .5em; } .digest > ul:after { display: none; } @keyframes showterm { 0% { top: -.5rem; opacity: 0; } 100% { top: 0rem; opacity: 1; } } ul:after { color: blue; border: solid gray 1px; border-radius: .1em; } ul > li > a { color: blue; background: white; } .digest li span.expandable:hover, .digest li span.expandable:hover + a, .digest li span.expandable:hover + a + ul li a, .digest li a:hover, .digest li a:hover + ul li a { background: rgba(50, 130, 136, 0) !important; color: #fff; cursor: pointer; z-index: 1000; text-decoration: none; } .digest li a:hover + ul li::after, .digest li a:hover + ul li::before, .digest li a:hover + ul::before, .digest li a:hover + ul ul::before { border-color: #99C794 !important; } /* color settings */ a { font-family: "Roboto", sans-serif; font-size: 14px; font-weight: 400; line-height: 1.5em; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; color: #000000; } .red { background: rgba(158, 56, 68, 0.5) !important; border: 1px solid #9e3844 !important; } .dark-red { background: rgba(236, 95, 103, 0.5) !important; border: 1px solid #EC5F67 !important; } .dark-blue { background: rgba(53, 105, 151, 0.75) !important; border: 1px solid #6699CC !important; } .blue { background: rgba(102, 153, 204, 0.25) !important; border: 1px solid #6699CC !important; } .purple { background: rgba(197, 148, 197, 0.5) !important; border: 1px solid #C594C0 !important; } .ont-hidden { margin: auto; position: fixed; top: 100px; left: 0; right: 0; width: 50%; max-width: 630px; min-width: 320px; opacity: 0; height: auto; z-index: 2000; visibility: hidden; -webkit-backface-visibility: hidden; -moz-backface-visibility: hidden; backface-visibility: hidden; } .ont-overlay { position: fixed; width: 100%; height: 100%; visibility: visible; top: 0; left: 0; z-index: 2000; opacity: 1; background: rgba(0, 0, 0, 0.9); -webkit-transition: all 0.3s; -moz-transition: all 0.3s; transition: all 0.1s; } button.ont-close { display: block; background: transparent; color: #c0c0c0; right: 2%; top: 2%; position: absolute; border-radius: 9px; font-size: 20px; cursor: pointer; border: none; } button.ont-close:hover { color: #fff; } .tooltip-toggle { cursor: pointer; position: relative; } .tooltip-toggle::before { border: 1px solid rgba(255, 255, 255, 0.8); position: absolute; top: -5rem; left: -3rem; background-color: rgba(0, 0, 0, 0.8); border-radius: 5px; color: #fff; content: attr(aria-label); padding: 1rem; text-transform: none; -webkit-transition: all 2s; transition: all 2s; width: 160px; } .tooltip-toggle::after { position: absolute; top: -.8rem; left: 5rem; border-left: 5px solid transparent; border-right: 5px solid transparent; border-top: 5px solid white; content: " "; font-size: 0; line-height: 0; margin-left: -5px; width: 0; } .tooltip-toggle::before, .tooltip-toggle::after { color: #efefef; font-family: monospace; font-size: 16px; opacity: 0; pointer-events: none; text-align: center; } .tooltip-toggle:focus::before, .tooltip-toggle:focus::after, .tooltip-toggle:hover::before, .tooltip-toggle:hover::after { opacity: 1; -webkit-transition: all 2s; transition: all 2s; } .topic-dropdown { z-index: 20000; margin: 4rem; left: 0%; color: rgba(0, 0, 0, 0.87); font-family: "Roboto", sans-serif; font-size: 14px; font-weight: 300; line-height: 1.5em; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; position: fixed; } .topic-dropdown div.title { text-transform: uppercase; font-weight: 500; font-size: 1.3em; color: #63BCF8; } .topic-dropdown .tdd-btn { + text-align: left; max-width: 1000px; outline: 0; /* takes care of blue outline */ display: -webkit-inline-box; display: -webkit-inline-flex; display: -ms-inline-flexbox; display: inline-flex; -webkit-box-align: center; -webkit-align-items: center; -ms-flex-align: center; align-items: center; -webkit-box-pack: justify; -webkit-justify-content: space-between; -ms-flex-pack: justify; justify-content: space-between; background: rgba(0, 0, 0, 0.75); border: 1px solid #63BCF8 !important; min-width: 400px; border: 0; border-radius: 4px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); box-sizing: border-box; padding: 16px 20px; color: #FFFFFF; font-size: 12px; font-weight: 300; letter-spacing: 1.2px; text-transform: uppercase; overflow: hidden; cursor: pointer; } .topic-dropdown .tdd-btn:hover { cursor: pointer; } .topic-dropdown .tdd-list { position: absolute; top: 100%; left: 0px; background: rgba(0, 0, 0, 0.75); border: 1px solid #63BCF8 !important; width: 100%; border-radius: 4px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); text-align: left; opacity: 0; visibility: hidden; -webkit-transition: 0.3s ease; transition: 0.3s ease; max-height: 75vh; overflow: auto; } .topic-dropdown .tdd-list a { display: block; border-bottom: 1px solid rgba(0, 0, 0, 0.05); padding: 16px 0; color: inherit; text-decoration: none; background: none !important; } .topic-dropdown .tdd-list:before { content: ''; position: absolute; top: -6px; left: 20px; width: 0; height: 0; box-shadow: 2px -2px 6px rgba(0, 0, 0, 0.05); border-top: 6px solid #63BCF8; border-right: 6px solid #63BCF8; border-bottom: 6px solid transparent; border-left: 6px solid transparent; -webkit-transform: rotate(-45deg); transform: rotate(-45deg); mix-blend-mode: multiple; } .topic-dropdown .tdd-list li { z-index: 100; position: relative; padding: 0 20px; color: white; } .topic-dropdown .tdd-list li.active { color: #5380F7; } .topic-dropdown .tdd-list li:first-child { border-radius: 4px 4px 0 0; } .topic-dropdown .tdd-list li:last-child { border-radius: 0 0 4px 4px; } .topic-dropdown .tdd-list li:last-child a { border-bottom: 0; } .topic-dropdown .tdd-list-open { -webkit-transform: translate(0, 20px); transform: translate(0, 20px); opacity: 1 !important; visibility: visible !important; } .dropdown-arrow { content: ''; opacity: .8; position: absolute; right: 10px; top: 43px; width: 0; height: 0; box-shadow: 2px -2px 6px rgba(0, 0, 0, 0.05); border-top: 4px solid #63BCF8; border-right: 4px solid #63BCF8; border-bottom: 4px solid transparent; border-left: 4px solid transparent; -webkit-transform: rotate(-45deg); transform: rotate(135deg); } diff --git a/apps/steward-app/src/main/js/app/assets/css/topic-dropdown.css b/apps/steward-app/src/main/js/app/assets/css/topic-dropdown.css index 139c6613e..d20bde020 100644 --- a/apps/steward-app/src/main/js/app/assets/css/topic-dropdown.css +++ b/apps/steward-app/src/main/js/app/assets/css/topic-dropdown.css @@ -1,106 +1,107 @@ .topic-dropdown { z-index: 20000; margin: 4rem; left: 0%; color: rgba(0, 0, 0, 0.87); font-family: "Roboto", sans-serif; font-size: 14px; font-weight: 300; line-height: 1.5em; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; position: fixed; } .topic-dropdown div.title { text-transform: uppercase; font-weight: 500; font-size: 1.3em; color: #63BCF8; } .topic-dropdown .tdd-btn { + text-align: left; max-width: 1000px; outline: 0; /* takes care of blue outline */ display: -webkit-inline-box; display: -webkit-inline-flex; display: -ms-inline-flexbox; display: inline-flex; -webkit-box-align: center; -webkit-align-items: center; -ms-flex-align: center; align-items: center; -webkit-box-pack: justify; -webkit-justify-content: space-between; -ms-flex-pack: justify; justify-content: space-between; background: rgba(0, 0, 0, 0.75); border: 1px solid #63BCF8 !important; min-width: 400px; border: 0; border-radius: 4px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); box-sizing: border-box; padding: 16px 20px; color: #FFFFFF; font-size: 12px; font-weight: 300; letter-spacing: 1.2px; text-transform: uppercase; overflow: hidden; cursor: pointer; } .topic-dropdown .tdd-btn:hover { cursor: pointer; } .topic-dropdown .tdd-list { position: absolute; top: 100%; left: 0px; background: rgba(0, 0, 0, 0.75); border: 1px solid #63BCF8 !important; width: 100%; border-radius: 4px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); text-align: left; opacity: 0; visibility: hidden; -webkit-transition: 0.3s ease; transition: 0.3s ease; max-height: 75vh; overflow: auto; } .topic-dropdown .tdd-list a { display: block; border-bottom: 1px solid rgba(0, 0, 0, 0.05); padding: 16px 0; color: inherit; text-decoration: none; background: none !important; } .topic-dropdown .tdd-list:before { content: ''; position: absolute; top: -6px; left: 20px; width: 0; height: 0; box-shadow: 2px -2px 6px rgba(0, 0, 0, 0.05); border-top: 6px solid #63BCF8; border-right: 6px solid #63BCF8; border-bottom: 6px solid transparent; border-left: 6px solid transparent; -webkit-transform: rotate(-45deg); transform: rotate(-45deg); mix-blend-mode: multiple; } .topic-dropdown .tdd-list li { z-index: 100; position: relative; padding: 0 20px; color: white; } .topic-dropdown .tdd-list li.active { color: #5380F7; } .topic-dropdown .tdd-list li:first-child { border-radius: 4px 4px 0 0; } .topic-dropdown .tdd-list li:last-child { border-radius: 0 0 4px 4px; } .topic-dropdown .tdd-list li:last-child a { border-bottom: 0; } .topic-dropdown .tdd-list-open { -webkit-transform: translate(0, 20px); transform: translate(0, 20px); opacity: 1 !important; visibility: visible !important; } diff --git a/apps/steward-app/src/main/js/app/sass/topic-dropdown.scss b/apps/steward-app/src/main/js/app/sass/topic-dropdown.scss index 3f3ece3dd..ec213bc71 100644 --- a/apps/steward-app/src/main/js/app/sass/topic-dropdown.scss +++ b/apps/steward-app/src/main/js/app/sass/topic-dropdown.scss @@ -1,129 +1,130 @@ .topic-dropdown { div.title{ text-transform: uppercase; font-weight: 500; font-size: 1.3em; color: #63BCF8; } z-index: 20000; margin: 4rem; left: 0%; color: rgba(0, 0, 0, 0.87); font-family: "Roboto", sans-serif; font-size: 14px; font-weight: 300; line-height: 1.5em; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; position: fixed; .tdd-btn { &:hover{ cursor: pointer; } + text-align: left; max-width: 1000px; outline: 0; /* takes care of blue outline */ display: -webkit-inline-box; display: -webkit-inline-flex; display: -ms-inline-flexbox; display: inline-flex; -webkit-box-align: center; -webkit-align-items: center; -ms-flex-align: center; align-items: center; -webkit-box-pack: justify; -webkit-justify-content: space-between; -ms-flex-pack: justify; justify-content: space-between; background: rgba(0,0,0, .75); border: 1px solid #63BCF8 !important; min-width: 400px; border: 0; border-radius: 4px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); box-sizing: border-box; padding: 16px 20px; color: #FFFFFF; font-size: 12px; font-weight: 300; letter-spacing: 1.2px; text-transform: uppercase; overflow: hidden; cursor: pointer; }; .tdd-list { position: absolute; top: 100%; left: 0px; background: rgba(0,0,0, .75); border: 1px solid #63BCF8 !important; width: 100%; border-radius: 4px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); text-align: left; opacity: 0; visibility: hidden; -webkit-transition: 0.3s ease; transition: 0.3s ease; max-height: 75vh; overflow: auto; a { display: block; border-bottom: 1px solid rgba(0, 0, 0, 0.05); padding: 16px 0; color: inherit; text-decoration: none; background: none !important; }; &:before { content: ''; position: absolute; top: -6px; left: 20px; width: 0; height: 0; box-shadow: 2px -2px 6px rgba(0, 0, 0, 0.05); border-top: 6px solid #63BCF8;; border-right: 6px solid #63BCF8;; border-bottom: 6px solid transparent; border-left: 6px solid transparent; -webkit-transform: rotate(-45deg); transform: rotate(-45deg); mix-blend-mode: multiple; }; li { z-index: 100; position: relative; padding: 0 20px; color: white; &.active { color: #5380F7; } &:first-child { border-radius: 4px 4px 0 0; } &:last-child { border-radius: 0 0 4px 4px; } &:last-child a { border-bottom: 0; } }; }; .tdd-list-open { -webkit-transform: translate(0, 20px); transform: translate(0, 20px); opacity: 1 !important; visibility: visible !important; }; } diff --git a/apps/steward-war/pom.xml b/apps/steward-war/pom.xml index f830ab70a..0deea83b0 100644 --- a/apps/steward-war/pom.xml +++ b/apps/steward-war/pom.xml @@ -1,43 +1,43 @@ shrine-base net.shrine - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT ../../pom.xml 4.0.0 steward Steward War war org.apache.maven.plugins maven-war-plugin steward net.shrine steward-app ${project.version} com.h2database h2 ${h2-version} test org.slf4j slf4j-log4j12 ${slf4j-version} runtime diff --git a/apps/war/pom.xml b/apps/war/pom.xml index a77e4f4e1..7b1d95702 100644 --- a/apps/war/pom.xml +++ b/apps/war/pom.xml @@ -1,58 +1,58 @@ 4.0.0 SHRINE War shrine-war war net.shrine shrine-base - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT ../../pom.xml net.shrine shrine-test-commons ${project.version} test-jar test net.shrine shrine-app ${project.version} net.shrine shrine-adapter-service ${project.version} javax.servlet javax.servlet-api provided com.sun.jersey jersey-servlet net.shrine shrine-adapter-service ${project.version} test-jar test shrine-cell src/main/scala src/test/scala net.alchim31.maven scala-maven-plugin diff --git a/commons/auth/pom.xml b/commons/auth/pom.xml index 8e99ff974..fdbcf1c2e 100644 --- a/commons/auth/pom.xml +++ b/commons/auth/pom.xml @@ -1,97 +1,97 @@ 4.0.0 SHRINE Auth* shrine-auth jar net.shrine shrine-base - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT ../../pom.xml net.shrine shrine-protocol ${project.version} com.typesafe.akka akka-actor_2.11 ${akka-version} org.json4s json4s-native_2.11 ${json4s-version} io.spray spray-client_2.11 ${spray-version} io.spray spray-routing_2.11 ${spray-version} io.spray spray-servlet_2.11 ${spray-version} io.spray spray-util_2.11 ${spray-version} io.spray spray-testkit_2.11 ${spray-version} test com.typesafe.akka akka-slf4j_2.11 ${akka-version} com.typesafe.akka akka-testkit_2.11 ${akka-version} test net.shrine shrine-client ${project.version} net.shrine shrine-test-commons ${project.version} test-jar test src/main/scala src/test/scala net.alchim31.maven scala-maven-plugin diff --git a/commons/client/pom.xml b/commons/client/pom.xml index 2889c0640..8615fc0dd 100644 --- a/commons/client/pom.xml +++ b/commons/client/pom.xml @@ -1,68 +1,68 @@ 4.0.0 SHRINE Client shrine-client jar net.shrine shrine-base - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT ../../pom.xml net.shrine shrine-test-commons ${project.version} test-jar test net.shrine shrine-protocol ${project.version} net.shrine shrine-crypto ${project.version} net.shrine shrine-crypto ${project.version} test-jar test net.shrine shrine-config ${project.version} com.sun.jersey jersey-client src/main/scala src/test/scala net.alchim31.maven scala-maven-plugin org.apache.maven.plugins maven-jar-plugin test-jar diff --git a/commons/config/pom.xml b/commons/config/pom.xml index b3c29e105..8fd33d9b2 100644 --- a/commons/config/pom.xml +++ b/commons/config/pom.xml @@ -1,56 +1,56 @@ 4.0.0 SHRINE Config shrine-config jar net.shrine shrine-base - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT ../../pom.xml net.shrine shrine-test-commons ${project.version} test-jar test net.sf.opencsv opencsv com.typesafe config net.shrine shrine-util ${project.version} src/main/scala src/test/scala net.alchim31.maven scala-maven-plugin org.apache.maven.plugins maven-jar-plugin test-jar diff --git a/commons/crypto/pom.xml b/commons/crypto/pom.xml index 0d9056097..664eca1ba 100644 --- a/commons/crypto/pom.xml +++ b/commons/crypto/pom.xml @@ -1,58 +1,58 @@ 4.0.0 SHRINE Crypto shrine-crypto jar net.shrine shrine-base - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT ../../pom.xml net.shrine shrine-test-commons ${project.version} test-jar test net.shrine shrine-protocol ${project.version} net.shrine shrine-config ${project.version} org.bouncycastle bcpkix-jdk15on ${bouncy-castle-version} src/main/scala src/test/scala net.alchim31.maven scala-maven-plugin org.apache.maven.plugins maven-jar-plugin test-jar diff --git a/commons/data-commons/pom.xml b/commons/data-commons/pom.xml index a360f3497..1f54021af 100644 --- a/commons/data-commons/pom.xml +++ b/commons/data-commons/pom.xml @@ -1,87 +1,87 @@ 4.0.0 SHRINE Data Access Classes shrine-data-commons jar net.shrine shrine-base - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT ../../pom.xml src/main/scala src/test/scala net.alchim31.maven scala-maven-plugin org.apache.maven.plugins maven-jar-plugin test-jar net.shrine shrine-test-commons ${project.version} test-jar test net.shrine shrine-config ${project.version} net.shrine shrine-util ${project.version} org.suecarter freeslick_2.11 ${freeslick-version} org.squeryl squeryl_${scala-major-version} org.scala-lang scalap org.scala-lang scalap ${scala-version} org.springframework spring-jdbc test com.h2database h2 test diff --git a/commons/email/pom.xml b/commons/email/pom.xml index 08311f125..71ea3196c 100644 --- a/commons/email/pom.xml +++ b/commons/email/pom.xml @@ -1,71 +1,71 @@ 4.0.0 SHRINE Email shrine-email jar net.shrine shrine-base - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT ../../pom.xml org.slf4j slf4j-simple 1.6.4 org.json4s json4s-native_2.11 ${json4s-version} me.lessis courier_2.11 0.1.3 net.shrine shrine-config ${project.version} net.shrine shrine-test-commons ${project.version} test-jar test org.jvnet.mock-javamail mock-javamail 1.9 test src/main/scala src/test/scala net.alchim31.maven scala-maven-plugin org.apache.maven.plugins maven-jar-plugin 2.6 test-jar diff --git a/commons/ont-support/pom.xml b/commons/ont-support/pom.xml index 7893a1d59..753523a89 100644 --- a/commons/ont-support/pom.xml +++ b/commons/ont-support/pom.xml @@ -1,63 +1,63 @@ 4.0.0 SHRINE Ontology Support shrine-ont-support jar net.shrine shrine-base - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT ../../pom.xml net.shrine shrine-test-commons ${project.version} test-jar test net.shrine shrine-protocol ${project.version} net.shrine shrine-util ${project.version} net.shrine shrine-crypto ${project.version} net.shrine shrine-crypto ${project.version} test-jar test net.shrine shrine-config ${project.version} net.shrine shrine-client ${project.version} src/main/scala src/test/scala net.alchim31.maven scala-maven-plugin diff --git a/commons/protocol-query/pom.xml b/commons/protocol-query/pom.xml index 9312329da..eece064a0 100644 --- a/commons/protocol-query/pom.xml +++ b/commons/protocol-query/pom.xml @@ -1,57 +1,57 @@ 4.0.0 SHRINE Query Protocol shrine-protocol-query jar net.shrine shrine-base - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT ../../pom.xml net.shrine shrine-test-commons ${project.version} test-jar test net.liftweb lift-json_${scala-major-version} org.scala-lang scalap org.scala-lang scalap ${scala-version} net.shrine shrine-util ${project.version} src/main/scala src/test/scala net.alchim31.maven scala-maven-plugin diff --git a/commons/protocol/pom.xml b/commons/protocol/pom.xml index 9473047da..ce30af28e 100644 --- a/commons/protocol/pom.xml +++ b/commons/protocol/pom.xml @@ -1,91 +1,91 @@ 4.0.0 SHRINE Protocol shrine-protocol jar net.shrine shrine-base - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT ../../pom.xml net.shrine shrine-protocol-query ${project.version} net.shrine shrine-util ${project.version} net.shrine shrine-util ${project.version} test-jar test net.shrine shrine-config ${project.version} net.shrine shrine-data-commons ${project.version} net.liftweb lift-json_${scala-major-version} org.easymock easymock test net.shrine shrine-test-commons ${project.version} test-jar test src/main/scala src/test/scala src/main/resources true shrine-versions.properties net.alchim31.maven scala-maven-plugin net.alchim31.maven scala-maven-plugin org.apache.maven.plugins maven-jar-plugin test-jar diff --git a/commons/test-commons/pom.xml b/commons/test-commons/pom.xml index c53f8636f..544f05b78 100644 --- a/commons/test-commons/pom.xml +++ b/commons/test-commons/pom.xml @@ -1,48 +1,48 @@ 4.0.0 SHRINE Test Commons shrine-test-commons jar net.shrine shrine-base - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT ../../pom.xml com.sun.jersey jersey-client test com.sun.jersey.jersey-test-framework jersey-test-framework-http ${jersey-version} test src/test/scala org.apache.maven.plugins maven-jar-plugin test-jar net.alchim31.maven scala-maven-plugin diff --git a/commons/util/pom.xml b/commons/util/pom.xml index afe8b689b..d814e28c6 100644 --- a/commons/util/pom.xml +++ b/commons/util/pom.xml @@ -1,102 +1,102 @@ 4.0.0 SHRINE Utility Code shrine-util jar net.shrine shrine-base - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT ../../pom.xml com.typesafe.slick slick_2.11 ${slick-version} io.spray spray-httpx_2.11 ${spray-version} org.slf4j slf4j-simple 1.6.4 net.liftweb lift-json_${scala-major-version} org.scala-lang scalap org.scala-lang scalap ${scala-version} log4j log4j net.shrine shrine-test-commons ${project.version} test-jar test org.json4s json4s-native_2.11 ${json4s-version} com.h2database h2 test src/main/scala src/test/scala src/main/resources true shrine-versions.properties net.alchim31.maven scala-maven-plugin org.apache.maven.plugins maven-jar-plugin 2.6 test-jar diff --git a/hms-support/hms-core/pom.xml b/hms-support/hms-core/pom.xml index dd4869ccd..d283ed4f2 100644 --- a/hms-support/hms-core/pom.xml +++ b/hms-support/hms-core/pom.xml @@ -1,62 +1,62 @@ net.shrine shrine-hms-support - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT 4.0.0 shrine-hms-core SHRINE HMS Support (Core Classes) net.shrine shrine-test-commons ${project.version} test-jar test net.shrine shrine-auth ${project.version} net.shrine shrine-protocol ${project.version} net.shrine shrine-client ${project.version} net.shrine shrine-crypto ${project.version} net.shrine shrine-crypto ${project.version} test-jar test org.easymock easymock test src/main/scala src/test/scala net.alchim31.maven scala-maven-plugin diff --git a/hms-support/pom.xml b/hms-support/pom.xml index bad150b17..e86a2419c 100644 --- a/hms-support/pom.xml +++ b/hms-support/pom.xml @@ -1,15 +1,15 @@ 4.0.0 SHRINE HMS Support shrine-hms-support pom net.shrine shrine-base - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT hms-core diff --git a/hub/broadcaster-aggregator/pom.xml b/hub/broadcaster-aggregator/pom.xml index aa71a4834..f5e10dfb0 100644 --- a/hub/broadcaster-aggregator/pom.xml +++ b/hub/broadcaster-aggregator/pom.xml @@ -1,161 +1,161 @@ 4.0.0 SHRINE Broadcaster Aggregator shrine-broadcaster-aggregator jar net.shrine shrine-base - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT ../../pom.xml src/main/scala src/test/scala org.codehaus.mojo tomcat-maven-plugin 1.0-beta-1 net.alchim31.maven scala-maven-plugin org.apache.maven.plugins maven-jar-plugin test-jar net.shrine shrine-adapter-client-api ${project.version} net.shrine shrine-client ${project.version} net.shrine shrine-protocol ${project.version} net.shrine shrine-data-commons ${project.version} mysql mysql-connector-java net.shrine shrine-util ${project.version} test-jar test net.shrine shrine-crypto ${project.version} test-jar test net.shrine shrine-test-commons ${project.version} test-jar test net.shrine shrine-data-commons ${project.version} test-jar test net.shrine shrine-config ${project.version} test-jar test com.sun.jersey.jersey-test-framework jersey-test-framework-http ${jersey-version} test com.sun.jersey.contribs jersey-simple-server ${jersey-version} test com.h2database h2 test org.easymock easymock test org.springframework spring-jdbc test org.codehaus.mojo findbugs-maven-plugin 2.3.1 Max org.codehaus.mojo cobertura-maven-plugin 2.3 org.apache.maven.plugins maven-checkstyle-plugin 2.5 org.apache.maven.plugins maven-pmd-plugin 2.4 1.6 diff --git a/hub/broadcaster-service/pom.xml b/hub/broadcaster-service/pom.xml index d3643ce92..83588c7bd 100644 --- a/hub/broadcaster-service/pom.xml +++ b/hub/broadcaster-service/pom.xml @@ -1,148 +1,148 @@ 4.0.0 SHRINE Broadcast Service shrine-broadcaster-service jar net.shrine shrine-base - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT ../../pom.xml src/main/scala src/test/scala net.alchim31.maven scala-maven-plugin com.sun.jersey jersey-server net.shrine shrine-broadcaster-aggregator ${project.version} net.shrine shrine-crypto ${project.version} net.shrine shrine-client ${project.version} net.shrine shrine-adapter-client-api ${project.version} net.shrine shrine-protocol ${project.version} net.shrine shrine-data-commons ${project.version} log4j log4j com.sun.jersey.jersey-test-framework jersey-test-framework-http ${jersey-version} test com.sun.jersey.contribs jersey-simple-server ${jersey-version} test com.h2database h2 test org.easymock easymock test org.springframework spring-jdbc test net.shrine shrine-test-commons ${project.version} test-jar test net.shrine shrine-data-commons ${project.version} test-jar test org.codehaus.mojo findbugs-maven-plugin 2.3.1 Max org.codehaus.mojo cobertura-maven-plugin 2.3 org.apache.maven.plugins maven-checkstyle-plugin 2.5 org.apache.maven.plugins maven-pmd-plugin 2.4 1.6 diff --git a/install/pom.xml b/install/pom.xml index 727033eaa..8b70c3002 100644 --- a/install/pom.xml +++ b/install/pom.xml @@ -1,34 +1,34 @@ 4.0.0 SHRINE Install Scripts shrine-install-scripts pom net.shrine shrine-base - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT shrine-install-scripts org.apache.maven.plugins maven-assembly-plugin src/main/assembly/assembly.xml make-assembly package single diff --git a/integration/pom.xml b/integration/pom.xml index e72a3cb0b..c6d77e69f 100644 --- a/integration/pom.xml +++ b/integration/pom.xml @@ -1,127 +1,127 @@ 4.0.0 SHRINE Integration Tests shrine-integration-tests net.shrine shrine-base - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT src/main/scala src/test/scala net.alchim31.maven scala-maven-plugin net.shrine shrine-crypto ${project.version} net.shrine shrine-crypto ${project.version} test-jar test net.shrine shrine-test-commons ${project.version} test-jar test net.shrine shrine-data-commons ${project.version} test-jar test net.shrine shrine-protocol ${project.version} test net.shrine shrine-adapter-service ${project.version} test net.shrine shrine-adapter-service ${project.version} test-jar test net.shrine shrine-adapter-client-api ${project.version} test net.shrine shrine-broadcaster-aggregator ${project.version} test net.shrine shrine-qep ${project.version} test net.shrine shrine-broadcaster-service ${project.version} test com.h2database h2 test org.slf4j slf4j-log4j12 test org.springframework spring-jdbc test com.sun.jersey.jersey-test-framework jersey-test-framework-http ${jersey-version} test com.sun.jersey.contribs jersey-simple-server ${jersey-version} test com.typesafe.slick slick-hikaricp_2.11 ${slick-version} test diff --git a/pom.xml b/pom.xml index bab19ae16..c4ae6d396 100644 --- a/pom.xml +++ b/pom.xml @@ -1,387 +1,387 @@ 4.0.0 SHRINE net.shrine shrine-base pom - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT UTF-8 4.2.5.RELEASE 2.6.2 2.11.8 2.11 4.12 1.7.18 1.2.17 1.19 2.2.6 3.2.2 0.9.6-RC4 1.2.1 1.4.191 3.4 5.1.38 2.3 3.0.1 0.9.5 1.3.3 2.3.14 2.4.0 3.3.0 3.1.1 3.1.1.1 1.55 apps/meta-app apps/meta-war apps/dashboard-app apps/dashboard-war apps/steward-app apps/steward-war apps/proxy apps/shrine-app apps/war qep/service hub/broadcaster-aggregator hub/broadcaster-service adapter/adapter-api adapter/adapter-service hms-support tools commons/util commons/email commons/auth commons/protocol-query commons/data-commons commons/protocol commons/crypto commons/client commons/config commons/ont-support commons/test-commons install integration shrine-webclient net.alchim31.maven scala-maven-plugin ${scala-maven-plugin-version} compile compile compile test-compile testCompile test-compile process-resources compile incremental true -XX:+AggressiveOpts -XX:CompileThreshold=500 -XX:+UseFastAccessorMethods -XX:+UseStringCache -XX:+OptimizeStringConcat -XX:+TieredCompilation -XX:+UseConcMarkSweepGC -XX:+DoEscapeAnalysis -server -Xms64m -Xmx1024m -XX:MaxPermSize=384m ${scala-version} -Xcheckinit -unchecked -deprecation -Xlint:adapted-args,inaccessible,infer-any,missing-interpolator,private-shadow,type-parameter-shadow,unsound-match 7 7 maven-compiler-plugin 7 7 org.codehaus.mojo buildnumber-maven-plugin 1.1 org.apache.maven.plugins maven-jar-plugin 2.4 org.apache.maven.plugins maven-war-plugin 2.1.1 org.codehaus.mojo buildnumber-maven-plugin validate create {0,date,yyyy-MM-dd HH:mm:ss} (not available) org.apache.maven.plugins maven-jar-plugin true ${buildNumber} ${scmBranch} ${timestamp} org.apache.maven.plugins maven-war-plugin true ${buildNumber} ${scmBranch} ${timestamp} org.apache.tomcat.maven tomcat7-maven-plugin 2.2 true true true true http://shrine-dev1.catalyst:6060/shrine/rest/happy scm:git:https://open.med.harvard.edu/stash/scm/shrine/shrine.git CBMI-Nexus https://repo.open.med.harvard.edu/nexus/content/groups/public/ com.typesafe config ${typesafe-config-version} log4j log4j ${log4j-version} org.springframework spring-jdbc ${spring.version} test com.h2database h2 ${h2-version} test org.easymock easymock ${easymock-version} test org.slf4j slf4j-log4j12 ${slf4j-version} test mysql mysql-connector-java ${mysql-version} net.sf.opencsv opencsv ${opencsv-version} net.liftweb lift-json_${scala-major-version} ${lift-version} com.sun.jersey jersey-server ${jersey-version} com.sun.jersey jersey-servlet ${jersey-version} com.sun.jersey jersey-client ${jersey-version} org.squeryl squeryl_${scala-major-version} ${squeryl-version} javax.servlet javax.servlet-api ${servlet-api-version} provided org.bouncycastle bcpkix-jdk15on ${bouncy-castle-version} org.scala-lang scala-library ${scala-version} junit junit ${junit-version} test org.scalatest scalatest_${scala-major-version} ${scalatest-version} test org.scala-lang scala-actors org.scala-lang scala-reflect org.scala-lang scala-actors ${scala-version} test org.scala-lang scala-reflect ${scala-version} nexus Nexus Repo https://repo.open.med.harvard.edu/nexus/content/repositories/snapshots false nexus Nexus Repo https://repo.open.med.harvard.edu/nexus/content/repositories/releases-internal diff --git a/qep/service/pom.xml b/qep/service/pom.xml index 07ac8a617..ebc101932 100644 --- a/qep/service/pom.xml +++ b/qep/service/pom.xml @@ -1,218 +1,218 @@ 4.0.0 SHRINE Service shrine-qep jar net.shrine shrine-base - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT ../../pom.xml src/main/scala src/test/scala net.alchim31.maven scala-maven-plugin false org.squeryl squeryl_${scala-major-version} org.scala-lang scalap org.scala-lang scalap ${scala-version} com.sun.jersey jersey-server com.sun.jersey jersey-client net.shrine shrine-auth ${project.version} net.shrine shrine-broadcaster-aggregator ${project.version} net.shrine shrine-crypto ${project.version} net.shrine shrine-client ${project.version} net.shrine shrine-adapter-client-api ${project.version} net.shrine shrine-protocol ${project.version} net.shrine shrine-data-commons ${project.version} net.shrine shrine-hms-core ${project.version} com.typesafe.slick slick_2.11 ${slick-version} org.suecarter freeslick_2.11 ${freeslick-version} org.slf4j slf4j-log4j12 ${slf4j-version} mysql mysql-connector-java log4j log4j com.sun.jersey.jersey-test-framework jersey-test-framework-http ${jersey-version} test com.sun.jersey.contribs jersey-simple-server ${jersey-version} test com.h2database h2 test org.easymock easymock test org.springframework spring-jdbc test net.shrine shrine-test-commons ${project.version} test-jar test net.shrine shrine-data-commons ${project.version} test-jar test net.shrine shrine-config ${project.version} test-jar test net.shrine shrine-util ${project.version} test-jar test org.codehaus.mojo findbugs-maven-plugin 2.3.1 Max org.codehaus.mojo cobertura-maven-plugin 2.3 org.apache.maven.plugins maven-checkstyle-plugin 2.5 org.apache.maven.plugins maven-pmd-plugin 2.4 1.6 diff --git a/shrine-webclient/pom.xml b/shrine-webclient/pom.xml index 704bd1aca..7dd09b691 100644 --- a/shrine-webclient/pom.xml +++ b/shrine-webclient/pom.xml @@ -1,41 +1,41 @@ 4.0.0 SHRINE Web Client shrine-webclient pom net.shrine shrine-base - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT 1.5 2.4 maven-assembly-plugin ${assembly-plugin-version} assembly package single src/main/assembly/assembly.xml diff --git a/tools/adapter-queries-to-qep/pom.xml b/tools/adapter-queries-to-qep/pom.xml index af918b68e..cbbb2e728 100644 --- a/tools/adapter-queries-to-qep/pom.xml +++ b/tools/adapter-queries-to-qep/pom.xml @@ -1,57 +1,57 @@ 4.0.0 SHRINE Copy Adapter Queries to QEP adapter-queries-to-qep jar net.shrine shrine-tools - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT net.shrine shrine-data-commons ${project.version} com.typesafe config net.shrine shrine-adapter-service ${project.version} net.shrine shrine-qep ${project.version} org.slf4j slf4j-log4j12 ${slf4j-version} runtime src/main/scala src/test/scala net.alchim31.maven scala-maven-plugin maven-assembly-plugin adapter-queries-to-qep-${project.version} false diff --git a/tools/batch-querier/pom.xml b/tools/batch-querier/pom.xml index 620a485be..76f575f36 100644 --- a/tools/batch-querier/pom.xml +++ b/tools/batch-querier/pom.xml @@ -1,86 +1,86 @@ 4.0.0 SHRINE Query Automation Utility shrine-batch-querier-utility jar net.shrine shrine-tools - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT net.shrine shrine-crypto ${project.version} net.shrine shrine-client ${project.version} net.shrine shrine-utility-commons ${project.version} net.shrine shrine-utility-commons ${project.version} test-jar test net.shrine shrine-test-commons ${project.version} test-jar test org.rogach scallop_${scala-major-version} ${scallop-version} net.sf.opencsv opencsv com.typesafe config src/main/scala src/test/scala net.alchim31.maven scala-maven-plugin maven-assembly-plugin shrine-batch-querier-${project.version} false diff --git a/tools/mapping-automation/pom.xml b/tools/mapping-automation/pom.xml index 3b1f89895..75d96bedd 100644 --- a/tools/mapping-automation/pom.xml +++ b/tools/mapping-automation/pom.xml @@ -1,85 +1,85 @@ 4.0.0 SHRINE Term Mapping Automation Utilities shrine-mapping-automation jar net.shrine shrine-tools - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT net.shrine shrine-test-commons ${project.version} test-jar test net.shrine shrine-config ${project.version} net.shrine shrine-ont-support ${project.version} net.shrine shrine-utility-commons ${project.version} net.shrine shrine-utility-commons ${project.version} test-jar test org.rogach scallop_${scala-major-version} ${scallop-version} net.sf.opencsv opencsv com.typesafe config src/main/scala src/test/scala net.alchim31.maven scala-maven-plugin maven-assembly-plugin shrine-mapping-tools-${project.version} false diff --git a/tools/monitor/pom.xml b/tools/monitor/pom.xml index 7ac7e996d..acab5d41f 100644 --- a/tools/monitor/pom.xml +++ b/tools/monitor/pom.xml @@ -1,90 +1,90 @@ 4.0.0 SHRINE Monitor Utilities shrine-monitor-utilities jar net.shrine shrine-tools - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT ant ant-javamail 1.6.5 net.shrine shrine-broadcaster-aggregator ${project.version} net.shrine shrine-config ${project.version} org.codehaus.groovy.modules.http-builder http-builder 0.5.1 org.codehaus.groovy groovy org.codehaus.gmaven.runtime gmaven-runtime-1.7 1.3 mysql mysql-connector-java com.h2database h2 test net.sf.opencsv opencsv org.apache.commons commons-email 1.2 org.codehaus.gmaven gmaven-plugin maven-assembly-plugin shrine-monitor-${project.version}.zip false diff --git a/tools/pom.xml b/tools/pom.xml index 314bf825f..1e882a8fa 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -1,80 +1,80 @@ 4.0.0 SHRINE Tools shrine-tools pom net.shrine shrine-base - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT 1.5 2.4 monitor utility-commons scanner batch-querier adapter-queries-to-qep mapping-automation net.shrine shrine-protocol ${project.version} org.codehaus.gmaven gmaven-plugin ${gmaven-plugin-version} compile testCompile true true true maven-assembly-plugin ${assembly-plugin-version} assembly package single src/main/assembly/assembly.xml diff --git a/tools/scanner/pom.xml b/tools/scanner/pom.xml index f8a85bfe8..5ef311304 100644 --- a/tools/scanner/pom.xml +++ b/tools/scanner/pom.xml @@ -1,103 +1,103 @@ 4.0.0 SHRINE Scanner Utility shrine-scanner-utility jar net.shrine shrine-tools - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT net.shrine shrine-test-commons ${project.version} test-jar test net.shrine shrine-ont-support ${project.version} net.shrine shrine-hms-core ${project.version} net.shrine shrine-config ${project.version} net.shrine shrine-crypto ${project.version} net.shrine shrine-utility-commons ${project.version} net.shrine shrine-utility-commons ${project.version} test-jar test org.rogach scallop_${scala-major-version} ${scallop-version} net.sf.opencsv opencsv com.typesafe config net.shrine shrine-broadcaster-aggregator ${project.version} src/main/scala src/test/scala net.alchim31.maven scala-maven-plugin maven-assembly-plugin shrine-scanner-${project.version} false diff --git a/tools/utility-commons/pom.xml b/tools/utility-commons/pom.xml index 9728cfddd..33a247b8c 100644 --- a/tools/utility-commons/pom.xml +++ b/tools/utility-commons/pom.xml @@ -1,64 +1,64 @@ 4.0.0 SHRINE Common Classes for Utilities shrine-utility-commons jar net.shrine shrine-tools - 1.22.2.4-SNAPSHOT + 1.22.2.5-SNAPSHOT net.shrine shrine-test-commons ${project.version} test-jar test org.rogach scallop_${scala-major-version} ${scallop-version} net.sf.opencsv opencsv com.typesafe config src/main/scala src/test/scala net.alchim31.maven scala-maven-plugin org.apache.maven.plugins maven-jar-plugin test-jar