Skip to content

Commit eb8fd85

Browse files
committed
Merge pull request #3 from hmrc/API-1345
Api 1345 Ability for End-User to view a list of Authorised Applications
2 parents 6cb5a05 + b7c53e9 commit eb8fd85

File tree

13 files changed

+365
-67
lines changed

13 files changed

+365
-67
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2016 HM Revenue & Customs
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package connectors
18+
19+
import config.WSHttp
20+
import models.AppAuthorisation
21+
import uk.gov.hmrc.play.config.ServicesConfig
22+
import uk.gov.hmrc.play.http.{HeaderCarrier, HttpGet, HttpPost}
23+
24+
trait DelegatedAuthorityConnector {
25+
26+
val delegatedAuthorityUrl: String
27+
val http: HttpPost with HttpGet
28+
29+
def fetchApplicationAuthorities()(implicit hc: HeaderCarrier) = {
30+
http.GET[Seq[AppAuthorisation]](s"$delegatedAuthorityUrl/authority/granted-applications")
31+
}
32+
}
33+
34+
object DelegatedAuthorityConnector extends DelegatedAuthorityConnector with ServicesConfig {
35+
override val delegatedAuthorityUrl = s"${baseUrl("third-party-delegated-authority")}"
36+
override val http = WSHttp
37+
}

app/controllers/Revocation.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package controllers
1818

19+
import connectors.DelegatedAuthorityConnector
1920
import play.api.mvc.Action
2021
import uk.gov.hmrc.play.frontend.auth.connectors.AuthConnector
2122
import uk.gov.hmrc.play.frontend.controller.FrontendController
@@ -26,16 +27,19 @@ import scala.concurrent.Future
2627
trait Revocation extends FrontendController with Authentication {
2728

2829
val authConnector: AuthConnector
30+
val delegatedAuthorityConnector: DelegatedAuthorityConnector
2931

3032
val start = Action.async { implicit request =>
3133
Future.successful(Ok(views.html.revocation.start()))
3234
}
3335

3436
val listAuthorizedApplications = authenticated.async { implicit user => implicit request =>
35-
Future.successful(Ok(views.html.revocation.authorizedApplications()))
37+
delegatedAuthorityConnector.fetchApplicationAuthorities()
38+
.map(applications => Ok(views.html.revocation.authorizedApplications(applications)))
3639
}
3740
}
3841

3942
object Revocation extends Revocation {
4043
override val authConnector = FrontendAuthConnector
44+
override val delegatedAuthorityConnector = DelegatedAuthorityConnector
4145
}

app/models/Authority.scala

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2016 HM Revenue & Customs
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package models
18+
19+
import org.joda.time.DateTime
20+
import play.api.libs.json.Json
21+
22+
case class Scope(key: String, name: String, description: String)
23+
24+
object Scope {
25+
implicit val format = Json.format[Scope]
26+
}
27+
28+
case class ThirdPartyApplication(id: String, name: String)
29+
30+
object ThirdPartyApplication {
31+
implicit val format = Json.format[ThirdPartyApplication]
32+
}
33+
34+
case class AppAuthorisation(application: ThirdPartyApplication,
35+
scopes: Set[Scope],
36+
earliestGrantDate: DateTime)
37+
38+
object AppAuthorisation {
39+
implicit val format = Json.format[AppAuthorisation]
40+
}

