diff --git a/apps/dashboard-app/src/test/resources/shrine.conf b/apps/dashboard-app/src/test/resources/shrine.conf
index ef23c03a9..92a96beb0 100644
--- a/apps/dashboard-app/src/test/resources/shrine.conf
+++ b/apps/dashboard-app/src/test/resources/shrine.conf
@@ -1,57 +1,56 @@
shrine {
authenticate {
usersource {
//Bogus security for testing
type = "ConfigUserSource" //Must be ConfigUserSource (for isolated testing) or PmUserSource (for everything else)
researcher {
username = "ben"
password = "kapow"
}
steward {
username = "dave"
password = "kablam"
}
qep{
username = "qep"
password = "trustme"
}
admin{
username = "keith"
password = "shh!"
}
}
}
dashboard {
happyBaseUrl = "classpath://resources/testhappy"
statusBaseUrl = "classpath://resources/teststatus"
}
keystore {
file = "shrine.keystore"
password = "justatestpassword"
privateKeyAlias = "shrine-test"
keyStoreType = "JKS"
caCertAliases = [shrine-test-ca]
}
queryEntryPoint {
broadcasterServiceEndpoint = {
url = "https://localhost:8080/shrine/test"
}
- trustModelIsHub = true
}
problem {
database {
dataSourceFrom = "testDataSource"
slickProfileClassName = "slick.driver.H2Driver$"
createTestValuesOnStart = true
createTablesOnStart = true
// For testing without JNDI
testDataSource {
//typical test settings for unit tests
driverClassName = "org.h2.Driver"
url = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"
}
}
}
}
\ No newline at end of file
diff --git a/apps/shrine-app/src/test/resources/shrine.conf b/apps/shrine-app/src/test/resources/shrine.conf
index dd08457e6..95dc79d16 100644
--- a/apps/shrine-app/src/test/resources/shrine.conf
+++ b/apps/shrine-app/src/test/resources/shrine.conf
@@ -1,95 +1,92 @@
shrine {
problem {
problemHandler = "net.shrine.problem.NoOpProblemHandler$"
}
ontEndpoint {
url = "http://services.i2b2.org/i2b2/rest/OntologyService"
acceptAllCerts = true
timeout {
seconds = 1
}
}
keystore {
file = "shrine.keystore"
password = "justatestpassword"
keyStoreType = "JKS"
- trustModelIsHub = true
caCertAliases = [shrine-test-ca]
isHub = false
verifyTimeout = ".5 seconds"
}
queryEntryPoint {
broadcasterServiceEndpoint {
url = "https://shrine-dev1.catalyst:6443/shrine/testing"
}
audit {
collectQepAudit = false
database {
slickProfileClassName = "slick.driver.H2Driver$"
createTablesOnStart = true //for testing with H2 in memory, when not running unit tests. Set to false normally
dataSourceFrom = "testDataSource" //Can be JNDI or testDataSource . Use testDataSource for tests, JNDI everywhere else
testDataSource {
driverClassName = "org.h2.Driver"
url = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1" //H2 embedded in-memory for unit tests ;TRACE_LEVEL_SYSTEM_OUT=2 for H2's trace
}
}
}
- trustModelIsHub = true
-
authenticationType = "pm" //can be none, pm, or ecommons
authorizationType = "shrine-steward" //can be none, shrine-steward, or hms-steward
shrineSteward {
qepUserName = "qep"
qepPassword = "trustme"
stewardBaseUrl = "https://localhost:6443"
}
}
adapter {
create = true
audit {
collectQepAudit = false
database {
slickProfileClassName = "slick.driver.H2Driver$"
createTablesOnStart = true //for testing with H2 in memory, when not running unit tests. Set to false normally
dataSourceFrom = "testDataSource" //Can be JNDI or testDataSource . Use testDataSource for tests, JNDI everywhere else
testDataSource {
driverClassName = "org.h2.Driver"
url = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1" //H2 embedded in-memory for unit tests ;TRACE_LEVEL_SYSTEM_OUT=2 for H2's trace
}
}
}
}
squerylDataSource {
database {
dataSourceFrom = "testDataSource" //Can be JNDI or testDataSource . Use testDataSource for tests, JNDI everywhere else
testDataSource {
driverClassName = "org.h2.Driver"
url = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1" //H2 embedded in-memory for unit tests ;TRACE_LEVEL_SYSTEM_OUT=2 for H2's trace
}
}
}
// squerylDataSource {
// database {
// dataSourceFrom = "testDataSource" //Can be JNDI or testDataSource . Use testDataSource for tests, JNDI everywhere else
//
// testDataSource {
// driverClassName = "org.h2.Driver"
// url = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1" //H2 embedded in-memory for unit tests ;TRACE_LEVEL_SYSTEM_OUT=2 for H2's trace
// }
// }
// }
}
\ No newline at end of file
diff --git a/apps/steward-app/src/test/resources/test.conf b/apps/steward-app/src/test/resources/test.conf
index dd8ec7ab8..407ec61fc 100644
--- a/apps/steward-app/src/test/resources/test.conf
+++ b/apps/steward-app/src/test/resources/test.conf
@@ -1,102 +1,101 @@
shrine {
pmEndpoint {
url = "http://shrine-dev2.catalyst/i2b2/services/PMService/getServices"
}
ontEndpoint {
url = "http://shrine-dev2.catalyst/i2b2/rest/OntologyService/"
}
hiveCredentials {
domain = "i2b2demo"
username = "demo"
password = "demouser"
crcProjectId = "Demo"
ontProjectId = "SHRINE"
}
breakdownResultOutputTypes {
PATIENT_AGE_COUNT_XML {
description = "Age patient breakdown"
}
PATIENT_RACE_COUNT_XML {
description = "Race patient breakdown"
}
PATIENT_VITALSTATUS_COUNT_XML {
description = "Vital Status patient breakdown"
}
PATIENT_GENDER_COUNT_XML {
description = "Gender patient breakdown"
}
}
queryEntryPoint {
authenticationType = "pm"
authorizationType = "none"
broadcasterServiceEndpoint {
url = "https://shrine-dev1.catalyst:6443/shrine/rest/broadcaster/broadcast"
}
shrineSteward {
qepUserName = "qep"
qepPassword = "trustme"
stewardBaseUrl = "https://shrine-dev2.catalyst:6443"
}
includeAggregateResults = false
maxQueryWaitTime {
minutes = 5
}
attachSigningCert = true
}
adapter {
crcEndpoint {
url = "http://shrine-dev2.catalyst/i2b2/services/QueryToolService/"
}
setSizeObfuscation = true
adapterLockoutAttemptsThreshold = 1000
adapterMappingsFileName = "AdapterMappings.xml"
maxSignatureAge {
minutes = 5
}
immediatelyRunIncomingQueries = true
}
networkStatusQuery = "\\\\SHRINE\\SHRINE\\Demographics\\Gender\\Male\\"
humanReadableNodeName = "shrine-dev2"
shrineDatabaseType = "mysql"
keystore {
file = "/opt/shrine/shrine.keystore"
password = "changeit"
privateKeyAlias = "shrine-dev2.catalyst"
keyStoreType = "JKS"
- trustModelIsHub = true
isHub = false
caCertAliases = [
"shrine-dev-ca"
]
}
problem {
database {
createTablesOnStart = false
dataSourceFrom = "JNDI"
jndiDataSourceName = "java:comp/env/jdbc/problemDB"
slickProfileClassName = "slick.driver.MySQLDriver$"
}
}
authenticate {
usersource {
domain = "i2b2demo"
}
}
steward {
createTopicsMode = "Pending"
database {
dataSourceFrom = "JNDI"
jndiDataSourceName = "java:comp/env/jdbc/stewardDB"
slickProfileClassName = "slick.driver.MySQLDriver$"
}
emailDataSteward {
sendAuditEmails = false
}
}
}
akka {
loglevel = "INFO"
loggers = [
"akka.event.slf4j.Slf4jLogger"
]
}
diff --git a/commons/auth/pom.xml b/commons/auth/pom.xml
index 0c95f05a6..5140856fd 100644
--- a/commons/auth/pom.xml
+++ b/commons/auth/pom.xml
@@ -1,97 +1,90 @@
4.0.0
SHRINE Auth*
shrine-auth
jar
net.shrine
shrine-base
1.23.3.1-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/crypto/pom.xml b/commons/crypto/pom.xml
index 043a1c2ee..10f4eec0c 100644
--- a/commons/crypto/pom.xml
+++ b/commons/crypto/pom.xml
@@ -1,58 +1,71 @@
4.0.0
SHRINE Crypto
shrine-crypto
jar
net.shrine
shrine-base
1.23.3.1-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
+
+
+
+
diff --git a/commons/crypto/src/main/resources/reference.conf b/commons/crypto/src/main/resources/reference.conf
new file mode 100644
index 000000000..88c11b9d5
--- /dev/null
+++ b/commons/crypto/src/main/resources/reference.conf
@@ -0,0 +1,5 @@
+shrine {
+ queryEntryPoint {
+ trustModelIsHub = true //false for P2P networks
+ }
+}
\ No newline at end of file
diff --git a/commons/crypto/src/main/scala/net/shrine/crypto/KeyStoreDescriptorParser.scala b/commons/crypto/src/main/scala/net/shrine/crypto/KeyStoreDescriptorParser.scala
index fe9297955..95ead8a3f 100644
--- a/commons/crypto/src/main/scala/net/shrine/crypto/KeyStoreDescriptorParser.scala
+++ b/commons/crypto/src/main/scala/net/shrine/crypto/KeyStoreDescriptorParser.scala
@@ -1,130 +1,129 @@
package net.shrine.crypto
import java.net.URL
import com.typesafe.config.{Config, ConfigValue, ConfigValueType}
import net.shrine.config.ConfigExtensions
import net.shrine.log.Loggable
import net.shrine.util.{PeerToPeerModel, SingleHubModel, TrustModel}
import scala.collection.JavaConverters._
/**
* @author clint
* @since Dec 9, 2013
*/
object KeyStoreDescriptorParser extends Loggable {
object Keys {
val file = "file"
val password = "password"
val privateKeyAlias = "privateKeyAlias"
val keyStoreType = "keyStoreType"
val caCertAliases = "caCertAliases"
val trustModel = "trustModelIsHub"
val isHub = "create"
val qepEndpoint = "broadcasterServiceEndpoint"
val url = "url"
val downStreamNodes = "downstreamNodes"
val aliasMap = "aliasMap"
}
def apply(keyStoreConfig: Config, hubConfig: Config, qepConfig: Config): KeyStoreDescriptor = {
import Keys._
import scala.collection.JavaConversions._
def getTrustModel: TrustModel = {
- val hasModel = qepConfig.hasPath(trustModel)
- if (hasModel && !qepConfig.getBoolean(trustModel))
+
+ println(qepConfig)
+
+ if (!qepConfig.getBoolean(trustModel))
PeerToPeerModel
- else if (hasModel && hubConfig.hasPath(isHub))
+ else if (hubConfig.hasPath(isHub))
SingleHubModel(hubConfig.getBoolean(isHub))
- else if (hasModel) {
+ else {
warn(s"Did not specify whether this is the hub or a downStreamNode, assuming it ${if (hubConfig.isEmpty) "isn't" else "is"} because the hub config is ${if (hubConfig.isEmpty) "empty" else "defined"}")
SingleHubModel(!hubConfig.isEmpty)
- } else {
- info("No Trust Model specified for this network configuration, assuming that a Peer configuration is being used")
- PeerToPeerModel
}
}
val tm = getTrustModel
def parseUrl(url: String): String = new URL(url).getHost
def parsePort(url:String): String = {
val jURL = new URL(url)
if (jURL.getPort == -1)
jURL.getDefaultPort.toString
else
jURL.getPort.toString
}
def getRemoteSites: Seq[RemoteSiteDescriptor] = {
tm match {
case PeerToPeerModel => parseAliasMap
case SingleHubModel(true) => parseRemoteSitesForHub
case SingleHubModel(false) => parseRemoteSiteFromQep
}
}
def getCaCertAliases: Seq[String] = {
def isString(cv: ConfigValue) = cv.valueType == ConfigValueType.STRING
keyStoreConfig.getOption(caCertAliases,_.getList).fold(Seq.empty[ConfigValue])(list => list.asScala).collect{ case cv if isString(cv) => cvToString(cv) }
}
def parseRemoteSitesForHub: Seq[RemoteSiteDescriptor] = {
val downStreamAliases = hubConfig.getConfig(downStreamNodes).entrySet
downStreamAliases.map(entry => {
val url = cvToString(entry.getValue)
RemoteSiteDescriptor(entry.getKey, None, parseUrl(url), parsePort(url))}).toList
}
def parseRemoteSiteFromQep: Seq[RemoteSiteDescriptor] = {
val aliases = getCaCertAliases
assert(aliases.nonEmpty, "There has to be at least one caCertAlias")
val qepUrl = qepConfig.getString(s"$qepEndpoint.$url")
RemoteSiteDescriptor("Hub", Some(aliases.head), parseUrl(qepUrl), parsePort(qepUrl)) +: Nil
}
def parseAliasMap: Seq[RemoteSiteDescriptor] = {
val aliases = keyStoreConfig.getConfig(aliasMap).entrySet()
val downStreamAliases = hubConfig.getConfig(downStreamNodes).entrySet()
assert(aliases.size() == downStreamAliases.size(), "The aliasMap has to match one-to-one with the Hub's downstreamNodes")
(aliases ++ downStreamAliases).foreach(entry => assert(entry.getValue.valueType() == ConfigValueType.STRING))
assert(aliases.size == aliases.map(_.getKey).intersect(downStreamAliases.map(_.getKey)).size)
aliases.map(siteAlias => {
val url = cvToString(downStreamAliases.find(_.getKey == siteAlias.getKey).get.getValue)
RemoteSiteDescriptor(siteAlias.getKey, Some(cvToString(siteAlias.getValue)), parseUrl(url), parsePort(url))}).toSeq
}
def getKeyStoreType: KeyStoreType = {
val typeOption = keyStoreConfig.getOption(keyStoreType,_.getString)
typeOption.flatMap(KeyStoreType.valueOf).getOrElse {
info(s"Unknown keystore type '${typeOption.getOrElse("")}', allowed types are ${KeyStoreType.JKS.name} and ${KeyStoreType.PKCS12.name}")
KeyStoreType.Default
}
}
def cvToString(cv: ConfigValue): String = {
cv.unwrapped.toString
}
KeyStoreDescriptor(
keyStoreConfig.getString(file),
keyStoreConfig.getString(password),
keyStoreConfig.getOption(privateKeyAlias, _.getString),
getCaCertAliases,
tm,
getRemoteSites,
getKeyStoreType
)
}
}
case class RemoteSiteDescriptor(siteAlias: String, keyStoreAlias: Option[String], url: String, port: String)
\ No newline at end of file
diff --git a/commons/crypto/src/test/scala/net/shrine/crypto/KeyStoreDescriptorParserTest.scala b/commons/crypto/src/test/scala/net/shrine/crypto/KeyStoreDescriptorParserTest.scala
index 0758f67e1..4d057bc1a 100644
--- a/commons/crypto/src/test/scala/net/shrine/crypto/KeyStoreDescriptorParserTest.scala
+++ b/commons/crypto/src/test/scala/net/shrine/crypto/KeyStoreDescriptorParserTest.scala
@@ -1,203 +1,209 @@
package net.shrine.crypto
import com.typesafe.config.ConfigFactory
import net.shrine.util.{PeerToPeerModel, ShouldMatchersForJUnit, SingleHubModel}
import org.junit.Test
/**
* @author clint
* @since Dec 11, 2013
*/
final class KeyStoreDescriptorParserTest extends ShouldMatchersForJUnit {
@Test
def testApply {
//All fields, JKS
{
val descriptor = KeyStoreDescriptorParser(
ConfigFactory.parseString("""
file="foo"
password="bar"
privateKeyAlias="baz"
keyStoreType="jks"
caCertAliases = [foo, bar]
"""),
ConfigFactory.parseString(
"""
|downstreamNodes {
| site1 = "https://localhost:8080/shrine/"
|}
""".stripMargin),
ConfigFactory.parseString(
"""
|trustModelIsHub = true
""".stripMargin)
)
descriptor.file should be("foo")
descriptor.password should be("bar")
descriptor.privateKeyAlias should be(Some("baz"))
descriptor.keyStoreType should be(KeyStoreType.JKS)
descriptor.caCertAliases.toSet should be(Set("foo", "bar"))
descriptor.trustModel should be(SingleHubModel(true))
descriptor.remoteSiteDescriptors should be(Seq(RemoteSiteDescriptor("site1", None, "localhost", "8080")))
}
//All fields, PKCS12
{
val descriptor = KeyStoreDescriptorParser(
ConfigFactory.parseString("""
file="foo"
password="bar"
privateKeyAlias="baz"
keyStoreType="pkcs12"
caCertAliases = [carra ca]
"""),
ConfigFactory.parseString(
"""
|create = false
""".stripMargin
),
ConfigFactory.parseString(
"""
|broadcasterServiceEndpoint {
| url = "https://localhost:8080/shrine/"
|}
|trustModelIsHub = true
""".stripMargin)
)
descriptor.file should be("foo")
descriptor.password should be("bar")
descriptor.privateKeyAlias should be(Some("baz"))
descriptor.keyStoreType should be(KeyStoreType.PKCS12)
descriptor.trustModel should be(SingleHubModel(false))
descriptor.remoteSiteDescriptors should be(Seq(RemoteSiteDescriptor("Hub", Some("carra ca"), "localhost", "8080")))
}
//no keystore type
{
val descriptor = KeyStoreDescriptorParser(
ConfigFactory.parseString("""
file="foo"
password="bar"
privateKeyAlias="baz"
caCertAliases = [carra ca]
"""),
ConfigFactory.parseString(
"""
|create = true
|downstreamNodes = {
| site1 = "https://someRemoteSite:7777/shrine/test"
| site2 = "https://someOtherSite:8888/shrine/test"
|}
""".stripMargin),
ConfigFactory.parseString(
"""
|trustModelIsHub = true
""".stripMargin)
)
descriptor.file should be("foo")
descriptor.password should be("bar")
descriptor.privateKeyAlias should be(Some("baz"))
descriptor.keyStoreType should be(KeyStoreType.Default)
descriptor.trustModel should be (SingleHubModel(true))
descriptor.remoteSiteDescriptors should contain theSameElementsAs Seq(
RemoteSiteDescriptor("site1", None, "someRemoteSite", "7777"),
RemoteSiteDescriptor("site2", None, "someOtherSite", "8888"))
}
//no private key alias
{
val descriptor = KeyStoreDescriptorParser(
ConfigFactory.parseString("""
file="foo"
password="bar"
keyStoreType="jks"
aliasMap = {
site1 = "node1"
}
"""),
ConfigFactory.parseString(
"""
|downstreamNodes = {
| site1 = "https://somePeerSite:9999/shrine/blah"
|}
""".stripMargin),
ConfigFactory.parseString(
"""
|trustModelIsHub = false
""".stripMargin
)
)
descriptor.file should be("foo")
descriptor.password should be("bar")
descriptor.privateKeyAlias should be(None)
descriptor.trustModel should be(PeerToPeerModel)
descriptor.keyStoreType should be(KeyStoreType.JKS)
descriptor.remoteSiteDescriptors should be(Seq(RemoteSiteDescriptor("site1", Some("node1"), "somePeerSite", "9999")))
}
//No file
intercept[Exception] {
KeyStoreDescriptorParser(ConfigFactory.parseString(""" password="bar" """), ConfigFactory.empty(), ConfigFactory.empty())
}
//No password
intercept[Exception] {
KeyStoreDescriptorParser(ConfigFactory.parseString(""" file="foo" """), ConfigFactory.empty(), ConfigFactory.empty())
}
//Alias size doesn't match up
intercept[AssertionError] {
{
val descriptor = KeyStoreDescriptorParser(
ConfigFactory.parseString("""
file="foo"
password="bar"
privateKeyAlias="baz"
- trustModelIsHub = false
aliasMap = {
site1 = "downstream1"
site2 = "downstream2"
}
"""),
ConfigFactory.parseString(
"""
|downstreamNodes = {
| site1 = "https://someRemoteSite:7777/shrine/test"
|}
""".stripMargin),
- ConfigFactory.empty()
+ ConfigFactory.parseString(
+ """
+ |trustModelIsHub = false
+ """.stripMargin
+ )
)
}
}
//Names don't correspond
intercept[AssertionError] {
{
val descriptor = KeyStoreDescriptorParser(
ConfigFactory.parseString("""
file="foo"
password="bar"
privateKeyAlias="baz"
- trustModelIsHub = false
aliasMap = {
site1 = "downstream1"
site2 = "downstream2"
}
"""),
ConfigFactory.parseString(
"""
|downstreamNodes = {
| site1 = "https://someRemoteSite:7777/shrine/test"
| site3 = "https://someRemoteSite:7777/shrine/test"
|}
""".stripMargin),
- ConfigFactory.empty()
+ ConfigFactory.parseString(
+ """
+ |trustModelIsHub = false
+ """.stripMargin
+ )
)
}
}
}
}
\ No newline at end of file
diff --git a/qep/service/src/main/resources/reference.conf b/qep/service/src/main/resources/reference.conf
index b0ee7c088..14c480ddc 100644
--- a/qep/service/src/main/resources/reference.conf
+++ b/qep/service/src/main/resources/reference.conf
@@ -1,76 +1,74 @@
shrine {
queryEntryPoint {
create = true
audit {
collectQepAudit = true
database {
dataSourceFrom = "JNDI" //Can be JNDI or testDataSource . Use testDataSource for tests, JNDI everywhere else
jndiDataSourceName = "java:comp/env/jdbc/qepAuditDB" //or leave out for tests
slickProfileClassName = "slick.driver.MySQLDriver$" // Can be
// slick.driver.H2Driver$
// slick.driver.MySQLDriver$
// slick.driver.PostgresDriver$
// slick.driver.SQLServerDriver$
// slick.driver.JdbcDriver$
// freeslick.OracleProfile$
// freeslick.MSSQLServerProfile$
//
// (Yes, with the $ on the end)
// For testing without JNDI
// testDataSource {
//typical test settings for unit tests
//driverClassName = "org.h2.Driver"
//url = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1" //H2 embedded in-memory for unit tests
//url = "jdbc:h2:~/stewardTest.h2" //H2 embedded on disk at ~/test
// }
timeout = 30 //time to wait before db gives up, in seconds.
createTablesOnStart = false //for testing with H2 in memory, when not running unit tests. Set to false normally
}
}
- trustModelIsHub = true // False for P2P networks.
-
attachSigningCert = true
authenticationType = "pm" //can be none, pm, or ecommons
authorizationType = "shrine-steward" //can be none, shrine-steward, or hms-steward
//hms-steward config
// sheriffEndpoint {
// url = "http://localhost:8080/shrine-hms-authorization/queryAuthorization"
// acceptAllCerts = true
// timeout {
// seconds = 1
// }
// }
// sheriffCredentials {
// username = "sheriffUsername"
// password = "sheriffPassword"
// }
//shrine-steward config
shrineSteward {
qepUserName = "qep"
// qepPassword = "trustme"
// stewardBaseUrl = "https://localhost:6443"
}
// includeAggregateResults = false
//
// maxQueryWaitTime {
// minutes = 5
// }
// broadcasterServiceEndpoint {
// url = "http://example.com/shrine/rest/broadcaster/broadcast"
// acceptAllCerts = true
// timeout {
// seconds = 1
// }
// }
}
}
\ No newline at end of file