diff --git a/obp-api/src/main/resources/props/sample.props.template b/obp-api/src/main/resources/props/sample.props.template index 20c34d4dde..825c3cb523 100644 --- a/obp-api/src/main/resources/props/sample.props.template +++ b/obp-api/src/main/resources/props/sample.props.template @@ -1632,7 +1632,7 @@ regulated_entities = [] # "grantee_consumer_id": "fb327484-94d7-44d2-83e5-8d27301e8279" \ #}] -# Bootstrap Super User +# Bootstrap Super User / Break Glass # Given the following credentials, OBP will create a user if they do not already exist. # This user's password will be valid for a limited time. # This user will be granted ONLY the CanCreateEntitlementAtAnyBank permission. diff --git a/obp-api/src/main/scala/code/api/directlogin.scala b/obp-api/src/main/scala/code/api/directlogin.scala index 482c72fcf4..b279326832 100644 --- a/obp-api/src/main/scala/code/api/directlogin.scala +++ b/obp-api/src/main/scala/code/api/directlogin.scala @@ -352,10 +352,10 @@ object DirectLogin extends RestHelper with MdcLoggable { case false => false }*/ case _ => false - } recover { + } recoverWith { case e: Throwable => logger.error(s"validatorFuture.validAccessTokenFuture failed: ${e.getMessage}", e) - false + Future.failed(e) } } @@ -431,10 +431,10 @@ object DirectLogin extends RestHelper with MdcLoggable { Tokens.tokens.vend.getTokenByKeyAndTypeFuture(tokenKey, TokenType.Access) map { case Full(token) => token.isValid case _ => false - } recover { + } recoverWith { case e: Throwable => logger.error(s"validatorFutureWithParams.validAccessTokenFuture failed: ${e.getMessage}", e) - false + Future.failed(e) } } @@ -638,10 +638,10 @@ object DirectLogin extends RestHelper with MdcLoggable { Tokens.tokens.vend.getTokenByKeyFuture(token) map { case Full(t) => t.consumerId.foreign case _ => Empty - } recover { + } recoverWith { case e: Throwable => logger.error(s"getConsumerFromDirectLoginToken failed: ${e.getMessage}", e) - Empty + Future.failed(e) } } @@ -661,10 +661,10 @@ object DirectLogin extends RestHelper with MdcLoggable { } } yield { user - }) recover { + }) recoverWith { case e: Throwable => logger.error(s"getUserFromDirectLoginToken failed: ${e.getMessage}", e) - Empty + Future.failed(e) } } } diff --git a/obp-api/src/main/scala/code/api/util/APIUtil.scala b/obp-api/src/main/scala/code/api/util/APIUtil.scala index 7cc228882e..bb02be7955 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -3998,7 +3998,8 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ "public_keycloak_url" -> getPropsValue("public_keycloak_url").openOr("http://localhost:7787"), "public_obp_hola_url" -> getPropsValue("public_obp_hola_url").openOr("http://localhost:8087"), "public_obp_mcp_url" -> getPropsValue("public_obp_mcp_url").openOr("http://localhost:9100"), - "public_obp_opey_url" -> getPropsValue("public_obp_opey_url").openOr("http://localhost:5000") + "public_obp_opey_url" -> getPropsValue("public_obp_opey_url").openOr("http://localhost:5000"), + "public_rabbit_cats_adapter_url" -> getPropsValue("public_rabbit_cats_adapter_url").openOr("http://localhost:8089") ) val publicAppUrlPropNames: List[String] = publicAppUrlDefaults.keys.toList.sorted // Register defaults so they appear in getConfigPropsPairs diff --git a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala index 6c6d6c3875..1e8b90773a 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala @@ -2374,6 +2374,19 @@ trait APIMethods510 { } requestedEntitlements = consentJson.entitlements myEntitlements <- Entitlement.entitlement.vend.getEntitlementsByUserIdFuture(user.userId) + _ = logger.info(s"createConsent says: userId=${user.userId}, userName=${user.name}, requestedEntitlements=${requestedEntitlements.map(re => s"(role_name=${re.role_name}, bank_id=${re.bank_id})")}, myEntitlements=${myEntitlements.getOrElse(Nil).map(e => s"(roleName=${e.roleName}, bankId=${e.bankId})")}") + _ = { + val myEnts = myEntitlements.getOrElse(Nil) + requestedEntitlements.foreach { re => + val matched = myEnts.exists(e => e.roleName == re.role_name && e.bankId == re.bank_id) + logger.info(s"createConsent says: checking requested role_name=${re.role_name}, bank_id='${re.bank_id}' => matched=$matched") + if (!matched) { + myEnts.foreach { e => + logger.info(s"createConsent says: comparing with roleName=${e.roleName}, bankId='${e.bankId}' => nameMatch=${e.roleName == re.role_name}, bankIdMatch=${e.bankId == re.bank_id}") + } + } + } + } _ <- Helper.booleanToFuture(RolesAllowedInConsent, cc = callContext) { requestedEntitlements.forall( re => myEntitlements.getOrElse(Nil).exists( diff --git a/obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala b/obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala index df99c89907..2daffc7682 100644 --- a/obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala +++ b/obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala @@ -2253,11 +2253,10 @@ trait APIMethods600 { JSONFactory600.createProvidersJson(List("http://127.0.0.1:8080", "OBP", "google.com")), List( $AuthenticatedUserIsRequired, - UserHasMissingRoles, UnknownError ), List(apiTagUser), - Some(List(canGetProviders)) + None ) lazy val getProviders: OBPEndpoint = { @@ -2265,7 +2264,6 @@ trait APIMethods600 { cc => implicit val ec = EndpointContext(Some(cc)) for { (Full(u), callContext) <- authenticatedAccess(cc) - _ <- NewStyle.function.hasEntitlement("", u.userId, canGetProviders, callContext) providers <- Future { code.model.dataAccess.ResourceUser.getDistinctProviders } } yield { (JSONFactory600.createProvidersJson(providers), HttpCode.`200`(callContext)) @@ -8751,8 +8749,12 @@ trait APIMethods600 { lazy val verifyUserCredentials: OBPEndpoint = { case "users" :: "verify-credentials" :: Nil JsonPost json -> _ => { cc => implicit val ec = EndpointContext(Some(cc)) + // TODO: Consider allowing Client Credentials (app-level) auth instead of user-level auth, + // so the caller doesn't need to be logged in as a user (which is circular for credential verification). + // TODO: Add rate limiting / anti-DOS protection for this endpoint to prevent credential enumeration/spamming. for { - postedData <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the PostVerifyUserCredentialsJsonV600", 400, Some(cc)) { + (Full(u), callContext) <- authenticatedAccess(cc) + postedData <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the PostVerifyUserCredentialsJsonV600", 400, callContext) { json.extract[PostVerifyUserCredentialsJsonV600] } // Validate credentials using the existing AuthUser mechanism @@ -8787,27 +8789,27 @@ trait APIMethods600 { } } // Check if account is locked - _ <- Helper.booleanToFuture(UsernameHasBeenLocked, 401, Some(cc)) { + _ <- Helper.booleanToFuture(UsernameHasBeenLocked, 401, callContext) { resourceUserIdBox != Full(code.model.dataAccess.AuthUser.usernameLockedStateCode) } // Check if credentials are valid resourceUserId <- Future { resourceUserIdBox } map { - x => unboxFullOrFail(x, Some(cc), s"$InvalidLoginCredentials Failed to authenticate user credentials.", 401) + x => unboxFullOrFail(x, callContext, s"$InvalidLoginCredentials Failed to authenticate user credentials.", 401) } // Get the user object user <- Future { Users.users.vend.getUserByResourceUserId(resourceUserId) } map { - x => unboxFullOrFail(x, Some(cc), s"$InvalidLoginCredentials User account not found in system.", 401) + x => unboxFullOrFail(x, callContext, s"$InvalidLoginCredentials User account not found in system.", 401) } // Verify provider matches if specified and not empty - _ <- Helper.booleanToFuture(s"$InvalidLoginCredentials Authentication provider mismatch.", 401, Some(cc)) { + _ <- Helper.booleanToFuture(s"$InvalidLoginCredentials Authentication provider mismatch.", 401, callContext) { postedData.provider.isEmpty || user.provider == postedData.provider } } yield { - (JSONFactory200.createUserJSON(user), HttpCode.`200`(Some(cc))) + (JSONFactory200.createUserJSON(user), HttpCode.`200`(callContext)) } } }