feature/implement_FAPI_flow: remove flow-A related code, rename login_with_hydra to integrate_with_hydra, enable Consumer and disable Consumer update OAuth2 client grantTypes to enable and disable it.

This commit is contained in:
shuang 2020-11-17 19:31:22 +08:00
parent 84213a6c7a
commit 820dc2a31a
7 changed files with 50 additions and 436 deletions

View File

@ -865,8 +865,8 @@ outboundAdapterCallContext.generalContext
# ------------------------------------------------------------------------------------------
# ------------------------------ Hydra oauth2 props ------------------------------
## if login_with_hydra set to true, all other props must not be empty
#login_with_hydra=true
## if integrate_with_hydra set to true, all other props must not be empty
#integrate_with_hydra=true
#hydra_public_url=http://127.0.0.1:4444
#hydra_admin_url=http://127.0.0.1:4445
#hydra_consents=ReadAccountsBasic,ReadAccountsDetail,ReadBalances,ReadTransactionsBasic,ReadTransactionsDebits,ReadTransactionsDetail

View File

@ -25,7 +25,7 @@ TESOBE (http://www.tesobe.com/)
*/
package code.model
import java.util.Date
import java.util.{Collections, Date}
import code.api.util.APIUtil
import code.api.util.migration.Migration.DbFunction
@ -36,6 +36,7 @@ import code.nonce.NoncesProvider
import code.token.TokensProvider
import code.users.Users
import code.util.Helper.MdcLoggable
import code.util.HydraUtil
import code.util.HydraUtil._
import com.github.dwickern.macros.NameOf
import com.openbankproject.commons.ExecutionContext.Implicits.global
@ -186,7 +187,7 @@ object MappedConsumersProvider extends ConsumersProvider with MdcLoggable {
}
def deleteConsumer(consumer: Consumer): Boolean = {
if(mirrorConsumerInHydra) hydraAdmin.deleteOAuth2Client(consumer.key.get)
if(integrateWithHydra) hydraAdmin.deleteOAuth2Client(consumer.key.get)
Consumer.delete_!(consumer)
}
@ -245,10 +246,28 @@ object MappedConsumersProvider extends ConsumersProvider with MdcLoggable {
}
val updatedConsumer = c.saveMe()
if(mirrorConsumerInHydra && Option(originIsActive) != isActive) {
if(integrateWithHydra && Option(originIsActive) != isActive && isActive.isDefined) {
val clientId = c.key.get
val existsOAuth2Client = Box.tryo(hydraAdmin.getOAuth2Client(clientId))
.filter(null !=)
// if disable consumer, delete hydra client, else if enable consumer, create hydra client
// note: hydra update client endpoint have bug, can't update any client, So here delete and create new one
if (isActive == Some(false)) {
hydraAdmin.deleteOAuth2Client(c.key.get)
existsOAuth2Client
.map { oAuth2Client =>
hydraAdmin.deleteOAuth2Client(clientId)
// set grantTypes to empty to disable the client
oAuth2Client.setGrantTypes(Collections.emptyList())
hydraAdmin.createOAuth2Client(oAuth2Client)
}
} else if(isActive == Some(true) && existsOAuth2Client.isDefined) {
existsOAuth2Client
.map { oAuth2Client =>
hydraAdmin.deleteOAuth2Client(clientId)
// set grantTypes to correct value to enable the client
oAuth2Client.setGrantTypes(HydraUtil.grantTypes)
hydraAdmin.createOAuth2Client(oAuth2Client)
}
} else if(isActive == Some(true)) {
createHydraClient(updatedConsumer)
}
@ -402,7 +421,7 @@ object MappedConsumersProvider extends ConsumersProvider with MdcLoggable {
case None =>
}
val createdConsumer = c.saveMe()
if(mirrorConsumerInHydra) createHydraClient(createdConsumer)
if(integrateWithHydra) createHydraClient(createdConsumer)
createdConsumer
}
}

View File

@ -859,22 +859,7 @@ def restoreSomeSessions(): Unit = {
// val currentUrl = S.uriAndQueryString.getOrElse("/")
// AuthUser.loginRedirect.set(Full(Helpers.appendParams(currentUrl, List((LogUserOutParam, "false")))))
def checkInternalRedirectAndLogUseIn(preLoginState: () => Unit, redirect: String, user: AuthUser) = {
val loginChallengeBox = S.param("login_challenge")
.filter(StringUtils.isNotBlank(_))
if(loginWithHydra && loginChallengeBox.isDefined) {
val challenge = loginChallengeBox.orNull
val acceptLoginRequest = new AcceptLoginRequest()
acceptLoginRequest.setSubject(user.username.get)
acceptLoginRequest.remember(false)
acceptLoginRequest.rememberFor(3600)
val response = hydraAdmin.acceptLoginRequest(challenge, acceptLoginRequest)
val redirectTo = response.getRedirectTo
logUserIn(user, () => {
S.notice(S.?("logged.in"))
preLoginState()
S.redirectTo(redirectTo)
})
} else if (Helper.isValidInternalRedirectUrl(redirect)) {
if (Helper.isValidInternalRedirectUrl(redirect)) {
logUserIn(user, () => {
S.notice(S.?("logged.in"))
preLoginState()

View File

@ -1,249 +0,0 @@
/**
* Open Bank Project - API
* Copyright (C) 2011-2019, TESOBE GmbH.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Email: contact@tesobe.com
* TESOBE GmbH.
* Osloer Strasse 16/17
* Berlin 13359, Germany
*
* This product includes software developed at
* TESOBE (http://www.tesobe.com/)
*
*/
package code.snippet
import java.util.Date
import code.api.UKOpenBanking.v3_1_0.UtilForUKV310
import code.api.util.{APIUtil, NewStyle}
import code.consent.{Consent, Consents}
import code.consumer.Consumers
import code.model.dataAccess.AuthUser
import code.util.Helper.MdcLoggable
import code.util.HydraUtil
import code.views.Views
import code.webuiprops.MappedWebUiPropsProvider.getWebUiPropsValue
import net.liftweb.http.{RequestVar, S, SHtml}
import net.liftweb.util.CssSel
import net.liftweb.util.Helpers._
import sh.ory.hydra.model.{AcceptConsentRequest, ConsentRequestSession, RejectRequest}
import scala.jdk.CollectionConverters.{asScalaBufferConverter, mapAsJavaMapConverter, seqAsJavaListConverter}
import com.openbankproject.commons.ExecutionContext.Implicits.global
import com.openbankproject.commons.model.{AccountId, BankId, BankIdAccountId, ViewId, ViewIdBankIdAccountId}
import com.openbankproject.commons.util.Functions.Implicits._
import net.liftweb.common.{Box, Full}
import scala.concurrent.{Await, Future}
import scala.concurrent.duration.{Duration, SECONDS}
class ConsentConfirmation extends MdcLoggable {
private object submitButtonDefenseFlag extends RequestVar("")
private object cancelButtonDefenseFlag extends RequestVar("")
val confirmConsentButtonValue = getWebUiPropsValue("webui_post_confirm_consent_submit_button_value", "Yes, I confirm")
val rejectConsentButtonValue = getWebUiPropsValue("webui_post_reject_consent_submit_button_value", "Cancel")
def confirmConsentsForm: CssSel = {
def submitButtonDefense: Unit = {
submitButtonDefenseFlag("true")
}
def cancelButtonDefense: Unit = {
cancelButtonDefenseFlag("true")
}
def formElement(ele: => CssSel): CssSel =
"form" #> {
"type=submit" #> SHtml.submit(s"$confirmConsentButtonValue", () => submitButtonDefense) &
"type=button" #> SHtml.submit(s"$rejectConsentButtonValue", () => cancelButtonDefense) &
ele
}
val consentChallengeBox = S.param("consent_challenge")
if (consentChallengeBox.isEmpty) {
return formElement {
"#confirm-errors" #> "Please login first."
}
}
val consentChallenge = consentChallengeBox.orNull
if (cancelButtonDefenseFlag.get == "true") {
val rejectRequest = new RejectRequest()
rejectRequest.setError("access_denied")
rejectRequest.setErrorDescription("The resource owner denied the request")
val rejectResponse = HydraUtil.hydraAdmin.rejectConsentRequest(consentChallenge, rejectRequest)
AuthUser.logUserOut()
return S.redirectTo(rejectResponse.getRedirectTo)
}
val consentResponse = HydraUtil.hydraAdmin.getConsentRequest(consentChallenge)
val DateTimeRegex = """(\d+-\d{2}-\d{2}).*(\d{2}:\d{2}:\d{2}).*""".r // style example: 2020-09-09T11:55:22Z
def getDateTime(paramName: String): Date = S.param(paramName) match {
case Full(DateTimeRegex(date, time)) => APIUtil.DateWithSecondsFormat.parse(s"${date}T${time}Z")
case Full(v) => throw new IllegalArgumentException(s"request parameter $paramName is not correct date time format: $v")
case _ => throw new IllegalArgumentException(s"request parameter $paramName should not be empty.")
}
if (S.post_?) {
// get values of submit form
val consents = S.params("consent_scope")
val bankId = S.param("bank_id")
val accountIds = S.params("account_id")
val fromDate = getDateTime("from_date")
val toDate = getDateTime("to_date")
val expirationDate = getDateTime("expiration_date")
val currentUser = AuthUser.getCurrentUser.openOrThrowException("User is not logged in, in order to confirm consent the user must be authenticated.")
{ // TO create consent
val accountIdsOpt = if (accountIds.isEmpty) None else Some(accountIds)
val consent: Box[Consent] = {
val consumer = Consumers.consumers.vend.getConsumerByConsumerKey(consentResponse.getClient.getClientId)
val consumerId = consumer.map(_.consumerId.get)
Consents.consentProvider.vend.saveUKConsent(Some(currentUser), bankId, accountIdsOpt, consumerId, consents, expirationDate, fromDate, toDate, Some("MXOpenFinance"), Some("0.0.1"))
}
}
{ // revoke all consents for all accounts
// AuthUser.hydraConsents is just the follow values, read from props
//ViewId: six fixed
//"ReadAccountsBasic"
//"ReadAccountsDetail"
//"ReadBalances"
//"ReadTransactionsBasic"
//"ReadTransactionsDebits"
//"ReadTransactionsDetail"
val bankIdAccountIdsFuture: Future[List[BankIdAccountId]] = for {
availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(currentUser)
(accounts, _) <- NewStyle.function.getCoreBankAccountsFuture(availablePrivateAccounts, None)
} yield {
accounts.map(account => BankIdAccountId(BankId(account.bankId), AccountId(account.id)))
}
// all the BankIdAccountId for current user
val bankIdAccountIds = Await.result(bankIdAccountIdsFuture, Duration(30, SECONDS))
val revokeAccessIds: List[ViewIdBankIdAccountId] = for {
consent <- HydraUtil.hydraConsents
bankIdAccountId <- bankIdAccountIds
} yield ViewIdBankIdAccountId(ViewId(consent), bankIdAccountId.bankId, bankIdAccountId.accountId)
UtilForUKV310.revokeAccessToViews(currentUser, revokeAccessIds)
}
{ // grant checked consents
val grantAccessIds: List[ViewIdBankIdAccountId] = for {
consent <- consents
accountId <- accountIds
} yield ViewIdBankIdAccountId(ViewId(consent), BankId(bankId.orNull), AccountId(accountId))
UtilForUKV310.grantAccessToViews(currentUser, grantAccessIds)
}
// inform hydra
val consentRequest = new AcceptConsentRequest()
val scopes = "openid" :: "offline" :: consents
consentRequest.setGrantScope(scopes.asJava)
consentRequest.setGrantAccessTokenAudience(consentResponse.getRequestedAccessTokenAudience)
consentRequest.setRemember(false)
consentRequest.setRememberFor(3600) // TODO set in props
val session = new ConsentRequestSession()
val userName = currentUser.name
val idTokenValues = Map("given_name" -> userName,
"family_name" -> userName,
"name" -> userName,
"email" -> currentUser.emailAddress,
"email_verified" -> true).asJava
session.setIdToken(idTokenValues)
val accessToken = Map(
"bank_id" -> bankId.orNull,
"account_id" -> accountIds.asJava,
"transactionFromDateTime" -> fromDate,
"transactionToDateTime" -> toDate,
"expirationDateTime" -> expirationDate,
).asJava
session.accessToken(accessToken)
consentRequest.setSession(session)
val acceptConsentResponse = HydraUtil.hydraAdmin.acceptConsentRequest(consentChallenge, consentRequest)
S.redirectTo(acceptConsentResponse.getRedirectTo)
} else {
if (consentResponse.getSkip) {
val requestBody = new AcceptConsentRequest()
requestBody.setGrantScope(consentResponse.getRequestedScope)
requestBody.setGrantAccessTokenAudience(consentResponse.getRequestedAccessTokenAudience)
val requestSession = new ConsentRequestSession()
requestBody.setSession(requestSession)
val skipResponse = HydraUtil.hydraAdmin.acceptConsentRequest(consentChallenge, requestBody)
S.redirectTo(skipResponse.getRedirectTo)
} else {
val currentUser = AuthUser.getCurrentUser.openOrThrowException("User is not login, do confirm consent must be authenticated user.")
val bankAndAccountFuture: Future[List[(String, String, String, String)]] = for {
availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(currentUser)
(accounts, _) <- NewStyle.function.getCoreBankAccountsFuture(availablePrivateAccounts, None)
(banks, _) <- NewStyle.function.getBanks(None)
} yield {
for {
bank <- banks
account <- accounts
if account.bankId == bank.bankId.value
} yield (bank.bankId.value, bank.shortName, account.id, account.label)
}
//(bankId, bankName, accountId, accountLabel)
val bankAndAccount: List[(String, String, String, String)] = Await.result(bankAndAccountFuture, Duration(30, SECONDS))
val banks = bankAndAccount.map(it => it._1 -> it._2).distinctBy(_._1)
formElement {
"#confirm-errors" #> "" &
"#consent_challenge [value]" #> consentChallenge &
".bank" #> {
banks.map { it =>
".bank [value]" #> it._1 &
".bank *" #> it._2
}
} &
"#account_group" #> {
bankAndAccount.map { account =>
val (bankId, _, accountId, label) = account
"@account_id [value]" #> accountId &
"@account_id [id]" #> s"account_$accountId" &
"@account_id [bank_id]" #> bankId &
"@account_id_label [for]" #> s"account_$accountId" &
"@account_id_label *" #> label
}
} &
"#scope_group" #> consentResponse.getRequestedScope.asScala.filter(it => it != "openid" && it != "offline").map { scope =>
"@consent_scope [value]" #> scope &
"@consent_scope [id]" #> s"consent_$scope" &
"@consent_scope_label [for]" #> s"consent_$scope" &
"@consent_scope_label *" #> scope
}
}
}
}
}
}

View File

@ -99,13 +99,13 @@ class ConsumerRegistration extends MdcLoggable {
"#appType" #> SHtml.select(appTypes, Box!! appType.is, appType(_)) &
"#appName" #> SHtml.text(nameVar.is, nameVar(_)) &
"#redirect_url_label" #> {
if (HydraUtil.mirrorConsumerInHydra) "Redirect URL" else "Redirect URL (Optional)"
if (HydraUtil.integrateWithHydra) "Redirect URL" else "Redirect URL (Optional)"
} &
"#appRedirectUrl" #> SHtml.text(redirectionURLVar, redirectionURLVar(_)) &
"#appDev" #> SHtml.text(devEmailVar, devEmailVar(_)) &
"#appDesc" #> SHtml.textarea(descriptionVar, descriptionVar (_)) &
"#appUserAuthenticationUrl" #> SHtml.text(authenticationURLVar.is, authenticationURLVar(_)) & {
if(HydraUtil.mirrorConsumerInHydra) {
if(HydraUtil.integrateWithHydra) {
"#app-client_certificate" #> SHtml.textarea(clientCertificateVar, clientCertificateVar (_))&
"#app-request_uri" #> SHtml.text(requestUriVar, requestUriVar(_)) &
"#app-signing_alg" #> SHtml.select(signingAlgs, Box!! signingAlgVar.is, signingAlgVar(_)) &
@ -127,7 +127,7 @@ class ConsumerRegistration extends MdcLoggable {
val jwks = jwksVar.is
val jwsAlg = signingAlgVar.is
var jwkPrivateKey: String = s"Please change this value to ${if(StringUtils.isNotBlank(jwksUri)) "jwks_uri" else "jwks"} corresponding private key"
if(HydraUtil.mirrorConsumerInHydra) {
if(HydraUtil.integrateWithHydra) {
HydraUtil.createHydraClient(consumer, oAuth2Client => {
val signingAlg = signingAlgVar.is
@ -182,7 +182,7 @@ class ConsumerRegistration extends MdcLoggable {
"#directlogin-endpoint a [href]" #> urlDirectLoginEndpoint &
"#post-consumer-registration-more-info-link a *" #> registrationMoreInfoText &
"#post-consumer-registration-more-info-link a [href]" #> registrationMoreInfoUrl & {
if(HydraUtil.mirrorConsumerInHydra) {
if(HydraUtil.integrateWithHydra) {
"#hydra-client-info-title *" #>"OAuth2" &
"#admin_url *" #> HydraUtil.hydraAdminUrl &
"#client_id *" #> {consumer.key.get} &
@ -314,17 +314,15 @@ class ConsumerRegistration extends MdcLoggable {
jwksUriVar.set(jwksUri)
jwksVar.set(jwks)
val oauth2ParamError: CssSel = if(HydraUtil.mirrorConsumerInHydra) {
val oauth2ParamError: CssSel = if(HydraUtil.integrateWithHydra) {
if(StringUtils.isBlank(redirectionURLVar.is) || Consumer.redirectURLRegex.findFirstIn(redirectionURLVar.is).isEmpty) {
showErrorsForDescription("The 'Redirect URL' should be a valid url !")
} else if(StringUtils.isNotBlank(requestUri) && !requestUri.matches("""^https?://(www.)?\S+?(:\d{2,6})?\S*$""")) {
showErrorsForDescription("The 'request_uri' should be a valid url !")
} else if(StringUtils.isNotBlank(jwksUri) && !jwksUri.matches("""^https?://(www.)?\S+?(:\d{2,6})?\S*$""")) {
showErrorsForDescription("The 'jwks_uri' should be a valid url !")
} else if(StringUtils.isNotBlank(jwksUri) && StringUtils.isBlank(signingAlg)) {
showErrorsForDescription("The 'signing_alg' should not be empty when request_uri have value!")
} else if(!StringUtils.isAllBlank(jwksUri, jwks) && StringUtils.isBlank(signingAlg)) {
showErrorsForDescription("The 'signing_alg' must have value when 'jwks_uri' or 'jwks' have value!")
} else if(StringUtils.isBlank(signingAlg)) {
showErrorsForDescription("The 'signing_alg' should not be empty!")
} else if(StringUtils.isNoneBlank(jwksUri, jwks)) {
showErrorsForDescription("The 'jwks_uri' and 'jwks' should not have value at the same time!")
} else if (StringUtils.isNotBlank(clientCertificate) && X509.validate(clientCertificate) != Full(true)) {

View File

@ -18,25 +18,32 @@ import scala.jdk.CollectionConverters.{mapAsJavaMapConverter, seqAsJavaListConve
object HydraUtil {
val loginWithHydra = APIUtil.getPropsAsBoolValue("login_with_hydra", false)
private val INTEGRATE_WITH_HYDRA = "integrate_with_hydra"
val integrateWithHydra = APIUtil.getPropsAsBoolValue(INTEGRATE_WITH_HYDRA, false)
val mirrorConsumerInHydra = APIUtil.getPropsAsBoolValue("mirror_consumer_in_hydra", false)
lazy val hydraPublicUrl = APIUtil.getPropsValue("hydra_public_url")
.openOrThrowException("If props login_with_hydra is true, hydra_public_url value should not be blank")
.openOrThrowException(s"If props $INTEGRATE_WITH_HYDRA is true, hydra_public_url value should not be blank")
.replaceFirst("/$", "")
lazy val hydraAdminUrl = APIUtil.getPropsValue("hydra_admin_url")
.openOrThrowException("If props login_with_hydra is true, hydra_admin_url value should not be blank")
.openOrThrowException(s"If props $INTEGRATE_WITH_HYDRA is true, hydra_admin_url value should not be blank")
.replaceFirst("/$", "")
lazy val hydraConsents = APIUtil.getPropsValue("hydra_consents")
.openOrThrowException("If props login_with_hydra is true, hydra_client_scope value should not be blank")
.openOrThrowException(s"If props $INTEGRATE_WITH_HYDRA is true, hydra_client_scope value should not be blank")
.trim.split("""\s*,\s*""").toList
private val allConsents = hydraConsents.mkString("openid offline ", " ","")
val grantTypes = ("authorization_code" :: "client_credentials" :: "refresh_token" :: "implicit" :: Nil).asJava
lazy val hydraAdmin = {
val hydraAdminUrl = APIUtil.getPropsValue("hydra_admin_url")
.openOrThrowException("If props login_with_hydra is true, hydra_admin_url value should not be blank")
.openOrThrowException(s"If props $INTEGRATE_WITH_HYDRA is true, hydra_admin_url value should not be blank")
val defaultClient = Configuration.getDefaultApiClient
defaultClient.setBasePath(hydraAdminUrl)
new AdminApi(defaultClient)
@ -44,7 +51,7 @@ object HydraUtil {
lazy val hydraPublic = {
val hydraPublicUrl = APIUtil.getPropsValue("hydra_public_url")
.openOrThrowException("If props login_with_hydra is true, hydra_public_url value should not be blank")
.openOrThrowException(s"If props $INTEGRATE_WITH_HYDRA is true, hydra_public_url value should not be blank")
val apiClient = new ApiClient
apiClient.setBasePath(hydraPublicUrl)
new PublicApi(apiClient)
@ -65,10 +72,10 @@ object HydraUtil {
val oAuth2Client = new OAuth2Client()
oAuth2Client.setClientId(consumer.key.get)
oAuth2Client.setClientSecret(consumer.secret.get)
val allConsents = "openid" :: "offline" :: hydraConsents
oAuth2Client.setScope(allConsents.mkString(" "))
oAuth2Client.setGrantTypes(("authorization_code" :: "client_credentials" :: "refresh_token" :: "implicit" :: Nil).asJava)
oAuth2Client.setScope(allConsents)
oAuth2Client.setGrantTypes(grantTypes)
oAuth2Client.setResponseTypes(("code" :: "id_token" :: "token" :: "code id_token" :: Nil).asJava)
oAuth2Client.setPostLogoutRedirectUris(List(redirectUrl).asJava)

View File

@ -1,146 +0,0 @@
<!--
Open Bank Project - API
Copyright (C) 2011-2017, TESOBE GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Email: contact@tesobe.com
TESOBE GmbH
Osloerstrasse 16/17
Berlin 13359, Germany
This product includes software developed at
TESOBE (http://www.tesobe.com/)
by
Simon Redfern : simon AT tesobe DOT com
Sebastian Henschel : sebastian AT tesobe DOT com
-->
<style>
.bootstrap-datetimepicker-widget,
.bootstrap-datetimepicker-widget .picker-switch td span{
background-color: #fff ;
color: #333 ;
}
</style>
<div id="register-consumer" data-lift="surround?with=default;at=content">
<div data-lift="ConsentConfirmation.confirmConsentsForm">
<h2 style="text-align: center">Confirm the following permissions on your account for your Application:</h2>
<form method="post">
<div class="row">
<div class="col-xs-12 col-sm-6">
<div class="form-group">
<label for="bank_id">Banks</label>
<select class="form-control" id="bank_id" name="bank_id">
<option value="">--select a bank--</option>
<option class="bank" value="psd201-bank-y--uk">Bank y</option>
</select>
</div>
</div>
</div>
<hr>
<div class="row">
<div class="col-xs-12 col-sm-12">
<h4>Accounts:</h4>
</div>
</div>
<br>
<div class="row">
<div class="col-xs-12 col-sm-6" id="account_group">
<div class="form-group">
<input type="checkbox" name="account_id" id="account_id" value="account_x" bank_id="bank_x_y">
<label for="account_id" name="account_id_label">account x</label>
</div>
</div>
</div>
<hr>
<div class="row">
<div class="col-xs-12 col-sm-12">
<h4>Consents:</h4>
</div>
</div>
<br>
<div class="row">
<div class="col-xs-12 col-sm-12" id="scope_group">
<div class="form-group">
<input type="checkbox" name="consent_scope" id="consent_scope_openid" value="openid">
<label for="consent_scope_openid" name="consent_scope_label">openid</label>
</div>
</div>
</div>
<hr>
<div class="row">
<div class="col-xs-12 col-sm-12">
<h4>Date Time:</h4>
</div>
</div>
<br>
<div class="row">
<div class="col-xs-12 col-sm-6">
<div class="form-group">
<label for="from_date">TransactionFromDateTime</label>
<input type="text" name="from_date" id="from_date" class="form-control" data-date-format="YYYY-MM-DDTHH:mm:ss">
</div>
</div>
<div class="col-xs-12 col-sm-6">
<div class="form-group">
<label for="to_date">TransactionToDateTime</label>
<input type="text" name="to_date" id="to_date" class="form-control" data-date-format="YYYY-MM-DDTHH:mm:ss">
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-6">
<div class="form-group">
<label for="expiration_date">ExpirationDateTime</label>
<input type="text" name="expiration_date" id="expiration_date" class="form-control" data-date-format="YYYY-MM-DDTHH:mm:ss">
</div>
</div>
</div>
<input type="hidden" name="consent_challenge" id="consent_challenge">
<input type="button" name="confirm" value="Cancel" class="btn btn-default" />
<input type="submit" name="confirm" value="Yes, I confirm" class="btn btn-default" />
<div id = "confirm-errors-div" class="hide alert alert-danger">
<span data-lift="Msg?id=confirm-errors"/>
</div>
</form>
</div>
</div>
</div>
<script type="text/javascript">
$(function () {
$('#from_date, #to_date').datetimepicker({
defaultDate: new Date()
});
$('#expiration_date').datetimepicker({
defaultDate: new Date(new Date().setFullYear(new Date().getFullYear() + 1))
});
// hide all account checkboxes
$('input[name=account_id]').prop("checked", false).parent().parent().hide();
$('#bank_id').change(function(){
var bankId = $(this).val();
$('input[name=account_id]').prop("checked", false).parent().parent().hide();
if(bankId) {
$('input[name=account_id]')
.filter(function() { // not use .filter('[bank_id='+bankId+']') reason: bankId may have special characters
return $(this).attr('bank_id') === bankId;
}).parent().parent().show();
}
});
});
</script>