Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Atlassian Connect Play

[![Continuous integration](https://github.com/toolsplus/atlassian-connect-play/actions/workflows/ci.yml/badge.svg)](https://github.com/toolsplus/atlassian-connect-play/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/toolsplus/atlassian-connect-play/branch/master/graph/badge.svg)](https://codecov.io/gh/toolsplus/atlassian-connect-play)
[![Maven Central](https://img.shields.io/maven-central/v/io.toolsplus/atlassian-connect-play-core_2.13.svg)](https://maven-badges.herokuapp.com/maven-central/io.toolsplus/atlassian-connect-play-core_2.13)
[![Maven Central](https://img.shields.io/maven-central/v/io.toolsplus/atlassian-connect-play-core_3.svg)](https://maven-badges.herokuapp.com/maven-central/io.toolsplus/atlassian-connect-play-core_3)


This project contains a [Play Scala](https://www.playframework.com/) based
Expand All @@ -12,7 +12,7 @@ It serves as a starter for building Atlassian Connect add-ons for JIRA and Confl

## Quick start

atlassian-connect-play is published to Maven Central for Scala 2.13 and Play 2.8.x,
atlassian-connect-play is published to Maven Central for Scala 3 and Play 3,
so you can just add the following to your build:

libraryDependencies += "io.toolsplus" %% "atlassian-connect-play" % "x.x.x"
Expand Down
25 changes: 17 additions & 8 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@ import xerial.sbt.Sonatype.sonatypeCentralHost

val commonSettings = Seq(
organization := "io.toolsplus",
scalaVersion := "2.13.16",
scalaVersion := "3.3.6",
versionScheme := Some("early-semver"),
resolvers ++= Seq(
Resolver.typesafeRepo("releases"),
Resolver.jcenterRepo
)
),
scalacOptions ++= {
Seq(
"-Xmax-inlines:128"
)
}
)

val scoverageSettings = Seq(
Expand All @@ -19,7 +24,8 @@ lazy val publishSettings = Seq(
releasePublishArtifactsAction := PgpKeys.publishSigned.value,
homepage := Some(url("https://github.com/toolsplus/atlassian-connect-play")),
licenses := Seq(
"Apache 2.0" -> url("https://www.apache.org/licenses/LICENSE-2.0")),
"Apache 2.0" -> url("https://www.apache.org/licenses/LICENSE-2.0")
),
publishMavenStyle := true,
Test / publishArtifact := false,
pomIncludeRepository := { _ =>
Expand All @@ -35,10 +41,12 @@ lazy val publishSettings = Seq(
)
),
developers := List(
Developer("tbinna",
"Tobias Binna",
"tobias.binna@toolsplus.io",
url("https://twitter.com/tbinna"))
Developer(
"tbinna",
"Tobias Binna",
"tobias.binna@toolsplus.io",
url("https://twitter.com/tbinna")
)
)
)

Expand All @@ -48,7 +56,8 @@ lazy val noPublishSettings = Seq(
publishLocal := {},
publishArtifact := false,
publishTo := Some(
Resolver.file("Unused transient repository", file("target/dummyrepo")))
Resolver.file("Unused transient repository", file("target/dummyrepo"))
)
)

releaseProcess := Seq[ReleaseStep](
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
package io.toolsplus.atlassian.connect.play.api.models

/**
* Authentication principal for requests coming from an Atlassian host
/** Authentication principal for requests coming from an Atlassian host
* application in which the add-on is installed.
*/
trait AtlassianHostUser {

/** Host from which the request originated.
*
* @return Host associated with this operation.
* @return
* Host associated with this operation.
*/
def host: AtlassianHost

/**
* Atlassian Account ID of the user on whose behalf a request was made.
/** Atlassian Account ID of the user on whose behalf a request was made.
*
* @return Atlassian Account ID
* @return
* Atlassian Account ID
*/
def userAccountId: Option[String]
}
Expand All @@ -24,17 +24,17 @@ object AtlassianHostUser {

object Implicits {

import scala.language.implicitConversions

/**
* Implicitly convert an instance of [[AtlassianHostUser]] to an
* instance of [[AtlassianHost]].
/** Implicitly convert an instance of [[AtlassianHostUser]] to an instance
* of [[AtlassianHost]].
*
* @param hostUser Atlassian host user instance.
* @return Underlying Atlassian host instance.
* @param hostUser
* Atlassian host user instance.
* @return
* Underlying Atlassian host instance.
*/
implicit def hostUserToHost(
implicit hostUser: AtlassianHostUser): AtlassianHost =
implicit def hostUserToHost(implicit
hostUser: AtlassianHostUser
): AtlassianHost =
hostUser.host

}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.toolsplus.atlassian.connect.play.actions

import io.toolsplus.atlassian.connect.play.auth.jwt
import io.toolsplus.atlassian.connect.play.auth.jwt.{CanonicalPlayHttpRequest, JwtCredentials, symmetric}
import io.toolsplus.atlassian.connect.play.auth.jwt.JwtCredentials
import play.api.http.HeaderNames
import play.api.mvc.Request

Expand All @@ -16,11 +16,14 @@ object JwtExtractor {
request.headers
.get(HeaderNames.AUTHORIZATION)
.filter(header =>
header.nonEmpty && header.startsWith(AuthorizationHeaderPrefix))
header.nonEmpty && header.startsWith(AuthorizationHeaderPrefix)
)
.map(_.substring(AuthorizationHeaderPrefix.length).trim)
}

private def extractJwtFromParameter[A](request: Request[A]): Option[String] = {
private def extractJwtFromParameter[A](
request: Request[A]
): Option[String] = {
request.getQueryString(QueryParameterName).filter(_.nonEmpty)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ trait AbstractAssociateAtlassianHostUserActionRefiner[R[A] <: WrappedRequest[A]]

def hostSearchResultToActionResult[A](
maybeHost: Option[AtlassianHost],
request: ForgeRemoteContextRequest[A]): Either[Result, R[A]]
request: ForgeRemoteContextRequest[A]
): Either[Result, R[A]]

override def refine[A](
request: ForgeRemoteContextRequest[A]): Future[Either[Result, R[A]]] = {
request: ForgeRemoteContextRequest[A]
): Future[Either[Result, R[A]]] = {
val installationId = request.context.invocationContext.app.installationId
forgeInstallationRepository
.findByInstallationId(installationId)
Expand All @@ -42,67 +44,74 @@ trait AbstractAssociateAtlassianHostUserActionRefiner[R[A] <: WrappedRequest[A]]
.map(hostSearchResultToActionResult(_, request))
case None =>
logger.error(
s"Failed to associate Connect host to Forge Remote Compute invocation: No host mapping for installation id $installationId found")
s"Failed to associate Connect host to Forge Remote Compute invocation: No host mapping for installation id $installationId found"
)
Future.successful(Left(BadRequest(s"Missing Connect mapping")))
})
}
}

case class ForgeRemoteAssociateAtlassianHostUserRequest[A](
hostUser: AtlassianHostUser,
request: ForgeRemoteContextRequest[A])
extends AtlassianHostUserRequest[A](request)
request: ForgeRemoteContextRequest[A]
) extends AtlassianHostUserRequest[A](request)

/**
* Action that associates an Atlassian host to an existing Forge Remote context and fails if no host
* could be found.
/** Action that associates an Atlassian host to an existing Forge Remote context
* and fails if no host could be found.
*
* Extracts the installation id from the given Forge Remote context and tries to find the host associated
* with the installation. If no Atlassian host could be found, this action will return a 400 Bad Request result.
* Extracts the installation id from the given Forge Remote context and tries
* to find the host associated with the installation. If no Atlassian host
* could be found, this action will return a 400 Bad Request result.
*/
case class AssociateAtlassianHostUserActionRefiner @Inject()(
case class AssociateAtlassianHostUserActionRefiner @Inject() (
override val hostRepository: AtlassianHostRepository,
override val forgeInstallationRepository: ForgeInstallationRepository)(
override implicit val executionContext: ExecutionContext)
override val forgeInstallationRepository: ForgeInstallationRepository
)(override implicit val executionContext: ExecutionContext)
extends AbstractAssociateAtlassianHostUserActionRefiner[
ForgeRemoteAssociateAtlassianHostUserRequest] {
ForgeRemoteAssociateAtlassianHostUserRequest
] {
override val logger: Logger = Logger(
classOf[AssociateAtlassianHostUserActionRefiner])
classOf[AssociateAtlassianHostUserActionRefiner]
)

override def hostSearchResultToActionResult[A](
maybeHost: Option[AtlassianHost],
request: ForgeRemoteContextRequest[A])
: Either[Result, ForgeRemoteAssociateAtlassianHostUserRequest[A]] =
request: ForgeRemoteContextRequest[A]
): Either[Result, ForgeRemoteAssociateAtlassianHostUserRequest[A]] =
maybeHost match {
case Some(host) =>
Right(
ForgeRemoteAssociateAtlassianHostUserRequest(
DefaultAtlassianHostUser(
host,
request.context.invocationContext.principal),
request))
request.context.invocationContext.principal
),
request
)
)
case None =>
val installationId =
request.context.invocationContext.app.installationId
logger.error(
s"Failed to associate Connect host to Forge Remote Compute invocation: No host for installation id $installationId found")
s"Failed to associate Connect host to Forge Remote Compute invocation: No host for installation id $installationId found"
)
Left(BadRequest(s"Missing Connect installation"))
}

