mirror of
https://github.com/ysoftdevs/odc-analyzer.git
synced 2026-05-01 12:54:22 +02:00
Initial commit
This commit is contained in:
13
app/services/AllowAllCredentialsVerificationService.scala
Normal file
13
app/services/AllowAllCredentialsVerificationService.scala
Normal file
@@ -0,0 +1,13 @@
|
||||
package services
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
class AllowAllCredentialsVerificationService(app: play.api.Application) extends CredentialsVerificationService{
|
||||
if(app.mode != play.api.Mode.Dev){
|
||||
// safety check:
|
||||
sys.error("allow-all can be used in dev mode only")
|
||||
}
|
||||
|
||||
override def verifyCredentials(username: String, password: String): Future[Boolean] = Future.successful(true)
|
||||
|
||||
}
|
||||
7
app/services/CredentialsVerificationService.scala
Normal file
7
app/services/CredentialsVerificationService.scala
Normal file
@@ -0,0 +1,7 @@
|
||||
package services
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
trait CredentialsVerificationService {
|
||||
def verifyCredentials(username: String, password: String): Future[Boolean]
|
||||
}
|
||||
17
app/services/ExternalCredentialsVerificationService.scala
Normal file
17
app/services/ExternalCredentialsVerificationService.scala
Normal file
@@ -0,0 +1,17 @@
|
||||
package services
|
||||
|
||||
import play.api.libs.json.Json
|
||||
import play.api.libs.ws.{WS, WSClient}
|
||||
|
||||
import scala.concurrent.{Future, ExecutionContext}
|
||||
|
||||
class ExternalCredentialsVerificationService(url: String)(implicit executionContext: ExecutionContext, wSClient: WSClient) extends CredentialsVerificationService{
|
||||
override def verifyCredentials(username: String, password: String): Future[Boolean] = {
|
||||
WS.clientUrl(url).post(Json.toJson(Map("username" -> username, "password" -> password))).map{ response =>
|
||||
response.body match {
|
||||
case "OK" => true
|
||||
case "bad" => false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
78
app/services/LibrariesService.scala
Normal file
78
app/services/LibrariesService.scala
Normal file
@@ -0,0 +1,78 @@
|
||||
package services
|
||||
|
||||
import com.google.inject.Inject
|
||||
import models._
|
||||
import models.tables._
|
||||
import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider}
|
||||
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
|
||||
class LibrariesService @Inject() (protected val dbConfigProvider: DatabaseConfigProvider)(implicit executionContext: ExecutionContext) extends HasDatabaseConfigProvider[models.profile.type]{
|
||||
import dbConfig.driver.api._
|
||||
|
||||
def all: Future[Seq[(Int, Library)]] = db.run(libraries.result)
|
||||
|
||||
def allBase: Future[Seq[Library]] = db.run(libraries.map(_.base).result)
|
||||
|
||||
// TODO: unify or differentiate librariesForTags and byTags
|
||||
def librariesForTags(tagIds: Iterable[Int]): Future[Seq[(Int, (Int, Library))]] = db.run(
|
||||
libraryTagAssignments
|
||||
.join(libraries).on { case (a, l) => a.libraryId === l.id }
|
||||
.filter { case (a, l) => a.libraryTagId inSet tagIds }
|
||||
.map { case (a, l) => a.libraryTagId -> l }
|
||||
.result
|
||||
)
|
||||
|
||||
def byTags(libraryIds: Set[Int]) = {
|
||||
(db.run(
|
||||
libraryTagAssignments
|
||||
.filter(_.libraryId inSet libraryIds)
|
||||
.result
|
||||
): Future[Seq[LibraryTagAssignment]]).map(_.groupBy(_.libraryId).mapValues(_.toSet).map(identity))
|
||||
}
|
||||
|
||||
|
||||
def setClassified(libraryId: Int, classified: Boolean): Future[_] = db.run(libraries.filter(_.id === libraryId).map(_.classified).update(classified))
|
||||
|
||||
def touched(tagIds: Iterable[Int]): Future[Seq[(Int, Library)]] = db.run(libraries.filter(l => l.classified || l.id.inSet(tagIds) ).result)
|
||||
|
||||
def unclassified: Future[Seq[(Int, Library)]] = db.run(libraries.filter(!_.classified).sortBy(l => l.plainLibraryIdentifierUnmapped).result)
|
||||
|
||||
def insert(lib: Library) = db.run(libraries.map(_.base).returning(libraries.map(_.id)) += lib)
|
||||
|
||||
def insertMany(newItems: Iterable[Library]): Future[_] = db.run(libraries.map(_.base) ++= newItems)
|
||||
|
||||
def filtered(requiredClassification: Option[Boolean], requiredTagsOption: Option[Set[Int]]): Query[Libraries, (Int, Library), Seq] = {
|
||||
libraries
|
||||
.joinLeft(libraryTagAssignments).on { case (l, a) => l.id === a.libraryId }
|
||||
.filter { case (l, a) => requiredClassification.map(l.classified === _).getOrElse(l.classified === l.classified) } // classification matches
|
||||
.filter { case (l, a) =>
|
||||
requiredTagsOption.fold(
|
||||
// actually a.isEmpty; The t.isEmpty should work, but there is a bug – it uses (… = 1) on a null value, which has a different semantics in SQL. Related to https://github.com/slick/slick/issues/1156 .
|
||||
// So, I created following workaround:
|
||||
a.fold(true.asColumnOf[Boolean])(_ => false) // Filter only libraries with no tags (i.e. LEFT JOIN added NULLs (they corresponsd to None value))
|
||||
)(requiredTagsSet =>
|
||||
if (requiredTagsSet.isEmpty) true.asColumnOf[Boolean] // If we don't filter any by tag, we should allow all
|
||||
else a.map(_.libraryTagId inSet requiredTagsSet).getOrElse(false.asColumnOf[Boolean]) // Filter tags
|
||||
)
|
||||
}
|
||||
.groupBy { case (l, a) => l } // a library with multiple tags should be present only once
|
||||
.map { case (l, q) => (l, q.size) } // we are not interested in the tags, but only in their count
|
||||
.filter { case (l, c) => requiredTagsOption.fold( true.asColumnOf[Boolean] )(requiredTagsSet => c >= requiredTagsSet.size ) } // filter libraries with all the tags we are looking for
|
||||
.map { case (l, c) => l } // all is filtered, so we need libraries only
|
||||
.sortBy { l => l.plainLibraryIdentifierUnmapped }
|
||||
}
|
||||
|
||||
def byPlainLibraryIdentifiers(plainLibraryIdentifiers: Set[PlainLibraryIdentifier]): Future[Map[PlainLibraryIdentifier, (Int, Library)]] = {
|
||||
val groupedIdentifiers = plainLibraryIdentifiers.groupBy(_.libraryType).mapValues(_.map(_.libraryIdentifier)).map(identity)
|
||||
val resFuture: Future[Seq[(Int, Library)]] = db.run(libraries.filter{l =>
|
||||
val conditions = for((libraryType, identifiers) <- groupedIdentifiers) yield l.libraryType === libraryType && l.libraryIdentifier.inSet(identifiers)
|
||||
conditions.foldLeft(false.asColumnOf[Boolean])(_ || _)
|
||||
}.result)
|
||||
resFuture.map(
|
||||
//_.toSet.groupBy(_._2.plainLibraryIdentifier)
|
||||
_.map(x => x._2.plainLibraryIdentifier -> x).toMap
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
34
app/services/LibraryTagAssignmentsService.scala
Normal file
34
app/services/LibraryTagAssignmentsService.scala
Normal file
@@ -0,0 +1,34 @@
|
||||
package services
|
||||
|
||||
import com.google.inject.Inject
|
||||
import models.tables._
|
||||
import models.{LibraryTagAssignment, LibraryTagPair}
|
||||
import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider}
|
||||
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
class LibraryTagAssignmentsService @Inject() (protected val dbConfigProvider: DatabaseConfigProvider) extends HasDatabaseConfigProvider[models.profile.type]{
|
||||
import dbConfig.driver.api._
|
||||
|
||||
|
||||
def all = db.run(libraryTagAssignments.result): Future[Seq[LibraryTagAssignment]]
|
||||
|
||||
def insert(item: LibraryTagAssignment) = db.run(libraryTagAssignments += item)
|
||||
|
||||
def remove(libraryTagPair: LibraryTagPair) = db.run(
|
||||
libraryTagAssignments
|
||||
.filter(_.libraryTagId === libraryTagPair.tagId)
|
||||
.filter(_.libraryId === libraryTagPair.libraryId)
|
||||
.delete
|
||||
)
|
||||
|
||||
def forLibraries(libraryIds: Set[Int]): Future[Seq[LibraryTagAssignment]] = db.run(libraryTagAssignments.filter(_.libraryId inSet libraryIds).result)
|
||||
|
||||
def byLibrary(implicit executionContext: ExecutionContext) = all.map(_.groupBy(_.libraryId).withDefaultValue(Seq()))
|
||||
|
||||
def tagsToLibraries(tagAssignmentsFuture: Future[Seq[LibraryTagAssignment]])(implicit executionContext: ExecutionContext): Future[Map[Int, Set[LibraryTagAssignment]]] =
|
||||
tagAssignmentsFuture.map(x => tagsToLibraries(x))
|
||||
|
||||
def tagsToLibraries(tagAssignments: Seq[LibraryTagAssignment]): Map[Int, Set[LibraryTagAssignment]] = tagAssignments.groupBy(_.tagId).mapValues(_.toSet).map(identity).withDefaultValue(Set.empty)
|
||||
|
||||
|
||||
}
|
||||
124
app/services/OdcService.scala
Normal file
124
app/services/OdcService.scala
Normal file
@@ -0,0 +1,124 @@
|
||||
package services
|
||||
|
||||
import java.lang.{Boolean => JBoolean}
|
||||
import java.util.{Map => JMap}
|
||||
|
||||
import _root_.org.owasp.dependencycheck.data.nvdcve.CveDB
|
||||
import _root_.org.owasp.dependencycheck.dependency.VulnerableSoftware
|
||||
import _root_.org.owasp.dependencycheck.utils.{DependencyVersion, DependencyVersionUtil, Settings}
|
||||
import com.github.nscala_time.time.Imports._
|
||||
import com.google.inject.Inject
|
||||
import models.odc.OdcProperty
|
||||
import models.odc.tables._
|
||||
import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider}
|
||||
import play.db.NamedDatabase
|
||||
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
|
||||
class OdcService @Inject()(@NamedDatabase("odc") protected val dbConfigProvider: DatabaseConfigProvider)(implicit executionContext: ExecutionContext) extends HasDatabaseConfigProvider[models.odc.profile.type]{
|
||||
|
||||
import dbConfig.driver.api._
|
||||
|
||||
def getVulnerableSoftware(id: Int): Future[Seq[com.ysoft.odc.VulnerableSoftware]] = {
|
||||
db.run(softwareVulnerabilities.joinLeft(cpeEntries).on((sv, ce) => sv.cpeEntryId === ce.id).filter{case (sv, ceo) => sv.vulnerabilityId === id}.result).map{rawRefs =>
|
||||
rawRefs.map{
|
||||
case (softVuln, Some((_, cpeEntry))) => com.ysoft.odc.VulnerableSoftware(allPreviousVersion = softVuln.includesAllPreviousVersions, name=cpeEntry.cpe)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def getReferences(id: Int): Future[Seq[com.ysoft.odc.Reference]] = db.run(references.filter(_.cveId === id).map(_.base).result)
|
||||
|
||||
def getVulnerabilityDetails(id: Int): Future[Option[com.ysoft.odc.Vulnerability]] = {
|
||||
for {
|
||||
bareVulnOption <- db.run(vulnerabilities.filter(_.id === id).map(_.base).result).map(_.headOption)
|
||||
vulnerableSoftware <- getVulnerableSoftware(id)
|
||||
references <- getReferences(id)
|
||||
} yield bareVulnOption.map{bareVuln =>
|
||||
com.ysoft.odc.Vulnerability(
|
||||
name = bareVuln.cve,
|
||||
cweOption = bareVuln.cweOption,
|
||||
cvss = bareVuln.cvss,
|
||||
description = bareVuln.description,
|
||||
vulnerableSoftware = vulnerableSoftware,
|
||||
references = references
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private def parseCpe(cpe: String) = {
|
||||
val sw = new VulnerableSoftware()
|
||||
sw.parseName(cpe)
|
||||
sw
|
||||
}
|
||||
|
||||
def parseVersion(version: String): DependencyVersion = {
|
||||
DependencyVersionUtil.parseVersion(version)
|
||||
}
|
||||
|
||||
/*def parseCpeVersion(cpe: String): DependencyVersion = { // strongly inspired by org.owasp.dependencycheck.data.nvdcve.CveDB.parseDependencyVersion(cpe: VulnerableSoftware): DependencyVersion
|
||||
def StringOption(s: String) = Option(s).filterNot(_.isEmpty)
|
||||
val sw = parseCpe(cpe)
|
||||
StringOption(sw.getVersion) match {
|
||||
case None ⇒ new DependencyVersion("-")
|
||||
case Some(bareVersionString) ⇒
|
||||
DependencyVersionUtil.parseVersion(
|
||||
StringOption(sw.getUpdate) match {
|
||||
case None ⇒ bareVersionString
|
||||
case Some(update) ⇒ s"$bareVersionString.$update"
|
||||
}
|
||||
)
|
||||
}
|
||||
}*/
|
||||
|
||||
def findRelevantCpes(versionlessCpe: String, version: String) = {
|
||||
println(s"versionlessCpe: $versionlessCpe")
|
||||
val Seq("cpe", "/a", vendor, product, rest @ _*) = versionlessCpe.split(':').toSeq
|
||||
val cpesFuture = db.run(
|
||||
cpeEntries.filter(c =>
|
||||
c.vendor === vendor && c.product === product
|
||||
).result
|
||||
)
|
||||
for(cpes <- cpesFuture){println(s"cpes: $cpes")}
|
||||
val cpesMapFuture = cpesFuture.map(_.toMap)
|
||||
val cpeIdsFuture = cpesFuture.map(_.map(_._1))
|
||||
val parsedVersion = parseVersion(version)
|
||||
val res = for{
|
||||
cpeIds <- cpeIdsFuture
|
||||
relevantVulnerabilities <- db.run(
|
||||
softwareVulnerabilities.join(vulnerabilities).on( (sv, v) => sv.vulnerabilityId === v.id)
|
||||
.filter{case (sv, v) => sv.cpeEntryId inSet cpeIds}.map{case (sv, v) ⇒ sv}.result
|
||||
).map(_.groupBy(_.vulnerabilityId).mapValues(_.toSet))
|
||||
cpesMap <- cpesMapFuture
|
||||
//relevantVulnerabilities <- db.run(vulnerabilities.filter(_.id inSet relevantVulnerabilityIds).result)
|
||||
} yield relevantVulnerabilities.filter{case (vulnId, sv) => Option(CveDbHelper.matchSofware(
|
||||
vulnerableSoftware = sv.map(sv => cpesMap(sv.cpeEntryId).cpe -> sv.includesAllPreviousVersions).toMap,
|
||||
vendor = vendor,
|
||||
product = product,
|
||||
identifiedVersion = parsedVersion
|
||||
)).isDefined}
|
||||
res.map(_.values.toSet.flatten)
|
||||
}
|
||||
|
||||
def loadUpdateProperties(): Future[Map[String, Long]] = db.run(properties.filter(_.id like "NVD CVE%").result).map(_.map{case OdcProperty(id, value) => (id, value.toLong)}.toMap)
|
||||
|
||||
def loadLastDbUpdate(): Future[DateTime] = loadUpdateProperties().map(vals => new DateTime(vals.values.max)) // TODO: timezone (I don't care much, though)
|
||||
|
||||
}
|
||||
|
||||
|
||||
private[services] object CveDbHelper {
|
||||
|
||||
|
||||
def matchSofware(vulnerableSoftware: Map[String, Boolean], vendor: String, product: String, identifiedVersion: DependencyVersion) = {
|
||||
if(Settings.getInstance() == null){
|
||||
Settings.initialize()// Initiallize ODC environment on first use; Needed for each thread.
|
||||
}
|
||||
val cd = new CveDB()
|
||||
import scala.collection.JavaConversions._
|
||||
val method = cd.getClass.getDeclaredMethod("getMatchingSoftware", classOf[JMap[String, JBoolean]], classOf[String], classOf[String], classOf[DependencyVersion])
|
||||
method.setAccessible(true)
|
||||
method.invoke(cd, mapAsJavaMap(vulnerableSoftware).asInstanceOf[JMap[String, JBoolean]], vendor, product, identifiedVersion)
|
||||
}
|
||||
}
|
||||
|
||||
19
app/services/TagsService.scala
Normal file
19
app/services/TagsService.scala
Normal file
@@ -0,0 +1,19 @@
|
||||
package services
|
||||
|
||||
import com.google.inject.Inject
|
||||
import models._
|
||||
import models.tables._
|
||||
import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider}
|
||||
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
|
||||
class TagsService @Inject() (protected val dbConfigProvider: DatabaseConfigProvider) extends HasDatabaseConfigProvider[models.profile.type]{
|
||||
import dbConfig.driver.api._
|
||||
|
||||
def all: Future[Seq[(Int, LibraryTag)]] = db.run(tags.result)
|
||||
|
||||
def insertMany(newTags: Iterable[LibraryTag]): Future[_] = db.run(tags.map(_.base) ++= newTags)
|
||||
|
||||
def getById(id: Int)(implicit executionContext: ExecutionContext): Future[(Int, LibraryTag)] = db.run(tags.filter(_.id === id).result).map(_.head)
|
||||
|
||||
}
|
||||
36
app/services/TokenService.scala
Normal file
36
app/services/TokenService.scala
Normal file
@@ -0,0 +1,36 @@
|
||||
package services
|
||||
|
||||
import javax.inject.Inject
|
||||
|
||||
import com.mohiva.play.silhouette.impl.authenticators.CookieAuthenticator
|
||||
import com.mohiva.play.silhouette.impl.daos.AuthenticatorDAO
|
||||
import play.api.db.slick.{HasDatabaseConfigProvider, DatabaseConfigProvider}
|
||||
import models.tables._
|
||||
|
||||
import scala.concurrent.{Future, ExecutionContext}
|
||||
|
||||
|
||||
class TokenService @Inject() (protected val dbConfigProvider: DatabaseConfigProvider)(implicit executionContext: ExecutionContext)
|
||||
extends AuthenticatorDAO[CookieAuthenticator]
|
||||
with HasDatabaseConfigProvider[models.profile.type]{
|
||||
import dbConfig.driver.api._
|
||||
|
||||
println(authTokens.schema.create.statements.toIndexedSeq)
|
||||
|
||||
override def find(id: String): Future[Option[CookieAuthenticator]] = {
|
||||
db.run(authTokens.filter(_.id === id).result).map{_.headOption}
|
||||
}
|
||||
|
||||
override def add(authenticator: CookieAuthenticator): Future[CookieAuthenticator] = {
|
||||
db.run(authTokens += authenticator).map(_ => authenticator)
|
||||
}
|
||||
|
||||
override def remove(id: String): Future[Unit] = {
|
||||
db.run(authTokens.filter(_.id === id).delete).map(_ => ())
|
||||
}
|
||||
|
||||
override def update(authenticator: CookieAuthenticator): Future[CookieAuthenticator] = {
|
||||
db.run(authTokens.filter(_.id === authenticator.id).update(authenticator)).map(_ => authenticator)
|
||||
}
|
||||
|
||||
}
|
||||
29
app/services/UserService.scala
Normal file
29
app/services/UserService.scala
Normal file
@@ -0,0 +1,29 @@
|
||||
package services
|
||||
|
||||
import com.mohiva.play.silhouette.api.LoginInfo
|
||||
import com.mohiva.play.silhouette.api.services.IdentityService
|
||||
import com.mohiva.play.silhouette.api.util.PasswordInfo
|
||||
import com.mohiva.play.silhouette.impl.daos.DelegableAuthInfoDAO
|
||||
import models.User
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
class UserService extends DelegableAuthInfoDAO[PasswordInfo] with IdentityService[User]
|
||||
{
|
||||
override def retrieve(loginInfo: LoginInfo): Future[Option[User]] = if(loginInfo.providerID == "credentials-verification") Future.successful(Some(User(loginInfo.providerKey))) else Future.successful(None)
|
||||
|
||||
override def find(loginInfo: LoginInfo): Future[Option[PasswordInfo]] = {
|
||||
println(s"loginInfo: $loginInfo")
|
||||
|
||||
???
|
||||
}
|
||||
|
||||
override def update(loginInfo: LoginInfo, authInfo: PasswordInfo): Future[PasswordInfo] = ???
|
||||
|
||||
override def remove(loginInfo: LoginInfo): Future[Unit] = ???
|
||||
|
||||
override def save(loginInfo: LoginInfo, authInfo: PasswordInfo): Future[PasswordInfo] = ???
|
||||
|
||||
override def add(loginInfo: LoginInfo, authInfo: PasswordInfo): Future[PasswordInfo] = ???
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user