alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Play Framework/Scala example source code file (DiscoverySpec.scala)

This example Play Framework source code file (DiscoverySpec.scala) is included in my "Source Code Warehouse" project. The intent of this project is to help you more easily find Play Framework (and Scala) source code examples by using tags.

All credit for the original source code belongs to Play Framework; I'm just trying to make examples easier to find. (For my Scala work, see my Scala examples and tutorials.)

Play Framework tags/keywords

api, concurrent, discovery, identifier, lib, library, ok, openid, play, play framework, some, string, url, wsmock, xrds, yadis

The DiscoverySpec.scala Play Framework example source code

/*
 * Copyright (C) 2009-2013 Typesafe Inc. <http://www.typesafe.com>
 */
package play.api.libs.openid

import org.specs2.mutable.Specification
import org.specs2.mock._
import java.net.URL
import play.api.http.HeaderNames
import play.api.http.Status._
import scala.concurrent.duration.Duration
import scala.concurrent.Await
import java.util.concurrent.TimeUnit
import org.specs2.control.NoStackTraceFilter
import play.api.libs.ws._

object DiscoverySpec extends Specification with Mockito {

  val dur = Duration(10, TimeUnit.SECONDS)

  private def normalize(s: String) = {
    val ws = new WSMock
    val discovery = new Discovery(ws)
    discovery.normalizeIdentifier(s)
  }

  "Discovery normalization" should {
    // Adapted from org.openid4java.discovery.NormalizationTest
    // Original authors: Marius Scurtescu, Johnny Bufu
    "normalize uppercase URL identifiers" in {
      normalize("HTTP://EXAMPLE.COM/") must be equalTo "http://example.com/"
    }
    "normalize percent signs" in {
      normalize("HTTP://EXAMPLE.COM/%63") must be equalTo "http://example.com/c"
    }
    "normalize port" in {
      normalize("HTTP://EXAMPLE.COM:80/A/B?Q=Z#") must be equalTo "http://example.com/A/B?Q=Z"
      normalize("https://example.com:443") must be equalTo "https://example.com/"
    }
    "normalize paths" in {
      normalize("http://example.com//a/./b/../b/c/") must be equalTo "http://example.com/a/b/c/"
      normalize("http://example.com?bla") must be equalTo "http://example.com/?bla"
    }
  }

  "Discovery normalization" should {
    // http://openid.net/specs/openid-authentication-2_0.html#normalization_example
    "normalize URLs according to he OpenID example in the spec" in {
      "A URI with a missing scheme is normalized to a http URI" in {
        normalize("example.com") must be equalTo "http://example.com/"
      }
      "An empty path component is normalized to a slash" in {
        normalize("http://example.com") must be equalTo "http://example.com/"
      }
      "https URIs remain https URIs" in {
        normalize("https://example.com/") must be equalTo "https://example.com/"
      }
      "No trailing slash is added to non-empty path components" in {
        normalize("http://example.com/user") must be equalTo "http://example.com/user"
      }
      "Trailing slashes are preserved on non-empty path components" in {
        normalize("http://example.com/user/") must be equalTo "http://example.com/user/"
      }
      "Trailing slashes are preserved when the path is empty" in {
        normalize("http://example.com/") must be equalTo "http://example.com/"
      }
    }

    // Spec 7.2 - Normalization
    "normalize URLs according to he OpenID 2.0 spec" in {
      // XRIs are currently not supported
      // 1. If the user's input starts with the "xri://" prefix, it MUST be stripped off, so that XRIs are used in the canonical form.
      // 2. If the first character of the resulting string is an XRI Global Context Symbol ("=", "@", "+", "$", "!") or "(", as defined in Section 2.2.1 of [XRI_Syntax_2.0], then the input SHOULD be treated as an XRI.
      // XRI is currently not supported

      "The input SHOULD be treated as an http URL; if it does not include a \"http\" or \"https\" scheme, the Identifier MUST be prefixed with the string \"http://\"." in {
        normalize("example.com") must be equalTo "http://example.com/"
      }

      "If the URL contains a fragment part, it MUST be stripped off together with the fragment delimiter character \"#\"." in {
        normalize("example.com#thefragment") must be equalTo "http://example.com/"
        normalize("example.com/#thefragment") must be equalTo "http://example.com/"
        normalize("http://example.com#thefragment") must be equalTo "http://example.com/"
        normalize("https://example.com/#thefragment") must be equalTo "https://example.com/"
      }
    }
  }