object Implicits {

import scala.language.implicitConversions

/**
* Implicitly convert an instance of [[ForgeRemoteAssociateAtlassianHostUserRequest]] to an
* instance of AtlassianHostUser.
/** Implicitly convert an instance of
* [[ForgeRemoteAssociateAtlassianHostUserRequest]] to an instance of
* AtlassianHostUser.
*
* @param request Forge Remote associated host user request instance.
* @return Atlassian host user instance extracted from request.
* @param request
* Forge Remote associated host user request instance.
* @return
* Atlassian host user instance extracted from request.
*/
implicit def requestToHostUser(
implicit request: ForgeRemoteAssociateAtlassianHostUserRequest[_])
: AtlassianHostUser =
implicit def requestToHostUser(implicit
request: ForgeRemoteAssociateAtlassianHostUserRequest[_]
): AtlassianHostUser =
request.hostUser

}
Expand All @@ -111,33 +120,40 @@ case class AssociateAtlassianHostUserActionRefiner @Inject()(

case class ForgeRemoteAssociateMaybeAtlassianHostUserRequest[A](
hostUser: Option[AtlassianHostUser],
request: ForgeRemoteContextRequest[A])
extends WrappedRequest[A](request)
request: ForgeRemoteContextRequest[A]
) extends WrappedRequest[A](request)

