mirror of
https://github.com/ysoftdevs/odc-analyzer.git
synced 2026-01-15 16:23:52 +01:00
Added API for listing of scans
Added API support
This commit is contained in:
@@ -3,7 +3,7 @@ package controllers
|
||||
class Projects (
|
||||
val projectMap: Map[String, String],
|
||||
private val teamLeaders: Map[TeamId, String],
|
||||
private val projectToTeams: Map[String, Set[TeamId]]
|
||||
val projectToTeams: Map[String, Set[TeamId]]
|
||||
) {
|
||||
val projectSet: Set[String] = projectMap.keySet
|
||||
|
||||
@@ -24,4 +24,9 @@ class Projects (
|
||||
|
||||
def teamSet: Set[Team] = teamsById.values.toSet
|
||||
|
||||
def teamsByProjectId(projectId: String): Set[TeamId] = projectToTeams.filter{case (projectSpecification, _) =>
|
||||
val projectName = projectMap(projectId)
|
||||
(projectSpecification == projectName) || (projectSpecification startsWith s"$projectName:")
|
||||
}.values.flatten.toSet
|
||||
|
||||
}
|
||||
|
||||
@@ -6,16 +6,23 @@ import com.google.inject.name.Named
|
||||
import com.ysoft.odc.statistics.{LibDepStatistics, TagStatistics}
|
||||
import com.ysoft.odc.{ArtifactFile, ArtifactItem}
|
||||
import controllers.DependencyCheckReportsParser.ResultWithSelection
|
||||
import models.{ExportedVulnerability, LibraryTag}
|
||||
import controllers.api.{ApiConfig, ApiController}
|
||||
import models.LibraryTag
|
||||
import org.joda.time.DateTime
|
||||
import play.api.i18n.MessagesApi
|
||||
import play.api.libs.json._
|
||||
import play.twirl.api.Txt
|
||||
import services._
|
||||
import views.html.DefaultRequest
|
||||
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
|
||||
class Statistics @Inject() (
|
||||
final case class ScannedRepository(url: String, branch: String)
|
||||
|
||||
final case class ScannedProject(name: String, repos: Seq[ScannedRepository], projects: Seq[String], key: String)
|
||||
|
||||
//noinspection TypeAnnotation
|
||||
class Statistics @Inject()(
|
||||
reportsParser: DependencyCheckReportsParser,
|
||||
reportsProcessor: DependencyCheckReportsProcessor,
|
||||
projectReportsProvider: ProjectReportsProvider,
|
||||
@@ -28,8 +35,9 @@ class Statistics @Inject() (
|
||||
projects: Projects,
|
||||
vulnerabilityNotificationService: VulnerabilityNotificationService,
|
||||
issueTrackerServiceOption: Option[IssueTrackerService],
|
||||
protected val apiConfig: ApiConfig,
|
||||
val env: AuthEnv
|
||||
)(implicit val messagesApi: MessagesApi, executionContext: ExecutionContext) extends AuthenticatedController {
|
||||
)(implicit val messagesApi: MessagesApi, executionContext: ExecutionContext) extends AuthenticatedController with ApiController {
|
||||
|
||||
private val versions = Map[String, Int]()
|
||||
|
||||
@@ -194,6 +202,26 @@ class Statistics @Inject() (
|
||||
}
|
||||
}
|
||||
|
||||
implicit val scannedRepositoryFormat = Json.format[ScannedRepository]
|
||||
implicit val scannedProjectFormats = Json.format[ScannedProject]
|
||||
|
||||
|
||||
def table() = ApiAction(ProjectTable).async{
|
||||
val RepoFetch = """.*Fetching 'refs/heads/(.*)' from '(.*)'\..*""".r // Bamboo does not seem to have a suitable API, so we are parsing it from logs…
|
||||
val (lastRefreshTime, resultsFuture) = projectReportsProvider.resultsForVersions(versions)
|
||||
resultsFuture map { allResults =>
|
||||
val t = projects.projectMap
|
||||
val rows = t.toIndexedSeq.sortBy(r => (r._2.toLowerCase, r._2)).map{case (key, name) =>
|
||||
val repos = allResults._1.get(key).map(_._3.dataString.lines.collect{
|
||||
case RepoFetch(branch, repo) => ScannedRepository(repo, branch)
|
||||
}.toSet).getOrElse(Set.empty).toIndexedSeq.sortBy(ScannedRepository.unapply)
|
||||
ScannedProject(name, repos, projects.teamsByProjectId(key).toIndexedSeq.map(_.name).sorted, key)
|
||||
}
|
||||
Ok(Json.toJson(rows))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def vulnerableLibraries(selectorOption: Option[String]) = ReadAction.async { implicit req =>
|
||||
val (lastRefreshTime, resultsFuture) = projectReportsProvider.resultsForVersions(versions)
|
||||
resultsFuture flatMap { allResults =>
|
||||
|
||||
16
app/controllers/api/ApiApplication.scala
Normal file
16
app/controllers/api/ApiApplication.scala
Normal file
@@ -0,0 +1,16 @@
|
||||
package controllers.api
|
||||
|
||||
import play.api.libs.Crypto
|
||||
|
||||
sealed abstract class ApiApplication {
|
||||
def authenticate(appToken: String): Option[AuthenticatedApiApplication]
|
||||
}
|
||||
|
||||
object ApiApplication{
|
||||
final class Plain(token: String, authenticatedApiApplication: AuthenticatedApiApplication) extends ApiApplication{
|
||||
override def authenticate(appToken: String): Option[AuthenticatedApiApplication] = {
|
||||
if(Crypto.constantTimeEquals(appToken, token)) Some(authenticatedApiApplication)
|
||||
else None
|
||||
}
|
||||
}
|
||||
}
|
||||
9
app/controllers/api/ApiConfig.scala
Normal file
9
app/controllers/api/ApiConfig.scala
Normal file
@@ -0,0 +1,9 @@
|
||||
package controllers.api
|
||||
|
||||
|
||||
class ApiConfig(applications: Map[String, ApiApplication]){
|
||||
def getApplication(appName: String, appToken: String): Option[AuthenticatedApiApplication] = for{
|
||||
app <- applications.get(appName)
|
||||
authenticatedApp <- app.authenticate(appToken)
|
||||
} yield authenticatedApp
|
||||
}
|
||||
30
app/controllers/api/ApiController.scala
Normal file
30
app/controllers/api/ApiController.scala
Normal file
@@ -0,0 +1,30 @@
|
||||
package controllers.api
|
||||
|
||||
import controllers.AuthenticatedController
|
||||
import play.api.mvc.{ActionBuilder, Request, Result}
|
||||
import play.twirl.api.Txt
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
trait ApiController extends AuthenticatedController with ApiResources {
|
||||
|
||||
protected def apiConfig: ApiConfig
|
||||
|
||||
protected def ApiAction(resource: ApiResource) = new ActionBuilder[Request] {
|
||||
override def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]): Future[Result] = {
|
||||
val appNameOption = request.headers.get("x-app-name").orElse(request.getQueryString("app-name"))
|
||||
val appTokenOption = request.headers.get("x-app-token").orElse(request.getQueryString("app-token"))
|
||||
(appNameOption, appTokenOption) match {
|
||||
case (Some(appName), Some(appToken)) =>
|
||||
apiConfig.getApplication(appName, appToken) match {
|
||||
case Some(app) =>
|
||||
if(app.isAllowed(resource)) block(request)
|
||||
else Future.successful(Unauthorized(Txt("The application is not allowed to access "+resource.name)))
|
||||
case None => Future.successful(Unauthorized(Txt("Unknown application or bad token")))
|
||||
}
|
||||
case _ => Future.successful(Unauthorized(Txt("Missing auth headers x-app-name and x-app-token (or similar GET parameters).")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
4
app/controllers/api/ApiResource.scala
Normal file
4
app/controllers/api/ApiResource.scala
Normal file
@@ -0,0 +1,4 @@
|
||||
package controllers.api
|
||||
|
||||
final case class ApiResource private[api](name: String) extends AnyVal
|
||||
|
||||
11
app/controllers/api/ApiResources.scala
Normal file
11
app/controllers/api/ApiResources.scala
Normal file
@@ -0,0 +1,11 @@
|
||||
package controllers.api
|
||||
|
||||
trait ApiResources {
|
||||
val ProjectTable = ApiResource("project-table")
|
||||
}
|
||||
|
||||
object ApiResources extends ApiResources{
|
||||
val All = Set(ProjectTable)
|
||||
private val AllByName = All.map(res => res.name -> res).toMap
|
||||
def byName(name: String): Option[ApiResource] = AllByName.get(name)
|
||||
}
|
||||
5
app/controllers/api/AuthenticatedApiApplication.scala
Normal file
5
app/controllers/api/AuthenticatedApiApplication.scala
Normal file
@@ -0,0 +1,5 @@
|
||||
package controllers.api
|
||||
|
||||
class AuthenticatedApiApplication(resources: Set[ApiResource]) {
|
||||
def isAllowed(resource: ApiResource): Boolean = resources contains resource
|
||||
}
|
||||
@@ -6,7 +6,9 @@ import java.nio.file.{Files, Path, Paths}
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
import akka.util.ClassLoaderObjectInputStream
|
||||
import com.typesafe.config.{Config, ConfigObject, ConfigValue}
|
||||
import com.ysoft.odc._
|
||||
import controllers.api._
|
||||
import controllers.{MissingGavExclusions, Projects, TeamId, WarningSeverity}
|
||||
import net.ceedubs.ficus.Ficus._
|
||||
import net.ceedubs.ficus.readers.ArbitraryTypeReader._
|
||||
@@ -114,6 +116,33 @@ class ConfigModule extends Module {
|
||||
)
|
||||
}
|
||||
|
||||
private def parseApiApplication(value: Config): ApiApplication = {
|
||||
import scala.collection.JavaConversions._
|
||||
val authenticatedApiApplication = new AuthenticatedApiApplication(
|
||||
value.getStringList("resources").map(resName =>
|
||||
ApiResources.byName(resName).getOrElse(sys.error(s"unknown resource $resName"))
|
||||
).toSet)
|
||||
value.getString("tokenType") match {
|
||||
case "plain" => new ApiApplication.Plain(value.getString("token"), authenticatedApiApplication)
|
||||
}
|
||||
}
|
||||
|
||||
private def parseApiConfig(configuration: Configuration): ApiConfig = {
|
||||
import scala.collection.JavaConversions._
|
||||
new ApiConfig(
|
||||
configuration.getObject("yssdc.api.apps") match {
|
||||
case None => Map.empty[String, ApiApplication]
|
||||
case Some(obj) => Map(
|
||||
(
|
||||
for{
|
||||
(key, value) <- obj
|
||||
} yield key -> parseApiApplication(value.asInstanceOf[ConfigObject].toConfig)
|
||||
).toSeq: _*
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override def bindings(environment: Environment, configuration: Configuration): Seq[Binding[_]] = Seq(
|
||||
bind[String].qualifiedWith("bamboo-server-url").toInstance(configuration.getString("yssdc.bamboo.url").getOrElse(sys.error("Key yssdc.bamboo.url is not set"))),
|
||||
configuration.getString("yssdc.reports.provider") match{
|
||||
@@ -126,7 +155,8 @@ class ConfigModule extends Module {
|
||||
),
|
||||
bind[ExecutionContext].qualifiedWith("email-sending").toInstance(ExecutionContext.fromExecutor(Executors.newSingleThreadExecutor())),
|
||||
bind[LogSmellChecks].qualifiedWith("log-smells").toInstance(LogSmellChecks(configuration.underlying.getAs[Map[String, LogSmell]]("yssdc.logSmells").getOrElse(Map()))),
|
||||
bind[Projects].to(parseProjects(configuration))
|
||||
bind[Projects].to(parseProjects(configuration)),
|
||||
bind[ApiConfig].to(parseApiConfig(configuration))
|
||||
) ++
|
||||
configuration.underlying.getAs[Absolutizer]("app").map(a => bind[Absolutizer].toInstance(a)) ++
|
||||
configuration.getString("play.cache.path").map(cachePath => bind[CacheApi].toInstance(new FileCacheApi(Paths.get(cachePath)))) ++
|
||||
|
||||
@@ -73,7 +73,7 @@ libraryDependencies += "org.webjars.bower" % "jquery.scrollTo" % "2.1.2"
|
||||
|
||||
libraryDependencies += "net.codingwell" %% "scala-guice" % "4.0.0"
|
||||
|
||||
libraryDependencies += "com.iheart" %% "ficus" % "1.2.3"
|
||||
libraryDependencies += "com.iheart" %% "ficus" % "1.4.0"
|
||||
|
||||
libraryDependencies += "org.owasp" % "dependency-check-core" % "1.4.2"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user