Merge pull request #1947 from OpenBankProject/deploy

Deploy
This commit is contained in:
tesobe-daniel 2021-09-15 13:03:29 +02:00 committed by GitHub
commit 32d47782d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
127 changed files with 7222 additions and 2007 deletions

View File

@ -143,6 +143,27 @@ Please use next values:
User Name:
Password:
### Notes on the basic ussage of Postgres:
Once postgres is installed: (On Mac use brew)
psql postgres
create database obpdb; (or any other name of your choosing)
create user obp; (this is the user that OBP-API will use to create and access tables etc)
alter user obp with password 'daniel.says'; (put this password in the OBP-API Props)
grant all on database obpdb to obp; (So OBP-API can create tables etc.)
Then set the db.url in your Props:
db.driver=org.postgresql.Driver
db.url=jdbc:postgresql://localhost:5432/obpdb?user=obp&password=daniel.says
The restart OBP-API
### Notes on using Postgres with SSL:
Postgres needs to be compiled with SSL support.

View File

@ -178,7 +178,7 @@
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
<version>4.5.13</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
@ -324,7 +324,7 @@
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>9.7</version>
<version>9.9.3</version>
</dependency>
<dependency>
<groupId>com.github.OpenBankProject</groupId>
@ -347,7 +347,7 @@
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>oauth2-oidc-sdk</artifactId>
<version>7.4</version>
<version>9.5.2</version>
</dependency>
<dependency>
<groupId>com.vladsch.flexmark</groupId>

View File

@ -368,8 +368,8 @@ Deleted = Deleted
consumer.registration.nav.name=Get API Key
invalid.login.credentials=Invalid Login Credentials
invalid.username=Invalid Username: \
1) Username must be between 8 and 100 characters long \
2) Username must not start with _ or . \
3) Username cannot contain or . or ._ or .. \
4) Allowed characters are: a-z A-Z 0-9 . _ \
5) Username must not end with _ or .
1) The ONLY allowed characters in Usernames are: a-z A-Z 0-9 . _ \
2) Usernames MUST be between 8 and 100 characters long \
3) Usernames MUST NOT start with _ or . \
4) Usernames MUST NOT contain __ or ._ or ._ or .. \
5) Usernames MUST NOT end with _ or .

View File

