Skip to content

Commit

Permalink
Merge pull request #573 from dataswift/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
dataswifty authored Dec 28, 2021
2 parents 75461c2 + 684d227 commit 339b039
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 22 deletions.
32 changes: 16 additions & 16 deletions hat/app/org/hatdex/hat/api/controllers/Authentication.scala
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,8 @@ class Authentication @Inject() (
)
if (email == request.dynamicEnvironment.ownerEmail)
// Find the specific user who is the owner.
userService.listUsers
userService
.listUsers()
.map(_.find(_.roles.contains(Owner())))
.flatMap {
case Some(_) =>
Expand Down Expand Up @@ -343,10 +344,11 @@ class Authentication @Inject() (
// Token was found, is not signup nor expired
case Some(token) if !token.isSignUp && !token.isExpired =>
// Token.email matches the dynamicEnv (what is this)
if (token.email == request.dynamicEnvironment.ownerEmail)
if (token.email == request.dynamicEnvironment.ownerEmail) {
// Find the users with the owner role
// ???: Why not using the email
userService.listUsers
userService
.listUsers()
.map(_.find(_.roles.contains(Owner())))
.flatMap {
case Some(user) =>
Expand Down Expand Up @@ -378,7 +380,7 @@ class Authentication @Inject() (
case None =>
Future.successful(noUserMatchingToken)
}
else
} else
Future.successful(onlyHatOwnerCanReset)
case Some(_) =>
tokenService.consume(tokenId)
Expand All @@ -400,15 +402,12 @@ class Authentication @Inject() (
val email = request.dynamicEnvironment.ownerEmail
val response = Ok(Json.toJson(SuccessResponse("You will shortly receive an email with claim instructions")))

logger.info("Handling verification request")
// (email, applicationId) in the body
// Look up the application (Is this in the HAT itself? Not DEX)
if (claimHatRequest.email == email)
userService.listUsers
userService
.listUsers()
.map(_.find(u => (u.roles.contains(Owner()) && !(u.roles.contains(Verified("email"))))))
.flatMap {
case Some(user) =>
logger.info("User found")
val eventualClaimContext = for {
maybeApplication <- applicationsService
.applicationStatus()(request.dynamicEnvironment, user, request)
Expand All @@ -422,7 +421,7 @@ class Authentication @Inject() (
case (app, token) =>
val maybeSetupUrl: Option[String] = app.application.setup match {
case setupInfo: ApplicationSetup.External =>
setupInfo.validRedirectUris.find(_ == claimHatRequest.redirectUri)
setupInfo.validRedirectUris.find(x => claimHatRequest.redirectUri.startsWith(x))
case _ =>
None
}
Expand Down Expand Up @@ -469,7 +468,8 @@ class Authentication @Inject() (
tokenService.retrieve(verificationToken).flatMap {
case Some(token)
if token.isSignUp && !token.isExpired && token.email == request.dynamicEnvironment.ownerEmail =>
userService.listUsers
userService
.listUsers()
.map(_.find(u => (u.roles.contains(Owner()) && !(u.roles.contains(Verified("email"))))))
.flatMap {
case Some(user) =>
Expand Down Expand Up @@ -582,17 +582,17 @@ class Authentication @Inject() (
* Generate email verification string
*/
private def emailVerificationLink(
host: String,
token: String,
verificationOptions: EmailVerificationOptions): String = {
host: String,
token: String,
verificationOptions: EmailVerificationOptions): String = {
logger.info("Creating email verification link")
s"$emailScheme$host/auth/verify-email/$token?${verificationOptions.asQueryParameters}"
}

// TODO: add reset options support
private def passwordResetLink(
host: String,
token: String): String =
host: String,
token: String): String =
s"$emailScheme$host/auth/change-password/$token"

// private def roleMatcher(rolesToMatch: Seq[UserRole], rolesRequired: Seq[UserRole]): Boolean = {
Expand Down
150 changes: 146 additions & 4 deletions hat/test/org/hatdex/hat/api/HATTestContext.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import io.dataswift.models.hat.{ DataCredit, DataDebitOwner, Owner }
import net.codingwell.scalaguice.ScalaModule
import org.hatdex.hat.api.service._
import org.hatdex.hat.api.service.applications.{ TestApplicationProvider, TrustedApplicationProvider }
//import io.dataswift.models.hat.applications._
import org.hatdex.hat.authentication.HatApiAuthEnvironment
import org.hatdex.hat.authentication.models.HatUser
import org.hatdex.hat.dal.HatDbSchemaMigration
Expand All @@ -50,13 +51,27 @@ import org.scalatestplus.mockito.MockitoSugar
import play.api.http.HttpErrorHandler
import play.api.i18n.{ Lang, MessagesApi }
import play.api.inject.guice.GuiceApplicationBuilder
import play.api.{ Application, Configuration, Logger }
import play.api.{ Application => PlayApplication, Configuration, Logger }

import java.io.StringReader
import java.util.UUID
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.concurrent.{ Await, Future }
import io.dataswift.models.hat.FormattedText
import org.joda.time.{ DateTime, LocalDateTime }
import io.dataswift.models.hat.Drawable
import io.dataswift.models.hat.UserRole
import io.dataswift.models.hat.applications.ApplicationStatus
import io.dataswift.models.hat.applications.ApplicationSetup
import io.dataswift.models.hat.applications.ApplicationPermissions
import io.dataswift.models.hat.applications.ApplicationDeveloper
import io.dataswift.models.hat.applications.ApplicationInfo
import io.dataswift.models.hat.applications.Version
import io.dataswift.models.hat.applications.ApplicationKind
import io.dataswift.models.hat.applications.Application
import io.dataswift.models.hat.applications.DataFeedItem
import io.dataswift.models.hat.applications.ApplicationGraphics

abstract class HATTestContext extends PostgresqlSpec with MockitoSugar with BeforeAndAfter {

Expand Down Expand Up @@ -191,8 +206,13 @@ mO9kGhALaD5okBcI/VuAQiFvBXdK0ii/nVcBApXEu47PG4oYUgPI
Seq(DataCredit(""), DataCredit("namespace")),
enabled = true
)

implicit lazy val environment: Environment[HatApiAuthEnvironment] = FakeEnvironment[HatApiAuthEnvironment](
Seq(owner.loginInfo -> owner, dataDebitUser.loginInfo -> dataDebitUser, dataCreditUser.loginInfo -> dataCreditUser),
Seq(
owner.loginInfo -> owner,
dataDebitUser.loginInfo -> dataDebitUser,
dataCreditUser.loginInfo -> dataCreditUser
),
hatServer
)

Expand All @@ -204,6 +224,128 @@ mO9kGhALaD5okBcI/VuAQiFvBXdK0ii/nVcBApXEu47PG4oYUgPI
"evolutions/hat-database-schema/14_newHat.sql"
)

// Application
val kindAuth: ApplicationKind.Kind = ApplicationKind.App(
url = "https://itunes.apple.com/gb/app/notables/id1338778866?mt=8",
iosUrl = Some("https://itunes.apple.com/gb/app/notables/id1338778866?mt=8"),
androidUrl = None
)

val descriptionAuth: FormattedText = FormattedText(
text = "",
markdown = None,
html = None
)

val dataPreviewAuth: Seq[DataFeedItem] = List.empty

val graphicsAuth: ApplicationGraphics = ApplicationGraphics(
banner = Drawable(normal = "", small = None, large = None, xlarge = None),
logo = Drawable(
normal = "",
small = None,
large = None,
xlarge = None
),
screenshots = List(
Drawable(
normal = "",
large = None,
small = None,
xlarge = None
),
Drawable(
normal = "",
large = None,
small = None,
xlarge = None
),
Drawable(
normal = "",
large = None,
small = None,
xlarge = None
)
)
)

val appInfoAuth: ApplicationInfo = ApplicationInfo(
version = Version(1, 0, 0),
updateNotes = None,
published = true,
name = "Notables",
headline = "All your words",
description = descriptionAuth,
hmiDescription = None,
termsUrl = "https://example.com/terms",
privacyPolicyUrl = None,
dataUsePurpose = "Data Will be processed by Notables for the following purpose...",
supportContact = "[email protected]",
rating = None,
dataPreview = dataPreviewAuth,
graphics = graphicsAuth,
primaryColor = None,
callbackUrl = None
)

val developerAuth: ApplicationDeveloper = ApplicationDeveloper(
id = "dex",
name = "HATDeX",
url = "https://hatdex.org",
country = Some("United Kingdom"),
logo = Some(
Drawable(
normal =
"https://s3-eu-west-1.amazonaws.com/hubofallthings-com-dexservi-dexpublicassetsbucket-kex8hb7fsdge/notablesapp/0x0ss.png",
small = None,
large = None,
xlarge = None
)
)
)

val permissionsAuth: ApplicationPermissions = ApplicationPermissions(
rolesGranted = List(
UserRole.userRoleDeserialize("namespacewrite", Some("rumpel")),
UserRole.userRoleDeserialize("namespaceread", Some("rumpel")),
UserRole.userRoleDeserialize("datadebit", Some("app-notables"))
),
dataRetrieved = None,
dataRequired = None
)

val setupAuth: ApplicationSetup.External = ApplicationSetup.External(
url = None,
iosUrl = None,
androidUrl = None,
testingUrl = None,
validRedirectUris = List("https://api.onezero-me.com/"),
deauthorizeCallbackUrl = None,
onboarding = None,
preferences = None,
dependencies = None
)

val appStatusAuth: ApplicationStatus.Internal = ApplicationStatus.Internal(
compatibility = Version(1, 0, 0),
dataPreviewEndpoint = None,
staticDataPreviewEndpoint = None,
recentDataCheckEndpoint = Some("/rumpel/notablesv1"),
versionReleaseDate = DateTime.parse("2018-07-24T12:00:00")
)

val notablesAppAuth: Application =
Application(
id = "notablesAuth",
kind = kindAuth,
info = appInfoAuth,
developer = developerAuth,
permissions = permissionsAuth,
dependencies = None,
setup = setupAuth,
status = appStatusAuth
)

def databaseReady(): Future[Unit] = {
val schemaMigration = new HatDbSchemaMigration(application.configuration, db, global)
schemaMigration
Expand Down Expand Up @@ -253,10 +395,10 @@ mO9kGhALaD5okBcI/VuAQiFvBXdK0ii/nVcBApXEu47PG4oYUgPI

class EmptyAppProviderModule extends ScalaModule {
override def configure(): Unit =
bind[TrustedApplicationProvider].toInstance(new TestApplicationProvider(Seq()))
bind[TrustedApplicationProvider].toInstance(new TestApplicationProvider(Seq(notablesAppAuth)))
}

lazy val application: Application =
lazy val application: PlayApplication =
new GuiceApplicationBuilder()
.configure(conf)
.overrides(new IntegrationSpecModule)
Expand Down
32 changes: 30 additions & 2 deletions hat/test/org/hatdex/hat/api/controllers/AuthenticationSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import java.util.UUID
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.concurrent.{ Await, Future }
import org.hatdex.hat.phata.models.ApiVerificationRequest

class AuthenticationSpec extends AuthenticationContext {

Expand Down Expand Up @@ -190,7 +191,6 @@ class AuthenticationSpec extends AuthenticationContext {
val result = maybeResult.get

status(result) must equal(BAD_REQUEST)
// contentType(result) must beSome("application/json")
(contentAsJson(result) \ "error").as[String] must equal("Bad Request")
}

Expand Down Expand Up @@ -228,7 +228,6 @@ class AuthenticationSpec extends AuthenticationContext {
val result: Future[Result] = Helpers.call(controller.handleForgotPassword, request)

status(result) must equal(OK)
//there was one(mockMailer).passwordReset(any[String], any[String])(any[MessagesApi], any[Lang], any[HatServer])
}

"The `handleResetPassword` method" should "Return status 401 if no such token exists" in {
Expand Down Expand Up @@ -317,6 +316,29 @@ class AuthenticationSpec extends AuthenticationContext {
status(result) must equal(UNAUTHORIZED)
(contentAsJson(result) \ "cause").as[String] must equal("No user matching token")
}

"The `handleVerificationRequest` method" should "flexibly handle matching redirectURL" in {
val request = FakeRequest("POST", "http://hat.hubofallthings.net")
.withAuthenticator(owner.loginInfo)
.withJsonBody(Json.toJson(apiVerificationRequestMatching))

val controller = application.injector.instanceOf[Authentication]
val result: Future[Result] = Helpers.call(controller.handleVerificationRequest(None), request)

status(result) must equal(OK)
}

it should "not match a different redirect URL base" in {
val request = FakeRequest("POST", "http://hat.hubofallthings.net")
.withAuthenticator(owner.loginInfo)
.withJsonBody(Json.toJson(apiVerificationRequestNotMatching))

val controller = application.injector.instanceOf[Authentication]
val result: Future[Result] = Helpers.call(controller.handleVerificationRequest(None), request)

status(result) must equal(OK)
}

}

class AuthenticationContext extends HATTestContext {
Expand All @@ -332,4 +354,10 @@ class AuthenticationContext extends HATTestContext {

val passwordValidationIncorrect: ApiValidationRequest = ApiValidationRequest("[email protected]", "appId")
val passwordValidationOwner: ApiValidationRequest = ApiValidationRequest("[email protected]", "appId")

val apiVerificationRequestMatching: ApiVerificationRequest =
ApiVerificationRequest("notablesAuth", "[email protected]", "https://api.onezero-me.com/auth/redirectfromhat/scoring/facebook/avante/aruz3sc/8e4df03ffdffe0034da0c9008813f2a7:fd43cdc55b52fa31b47fa797ceef5424d52d269f5c3ed8b82858112c1a098a56a6689e101e5c8d499bc79eb28fe61b4d32ed2a9686727bd6f8f93e987b7a4e79")

val apiVerificationRequestNotMatching: ApiVerificationRequest =
ApiVerificationRequest("notablesAuth", "[email protected]", "https://api2.onezero-me.com/auth/redirectfromhat/scoring/facebook/avante/aruz3sc/8e4df03ffdffe0034da0c9008813f2a7:fd43cdc55b52fa31b47fa797ceef5424d52d269f5c3ed8b82858112c1a098a56a6689e101e5c8d499bc79eb28fe61b4d32ed2a9686727bd6f8f93e987b7a4e79")
}

0 comments on commit 339b039

Please sign in to comment.