Merge branch 'develop' of https://github.com/OpenBankProject/OBP-API into develop

This commit is contained in:
Simon Redfern 2017-03-31 11:13:38 +02:00
commit ae6b036eaf
12 changed files with 795 additions and 512 deletions

View File

@ -436,6 +436,7 @@ object ToSchemify {
MappedUserCustomerLink,
Consumer,
Token,
Nonce,
MappedCounterparty,
MappedCounterpartyMetadata,
MappedCounterpartyWhereTag,
@ -448,7 +449,6 @@ object ToSchemify {
val models = List(
AuthUser,
Admin,
Nonce,
MappedBank,
MappedBankAccount,
MappedTransaction,

View File

@ -30,15 +30,15 @@ import java.util.Date
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import code.token.Tokens
import code.api.Constant._
import code.api.util.{APIUtil, ErrorMessages}
import code.consumer.Consumers
import code.model.{Consumer, Nonce, TokenType, User}
import code.model.{Consumer, TokenType, User}
import code.nonce.Nonces
import code.token.Tokens
import net.liftweb.common._
import net.liftweb.http.rest.RestHelper
import net.liftweb.http.{InMemoryResponse, PostRequest, Req, S}
import net.liftweb.mapper.By
import net.liftweb.util.Helpers
import net.liftweb.util.Helpers.{tryo, _}
@ -219,13 +219,11 @@ object OAuthHandshake extends RestHelper with Loggable {
* same timestamp, client credentials, and token combinations.
*/
val token = parameters.get("oauth_token") getOrElse ""
Nonce.count(
By(Nonce.value,parameters.get("oauth_nonce").get),
By(Nonce.tokenKey, token),
By(Nonce.consumerkey,parameters.get("oauth_consumer_key").get),
By(Nonce.timestamp, new Date(parameters.get("oauth_timestamp").get.toLong))
) !=0
Nonces.nonces.vend.countNonces(consumerKey = parameters.get("oauth_consumer_key").get,
tokenKey = token,
timestamp = new Date(parameters.get("oauth_timestamp").get.toLong),
value = parameters.get("oauth_nonce").get
) !=0
}
def correctSignature(OAuthparameters : Map[String, String], httpMethod : String) = {
@ -433,13 +431,18 @@ object OAuthHandshake extends RestHelper with Loggable {
private def saveRequestToken(oAuthParameters : Map[String, String], tokenKey : String, tokenSecret : String) =
{
import code.model.{Nonce, TokenType}
import code.model.TokenType
val nonce = Nonce.create
nonce.consumerkey(oAuthParameters.get("oauth_consumer_key").get)
nonce.timestamp(new Date(oAuthParameters.get("oauth_timestamp").get.toLong))
nonce.value(oAuthParameters.get("oauth_nonce").get)
val nonceSaved = nonce.save()
val nonceSaved = Nonces.nonces.vend.createNonce(
id = None,
consumerKey = Some(oAuthParameters.get("oauth_consumer_key").get),
tokenKey = None,
timestamp = Some(new Date(oAuthParameters.get("oauth_timestamp").get.toLong)),
value = Some(oAuthParameters.get("oauth_nonce").get)
) match {
case Full(_) => true
case _ => false
}
val consumerId = Consumers.consumers.vend.getConsumerByConsumerKey(oAuthParameters.get("oauth_consumer_key").get) match {
case Full(consumer) => Some(consumer.id.get)
@ -472,14 +475,18 @@ object OAuthHandshake extends RestHelper with Loggable {
private def saveAuthorizationToken(oAuthParameters : Map[String, String], tokenKey : String, tokenSecret : String) =
{
import code.model.{Nonce, TokenType}
import code.model.TokenType
val nonce = Nonce.create
nonce.consumerkey(oAuthParameters.get("oauth_consumer_key").get)
nonce.timestamp(new Date(oAuthParameters.get("oauth_timestamp").get.toLong))
nonce.tokenKey(oAuthParameters.get("oauth_token").get)
nonce.value(oAuthParameters.get("oauth_nonce").get)
val nonceSaved = nonce.save()
val nonceSaved = Nonces.nonces.vend.createNonce(
id = None,
consumerKey = Some(oAuthParameters.get("oauth_consumer_key").get),
tokenKey = Some(oAuthParameters.get("oauth_token").get),
timestamp = Some(new Date(oAuthParameters.get("oauth_timestamp").get.toLong)),
value = Some(oAuthParameters.get("oauth_nonce").get)
) match {
case Full(_) => true
case _ => false
}
val consumerId = Consumers.consumers.vend.getConsumerByConsumerKey(oAuthParameters.get("oauth_consumer_key").get) match {
case Full(consumer) => Some(consumer.id.get)

View File

@ -616,31 +616,34 @@ object APIUtil extends Loggable {
catalogs: Catalogs,
tags: List[ResourceDocTag]
)
// TODO How to ensure that all outbound messages contain action and version - yet have other free form fields?
// Would we have to have a detail key? or can we keep it flat?
abstract class OutboundMessageBase {
/**
*
* This is the base class for all kafka outbound case class
* action and messageFormat are mandatory
* The optionalFields can be any other new fields .
*/
abstract class OutboundMessageBase(
optionalFields: String*
) {
def action: String
def messageFormat: String
}
abstract class ExampleOutboundMessage (
detail: JValue
) extends OutboundMessageBase
// Similar to above, how would we ensure we have messageId and errorCode in the exampleInboundMessage?
abstract class InboundMessageBase(
optionalFields: String*
) {
def errorCode: String
}
// Used to document the KafkaMessage calls
case class MessageDoc(
process: String,
messageFormat: String,
description: String,
exampleOutboundMessage: JValue, // TODO make this more formal see above.
exampleInboundMessage: JValue, // TODO Ditto: should probably always include errorCode, see above.
errorResponseMessages: List[JValue]
process: String,
messageFormat: String,
description: String,
exampleOutboundMessage: JValue,
exampleInboundMessage: JValue
)
// Define relations between API end points. Used to create _links in the JSON and maybe later for API Explorer browsing

View File

@ -34,7 +34,7 @@ package code.bankconnectors
import java.text.SimpleDateFormat
import java.util.{Date, Locale}
import code.api.util.APIUtil.{MessageDoc}
import code.api.util.APIUtil.{InboundMessageBase, MessageDoc, OutboundMessageBase}
import code.fx.FXRate
import code.metadata.counterparties.CounterpartyTrait
import code.model.dataAccess.MappedBankAccountData
@ -53,39 +53,40 @@ case class InboundAccountData(
accounts: List[InboundAccount]
)
case class OutboundUserByUsernamePassword(
case class OutboundUserByUsernamePasswordBase(
messageFormat: String,
action: String,
version: String,
username: String,
password: String
)
)extends OutboundMessageBase
case class OutboundUserAccountViews(
case class OutboundUserAccountViewsBase(
messageFormat: String,
action: String,
version: String,
username: String,
userId: String,
bankId: String
)
)extends OutboundMessageBase
case class OutboundBanks(
case class OutboundBanksBase(
messageFormat: String,
action: String,
version: String,
username: String,
userId: String
)
) extends OutboundMessageBase
case class OUTTBank(
messageFormat: String,
action: String,
version: String,
bankId: String,
userId: String,
username: String
)
)extends OutboundMessageBase
case class OutboundChallengeThreshold(
case class OutboundChallengeThresholdBase(
messageFormat: String,
action: String,
version: String,
bankId: String,
accountId: String,
viewId: String,
@ -93,11 +94,11 @@ case class OutboundChallengeThreshold(
currency: String,
userId: String,
username: String
)
) extends OutboundMessageBase
case class OutboundChargeLevel(
case class OutboundChargeLevelBase(
messageFormat: String,
action: String,
version: String,
bankId: String,
accountId: String,
viewId: String,
@ -105,78 +106,95 @@ case class OutboundChargeLevel(
username: String,
transactionRequestType: String,
currency: String
)
) extends OutboundMessageBase
case class OutboundChallenge(
case class OutboundChallengeBase(
messageFormat: String,
action: String,
version: String,
bankId: String,
accountId: String,
userId: String,
username: String,
transactionRequestType: String,
transactionRequestId: String
)
) extends OutboundMessageBase
case class OutboundChallengeAnswer(
case class OutboundChallengeAnswerBase(
messageFormat: String,
action: String,
version: String,
userId: String,
username: String,
challengeId: String,
hashOfSuppliedAnswer: String
)
) extends OutboundMessageBase
case class OutboundTransactionQuery(
case class OutboundTransactionQueryBase(
messageFormat: String,
action: String,
version: String,
userId: String,
username: String,
bankId: String,
accountId: String,
transactionId: String
)
) extends OutboundMessageBase
case class OutboundTransactionsQueryWithParams(
case class OutboundTransactionsQueryWithParamsBase(
messageFormat: String,
action: String,
version: String,
userId: String,
username: String,
bankId: String,
accountId: String,
queryParams: String
)
) extends OutboundMessageBase
case class OutboundBankAccount(
case class OutboundBankAccountBase(
messageFormat: String,
action: String,
version: String,
userId: String,
username: String,
bankId: String,
accountId: String
)
) extends OutboundMessageBase
case class OutboundBankAccounts(
case class OutboundBankAccountsBase(
messageFormat: String,
action: String,
version: String,
userId: String,
username: String,
bankId: String,
accountId: String
)
) extends OutboundMessageBase
case class OutboundAccountByNumber(
case class OutboundAccountByNumberBase(
messageFormat: String,
action: String,
version: String,
userId: String,
username: String,
bankId: String,
number: String
)
) extends OutboundMessageBase
case class OutboundSaveTransaction(
case class OutboundCounterpartyByIbanBase(
messageFormat: String,
action: String,
userId: String,
username: String,
otherAccountRoutingAddress: String,
otherAccountRoutingScheme: String
) extends OutboundMessageBase
case class OutboundCounterpartyByCounterpartyIdBase(
messageFormat: String,
action: String,
userId: String,
username: String,
counterpartyId: String
) extends OutboundMessageBase
case class OutboundSaveTransactionBase(
messageFormat: String,
action: String,
version: String,
userId: String,
username: String,
@ -204,57 +222,41 @@ case class OutboundSaveTransaction(
toCounterpartyRoutingScheme: String,
toCounterpartyBankRoutingAddress: String,
toCounterpartyBankRoutingScheme: String
)
) extends OutboundMessageBase
case class OutboundTransactionRequestStatuses(
action: String,
version: String
)
case class OutboundTransactionRequestStatusesBase(
messageFormat: String,
action: String
) extends OutboundMessageBase
case class OutboundCurrentFxRate(
case class OutboundCurrentFxRateBase(
messageFormat: String,
action: String,
version: String,
userId: String,
username: String,
fromCurrencyCode: String,
toCurrencyCode: String
)
) extends OutboundMessageBase
case class OutboundTransactionRequestTypeCharge(
case class OutboundTransactionRequestTypeChargeBase(
messageFormat: String,
action: String,
version: String,
userId: String,
username: String,
bankId: String,
accountId: String,
viewId: String,
transactionRequestType: String
)
) extends OutboundMessageBase
case class InboundValidatedUser(
errorCode: String,
email: String,
displayName: String
)
case class OutboundCounterpartyByIban(
action: String,
version: String,
userId: String,
username: String,
otherAccountRoutingAddress: String,
otherAccountRoutingScheme: String
)
case class OutboundCounterpartyByCounterpartyId(
action: String,
version: String,
userId: String,
username: String,
counterpartyId: String
)
) extends InboundMessageBase
case class InboundCounterparty(
errorCode: String,
name: String,
createdByUserId: String,
thisBankId: String,
@ -268,7 +270,7 @@ case class InboundCounterparty(
otherBranchRoutingScheme: String,
otherBranchRoutingAddress: String,
isBeneficiary: Boolean
)
) extends InboundMessageBase
case class CounterpartyTrait2(counterparty: InboundCounterparty) extends CounterpartyTrait {
@ -288,11 +290,12 @@ case class CounterpartyTrait2(counterparty: InboundCounterparty) extends Counter
}
case class InboundBank(
errorCode: String,
bankId: String,
name: String,
logo: String,
url: String
)
)extends InboundMessageBase
case class Bank2(r: InboundBank) extends Bank {
@ -306,6 +309,7 @@ case class Bank2(r: InboundBank) extends Bank {
}
case class InboundAccount(
errorCode: String,
accountId: String,
bankId: String,
label: String,
@ -318,7 +322,7 @@ case class InboundAccount(
generatePublicView: Boolean,
generateAccountantsView: Boolean,
generateAuditorsView: Boolean
)
)extends InboundMessageBase
case class BankAccount2(r: InboundAccount) extends BankAccount {
@ -345,12 +349,13 @@ case class BankAccount2(r: InboundAccount) extends BankAccount {
}
case class InboundFXRate(
errorCode: String,
fromCurrencyCode: String,
toCurrencyCode: String,
conversionValue: Double,
inverseConversionValue: Double,
effectiveDate: String
)
)extends InboundMessageBase
case class FXRate2(inboundFxRate: InboundFXRate) extends FXRate {
@ -363,12 +368,13 @@ case class FXRate2(inboundFxRate: InboundFXRate) extends FXRate {
}
case class InboundTransactionRequestTypeCharge(
errorCode: String,
transactionRequestType: String,
bankId: String,
chargeCurrency: String,
chargeAmount: String,
chargeSummary: String
)
)extends InboundMessageBase
case class TransactionRequestTypeCharge2(inboundTransactionRequestTypeCharge: InboundTransactionRequestTypeCharge) extends TransactionRequestTypeCharge {
@ -466,6 +472,7 @@ case class InboundLocation(
//InboundTransaction --> InternalTransaction -->OutboundTransaction
case class InternalTransaction(
errorCode: String,
transactionId: String,
accountId: String,
amount: String,
@ -480,7 +487,7 @@ case class InternalTransaction(
postedDate: String,
`type`: String,
userId: String
)
)extends InboundMessageBase
case class InboundAtm(
id: String,
@ -519,8 +526,9 @@ case class InboundCustomer(
)
case class InboundTransactionId(
errorCode: String,
transactionId: String
)
)extends InboundMessageBase
case class OutboundTransaction(
action: String,
@ -535,20 +543,27 @@ case class OutboundTransaction(
transactionType: String)
case class InboundChallengeLevel(
errorCode: String,
limit: String,
currency: String
)
)extends InboundMessageBase
case class InboundCreateChallange(
errorCode: String,
challengeId: String
)extends InboundMessageBase
case class OutboundCreateChallange(challengeId: String)
case class InboundValidateChallangeAnswer(answer: String)
case class InboundValidateChallangeAnswer(
answer: String,
errorCode: String
)extends InboundMessageBase
case class InboundChargeLevel(
errorCode: String,
currency: String,
amount: String
)
)extends InboundMessageBase
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -557,8 +572,7 @@ case class MessageDocJson(
message_format: String,
description: String,
example_outbound_message: JValue,
example_inbound_message: JValue,
possible_error_messages: List[JValue]
example_inbound_message: JValue
)
// Creates the json resource_docs
@ -576,8 +590,7 @@ object KafkaJSONFactory_vMar2017 {
message_format = md.messageFormat,
description = md.description,
example_outbound_message = md.exampleOutboundMessage,
example_inbound_message = md.exampleInboundMessage,
possible_error_messages = md.errorResponseMessages
example_inbound_message = md.exampleInboundMessage
)
}

View File

@ -36,6 +36,7 @@ import code.token.TokensProvider
import code.consumer.{Consumers, ConsumersProvider}
import code.model.TokenType.TokenType
import code.model.dataAccess.ResourceUser
import code.nonce.NoncesProvider
import code.users.Users
import net.liftweb.common._
import net.liftweb.http.S
@ -294,7 +295,56 @@ object Consumer extends Consumer with Loggable with LongKeyedMetaMapper[Consumer
</lift:crud.all>
}
object MappedNonceProvider extends NoncesProvider {
override def createNonce(id: Option[Long],
consumerKey: Option[String],
tokenKey: Option[String],
timestamp: Option[Date],
value: Option[String]): Box[Nonce] = {
tryo {
val n = Nonce.create
id match {
case Some(v) => n.id(v)
case None =>
}
consumerKey match {
case Some(v) => n.consumerkey(v)
case None =>
}
tokenKey match {
case Some(v) => n.tokenKey(v)
case None =>
}
timestamp match {
case Some(v) => n.timestamp(v)
case None =>
}
value match {
case Some(v) => n.value(v)
case None =>
}
val nonce = n.saveMe()
nonce
}
}
override def deleteExpiredNonces(currentDate: Date): Boolean = {
Nonce.findAll(By_<(Nonce.timestamp, currentDate)).forall(_.delete_!)
}
override def countNonces(consumerKey: String,
tokenKey: String,
timestamp: Date,
value: String): Long = {
Nonce.count(
By(Nonce.value, value),
By(Nonce.tokenKey, tokenKey),
By(Nonce.consumerkey, consumerKey),
By(Nonce.timestamp, timestamp)
)
}
}
class Nonce extends LongKeyedMapper[Nonce] {
def getSingleton = Nonce

View File

@ -0,0 +1,45 @@
package code.nonce
import java.util.Date
import code.model.Nonce
import net.liftweb.util.SimpleInjector
import code.remotedata.RemotedataNonces
import net.liftweb.common.Box
object Nonces extends SimpleInjector {
val nonces = new Inject(buildOne _) {}
def buildOne: NoncesProvider = RemotedataNonces
}
trait NoncesProvider {
def createNonce(id: Option[Long],
consumerKey: Option[String],
tokenKey: Option[String],
timestamp: Option[Date],
value: Option[String]): Box[Nonce]
def deleteExpiredNonces(currentDate: Date): Boolean
def countNonces(consumerKey: String,
tokenKey: String,
timestamp: Date,
value: String): Long
}
class RemotedataNoncesCaseClasses {
case class createNonce(id: Option[Long],
consumerKey: Option[String],
tokenKey: Option[String],
timestamp: Option[Date],
value: Option[String])
case class deleteExpiredNonces(currentDate: Date)
case class countNonces(consumerKey: String,
tokenKey: String,
timestamp: Date,
value: String)
}
object RemotedataNoncesCaseClasses extends RemotedataNoncesCaseClasses

View File

@ -96,7 +96,8 @@ object RemotedataActors extends Loggable {
ActorProps[RemotedataConsumersActor] -> RemotedataConsumers.actorName,
ActorProps[RemotedataTransactionRequestsActor] -> RemotedataTransactionRequests.actorName,
ActorProps[RemotedataMetricsActor] -> RemotedataMetrics.actorName,
ActorProps[RemotedataTokensActor] -> RemotedataTokens.actorName
ActorProps[RemotedataTokensActor] -> RemotedataTokens.actorName,
ActorProps[RemotedataNoncesActor] -> RemotedataNonces.actorName
)
actorsRemotedata.foreach { a => logger.info(actorSystem.actorOf(a._1, name = a._2)) }

View File

@ -0,0 +1,32 @@
package code.remotedata
import java.util.Date
import code.model.Nonce
import code.nonce.{NoncesProvider, RemotedataNoncesCaseClasses}
import net.liftweb.common.Box
import akka.pattern.ask
object RemotedataNonces extends ActorInit with NoncesProvider {
val cc = RemotedataNoncesCaseClasses
def createNonce(id: Option[Long],
consumerKey: Option[String],
tokenKey: Option[String],
timestamp: Option[Date],
value: Option[String]): Box[Nonce] =
extractFutureToBox(actor ? cc.createNonce(id, consumerKey, tokenKey, timestamp, value))
def deleteExpiredNonces(currentDate: Date): Boolean =
extractFuture(actor ? cc.deleteExpiredNonces(currentDate))
def countNonces(consumerKey: String,
tokenKey: String,
timestamp: Date,
value: String): Long =
extractFuture(actor ? cc.countNonces(consumerKey, tokenKey, timestamp, value))
}

View File

@ -0,0 +1,54 @@
package code.remotedata
import java.util.Date
import akka.actor.Actor
import akka.event.Logging
import code.model._
import code.nonce.RemotedataNoncesCaseClasses
class RemotedataNoncesActor extends Actor with ActorHelper {
val logger = Logging(context.system, this)
val mapper = MappedNonceProvider
val cc = RemotedataNoncesCaseClasses
def receive = {
case cc.createNonce(id: Option[Long],
consumerKey: Option[String],
tokenKey: Option[String],
timestamp: Option[Date],
value: Option[String]) =>
logger.debug("createNonce(" + id + ", " +
consumerKey+ ", " +
tokenKey + ", " +
timestamp + ", " +
value + ")")
sender ! extractResult(mapper.createNonce(id, consumerKey, tokenKey, timestamp, value))
case cc.deleteExpiredNonces(currentDate: Date) =>
logger.debug("deleteExpiredNonces(" + currentDate +")")
sender ! extractResult(mapper.deleteExpiredNonces(currentDate))
case cc.countNonces(consumerKey: String,
tokenKey: String,
timestamp: Date,
value: String) =>
logger.debug("countNonces(" + consumerKey + ", " +
tokenKey+ ", " +
timestamp + ", " +
value + ", " +
")")
sender ! extractResult(mapper.countNonces(consumerKey, tokenKey, timestamp, value))
case message => logger.warning("[AKKA ACTOR ERROR - REQUEST NOT RECOGNIZED] " + message)
}
}

View File

@ -36,11 +36,12 @@ package code.snippet
import java.util.Date
import code.token.Tokens
import code.api.util.APIUtil
import code.consumer.Consumers
import code.model.dataAccess.AuthUser
import code.model.{Nonce, Token, TokenType}
import code.model.{Token, TokenType}
import code.nonce.Nonces
import code.token.Tokens
import code.users.Users
import code.util.Helper
import code.util.Helper.NOOP_SELECTOR
@ -193,7 +194,6 @@ object OAuthAuthorisation {
//looks for expired tokens and nonces and deletes them
def dataBaseCleaner: Unit = {
import net.liftweb.mapper.By_<
import net.liftweb.util.Schedule
Schedule.schedule(dataBaseCleaner _, 1 hour)
@ -208,6 +208,6 @@ object OAuthAuthorisation {
//delete expired tokens and nonces
Tokens.tokens.vend.deleteExpiredTokens(currentDate)
Nonce.findAll(By_<(Nonce.timestamp, timeLimit)).foreach(t => t.delete_!)
Nonces.nonces.vend.deleteExpiredNonces(currentDate)
}
}

View File

@ -23,6 +23,31 @@
margin: 20px 20px 0 0;
}
p {
font-family: BNPPSansLight;
}
a {
font-family: BNPPSansLight;
}
.navlink {
font-family: BNPPSansLight;
text-transform: uppercase;
}
h1 {
font-family: BNPP-IRB-COND;
text-transform: uppercase;
}
h2 {
font-family: BNPP-IRB-COND;
}
#main-about-box #main-about-buttons a {
font-family: BNPP-IRB-Rounded2;
font-size: 18px;
@ -35,17 +60,16 @@
#main-about-text h2 {
text-transform: uppercase;
#font-family: "bu_sans", Arial, Helvetica, sans-serif;
font-family: BNPP-IRB-COND;
}
#main-about-text p {
font-family: "bu_light",Arial, Helvetica, sans-serif;
font-family: BNPPSansLight;
}
#main-about p {
color: white;
font-family: "bu_light",Arial, Helvetica, sans-serif;
font-family: BNPPSansLight;
margin-top: 30px;
margin-left: 30px;
margin-right: 30px;
@ -54,7 +78,6 @@
#main-about h2 {
color: white;
#font-family: "bu_sans", Arial, Helvetica, sans-serif;
font-family: BNPP-IRB-COND;
font-size: 32px;
margin-top: 30px;
@ -69,8 +92,6 @@
}
#footer2 {
overflow: auto;
@ -79,8 +100,6 @@
padding: 0px 0 20px;
}
#footer2 #footer2-logo-left {
margin: 0 0 0 450px;
float: left;
@ -93,27 +112,41 @@
}
#footer2 #footer2-middle-text {
font-size: 1em;
font-weight: bold;
margin: 5px 0 0 650px;
color: #000;
position: absolute;
font-family: BNPP-IRB-LIGHT;
font-size: 14px;
line-height: 23px;
margin: 5px 0 0 25px;
float: left;
font-family: BNPPSans;
}
#footer2-font {
font-family: BNPP-IRB-LIGHT;
font-family: BNPPSans;
font-size: 14px;
}
#copyright {
font-family: BNPPSansLight;
}
#main-support {
background-color: #24b3c7;
}
#top-text {
font-family: BNPPSans;
font-size: 17px;
font-weight: normal;
margin: 23px 0 0 30px;
display: inline-block;
float: left;
}
#webui_top_text {
position: absolute;
font-weight: bold;
margin: 45px 0 0 20px;
font-size: 1.4em;
padding-left: 10px;
font-family: BNPP-IRB-LIGHT;
color: #1d1d1b;
margin: 0;
font-size: 17px;
line-height: 31px;
font-weight: normal;
font-family: BNPPSans;
}