2013-12-23 11:39:18 +00:00
/* *
2016-05-13 16:38:40 +00:00
* Open Bank Project - API
2016-11-06 10:48:59 +00:00
* Copyright ( C ) 2011 - 2016 , TESOBE Ltd
2016-05-13 16:38:40 +00:00
**
* 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
2016-11-06 10:48:59 +00:00
* TESOBE Ltd
2016-05-13 16:38:40 +00:00
* Osloerstrasse 16 / 17
* Berlin 13359 , Germany
**
* This product includes software developed at
* TESOBE ( http : //www.tesobe.com/)
* by
* Simon Redfern : simon AT tesobe DOT com
* Stefan Bethge : stefan AT tesobe DOT com
* Everett Sochowski : everett AT tesobe DOT com
* Ayoub Benali : ayoub AT tesobe DOT com
*
2013-12-23 11:39:18 +00:00
*/
2014-09-10 15:13:33 +00:00
package code.api.util
2013-06-14 08:35:34 +00:00
2016-12-22 10:32:11 +00:00
import java.io.InputStream
2017-09-13 08:40:58 +00:00
import java.nio.charset.Charset
2017-04-10 07:20:10 +00:00
import java.text.SimpleDateFormat
2017-10-23 15:16:34 +00:00
import java.util. { Date , UUID }
2017-06-29 23:30:02 +00:00
2016-05-13 16:38:40 +00:00
import code.api.Constant._
2017-10-23 15:16:34 +00:00
import code.api.DirectLogin
2016-06-06 19:29:18 +00:00
import code.api.OAuthHandshake._
2017-09-13 08:40:58 +00:00
import code.api._
import code.api.util.APIUtil.ApiVersion.ApiVersion
2017-06-29 23:30:02 +00:00
import code.api.v1_2.ErrorMessage
2017-05-04 13:41:42 +00:00
import code.bankconnectors._
2017-03-14 09:26:32 +00:00
import code.consumer.Consumers
2016-07-20 08:40:12 +00:00
import code.customer.Customer
2016-06-17 09:19:05 +00:00
import code.entitlement.Entitlement
2017-07-07 10:34:58 +00:00
import code.metrics. { APIMetrics , ConnectorMetricsProvider }
2016-02-29 23:26:47 +00:00
import code.model._
2017-10-05 22:02:08 +00:00
import code.model.dataAccess.ResourceUserCaseClass
2017-04-19 13:21:28 +00:00
import code.sanitycheck.SanityCheck
2017-06-29 23:30:02 +00:00
import code.util.Helper. { MdcLoggable , SILENCE_IS_GOLDEN }
2016-05-13 16:38:40 +00:00
import dispatch.url
2017-09-07 13:13:47 +00:00
import net.liftweb.actor.LAFuture
2016-06-06 19:29:18 +00:00
import net.liftweb.common. { Empty , _ }
2017-06-29 23:30:02 +00:00
import net.liftweb.http._
2014-09-10 15:13:33 +00:00
import net.liftweb.http.js.JE.JsRaw
import net.liftweb.http.js.JsExp
2017-09-08 07:35:26 +00:00
import net.liftweb.http.rest.RestContinuation
2017-08-16 14:51:40 +00:00
import net.liftweb.json.JsonAST. { JField , JValue }
2017-09-15 14:11:04 +00:00
import net.liftweb.json.JsonParser.ParseException
import net.liftweb.json. { Extraction , MappingException , parse }
2013-06-14 08:35:34 +00:00
import net.liftweb.util.Helpers._
2017-08-16 14:51:40 +00:00
import net.liftweb.util. { Props , StringHelpers }
2017-06-29 23:30:02 +00:00
2016-05-13 16:38:40 +00:00
import scala.collection.JavaConverters._
2017-10-05 22:02:08 +00:00
import scala.collection.immutable.Nil
2017-06-29 23:30:02 +00:00
import scala.collection.mutable.ArrayBuffer
2017-04-26 09:17:10 +00:00
import scala.concurrent.Future
2017-06-29 23:30:02 +00:00
import scala.xml. { Elem , XML }
2017-10-05 22:02:08 +00:00
import scala.concurrent.ExecutionContext.Implicits.global
2017-04-26 09:17:10 +00:00
2016-02-21 10:55:02 +00:00
object ErrorMessages {
2017-05-04 13:41:42 +00:00
import code.api.util.APIUtil._
2016-02-21 09:50:53 +00:00
2017-11-05 16:14:00 +00:00
val dateformat = new java . text . SimpleDateFormat ( "yyyy-MM-dd" )
2017-06-25 07:16:52 +00:00
// Notes to developers. Please:
// 1) Follow (the existing) grouping of messages
// 2) Stick to existing terminology e.g. use "invalid" or "incorrect" rather than "wrong"
// 3) Before adding a new message, check that you can't use one that already exists.
// 4) Use Proper Names for OBP Resources.
// 5) Don't use abbreviations.
2017-09-06 14:31:17 +00:00
// 6) Any messaage defined here should be considered "fair game" to return over the API. Thus:
// 7) Since the existance of "OBP-..." in a message is used to determine if we should display to a user if display_internal_errors=false, do *not* concatenate internal or core banking system error messages to these strings.
2017-06-25 07:16:52 +00:00
// Infrastructure / config level messages (OBP-00XXX)
2016-03-05 07:04:41 +00:00
val HostnameNotSpecified = "OBP-00001: Hostname not specified. Could not get hostname from Props. Please edit your props file. Here are some example settings: hostname=http://127.0.0.1:8080 or hostname=https://www.example.com"
2017-04-28 09:24:10 +00:00
val DataImportDisabled = "OBP-00002: Data import is disabled for this API instance."
val TransactionDisabled = "OBP-00003: Transaction Requests is disabled in this API instance."
2017-06-25 07:16:52 +00:00
@deprecated ( "This is too generic" , "25-06-2017" )
val ServerAddDataError = "OBP-00004: Server error: could not add message" // Do not use this
val PublicViewsNotAllowedOnThisInstance = "OBP-00005: Public views not allowed on this instance. Please set allow_public_views = true in props files. "
2017-07-14 07:03:55 +00:00
val RemoteDataSecretMatchError = "OBP-00006: Remote data secret cannot be matched! Check OBP-API and OBP-Storage Props values for remotedata.hostname, remotedata.port and remotedata.secret." // (was OBP-20021)
val RemoteDataSecretObtainError = "OBP-00007: Remote data secret cannot be obtained! Check OBP-API and OBP-Storage Props values for remotedata.hostname, remotedata.port and remotedata.secret." // (was OBP-20022)
2017-06-25 07:16:52 +00:00
2017-09-12 16:47:44 +00:00
val ApiVersionNotSupported = "OBP-00008: The API version you called is not enabled on this server. Please contact your API administrator or use another version."
2017-06-25 07:16:52 +00:00
// General messages (OBP-10XXX)
2016-03-12 16:36:03 +00:00
val InvalidJsonFormat = "OBP-10001: Incorrect json format."
val InvalidNumber = "OBP-10002: Invalid Number. Could not convert value to a number."
2016-12-08 10:54:28 +00:00
val InvalidISOCurrencyCode = "OBP-10003: Invalid Currency Value. It should be three letters ISO Currency Code. "
2017-01-25 20:13:25 +00:00
val FXCurrencyCodeCombinationsNotSupported = "OBP-10004: ISO Currency code combination not supported for FX. Please modify the FROM_CURRENCY_CODE or TO_CURRENCY_CODE. "
2017-03-06 22:46:58 +00:00
val InvalidDateFormat = "OBP-10005: Invalid Date Format. Could not convert value to a Date."
2017-06-25 07:16:52 +00:00
val InvalidInputJsonFormat = "OBP-10006: Invalid input JSON format." // Why do we need this as well as InvalidJsonFormat?
val IncorrectRoleName = "OBP-10007: Incorrect Role name: "
2017-08-03 17:03:44 +00:00
val CouldNotTransformJsonToInternalModel = "OBP-10008: Could not transform Json to internal model."
val CountNotSaveOrUpdateResource = "OBP-10009: Could not save or update resource."
2017-08-22 12:59:40 +00:00
val NotImplemented = "OBP-10010: Not Implemented "
2017-06-25 07:16:52 +00:00
// General Sort and Paging
val FilterSortDirectionError = "OBP-10023: obp_sort_direction parameter can only take two values: DESC or ASC!" // was OBP-20023
val FilterOffersetError = "OBP-10024: wrong value for obp_offset parameter. Please send a positive integer (=>0)!" // was OBP-20024
val FilterLimitError = "OBP-10025: wrong value for obp_limit parameter. Please send a positive integer (=>1)!" // was OBP-20025
val FilterDateFormatError = s" OBP-10026: Failed to parse date string. Please use this format ${ defaultFilterFormat . toPattern } or that one ${ fallBackFilterFormat . toPattern } ! " // OBP-20026
2016-02-21 09:50:53 +00:00
2017-09-12 16:47:44 +00:00
val InvalidApiVersionString = "OBP-00027: Invalid API Version string. We could not find the version specified."
2016-02-21 09:50:53 +00:00
2016-05-24 19:35:52 +00:00
2017-06-25 07:16:52 +00:00
// Authentication / Authorisation / User messages (OBP-20XXX)
val UserNotLoggedIn = "OBP-20001: User not logged in. Authentication is required!"
2016-03-14 09:14:42 +00:00
val DirectLoginMissingParameters = "OBP-20002: These DirectLogin parameters are missing: "
val DirectLoginInvalidToken = "OBP-20003: This DirectLogin token is invalid or expired: "
2016-03-16 04:15:00 +00:00
val InvalidLoginCredentials = "OBP-20004: Invalid login credentials. Check username/password."
2017-01-22 15:42:59 +00:00
val UserNotFoundById = "OBP-20005: User not found. Please specify a valid value for USER_ID."
2017-06-29 13:32:01 +00:00
val UserHasMissingRoles = "OBP-20006: User is missing one or more roles: "
2016-06-28 08:22:54 +00:00
val UserNotFoundByEmail = "OBP-20007: User not found by email."
2016-03-14 09:14:42 +00:00
2016-07-22 16:20:02 +00:00
val InvalidConsumerKey = "OBP-20008: Invalid Consumer Key."
2016-11-01 15:31:47 +00:00
val InvalidConsumerCredentials = "OBP-20009: Invalid consumer credentials"
2017-09-12 16:47:44 +00:00
2016-12-22 12:14:42 +00:00
val InvalidValueLength = "OBP-20010: Value too long"
val InvalidValueCharacters = "OBP-20011: Value contains invalid characters"
val InvalidDirectLoginParameters = "OBP-20012: Invalid direct login parameters"
2016-12-20 16:27:50 +00:00
2016-12-31 15:16:24 +00:00
val UsernameHasBeenLocked = "OBP-20013: The account has been locked, please contact administrator !"
2016-12-30 23:12:12 +00:00
2017-01-26 10:17:53 +00:00
val InvalidConsumerId = "OBP-20014: Invalid Consumer ID. Please specify a valid value for CONSUMER_ID."
2017-09-12 16:47:44 +00:00
2017-01-26 10:17:53 +00:00
val UserNoPermissionUpdateConsumer = "OBP-20015: Only the developer that created the consumer key should be able to edit it, please login with the right user."
2017-02-01 13:41:07 +00:00
2017-02-08 13:45:22 +00:00
val UnexpectedErrorDuringLogin = "OBP-20016: An unexpected login error occurred. Please try again."
2017-02-20 08:28:26 +00:00
2017-04-27 17:11:31 +00:00
val UserNoPermissionAccessView = "OBP-20017: Current user does not have access to the view. Please specify a valid value for VIEW_ID."
2017-02-20 08:28:26 +00:00
2017-06-25 07:16:52 +00:00
2017-02-20 08:28:26 +00:00
val InvalidInternalRedirectUrl = "OBP-20018: Login failed, invalid internal redirectUrl."
2017-10-17 13:16:55 +00:00
val UserNoOwnerView = "OBP-20019: User does not have access to owner view. "
2017-10-31 16:25:39 +00:00
val InvalidCustomViewFormat = "OBP-20020: View name must start with `_`. eg: _work, _life "
val SystemViewsCanNotBeModified = "OBP-20021: System Views can not be modified. Only the created views can be modified."
2017-04-19 13:21:28 +00:00
2017-06-25 07:16:52 +00:00
2017-06-13 12:09:02 +00:00
val UserNotFoundByUsername = "OBP-20027: User not found by username."
2017-07-13 06:16:19 +00:00
val GatewayLoginMissingParameters = "OBP-20028: These GatewayLogin parameters are missing: "
2017-07-17 08:42:22 +00:00
val GatewayLoginUnknownError = "OBP-20029: Unknown Gateway login error."
val GatewayLoginHostPropertyMissing = "OBP-20030: Property gateway.host is not defined."
val GatewayLoginWhiteListAddresses = "OBP-20031: Gateway login can be done only from allowed addresses."
2017-08-30 11:10:49 +00:00
val GatewayLoginJwtTokenIsNotValid = "OBP-20040: The JWT is corrupted/changed during a transport."
val GatewayLoginCannotExtractJwtToken = "OBP-20040: Header, Payload and Signature cannot be extracted from the JWT."
2017-10-06 10:30:25 +00:00
val GatewayLoginNoNeedToCallCbs = "OBP-20041: There is no need to call CBS"
val GatewayLoginCannotFindUser = "OBP-20042: User cannot be found. Please initiate CBS communication in order to create it."
val GatewayLoginCannotGetCbsToken = "OBP-20043: Cannot get the CBSToken response from South side"
val GatewayLoginCannotGetOrCreateUser = "OBP-20044: Cannot get or create user during GatewayLogin process."
2017-10-31 07:05:28 +00:00
val GatewayLoginNoJwtForResponse = "OBP-200445: There is no useful value for JWT."
2017-06-25 07:16:52 +00:00
// Resource related messages (OBP-30XXX)
2016-03-06 08:35:13 +00:00
val BankNotFound = "OBP-30001: Bank not found. Please specify a valid value for BANK_ID."
val CustomerNotFound = "OBP-30002: Customer not found. Please specify a valid value for CUSTOMER_NUMBER."
2016-06-10 06:45:35 +00:00
val CustomerNotFoundByCustomerId = "OBP-30002: Customer not found. Please specify a valid value for CUSTOMER_ID."
2016-02-21 09:50:53 +00:00
2016-03-06 08:35:13 +00:00
val AccountNotFound = "OBP-30003: Account not found. Please specify a valid value for ACCOUNT_ID."
2016-04-17 02:40:22 +00:00
val CounterpartyNotFound = "OBP-30004: Counterparty not found. The BANK_ID / ACCOUNT_ID specified does not exist on this server."
2016-03-06 08:35:13 +00:00
val ViewNotFound = "OBP-30005: View not found for Account. Please specify a valid value for VIEW_ID"
2016-02-21 09:50:53 +00:00
2016-05-21 12:49:44 +00:00
val CustomerNumberAlreadyExists = "OBP-30006: Customer Number already exists. Please specify a different value for BANK_ID or CUSTOMER_NUMBER."
2016-10-05 13:19:41 +00:00
val CustomerAlreadyExistsForUser = "OBP-30007: The User is already linked to a Customer at the bank specified by BANK_ID"
2017-08-10 05:27:40 +00:00
val UserCustomerLinksNotFoundForUser = "OBP-30008: User Customer Link not found by USER_ID"
2016-11-23 14:19:52 +00:00
val AtmNotFoundByAtmId = "OBP-30009: ATM not found. Please specify a valid value for ATM_ID."
2016-11-24 16:25:00 +00:00
val BranchNotFoundByBranchId = "OBP-300010: Branch not found. Please specify a valid value for BRANCH_ID."
val ProductNotFoundByProductCode = "OBP-30011: Product not found. Please specify a valid value for PRODUCT_CODE."
2017-01-22 15:42:59 +00:00
val CounterpartyNotFoundByIban = "OBP-30012: Counterparty not found. Please specify a valid value for IBAN."
val CounterpartyBeneficiaryPermit = "OBP-30013: The account can not send money to the Counterparty. Please set the Counterparty 'isBeneficiary' true first"
2016-11-29 14:13:20 +00:00
val CounterpartyAlreadyExists = "OBP-30014: Counterparty already exists. Please specify a different value for BANK_ID or ACCOUNT_ID or VIEW_ID or NAME."
2017-07-04 07:32:38 +00:00
val CreateBranchError = "OBP-30015: Could not insert the Branch"
val UpdateBranchError = "OBP-30016: Could not update the Branch"
2017-01-22 15:42:59 +00:00
val CounterpartyNotFoundByCounterpartyId = "OBP-30017: Counterparty not found. Please specify a valid value for COUNTERPARTY_ID."
val BankAccountNotFound = "OBP-30018: Bank Account not found. Please specify valid values for BANK_ID and ACCOUNT_ID. "
2017-01-26 10:17:53 +00:00
val ConsumerNotFoundByConsumerId = "OBP-30019: Consumer not found. Please specify a valid value for CONSUMER_ID."
2017-09-12 16:47:44 +00:00
2017-07-04 07:32:38 +00:00
val CreateBankError = "OBP-30020: Could not create the Bank"
val UpdateBankError = "OBP-30021: Could not update the Bank"
2017-04-27 17:11:31 +00:00
val ViewNoPermission = "OBP-30022: The current view does not have the permission: "
2017-04-28 09:24:10 +00:00
val UpdateConsumerError = "OBP-30023: Cannot update Consumer "
2017-07-04 07:32:38 +00:00
val CreateConsumerError = "OBP-30024: Could not create Consumer "
2017-04-28 09:24:10 +00:00
val CreateUserCustomerLinksError = "OBP-30025: Could not create user_customer_links "
2017-05-04 15:02:23 +00:00
val ConsumerKeyAlreadyExists = "OBP-30026: Consumer Key already exists. Please specify a different value."
2017-06-26 14:30:27 +00:00
val NoExistingAccountHolders = "OBP-30027: Account Holders not found. The BANK_ID / ACCOUNT_ID specified for account holder does not exist on this server"
2017-07-04 07:32:38 +00:00
val CreateAtmError = "OBP-30028: Could not insert the ATM"
val UpdateAtmError = "OBP-30029: Could not update the ATM"
val CreateProductError = "OBP-30030: Could not insert the Product"
val UpdateProductError = "OBP-30031: Could not update the Product"
2017-09-12 16:47:44 +00:00
2017-08-12 10:14:56 +00:00
val CreateCardError = "OBP-30032: Could not insert the Card"
val UpdateCardError = "OBP-30033: Could not update the Card"
2017-09-12 16:47:44 +00:00
2017-08-12 10:14:56 +00:00
val ViewIdNotSupported = "OBP-30034: This ViewId is do not supported. Only support four now: Owner, Public, Accountant, Auditor."
2017-07-04 07:32:38 +00:00
2017-08-10 02:42:05 +00:00
2017-08-12 10:14:56 +00:00
val UserCustomerLinkNotFound = "OBP-30035: User Customer Link not found"
2017-08-10 02:42:05 +00:00
2017-09-12 16:47:44 +00:00
2016-05-21 12:49:44 +00:00
2017-06-25 07:16:52 +00:00
// Meetings
2016-05-22 12:01:37 +00:00
val MeetingsNotSupported = "OBP-30101: Meetings are not supported on this server."
val MeetingApiKeyNotConfigured = "OBP-30102: Meeting provider API Key is not configured."
val MeetingApiSecretNotConfigured = "OBP-30103: Meeting provider Secret is not configured."
2016-07-04 14:13:48 +00:00
val MeetingNotFound = "OBP-30104: Meeting not found."
2016-05-22 12:01:37 +00:00
2016-07-03 11:41:55 +00:00
val InvalidAccountBalanceCurrency = "OBP-30105: Invalid Balance Currency."
val InvalidAccountBalanceAmount = "OBP-30106: Invalid Balance Amount."
val InvalidUserId = "OBP-30107: Invalid User Id."
val InvalidAccountType = "OBP-30108: Invalid Account Type."
val InitialBalanceMustBeZero = "OBP-30109: Initial Balance of Account must be Zero (0)."
2016-12-08 10:54:28 +00:00
val InvalidAccountIdFormat = "OBP-30110: Invalid Account Id. The ACCOUNT_ID should only contain 0-9/a-z/A-Z/'-'/'.'/'_', the length should be smaller than 255."
val InvalidBankIdFormat = "OBP-30111: Invalid Bank Id. The BANK_ID should only contain 0-9/a-z/A-Z/'-'/'.'/'_', the length should be smaller than 255."
2017-02-08 13:45:22 +00:00
val InvalidAccountInitialBalance = "OBP-30112: Invalid Number. Initial balance must be a number, e.g 1000.00"
2016-08-02 11:27:04 +00:00
2016-08-02 13:49:35 +00:00
val EntitlementIsBankRole = "OBP-30205: This entitlement is a Bank Role. Please set bank_id to a valid bank id."
val EntitlementIsSystemRole = "OBP-30206: This entitlement is a System Role. Please set bank_id to empty string."
2016-08-01 13:02:21 +00:00
2017-03-21 15:52:52 +00:00
val InvalidStrongPasswordFormat = "OBP-30207: Invalid Password Format. Your password should EITHER be at least 10 characters long and contain mixed numbers and both upper and lower case letters and at least one special character, OR be longer than 16 characters."
2016-08-01 13:02:21 +00:00
2017-06-25 07:16:52 +00:00
val AccountIdAlreadyExsits = "OBP-30208: Account_ID already exists at the Bank."
2016-07-03 11:41:55 +00:00
2017-07-06 13:16:46 +00:00
val InsufficientAuthorisationToCreateBranch = "OBP-30209: Insufficient authorisation to Create Branch. You do not have the role CanCreateBranch." // was OBP-20019
val InsufficientAuthorisationToCreateBank = "OBP-30210: Insufficient authorisation to Create Bank. You do not have the role CanCreateBank." // was OBP-20020
2017-06-25 07:16:52 +00:00
// General Resource related messages above here
// Transaction Request related messages (OBP-40XXX)
2016-04-27 15:13:16 +00:00
val InvalidTransactionRequestType = "OBP-40001: Invalid value for TRANSACTION_REQUEST_TYPE"
2017-02-08 13:45:22 +00:00
val InsufficientAuthorisationToCreateTransactionRequest = "OBP-40002: Insufficient authorisation to create TransactionRequest. The Transaction Request could not be created because you don't have access to the owner view of the from account or you don't have access to canCreateAnyTransactionRequest."
2016-11-08 18:28:36 +00:00
val InvalidTransactionRequestCurrency = "OBP-40003: Transaction Request Currency must be the same as From Account Currency."
val InvalidTransactionRequestId = "OBP-40004: Transaction Request Id not found."
2016-11-20 11:22:23 +00:00
val InsufficientAuthorisationToCreateTransactionType = "OBP-40005: Insufficient authorisation to Create Transaction Type offered by the bank. The Request could not be created because you don't have access to CanCreateTransactionType."
2017-01-22 15:42:59 +00:00
val CreateTransactionTypeInsertError = "OBP-40006: Could not insert Transaction Type: Non unique BANK_ID / SHORT_CODE"
val CreateTransactionTypeUpdateError = "OBP-40007: Could not update Transaction Type: Non unique BANK_ID / SHORT_CODE"
2017-02-08 13:45:22 +00:00
val NotPositiveAmount = "OBP-40008: Can't send a payment with a value of 0 or less."
2017-02-10 11:17:41 +00:00
val TransactionRequestTypeHasChanged = "OBP-40009: The TRANSACTION_REQUEST_TYPE has changed."
2017-02-20 11:21:34 +00:00
val InvalidTransactionRequesChallengeId = "OBP-40010: Invalid Challenge Id. Please specify a valid value for CHALLENGE_ID."
2017-02-24 12:39:25 +00:00
val TransactionRequestStatusNotInitiated = "OBP-40011: Transaction Request Status is not INITIATED."
2017-02-27 16:56:15 +00:00
val CounterpartyNotFoundOtherAccountProvider = "OBP-40012: Please set up the otherAccountRoutingScheme and otherBankRoutingScheme fields of the Counterparty to 'OBP'"
2017-02-24 13:07:59 +00:00
val InvalidChargePolicy = "OBP-40013: Invalid Charge Policy. Please specify a valid value for Charge_Policy: SHARED, SENDER or RECEIVER. "
2017-04-28 07:31:33 +00:00
val AllowedAttemptsUsedUp = "OBP-40014: Sorry, you've used up your allowed attempts. "
2017-03-25 22:45:26 +00:00
val InvalidChallengeType = "OBP-40015: Invalid Challenge Type. Please specify a valid value for CHALLENGE_TYPE, when you create the transaction request."
2017-08-16 14:55:12 +00:00
val InvalidChallengeAnswer = "OBP-40016: Invalid Challenge Answer. Please specify a valid value for answer in Json body."
2017-09-13 14:38:25 +00:00
val InvalidPhoneNumber = "OBP-40017: Invalid Phone Number. Please specify a valid value for PHONE_NUMBER. Eg:+9722398746 "
2017-06-25 07:16:52 +00:00
// Exceptions (OBP-50XXX)
val UnknownError = "OBP-50000: Unknown Error."
val FutureTimeoutException = "OBP-50001: Future Timeout Exception."
2017-06-26 14:30:27 +00:00
val KafkaMessageClassCastException = "OBP-50002: Kafka Response Message Class Cast Exception."
2017-06-25 07:16:52 +00:00
val AdapterOrCoreBankingSystemException = "OBP-50003: Adapter Or Core Banking System Exception. Failed to get a valid response from the south side Adapter or Core Banking System."
2017-07-11 10:04:58 +00:00
// This error may not be shown to user, just for debugging.
val CurrentUserNotFoundException = "OBP-50004: Method (AuthUser.getCurrentUser) can not find the current user in the current context!"
2017-09-04 13:08:51 +00:00
val AnUnspecifiedOrInternalErrorOccurred = "OBP-50005: An unspecified or internal error occurred."
2017-10-20 13:41:36 +00:00
val KafkaInterruptedException = "OBP-50006: Kafka interrupted exception."
val KafkaExecutionException = "OBP-50007: Kafka execution exception."
val KafkaStreamTimeoutException = "OBP-50008: Akka Kafka stream timeout exception."
val KafkaUnknownError = "OBP-50009: Kafka unknown error."
2017-06-25 07:16:52 +00:00
// Connector Data Exceptions (OBP-502XX)
val ConnectorEmptyResponse = "OBP-50200: Connector cannot return the data we requested." // was OBP-30200
val InvalidConnectorResponseForGetBankAccounts = "OBP-50201: Connector did not return the set of accounts we requested." // was OBP-30201
val InvalidConnectorResponseForGetBankAccount = "OBP-50202: Connector did not return the account we requested." // was OBP-30202
val InvalidConnectorResponseForGetTransaction = "OBP-50203: Connector did not return the transaction we requested." // was OBP-30203
val InvalidConnectorResponseForGetTransactions = "OBP-50204: Connector did not return the set of transactions we requested." // was OBP-30204
2017-07-07 09:26:40 +00:00
// Adapter Exceptions (OBP-6XXXX)
// Reserved for adapter (south of Kafka) messages
///////////
2017-06-25 07:16:52 +00:00
2017-04-27 13:11:45 +00:00
//For Swagger, used reflect to list all the varible names and values.
// eg : val InvalidUserId = "OBP-30107: Invalid User Id."
// -->(InvalidUserId, "OBP-30107: Invalid User Id.")
2017-04-26 12:39:00 +00:00
val allFields =
for (
v <- this . getClass . getDeclaredFields
//add guard, ignore the SwaggerJSONsV220.this and allFieldsAndValues fields
if ( APIUtil . notExstingBaseClass ( v . getName ( ) ) )
) yield {
v . setAccessible ( true )
v . getName ( ) -> v . get ( this )
}
2017-09-12 16:47:44 +00:00
//For Swagger, get varible name by value:
2017-04-27 13:11:45 +00:00
// eg: val InvalidUserId = "OBP-30107: Invalid User Id."
// getFildNameByValue("OBP-30107: Invalid User Id.") return InvalidUserId
def getFildNameByValue ( value : String ) = {
val strings = for ( e <- allFields if ( e . _2 == ( value ) ) ) yield e . _1
strings . head
}
2016-02-21 09:21:01 +00:00
}
2017-04-19 19:09:58 +00:00
object APIUtil extends MdcLoggable {
2013-06-14 08:35:34 +00:00
2017-11-13 11:00:54 +00:00
implicit val formats = net . liftweb . json . DefaultFormats
2013-06-14 08:35:34 +00:00
implicit def errorToJson ( error : ErrorMessage ) : JValue = Extraction . decompose ( error )
2013-07-16 09:12:44 +00:00
val headers = ( "Access-Control-Allow-Origin" , "*" ) : : Nil
2017-04-10 07:20:10 +00:00
val defaultJValue = Extraction . decompose ( Nil ) ( APIUtil . formats )
val exampleDateString : String = "22/08/2013"
val simpleDateFormat : SimpleDateFormat = new SimpleDateFormat ( "dd/mm/yyyy" )
val exampleDate = simpleDateFormat . parse ( exampleDateString )
2017-05-04 13:41:42 +00:00
val emptyObjectJson = EmptyClassJson ( )
val defaultFilterFormat = new SimpleDateFormat ( "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" )
val fallBackFilterFormat = new SimpleDateFormat ( "yyyy-MM-dd'T'HH:mm:ss.SSSZ" )
import code.api.util.ErrorMessages._
2017-09-12 16:47:44 +00:00
2013-06-14 08:35:34 +00:00
def httpMethod : String =
S . request match {
case Full ( r ) => r . request . method
case _ => "GET"
}
2017-07-17 08:42:22 +00:00
def hasDirectLoginHeader : Boolean = hasHeader ( "DirectLogin" )
2016-01-30 11:13:04 +00:00
2017-07-17 08:42:22 +00:00
def hasAnOAuthHeader : Boolean = hasHeader ( "OAuth" )
2017-07-13 06:16:19 +00:00
2017-07-17 08:42:22 +00:00
def hasGatewayHeader ( ) = hasHeader ( "GatewayLogin" )
2017-06-30 15:18:36 +00:00
2017-07-17 08:42:22 +00:00
def hasHeader ( `type` : String ) : Boolean = {
2013-06-14 08:35:34 +00:00
S . request match {
case Full ( a ) => a . header ( "Authorization" ) match {
2017-07-13 06:16:19 +00:00
case Full ( parameters ) => parameters . contains ( `type` )
2013-06-14 08:35:34 +00:00
case _ => false
}
case _ => false
}
}
2016-08-11 17:17:43 +00:00
def registeredApplication ( consumerKey : String ) : Boolean = {
2017-03-14 09:26:32 +00:00
Consumers . consumers . vend . getConsumerByConsumerKey ( consumerKey ) match {
2017-08-04 07:00:59 +00:00
case Full ( application ) => application . isActive . get
2016-08-11 17:17:43 +00:00
case _ => false
}
}
2017-10-05 22:02:08 +00:00
def registeredApplicationFuture ( consumerKey : String ) : Future [ Boolean ] = {
Consumers . consumers . vend . getConsumerByConsumerKeyFuture ( consumerKey ) map {
case Full ( c ) => c . isActive . get
case _ => false
}
}
2017-04-11 12:56:06 +00:00
def logAPICall ( date : TimeSpan , duration : Long , rd : Option [ ResourceDoc ] ) = {
2015-05-30 15:17:18 +00:00
if ( Props . getBool ( "write_metrics" , false ) ) {
2016-06-06 19:29:18 +00:00
val user =
2017-07-17 08:42:22 +00:00
if ( hasAnOAuthHeader ) {
2016-06-06 19:29:18 +00:00
getUser match {
case Full ( u ) => Full ( u )
case _ => Empty
}
2017-07-17 08:42:22 +00:00
} else if ( Props . getBool ( "allow_direct_login" , true ) && hasDirectLoginHeader ) {
2016-06-06 19:29:18 +00:00
DirectLogin . getUser match {
case Full ( u ) => Full ( u )
case _ => Empty
}
} else {
Empty
}
2017-02-14 12:24:44 +00:00
val consumer =
2017-07-17 08:42:22 +00:00
if ( hasAnOAuthHeader ) {
2017-02-14 12:24:44 +00:00
getConsumer match {
case Full ( c ) => Full ( c )
case _ => Empty
}
2017-07-17 08:42:22 +00:00
} else if ( Props . getBool ( "allow_direct_login" , true ) && hasDirectLoginHeader ) {
2017-02-14 12:24:44 +00:00
DirectLogin . getConsumer match {
case Full ( c ) => Full ( c )
case _ => Empty
}
} else {
Empty
}
2016-03-05 07:04:41 +00:00
// TODO This should use Elastic Search or Kafka not an RDBMS
2017-02-14 12:24:44 +00:00
val u : User = user . orNull
2016-06-08 07:39:32 +00:00
val userId = if ( u != null ) u . userId else "null"
2016-07-26 12:12:49 +00:00
val userName = if ( u != null ) u . name else "null"
2017-02-14 12:24:44 +00:00
val c : Consumer = consumer . orNull
//The consumerId, not key
val consumerId = if ( u != null ) c . id . toString ( ) else "null"
var appName = if ( u != null ) c . name . toString ( ) else "null"
var developerEmail = if ( u != null ) c . developerEmail . toString ( ) else "null"
2017-04-11 12:56:06 +00:00
val implementedByPartialFunction = rd match {
2017-11-12 07:58:09 +00:00
case Some ( r ) => r . partialFunctionName
2017-04-11 12:56:06 +00:00
case _ => ""
}
2017-02-14 12:24:44 +00:00
//name of version where the call is implemented) -- S.request.get.view
2017-08-04 07:00:59 +00:00
val implementedInVersion = S . request . openOrThrowException ( "Attempted to open an empty Box." ) . view
2017-02-14 12:24:44 +00:00
//(GET, POST etc.) --S.request.get.requestType.method
2017-08-04 07:00:59 +00:00
val verb = S . request . openOrThrowException ( "Attempted to open an empty Box." ) . requestType . method
2017-06-14 15:04:49 +00:00
val url = S . uriAndQueryString . getOrElse ( "" )
2017-07-10 14:47:31 +00:00
val correlationId = getCorrelationId ( )
2017-02-14 12:24:44 +00:00
2017-05-31 17:16:49 +00:00
//execute saveMetric in future, as we do not need to know result of operation
Future {
APIMetrics . apiMetrics . vend . saveMetric (
userId ,
2017-06-14 15:04:49 +00:00
url ,
2017-05-31 17:16:49 +00:00
date ,
duration : Long ,
userName ,
appName ,
developerEmail ,
consumerId ,
implementedByPartialFunction ,
2017-07-10 08:08:46 +00:00
implementedInVersion , verb ,
correlationId
2017-05-31 17:16:49 +00:00
)
}
2015-05-30 15:17:18 +00:00
}
}
2013-06-14 08:35:34 +00:00
2015-05-12 10:48:15 +00:00
/*
Return the git commit . If we can 't for some reason ( not a git root etc ) then log and return ""
*/
2013-06-14 08:35:34 +00:00
def gitCommit : String = {
2015-05-12 10:48:15 +00:00
val commit = try {
2013-06-14 08:35:34 +00:00
val properties = new java . util . Properties ( )
2015-05-12 10:48:15 +00:00
logger . debug ( "Before getResourceAsStream git.properties" )
2013-06-14 08:35:34 +00:00
properties . load ( getClass ( ) . getClassLoader ( ) . getResourceAsStream ( "git.properties" ) )
2015-05-12 10:48:15 +00:00
logger . debug ( "Before get Property git.commit.id" )
2013-06-14 08:35:34 +00:00
properties . getProperty ( "git.commit.id" , "" )
2015-05-12 10:48:15 +00:00
} catch {
case e : Throwable => {
logger . warn ( "gitCommit says: Could not return git commit. Does resources/git.properties exist?" )
logger . error ( s" Exception in gitCommit: $e " )
"" // Return empty string
}
2013-06-14 08:35:34 +00:00
}
2015-05-12 10:48:15 +00:00
commit
2013-06-14 08:35:34 +00:00
}
2017-10-31 11:10:33 +00:00
/* *
*
* @param jwt is a JWT value extracted from GatewayLogin Authorization Header .
* Value None implies that Authorization Header is NOT GatewayLogin
* @return GatewayLogin Custom Response Header
* Example of the Header in Response generated by this function :
* GatewayLogin : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJsb2dpbl91c2VyX25hbWUiOiJON2p1dDhkIiwiaXNfZmlyc3QiOmZhbHNlLCJhcHBfaWQiOiIxMjMiLCJhcHBfbmFtZSI6Ik5hbWUgb2YgQ29uc3VtZXIiLCJ0aW1lc3RhbXAiOiIiLCJjYnNfdG9rZW4iOiI - LD8gICAgICAgICAgODE0MzMwMjAxMDI2MTIiLCJ0ZW1lbm9zX2lkIjoiIn0 . saE7W - ydZcwbjxfWx7q6HeQ1q4LMLYZiuYSx7qdP0k8
*/
2017-10-30 15:49:29 +00:00
def getGatewayLoginHeader ( jwt : Option [ String ] ) = {
2017-10-31 11:10:33 +00:00
jwt match {
case Some ( v ) =>
val header = ( gatewayResponseHeaderName , v )
CustomResponseHeaders ( List ( header ) )
case None =>
CustomResponseHeaders ( Nil )
}
2017-10-30 15:49:29 +00:00
}
def getHeadersCommonPart ( ) = headers : :: List ( ( " Correlation - Id " , getCorrelationId ( ) ) )
def getHeaders ( ) = getHeadersCommonPart ( ) : :: getGatewayResponseHeader ( )
2017-07-10 14:47:31 +00:00
2017-07-12 06:40:58 +00:00
case class CustomResponseHeaders ( list : List [ ( String , String ) ] )
2017-04-26 12:39:00 +00:00
//Note: changed noContent--> defaultSuccess, because of the Swagger format. (Not support empty in DataType, maybe fix it latter.)
2017-07-12 06:40:58 +00:00
def noContentJsonResponse ( implicit headers : CustomResponseHeaders = CustomResponseHeaders ( Nil ) ) : JsonResponse =
JsonResponse ( JsRaw ( "" ) , getHeaders ( ) : :: headers . list , Nil , 204 )
2013-06-14 08:35:34 +00:00
2017-07-12 06:40:58 +00:00
def successJsonResponse ( json : JsExp , httpCode : Int = 200 ) ( implicit headers : CustomResponseHeaders = CustomResponseHeaders ( Nil ) ) : JsonResponse =
JsonResponse ( json , getHeaders ( ) : :: headers . list , Nil , httpCode )
2015-10-02 21:25:15 +00:00
2017-07-12 06:40:58 +00:00
def createdJsonResponse ( json : JsExp , httpCode : Int = 201 ) ( implicit headers : CustomResponseHeaders = CustomResponseHeaders ( Nil ) ) : JsonResponse =
2017-07-12 06:49:20 +00:00
JsonResponse ( json , getHeaders ( ) : :: headers . list , Nil , httpCode )
2015-10-02 21:25:15 +00:00
2017-07-12 06:40:58 +00:00
def successJsonResponseFromCaseClass ( cc : Any , httpCode : Int = 200 ) ( implicit headers : CustomResponseHeaders = CustomResponseHeaders ( Nil ) ) : JsonResponse =
2017-08-16 14:51:40 +00:00
JsonResponse ( snakify ( Extraction . decompose ( cc ) ) , getHeaders ( ) : :: headers . list , Nil , httpCode )
2017-06-29 23:30:02 +00:00
2017-07-12 06:40:58 +00:00
def acceptedJsonResponse ( json : JsExp , httpCode : Int = 202 ) ( implicit headers : CustomResponseHeaders = CustomResponseHeaders ( Nil ) ) : JsonResponse =
JsonResponse ( json , getHeaders ( ) : :: headers . list , Nil , httpCode )
2013-06-14 08:35:34 +00:00
2017-07-12 06:40:58 +00:00
def errorJsonResponse ( message : String = "error" , httpCode : Int = 400 ) ( implicit headers : CustomResponseHeaders = CustomResponseHeaders ( Nil ) ) : JsonResponse =
JsonResponse ( Extraction . decompose ( ErrorMessage ( message ) ) , getHeaders ( ) : :: headers . list , Nil , httpCode )
2013-06-14 08:35:34 +00:00
2017-08-22 12:59:40 +00:00
def notImplementedJsonResponse ( message : String = ErrorMessages . NotImplemented , httpCode : Int = 501 ) ( implicit headers : CustomResponseHeaders = CustomResponseHeaders ( Nil ) ) : JsonResponse =
2017-07-12 06:40:58 +00:00
JsonResponse ( Extraction . decompose ( ErrorMessage ( message ) ) , getHeaders ( ) : :: headers . list , Nil , httpCode )
2015-10-22 13:27:41 +00:00
2017-07-12 06:40:58 +00:00
def oauthHeaderRequiredJsonResponse ( implicit headers : CustomResponseHeaders = CustomResponseHeaders ( Nil ) ) : JsonResponse =
JsonResponse ( Extraction . decompose ( ErrorMessage ( "Authentication via OAuth is required" ) ) , getHeaders ( ) : :: headers . list , Nil , 400 )
2013-06-14 08:35:34 +00:00
2016-12-22 12:14:42 +00:00
/* * check the currency ISO code from the ISOCurrencyCodes.xml file */
2016-12-08 10:54:28 +00:00
def isValidCurrencyISOCode ( currencyCode : String ) : Boolean = {
2016-12-22 10:32:11 +00:00
//just for initialization the Elem variable
var xml : Elem = < html />
LiftRules . getResource ( "/media/xml/ISOCurrencyCodes.xml" ) . map { url =>
val input : InputStream = url . openStream ( )
xml = XML . load ( input )
}
2016-12-08 10:54:28 +00:00
val stringArray = ( xml \ "Currency" \ "CurrencyCode" ) . map ( _ . text ) . mkString ( " " ) . split ( "\\s+" )
stringArray . contains ( currencyCode )
}
/* * Check the id values from GUI, such as ACCOUNT_ID, BANK_ID ... */
def isValidID ( id : String ) : Boolean = {
val regex = """^([A-Za-z0-9\-_.]+)$""" . r
id match {
2016-12-22 12:14:42 +00:00
case regex ( e ) if ( e . length < 256 ) => true
2016-12-08 10:54:28 +00:00
case _ => false
}
}
2017-09-12 16:47:44 +00:00
/* * enforce the password.
* The rules :
2017-01-23 22:16:44 +00:00
* 1 ) length is > 16 characters without validations
2017-09-12 16:47:44 +00:00
* 2 ) or Min 10 characters with mixed numbers + letters + upper + lower case + at least one special character .
2017-01-23 22:16:44 +00:00
* */
def isValidStrongPassword ( password : String ) : Boolean = {
/* *
* ( ?= . *\ d ) //should contain at least one digit
* ( ?= . * [ a - z ] ) //should contain at least one lower case
* ( ?= . * [ A - Z ] ) //should contain at least one upper case
* ( ?= . * [ ! " # $ %& ' \ ( \ ) *+ ,- . /: ; <=>?@\\ [ \\\\ ] ^ _ \\ ` { | } ~ ] ) //should contain at least one special character
* ( [ A - Za - z0 - 9 ! " # $ %& ' \ ( \ ) *+ ,- . /: ; <=>?@\\ [ \\\\ ] ^ _ \\ ` { | } ~ ] { 10 , 16 } ) //should contain 10 to 16 valid characters
**/
val regex =
"""^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!"#$%&'\(\)*+,-./:;<=>?@\\[\\\\]^_\\`{|}~])([A-Za-z0-9!"#$%&'\(\)*+,-./:;<=>?@\\[\\\\]^_\\`{|}~]{10,16})$""" . r
password match {
case password if ( password . length > 16 ) => true
case regex ( password ) => true
case _ => false
}
}
2017-09-12 16:47:44 +00:00
2017-01-23 22:16:44 +00:00
2016-12-31 18:44:45 +00:00
/* * These three functions check rather than assert. I.e. they are silent if OK and return an error message if not.
* They do not throw an exception on failure thus they are not assertions
*/
2017-01-18 11:57:17 +00:00
/* * only A-Z, a-z and max length <= 512 */
2016-12-31 18:44:45 +00:00
def checkMediumAlpha ( value : String ) : String = {
2016-12-22 12:14:42 +00:00
val valueLength = value . length
2016-12-20 16:27:50 +00:00
val regex = """^([A-Za-z]+)$""" . r
value match {
2016-12-31 18:44:45 +00:00
case regex ( e ) if ( valueLength <= 512 ) => SILENCE_IS_GOLDEN
2016-12-22 12:14:42 +00:00
case regex ( e ) if ( valueLength > 512 ) => ErrorMessages . InvalidValueLength
case _ => ErrorMessages . InvalidValueCharacters
2016-12-20 16:27:50 +00:00
}
}
2017-01-18 11:57:17 +00:00
/* * only A-Z, a-z, 0-9 and max length <= 512 */
2016-12-31 18:44:45 +00:00
def checkMediumAlphaNumeric ( value : String ) : String = {
2016-12-22 12:14:42 +00:00
val valueLength = value . length
2016-12-20 16:27:50 +00:00
val regex = """^([A-Za-z0-9]+)$""" . r
value match {
2016-12-31 18:44:45 +00:00
case regex ( e ) if ( valueLength <= 512 ) => SILENCE_IS_GOLDEN
2016-12-22 12:14:42 +00:00
case regex ( e ) if ( valueLength > 512 ) => ErrorMessages . InvalidValueLength
case _ => ErrorMessages . InvalidValueCharacters
2016-12-20 16:27:50 +00:00
}
}
2017-01-13 15:43:53 +00:00
/* * only A-Z, a-z, 0-9, all allowed characters for password and max length <= 512 */
def checkMediumPassword ( value : String ) : String = {
val valueLength = value . length
val regex = """^([A-Za-z0-9!"#$%&'\(\)*+,-./:;<=>?@\\[\\\\]^_\\`{|}~]+)$""" . r
value match {
case regex ( e ) if ( valueLength <= 512 ) => SILENCE_IS_GOLDEN
case regex ( e ) if ( valueLength > 512 ) => ErrorMessages . InvalidValueLength
case _ => ErrorMessages . InvalidValueCharacters
}
}
2017-01-22 15:42:59 +00:00
/* * only A-Z, a-z, 0-9, -, _, ., @, and max length <= 512 */
2016-12-31 18:44:45 +00:00
def checkMediumString ( value : String ) : String = {
2016-12-22 12:14:42 +00:00
val valueLength = value . length
2016-12-20 16:27:50 +00:00
val regex = """^([A-Za-z0-9\-._@]+)$""" . r
value match {
2016-12-31 18:44:45 +00:00
case regex ( e ) if ( valueLength <= 512 ) => SILENCE_IS_GOLDEN
2016-12-22 12:14:42 +00:00
case regex ( e ) if ( valueLength > 512 ) => ErrorMessages . InvalidValueLength
case _ => ErrorMessages . InvalidValueCharacters
2016-12-20 16:27:50 +00:00
}
}
2017-09-12 16:47:44 +00:00
2017-08-11 03:28:55 +00:00
def ValueOrOBP ( text : String ) =
text match {
case t if t == null => "OBP"
case t if t . length > 0 => t
case _ => "OBP"
}
2017-09-12 16:47:44 +00:00
2017-08-11 03:28:55 +00:00
def ValueOrOBPId ( text : String , OBPId : String ) =
text match {
case t if t == null => OBPId
case t if t . length > 0 => t
case _ => OBPId
}
2017-09-12 16:47:44 +00:00
2017-05-04 16:33:52 +00:00
def stringOrNull ( text : String ) =
if ( text == null || text . isEmpty )
null
else
text
2017-09-12 16:47:44 +00:00
2017-05-04 16:33:52 +00:00
def stringOptionOrNull ( text : Option [ String ] ) =
text match {
case Some ( t ) => stringOrNull ( t )
case _ => null
}
2016-12-20 16:27:50 +00:00
2017-05-04 13:41:42 +00:00
//started -- Filtering and Paging revelent methods////////////////////////////
object DateParser {
/* *
* first tries to parse dates using this pattern "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" ( 2012 - 07 - 01 T00 : 0 0 : 0 0 . 0 0 0 Z ) ==> time zone is UTC
* in case of failure ( for backward compatibility reason ) , try "yyyy-MM-dd'T'HH:mm:ss.SSSZ" ( 2012 - 07 - 01 T00 : 0 0 : 0 0 . 0 0 0 + 0 0 0 0 ) ==> time zone has to be specified
*/
def parse ( date : String ) : Box [ Date ] = {
val parsedDate = tryo {
defaultFilterFormat . parse ( date )
}
2017-09-12 16:47:44 +00:00
2017-05-04 13:41:42 +00:00
lazy val fallBackParsedDate = tryo {
fallBackFilterFormat . parse ( date )
}
2017-09-12 16:47:44 +00:00
2017-05-04 13:41:42 +00:00
if ( parsedDate . isDefined ) {
2017-08-04 07:00:59 +00:00
Full ( parsedDate . openOrThrowException ( "Attempted to open an empty Box." ) )
2017-05-04 13:41:42 +00:00
}
else if ( fallBackParsedDate . isDefined ) {
2017-08-04 07:00:59 +00:00
Full ( fallBackParsedDate . openOrThrowException ( "Attempted to open an empty Box." ) )
2017-05-04 13:41:42 +00:00
}
else {
Failure ( FilterDateFormatError )
}
}
}
2017-09-12 16:47:44 +00:00
2017-05-04 13:41:42 +00:00
def getSortDirection ( req : Req ) : Box [ OBPOrder ] = {
req . header ( "obp_sort_direction" ) match {
case Full ( v ) => {
if ( v . toLowerCase == "desc" || v . toLowerCase == "asc" ) {
Full ( OBPOrder ( Some ( v . toLowerCase ) ) )
}
else {
Failure ( FilterSortDirectionError )
}
}
case _ => Full ( OBPOrder ( None ) )
}
}
2017-09-12 16:47:44 +00:00
2017-05-04 13:41:42 +00:00
def getFromDate ( req : Req ) : Box [ OBPFromDate ] = {
val date : Box [ Date ] = req . header ( "obp_from_date" ) match {
case Full ( d ) => {
DateParser . parse ( d )
}
case _ => {
Full ( new Date ( 0 ) )
}
}
2017-09-12 16:47:44 +00:00
2017-05-04 13:41:42 +00:00
date . map ( OBPFromDate ( _ ) )
}
2017-09-12 16:47:44 +00:00
2017-05-04 13:41:42 +00:00
def getToDate ( req : Req ) : Box [ OBPToDate ] = {
val date : Box [ Date ] = req . header ( "obp_to_date" ) match {
case Full ( d ) => {
DateParser . parse ( d )
}
2017-11-05 16:14:00 +00:00
case _ => {
// Use a fixed date far into the future (rather than current date/time so that cache keys are more static)
// (Else caching is invlidated by constantly changing date)
val toDate = dateformat . parse ( "3049-01-01" )
Full ( toDate )
}
2017-05-04 13:41:42 +00:00
}
2017-09-12 16:47:44 +00:00
2017-05-04 13:41:42 +00:00
date . map ( OBPToDate ( _ ) )
}
2017-09-12 16:47:44 +00:00
2017-05-04 13:41:42 +00:00
def getOffset ( req : Req ) : Box [ OBPOffset ] = {
getPaginationParam ( req , "obp_offset" , 0 , 0 , FilterOffersetError ) . map ( OBPOffset ( _ ) )
}
2017-09-12 16:47:44 +00:00
2017-05-04 13:41:42 +00:00
def getLimit ( req : Req ) : Box [ OBPLimit ] = {
getPaginationParam ( req , "obp_limit" , 50 , 1 , FilterLimitError ) . map ( OBPLimit ( _ ) )
}
2017-09-12 16:47:44 +00:00
2017-05-04 13:41:42 +00:00
def getPaginationParam ( req : Req , paramName : String , defaultValue : Int , minimumValue : Int , errorMsg : String ) : Box [ Int ] = {
req . header ( paramName ) match {
case Full ( v ) => {
tryo {
v . toInt
} match {
case Full ( value ) => {
if ( value >= minimumValue ) {
Full ( value )
}
else {
Failure ( errorMsg )
}
}
case _ => Failure ( errorMsg )
}
}
case _ => Full ( defaultValue )
}
}
2017-09-12 16:47:44 +00:00
2017-05-04 13:41:42 +00:00
def getTransactionParams ( req : Req ) : Box [ List [ OBPQueryParam ] ] = {
for {
sortDirection <- getSortDirection ( req )
fromDate <- getFromDate ( req )
toDate <- getToDate ( req )
limit <- getLimit ( req )
offset <- getOffset ( req )
} yield {
/* *
* sortBy is currently disabled as it would open up a security hole :
*
* sortBy as currently implemented will take in a parameter that searches on the mongo field names . The issue here
* is that it will sort on the true value , and not the moderated output . So if a view is supposed to return an alias name
* rather than the true value , but someone uses sortBy on the other bank account name / holder , not only will the returned data
* have the wrong order , but information about the true account holder name will be exposed due to its position in the sorted order
*
* This applies to all fields that can have their data concealed . . . which in theory will eventually be most / all
*
*/
//val sortBy = json.header("obp_sort_by")
val sortBy = None
val ordering = OBPOrdering ( sortBy , sortDirection )
limit : : offset :: ordering :: fromDate :: toDate :: Nil
}
}
//ended -- Filtering and Paging revelent methods ////////////////////////////
2017-09-12 16:47:44 +00:00
2013-07-08 13:38:32 +00:00
/* * Import this object's methods to add signing operators to dispatch.Request */
object OAuth {
import javax.crypto
2015-04-24 17:17:09 +00:00
import dispatch. { Req => Request }
2014-09-10 15:13:33 +00:00
import org.apache.http.protocol.HTTP.UTF_8
2015-04-24 17:17:09 +00:00
import scala.collection.Map
2016-05-13 16:38:40 +00:00
import scala.collection.immutable. { TreeMap , Map => IMap }
case class ReqData (
url : String ,
method : String ,
body : String ,
body_encoding : String ,
headers : Map [ String , String ] ,
query_params : Map [ String ,String ] ,
form_params : Map [ String ,String ]
)
2013-07-08 13:38:32 +00:00
case class Consumer ( key : String , secret : String )
case class Token ( value : String , secret : String )
object Token {
def apply [ T <: Any ] ( m : Map [ String , T ] ) : Option [ Token ] = List ( "oauth_token" , "oauth_token_secret" ) . flatMap ( m . get ) match {
case value : : secret :: Nil => Some ( Token ( value . toString , secret . toString ) )
case _ => None
}
}
/* * @return oauth parameter map including signature */
def sign ( method : String , url : String , user_params : Map [ String , Any ] , consumer : Consumer , token : Option [ Token ] , verifier : Option [ String ] , callback : Option [ String ] ) = {
val oauth_params = IMap (
"oauth_consumer_key" -> consumer . key ,
"oauth_signature_method" -> "HMAC-SHA1" ,
"oauth_timestamp" -> ( System . currentTimeMillis / 1000 ) . toString ,
"oauth_nonce" -> System . nanoTime . toString ,
"oauth_version" -> "1.0"
) ++ token . map { "oauth_token" -> _ . value } ++
verifier . map { "oauth_verifier" -> _ } ++
callback . map { "oauth_callback" -> _ }
val encoded_ordered_params = (
new TreeMap [ String , String ] ++ ( user_params ++ oauth_params map %% )
) map { case ( k , v ) => k + "=" + v } mkString "&"
val message =
%% ( method . toUpperCase : : url :: encoded_ordered_params :: Nil )
val SHA1 = "HmacSHA1"
val key_str = %% ( consumer . secret : : ( token map { _ . secret } getOrElse " " ) :: Nil )
val key = new crypto . spec . SecretKeySpec ( bytes ( key_str ) , SHA1 )
val sig = {
val mac = crypto . Mac . getInstance ( SHA1 )
mac . init ( key )
2016-05-13 16:38:40 +00:00
base64Encode ( mac . doFinal ( bytes ( message ) ) )
2013-07-08 13:38:32 +00:00
}
oauth_params + ( "oauth_signature" -> sig )
}
/* * Out-of-band callback code */
val oob = "oob"
/* * Map with oauth_callback set to the given url */
def callback ( url : String ) = IMap ( "oauth_callback" -> url )
//normalize to OAuth percent encoding
private def %% ( str : String ) : String = {
val remaps = ( "+" , "%20" ) : : ( " % 7 E " , " ~ " ) : : ( " * " , " % 2 A " ) : : Nil
( encode_% ( str ) /: remaps ) { case ( str , ( a , b ) ) => str . replace ( a , b ) }
}
private def %% ( s : Seq [ String ] ) : String = s map %% mkString "&"
private def %% ( t : ( String , Any ) ) : ( String , String ) = ( %% ( t . _1 ) , %% ( t . _2 . toString ) )
private def bytes ( str : String ) = str . getBytes ( UTF_8 )
/* * Add OAuth operators to dispatch.Request */
implicit def Request2RequestSigner ( r : Request ) = new RequestSigner ( r )
/* * @return %-encoded string for use in URLs */
def encode_% ( s : String ) = java . net . URLEncoder . encode ( s , org . apache . http . protocol . HTTP . UTF_8 )
/* * @return %-decoded string e.g. from query string or form body */
def decode_% ( s : String ) = java . net . URLDecoder . decode ( s , org . apache . http . protocol . HTTP . UTF_8 )
class RequestSigner ( rb : Request ) {
2016-05-13 16:38:40 +00:00
private val r = rb . toRequest
2013-07-08 13:38:32 +00:00
@deprecated ( "use <@ (consumer, callback) to pass the callback in the header for a request-token request" )
def <@ ( consumer : Consumer ) : Request = sign ( consumer , None , None , None )
/* * sign a request with a callback, e.g. a request-token request */
def <@ ( consumer : Consumer , callback : String ) : Request = sign ( consumer , None , None , Some ( callback ) )
/* * sign a request with a consumer, token, and verifier, e.g. access-token request */
def <@ ( consumer : Consumer , token : Token , verifier : String ) : Request =
sign ( consumer , Some ( token ) , Some ( verifier ) , None )
/* * sign a request with a consumer and a token, e.g. an OAuth-signed API request */
def <@ ( consumer : Consumer , token : Token ) : Request = sign ( consumer , Some ( token ) , None , None )
2013-07-19 12:37:06 +00:00
def <@ ( consumerAndToken : Option [ ( Consumer ,Token ) ] ) : Request = {
consumerAndToken match {
case Some ( cAndt ) => sign ( cAndt . _1 , Some ( cAndt . _2 ) , None , None )
case _ => rb
}
}
2013-07-08 13:38:32 +00:00
/* * Sign request by reading Post (<<) and query string parameters */
private def sign ( consumer : Consumer , token : Option [ Token ] , verifier : Option [ String ] , callback : Option [ String ] ) = {
2016-05-13 16:38:40 +00:00
2013-07-08 13:38:32 +00:00
val oauth_url = r . getUrl . split ( '?' ) ( 0 )
2016-05-13 16:38:40 +00:00
val query_params = r . getQueryParams . asScala . groupBy ( _ . getName ) . mapValues ( _ . map ( _ . getValue ) ) . map {
case ( k , v ) => k -> v . toString
}
val form_params = r . getFormParams . asScala . groupBy ( _ . getName ) . mapValues ( _ . map ( _ . getValue ) ) . map {
case ( k , v ) => k -> v . toString
}
2017-08-11 13:42:03 +00:00
val body_encoding = r . getCharset
2016-05-13 16:38:40 +00:00
var body = new String ( )
if ( r . getByteData != null )
body = new String ( r . getByteData )
2013-07-08 13:38:32 +00:00
val oauth_params = OAuth . sign ( r . getMethod , oauth_url ,
query_params ++ form_params ,
consumer , token , verifier , callback )
2016-05-13 16:38:40 +00:00
def createRequest ( reqData : ReqData ) : Request = {
2017-08-11 13:42:03 +00:00
val charset = if ( reqData . body_encoding == "null" ) Charset . defaultCharset ( ) else Charset . forName ( reqData . body_encoding )
2016-05-13 16:38:40 +00:00
val rb = url ( reqData . url )
. setMethod ( reqData . method )
2017-08-11 13:42:03 +00:00
. setBodyEncoding ( charset )
2016-05-13 16:38:40 +00:00
. setBody ( reqData . body ) <:< reqData . headers
if ( reqData . query_params . nonEmpty )
rb <<? reqData . query_params
2013-07-08 13:38:32 +00:00
rb
}
2016-05-13 16:38:40 +00:00
createRequest ( ReqData (
oauth_url ,
r . getMethod ,
body ,
2017-08-11 13:42:03 +00:00
if ( body_encoding == null ) "null" else body_encoding . name ( ) ,
2013-07-08 13:38:32 +00:00
IMap ( "Authorization" -> ( "OAuth " + oauth_params . map {
2016-05-13 16:38:40 +00:00
case ( k , v ) => encode_% ( k ) + "=\"%s\"" . format ( encode_% ( v . toString ) )
} . mkString ( "," ) ) ) ,
query_params ,
form_params
) )
2013-07-08 13:38:32 +00:00
}
}
}
2015-07-25 12:31:15 +00:00
/*
2016-01-17 15:07:36 +00:00
Used to document API calls / resources .
TODO Can we extract apiVersion , apiFunction , requestVerb and requestUrl from partialFunction ?
2015-07-25 12:31:15 +00:00
*/
2016-02-08 20:58:11 +00:00
2017-11-12 07:58:09 +00:00
2016-02-29 23:26:47 +00:00
// Used to tag Resource Docs
2016-02-08 20:58:11 +00:00
case class ResourceDocTag ( tag : String )
2016-06-25 02:23:48 +00:00
// Use the *singular* case. for both the variable name and string.
// e.g. "This call is Payment related"
2016-06-27 07:34:51 +00:00
val apiTagTransactionRequest = ResourceDocTag ( "TransactionRequest" )
2017-09-26 16:48:37 +00:00
val apiTagApi = ResourceDocTag ( "API" )
2016-06-25 02:23:48 +00:00
val apiTagBank = ResourceDocTag ( "Bank" )
val apiTagAccount = ResourceDocTag ( "Account" )
2016-02-09 10:45:59 +00:00
val apiTagPublicData = ResourceDocTag ( "PublicData" )
val apiTagPrivateData = ResourceDocTag ( "PrivateData" )
2016-06-25 02:23:48 +00:00
val apiTagTransaction = ResourceDocTag ( "Transaction" )
2017-09-26 19:58:30 +00:00
val apiTagCounterpartyMetaData = ResourceDocTag ( "CounterpartyMetaData" )
val apiTagTransactionMetaData = ResourceDocTag ( "TransactionMetaData" )
2016-06-25 02:23:48 +00:00
val apiTagView = ResourceDocTag ( "View" )
val apiTagEntitlement = ResourceDocTag ( "Entitlement" )
2017-09-26 19:15:35 +00:00
val apiTagRole = ResourceDocTag ( "Role" )
2016-02-09 10:45:59 +00:00
val apiTagOwnerRequired = ResourceDocTag ( "OwnerViewRequired" )
2016-06-25 02:23:48 +00:00
val apiTagCounterparty = ResourceDocTag ( "Counterparty" )
2016-02-08 20:58:11 +00:00
val apiTagKyc = ResourceDocTag ( "KYC" )
val apiTagCustomer = ResourceDocTag ( "Customer" )
2016-05-22 12:01:37 +00:00
val apiTagOnboarding = ResourceDocTag ( "Onboarding" )
val apiTagUser = ResourceDocTag ( "User" )
val apiTagMeeting = ResourceDocTag ( "Meeting" )
2016-06-25 02:23:48 +00:00
val apiTagExperimental = ResourceDocTag ( "Experimental" )
2016-07-27 20:52:09 +00:00
val apiTagPerson = ResourceDocTag ( "Person" )
2017-09-15 11:46:12 +00:00
val apiTagCard = ResourceDocTag ( "Card" )
val apiTagSandbox = ResourceDocTag ( "Sandbox" )
2017-09-26 16:48:37 +00:00
val apiTagBranch = ResourceDocTag ( "Branch" )
val apiTagATM = ResourceDocTag ( "ATM" )
val apiTagProduct = ResourceDocTag ( "Product" )
val apiTagOpenData = ResourceDocTag ( "Open Data" )
val apiTagConsumer = ResourceDocTag ( "Consumer" )
val apiTagDataWarehouse = ResourceDocTag ( "Data Warehouse" )
val apiTagFx = ResourceDocTag ( "FX" )
2016-02-08 20:58:11 +00:00
2017-01-22 15:42:59 +00:00
case class Catalogs ( core : Boolean = false , psd2 : Boolean = false , obwg : Boolean = false )
2016-11-28 07:43:40 +00:00
val Core = true
val PSD2 = true
val OBWG = true
val notCore = false
val notPSD2 = false
val notOBWG = false
2017-09-12 16:47:44 +00:00
2017-04-26 12:39:00 +00:00
case class BaseErrorResponseBody (
2017-04-27 09:19:44 +00:00
//code: String,//maybe used, for now, 400,204,200...are handled in RestHelper class
//TODO, this should be a case class name, but for now, the InvalidNumber are just String, not the case class.
name : String ,
detail : String
2017-09-12 16:47:44 +00:00
)
2017-05-03 09:56:51 +00:00
//check #511, https://github.com/OpenBankProject/OBP-API/issues/511
2017-09-12 16:47:44 +00:00
// get rid of JValue, but in API-EXPLORER or other places, it need the Empty JValue "{}"
2017-05-03 09:56:51 +00:00
// So create the EmptyClassJson to set the empty JValue "{}"
case class EmptyClassJson ( )
2017-09-12 16:47:44 +00:00
2016-02-29 23:26:47 +00:00
// Used to document the API calls
2015-08-28 10:36:47 +00:00
case class ResourceDoc (
2017-09-09 20:02:17 +00:00
partialFunction : OBPEndpoint , // PartialFunction[Req, Box[User] => Box[JsonResponse]],
2017-11-12 10:55:56 +00:00
implementedInApiVersion : String , // TODO: Use ApiVersion enumeration instead of string
2017-11-12 07:58:09 +00:00
partialFunctionName : String , // The string name of the partial function that implements this resource. Could use it to link to the source code that implements the call
2017-09-09 20:02:17 +00:00
requestVerb : String , // GET, POST etc. TODO: Constrain to GET, POST etc.
requestUrl : String , // The URL (not including /obp/vX.X). Starts with / No trailing slash. TODO Constrain the string?
summary : String , // A summary of the call (originally taken from code comment) SHOULD be under 120 chars to be inline with Swagger
description : String , // Longer description (originally taken from github wiki)
exampleRequestBody : scala . Product , // An example of the body required (maybe empty)
successResponseBody : scala . Product , // A successful response body
errorResponseBodies : List [ String ] , // Possible error responses
catalogs : Catalogs ,
tags : List [ ResourceDocTag ]
2016-02-29 23:26:47 +00:00
)
2017-09-09 20:02:17 +00:00
2017-03-30 11:00:20 +00:00
/* *
2017-09-09 20:02:17 +00:00
*
2017-03-30 11:00:20 +00:00
* This is the base class for all kafka outbound case class
2017-09-09 20:02:17 +00:00
* action and messageFormat are mandatory
2017-03-30 11:00:20 +00:00
* The optionalFields can be any other new fields .
*/
abstract class OutboundMessageBase (
optionalFields : String *
) {
2017-03-16 18:22:59 +00:00
def action : String
2017-03-29 14:15:33 +00:00
def messageFormat : String
2017-03-16 18:22:59 +00:00
}
2017-09-09 20:02:17 +00:00
2017-03-30 11:00:20 +00:00
abstract class InboundMessageBase (
optionalFields : String *
) {
def errorCode : String
}
2017-03-16 18:22:59 +00:00
2017-03-10 14:56:13 +00:00
// Used to document the KafkaMessage calls
2017-03-14 14:56:16 +00:00
case class MessageDoc (
2017-03-30 11:00:20 +00:00
process : String ,
messageFormat : String ,
description : String ,
exampleOutboundMessage : JValue ,
2017-09-09 20:02:17 +00:00
exampleInboundMessage : JValue
2017-03-10 14:56:13 +00:00
)
2017-09-09 20:02:17 +00:00
2016-02-29 23:26:47 +00:00
// Define relations between API end points. Used to create _links in the JSON and maybe later for API Explorer browsing
case class ApiRelation (
fromPF : PartialFunction [ Req , Box [ User ] => Box [ JsonResponse ] ] ,
toPF : PartialFunction [ Req , Box [ User ] => Box [ JsonResponse ] ] ,
rel : String
)
// Populated from Resource Doc and ApiRelation
case class InternalApiLink (
fromPF : PartialFunction [ Req , Box [ User ] => Box [ JsonResponse ] ] ,
toPF : PartialFunction [ Req , Box [ User ] => Box [ JsonResponse ] ] ,
rel : String ,
requestUrl : String
)
// Used to pass context of current API call to the function that generates links for related Api calls.
case class DataContext (
user : Box [ User ] ,
bankId : Option [ BankId ] ,
accountId : Option [ AccountId ] ,
viewId : Option [ ViewId ] ,
counterpartyId : Option [ CounterpartyId ] ,
transactionId : Option [ TransactionId ]
)
2016-03-02 12:51:13 +00:00
case class CallerContext (
caller : PartialFunction [ Req , Box [ User ] => Box [ JsonResponse ] ]
)
2016-02-29 23:26:47 +00:00
case class CodeContext (
2016-03-02 12:51:13 +00:00
resourceDocsArrayBuffer : ArrayBuffer [ ResourceDoc ] ,
relationsArrayBuffer : ArrayBuffer [ ApiRelation ]
2016-02-29 23:26:47 +00:00
)
2016-03-05 07:04:41 +00:00
2016-02-29 23:26:47 +00:00
case class ApiLink (
2016-03-02 12:51:13 +00:00
rel : String ,
href : String
2016-02-29 23:26:47 +00:00
)
case class LinksJSON (
_links : List [ ApiLink ]
)
case class ResultAndLinksJSON (
result : JValue ,
_links : List [ ApiLink ]
)
def createResultAndLinksJSON ( result : JValue , links : List [ ApiLink ] ) : ResultAndLinksJSON = {
new ResultAndLinksJSON (
result ,
links
)
}
2016-01-20 21:29:43 +00:00
2016-07-15 00:24:38 +00:00
/*
Returns a string showed to the developer
*/
2016-01-20 21:29:43 +00:00
def authenticationRequiredMessage ( authRequired : Boolean ) : String =
2016-07-15 00:24:38 +00:00
authRequired match {
case true => "Authentication is Mandatory"
case false => "Authentication is Optional"
2016-01-20 21:29:43 +00:00
}
2016-03-05 07:04:41 +00:00
def apiVersionWithV ( apiVersion : String ) : String = {
// TODO Define a list of supported versions (put in Constant) and constrain the input
// Append v and replace _ with .
s" v ${ apiVersion . replaceAll ( "_" , "." ) } "
}
def fullBaseUrl : String = {
val crv = CurrentReq . value
val apiPathZeroFromRequest = crv . path . partPath ( 0 )
if ( apiPathZeroFromRequest != ApiPathZero ) throw new Exception ( "Configured ApiPathZero is not the same as the actual." )
val path = s" $HostName / $ApiPathZero "
path
}
2016-02-29 23:26:47 +00:00
// Modify URL replacing placeholders for Ids
def contextModifiedUrl ( url : String , context : DataContext ) = {
2016-03-05 07:04:41 +00:00
// Potentially replace BANK_ID
2016-02-29 23:26:47 +00:00
val url2 : String = context . bankId match {
case Some ( x ) => url . replaceAll ( "BANK_ID" , x . value )
case _ => url
}
val url3 : String = context . accountId match {
// Take care *not* to change OTHER_ACCOUNT_ID HERE
case Some ( x ) => url2 . replaceAll ( "/ACCOUNT_ID" , s" / ${ x . value } " ) . replaceAll ( "COUNTERPARTY_ID" , x . value )
case _ => url2
}
val url4 : String = context . viewId match {
case Some ( x ) => url3 . replaceAll ( "VIEW_ID" , { x . value } )
case _ => url3
}
val url5 : String = context . counterpartyId match {
// Change OTHER_ACCOUNT_ID or COUNTERPARTY_ID
case Some ( x ) => url4 . replaceAll ( "OTHER_ACCOUNT_ID" , x . value ) . replaceAll ( "COUNTERPARTY_ID" , x . value )
case _ => url4
}
val url6 : String = context . transactionId match {
case Some ( x ) => url5 . replaceAll ( "TRANSACTION_ID" , x . value )
case _ => url5
}
2016-03-05 07:04:41 +00:00
// Add host, port, prefix, version.
// not correct because call could be in other version
val fullUrl = s" $fullBaseUrl$url6 "
fullUrl
2016-02-29 23:26:47 +00:00
}
2016-03-02 12:51:13 +00:00
def getApiLinkTemplates ( callerContext : CallerContext ,
codeContext : CodeContext
2016-02-29 23:26:47 +00:00
) : List [ InternalApiLink ] = {
2016-03-12 16:36:03 +00:00
// Relations of the API version where the caller is defined.
2016-02-29 23:26:47 +00:00
val relations = codeContext . relationsArrayBuffer . toList
2016-03-12 16:36:03 +00:00
// Resource Docs
// Note: This doesn't allow linking to calls in earlier versions of the API
// TODO: Fix me
2016-02-29 23:26:47 +00:00
val resourceDocs = codeContext . resourceDocsArrayBuffer
2016-03-02 12:51:13 +00:00
val pf = callerContext . caller
2016-02-29 23:26:47 +00:00
val internalApiLinks : List [ InternalApiLink ] = for {
relation <- relations . filter ( r => r . fromPF == pf )
toResourceDoc <- resourceDocs . find ( rd => rd . partialFunction == relation . toPF )
}
yield new InternalApiLink (
pf ,
toResourceDoc . partialFunction ,
relation . rel ,
2016-03-05 07:04:41 +00:00
// Add the vVersion to the documented url
2017-09-09 20:02:17 +00:00
s" / ${ apiVersionWithV ( toResourceDoc . implementedInApiVersion ) } ${ toResourceDoc . requestUrl } "
2016-02-29 23:26:47 +00:00
)
internalApiLinks
}
2016-03-02 12:51:13 +00:00
// This is not currently including "templated" attribute
def halLinkFragment ( link : ApiLink ) : String = {
"\"" + link . rel + "\": { \"href\": \"" + link . href + "\" }"
}
// Since HAL links can't be represented via a case class, (they have dynamic attributes rather than a list) we need to generate them here.
def buildHalLinks ( links : List [ ApiLink ] ) : JValue = {
val halLinksString = links match {
case head : : tail => tail . foldLeft ( "{" ) { ( r : String , c : ApiLink ) => ( r + " " + halLinkFragment ( c ) + " ," ) } + halLinkFragment ( head ) + "}"
case Nil => "{}"
}
parse ( halLinksString )
}
// Returns API links (a list of them) that have placeholders (e.g. BANK_ID) replaced by values (e.g. ulster-bank)
def getApiLinks ( callerContext : CallerContext , codeContext : CodeContext , dataContext : DataContext ) : List [ ApiLink ] = {
val templates = getApiLinkTemplates ( callerContext , codeContext )
2016-02-29 23:26:47 +00:00
// Replace place holders in the urls like BANK_ID with the current value e.g. 'ulster-bank' and return as ApiLinks for external consumption
2016-03-02 12:51:13 +00:00
val links = templates . map ( i => ApiLink ( i . rel ,
contextModifiedUrl ( i . requestUrl , dataContext ) )
)
links
2016-02-29 23:26:47 +00:00
}
2016-01-20 21:29:43 +00:00
2016-03-02 12:51:13 +00:00
// Returns links formatted at objects.
def getHalLinks ( callerContext : CallerContext , codeContext : CodeContext , dataContext : DataContext ) : JValue = {
val links = getApiLinks ( callerContext , codeContext , dataContext )
getHalLinksFromApiLinks ( links )
}
2016-01-20 21:29:43 +00:00
2016-03-02 12:51:13 +00:00
def getHalLinksFromApiLinks ( links : List [ ApiLink ] ) : JValue = {
val halLinksJson = buildHalLinks ( links )
halLinksJson
}
2016-06-03 15:01:55 +00:00
def isSuperAdmin ( user_id : String ) : Boolean = {
val user_ids = Props . get ( "super_admin_user_ids" , "super_admin_user_ids is not defined" ) . split ( "," ) . map ( _ . trim ) . toList
user_ids . filter ( _ == user_id ) . length > 0
}
def hasEntitlement ( bankId : String , userId : String , role : ApiRole ) : Boolean = {
2016-06-17 09:19:05 +00:00
! Entitlement . entitlement . vend . getEntitlement ( bankId , userId , role . toString ) . isEmpty
2016-06-03 15:01:55 +00:00
}
2016-10-18 09:36:08 +00:00
// Function checks does a user specified by a parameter userId has at least one role provided by a parameter roles at a bank specified by a parameter bankId
// i.e. does user has assigned at least one role from the list
2016-10-17 07:21:46 +00:00
def hasAtLeastOneEntitlement ( bankId : String , userId : String , roles : List [ ApiRole ] ) : Boolean = {
val list : List [ Boolean ] = for ( role <- roles ) yield {
! Entitlement . entitlement . vend . getEntitlement ( if ( role . requiresBankId == true ) bankId else "" , userId , role . toString ) . isEmpty
}
list . exists ( _ == true )
}
2016-10-18 09:36:08 +00:00
// Function checks does a user specified by a parameter userId has all roles provided by a parameter roles at a bank specified by a parameter bankId
// i.e. does user has assigned all roles from the list
2017-06-26 10:53:22 +00:00
// TODO Should we accept Option[BankId] for bankId instead of String ?
2016-10-18 09:36:08 +00:00
def hasAllEntitlements ( bankId : String , userId : String , roles : List [ ApiRole ] ) : Boolean = {
val list : List [ Boolean ] = for ( role <- roles ) yield {
! Entitlement . entitlement . vend . getEntitlement ( if ( role . requiresBankId == true ) bankId else "" , userId , role . toString ) . isEmpty
}
list . forall ( _ == true )
}
2016-07-20 08:40:12 +00:00
def getCustomers ( ids : List [ String ] ) : List [ Customer ] = {
val customers = {
for { id <- ids
c = Customer . customerProvider . vend . getCustomerByCustomerId ( id )
u <- c
} yield {
u
}
}
customers
}
2016-12-27 10:08:43 +00:00
def getAutocompleteValue : String = {
Props . get ( "autocomplete_at_login_form_enabled" , "false" ) match {
case "true" => "on"
case "false" => "off"
case _ => "off"
}
}
2017-09-12 16:47:44 +00:00
2017-04-09 15:16:12 +00:00
// check is there a "$" in the input value.
// eg: MODULE$ is not the useful input.
// eg2: allFieldsAndValues is just for SwaggerJSONsV220.allFieldsAndValues,it is not useful.
def notExstingBaseClass ( input : String ) : Boolean = {
2017-04-10 07:20:10 +00:00
! input . contains ( "$" ) && ! input . equalsIgnoreCase ( "allFieldsAndValues" )
2017-04-09 15:16:12 +00:00
}
2016-12-27 10:08:43 +00:00
2017-04-13 15:24:12 +00:00
def saveConnectorMetric [ R ] ( blockOfCode : => R ) ( nameOfFunction : String = "" ) ( implicit nameOfConnector : String ) : R = {
val t0 = System . currentTimeMillis ( )
val result = blockOfCode
// call-by-name
val t1 = System . currentTimeMillis ( )
2017-04-20 09:07:44 +00:00
if ( Props . getBool ( "write_metrics" , false ) ) {
2017-07-10 14:47:31 +00:00
val correlationId = getCorrelationId ( )
2017-04-26 09:17:10 +00:00
Future {
2017-07-07 10:34:58 +00:00
ConnectorMetricsProvider . metrics . vend . saveConnectorMetric ( nameOfConnector , nameOfFunction , correlationId , now , t1 - t0 )
2017-04-26 09:17:10 +00:00
}
2017-04-20 09:07:44 +00:00
}
2017-04-13 15:24:12 +00:00
result
}
2017-04-19 13:21:28 +00:00
def akkaSanityCheck ( ) : Box [ Boolean ] = {
2017-07-18 09:51:28 +00:00
Props . getBool ( "use_akka" , false ) match {
case true =>
val remotedataSecret = Props . get ( "remotedata.secret" ) . openOrThrowException ( "Cannot obtain property remotedata.secret" )
SanityCheck . sanityCheck . vend . remoteAkkaSanityCheck ( remotedataSecret )
case false => Empty
2017-04-20 16:50:32 +00:00
}
2017-07-18 09:51:28 +00:00
2017-04-20 16:50:32 +00:00
2017-04-19 13:21:28 +00:00
}
2017-07-11 13:18:41 +00:00
/* *
* @return - the HTTP session ID
*/
2017-07-10 14:47:31 +00:00
def getCorrelationId ( ) : String = S . containerSession . map ( _ . sessionId ) . openOr ( "" )
2017-07-11 13:18:41 +00:00
/* *
* @return - the remote address of the client or the last seen proxy .
*/
def getRemoteIpAddress ( ) : String = S . containerRequest . map ( _ . remoteAddress ) . openOr ( "Unknown" )
/* *
* @return - the fully qualified name of the client host or last seen proxy
*/
def getRemoteHost ( ) : String = S . containerRequest . map ( _ . remoteHost ) . openOr ( "Unknown" )
/* *
* @return - the source port of the client or last seen proxy .
*/
def getRemotePort ( ) : Int = S . containerRequest . map ( _ . remotePort ) . openOr ( 0 )
2017-07-10 14:47:31 +00:00
2017-07-18 10:24:45 +00:00
/* *
* Defines Gateway Custom Response Header .
*/
2017-07-19 13:48:11 +00:00
val gatewayResponseHeaderName = "GatewayLogin"
2017-07-18 10:24:45 +00:00
/* *
* Set value of Gateway Custom Response Header .
*/
2017-10-23 15:16:34 +00:00
def setGatewayResponseHeader ( s : S ) ( value : String ) = s . setSessionAttribute ( gatewayResponseHeaderName , value )
2017-07-18 10:24:45 +00:00
/* *
* @return - Gateway Custom Response Header .
*/
def getGatewayResponseHeader ( ) = {
2017-07-19 13:21:11 +00:00
S . getSessionAttribute ( gatewayResponseHeaderName ) match {
case Full ( h ) => List ( ( gatewayResponseHeaderName , h ) )
2017-07-18 10:24:45 +00:00
case _ => Nil
}
}
2017-11-10 09:36:42 +00:00
def getGatewayLoginJwt ( ) : Option [ String ] = {
2017-11-10 08:49:33 +00:00
getGatewayResponseHeader ( ) match {
case Nil =>
None
case x : : Nil =>
Some ( x . _2 )
}
}
2017-10-23 15:16:34 +00:00
/* *
* Set value of GatewayLogin username .
*/
def setGatewayLoginUsername ( s : S ) ( value : String ) = s . setSessionAttribute ( gatewayResponseHeaderName + "username" , value )
2017-11-08 15:20:18 +00:00
2017-10-23 15:16:34 +00:00
/* *
2017-10-23 22:02:39 +00:00
* Set value of GatewayLogin cbsToken .
2017-10-23 15:16:34 +00:00
*/
2017-11-08 15:20:18 +00:00
def setGatewayLoginCbsToken ( s : S ) ( value : Option [ String ] ) = {
value match {
case Some ( v ) => s . setSessionAttribute ( gatewayResponseHeaderName + "cbstoken" , v )
case _ => // Do nothing
}
}
2017-11-12 07:58:09 +00:00
2017-10-23 15:16:34 +00:00
/* *
* @return - GatewayLogin username Header .
*/
def getGatewayLoginUsername ( ) = {
S . getSessionAttribute ( gatewayResponseHeaderName + "username" ) match {
case Full ( h ) => h
case _ => ""
}
}
2017-11-12 07:58:09 +00:00
2017-10-23 15:16:34 +00:00
/* *
* @return - GatewayLogin cbsToken Header .
*/
def getGatewayLoginCbsToken ( ) = {
S . getSessionAttribute ( gatewayResponseHeaderName + "cbstoken" ) match {
case Full ( h ) => h
case _ => ""
}
}
2017-07-18 10:24:45 +00:00
2017-08-24 13:00:44 +00:00
/* *
* Turn a string of format "FooBar" into snake case "foo_bar"
*
* Note : snakify is not reversible , ie . in general the following will _not_ be true :
2017-08-16 14:51:40 +00:00
*
* s == camelify ( snakify ( s ) )
*
* @return the underscored JValue
*/
def snakify ( json : JValue ) : JValue = json mapField {
case JField ( name , x ) => JField ( StringHelpers . snakify ( name ) , x )
}
/* *
* Turns a string of format "foo_bar" into camel case "FooBar"
*
* Functional code courtesy of Jamie Webb ( j @jmawebb . cjb . net ) 2006 / 11 / 28
* @param json the JValue to CamelCase
*
* @return the CamelCased JValue
*/
def camelify ( json : JValue ) : JValue = json mapField {
case JField ( name , x ) => JField ( StringHelpers . camelify ( name ) , x )
}
/* *
* Turn a string of format "foo_bar" into camel case with the first letter in lower case : " fooBar "
* This function is especially used to camelCase method names .
*
* @param json the JValue to CamelCase
*
* @return the CamelCased JValue
*/
def camelifyMethod ( json : JValue ) : JValue = json mapField {
case JField ( name , x ) => JField ( StringHelpers . camelifyMethod ( name ) , x )
}
2017-09-06 14:31:17 +00:00
2017-09-09 20:02:17 +00:00
def getDisabledVersions ( ) : List [ String ] = Props . get ( "api_disabled_versions" ) . getOrElse ( "" ) . replace ( "[" , "" ) . replace ( "]" , "" ) . split ( "," ) . toList . filter ( _ . nonEmpty )
def getDisabledEndpoints ( ) : List [ String ] = Props . get ( "api_disabled_endpoints" ) . getOrElse ( "" ) . replace ( "[" , "" ) . replace ( "]" , "" ) . split ( "," ) . toList . filter ( _ . nonEmpty )
def getEnabledVersions ( ) : List [ String ] = Props . get ( "api_enabled_versions" ) . getOrElse ( "" ) . replace ( "[" , "" ) . replace ( "]" , "" ) . split ( "," ) . toList . filter ( _ . nonEmpty )
def getEnabledEndpoints ( ) : List [ String ] = Props . get ( "api_enabled_endpoints" ) . getOrElse ( "" ) . replace ( "[" , "" ) . replace ( "]" , "" ) . split ( "," ) . toList . filter ( _ . nonEmpty )
2017-11-12 07:58:09 +00:00
2017-09-13 14:38:25 +00:00
def validatePhoneNumber ( number : String ) : Boolean = {
number . toList match {
case x : : _ if x != ' + ' => false // First char has to be +
case _ : : xs if xs . size > 1 5 => false // Number of digits has to be up to 15
case _ : : xs if xs . size < 5 => false // Minimal number of digits is 5
case _ : : xs if xs . exists ( c => Character . isDigit ( c ) = = false ) => false // Ony digits are allowed
case _ => true
2017-09-09 20:02:17 +00:00
2017-09-13 14:38:25 +00:00
}
} /*
2017-09-12 16:47:44 +00:00
Determine if a version should be allowed .
2017-09-09 20:02:17 +00:00
2017-09-12 16:47:44 +00:00
For a VERSION to be allowed it must be :
2017-09-09 20:02:17 +00:00
2017-09-12 16:47:44 +00:00
1 ) Absent from Props api_disabled_versions
2 ) Present here ( api_enabled_versions = [ v2_2_0 ,v3_0_0 ] ) - OR - api_enabled_versions must be empty .
2017-09-09 20:02:17 +00:00
2017-09-12 16:47:44 +00:00
Note we use "v" and "_" in the name to match the ApiVersions enumeration in ApiUtil . scala
*/
def versionIsAllowed ( version : ApiVersion ) : Boolean = {
2017-09-09 20:02:17 +00:00
val disabledVersions : List [ String ] = getDisabledVersions ( )
val enabledVersions : List [ String ] = getEnabledVersions ( )
2017-09-12 16:47:44 +00:00
if (
2017-09-09 20:02:17 +00:00
! disabledVersions . contains ( version . toString ) &&
// Enabled versions or all
( enabledVersions . contains ( version . toString ) || enabledVersions . isEmpty )
2017-09-12 16:47:44 +00:00
) true
else
false
}
/*
If a version is allowed , enable its endpoints .
Note a version such as v3_0_0 . OBPAPI3_0_0 may well include routes from other earlier versions .
*/
2017-09-09 20:02:17 +00:00
2017-09-12 16:47:44 +00:00
def enableVersionIfAllowed ( version : ApiVersion ) : Boolean = {
val allowed : Boolean = if ( versionIsAllowed ( version )
) {
2017-09-09 20:02:17 +00:00
version match {
2017-09-13 07:37:39 +00:00
// case ApiVersion.v1_0 => LiftRules.statelessDispatch.append(v1_0.OBPAPI1_0)
// case ApiVersion.v1_1 => LiftRules.statelessDispatch.append(v1_1.OBPAPI1_1)
// case ApiVersion.v1_2 => LiftRules.statelessDispatch.append(v1_2.OBPAPI1_2)
2017-09-09 20:02:17 +00:00
// Can we depreciate the above?
2017-09-12 16:47:44 +00:00
case ApiVersion . v1_2_1 => LiftRules . statelessDispatch . append ( v1_2_1 . OBPAPI1_2_1 )
case ApiVersion . v1_3_0 => LiftRules . statelessDispatch . append ( v1_3_0 . OBPAPI1_3_0 )
case ApiVersion . v1_4_0 => LiftRules . statelessDispatch . append ( v1_4_0 . OBPAPI1_4_0 )
case ApiVersion . v2_0_0 => LiftRules . statelessDispatch . append ( v2_0_0 . OBPAPI2_0_0 )
case ApiVersion . v2_1_0 => LiftRules . statelessDispatch . append ( v2_1_0 . OBPAPI2_1_0 )
case ApiVersion . v2_2_0 => LiftRules . statelessDispatch . append ( v2_2_0 . OBPAPI2_2_0 )
case ApiVersion . v3_0_0 => LiftRules . statelessDispatch . append ( v3_0_0 . OBPAPI3_0_0 )
2017-09-09 20:02:17 +00:00
}
logger . info ( s" ${ version . toString } was ENABLED " )
true
} else {
logger . info ( s" ${ version . toString } was NOT enabled " )
false
}
allowed
}
type OBPEndpoint = PartialFunction [ Req , Box [ User ] => Box [ JsonResponse ] ]
/*
Versions are groups of endpoints in a file
*/
2017-09-12 16:47:44 +00:00
object ApiVersion extends Enumeration {
type ApiVersion = Value
2017-09-09 20:02:17 +00:00
val v1_0 , v1_1 , v1_2 , v1_2_1 , v1_3_0 , v1_4_0 , v2_0_0 , v2_1_0 , v2_2_0 , v3_0_0 , importerApi , accountsApi , bankMockApi = Value
}
2017-09-12 16:47:44 +00:00
def convertToApiVersion ( apiVersionString : String ) : Box [ ApiVersion ] = {
// Make sure the string has the "v" prefix
try {
val apiVersionStringWithV : String = if ( apiVersionString . take ( 1 ) . toLowerCase != "v" ) {
s" v $apiVersionString "
} else
apiVersionString
// replace dots with _
Full ( ApiVersion . withName ( apiVersionStringWithV . replace ( "." , "_" ) ) )
} catch {
case e : Exception => Failure ( InvalidApiVersionString )
}
}
def dottedApiVersion ( apiVersion : ApiVersion ) : String = apiVersion . toString . replace ( "_" , "." ) . replace ( "v" , "" )
def vDottedApiVersion ( apiVersion : ApiVersion ) : String = apiVersion . toString . replace ( "_" , "." )
2017-09-09 20:02:17 +00:00
2017-11-12 10:55:56 +00:00
// TODO make this a method of the ApiVersion object so its easier to call
def noV ( apiVersion : ApiVersion ) : String = apiVersion . toString . replace ( "v" , "" ) . replace ( "V" , "" )
2017-09-09 20:02:17 +00:00
def getAllowedEndpoints ( endpoints : List [ OBPEndpoint ] , resourceDocs : ArrayBuffer [ ResourceDoc ] ) : List [ OBPEndpoint ] = {
2017-09-06 14:31:17 +00:00
2017-09-09 20:02:17 +00:00
// Endpoints
val disabledEndpoints = getDisabledEndpoints
2017-09-06 14:31:17 +00:00
2017-09-09 20:02:17 +00:00
// Endpoints
val enabledEndpoints = getEnabledEndpoints
2017-09-06 14:31:17 +00:00
2017-09-09 20:02:17 +00:00
val routes = for (
item <- resourceDocs
if
// Remove any Resource Doc / endpoint mentioned in Disabled endpoints in Props
2017-11-12 07:58:09 +00:00
! disabledEndpoints . contains ( item . partialFunctionName ) &&
2017-09-09 20:02:17 +00:00
// Only allow Resrouce Doc / endpoints mentioned in enabled endpoints - unless none are mentioned in which case ignore.
2017-11-12 07:58:09 +00:00
( enabledEndpoints . contains ( item . partialFunctionName ) || enabledEndpoints . isEmpty ) &&
2017-09-09 20:02:17 +00:00
// Only allow Resource Doc if it matches one of the pre selected endpoints from the version list.
// i.e. this function may recieve more Resource Docs than version endpoints
endpoints . exists ( _ == item . partialFunction )
)
yield item . partialFunction
routes . toList
}
2017-09-15 14:11:04 +00:00
def extractToCaseClass [ T ] ( in : String ) ( implicit ev : Manifest [ T ] ) : Box [ T ] = {
2017-11-13 11:00:54 +00:00
implicit val formats = net . liftweb . json . DefaultFormats
2017-09-15 14:11:04 +00:00
try {
val parseJValue : JValue = parse ( in )
val t : T = parseJValue . extract [ T ]
Full ( t )
} catch {
case m : ParseException =>
logger . error ( "String-->Jvalue parse error" + in , m )
Failure ( "String-->Jvalue parse error" + in + m . getMessage )
case m : MappingException =>
logger . error ( "JValue-->CaseClass extract error" + in , m )
Failure ( "JValue-->CaseClass extract error" + in + m . getMessage )
case m : Throwable =>
logger . error ( "extractToCaseClass unknow error" + in , m )
Failure ( "extractToCaseClass unknow error" + in + m . getMessage )
}
}
2017-09-06 14:31:17 +00:00
2017-09-07 13:13:47 +00:00
def scalaFutureToLaFuture [ T ] ( scf : Future [ T ] ) ( implicit m : Manifest [ T ] ) : LAFuture [ T ] = {
val laf = new LAFuture [ T ]
scf . onSuccess {
case v : T => laf . satisfy ( v )
case _ => laf . abort
}
scf . onFailure {
case e : Throwable => laf . fail ( Failure ( e . getMessage ( ) , Full ( e ) , Empty ) )
}
laf
}
2017-10-31 07:05:28 +00:00
/* *
* @param in LAFuture with a useful payload . Payload is tuple ( Case Class , Custom Response Header )
* @return value of type JsonResponse
*
* Process a request asynchronously . The thread will not
* block until there 's a response . The parameter is a function
* that takes a function as it 's parameter . The function is invoked
* when the calculation response is ready to be rendered :
* RestContinuation . async {
* reply => {
* myActor ! DoCalc ( 123 , answer => reply { XmlResponse ( < i > { answer } </ i > ) } )
* }
* }
* The body of the function will be executed on a separate thread .
* When the answer is ready , apply the reply function . . . the function
* body will be executed in the scope of the current request ( the
* current session and the current Req object ) .
*/
2017-10-30 15:49:29 +00:00
def futureToResponse [ T ] ( in : LAFuture [ ( T , CustomResponseHeaders ) ] ) : JsonResponse = {
2017-09-08 07:35:26 +00:00
RestContinuation . async ( reply => {
2017-10-30 15:49:29 +00:00
in . onSuccess ( t => reply . apply ( successJsonResponseFromCaseClass ( t . _1 ) ( t . _2 ) ) )
2017-09-08 07:35:26 +00:00
in . onFail {
case Failure ( msg , _ , _ ) => reply . apply ( errorJsonResponse ( msg ) )
case _ => reply . apply ( errorJsonResponse ( "Error" ) )
}
} )
}
2017-10-31 07:05:28 +00:00
/* *
* @param in LAFuture with a useful payload . Payload is tuple ( Case Class , Custom Response Header )
* @return value of type Box [ JsonResponse ]
*
* Process a request asynchronously . The thread will not
* block until there 's a response . The parameter is a function
* that takes a function as it 's parameter . The function is invoked
* when the calculation response is ready to be rendered :
* RestContinuation . async {
* reply => {
* myActor ! DoCalc ( 123 , answer => reply { XmlResponse ( < i > { answer } </ i > ) } )
* }
* }
* The body of the function will be executed on a separate thread .
* When the answer is ready , apply the reply function . . . the function
* body will be executed in the scope of the current request ( the
* current session and the current Req object ) .
*/
2017-10-30 15:49:29 +00:00
def futureToBoxedResponse [ T ] ( in : LAFuture [ ( T , CustomResponseHeaders ) ] ) : Box [ JsonResponse ] = {
2017-10-05 22:02:08 +00:00
RestContinuation . async ( reply => {
2017-10-30 15:49:29 +00:00
in . onSuccess ( t => Full ( reply . apply ( successJsonResponseFromCaseClass ( t . _1 ) ( t . _2 ) ) ) )
2017-10-05 22:02:08 +00:00
in . onFail {
case Failure ( msg , _ , _ ) => Full ( reply . apply ( errorJsonResponse ( msg ) ) )
case _ => Full ( reply . apply ( errorJsonResponse ( "Error" ) ) )
}
} )
}
2017-10-30 15:49:29 +00:00
implicit def scalaFutureToJsonResponse [ T ] ( scf : Future [ ( T , CustomResponseHeaders ) ] ) ( implicit m : Manifest [ T ] ) : JsonResponse = {
2017-10-05 22:02:08 +00:00
futureToResponse ( scalaFutureToLaFuture ( scf ) )
2017-09-19 12:16:48 +00:00
}
2017-10-05 22:02:08 +00:00
/* *
* This function is implicitly used at Endpoints to transform a Scala Future to Box [ JsonResponse ] for instance next part of code
* for {
users <- Future { someComputation }
} yield {
users
}
will be translated by Scala compiler to
APIUtil . scalaFutureToBoxedJsonResponse (
for {
users <- Future { someComputation }
} yield {
users
}
)
* @param scf
* @param m
* @tparam T
* @return
*/
2017-10-30 15:49:29 +00:00
implicit def scalaFutureToBoxedJsonResponse [ T ] ( scf : Future [ ( T , CustomResponseHeaders ) ] ) ( implicit m : Manifest [ T ] ) : Box [ JsonResponse ] = {
2017-10-05 22:02:08 +00:00
futureToBoxedResponse ( scalaFutureToLaFuture ( scf ) )
}
/* *
* This function is planed t be used at an endpoint in order to get a User based on Authorization Header data
* It should do the same thing as function OBPRestHelper . failIfBadAuthorizationHeader does
* The only difference is that this function use Akka 's Future in non - blocking way i . e . without using Await . result
* @return An User wrapped into a Future
*/
2017-10-23 19:02:41 +00:00
def getUserFromAuthorizationHeaderFuture ( ) : Future [ ( Box [ User ] , Option [ String ] ) ] = {
2017-10-05 22:02:08 +00:00
if ( hasAnOAuthHeader ) {
getUserFromOAuthHeaderFuture ( )
} else if ( Props . getBool ( "allow_direct_login" , true ) && hasDirectLoginHeader ) {
DirectLogin . getUserFromDirectLoginHeaderFuture ( )
} else if ( Props . getBool ( "allow_gateway_login" , false ) && hasGatewayHeader ) {
Props . get ( "gateway.host" ) match {
case Full ( h ) if h . split ( "," ) . toList . exists ( _ . equalsIgnoreCase ( getRemoteIpAddress ( ) ) == true ) => // Only addresses from white list can use this feature
2017-10-23 19:02:41 +00:00
val s = S
val ( httpCode , message , parameters ) = GatewayLogin . validator ( s . request )
2017-10-05 22:02:08 +00:00
httpCode match {
case 200 =>
val payload = GatewayLogin . parseJwt ( parameters )
payload match {
case Full ( payload ) =>
GatewayLogin . getOrCreateResourceUserFuture ( payload : String ) map {
case Full ( ( u , cbsAuthToken ) ) => // Authentication is successful
GatewayLogin . getOrCreateConsumer ( payload , u )
2017-10-30 15:49:29 +00:00
val jwt = GatewayLogin . createJwt ( payload , cbsAuthToken )
( Full ( u ) , Full ( jwt ) )
2017-10-23 19:02:41 +00:00
case Failure ( msg , t , c ) =>
( Failure ( msg , t , c ) , None )
2017-10-05 22:02:08 +00:00
case _ =>
2017-10-23 19:02:41 +00:00
( Failure ( payload ) , None )
2017-10-05 22:02:08 +00:00
}
2017-10-23 19:02:41 +00:00
case Failure ( msg , t , c ) =>
Future { ( Failure ( msg , t , c ) , None ) }
2017-10-05 22:02:08 +00:00
case _ =>
2017-10-23 19:02:41 +00:00
Future { ( Failure ( ErrorMessages . GatewayLoginUnknownError ) , None ) }
2017-10-05 22:02:08 +00:00
}
case _ =>
2017-10-23 19:02:41 +00:00
Future { ( Failure ( message ) , None ) }
2017-10-05 22:02:08 +00:00
}
case Full ( h ) if h . split ( "," ) . toList . exists ( _ . equalsIgnoreCase ( getRemoteIpAddress ( ) ) == false ) => // All other addresses will be rejected
2017-10-23 19:02:41 +00:00
Future { ( Failure ( ErrorMessages . GatewayLoginWhiteListAddresses ) , None ) }
2017-10-05 22:02:08 +00:00
case Empty =>
2017-10-23 19:02:41 +00:00
Future { ( Failure ( ErrorMessages . GatewayLoginHostPropertyMissing ) , None ) } // There is no gateway.host in props file
case Failure ( msg , t , c ) =>
Future { ( Failure ( msg , t , c ) , None ) }
2017-10-05 22:02:08 +00:00
case _ =>
2017-10-23 19:02:41 +00:00
Future { ( Failure ( ErrorMessages . GatewayLoginUnknownError ) , None ) }
2017-10-05 22:02:08 +00:00
}
} else {
2017-10-23 19:02:41 +00:00
Future { ( Empty , None ) }
2017-10-05 22:02:08 +00:00
}
}
2017-10-12 19:23:26 +00:00
/* *
* This function is used to factor out common code at endpoints regarding Authorized access
2017-10-13 12:33:16 +00:00
* @param errorMsg is a message which will be provided as a response in case that Box [ User ] = Empty
2017-10-12 19:23:26 +00:00
*/
2017-10-23 19:02:41 +00:00
def extractUserFromHeaderOrError ( errorMsg : String ) : Future [ ( Box [ User ] , Option [ String ] ) ] = {
2017-10-12 19:23:26 +00:00
getUserFromAuthorizationHeaderFuture ( ) map {
2017-10-23 19:02:41 +00:00
x => ( fullBoxOrException ( x . _1 ?~! errorMsg ) , x . _2 )
2017-10-12 19:23:26 +00:00
}
}
2017-10-05 22:02:08 +00:00
/* *
* This Function is used to terminate a Future used in for - comprehension with specific message
* Please note that boxToFailed ( Empty ?~ ( "Some failure message" ) ) will be transformed to Failure ( "Some failure message" , Empty , Empty )
* @param box Some boxed type
* @return Boxed value or throw some exception
*/
def fullBoxOrException [ T ] ( box : Box [ T ] ) ( implicit m : Manifest [ T ] ) : Box [ T ] = {
box match {
case Full ( v ) => // Just forwarding
Full ( v )
case Empty => // Just forwarding
throw new Exception ( "Empty Box not allowed" )
case ParamFailure ( msg , _ , _ , _ ) =>
throw new Exception ( msg )
case obj @Failure ( msg , _ , c ) =>
val failuresMsg = Props . getBool ( "display_internal_errors" ) . openOr ( false ) match {
case true => // Show all error in a chain
obj . messageChain
case false => // Do not display internal errors
val obpFailures = obj . failureChain . filter ( _ . msg . contains ( "OBP-" ) )
obpFailures match {
case Nil => ErrorMessages . AnUnspecifiedOrInternalErrorOccurred
case _ => obpFailures . map ( _ . msg ) . mkString ( " <- " )
}
}
throw new Exception ( failuresMsg )
case _ =>
throw new Exception ( UnknownError )
}
}
2017-10-23 19:02:41 +00:00
def unboxFullAndWrapIntoFuture [ T ] ( box : Box [ T ] ) ( implicit m : Manifest [ T ] ) : Future [ T ] = {
Future {
unboxFull ( box )
}
}
2017-10-11 10:35:55 +00:00
def unboxFull [ T ] ( box : Box [ T ] ) ( implicit m : Manifest [ T ] ) : T = {
box match {
2017-10-12 19:48:28 +00:00
case Full ( value ) =>
value
case _ =>
throw new Exception ( "Only Full Box is allowed at function unboxFull" )
2017-10-11 10:35:55 +00:00
}
}
2017-10-05 22:02:08 +00:00
2013-06-14 08:35:34 +00:00
}