app/views/govuk_wrapper.scala.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030
@insideHeader = {
3131
@uiLayouts.header_nav(
32-
navTitle = None,
32+
navTitle = Some("Data permissions withdrawal"),
3333
navTitleLink = None,
3434
showBetaLink = false,
3535
navLinks = Some(headerNavLinks))

app/views/revocation/authorizedApplications.scala.html

Lines changed: 41 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
@()(implicit request: Request[_])
1+
@import org.joda.time.format.DateTimeFormat
2+
@(applications: Seq[AppAuthorisation])(implicit request: Request[_])
23

34
@body = {
45

@@ -8,57 +9,46 @@
89
<h1>Authorised software applications</h1>
910
</header>
1011

11-
<p>You have granted permission to the following software applications to access HMRC data. You can with withdraw this permission at any time.</p>
12-
13-
<ul>
14-
<li>
15-
<details>
16-
<summary>Lion</summary>
17-
<div class="panel-indent">
18-
<p>
19-
<strong>This application can perform the following actions in relation to HMRC data:</strong>
20-
</p>
21-
<ul class="bullets">
22-
<li>access national insurance information</li>
23-
<li>access tax account information</li>
24-
<li>access income information</li>
25-
<li>access benefits information</li>
26-
</ul>
27-
<p>
28-
<strong>Permission granted on:</strong> 6 April 2016
29-
</p>
30-
<form action="">
31-
<input type="submit" class="button" value="Withdraw permission"/>
32-
</form>
33-
</div>
34-
</details>
35-
</li>
36-
<li>
37-
<details>
38-
<summary>Fast Trax Tax</summary>
39-
<div class="panel-indent">
40-
<p>
41-
<strong>Has access to your clients' data relating to:</strong>
42-
</p>
43-
<ul class="bullets">
44-
<li>access income information</li>
45-
<li>access benefits information</li>
46-
</ul>
47-
<p>
48-
<strong>Permission granted on:</strong> 21 January 2015
49-
</p>
50-
<form action="">
51-
<input type="submit" class="button" value="Withdraw permission"/>
52-
</form>
53-
</div>
54-
</details>
55-
</li>
56-
</ul>
57-
58-
<p class="faded-text">You can grant permission to software to access data in the software application itself.</p>
59-
12+
@if(applications.isEmpty) {
13+
<p data-info-message>There are currently no applications which have access to HMRC data. If you want to grant permission to an application, you must do so in the application itself.</p>
14+
} else {
15+
<p data-info-message>You have granted permission to the following software applications to access HMRC data. You can with withdraw this permission at any time.</p>
16+
17+
@defining(DateTimeFormat.forPattern("dd MMMM yyyy")) { dateFormatter =>
18+
<ul>
19+
@for(application <- applications) {
20+
<li>
21+
<details data-@{application.application.id}>
22+
<summary data-name-@{application.application.id}>
23+
@{application.application.name}
24+
</summary>
25+
<div class="panel-indent">
26+
<p>
27+
<strong>This application can perform the following actions in relation to HMRC data:</strong>
28+
</p>
29+
<ul class="bullets">
30+
@for(scope <- application.scopes) {
31+
<li data-scope-@{application.application.id}='@{scope.key}'>
32+
@{scope.description}
33+
</li>
34+
}
35+
</ul>
36+
<p data-grant-date-@{application.application.id}>
37+
<strong>Permission granted on:</strong> @{dateFormatter.print(application.earliestGrantDate)}
38+
</p>
39+
<form action="">
40+
<input type="submit" class="button" value="Withdraw permission"/>
41+
</form>
42+
</div>
43+
</details>
44+
</li>
45+
}
46+
</ul>
47+
}
48+
49+
<p class="faded-text">You can grant permission to software to access data in the software application itself.</p>
50+
}
6051
</div>
61-
6252
}
6353

6454
@main_template(title = "Authorised software applications", bodyClasses = None) {

conf/application.conf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ microservice {
3636
host = localhost
3737
port = 8500
3838
}
39+
40+
third-party-delegated-authority {
41+
host = localhost
42+
port = 9606
43+
}
3944
}
4045
}
4146

test/acceptance/BaseSpec.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ trait BaseSpec extends FeatureSpec with BeforeAndAfterAll with BeforeAndAfterEac
4141
"auditing.traceRequests" -> false,
4242
"microservice.services.auth.host" -> stubHost,
4343
"microservice.services.auth.port" -> stubPort,
44+
"microservice.services.third-party-delegated-authority.host" -> stubHost,
45+
"microservice.services.third-party-delegated-authority.port" -> stubPort,
4446
"api-revocation-frontend.host" -> "",
4547
"ca-frontend.host" -> s"http://localhost:$stubPort"
4648
))

test/acceptance/NavigationSugar.scala

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,12 @@
1616

1717
package acceptance
1818

19-
import acceptance.{WebLink, WebPage}
2019
import org.openqa.selenium.support.ui.{ExpectedCondition, WebDriverWait}
2120
import org.openqa.selenium.{By, WebDriver, WebElement}
22-
import org.scalatest.{Assertions, Matchers}
2321
import org.scalatest.concurrent.Eventually
2422
import org.scalatest.selenium.WebBrowser
2523
import org.scalatest.selenium.WebBrowser.{go => goo}
24+
import org.scalatest.{Assertions, Matchers}
2625

2726
trait NavigationSugar extends WebBrowser with Eventually with Assertions with Matchers {
2827

@@ -35,6 +34,14 @@ trait NavigationSugar extends WebBrowser with Eventually with Assertions with Ma
3534
goo to page
3635
}
3736

37+
def clickOnElement(selectorId: String)(implicit webDriver: WebDriver) = {
38+
webDriver.findElement(By.cssSelector(s"[$selectorId]")).click()
39+
}
40+
41+
def verifyText(selectorId: String, expected: String)(implicit webDriver: WebDriver) = {
42+
webDriver.findElement(By.cssSelector(s"[$selectorId]")).getText contains expected
43+
}
44+
3845
def redirectedTo(page: WebLink)(implicit webDriver: WebDriver) = {
3946
assertResult(page.url)(webDriver.getCurrentUrl)
4047
}

