Skip to content

Commit

Permalink
Merge pull request #52 from cjww-development/bug/app-name-desc-icon
Browse files Browse the repository at this point in the history
#46 Added UI fields to update a clients name, desc and icon url
  • Loading branch information
chrisjwwalker authored Jun 28, 2021
2 parents b92f7c5 + 1b6436e commit 2594a8b
Show file tree
Hide file tree
Showing 11 changed files with 139 additions and 42 deletions.
25 changes: 0 additions & 25 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,6 @@ pipeline {
GH_TOKEN = credentials('github-api')
SBT_OPS = '-DMONGO_URI=mongodb://jenkins-mongo:27017 -Dsbt.global.base=.sbt -Dsbt.boot.directory=.sbt -Dsbt.ivy.home=.ivy2 -Dlocal=false'
}
parameters {
choice(
name: 'VersionType',
choices: "minor\nmajor\nhotfix",
description: 'What type of version to build.'
)
}
options {
ansiColor('xterm')
}
Expand All @@ -34,24 +27,6 @@ pipeline {
step([$class: 'ScoveragePublisher', reportDir: './target/scala-2.13/scoverage-report', reportFile: 'scoverage.xml'])
}
}
stage('Version project') {
when {
branch 'master'
}
steps {
script {
build job: 'operations/create-a-release', parameters: [string(name: 'Project', value: 'gatekeeper'), string(name: 'Type', value: params.VersionType)]
gitVersion = sh(
script: '''
JQ=./jq
curl https://stedolan.github.io/jq/download/linux64/jq > $JQ && chmod +x $JQ
curl -H "Accept: application/vnd.github.manifold-preview" -H 'Authorization: token '$GH_TOKEN'' -s 'https://api.github.com/repos/cjww-development/gatekeeper/releases/latest' | ./jq -r '.tag_name'
''',
returnStdout: true
).trim()
}
}
}
}
post {
always {
Expand Down
11 changes: 11 additions & 0 deletions app/controllers/ui/ClientController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -197,4 +197,15 @@ trait ClientController extends BaseController with I18NSupportLowPriorityImplici
_ => Redirect(routes.ClientController.getClientDetails(appId))
}
}

def updateBasicDetails(appId: String): Action[AnyContent] = authenticatedOrgUser { implicit req => orgUserId =>
val body = req.body.asFormUrlEncoded.getOrElse(Map())
val name = body.getOrElse("name", Seq("")).head
val desc = body.getOrElse("desc", Seq("")).head
val iconUrl = body.get("icon-url").filter(_.head != "").map(_.head)

clientOrchestrator.updateBasicDetails(appId, orgUserId, name, desc, iconUrl) map {
_ => Redirect(routes.ClientController.getClientDetails(appId))
}
}
}
12 changes: 12 additions & 0 deletions app/orchestrators/ClientOrchestrator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ case object UpdatedFailed extends AppUpdateResponse
case object FlowsUpdated extends AppUpdateResponse
case object ExpiryUpdated extends AppUpdateResponse
case object UrlsUpdated extends AppUpdateResponse
case object BasicsUpdates extends AppUpdateResponse

class DefaultClientOrchestrator @Inject()(val clientService: ClientService,
val userService: UserService,
Expand Down Expand Up @@ -178,4 +179,15 @@ trait ClientOrchestrator {
UpdatedFailed
}
}

