blob: 82cb27646d2ea9774484a00cab4dc85c7c844d1a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.gearpump.services.security.oauth2
import scala.collection.JavaConverters._
import scala.concurrent.Await
import scala.concurrent.duration._
import akka.actor.ActorSystem
import akka.http.scaladsl.model.HttpEntity.Strict
import akka.http.scaladsl.model.MediaTypes._
import akka.http.scaladsl.model.Uri.{Path, Query}
import akka.http.scaladsl.model._
import akka.http.scaladsl.testkit.ScalatestRouteTest
import com.typesafe.config.ConfigFactory
import org.scalatest.FlatSpec
import org.apache.gearpump.security.Authenticator
// NOTE: This cannot be removed!!!
import org.apache.gearpump.services.util.UpickleUtil._
import org.apache.gearpump.services.security.oauth2.GoogleOAuth2AuthenticatorSpec.MockGoogleAuthenticator
import org.apache.gearpump.services.security.oauth2.impl.GoogleOAuth2Authenticator
class GoogleOAuth2AuthenticatorSpec extends FlatSpec with ScalatestRouteTest {
implicit val actorSystem: ActorSystem = system
private val server = new MockOAuth2Server(system, null)
server.start()
private val serverHost = s"http://127.0.0.1:${server.port}"
val configMap = Map(
"class" -> "org.apache.gearpump.services.security.oauth2.impl.GoogleOAuth2Authenticator",
"callback" -> s"$serverHost/login/oauth2/google/callback",
"clientid" -> "170234147043-a1tag68jtq6ab4bi11jvsj7vbaqcmhkt.apps.googleusercontent.com",
"clientsecret" -> "ioeWLLDipz2S7aTDXym2-obe",
"default-userrole" -> "guest",
"icon" -> "/icons/google.png")
val configString = ConfigFactory.parseMap(configMap.asJava)
private lazy val google = {
val google = new MockGoogleAuthenticator(serverHost)
google.init(configString, system.dispatcher)
google
}
it should "generate the correct authorization request" in {
val parameters = Uri(google.getAuthorizationUrl()).query().toMap
assert(parameters("response_type") == "code")
assert(parameters("client_id") == configMap("clientid"))
assert(parameters("redirect_uri") == configMap("callback"))
assert(parameters("scope") == GoogleOAuth2Authenticator.Scope)
}
it should "authenticate the authorization code and return the correct profile" in {
val code = Map("code" -> "4/PME0pfxjiBA42SukR-OTGl7fpFzTWzvZPf1TbkpXL4M#")
val accessToken = "e2922002-0218-4513-a62d-1da2ba64ee4c"
val refreshToken = "eyJhbGciOiJSUzI1NiJ9.eyJqdGkiOiI2Nm"
val mail = "test@gearpump.apache.org"
def accessTokenEndpoint(request: HttpRequest): HttpResponse = {
assert(request.entity.contentType.mediaType.value == "application/x-www-form-urlencoded")
val body = request.entity.asInstanceOf[Strict].data.decodeString("UTF-8")
val form = Uri./.withQuery(Query(body)).query().toMap
assert(form("client_id") == configMap("clientid"))
assert(form("client_secret") == configMap("clientsecret"))
assert(form("grant_type") == "authorization_code")
assert(form("code") == code("code"))
assert(form("redirect_uri") == configMap("callback"))
assert(form("scope") == GoogleOAuth2Authenticator.Scope)
// scalastyle:off line.size.limit
val response =
s"""
|{
| "access_token": "$accessToken",
| "token_type": "Bearer",
| "expires_in": 3591,
| "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImY1NjQyYzY2MzdhYWQyOTJiOThlOGIwN2MwMzIxN2QwMzBmOTdkODkifQ.eyJpc3"
|}
""".stripMargin
// scalastyle:on line.size.limit
HttpResponse(entity = HttpEntity(ContentType(`application/json`), response))
}
def protectedResourceEndpoint(request: HttpRequest): HttpResponse = {
assert(request.getUri().query().get("access_token").get == accessToken)
val response =
s"""
|{
| "kind": "plus#person",
| "etag": "4OZ_Kt6ujOh1jaML_U6RM6APqoE/mZ57HcMOYXaNXYXS5XEGJ9yVsI8",
| "nickname": "gearpump",
| "gender": "female",
| "emails": [
| {
| "value": "$mail",
| "type": "account"
| }
| ]
| }
""".stripMargin
HttpResponse(entity = HttpEntity(ContentType(`application/json`), response))
}
server.requestHandler = (request: HttpRequest) => {
if (request.uri.path.startsWith(Path("/oauth2/v4/token"))) {
accessTokenEndpoint(request)
} else if (request.uri.path.startsWith(Path("/plus/v1/people/me"))) {
protectedResourceEndpoint(request)
} else {
fail("Unexpected access to " + request.uri.toString())
}
}
val userFuture = google.authenticate(code)
val user = Await.result(userFuture, 30.seconds)
assert(user.user == mail)
assert(user.permissionLevel == Authenticator.Guest.permissionLevel)
}
override def cleanUp(): Unit = {
server.stop()
google.close()
super.cleanUp()
}
}
object GoogleOAuth2AuthenticatorSpec {
class MockGoogleAuthenticator(host: String) extends GoogleOAuth2Authenticator {
protected override def authorizeUrl: String = {
super.authorizeUrl.replace("https://accounts.google.com", host)
}
protected override def accessTokenEndpoint: String = {
super.accessTokenEndpoint.replace("https://www.googleapis.com", host)
}
protected override def protectedResourceUrl: String = {
super.protectedResourceUrl.replace("https://www.googleapis.com", host)
}
}
}