/**
* Action that attempts to associate an Atlassian host to an existing Forge Remote context.
/** Action that attempts to associate an Atlassian host to an existing Forge
* Remote context.
*
* Extracts the installation id from the given Forge Remote context and tries to find the host associated
* with the installation. If no Atlassian host could be found, this action will succeed with a None option value.
* Extracts the installation id from the given Forge Remote context and tries
* to find the host associated with the installation. If no Atlassian host
* could be found, this action will succeed with a None option value.
*/
case class AssociateMaybeAtlassianHostUserActionRefiner @Inject()(
case class AssociateMaybeAtlassianHostUserActionRefiner @Inject() (
override val hostRepository: AtlassianHostRepository,
override val forgeInstallationRepository: ForgeInstallationRepository)(
override implicit val executionContext: ExecutionContext)
override val forgeInstallationRepository: ForgeInstallationRepository
)(override implicit val executionContext: ExecutionContext)
extends AbstractAssociateAtlassianHostUserActionRefiner[
ForgeRemoteAssociateMaybeAtlassianHostUserRequest] {
ForgeRemoteAssociateMaybeAtlassianHostUserRequest
] {
override val logger: Logger = Logger(
classOf[AssociateMaybeAtlassianHostUserActionRefiner])
classOf[AssociateMaybeAtlassianHostUserActionRefiner]
)

override def hostSearchResultToActionResult[A](
maybeHost: Option[AtlassianHost],
request: ForgeRemoteContextRequest[A])
: Either[Result, ForgeRemoteAssociateMaybeAtlassianHostUserRequest[A]] =
request: ForgeRemoteContextRequest[A]
): Either[Result, ForgeRemoteAssociateMaybeAtlassianHostUserRequest[A]] =
Right(
ForgeRemoteAssociateMaybeAtlassianHostUserRequest(
maybeHost.map(
DefaultAtlassianHostUser(
_,
request.context.invocationContext.principal)),
request))
request.context.invocationContext.principal
)
),
request
)
)
}
Loading