\
-# API Explorer url. Change this to the instance of Sofi linked to this API. e.g. sofi.openbankproject.com/api-explorer -webui_api_explorer_url = http://sofi.openbankproject.com/api-explorer +# API Explorer url. Change to your instance +webui_api_explorer_url = http://apiexplorer.openbankproject.com + +# Sofi url. (AKA Social Finance) Change to your instance +webui_sofi_url = http://sofi.openbankproject.com # Starting page of documentation. Change this if you have a specific landing page. webui_api_documentation_url = https://github.com/OpenBankProject/OBP-API/wiki +# To display a custom message above the username / password box +# We currently use this to display example customer login in sandbox etc. +webui_login_page_special_instructions= + +# Link for SDKs +webui_sdks_url = https://github.com/OpenBankProject/OBP-API/wiki/OAuth-Client-SDKS + + + ## For partner logos and links webui_main_partners=[\ {"logoUrl":"http://www.example.com/images/logo.png", "homePageUrl":"http://www.example.com", "altText":"Example 1"},\ @@ -131,3 +151,13 @@ webui_main_style_sheet = /media/css/website.css # Override certain elements (with important styles) webui_override_style_sheet = + + +## API Options +apiOptions.getBranchesIsPublic = true +apiOptions.getAtmsIsPublic = true +apiOptions.getProductsIsPublic = true + +# Default Bank. Incase the server wants to support a default bank so developers don't have to specify BANK_ID +# e.g. developers could use /my/accounts as well as /my/banks/BANK_ID/accounts +defaultBank.bank_id=THE_DEFAULT_BANK_ID diff --git a/src/main/scala/bootstrap/liftweb/Boot.scala b/src/main/scala/bootstrap/liftweb/Boot.scala index 20b70a600..cbe1955e9 100755 --- a/src/main/scala/bootstrap/liftweb/Boot.scala +++ b/src/main/scala/bootstrap/liftweb/Boot.scala @@ -31,41 +31,45 @@ Berlin 13359, Germany */ package bootstrap.liftweb +import java.io.{File, FileInputStream} +import java.util.Locale +import javax.mail.internet.MimeMessage + +import code.api.ResourceDocs1_4_0.ResourceDocs +import code.api._ import code.api.sandbox.SandboxApiCalls +import code.atms.MappedAtm +import code.branches.MappedBranch import code.crm.MappedCrmEvent -import code.management.ImporterAPI -import code.management.AccountsAPI +import code.customer.{MappedCustomer, MappedCustomerMessage} +import code.kycdocuments.MappedKycDocument +import code.kycmedias.MappedKycMedia +import code.kycchecks.MappedKycCheck +import code.kycstatuses.MappedKycStatus +import code.socialmedia.MappedSocialMedia +import code.management.{AccountsAPI, ImporterAPI} import code.metadata.comments.MappedComment -import code.metadata.counterparties.{MappedCounterpartyWhereTag, MappedCounterpartyMetadata} +import code.metadata.counterparties.{MappedCounterpartyMetadata, MappedCounterpartyWhereTag} import code.metadata.narrative.MappedNarrative import code.metadata.tags.MappedTag import code.metadata.transactionimages.MappedTransactionImage import code.metadata.wheretags.MappedWhereTag import code.metrics.MappedMetric -import code.branches.{MappedBranch} -import code.atms.{MappedAtm} -import code.customer.{MappedCustomerMessage, MappedCustomer} -import code.products.MappedProduct -import code.tesobe.CashAccountAPI -import code.transactionrequests.MappedTransactionRequest -import net.liftweb._ -import util._ -import common._ -import http._ -import sitemap._ -import Loc._ -import mapper._ -import net.liftweb.util.Helpers._ -import net.liftweb.util.Schedule -import net.liftweb.util.Helpers -import java.util.Locale -import java.io.FileInputStream -import java.io.File -import javax.mail.internet.MimeMessage import code.model._ import code.model.dataAccess._ -import code.api._ +import code.products.MappedProduct import code.snippet.{OAuthAuthorisation, OAuthWorkedThanks} +import code.tesobe.CashAccountAPI +import code.transactionrequests.MappedTransactionRequest +import net.liftweb.common._ +import net.liftweb.http._ +import net.liftweb.mapper._ +import net.liftweb.sitemap.Loc._ +import net.liftweb.sitemap._ +import net.liftweb.util.Helpers._ +import net.liftweb.util.{Helpers, Schedule, _} +import code.api.Constant._ + /** * A class that's instantiated early and run. It allows the application @@ -175,6 +179,7 @@ class Boot extends Loggable{ } logger.info("running mode: " + runningMode) + logger.info(s"ApiPathZero (the bit before version) is $ApiPathZero") // where to search snippets LiftRules.addToPackages("code") @@ -182,13 +187,20 @@ class Boot extends Loggable{ //OAuth API call LiftRules.statelessDispatch.append(OAuthHandshake) + // JWT auth endpoints + if(Props.getBool("allow_direct_login", true)) { + LiftRules.statelessDispatch.append(DirectLogin) + } + // Add the various API versions LiftRules.statelessDispatch.append(v1_0.OBPAPI1_0) LiftRules.statelessDispatch.append(v1_1.OBPAPI1_1) LiftRules.statelessDispatch.append(v1_2.OBPAPI1_2) + // Can we depreciate the above? LiftRules.statelessDispatch.append(v1_2_1.OBPAPI1_2_1) LiftRules.statelessDispatch.append(v1_3_0.OBPAPI1_3_0) LiftRules.statelessDispatch.append(v1_4_0.OBPAPI1_4_0) + LiftRules.statelessDispatch.append(v2_0_0.OBPAPI2_0_0) //add management apis LiftRules.statelessDispatch.append(ImporterAPI) @@ -198,6 +210,11 @@ class Boot extends Loggable{ LiftRules.statelessDispatch.append(CashAccountAPI) LiftRules.statelessDispatch.append(BankMockAPI) + + + // Add Resource Docs + LiftRules.statelessDispatch.append(ResourceDocs) + // LiftRules.statelessDispatch.append(Metrics) TODO: see metric menu entry below //add sandbox api calls only if we're running in sandbox mode @@ -282,13 +299,13 @@ class Boot extends Loggable{ // Make a transaction span the whole HTTP request S.addAround(DB.buildLoanWrapper) - val useMessageQueue = Props.getBool("messageQueue.createBankAccounts", false) - if(useMessageQueue) - try { + try { + val useMessageQueue = Props.getBool("messageQueue.createBankAccounts", false) + if(useMessageQueue) BankAccountCreationListener.startListen - } catch { - case e: java.lang.ExceptionInInitializerError => logger.warn(s"BankAccountCreationListener Exception: $e") - } + } catch { + case e: java.lang.ExceptionInInitializerError => logger.warn(s"BankAccountCreationListener Exception: $e") + } Mailer.devModeSend.default.set( (m : MimeMessage) => { logger.info("Would have sent email if not in dev mode: " + m.getContent) @@ -327,8 +344,8 @@ class Boot extends Loggable{ } private def sendExceptionEmail(exception: Throwable): Unit = { + import Mailer.{From, PlainMailBodyType, Subject, To} import net.liftweb.util.Helpers.now - import Mailer.{From, To, Subject, PlainMailBodyType} val outputStream = new java.io.ByteArrayOutputStream val printStream = new java.io.PrintStream(outputStream) @@ -390,5 +407,10 @@ object ToSchemify { MappedBranch, MappedAtm, MappedProduct, - MappedCrmEvent) + MappedCrmEvent, + MappedKycDocument, + MappedKycMedia, + MappedKycCheck, + MappedKycStatus, + MappedSocialMedia) } diff --git a/src/main/scala/code/api/OBPRestHelper.scala b/src/main/scala/code/api/OBPRestHelper.scala index fe22f121a..f1ee71f43 100644 --- a/src/main/scala/code/api/OBPRestHelper.scala +++ b/src/main/scala/code/api/OBPRestHelper.scala @@ -28,7 +28,7 @@ Berlin 13359, Germany Everett Sochowski : everett AT tesobe DOT com Ayoub Benali: ayoub AT tesobe DOT com - */ + */ package code.api @@ -43,6 +43,8 @@ import code.model.User import code.api.OAuthHandshake._ import net.liftweb.json.JsonAST.JValue import net.liftweb.json.Extraction +import net.liftweb.util.Props +import code.api.Constant._ trait APIFailure{ val msg : String @@ -61,7 +63,7 @@ object APIFailure { //that all stable versions retain the same behavior case class UserNotFound(providerId : String, userId: String) extends APIFailure { val responseCode = 400 //TODO: better as 404? -> would break some backwards compatibility (or at least the tests!) - + //to reiterate the comment about preserving backwards compatibility: //consider the case that an app may be parsing this string to decide what message to show their users //e.g. when granting view permissions, an app may not give their users a choice of provider and only @@ -79,7 +81,8 @@ trait OBPRestHelper extends RestHelper with Loggable { val VERSION : String def vPlusVersion = "v" + VERSION - def apiPrefix = ("obp" / vPlusVersion).oPrefix(_) + + def apiPrefix = (ApiPathZero / vPlusVersion).oPrefix(_) /* An implicit function to convert magically between a Boxed JsonResponse and a JsonResponse @@ -135,16 +138,25 @@ trait OBPRestHelper extends RestHelper with Loggable { case Failure(msg, _, _) => errorJsonResponse(msg) case _ => errorJsonResponse("oauth error") } - } else fn(Empty) + } else if (Props.getBool("allow_direct_login", true) && isThereDirectLoginHeader) { + val user = DirectLogin.getUser + if (user.isDefined) { + fn(user) + } else { + fn(Empty) + } + } else { + fn(Empty) + } } class RichStringList(list: List[String]) { val listLen = list.length /** - * Normally we would use ListServeMagic's prefix function, but it works with PartialFunction[Req, () => Box[LiftResponse]] - * instead of the PartialFunction[Req, Box[User] => Box[JsonResponse]] that we need. This function does the same thing, really. - */ + * Normally we would use ListServeMagic's prefix function, but it works with PartialFunction[Req, () => Box[LiftResponse]] + * instead of the PartialFunction[Req, Box[User] => Box[JsonResponse]] that we need. This function does the same thing, really. + */ def oPrefix(pf: PartialFunction[Req, Box[User] => Box[JsonResponse]]): PartialFunction[Req, Box[User] => Box[JsonResponse]] = new PartialFunction[Req, Box[User] => Box[JsonResponse]] { def isDefinedAt(req: Req): Boolean = diff --git a/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocs.scala b/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocs.scala new file mode 100644 index 000000000..5fd207836 --- /dev/null +++ b/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocs.scala @@ -0,0 +1,23 @@ +package code.api.ResourceDocs1_4_0 + +import code.api.OBPRestHelper + + +import net.liftweb.common.Loggable + + +object ResourceDocs extends OBPRestHelper with ResourceDocsAPIMethods with Loggable { + + val VERSION = "1.4.0" + + + val routes = List( + ImplementationsResourceDocs.getResourceDocsObp, + ImplementationsResourceDocs.getResourceDocsSwagger + ) + + routes.foreach(route => { + oauthServe(apiPrefix{route}) + }) + +} diff --git a/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala b/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala new file mode 100644 index 000000000..ff5219f54 --- /dev/null +++ b/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala @@ -0,0 +1,236 @@ +package code.api.ResourceDocs1_4_0 + +import code.api.v1_4_0.{APIMethods140, JSONFactory1_4_0, OBPAPI1_4_0} +import net.liftweb.common.{Box, Full, Loggable} +import net.liftweb.http.rest.RestHelper +import net.liftweb.http.{JsonResponse, Req} +import net.liftweb.json.Extraction +import net.liftweb.json.JsonAST.JValue +import net.liftweb.json.JsonDSL._ +import net.liftweb.util.Props + +import scala.collection.immutable.Nil + +// JObject creation +import code.api.v1_2_1.{APIInfoJSON, APIMethods121, HostedBy, OBPAPI1_2_1} +import code.api.v1_3_0.{APIMethods130, OBPAPI1_3_0} +import code.api.v2_0_0.{APIMethods200, OBPAPI2_0_0} + +import scala.collection.mutable.ArrayBuffer + +// So we can include resource docs from future versions +import java.text.SimpleDateFormat + +import code.api.util.APIUtil.{ResourceDoc, _} +import code.model._ + +trait ResourceDocsAPIMethods extends Loggable with APIMethods200 with APIMethods140 with APIMethods130 with APIMethods121{ + //needs to be a RestHelper to get access to JsonGet, JsonPost, etc. + // We add previous APIMethods so we have access to the Resource Docs + self: RestHelper => + + val ImplementationsResourceDocs = new Object() { + + val resourceDocs = ArrayBuffer[ResourceDoc]() + val emptyObjectJson : JValue = Nil + val apiVersion : String = "1_4_0" + + val exampleDateString : String ="22/08/2013" + val simpleDateFormat : SimpleDateFormat = new SimpleDateFormat("dd/mm/yyyy") + val exampleDate = simpleDateFormat.parse(exampleDateString) + + + def getResourceDocsList(requestedApiVersion : String) : Option[List[ResourceDoc]] = + { + // Return a different list of resource docs depending on the version being called. + // For instance 1_3_0 will have the docs for 1_3_0 and 1_2_1 (when we started adding resource docs) etc. + + logger.info(s"getResourceDocsList says requestedApiVersion is $requestedApiVersion") + + val resourceDocs = requestedApiVersion match { + case "2.0.0" => Implementations2_0_0.resourceDocs ++ Implementations1_4_0.resourceDocs ++ Implementations1_3_0.resourceDocs ++ Implementations1_2_1.resourceDocs + case "1.4.0" => Implementations1_4_0.resourceDocs ++ Implementations1_3_0.resourceDocs ++ Implementations1_2_1.resourceDocs + case "1.3.0" => Implementations1_3_0.resourceDocs ++ Implementations1_2_1.resourceDocs + case "1.2.1" => Implementations1_2_1.resourceDocs + } + + logger.info(s"There are ${resourceDocs.length} resource docs available to $requestedApiVersion") + + val versionRoutes = requestedApiVersion match { + case "2.0.0" => OBPAPI2_0_0.routes + case "1.4.0" => OBPAPI1_4_0.routes + case "1.3.0" => OBPAPI1_3_0.routes + case "1.2.1" => OBPAPI1_2_1.routes + } + + logger.info(s"There are ${versionRoutes.length} routes available to $requestedApiVersion") + + + // We only want the resource docs for which a API route exists else users will see 404s + // Get a list of the partial function classes represented in the routes available to this version. + val versionRoutesClasses = versionRoutes.map { vr => vr.getClass } + + // Only return the resource docs that have available routes + val activeResourceDocs = resourceDocs.filter(rd => versionRoutesClasses.contains(rd.partialFunction.getClass)) + + logger.info(s"There are ${activeResourceDocs.length} resource docs available to $requestedApiVersion") + + // Sort by endpoint, verb. Thus / is shown first then /accounts and /banks etc. Seems to read quite well like that. + Some(activeResourceDocs.toList.sortBy(rd => (rd.requestUrl, rd.requestVerb))) + } + + + + resourceDocs += ResourceDoc( + getResourceDocsObp, + apiVersion, + "getResourceDocsObp", + "GET", + "/resource-docs/obp", + "Get Resource Documentation in OBP format.", + """Returns documentation about the RESTful resources on this server including example body for POST or PUT requests. + | Thus the OBP API Explorer (and other apps) can display and work with the API documentation. + | In the future this information will be used to create Swagger (WIP) and RAML files. + |- Total of {Consumer.count} applications from {recordsWithUniqueEmails.getOrElse("ERROR")} unique email addresses. - {recordsWithUniqueAppNames.getOrElse("ERROR")} unique app names. -
-
+ Total of {Consumer.count} applications from {recordsWithUniqueEmails.getOrElse("ERROR")} unique email addresses.
+ {recordsWithUniqueAppNames.getOrElse("ERROR")} unique app names.
+