def updateBasicDetails(appId: String, orgUserId: String, name: String, desc: String, iconUrl: Option[String])(implicit ec: ExC): Future[AppUpdateResponse] = {
clientService.updateBasicDetails(orgUserId, appId, name, desc, iconUrl) map {
case MongoSuccessUpdate =>
logger.info(s"[updateBasicDetails] - Updated the basics for app $appId belonging to org user $orgUserId")
BasicsUpdates
case MongoFailedUpdate =>
logger.warn(s"[updateBasicDetails] - Failed to update basics for app $appId belonging to org user $orgUserId")
UpdatedFailed
}
}
}
31 changes: 23 additions & 8 deletions app/services/oauth2/ClientService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import database.AppStore
import dev.cjww.mongo.responses.{MongoDeleteResponse, MongoFailedUpdate, MongoSuccessUpdate, MongoUpdatedResponse}
import models.{PresetService, RegisteredApplication}
import org.mongodb.scala.model.Filters.{and, equal}
import org.mongodb.scala.model.Updates.set
import org.mongodb.scala.model.Updates.{combine, set, unset}
import org.slf4j.{Logger, LoggerFactory}
import play.api.Configuration
import utils.StringUtils._
Expand Down Expand Up @@ -139,9 +139,9 @@ trait ClientService {
)

val update = if(isConfidential) {
and(set("clientId", clientId), set("clientSecret", clientSecret))
combine(set("clientId", clientId), set("clientSecret", clientSecret))
} else {
and(set("clientId", clientId))
combine(set("clientId", clientId))
}

