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

This commit is contained in:
Simon Redfern 2016-07-27 22:53:02 +02:00
commit e3163876d4
14 changed files with 176 additions and 151 deletions

View File

@ -299,7 +299,11 @@ object DirectLogin extends RestHelper with Loggable {
case p: String => p
case _ => "error"
}
val userId = OBPUser.getUserId(username, password)
var userId:Long = OBPUser.getUserId(username, password).getOrElse(0)
if (userId == 0) {
OBPUser.externalUserHelper(directLoginParameters.getOrElse("username", ""), directLoginParameters.getOrElse("password", ""))
userId = OBPUser.getUserId(username, password).getOrElse(0)
}
userId
}

View File

@ -483,6 +483,24 @@ object OAuthHandshake extends RestHelper with Loggable {
nonceSaved && tokenSaved
}
def getConsumer: List[Consumer] = {
val httpMethod = S.request match {
case Full(r) => r.request.method
case _ => "GET"
}
val (httpCode, message, oAuthParameters) = validator("protectedResource", httpMethod)
import code.model.Token
val consumer: Option[Consumer] = for {
tokenId: String <- oAuthParameters.get("oauth_token")
token: Token <- Token.find(By(Token.key, tokenId))
consumer: Consumer <- token.consumerId.foreign
} yield {
consumer
}
consumer.toList
}
def getUser : Box[User] = {
val httpMethod = S.request match {
case Full(r) => r.request.method

View File

@ -173,7 +173,14 @@ object APIUtil extends Loggable {
// TODO This should use Elastic Search or Kafka not an RDBMS
val u = user.orNull
val userId = if (u != null) u.userId else "null"
APIMetrics.apiMetrics.vend.saveMetric(userId, S.uriAndQueryString.getOrElse(""), (now: TimeSpan))
val userName = if (u != null) u.name else "null"
var appName = "null"
var developerEmail = "null"
for (c <- getConsumer) {
appName = c.name.get
developerEmail = c.developerEmail.get
}
APIMetrics.apiMetrics.vend.saveMetric(userId, S.uriAndQueryString.getOrElse(""), (now: TimeSpan), userName, appName, developerEmail)
}
}

View File

@ -68,36 +68,19 @@ object KafkaMappedConnector extends Connector with CreateViewImpls with Loggable
}
def setAccountOwner(owner : String, account: KafkaInboundAccount) : Unit = {
val apiUserOwner = APIUser.findAll.find(user => owner == user.emailAddress)
apiUserOwner match {
case Some(o) => {
if ( ! accountOwnerExists(o, account)) {
MappedAccountHolder.createMappedAccountHolder(o.apiId.value, account.bank, account.id, "KafkaMappedConnector")
}
if (account.owners.contains(owner)) {
val apiUserOwner = APIUser.findAll.find(user => owner == user.emailAddress)
apiUserOwner match {
case Some(o) => {
if ( ! accountOwnerExists(o, account)) {
MappedAccountHolder.createMappedAccountHolder(o.apiId.value, account.bank, account.id, "KafkaMappedConnector")
}
}
case None => {
//This shouldn't happen as OBPUser should generate the APIUsers when saved
logger.error(s"api user(s) with email $owner not found.")
}
}
case None => {
//This shouldn't happen as OBPUser should generate the APIUsers when saved
logger.error(s"api user(s) with email $owner not found.")
}
}
}
def updateAccountViews(user: APIUser, account: KafkaInboundAccount ) = {
for {
email <- tryo{user.emailAddress}
views <- tryo{createSaveableViews(account, account.owners.contains(email))}
existing_views <- tryo {Views.views.vend.views(new KafkaBankAccount(account))}
} yield {
views.foreach(_.save())
views.map(_.value).foreach(v => {
Views.views.vend.addPermission(v.uid, user)
logger.info(s"------------> updated view ${v.uid} for apiuser ${user} and account ${account}")
})
existing_views.filterNot(_.users.contains(user)).foreach (v => {
Views.views.vend.addPermission(v.uid, user)
logger.info(s"------------> added apiuser ${user} to view ${v.uid} for account ${account}")
})
setAccountOwner(email, account)
}
}
@ -121,7 +104,7 @@ object KafkaMappedConnector extends Connector with CreateViewImpls with Loggable
views.foreach(_.save())
views.map(_.value).foreach(v => {
Views.views.vend.addPermission(v.uid, user)
logger.info(s"------------> added view ${v.uid} for apiuser ${user} and account ${acc}")
logger.info(s"------------> updated view ${v.uid} for apiuser ${user} and account ${acc}")
})
existing_views.filterNot(_.users.contains(user)).foreach (v => {
Views.views.vend.addPermission(v.uid, user)
@ -291,14 +274,7 @@ object KafkaMappedConnector extends Connector with CreateViewImpls with Loggable
val r = {
cachedAccount.getOrElseUpdate( argList.toString, () => process(reqId, "getBankAccount", argList).extract[KafkaInboundAccount])
}
val res = new KafkaBankAccount(r)
for {
e <- tryo{OBPUser.getCurrentUserUsername}
u <- OBPUser.getApiUserByEmail(e)
} yield {
updateAccountViews(u, r)
}
Full(res)
Full(new KafkaBankAccount(r))
}
override def getBankAccounts(accts: List[(BankId, AccountId)]): List[KafkaBankAccount] = {
@ -314,17 +290,7 @@ object KafkaMappedConnector extends Connector with CreateViewImpls with Loggable
val r = {
cachedAccounts.getOrElseUpdate( argList.toString, () => process(reqId, "getBankAccounts", argList).extract[List[KafkaInboundAccount]])
}
val res = r.map { t =>
val a = new KafkaBankAccount(t)
for {
e <- tryo{OBPUser.getCurrentUserUsername}
u <- OBPUser.getApiUserByEmail(e)
} yield {
updateAccountViews(u, t)
}
a
}
res
r.map { t => new KafkaBankAccount(t) }
}
private def getAccountByNumber(bankId : BankId, number : String) : Box[AccountType] = {
@ -340,14 +306,7 @@ object KafkaMappedConnector extends Connector with CreateViewImpls with Loggable
val r = {
cachedAccount.getOrElseUpdate( argList.toString, () => process(reqId, "getBankAccount", argList).extract[KafkaInboundAccount])
}
val res = new KafkaBankAccount(r)
for {
e <- tryo{OBPUser.getCurrentUserUsername}
u <- OBPUser.getApiUserByEmail(e)
} yield {
updateAccountViews(u, r)
}
Full(res)
Full(new KafkaBankAccount(r))
}
def getOtherBankAccount(thisAccountBankId : BankId, thisAccountId : AccountId, metadata : OtherBankAccountMetadata) : Box[OtherBankAccount] = {

View File

@ -34,11 +34,11 @@ object APIMetrics extends SimpleInjector {
trait APIMetrics {
def saveMetric(userId: String, url : String, date : Date) : Unit
def saveMetric(userId: String, url : String, date : Date, userName: String, appName: String, developerEmail: String) : Unit
def saveMetric(url : String, date : Date) : Unit ={
//TODO: update all places calling old function before removing this
saveMetric ("TODO: userId", url, date)
saveMetric ("TODO: userId", url, date, "TODO: userName", "TODO: appName", "TODO: developerEmail")
}
//TODO: ordering of list? should this be by date? currently not enforced
@ -57,4 +57,7 @@ trait APIMetric {
def getUrl() : String
def getDate() : Date
def getUserId() : String
def getUserName() : String
def getAppName : String
def getDeveloperEmail() : String
}

View File

@ -9,10 +9,10 @@ object ElasticsearchMetrics extends APIMetrics {
val es = new elasticsearchMetrics
override def saveMetric(userId: String, url: String, date: Date): Unit = {
override def saveMetric(userId: String, url: String, date: Date, userName: String, appName: String, developerEmail: String): Unit = {
if (Props.getBool("allow_elasticsearch", false) && Props.getBool("allow_elasticsearch_metrics", false) ) {
es.indexMetric(userId, url, date)
es.indexMetric(userId, url, date, userName, appName, developerEmail)
}
}

View File

@ -7,8 +7,8 @@ import net.liftweb.mapper._
object MappedMetrics extends APIMetrics {
override def saveMetric(userId: String, url: String, date: Date): Unit = {
MappedMetric.create.url(url).date(date).save
override def saveMetric(userId: String, url: String, date: Date, userName: String, appName: String, developerEmail: String): Unit = {
MappedMetric.create.url(url).date(date).userName(userName).appName(appName).developerEmail(developerEmail).save
}
override def getAllGroupedByUserId(): Map[String, List[APIMetric]] = {
@ -34,13 +34,19 @@ class MappedMetric extends APIMetric with LongKeyedMapper[MappedMetric] with IdP
object userId extends DefaultStringField(this)
object url extends DefaultStringField(this)
object date extends MappedDateTime(this)
object userName extends DefaultStringField(this)
object appName extends DefaultStringField(this)
object developerEmail extends DefaultStringField(this)
override def getUrl(): String = url.get
override def getDate(): Date = date.get
override def getUserId(): String = userId.get
override def getUserName(): String = userName.get
override def getAppName(): String = appName.get
override def getDeveloperEmail(): String = developerEmail.get
}
object MappedMetric extends MappedMetric with LongKeyedMetaMapper[MappedMetric] {
override def dbIndexes = Index(userId) :: Index(url) :: Index(date) :: super.dbIndexes
override def dbIndexes = Index(userId) :: Index(url) :: Index(date) :: Index(userName) :: Index(appName) :: Index(developerEmail) :: super.dbIndexes
}

View File

@ -43,19 +43,28 @@ import net.liftweb.record.field.StringField
object userId extends StringField(this,255)
object url extends StringField(this,255)
object date extends DateField(this)
object userName extends StringField(this,255)
object appName extends StringField(this,255)
object developerEmail extends StringField(this,255)
def getUrl() = url.get
def getDate() = date.get
def getUserId() = userId.get
def getUserName(): String = userName.get
def getAppName(): String = appName.get
def getDeveloperEmail(): String = developerEmail.get
}
private object MongoAPIMetric extends MongoAPIMetric with MongoMetaRecord[MongoAPIMetric] with APIMetrics {
def saveMetric(userId: String, url : String, date : Date) : Unit = {
def saveMetric(userId: String, url : String, date : Date, userName: String, appName: String, developerEmail: String) : Unit = {
MongoAPIMetric.createRecord.
userId(userId).
url(url).
date(date).
userName(userName).
appName(appName).
developerEmail(developerEmail).
save
}

View File

@ -279,23 +279,25 @@ import net.liftweb.util.Helpers._
// What if we just want to return the userId without sending username/password??
def getUserId(username: String, password: String): Long = {
def getUserId(username: String, password: String): Box[Long] = {
findUserByUserName(username) match {
case Full(user) => {
if (user.validated_? &&
user.getProvider() == Props.get("hostname","") &&
user.testPassword(Full(password)))
{
user.id.toLong
Full(user.id.toLong)
}
else {
Props.get("connector").openOrThrowException("no connector set") match {
case "kafka" => getUserFromKafka(username, password).get.id.toLong
case _ => 0
case "kafka" =>
for { kafkaUser <- getUserFromKafka(username, password)
kafkaUserId <- tryo{kafkaUser.id} } yield kafkaUserId.toLong
case _ => Full(0)
}
}
}
case _ => 0
case _ => Full(0)
}
}
@ -363,7 +365,7 @@ import net.liftweb.util.Helpers._
val redir = loginRedirect.get match {
case Full(url) =>
loginRedirect(Empty)
url
url
case _ =>
homePage
}
@ -390,26 +392,13 @@ import net.liftweb.util.Helpers._
homePage
}
for {
username_ <- S.param("username")
password_ <- S.param("password")
user_ <- getUserFromKafka(username_, password_)
user_ <- externalUserHelper(S.param("username").getOrElse(""), S.param("password").getOrElse(""))
} yield {
if (user != null) {
logUserIn(user_, () => {
for {
u <- APIUser.find(By(APIUser.email, user_.email))
v <- tryo {
KafkaMappedConnector.updateUserAccountViews(u)
}
}
S.notice(S.?("logged.in"))
preLoginState()
S.redirectTo(redir)
})
user_
}
else
userLoginFailed
logUserIn(user_, () => {
S.notice(S.?("logged.in"))
preLoginState()
S.redirectTo(redir)
})
}
}
}
@ -422,6 +411,19 @@ import net.liftweb.util.Helpers._
"submit" -> loginSubmitButton(S.?("log.in")))
}
def externalUserHelper(username: String, password: String): Box[OBPUser] = {
if (Props.get("connector").openOrThrowException("no connector set") == "kafka") {
for {
user <- getUserFromKafka(username, password)
u <- APIUser.find(By(APIUser.email, user.email))
v <- tryo {KafkaMappedConnector.updateUserAccountViews(u)}
} yield {
user
}
} else Full(OBPUser)
}
//overridden to allow redirect to loginRedirect after signup. This is mostly to allow
// loginFirst menu items to work if the user doesn't have an account. Without this,
// if a user tries to access a logged-in only page, and then signs up, they don't get redirected

View File

@ -142,7 +142,10 @@ class elasticsearchMetrics extends elasticsearch {
"request" as (
"userId" typed StringType,
"url" typed StringType,
"date" typed DateType
"date" typed DateType,
"userName" typed StringType,
"appName" typed StringType,
"developerEmail" typed StringType
)
)
}
@ -152,14 +155,17 @@ class elasticsearchMetrics extends elasticsearch {
}
}
def indexMetric(userId: String, url: String, date: Date) {
def indexMetric(userId: String, url: String, date: Date, userName: String, appName: String, developerEmail: String) {
if (Props.getBool("allow_elasticsearch", false) && Props.getBool("allow_elasticsearch_metrics", false) ) {
try {
client.execute {
index into esIndex / "request" fields (
"userId" -> userId,
"url" -> url,
"date" -> date
"date" -> date,
"userName" -> userName,
"appName" -> appName,
"developerEmail" -> developerEmail
)
}
}

View File

@ -82,7 +82,7 @@ Berlin 13359, Germany
</div>
</div>
<div class="main-get_api_key">
<div class="green-button">
<a href="/consumer-registration">Get your API key</a>
</div>
</div>
@ -176,7 +176,7 @@ Berlin 13359, Germany
</div>
<div id="main-apiexplorer">
<div id="main-apiexplorer" class="green-button">
<a class="api-explorer-link" data-lift="WebUI.apiExplorerLink" href="">Go to API Explorer</a>
</div>
@ -187,50 +187,50 @@ Berlin 13359, Germany
<div class="main-showcases-item">
<a href="https://github.com/OpenBankProject/Hello-OBP-DirectLogin-Python"><img src="https://static.openbankproject.com/images/sandbox/showcases/python.png" width="156" height="156" alt="python" /></a>
<h2>Python</h2>
By OpenBankProject
By <a href="https://github.com/OpenBankProject/Hello-OBP-DirectLogin-Python">OpenBankProject</a>
</div>
<div class="main-showcases-item">
<a href="https://github.com/OpenBankProject/Hello-OBP-OAuth1.0a-Django"><img src="https://static.openbankproject.com/images/sandbox/showcases/django.png" width="156" height="156" alt="django" /></a>
<h2>Django</h2>
By Azd325
By <a href="https://github.com/OpenBankProject/Hello-OBP-OAuth1.0a-Django">OpenBankProject</a>
</div>
<div class="main-showcases-item">
<a href="https://github.com/OpenBankProject/Hello-OBP-OAuth1.0a-Node"><img src="https://static.openbankproject.com/images/sandbox/showcases/nodejs.png" width="156" height="156" alt="nodejs" /></a>
<h2>NodeJS</h2>
By OpenBankProject
By <a href="https://github.com/OpenBankProject/Hello-OBP-OAuth1.0a-Node">OpenBankProject</a>
</div>
<div class="main-showcases-item">
<a href="https://github.com/OpenBankProject/Hello-OBP-OAuth1.0a-Mac"><img src="https://static.openbankproject.com/images/sandbox/showcases/mac.png" width="156" height="156" alt="mac"/></a>
<h2>Mac</h2>
By T0rst
By <a href="https://github.com/OpenBankProject/Hello-OBP-OAuth1.0a-Mac">OpenBankProject</a>
</div>
<div class="main-showcases-item">
<a href="https://github.com/OpenBankProject/Hello-OBP-OAuth1.0a-IOS"><img src="https://static.openbankproject.com/images/sandbox/showcases/ios.png" width="156" height="156" alt="ios" /></a>
<h2>IOS</h2>
By T0rst
By <a href="https://github.com/OpenBankProject/Hello-OBP-OAuth1.0a-IOS">OpenBankProject</a>
</div>
<div class="main-showcases-item">
<a href="https://github.com/OpenBankProject/Hello-OBP-OAuth1.0a-Android"><img src="https://static.openbankproject.com/images/sandbox/showcases/android.png" width="156" height="156" alt="android" /></a>
<h2>Android</h2>
By OpenBankProject
By <a href="https://github.com/OpenBankProject/Hello-OBP-OAuth1.0a-Android">OpenBankProject</a>
</div>
<div class="main-showcases-item">
<a href="https://github.com/OpenBankProject/Social-Finance"><img src="https://static.openbankproject.com/images/sandbox/showcases/scala.png" width="156" height="156" alt="scala" /></a>
<h2>Scala (liftweb)</h2>
By OpenBankProject
By <a href="https://github.com/OpenBankProject/Social-Finance">OpenBankProject</a>
</div>
<div class="main-showcases-item">
<a href="https://github.com/solonas/OBP-PHP-HelloWorld"><img src="https://static.openbankproject.com/images/sandbox/showcases/php.png" width="156" height="156" alt="php" /></a>
<h2>PHP</h2>
By Solonas
By <a href="https://github.com/solonas/OBP-PHP-HelloWorld">Solonas</a>
</div>
<div class="main-showcases-item">
<a href="http://obp.sckhoo.com"><img src="https://static.openbankproject.com/images/sandbox/showcases/csharp.png" width="156" height="156" alt="csharp" /></a>
<h2>C#</h2>
By Sweechem<br /><span class="small">(this has a nice OAuth walk through with C# snippets)</span>
By <a href="http://obp.sckhoo.com">Sweechem</a>
</div>
<p>Please make sure you are using the correct sandbox domain in all calls when using the SDKs. In doubt, drop us a line.</p>
<p>Please make sure you are using the correct sandbox domain when using the SDKs. In doubt, drop us a line.</p>
</div>
@ -312,7 +312,7 @@ Berlin 13359, Germany
<div id="main-start_building">
<h1>Get started building your application using this sandbox now, register for a developer key</h1>
<div class="main-get_api_key">
<div class="green-button">
<a href="/consumer-registration">Get your API key</a>
</div>
</div>

View File

@ -1,6 +1,8 @@
#authorizeSection,
#registerAppSection,
#create-sandbox-account,
#header-decoration {
#header-decoration,
#main-showcases,
#main-support {
background-color: #ff6a10 !important;
}

View File

@ -1,14 +1,15 @@
@import url('reset.css');
body {
font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
font-weight: 400;
}
html {
height: 100%
}
body, div, h1, h2, h3, h4, p, span {
font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
font-weight: 400;
}
a {
color: #666666;
text-decoration: none;
@ -211,7 +212,10 @@ input.submit {
}
#main-about p.about-text a {
color: lightgray;
color: white;
}
#main-about p.about-text a:hover {
text-decoration: underline;
}
#main-links {
@ -228,6 +232,7 @@ input.submit {
border: 2px solid gray;
border-radius: 25px;
margin: 30px 20px 0px 20px;
font-size: 24px;
}
#main-sandbox {
@ -239,7 +244,7 @@ input.submit {
#main-sandbox h1 {
color: white;
font-size: 35px;
font-size: 36px;
margin-bottom: 15px;
margin-left: -5px;
margin-top: 15px;
@ -248,7 +253,7 @@ input.submit {
#main-get_started {
text-align: left;
margin: 40px auto;
margin: 50px auto;
background-color: #f1f4f8;
}
@ -286,7 +291,7 @@ input.submit {
#main-get_started h1 {
text-align: center;
font-weight: bold;
font-size: 35px;
font-size: 36px;
margin: 30px 0 30px 0;
display: table-caption;
}
@ -297,13 +302,14 @@ input.submit {
color: #333333;
}
.main-get_api_key {
padding: 30px 0;
#main-get_started .green-button {
padding-bottom: 40px;
}
#main-apis {
text-align: left;
margin: 40px auto;
margin: 50px auto;
display: table;
}
.main-apis-row {
@ -330,8 +336,8 @@ input.submit {
#main-apis h1 {
text-align: center;
font-weight: bold;
font-size: 35px;
margin: 30px 0 30px 0;
font-size: 36px;
margin: 0px 0 30px 0;
display: table-caption;
}
#main-apis h2 {
@ -340,19 +346,6 @@ input.submit {
margin-top: 13px;
}
#main-apiexplorer, .main-get_api_key {
width: 300px;
margin: 30px auto;
text-align: center;
}
#main-apiexplorer a, .main-get_api_key a {
border: 3px solid green;
border-radius: 25px;
color: green;
padding: 10px;
font-size: 24px;
}
#main-showcases {
margin: 50px auto;
@ -370,24 +363,24 @@ input.submit {
font-size: 24px;
}
#main-showcases p {
padding: 0 10px;
}
.main-showcases-item {
display: inline-block;
margin: 30px 10px;
width: 230px;
font-size: small;
}
.main-showcases-item span {
font-size: small;
.main-showcases-item a {
color: white;
}
.main-showcases-item a:hover {
text-decoration: underline;
}
#main-faq {
text-align: left;
margin: 40px auto;
margin: 50px auto;
padding:0 40px;
background-color: #f1f4f8;
}
@ -401,7 +394,7 @@ input.submit {
}
#main-faq h2 {
font-size: 18px;
font-size: 18px; /* text won't fit on line with 24px */
font-weight: bold;
color: #333333;
}
@ -447,8 +440,8 @@ input.submit {
#main-start_building {
margin: 40px 0;
padding: 40px 80px;
margin: 50px 0;
padding: 0 80px;
}
#main-start_building h1 {
font-size: 36px;
@ -458,7 +451,7 @@ input.submit {
#main-partners {
margin: 40px auto 0 auto;
margin: 50px auto;
text-align: center;
}
@ -499,6 +492,21 @@ input.submit {
.green-button {
width: 300px;
margin: 30px auto;
text-align: center;
}
.green-button a {
border: 3px solid green;
border-radius: 25px;
color: green;
padding: 10px;
font-size: 24px;
}
@ -630,7 +638,6 @@ input.submit {
width: inherit;
text-transform: uppercase;
font-weight: 500;
font-family: inherit;
}
.account-in-content form.login .field .forgot {
@ -921,6 +928,9 @@ span.alias_indicator_private {
#footer a {
color: white;
}
#footer #copyright {
margin-bottom: 10px;
}
#authorizeSection {

View File

@ -114,9 +114,8 @@ Berlin 13359, Germany
The main content gets bound here
</section>
<footer id="footer">
<div>
<a href="http://openbankproject.com">Open Bank Project</a> is &copy;2011-2016 <a href="http://tesobe.com">TESOBE</a> and distributed under the AGPL and commercial licenses.</p>
<br/>
<div id="copyright">
<a href="http://openbankproject.com">Open Bank Project</a> is &copy;2011-2016 <a href="http://tesobe.com">TESOBE</a> and distributed under the AGPL and commercial licenses.
</div>
<div>
<a href="http://twitter.com/#!/OpenBankProject">Twitter</a> |