  "The XRDS resolver" should {

    import Discovery._

    "parse a Google account response" in {
      val response = mock[WSResponse]
      response.header(HeaderNames.CONTENT_TYPE) returns Some("application/xrds+xml")
      response.xml returns scala.xml.XML.loadString(readFixture("discovery/xrds/google-account-response.xml"))
      val maybeOpenIdServer = new XrdsResolver().resolve(response)
      maybeOpenIdServer.map(_.url) must beSome("https://www.google.com/accounts/o8/ud")
    }

    "parse an XRDS response with a single Service element" in {
      val response = mock[WSResponse]
      response.header(HeaderNames.CONTENT_TYPE) returns Some("application/xrds+xml")
      response.xml returns scala.xml.XML.loadString(readFixture("discovery/xrds/simple-op.xml"))
      val maybeOpenIdServer = new XrdsResolver().resolve(response)
      maybeOpenIdServer.map(_.url) must beSome("https://www.google.com/a/example.com/o8/ud?be=o8")
    }

    "parse an XRDS response with multiple Service elements" in {
      val response = mock[WSResponse]
      response.header(HeaderNames.CONTENT_TYPE) returns Some("application/xrds+xml")
      response.xml returns scala.xml.XML.loadString(readFixture("discovery/xrds/multi-service.xml"))
      val maybeOpenIdServer = new XrdsResolver().resolve(response)
      maybeOpenIdServer.map(_.url) must beSome("http://www.myopenid.com/server")
    }

    // See 7.3.2.2.  Extracting Authentication Data
    "return the OP Identifier over the Claimed Identifier if both are present" in {
      val response = mock[WSResponse]
      response.header(HeaderNames.CONTENT_TYPE) returns Some("application/xrds+xml")
      response.xml returns scala.xml.XML.loadString(readFixture("discovery/xrds/multi-service-with-op-and-claimed-id-service.xml"))
      val maybeOpenIdServer = new XrdsResolver().resolve(response)
      maybeOpenIdServer.map(_.url) must beSome("http://openidprovider-opid.example.com")
    }

    "extract and use OpenID Authentication 1.0 service elements from XRDS documents, if Yadis succeeds on an URL Identifier." in {
      val response = mock[WSResponse]
      response.header(HeaderNames.CONTENT_TYPE) returns Some("application/xrds+xml")
      response.xml returns scala.xml.XML.loadString(readFixture("discovery/xrds/simple-openid-1-op.xml"))
      val maybeOpenIdServer = new XrdsResolver().resolve(response)
      maybeOpenIdServer.map(_.url) must beSome("http://openidprovider-server-1.example.com")
    }

    "extract and use OpenID Authentication 1.1 service elements from XRDS documents, if Yadis succeeds on an URL Identifier." in {
      val response = mock[WSResponse]
      response.header(HeaderNames.CONTENT_TYPE) returns Some("application/xrds+xml")
      response.xml returns scala.xml.XML.loadString(readFixture("discovery/xrds/simple-openid-1.1-op.xml"))
      val maybeOpenIdServer = new XrdsResolver().resolve(response)
      maybeOpenIdServer.map(_.url) must beSome("http://openidprovider-server-1.1.example.com")
    }
  }

