Skip to content
This repository was archived by the owner on Aug 18, 2020. It is now read-only.

Commit ab31083

Browse files
committed
Added encrypted credentials posting and deleting to the rest api.
1 parent 87f5e69 commit ab31083

File tree

3 files changed

+152
-7
lines changed

3 files changed

+152
-7
lines changed

src/main/scala/org/codeoverflow/chatoverflow/ui/web/rest/DTOs.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ object DTOs {
2727
case class CredentialsDetails(found: Boolean, requiredCredentials: Map[String, String] = Map[String, String](),
2828
optionalCredentials: Map[String, String] = Map[String, String]())
2929

30+
case class CredentialsEntry(found: Boolean, key: String = "", value: String = "")
31+
32+
case class EncryptedKeyValuePair(key: String, value: String)
33+
3034
case class PluginInstanceRef(instanceName: String, pluginName: String, pluginAuthor: String)
3135

3236
case class ResultMessage(success: Boolean, message: String = "")
@@ -35,4 +39,6 @@ object DTOs {
3539

3640
case class Password(password: String)
3741

42+
case class AuthKey(authKey: String)
43+
3844
}

src/main/scala/org/codeoverflow/chatoverflow/ui/web/rest/connector/ConnectorController.scala

Lines changed: 120 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package org.codeoverflow.chatoverflow.ui.web.rest.connector
33
import org.codeoverflow.chatoverflow.configuration.{Credentials, CryptoUtil}
44
import org.codeoverflow.chatoverflow.connector.ConnectorRegistry
55
import org.codeoverflow.chatoverflow.ui.web.JsonServlet
6-
import org.codeoverflow.chatoverflow.ui.web.rest.DTOs.{ConnectorDetails, ConnectorRef, CredentialsDetails, ResultMessage}
6+
import org.codeoverflow.chatoverflow.ui.web.rest.DTOs._
77
import org.scalatra.swagger.Swagger
88

99
import scala.collection.mutable
@@ -102,20 +102,135 @@ class ConnectorController(implicit val swagger: Swagger) extends JsonServlet wit
102102
}
103103
}
104104

105+
get("/:sourceIdentifier/:qualifiedConnectorType/credentials/:key", operation(getCredentialsEntry)) {
106+
val key = params("key")
107+
108+
if (!chatOverflow.isLoaded) {
109+
CredentialsEntry(found = false)
110+
} else {
111+
val connector = ConnectorRegistry.getConnector(params("sourceIdentifier"), params("qualifiedConnectorType"))
112+
113+
if (connector.isEmpty) {
114+
CredentialsEntry(found = false)
115+
116+
} else if (!connector.get.areCredentialsSet) {
117+
CredentialsEntry(found = false)
118+
119+
} else if (!connector.get.getCredentials.get.exists(key)) {
120+
CredentialsEntry(found = false)
121+
122+
} else {
123+
val plainValue = connector.get.getCredentials.get.getValue(key).get
124+
val encryptedValue = encryptCredentialsValue(plainValue)
125+
126+
CredentialsEntry(found = true, key, encryptedValue)
127+
}
128+
}
129+
}
130+
131+
post("/:sourceIdentifier/:qualifiedConnectorType/credentials/", operation(postCredentialsEntry)) {
132+
parsedAs[EncryptedKeyValuePair] {
133+
case EncryptedKeyValuePair(key, value) =>
134+
if (!chatOverflow.isLoaded) {
135+
ResultMessage(success = false, "Chat Overflow is not loaded.")
136+
137+
} else {
138+
val connector = ConnectorRegistry.getConnector(params("sourceIdentifier"), params("qualifiedConnectorType"))
139+
140+
if (connector.isEmpty) {
141+
ResultMessage(success = false, "Connector not found.")
142+
143+
} else if (!connector.get.areCredentialsSet) {
144+
ResultMessage(success = false, "No credentials subobject found.")
145+
146+
} else if (connector.get.isRunning) {
147+
// Should hot swapping be a thing?
148+
ResultMessage(success = false, "Connector is running.")
149+
150+
} else if (!connector.get.getRequiredCredentialKeys.contains(key) &&
151+
!connector.get.getOptionalCredentialKeys.contains(key)) {
152+
// Note that its only possible to add values to required and optional keys
153+
// Let's not dumb everything into the credentials object please
154+
ResultMessage(success = false, "Key is not required/optional for this connector.")
155+
156+
} else {
157+
158+
val decryptedValue = decryptCredentialsValue(value)
159+
160+
if (decryptedValue.isEmpty) {
161+
ResultMessage(success = false, "Value encrypted with wrong auth key.")
162+
163+
} else if (!connector.get.setCredentialsValue(key, decryptedValue.get)) {
164+
ResultMessage(success = false, "Unable to set credentials value.")
165+
166+
} else {
167+
chatOverflow.save()
168+
ResultMessage(success = true)
169+
}
170+
}
171+
}
172+
}
173+
}
174+
175+
delete("/:sourceIdentifier/:qualifiedConnectorType/credentials/:key", operation(deleteCredentialsEntry)) {
176+
parsedAs[AuthKey] {
177+
case AuthKey(authKey) =>
178+
val key = params("key")
179+
val frameworkAuthKey = chatOverflow.credentialsService.generateAuthKey()
180+
181+
if (authKey != frameworkAuthKey) {
182+
ResultMessage(success = false, "Wrong auth key supplied.")
183+
} else {
184+
185+
if (!chatOverflow.isLoaded) {
186+
ResultMessage(success = false, "Chat Overflow is not loaded.")
187+
} else {
188+
val connector = ConnectorRegistry.getConnector(params("sourceIdentifier"), params("qualifiedConnectorType"))
189+
190+
if (connector.isEmpty) {
191+
ResultMessage(success = false, "Connector not found.")
192+
193+
} else if (!connector.get.areCredentialsSet) {
194+
ResultMessage(success = false, "No credentials subobject found.")
195+
196+
} else if (connector.get.isRunning) {
197+
ResultMessage(success = false, "Connector is running.")
198+
199+
} else if (!connector.get.getCredentials.get.exists(key)) {
200+
ResultMessage(success = false, "Credentials key not found.")
201+
202+
} else {
203+
connector.get.getCredentials.get.removeValue(key)
204+
chatOverflow.save()
205+
206+
ResultMessage(success = true)
207+
}
208+
}
209+
}
210+
}
211+
}
212+
105213
protected def getCredentialsMap(keys: List[String], credentials: Credentials): Map[String, String] = {
106-
val authKey = chatOverflow.credentialsService.generateAuthKey()
107214
val credentialsMap = mutable.Map[String, String]()
108215

109216
for (key <- keys) {
110217
if (credentials.exists(key)) {
111218
val plainValue = credentials.getValue(key).get
112-
val encryptedValue = CryptoUtil.encryptSSLcompliant(authKey, plainValue)
113-
114-
credentialsMap += key -> encryptedValue
219+
credentialsMap += key -> encryptCredentialsValue(plainValue)
115220
}
116221
}
117222

118223
credentialsMap.toMap
119224
}
120225

