Skip to content
This repository has been archived by the owner on Sep 12, 2021. It is now read-only.

Commit

Permalink
Merge pull request #118 from akkie/master
Browse files Browse the repository at this point in the history
Test for GitHub provider
  • Loading branch information
akkie committed Mar 2, 2014
2 parents b642eac + a59f67a commit 86bb30a
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ abstract class OAuth2Provider(
extends SocialProvider[OAuth2Info]
with Logger {

/**
* A list with headers to send to the API.
*/
protected val headers: Seq[(String, String)] = Seq()

/**
* Converts the JSON into a [[com.mohiva.play.silhouette.core.providers.OAuth2Info]] object.
*/
Expand Down Expand Up @@ -102,7 +107,7 @@ abstract class OAuth2Provider(
* @return The info containing the access token.
*/
protected def getAccessToken(code: String): Future[OAuth2Info] = {
httpLayer.url(settings.accessTokenURL).post(Map(
httpLayer.url(settings.accessTokenURL).withHeaders(headers: _*).post(Map(
ClientID -> Seq(settings.clientID),
ClientSecret -> Seq(settings.clientSecret),
GrantType -> Seq(AuthorizationCode),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,14 @@
*/
package com.mohiva.play.silhouette.core.providers.oauth2

import play.api.libs.ws.Response
import play.api.http.HeaderNames
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import com.mohiva.play.silhouette.core._
import com.mohiva.play.silhouette.core.utils.{ HTTPLayer, CacheLayer }
import com.mohiva.play.silhouette.core.providers.{ SocialProfile, OAuth2Info, OAuth2Settings, OAuth2Provider }
import com.mohiva.play.silhouette.core.services.AuthInfoService
import GitHubProvider._
import OAuth2Provider._

/**
* A GitHub OAuth2 Provider.
Expand All @@ -36,6 +35,7 @@ import OAuth2Provider._
* @param cacheLayer The cache layer implementation.
* @param httpLayer The HTTP layer implementation.
* @param settings The provider settings.
* @see https://developer.github.com/v3/oauth/
*/
class GitHubProvider(
protected val authInfoService: AuthInfoService,
Expand All @@ -44,6 +44,16 @@ class GitHubProvider(
settings: OAuth2Settings)
extends OAuth2Provider(settings, cacheLayer, httpLayer) {

/**
* A list with headers to send to the API.
*
* Without defining the accept header, the response will take the following form:
* access_token=e72e16c7e42f292c6912e7710c838347ae178b4a&scope=user%2Cgist&token_type=bearer
*
* @see https://developer.github.com/v3/oauth/#response
*/
override protected val headers = Seq(HeaderNames.ACCEPT -> "application/json")

/**
* Gets the provider ID.
*
Expand All @@ -61,7 +71,10 @@ class GitHubProvider(
httpLayer.url(API.format(authInfo.accessToken)).get().map { response =>
val json = response.json
(json \ Message).asOpt[String] match {
case Some(msg) => throw new AuthenticationException(SpecifiedProfileError.format(id, msg))
case Some(msg) =>
val docURL = (json \ DocURL).asOpt[String]

throw new AuthenticationException(SpecifiedProfileError.format(id, msg, docURL))
case _ =>
val userID = (json \ ID).as[Int]
val fullName = (json \ Name).asOpt[String]
Expand All @@ -74,28 +87,9 @@ class GitHubProvider(
avatarURL = avatarUrl,
email = email)
}
}.recover { case e => throw new AuthenticationException(UnspecifiedProfileError.format(id), e) }
}

/**
* Builds the OAuth2 info.
*
* @param response The response from the provider.
* @return The OAuth2 info.
*/
override protected def buildInfo(response: Response): OAuth2Info = {
val values: Map[String, String] = response.body.split("&").toList
.map(_.split("=")).withFilter(_.size == 2)
.map(r => (r(0), r(1)))(collection.breakOut)

values.get(AccessToken) match {
case Some(accessToken) => OAuth2Info(
accessToken,
values.get(TokenType),
values.get(ExpiresIn).map(_.toInt),
values.get(RefreshToken)
)
case _ => throw new AuthenticationException(MissingAccessToken.format(id))
}.recover {
case e if !e.isInstanceOf[AuthenticationException] =>
throw new AuthenticationException(UnspecifiedProfileError.format(id), e)
}
}
}
Expand All @@ -109,15 +103,15 @@ object GitHubProvider {
* The error messages.
*/
val UnspecifiedProfileError = "[Silhouette][%s] Error retrieving profile information"
val SpecifiedProfileError = "[Silhouette][%s] Error retrieving profile information. Error message: %s"
val MissingAccessToken = "[Silhouette][%s] Did not get access token"
val SpecifiedProfileError = "[Silhouette][%s] Error retrieving profile information. Error message: %s, doc URL: %s"

/**
* The Foursquare constants.
*/
val GitHub = "github"
val API = "https://api.github.com/user?access_token=%s"
val Message = "message"
val DocURL = "documentation_url"
val ID = "id"
val Name = "name"
val AvatarURL = "avatar_url"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ abstract class OAuth2ProviderSpec extends PlaySpecification with Mockito with Js
RedirectURI -> Seq(c.oAuthSettings.redirectURL)) ++ c.oAuthSettings.accessTokenParams.mapValues(Seq(_))
implicit val req = FakeRequest(GET, "?" + Code + "=my.code&" + State + "=" + state).withSession(CacheKey -> cacheID)

requestHolder.withHeaders(any) returns requestHolder

// We must use this neat trick here because it isn't possible to check the post call with a verification,
// because of the implicit params needed for the post call. On the other hand we can test it in the abstract
// spec, because we throw an exception in both cases which stops the test once the post method was called.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class FacebookProviderSpec extends OAuth2ProviderSpec {
val response = mock[Response]
implicit val req = FakeRequest(GET, "?" + Code + "=my.code&" + State + "=" + state).withSession(CacheKey -> cacheID)
response.body returns ""
requestHolder.withHeaders(any) returns requestHolder
requestHolder.post[Map[String, Seq[String]]](any)(any, any) returns Future.successful(response)
cacheLayer.get[String](cacheID) returns Future.successful(Some(state))
httpLayer.url(oAuthSettings.accessTokenURL) returns requestHolder
Expand All @@ -55,6 +56,7 @@ class FacebookProviderSpec extends OAuth2ProviderSpec {
implicit val req = FakeRequest(GET, "?" + Code + "=my.code&" + State + "=" + state).withSession(CacheKey -> cacheID)
response.body returns AccessToken + "=my.access.token&" + Expires + "=1"
response.json returns Helper.loadJson("providers/oauth2/facebook.error.json")
requestHolder.withHeaders(any) returns requestHolder
requestHolder.post[Map[String, Seq[String]]](any)(any, any) returns Future.successful(response)
requestHolder.get() returns Future.successful(response)
cacheLayer.get[String](cacheID) returns Future.successful(Some(state))
Expand All @@ -78,6 +80,7 @@ class FacebookProviderSpec extends OAuth2ProviderSpec {
implicit val req = FakeRequest(GET, "?" + Code + "=my.code&" + State + "=" + state).withSession(CacheKey -> cacheID)
response.body returns AccessToken + "=my.access.token&" + Expires + "=1"
response.json throws new RuntimeException("")
requestHolder.withHeaders(any) returns requestHolder
requestHolder.post[Map[String, Seq[String]]](any)(any, any) returns Future.successful(response)
requestHolder.get() returns Future.successful(response)
cacheLayer.get[String](cacheID) returns Future.successful(Some(state))
Expand All @@ -97,6 +100,7 @@ class FacebookProviderSpec extends OAuth2ProviderSpec {
implicit val req = FakeRequest(GET, "?" + Code + "=my.code&" + State + "=" + state).withSession(CacheKey -> cacheID)
response.body returns AccessToken + "=my.access.token&" + Expires + "=1"
response.json returns Helper.loadJson("providers/oauth2/facebook.success.json")
requestHolder.withHeaders(any) returns requestHolder
requestHolder.post[Map[String, Seq[String]]](any)(any, any) returns Future.successful(response)
requestHolder.get() returns Future.successful(response)
cacheLayer.get[String](cacheID) returns Future.successful(Some(state))
Expand Down Expand Up @@ -124,6 +128,7 @@ class FacebookProviderSpec extends OAuth2ProviderSpec {
implicit val req = FakeRequest(GET, "?" + Code + "=my.code&" + State + "=" + state).withSession(CacheKey -> cacheID)
response.body returns AccessToken + "=my.access.token&" + Expires + "=1"
response.json returns Helper.loadJson("providers/oauth2/facebook.success.json")
requestHolder.withHeaders(any) returns requestHolder
requestHolder.post[Map[String, Seq[String]]](any)(any, any) returns Future.successful(response)
requestHolder.get() returns Future.successful(response)
cacheLayer.get[String](cacheID) returns Future.successful(Some(state))
Expand All @@ -146,6 +151,7 @@ class FacebookProviderSpec extends OAuth2ProviderSpec {
implicit val req = FakeRequest(GET, "?" + Code + "=my.code&" + State + "=" + state).withSession(CacheKey -> cacheID)
response.body returns AccessToken + "=my.access.token"
response.json returns Helper.loadJson("providers/oauth2/facebook.success.json")
requestHolder.withHeaders(any) returns requestHolder
requestHolder.post[Map[String, Seq[String]]](any)(any, any) returns Future.successful(response)
requestHolder.get() returns Future.successful(response)
cacheLayer.get[String](cacheID) returns Future.successful(Some(state))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class FoursquareProviderSpec extends OAuth2ProviderSpec {
val response = mock[Response]
implicit val req = FakeRequest(GET, "?" + Code + "=my.code&" + State + "=" + state).withSession(CacheKey -> cacheID)
response.json returns Json.obj()
requestHolder.withHeaders(any) returns requestHolder
requestHolder.post[Map[String, Seq[String]]](any)(any, any) returns Future.successful(response)
cacheLayer.get[String](cacheID) returns Future.successful(Some(state))
httpLayer.url(oAuthSettings.accessTokenURL) returns requestHolder
Expand All @@ -55,6 +56,7 @@ class FoursquareProviderSpec extends OAuth2ProviderSpec {
val response = mock[Response]
implicit val req = FakeRequest(GET, "?" + Code + "=my.code&" + State + "=" + state).withSession(CacheKey -> cacheID)
response.json returns oAuthInfo thenReturns Helper.loadJson("providers/oauth2/foursquare.error.json")
requestHolder.withHeaders(any) returns requestHolder
requestHolder.post[Map[String, Seq[String]]](any)(any, any) returns Future.successful(response)
requestHolder.get() returns Future.successful(response)
cacheLayer.get[String](cacheID) returns Future.successful(Some(state))
Expand All @@ -77,6 +79,7 @@ class FoursquareProviderSpec extends OAuth2ProviderSpec {
val response = mock[Response]
implicit val req = FakeRequest(GET, "?" + Code + "=my.code&" + State + "=" + state).withSession(CacheKey -> cacheID)
response.json returns oAuthInfo thenThrows new RuntimeException("")
requestHolder.withHeaders(any) returns requestHolder
requestHolder.post[Map[String, Seq[String]]](any)(any, any) returns Future.successful(response)
requestHolder.get() returns Future.successful(response)
cacheLayer.get[String](cacheID) returns Future.successful(Some(state))
Expand All @@ -95,6 +98,7 @@ class FoursquareProviderSpec extends OAuth2ProviderSpec {
val response = mock[Response]
implicit val req = FakeRequest(GET, "?" + Code + "=my.code&" + State + "=" + state).withSession(CacheKey -> cacheID)
response.json returns oAuthInfo thenReturns Helper.loadJson("providers/oauth2/foursquare.success.json")
requestHolder.withHeaders(any) returns requestHolder
requestHolder.post[Map[String, Seq[String]]](any)(any, any) returns Future.successful(response)
requestHolder.get() returns Future.successful(response)
cacheLayer.get[String](cacheID) returns Future.successful(Some(state))
Expand All @@ -120,6 +124,7 @@ class FoursquareProviderSpec extends OAuth2ProviderSpec {
val response = mock[Response]
implicit val req = FakeRequest(GET, "?" + Code + "=my.code&" + State + "=" + state).withSession(CacheKey -> cacheID)
response.json returns oAuthInfo thenReturns Helper.loadJson("providers/oauth2/foursquare.deprecated.json")
requestHolder.withHeaders(any) returns requestHolder
requestHolder.post[Map[String, Seq[String]]](any)(any, any) returns Future.successful(response)
requestHolder.get() returns Future.successful(response)
cacheLayer.get[String](cacheID) returns Future.successful(Some(state))
Expand Down Expand Up @@ -153,6 +158,7 @@ class FoursquareProviderSpec extends OAuth2ProviderSpec {
)

response.json returns oAuthInfo thenReturns Helper.loadJson("providers/oauth2/foursquare.success.json")
requestHolder.withHeaders(any) returns requestHolder
requestHolder.post[Map[String, Seq[String]]](any)(any, any) returns Future.successful(response)
requestHolder.get() returns Future.successful(response)
cacheLayer.get[String](cacheID) returns Future.successful(Some(state))
Expand Down Expand Up @@ -186,6 +192,7 @@ class FoursquareProviderSpec extends OAuth2ProviderSpec {
)

response.json returns oAuthInfo thenReturns Helper.loadJson("providers/oauth2/foursquare.success.json")
requestHolder.withHeaders(any) returns requestHolder
requestHolder.post[Map[String, Seq[String]]](any)(any, any) returns Future.successful(response)
requestHolder.get() returns Future.successful(response)
cacheLayer.get[String](cacheID) returns Future.successful(Some(state))
Expand Down

0 comments on commit 86bb30a

Please sign in to comment.