Skip to content
This repository has been archived by the owner on Oct 24, 2022. It is now read-only.

Commit

Permalink
Merge c202a31 into 2c89e4f
Browse files Browse the repository at this point in the history
  • Loading branch information
JPrevost committed Mar 31, 2015
2 parents 2c89e4f + c202a31 commit 437d2d3
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 1 deletion.
12 changes: 12 additions & 0 deletions app/controllers/Application.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import scala.concurrent.Await
import akka.actor.Props

import java.util.Date
import java.util.UUID

import services.OAuth2
import play.api._
import play.api.data._
import play.api.data.Forms._
Expand All @@ -36,6 +38,16 @@ object Application extends Controller {
val indexer = Akka.system.actorOf(Props[workers.IndexWorker], name="indexer")
val conveyor = Akka.system.actorOf(Props[workers.ConveyorWorker], name="conveyor")

def login = Action { implicit request =>
val oauth2 = new OAuth2(Play.current)
val callbackUrl = services.routes.OAuth2.callback(None, None).absoluteURL()
val scope = "openid email profile" // this is used auth the ticket to provide info we need later
val state = UUID.randomUUID().toString // random confirmation string
val redirectUrl = oauth2.getAuthorizationUrl(callbackUrl, scope, state)
Ok(views.html.index("Your new application is ready.", redirectUrl)).
withSession("oauth-state" -> state)
}

def index = Action {
// Create dummy user if not allready there
val user = User.findById(1)
Expand Down
80 changes: 80 additions & 0 deletions app/services/OAuth2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package services

import play.api.Application
import play.api.Play
import play.api.http.{MimeTypes, HeaderNames}
import play.api.libs.ws.WS
import play.api.libs.ws._
import play.api.mvc.{Results, Action, Controller}

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

class OAuth2(application: Application) {
lazy val mitAuthId = application.configuration.getString("mit.client.id").get
lazy val mitAuthSecret = application.configuration.getString("mit.client.secret").get

def getAuthorizationUrl(redirectUri: String, scope: String, state: String): String = {
val baseUrl = application.configuration.getString("mit.redirect.url").get
baseUrl.format(mitAuthId, redirectUri, scope, state)
}

def getToken(code: String): Future[String] = {
val tokenResponse = WS.url("https://oidc.mit.edu/token")(application).
withQueryString("client_id" -> mitAuthId,
"client_secret" -> mitAuthSecret,
"code" -> code,
"grant_type" -> "authorization_code",
"redirect_uri" -> "http://localhost:9000/_oauth-callback").
withAuth(mitAuthId, mitAuthSecret, WSAuthScheme.BASIC).
withHeaders(HeaderNames.ACCEPT -> MimeTypes.JSON).
post(Results.EmptyContent())

tokenResponse.flatMap { response =>
(response.json \ "access_token").asOpt[String].fold(Future.failed[String](new IllegalStateException("Sod off!"))) { accessToken =>
Future.successful(accessToken)
}
}
}
}

object OAuth2 extends Controller {
lazy val oauth2 = new OAuth2(Play.current)

def callback(codeOpt: Option[String] = None, stateOpt: Option[String] = None) = Action.async { implicit request =>
println(codeOpt)
println(stateOpt)
println(request.session.get("oauth-state"))
println(request)
(for {
code <- codeOpt
state <- stateOpt
oauthState <- request.session.get("oauth-state")
} yield {
if (state == oauthState) {
oauth2.getToken(code).map { accessToken =>
Redirect(services.routes.OAuth2.success()).withSession("oauth-token" -> accessToken)
}.recover {
case ex: IllegalStateException => Unauthorized(ex.getMessage)
}
}
else {
Future.successful(BadRequest("Invalid mit login"))
}
}).getOrElse(Future.successful(BadRequest("No parameters supplied")))
}

def success() = Action.async { request =>
println("___oath token start___")
println(request.session.get("oauth-token"))
println("___oath token end___")
implicit val app = Play.current
request.session.get("oauth-token").fold(Future.successful(Unauthorized("No way Jose"))) { authToken =>
WS.url("https://oidc.mit.edu/userinfo").
withHeaders(HeaderNames.AUTHORIZATION -> ("Bearer " + s"$authToken")).
get().map { response =>
Ok(response.json)
}
}
}
}
6 changes: 6 additions & 0 deletions app/views/index.scala.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@(message: String, redirectUrl: String)

@layout.main("Login to SCOAP3 - TopicHub") {
<h2>Not Yet Authenticated</h2>
<a id="openid" href="@redirectUrl"><em><b>Log in with MITID!</b></em></a>
}
3 changes: 2 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ libraryDependencies ++= Seq(
"org.scalesxml" %% "scales-xml" % "0.6.0-M3",
"org.scalesxml" %% "scales-jaxen" % "0.6.0-M3" intransitive(),
"jaxen" % "jaxen" % "1.1.6" intransitive(),
"org.postgresql" % "postgresql" % "9.4-1200-jdbc41"
"org.postgresql" % "postgresql" % "9.4-1200-jdbc41",
"org.mockito" % "mockito-core" % "1.10.19" % "test"
)
7 changes: 7 additions & 0 deletions conf/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,10 @@ hub.index.url=${?BONSAI_URL}
hub.email.url="https://api.mailgun.net/v2/topichub.mailgun.org/messages"
hub.email.apikey="fake"
hub.email.apikey=${?MAILGUN_API_KEY}

mit.client.id="fake_key"
mit.client.secret="fake_secret"
mit.client.id=${?MIT_KEY}
mit.client.secret=${?MIT_SECRET}
mit.redirect.url="https://oidc.mit.edu/authorize?client_id=%s&redirect_uri=%s&scope=%s&state=%s&response_type=code"

5 changes: 5 additions & 0 deletions conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
# This file defines all application routes (Higher priority routes first)
# ~~~~

# OAuth2 Stuff
GET /_oauth-callback services.OAuth2.callback(code: Option[String], state: Option[String])
GET /_oauth-success services.OAuth2.success
GET /login controllers.Application.login

# Home and informational page
GET / controllers.Application.index
#GET /explain controllers.Application.explain
Expand Down
22 changes: 22 additions & 0 deletions test/ApplicationSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import org.junit.runner._

import play.api.test._
import play.api.test.Helpers._
import play.api.libs.json._

/**
* Add your spec here.
Expand All @@ -26,5 +27,26 @@ class ApplicationSpec extends Specification {
contentType(home) must beSome.which(_ == "text/html")
contentAsString(home) must contain ("Welcome to SCOAP")
}

"display a login screen" in new WithBrowser {
val action = route(FakeRequest(GET, "/login")).get
status(action) must equalTo(OK)
contentType(action) must beSome.which(_ == "text/html")
contentAsString(action) must contain ("Log in with MITID")
}

// "parse returned user json" in new WithBrowser {
// val action = route(
// FakeRequest(GET, "/_oauth-success")
// .withHeaders(CONTENT_TYPE -> "application/json")
// .withSession(("oauth-token", "asdffdsa"))
// .withBody("""
// {"sub":"asdffdsa","name":"Some M User","preferred_username":"suser","given_name":"Some","family_name":"User","middle_name":"M","email":"suser@example.edu","email_verified":true}
// """)
// ).get
// status(action) must equalTo(OK)
// contentType(action) must beSome.which(_ == "application/json")
// contentAsString(action) must contain ("Log in with MITID")
// }
}
}
28 changes: 28 additions & 0 deletions test/AuthenticationSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import org.specs2.mutable._
import org.specs2.runner._
import org.junit.runner._
import org.specs2.mock._

import play.api.test._
import play.api.test.Helpers._
import org.fest.assertions.Assertions.assertThat

/**
* add your integration spec here.
* An integration test will fire up a whole play application in a real (or headless) browser
*/
class AuthenticationSpec extends Specification with Mockito {
"Application" should {
"display a login screen" in new WithBrowser {
browser.goTo("http://localhost:" + port + "/login")
browser.pageSource must contain("Log in with MITID")
assertThat(browser.title()).isEqualTo("Login to SCOAP3 - TopicHub")
}

"redirect when login is clicked" in new WithBrowser {
browser.goTo("http://localhost:" + port + "/login")
browser.$("#openid").click();
assertThat(browser.title()).isEqualTo("MIT OpenID Connect Pilot - Log In")
}
}
}

0 comments on commit 437d2d3

Please sign in to comment.