@ -1,6 +1,9 @@
#this is a sample props file you should edit and rename
#see https://www.assembla.com/wiki/show/liftweb/Properties for all the naming options, or just use "default.props" in this same folder
# See notes about WebUI Props below
### OBP-API configuration
@ -84,7 +87,8 @@ connectorMethod.cache.ttl.seconds=40
## swagger file should not generated for every request, this is a time-to-live in seconds for the generated swagger of OBP api,
## this value also represent how many seconds before the new endpoints will be shown after upload a new DynamicEntity.
## So if you want the new endpoints shown timely, set this value to a small number.
resourceDocsObp.cache.ttl.seconds=3600
dynamicResourceDocsObp.cache.ttl.seconds=3600
staticResourceDocsObp.cache.ttl.seconds=86400
## This can change the behavior of `Get Resource Docs`/`Get API Glossary`. If we set it to `true`, OBP will check the authentication and CanReadResourceDoc/CanReadGlossary Role
# the default value is false, so the `Get Resource Docs`/`Get API Glossary` is anonymous as default.
@ -336,6 +340,16 @@ BankMockKey=change_me
# Add WebUiProps
# Delete WebUiProps
# Get WebUiProps
# Please note: The non-commented-out webui_ props in this file are used to generate the default webui_ props used by the getWebUiProps endpoint which is used by API Manager -> Configurations -> WebUi Props
# i.e. If you add a webui_ props in code but don't add it here, it will be missing from the API Manager screen where users can set it.
# A note about WebUi Props precedence
# If a database WebUI Props is found it will be used first.
# If a database WebUI Props is not found, the version in the Props file will be used.
# Note: Props can also be loaded from the environment.
####################################################################################
@ -952,4 +966,98 @@ default_auth_context_update_request_key=CUSTOMER_NUMBER
#featured_api_collection_ids=
# the alias prefix path for BerlinGroupV1.3 (OBP built-in is berlin-group/v1.3), the format must be xxx/yyy, eg: 0.6/v1
#berlin_group_v1.3_alias.path=
#berlin_group_v1.3_alias.path=
# Support multiple brands on one instance. Note this needs checking on a clustered environment
#brands_enabled=false
# Support removing the app type checkbox during consumer registration
#consumer_registration.display_app_type=true
# if set this props, we can automatically grant the Entitlements required to use all the Dynamic Endpoint roles belonging
# to the bank_ids (Spaces) the User has access to via their validated email domain. Entitlements are generated /refreshed
# both following manual login and Direct Login token generation (POST).
# the default value is empty
#email_domain_to_space_mappings=
# And here we provide an example to show how to prepare the mappings
#email_domain_to_space_mappings=[ \
# { \
# "domain": "example.com", \
# "bank_ids": [ \
# "gh.29.uk", \
# "gh.29.fr" \
# ] \
# }, \
# { \
# "domain": "example2.com", \
# "bank_ids": [ \
# "gh.29.uk", \
# "gh.29.it" \
# ] \
# }\
# ]\
#
# if set this props, we can automatically grant the Entitlements required to the User has access to via their validated email domain.
# Entitlements are generated /refreshed both following manual login and Direct Login token generation (POST).
# the default value is empty
#email_domain_to_entitlement_mappings=
# And here we provide an example to show how to prepare the mappings
#email_domain_to_entitlement_mappings = [\
# {\
# "domain": "example.com",\
# "entitlements": [\
# {\
# "role_name": "CanReadResourceDoc",\
# "bank_id": ""\
# }\
# ]\
# }\
# ]\
#
# User Invitation Time To Live
# user_invitation.ttl.seconds=86400
# User (Developer) Invitation
webui_post_user_invitation_submit_button_value=Register as a Developer
webui_post_user_invitation_privacy_conditions_value=Privacy conditions..
webui_post_user_invitation_terms_and_conditions_value=Terms and Conditions..
webui_post_user_invitation_terms_and_conditions_checkbox_value=I agree to the above Developer Terms and Conditions
webui_developer_user_invitation_email_html_text=<!DOCTYPE html>\
<html>\
<head>\
<style>\
.a {\
border: none;\
color: white;\
padding: 15px 32px;\
text-align: center;\
text-decoration: none;\
display: inline-block;\
font-size: 16px;\
margin: 4px 2px;\
cursor: pointer;\
}\
\
.a1 {background-color: #4CAF50;} /* Green */\
.a2 {background-color: #008CBA;} /* Blue */\
</style>\
</head>\
<body>\
<img src="https://static.openbankproject.com/images/OBP_full_web_25pc.png"></img>\
<hr></hr><br></br>\
<p>Hi ${emailRecipient},<br></br>\
Welcome to the Open Bank Project API. Your account has been registered. Please use the below link to activate it.</p>\
<a href="${activateYourAccount}" class="a a1">Activate your account</a>\
<p>Our operations team has granted you the appropriate access to the OBP-API. If you have any questions, or you need any assistance, please contact our support.</p>\
<p>Thanks,<br></br> Your OBP API team</p><br></br>\
<hr></hr>\
<p>\
Please do not reply to this email. Should you wish to contact us, please raise a ticket at our support page. We maintain strict security standards and procedures to prevent unauthorised access to information about you. We will never contact you by email or otherwise and ask you to validate personal information such as your user ID, password or account numbers. This e-mail is confidential. It may also be legally privileged. If you are not the addressee you may not copy, forward, disclose or use any part of it. If you have received this message in error, please delete it and all copies from your system. Internet communications cannot be guaranteed to be timely, secure, error or virus-free. The sender does not accept liability for any errors or omissions.\
</p>\
</body>\
</html>

View File

@ -118,6 +118,7 @@ import code.transactionattribute.MappedTransactionAttribute
import code.transactionrequests.{MappedTransactionRequest, MappedTransactionRequestTypeCharge, TransactionRequestReasons}
import code.usercustomerlinks.MappedUserCustomerLink
import code.userlocks.UserLocks
import code.users.{UserAgreement, UserInvitation}
import code.util.Helper.MdcLoggable
import code.util.{Helper, HydraUtil}
import code.validation.JsonSchemaValidation
@ -128,6 +129,7 @@ import code.webuiprops.WebUiProps
import com.openbankproject.commons.model.ErrorMessage
import com.openbankproject.commons.util.Functions.Implicits._
import com.openbankproject.commons.util.{ApiVersion, Functions}
import javax.mail.{Authenticator, PasswordAuthentication}
import javax.mail.internet.MimeMessage
import net.liftweb.common._
import net.liftweb.db.DBLogEntry
@ -297,7 +299,7 @@ class Boot extends MdcLoggable {
case true => // DB already exist
// Migration Scripts are used to update the model of OBP-API DB to a latest version.
// Please note that migration scripts are executed before Lift Mapper Schemifier
Migration.database.executeScripts()
Migration.database.executeScripts(startedBeforeSchemifier = true)
logger.info("The Mapper database already exits. The scripts are executed BEFORE Lift Mapper Schemifier.")
case false => // DB is still not created. The scripts will be executed after Lift Mapper Schemifier
logger.info("The Mapper database is still not created. The scripts are going to be executed AFTER Lift Mapper Schemifier.")
@ -483,30 +485,31 @@ class Boot extends MdcLoggable {
logger.info (s"props_identifier is : ${APIUtil.getPropsValue("props_identifier", "NONE-SET")}")
// Build SiteMap
val indexPage = APIUtil.getPropsValue("server_mode", "apis,portal") match {
case mode if mode == "portal" => List(Menu.i("Home") / "index")
case mode if mode == "apis" => List()
case mode if mode.contains("apis") && mode.contains("portal") => List(Menu.i("Home") / "index")
case _ => List(Menu.i("Home") / "index")
}
val sitemap = indexPage ::: List(
Menu.i("Plain") / "plain",
Menu.i("Consumer Admin") / "admin" / "consumers" >> Admin.loginFirst >> LocGroup("admin")
submenus(Consumer.menus : _*),
Menu("Consumer Registration", Helper.i18n("consumer.registration.nav.name")) / "consumer-registration" >> AuthUser.loginFirst,
Menu("Dummy user tokens", "Get Dummy user tokens") / "dummy-user-tokens" >> AuthUser.loginFirst,
Menu("Validate OTP", "Validate OTP") / "otp" >> AuthUser.loginFirst,
// Menu.i("Metrics") / "metrics", //TODO: allow this page once we can make the account number anonymous in the URL
Menu.i("OAuth") / "oauth" / "authorize", //OAuth authorization page
Menu.i("Consent") / "consent" >> AuthUser.loginFirst,//OAuth consent page
OAuthWorkedThanks.menu, //OAuth thanks page that will do the redirect
Menu.i("INTRODUCTION") / "introduction",
Menu.i("add-user-auth-context-update-request") / "add-user-auth-context-update-request",
Menu.i("confirm-user-auth-context-update-request") / "confirm-user-auth-context-update-request"
val commonMap = List(Menu.i("Home") / "index") ::: List(
Menu.i("Plain") / "plain",
Menu.i("Consumer Admin") / "admin" / "consumers" >> Admin.loginFirst >> LocGroup("admin")
submenus(Consumer.menus : _*),
Menu("Consumer Registration", Helper.i18n("consumer.registration.nav.name")) / "consumer-registration" >> AuthUser.loginFirst,
Menu("Dummy user tokens", "Get Dummy user tokens") / "dummy-user-tokens" >> AuthUser.loginFirst,
Menu("Validate OTP", "Validate OTP") / "otp" >> AuthUser.loginFirst,
Menu("User Invitation", "User Invitation") / "user-invitation",
// Menu.i("Metrics") / "metrics", //TODO: allow this page once we can make the account number anonymous in the URL
Menu.i("OAuth") / "oauth" / "authorize", //OAuth authorization page
Menu.i("Consent") / "consent" >> AuthUser.loginFirst,//OAuth consent page
OAuthWorkedThanks.menu, //OAuth thanks page that will do the redirect
Menu.i("Introduction") / "introduction",
Menu.i("add-user-auth-context-update-request") / "add-user-auth-context-update-request",
Menu.i("confirm-user-auth-context-update-request") / "confirm-user-auth-context-update-request"
) ++ accountCreation ++ Admin.menus
// Build SiteMap
val sitemap = APIUtil.getPropsValue("server_mode", "apis,portal") match {
case mode if mode == "portal" => commonMap
case mode if mode == "apis" => List()
case mode if mode.contains("apis") && mode.contains("portal") => commonMap
case _ => commonMap
}
def sitemapMutators = AuthUser.sitemapMutator
@ -569,6 +572,13 @@ class Boot extends MdcLoggable {
implicit val formats = CustomJsonFormats.formats
LiftRules.exceptionHandler.prepend{
case(_, r, e) if DB.use(DefaultConnectionIdentifier){ conn => conn}.isClosed => {
logger.error("Exception being returned to browser when processing " + r.uri.toString, e)
JsonResponse(
Extraction.decompose(ErrorMessage(code = 500, message = s"${ErrorMessages.DatabaseConnectionClosedError}")),
500
)
}
case(Props.RunModes.Development, r, e) => {
logger.error("Exception being returned to browser when processing " + r.uri.toString, e)
JsonResponse(
@ -615,7 +625,7 @@ class Boot extends MdcLoggable {
// Migration Scripts are used to update the model of OBP-API DB to a latest version.
// Please note that migration scripts are executed after Lift Mapper Schemifier
Migration.database.executeScripts()
Migration.database.executeScripts(startedBeforeSchemifier = false)
// export one Connector's methods as endpoints, it is just for develop
APIUtil.getPropsValue("connector.name.export.as.endpoints").foreach { connectorName =>
@ -821,6 +831,8 @@ object ToSchemify {
AccountAccess,
ViewDefinition,
ResourceUser,
UserInvitation,
UserAgreement,
MappedComment,
MappedTag,
MappedWhereTag,

View File

@ -105,10 +105,14 @@ object GatewayLogin extends RestHelper with MdcLoggable {
def parseJwt(parameters: Map[String, String]): Box[String] = {
val jwt = getToken(parameters)
logger.debug("parseJwt says jwt.toString is: " + jwt.toString)
logger.debug("parseJwt says: validateJwtToken(jwt) is:" + validateJwtToken(jwt))
validateJwtToken(jwt) match {
case Full(jwtPayload) =>
logger.debug("parseJwt says: Full: " + jwtPayload.toString)
Full(compactRender(Extraction.decompose(jwtPayload)))
case _ =>
logger.debug("parseJwt says: Not Full(jwtPayload)")
Failure(ErrorMessages.GatewayLoginJwtTokenIsNotValid)
}
}
@ -119,11 +123,16 @@ object GatewayLogin extends RestHelper with MdcLoggable {
val claim = CertificateUtil.decryptJwtWithRsa(token)
Box(parse(claim.toString).extractOpt[PayloadOfJwtJSON])
case false =>
logger.debug("validateJwtToken says: verifying jwt token: " + token)
logger.debug(CertificateUtil.verifywtWithHmacProtection(token).toString)
CertificateUtil.verifywtWithHmacProtection(token) match {
case true =>
logger.debug("validateJwtToken says: jwt is verified: " + token)
val claim = CertificateUtil.parseJwtWithHmacProtection(token)
logger.debug("validateJwtToken says: this is claim of verified jwt: " + claim.toString())
Box(parse(claim.toString).extractOpt[PayloadOfJwtJSON])
case _ =>
logger.debug("validateJwtToken says: could not verify jwt")
Failure(ErrorMessages.GatewayLoginJwtTokenIsNotValid)
}
}
@ -251,7 +260,9 @@ object GatewayLogin extends RestHelper with MdcLoggable {
None,
name = Some(username),
email = None,
userId = None
userId = None,
createdByUserInvitationId = None,
company = None
)
} match {
case Full(u) =>
@ -418,7 +429,9 @@ object GatewayLogin extends RestHelper with MdcLoggable {
}
private def getToken(params: Map[String, String]): String = {
logger.debug("getToken params are: " + params.toString())
val token = params.getOrElse("token", "")
logger.debug("getToken wants to return token: " + token)
token
}

View File

@ -154,6 +154,8 @@ object OAuth2Login extends RestHelper with MdcLoggable {
hydraAdmin.deleteOAuth2Client(clientId)
hydraAdmin.createOAuth2Client(oAuth2Client)
} else if(stringNotEq(certInConsumer, cert)) {
logger.debug("Cert in Consumer: " + certInConsumer)
logger.debug("Cert in Request: " + cert)
return (Failure(Oauth2TokenMatchCertificateFail), Some(cc.copy(consumer = Failure(Oauth2TokenMatchCertificateFail))))
}
}
@ -279,7 +281,9 @@ object OAuth2Login extends RestHelper with MdcLoggable {
None,
name = getClaim(name = "given_name", idToken = idToken).orElse(Some(subject)),
email = getClaim(name = "email", idToken = idToken),
userId = None
userId = None,
createdByUserInvitationId = None,
company = None
)
}
}

View File

@ -262,6 +262,7 @@ trait OBPRestHelper extends RestHelper with MdcLoggable {
def failIfBadAuthorizationHeader(rd: Option[ResourceDoc])(fn: CallContext => Box[JsonResponse]) : JsonResponse = {
val authorization = S.request.map(_.header("Authorization")).flatten
val directLogin: Box[String] = S.request.map(_.header("DirectLogin")).flatten
val body: Box[String] = getRequestBody(S.request)
val implementedInVersion = S.request.openOrThrowException(attemptedToOpenAnEmptyBox).view
val verb = S.request.openOrThrowException(attemptedToOpenAnEmptyBox).requestType.method
@ -315,7 +316,9 @@ trait OBPRestHelper extends RestHelper with MdcLoggable {
case Failure(msg, t, c) => Failure(msg, t, c)
case _ => Failure("oauth error")
}
} else if (APIUtil.getPropsAsBoolValue("allow_direct_login", true) && hasDirectLoginHeader(authorization)) {
}
// Direct Login Deprecated i.e Authorization: DirectLogin token=eyJhbGciOiJIUzI1NiJ9.eyIiOiIifQ.Y0jk1EQGB4XgdqmYZUHT6potmH3mKj5mEaA9qrIXXWQ
else if (APIUtil.getPropsAsBoolValue("allow_direct_login", true) && directLogin.isDefined) {
DirectLogin.getUser match {
case Full(u) => {
val consumer = DirectLogin.getConsumer
@ -326,7 +329,21 @@ trait OBPRestHelper extends RestHelper with MdcLoggable {
Full(errorJsonResponse(message, httpCode))
}
}
} else if (APIUtil.getPropsAsBoolValue("allow_gateway_login", false) && hasGatewayHeader(authorization)) {
}
// Direct Login i.e DirectLogin: token=eyJhbGciOiJIUzI1NiJ9.eyIiOiIifQ.Y0jk1EQGB4XgdqmYZUHT6potmH3mKj5mEaA9qrIXXWQ
else if (APIUtil.getPropsAsBoolValue("allow_direct_login", true) && hasDirectLoginHeader(authorization)) {
DirectLogin.getUser match {
case Full(u) => {
val consumer = DirectLogin.getConsumer
fn(cc.copy(user = Full(u), consumer=consumer))
}// Authentication is successful
case _ => {
var (httpCode, message, directLoginParameters) = DirectLogin.validator("protectedResource")
Full(errorJsonResponse(message, httpCode))
}
}
}
else if (APIUtil.getPropsAsBoolValue("allow_gateway_login", false) && hasGatewayHeader(authorization)) {
logger.info("allow_gateway_login-getRemoteIpAddress: " + remoteIpAddress )
APIUtil.getPropsValue("gateway.host") match {
case Full(h) if h.split(",").toList.exists(_.equalsIgnoreCase(remoteIpAddress) == true) => // Only addresses from white list can use this feature

View File

@ -1,11 +1,13 @@
package code.api.ResourceDocs1_4_0
import java.util.UUID.randomUUID
import code.api.OBPRestHelper
import code.api.builder.OBP_APIBuilder
import code.api.cache.Caching
import code.api.util.APIUtil._
import code.api.util.ApiTag._
import code.api.util.ExampleValue.endpointMappingRequestBodyExample
import code.api.util.{APIUtil, _}
import code.api.v1_4_0.JSONFactory1_4_0.ResourceDocsJson
import code.api.v1_4_0.{APIMethods140, JSONFactory1_4_0, OBPAPI1_4_0}
@ -17,7 +19,7 @@ import code.api.v4_0_0.{APIMethods400, OBPAPI4_0_0}
import code.apicollectionendpoint.MappedApiCollectionEndpointsProvider
import code.util.Helper.MdcLoggable
import com.github.dwickern.macros.NameOf.nameOf
import com.openbankproject.commons.model.ListResult
import com.openbankproject.commons.model.{ListResult, User}
import com.openbankproject.commons.model.enums.ContentParam.{ALL, DYNAMIC, STATIC}
import com.openbankproject.commons.model.enums.LanguageParam._
import com.openbankproject.commons.model.enums.{ContentParam, LanguageParam}
@ -31,9 +33,13 @@ import net.liftweb.json.JsonAST.{JField, JString, JValue}
import net.liftweb.json._
import net.liftweb.util.Helpers.tryo
import net.liftweb.util.Props
import java.util.concurrent.ConcurrentHashMap
import code.api.util.NewStyle.HttpCode
import code.util.Helper
import scala.collection.immutable.{List, Nil}
import scala.concurrent.Future
// JObject creation
import code.api.v1_2_1.{APIInfoJSON, APIMethods121, HostedBy, OBPAPI1_2_1}
@ -49,6 +55,8 @@ import code.util.Helper.booleanToBox
import scala.concurrent.duration._
import com.openbankproject.commons.ExecutionContext.Implicits.global
trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMethods210 with APIMethods200 with APIMethods140 with APIMethods130 with APIMethods121{
//needs to be a RestHelper to get access to JsonGet, JsonPost, etc.
@ -203,7 +211,8 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
//implicit val scalaCache = ScalaCache(GuavaCache(underlyingGuavaCache))
// if upload DynamicEntity, will generate corresponding endpoints, when current cache timeout, the new endpoints will be shown.
// so if you want the new generated endpoints shown timely, set this value to a small number, or set to a big number
val getResourceDocsTTL : Int = APIUtil.getPropsValue(s"resourceDocsObp.cache.ttl.seconds", "3600").toInt
val getDynamicResourceDocsTTL : Int = APIUtil.getPropsValue(s"dynamicResourceDocsObp.cache.ttl.seconds", "0").toInt
val getStaticResourceDocsTTL : Int = APIUtil.getPropsValue(s"staticResourceDocsObp.cache.ttl.seconds", "0").toInt
/**
*
@ -213,7 +222,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
* @param contentParam if this is Some(`true`), only show dynamic endpoints, if Some(`false`), only show static. If it is None, we will show all. default is None
* @return
*/
private def getResourceDocsObpCached(requestedApiVersion : ApiVersion,
private def getStaticResourceDocsObpCached(requestedApiVersion : ApiVersion,
resourceDocTags: Option[List[ResourceDocTag]],
partialFunctionNames: Option[List[String]]
) : Box[JValue] = {
@ -225,7 +234,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
*/
var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString)
CacheKeyFromArguments.buildCacheKey {
Caching.memoizeSyncWithProvider (Some(cacheKey.toString())) (getResourceDocsTTL second) {
Caching.memoizeSyncWithProvider (Some(cacheKey.toString())) (getStaticResourceDocsTTL second) {
logger.debug(s"Generating OBP Resource Docs requestedApiVersion is $requestedApiVersion")
val resourceDocJson = resourceDocsToResourceDocJson(getResourceDocsList(requestedApiVersion), resourceDocTags, partialFunctionNames)
@ -234,39 +243,99 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
}
}
private def getResourceDocsObpDynamic(requestedApiVersion : ApiVersion,
/**
*
* @param requestedApiVersion
* @param resourceDocTags
* @param partialFunctionNames
* @param contentParam if this is Some(`true`), only show dynamic endpoints, if Some(`false`), only show static. If it is None, we will show all. default is None
* @return
*/
private def getAllResourceDocsObpCached(requestedApiVersion : ApiVersion,
resourceDocTags: Option[List[ResourceDocTag]],
partialFunctionNames: Option[List[String]]
) : Box[JValue] = {
/**
* Please note that "var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString)"
* is just a temporary value field with UUID values in order to prevent any ambiguity.
* The real value will be assigned by Macro during compile time at this line of a code:
* https://github.com/OpenBankProject/scala-macros/blob/master/macros/src/main/scala/com/tesobe/CacheKeyFromArgumentsMacro.scala#L49
*/
var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString)
CacheKeyFromArguments.buildCacheKey {
Caching.memoizeSyncWithProvider (Some(cacheKey.toString())) (getStaticResourceDocsTTL second) {
val dynamicDocs = (DynamicEntityHelper.doc ++ DynamicEndpointHelper.doc ++ DynamicEndpoints.dynamicResourceDocs)
.filter(rd => rd.implementedInApiVersion == requestedApiVersion)
.map(it => it.specifiedUrl match {
case Some(_) => it
case _ =>
it.specifiedUrl = Some(s"/${it.implementedInApiVersion.urlPrefix}/${requestedApiVersion.vDottedApiVersion}${it.requestUrl}")
it
})
.toList
val filteredDocs = resourceDocTags match {
// We have tags
case Some(tags) => {
// This can create duplicates to use toSet below
for {
r <- dynamicDocs
t <- tags
if r.tags.contains(t)
} yield {
r
}
}
// tags param was not mentioned in url or was empty, so return all
case None => dynamicDocs
}
val staticDocs = getResourceDocsList(requestedApiVersion)
val allDocs = staticDocs.map( _ ++ filteredDocs)
val resourceDocJson = resourceDocsToResourceDocJson(allDocs, resourceDocTags, partialFunctionNames)
resourceDocJson.map(resourceDocsJsonToJsonResponse)
}
}
}
private def getResourceDocsObpDynamicCached(requestedApiVersion : ApiVersion,
resourceDocTags: Option[List[ResourceDocTag]],
partialFunctionNames: Option[List[String]]
): Option[JValue] = {
val dynamicDocs = (DynamicEntityHelper.doc ++ DynamicEndpointHelper.doc ++ DynamicEndpoints.dynamicResourceDocs)
.filter(rd => rd.implementedInApiVersion == requestedApiVersion)
.map(it => it.specifiedUrl match {
case Some(_) => it
case _ =>
it.specifiedUrl = Some(s"/${it.implementedInApiVersion.urlPrefix}/${requestedApiVersion.vDottedApiVersion}${it.requestUrl}")
it
})
.toList
val filteredDocs = resourceDocTags match {
// We have tags
case Some(tags) => {
// This can create duplicates to use toSet below
for {
r <- dynamicDocs
t <- tags
if r.tags.contains(t)
} yield {
r
var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString)
CacheKeyFromArguments.buildCacheKey {
Caching.memoizeSyncWithProvider (Some(cacheKey.toString())) (getDynamicResourceDocsTTL second) {
val dynamicDocs = (DynamicEntityHelper.doc ++ DynamicEndpointHelper.doc ++ DynamicEndpoints.dynamicResourceDocs)
.filter(rd => rd.implementedInApiVersion == requestedApiVersion)
.map(it => it.specifiedUrl match {
case Some(_) => it
case _ =>
it.specifiedUrl = Some(s"/${it.implementedInApiVersion.urlPrefix}/${requestedApiVersion.vDottedApiVersion}${it.requestUrl}")
it
})
.toList
val filteredDocs = resourceDocTags match {
// We have tags
case Some(tags) => {
// This can create duplicates to use toSet below
for {
r <- dynamicDocs
t <- tags
if r.tags.contains(t)
} yield {
r
}
}
// tags param was not mentioned in url or was empty, so return all
case None => dynamicDocs
}
}
// tags param was not mentioned in url or was empty, so return all
case None => dynamicDocs
}
val resourceDocJson = resourceDocsToResourceDocJson(Some(filteredDocs), resourceDocTags, partialFunctionNames)
resourceDocJson.map(resourceDocsJsonToJsonResponse)
}
val resourceDocJson = resourceDocsToResourceDocJson(Some(filteredDocs), resourceDocTags, partialFunctionNames)
resourceDocJson.map(resourceDocsJsonToJsonResponse)
}}}
@ -406,54 +475,45 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
// Note: description uses html markup because original markdown doesn't easily support "_" and there are multiple versions of markdown.
def getResourceDocsObp : OBPEndpoint = {
case "resource-docs" :: requestedApiVersionString :: "obp" :: Nil JsonGet _ => {
cc =>{
val (tags, partialFunctions, languageParam, contentParam, apiCollectionIdParam) = ResourceDocsAPIMethodsUtil.getParams()
cc =>
for {
_ <- if (resourceDocsRequireRole)//If set resource_docs_requires_role=true, we need check the authentication and the roles
for{
u <- cc.user ?~ UserNotLoggedIn
hasCanReadResourceDocRole <- NewStyle.function.ownEntitlement("", u.userId, ApiRole.canReadResourceDoc, cc.callContext)
} yield{
hasCanReadResourceDocRole
}
else
Full()//If set resource_docs_requires_role=false, just return the response directly..
(tags, partialFunctions, languageParam, contentParam, apiCollectionIdParam) <- Full(ResourceDocsAPIMethodsUtil.getParams())
requestedApiVersion <- tryo {ApiVersionUtils.valueOf(requestedApiVersionString)} ?~! s"$InvalidApiVersionString $requestedApiVersionString"
_ <- booleanToBox(versionIsAllowed(requestedApiVersion), s"$ApiVersionNotSupported $requestedApiVersionString")
(u: Box[User], callContext: Option[CallContext]) <- resourceDocsRequireRole match {
case false => anonymousAccess(cc)
case true => authenticatedAccess(cc) // If set resource_docs_requires_role=true, we need check the authentication
}
_ <- resourceDocsRequireRole match {
case false => Future()
case true => // If set resource_docs_requires_role=true, we need check the the roles as well
Future(NewStyle.function.ownEntitlement("", u.map(_.userId).getOrElse(""), ApiRole.canReadResourceDoc, cc.callContext))
}
requestedApiVersion <- NewStyle.function.tryons(s"$InvalidApiVersionString $requestedApiVersionString", 400, callContext) {ApiVersionUtils.valueOf(requestedApiVersionString)}
_ <- Helper.booleanToFuture(s"$ApiVersionNotSupported $requestedApiVersionString", 400, callContext)(versionIsAllowed(requestedApiVersion))
json <- languageParam match {
case Some(ZH) => getChineseVersionResourceDocs
case Some(ZH) => Future(getChineseVersionResourceDocs)
case _ if(apiCollectionIdParam.isDefined) =>
val operationIds = MappedApiCollectionEndpointsProvider.getApiCollectionEndpoints(apiCollectionIdParam.getOrElse("")).map(_.operationId).map(getObpFormatOperationId)
val resourceDocs = ResourceDoc.getResourceDocs(operationIds)
val resourceDocsJson = JSONFactory1_4_0.createResourceDocsJson(resourceDocs)
val resourceDocsJsonJValue = Full(resourceDocsJsonToJsonResponse(resourceDocsJson))
resourceDocsJsonJValue.map(successJsonResponse(_))
Future(resourceDocsJsonJValue.map(successJsonResponse(_)))
case _ =>
contentParam match {
case Some(DYNAMIC) =>
val dynamicDocs: Box[JValue] = getResourceDocsObpDynamic(requestedApiVersion, tags, partialFunctions)
dynamicDocs.map(successJsonResponse(_))
val dynamicDocs: Box[JValue] = getResourceDocsObpDynamicCached(requestedApiVersion, tags, partialFunctions)
Future(dynamicDocs.map(successJsonResponse(_)))
case Some(STATIC) =>
val staticDocs: Box[JValue] = getResourceDocsObpCached(requestedApiVersion, tags, partialFunctions)
staticDocs.map(successJsonResponse(_))
val staticDocs: Box[JValue] = getStaticResourceDocsObpCached(requestedApiVersion, tags, partialFunctions)
Future(staticDocs.map(successJsonResponse(_)))
case _ =>
val dynamicDocs: Box[JValue] = getResourceDocsObpDynamic(requestedApiVersion, tags, partialFunctions)
val staticDocs: Box[JValue] = getResourceDocsObpCached(requestedApiVersion, tags, partialFunctions)
for {
dDocs <- dynamicDocs
sDocs <- staticDocs
} yield {
val mergedJson = dDocs.merge(sDocs)
successJsonResponse(mergedJson)
}
val docs: Box[JValue] = getAllResourceDocsObpCached(requestedApiVersion, tags, partialFunctions)
Future(docs.map(successJsonResponse(_)))
}
}
} yield {
json
(json, HttpCode.`200`(callContext))
}
}
}
}
@ -519,7 +579,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
*/
var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString)
CacheKeyFromArguments.buildCacheKey {
Caching.memoizeSyncWithProvider (Some(cacheKey.toString())) (getResourceDocsTTL second) {
Caching.memoizeSyncWithProvider (Some(cacheKey.toString())) (getStaticResourceDocsTTL second) {
logger.debug(s"Generating Swagger requestedApiVersion is $requestedApiVersionString")
Box.tryo(ApiVersionUtils.valueOf(requestedApiVersionString)) match {
@ -582,16 +642,26 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
* dynamic endpoints related structure is not STABLE structure, no need be parsed to a static structure.
* So here filter out them.
*/
case doc if doc.partialFunctionName == nameOf(APIMethods400.Implementations4_0_0.createDynamicEndpoint) =>
case doc if (doc.partialFunctionName == nameOf(APIMethods400.Implementations4_0_0.createDynamicEndpoint) ||
doc.partialFunctionName == nameOf(APIMethods400.Implementations4_0_0.createBankLevelDynamicEndpoint) ) =>
doc.copy(exampleRequestBody = ExampleValue.dynamicEndpointRequestBodyEmptyExample,
successResponseBody = ExampleValue.dynamicEndpointResponseBodyEmptyExample
)
case doc if doc.partialFunctionName == nameOf(APIMethods400.Implementations4_0_0.getDynamicEndpoint) =>
case doc if (doc.partialFunctionName == nameOf(APIMethods400.Implementations4_0_0.createEndpointMapping) ||
doc.partialFunctionName == nameOf(APIMethods400.Implementations4_0_0.createBankLevelEndpointMapping) ) =>
doc.copy(
exampleRequestBody = endpointMappingRequestBodyExample,
successResponseBody = endpointMappingRequestBodyExample
)
case doc if ( doc.partialFunctionName == nameOf(APIMethods400.Implementations4_0_0.getDynamicEndpoint) ||
doc.partialFunctionName == nameOf(APIMethods400.Implementations4_0_0.getBankLevelDynamicEndpoint)) =>
doc.copy(successResponseBody = ExampleValue.dynamicEndpointResponseBodyEmptyExample)
case doc if (doc.partialFunctionName == nameOf(APIMethods400.Implementations4_0_0.getDynamicEndpoints) ||
doc.partialFunctionName == nameOf(APIMethods400.Implementations4_0_0.getMyDynamicEndpoints)
doc.partialFunctionName == nameOf(APIMethods400.Implementations4_0_0.getMyDynamicEndpoints) ||
doc.partialFunctionName == nameOf(APIMethods400.Implementations4_0_0.getBankLevelDynamicEndpoints)
)=>
doc.copy(successResponseBody = ListResult(
"dynamic_endpoints",

View File

@ -1691,6 +1691,27 @@ object SwaggerDefinitionsJSON {
username = usernameExample.value,
entitlements = entitlementJSONs
)
val userIdJsonV400 = UserIdJsonV400(
user_id = ExampleValue.userIdExample.value
)
val userInvitationPostJsonV400 = PostUserInvitationJsonV400(
first_name = ExampleValue.nameExample.value,
last_name = ExampleValue.nameExample.value,
email = ExampleValue.emailExample.value,
company = "Tesobe",
country = "Germany",
purpose = "Developer"
)
val userInvitationJsonV400 = UserInvitationJsonV400(
first_name = ExampleValue.nameExample.value,
last_name = ExampleValue.nameExample.value,
email = ExampleValue.emailExample.value,
company = "TESOBE",
country = "Germany",
purpose = "Developer",
status = "CREATED"
)
val entitlementRequestJSON =
code.api.v3_0_0.EntitlementRequestJSON(
@ -2290,6 +2311,24 @@ object SwaggerDefinitionsJSON {
created = DateWithDayExampleObject
)
val consumerJsonV400 = ConsumerJson(
consumer_id = ExampleValue.consumerIdExample.value,
key = ExampleValue.consumerSecretExample.value,
secret = ExampleValue.consumerKeyExample.value,
app_name = "SOFI",
app_type = "Web",
description = "Account Management",
client_certificate = """-----BEGIN CERTIFICATE-----
|client_certificate_content
|-----END CERTIFICATE-----""".stripMargin,
developer_email = ExampleValue.emailExample.value,
redirect_url = "www.openbankproject.com",
created_by_user_id = ExampleValue.userIdExample.value,
created_by_user = resourceUserJSON,
enabled = true,
created = DateWithDayExampleObject
)
val consumersJson310 = ConsumersJsonV310(
List(consumerJsonV310)
)
@ -4136,13 +4175,6 @@ object SwaggerDefinitionsJSON {
val jsonCodeTemplate = "code" -> URLEncoder.encode("""println("hello")""", "UTF-8")
val endpointMappingJson = EndpointMappingCommons(
Some("b4e0352a-9a0f-4bfa-b30b-9003aa467f50"),
"OBPv4.0.0-dynamicEndpoint_GET_pet_PET_ID",
"""{}""".stripMargin,
"""{}""".stripMargin
)
val supportedCurrenciesJson = SupportedCurrenciesJson(
supportedCurrenciesExample.value
.replaceAll(""""""","").replace("""[""","")

View File

@ -491,13 +491,7 @@ object SwaggerJSONFactory extends MdcLoggable {
tags = rd.tags.map(_.tag),
summary = rd.summary,
description = PegdownOptions.convertPegdownToHtmlTweaked(rd.description.stripMargin).replaceAll("\n", ""),
operationId =
rd.partialFunctionName match {
//No longer need this special case since all transaction request Resource Docs have explicit URL
//case "createTransactionRequest" => s"${rd.apiVersion.toString }-${rd.apiFunction.toString}-${UUID.randomUUID().toString}"
// Note: The operationId should not start with a number becuase Javascript constructors may use it to build variables.
case _ => s"${rd.implementedInApiVersion.fullyQualifiedVersion }-${rd.partialFunctionName.toString }"
},
operationId =s"${rd.implementedInApiVersion.fullyQualifiedVersion }-${rd.partialFunctionName.toString }",
parameters ={
val description = rd.exampleRequestBody match {
case EmptyBody => ""

View File

@ -260,33 +260,35 @@ of the PSU at this ASPSP.
""",
emptyObjectJson,
json.parse("""{
| "accounts":[{
| "resourceId":"8ca8a7e4-6d02-40e3-a129-0b2bf89de9f0",
| "iban":"DE91 1000 0000 0123 4567 89",
| "bban":" 1000 0000 0123 4567 89",
| "currency":"EUR",
| "name":"TOM",
| "product":"AC",
| "cashAccountType":"AC",
| "bic":"AAAADEBBXXX",
| "balances":{
| "balanceAmount":{
| "currency":"EUR",
| "amount":"50.89"
| },
| "balanceType":"AC",
| "lastChangeDateTime":"2020-07-02T10:23:57.81Z",
| "referenceDate":"2020-07-02",
| "lastCommittedTransaction":"entryReference of the last commited transaction to support the TPP in identifying whether all PSU transactions are already known."
| "accounts": [
| {
| "resourceId": "3dc3d5b3-7023-4848-9853-f5400a64e80f",
| "iban": "DE2310010010123456789",
| "currency": "EUR",
| "product": "Girokonto",
| "cashAccountType": "CACC",
| "name": "Main Account",
| "_links": {
| "balances": {
| "href": "/v1/accounts/3dc3d5b3-7023-4848-9853-f5400a64e80f/balances"
| }
| }
| },
| "_links":{
| "balances":{
| "href":"/v1.3/accounts/8ca8a7e4-6d02-40e3-a129-0b2bf89de9f0/balances"
| {
| "resourceId": "3dc3d5b3-7023-4848-9853-f5400a64e81g",
| "iban": "DE2310010010123456788",
| "currency": "USD",
| "product": "Fremdwährungskonto",
| "cashAccountType": "CACC",
| "name": "US Dollar Account",
| "_links": {
| "balances": {
| "href": "/v1/accounts/3dc3d5b3-7023-4848-9853-f5400a64e81g/balances"
| }
| }
| }
| }]
|}
|""".stripMargin),
| ]
|}""".stripMargin),
List(UserNotLoggedIn, UnknownError),
ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil
)
@ -357,8 +359,9 @@ The account-id is constant at least throughout the lifecycle of a given consent.
_ <- passesPsd2Aisp(callContext)
(account: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(accountId, callContext)
_ <- checkAccountAccess(ViewId(SYSTEM_READ_BALANCES_BERLIN_GROUP_VIEW_ID), u, account, callContext)
(accountBalances, callContext)<- NewStyle.function.getBankAccountBalances(BankIdAccountId(account.bankId,account.accountId), callContext)
} yield {
(JSONFactory_BERLIN_GROUP_1_3.createAccountBalanceJSON(account), HttpCode.`200`(callContext))
(JSONFactory_BERLIN_GROUP_1_3.createAccountBalanceJSON(account, accountBalances), HttpCode.`200`(callContext))
}
}
}
@ -390,25 +393,9 @@ respectively the OAuth2 access token.
"currency": "EUR",
"amount": 15000
},
"balances": [
{
"balanceType": "interimBooked",
"balanceAmount": {
"currency": "EUR",
"amount": 14355.78
}
},
{
"balanceType": "nonBilled",
"balanceAmount": {
"currency": "EUR",
"amount": 4175.86
}
}
],
"_links": {
"transactions": {
"href": "/v1/card-accounts/3d9a81b3-a47d-4130-8765-a9c0ff861b99/transactions"
"balances": {
"href": "/v1/card-accounts/3d9a81b3-a47d-4130-8765-a9c0ff861b99/balances"
}
}
}
@ -487,8 +474,9 @@ This account-id then can be retrieved by the
_ <- passesPsd2Aisp(callContext)
(account: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(AccountId(accountId), callContext)
_ <- checkAccountAccess(ViewId(SYSTEM_READ_BALANCES_BERLIN_GROUP_VIEW_ID), u, account, callContext)
(accountBalances, callContext)<- NewStyle.function.getBankAccountBalances(BankIdAccountId(account.bankId,account.accountId), callContext)
} yield {
(JSONFactory_BERLIN_GROUP_1_3.createCardAccountBalanceJSON(account), HttpCode.`200`(callContext))
(JSONFactory_BERLIN_GROUP_1_3.createCardAccountBalanceJSON(account, accountBalances), HttpCode.`200`(callContext))
}
}
}
@ -942,24 +930,22 @@ Give detailed information about the addressed account together with balance info
""",
emptyObjectJson,
json.parse("""{
"cashAccountType" : { },
"product" : "product",
"resourceId" : "resourceId",
"bban" : "BARC12345612345678",
"_links" : {
"balances" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983",
"transactions" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983"
},
"usage" : "PRIV",
"balances" : "",
"iban" : "FR7612345987650123456789014",
"linkedAccounts" : "linkedAccounts",
"name" : "name",
"currency" : "EUR",
"details" : "details",
"msisdn" : "+49 170 1234567",
"bic" : "AAAADEBBXXX",
"status" : { }
"account": {
"resourceId": "3dc3d5b3-7023-4848-9853-f5400a64e80f",
"iban": "FR7612345987650123456789014",
"currency": "EUR",
"product": "Girokonto",
"cashAccountType": "CACC",
"name": "Main Account",
"_links": {
"balances": {
"href": "/v1/accounts/3dc3d5b3-7023-4848-9853-f5400a64e80f/balances"
},
"transactions": {
"href": "/v1/accounts/3dc3d5b3-7023-4848-9853-f5400a64e80f/transactions"
}
}
}
}"""),
List(UserNotLoggedIn, UnknownError),
ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil
@ -994,24 +980,27 @@ respectively the OAuth2 access token.
""",
emptyObjectJson,
json.parse("""{
"balances" : "",
"product" : "product",
"resourceId" : "resourceId",
"maskedPan" : "123456xxxxxx1234",
"_links" : {
"balances" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983",
"transactions" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983"
},
"usage" : "PRIV",
"name" : "name",
"creditLimit" : {
"amount" : "123",
"currency" : "EUR"
},
"currency" : "EUR",
"details" : "details",
"status" : { }
}"""),
| "cardAccount": {
| "resourceId": "3d9a81b3-a47d-4130-8765-a9c0ff861b99",
| "maskedPan": "525412******3241",
| "currency": "EUR",
| "name": "Main",
| "product": "Basic Credit",
| "status": "enabled",
| "creditLimit": {
| "currency": "EUR",
| "amount": "15000"
| },
| "_links": {
| "balances": {
| "href": "/v1/card-accounts/3d9a81b3-a47d-4130-8765-a9c0ff861b99/balances"
| },
| "transactions": {
| "href": "/v1/card-accounts/3d9a81b3-a47d-4130-8765-a9c0ff861b99/transactions"
| }
| }
| }
|}""".stripMargin),
List(UserNotLoggedIn, UnknownError),
ApiTag("Account Information Service (AIS)") :: Nil
)

View File

@ -51,7 +51,7 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats {
// linkedAccounts: String ="string",
// usage: String ="PRIV",
// details: String ="",
balances: CoreAccountBalancesJson,
// balances: CoreAccountBalancesJson,// We put this under the _links, not need to show it here.
_links: CoreAccountLinksJsonV13,
)
@ -277,14 +277,7 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats {
x =>
val (iBan: String, bBan: String) = getIbanAndBban(x)
val balance =
CoreAccountBalancesJson(
balanceAmount = AmountOfMoneyV13(x.currency,x.balance.toString()),
balanceType = APIUtil.stringOrNull(x.accountType),
lastChangeDateTime= APIUtil.dateOrNull(x.lastUpdate),
referenceDate = APIUtil.dateOrNull(x.lastUpdate),
lastCommittedTransaction = ""
)
CoreAccountJsonV13(
resourceId = x.accountId.value,
iban = iBan,
@ -294,7 +287,6 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats {
bic = getBicFromBankId(x.bankId.value),
cashAccountType = x.accountType,
product = x.accountType,
balances = balance,
_links = CoreAccountLinksJsonV13(LinkHrefJson(s"/${OBP_BERLIN_GROUP_1_3.apiVersion.urlPrefix}/${OBP_BERLIN_GROUP_1_3.version}/accounts/${x.accountId.value}/balances"))
)
}
@ -306,14 +298,6 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats {
x =>
val (iBan: String, bBan: String) = getIbanAndBban(x)
val balance =
CoreAccountBalancesJson(
balanceAmount = AmountOfMoneyV13(x.currency,x.balance.toString()),
balanceType = APIUtil.stringOrNull(x.accountType),
lastChangeDateTime= APIUtil.dateOrNull(x.lastUpdate),
referenceDate = APIUtil.dateOrNull(x.lastUpdate),
lastCommittedTransaction = "String"
)
CoreAccountJsonV13(
resourceId = x.accountId.value,
iban = iBan,
@ -323,7 +307,6 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats {
bic = getBicFromBankId(x.bankId.value),
cashAccountType = x.accountType,
product = x.accountType,
balances = balance,
_links = CoreAccountLinksJsonV13(LinkHrefJson(s"/${OBP_BERLIN_GROUP_1_3.apiVersion.urlPrefix}/${OBP_BERLIN_GROUP_1_3.version}/accounts/${x.accountId.value}/balances"))
)
}
@ -359,12 +342,12 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats {
(iBan, bBan)
}
def createCardAccountBalanceJSON(bankAccount: BankAccount): CardAccountBalancesV13 = {
val accountBalancesV13 = createAccountBalanceJSON(bankAccount: BankAccount)
def createCardAccountBalanceJSON(bankAccount: BankAccount, accountBalances: AccountBalances): CardAccountBalancesV13 = {
val accountBalancesV13 = createAccountBalanceJSON(bankAccount: BankAccount, accountBalances)
CardAccountBalancesV13(accountBalancesV13.account,accountBalancesV13.`balances`)
}
def createAccountBalanceJSON(bankAccount: BankAccount): AccountBalancesV13 = {
def createAccountBalanceJSON(bankAccount: BankAccount, accountBalances: AccountBalances): AccountBalancesV13 = {
val (iban: String, bban: String) = getIbanAndBban(bankAccount)
@ -372,17 +355,14 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats {
account = FromAccount(
iban = iban,
),
`balances` = AccountBalance(
balanceAmount = AmountOfMoneyV13(
currency = APIUtil.stringOrNull(bankAccount.currency),
amount = bankAccount.balance.toString()
),
balanceType = APIUtil.stringOrNull(bankAccount.accountType),
`balances` = accountBalances.balances.map(accountBalance => AccountBalance(
balanceAmount = AmountOfMoneyV13(accountBalance.balance.currency, accountBalance.balance.amount),
balanceType = accountBalance.balanceType,
lastChangeDateTime = APIUtil.dateOrNull(bankAccount.lastUpdate),
referenceDate = APIUtil.dateOrNull(bankAccount.lastUpdate),
lastCommittedTransaction = "String"
) :: Nil
)
)
))
}
def createTransactionJSON(bankAccount: BankAccount, transaction : ModeratedTransaction, creditorAccount: CreditorAccountJson) : TransactionJsonV13 = {

View File

@ -29,11 +29,12 @@ package code.api
import java.util.Date
import code.api.util.APIUtil._
import code.api.util.ErrorMessages.InvalidDirectLoginParameters
import code.api.util.NewStyle.HttpCode
import code.api.util._
import code.consumer.Consumers._
import code.model.dataAccess.AuthUser
import code.model.{Consumer, Token, TokenType}
import code.model.{Consumer, Token, TokenType, UserX}
import code.token.Tokens
import code.util.Helper.{MdcLoggable, SILENCE_IS_GOLDEN}
import com.nimbusds.jwt.JWTClaimsSet
@ -43,6 +44,7 @@ import net.liftweb.common._
import net.liftweb.http._
import net.liftweb.http.rest.RestHelper
import net.liftweb.util.Helpers
import net.liftweb.util.Helpers.tryo
import scala.compat.Platform
import scala.concurrent.Future
@ -93,9 +95,10 @@ object DirectLogin extends RestHelper with MdcLoggable {
{
//Handling get request for a token
case Req("my" :: "logins" :: "direct" :: Nil,_ , PostRequest) => {
for(
(httpCode: Int, message: String) <- createTokenFuture(getAllParameters)
) yield {
for{
(httpCode: Int, message: String, userId:Long) <- createTokenFuture(getAllParameters)
_ <- Future{grantEntitlementsToUseDynamicEndpointsInSpacesInDirectLogin(userId)}
} yield {
if (httpCode == 200) {
(JSONFactory.createTokenJSON(message), HttpCode.`201`(CallContext()))
} else {
@ -105,12 +108,28 @@ object DirectLogin extends RestHelper with MdcLoggable {
}
}
def grantEntitlementsToUseDynamicEndpointsInSpacesInDirectLogin(userId:Long) = {
try {
val resourceUser = UserX.findByResourceUserId(userId).openOrThrowException(s"$InvalidDirectLoginParameters can not find the resourceUser!")
val authUser = AuthUser.findUserByUsernameLocally(resourceUser.name).openOrThrowException(s"$InvalidDirectLoginParameters can not find the auth user!")
if(!emailDomainToSpaceMappings.isEmpty){
AuthUser.grantEntitlementsToUseDynamicEndpointsInSpaces(authUser)
}
if(!emailDomainToEntitlementMappings.isEmpty){
AuthUser.grantEmailDomainEntitlementsToUser(authUser)
}
} catch {
case e: Throwable => // error handling, found wrong props value as early as possible.
this.logger.error(s"directLogin.grantEntitlementsToUseDynamicEndpointsInSpacesInDirectLogin throw exception, details: $e" );
}
}
/**
* according username, password, consumer_key to generate a DirectLogin token
* @param allParameters map {"username": "some_username", "password": "some_password", "consumer_key": "some_consumer_key"}
* @return httpCode and token value
*/
def createTokenFuture(allParameters: Map[String, String]): Future[(Int, String)] = {
def createTokenFuture(allParameters: Map[String, String]): Future[(Int, String, Long)] = {
val httpMethod = S.request match {
case Full(r) => r.request.method
case _ => "GET"
@ -134,12 +153,11 @@ object DirectLogin extends RestHelper with MdcLoggable {
createTokenCommonPart(httpCode, message, directLoginParameters)
}
def createTokenCommonPart(code: Int, msg: String, directLoginParameters: Map[String, String]): (Int, String) = {
def createTokenCommonPart(code: Int, msg: String, directLoginParameters: Map[String, String]): (Int, String, Long) = {
var message = msg
var httpCode = code
val userId: Long = (for {id <- getUserId(directLoginParameters)} yield id).getOrElse(0)
if (httpCode == 200) {
val userId: Long = (for {id <- getUserId(directLoginParameters)} yield id).getOrElse(0)
if (userId == 0) {
message = ErrorMessages.InvalidLoginCredentials
httpCode = 401
@ -164,7 +182,7 @@ object DirectLogin extends RestHelper with MdcLoggable {
}
}
}
(httpCode, message)
(httpCode, message, userId)
}
def getHttpMethod = S.request match {
@ -244,7 +262,11 @@ object DirectLogin extends RestHelper with MdcLoggable {
else
Map("error" -> "header incorrect")
}
case _ => Map("error" -> "missing header")
case _ =>
a.header("DirectLogin") match {
case Full(header) => toMap(header)
case _ => Map("error" -> "missing header")
}
}
case _ => Map("error" -> "request incorrect")
}

View File

@ -184,7 +184,9 @@ object OpenIdConnect extends OBPRestHelper with MdcLoggable {
createdByConsentId = None,
name = getClaim(name = "given_name", idToken = idToken).orElse(subject),
email = getClaim(name = "email", idToken = idToken),
userId = None
userId = None,
createdByUserInvitationId = None,
company = None
)
}
}
@ -257,7 +259,7 @@ object OpenIdConnect extends OBPRestHelper with MdcLoggable {
JwtUtil.getSubject(idToken),
Some(true),
name = Some(Helpers.randomString(10).toLowerCase),
appType = Some(AppType.Web),
appType = Some(AppType.Confidential),
description = Some(openIdConnect),
developerEmail = getClaim(name = "email", idToken = idToken),
redirectURL = None,

View File

@ -47,6 +47,7 @@ import code.api.util.Glossary.GlossaryItem
import code.api.util.JwsUtil.getJwsHeaderValue
import code.api.util.RateLimitingJson.CallLimit
import code.api.v1_2.ErrorMessage
import code.api.v2_0_0.CreateEntitlementJSON
import code.api.{DirectLogin, _}
import code.api.v4_0_0.dynamic.{DynamicEndpointHelper, DynamicEndpoints, DynamicEntityHelper}
import code.authtypevalidation.AuthenticationTypeValidationProvider
@ -78,7 +79,7 @@ import net.liftweb.http.js.JE.JsRaw
import net.liftweb.http.provider.HTTPParam
import net.liftweb.http.rest.RestContinuation
import net.liftweb.json
import net.liftweb.json.JsonAST.{JField, JObject, JString, JValue}
import net.liftweb.json.JsonAST.{JField, JNothing, JObject, JString, JValue}
import net.liftweb.json.JsonParser.ParseException
import net.liftweb.json._
import net.liftweb.util.Helpers._
@ -161,6 +162,8 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
}
def hasDirectLoginHeader(authorization: Box[String]): Boolean = hasHeader("DirectLogin", authorization)
def has2021DirectLoginHeader(requestHeaders: List[HTTPParam]): Boolean = requestHeaders.find(_.name == "DirectLogin").isDefined
def hasAnOAuthHeader(authorization: Box[String]): Boolean = hasHeader("OAuth", authorization)
@ -298,6 +301,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
def logAPICall(date: TimeSpan, duration: Long, rd: Option[ResourceDoc]) = {
val authorization = S.request.map(_.header("Authorization")).flatten
val directLogin: Box[String] = S.request.map(_.header("DirectLogin")).flatten
if(getPropsAsBoolValue("write_metrics", false)) {
val user =
if (hasAnOAuthHeader(authorization)) {
@ -305,7 +309,14 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
case Full(u) => Full(u)
case _ => Empty
}
} else if (getPropsAsBoolValue("allow_direct_login", true) && hasDirectLoginHeader(authorization)) {
} // Direct Login
else if (getPropsAsBoolValue("allow_direct_login", true) && directLogin.isDefined) {
DirectLogin.getUser match {
case Full(u) => Full(u)
case _ => Empty
}
} // Direct Login Deprecated
else if (getPropsAsBoolValue("allow_direct_login", true) && hasDirectLoginHeader(authorization)) {
DirectLogin.getUser match {
case Full(u) => Full(u)
case _ => Empty
@ -320,7 +331,14 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
case Full(c) => Full(c)
case _ => Empty
}
} else if (getPropsAsBoolValue("allow_direct_login", true) && hasDirectLoginHeader(authorization)) {
} // Direct Login
else if (getPropsAsBoolValue("allow_direct_login", true) && directLogin.isDefined) {
DirectLogin.getConsumer match {
case Full(c) => Full(c)
case _ => Empty
}
} // Direct Login Deprecated
else if (getPropsAsBoolValue("allow_direct_login", true) && hasDirectLoginHeader(authorization)) {
DirectLogin.getConsumer match {
case Full(c) => Full(c)
case _ => Empty
@ -506,6 +524,16 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
def getHeaders() = getHeadersCommonPart() ::: getGatewayResponseHeader()
case class CustomResponseHeaders(list: List[(String, String)])
//This is used for get the value from props `email_domain_to_space_mappings`
case class EmailDomainToSpaceMapping(
domain: String,
bank_ids: List[String]
)
case class EmailDomainToEntitlementMapping(
domain: String,
entitlements: List[CreateEntitlementJSON]
)
//Note: changed noContent--> defaultSuccess, because of the Swagger format. (Not support empty in DataType, maybe fix it latter.)
def noContentJsonResponse(implicit headers: CustomResponseHeaders = CustomResponseHeaders(Nil)) : JsonResponse =
@ -877,7 +905,11 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
value <- tryo(values.head.toBoolean)?~! FilterAnonFormatError
anon = OBPAnon(value)
}yield anon
case "is_deleted" =>
for {
value <- tryo(values.head.toBoolean) ?~! FilterIsDeletedFormatError
deleted = OBPIsDeleted(value)
} yield deleted
case "consumer_id" => Full(OBPConsumerId(values.head))
case "user_id" => Full(OBPUserId(values.head))
case "bank_id" => Full(OBPBankId(values.head))
@ -920,6 +952,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
offset <- getOffset(httpParams)
//all optional fields
anon <- getHttpParamValuesByName(httpParams,"anon")
deletedStatus <- getHttpParamValuesByName(httpParams,"is_deleted")
consumerId <- getHttpParamValuesByName(httpParams,"consumer_id")
userId <- getHttpParamValuesByName(httpParams, "user_id")
bankId <- getHttpParamValuesByName(httpParams, "bank_id")
@ -957,7 +990,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
List(limit, offset, ordering, fromDate, toDate,
anon, consumerId, userId, url, appName, implementedByPartialFunction, implementedInVersion,
verb, correlationId, duration, excludeAppNames, excludeUrlPattern, excludeImplementedByPartialfunctions,
connectorName,functionName, bankId, accountId, customerId, lockedStatus
connectorName,functionName, bankId, accountId, customerId, lockedStatus, deletedStatus
).filter(_ != OBPEmpty())
}
}
@ -980,6 +1013,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
val limit = getHttpRequestUrlParam(httpRequestUrl,"limit")
val offset = getHttpRequestUrlParam(httpRequestUrl,"offset")
val anon = getHttpRequestUrlParam(httpRequestUrl,"anon")
val isDeleted = getHttpRequestUrlParam(httpRequestUrl, "is_deleted")
val consumerId = getHttpRequestUrlParam(httpRequestUrl,"consumer_id")
val userId = getHttpRequestUrlParam(httpRequestUrl, "user_id")
val bankId = getHttpRequestUrlParam(httpRequestUrl, "bank_id")
@ -1018,6 +1052,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
HTTPParam("account_id", accountId),
HTTPParam("connector_name", connectorName),
HTTPParam("customer_id", customerId),
HTTPParam("is_deleted", isDeleted),
HTTPParam("locked_status", lockedStatus)
).filter(_.values.head != ""))//Here filter the field when value = "".
}
@ -2626,11 +2661,14 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
AuthUser.updateUserAccountViewsFuture(user.openOrThrowException("Can not be empty here"), callContext)
(user, callContext)
}
// Direct Login
} else if (getPropsAsBoolValue("allow_direct_login", true) && hasDirectLoginHeader(cc.authReqHeaderField)) {
} // Direct Login i.e DirectLogin: token=eyJhbGciOiJIUzI1NiJ9.eyIiOiIifQ.Y0jk1EQGB4XgdqmYZUHT6potmH3mKj5mEaA9qrIXXWQ
else if (getPropsAsBoolValue("allow_direct_login", true) && has2021DirectLoginHeader(cc.requestHeaders)) {
DirectLogin.getUserFromDirectLoginHeaderFuture(cc)
// Gateway Login
} else if (getPropsAsBoolValue("allow_gateway_login", false) && hasGatewayHeader(cc.authReqHeaderField)) {
} // Direct Login Deprecated i.e Authorization: DirectLogin token=eyJhbGciOiJIUzI1NiJ9.eyIiOiIifQ.Y0jk1EQGB4XgdqmYZUHT6potmH3mKj5mEaA9qrIXXWQ
else if (getPropsAsBoolValue("allow_direct_login", true) && hasDirectLoginHeader(cc.authReqHeaderField)) {
DirectLogin.getUserFromDirectLoginHeaderFuture(cc)
} // Gateway Login
else if (getPropsAsBoolValue("allow_gateway_login", false) && hasGatewayHeader(cc.authReqHeaderField)) {
APIUtil.getPropsValue("gateway.host") match {
case Full(h) if h.split(",").toList.exists(_.equalsIgnoreCase(remoteIpAddress) == true) => // Only addresses from white list can use this feature
val (httpCode, message, parameters) = GatewayLogin.validator(s.request)
@ -2816,6 +2854,10 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
)
} map {
x =>
//TODO due to performance issue, first comment this out,
// val authUser = AuthUser.findUserByUsernameLocally(x._1.head.name).openOrThrowException("")
// tryo{AuthUser.grantEntitlementsToUseDynamicEndpointsInSpaces(authUser, x._2)}.openOr(logger.error(s"${x._1} authenticatedAccess.grantEntitlementsToUseDynamicEndpointsInSpaces throw exception! "))
// make sure, if `refreshUserIfRequired` throw exception, do not break the `authenticatedAccess`,
// TODO better move `refreshUserIfRequired` to other place.
tryo{refreshUserIfRequired(x._1,x._2)}.openOr(logger.error(s"${x._1} authenticatedAccess.refreshUserIfRequired throw exception! "))
@ -3119,12 +3161,23 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
Else just return the session
Note there are Read and Write side effects here!
*/
// getPropsAsBoolValue cannot be called directly inside the function activeBrand due to java.lang.StackOverflowError
val brandsEnabled = APIUtil.getPropsAsBoolValue("brands_enabled", false)
def activeBrand() : Option[String] = {
brandsEnabled match {
case true =>
getActiveBrand()
case false =>
None
}
}
// TODO This function needs testing in a cluster environment
private def getActiveBrand(): Option[String] = {
val brandParameter = "brand"
// Use brand in parameter (query or form)
val brand : Option[String] = S.param(brandParameter) match {
val brand: Option[String] = S.param(brandParameter) match {
case Full(value) => {
// If found, and has a valid format, set the session.
if (isValidID(value)) {
@ -3132,11 +3185,11 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
logger.debug(s"activeBrand says: I found a $brandParameter param. $brandParameter session has been set to: ${S.getSessionAttribute(brandParameter)}")
Some(value)
} else {
logger.warn (s"activeBrand says: ${ErrorMessages.InvalidBankIdFormat}")
logger.warn(s"activeBrand says: ${ErrorMessages.InvalidBankIdFormat}")
None
}
}
case _ => {
case _ => {
// Else look in the session
S.getSessionAttribute(brandParameter)
}
@ -3168,14 +3221,17 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
val ALLOW_PUBLIC_VIEWS: Boolean = getPropsAsBoolValue("allow_public_views", false)
val ALLOW_ACCOUNT_FIREHOSE: Boolean = ApiPropsWithAlias.allowAccountFirehose
val ALLOW_CUSTOMER_FIREHOSE: Boolean = ApiPropsWithAlias.allowCustomerFirehose
def allowPublicViews: Boolean = getPropsAsBoolValue("allow_public_views", false)
def allowAccountFirehose: Boolean = ApiPropsWithAlias.allowAccountFirehose
def allowCustomerFirehose: Boolean = ApiPropsWithAlias.allowCustomerFirehose
def canUseAccountFirehose(user: User): Boolean = {
ALLOW_ACCOUNT_FIREHOSE && hasEntitlement("", user.userId, ApiRole.canUseAccountFirehoseAtAnyBank)
allowAccountFirehose && hasEntitlement("", user.userId, ApiRole.canUseAccountFirehoseAtAnyBank)
}
def canUseAccountFirehoseAtBank(user: User, bankId: BankId): Boolean = {
allowAccountFirehose && hasEntitlement(bankId.value, user.userId, ApiRole.canUseAccountFirehose)
}
def canUseCustomerFirehose(user: User): Boolean = {
ALLOW_CUSTOMER_FIREHOSE && hasEntitlement("", user.userId, ApiRole.canUseCustomerFirehoseAtAnyBank)
allowCustomerFirehose && hasEntitlement("", user.userId, ApiRole.canUseCustomerFirehoseAtAnyBank)
}
/**
* This will accept all kinds of view and user.
@ -3190,6 +3246,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
true
else
user match {
case Some(u) if hasAccountFirehoseAccessAtBank(view,u, bankIdAccountId.bankId) => true //Login User and Firehose access
case Some(u) if hasAccountFirehoseAccess(view,u) => true//Login User and Firehose access
case Some(u) if u.hasAccountAccess(view, bankIdAccountId)=> true // Login User and check view access
case _ =>
@ -3205,7 +3262,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
val customView = Views.views.vend.customView(viewId, bankIdAccountId)
customView match { // CHECK CUSTOM VIEWS
// 1st: View is Pubic and Public views are NOT allowed on this instance.
case Full(v) if(v.isPublic && !ALLOW_PUBLIC_VIEWS) => Failure(PublicViewsNotAllowedOnThisInstance)
case Full(v) if(v.isPublic && !allowPublicViews) => Failure(PublicViewsNotAllowedOnThisInstance)
// 2nd: View is Pubic and Public views are allowed on this instance.
case Full(v) if(isPublicView(v)) => customView
// 3rd: The user has account access to this custom view
@ -3215,13 +3272,15 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
val systemView = Views.views.vend.systemView(viewId)
systemView match { // CHECK SYSTEM VIEWS
// 1st: View is Pubic and Public views are NOT allowed on this instance.
case Full(v) if(v.isPublic && !ALLOW_PUBLIC_VIEWS) => Failure(PublicViewsNotAllowedOnThisInstance)
case Full(v) if(v.isPublic && !allowPublicViews) => Failure(PublicViewsNotAllowedOnThisInstance)
// 2nd: View is Pubic and Public views are allowed on this instance.
case Full(v) if(isPublicView(v)) => systemView
// 3rd: The user has account access to this system view
case Full(v) if (user.isDefined && user.get.hasAccountAccess(v, bankIdAccountId)) => systemView
// 4th: The user has firehose access to this system view
case Full(v) if (user.isDefined && hasAccountFirehoseAccess(v, user.get)) => systemView
// 5th: The user has firehose access at a bank to this system view
case Full(v) if (user.isDefined && hasAccountFirehoseAccessAtBank(v, user.get, bankIdAccountId.bankId)) => systemView
// The user has NO account access at all
case _ => Empty
}
@ -3243,7 +3302,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
case true if view.isPublic => // Sanity check. We don't want a public owner view.
logger.warn(s"Public owner encountered. Primary view id: ${view.id}")
false
case _ => view.isPublic && APIUtil.ALLOW_PUBLIC_VIEWS
case _ => view.isPublic && APIUtil.allowPublicViews
}
}
/**
@ -3253,6 +3312,13 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
if(view.isFirehose && canUseAccountFirehose(user)) true
else false
}
/**
* This view Firehose is true and set `allow_account_firehose = true` and the user has `CanUseAccountFirehoseAtAnyBank` role
*/
def hasAccountFirehoseAccessAtBank(view: View, user: User, bankId: BankId) : Boolean = {
if(view.isFirehose && canUseAccountFirehoseAtBank(user, bankId)) true
else false
}
/**
* This value is used to construct some urls in Resource Docs
@ -3432,14 +3498,18 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
case true =>
`getPSD2-CERT`(cc.map(_.requestHeaders).getOrElse(Nil)) match {
case Some(pem) =>
val validatedPem = X509.validate(pem)
logger.debug("PSD2-CERT pem: " + pem)
val decodedPem = URLDecoder.decode(pem,"UTF-8")
val validatedPem = X509.validate(decodedPem)
logger.debug("validatedPem: " + validatedPem)
validatedPem match {
case Full(true) =>
val roles = X509.extractPsd2Roles(pem).map(_.exists(_ == serviceProvider))
roles match {
val hasServiceProvider = X509.extractPsd2Roles(decodedPem).map(_.exists(_ == serviceProvider))
logger.debug("hasServiceProvider: " + hasServiceProvider)
hasServiceProvider match {
case Full(true) => Full(true)
case Full(false) => Failure(X509ActionIsNotAllowed)
case _ => roles
case _ => hasServiceProvider
}
case _ =>
validatedPem
@ -3948,4 +4018,38 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
val berlinGroupV13AliasPath = APIUtil.getPropsValue("berlin_group_v1.3_alias.path","").split("/").toList.map(_.trim)
val getAtmsIsPublic = APIUtil.getPropsAsBoolValue("apiOptions.getAtmsIsPublic", true)
val emailDomainToSpaceMappings: List[EmailDomainToSpaceMapping] = {
def extractor(str: String) = try {
val emailToSpaceMappings = json.parse(str).extract[List[EmailDomainToSpaceMapping]]
//The props value can be parse to JNothing.
if(str.nonEmpty && emailToSpaceMappings == Nil)
throw new RuntimeException("props [email_domain_to_space_mappings] parse -> extract to Nil!")
else
emailToSpaceMappings
} catch {
case e: Throwable => // error handling, found wrong props value as early as possible.
this.logger.error(s"props [email_domain_to_space_mappings] value is invalid, it should be the class($EmailDomainToSpaceMapping) json format, current value is $str ." );
throw e;
}
APIUtil.getPropsValue("email_domain_to_space_mappings").map(extractor).getOrElse(Nil)
}
val emailDomainToEntitlementMappings: List[EmailDomainToEntitlementMapping] = {
def extractor(str: String) = try {
val emailDomainToEntitlementMappings = json.parse(str).extract[List[EmailDomainToEntitlementMapping]]
//The props value can be parse to JNothing.
if(str.nonEmpty && emailDomainToEntitlementMappings == Nil)
throw new RuntimeException("props [email_domain_to_entitlement_mappings] parse -> extract to Nil!")
else
emailDomainToEntitlementMappings
} catch {
case e: Throwable => // error handling, found wrong props value as early as possible.
this.logger.error(s"props [email_domain_to_entitlement_mappings] value is invalid, it should be the class($EmailDomainToEntitlementMapping) json format, current value is $str ." );
throw e;
}
APIUtil.getPropsValue("email_domain_to_entitlement_mappings").map(extractor).getOrElse(Nil)
}
}

View File

@ -13,19 +13,19 @@ import net.liftweb.common.Full
*/
object ApiPropsWithAlias {
import HelperFunctions._
val requireScopesForAllRoles = getValueByNameOrAliasAsBoolean(
def requireScopesForAllRoles = getValueByNameOrAliasAsBoolean(
name="require_scopes_for_all_roles",
alias="require_scopes",
defaultValue="false")
val migrationScriptsEnabled = getValueByNameOrAliasAsBoolean(
def migrationScriptsEnabled = getValueByNameOrAliasAsBoolean(
name="migration_scripts.enabled",
alias="migration_scripts.execute",
defaultValue="false")
val allowAccountFirehose = getValueByNameOrAliasAsBoolean(
def allowAccountFirehose = getValueByNameOrAliasAsBoolean(
name="allow_account_firehose",
alias="allow_firehose_views",
defaultValue="false")
val allowCustomerFirehose = getValueByNameOrAliasAsBoolean(
def allowCustomerFirehose = getValueByNameOrAliasAsBoolean(
name="allow_customer_firehose",
alias="allow_firehose_views",
defaultValue="false")

View File

@ -320,6 +320,9 @@ object ApiRole {
case class CanUseAccountFirehoseAtAnyBank(requiresBankId: Boolean = false) extends ApiRole
lazy val canUseAccountFirehoseAtAnyBank = CanUseAccountFirehoseAtAnyBank()
case class CanUseAccountFirehose(requiresBankId: Boolean = true) extends ApiRole
lazy val canUseAccountFirehose = CanUseAccountFirehose()
case class CanUseCustomerFirehoseAtAnyBank(requiresBankId: Boolean = false) extends ApiRole
lazy val canUseCustomerFirehoseAtAnyBank = CanUseCustomerFirehoseAtAnyBank()
@ -343,6 +346,9 @@ object ApiRole {
case class CanLockUser (requiresBankId: Boolean = false) extends ApiRole
lazy val canLockUser = CanLockUser()
case class CanDeleteUser (requiresBankId: Boolean = false) extends ApiRole
lazy val canDeleteUser = CanDeleteUser()
case class CanReadUserLockedStatus(requiresBankId: Boolean = false) extends ApiRole
lazy val canReadUserLockedStatus = CanReadUserLockedStatus()
@ -456,12 +462,21 @@ object ApiRole {
case class CanCreateDynamicEntity(requiresBankId: Boolean = false) extends ApiRole
lazy val canCreateDynamicEntity = CanCreateDynamicEntity()
case class CanCreateBankLevelDynamicEntity(requiresBankId: Boolean = true) extends ApiRole
lazy val canCreateBankLevelDynamicEntity = CanCreateBankLevelDynamicEntity()
case class CanUpdateDynamicEntity(requiresBankId: Boolean = false) extends ApiRole
lazy val canUpdateDynamicEntity = CanUpdateDynamicEntity()
case class CanUpdateBankLevelDynamicEntity(requiresBankId: Boolean = true) extends ApiRole
lazy val canUpdateBankLevelDynamicEntity = CanUpdateBankLevelDynamicEntity()
case class CanDeleteDynamicEntity(requiresBankId: Boolean = false) extends ApiRole
lazy val canDeleteDynamicEntity = CanDeleteDynamicEntity()
case class CanDeleteBankLevelDynamicEntity(requiresBankId: Boolean = true) extends ApiRole
lazy val canDeleteBankLevelDynamicEntity = CanDeleteBankLevelDynamicEntity()
case class CanGetBankLevelDynamicEntities(requiresBankId: Boolean = true) extends ApiRole
lazy val canGetBankLevelDynamicEntities = CanGetBankLevelDynamicEntities()
@ -471,14 +486,29 @@ object ApiRole {
case class CanGetDynamicEndpoints(requiresBankId: Boolean = false) extends ApiRole
lazy val canGetDynamicEndpoints = CanGetDynamicEndpoints()
case class CanGetBankLevelDynamicEndpoint(requiresBankId: Boolean = true) extends ApiRole
lazy val canGetBankLevelDynamicEndpoint = CanGetBankLevelDynamicEndpoint()
case class CanGetBankLevelDynamicEndpoints(requiresBankId: Boolean = true) extends ApiRole
lazy val canGetBankLevelDynamicEndpoints = CanGetBankLevelDynamicEndpoints()
case class CanCreateDynamicEndpoint(requiresBankId: Boolean = false) extends ApiRole
lazy val canCreateDynamicEndpoint = CanCreateDynamicEndpoint()
case class CanCreateBankLevelDynamicEndpoint(requiresBankId: Boolean = true) extends ApiRole
lazy val canCreateBankLevelDynamicEndpoint = CanCreateBankLevelDynamicEndpoint()
case class CanUpdateDynamicEndpoint(requiresBankId: Boolean = false) extends ApiRole
lazy val canUpdateDynamicEndpoint = CanUpdateDynamicEndpoint()
case class CanUpdateBankLevelDynamicEndpoint(requiresBankId: Boolean = true) extends ApiRole
lazy val canUpdateBankLevelDynamicEndpoint = CanUpdateBankLevelDynamicEndpoint()
case class CanDeleteDynamicEndpoint(requiresBankId: Boolean = false) extends ApiRole
lazy val canDeleteDynamicEndpoint = CanDeleteDynamicEndpoint()
case class CanDeleteBankLevelDynamicEndpoint(requiresBankId: Boolean = true) extends ApiRole
lazy val canDeleteBankLevelDynamicEndpoint = CanDeleteBankLevelDynamicEndpoint()
case class CanCreateResetPasswordUrl(requiresBankId: Boolean = false) extends ApiRole
lazy val canCreateResetPasswordUrl = CanCreateResetPasswordUrl()
@ -714,6 +744,26 @@ object ApiRole {
case class CanDeleteEndpointMapping(requiresBankId: Boolean = false) extends ApiRole
lazy val canDeleteEndpointMapping = CanDeleteEndpointMapping()
case class CanCreateBankLevelEndpointMapping(requiresBankId: Boolean = true) extends ApiRole
lazy val canCreateBankLevelEndpointMapping = CanCreateBankLevelEndpointMapping()
case class CanUpdateBankLevelEndpointMapping(requiresBankId: Boolean = true) extends ApiRole
lazy val canUpdateBankLevelEndpointMapping = CanUpdateBankLevelEndpointMapping()
case class CanGetBankLevelEndpointMapping(requiresBankId: Boolean = true) extends ApiRole
lazy val canGetBankLevelEndpointMapping = CanGetBankLevelEndpointMapping()
case class CanGetAllBankLevelEndpointMappings(requiresBankId: Boolean = true) extends ApiRole
lazy val canGetAllBankLevelEndpointMappings = CanGetAllBankLevelEndpointMappings()
case class CanDeleteBankLevelEndpointMapping(requiresBankId: Boolean = true) extends ApiRole
lazy val canDeleteBankLevelEndpointMapping = CanDeleteBankLevelEndpointMapping()
case class CanCreateUserInvitation(requiresBankId: Boolean = true) extends ApiRole
lazy val canCreateUserInvitation = CanCreateUserInvitation()
case class CanGetUserInvitation(requiresBankId: Boolean = true) extends ApiRole
lazy val canGetUserInvitation = CanGetUserInvitation()
private val dynamicApiRoles = new ConcurrentHashMap[String, ApiRole]
private case class DynamicApiRole(role: String, requiresBankId: Boolean = false) extends ApiRole{

View File

@ -137,7 +137,9 @@ case class CallContext(
def authType: AuthenticationType = {
if(hasGatewayHeader(authReqHeaderField)) {
GatewayLogin
} else if(hasDirectLoginHeader(authReqHeaderField)) {
} else if(has2021DirectLoginHeader(requestHeaders)) { // Direct Login
DirectLogin
} else if(hasDirectLoginHeader(authReqHeaderField)) { // Direct Login Deprecated
DirectLogin
} else if(hasAnOAuthHeader(authReqHeaderField)) {
AuthenticationType.`OAuth1.0a`

View File

@ -42,6 +42,7 @@ object ApiTag {
val apiTagCustomer = ResourceDocTag("Customer")
val apiTagOnboarding = ResourceDocTag("Onboarding")
val apiTagUser = ResourceDocTag("User") // Use for User Management / Info APIs
val apiTagUserInvitation = ResourceDocTag("User-Invitation")
val apiTagMeeting = ResourceDocTag("Customer-Meeting")
val apiTagExperimental = ResourceDocTag("Experimental")
val apiTagPerson = ResourceDocTag("Person")
@ -68,10 +69,10 @@ object ApiTag {
val apiTagMockedData = ResourceDocTag("Mocked-Data")
val apiTagConsent = ResourceDocTag("Consent")
val apiTagMethodRouting = ResourceDocTag("Method-Routing")
val apiTagEndpointMapping = ResourceDocTag("Endpoint-Mapping")
val apiTagWebUiProps = ResourceDocTag("WebUi-Props")
val apiTagEndpointMapping = ResourceDocTag("Endpoint-Mapping-Manage")
val apiTagManageDynamicEndpoint = ResourceDocTag("Dynamic-Endpoint-Manage")
val apiTagManageDynamicEntity = ResourceDocTag("Dynamic-Entity-Manage")
val apiTagDynamicSwaggerDoc = ResourceDocTag("Dynamic-Swagger-Doc-Manage")
val apiTagDynamicResourceDoc = ResourceDocTag("Dynamic-Resource-Doc-Manage")
val apiTagDynamicMessageDoc = ResourceDocTag("Dynamic-Message-Doc-Manage")
val apiTagApiCollection = ResourceDocTag("Api-Collection")

View File

@ -69,6 +69,7 @@ object CertificateUtil extends MdcLoggable {
val jkspath = APIUtil.getPropsValue("keystore.path").getOrElse("")
val jkspasswd = APIUtil.getPropsValue("keystore.password").getOrElse(APIUtil.initPasswd)
val keypasswd = APIUtil.getPropsValue("keystore.passphrase").getOrElse(APIUtil.initPasswd)
// This is used for QWAC certificate. Alias needs to be of that certificate.
val alias = APIUtil.getPropsValue("keystore.alias").getOrElse("")
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType)
val inputStream = new FileInputStream(jkspath)
@ -103,7 +104,14 @@ object CertificateUtil extends MdcLoggable {
.keyIDFromThumbprint()
.build()
jwk.toJSONString()
}
}
/**
* This is used for QWAC certificate.
* x5s is the part of te JOSE Protected header we use it in case of Java Web Signature.
* We sign response with rsaSigner and send it via "x-jws-signature" response header.
* it's verified via x5c value at third party app.
*/
lazy val (rsaSigner, x5c, rsaPublicKey) = {
val (privateKey: PrivateKey, certificate: Certificate) =
Props.mode match {

View File

@ -196,7 +196,9 @@ object Consent {
createdByConsentId = consentId,
name = name,
email = email,
userId = None
userId = None,
createdByUserInvitationId = None,
company = None
)
}
}

View File

@ -99,6 +99,7 @@ object ErrorMessages {
val FilterDateFormatError = s"OBP-10026: Failed to parse date string. Please use this format ${DateWithMsFormat.toPattern}!" // OBP-20026
val FilterAnonFormatError = s"OBP-10028: anon parameter can only take two values: TRUE or FALSE!"
val FilterDurationFormatError = s"OBP-10029: wrong value for `duration` parameter. Please send a positive integer (=>0)!"
val FilterIsDeletedFormatError = s"OBP-10036: is_deleted parameter can only take two values: TRUE or FALSE!"
val InvalidApiVersionString = "OBP-00027: Invalid API Version string. We could not find the version specified."
val IncorrectTriggerName = "OBP-10028: Incorrect Trigger name: "
@ -455,6 +456,11 @@ object ErrorMessages {
val InvalidEndpointMapping = "OBP-36006: Invalid Endpoint Mapping. "
// General Resource related messages above here
// User Invitation
val CannotCreateUserInvitation = "OBP-37081: Cannot create user invitation."
val CannotGetUserInvitation = "OBP-37882: Cannot get user invitation."
val CannotFindUserInvitation = "OBP-37883: Cannot find user invitation."
// Transaction Request related messages (OBP-40XXX)
val InvalidTransactionRequestType = "OBP-40001: Invalid value for TRANSACTION_REQUEST_TYPE"
@ -526,6 +532,8 @@ object ErrorMessages {
val InternalServerError = "OBP-50015: The server encountered an unexpected condition which prevented it from fulfilling the request."
val KafkaServerUnavailable = "OBP-50016: The kafka server is unavailable."
val NotAllowedEndpoint = "OBP-50017: The endpoint is forbidden at this API instance."
val UnderConstructionError = "OBP-50018: Under Construction Error."
val DatabaseConnectionClosedError = "OBP-50019: Cannot connect to the OBP database."
// Connector Data Exceptions (OBP-502XX)

File diff suppressed because it is too large Load Diff

View File

@ -1794,11 +1794,14 @@ object Glossary {
|
|```
|{
| "username": "simonr",
| "is_first": true,
| "timestamp": "timestamp",
| "consumer_id": "123",
| "consumer_name": "Name of Consumer"
| "login_user_name": "username",
| "is_first": false,
| "app_id": "85a965f0-0d55-4e0a-8b1c-649c4b01c4fb",
| "app_name": "GWL",
| "time_stamp": "2018-08-20T14:13:40Z",
| "cbs_token": "your_token",
| "cbs_id": "your_cbs_id",
| "session_id": "123456789"
|}
|```
|VERIFY SIGNATURE
@ -1884,6 +1887,36 @@ object Glossary {
|AS8D76F7A89S87D6F7A9SD876FA789SD78F6A7S9D78F6AS79DF87A6S7D9F7A6S7D9F78A6SD798F78679D786S789D78F6A7S9D78F6AS79DF876A7S89DF786AS9D87F69AS7D6FN1bWVyIn0.
|KEuvjv3dmwkOhQ3JJ6dIShK8CG_fd2REApOGn1TRmgU"
|
|### Example python script
|```
|import jwt
|from datetime import datetime, timezone
|from obp_python.config import obp_api_host
|import requests
|
|env = 'local'
|DATE_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
|
|payload = {
| "login_user_name": "username",
| "is_first": False,
| "app_id": "85a965f0-0d55-4e0a-8b1c-649c4b01c4fb",
| "app_name": "Name",
| "time_stamp": datetime.now(timezone.utc).strftime(DATE_FORMAT),
| "cbs_token": "yourtokenforcbs",
| "cbs_id": "yourcbs_id",
| "session_id": "123456789"
|}
|
|
|token = jwt.encode(payload, 'secretsecretsecretstsecretssssss', algorithm='HS256')
|authorization = 'GatewayLogin token="{}"'.format(token)
|headers = {'Authorization': authorization}
|url = obp_api_host + '/obp/v4.0.0/users/current'
|req = requests.get(url, headers=headers)
|print(req.text)
|```
|
|### Under the hood
|
|The file, GatewayLogin.scala handles the Gateway Login.
@ -2037,6 +2070,38 @@ object Glossary {
""")
// TODO put the following wiki text here in source code with soft coded hosts etc. The problem is the text is currently too long
glossaryItems += GlossaryItem(
title = "Hola App log trace",
description =
s"""
Please see:
[OBP Hola App Log Trace](https://github.com/OpenBankProject/OBP-API/wiki/Log-trace-of-the-Hola-App-performing-Georgian-flavour-of-Berlin-Group-authentication,-consent-generation-and-consuming-Berlin-Group-Account,-Balance-and-Transaction-resources)
""")
glossaryItems += GlossaryItem(
title = "API Collection",
description =
s"""|An API Collection is a collection of endpoints grouped together for a certain purpose.
|
|Having read access to a Collection does not constitute execute access on the endpoints in the Collection.
|
|(Execute access is governed by Entitlements to Roles - and in some cases, Views.)
|
|Collections can be created and shared. You can make a collection non-sharable but the default is sharable.
|
|Your "Favourites" in API Explorer is actually a collection you control named "Favourites".
|
|To share a Collection (e.g. your Favourites) just click on your Favourites in the API Explorer and share the URL in the browser. If you want to share the Collection via an API, just share the collection_id with a developer.
|
|If you share a Collection it can't be modified by anyone else, but anyone can use it as a basis for their own Favourites or another collection.
|
|At the time of writing (July 2021), there are 13 endpoints for controlling Collections.
|
""")
///////////////////////////////////////////////////////////////////
// NOTE! Some glossary items are generated in ExampleValue.scala
//////////////////////////////////////////////////////////////////

View File

@ -81,7 +81,6 @@ object JwsUtil extends MdcLoggable {
requestHeaders.find(_.name == "x-jws-signature").isDefined ||
requestHeaders.find(_.name == "digest").isDefined
}
def createDigestHeader(input: String): String = s"digest: SHA-256=$input"
private def getDeferredCriticalHeaders() = {
val deferredCriticalHeaders = new util.HashSet[String]()
deferredCriticalHeaders.add("sigT")

View File

@ -1,5 +1,6 @@
package code.api.util
import java.io
import java.util.Date
import java.util.UUID.randomUUID
@ -11,6 +12,7 @@ import code.api.cache.Caching
import code.api.util.APIUtil.{EntitlementAndScopeStatus, JsonResponseExtractor, OBPReturnType, afterAuthenticateInterceptResult, canGrantAccessToViewCommon, canRevokeAccessToViewCommon, connectorEmptyResponse, createHttpParamsByUrlFuture, createQueriesByHttpParamsFuture, fullBoxOrException, generateUUID, unboxFull, unboxFullOrFail}
import code.api.util.ApiRole.canCreateAnyTransactionRequest
import code.api.util.ErrorMessages.{InsufficientAuthorisationToCreateTransactionRequest, _}
import code.api.ResourceDocs1_4_0.ResourceDocs140.ImplementationsResourceDocs
import code.api.v1_2_1.OBPAPI1_2_1.Implementations1_2_1
import code.api.v1_4_0.OBPAPI1_4_0.Implementations1_4_0
import code.api.v2_0_0.OBPAPI2_0_0.Implementations2_0_0
@ -31,10 +33,10 @@ import code.methodrouting.{MethodRoutingCommons, MethodRoutingProvider, MethodRo
import code.model._
import code.apicollectionendpoint.{ApiCollectionEndpointTrait, MappedApiCollectionEndpointsProvider}
import code.apicollection.{ApiCollectionTrait, MappedApiCollectionsProvider}
import code.model.dataAccess.BankAccountRouting
import code.model.dataAccess.{AuthUser, BankAccountRouting}
import code.standingorders.StandingOrderTrait
import code.usercustomerlinks.UserCustomerLink
import code.users.Users
import code.users.{UserInvitation, UserInvitationProvider, Users}
import code.util.Helper
import com.openbankproject.commons.util.{ApiVersion, JsonUtils}
import code.views.Views
@ -70,7 +72,9 @@ import net.liftweb.json
object NewStyle {
lazy val endpoints: List[(String, String)] = List(
(nameOf(ImplementationsResourceDocs.getResourceDocsObp), ApiVersion.v1_4_0.toString),
(nameOf(Implementations1_2_1.deleteWhereTagForViewOnTransaction), ApiVersion.v1_2_1.toString),
(nameOf(Implementations1_2_1.getOtherAccountForTransaction), ApiVersion.v1_2_1.toString),
(nameOf(Implementations1_2_1.getOtherAccountMetadata), ApiVersion.v1_2_1.toString),
(nameOf(Implementations1_2_1.getCounterpartyPublicAlias), ApiVersion.v1_2_1.toString),
(nameOf(Implementations1_2_1.addCounterpartyMoreInfo), ApiVersion.v1_2_1.toString),
@ -311,11 +315,6 @@ object NewStyle {
}
} map { unboxFull(_) }
}
def getBalances(callContext: Option[CallContext]) : OBPReturnType[List[Bank]] = {
Connector.connector.vend.getBanks(callContext: Option[CallContext]) map {
connectorEmptyResponse(_, callContext)
}
}
def getBankAccount(bankId : BankId, accountId : AccountId, callContext: Option[CallContext]): OBPReturnType[BankAccount] = {
Connector.connector.vend.checkBankAccountExists(bankId, accountId, callContext) map { i =>
(unboxFullOrFail(i._1, callContext,s"$BankAccountNotFound Current BankId is $bankId and Current AccountId is $accountId", 404 ), i._2)
@ -344,6 +343,12 @@ object NewStyle {
}
}
def getBankAccountBalances(bankIdAccountId: BankIdAccountId, callContext: Option[CallContext]): OBPReturnType[AccountBalances] = {
Connector.connector.vend.getBankAccountBalances(bankIdAccountId: BankIdAccountId, callContext: Option[CallContext]) map { i =>
(unboxFullOrFail(i._1, callContext,s"$InvalidConnectorResponseForGetBankAccounts", 400 ), i._2)
}
}
def getAccountRouting(bankId: Option[BankId], scheme: String, address: String, callContext: Option[CallContext]) : OBPReturnType[BankAccountRouting] = {
Future(Connector.connector.vend.getAccountRouting(bankId: Option[BankId], scheme: String, address : String, callContext: Option[CallContext])) map { i =>
unboxFullOrFail(i, callContext,s"$AccountRoutingNotFound Current scheme is $scheme, current address is $address, current bankId is $bankId", 404 )
@ -730,7 +735,19 @@ object NewStyle {
i => (connectorEmptyResponse(i._1, callContext), i._2)
}
}
def createUserInvitation(bankId: BankId, firstName: String, lastName: String, email: String, company: String, country: String, purpose: String, callContext: Option[CallContext]): OBPReturnType[UserInvitation] = Future {
val response: Box[UserInvitation] = UserInvitationProvider.userInvitationProvider.vend.createUserInvitation(bankId, firstName, lastName, email, company, country, purpose)
(unboxFullOrFail(response, callContext, s"$CannotCreateUserInvitation", 400), callContext)
}
def getUserInvitation(bankId: BankId, secretLink: Long, callContext: Option[CallContext]): OBPReturnType[UserInvitation] = Future {
val response: Box[UserInvitation] = UserInvitationProvider.userInvitationProvider.vend.getUserInvitation(bankId, secretLink)
(unboxFullOrFail(response, callContext, s"$CannotGetUserInvitation", 400), callContext)
}
def getUserInvitations(bankId: BankId, callContext: Option[CallContext]): OBPReturnType[List[UserInvitation]] = Future {
val response = UserInvitationProvider.userInvitationProvider.vend.getUserInvitations(bankId)
(unboxFullOrFail(response, callContext, s"$CannotGetUserInvitation", 400), callContext)
}
def getAdapterInfo(callContext: Option[CallContext]): OBPReturnType[InboundAdapterInfoInternal] = {
Connector.connector.vend.getAdapterInfo(callContext) map {
connectorEmptyResponse(_, callContext)
@ -845,8 +862,10 @@ object NewStyle {
}
def hasEntitlement(bankId: String, userId: String, role: ApiRole, callContext: Option[CallContext], errorMsg: String = ""): Future[Box[Unit]] = {
val errorInfo = if(StringUtils.isBlank(errorMsg)) UserHasMissingRoles + role.toString()
else errorMsg
val errorInfo =
if(StringUtils.isBlank(errorMsg)&& !bankId.isEmpty) UserHasMissingRoles + role.toString() + s" at Bank($bankId)"
else if(StringUtils.isBlank(errorMsg)&& bankId.isEmpty) UserHasMissingRoles + role.toString()
else errorMsg
Helper.booleanToFuture(errorInfo, cc=callContext) {
APIUtil.hasEntitlement(bankId, userId, role)
@ -865,11 +884,18 @@ object NewStyle {
APIUtil.hasAtLeastOneEntitlement(bankId, userId, roles)
} map validateRequestPayload(callContext)
def hasAtLeastOneEntitlement(bankId: String, userId: String, roles: List[ApiRole], callContext: Option[CallContext]): Future[Box[Unit]] =
hasAtLeastOneEntitlement(UserHasMissingRoles + roles.mkString(" or "))(bankId, userId, roles, callContext)
def hasAtLeastOneEntitlement(bankId: String, userId: String, roles: List[ApiRole], callContext: Option[CallContext]): Future[Box[Unit]] = {
val errorMessage = if (roles.filter(_.requiresBankId).isEmpty) UserHasMissingRoles + roles.mkString(" or ") else UserHasMissingRoles + roles.mkString(" or ") + s" for BankId($bankId)."
hasAtLeastOneEntitlement(errorMessage)(bankId, userId, roles, callContext)
}
def hasAllEntitlements(bankId: String, userId: String, roles: List[ApiRole], callContext: Option[CallContext]): Box[Unit] = {
val boxResult = Helper.booleanToBox(APIUtil.hasAllEntitlements(bankId, userId, roles), s"$UserHasMissingRoles${roles.mkString(" and ")} entitlements are required.")
val errorMessage = if (roles.filter(_.requiresBankId).isEmpty)
s"$UserHasMissingRoles${roles.mkString(" and ")} entitlements are required."
else
s"$UserHasMissingRoles${roles.mkString(" and ")} entitlements are required for BankId($bankId)."
val boxResult = Helper.booleanToBox(APIUtil.hasAllEntitlements(bankId, userId, roles), errorMessage)
validateRequestPayload(callContext)(boxResult)
}
@ -912,7 +938,21 @@ object NewStyle {
i => (connectorEmptyResponse(i._1, callContext), i._2)
}
}
def deleteUser(userPrimaryKey: UserPrimaryKey, callContext: Option[CallContext]): OBPReturnType[Boolean] = Future {
AuthUser.scrambleAuthUser(userPrimaryKey) match {
case Full(true) =>
Users.users.vend.scrambleDataOfResourceUser(userPrimaryKey) match {
case Full(true) =>
val createdByUserInvitationId: String = Users.users.vend.getUserByResourceUserId(userPrimaryKey.value).flatMap(_.createdByUserInvitationId).getOrElse(generateUUID())
(UserInvitationProvider.userInvitationProvider.vend.scrambleUserInvitation(createdByUserInvitationId).getOrElse(false), callContext)
case _ =>
(false, callContext)
}
case _ =>
(false, callContext)
}
}
def findByUserId(userId: String, callContext: Option[CallContext]): OBPReturnType[User] = {
Future { UserX.findByUserId(userId).map(user =>(user, callContext))} map {
@ -2340,28 +2380,28 @@ object NewStyle {
}
}
def createOrUpdateEndpointMapping(endpointMapping: EndpointMappingT, callContext: Option[CallContext]) = Future {
(EndpointMappingProvider.endpointMappingProvider.vend.createOrUpdate(endpointMapping), callContext)
def createOrUpdateEndpointMapping(bankId: Option[String], endpointMapping: EndpointMappingT, callContext: Option[CallContext]) = Future {
(EndpointMappingProvider.endpointMappingProvider.vend.createOrUpdate(bankId, endpointMapping), callContext)
} map {
i => (connectorEmptyResponse(i._1, callContext), i._2)
}
def deleteEndpointMapping(endpointMappingId: String, callContext: Option[CallContext]) = Future {
(EndpointMappingProvider.endpointMappingProvider.vend.delete(endpointMappingId), callContext)
def deleteEndpointMapping(bankId: Option[String], endpointMappingId: String, callContext: Option[CallContext]) = Future {
(EndpointMappingProvider.endpointMappingProvider.vend.delete(bankId, endpointMappingId), callContext)
} map {
i => (connectorEmptyResponse(i._1, callContext), i._2)
}
def getEndpointMappingById(endpointMappingId : String, callContext: Option[CallContext]): OBPReturnType[EndpointMappingT] = {
val endpointMappingBox: Box[EndpointMappingT] = EndpointMappingProvider.endpointMappingProvider.vend.getById(endpointMappingId)
def getEndpointMappingById(bankId: Option[String], endpointMappingId : String, callContext: Option[CallContext]): OBPReturnType[EndpointMappingT] = {
val endpointMappingBox: Box[EndpointMappingT] = EndpointMappingProvider.endpointMappingProvider.vend.getById(bankId, endpointMappingId)
Future{
val endpointMapping = unboxFullOrFail(endpointMappingBox, callContext, s"$EndpointMappingNotFoundByEndpointMappingId Current ENDPOINT_MAPPING_ID is $endpointMappingId", 404)
(endpointMapping, callContext)
}
}
def getEndpointMappingByOperationId(operationId : String, callContext: Option[CallContext]): OBPReturnType[EndpointMappingT] = {
val endpointMappingBox: Box[EndpointMappingT] = EndpointMappingProvider.endpointMappingProvider.vend.getByOperationId(operationId)
def getEndpointMappingByOperationId(bankId: Option[String], operationId : String, callContext: Option[CallContext]): OBPReturnType[EndpointMappingT] = {
val endpointMappingBox: Box[EndpointMappingT] = EndpointMappingProvider.endpointMappingProvider.vend.getByOperationId(bankId, operationId)
Future{
val endpointMapping = unboxFullOrFail(endpointMappingBox, callContext, s"$EndpointMappingNotFoundByOperationId Current OPERATION_ID is $operationId",404)
(endpointMapping, callContext)
@ -2370,13 +2410,13 @@ object NewStyle {
private[this] val endpointMappingTTL = APIUtil.getPropsValue(s"endpointMapping.cache.ttl.seconds", "0").toInt
def getEndpointMappings(callContext: Option[CallContext]): OBPReturnType[List[EndpointMappingT]] = {
def getEndpointMappings(bankId: Option[String], callContext: Option[CallContext]): OBPReturnType[List[EndpointMappingT]] = {
import scala.concurrent.duration._
var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString)
CacheKeyFromArguments.buildCacheKey {
Caching.memoizeSyncWithProvider(Some(cacheKey.toString()))(endpointMappingTTL second) {
Future{(EndpointMappingProvider.endpointMappingProvider.vend.getAllEndpointMappings(), callContext)}
Future{(EndpointMappingProvider.endpointMappingProvider.vend.getAllEndpointMappings(bankId), callContext)}
}
}
}
@ -2395,7 +2435,7 @@ object NewStyle {
}
private def updateDynamicEntity(dynamicEntity: DynamicEntityT, dynamicEntityId: String , callContext: Option[CallContext]): Future[Box[DynamicEntityT]] = {
val originEntity = DynamicEntityProvider.connectorMethodProvider.vend.getById(dynamicEntityId)
val originEntity = DynamicEntityProvider.connectorMethodProvider.vend.getById(dynamicEntity.bankId, dynamicEntityId)
// if can't find by id, return 404 error
val idNotExistsMsg = s"$DynamicEntityNotFoundByDynamicEntityId dynamicEntityId = ${dynamicEntity.dynamicEntityId.get}."
@ -2431,9 +2471,9 @@ object NewStyle {
* @param dynamicEntityId
* @return
*/
def deleteDynamicEntity(dynamicEntityId: String): Future[Box[Boolean]] = Future {
def deleteDynamicEntity(bankId: Option[String], dynamicEntityId: String): Future[Box[Boolean]] = Future {
for {
entity <- DynamicEntityProvider.connectorMethodProvider.vend.getById(dynamicEntityId)
entity <- DynamicEntityProvider.connectorMethodProvider.vend.getById(bankId, dynamicEntityId)
deleteEntityResult <- DynamicEntityProvider.connectorMethodProvider.vend.delete(entity)
deleteEntitleMentResult <- if(deleteEntityResult) {
Entitlement.entitlement.vend.deleteDynamicEntityEntitlement(entity.entityName, entity.bankId)
@ -2448,15 +2488,15 @@ object NewStyle {
}
}
def getDynamicEntityById(dynamicEntityId : String, callContext: Option[CallContext]): OBPReturnType[DynamicEntityT] = {
val dynamicEntityBox: Box[DynamicEntityT] = DynamicEntityProvider.connectorMethodProvider.vend.getById(dynamicEntityId)
def getDynamicEntityById(bankId: Option[String], dynamicEntityId : String, callContext: Option[CallContext]): OBPReturnType[DynamicEntityT] = {
val dynamicEntityBox: Box[DynamicEntityT] = DynamicEntityProvider.connectorMethodProvider.vend.getById(bankId, dynamicEntityId)
val dynamicEntity = unboxFullOrFail(dynamicEntityBox, callContext, DynamicEntityNotFoundByDynamicEntityId, 404)
Future{
(dynamicEntity, callContext)
}
}
def getDynamicEntityByEntityName(entityName : String, callContext: Option[CallContext]): OBPReturnType[Box[DynamicEntityT]] = Future {
def getDynamicEntityByEntityName(bankId: Option[String], entityName : String, callContext: Option[CallContext]): OBPReturnType[Box[DynamicEntityT]] = Future {
val boxedDynamicEntity = DynamicEntityProvider.connectorMethodProvider.vend.getByEntityName(entityName)
(boxedDynamicEntity, callContext)
}
@ -2466,28 +2506,17 @@ object NewStyle {
else APIUtil.getPropsValue(s"dynamicEntity.cache.ttl.seconds", "30").toInt
}
def getDynamicEntities(): List[DynamicEntityT] = {
def getDynamicEntities(bankId: Option[String]): List[DynamicEntityT] = {
import scala.concurrent.duration._
var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString)
CacheKeyFromArguments.buildCacheKey {
Caching.memoizeSyncWithProvider(Some(cacheKey.toString()))(dynamicEntityTTL second) {
DynamicEntityProvider.connectorMethodProvider.vend.getDynamicEntities()
DynamicEntityProvider.connectorMethodProvider.vend.getDynamicEntities(bankId)
}
}
}
def getDynamicEntitiesByBankId(bankId: String): List[DynamicEntityT] = {
import scala.concurrent.duration._
var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString)
CacheKeyFromArguments.buildCacheKey {
Caching.memoizeSyncWithProvider(Some(cacheKey.toString()))(dynamicEntityTTL second) {
DynamicEntityProvider.connectorMethodProvider.vend.getDynamicEntitiesByBankId(bankId)
}
}
}
def getDynamicEntitiesByUserId(userId: String): List[DynamicEntityT] = {
import scala.concurrent.duration._
@ -2688,28 +2717,33 @@ object NewStyle {
getConnectorByName(connectorName).flatMap(_.callableMethods.get(methodName))
}
def createDynamicEndpoint(userId: String, swaggerString: String, callContext: Option[CallContext]): OBPReturnType[DynamicEndpointT] = Future {
(DynamicEndpointProvider.connectorMethodProvider.vend.create(userId, swaggerString), callContext)
def createDynamicEndpoint(bankId:Option[String], userId: String, swaggerString: String, callContext: Option[CallContext]): OBPReturnType[DynamicEndpointT] = Future {
(DynamicEndpointProvider.connectorMethodProvider.vend.create(bankId:Option[String], userId, swaggerString), callContext)
} map {
i => (connectorEmptyResponse(i._1, callContext), i._2)
}
def updateDynamicEndpointHost(userId: String, swaggerString: String, callContext: Option[CallContext]): OBPReturnType[DynamicEndpointT] = Future {
(DynamicEndpointProvider.connectorMethodProvider.vend.updateHost(userId, swaggerString), callContext)
def updateDynamicEndpointHost(bankId: Option[String], userId: String, swaggerString: String, callContext: Option[CallContext]): OBPReturnType[DynamicEndpointT] = Future {
(DynamicEndpointProvider.connectorMethodProvider.vend.updateHost(bankId, userId, swaggerString), callContext)
} map {
i => (connectorEmptyResponse(i._1, callContext), i._2)
}
def getDynamicEndpoint(dynamicEndpointId: String, callContext: Option[CallContext]): OBPReturnType[DynamicEndpointT] = {
val dynamicEndpointBox: Box[DynamicEndpointT] = DynamicEndpointProvider.connectorMethodProvider.vend.get(dynamicEndpointId)
val dynamicEndpoint = unboxFullOrFail(dynamicEndpointBox, callContext, DynamicEndpointNotFoundByDynamicEndpointId, 404)
def getDynamicEndpoint(bankId: Option[String], dynamicEndpointId: String, callContext: Option[CallContext]): OBPReturnType[DynamicEndpointT] = {
val dynamicEndpointBox: Box[DynamicEndpointT] = DynamicEndpointProvider.connectorMethodProvider.vend.get(bankId, dynamicEndpointId)
val errorMessage =
if(bankId.isEmpty)
DynamicEndpointNotFoundByDynamicEndpointId.replace("DYNAMIC_ENDPOINT_ID.", s"DYNAMIC_ENDPOINT_ID($dynamicEndpointId).")
else
DynamicEndpointNotFoundByDynamicEndpointId.replace("for DYNAMIC_ENDPOINT_ID.",s"for DYNAMIC_ENDPOINT_ID($dynamicEndpointId) and BANK_ID(${bankId.getOrElse("")}).")
val dynamicEndpoint = unboxFullOrFail(dynamicEndpointBox, callContext, errorMessage, 404)
Future{
(dynamicEndpoint, callContext)
}
}
def getDynamicEndpoints(callContext: Option[CallContext]): OBPReturnType[List[DynamicEndpointT]] = Future {
(DynamicEndpointProvider.connectorMethodProvider.vend.getAll(), callContext)
def getDynamicEndpoints(bankId: Option[String], callContext: Option[CallContext]): OBPReturnType[List[DynamicEndpointT]] = Future {
(DynamicEndpointProvider.connectorMethodProvider.vend.getAll(bankId), callContext)
}
def getDynamicEndpointsByUserId(userId: String, callContext: Option[CallContext]): OBPReturnType[List[DynamicEndpointT]] = Future {
@ -2721,18 +2755,18 @@ object NewStyle {
* @param callContext
* @return
*/
def deleteDynamicEndpoint(dynamicEndpointId: String, callContext: Option[CallContext]): Future[Box[Boolean]] = {
val dynamicEndpoint: OBPReturnType[DynamicEndpointT] = this.getDynamicEndpoint(dynamicEndpointId, callContext)
def deleteDynamicEndpoint(bankId: Option[String], dynamicEndpointId: String, callContext: Option[CallContext]): Future[Box[Boolean]] = {
val dynamicEndpoint: OBPReturnType[DynamicEndpointT] = this.getDynamicEndpoint(bankId, dynamicEndpointId, callContext)
for {
(entity, _) <- dynamicEndpoint
deleteEndpointResult: Box[Boolean] = {
val roles = DynamicEndpointHelper.getRoles(dynamicEndpointId).map(_.toString())
val roles = DynamicEndpointHelper.getRoles(bankId, dynamicEndpointId).map(_.toString())
roles.foreach(ApiRole.removeDynamicApiRole(_))
val rolesDeleteResult: Box[Boolean] = Entitlement.entitlement.vend.deleteEntitlements(roles)
Box !! (rolesDeleteResult == Full(true))
}
deleteSuccess = if(deleteEndpointResult.isDefined && deleteEndpointResult.head) {
tryo {DynamicEndpointProvider.connectorMethodProvider.vend.delete(dynamicEndpointId)}
tryo {DynamicEndpointProvider.connectorMethodProvider.vend.delete(bankId, dynamicEndpointId)}
}else{
Full(false)
}

View File

@ -45,6 +45,7 @@ case class OBPConnectorName(value: String) extends OBPQueryParam
case class OBPEmpty() extends OBPQueryParam
case class OBPCustomerId(value: String) extends OBPQueryParam
case class OBPLockedStatus(value: String) extends OBPQueryParam
case class OBPIsDeleted(value: Boolean) extends OBPQueryParam
object OBPQueryParam {
val LIMIT = "limit"

View File

@ -33,7 +33,9 @@ object Migration extends MdcLoggable {
val toExecute: Boolean = executeAll || scriptsToExecute.contains(name)
val isExecuted = MigrationScriptLogProvider.migrationScriptLogProvider.vend.isExecuted(name)
(toExecute, isExecuted) match {
case (true, false) => blockOfCode
case (true, false) =>
logger.warn(s"Migration.database.$name is started at this instance.")
blockOfCode
case _ => true
}
}
@ -49,6 +51,7 @@ object Migration extends MdcLoggable {
}
MigrationScriptLogProvider.migrationScriptLogProvider.vend.saveLog(name, commitId, isSuccessful, startDate, endDate, remark) match {
case true =>
logger.warn(s"Migration.database.$name is executed at this instance.")
case false =>
logger.warn(s"Migration.database.$name is executed at this instance but the corresponding log is not saved!!!!!!")
}
@ -56,7 +59,7 @@ object Migration extends MdcLoggable {
object database {
def executeScripts(): Boolean = executeScript {
def executeScripts(startedBeforeSchemifier: Boolean): Boolean = executeScript {
dummyScript()
populateTableViewDefinition()
populateTableAccountAccess()
@ -77,6 +80,7 @@ object Migration extends MdcLoggable {
alterColumnStatusAtTableMappedConsent()
alterColumnDetailsAtTableTransactionRequest()
deleteDuplicatedRowsInTheTableUserAuthContext()
populateTheFieldDeletedAtResourceUser(startedBeforeSchemifier)
}
private def dummyScript(): Boolean = {
@ -232,6 +236,17 @@ object Migration extends MdcLoggable {
MigrationOfUserAuthContext.removeDuplicates(name)
}
}
private def populateTheFieldDeletedAtResourceUser(startedBeforeSchemifier: Boolean): Boolean = {
if(startedBeforeSchemifier == true) {
logger.warn(s"Migration.database.populateTheFieldDeletedAtResourceUser(true) cannot be run before Schemifier.")
true
} else {
val name = nameOf(populateTheFieldDeletedAtResourceUser(startedBeforeSchemifier))
runOnce(name) {
MigrationOfResourceUser.populateNewFieldIsDeleted(name)
}
}
}
}

View File

@ -36,7 +36,7 @@ object MigrationOfConsumer {
consumer <- Consumer.findAll() if consumer.appType.get.isEmpty()
} yield {
consumer
.appType(AppType.Web.toString())
.appType(AppType.Confidential.toString())
.saveMe()
}

View File

@ -0,0 +1,56 @@
package code.api.util.migration
import java.time.format.DateTimeFormatter
import java.time.{ZoneId, ZonedDateTime}
import code.api.util.APIUtil
import code.api.util.migration.Migration.{DbFunction, saveLog}
import code.model.dataAccess.ResourceUser
import code.model.{AppType, Consumer}
import net.liftweb.mapper.DB
import net.liftweb.util.{DefaultConnectionIdentifier, Helpers}
object MigrationOfResourceUser {
val oneDayAgo = ZonedDateTime.now(ZoneId.of("UTC")).minusDays(1)
val oneYearInFuture = ZonedDateTime.now(ZoneId.of("UTC")).plusYears(1)
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm'Z'")
def populateNewFieldIsDeleted(name: String): Boolean = {
DbFunction.tableExists(ResourceUser, (DB.use(DefaultConnectionIdentifier){ conn => conn})) match {
case true =>
val startDate = System.currentTimeMillis()
val commitId: String = APIUtil.gitCommit
var isSuccessful = false
// Make back up
DbFunction.makeBackUpOfTable(ResourceUser)
val emptyDeletedField =
for {
user <- ResourceUser.findAll() if user.isDeleted == false
} yield {
user.IsDeleted(false).saveMe()
}
val endDate = System.currentTimeMillis()
val comment: String =
s"""Updated number of rows:
|${emptyDeletedField.size}
|""".stripMargin
isSuccessful = true
saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
isSuccessful
case false =>
val startDate = System.currentTimeMillis()
val commitId: String = APIUtil.gitCommit
val isSuccessful = false
val endDate = System.currentTimeMillis()
val comment: String =
s"""${Consumer._dbTableNameLC} table does not exist""".stripMargin
saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
isSuccessful
}
}
}

View File

@ -3106,13 +3106,18 @@ trait APIMethods121 {
case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transactions":: TransactionId(transactionId) :: "other_account" :: Nil JsonGet req => {
cc =>
for {
account <- BankAccountX(bankId, accountId) ?~! BankAccountNotFound
view <- APIUtil.checkViewAccessAndReturnView(viewId, BankIdAccountId(account.bankId, account.accountId), cc.user) ?~! ViewNotFound
(transaction, callerContext) <- account.moderatedTransaction(transactionId, view, BankIdAccountId(bankId,accountId), cc.user, Some(cc))
moderatedOtherBankAccount <- transaction.otherBankAccount
(Full(u), callContext) <- authenticatedAccess(cc)
(account, callContext) <- NewStyle.function.checkBankAccountExists(bankId, accountId, callContext)
view <- NewStyle.function.checkViewAccessAndReturnView(viewId, BankIdAccountId(bankId, accountId), Some(u), callContext)
(moderatedTransaction, callContext) <- account.moderatedTransactionFuture(transactionId, view, Full(u), callContext) map {
unboxFullOrFail(_, callContext, GetTransactionsException)
}
_ <- NewStyle.function.tryons(GetTransactionsException, 400, callContext) {
moderatedTransaction.otherBankAccount.isDefined
}
} yield {
val otherBankAccountJson = JSONFactory.createOtherBankAccount(moderatedOtherBankAccount)
successJsonResponse(Extraction.decompose(otherBankAccountJson))
val otherBankAccountJson = JSONFactory.createOtherBankAccount(moderatedTransaction.otherBankAccount.get)
(otherBankAccountJson, HttpCode.`200`(callContext))
}
}

View File

@ -481,9 +481,9 @@ trait APIMethods300 {
|""".stripMargin,
emptyObjectJson,
moderatedCoreAccountsJsonV300,
List(UserNotLoggedIn,UnknownError),
List(UserNotLoggedIn,AccountFirehoseNotAllowedOnThisInstance,UnknownError),
List(apiTagAccount, apiTagAccountFirehose, apiTagFirehoseData, apiTagNewStyle),
Some(List(canUseAccountFirehoseAtAnyBank))
Some(List(canUseAccountFirehoseAtAnyBank, ApiRole.canUseAccountFirehose))
)
lazy val getFirehoseAccountsAtOneBank : OBPEndpoint = {
@ -492,11 +492,12 @@ trait APIMethods300 {
cc =>
for {
(Full(u), callContext) <- authenticatedAccess(cc)
_ <- Helper.booleanToFuture(failMsg = AccountFirehoseNotAllowedOnThisInstance +" or " + UserHasMissingRoles + CanUseAccountFirehoseAtAnyBank, cc=callContext) {
canUseAccountFirehose(u)
_ <- Helper.booleanToFuture(failMsg = AccountFirehoseNotAllowedOnThisInstance , cc=cc.callContext) {
allowAccountFirehose
}
_ <- NewStyle.function.hasAtLeastOneEntitlement(bankId.value, u.userId, ApiRole.canUseAccountFirehose :: canUseAccountFirehoseAtAnyBank :: Nil, callContext)
(bank, callContext) <- NewStyle.function.getBank(bankId, callContext)
view <- NewStyle.function.checkViewAccessAndReturnView(viewId, BankIdAccountId(BankId(""), AccountId("")), Some(u), callContext)
view <- NewStyle.function.checkViewAccessAndReturnView(viewId, BankIdAccountId(bankId, AccountId("")), Some(u), callContext)
availableBankIdAccountIdList <- Future {
Views.views.vend.getAllFirehoseAccounts(bank.bankId).map(a => BankIdAccountId(a.bankId,a.accountId))
}
@ -578,9 +579,10 @@ trait APIMethods300 {
cc =>
for {
(Full(u), callContext) <- authenticatedAccess(cc)
_ <- Helper.booleanToFuture(failMsg = AccountFirehoseNotAllowedOnThisInstance +" or " + UserHasMissingRoles + CanUseAccountFirehoseAtAnyBank , cc=callContext) {
canUseAccountFirehose(u)
_ <- Helper.booleanToFuture(failMsg = AccountFirehoseNotAllowedOnThisInstance , cc=callContext) {
allowAccountFirehose
}
_ <- NewStyle.function.hasEntitlement("", u.userId, ApiRole.canUseAccountFirehoseAtAnyBank, callContext)
(bank, callContext) <- NewStyle.function.getBank(bankId, callContext)
(bankAccount, callContext) <- NewStyle.function.getBankAccount(bankId, accountId, callContext)
view <- NewStyle.function.checkViewAccessAndReturnView(viewId, BankIdAccountId(bankAccount.bankId, bankAccount.accountId),Some(u), callContext)

View File

@ -465,10 +465,11 @@ trait APIMethods310 {
cc =>
for {
(Full(u), callContext) <- authenticatedAccess(cc)
(_, callContext) <- NewStyle.function.getBank(bankId, callContext)
_ <- Helper.booleanToFuture(failMsg = CustomerFirehoseNotAllowedOnThisInstance +" or " + UserHasMissingRoles + CanUseCustomerFirehoseAtAnyBank, cc=callContext) {
canUseCustomerFirehose(u)
_ <- Helper.booleanToFuture(failMsg = AccountFirehoseNotAllowedOnThisInstance , cc=callContext) {
allowCustomerFirehose
}
_ <- NewStyle.function.hasEntitlement("", u.userId, ApiRole.canUseCustomerFirehoseAtAnyBank, callContext)
(_, callContext) <- NewStyle.function.getBank(bankId, callContext)
allowedParams = List("sort_direction", "limit", "offset", "from_date", "to_date")
httpParams <- NewStyle.function.extractHttpParamsFromUrl(cc.url)
obpQueryParams <- NewStyle.function.createObpParams(httpParams, allowedParams, callContext)
@ -1191,8 +1192,10 @@ trait APIMethods310 {
"/banks/BANK_ID/customers",
"Create Customer",
s"""
|The Customer resource stores the customer number, legal name, email, phone number, their date of birth, relationship status, education attained, a url for a profile image, KYC status etc.
|The Customer resource stores the customer number (which is set by the backend), legal name, email, phone number, their date of birth, relationship status, education attained, a url for a profile image, KYC status etc.
|Dates need to be in the format 2013-01-21T23:08:00Z
|
|Note: If you need to set a specific customer number, use the Update Customer Number endpoint after this call.
|
|${authenticationRequiredMessage(true)}
|""",

File diff suppressed because it is too large Load Diff

View File

@ -28,17 +28,18 @@ package code.api.v4_0_0
import java.text.SimpleDateFormat
import java.util.Date
import code.api.attributedefinition.AttributeDefinition
import code.api.util.APIUtil
import code.api.util.APIUtil.{DateWithDay, DateWithSeconds, stringOptionOrNull, stringOrNull}
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.{LocationJsonV140, MetaJsonV140, TransactionRequestAccountJsonV140, transformToLocationFromV140, transformToMetaFromV140}
import code.api.v2_0_0.TransactionRequestChargeJsonV200
import code.api.v2_0_0.{EntitlementJSONs, JSONFactory200, TransactionRequestChargeJsonV200}
import code.api.v2_1_0.{IbanJson, JSONFactory210, PostCounterpartyBespokeJson, ResourceUserJSON}
import code.api.v2_2_0.CounterpartyMetadataJson
import code.api.v3_0_0.JSONFactory300.{createAccountRoutingsJSON, createAccountRulesJSON, createLocationJson, createMetaJson, transformToAddressFromV300}
import code.api.v3_0_0.{AccountRuleJsonV300, AddressJsonV300, CustomerAttributeResponseJsonV300, OpeningTimesV300}
import code.api.v3_0_0.{AccountRuleJsonV300, AddressJsonV300, CustomerAttributeResponseJsonV300, OpeningTimesV300, ViewJSON300, ViewsJSON300}
import code.api.v3_1_0.JSONFactory310.createAccountAttributeJson
import code.api.v3_1_0.{AccountAttributeResponseJson, RedisCallLimitJson}
import code.apicollection.ApiCollectionTrait
@ -51,6 +52,7 @@ import code.ratelimiting.RateLimiting
import code.standingorders.StandingOrderTrait
import code.transactionrequests.TransactionRequests.TransactionChallengeTypes
import code.userlocks.UserLocks
import code.users.UserInvitation
import com.openbankproject.commons.model.{DirectDebitTrait, _}
import net.liftweb.common.{Box, Full}
import net.liftweb.json.JValue
@ -123,6 +125,24 @@ case class TransactionRequestWithChargeJSON400(
case class PostResetPasswordUrlJsonV400(username: String, email: String, user_id: String)
case class ResetPasswordUrlJsonV400(reset_password_url: String)
case class PostUserInvitationAnonymousJsonV400(secret_key: Long)
case class PostUserInvitationJsonV400(first_name: String,
last_name: String,
email: String,
company: String,
country: String,
purpose: String)
case class UserInvitationJsonV400(first_name: String,
last_name: String,
email: String,
company: String,
country: String,
purpose: String,
status: String)
case class UserInvitationsJsonV400(user_invitations: List[UserInvitationJsonV400])
case class UserIdJsonV400(user_id: String)
case class APIInfoJson400(
version : String,
version_status: String,
@ -232,6 +252,15 @@ case class AccountBalanceJsonV400(
balances: List[BalanceJsonV400]
)
case class AccountBalancesJsonV400(
account_id: String,
bank_id: String,
account_routings: List[AccountRouting],
label: String,
balances: List[BalanceJsonV400],
)
case class PostCustomerPhoneNumberJsonV400(mobile_phone_number: String)
case class PostDirectDebitJsonV400(customer_id: String,
user_id: String,
@ -738,8 +767,32 @@ case class AtmJsonV400 (
case class AtmsJsonV400(atms : List[AtmJsonV400])
case class UserJsonV400(
user_id: String,
email : String,
provider_id: String,
provider : String,
username : String,
entitlements : EntitlementJSONs,
views: Option[ViewsJSON300],
is_deleted: Boolean
)
object JSONFactory400 {
def createUserInfoJSON(user : User, entitlements: List[Entitlement]) : UserJsonV400 = {
UserJsonV400(
user_id = user.userId,
email = user.emailAddress,
username = stringOrNull(user.name),
provider_id = user.idGivenByProvider,
provider = stringOrNull(user.provider),
entitlements = JSONFactory200.createEntitlementJSONs(entitlements),
views = None,
is_deleted = user.isDeleted.getOrElse(false)
)
}
def createCallsLimitJson(rateLimiting: RateLimiting) : CallLimitJsonV400 = {
CallLimitJsonV400(
rateLimiting.fromDate,
@ -780,6 +833,10 @@ object JSONFactory400 {
BanksJson400(l.map(createBankJSON400))
}
def createUserIdInfoJson(user : User) : UserIdJsonV400 = {
UserIdJsonV400(user_id = user.userId)
}
def createSettlementAccountJson(userId: String, account: BankAccount, accountAttributes: List[AccountAttribute]): SettlementAccountResponseJson =
SettlementAccountResponseJson(
account_id = account.accountId.value,
@ -1152,6 +1209,22 @@ object JSONFactory400 {
def createCounterpartiesJson400(counterparties: List[CounterpartyTrait]): CounterpartiesJson400 =
CounterpartiesJson400(counterparties.map(createCounterpartyJson400))
def createUserInvitationJson(userInvitation: UserInvitation): UserInvitationJsonV400 = {
UserInvitationJsonV400(
first_name = userInvitation.firstName,
last_name = userInvitation.lastName,
email = userInvitation.email,
company = userInvitation.company,
country = userInvitation.country,
purpose = userInvitation.purpose,
status = userInvitation.status
)
}
def createUserInvitationJson(userInvitations: List[UserInvitation]): UserInvitationsJsonV400 = {
UserInvitationsJsonV400(userInvitations.map(createUserInvitationJson))
}
def createBalancesJson(accountsBalances: AccountsBalances) = {
AccountsBalancesJsonV400(
accounts = accountsBalances.accounts.map(
@ -1161,12 +1234,24 @@ object JSONFactory400 {
account_routings = account.accountRoutings,
label = account.label,
balances = List(
BalanceJsonV400(`type` = "", currency = account.balance.currency, amount = account.balance.amount)
BalanceJsonV400(`type` = "OpeningBooked", currency = account.balance.currency, amount = account.balance.amount)
)
)
)
)
}
def createAccountBalancesJson(accountBalances: AccountBalances) = {
AccountBalanceJsonV400(
account_id = accountBalances.id,
bank_id = accountBalances.bankId,
account_routings = accountBalances.accountRoutings,
label = accountBalances.label,
balances = accountBalances.balances.map( balance =>
BalanceJsonV400(`type`=balance.balanceType, currency = balance.balance.currency, amount = balance.balance.amount)
)
)
}
def createConsentsJsonV400(consents: List[MappedConsent]): ConsentsJsonV400= {
ConsentsJsonV400(consents.map(c => ConsentJsonV400(c.consentId, c.jsonWebToken, c.status, c.apiStandard, c.apiVersion)))

View File

@ -25,7 +25,7 @@ import net.liftweb.json.JsonDSL._
import net.liftweb.json.JsonParser.ParseException
import org.apache.commons.lang3.{StringUtils, Validate}
import net.liftweb.util.{StringHelpers, ThreadGlobal}
import org.apache.commons.collections4.MapUtils
import org.apache.commons.collections4.{ListUtils, MapUtils}
import org.apache.commons.io.FileUtils
import org.apache.commons.lang3.StringUtils
@ -40,7 +40,7 @@ import net.liftweb.json.Formats
import scala.collection.JavaConverters._
import scala.collection.immutable.List
import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
import scala.collection.mutable.{ArrayBuffer, ListBuffer}
object DynamicEndpointHelper extends RestHelper {
@ -51,27 +51,30 @@ object DynamicEndpointHelper extends RestHelper {
*/
val urlPrefix = APIUtil.getPropsValue("dynamic_endpoints_url_prefix", "dynamic")
private val implementedInApiVersion = ApiVersion.v4_0_0
private val IsDynamicEntityUrl = """https?://dynamic_entity.*"""
private val IsDynamicEntityUrl = """.*dynamic_entity.*"""
private val IsMockUrlString = """.*obp_mock(?::\d+)?.*"""
private val IsMockUrl = IsMockUrlString.r
def isDynamicEntityResponse (serverUrl : String) = serverUrl matches (IsDynamicEntityUrl)
def isMockedResponse (serverUrl : String) = serverUrl matches (IsMockUrlString)
private def dynamicEndpointInfos: List[DynamicEndpointInfo] = {
val dynamicEndpoints: List[DynamicEndpointT] = DynamicEndpointProvider.connectorMethodProvider.vend.getAll()
val infos = dynamicEndpoints.map(it => buildDynamicEndpointInfo(it.swaggerString, it.dynamicEndpointId.get))
val dynamicEndpoints: List[DynamicEndpointT] = DynamicEndpointProvider.connectorMethodProvider.vend.getAll(None)
val infos = dynamicEndpoints.map(it => buildDynamicEndpointInfo(it.swaggerString, it.dynamicEndpointId.get, it.bankId))
infos
}
def allDynamicEndpointRoles: List[ApiRole] = {
for {
dynamicEndpoint <- DynamicEndpointProvider.connectorMethodProvider.vend.getAll()
info = buildDynamicEndpointInfo(dynamicEndpoint.swaggerString, dynamicEndpoint.dynamicEndpointId.get)
dynamicEndpoint <- DynamicEndpointProvider.connectorMethodProvider.vend.getAll(None)
info = buildDynamicEndpointInfo(dynamicEndpoint.swaggerString, dynamicEndpoint.dynamicEndpointId.get, dynamicEndpoint.bankId)
role <- getRoles(info)
} yield role
}
def getRoles(dynamicEndpointId: String): List[ApiRole] = {
val foundInfos: Box[DynamicEndpointInfo] = DynamicEndpointProvider.connectorMethodProvider.vend.get(dynamicEndpointId)
.map(dynamicEndpoint => buildDynamicEndpointInfo(dynamicEndpoint.swaggerString, dynamicEndpoint.dynamicEndpointId.get))
def getRoles(bankId: Option[String], dynamicEndpointId: String): List[ApiRole] = {
val foundInfos: Box[DynamicEndpointInfo] = DynamicEndpointProvider.connectorMethodProvider.vend.get(bankId, dynamicEndpointId)
.map(dynamicEndpoint => buildDynamicEndpointInfo(dynamicEndpoint.swaggerString, dynamicEndpoint.dynamicEndpointId.get, dynamicEndpoint.bankId))
val roles: List[ApiRole] = foundInfos match {
@ -82,6 +85,13 @@ object DynamicEndpointHelper extends RestHelper {
roles
}
def listOfRolesToUseAllDynamicEndpointsAOneBank(bankId: Option[String]): List[ApiRole] = {
val foundInfos: List[DynamicEndpointInfo] = DynamicEndpointProvider.connectorMethodProvider.vend.getAll(bankId)
.map(dynamicEndpoint => buildDynamicEndpointInfo(dynamicEndpoint.swaggerString, dynamicEndpoint.dynamicEndpointId.get, dynamicEndpoint.bankId))
foundInfos.map(getRoles(_)).flatten.toSet.toList
}
def getRoles(dynamicEndpointInfo: DynamicEndpointInfo): List[ApiRole] =
for {
resourceDoc <- dynamicEndpointInfo.resourceDocs.toList
@ -96,7 +106,6 @@ object DynamicEndpointHelper extends RestHelper {
object DynamicReq extends JsonTest with JsonBody {
private val ExpressionRegx = """\{(.+?)\}""".r
private val IsMockUrl = """https?://obp_mock(?::\d+)?/.*""".r
/**
* unapply Request to (request url, json, http method, request parameters, path parameters, role)
* request url is current request target url to remote server
@ -108,7 +117,7 @@ object DynamicEndpointHelper extends RestHelper {
* @param r HttpRequest
* @return (adapterUrl, requestBodyJson, httpMethod, requestParams, pathParams, role, operationId, mockResponseCode->mockResponseBody)
*/
def unapply(r: Req): Option[(String, JValue, AkkaHttpMethod, Map[String, List[String]], Map[String, String], ApiRole, String, Option[(Int, JValue)])] = {
def unapply(r: Req): Option[(String, JValue, AkkaHttpMethod, Map[String, List[String]], Map[String, String], ApiRole, String, Option[(Int, JValue)], Option[String])] = {
val partPath = r.path.partPath//eg: List("dynamic","feature-test")
if (!testResponse_?(r) || partPath.headOption != Option(urlPrefix))//if check the Content-Type contains json or not, and check the if it is the `dynamic_endpoints_url_prefix`
None //if do not match `URL and Content-Type`, then can not find this endpoint. return None.
@ -118,7 +127,7 @@ object DynamicEndpointHelper extends RestHelper {
val urlQueryParameters = r.params
// url that match original swagger endpoint.
val url = partPath.tail.mkString("/", "/", "") // eg: --> /feature-test
val foundDynamicEndpoint: Option[(String, String, Int, ResourceDoc)] = dynamicEndpointInfos
val foundDynamicEndpoint: Option[(String, String, Int, ResourceDoc, Option[String])] = dynamicEndpointInfos
.map(_.findDynamicEndpoint(httpMethod, url))
.collectFirst {
case Some(x) => x
@ -126,7 +135,7 @@ object DynamicEndpointHelper extends RestHelper {
foundDynamicEndpoint
.flatMap { it =>
val (serverUrl, endpointUrl, code, doc) = it
val (serverUrl, endpointUrl, code, doc, bankId) = it
val pathParams: Map[String, String] = if(endpointUrl == url) {
Map.empty[String, String]
@ -152,7 +161,7 @@ object DynamicEndpointHelper extends RestHelper {
val Some(role::_) = doc.roles
val requestBodyJValue = body(r).getOrElse(JNothing)
Full(s"""$serverUrl$url""", requestBodyJValue, akkaHttpMethod, urlQueryParameters, pathParams, role, doc.operationId, mockResponse)
Full(s"""$serverUrl$url""", requestBodyJValue, akkaHttpMethod, urlQueryParameters, pathParams, role, doc.operationId, mockResponse, bankId)
}
}
@ -160,7 +169,7 @@ object DynamicEndpointHelper extends RestHelper {
}
def findExistsEndpoints(openAPI: OpenAPI): List[(HttpMethod, String)] = {
def findExistingDynamicEndpoints(openAPI: OpenAPI): List[(HttpMethod, String)] = {
for {
(path, pathItem) <- openAPI.getPaths.asScala.toList
(method: HttpMethod, _) <- pathItem.readOperationsMap.asScala
@ -171,14 +180,14 @@ object DynamicEndpointHelper extends RestHelper {
private val dynamicEndpointInfoMemo = new Memo[String, DynamicEndpointInfo]
private def buildDynamicEndpointInfo(content: String, id: String): DynamicEndpointInfo =
private def buildDynamicEndpointInfo(content: String, id: String, bankId:Option[String]): DynamicEndpointInfo =
dynamicEndpointInfoMemo.memoize(content) {
val openAPI: OpenAPI = parseSwaggerContent(content)
buildDynamicEndpointInfo(openAPI, id)
buildDynamicEndpointInfo(openAPI, id, bankId)
}
private def buildDynamicEndpointInfo(openAPI: OpenAPI, id: String): DynamicEndpointInfo = {
val tags: List[ResourceDocTag] = List(ApiTag(openAPI.getInfo.getTitle), apiTagNewStyle, apiTagDynamicEntity, apiTagDynamic)
def buildDynamicEndpointInfo(openAPI: OpenAPI, id: String, bankId:Option[String]): DynamicEndpointInfo = {
val tags: List[ResourceDocTag] = List(ApiTag(openAPI.getInfo.getTitle), apiTagNewStyle, apiTagDynamicEndpoint, apiTagDynamic)
val serverUrl = {
val servers = openAPI.getServers
@ -249,7 +258,7 @@ object DynamicEndpointHelper extends RestHelper {
val opName = method.name().toLowerCase().capitalize
s"Can${opName}DynamicEndpoint_"
}
val roleName = if(StringUtils.isNotBlank(op.getOperationId)) {
var roleName = if(StringUtils.isNotBlank(op.getOperationId)) {
val prettyOperationId = op.getOperationId
.replaceAll("""(?i)(get|find|search|add|create|delete|update|of|new|the|one|that|\s)""", "")
.capitalize
@ -265,9 +274,12 @@ object DynamicEndpointHelper extends RestHelper {
s"$roleNamePrefix$prettySummary${entitlementSuffix(path)}"
}
// substring role name to avoid it have over the maximum length of db column.
if(roleName.size > 64) {
roleName = StringUtils.substring(roleName, 0, 53) + roleName.hashCode()
}
Some(List(
ApiRole.getOrCreateDynamicApiRole(roleName)
ApiRole.getOrCreateDynamicApiRole(roleName, bankId.isDefined)
))
}
val doc = ResourceDoc(
@ -287,7 +299,7 @@ object DynamicEndpointHelper extends RestHelper {
DynamicEndpointItem(path, successCode, doc)
}
DynamicEndpointInfo(id, dynamicEndpointItems, serverUrl)
DynamicEndpointInfo(id, dynamicEndpointItems, serverUrl, bankId)
}
private val PathParamRegx = """\{(.+?)\}""".r
@ -477,96 +489,117 @@ object DynamicEndpointHelper extends RestHelper {
implicit val formats = CustomJsonFormats.formats
val example: Any = getExampleBySchema(openAPI, schema)
convertToProduct(example)
}
example match {
case null => EmptyBody
case v: String => StringBody(v)
case v: Boolean => BooleanBody(v)
case v: Int => IntBody(v)
case v: Long => LongBody(v)
case v: BigInt => BigIntBody(v)
case v: Float => FloatBody(v)
case v: Double => DoubleBody(v)
case v: BigDecimal => BigDecimalBody(v)
case v: JArray => JArrayBody(v)
case v: JObject => v
case v :scala.Product => v
case v => json.Extraction.decompose(v) match {
case o: JObject => o
case JArray(arr) => arr
case _ => throw new RuntimeException(s"Not supporting example type: $v, ${v.getClass}")
}
private def convertToProduct(example: Any): Product = example match {
case null => EmptyBody
case v: String => StringBody(v)
case v: Boolean => BooleanBody(v)
case v: Int => IntBody(v)
case v: Long => LongBody(v)
case v: BigInt => BigIntBody(v)
case v: Float => FloatBody(v)
case v: Double => DoubleBody(v)
case v: BigDecimal => BigDecimalBody(v)
case v: JArray => JArrayBody(v)
case v: JObject => v
case v :scala.Product => v
case v => json.Extraction.decompose(v) match {
case o: JObject => o
case JArray(arr) => arr
case _ => throw new RuntimeException(s"Not supporting example type: $v, ${v.getClass}")
}
}
private def getExampleBySchema(openAPI: OpenAPI, schema: Schema[_]):Any = {
def getDefaultValue[T](schema: Schema[_<:T], t: => T): T = Option(schema.getExample.asInstanceOf[T])
.orElse(Option(schema.getDefault))
.orElse{
Option(schema.getEnum())
.filterNot(_.isEmpty)
.map(_.get(0))
schema.getEnum() match {
case null => None
case l if l.isEmpty => None
case l => Option(l.get(0))
}
}
.getOrElse(t)
schema match {
case null => null
case v: BooleanSchema => getDefaultValue(v, true)
case v if v.getType() =="boolean" => true
case v: DateSchema => getDefaultValue(v, {
APIUtil.DateWithDayFormat.format(new Date())
})
case v if v.getFormat() == "date" => getDefaultValue(v, {
APIUtil.DateWithDayFormat.format(new Date())
})
case v: DateTimeSchema => getDefaultValue(v, {
APIUtil.DateWithSecondsFormat.format(new Date())
})
case v if v.getFormat() == "date-time" => getDefaultValue(v, {
APIUtil.DateWithSecondsFormat.format(new Date())
})
case v: IntegerSchema => getDefaultValue(v, 1)
case v if v.getFormat() == "int32" => 1
case v: NumberSchema => getDefaultValue(v, 1.2)
case v if v.getType() == "number" => 1.2
case v: StringSchema => getDefaultValue(v, "string")
case v: UUIDSchema => getDefaultValue(v, UUID.randomUUID())
case v if v.getFormat() == "uuid" => UUID.randomUUID()
case v: EmailSchema => getDefaultValue(v, "example@tesobe.com")
case v if v.getFormat() == "email" => "example@tesobe.com"
case v: FileSchema => getDefaultValue(v, "file_example.txt")
case v if v.getFormat() == "binary" => "file_example.txt"
case v: PasswordSchema => getDefaultValue(v, "very_complex_password_I_promise_!!")
case v if v.getFormat() == "password" => "very_complex_password_I_promise_!!"
case v: ArraySchema =>
getDefaultValue(v, {
val itemsSchema: Schema[_] = v.getItems
val singleItemExample = getExampleBySchema(openAPI, itemsSchema)
singleItemExample match {
case v: JValue => JArray(v::Nil)
case v => json.Extraction.decompose(Array(v))
}
val schemas:ListBuffer[Schema[_]] = ListBuffer()
def rec(schema: Schema[_]): Any = {
if(schema.isInstanceOf[ObjectSchema]) {
schemas += schema
}
// check whether this schema already recurse two times
if(schemas.count(schema ==) > 3) {
return JObject(Nil)
}
schema match {
case null => null
case v: BooleanSchema => getDefaultValue(v, true)
case v if v.getType() =="boolean" => true
case v: DateSchema => getDefaultValue(v, {
APIUtil.DateWithDayFormat.format(new Date())
})
case v: MapSchema => getDefaultValue(v, Map("name"-> "John", "age" -> 12))
case v if v.getFormat() == "date" => getDefaultValue(v, {
APIUtil.DateWithDayFormat.format(new Date())
})
case v: DateTimeSchema => getDefaultValue(v, {
APIUtil.DateWithSecondsFormat.format(new Date())
})
case v if v.getFormat() == "date-time" => getDefaultValue(v, {
APIUtil.DateWithSecondsFormat.format(new Date())
})
case v: IntegerSchema => getDefaultValue(v, 1)
case v if v.getFormat() == "int32" => 1
case v: NumberSchema => getDefaultValue(v, 1.2)
case v if v.getType() == "number" => 1.2
case v: StringSchema => getDefaultValue(v, "string")
case v: UUIDSchema => getDefaultValue(v, UUID.randomUUID())
case v if v.getFormat() == "uuid" => UUID.randomUUID()
case v: EmailSchema => getDefaultValue(v, "example@tesobe.com")
case v if v.getFormat() == "email" => "example@tesobe.com"
case v: FileSchema => getDefaultValue(v, "file_example.txt")
case v if v.getFormat() == "binary" => "file_example.txt"
case v: PasswordSchema => getDefaultValue(v, "very_complex_password_I_promise_!!")
case v if v.getFormat() == "password" => "very_complex_password_I_promise_!!"
case v: ArraySchema =>
getDefaultValue(v, {
rec(v.getItems) match {
case v: JValue => JArray(v::Nil)
case v => json.Extraction.decompose(Array(v))
}
})
case v: MapSchema => getDefaultValue(v, Map("name"-> "John", "age" -> 12))
//The swagger object schema may not contain any properties: eg:
// "Account": {
// "title": "accountTransactibility",
// "type": "object"
// }
case v if v.isInstanceOf[ObjectSchema] && MapUtils.isEmpty(v.getProperties()) =>
EmptyBody
case v if v.isInstanceOf[ObjectSchema] || MapUtils.isNotEmpty(v.getProperties()) =>
val properties: util.Map[String, Schema[_]] = v.getProperties
case v if v.isInstanceOf[ObjectSchema] || MapUtils.isNotEmpty(v.getProperties()) =>
val properties: util.Map[String, Schema[_]] = v.getProperties
val jFields: mutable.Iterable[JField] = properties.asScala.map { kv =>
val (name, value) = kv
val valueExample = getExampleBySchema(openAPI, value)
JField(name, json.Extraction.decompose(valueExample))
}
JObject(jFields.toList)
val jFields: mutable.Iterable[JField] = properties.asScala.map { kv =>
val (name, value) = kv
val valueExample = rec(value)
JField(name, json.Extraction.decompose(valueExample))
}
JObject(jFields.toList)
case v: Schema[_] if StringUtils.isNotBlank(v.get$ref()) =>
val refSchema = getRefSchema(openAPI, v.get$ref())
case v: Schema[_] if StringUtils.isNotBlank(v.get$ref()) =>
val refSchema = getRefSchema(openAPI, v.get$ref())
convertToProduct(rec(refSchema))
getExample(openAPI, refSchema)
case v if v.getType() == "string" => "string"
case _ => throw new RuntimeException(s"Not support type $schema, please support it if necessary.")
case v if v.getType() == "string" => "string"
case _ => throw new RuntimeException(s"Not support type $schema, please support it if necessary.")
}
}
rec(schema)
}
@ -805,6 +838,27 @@ object DynamicEndpointHelper extends RestHelper {
val (dynamicEntityName, dynamicDateId) = findDynamicData(dynamicDataList, dynamicDataJson)
JBool(DynamicDataProvider.connectorMethodProvider.vend.delete(dynamicEntityName, dynamicDateId).getOrElse(false))
}
def addedBankToPath(swagger: String, bankId: Option[String]): JValue = {
val jvalue = json.parse(swagger)
addedBankToPath(jvalue, bankId)
}
// If it is bank is defined, we will add the bank into the path, better check the scala tests
// eg: /fashion-brand-list --> /banks/gh.29.uk/fashion-brand-list
def addedBankToPath(swagger: JValue, bankId: Option[String]): JValue = {
if(bankId.isDefined){
swagger transformField {
case JField(name, JObject(obj)) =>
if (name.startsWith("/"))
JField(s"/banks/${bankId.get}$name", JObject(obj))
else
JField(name, JObject(obj))
}
} else{
swagger
}
}
}
@ -822,7 +876,7 @@ case class DynamicEndpointItem(path: String, successCode: Int, resourceDoc: Reso
* @param dynamicEndpointItems ResourceDoc to url that defined in swagger content
* @param serverUrl base url that defined in swagger content
*/
case class DynamicEndpointInfo(id: String, dynamicEndpointItems: mutable.Iterable[DynamicEndpointItem], serverUrl: String) {
case class DynamicEndpointInfo(id: String, dynamicEndpointItems: mutable.Iterable[DynamicEndpointItem], serverUrl: String, bankId:Option[String]) {
val resourceDocs: mutable.Iterable[ResourceDoc] = dynamicEndpointItems.map(_.resourceDoc)
private val existsUrlToMethod: mutable.Iterable[(HttpMethod, String, Int, ResourceDoc)] =
@ -833,10 +887,10 @@ case class DynamicEndpointInfo(id: String, dynamicEndpointItems: mutable.Iterabl
})
// return (serverUrl, endpointUrl, successCode, resourceDoc)
def findDynamicEndpoint(newMethod: HttpMethod, newUrl: String): Option[(String, String, Int, ResourceDoc)] =
def findDynamicEndpoint(newMethod: HttpMethod, newUrl: String): Option[(String, String, Int, ResourceDoc, Option[String])] =
existsUrlToMethod collectFirst {
case (method, url, code, doc) if isSameUrl(newUrl, url) && newMethod == method =>
(this.serverUrl, url, code, doc)
(this.serverUrl, url, code, doc, this.bankId)
}
def existsEndpoint(newMethod: HttpMethod, newUrl: String): Boolean = findDynamicEndpoint(newMethod, newUrl).isDefined

View File

@ -47,9 +47,9 @@ object EntityName {
object DynamicEntityHelper {
private val implementedInApiVersion = ApiVersion.v4_0_0
def definitionsMap: Map[String, DynamicEntityInfo] = NewStyle.function.getDynamicEntities().map(it => (it.entityName, DynamicEntityInfo(it.metadataJson, it.entityName, it.bankId))).toMap
def definitionsMap: Map[String, DynamicEntityInfo] = NewStyle.function.getDynamicEntities(None).map(it => (it.entityName, DynamicEntityInfo(it.metadataJson, it.entityName, it.bankId))).toMap
def dynamicEntityRoles: List[String] = NewStyle.function.getDynamicEntities().flatMap(dEntity => DynamicEntityInfo.roleNames(dEntity.entityName, dEntity.bankId))
def dynamicEntityRoles: List[String] = NewStyle.function.getDynamicEntities(None).flatMap(dEntity => DynamicEntityInfo.roleNames(dEntity.entityName, dEntity.bankId))
def doc: ArrayBuffer[ResourceDoc] = {
val docs = operationToResourceDoc.values.toList
@ -153,7 +153,7 @@ object DynamicEntityHelper {
UserHasMissingRoles,
UnknownError
),
List(apiTag, apiTagNewStyle, apiTagDynamicEndpoint, apiTagDynamic),
List(apiTag, apiTagNewStyle, apiTagDynamicEntity, apiTagDynamic),
Some(List(dynamicEntityInfo.canGetRole))
)
resourceDocs += (DynamicEntityOperation.GET_ONE, entityName) -> ResourceDoc(
@ -179,7 +179,7 @@ object DynamicEntityHelper {
UserHasMissingRoles,
UnknownError
),
List(apiTag, apiTagNewStyle, apiTagDynamicEndpoint, apiTagDynamic),
List(apiTag, apiTagNewStyle, apiTagDynamicEntity, apiTagDynamic),
Some(List(dynamicEntityInfo.canGetRole))
)
@ -208,7 +208,7 @@ object DynamicEntityHelper {
InvalidJsonFormat,
UnknownError
),
List(apiTag, apiTagNewStyle, apiTagDynamicEndpoint, apiTagDynamic),
List(apiTag, apiTagNewStyle, apiTagDynamicEntity, apiTagDynamic),
Some(List(dynamicEntityInfo.canCreateRole))
)
@ -237,7 +237,7 @@ object DynamicEntityHelper {
InvalidJsonFormat,
UnknownError
),
List(apiTag, apiTagNewStyle, apiTagDynamicEndpoint, apiTagDynamic),
List(apiTag, apiTagNewStyle, apiTagDynamicEntity, apiTagDynamic),
Some(List(dynamicEntityInfo.canUpdateRole))
)
@ -263,7 +263,7 @@ object DynamicEntityHelper {
InvalidJsonFormat,
UnknownError
),
List(apiTag, apiTagNewStyle, apiTagDynamicEndpoint, apiTagDynamic),
List(apiTag, apiTagNewStyle, apiTagDynamicEntity, apiTagDynamic),
Some(List(dynamicEntityInfo.canDeleteRole))
)

View File

@ -492,6 +492,8 @@ trait Connector extends MdcLoggable {
def getBankAccounts(bankIdAccountIds: List[BankIdAccountId], callContext: Option[CallContext]) : OBPReturnType[Box[List[BankAccount]]]= Future{(Failure(setUnimplementedError), callContext)}
def getBankAccountsBalances(bankIdAccountIds: List[BankIdAccountId], callContext: Option[CallContext]) : OBPReturnType[Box[AccountsBalances]]= Future{(Failure(setUnimplementedError), callContext)}
def getBankAccountBalances(bankIdAccountId: BankIdAccountId, callContext: Option[CallContext]) : OBPReturnType[Box[AccountBalances]]= Future{(Failure(setUnimplementedError), callContext)}
def getCoreBankAccountsLegacy(bankIdAccountIds: List[BankIdAccountId], callContext: Option[CallContext]) : Box[(List[CoreAccount], Option[CallContext])] =
Failure(setUnimplementedError)

View File

@ -47,6 +47,7 @@ import code.metadata.tags.Tags
import code.metadata.transactionimages.TransactionImages
import code.metadata.wheretags.WhereTags
import code.model._
import code.model.dataAccess.AuthUser.findUserByUsernameLocally
import code.model.dataAccess._
import code.productAttributeattribute.MappedProductAttribute
import code.productattribute.ProductAttributeX
@ -81,7 +82,7 @@ import com.tesobe.model.UpdateBankAccount
import net.liftweb.common._
import net.liftweb.json
import net.liftweb.json.JsonAST.JField
import net.liftweb.json.{JArray, JBool, JInt, JObject, JValue,JString}
import net.liftweb.json.{JArray, JBool, JInt, JObject, JString, JValue}
import net.liftweb.mapper.{By, _}
import net.liftweb.util.Helpers.{hours, now, time, tryo}
import net.liftweb.util.Mailer
@ -743,6 +744,24 @@ object LocalMappedConnector extends Connector with MdcLoggable {
)), callContext)
}
override def getBankAccountBalances(bankIdAccountId: BankIdAccountId, callContext: Option[CallContext]): OBPReturnType[Box[AccountBalances]] =
Future {
for {
bankAccount <- getBankAccountOld(bankIdAccountId.bankId, bankIdAccountId.accountId) ?~! s"${ErrorMessages.BankAccountNotFound} current BANK_ID(${bankIdAccountId.bankId}) and ACCOUNT_ID(${bankIdAccountId.accountId})"
accountBalances = AccountBalances(
id = bankAccount.accountId.value,
label = bankAccount.label,
bankId = bankAccount.bankId.value,
accountRoutings = bankAccount.accountRoutings.map(accountRounting => AccountRouting(accountRounting.scheme, accountRounting.address)),
balances = List(BankAccountBalance(AmountOfMoney(bankAccount.currency, bankAccount.balance.toString),"OpeningBooked")),
overallBalance = AmountOfMoney(bankAccount.currency, bankAccount.balance.toString),
overallBalanceDate = now
)
} yield {
(accountBalances,callContext)
}
}
override def checkBankAccountExistsLegacy(bankId: BankId, accountId: AccountId, callContext: Option[CallContext]): Box[(BankAccount, Option[CallContext])] = {
getBankAccountLegacy(bankId: BankId, accountId: AccountId, callContext)
}
@ -4943,7 +4962,20 @@ object LocalMappedConnector extends Connector with MdcLoggable {
//NOTE: this method is not for mapped connector, we put it here for the star default implementation.
// : we call that method only when we set external authentication and provider is not OBP-API
override def checkExternalUserExists(username: String, callContext: Option[CallContext]): Box[InboundExternalUser] = Failure("")
override def checkExternalUserExists(username: String, callContext: Option[CallContext]): Box[InboundExternalUser] = {
findUserByUsernameLocally(username).map( user =>
InboundExternalUser(aud = "",
exp = "",
iat = "",
iss = "",
sub = user.username.get,
azp = None,
email = None,
emailVerified = None,
name = None
)
)
}
override def validateUserAuthContextUpdateRequest(

View File

@ -31,7 +31,7 @@ trait ConsumersProvider {
def getConsumerByConsumerIdFuture(consumerId: String): Future[Box[Consumer]]
def getConsumersByUserIdFuture(userId: String): Future[List[Consumer]]
def getConsumersFuture(): Future[List[Consumer]]
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], clientCertificate: Option[String] = None): Box[Consumer]
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], clientCertificate: Option[String] = None, company: Option[String] = None): Box[Consumer]
def deleteConsumer(consumer: Consumer): Boolean
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]]
@ -63,7 +63,7 @@ class RemotedataConsumersCaseClasses {
case class getConsumerByConsumerIdFuture(consumerId: String)
case class getConsumersByUserIdFuture(userId: String)
case class getConsumersFuture()
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], clientCertificate: Option[String])
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], clientCertificate: Option[String], company: 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 deleteConsumer(consumer: Consumer)
case class updateConsumerCallLimits(id: Long, perSecond: Option[String], perMinute: Option[String], perHour: Option[String], perDay: Option[String], perWeek: Option[String], perMonth: Option[String])

View File

@ -18,12 +18,14 @@ trait DynamicEndpointT {
* The user who create this DynamicEndpoint
*/
def userId: String
def bankId: Option[String]
}
case class DynamicEndpointCommons(
dynamicEndpointId: Option[String] = None,
swaggerString: String,
userId: String
userId: String,
bankId: Option[String]
) extends DynamicEndpointT with JsonFieldReName
object DynamicEndpointCommons extends Converter[DynamicEndpointT, DynamicEndpointCommons]
@ -31,11 +33,11 @@ object DynamicEndpointCommons extends Converter[DynamicEndpointT, DynamicEndpoin
case class DynamicEndpointSwagger(swaggerString: String, dynamicEndpointId: Option[String] = None)
trait DynamicEndpointProvider {
def create(userId: String, swaggerString: String): Box[DynamicEndpointT]
def update(dynamicEndpointId: String, swaggerString: String): Box[DynamicEndpointT]
def updateHost(dynamicEndpointId: String, hostString: String): Box[DynamicEndpointT]
def get(dynamicEndpointId: String): Box[DynamicEndpointT]
def getAll(): List[DynamicEndpointT]
def create(bankId:Option[String], userId: String, swaggerString: String): Box[DynamicEndpointT]
def update(bankId:Option[String], dynamicEndpointId: String, swaggerString: String): Box[DynamicEndpointT]
def updateHost(bankId:Option[String], dynamicEndpointId: String, hostString: String): Box[DynamicEndpointT]
def get(bankId:Option[String],dynamicEndpointId: String): Box[DynamicEndpointT]
def getAll(bankId:Option[String]): List[DynamicEndpointT]
def getDynamicEndpointsByUserId(userId: String): List[DynamicEndpointT]
def delete(dynamicEndpointId: String): Boolean
def delete(bankId:Option[String], dynamicEndpointId: String): Boolean
}

View File

@ -17,36 +17,78 @@ import scala.concurrent.duration.DurationInt
object MappedDynamicEndpointProvider extends DynamicEndpointProvider with CustomJsonFormats{
val dynamicEndpointTTL : Int = {
if(Props.testMode) 0
else APIUtil.getPropsValue(s"dynamicEndpoint.cache.ttl.seconds", "32").toInt
else //Better set this to 0, we maybe create multiple endpoints, when we create new ones.
APIUtil.getPropsValue(s"dynamicEndpoint.cache.ttl.seconds", "32").toInt
}
override def create(userId: String, swaggerString: String): Box[DynamicEndpointT] = {
tryo{DynamicEndpoint.create.UserId(userId).SwaggerString(swaggerString).saveMe()}
override def create(bankId:Option[String], userId: String, swaggerString: String): Box[DynamicEndpointT] = {
tryo{DynamicEndpoint.create
.UserId(userId)
.BankId(bankId.getOrElse(null))
.SwaggerString(swaggerString)
.saveMe()
}
}
override def update(dynamicEndpointId: String, swaggerString: String): Box[DynamicEndpointT] = {
DynamicEndpoint.find(By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId)).map(_.SwaggerString(swaggerString).saveMe())
override def update(bankId:Option[String], dynamicEndpointId: String, swaggerString: String): Box[DynamicEndpointT] = {
(if (bankId.isEmpty)
DynamicEndpoint.find(By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId))
else
DynamicEndpoint.find(
By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId),
By(DynamicEndpoint.BankId, bankId.getOrElse(""))
)
).map(_.SwaggerString(swaggerString).saveMe())
}
override def updateHost(dynamicEndpointId: String, hostString: String): Box[DynamicEndpointT] = {
DynamicEndpoint.find(By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId))
.map(dynamicEndpoint => {
override def updateHost(bankId: Option[String], dynamicEndpointId: String, hostString: String): Box[DynamicEndpointT] = {
(if (bankId.isEmpty)
DynamicEndpoint.find(By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId))
else
DynamicEndpoint.find(
By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId),
By(DynamicEndpoint.BankId, bankId.getOrElse(""))
)
).map(dynamicEndpoint => {
dynamicEndpoint.SwaggerString(json.compactRender(json.parse(dynamicEndpoint.swaggerString).replace("host" :: Nil, JString(hostString)))).saveMe()
}
)
}
override def get(dynamicEndpointId: String): Box[DynamicEndpointT] = DynamicEndpoint.find(By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId))
override def get(bankId: Option[String], dynamicEndpointId: String): Box[DynamicEndpointT] = {
if (bankId.isEmpty)
DynamicEndpoint.find(By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId))
else
DynamicEndpoint.find(
By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId),
By(DynamicEndpoint.BankId, bankId.getOrElse(""))
)
}
override def getAll(): List[DynamicEndpointT] = {
override def getAll(bankId: Option[String]): List[DynamicEndpointT] = {
var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString)
CacheKeyFromArguments.buildCacheKey {
Caching.memoizeSyncWithProvider (Some(cacheKey.toString())) (dynamicEndpointTTL second) {
DynamicEndpoint.findAll()
}}
if (bankId.isEmpty)
DynamicEndpoint.findAll()
else
DynamicEndpoint.findAll(By(DynamicEndpoint.BankId, bankId.getOrElse("")))
}
}
}
override def getDynamicEndpointsByUserId(userId: String): List[DynamicEndpointT] = DynamicEndpoint.findAll(By(DynamicEndpoint.UserId, userId))
override def delete(dynamicEndpointId: String): Boolean = DynamicEndpoint.bulkDelete_!!(By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId))
override def delete(bankId: Option[String], dynamicEndpointId: String): Boolean = {
if (bankId.isEmpty)
DynamicEndpoint.bulkDelete_!!(By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId))
else
DynamicEndpoint.bulkDelete_!!(
By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId),
By(DynamicEndpoint.BankId, bankId.getOrElse(""))
)
}
}
@ -59,10 +101,13 @@ class DynamicEndpoint extends DynamicEndpointT with LongKeyedMapper[DynamicEndpo
object SwaggerString extends MappedText(this)
object UserId extends MappedString(this, 255)
object BankId extends MappedString(this, 255)
override def dynamicEndpointId: Option[String] = Option(DynamicEndpointId.get)
override def swaggerString: String = SwaggerString.get
override def userId: String = UserId.get
override def bankId: Option[String] = if (BankId.get == null || BankId.get.isEmpty) None else Some(BankId.get)
}
object DynamicEndpoint extends DynamicEndpoint with LongKeyedMetaMapper[DynamicEndpoint] {

View File

@ -169,7 +169,7 @@ object ReferenceType {
.recover(recoverFn(fieldName, value, "MethodRouting"))
},
"reference:DynamicEntity" -> {(fieldName, value, callContext) =>
NewStyle.function.getDynamicEntityById(value, callContext)
NewStyle.function.getDynamicEntityById(None, value, callContext)
.map(mapFn(fieldName, value, "DynamicEntity"))
.recover(recoverFn(fieldName, value, "DynamicEntity"))
},
@ -283,7 +283,7 @@ object ReferenceType {
)
def referenceTypeNames: List[String] = {
val dynamicRefs: List[String] = NewStyle.function.getDynamicEntities()
val dynamicRefs: List[String] = NewStyle.function.getDynamicEntities(None)
.map(entity => s"reference:${entity.entityName}")
val staticRefs: List[String] = staticRefTypeToValidateFunction.keys.toList
@ -534,16 +534,16 @@ case class DynamicEntityIntTypeExample(`type`: DynamicEntityFieldType, example:
trait DynamicEntityProvider {
def getById(dynamicEntityId: String): Box[DynamicEntityT]
def getById(bankId: Option[String], dynamicEntityId: String): Box[DynamicEntityT]
//Note, we use entity name to create the roles, and bank level and system level can not be mixed,
// so --> here can not use bankId as parameters:
def getByEntityName(entityName: String): Box[DynamicEntityT]
def getDynamicEntities(): List[DynamicEntityT]
def getDynamicEntities(bankId: Option[String]): List[DynamicEntityT]
def getDynamicEntitiesByUserId(userId: String): List[DynamicEntity]
def getDynamicEntitiesByBankId(bankId: String): List[DynamicEntity]
def createOrUpdate(dynamicEntity: DynamicEntityT): Box[DynamicEntityT]
def delete(dynamicEntity: DynamicEntityT):Box[Boolean]

View File

@ -10,22 +10,27 @@ import org.apache.commons.lang3.StringUtils
object MappedDynamicEntityProvider extends DynamicEntityProvider with CustomJsonFormats with MdcLoggable {
override def getById(dynamicEntityId: String): Box[DynamicEntityT] = DynamicEntity.find(
By(DynamicEntity.DynamicEntityId, dynamicEntityId)
)
override def getByEntityName(entityName: String): Box[DynamicEntityT] = DynamicEntity.find(
By(DynamicEntity.EntityName, entityName)
)
override def getDynamicEntities(): List[DynamicEntity] = {
DynamicEntity.findAll()
override def getById(bankId: Option[String], dynamicEntityId: String): Box[DynamicEntityT] = {
if (bankId.isEmpty)
DynamicEntity.find(By(DynamicEntity.DynamicEntityId, dynamicEntityId))
else
DynamicEntity.find(
By(DynamicEntity.DynamicEntityId, dynamicEntityId),
By(DynamicEntity.BankId, bankId.getOrElse("")
))
}
override def getDynamicEntitiesByBankId(bankId: String): List[DynamicEntity] = {
DynamicEntity.findAll(By(DynamicEntity.BankId, bankId))
override def getByEntityName(entityName: String): Box[DynamicEntityT] =
DynamicEntity.find(By(DynamicEntity.EntityName, entityName))
override def getDynamicEntities(bankId: Option[String]): List[DynamicEntity] = {
if (bankId.isEmpty)
DynamicEntity.findAll()
else
DynamicEntity.findAll(By(DynamicEntity.BankId, bankId.getOrElse("")))
}
override def getDynamicEntitiesByUserId(userId: String): List[DynamicEntity] = {
DynamicEntity.findAll(By(DynamicEntity.UserId, userId))
}

View File

@ -7,7 +7,7 @@ import com.openbankproject.commons.model.{Converter, JsonFieldReName}
import net.liftweb.common.Box
import net.liftweb.json
import net.liftweb.json.Formats
import net.liftweb.json.JsonAST.{JArray, JField, JNull, JObject, JString}
import net.liftweb.json.JsonAST.{JArray, JField, JNull, JObject, JString, JValue}
import net.liftweb.util.SimpleInjector
object EndpointMappingProvider extends SimpleInjector {
@ -22,14 +22,16 @@ trait EndpointMappingT {
def operationId: String
def requestMapping: String
def responseMapping: String
def bankId: Option[String]
}
case class EndpointMappingCommons(
endpointMappingId: Option[String],
operationId: String,
requestMapping: String,
responseMapping: String
) extends EndpointMappingT with JsonFieldReName {
responseMapping: String,
bankId: Option[String]
) extends EndpointMappingT with JsonFieldReName {
/**
* when serialized to json, the Option field will be not shown, this endpoint just generate a full fields json, include all None value fields
* @return JObject include all fields
@ -39,7 +41,8 @@ case class EndpointMappingCommons(
JField("operation_id", JString(this.operationId)),
JField("request_mapping", json.parse(this.requestMapping)),
JField("response_mapping", json.parse(this.responseMapping)),
JField("endpoint_mapping_id", this.endpointMappingId.map(JString(_)).getOrElse(JNull))
JField("endpoint_mapping_id", this.endpointMappingId.map(JString(_)).getOrElse(JNull)),
JField("bank_id", this.bankId.map(JString(_)).getOrElse(JNull))
))
}
}
@ -47,15 +50,15 @@ case class EndpointMappingCommons(
object EndpointMappingCommons extends Converter[EndpointMappingT, EndpointMappingCommons]
trait EndpointMappingProvider {
def getById(endpointMappingId: String): Box[EndpointMappingT]
def getById(bankId: Option[String], endpointMappingId: String): Box[EndpointMappingT]
def getByOperationId(operationId: String): Box[EndpointMappingT]
def getByOperationId(bankId: Option[String], operationId: String): Box[EndpointMappingT]
def getAllEndpointMappings: List[EndpointMappingT]
def getAllEndpointMappings(bankId: Option[String]): List[EndpointMappingT]
def createOrUpdate(endpointMapping: EndpointMappingT): Box[EndpointMappingT]
def createOrUpdate(bankId: Option[String], endpointMapping: EndpointMappingT): Box[EndpointMappingT]
def delete(endpointMappingId: String):Box[Boolean]
def delete(bankId: Option[String], endpointMappingId: String):Box[Boolean]
}

View File

@ -13,11 +13,20 @@ import net.liftweb.json.JsonAST.JArray
object MappedEndpointMappingProvider extends EndpointMappingProvider with CustomJsonFormats{
override def getById(endpointMappingId: String): Box[EndpointMappingT] = getByEndpointMappingId(endpointMappingId)
override def getByOperationId(operationId: String): Box[EndpointMappingT] = EndpointMapping.find(By(EndpointMapping.OperationId, operationId))
override def getById(bankId: Option[String], endpointMappingId: String): Box[EndpointMappingT] = {
if (bankId.isEmpty) getByEndpointMappingId(endpointMappingId)
else getByEndpointMappingId(bankId.getOrElse(""), endpointMappingId)
}
override def createOrUpdate(endpointMapping: EndpointMappingT): Box[EndpointMappingT] = {
override def getByOperationId(bankId: Option[String], operationId: String): Box[EndpointMappingT] = {
if (bankId.isEmpty) EndpointMapping.find(By(EndpointMapping.OperationId, operationId))
else EndpointMapping.find(
By(EndpointMapping.OperationId, operationId),
By(EndpointMapping.BankId, bankId.getOrElse(""))
)
}
override def createOrUpdate(bankId: Option[String], endpointMapping: EndpointMappingT): Box[EndpointMappingT] = {
//to find exists endpointMapping, if endpointMappingId supplied, query by endpointMappingId, or use endpointName and endpointMappingId to do query
val existsEndpointMapping: Box[EndpointMapping] = endpointMapping.endpointMappingId match {
case Some(id) if (StringUtils.isNotBlank(id)) => getByEndpointMappingId(id)
@ -33,16 +42,24 @@ object MappedEndpointMappingProvider extends EndpointMappingProvider with Custom
.OperationId(endpointMapping.operationId)
.RequestMapping(endpointMapping.requestMapping)
.ResponseMapping(endpointMapping.responseMapping)
.BankId(endpointMapping.bankId.getOrElse(null))
.saveMe()
}
}
override def delete(endpointMappingId: String): Box[Boolean] = getByEndpointMappingId(endpointMappingId).map(_.delete_!)
override def delete(bankId: Option[String], endpointMappingId: String): Box[Boolean] =
if (bankId.isEmpty) getByEndpointMappingId(endpointMappingId).map(_.delete_!)
else getByEndpointMappingId(bankId.getOrElse(""),endpointMappingId).map(_.delete_!)
private[this] def getByEndpointMappingId(endpointMappingId: String): Box[EndpointMapping] = EndpointMapping.find(By(EndpointMapping.EndpointMappingId, endpointMappingId))
private[this] def getByEndpointMappingId(bankId: String, endpointMappingId: String): Box[EndpointMapping] = EndpointMapping.find(
By(EndpointMapping.EndpointMappingId, endpointMappingId),
By(EndpointMapping.BankId, bankId),
)
override def getAllEndpointMappings(): List[EndpointMappingT] = EndpointMapping.findAll()
override def getAllEndpointMappings(bankId: Option[String]): List[EndpointMappingT] =
if (bankId.isEmpty) EndpointMapping.findAll()
else EndpointMapping.findAll(By(EndpointMapping.BankId, bankId.getOrElse("")))
}
class EndpointMapping extends EndpointMappingT with LongKeyedMapper[EndpointMapping] with IdPK with CustomJsonFormats{
@ -53,11 +70,13 @@ class EndpointMapping extends EndpointMappingT with LongKeyedMapper[EndpointMapp
object OperationId extends MappedString(this, 255)
object RequestMapping extends MappedText(this)
object ResponseMapping extends MappedText(this)
object BankId extends MappedString(this, 255)
override def endpointMappingId: Option[String] = Option(EndpointMappingId.get)
override def operationId: String = OperationId.get
override def requestMapping: String = RequestMapping.get
override def responseMapping: String = ResponseMapping.get
override def bankId: Option[String] = if (BankId.get == null || BankId.get.isEmpty) None else Some(BankId.get)
}
object EndpointMapping extends EndpointMapping with LongKeyedMetaMapper[EndpointMapping] {

View File

@ -24,13 +24,13 @@ trait EntitlementProvider {
def getEntitlementById(entitlementId: String) : Box[Entitlement]
def getEntitlementsByUserId(userId: String) : Box[List[Entitlement]]
def getEntitlementsByUserIdFuture(userId: String) : Future[Box[List[Entitlement]]]
def getEntitlementsByBankId(userId: String) : Future[Box[List[Entitlement]]]
def getEntitlementsByBankId(bankId: String) : Future[Box[List[Entitlement]]]
def deleteEntitlement(entitlement: Box[Entitlement]) : Box[Boolean]
def getEntitlements() : Box[List[Entitlement]]
def getEntitlementsByRole(roleName: String): Box[List[Entitlement]]
def getEntitlementsFuture() : Future[Box[List[Entitlement]]]
def getEntitlementsByRoleFuture(roleName: String) : Future[Box[List[Entitlement]]]
def addEntitlement(bankId: String, userId: String, roleName: String) : Box[Entitlement]
def addEntitlement(bankId: String, userId: String, roleName: String, createdByProcess: String="manual") : Box[Entitlement]
def deleteDynamicEntityEntitlement(entityName: String, bankId:Option[String]) : Box[Boolean]
def deleteEntitlements(entityNames: List[String]) : Box[Boolean]
}
@ -40,6 +40,7 @@ trait Entitlement {
def bankId : String
def userId : String
def roleName : String
def createdByProcess : String
}
class RemotedataEntitlementsCaseClasses {
@ -53,7 +54,7 @@ class RemotedataEntitlementsCaseClasses {
case class getEntitlementsByRole(roleName: String)
case class getEntitlementsFuture()
case class getEntitlementsByRoleFuture(roleName: String)
case class addEntitlement(bankId: String, userId: String, roleName: String)
case class addEntitlement(bankId: String, userId: String, roleName: String, createdByProcess: String="manual")
case class deleteDynamicEntityEntitlement(entityName: String, bankId:Option[String])
case class deleteEntitlements(entityNames: List[String])
}

View File

@ -102,12 +102,13 @@ object MappedEntitlementsProvider extends EntitlementProvider {
}
}
override def addEntitlement(bankId: String, userId: String, roleName: String): Box[Entitlement] = {
override def addEntitlement(bankId: String, userId: String, roleName: String, createdByProcess: String ="manual"): Box[Entitlement] = {
// Return a Box so we can handle errors later.
val addEntitlement = MappedEntitlement.create
.mBankId(bankId)
.mUserId(userId)
.mRoleName(roleName)
.mCreatedByProcess(createdByProcess)
.saveMe()
Some(addEntitlement)
}
@ -122,11 +123,14 @@ class MappedEntitlement extends Entitlement
object mBankId extends UUIDString(this)
object mUserId extends UUIDString(this)
object mRoleName extends MappedString(this, 64)
object mCreatedByProcess extends MappedString(this, 255)
override def entitlementId: String = mEntitlementId.get.toString
override def bankId: String = mBankId.get
override def userId: String = mUserId.get
override def roleName: String = mRoleName.get
override def createdByProcess: String =
if(mCreatedByProcess.get == null || mCreatedByProcess.get.isEmpty) "manual" else mCreatedByProcess.get
}

View File

@ -197,6 +197,8 @@ case class BankAccountExtended(val bankAccount: BankAccount) extends MdcLoggable
val emailAddress = ""
val name : String = bankAccount.accountHolder
val createdByConsentId = None
val createdByUserInvitationId = None
val isDeleted = None
})
} else {
accountHolders

View File

@ -30,7 +30,7 @@ import java.util.{Collections, Date}
import code.api.util.APIUtil
import code.api.util.migration.Migration.DbFunction
import code.consumer.{Consumers, ConsumersProvider}
import code.model.AppType.{Mobile, Web}
import code.model.AppType.{Public, Confidential}
import code.model.dataAccess.ResourceUser
import code.nonce.NoncesProvider
import code.token.TokensProvider
@ -53,11 +53,15 @@ import scala.concurrent.Future
sealed trait AppType
object AppType {
case object Web extends AppType
case object Mobile extends AppType
case object Confidential extends AppType
case object Public extends AppType
case object Unknown extends AppType
def valueOf(value: String): AppType = value match {
case "Web" => Web
case "Mobile" => Mobile
case "Web" => Confidential
case "Confidential" => Confidential
case "Mobile" => Public
case "Public" => Public
case "Unknown" => Unknown
}
}
@ -128,7 +132,9 @@ object MappedConsumersProvider extends ConsumersProvider with MdcLoggable {
developerEmail: Option[String],
redirectURL: Option[String],
createdByUserId: Option[String],
clientCertificate: Option[String] = None): Box[Consumer] = {
clientCertificate: Option[String] = None,
company: Option[String] = None
): Box[Consumer] = {
tryo {
val c = Consumer.create
key match {
@ -154,8 +160,8 @@ object MappedConsumersProvider extends ConsumersProvider with MdcLoggable {
}
appType match {
case Some(v) => v match {
case Web => c.appType(Web.toString)
case Mobile => c.appType(Mobile.toString)
case Confidential => c.appType(Confidential.toString)
case Public => c.appType(Public.toString)
}
case None =>
}
@ -175,6 +181,10 @@ object MappedConsumersProvider extends ConsumersProvider with MdcLoggable {
case Some(v) => c.createdByUserId(v)
case None =>
}
company match {
case Some(v) => c.company(v)
case None =>
}
clientCertificate.filter(StringUtils.isNotBlank).foreach(c.clientCertificate(_))
@ -223,8 +233,8 @@ object MappedConsumersProvider extends ConsumersProvider with MdcLoggable {
}
appType match {
case Some(v) => v match {
case Web => c.appType(Web.toString)
case Mobile => c.appType(Mobile.toString)
case Confidential => c.appType(Confidential.toString)
case Public => c.appType(Public.toString)
}
case None =>
}
@ -395,8 +405,8 @@ object MappedConsumersProvider extends ConsumersProvider with MdcLoggable {
}
appType match {
case Some(v) => v match {
case Web => c.appType(Web.toString)
case Mobile => c.appType(Mobile.toString)
case Confidential => c.appType(Confidential.toString)
case Public => c.appType(Public.toString)
}
case None =>
}
@ -568,6 +578,9 @@ class Consumer extends LongKeyedMapper[Consumer] with CreatedUpdated{
override def defaultValue = -1
}
object clientCertificate extends MappedString(this, 4000)
object company extends MappedString(this, 100) {
override def displayName = "Company:"
}
}
/**

View File

@ -130,15 +130,8 @@ object UserX {
}
}
def findAll() = {
Users.users.vend.getAllUsers() match {
case Full(list) => list
case _ => List()
}
}
def createResourceUser(provider: String, providerId: Option[String], createdByConsentId: Option[String], name: Option[String], email: Option[String], userId: Option[String]) = {
Users.users.vend.createResourceUser(provider, providerId, createdByConsentId, name, email, userId)
def createResourceUser(provider: String, providerId: Option[String], createdByConsentId: Option[String], name: Option[String], email: Option[String], userId: Option[String], company: Option[String]) = {
Users.users.vend.createResourceUser(provider, providerId, createdByConsentId, name, email, userId, None, company)
}
def createUnsavedResourceUser(provider: String, providerId: Option[String], name: Option[String], email: Option[String], userId: Option[String]) = {

View File

@ -161,7 +161,7 @@ case class ViewExtended(val view: View) {
else None
val transactionBalance =
if (view.canSeeTransactionBalance) transaction.balance.toString()
if (view.canSeeTransactionBalance && transaction.balance != null) transaction.balance.toString()
else ""
new ModeratedTransaction(
@ -228,7 +228,7 @@ case class ViewExtended(val view: View) {
else None
val transactionBalance =
if (view.canSeeTransactionBalance) transactionCore.balance.toString()
if (view.canSeeTransactionBalance && transactionCore.balance != null) transactionCore.balance.toString()
else ""
new ModeratedTransactionCore(
@ -312,7 +312,7 @@ case class ViewExtended(val view: View) {
if(view.canSeeTransactionThisBankAccount)
{
val owners : Set[User] = if(view.canSeeBankAccountOwners) bankAccount.userOwners else Set()
val balance = if(view.canSeeBankAccountBalance) bankAccount.balance.toString else ""
val balance = if(view.canSeeBankAccountBalance && bankAccount.balance != null) bankAccount.balance.toString else ""
val accountType = if(view.canSeeBankAccountType) Some(bankAccount.accountType) else None
val currency = if(view.canSeeBankAccountCurrency) Some(bankAccount.currency) else None
val label = if(view.canSeeBankAccountLabel) Some(bankAccount.label) else None
@ -362,7 +362,7 @@ case class ViewExtended(val view: View) {
if(view.canSeeTransactionThisBankAccount)
{
val owners : Set[User] = if(view.canSeeBankAccountOwners) bankAccount.userOwners else Set()
val balance = if(view.canSeeBankAccountBalance) bankAccount.balance.toString else ""
val balance = if(view.canSeeBankAccountBalance && bankAccount.balance !=null) bankAccount.balance.toString else ""
val accountType = if(view.canSeeBankAccountType) Some(bankAccount.accountType) else None
val currency = if(view.canSeeBankAccountCurrency) Some(bankAccount.currency) else None
val label = if(view.canSeeBankAccountLabel) Some(bankAccount.label) else None
@ -409,7 +409,7 @@ case class ViewExtended(val view: View) {
if(view.canSeeTransactionThisBankAccount)
{
val owners : Set[User] = if(view.canSeeBankAccountOwners) bankAccount.userOwners else Set()
val balance = if(view.canSeeBankAccountBalance) Some(bankAccount.balance.toString) else None
val balance = if(view.canSeeBankAccountBalance && bankAccount.balance != null) Some(bankAccount.balance.toString) else None
val accountType = if(view.canSeeBankAccountType) Some(bankAccount.accountType) else None
val currency = if(view.canSeeBankAccountCurrency) Some(bankAccount.currency) else None
val label = if(view.canSeeBankAccountLabel) Some(bankAccount.label) else None

View File

@ -31,9 +31,11 @@ import code.accountholders.AccountHolders
import code.api.util.APIUtil.{hasAnOAuthHeader, isValidStrongPassword, logger, _}
import code.api.util.ErrorMessages._
import code.api.util._
import code.api.v4_0_0.dynamic.DynamicEndpointHelper
import code.api.{APIFailure, DirectLogin, GatewayLogin, OAuthHandshake}
import code.bankconnectors.Connector
import code.context.UserAuthContextProvider
import code.entitlement.Entitlement
import code.loginattempts.LoginAttempt
import code.users.Users
import code.util.Helper
@ -52,9 +54,12 @@ import com.openbankproject.commons.ExecutionContext.Implicits.global
import code.webuiprops.MappedWebUiPropsProvider.getWebUiPropsValue
import org.apache.commons.lang3.StringUtils
import code.util.HydraUtil._
import com.github.dwickern.macros.NameOf.nameOf
import sh.ory.hydra.model.AcceptLoginRequest
import net.liftweb.http.S.fmapFunc
import scala.concurrent.Future
/**
* An O-R mapped "User" class that includes first name, last name, password
*
@ -332,13 +337,6 @@ class AuthUser extends MegaProtoUser[AuthUser] with MdcLoggable {
}
}
def getResourceUsers(): List[ResourceUser] = {
Users.users.vend.getAllUsers match {
case Full(userList) => userList
case _ => List()
}
}
def getResourceUserByUsername(username: String) : Box[User] = {
Users.users.vend.getUserByUserName(username)
}
@ -421,8 +419,8 @@ import net.liftweb.util.Helpers._
override def fieldOrder = List(id, firstName, lastName, email, username, password, provider)
override def signupFields = List(firstName, lastName, email, username, password)
// If we want to validate email addresses set this to false
override def skipEmailValidation = APIUtil.getPropsAsBoolValue("authUser.skipEmailValidation", true)
// To force validation of email addresses set this to false (default as of 29 June 2021)
override def skipEmailValidation = APIUtil.getPropsAsBoolValue("authUser.skipEmailValidation", false)
override def loginXhtml = {
val loginXml = Templates(List("templates-hidden","_login")).map({
@ -455,12 +453,15 @@ import net.liftweb.util.Helpers._
*
*/
def getCurrentUser: Box[User] = {
val authorization = S.request.map(_.header("Authorization")).flatten
val authorization: Box[String] = S.request.map(_.header("Authorization")).flatten
val directLogin: Box[String] = S.request.map(_.header("DirectLogin")).flatten
for {
resourceUser <- if (AuthUser.currentUser.isDefined)
//AuthUser.currentUser.get.user.foreign // this will be issue when the resource user is in remote side
Users.users.vend.getUserByUserName(AuthUser.currentUser.openOrThrowException(ErrorMessages.attemptedToOpenAnEmptyBox).username.get)
else if (hasDirectLoginHeader(authorization))
else if (directLogin.isDefined) // Direct Login
DirectLogin.getUser
else if (hasDirectLoginHeader(authorization)) // Direct Login Deprecated
DirectLogin.getUser
else if (hasAnOAuthHeader(authorization)) {
OAuthHandshake.getUser
@ -923,6 +924,16 @@ def restoreSomeSessions(): Unit = {
logUserIn(user, () => {
S.notice(S.?("logged.in"))
preLoginState()
if(emailDomainToSpaceMappings.nonEmpty){
Future{
tryo{AuthUser.grantEntitlementsToUseDynamicEndpointsInSpaces(user)}
.openOr(logger.error(s"${user} checkInternalRedirectAndLogUseIn.grantEntitlementsToUseDynamicEndpointsInSpaces throw exception! "))
}}
if(emailDomainToEntitlementMappings.nonEmpty){
Future{
tryo{AuthUser.grantEmailDomainEntitlementsToUser(user)}
.openOr(logger.error(s"${user} checkInternalRedirectAndLogUseIn.grantEmailDomainEntitlementsToUser throw exception! "))
}}
S.redirectTo(redirect)
})
} else {
@ -1101,6 +1112,117 @@ def restoreSomeSessions(): Unit = {
} yield v
}
}
/**
* A Space is an alias for the OBP Bank. Each Bank / Space can contain many Dynamic Endpoints. If a User belongs to a Space,
* the User can use those endpoints but not modify them. If a User creates a Bank (aka Space) the user can create
* and modify Dynamic Endpoints and other objects in that Bank / Space.
*
* @return
*/
def mySpaces(user: AuthUser): List[BankId] = {
//1st: first check the user is validated
if (user.validated_?) {
//userEmail = robert.uk.29@example.com
// 2st get the email domain - `example.com`
val emailDomain = StringUtils.substringAfterLast(user.email.get, "@")
//3 return the bankIds
emailDomainToSpaceMappings.collectFirst {
case EmailDomainToSpaceMapping(`emailDomain`, ids) => ids.map(BankId(_));
} getOrElse Nil
} else {
Nil
}
}
def grantEntitlementsToUseDynamicEndpointsInSpaces(user: AuthUser) = {
val createdByProcess = "grantEntitlementsToUseDynamicEndpointsInSpaces"
val userId = user.user.obj.map(_.userId).getOrElse("")
// user's already auto granted entitlements.
val entitlementsGrantedByThisProcess = Entitlement.entitlement.vend.getEntitlementsByUserId(userId)
.map(_.filter(role => role.createdByProcess == createdByProcess))
.getOrElse(Nil)
def alreadyHasEntitlement(role:ApiRole, bankId: String): Boolean =
entitlementsGrantedByThisProcess.exists(entitlement => entitlement.roleName == role.toString() && entitlement.bankId == bankId)
//call mySpaces --> get BankIds --> listOfRolesToUseAllDynamicEndpointsAOneBank (at each bank)--> Grant roles (for each role)
val allCurrentDynamicRoleToBankIdPairs: List[(ApiRole, String)] = for {
BankId(bankId) <- mySpaces(user: AuthUser)
role <- DynamicEndpointHelper.listOfRolesToUseAllDynamicEndpointsAOneBank(Some(bankId))
} yield {
if (!alreadyHasEntitlement(role, bankId)) {
Entitlement.entitlement.vend.addEntitlement(bankId, userId, role.toString, createdByProcess)
}
role -> bankId
}
// if user's auto granted entitlement invalid, delete it.
// invalid happens when some dynamic endpoints are removed, so the entitlements linked to the deleted dynamic endpoints are invalid.
for {
grantedEntitlement <- entitlementsGrantedByThisProcess
grantedEntitlementRoleName = grantedEntitlement.roleName
grantedEntitlementBankId = grantedEntitlement.bankId
} {
val isInValidEntitlement = !allCurrentDynamicRoleToBankIdPairs.exists { roleToBankIdPair =>
val(role, roleBankId) = roleToBankIdPair
role.toString() == grantedEntitlementRoleName && roleBankId == grantedEntitlementBankId
}
if(isInValidEntitlement) {
Entitlement.entitlement.vend.deleteEntitlement(Full(grantedEntitlement))
}
}
}
def grantEmailDomainEntitlementsToUser(user: AuthUser) = {
if(emailDomainToEntitlementMappings.nonEmpty){
val createdByProcess = "grantEmailDomainEntitlementsToUser"
val userId = user.user.obj.map(_.userId).getOrElse("")
// user's already auto granted entitlements.
val entitlementsGrantedByThisProcess = Entitlement.entitlement.vend.getEntitlementsByUserId(userId)
.map(_.filter(role => role.createdByProcess == createdByProcess))
.getOrElse(Nil)
def alreadyHasEntitlement(bankId: String, roleName:String): Boolean =
entitlementsGrantedByThisProcess.exists(entitlement => entitlement.roleName == roleName && entitlement.bankId == bankId)
val allEntitlementsFromCurrentProps: List[(String, String)] = for{
emailDomainToEntitlementMapping <- emailDomainToEntitlementMappings
domain = emailDomainToEntitlementMapping.domain
entitlement <- emailDomainToEntitlementMapping.entitlements if StringUtils.substringAfterLast(user.email.get, "@") == domain
roleName = entitlement.role_name
roleBankId = entitlement.bank_id
} yield {
if (!alreadyHasEntitlement(roleBankId, roleName)) {
Entitlement.entitlement.vend.addEntitlement(roleBankId, userId, roleName, createdByProcess)
}
roleName -> roleBankId
}
// if user's auto granted entitlement invalid, delete it.
// invalid happens when some dynamic endpoints are removed, so the entitlements linked to the deleted dynamic endpoints are invalid.
for {
grantedEntitlement <- entitlementsGrantedByThisProcess
grantedEntitlementRoleName = grantedEntitlement.roleName
grantedEntitlementBankId = grantedEntitlement.bankId
} {
val isInValidEntitlement = !allEntitlementsFromCurrentProps.exists { roleNameToBankIdPair =>
val(roleName, roleBankId) = roleNameToBankIdPair
roleName == grantedEntitlementRoleName && roleBankId == grantedEntitlementBankId
}
if(isInValidEntitlement) {
Entitlement.entitlement.vend.deleteEntitlement(Full(grantedEntitlement))
}
}
}
}
/**
* This is a helper method
@ -1149,7 +1271,7 @@ def restoreSomeSessions(): Unit = {
* Find the authUser by author user name(authUser and resourceUser are the same).
* Only search for the local database.
*/
protected def findUserByUsernameLocally(name: String): Box[TheUserType] = {
def findUserByUsernameLocally(name: String): Box[TheUserType] = {
find(By(this.username, name))
}
@ -1169,6 +1291,24 @@ def restoreSomeSessions(): Unit = {
case _ => ""
}
}
override def passwordResetXhtml = {
<div id="recover-password" tabindex="-1">
<h1>{if(S.queryString.isDefined) Helper.i18n("set.your.password") else S.?("reset.your.password")}</h1>
<form action={S.uri} method="post">
<div class="form-group">
<label for="password">{S.?("enter.your.new.password")}</label> <span><input id="password" class="form-control" type="password" /></span>
</div>
<div class="form-group">
<label for="repeatpassword">{S.?("repeat.your.new.password")}</label> <span><input id="repeatpassword" class="form-control" type="password" /></span>
</div>
<div class="form-group">
<input type="submit" class="btn btn-danger" />
</div>
</form>
</div>
}
/**
* Find the authUsers by author email(authUser and resourceUser are the same).
* Only search for the local database.
@ -1235,4 +1375,21 @@ def restoreSomeSessions(): Unit = {
innerSignup
}
def scrambleAuthUser(userPrimaryKey: UserPrimaryKey): Box[Boolean] = tryo {
AuthUser.find(By(AuthUser.user, userPrimaryKey.value)) match {
case Full(user) =>
val scrambledUser = user.firstName(Helpers.randomString(16))
.email(Helpers.randomString(10) + "@example.com")
.username("DELETED-" + Helpers.randomString(16))
.firstName(Helpers.randomString(16))
.lastName(Helpers.randomString(16))
.password(Helpers.randomString(40))
.validated(false)
scrambledUser.save()
case Empty => true // There is a resource user but no the correlated Auth user
case _ => false // Error case
}
}
}

View File

@ -75,8 +75,12 @@ class ResourceUser extends LongKeyedMapper[ResourceUser] with User with ManyToMa
object providerId extends MappedString(this, 100){
override def defaultValue = java.util.UUID.randomUUID.toString
}
object Company extends MappedString(this, 50)
object CreatedByConsentId extends MappedString(this, 100)
object CreatedByUserInvitationId extends MappedString(this, 100)
object IsDeleted extends MappedBoolean(this) {
override def defaultValue = false
}
def emailAddress = {
val e = email.get
@ -90,6 +94,7 @@ class ResourceUser extends LongKeyedMapper[ResourceUser] with User with ManyToMa
def name : String = name_.get
def provider = provider_.get
def company: String = Company.get
def toCaseClass: ResourceUserCaseClass =
ResourceUserCaseClass(
@ -102,6 +107,8 @@ class ResourceUser extends LongKeyedMapper[ResourceUser] with User with ManyToMa
)
override def createdByConsentId = if(CreatedByConsentId.get == null) None else if (CreatedByConsentId.get.isEmpty) None else Some(CreatedByConsentId.get) //null --> None
override def createdByUserInvitationId = if(CreatedByUserInvitationId.get == null) None else if (CreatedByUserInvitationId.get.isEmpty) None else Some(CreatedByUserInvitationId.get) //null --> None
override def isDeleted: Option[Boolean] = if(IsDeleted.get == null) None else Some(IsDeleted.get) // null --> false
}
object ResourceUser extends ResourceUser with LongKeyedMetaMapper[ResourceUser]{

View File

@ -62,7 +62,11 @@ object RemotedataActors extends MdcLoggable {
ActorProps[RemotedataCustomerAttributeActor] -> RemotedataCustomerAttribute.actorName,
ActorProps[RemotedataTransactionAttributeActor] -> RemotedataTransactionAttribute.actorName,
ActorProps[RemotedataRateLimitingActor] -> RemotedataRateLimiting.actorName,
ActorProps[RemotedataAttributeDefinitionActor] -> RemotedataAttributeDefinition.actorName
ActorProps[RemotedataAttributeDefinitionActor] -> RemotedataAttributeDefinition.actorName,
ActorProps[RemotedataUserInvitationActor] -> RemotedataUserInvitation.actorName,
ActorProps[RemotedataConsentAuthContextActor] -> RemotedataConsentAuthContext.actorName,
ActorProps[RemotedataTransactionRequestAttributeActor] -> RemotedataTransactionRequestAttribute.actorName,
ActorProps[RemotedataUserAgreementActor] -> RemotedataUserAgreement.actorName
)
actorsRemotedata.foreach { a => logger.info(actorSystem.actorOf(a._1, name = a._2)) }

View File

@ -31,8 +31,12 @@ class RemotedataChallengesActor extends Actor with ObpActorHelper with MdcLoggab
logger.debug(s"validateChallenge($challengeId, $challengeAnswer, $userId)")
sender ! (mapper.validateChallenge(challengeId, challengeAnswer, userId))
case cc.getChallengesByTransactionRequestId(transactionRequestId: String)=>
logger.debug(s"getChallengesByTransactionRequestId($transactionRequestId)")
sender ! (mapper.getChallengesByTransactionRequestId(transactionRequestId))
case cc.getChallengesByConsentId(consentId: String)=>
logger.debug(s"validateChallenge($consentId)")
logger.debug(s"getChallengesByConsentId($consentId)")
sender ! (mapper.getChallengesByConsentId(consentId))
case message => logger.warn("[AKKA ACTOR ERROR - REQUEST NOT RECOGNIZED] " + message)

View File

@ -47,8 +47,8 @@ object RemotedataConsumers extends ObpActorInit with ConsumersProvider {
def getConsumersFuture(): Future[List[Consumer]] =
(actor ? cc.getConsumersFuture()).mapTo[List[Consumer]]
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], clientCertificate: Option[String] = None): Box[Consumer] = getValueFromFuture(
(actor ? cc.createConsumer(key, secret, isActive, name, appType, description, developerEmail, redirectURL, createdByUserId, clientCertificate)).mapTo[Box[Consumer]]
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], clientCertificate: Option[String] = None, company: Option[String] = None): Box[Consumer] = getValueFromFuture(
(actor ? cc.createConsumer(key, secret, isActive, name, appType, description, developerEmail, redirectURL, createdByUserId, clientCertificate, company)).mapTo[Box[Consumer]]
)
def deleteConsumer(consumer: Consumer): Boolean = getValueFromFuture(

View File

@ -45,9 +45,9 @@ class RemotedataConsumersActor extends Actor with ObpActorHelper with MdcLoggabl
logger.debug(s"getConsumersFuture()")
sender ! (mapper.getConsumers())
case cc.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], clientCertificate: Option[String]) =>
logger.debug(s"createConsumer(*****, *****, ${isActive.getOrElse("None")}, ${name.getOrElse("None")}, ${appType.getOrElse("None")}, ${description.getOrElse("None")}, ${developerEmail.getOrElse("None")}, ${redirectURL.getOrElse("None")}, ${createdByUserId.getOrElse("None")}, ${clientCertificate.getOrElse("None")})")
sender ! (mapper.createConsumer(key, secret, isActive, name, appType, description, developerEmail, redirectURL, createdByUserId, clientCertificate))
case cc.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], clientCertificate: Option[String], company: Option[String]) =>
logger.debug(s"createConsumer(*****, *****, ${isActive.getOrElse("None")}, ${name.getOrElse("None")}, ${appType.getOrElse("None")}, ${description.getOrElse("None")}, ${developerEmail.getOrElse("None")}, ${redirectURL.getOrElse("None")}, ${createdByUserId.getOrElse("None")}, ${clientCertificate.getOrElse("None")}, ${company.getOrElse("None")})")
sender ! (mapper.createConsumer(key, secret, isActive, name, appType, description, developerEmail, redirectURL, createdByUserId, clientCertificate, company))
case cc.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]) =>
logger.debug(s"updateConsumer($id, *****, *****, ${isActive.getOrElse("None")}, ${name.getOrElse("None")}, ${appType.getOrElse("None")}, ${description.getOrElse("None")}, ${developerEmail.getOrElse("None")}, ${redirectURL.getOrElse("None")}, ${createdByUserId.getOrElse("None")})")

View File

@ -88,6 +88,10 @@ class RemotedataCounterpartiesActor extends Actor with ObpActorHelper with MdcLo
case cc.getCounterpartyByIban(iban: String) =>
logger.debug(s"getOrCreateMetadata($iban)")
sender ! (mapper.getCounterpartyByIban(iban: String))
case cc.getCounterpartyByIbanAndBankAccountId(iban: String, bankId: BankId, accountId: AccountId) =>
logger.debug(s"getCounterpartyByIbanAndBankAccountId($iban, $bankId, $accountId)")
sender ! (mapper.getCounterpartyByIbanAndBankAccountId(iban, bankId, accountId))
case cc.getPublicAlias(counterpartyId: String) =>
logger.debug(s"getPublicAlias($counterpartyId)")

View File

@ -48,8 +48,8 @@ object RemotedataEntitlements extends ObpActorInit with EntitlementProvider {
def getEntitlementsByRoleFuture(roleName: String) : Future[Box[List[Entitlement]]] =
(actor ? cc.getEntitlementsByRoleFuture(roleName)).mapTo[Box[List[Entitlement]]]
def addEntitlement(bankId: String, userId: String, roleName: String) : Box[Entitlement] = getValueFromFuture(
(actor ? cc.addEntitlement(bankId, userId, roleName)).mapTo[Box[Entitlement]]
def addEntitlement(bankId: String, userId: String, roleName: String, createdByProcess: String="manual") : Box[Entitlement] = getValueFromFuture(
(actor ? cc.addEntitlement(bankId, userId, roleName, createdByProcess: String)).mapTo[Box[Entitlement]]
)
override def deleteDynamicEntityEntitlement(entityName: String, bankId:Option[String]): Box[Boolean] = getValueFromFuture(

View File

@ -55,9 +55,9 @@ class RemotedataEntitlementsActor extends Actor with ObpActorHelper with MdcLogg
logger.debug(s"getEntitlementsByRole($role)")
sender ! (mapper.getEntitlementsByRole(role))
case cc.addEntitlement(bankId: String, userId: String, roleName: String) =>
logger.debug(s"addEntitlement($bankId, $userId, $roleName)")
sender ! (mapper.addEntitlement(bankId, userId, roleName))
case cc.addEntitlement(bankId: String, userId: String, roleName: String, createdByProcess: String) =>
logger.debug(s"addEntitlement($bankId, $userId, $roleName, $createdByProcess)")
sender ! (mapper.addEntitlement(bankId, userId, roleName, createdByProcess: String))
case cc.deleteDynamicEntityEntitlement(entityName: String, bankId:Option[String]) =>
logger.debug(s"deleteDynamicEntityEntitlement($entityName) bankId($bankId)")

View File

@ -0,0 +1,63 @@
package code.remotedata
import akka.actor.Actor
import code.actorsystem.ObpActorHelper
import code.transactionRequestAttribute.{MappedTransactionRequestAttributeProvider, RemotedataTransactionRequestAttributeCaseClasses}
import code.util.Helper.MdcLoggable
import com.openbankproject.commons.model._
import com.openbankproject.commons.model.enums.TransactionRequestAttributeType
import akka.pattern.pipe
import scala.collection.immutable.List
import com.openbankproject.commons.ExecutionContext.Implicits.global
class RemotedataTransactionRequestAttributeActor extends Actor with ObpActorHelper with MdcLoggable {
val mapper = MappedTransactionRequestAttributeProvider
val cc = RemotedataTransactionRequestAttributeCaseClasses
def receive: PartialFunction[Any, Unit] = {
case cc.getTransactionRequestAttributesFromProvider(transactionRequestId: TransactionRequestId) =>
logger.debug(s"getTransactionRequestAttributesFromProvider($transactionRequestId)")
mapper.getTransactionRequestAttributesFromProvider(transactionRequestId) pipeTo sender
case cc.getTransactionRequestAttributes(bankId : BankId, transactionRequestId: TransactionRequestId) =>
logger.debug(s"getTransactionRequestAttributes($bankId, $transactionRequestId)")
mapper.getTransactionRequestAttributes(bankId, transactionRequestId) pipeTo sender
case cc.getTransactionRequestAttributesCanBeSeenOnView(bankId : BankId, transactionRequestId: TransactionRequestId, viewId: ViewId) =>
logger.debug(s"getTransactionRequestAttributesCanBeSeenOnView($bankId, $transactionRequestId, $viewId)")
mapper.getTransactionRequestAttributesCanBeSeenOnView(bankId, transactionRequestId, viewId) pipeTo sender
case cc.getTransactionRequestAttributeById(transactionRequestAttributeId: String) =>
logger.debug(s"getTransactionRequestAttributeById($transactionRequestAttributeId)")
mapper.getTransactionRequestAttributeById(transactionRequestAttributeId) pipeTo sender
case cc.getTransactionRequestIdsByAttributeNameValues(bankId: BankId, params: Map[String, List[String]]) =>
logger.debug(s"getTransactionRequestIdsByAttributeNameValues($bankId, $params)")
mapper.getTransactionRequestIdsByAttributeNameValues(bankId, params) pipeTo sender
case cc.createOrUpdateTransactionRequestAttribute(bankId: BankId,
transactionRequestId: TransactionRequestId,
transactionRequestAttributeId: Option[String],
name: String,
attributeType: TransactionRequestAttributeType.Value,
value: String) =>
logger.debug(s"createOrUpdateTransactionRequestAttribute($bankId, $transactionRequestId, $transactionRequestAttributeId, $name, $attributeType, $value)")
mapper.createOrUpdateTransactionRequestAttribute(bankId, transactionRequestId, transactionRequestAttributeId, name, attributeType, value) pipeTo sender
case cc.createTransactionRequestAttributes(bankId : BankId, transactionRequestId: TransactionRequestId, transactionRequestAttributes: List[TransactionRequestAttributeTrait]) =>
logger.debug(s"createTransactionRequestAttributes($bankId, $transactionRequestId, $transactionRequestAttributes)")
mapper.createTransactionRequestAttributes(bankId, transactionRequestId, transactionRequestAttributes) pipeTo sender
case cc.deleteTransactionRequestAttribute(transactionRequestAttributeId: String) =>
logger.debug(s"deleteTransactionRequestAttribute($transactionRequestAttributeId)")
mapper.deleteTransactionRequestAttribute(transactionRequestAttributeId) pipeTo sender
case message => logger.warn("[AKKA ACTOR ERROR - REQUEST NOT RECOGNIZED] " + message)
}
}

View File

@ -0,0 +1,16 @@
package code.remotedata
import akka.pattern.ask
import code.actorsystem.ObpActorInit
import code.users.{RemotedataUserAgreementProviderCaseClass, UserAgreement, UserAgreementProvider}
import net.liftweb.common._
object RemotedataUserAgreement extends ObpActorInit with UserAgreementProvider {
val cc = RemotedataUserAgreementProviderCaseClass
def createOrUpdateUserAgreement(userId: String, summary: String, agreementText: String, acceptMarketingInfo: Boolean): Box[UserAgreement] = getValueFromFuture(
(actor ? cc.createOrUpdateUserAgreement(userId, summary, agreementText, acceptMarketingInfo)).mapTo[Box[UserAgreement]]
)
}

View File

@ -0,0 +1,24 @@
package code.remotedata
import akka.actor.Actor
import code.actorsystem.ObpActorHelper
import code.users.{MappedUserAgreementProvider, RemotedataUserAgreementProviderCaseClass}
import code.util.Helper.MdcLoggable
class RemotedataUserAgreementActor extends Actor with ObpActorHelper with MdcLoggable {
val mapper = MappedUserAgreementProvider
val cc = RemotedataUserAgreementProviderCaseClass
def receive: PartialFunction[Any, Unit] = {
case cc.createOrUpdateUserAgreement(userId: String, summary: String, agreementText: String, acceptMarketingInfo: Boolean) =>
logger.debug(s"createOrUpdateUserAgreement($userId, $summary, $agreementText, $acceptMarketingInfo)")
sender ! (mapper.createOrUpdateUserAgreement(userId, summary, agreementText, acceptMarketingInfo))
case message => logger.warn("[AKKA ACTOR ERROR - REQUEST NOT RECOGNIZED] " + message)
}
}

View File

@ -0,0 +1,32 @@
package code.remotedata
import akka.pattern.ask
import code.actorsystem.ObpActorInit
import code.users.{RemotedataUserInvitationProviderCaseClass, UserInvitation, UserInvitationProvider}
import com.openbankproject.commons.model.BankId
import net.liftweb.common._
object RemotedataUserInvitation extends ObpActorInit with UserInvitationProvider {
val cc = RemotedataUserInvitationProviderCaseClass
def createUserInvitation(bankId: BankId, firstName: String, lastName: String, email: String, company: String, country: String, purpose: String): Box[UserInvitation] = getValueFromFuture(
(actor ? cc.createUserInvitation(bankId, firstName, lastName, email, company, country, purpose)).mapTo[Box[UserInvitation]]
)
def getUserInvitationBySecretLink(secretLink: Long): Box[UserInvitation] = getValueFromFuture(
(actor ? cc.getUserInvitationBySecretLink(secretLink)).mapTo[Box[UserInvitation]]
)
def updateStatusOfUserInvitation(userInvitationId: String, status: String): Box[Boolean] = getValueFromFuture(
(actor ? cc.updateStatusOfUserInvitation(userInvitationId, status)).mapTo[Box[Boolean]]
)
def scrambleUserInvitation(userInvitationId: String): Box[Boolean] = getValueFromFuture(
(actor ? cc.scrambleUserInvitation(userInvitationId)).mapTo[Box[Boolean]]
)
def getUserInvitation(bankId: BankId, secretLink: Long): Box[UserInvitation] = getValueFromFuture(
(actor ? cc.getUserInvitation(bankId, secretLink)).mapTo[Box[UserInvitation]]
)
def getUserInvitations(bankId: BankId): Box[List[UserInvitation]] = getValueFromFuture(
(actor ? cc.getUserInvitations(bankId)).mapTo[Box[List[UserInvitation]]]
)
}

View File

@ -0,0 +1,45 @@
package code.remotedata
import akka.actor.Actor
import code.actorsystem.ObpActorHelper
import code.users.{MappedUserInvitationProvider, RemotedataUserInvitationProviderCaseClass}
import code.util.Helper.MdcLoggable
import com.openbankproject.commons.model.BankId
class RemotedataUserInvitationActor extends Actor with ObpActorHelper with MdcLoggable {
val mapper = MappedUserInvitationProvider
val cc = RemotedataUserInvitationProviderCaseClass
def receive: PartialFunction[Any, Unit] = {
case cc.createUserInvitation(bankId: BankId, firstName: String, lastName: String, email: String, company: String, country: String, purpose: String) =>
logger.debug(s"createUserInvitation($bankId, $firstName, $lastName, $email, $company, $country, $purpose)")
sender ! (mapper.createUserInvitation(bankId, firstName, lastName, email, company, country, purpose))
case cc.getUserInvitationBySecretLink(secretLink: Long) =>
logger.debug(s"getUserInvitationBySecretLink($secretLink)")
sender ! (mapper.getUserInvitationBySecretLink(secretLink))
case cc.updateStatusOfUserInvitation(userInvitationId: String, status: String) =>
logger.debug(s"updateStatusOfUserInvitation($userInvitationId, $status)")
sender ! (mapper.updateStatusOfUserInvitation(userInvitationId, status))
case cc.scrambleUserInvitation(userInvitationId: String) =>
logger.debug(s"scrambleUserInvitation($userInvitationId)")
sender ! (mapper.scrambleUserInvitation(userInvitationId))
case cc.getUserInvitation(bankId: BankId, secretLink: Long) =>
logger.debug(s"getUserInvitation($bankId, $secretLink)")
sender ! (mapper.getUserInvitation(bankId, secretLink))
case cc.getUserInvitations(bankId: BankId) =>
logger.debug(s"getUserInvitations($bankId)")
sender ! (mapper.getUserInvitations(bankId))
case message => logger.warn("[AKKA ACTOR ERROR - REQUEST NOT RECOGNIZED] " + message)
}
}

View File

@ -6,7 +6,7 @@ import code.api.util.OBPQueryParam
import code.entitlement.Entitlement
import code.model.dataAccess.ResourceUser
import code.users.{RemotedataUsersCaseClasses, Users}
import com.openbankproject.commons.model.User
import com.openbankproject.commons.model.{User, UserPrimaryKey}
import net.liftweb.common.Box
import scala.collection.immutable.List
@ -71,8 +71,8 @@ object RemotedataUsers extends ObpActorInit with Users {
res.mapTo[List[(ResourceUser, Box[List[Entitlement]])]]
}
def createResourceUser(provider: String, providerId: Option[String], createdByConsentId: Option[String], name: Option[String], email: Option[String], userId: Option[String]) : Box[ResourceUser] = getValueFromFuture(
(actor ? cc.createResourceUser(provider, providerId, createdByConsentId, name, email, userId)).mapTo[Box[ResourceUser]]
def createResourceUser(provider: String, providerId: Option[String], createdByConsentId: Option[String], name: Option[String], email: Option[String], userId: Option[String], createdByUserInvitationId: Option[String], company: Option[String]) : Box[ResourceUser] = getValueFromFuture(
(actor ? cc.createResourceUser(provider, providerId, createdByConsentId, name, email, userId, createdByUserInvitationId, company)).mapTo[Box[ResourceUser]]
)
def createUnsavedResourceUser(provider: String, providerId: Option[String], name: Option[String], email: Option[String], userId: Option[String]) : Box[ResourceUser] = getValueFromFuture(
@ -86,6 +86,10 @@ object RemotedataUsers extends ObpActorInit with Users {
def deleteResourceUser(userId: Long) : Box[Boolean] = getValueFromFuture(
(actor ? cc.deleteResourceUser(userId)).mapTo[Box[Boolean]]
)
def scrambleDataOfResourceUser(userPrimaryKey: UserPrimaryKey) : Box[Boolean] = getValueFromFuture(
(actor ? cc.scrambleDataOfResourceUser(userPrimaryKey)).mapTo[Box[Boolean]]
)
def bulkDeleteAllResourceUsers(): Box[Boolean] = getValueFromFuture(
(actor ? cc.bulkDeleteAllResourceUsers()).mapTo[Box[Boolean]]

View File

@ -10,6 +10,7 @@ import code.util.Helper.MdcLoggable
import scala.collection.immutable.List
import com.openbankproject.commons.ExecutionContext.Implicits.global
import com.openbankproject.commons.model.UserPrimaryKey
class RemotedataUsersActor extends Actor with ObpActorHelper with MdcLoggable {
@ -76,11 +77,11 @@ class RemotedataUsersActor extends Actor with ObpActorHelper with MdcLoggable {
case cc.getAllUsersF(queryParams: List[OBPQueryParam]) =>
logger.debug(s"getAllUsersF(queryParams: ($queryParams))")
sender ! (mapper.getAllUsersFF(queryParams))
mapper.getAllUsersF(queryParams) pipeTo sender
case cc.createResourceUser(provider: String, providerId: Option[String], createdByConsentId: Option[String], name: Option[String], email: Option[String], userId: Option[String]) =>
logger.debug("createResourceUser(" + provider + ", " + providerId.getOrElse("None") + ", " + name.getOrElse("None") + ", " + email.getOrElse("None") + ", " + userId.getOrElse("None") + ")")
sender ! (mapper.createResourceUser(provider, providerId, createdByConsentId, name, email, userId))
case cc.createResourceUser(provider: String, providerId: Option[String], createdByConsentId: Option[String], name: Option[String], email: Option[String], userId: Option[String], createdByUserInvitationId: Option[String], company: Option[String]) =>
logger.debug("createResourceUser(" + provider + ", " + providerId.getOrElse("None") + ", " + name.getOrElse("None") + ", " + email.getOrElse("None") + ", " + userId.getOrElse("None") + ", " + createdByUserInvitationId.getOrElse("None") + ", " + company.getOrElse("None") + ")")
sender ! (mapper.createResourceUser(provider, providerId, createdByConsentId, name, email, userId, createdByUserInvitationId, company))
case cc.createUnsavedResourceUser(provider: String, providerId: Option[String], name: Option[String], email: Option[String], userId: Option[String]) =>
logger.debug("createUnsavedResourceUser(" + provider + ", " + providerId.getOrElse("None") + ", " + name.getOrElse("None") + ", " + email.getOrElse("None") + ", " + userId.getOrElse("None") + ")")
@ -93,6 +94,10 @@ class RemotedataUsersActor extends Actor with ObpActorHelper with MdcLoggable {
case cc.deleteResourceUser(id: Long) =>
logger.debug("deleteResourceUser(" + id +")")
sender ! (mapper.deleteResourceUser(id))
case cc.scrambleDataOfResourceUser(userPrimaryKey: UserPrimaryKey) =>
logger.debug("scrambleDataOfResourceUser(" + userPrimaryKey +")")
sender ! (mapper.scrambleDataOfResourceUser(userPrimaryKey))
case cc.bulkDeleteAllResourceUsers() =>
logger.debug("bulkDeleteAllResourceUsers()")

View File

@ -53,10 +53,11 @@ class ConsumerRegistration extends MdcLoggable {
private object redirectionURLVar extends RequestVar("")
private object requestUriVar extends RequestVar("")
private object authenticationURLVar extends RequestVar("")
private object appTypeVar extends RequestVar[AppType](AppType.Web)
private object appTypeVar extends RequestVar[AppType](AppType.Confidential)
private object descriptionVar extends RequestVar("")
private object devEmailVar extends RequestVar("")
private object appType extends RequestVar("Web")
private object companyVar extends RequestVar("")
private object appType extends RequestVar("Unknown")
private object clientCertificateVar extends RequestVar("")
private object signingAlgVar extends RequestVar("")
private object jwksUriVar extends RequestVar("")
@ -79,7 +80,7 @@ class ConsumerRegistration extends MdcLoggable {
def registerForm = {
val appTypes = List((AppType.Web.toString, AppType.Web.toString), (AppType.Mobile.toString, AppType.Mobile.toString))
val appTypes = List((AppType.Confidential.toString, AppType.Confidential.toString), (AppType.Public.toString, AppType.Public.toString))
val signingAlgs = List(
"ES256", "ES384", "ES512",
//Hydra support alg: RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384 and ES512
@ -93,9 +94,12 @@ class ConsumerRegistration extends MdcLoggable {
def registerWithoutWarnings =
register &
"#register-consumer-errors" #> ""
def displayAppType() = if(APIUtil.getPropsAsBoolValue("consumer_registration.display_app_type", true)) "display: block;" else "display: none"
def register = {
"form" #> {
"#app-type-div [style] " #> displayAppType() &
"#appType" #> SHtml.select(appTypes, Box!! appType.is, appType(_)) &
"#appName" #> SHtml.text(nameVar.is, nameVar(_)) &
"#redirect_url_label *" #> {
@ -103,6 +107,7 @@ class ConsumerRegistration extends MdcLoggable {
} &
"#appRedirectUrl" #> SHtml.text(redirectionURLVar, redirectionURLVar(_)) &
"#appDev" #> SHtml.text(devEmailVar, devEmailVar(_)) &
"#company" #> SHtml.text(companyVar, companyVar(_)) &
"#appDesc" #> SHtml.textarea(descriptionVar, descriptionVar (_)) &
"#appUserAuthenticationUrl" #> SHtml.text(authenticationURLVar.is, authenticationURLVar(_)) & {
if(HydraUtil.integrateWithHydra) {
@ -306,6 +311,7 @@ class ConsumerRegistration extends MdcLoggable {
appTypeVar.set(appTypeSelected.get)
descriptionVar.set(descriptionVar.is)
devEmailVar.set(devEmailVar.is)
companyVar.set(companyVar.is)
redirectionURLVar.set(redirectionURLVar.is)
requestUriVar.set(requestUri)
@ -345,7 +351,8 @@ class ConsumerRegistration extends MdcLoggable {
Some(devEmailVar.is),
Some(redirectionURLVar.is),
Some(AuthUser.getCurrentResourceUserUserId),
Some(clientCertificate))
Some(clientCertificate),
company = Some(companyVar.is))
logger.debug("consumer: " + consumer)
consumer match {
case Full(x) =>
@ -471,7 +478,7 @@ class ConsumerRegistration extends MdcLoggable {
while(matcher.find()) {
val userName = matcher.group(1)
val password = matcher.group(2)
val (code, token) = DirectLogin.createToken(Map(("username", userName), ("password", password), ("consumer_key", consumerKey)))
val (code, token, userId) = DirectLogin.createToken(Map(("username", userName), ("password", password), ("consumer_key", consumerKey)))
val authHeader = code match {
case 200 => (userName, password) -> s"""Authorization: DirectLogin token="$token""""
case _ => (userName, password) -> "username or password is invalid, generate token fail"

View File

@ -0,0 +1,201 @@
/**
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.time.{Duration, ZoneId, ZoneOffset, ZonedDateTime}
import code.api.util.APIUtil
import code.model.dataAccess.{AuthUser, ResourceUser}
import code.users
import code.users.{UserAgreementProvider, UserInvitationProvider, Users}
import code.util.Helper
import code.util.Helper.MdcLoggable
import code.webuiprops.MappedWebUiPropsProvider.getWebUiPropsValue
import com.openbankproject.commons.model.User
import net.liftweb.common.Box
import net.liftweb.http.{RequestVar, S, SHtml}
import net.liftweb.util.CssSel
import net.liftweb.util.Helpers._
import scala.collection.immutable.List
class UserInvitation extends MdcLoggable {
private object firstNameVar extends RequestVar("")
private object lastNameVar extends RequestVar("")
private object companyVar extends RequestVar("")
private object countryVar extends RequestVar("None")
private object devEmailVar extends RequestVar("")
private object usernameVar extends RequestVar("")
private object termsCheckboxVar extends RequestVar(false)
private object marketingInfoCheckboxVar extends RequestVar(false)
private object privacyCheckboxVar extends RequestVar(false)
val ttl = APIUtil.getPropsAsLongValue("user_invitation.ttl.seconds", 86400)
val registrationConsumerButtonValue: String = getWebUiPropsValue("webui_post_user_invitation_submit_button_value", "Register as a Developer")
val privacyConditionsValue: String = getWebUiPropsValue("webui_post_user_invitation_privacy_conditions_value", "Privacy conditions..")
val termsAndConditionsValue: String = getWebUiPropsValue("webui_post_user_invitation_terms_and_conditions_value", "Terms and Conditions..")
val termsAndConditionsCheckboxValue: String = getWebUiPropsValue("webui_post_user_invitation_terms_and_conditions_checkbox_value", "I agree to the above Developer Terms and Conditions")
def registerForm: CssSel = {
val secretLink: Box[Long] = tryo {
S.param("id").getOrElse("0").toLong
}
val userInvitation: Box[users.UserInvitation] = UserInvitationProvider.userInvitationProvider.vend.getUserInvitationBySecretLink(secretLink.getOrElse(0))
firstNameVar.set(userInvitation.map(_.firstName).getOrElse("None"))
lastNameVar.set(userInvitation.map(_.lastName).getOrElse("None"))
val email = userInvitation.map(_.email).getOrElse("None")
devEmailVar.set(email)
companyVar.set(userInvitation.map(_.company).getOrElse("None"))
countryVar.set(userInvitation.map(_.country).getOrElse("None"))
usernameVar.set(firstNameVar.is.toLowerCase + "." + lastNameVar.is.toLowerCase())
def submitButtonDefense(): Unit = {
val verifyingTime = ZonedDateTime.now(ZoneOffset.UTC)
val createdAt = userInvitation.map(_.createdAt.get).getOrElse(time(239932800))
val timeOfCreation = ZonedDateTime.ofInstant(createdAt.toInstant(), ZoneId.systemDefault())
val timeDifference = Duration.between(verifyingTime, timeOfCreation)
logger.debug("User invitation TTL time: " + ttl)
logger.debug("User invitation expiration time: " + timeDifference.abs.getSeconds)
if(secretLink.isEmpty || userInvitation.isEmpty) showErrorsForSecretLink()
else if(userInvitation.map(_.status != "CREATED").getOrElse(false)) showErrorsForStatus()
else if(timeDifference.abs.getSeconds > ttl) showErrorsForTtl()
else if(Users.users.vend.getUserByUserName(usernameVar.is).isDefined) showErrorsForUsername()
else if(privacyCheckboxVar.is == false) showErrorsForPrivacyConditions()
else if(termsCheckboxVar.is == false) showErrorsForTermsAndConditions()
else {
// Resource User table
createResourceUser(
provider = "OBP-User-Invitation",
providerId = Some(usernameVar.is),
name = Some(firstNameVar.is + " " + lastNameVar.is),
email = Some(email),
userInvitationId = userInvitation.map(_.userInvitationId).toOption,
company = userInvitation.map(_.company).toOption
).map{ u =>
// AuthUser table
createAuthUser(user = u, firstName = firstNameVar.is, lastName = lastNameVar.is, password = "")
// Use Agreement table
UserAgreementProvider.userAgreementProvider.vend.createOrUpdateUserAgreement(
u.userId, privacyConditionsValue, termsAndConditionsValue, marketingInfoCheckboxVar.is)
// Set the status of the user invitation to "FINISHED"
UserInvitationProvider.userInvitationProvider.vend.updateStatusOfUserInvitation(userInvitation.map(_.userInvitationId).getOrElse(""), "FINISHED")
// Set a new password
val resetLink = AuthUser.passwordResetUrl(u.idGivenByProvider, u.emailAddress, u.userId) + "?action=set"
S.redirectTo(resetLink)
}
}
}
def showError(usernameError: String) = {
S.error("register-consumer-errors", usernameError)
register &
"#register-consumer-errors *" #> {
".error *" #>
List(usernameError).map({ e =>
".errorContent *" #> e
})
}
}
def showErrorsForSecretLink() = {
showError(Helper.i18n("your.secret.link.is.not.valid"))
}
def showErrorsForUsername() = {
showError(Helper.i18n("unique.username"))
}
def showErrorsForStatus() = {
showError(Helper.i18n("user.invitation.is.already.finished"))
}
def showErrorsForTtl() = {
showError(Helper.i18n("user.invitation.is.expired"))
}
def showErrorsForTermsAndConditions() = {
showError(Helper.i18n("terms.and.conditions.are.not.selected"))
}
def showErrorsForPrivacyConditions() = {
showError(Helper.i18n("privacy.conditions.are.not.selected"))
}
def register = {
"form" #> {
"#country" #> SHtml.text(countryVar.is, countryVar(_)) &
"#firstName" #> SHtml.text(firstNameVar.is, firstNameVar(_)) &
"#lastName" #> SHtml.text(lastNameVar.is, lastNameVar(_)) &
"#companyName" #> SHtml.text(companyVar.is, companyVar(_)) &
"#devEmail" #> SHtml.text(devEmailVar, devEmailVar(_)) &
"#username" #> SHtml.text(usernameVar, usernameVar(_)) &
"#privacy" #> SHtml.textarea(privacyConditionsValue, privacyConditionsValue => privacyConditionsValue) &
"#privacy_checkbox" #> SHtml.checkbox(privacyCheckboxVar, privacyCheckboxVar(_)) &
"#terms" #> SHtml.textarea(termsAndConditionsValue, termsAndConditionsValue => termsAndConditionsValue) &
"#terms_checkbox" #> SHtml.checkbox(termsCheckboxVar, termsCheckboxVar(_)) &
"#marketing_info_checkbox" #> SHtml.checkbox(marketingInfoCheckboxVar, marketingInfoCheckboxVar(_)) &
"type=submit" #> SHtml.submit(s"$registrationConsumerButtonValue", () => submitButtonDefense)
} &
"#register-consumer-success" #> ""
}
register
}
private def createAuthUser(user: User, firstName: String, lastName: String, password: String): Box[AuthUser] = tryo {
val newUser = AuthUser.create
.firstName(firstName)
.lastName(lastName)
.email(user.emailAddress)
.user(user.userPrimaryKey.value)
.username(user.idGivenByProvider)
.provider(user.provider)
.password(password)
.validated(true)
// Save the user
newUser.saveMe()
}
private def createResourceUser(provider: String,
providerId: Option[String],
name: Option[String],
email: Option[String],
userInvitationId: Option[String],
company: Option[String]
): Box[ResourceUser] = {
Users.users.vend.createResourceUser(
provider = provider,
providerId = providerId,
createdByConsentId = None,
name = name,
email = email,
userId = None,
createdByUserInvitationId = userInvitationId,
company = company
)
}
}

View File

@ -0,0 +1,85 @@
package code.snippet
import scala.collection.mutable.ArrayBuffer
case class WebUIDoc(webUiPropsName: String, defaultValue: String, typeOfValue: String, placeholders: List[String])
object WebUIPlaceholder {
val emailRecipient = "@EMAIL_RECIPIENT"
val activateYourAccount = "@ACTIVATE_YOUR_ACCOUNT"
}
object WebUITemplate {
import WebUIPlaceholder._
val webUIDoc = ArrayBuffer[WebUIDoc]()
webUIDoc += WebUIDoc(
webUiPropsName = "webui_developer_user_invitation_email_text",
defaultValue = webUiDeveloperUserInvitationEmailText,
typeOfValue = "plain_text",
placeholders = List(emailRecipient, activateYourAccount)
)
val webUiDeveloperUserInvitationEmailText =
s"""
|Hi ${emailRecipient},
|Welcome to the Open Bank Project API. Your account has been registered. Please use the below link to activate it.
|
|Activate your account: ${activateYourAccount}
|
|Our operations team has granted you the appropriate access to the OBP-API. If you have any questions, or you need any assistance, please contact our support.
|
|Thanks,
|Your OBP API team
|
|
|
|Please do not reply to this email. Should you wish to contact us, please raise a ticket at our support page. We maintain strict security standards and procedures to prevent unauthorised access to information about you. We will never contact you by email or otherwise and ask you to validate personal information such as your user ID, password or account numbers. This e-mail is confidential. It may also be legally privileged. If you are not the addressee you may not copy, forward, disclose or use any part of it. If you have received this message in error, please delete it and all copies from your system. Internet communications cannot be guaranteed to be timely, secure, error or virus-free. The sender does not accept liability for any errors or omissions.
|""".stripMargin
webUIDoc += WebUIDoc(
webUiPropsName = "webui_developer_user_invitation_email_html_text",
defaultValue = webUiDeveloperUserInvitationEmailHtmlText,
typeOfValue = "html",
placeholders = List(emailRecipient, activateYourAccount)
)
val webUiDeveloperUserInvitationEmailHtmlText =
s"""<!DOCTYPE html>
|<html>
|<head>
|<style>
|.a {
| border: none;
| color: white;
| padding: 15px 32px;
| text-align: center;
| text-decoration: none;
| display: inline-block;
| font-size: 16px;
| margin: 4px 2px;
| cursor: pointer;
|}
|
|.a1 {background-color: #4CAF50;} /* Green */
|.a2 {background-color: #008CBA;} /* Blue */
|</style>
|</head>
|<body>
|<img src="https://static.openbankproject.com/images/OBP_full_web_25pc.png"></img>
|<hr></hr><br></br>
|<p>Hi ${emailRecipient},<br></br>
|Welcome to the Open Bank Project API. Your account has been registered. Please use the below link to activate it.</p>
|<a href="${activateYourAccount}" class="a a1">Activate your account</a>
|<p>Our operations team has granted you the appropriate access to the OBP-API. If you have any questions, or you need any assistance, please contact our support.</p>
|<p>Thanks,<br></br> Your OBP API team</p><br></br>
|<hr></hr>
|<p>
|Please do not reply to this email. Should you wish to contact us, please raise a ticket at our support page. We maintain strict security standards and procedures to prevent unauthorised access to information about you. We will never contact you by email or otherwise and ask you to validate personal information such as your user ID, password or account numbers. This e-mail is confidential. It may also be legally privileged. If you are not the addressee you may not copy, forward, disclose or use any part of it. If you have received this message in error, please delete it and all copies from your system. Internet communications cannot be guaranteed to be timely, secure, error or virus-free. The sender does not accept liability for any errors or omissions.
|</p>
|</body>
|</html>
|
|""".stripMargin
}

View File

@ -1,19 +1,19 @@
package code.users
import code.api.util.{OBPLimit, OBPLockedStatus, OBPOffset, OBPQueryParam}
import code.api.util._
import code.entitlement.Entitlement
import code.loginattempts.LoginAttempt.maxBadLoginAttempts
import code.loginattempts.MappedBadLoginAttempt
import code.model.dataAccess.ResourceUser
import code.model.dataAccess.{AuthUser, ResourceUser}
import code.util.Helper.MdcLoggable
import com.openbankproject.commons.model.User
import net.liftweb.common.{Box, Full}
import net.liftweb.mapper._
import scala.collection.immutable.List
import com.openbankproject.commons.ExecutionContext.Implicits.global
import com.openbankproject.commons.model.{User, UserPrimaryKey}
import net.liftweb.common.{Box, Empty, Full}
import net.liftweb.mapper._
import net.liftweb.util.Helpers
import scala.collection.immutable
import scala.collection.immutable.List
import scala.concurrent.Future
object LiftUsers extends Users with MdcLoggable{
@ -58,7 +58,9 @@ object LiftUsers extends Users with MdcLoggable{
createdByConsentId = consentId,
name = name,
email = email,
userId = None
userId = None,
createdByUserInvitationId = None,
company = None
)
(newUser, true)
}
@ -125,8 +127,16 @@ object LiftUsers extends Users with MdcLoggable{
val limit = queryParams.collect { case OBPLimit(value) => MaxRows[ResourceUser](value) }.headOption
val offset: Option[StartAt[ResourceUser]] = queryParams.collect { case OBPOffset(value) => StartAt[ResourceUser](value) }.headOption
val locked: Option[String] = queryParams.collect { case OBPLockedStatus(value) => value }.headOption
val deleted = queryParams.collect {
case OBPIsDeleted(value) if value == true => // ?is_deleted=true
By(ResourceUser.IsDeleted, true)
case OBPIsDeleted(value) if value == false => // ?is_deleted=false
By(ResourceUser.IsDeleted, false)
}.headOption.orElse(
Some(By(ResourceUser.IsDeleted, false)) // There is no query parameter "is_deleted"
)
val optionalParams: Seq[QueryParam[ResourceUser]] = Seq(limit.toSeq, offset.toSeq).flatten
val optionalParams: Seq[QueryParam[ResourceUser]] = Seq(limit.toSeq, offset.toSeq, deleted.toSeq).flatten
def getAllResourceUsers(): List[ResourceUser] = ResourceUser.findAll(optionalParams: _*)
val showUsers: List[ResourceUser] = locked.map(_.toLowerCase()) match {
@ -155,24 +165,16 @@ object LiftUsers extends Users with MdcLoggable{
}
}
}
def getAllUsersFF(queryParams: List[OBPQueryParam]): List[(ResourceUser, Box[List[Entitlement]])] = {
val limit = queryParams.collect { case OBPLimit(value) => MaxRows[ResourceUser](value) }.headOption
val offset = queryParams.collect { case OBPOffset(value) => StartAt[ResourceUser](value) }.headOption
val optionalParams: Seq[QueryParam[ResourceUser]] = Seq(limit.toSeq, offset.toSeq).flatten
logger.debug(s"getAllUsersFF parameters $optionalParams")
val users = ResourceUser.findAll(optionalParams: _*)
logger.debug(s"getAllUsersFF response $users")
for {
user <- users
} yield {
(user, Entitlement.entitlement.vend.getEntitlementsByUserId(user.userId).map(_.sortWith(_.roleName < _.roleName)))
}
}
override def createResourceUser(provider: String, providerId: Option[String], createdByConsentId: Option[String], name: Option[String], email: Option[String], userId: Option[String]): Box[ResourceUser] = {
override def createResourceUser(provider: String,
providerId: Option[String],
createdByConsentId: Option[String],
name: Option[String],
email: Option[String],
userId: Option[String],
createdByUserInvitationId: Option[String],
company: Option[String]): Box[ResourceUser] = {
val ru = ResourceUser.create
ru.provider_(provider)
providerId match {
@ -183,6 +185,10 @@ object LiftUsers extends Users with MdcLoggable{
case Some(consentId) => ru.CreatedByConsentId(consentId)
case None => ru.CreatedByConsentId(null)
}
createdByUserInvitationId match {
case Some(invitationId) => ru.CreatedByUserInvitationId(invitationId)
case None => ru.CreatedByConsentId(null)
}
name match {
case Some(v) => ru.name_(v)
case None =>
@ -195,6 +201,10 @@ object LiftUsers extends Users with MdcLoggable{
case Some(v) => ru.userId_(v)
case None =>
}
company match {
case Some(v) => ru.Company(v)
case None =>
}
Full(ru.saveMe())
}
@ -236,5 +246,26 @@ object LiftUsers extends Users with MdcLoggable{
u.delete_!
}
}
override def scrambleDataOfResourceUser(userPrimaryKey: UserPrimaryKey): Box[Boolean] = {
for {
u <- ResourceUser.find(By(ResourceUser.id, userPrimaryKey.value))
} yield {
AuthUser.find(By(AuthUser.user, userPrimaryKey.value)) match {
case Empty =>
u
.Company(Helpers.randomString(16))
.IsDeleted(true)
.name_("DELETED-" + Helpers.randomString(16))
.email(Helpers.randomString(10) + "@example.com")
.providerId(Helpers.randomString(16))
.save()
case _ =>
u
.Company(Helpers.randomString(16))
.IsDeleted(true)
.save()
}
}
}
}

View File

@ -0,0 +1,63 @@
package code.users
import java.util.Date
import java.util.UUID.randomUUID
import code.api.util.HashUtil
import code.util.UUIDString
import net.liftweb.common.{Box, Empty, Full}
import net.liftweb.mapper._
object MappedUserAgreementProvider extends UserAgreementProvider {
override def createOrUpdateUserAgreement(userId: String, summary: String, agreementText: String, acceptMarketingInfo: Boolean): Box[UserAgreement] = {
UserAgreement.find(By(UserAgreement.UserId, userId)) match {
case Full(existingUser) =>
Full(
existingUser
.Summary(summary)
.AgreementText(agreementText)
.AcceptMarketingInfo(acceptMarketingInfo)
.saveMe()
)
case Empty =>
Full(
UserAgreement.create
.UserId(userId)
.Summary(summary)
.AgreementText(agreementText)
.AcceptMarketingInfo(acceptMarketingInfo)
.Date(new Date)
.saveMe()
)
case everythingElse => everythingElse
}
}
}
class UserAgreement extends UserAgreementTrait with LongKeyedMapper[UserAgreement] with IdPK with CreatedUpdated {
def getSingleton = UserAgreement
object UserAgreementId extends UUIDString(this) {
override def defaultValue = randomUUID().toString
}
object UserId extends MappedString(this, 255)
object Date extends MappedDate(this)
object Summary extends MappedText(this)
object AgreementText extends MappedText(this)
object AgreementHash extends MappedString(this, 64) {
override def defaultValue: String = HashUtil.Sha256Hash(AgreementText.get)
}
object AcceptMarketingInfo extends MappedBoolean(this)
override def userInvitationId: String = UserAgreementId.get
override def userId: String = UserId.get
override def summary: String = Summary.get
override def agreementText: String = AgreementText.get
override def agreementHash: String = AgreementHash.get
override def acceptMarketingInfo: Boolean = AcceptMarketingInfo.get
}
object UserAgreement extends UserAgreement with LongKeyedMetaMapper[UserAgreement] {
override def dbIndexes: List[BaseIndex[UserAgreement]] = UniqueIndex(UserAgreementId) :: super.dbIndexes
}

View File

@ -0,0 +1,39 @@
package code.users
import code.api.util.APIUtil
import code.remotedata.{RemotedataUserAgreement, RemotedataUserInvitation}
import com.openbankproject.commons.model.BankId
import net.liftweb.common.Box
import net.liftweb.util.SimpleInjector
object UserAgreementProvider extends SimpleInjector {
val userAgreementProvider = new Inject(buildOne _) {}
def buildOne: UserAgreementProvider =
APIUtil.getPropsAsBoolValue("use_akka", false) match {
case false => MappedUserAgreementProvider
case true => RemotedataUserAgreement // We will use Akka as a middleware
}
}
trait UserAgreementProvider {
def createOrUpdateUserAgreement(userId: String, summary: String, agreementText: String, acceptMarketingInfo: Boolean): Box[UserAgreement]
}
class RemotedataUserAgreementProviderCaseClass {
case class createOrUpdateUserAgreement(userId: String, summary: String, agreementText: String, acceptMarketingInfo: Boolean)
}
object RemotedataUserAgreementProviderCaseClass extends RemotedataUserAgreementProviderCaseClass
trait UserAgreementTrait {
def userInvitationId: String
def userId: String
def summary: String
def agreementText: String
def agreementHash: String
def acceptMarketingInfo: Boolean
}

View File

@ -0,0 +1,53 @@
package code.users
import code.api.util.APIUtil
import code.remotedata.RemotedataUserInvitation
import com.openbankproject.commons.model.BankId
import net.liftweb.common.Box
import net.liftweb.util.SimpleInjector
object UserInvitationProvider extends SimpleInjector {
val userInvitationProvider = new Inject(buildOne _) {}
def buildOne: UserInvitationProvider =
APIUtil.getPropsAsBoolValue("use_akka", false) match {
case false => MappedUserInvitationProvider
case true => RemotedataUserInvitation // We will use Akka as a middleware
}
}
trait UserInvitationProvider {
def createUserInvitation(bankId: BankId, firstName: String, lastName: String, email: String, company: String, country: String, purpose: String): Box[UserInvitation]
def getUserInvitationBySecretLink(secretLink: Long): Box[UserInvitation]
def scrambleUserInvitation(userInvitationId: String): Box[Boolean]
def updateStatusOfUserInvitation(userInvitationId: String, status: String): Box[Boolean]
def getUserInvitation(bankId: BankId, secretLink: Long): Box[UserInvitation]
def getUserInvitations(bankId: BankId): Box[List[UserInvitation]]
}
class RemotedataUserInvitationProviderCaseClass {
case class createUserInvitation(bankId: BankId, firstName: String, lastName: String, email: String, company: String, country: String, purpose: String)
case class getUserInvitationBySecretLink(secretLink: Long)
case class updateStatusOfUserInvitation(userInvitationId: String, status: String)
case class scrambleUserInvitation(userInvitationId: String)
case class getUserInvitation(bankId: BankId, secretLink: Long)
case class getUserInvitations(bankId: BankId)
}
object RemotedataUserInvitationProviderCaseClass extends RemotedataUserInvitationProviderCaseClass
trait UserInvitationTrait {
def userInvitationId: String
def bankId: String
def firstName: String
def lastName: String
def email: String
def company: String
def country: String
def status: String
def purpose: String
def secretKey: Long
}

View File

@ -0,0 +1,100 @@
package code.users
import java.util.UUID.randomUUID
import code.api.util.SecureRandomUtil
import code.util.UUIDString
import com.openbankproject.commons.model.BankId
import net.liftweb.common.{Box, Full}
import net.liftweb.mapper._
import net.liftweb.util.Helpers
import net.liftweb.util.Helpers.tryo
object MappedUserInvitationProvider extends UserInvitationProvider {
override def createUserInvitation(bankId: BankId, firstName: String, lastName: String, email: String, company: String, country: String, purpose: String): Box[UserInvitation] = tryo {
UserInvitation.create
.BankId(bankId.value)
.FirstName(firstName)
.LastName(lastName)
.Email(email)
.Company(company)
.Country(country)
.Status("CREATED")
.Purpose(purpose)
.saveMe()
}
override def getUserInvitationBySecretLink(secretLink: Long): Box[UserInvitation] = {
UserInvitation.find(
By(UserInvitation.SecretKey, secretLink)
)
}
override def updateStatusOfUserInvitation(userInvitationId: String, status: String): Box[Boolean] = tryo {
UserInvitation.find(
By(UserInvitation.UserInvitationId, userInvitationId)
) match {
case Full(userInvitation) => userInvitation.Status(status).save()
case _ => false
}
}
override def scrambleUserInvitation(userInvitationId: String): Box[Boolean] = tryo {
UserInvitation.find(
By(UserInvitation.UserInvitationId, userInvitationId)
) match {
case Full(userInvitation) =>
userInvitation
.Email(Helpers.randomString(10) + "@example.com")
.FirstName(Helpers.randomString(userInvitation.firstName.length))
.LastName(Helpers.randomString(userInvitation.lastName.length))
.Company(Helpers.randomString(userInvitation.company.length))
.Country(Helpers.randomString(userInvitation.country.length))
.Purpose(Helpers.randomString(userInvitation.purpose.length))
.Status("DELETED")
.save()
case _ => false
}
}
override def getUserInvitation(bankId: BankId, secretLink: Long): Box[UserInvitation] = {
UserInvitation.find(
By(UserInvitation.BankId, bankId.value),
By(UserInvitation.SecretKey, secretLink)
)
}
override def getUserInvitations(bankId: BankId): Box[List[UserInvitation]] = tryo {
UserInvitation.findAll(By(UserInvitation.BankId, bankId.value))
}
}
class UserInvitation extends UserInvitationTrait with LongKeyedMapper[UserInvitation] with IdPK with CreatedUpdated {
def getSingleton = UserInvitation
object UserInvitationId extends UUIDString(this) {
override def defaultValue = randomUUID().toString
}
object BankId extends MappedString(this, 255)
object FirstName extends MappedString(this, 50)
object LastName extends MappedString(this, 50)
object Email extends MappedString(this, 50)
object Company extends MappedString(this, 50)
object Country extends MappedString(this, 50)
object Status extends MappedString(this, 50)
object Purpose extends MappedString(this, 50)
object SecretKey extends MappedLong(this) {
override def defaultValue: Long = SecureRandomUtil.csprng.nextLong()
}
override def userInvitationId: String = UserInvitationId.get
override def bankId: String = BankId.get
override def firstName: String = FirstName.get
override def lastName: String = LastName.get
override def email: String = Email.get
override def company: String = Company.get
override def country: String = Country.get
override def status: String = Status.get
override def purpose: String = Purpose.get
override def secretKey: Long = SecretKey.get
}
object UserInvitation extends UserInvitation with LongKeyedMetaMapper[UserInvitation] {
override def dbIndexes: List[BaseIndex[UserInvitation]] = UniqueIndex(UserInvitationId) :: super.dbIndexes
}

View File

@ -4,7 +4,7 @@ import code.api.util.{APIUtil, OBPQueryParam}
import code.entitlement.Entitlement
import code.model.dataAccess.ResourceUser
import code.remotedata.RemotedataUsers
import com.openbankproject.commons.model.User
import com.openbankproject.commons.model.{User, UserPrimaryKey}
import net.liftweb.common.Box
import net.liftweb.util.SimpleInjector
@ -51,13 +51,15 @@ trait Users {
def getAllUsersF(queryParams: List[OBPQueryParam]) : Future[List[(ResourceUser, Box[List[Entitlement]])]]
def createResourceUser(provider: String, providerId: Option[String], createdByConsentId: Option[String], name: Option[String], email: Option[String], userId: Option[String]) : Box[ResourceUser]
def createResourceUser(provider: String, providerId: Option[String], createdByConsentId: Option[String], name: Option[String], email: Option[String], userId: Option[String], createdByUserInvitationId: Option[String], company: Option[String]) : Box[ResourceUser]
def createUnsavedResourceUser(provider: String, providerId: Option[String], name: Option[String], email: Option[String], userId: Option[String]) : Box[ResourceUser]
def saveResourceUser(resourceUser: ResourceUser) : Box[ResourceUser]
def deleteResourceUser(userId: Long) : Box[Boolean]
def scrambleDataOfResourceUser(userPrimaryKey: UserPrimaryKey) : Box[Boolean]
def bulkDeleteAllResourceUsers() : Box[Boolean]
}
@ -78,10 +80,11 @@ class RemotedataUsersCaseClasses {
case class getUserByEmailFuture(email : String)
case class getAllUsers()
case class getAllUsersF(queryParams: List[OBPQueryParam])
case class createResourceUser(provider: String, providerId: Option[String],createdByConsentId: Option[String], name: Option[String], email: Option[String], userId: Option[String])
case class createResourceUser(provider: String, providerId: Option[String],createdByConsentId: Option[String], name: Option[String], email: Option[String], userId: Option[String], createdByUserInvitationId: Option[String], company: Option[String])
case class createUnsavedResourceUser(provider: String, providerId: Option[String], name: Option[String], email: Option[String], userId: Option[String])
case class saveResourceUser(resourceUser: ResourceUser)
case class deleteResourceUser(userId: Long)
case class scrambleDataOfResourceUser(userPrimaryKey: UserPrimaryKey)
case class bulkDeleteAllResourceUsers()
}

View File

@ -15,7 +15,7 @@ import net.liftweb.json.JsonAST._
import net.liftweb.json.{DateFormat, Formats}
import org.apache.commons.lang3.StringUtils
import com.openbankproject.commons.ExecutionContext.Implicits.global
import com.openbankproject.commons.model.{AccountBalance, AccountHeld, AccountId, CoreAccount, Customer, CustomerId}
import com.openbankproject.commons.model.{AccountBalance, AccountBalances, AccountHeld, AccountId, CoreAccount, Customer, CustomerId}
import com.openbankproject.commons.util.{ReflectUtils, RequiredFieldValidation, RequiredInfo}
import com.tesobe.CacheKeyFromArguments
import net.liftweb.http.S
@ -415,6 +415,7 @@ object Helper{
(fieldName.equalsIgnoreCase("accountId") && fieldType =:= typeOf[String])||
(ownerType <:< typeOf[CoreAccount] && fieldName.equalsIgnoreCase("id") && fieldType =:= typeOf[String])||
(ownerType <:< typeOf[AccountBalance] && fieldName.equalsIgnoreCase("id") && fieldType =:= typeOf[String])||
(ownerType <:< typeOf[AccountBalances] && fieldName.equalsIgnoreCase("id") && fieldType =:= typeOf[String])||
(ownerType <:< typeOf[AccountHeld] && fieldName.equalsIgnoreCase("id") && fieldType =:= typeOf[String])
}

View File

@ -53,7 +53,7 @@ object MapperViews extends Views with MdcLoggable {
.map(v => v.bank_id(a.bank_id.get).account_id(a.account_id.get))
).filter(
v =>
if (ALLOW_PUBLIC_VIEWS) {
if (allowPublicViews) {
true // All views
} else {
v.isPrivate == true // Only private views
@ -122,7 +122,7 @@ object MapperViews extends Views with MdcLoggable {
viewDefinition match {
case Full(v) => {
if(v.isPublic && !ALLOW_PUBLIC_VIEWS) return Failure(PublicViewsNotAllowedOnThisInstance)
if(v.isPublic && !allowPublicViews) return Failure(PublicViewsNotAllowedOnThisInstance)
// SQL Select Count ViewPrivileges where
// This is idempotent
getOrGrantAccessToCustomView(user, v, viewIdBankIdAccountId.bankId.value, viewIdBankIdAccountId.accountId.value) //privilege already exists, no need to create one
@ -133,7 +133,7 @@ object MapperViews extends Views with MdcLoggable {
}
}
def grantAccessToSystemView(bankId: BankId, accountId: AccountId, view: View, user: User): Box[View] = {
{ view.isPublic && !ALLOW_PUBLIC_VIEWS } match {
{ view.isPublic && !allowPublicViews } match {
case true => Failure(PublicViewsNotAllowedOnThisInstance)
case false => getOrGrantAccessToSystemView(bankId: BankId, accountId: AccountId, user, view)
}
@ -153,7 +153,7 @@ object MapperViews extends Views with MdcLoggable {
//TODO: APIFailures with http response codes belong at a higher level in the code
} else {
viewDefinitions.foreach(v => {
if(v._1.isPublic && !ALLOW_PUBLIC_VIEWS) return Failure(PublicViewsNotAllowedOnThisInstance)
if(v._1.isPublic && !allowPublicViews) return Failure(PublicViewsNotAllowedOnThisInstance)
val viewDefinition = v._1
val viewIdBankIdAccountId = v._2
// This is idempotent
@ -176,7 +176,7 @@ object MapperViews extends Views with MdcLoggable {
//TODO: APIFailures with http response codes belong at a higher level in the code
} else {
viewDefinitions.foreach(v => {
if(v._1.isPublic && !ALLOW_PUBLIC_VIEWS) return Failure(PublicViewsNotAllowedOnThisInstance)
if(v._1.isPublic && !allowPublicViews) return Failure(PublicViewsNotAllowedOnThisInstance)
val viewDefinition = v._1
val viewIdBankIdAccountId = v._2
// This is idempotent
@ -300,7 +300,7 @@ object MapperViews extends Views with MdcLoggable {
def customView(viewId : ViewId, account: BankIdAccountId) : Box[View] = {
val view = ViewDefinition.findCustomView(account.bankId.value, account.accountId.value, viewId.value)
if(view.isDefined && view.openOrThrowException(attemptedToOpenAnEmptyBox).isPublic && !ALLOW_PUBLIC_VIEWS) return Failure(PublicViewsNotAllowedOnThisInstance)
if(view.isDefined && view.openOrThrowException(attemptedToOpenAnEmptyBox).isPublic && !allowPublicViews) return Failure(PublicViewsNotAllowedOnThisInstance)
view
}
@ -360,7 +360,7 @@ object MapperViews extends Views with MdcLoggable {
* */
def createView(bankAccountId: BankIdAccountId, view: CreateViewJson): Box[View] = {
if(view.is_public && !ALLOW_PUBLIC_VIEWS) {
if(view.is_public && !allowPublicViews) {
return Failure(PublicViewsNotAllowedOnThisInstance)
}
@ -456,7 +456,7 @@ object MapperViews extends Views with MdcLoggable {
}
def publicViews: (List[View], List[AccountAccess]) = {
if (APIUtil.ALLOW_PUBLIC_VIEWS) {
if (APIUtil.allowPublicViews) {
val publicViews = ViewDefinition.findAll(By(ViewDefinition.isPublic_, true)) // Custom and System views
val publicAccountAccesses = AccountAccess.findAll(ByList(AccountAccess.view_fk, publicViews.map(_.id)))
(publicViews, publicAccountAccesses)
@ -466,7 +466,7 @@ object MapperViews extends Views with MdcLoggable {
}
def publicViewsForBank(bankId: BankId): (List[View], List[AccountAccess]) ={
if (APIUtil.ALLOW_PUBLIC_VIEWS) {
if (APIUtil.allowPublicViews) {
val publicViews =
ViewDefinition.findAll(By(ViewDefinition.isPublic_, true), By(ViewDefinition.bank_id, bankId.value), By(ViewDefinition.isSystem_, false)) ::: // Custom views
ViewDefinition.findAll(By(ViewDefinition.isPublic_, true), By(ViewDefinition.isSystem_, true)) ::: // System views
@ -723,7 +723,7 @@ object MapperViews extends Views with MdcLoggable {
}
def createDefaultPublicView(bankId: BankId, accountId: AccountId, name: String): Box[View] = {
if(!ALLOW_PUBLIC_VIEWS) {
if(!allowPublicViews) {
return Failure(PublicViewsNotAllowedOnThisInstance)
}
createAndSaveDefaultPublicView(bankId, accountId, "Public View")
@ -739,12 +739,12 @@ object MapperViews extends Views with MdcLoggable {
def getExistingView(bankId: BankId, accountId: AccountId, name: String): Box[View] = {
val res = ViewDefinition.findCustomView(bankId.value, accountId.value, name)
if(res.isDefined && res.openOrThrowException(attemptedToOpenAnEmptyBox).isPublic && !ALLOW_PUBLIC_VIEWS) return Failure(PublicViewsNotAllowedOnThisInstance)
if(res.isDefined && res.openOrThrowException(attemptedToOpenAnEmptyBox).isPublic && !allowPublicViews) return Failure(PublicViewsNotAllowedOnThisInstance)
res
}
def getExistingSystemView(name: String): Box[View] = {
val res = ViewDefinition.findSystemView(name)
if(res.isDefined && res.openOrThrowException(attemptedToOpenAnEmptyBox).isPublic && !ALLOW_PUBLIC_VIEWS) return Failure(PublicViewsNotAllowedOnThisInstance)
if(res.isDefined && res.openOrThrowException(attemptedToOpenAnEmptyBox).isPublic && !allowPublicViews) return Failure(PublicViewsNotAllowedOnThisInstance)
res
}
@ -1135,7 +1135,7 @@ object MapperViews extends Views with MdcLoggable {
}
def createAndSaveDefaultPublicView(bankId : BankId, accountId: AccountId, description: String) : Box[View] = {
if(!ALLOW_PUBLIC_VIEWS) {
if(!allowPublicViews) {
return Failure(PublicViewsNotAllowedOnThisInstance)
}
val res = unsavedDefaultPublicView(bankId, accountId, description).saveMe

View File

@ -1,7 +1,6 @@
package code.views
import code.api.util.APIUtil
import code.api.util.APIUtil.canUseAccountFirehose
import code.model.dataAccess.{MappedBankAccount, ViewImpl, ViewPrivileges}
import code.remotedata.RemotedataViews
import code.views.MapperViews.getPrivateBankAccounts

View File

@ -45,7 +45,7 @@ Berlin 13359, Germany
<form method="post">
<div class="row">
<div class="col-xs-12 col-sm-12">
<div class="form-group">
<div class="form-group" id="app-type-div">
<label for="appType" id="appTypeLabel">Application type</label>
<select name="app-type" id="appType" class="form-control js-example-basic-single">
<option class="app-type-option"></option>
@ -60,6 +60,7 @@ Berlin 13359, Germany
</div>
<div class="form-group">
<label for="appRedirectUrl" id="redirect_url_label">Redirect URL (Optional)</label>
<img src="/media/icons/info-square.svg" alt="i" style="padding-left: 5px;" width="16" height="16" data-toggle="tooltip" data-placement="right" title="For use in OAuth flows: This is the URL served by your App that the OBP authorisation server will redirect to with an authorization code or access token as parameters in the URL following successful authorisation.">
<input type="text" name="app-redirect-url" id="appRedirectUrl" class="form-control" aria-describedby="consumer-registration-app-redirect-url-error">
<div id = "consumer-registration-app-redirect-url-error-div" class="hide">
<span data-lift="Msg?id=consumer-registration-app-redirect-url-error"/>
@ -79,6 +80,13 @@ Berlin 13359, Germany
<span data-lift="Msg?id=consumer-registration-app-description-error"/>
</div>
</div>
<div class="form-group">
<label for="company">Company</label>
<input type="text" name="app-company" id="company" class="form-control" aria-describedby="consumer-registration-app-company-error">
<div id = "consumer-registration-app-company-error-div" class="hide">
<span data-lift="Msg?id=consumer-registration-app-company-error"/>
</div>
</div>
</div>
</div>

View File

@ -19,8 +19,8 @@ time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
font-size: 100%;
vertical-align: baseline;
font-family: Roboto-Light;
}

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-info-square" viewBox="0 0 16 16">
<path d="M14 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h12zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
<path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"/>
</svg>

After

Width:  |  Height:  |  Size: 548 B

View File

@ -31,7 +31,7 @@ function showIndicatorCookiePage(id)
{
var cookieValue = getCookieValue('we-use-cookies-indicator');
//if the value of 'we-use-cookies-indicator' is not the same as we set before (set it '1' before).
if (cookieValue!= undefined && cookieValue != '1')
if (cookieValue!= undefined && cookieValue != '1' && document.getElementById(id) != null)
{
//show the cookie page
document.getElementById(id).style.display="block";

File diff suppressed because one or more lines are too long

View File

@ -43,20 +43,20 @@
<a href="/user_mgt/sign_up" id="authorise-signup" class="btn btn-default pull-right authorise-button" tabindex="0">Register</a>
</div>
<!-- <div class ="login-or"> or Login with OpenID : </div>-->
<!-- <hr>-->
<!-- <div data-lift="OpenIDConnectSnippet.showFirstButton">-->
<!-- <div data-lift="OpenidConnectInvoke.linkButtonFirstProvider">-->
<!-- <div class="authorise-button-oidc"><a id="open-id-connect-button-1" data-lift="OpenIDConnectSnippet.getFirstButtonText" class="btn btn-danger">OIDC 1</a>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div data-lift="OpenIDConnectSnippet.showSecondButton">-->
<!-- <div data-lift="OpenidConnectInvoke.linkButtonSecondProvider">-->
<!-- <div class="authorise-button-oidc"> <a id="open-id-connect-button-2" data-lift="OpenIDConnectSnippet.getSecondButtonText" class="btn btn-danger">OIDC 2</a>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<div class ="login-or"> or Login with OpenID : </div>
<hr>
<div data-lift="OpenIDConnectSnippet.showFirstButton">
<div data-lift="OpenidConnectInvoke.linkButtonFirstProvider">
<div class="authorise-button-oidc"><a id="open-id-connect-button-1" data-lift="OpenIDConnectSnippet.getFirstButtonText" class="btn btn-danger">OIDC 1</a>
</div>
</div>
</div>
<div data-lift="OpenIDConnectSnippet.showSecondButton">
<div data-lift="OpenidConnectInvoke.linkButtonSecondProvider">
<div class="authorise-button-oidc"> <a id="open-id-connect-button-2" data-lift="OpenIDConnectSnippet.getSecondButtonText" class="btn btn-danger">OIDC 2</a>
</div>
</div>
</div>
</form>
</div>

View File

@ -45,20 +45,20 @@
<a href="/user_mgt/sign_up" id="authorise-signup" class="btn btn-default pull-right authorise-button" tabindex="0">Register</a>
</div>
<!-- <div class ="login-or"> or Login with OpenID : </div>-->
<!-- <hr>-->
<!-- <div data-lift="OpenIDConnectSnippet.showFirstButton">-->
<!-- <div data-lift="OpenidConnectInvoke.linkButtonFirstProvider">-->
<!-- <div class="authorise-button-oidc"><a id="open-id-connect-button-1" data-lift="OpenIDConnectSnippet.getFirstButtonText" class="btn btn-danger">OIDC 1</a>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div data-lift="OpenIDConnectSnippet.showSecondButton">-->
<!-- <div data-lift="OpenidConnectInvoke.linkButtonSecondProvider">-->
<!-- <div class="authorise-button-oidc"> <a id="open-id-connect-button-2" data-lift="OpenIDConnectSnippet.getSecondButtonText" class="btn btn-danger">OIDC 2</a>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<div class ="login-or"> or Login with OpenID : </div>
<hr>
<div data-lift="OpenIDConnectSnippet.showFirstButton">
<div data-lift="OpenidConnectInvoke.linkButtonFirstProvider">
<div class="authorise-button-oidc"><a id="open-id-connect-button-1" data-lift="OpenIDConnectSnippet.getFirstButtonText" class="btn btn-danger">OIDC 1</a>
</div>
</div>
</div>
<div data-lift="OpenIDConnectSnippet.showSecondButton">
<div data-lift="OpenidConnectInvoke.linkButtonSecondProvider">
<div class="authorise-button-oidc"> <a id="open-id-connect-button-2" data-lift="OpenIDConnectSnippet.getSecondButtonText" class="btn btn-danger">OIDC 2</a>
</div>
</div>
</div>
</form>
</div>
</div>

View File

@ -52,6 +52,7 @@ Berlin 13359, Germany
<script src="/media/js/cookies-consent.js"></script>
<script src="/media/js/moment-with-locales.min.js"></script>
<script src="/media/js/bootstrap-datetimepicker.min.js"></script>
<script src="/media/js/popper.min.js"></script>
</head>
<body id="page_init">
<div id="cookies-consent" data-lift="WebUI.cookieConsent">
@ -182,7 +183,6 @@ Berlin 13359, Germany
</ul>
</div>
<hr style="margin: 0">
<section id="content">
<lift:bind name="content"/>
The main content gets bound here

View File

@ -0,0 +1,112 @@
<!--
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/)
-->
<div id="register-consumer" data-lift="surround?with=default;at=content" tabindex="-1">
<div data-lift="UserInvitation.registerForm">
<div id="register-consumer-input" tabindex="-1">
<div id="register-consumer-explanation">
<h1>Complete your user invitation</h1>
<p>Please complete the information about the user invitation application below.</p>
<p>All fields are required unless marked as 'optional'</p>
</div>
<form method="post">
<style>
.form-control[disabled],
.form-control[readonly],
fieldset[disabled] .form-control {
background-color: #ffffff;
}
</style>
<div class="row">
<div class="col-xs-12 col-sm-12">
<div class="form-group">
<label for="firstName">First name</label>
<input readonly type="text" name="first-name" id="firstName" class="form-control" aria-describedby="consumer-registration-first-name-error">
</div>
<div class="form-group">
<label for="lastName">Last name</label>
<input readonly type="text" name="last-name" id="lastName" class="form-control" aria-describedby="consumer-registration-app-name-error">
</div>
<div class="form-group">
<label for="devEmail">Developer email</label>
<input readonly type="text" name="app-developer" id="devEmail" class="form-control" aria-describedby="consumer-registration-app-developer-error">
</div>
<div class="form-group">
<label for="companyName">Company</label>
<input readonly type="text" name="app-developer" id="companyName" class="form-control" aria-describedby="consumer-registration-company-error">
</div>
<div class="form-group">
<label for="country">Country</label>
<input readonly type="text" name="country" id="country" class="form-control" aria-describedby="consumer-registration-company-error">
</div>
<div class="form-group">
<label for="username">Username</label>
<input type="text" name="username" id="username" class="form-control" aria-describedby="consumer-registration-company-error">
</div>
<div class="form-group" id="privacy-conditions-div" onclick="myFunction()">
<label for="privacy"></label>
<textarea readonly rows="4" name="privacy-conditions" id="privacy" class="form-control" aria-describedby="consumer-registration-company-error"></textarea>
<input type="checkbox" class="form-check-input" id="privacy_checkbox">
<label class="form-check-label" for="privacy_checkbox">I agree to the above privacy conditions</label>
</div>
<div class="form-group">
<input onclick="disableBtn()" type="checkbox" class="form-check-input" id="marketing_info_checkbox">
<label class="marketing-info-label" for="marketing_info_checkbox">I would like to receive developer related emails from the bank</label>
</div>
<div class="form-group" id="terms-and-conditions-div" onclick="myFunction()">
<label for="terms"></label>
<textarea readonly rows="4" name="terms-and-conditions" id="terms" class="form-control" aria-describedby="consumer-registration-company-error"></textarea>
<input type="checkbox" class="form-check-input" id="terms_checkbox" >
<label id="terms_checkbox_value" class="form-check-label" for="terms_checkbox">I agree to the above Developer Terms and Conditions</label>
</div>
</div>
</div>
<script>
function myFunction() {
var checkBox = document.getElementById("terms-and-conditions-div").querySelector("input[type=checkbox]");
var checkBox2 = document.getElementById("privacy-conditions-div").querySelector("input[type=checkbox]");
var button = document.getElementById("submit-button");
if (checkBox.checked == true && checkBox2.checked == true){
button.disabled = false;
} else {
button.disabled = true;
}
}
</script>
<input disabled="true" id="submit-button" type="submit" value="Submit" class="btn btn-danger" id="register-developer-button" aria-describedby="register-consumer-errors"/>
<div id = "register-consumer-errors-div" class="hide">
<span data-lift="Msg?id=register-consumer-errors"/>
</div>
</form>
</div>
</div>
</div>

View File

@ -88,7 +88,10 @@ class directloginTest extends ServerSetup with BeforeAndAfter {
val invalidConsumerKeyHeader = ("Authorization", ("DirectLogin username=%s, " +
"password=%s, consumer_key=%s").format(USERNAME, PASSWORD, "invalid"))
val validHeader = ("Authorization", "DirectLogin username=%s, password=%s, consumer_key=%s".
val validDeprecatedHeader = ("Authorization", "DirectLogin username=%s, password=%s, consumer_key=%s".
format(USERNAME, PASSWORD, KEY))
val validHeader = ("DirectLogin", "username=%s, password=%s, consumer_key=%s".
format(USERNAME, PASSWORD, KEY))
val disabledConsumerValidHeader = ("Authorization", "DirectLogin username=%s, password=%s, consumer_key=%s".
@ -103,6 +106,7 @@ class directloginTest extends ServerSetup with BeforeAndAfter {
val invalidConsumerKeyHeaders = List(accessControlOriginHeader, invalidConsumerKeyHeader)
val validHeaders = List(accessControlOriginHeader, validHeader)
val validDeprecatedHeaders = List(accessControlOriginHeader, validDeprecatedHeader)
val disabledConsumerKeyHeaders = List(accessControlOriginHeader, disabledConsumerValidHeader)
@ -224,6 +228,61 @@ class directloginTest extends ServerSetup with BeforeAndAfter {
assertResponse(response, ErrorMessages.InvalidConsumerKey)
}
scenario("Login with correct everything! - Deprecated Header", ApiEndpoint1, ApiEndpoint2) {
//setupUserAndConsumer
Given("the app we are testing is registered and active")
Then("We should be able to find it")
//assert(registeredApplication(KEY) == true)
When("the header and credentials are good")
val request = directLoginRequest
val response = makePostRequestAdditionalHeader(request, "", validDeprecatedHeaders)
var token = "INVALID"
Then("We should get a 201 - OK and a token")
response.code should equal(201)
response.body match {
case JObject(List(JField(name, JString(value)))) =>
name should equal("token")
value.length should be > 0
token = value
case _ => fail("Expected a token")
}
// TODO Check that we are logged in. TODO Add an endpoint like /me that returns the currently logged in user.
When("when we use the token it should work")
val headerWithToken = ("Authorization", "DirectLogin token=%s".format(token))
val validHeadersWithToken = List(accessControlOriginHeader, headerWithToken)
val request2 = baseRequest / "obp" / "v2.0.0" / "my" / "accounts"
val response2 = makeGetRequest(request2, validHeadersWithToken)
Then("We should get a 200 - OK and an empty list of accounts")
response2.code should equal(200)
response2.body match {
case JArray(List()) =>
case _ => fail("Expected empty list of accounts")
}
When("when we use the token to get current user and it should work - New Style")
val requestCurrentUserNewStyle = baseRequest / "obp" / "v3.0.0" / "users" / "current"
val responseCurrentUserNewStyle = makeGetRequest(requestCurrentUserNewStyle, validHeadersWithToken)
And("We should get a 200")
responseCurrentUserNewStyle.code should equal(200)
val currentUserNewStyle = responseCurrentUserNewStyle.body.extract[UserJsonV300]
currentUserNewStyle.username shouldBe USERNAME
When("when we use the token to get current user and it should work - Old Style")
val requestCurrentUserOldStyle = baseRequest / "obp" / "v2.0.0" / "users" / "current"
val responseCurrentUserOldStyle = makeGetRequest(requestCurrentUserOldStyle, validHeadersWithToken)
And("We should get a 200")
responseCurrentUserOldStyle.code should equal(200)
val currentUserOldStyle = responseCurrentUserOldStyle.body.extract[UserJsonV300]
currentUserOldStyle.username shouldBe USERNAME
currentUserNewStyle.username shouldBe currentUserOldStyle.username
}
scenario("Login with correct everything!", ApiEndpoint1, ApiEndpoint2) {
//setupUserAndConsumer

View File

@ -6471,8 +6471,8 @@ class API1_2_1Test extends ServerSetupWithTestData with DefaultUsers with Privat
val transaction = randomTransaction(bankId, bankAccount.id, view)
When("the request is sent")
val reply = getTheCounterpartyOfOneTransaction(bankId, bankAccount.id, view, transaction.id, None)
Then("we should get a 400 code")
reply.code should equal (400)
Then("we should get a 401 code")
reply.code should equal (401)
And("we should get an error message")
reply.body.extract[ErrorMessage].message.nonEmpty should equal (true)
}
@ -6485,8 +6485,8 @@ class API1_2_1Test extends ServerSetupWithTestData with DefaultUsers with Privat
val transaction = randomTransaction(bankId, bankAccount.id, view)
When("the request is sent")
val reply = getTheCounterpartyOfOneTransaction(bankId, bankAccount.id, view, transaction.id, user3)
Then("we should get a 400 code")
reply.code should equal (400)
Then("we should get a 403 code")
reply.code should equal (403)
And("we should get an error message")
reply.body.extract[ErrorMessage].message.nonEmpty should equal (true)
}
@ -6499,10 +6499,10 @@ class API1_2_1Test extends ServerSetupWithTestData with DefaultUsers with Privat
val transaction = randomTransaction(bankId, bankAccount.id, view)
When("the request is sent")
val reply = getTheCounterpartyOfOneTransaction(bankId, bankAccount.id, randomString(5), transaction.id, user1)
Then("we should get a 400 code")
reply.code should equal (400)
Then("we should get a 403 code")
reply.code should equal (403)
And("we should get an error message")
reply.body.extract[ErrorMessage].message should equal (ViewNotFound)
reply.body.extract[ErrorMessage].message should equal (UserNoPermissionAccessView)
}
scenario("we will not get get the other bank account of a random transaction because the transaction does not exist", API1_2_1, GetTransactionAccount){

View File

@ -1,9 +1,8 @@
package code.api.v1_3_0
import java.util.Date
import code.api.util.APIUtil.OAuth._
import code.api.util.{CallContext, OBPQueryParam}
import code.api.util.{APIUtil, CallContext, OBPQueryParam}
import code.bankconnectors.Connector
import code.setup.{DefaultConnectorTestSetup, DefaultUsers, ServerSetup}
import code.util.Helper.MdcLoggable
@ -14,7 +13,7 @@ class PhysicalCardsTest extends ServerSetup with DefaultUsers with DefaultConne
def v1_3Request = baseRequest / "obp" / "v1.3.0"
lazy val bank = createBank("a-bank")
lazy val bank = createBank(APIUtil.defaultBankId)
lazy val accId = "a-account"
lazy val accountCurrency = "EUR"
lazy val account = createAccount(bank.bankId, AccountId(accId), accountCurrency)
@ -94,6 +93,7 @@ class PhysicalCardsTest extends ServerSetup with DefaultUsers with DefaultConne
super.afterAll()
//reset the default connector
Connector.connector.default.set(Connector.buildOne)
wipeTestData()
}
feature("Getting details of physical cards") {

View File

@ -51,9 +51,9 @@ class AccountTest extends V300ServerSetup {
val requestGet = (v3_0Request / "banks" / "BANK_ID" / "firehose" / "accounts" / "views" / "VIEW_ID").GET <@ (user1)
val responseGet = makeGetRequest(requestGet)
And("We should get a 403")
responseGet.code should equal(403)
responseGet.body.extract[ErrorMessage].message should equal(AccountFirehoseNotAllowedOnThisInstance +" or " + UserHasMissingRoles + CanUseAccountFirehoseAtAnyBank )
And("We should get a 400")
responseGet.code should equal(400)
responseGet.body.extract[ErrorMessage].message contains AccountFirehoseNotAllowedOnThisInstance should be (true)
}}

View File

@ -0,0 +1,119 @@
package code.api.v3_0_0
import code.api.util.APIUtil.OAuth._
import code.api.util.ApiRole
import code.api.util.ApiRole.CanUseAccountFirehoseAtAnyBank
import code.api.util.ErrorMessages.AccountFirehoseNotAllowedOnThisInstance
import code.api.v3_0_0.OBPAPI3_0_0.Implementations3_0_0
import code.entitlement.Entitlement
import code.setup.PropsReset
import com.github.dwickern.macros.NameOf.nameOf
import com.openbankproject.commons.util.ApiVersion
import org.scalatest.Tag
class FirehoseTest extends V300ServerSetup with PropsReset{
/**
* Test tags
* Example: To run tests with tag "getPermissions":
* mvn test -D tagsToInclude
*
* This is made possible by the scalatest maven plugin
*/
object VersionOfApi extends Tag(ApiVersion.v3_0_0.toString)
object ApiEndpoint2 extends Tag(nameOf(Implementations3_0_0.getFirehoseAccountsAtOneBank))
object ApiEndpoint4 extends Tag(nameOf(Implementations3_0_0.getFirehoseTransactionsForBankAccount))
feature(s"test ${ApiEndpoint2}") {
scenario("We will call the endpoint with user credentials", VersionOfApi, ApiEndpoint2) {
setPropsValues("allow_account_firehose" -> "true")
setPropsValues("enable.force_error"->"true")
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.CanUseAccountFirehoseAtAnyBank.toString)
When("We send the request")
val request = (v3_0Request / "banks" / testBankId1.value /"firehose" / "accounts" / "views"/"owner").GET <@ (user1)
val response = makeGetRequest(request)
Then("We should get a 200 and check the response body")
response.code should equal(200)
response.body.extract[ModeratedCoreAccountsJsonV300]
}
scenario("We will call the endpoint with user credentials, props alias", VersionOfApi, ApiEndpoint2) {
setPropsValues("allow_firehose_views" -> "true")
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.CanUseAccountFirehoseAtAnyBank.toString)
When("We send the request")
val request = (v3_0Request / "banks" / testBankId1.value /"firehose" / "accounts" / "views"/"owner").GET <@ (user1)
val response = makeGetRequest(request)
Then("We should get a 200 and check the response body")
response.code should equal(200)
response.body.extract[ModeratedCoreAccountsJsonV300]
}
scenario("We will call the endpoint missing role", VersionOfApi, ApiEndpoint2) {
setPropsValues("allow_account_firehose" -> "true")
When("We send the request")
val request = (v3_0Request / "banks" / testBankId1.value / "firehose" / "accounts" / "views" / "owner").GET <@ (user1)
val response = makeGetRequest(request)
Then("We should get a 403 and check the response body")
response.code should equal(403)
response.body.toString contains (CanUseAccountFirehoseAtAnyBank.toString()) should be(true)
}
scenario("We will call the endpoint missing props ", VersionOfApi, ApiEndpoint2) {
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.CanUseAccountFirehoseAtAnyBank.toString)
When("We send the request")
val request = (v3_0Request / "banks" / testBankId1.value /"firehose" / "accounts" / "views"/"owner").GET <@ (user1)
val response = makeGetRequest(request)
Then("We should get a 400 and check the response body")
response.code should equal(400)
response.body.toString contains (AccountFirehoseNotAllowedOnThisInstance) should be (true)
}
}
feature(s"test ${ApiEndpoint4.name}") {
scenario("We will call the endpoint with user credentials", VersionOfApi, ApiEndpoint4) {
setPropsValues("allow_account_firehose" -> "true")
setPropsValues("enable.force_error"->"true")
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.CanUseAccountFirehoseAtAnyBank.toString)
When("We send the request")
val request = (v3_0Request / "banks" / testBankId1.value /"firehose" / "accounts" / testAccountId1.value / "views"/"owner"/"transactions").GET <@ (user1)
val response = makeGetRequest(request)
Then("We should get a 200 and check the response body")
response.code should equal(200)
response.body.extract[ModeratedCoreAccountsJsonV300]
}
scenario("We will call the endpoint with user credentials, props alias", VersionOfApi, ApiEndpoint4) {
setPropsValues("allow_firehose_views" -> "true")
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.CanUseAccountFirehoseAtAnyBank.toString)
When("We send the request")
val request = (v3_0Request / "banks" / testBankId1.value /"firehose" / "accounts" / testAccountId1.value / "views"/"owner"/"transactions").GET <@ (user1)
val response = makeGetRequest(request)
Then("We should get a 200 and check the response body")
response.code should equal(200)
response.body.extract[ModeratedCoreAccountsJsonV300]
}
scenario("We will call the endpoint missing role", VersionOfApi, ApiEndpoint4) {
setPropsValues("allow_account_firehose" -> "true")
When("We send the request")
val request = (v3_0Request / "banks" / testBankId1.value / "firehose" / "accounts" / testAccountId1.value /"views" / "owner"/"transactions").GET <@ (user1)
val response = makeGetRequest(request)
Then("We should get a 403 and check the response body")
response.code should equal(403)
response.body.toString contains (CanUseAccountFirehoseAtAnyBank.toString()) should be(true)
}
scenario("We will call the endpoint missing props ", VersionOfApi, ApiEndpoint4) {
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.CanUseAccountFirehoseAtAnyBank.toString)
When("We send the request")
val request = (v3_0Request / "banks" / testBankId1.value /"firehose" / "accounts" / testAccountId1.value / "views"/"owner"/"transactions").GET <@ (user1)
val response = makeGetRequest(request)
Then("We should get a 400 and check the response body")
response.code should equal(400)
response.body.toString contains (AccountFirehoseNotAllowedOnThisInstance) should be (true)
}
}
}

View File

@ -401,9 +401,9 @@ class TransactionsTest extends V300ServerSetup {
val requestGet = (v3_0Request / "banks" / "BANK_ID" / "firehose" / "accounts" / "AccountId(accountId)" / "views" / "ViewId(viewId)" / "transactions").GET <@ (user1)
val responseGet = makeGetRequest(requestGet)
And("We should get a 403")
responseGet.code should equal(403)
responseGet.body.extract[ErrorMessage].message should equal(AccountFirehoseNotAllowedOnThisInstance +" or " + UserHasMissingRoles + CanUseAccountFirehoseAtAnyBank )
And("We should get a 400")
responseGet.code should equal(400)
responseGet.body.extract[ErrorMessage].message contains AccountFirehoseNotAllowedOnThisInstance should be (true)
}}

View File

@ -131,7 +131,10 @@ class AccountAttributeTest extends V310ServerSetup {
response310.code should equal(403)
val errorMessageText = UserHasMissingRoles + canCreateAccountAttributeAtOneBank
And("error should be " + errorMessageText)
response310.body.extract[ErrorMessage].message should equal (errorMessageText)
val errorMessage = response310.body.extract[ErrorMessage].message
errorMessage contains (UserHasMissingRoles) should be (true)
errorMessage contains (canCreateAccountAttributeAtOneBank.toString()) should be (true)
}
scenario("We will call the Create endpoint but wrong `type` ", ApiEndpoint1, VersionOfApi) {
When(s"We make a request $VersionOfApi")
@ -163,7 +166,9 @@ class AccountAttributeTest extends V310ServerSetup {
response310.code should equal(403)
val errorMessageText = UserHasMissingRoles + canUpdateAccountAttribute
And("error should be " + errorMessageText)
response310.body.extract[ErrorMessage].message should equal (errorMessageText)
val errorMessage = response310.body.extract[ErrorMessage].message
errorMessage contains (UserHasMissingRoles) should be (true)
errorMessage contains (canUpdateAccountAttribute.toString()) should be (true)
}
}

View File

@ -88,7 +88,9 @@ class CustomerAddressTest extends V310ServerSetup {
Then("We should get a 403")
response310.code should equal(403)
And("error should be " + UserHasMissingRoles + CanCreateCustomerAddress)
response310.body.extract[ErrorMessage].message should equal (UserHasMissingRoles + CanCreateCustomerAddress)
val errorMessage = response310.body.extract[ErrorMessage].message
errorMessage contains (UserHasMissingRoles) should be (true)
errorMessage contains (CanCreateCustomerAddress.toString()) should be (true)
}
scenario("We will call the Get endpoint without a user credentials", ApiEndpoint2, VersionOfApi) {
@ -107,7 +109,9 @@ class CustomerAddressTest extends V310ServerSetup {
Then("We should get a 403")
response310.code should equal(403)
And("error should be " + UserHasMissingRoles + CanGetCustomerAddress)
response310.body.extract[ErrorMessage].message should equal (UserHasMissingRoles + CanGetCustomerAddress)
val errorMessage = response310.body.extract[ErrorMessage].message
errorMessage contains (UserHasMissingRoles) should be (true)
errorMessage contains (CanGetCustomerAddress.toString()) should be (true)
}
scenario("We will call the Delete endpoint without a user credentials", ApiEndpoint3, VersionOfApi) {
@ -126,7 +130,9 @@ class CustomerAddressTest extends V310ServerSetup {
Then("We should get a 403")
response310.code should equal(403)
And("error should be " + UserHasMissingRoles + CanDeleteCustomerAddress)
response310.body.extract[ErrorMessage].message should equal (UserHasMissingRoles + CanDeleteCustomerAddress)
val errorMessage = response310.body.extract[ErrorMessage].message
errorMessage contains (UserHasMissingRoles) should be (true)
errorMessage contains (CanDeleteCustomerAddress.toString()) should be (true)
}
scenario("We will call the Add, Get and Delete endpoints with user credentials and role", ApiEndpoint1, ApiEndpoint2, ApiEndpoint3, ApiEndpoint4, VersionOfApi) {

View File

@ -28,18 +28,21 @@ package code.api.v3_1_0
import com.openbankproject.commons.model.ErrorMessage
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON
import code.api.util.APIUtil.OAuth._
import code.api.util.ApiRole
import code.api.util.ApiRole._
import com.openbankproject.commons.util.ApiVersion
import code.api.util.ErrorMessages._
import code.api.v3_0_0.ModeratedCoreAccountsJsonV300
import code.api.v3_1_0.OBPAPI3_1_0.Implementations3_1_0
import code.customer.CustomerX
import code.entitlement.Entitlement
import code.setup.PropsReset
import code.usercustomerlinks.UserCustomerLink
import com.github.dwickern.macros.NameOf.nameOf
import net.liftweb.json.Serialization.write
import org.scalatest.Tag
class CustomerTest extends V310ServerSetup {
class CustomerTest extends V310ServerSetup with PropsReset{
override def beforeAll(): Unit = {
super.beforeAll()
@ -70,6 +73,7 @@ class CustomerTest extends V310ServerSetup {
object ApiEndpoint9 extends Tag(nameOf(Implementations3_1_0.updateCustomerBranch))
object ApiEndpoint10 extends Tag(nameOf(Implementations3_1_0.updateCustomerData))
object ApiEndpoint11 extends Tag(nameOf(Implementations3_1_0.updateCustomerNumber))
object ApiEndpoint12 extends Tag(nameOf(Implementations3_1_0.getFirehoseCustomers))
val customerNumberJson = PostCustomerNumberJsonV310(customer_number = "123")
val postCustomerJson = SwaggerDefinitionsJSON.postCustomerJsonV310
@ -104,7 +108,9 @@ class CustomerTest extends V310ServerSetup {
response310.code should equal(403)
val errorMsg = UserHasMissingRoles + canCreateCustomer + " or " + canCreateCustomerAtAnyBank
And("error should be " + errorMsg)
response310.body.extract[ErrorMessage].message should equal (errorMsg)
val errorMessage = response310.body.extract[ErrorMessage].message
errorMessage contains (UserHasMissingRoles) should be (true)
errorMessage contains (canCreateCustomerAtAnyBank.toString()) should be (true)
}
scenario("We will call the endpoint with a user credentials and a proper role", ApiEndpoint3, VersionOfApi) {
Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanCreateCustomer.toString)
@ -137,7 +143,9 @@ class CustomerTest extends V310ServerSetup {
Then("We should get a 403")
response310.code should equal(403)
And("error should be " + UserHasMissingRoles + CanGetCustomer)
response310.body.extract[ErrorMessage].message should equal (UserHasMissingRoles + CanGetCustomer)
val errorMessage = response310.body.extract[ErrorMessage].message
errorMessage contains (UserHasMissingRoles) should be (true)
errorMessage contains (CanGetCustomer.toString()) should be (true)
}
scenario("We will call the endpoint with the proper Role " + canGetCustomer, ApiEndpoint1, VersionOfApi) {
Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanGetCustomer.toString)
@ -171,7 +179,9 @@ class CustomerTest extends V310ServerSetup {
Then("We should get a 403")
response310.code should equal(403)
And("error should be " + UserHasMissingRoles + CanGetCustomer)
response310.body.extract[ErrorMessage].message should equal (UserHasMissingRoles + CanGetCustomer)
val errorMessage = response310.body.extract[ErrorMessage].message
errorMessage contains (UserHasMissingRoles) should be (true)
errorMessage contains (CanGetCustomer.toString()) should be (true)
}
scenario("We will call the endpoint with the proper Role " + canGetCustomer, ApiEndpoint2, VersionOfApi) {
Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanGetCustomer.toString)
@ -205,7 +215,9 @@ class CustomerTest extends V310ServerSetup {
response310.code should equal(403)
val errorMsg = UserHasMissingRoles + canUpdateCustomerEmail
And("error should be " + errorMsg)
response310.body.extract[ErrorMessage].message should equal (errorMsg)
val errorMessage = response310.body.extract[ErrorMessage].message
errorMessage contains (UserHasMissingRoles) should be (true)
errorMessage contains (canUpdateCustomerEmail.toString()) should be (true)
}
scenario("We will call the endpoint with user credentials and the proper role", ApiEndpoint3, ApiEndpoint4, VersionOfApi) {
Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanCreateCustomer.toString)
@ -248,7 +260,9 @@ class CustomerTest extends V310ServerSetup {
response310.code should equal(403)
val errorMsg = UserHasMissingRoles + canUpdateCustomerMobilePhoneNumber
And("error should be " + errorMsg)
response310.body.extract[ErrorMessage].message should equal (errorMsg)
val errorMessage = response310.body.extract[ErrorMessage].message
errorMessage contains (UserHasMissingRoles) should be (true)
errorMessage contains (canUpdateCustomerMobilePhoneNumber.toString()) should be (true)
}
scenario("We will call the endpoint with user credentials and the proper role", ApiEndpoint5, VersionOfApi) {
Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanCreateCustomer.toString)
@ -292,7 +306,9 @@ class CustomerTest extends V310ServerSetup {
response310.code should equal(403)
val errorMsg = UserHasMissingRoles + canUpdateCustomerIdentity
And("error should be " + errorMsg)
response310.body.extract[ErrorMessage].message should equal (errorMsg)
val errorMessage = response310.body.extract[ErrorMessage].message
errorMessage contains (UserHasMissingRoles) should be (true)
errorMessage contains (canUpdateCustomerIdentity.toString()) should be (true)
}
scenario("We will call the endpoint with user credentials and the proper role", ApiEndpoint6, VersionOfApi) {
Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanCreateCustomer.toString)
@ -339,7 +355,9 @@ class CustomerTest extends V310ServerSetup {
response310.code should equal(403)
val errorMsg = UserHasMissingRoles + canUpdateCustomerCreditLimit
And("error should be " + errorMsg)
response310.body.extract[ErrorMessage].message should equal (errorMsg)
val errorMessage = response310.body.extract[ErrorMessage].message
errorMessage contains (UserHasMissingRoles) should be (true)
errorMessage contains (canUpdateCustomerCreditLimit.toString()) should be (true)
}
scenario("We will call the endpoint with user credentials and the proper role", ApiEndpoint7, VersionOfApi) {
Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanCreateCustomer.toString)
@ -384,7 +402,9 @@ class CustomerTest extends V310ServerSetup {
response310.code should equal(403)
val errorMsg = UserHasMissingRoles + canUpdateCustomerCreditRatingAndSource
And("error should be " + errorMsg)
response310.body.extract[ErrorMessage].message should equal (errorMsg)
val errorMessage = response310.body.extract[ErrorMessage].message
errorMessage contains (UserHasMissingRoles) should be (true)
errorMessage contains (canUpdateCustomerCreditRatingAndSource.toString()) should be (true)
}
scenario("We will call the endpoint with user credentials and the proper role", ApiEndpoint8, VersionOfApi) {
Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanCreateCustomer.toString)
@ -429,7 +449,9 @@ class CustomerTest extends V310ServerSetup {
response310.code should equal(403)
val errorMsg = UserHasMissingRoles + canUpdateCustomerBranch
And("error should be " + errorMsg)
response310.body.extract[ErrorMessage].message should equal (errorMsg)
val errorMessage = response310.body.extract[ErrorMessage].message
errorMessage contains (UserHasMissingRoles) should be (true)
errorMessage contains (canUpdateCustomerBranch.toString()) should be (true)
}
scenario("We will call the endpoint with user credentials and the proper role", ApiEndpoint9, VersionOfApi) {
Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanCreateCustomer.toString)
@ -473,7 +495,9 @@ class CustomerTest extends V310ServerSetup {
response310.code should equal(403)
val errorMsg = UserHasMissingRoles + canUpdateCustomerData
And("error should be " + errorMsg)
response310.body.extract[ErrorMessage].message should equal (errorMsg)
val errorMessage = response310.body.extract[ErrorMessage].message
errorMessage contains (UserHasMissingRoles) should be (true)
errorMessage contains (canUpdateCustomerData.toString()) should be (true)
}
scenario("We will call the endpoint with user credentials and the proper role", ApiEndpoint10, VersionOfApi) {
Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanCreateCustomer.toString)
@ -521,7 +545,9 @@ class CustomerTest extends V310ServerSetup {
response310.code should equal(403)
val errorMsg = UserHasMissingRoles + canUpdateCustomerNumber
And("error should be " + errorMsg)
response310.body.extract[ErrorMessage].message should equal (errorMsg)
val errorMessage = response310.body.extract[ErrorMessage].message
errorMessage contains (UserHasMissingRoles) should be (true)
errorMessage contains (canUpdateCustomerNumber.toString()) should be (true)
}
scenario("We will call the endpoint with user credentials and the proper role", ApiEndpoint3, ApiEndpoint11, VersionOfApi) {
Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanCreateCustomer.toString)
@ -560,4 +586,61 @@ class CustomerTest extends V310ServerSetup {
}
}
feature(s" $ApiEndpoint12- Authorized access") {
//first we create the customers:
Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanCreateCustomer.toString)
When("We make a request v3.1.0")
val request310 = (v3_1_0_Request / "banks" / bankId / "customers").POST <@(user1)
val response310 = makePostRequest(request310, write(postCustomerJson))
Then("We should get a 201")
response310.code should equal(201)
scenario("We will call the endpoint with user credentials", VersionOfApi, ApiEndpoint4) {
setPropsValues("allow_customer_firehose" -> "true")
setPropsValues("enable.force_error"->"true")
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.CanUseCustomerFirehoseAtAnyBank.toString)
When("We send the request")
val request = (v3_1_0_Request / "banks" / testBankId1.value /"firehose" / "customers" ).GET <@ (user1)
val response = makeGetRequest(request)
Then("We should get a 200 and check the response body")
response.code should equal(200)
response.body.extract[ModeratedCoreAccountsJsonV300]
}
scenario("We will call the endpoint with user credentials, props alias", VersionOfApi, ApiEndpoint4) {
setPropsValues("allow_firehose_views" -> "true")
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.CanUseCustomerFirehoseAtAnyBank.toString)
When("We send the request")
val request = (v3_1_0_Request / "banks" / testBankId1.value /"firehose" / "customers" ).GET <@ (user1)
val response = makeGetRequest(request)
Then("We should get a 200 and check the response body")
response.code should equal(200)
response.body.extract[ModeratedCoreAccountsJsonV300]
}
scenario("We will call the endpoint missing role", VersionOfApi, ApiEndpoint4) {
setPropsValues("allow_customer_firehose" -> "true")
When("We send the request")
val request = (v3_1_0_Request / "banks" / testBankId1.value / "firehose" / "customers").GET <@ (user1)
val response = makeGetRequest(request)
Then("We should get a 403 and check the response body")
response.code should equal(403)
response.body.toString contains (CanUseCustomerFirehoseAtAnyBank.toString()) should be(true)
}
scenario("We will call the endpoint missing props ", VersionOfApi, ApiEndpoint4) {
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.CanUseCustomerFirehoseAtAnyBank.toString)
When("We send the request")
val request = (v3_1_0_Request / "banks" / testBankId1.value /"firehose" / "customers" ).GET <@ (user1)
val response = makeGetRequest(request)
Then("We should get a 400 and check the response body")
response.code should equal(400)
response.body.toString contains (AccountFirehoseNotAllowedOnThisInstance) should be (true)
}
}
}

View File

@ -144,7 +144,9 @@ class ProductAttributeTest extends V310ServerSetup {
response310.code should equal(403)
val createProductEntitlementsRequiredText = UserHasMissingRoles + canCreateProductAttribute
And("error should be " + createProductEntitlementsRequiredText)
response310.body.extract[ErrorMessage].message should equal (createProductEntitlementsRequiredText)
val errorMessage = response310.body.extract[ErrorMessage].message
errorMessage contains (UserHasMissingRoles) should be (true)
errorMessage contains (createProductEntitlementsRequiredText.toString()) should be (true)
}
scenario("We will call the Create endpoint but wrong `type` ", ApiEndpoint1, VersionOfApi) {
When("We make a request v3.1.0")
@ -176,7 +178,9 @@ class ProductAttributeTest extends V310ServerSetup {
response310.code should equal(403)
val createProductEntitlementsRequiredText = UserHasMissingRoles + canGetProductAttribute
And("error should be " + createProductEntitlementsRequiredText)
response310.body.extract[ErrorMessage].message should equal (createProductEntitlementsRequiredText)
val errorMessage = response310.body.extract[ErrorMessage].message
errorMessage contains (UserHasMissingRoles) should be (true)
errorMessage contains (canGetProductAttribute.toString()) should be (true)
}
}
@ -198,7 +202,9 @@ class ProductAttributeTest extends V310ServerSetup {
response310.code should equal(403)
val createProductEntitlementsRequiredText = UserHasMissingRoles + canUpdateProductAttribute
And("error should be " + createProductEntitlementsRequiredText)
response310.body.extract[ErrorMessage].message should equal (createProductEntitlementsRequiredText)
val errorMessage = response310.body.extract[ErrorMessage].message
errorMessage contains (UserHasMissingRoles) should be (true)
errorMessage contains (createProductEntitlementsRequiredText.toString()) should be (true)
}
}
@ -220,7 +226,9 @@ class ProductAttributeTest extends V310ServerSetup {
response310.code should equal(403)
val createProductEntitlementsRequiredText = UserHasMissingRoles + canDeleteProductAttribute
And("error should be " + createProductEntitlementsRequiredText)
response310.body.extract[ErrorMessage].message should equal (createProductEntitlementsRequiredText)
val errorMessage = response310.body.extract[ErrorMessage].message
errorMessage contains (UserHasMissingRoles) should be (true)
errorMessage contains (canDeleteProductAttribute.toString()) should be (true)
}
}

Some files were not shown because too many files have changed in this diff Show More