  "OpenID.redirectURL" should {

    "resolve an OpenID server via Yadis" in {
      "with a single service element" in {
        val ws = new WSMock
        ws.response.xml returns scala.xml.XML.loadString(readFixture("discovery/xrds/simple-op.xml"))
        ws.response.header(HeaderNames.CONTENT_TYPE) returns Some("application/xrds+xml")

        val returnTo = "http://foo.bar.com/openid"
        val openId = "http://abc.example.com/foo"
        val redirectUrl = Await.result(new OpenIDClient(ws).redirectURL(openId, returnTo), dur)

        there was one(ws.request).get()

        new URL(redirectUrl).hostAndPath must be equalTo "https://www.google.com/a/example.com/o8/ud"

        verifyValidOpenIDRequest(parseQueryString(redirectUrl), openId, returnTo)
      }

      "should fall back to HTML based discovery if OP Identifier cannot be found in the XRDS" in {
        val ws = new WSMock
        ws.response.status returns OK thenReturns OK
        ws.response.body returns readFixture("discovery/html/openIDProvider.html")
        ws.response.xml returns scala.xml.XML.loadString(readFixture("discovery/xrds/invalid-op-identifier.xml"))
        ws.response.header(HeaderNames.CONTENT_TYPE) returns Some("text/html") thenReturns Some("application/xrds+xml")

        val returnTo = "http://foo.bar.com/openid"
        val openId = "http://abc.example.com/foo"
        val redirectUrl = Await.result(new OpenIDClient(ws).redirectURL(openId, returnTo), dur)

        there was one(ws.request).get()

        new URL(redirectUrl).hostAndPath must be equalTo "https://www.example.com/openidserver/openid.server"

        verifyValidOpenIDRequest(parseQueryString(redirectUrl), openId, returnTo)
      }

      // OpenID 1.1 compatibility - http://openid.net/specs/openid-authentication-2_0.html#anchor38
      "should fall back to HTML based discovery (with an OpenID 1.1 document) if OP Identifier cannot be found in the XRDS" in {
        val ws = new WSMock
        ws.response.status returns OK thenReturns OK
        ws.response.body returns readFixture("discovery/html/openIDProvider-OpenID-1.1.html")
        ws.response.xml returns scala.xml.XML.loadString(readFixture("discovery/xrds/invalid-op-identifier.xml"))
        ws.response.header(HeaderNames.CONTENT_TYPE) returns Some("text/html") thenReturns Some("application/xrds+xml")

        val returnTo = "http://foo.bar.com/openid"
        val openId = "http://abc.example.com/foo"
        val redirectUrl = Await.result(new OpenIDClient(ws).redirectURL(openId, returnTo), dur)

        there was one(ws.request).get()

        new URL(redirectUrl).hostAndPath must be equalTo "https://www.example.com/openidserver/openid.server-1"

        verifyValidOpenIDRequest(parseQueryString(redirectUrl), openId, returnTo)
      }

    }

    "resolve an OpenID server via HTML" in {

      "when given a response that includes openid meta information" in {
        val ws = new WSMock
        ws.response.body returns readFixture("discovery/html/openIDProvider.html")

        val returnTo = "http://foo.bar.com/openid"
        val openId = "http://abc.example.com/foo"
        val redirectUrl = Await.result(new OpenIDClient(ws).redirectURL(openId, returnTo), dur)

        there was one(ws.request).get()

        new URL(redirectUrl).hostAndPath must be equalTo "https://www.example.com/openidserver/openid.server"

        verifyValidOpenIDRequest(parseQueryString(redirectUrl), openId, returnTo)
      }

      "when given a response that includes a local identifier (using openid2.local_id openid.delegate)" in {
        val ws = new WSMock
        ws.response.body returns readFixture("discovery/html/opLocalIdentityPage.html")

        val returnTo = "http://foo.bar.com/openid"
        val redirectUrl = Await.result(new OpenIDClient(ws).redirectURL("http://example.com/", returnTo), dur)

        there was one(ws.request).get()

        new URL(redirectUrl).hostAndPath must be equalTo "http://www.example.com:8080/openidserver/openid.server"

        verifyValidOpenIDRequest(parseQueryString(redirectUrl), "http://example.com/", returnTo,
          opLocalIdentifier = Some("http://exampleuser.example.com/"))
      }
    }
  }

  // See 9.1 http://openid.net/specs/openid-authentication-2_0.html#anchor27
  private def verifyValidOpenIDRequest(params: Map[String, Seq[String]],
                                       claimedId: String,
                                       returnTo: String,
                                       opLocalIdentifier: Option[String] = None,
                                       realm: Option[String] = None) = {
    "valid request parameters need to be present" in {
      params.get("openid.ns") must_== Some(Seq("http://specs.openid.net/auth/2.0"))
      params.get("openid.mode") must_== Some(Seq("checkid_setup"))
      params.get("openid.claimed_id") must_== Some(Seq(claimedId))
      params.get("openid.return_to") must_== Some(Seq(returnTo))
    }

    "realm must be handled correctly (absent if not defined)" in {
      verifyOptionalParam(params, "openid.realm", realm)
    }

    "OP-Local Identifiers must be handled correctly (if a different OP-Local Identifier is not specified, the claimed identifier MUST be used as the value for openid.identity." in {
      val value = params.get("openid.identity")
      opLocalIdentifier match {
        case Some(id) => value must_== Some(Seq(id))
        case _ => value must be equalTo params.get("openid.claimed_id")
      }
    }

    "request parameters need to be absent in stateless mode" in {
      params.get("openid.assoc_handle") must beNone
    }
  }

  // Define matchers based on the expected value. Param must be absent if the expected value is None, it must match otherwise
  private def verifyOptionalParam(params: Params, key: String, expected: Option[String] = None) = expected match {
    case Some(value) => params.get(key) must_== Some(Seq(value))
    case _ => params.get(key) must beNone
  }
}

Other Play Framework source code examples

Here is a short list of links related to this Play Framework DiscoverySpec.scala source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.