appStore.updateApp(query, update) map {
Expand All @@ -155,7 +155,7 @@ trait ClientService {
}

def deleteClient(orgUserId: String, appId: String)(implicit ec: ExC): Future[MongoDeleteResponse] = {
val query = and(
val query = combine(
equal("owner", orgUserId),
equal("appId", appId)
)
Expand All @@ -164,7 +164,7 @@ trait ClientService {
}

def updateOAuth2Flows(flows: Seq[String], appId: String, orgUserId: String)(implicit ec: ExC): Future[MongoUpdatedResponse] = {
val query = and(
val query = combine(
equal("owner", orgUserId),
equal("appId", appId)
)
Expand All @@ -175,7 +175,7 @@ trait ClientService {
}

def updateOAuth2Scopes(scopes: Seq[String], appId: String, orgUserId: String)(implicit ec: ExC): Future[MongoUpdatedResponse] = {
val query = and(
val query = combine(
equal("owner", orgUserId),
equal("appId", appId)
)
Expand All @@ -192,7 +192,7 @@ trait ClientService {
equal("appId", appId)
)

val update = and(
val update = combine(
set("idTokenExpiry", idExpiry),
set("accessTokenExpiry", accessExpiry),
set("refreshTokenExpiry", refreshExpiry)
Expand All @@ -207,11 +207,26 @@ trait ClientService {
equal("appId", appId)
)

val update = and(
val update = combine(
set("homeUrl", homeUrl),
set("redirectUrl", redirectUrl)
)

appStore.updateApp(query, update)
}

def updateBasicDetails(owner: String, appId: String, name: String, desc: String, iconUrl: Option[String])(implicit ec: ExC): Future[MongoUpdatedResponse] = {
val query = and(
equal("owner", owner),
equal("appId", appId)
)

val update = if(iconUrl.nonEmpty) {
combine(set("name", name), set("desc", desc), set("iconUrl", iconUrl.get))
} else {
combine(set("name", name), set("desc", desc), unset("iconUrl"))
}

appStore.updateApp(query, update)
}
}
4 changes: 2 additions & 2 deletions app/services/oauth2/TokenService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import dev.cjww.mongo.responses._
import models.{RefreshToken, TokenExpiry, TokenRecord}
import org.joda.time.DateTime
import org.mongodb.scala.model.Filters.{and, equal}
import org.mongodb.scala.model.Updates.set
import org.mongodb.scala.model.Updates.{combine, set}
import org.slf4j.LoggerFactory
import pdi.jwt.{Jwt, JwtAlgorithm, JwtClaim, JwtHeader}
import play.api.Configuration
Expand Down Expand Up @@ -140,7 +140,7 @@ trait TokenService {

def updateTokenRecordSet(recordSetId: String, accessTokenId: String, idTokenId: String)(implicit ec: ExC): Future[MongoUpdatedResponse] = {
val query = equal("tokenSetId", recordSetId)
val update = and(
val update = combine(
set("accessTokenId", accessTokenId),
set("idTokenId", idTokenId),
set("issuedAt", new DateTime())
Expand Down
10 changes: 5 additions & 5 deletions app/services/users/UserService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import models._
import org.joda.time.DateTime
import org.mongodb.scala.bson.conversions.Bson
import org.mongodb.scala.model.Filters.{and, equal}
import org.mongodb.scala.model.Updates.{set, unset}
import org.mongodb.scala.model.Updates.{combine, set, unset}
import org.slf4j.{Logger, LoggerFactory}
import utils.StringUtils

Expand Down Expand Up @@ -124,7 +124,7 @@ trait UserService extends DeObfuscators with SecurityConfiguration with UserStor

def updateUserEmailAddress(userId: String, emailAddress: String)(implicit ec: ExC): Future[MongoUpdatedResponse] = {
val collection = getUserStore(userId)
val update: String => Bson = email => and(
val update: String => Bson = email => combine(
set("email", email),
set("emailVerified", false)
)
Expand All @@ -141,7 +141,7 @@ trait UserService extends DeObfuscators with SecurityConfiguration with UserStor

def updatePassword(userId: String, password: String, salt: String)(implicit ec: ExC): Future[MongoUpdatedResponse] = {
val collection = getUserStore(userId)
val update = and(
val update = combine(
set("password", password),
set("salt", salt)
)
Expand All @@ -159,7 +159,7 @@ trait UserService extends DeObfuscators with SecurityConfiguration with UserStor
def updateName(userId: String, firstName: Option[String], middleName: Option[String], lastName: Option[String], nickName: Option[String])
(implicit ec: ExC): Future[MongoUpdatedResponse] = {
val collection = getUserStore(userId)
val update = and(
val update = combine(
firstName.fold(unset("profile.givenName"))(fn => set("profile.givenName", fn)),
middleName.fold(unset("profile.middleName"))(mN => set("profile.middleName", mN)),
lastName.fold(unset("profile.familyName"))(lN => set("profile.familyName", lN)),
Expand Down Expand Up @@ -224,7 +224,7 @@ trait UserService extends DeObfuscators with SecurityConfiguration with UserStor

def setVerifiedPhoneNumber(userId: String, phoneNumber: String)(implicit ec: ExC): Future[MongoUpdatedResponse] = {
val collection = getUserStore(userId)
val phoneNumberUpdate = and(
val phoneNumberUpdate = combine(
set("digitalContact.phone.number", phoneNumber),
set("digitalContact.phone.verified", true)
)
Expand Down
34 changes: 34 additions & 0 deletions app/views/client/ClientView.scala.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,40 @@ <h4 class="card-title default-text">Client details</h4>
</div>
</div>

<div class="card">
<div class="card-body">
<h4 class="card-title default-text">Basic details</h4>
<form action="@uiRoutes.ClientController.updateBasicDetails(app.appId)" method="post">
@CSRF.formField

<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" id="basic-addon1">Name</span>
</div>
<input type="text" class="form-control" name="name" value="@{app.name}" placeholder="Name of the client">
</div>

<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" id="basic-addon1">Description</span>
</div>
<input type="text" class="form-control" name="desc" value="@{app.desc}" placeholder="Description of what the client does">
</div>

<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" id="basic-addon1">Icon Url</span>
</div>
<input type="text" class="form-control" name="icon-url" @if(app.iconUrl.nonEmpty) { value="@{app.iconUrl.get}" } placeholder="Url to the clients icon file">
</div>

<hr>

<button class="btn btn-lg btn-primary btn-block" type="submit">Update basic details</button>
</form>
</div>
</div>

<div class="card">
<div class="card-body">
<h4 class="card-title default-text">Redirects</h4>
Expand Down
1 change: 1 addition & 0 deletions conf/gatekeeper.routes
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,6 @@ POST /client/update/flows controllers.ui.ClientControl
POST /client/update/scopes controllers.ui.ClientController.updateOAuthScopes(appId)
POST /client/update/expiry controllers.ui.ClientController.updateTokenExpiry(appId)
POST /client/update/urls controllers.ui.ClientController.updateHomeAndRedirect(appId)
POST /client/update/basics controllers.ui.ClientController.updateBasicDetails(appId)

GET /assets/*file controllers.Assets.at(path="/public", file)
7 changes: 6 additions & 1 deletion test/helpers/services/MockClientService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

package helpers.services

import dev.cjww.mongo.responses.MongoDeleteResponse
import dev.cjww.mongo.responses.{MongoDeleteResponse, MongoUpdatedResponse}
import models.{PresetService, RegisteredApplication}
import org.mockito.ArgumentMatchers
import org.mockito.Mockito.{reset, when}
Expand Down Expand Up @@ -77,4 +77,9 @@ trait MockClientService extends MockitoSugar with BeforeAndAfterEach {
when(mockClientService.deleteClient(ArgumentMatchers.any[String](), ArgumentMatchers.any[String]())(ArgumentMatchers.any()))
.thenReturn(Future.successful(resp))
}

def mockUpdateBasicDetails(resp: MongoUpdatedResponse): OngoingStubbing[Future[MongoUpdatedResponse]] = {
when(mockClientService.updateBasicDetails(ArgumentMatchers.any[String](), ArgumentMatchers.any[String](), ArgumentMatchers.any[String](), ArgumentMatchers.any[String](), ArgumentMatchers.any[Option[String]]())(ArgumentMatchers.any()))
.thenReturn(Future.successful(resp))
}
}
24 changes: 23 additions & 1 deletion test/orchestrators/ClientOrchestratorSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

package orchestrators

import dev.cjww.mongo.responses.{MongoFailedDelete, MongoSuccessDelete}
import dev.cjww.mongo.responses.{MongoFailedDelete, MongoFailedUpdate, MongoSuccessDelete, MongoSuccessUpdate}
import dev.cjww.security.Implicits._
import dev.cjww.security.obfuscation.Obfuscators
import helpers.Assertions
Expand Down Expand Up @@ -389,4 +389,26 @@ class ClientOrchestratorSpec
}
}
}

"updateBasicDetails" should {
"return a MongoSuccessUpdate" when {
"the apps basic details were updated" in {
mockUpdateBasicDetails(resp = MongoSuccessUpdate)

awaitAndAssert(testOrchestrator.updateBasicDetails("testOwner", "testAppId", "testName", "testDesc", Some("testIconUrl"))) {
_ mustBe BasicsUpdates
}
}
}

"return a MongoFailedUpdate" when {
"the apps basic details could not be updated" in {
mockUpdateBasicDetails(resp = MongoFailedUpdate)

awaitAndAssert(testOrchestrator.updateBasicDetails("testOwner", "testAppId", "testName", "testDesc", Some("testIconUrl"))) {
_ mustBe UpdatedFailed
}
}
}
}
}
22 changes: 22 additions & 0 deletions test/services/ClientServiceSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -262,4 +262,26 @@ class ClientServiceSpec
}
}
}

"updateBasicDetails" should {
"return a MongoSuccessUpdate" when {
"the apps basic details were updated" in {
mockUpdateApp(resp = MongoSuccessUpdate)

awaitAndAssert(testService.updateBasicDetails("testOwner", "testAppId", "testName", "testDesc", Some("testIconUrl"))) {
_ mustBe MongoSuccessUpdate
}
}
}

"return a MongoFailedUpdate" when {
"the apps basic details could not be updated" in {
mockUpdateApp(resp = MongoFailedUpdate)

awaitAndAssert(testService.updateBasicDetails("testOwner", "testAppId", "testName", "testDesc", Some("testIconUrl"))) {
_ mustBe MongoFailedUpdate
}
}
}
}
}

0 comments on commit 2594a8b

Please sign in to comment.