test/acceptance/specs/AuthorizedApplicationsSpec.scala

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,29 +16,65 @@
1616

1717
package acceptance.specs
1818

19-
import acceptance.BaseSpec
20-
import acceptance.pages.{LoginPage, AuthorizedApplicationsPage}
21-
import acceptance.stubs.LoginStub
19+
import acceptance.pages.{AuthorizedApplicationsPage, LoginPage}
20+
import acceptance.stubs.{DelegatedAuthorityStub, LoginStub}
21+
import acceptance.{BaseSpec, NavigationSugar}
22+
import models.{AppAuthorisation, Scope, ThirdPartyApplication}
23+
import org.joda.time.DateTime
2224

23-
class AuthorizedApplicationsSpec extends BaseSpec {
25+
class AuthorizedApplicationsSpec extends BaseSpec with NavigationSugar {
2426

2527
feature("Logged in") {
2628

2729
scenario("User is redirected to the sign in page when not logged in") {
2830

2931
go(AuthorizedApplicationsPage)
30-
3132
redirectedTo(LoginPage)
3233
}
3334

3435
scenario("User sees his authorized applications when logged in") {
3536

37+
val applications = Seq(
38+
applicationAuthority("firstAppId", "First Application",
39+
Set(Scope("read:api-1", "scope name", "Access personal information"),
40+
Scope("read:api-3", "scope name", "Access tax information")), DateTime.now),
41+
42+
applicationAuthority("secondAppId", "Second Application",
43+
Set(Scope("read:api-2", "scope name", "Access confidential information")), DateTime.now.minusDays(2)),
44+
45+
applicationAuthority("thirdAppId", "Third Application",
46+
Set(), DateTime.now.minusMonths(2))
47+
)
48+
3649
LoginStub.stubSuccessfulLogin()
50+
DelegatedAuthorityStub.stubSuccessfulFetchApplicationAuthorities(applications)
3751

3852
go(AuthorizedApplicationsPage)
53+
on(AuthorizedApplicationsPage)
54+
55+
verifyText("data-info-message", "You have granted permission to the following software applications to access HMRC data. You can with withdraw this permission at any time.")
56+
applications.foreach(assertApplication)
57+
}
3958

59+
scenario("User sees correct message if there are not authorized applications") {
60+
LoginStub.stubSuccessfulLogin()
61+
DelegatedAuthorityStub.stubSuccessfulFetchApplicationAuthorities(Seq.empty)
62+
63+
go(AuthorizedApplicationsPage)
4064
on(AuthorizedApplicationsPage)
65+
66+
verifyText("data-info-message", "There are currently no applications which have access to HMRC data. If you want to grant permission to an application, you must do so in the application itself.")
4167
}
68+
}
69+
70+
private def assertApplication(app: AppAuthorisation) = {
71+
verifyText(s"data-name-${app.application.id}", app.application.name)
72+
clickOnElement(s"data-name-${app.application.id}")
73+
app.scopes.foreach(scope => verifyText(s"data-scope-${app.application.id}='${scope.key}'", scope.description))
74+
verifyText(s"data-grant-date-${app.application.id}", app.earliestGrantDate.toString("dd MMMM yyyy"))
75+
}
4276

77+
private def applicationAuthority(appId: String, appName: String, scopes: Set[Scope], earliestGrantDate: DateTime) = {
78+
AppAuthorisation(ThirdPartyApplication(appId, appName), scopes, earliestGrantDate)
4379
}
4480
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2016 HM Revenue & Customs
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package acceptance.stubs
18+
19+
import com.github.tomakehurst.wiremock.client.WireMock._
20+
import models.{Scope, AppAuthorisation}
21+
22+
object DelegatedAuthorityStub {
23+
def stubSuccessfulFetchApplicationAuthorities(applications: Seq[AppAuthorisation]) = {
24+
25+
def toScopeJson(scope: Scope) =
26+
s"""
27+
|{
28+
| "key":"${scope.key}",
29+
| "name":"${scope.name}",
30+
| "description":"${scope.description}"
31+
|}
32+
""".stripMargin
33+
34+
def toAppJson(application: AppAuthorisation) = {
35+
s"""
36+
|{
37+
| "application" : {
38+
| "id":"${application.application.id}",
39+
| "name":"${application.application.name}"
40+
| },
41+
| "scopes": [${application.scopes.map(toScopeJson).mkString(",")}],
42+
| "earliestGrantDate":${application.earliestGrantDate.getMillis}
43+
|}
44+
""".stripMargin
45+
}
46+
47+
stubFor(get(urlEqualTo(s"/authority/granted-applications")).willReturn(
48+
aResponse().withStatus(200).withBody(
49+
s"""[${applications.map(toAppJson).mkString(",")}]""".stripMargin)))
50+
}
51+
}

0 commit comments

Comments
 (0)