226+
protected def encryptCredentialsValue(plainValue: String): String = {
227+
val authKey = chatOverflow.credentialsService.generateAuthKey()
228+
CryptoUtil.encryptSSLcompliant(authKey, plainValue)
229+
}
230+
231+
protected def decryptCredentialsValue(cipherValue: String): Option[String] = {
232+
val authKey = chatOverflow.credentialsService.generateAuthKey()
233+
CryptoUtil.decryptSSLcompliant(authKey, cipherValue)
234+
}
235+
121236
}

src/main/scala/org/codeoverflow/chatoverflow/ui/web/rest/connector/ConnectorControllerDefinition.scala

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package org.codeoverflow.chatoverflow.ui.web.rest.connector
22

33
import org.codeoverflow.chatoverflow.connector.ConnectorKey
4-
import org.codeoverflow.chatoverflow.ui.web.rest.DTOs.{ConnectorDetails, ConnectorRef, ResultMessage}
4+
import org.codeoverflow.chatoverflow.ui.web.rest.DTOs._
55
import org.scalatra.swagger.SwaggerSupport
66
import org.scalatra.swagger.SwaggerSupportSyntax.OperationBuilder
77

@@ -39,8 +39,32 @@ trait ConnectorControllerDefinition extends SwaggerSupport {
3939
parameter pathParam[String]("sourceIdentifier").description("The (connector unique) identifier of e.g. a account to connect to")
4040
parameter pathParam[String]("qualifiedConnectorType").description("The fully qualified type of the connector."))
4141

42+
val getCredentialsEntry: OperationBuilder =
43+
(apiOperation[CredentialsEntry]("getCredentialsEntry")
44+
summary "Shows a specific credentials entry of a specific connector."
45+
description "Shows one credentials entry if existent. Note, that the user has to be logged in and the value is encrypted using the auth key. "
46+
parameter pathParam[String]("sourceIdentifier").description("The (connector unique) identifier of e.g. a account to connect to")
47+
parameter pathParam[String]("qualifiedConnectorType").description("The fully qualified type of the connector.")
48+
parameter pathParam[String]("key").description("The key of the credentials entry."))
4249

43-
override protected def applicationDescription: String = "Handles platform connectors."
50+
val postCredentialsEntry: OperationBuilder =
51+
(apiOperation[ResultMessage]("postCredentialsEntry")
52+
summary "Creates a new credentials entry."
53+
description "Creates a new credentials entry with given parameters for a given connector. Note that only required & optional keys can be added."
54+
parameter pathParam[String]("sourceIdentifier").description("The (connector unique) identifier of e.g. a account to connect to")
55+
parameter pathParam[String]("qualifiedConnectorType").description("The fully qualified type of the connector.")
56+
parameter bodyParam[EncryptedKeyValuePair]("body").description("Requires a key-value pair. The value must be encrypted using the auth key."))
4457

58+
val deleteCredentialsEntry: OperationBuilder =
59+
(apiOperation[ResultMessage]("deleteCredentialsEntry")
60+
summary "Deletes a specific credentials entry."
61+
description "Deletes a specific credentials entry of a given connector, if possible."
62+
parameter pathParam[String]("sourceIdentifier").description("The (connector unique) identifier of e.g. a account to connect to")
63+
parameter pathParam[String]("qualifiedConnectorType").description("The fully qualified type of the connector.")
64+
parameter pathParam[String]("key").description("The key of the credentials entry.")
65+
parameter bodyParam[AuthKey]("body").description("Requires the client auth key since this is a sensible operation."))
66+
67+
68+
override protected def applicationDescription: String = "Handles platform connectors."
4569

4670
}

0 commit comments

Comments
 (0)