Skip to content

Commit

Permalink
Merge pull request #2318 from constantine2nd/develop
Browse files Browse the repository at this point in the history
Logout a user after 2 minutes of inactivity
  • Loading branch information
simonredfern authored Nov 11, 2023
2 parents 3cf4ffa + cd25ceb commit 303b56f
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 15 deletions.
19 changes: 16 additions & 3 deletions obp-api/src/main/scala/bootstrap/liftweb/Boot.scala
Original file line number Diff line number Diff line change
Expand Up @@ -748,7 +748,19 @@ class Boot extends MdcLoggable {
}

object UsernameLockedChecker {
def beginServicing(session: LiftSession, req: Req){
def onBeginServicing(session: LiftSession, req: Req): Unit = {
logger.debug(s"Hello from UsernameLockedChecker.onBeginServicing")
checkIsLocked()
}
def onSessionActivate(session: LiftSession): Unit = {
logger.debug(s"Hello from UsernameLockedChecker.onSessionActivate")
checkIsLocked()
}
def onSessionPassivate(session: LiftSession): Unit = {
logger.debug(s"Hello from UsernameLockedChecker.onSessionPassivate")
checkIsLocked()
}
private def checkIsLocked(): Unit = {
AuthUser.currentUser match {
case Full(user) =>
LoginAttempt.userIsLocked(localIdentityProvider, user.username.get) match {
Expand All @@ -761,8 +773,9 @@ class Boot extends MdcLoggable {
}
}
}
LiftSession.onBeginServicing = UsernameLockedChecker.beginServicing _ ::
LiftSession.onBeginServicing
LiftSession.onBeginServicing = UsernameLockedChecker.onBeginServicing _ :: LiftSession.onBeginServicing
LiftSession.onSessionActivate = UsernameLockedChecker.onSessionActivate _ :: LiftSession.onSessionActivate
LiftSession.onSessionPassivate = UsernameLockedChecker.onSessionPassivate _ :: LiftSession.onSessionPassivate

APIUtil.akkaSanityCheck() match {
case Full(c) if c == true => logger.info(s"remotedata.secret matched = $c")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2215,6 +2215,23 @@ object SwaggerDefinitionsJSON {
duration = 39
)

val metricJson510 = MetricJsonV510(
user_id = ExampleValue.userIdExample.value,
url = "www.openbankproject.com",
date = DateWithDayExampleObject,
user_name = "OBP",
app_name = "SOFI",
developer_email = ExampleValue.emailExample.value,
implemented_by_partial_function = "getBanks",
implemented_in_version = "v210",
consumer_id = "123",
verb = "get",
correlation_id = "v8ho6h5ivel3uq7a5zcnv0w1",
duration = 39,
target_ip = "2001:0db8:3c4d:0015:0000:0000:1a2f:1a2b",
source_ip = "2001:0db8:3c4d:0015:0000:0000:1a2f:1a2b"
)

val resourceUserJSON = ResourceUserJSON(
user_id = ExampleValue.userIdExample.value,
email = ExampleValue.emailExample.value,
Expand Down Expand Up @@ -2788,6 +2805,9 @@ object SwaggerDefinitionsJSON {
val metricsJson = MetricsJson(
metrics = List(metricJson)
)
val metricsJsonV510 = MetricsJsonV510(
metrics = List(metricJson510)
)

val branchJsonPut = BranchJsonPutV210("gh.29.fi", "OBP",
addressJsonV140,
Expand Down
92 changes: 92 additions & 0 deletions obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1242,6 +1242,98 @@ trait APIMethods510 {
}


staticResourceDocs += ResourceDoc(
getMetrics,
implementedInApiVersion,
"getMetrics",
"GET",
"/management/metrics",
"Get Metrics",
s"""Get the all metrics
|
|require CanReadMetrics role
|
|Filters Part 1.*filtering* (no wilde cards etc.) parameters to GET /management/metrics
|
|Should be able to filter on the following metrics fields
|
|eg: /management/metrics?from_date=$DateWithMsExampleString&to_date=$DateWithMsExampleString&limit=50&offset=2
|
|1 from_date (defaults to one week before current date): eg:from_date=$DateWithMsExampleString
|
|2 to_date (defaults to current date) eg:to_date=$DateWithMsExampleString
|
|3 limit (for pagination: defaults to 50) eg:limit=200
|
|4 offset (for pagination: zero index, defaults to 0) eg: offset=10
|
|5 sort_by (defaults to date field) eg: sort_by=date
| possible values:
| "url",
| "date",
| "user_name",
| "app_name",
| "developer_email",
| "implemented_by_partial_function",
| "implemented_in_version",
| "consumer_id",
| "verb"
|
|6 direction (defaults to date desc) eg: direction=desc
|
|eg: /management/metrics?from_date=$DateWithMsExampleString&to_date=$DateWithMsExampleString&limit=10000&offset=0&anon=false&app_name=TeatApp&implemented_in_version=v2.1.0&verb=POST&user_id=c7b6cb47-cb96-4441-8801-35b57456753a&[email protected]&consumer_id=78
|
|Other filters:
|
|7 consumer_id (if null ignore)
|
|8 user_id (if null ignore)
|
|9 anon (if null ignore) only support two value : true (return where user_id is null.) or false (return where user_id is not null.)
|
|10 url (if null ignore), note: can not contain '&'.
|
|11 app_name (if null ignore)
|
|12 implemented_by_partial_function (if null ignore),
|
|13 implemented_in_version (if null ignore)
|
|14 verb (if null ignore)
|
|15 correlation_id (if null ignore)
|
|16 duration (if null ignore) non digit chars will be silently omitted
|
""".stripMargin,
emptyObjectJson,
metricsJsonV510,
List(
UserNotLoggedIn,
UserHasMissingRoles,
UnknownError
),
List(apiTagMetric, apiTagApi),
Some(List(canReadMetrics)))

lazy val getMetrics: OBPEndpoint = {
case "management" :: "metrics" :: Nil JsonGet _ => {
cc => {
implicit val ec = EndpointContext(Some(cc))
for {
(Full(u), callContext) <- authenticatedAccess(cc)
_ <- NewStyle.function.hasEntitlement("", u.userId, ApiRole.canReadMetrics, callContext)
httpParams <- NewStyle.function.extractHttpParamsFromUrl(cc.url)
(obpQueryParams, callContext) <- createQueriesByHttpParamsFuture(httpParams, callContext)
metrics <- Future(APIMetrics.apiMetrics.vend.getAllMetrics(obpQueryParams))
} yield {
(JSONFactory510.createMetricsJson(metrics), HttpCode.`200`(callContext))
}
}
}
}



staticResourceDocs += ResourceDoc(
getCustomersForUserIdsOnly,
Expand Down
66 changes: 55 additions & 11 deletions obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ import code.users.UserAttribute
import code.views.system.{AccountAccess, ViewDefinition}
import com.openbankproject.commons.model.{Address, AtmId, AtmT, BankId, BankIdAccountId, Customer, Location, Meta}
import com.openbankproject.commons.util.{ApiVersion, ScannedApiVersion}
import java.util.Date

import java.util.Date
import code.consent.MappedConsent
import code.metrics.APIMetric
import net.liftweb.common.Box
import net.liftweb.json.parse

Expand Down Expand Up @@ -229,11 +230,29 @@ case class UserAttributesResponseJsonV510(
case class CustomerIdJson(id: String)
case class CustomersIdsJsonV510(customers: List[CustomerIdJson])

case class MetricJsonV510(
user_id: String,
url: String,
date: Date,
user_name: String,
app_name: String,
developer_email: String,
implemented_by_partial_function: String,
implemented_in_version: String,
consumer_id: String,
verb: String,
correlation_id: String,
duration: Long,
target_ip: String,
source_ip: String
)
case class MetricsJsonV510(metrics: List[MetricJsonV510])

object JSONFactory510 extends CustomJsonFormats {

def createCustomersIds(customers : List[Customer]): CustomersIdsJsonV510 =
CustomersIdsJsonV510(customers.map(x => CustomerIdJson(x.customerId)))

def waitingForGodot(sleep: Long): WaitingForGodotJsonV510 = WaitingForGodotJsonV510(sleep)

def createAtmsJsonV510(atmAndAttributesTupleList: List[(AtmT, List[AtmAttribute])] ): AtmsJsonV510 = {
Expand All @@ -242,7 +261,7 @@ object JSONFactory510 extends CustomJsonFormats {
createAtmJsonV510(atmAndAttributesTuple._1,atmAndAttributesTuple._2)
))
}

def createAtmJsonV510(atm: AtmT, atmAttributes:List[AtmAttribute]): AtmJsonV510 = {
AtmJsonV510(
id = Some(atm.atmId.value),
Expand Down Expand Up @@ -397,31 +416,31 @@ object JSONFactory510 extends CustomJsonFormats {
phone = Some(atmJsonV510.phone)
)
}

def getCustomViewNamesCheck(views: List[ViewDefinition]): CheckSystemIntegrityJsonV510 = {
val success = views.size == 0
val debugInfo = if(success) None else Some(s"Incorrect custom views: ${views.map(_.viewId.value).mkString(",")}")
CheckSystemIntegrityJsonV510(
success = success,
debug_info = debugInfo
)
}
}
def getSystemViewNamesCheck(views: List[ViewDefinition]): CheckSystemIntegrityJsonV510 = {
val success = views.size == 0
val debugInfo = if(success) None else Some(s"Incorrect system views: ${views.map(_.viewId.value).mkString(",")}")
CheckSystemIntegrityJsonV510(
success = success,
debug_info = debugInfo
)
}
}
def getAccountAccessUniqueIndexCheck(groupedRows: Map[String, List[AccountAccess]]): CheckSystemIntegrityJsonV510 = {
val success = groupedRows.size == 0
val debugInfo = if(success) None else Some(s"Incorrect system views: ${groupedRows.map(_._1).mkString(",")}")
CheckSystemIntegrityJsonV510(
success = success,
debug_info = debugInfo
)
}
}
def getSensibleCurrenciesCheck(bankCurrencies: List[String], accountCurrencies: List[String]): CheckSystemIntegrityJsonV510 = {
val incorrectCurrencies: List[String] = bankCurrencies.filterNot(c => accountCurrencies.contains(c))
val success = incorrectCurrencies.size == 0
Expand All @@ -430,7 +449,7 @@ object JSONFactory510 extends CustomJsonFormats {
success = success,
debug_info = debugInfo
)
}
}
def getOrphanedAccountsCheck(orphanedAccounts: List[String]): CheckSystemIntegrityJsonV510 = {
val success = orphanedAccounts.size == 0
val debugInfo = if(success) None else Some(s"Orphaned account's ids: ${orphanedAccounts.mkString(",")}")
Expand All @@ -439,7 +458,7 @@ object JSONFactory510 extends CustomJsonFormats {
debug_info = debugInfo
)
}

def getConsentInfoJson(consent: MappedConsent): ConsentJsonV510 = {
val jsonWebTokenAsJValue: Box[ConsentJWT] = JwtUtil.getSignedPayloadAsJson(consent.jsonWebToken).map(parse(_).extract[ConsentJWT])
ConsentJsonV510(
Expand All @@ -450,7 +469,7 @@ object JSONFactory510 extends CustomJsonFormats {
jsonWebTokenAsJValue.map(_.entitlements).toOption
)
}

def getApiInfoJSON(apiVersion : ApiVersion, apiVersionStatus: String) = {
val organisation = APIUtil.getPropsValue("hosted_by.organisation", "TESOBE")
val email = APIUtil.getPropsValue("hosted_by.email", "[email protected]")
Expand Down Expand Up @@ -494,7 +513,7 @@ object JSONFactory510 extends CustomJsonFormats {
value = atmAttribute.value,
is_active = atmAttribute.isActive
)

def createAtmAttributesJson(atmAttributes: List[AtmAttribute]): AtmAttributesResponseJsonV510 =
AtmAttributesResponseJsonV510(atmAttributes.map(createAtmAttributeJson))

Expand All @@ -512,5 +531,30 @@ object JSONFactory510 extends CustomJsonFormats {
def createUserAttributesJson(userAttribute: List[UserAttribute]): UserAttributesResponseJsonV510 = {
UserAttributesResponseJsonV510(userAttribute.map(createUserAttributeJson))
}

def createMetricJson(metric: APIMetric): MetricJsonV510 = {
MetricJsonV510(
user_id = metric.getUserId(),
user_name = metric.getUserName(),
developer_email = metric.getDeveloperEmail(),
app_name = metric.getAppName(),
url = metric.getUrl(),
date = metric.getDate(),
consumer_id = metric.getConsumerId(),
verb = metric.getVerb(),
implemented_in_version = metric.getImplementedInVersion(),
implemented_by_partial_function = metric.getImplementedByPartialFunction(),
correlation_id = metric.getCorrelationId(),
duration = metric.getDuration(),
target_ip = metric.getSourceIp(),
source_ip = metric.getTargetIp()
)
}

def createMetricsJson(metrics: List[APIMetric]): MetricsJsonV510 = {
MetricsJsonV510(metrics.map(createMetricJson))
}


}

63 changes: 63 additions & 0 deletions obp-api/src/main/webapp/media/js/inactivity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// holds the idle duration in ms (current value = 2 minutes)
var timeoutInterval = 120000;
// holds the timeout variables for easy destruction and reconstruction of the setTimeout hooks
var timeHook = null;

function initializeTimeHook() {
// this method has the purpose of creating our timehooks and scheduling the call to our logout function when the idle time has been reached
if (timeHook == null) {
timeHook = setTimeout( function () { destroyTimeHook(); logout(); }.bind(this), timeoutInterval);
}
}

function destroyTimeHook() {
// this method has the sole purpose of destroying any time hooks we might have created
clearTimeout(timeHook);
timeHook = null;
}

function resetTimeHook() {
// this method replaces the current time hook with a new time hook
destroyTimeHook();
initializeTimeHook();
console.log("Reset inactivity of a user");
}

function setupListeners() {
// here we setup the event listener for the mouse click operation
document.addEventListener("click", function () { resetTimeHook(); }.bind(this));
document.addEventListener("mousemove", function () { resetTimeHook(); }.bind(this));
document.addEventListener("mousedown", function () { resetTimeHook(); }.bind(this));
document.addEventListener("keypress", function () { resetTimeHook(); }.bind(this));
document.addEventListener("touchmove", function () { resetTimeHook(); }.bind(this));
console.log("Listeners for user inactivity activated");
}

function destroyListeners() {
// here we destroy event listeners for the mouse click operation
document.removeEventListener("click", function () { resetTimeHook(); }.bind(this));
document.removeEventListener("mousemove", function () { resetTimeHook(); }.bind(this));
document.removeEventListener("mousedown", function () { resetTimeHook(); }.bind(this));
document.removeEventListener("keypress", function () { resetTimeHook(); }.bind(this));
document.removeEventListener("touchmove", function () { resetTimeHook(); }.bind(this));
console.log("Listeners for user inactivity deactivated");
}

function logout() {
const elem = document.getElementById("loggedIn-username");
if(elem) {
location.href = '/user_mgt/logout';
destroyListeners();
console.log("Logging you out due to inactivity..");
}
}

// self executing function to trigger the operation on page load
(function () {
// to prevent any lingering timeout handlers preventing memory leaks
destroyTimeHook();
// setup a fresh time hook
initializeTimeHook();
// setup initial event listeners
setupListeners();
})();
1 change: 1 addition & 0 deletions obp-api/src/main/webapp/templates-hidden/default.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
<script src="/media/js/moment-with-locales.min.js"></script>
<script src="/media/js/bootstrap-datetimepicker.min.js"></script>
<script src="/media/js/popper.min.js"></script>
<script defer src="/media/js/inactivity.js"></script> <!-- The script loads “in the background”, and then runs when the DOM is fully built. -->
</head>
<body id="page_init">
<div id="cookies-consent" data-lift="WebUI.cookieConsent">
Expand Down
Loading

0 comments on commit 303b56f

Please sign in to comment.