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
maven-compiler-plugin
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