Merge pull request #1441 from constantine2nd/develop

OAuth2
This commit is contained in:
Simon Redfern 2019-10-25 18:51:32 +02:00 committed by GitHub
commit 331908d853
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 255 additions and 69 deletions

View File

@ -177,7 +177,7 @@ The two user models are now called AuthUser and ResourceUser
* Answer Transaction Request Challenge (updated)
* Get Transaction Requests (updated)
* Get Roles (new)
* Get Entitlements By Bank And User (naaew)
* Get Entitlements By Bank And User (new)
* Get Consumer (App) (new)
* Get Consumers (App) (new)
* Enable Disable Consumers (Apps) (new)

View File

@ -353,6 +353,9 @@ object GatewayLogin extends RestHelper with MdcLoggable {
consumerId=Some(consumerId),
Some(Helpers.randomString(40).toLowerCase),
Some(Helpers.randomString(40).toLowerCase),
None,
None,
None,
Some(true),
name = Some(consumerName),
appType = None,

View File

@ -29,6 +29,8 @@ package code.api
import java.net.URI
import code.api.util.{APIUtil, CallContext, ErrorMessages, JwtUtil}
import code.consumer.Consumers
import code.model.Consumer
import code.users.Users
import code.util.Helper.MdcLoggable
import com.nimbusds.jwt.JWTClaimsSet
@ -36,6 +38,7 @@ import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet
import com.openbankproject.commons.model.User
import net.liftweb.common._
import net.liftweb.http.rest.RestHelper
import net.liftweb.util.Helpers
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
@ -232,12 +235,52 @@ object OAuth2Login extends RestHelper with MdcLoggable {
)
}
}
/**
* This function creates a consumer based on "azp", "sub", "iss", "name" and "email" fields
* Please note that a user must be created before consumer.
* Unique criteria to decide do we create or get a consumer is pair o values: < sub : azp > i.e.
* We cannot find consumer by sub and azp => Create
* We can find consumer by sub and azp => Get
* @param idToken Google's response example:
* {
* "access_token": "ya29.GluUBg5DflrJciFikW5hqeKEp9r1whWnU5x2JXCm9rKkRMs2WseXX8O5UugFMDsIKuKCZlE7tTm1fMII_YYpvcMX6quyR5DXNHH8Lbx5TrZN__fA92kszHJEVqPc",
* "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjA4ZDMyNDVjNjJmODZiNjM2MmFmY2JiZmZlMWQwNjk4MjZkZDFkYzEiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTM5NjY4NTQyNDU3ODA4OTI5NTkiLCJlbWFpbCI6Im1hcmtvLm1pbGljLnNyYmlqYUBnbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXRfaGFzaCI6Im5HS1JUb0tOblZBMjhINk1od1hCeHciLCJuYW1lIjoiTWFya28gTWlsacSHIiwicGljdHVyZSI6Imh0dHBzOi8vbGg1Lmdvb2dsZXVzZXJjb250ZW50LmNvbS8tWGQ0NGhuSjZURG8vQUFBQUFBQUFBQUkvQUFBQUFBQUFBQUEvQUt4cndjYWR3emhtNE40dFdrNUU4QXZ4aS1aSzZrczRxZy9zOTYtYy9waG90by5qcGciLCJnaXZlbl9uYW1lIjoiTWFya28iLCJmYW1pbHlfbmFtZSI6Ik1pbGnEhyIsImxvY2FsZSI6ImVuIiwiaWF0IjoxNTQ3NzA1NjkxLCJleHAiOjE1NDc3MDkyOTF9.iUxhF_SU2vi76zPuRqAKJvFOzpb_EeP3lc5u9FO9o5xoXzVq3QooXexTfK2f1YAcWEy9LSftA34PB0QTuCZpkQChZVM359n3a3hplf6oWWkBXZN2_IG10NwEH4g0VVBCsjWBDMp6lvepN_Zn15x8opUB7272m4-smAou_WmUPTeivXRF8yPcp4J55DigcY31YP59dMQr2X-6Rr1vCRnJ6niqqJ1UDldfsgt4L7dXmUCnkDdXHwEQAZwbKbR4dUoEha3QeylCiBErmLdpIyqfKECphC6piGXZB-rRRqLz41WNfuF-3fswQvGmIkzTJDR7lQaletMp7ivsfVw8N5jFxg",
* "expires_in": 3600,
* "token_type": "Bearer",
* "scope": "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email",
* "refresh_token": "1/HkTtUahtUTdG7D6urpPNz6g-_qufF-Y1YppcBf0v3Cs"
* }
* @return an existing or a new consumer
*/
def getOrCreateConsumerFuture(idToken: String, userId: Box[String]): Box[Consumer] = {
val azp = getClaim(name = "azp", idToken = idToken)
val iss = getClaim(name = "iss", idToken = idToken)
val sub = getClaim(name = "sub", idToken = idToken)
val email = getClaim(name = "email", idToken = idToken)
val name = getClaim(name = "name", idToken = idToken)
Consumers.consumers.vend.getOrCreateConsumer(
consumerId = None,
key = Some(Helpers.randomString(40).toLowerCase),
secret = Some(Helpers.randomString(40).toLowerCase),
azp = azp,
iss = iss,
sub = sub,
Some(true),
name = name,
appType = None,
description = iss.map(v => "Via " + v),
developerEmail = email,
redirectURL = None,
createdByUserId = userId.toOption
)
}
def applyRules(value: String, cc: CallContext): (Box[User], Some[CallContext]) = {
validateIdToken(value) match {
case Full(_) =>
val user = Google.getOrCreateResourceUser(value)
(user, Some(cc))
val consumer = Google.getOrCreateConsumerFuture(value, user.map(_.userId))
(user, Some(cc.copy(consumer = consumer)))
case ParamFailure(a, b, c, apiFailure : APIFailure) =>
(ParamFailure(a, b, c, apiFailure : APIFailure), Some(cc))
case Failure(msg, t, c) =>
@ -251,8 +294,9 @@ object OAuth2Login extends RestHelper with MdcLoggable {
case Full(_) =>
for {
user <- Google.getOrCreateResourceUserFuture(value)
consumer <- Future{Google.getOrCreateConsumerFuture(value, user.map(_.userId))}
} yield {
(user, Some(cc))
(user, Some(cc.copy(consumer = consumer)))
}
case ParamFailure(a, b, c, apiFailure : APIFailure) =>
Future((ParamFailure(a, b, c, apiFailure : APIFailure), Some(cc)))

View File

@ -15,7 +15,7 @@ import code.api.v3_0_0.JSONFactory300.createBranchJsonV300
import code.api.v3_0_0.custom.JSONFactoryCustom300
import code.api.v3_0_0.{LobbyJsonV330, _}
import code.api.v3_1_0.{AccountBalanceV310, AccountsBalancesV310Json, BadLoginStatusJson, ContactDetailsJson, InviteeJson, ObpApiLoopbackJson, PhysicalCardWithAttributesJsonV310, PutUpdateCustomerEmailJsonV310, _}
import code.api.v4_0_0.{APIInfoJson400, EnergySource400, HostedAt400, HostedBy400, ModeratedCoreAccountJsonV400}
import code.api.v4_0_0.{APIInfoJson400, AccountTagJSON, AccountTagsJSON, EnergySource400, HostedAt400, HostedBy400, ModeratedCoreAccountJsonV400, PostAccountTagJSON}
import code.branches.Branches.{Branch, DriveUpString, LobbyString}
import code.consent.ConsentStatus
import code.sandbox.SandboxData
@ -505,6 +505,9 @@ object SwaggerDefinitionsJSON {
val postTransactionTagJSON = PostTransactionTagJSON(
value = "String"
)
val postAccountTagJSON = PostAccountTagJSON(
value = "String"
)
val aliasJSON = AliasJSON(
alias = "String"
)
@ -895,6 +898,16 @@ object SwaggerDefinitionsJSON {
tags = List(transactionTagJSON)
)
val accountTagJSON = AccountTagJSON(
id = "5995d6a2-01b3-423c-a173-5481df49bdaf",
value = "OBP",
date = DateWithDayExampleObject,
user = userJSONV121
)
val accountTagsJSON = AccountTagsJSON(
tags = List(accountTagJSON)
)
val transactionMetadataJSON = TransactionMetadataJSON(
narrative = "NONE",
comments = List(transactionCommentJSON),
@ -3248,15 +3261,17 @@ object SwaggerDefinitionsJSON {
)
val postConsentEmailJsonV310 = PostConsentEmailJsonV310(
`for` = "ALL_MY_ACCOUNTS",
view = "owner",
email = "marko@tesobe.com"
everything = false,
views = List(ViewJsonV400(bankIdExample.value, accountIdExample.value, viewIdExample.value)),
entitlements = List(EntitlementJsonV400(bankIdExample.value, "CanQueryOtherUser")),
email = emailExample.value
)
val postConsentPhoneJsonV310 = PostConsentPhoneJsonV310(
`for` = "ALL_MY_ACCOUNTS",
view = "owner",
phone_number = "493081453994"
everything = false,
views = List(ViewJsonV400(bankIdExample.value, accountIdExample.value, viewIdExample.value)),
entitlements = List(EntitlementJsonV400(bankIdExample.value, "CanQueryOtherUser")),
phone_number = mobileNumberExample.value
)
val consentsJsonV310 = ConsentsJsonV310(List(consentJsonV310))
@ -3367,7 +3382,7 @@ object SwaggerDefinitionsJSON {
views_basic = List(viewBasicV300),
account_attributes = List(accountAttributeResponseJson)
)
val newModeratedCoreAccountJsonV400 = ModeratedCoreAccountJsonV400(
val moderatedCoreAccountJsonV400 = ModeratedCoreAccountJsonV400(
id = accountIdExample.value,
bank_id= bankIdExample.value,
label= labelExample.value,
@ -3378,7 +3393,7 @@ object SwaggerDefinitionsJSON {
account_routings = List(accountRoutingJsonV121),
views_basic = List(viewBasicV300),
account_attributes = List(accountAttributeResponseJson),
tags = List(transactionTagJSON)
tags = List(accountTagJSON)
)
val postHistoricalTransactionJson = PostHistoricalTransactionJson(

View File

@ -1,5 +1,6 @@
package code.api.util
import code.api.v3_1_0.{EntitlementJsonV400, PostConsentBodyCommonJson, ViewJsonV400}
import code.consent.{ConsentStatus, Consents, MappedConsent}
import code.consumer.Consumers
import code.entitlement.Entitlement
@ -13,6 +14,7 @@ import net.liftweb.json.JsonParser.ParseException
import net.liftweb.json.{Extraction, MappingException, compactRender}
import net.liftweb.mapper.By
import scala.collection.immutable.List
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
@ -310,21 +312,30 @@ object Consent {
}
def createConsentJWT(user: User, viewId: String, secret: String, consentId: String): String = {
def createConsentJWT(user: User,
consent: PostConsentBodyCommonJson,
secret: String,
consentId: String): String = {
val consumerKey = Consumer.findAll(By(Consumer.createdByUserId, user.userId)).map(_.key.get).headOption.getOrElse("")
val currentTimeInSeconds = System.currentTimeMillis / 1000
val views: Box[List[ConsentView]] = {
Views.views.vend.getPermissionForUser(user) map {
_.views map {
view =>
ConsentView(
bank_id = view.bankId.value,
account_id = view.accountId.value,
view_id = viewId
)
}
val views: Seq[ConsentView] =
for {
view <- Views.views.vend.getPermissionForUser(user).map(_.views).getOrElse(Nil)
if consent.everything || consent.views.exists(_ == ViewJsonV400(view.bankId.value,view.accountId.value, view.viewId.value))
} yield {
ConsentView(
bank_id = view.bankId.value,
account_id = view.accountId.value,
view_id = view.viewId.value
)
}
val entitlements: Seq[Role] =
for {
entitlement <- Entitlement.entitlement.vend.getEntitlementsByUserId(user.userId).getOrElse(Nil)
if consent.everything || consent.entitlements.exists(_ == EntitlementJsonV400(entitlement.bankId,entitlement.roleName))
} yield {
Role(entitlement.roleName, entitlement.bankId)
}
}.map(_.distinct)
val json = ConsentJWT(
createdByUserId=user.userId,
sub=APIUtil.generateUUID(),
@ -336,8 +347,8 @@ object Consent {
exp=currentTimeInSeconds + 3600,
name=None,
email=None,
entitlements=Nil,
views=views.getOrElse(Nil)
entitlements=entitlements.toList,
views=views.toList
)
implicit val formats = CustomJsonFormats.formats

View File

@ -32,8 +32,10 @@ object PegdownOptions {
// convertPegdownToHtmlTweaked not support insert image, so here manual convert to html img tag
private def convertImgTag(markdown: String) = markdown.stripMargin.replaceAll("""!\[(.*)\]\((.*) =(.*?)x(.*?)\)""", """<img alt="$1" src="$2" width="$3" height="$4" />""")
def convertMarkdownToHtml(description: String): String = {
def convertGitHubDocMarkdownToHtml(description: String): String = {
val options = new MutableDataSet()
import com.vladsch.flexmark.parser.ParserEmulationProfile
options.setFrom(ParserEmulationProfile.GITHUB_DOC)
val parser = Parser.builder(options).build
val renderer = HtmlRenderer.builder(options).build
val document = parser.parse(description.stripMargin)

View File

@ -3390,7 +3390,7 @@ trait APIMethods310 {
createdConsent <- Future(Consents.consentProvider.vend.createConsent(user)) map {
i => connectorEmptyResponse(i, callContext)
}
consentJWT = Consent.createConsentJWT(user, consentJson.view, createdConsent.secret, createdConsent.consentId)
consentJWT = Consent.createConsentJWT(user, consentJson, createdConsent.secret, createdConsent.consentId)
_ <- Future(Consents.consentProvider.vend.setJsonWebToken(createdConsent.consentId, consentJWT)) map {
i => connectorEmptyResponse(i, callContext)
}

View File

@ -490,27 +490,32 @@ case class MeetingJsonV310(
case class MeetingsJsonV310(
meetings: List[MeetingJsonV310]
)
case class EntitlementJsonV400(bank_id: String, role_name: String)
case class ViewJsonV400(bank_id: String, account_id: String, view_id: String)
trait PostConsentCommonBody{
val `for`: String
val view: String
val everything: Boolean
val views: List[ViewJsonV400]
val entitlements: List[EntitlementJsonV400]
}
case class PostConsentBodyCommonJson(
`for`: String,
view: String
everything: Boolean,
views: List[ViewJsonV400],
entitlements: List[EntitlementJsonV400]
) extends PostConsentCommonBody
case class PostConsentEmailJsonV310(
`for`: String,
view: String,
email: String
everything: Boolean,
views: List[ViewJsonV400],
entitlements: List[EntitlementJsonV400],
email: String
) extends PostConsentCommonBody
case class PostConsentPhoneJsonV310(
`for`: String,
view: String,
phone_number: String
everything: Boolean,
views: List[ViewJsonV400],
entitlements: List[EntitlementJsonV400],
phone_number: String
) extends PostConsentCommonBody
case class ConsentJsonV310(consent_id: String, jwt: String, status: String)

View File

@ -1117,8 +1117,8 @@ trait APIMethods400 {
|${authenticationRequiredMessage(true)}
|
|Authentication is required as the tag is linked with the user.""",
postTransactionTagJSON,
transactionTagJSON,
postAccountTagJSON,
accountTagJSON,
List(
UserNotLoggedIn,
BankAccountNotFound,
@ -1149,7 +1149,7 @@ trait APIMethods400 {
i => (connectorEmptyResponse(i, callContext), callContext)
}
} yield {
(JSONFactory.createTransactionTagJSON(postedTag), HttpCode.`201`(callContext))
(JSONFactory400.createAccountTagJSON(postedTag), HttpCode.`201`(callContext))
}
}
}
@ -1209,7 +1209,7 @@ trait APIMethods400 {
|
|Authentication is required as the tag is linked with the user.""",
emptyObjectJson,
transactionTagJSON,
accountTagsJSON,
List(
BankAccountNotFound,
NoViewPermission,
@ -1234,7 +1234,7 @@ trait APIMethods400 {
}
tags <- Future(Tags.tags.vend.getTagsOnAccount(bankId, accountId)(viewId))
} yield {
val json = JSONFactory.createTransactionTagsJSON(tags)
val json = JSONFactory400.createAccountTagsJSON(tags)
(json, HttpCode.`200`(callContext))
}
}
@ -1259,6 +1259,8 @@ trait APIMethods400 {
|* Balance - Currency and Value
|* Account Routings - A list that might include IBAN or national account identifiers
|* Account Rules - A list that might include Overdraft and other bank specific rules
|* Account Attributes - A list that might include custom defined account attribute
|* Tags - A list of Tags assigned to this account
|
|This call returns the owner view and requires access to that view.
|
@ -1267,7 +1269,7 @@ trait APIMethods400 {
|
|""".stripMargin,
emptyObjectJson,
newModeratedCoreAccountJsonV400,
moderatedCoreAccountJsonV400,
List(BankAccountNotFound,UnknownError),
Catalogs(Core, PSD2, notOBWG),
apiTagAccount :: apiTagPSD2AIS :: apiTagNewStyle :: Nil)

View File

@ -30,18 +30,17 @@ import java.util.Date
import code.api.util.APIUtil
import code.api.util.APIUtil.{stringOptionOrNull, stringOrNull}
import code.api.v1_2_1.JSONFactory.{createAmountOfMoneyJSON, createOwnersJSON, createTransactionTagJSON}
import code.api.v1_2_1.{BankRoutingJsonV121, TransactionTagJSON, UserJSONV121, ViewJSONV121}
import code.api.v1_2_1.JSONFactory.{createAmountOfMoneyJSON, createOwnersJSON}
import code.api.v1_2_1.{BankRoutingJsonV121, JSONFactory, UserJSONV121, ViewJSONV121}
import code.api.v1_4_0.JSONFactory1_4_0.TransactionRequestAccountJsonV140
import code.api.v2_0_0.TransactionRequestChargeJsonV200
import code.api.v3_0_0.JSONFactory300.createAccountRoutingsJSON
import code.api.v3_0_0.{NewModeratedCoreAccountJsonV300, ViewBasicV300}
import code.api.v3_0_0.ViewBasicV300
import code.api.v3_1_0.AccountAttributeResponseJson
import code.api.v3_1_0.JSONFactory310.createAccountAttributeJson
import code.model.ModeratedBankAccount
import code.transactionrequests.TransactionRequests.TransactionChallengeTypes
import code.transactionrequests.TransactionRequests.TransactionRequestTypes.ACCOUNT_OTP
import com.openbankproject.commons.model.{AccountAttribute, AccountRoutingJsonV121, AmountOfMoneyJsonV121, Bank, TransactionRequest, TransactionRequestBodyAllTypes, TransactionTag, View}
import com.openbankproject.commons.model._
import scala.collection.immutable.List
@ -114,7 +113,7 @@ case class ModeratedCoreAccountJsonV400(
account_routings: List[AccountRoutingJsonV121],
views_basic: List[ViewBasicV300],
account_attributes: List[AccountAttributeResponseJson],
tags: List[TransactionTagJSON]
tags: List[AccountTagJSON]
)
case class ModeratedAccountJSON400(
@ -128,9 +127,22 @@ case class ModeratedAccountJSON400(
bank_id : String,
account_routing :AccountRoutingJsonV121,
account_attributes: List[AccountAttributeResponseJson],
tags: List[TransactionTagJSON]
tags: List[AccountTagJSON]
)
case class AccountTagJSON(
id : String,
value : String,
date : Date,
user : UserJSONV121
)
case class AccountTagsJSON(
tags: List[AccountTagJSON]
)
case class PostAccountTagJSON(
value : String
)
object JSONFactory400 {
def createBankJSON400(bank: Bank): BankJson400 = {
val obp = BankRoutingJsonV121("OBP", bank.bankId.value)
@ -227,7 +239,7 @@ object JSONFactory400 {
createAccountRoutingsJSON(account.accountRoutings),
views_basic = availableViews.map(view => code.api.v3_0_0.ViewBasicV300(id = view.viewId.value, short_name = view.name, description = view.description, is_public = view.isPublic)),
accountAttributes.map(createAccountAttributeJson),
tags.map(createTransactionTagJSON)
tags.map(createAccountTagJSON)
)
}
@ -248,7 +260,20 @@ object JSONFactory400 {
stringOrNull(account.bankId.value),
AccountRoutingJsonV121(stringOptionOrNull(account.accountRoutingScheme),stringOptionOrNull(account.accountRoutingAddress)),
accountAttributes.map(createAccountAttributeJson),
tags.map(createTransactionTagJSON)
tags.map(createAccountTagJSON)
)
}
def createAccountTagsJSON(tags : List[TransactionTag]) : AccountTagsJSON = {
new AccountTagsJSON(tags.map(createAccountTagJSON))
}
def createAccountTagJSON(tag : TransactionTag) : AccountTagJSON = {
new AccountTagJSON(
id = tag.id_,
value = tag.value,
date = tag.datePosted,
user = JSONFactory.createUserJSON(tag.postedBy)
)
}

View File

@ -33,7 +33,19 @@ trait ConsumersProvider {
def createConsumer(key: Option[String], secret: Option[String], isActive: Option[Boolean], name: Option[String], appType: Option[AppType], description: Option[String], developerEmail: Option[String], redirectURL: Option[String], createdByUserId: Option[String]): Box[Consumer]
def updateConsumer(id: Long, key: Option[String], secret: Option[String], isActive: Option[Boolean], name: Option[String], appType: Option[AppType], description: Option[String], developerEmail: Option[String], redirectURL: Option[String], createdByUserId: Option[String]): Box[Consumer]
def updateConsumerCallLimits(id: Long, perSecond: Option[String], perMinute: Option[String], perHour: Option[String], perDay: Option[String], perWeek: Option[String], perMonth: Option[String]): Future[Box[Consumer]]
def getOrCreateConsumer(consumerId: Option[String], key: Option[String], secret: Option[String], isActive: Option[Boolean], name: Option[String], appType: Option[AppType], description: Option[String], developerEmail: Option[String], redirectURL: Option[String], createdByUserId: Option[String]): Box[Consumer]
def getOrCreateConsumer(consumerId: Option[String],
key: Option[String],
secret: Option[String],
azp: Option[String],
iss: Option[String],
sub: Option[String],
isActive: Option[Boolean],
name: Option[String],
appType: Option[AppType],
description: Option[String],
developerEmail: Option[String],
redirectURL: Option[String],
createdByUserId: Option[String]): Box[Consumer]
def populateMissingUUIDs(): Boolean
}
@ -50,7 +62,19 @@ class RemotedataConsumersCaseClasses {
case class createConsumer(key: Option[String], secret: Option[String], isActive: Option[Boolean], name: Option[String], appType: Option[AppType], description: Option[String], developerEmail: Option[String], redirectURL: Option[String], createdByUserId: Option[String])
case class updateConsumer(id: Long, key: Option[String], secret: Option[String], isActive: Option[Boolean], name: Option[String], appType: Option[AppType], description: Option[String], developerEmail: Option[String], redirectURL: Option[String], createdByUserId: Option[String])
case class updateConsumerCallLimits(id: Long, perSecond: Option[String], perMinute: Option[String], perHour: Option[String], perDay: Option[String], perWeek: Option[String], perMonth: Option[String])
case class getOrCreateConsumer(consumerId: Option[String], key: Option[String], secret: Option[String], isActive: Option[Boolean], name: Option[String], appType: Option[AppType], description: Option[String], developerEmail: Option[String], redirectURL: Option[String], createdByUserId: Option[String])
case class getOrCreateConsumer(consumerId: Option[String],
key: Option[String],
secret: Option[String],
azp: Option[String],
iss: Option[String],
sub: Option[String],
isActive: Option[Boolean],
name: Option[String],
appType: Option[AppType],
description: Option[String],
developerEmail: Option[String],
redirectURL: Option[String],
createdByUserId: Option[String])
case class populateMissingUUIDs()
}

View File

@ -280,6 +280,9 @@ object MappedConsumersProvider extends ConsumersProvider with MdcLoggable {
override def getOrCreateConsumer(consumerId: Option[String],
key: Option[String],
secret: Option[String],
azp: Option[String],
iss: Option[String],
sub: Option[String],
isActive: Option[Boolean],
name: Option[String],
appType: Option[AppType],
@ -288,7 +291,13 @@ object MappedConsumersProvider extends ConsumersProvider with MdcLoggable {
redirectURL: Option[String],
createdByUserId: Option[String]): Box[Consumer] = {
Consumer.find(By(Consumer.consumerId, consumerId.getOrElse(Helpers.randomString(40)))) match {
val consumer =
// 1st try represent GatewayLogin usage of this function
Consumer.find(By(Consumer.consumerId, consumerId.getOrElse("None"))) or {
// 2nd try represent OAuth2 usage of this function
Consumer.find(By(Consumer.azp, azp.getOrElse("None")), By(Consumer.sub, sub.getOrElse("None")))
}
consumer match {
case Full(c) => Full(c)
case Failure(msg, t, c) => Failure(msg, t, c)
case ParamFailure(x,y,z,q) => ParamFailure(x,y,z,q)
@ -303,6 +312,18 @@ object MappedConsumersProvider extends ConsumersProvider with MdcLoggable {
case Some(v) => c.secret(v)
case None =>
}
azp match {
case Some(v) => c.azp(v)
case None =>
}
iss match {
case Some(v) => c.iss(v)
case None =>
}
sub match {
case Some(v) => c.sub(v)
case None =>
}
isActive match {
case Some(v) => c.isActive(v)
case None =>
@ -407,6 +428,15 @@ class Consumer extends LongKeyedMapper[Consumer] with CreatedUpdated{
object key extends MappedString(this, 250)
object secret extends MappedString(this, 250)
object azp extends MappedString(this, 250) {
override def defaultValue = null
}
object iss extends MappedString(this, 250) {
override def defaultValue = null
}
object sub extends MappedString(this, 250) {
override def defaultValue = null
}
object isActive extends MappedBoolean(this){
override def defaultValue = APIUtil.getPropsAsBoolValue("consumers_enabled_by_default", false)
}
@ -465,7 +495,7 @@ class Consumer extends LongKeyedMapper[Consumer] with CreatedUpdated{
*/
object Consumer extends Consumer with MdcLoggable with LongKeyedMetaMapper[Consumer] with CRUDify[Long, Consumer]{
override def dbIndexes = UniqueIndex(key) :: super.dbIndexes
override def dbIndexes = UniqueIndex(key) :: UniqueIndex(azp, sub) :: super.dbIndexes
//list all path : /admin/consumer/list
override def calcPrefix = List("admin",_dbTableNameLC)

View File

@ -54,8 +54,20 @@ object RemotedataConsumers extends ObpActorInit with ConsumersProvider {
def updateConsumerCallLimits(id: Long, perSecond: Option[String], perMinute: Option[String], perHour: Option[String], perDay: Option[String], perWeek: Option[String], perMonth: Option[String]): Future[Box[Consumer]] =
(actor ? cc.updateConsumerCallLimits(id, perSecond, perMinute, perHour, perDay, perWeek, perMonth)).mapTo[Box[Consumer]]
def getOrCreateConsumer(consumerId: Option[String], key: Option[String], secret: Option[String], isActive: Option[Boolean], name: Option[String], appType: Option[AppType], description: Option[String], developerEmail: Option[String], redirectURL: Option[String], createdByUserId: Option[String]): Box[Consumer] = getValueFromFuture(
(actor ? cc.getOrCreateConsumer(consumerId, key, secret, isActive, name, appType, description, developerEmail, redirectURL, createdByUserId)).mapTo[Box[Consumer]]
def getOrCreateConsumer(consumerId: Option[String],
key: Option[String],
secret: Option[String],
azp: Option[String],
iss: Option[String],
sub: Option[String],
isActive: Option[Boolean],
name: Option[String],
appType: Option[AppType],
description: Option[String],
developerEmail: Option[String],
redirectURL: Option[String],
createdByUserId: Option[String]): Box[Consumer] = getValueFromFuture(
(actor ? cc.getOrCreateConsumer(consumerId, key, secret, azp, iss, sub, isActive, name, appType, description, developerEmail, redirectURL, createdByUserId)).mapTo[Box[Consumer]]
)
def populateMissingUUIDs(): Boolean = getValueFromFuture(
(actor ? cc.populateMissingUUIDs()).mapTo[Boolean]

View File

@ -53,9 +53,9 @@ class RemotedataConsumersActor extends Actor with ObpActorHelper with MdcLoggabl
logger.debug("updateConsumerCallLimits(" + id + ", " + perSecond.getOrElse("None")+ ", " + perMinute.getOrElse("None") + ", " + perHour.getOrElse("None") + ", " + perDay.getOrElse("None") + ", " + perWeek.getOrElse("None") + ", " + perMonth.getOrElse("None") + ")")
sender ! (mapper.updateConsumerCallLimitsRemote(id, perSecond, perMinute, perHour, perDay, perWeek, perMonth))
case cc.getOrCreateConsumer(consumerId: Option[String], key: Option[String], secret: Option[String], isActive: Option[Boolean], name: Option[String], appType: Option[AppType], description: Option[String], developerEmail: Option[String], redirectURL: Option[String], createdByUserId: Option[String]) =>
logger.debug("getOrCreateConsumer(" + consumerId.getOrElse("None") + ", " + "*****" + ", " + "*****" + ", " + isActive.getOrElse("None") + ", " + name.getOrElse("None") + ", " + appType.getOrElse("None") + ", " + description.getOrElse("None") + ", " + developerEmail.getOrElse("None") + ", " + redirectURL.getOrElse("None") + ", " + createdByUserId.getOrElse("None") + ")")
sender ! (mapper.getOrCreateConsumer(consumerId, key, secret, isActive, name, appType, description, developerEmail, redirectURL, createdByUserId))
case cc.getOrCreateConsumer(consumerId: Option[String], key: Option[String], secret: Option[String], azp: Option[String], iss: Option[String], sub: Option[String], isActive: Option[Boolean], name: Option[String], appType: Option[AppType], description: Option[String], developerEmail: Option[String], redirectURL: Option[String], createdByUserId: Option[String]) =>
logger.debug("getOrCreateConsumer(" + consumerId.getOrElse("None") + ", " + "*****" + ", " + "*****" + ", " + azp.getOrElse("None") + ", " + iss.getOrElse("None") + ", " + sub.getOrElse("None") + ", " + name.getOrElse("None") + ", " + appType.getOrElse("None") + ", " + description.getOrElse("None") + ", " + developerEmail.getOrElse("None") + ", " + redirectURL.getOrElse("None") + ", " + createdByUserId.getOrElse("None") + ")")
sender ! (mapper.getOrCreateConsumer(consumerId, key, secret, azp, iss, sub, isActive, name, appType, description, developerEmail, redirectURL, createdByUserId))
case cc.populateMissingUUIDs() =>
logger.debug("populateMissingUUIDs()")

View File

@ -43,6 +43,20 @@ class WebUI extends MdcLoggable{
@transient protected val log = logger //Logger(this.getClass)
/**
* This function transform GitHub markdown to html.
* In case text contains html tag we avoid conversion.
* @param text markdown/html
* @return html code
*/
def makeHtml(text: String) = {
val hasHtmlTag = """<[^>]*>""".r.findFirstIn(text).isDefined
hasHtmlTag match {
case false => PegdownOptions.convertGitHubDocMarkdownToHtml(text)
case true => text
}
}
// Cookie Consent button.
// Note we don't currently (7th Jan 2017) need to display the cookie consent message due to our limited use of cookies
// If a deployment does make more use of cookies we would need to add a No button and we might want to make use of the

View File

@ -5,7 +5,6 @@ import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON
import code.api.util.APIUtil.OAuth._
import code.api.util.ApiVersion
import code.api.util.ErrorMessages.UserNotLoggedIn
import code.api.v1_2_1.{TransactionTagJSON, TransactionTagsJSON}
import code.api.v4_0_0.OBPAPI4_0_0.Implementations4_0_0
import com.github.dwickern.macros.NameOf.nameOf
import net.liftweb.json.Serialization.write
@ -24,7 +23,7 @@ class AccountTagTest extends V400ServerSetup {
object ApiEndpoint2 extends Tag(nameOf(Implementations4_0_0.deleteTagForViewOnAccount))
object ApiEndpoint3 extends Tag(nameOf(Implementations4_0_0.getTagsForViewOnAccount))
lazy val accountTag = SwaggerDefinitionsJSON.transactionTagJSON
lazy val accountTag = SwaggerDefinitionsJSON.accountTagJSON
lazy val bankId = randomBankId
lazy val bankAccount = randomPrivateAccount(bankId)
lazy val view = randomOwnerViewPermalink(bankId, bankAccount)
@ -71,12 +70,12 @@ class AccountTagTest extends V400ServerSetup {
Then("We should get a 201 and check the response body")
response.code should equal(201)
response.body.extract[TransactionTagJSON]
response.body.extract[AccountTagJSON]
val requestGet = (v4_0_0_Request / "banks" / bankId / "accounts" / bankAccount.id / view / "metadata" / "tags").GET <@ (user1)
val responseGet = makeGetRequest(requestGet)
responseGet.code should equal(200)
val tags = responseGet.body.extract[TransactionTagsJSON].tags
val tags = responseGet.body.extract[AccountTagsJSON].tags
tags.exists(_.value == accountTag.value) equals true
val tagId = tags.map(_.id).headOption.getOrElse("")

View File

@ -164,8 +164,8 @@ case class UpdateViewJSON(
* @define canEditOwnerComment If true, the view can edit the Transaction Owner Comment
* @define canAddComment If true, the view can add a Transaction Comment
* @define canDeleteComment If true, the view can delete a Transaction Comment
* @define canAddTag If true, the view can add a Transaction Tag
* @define canDeleteTag If true, the view can delete a Transaction Tag
* @define canAddTag If true, the view can add a Transaction/Account Tag
* @define canDeleteTag If true, the view can delete a Transaction/Account Tag
* @define canAddImage If true, the view can add a Transaction Image
* @define canDeleteImage If true, the view can delete a Transaction Image
* @define canAddWhereTag If true, the view can add a Transaction Where Tag