diff --git a/obp-api/src/main/scala/code/api/util/NewStyle.scala b/obp-api/src/main/scala/code/api/util/NewStyle.scala index ab5944ce17..b73b9a2e43 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -833,7 +833,7 @@ object NewStyle extends MdcLoggable{ } def getUserInvitation(bankId: BankId, secretLink: Long, callContext: Option[CallContext]): OBPReturnType[UserInvitation] = Future { val response: Box[UserInvitation] = UserInvitationProvider.userInvitationProvider.vend.getUserInvitation(bankId, secretLink) - (unboxFullOrFail(response, callContext, s"$CannotGetUserInvitation", 400), callContext) + (unboxFullOrFail(response, callContext, s"$CannotGetUserInvitation", 404), callContext) } def getUserInvitations(bankId: BankId, callContext: Option[CallContext]): OBPReturnType[List[UserInvitation]] = Future { val response = UserInvitationProvider.userInvitationProvider.vend.getUserInvitations(bankId) @@ -852,7 +852,7 @@ object NewStyle extends MdcLoggable{ } } def getAgreementByUserId(userId: String, agreementType: String, callContext: Option[CallContext]): Future[Box[UserAgreement]] = { - Future(UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(userId, agreementType)) + Future(UserAgreementProvider.userAgreementProvider.vend.getLastUserAgreement(userId, agreementType)) } def getEntitlementsByBankId(bankId: String, callContext: Option[CallContext]): Future[List[Entitlement]] = { diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index 88841a027f..bfdc3711a5 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -3869,16 +3869,17 @@ trait APIMethods400 extends MdcLoggable { "POST", "/banks/BANK_ID/user-invitations", "Get User Invitation Information", - s"""Create User Invitation Information. + s"""Get User Invitation Information. | |${authenticationRequiredMessage(false)} |""", PostUserInvitationAnonymousJsonV400(secret_key = 5819479115482092878L), userInvitationJsonV400, List( - UserNotLoggedIn, $BankNotFound, UserCustomerLinksNotFoundForUser, + CannotGetUserInvitation, + CannotFindUserInvitation, UnknownError ), List(apiTagUserInvitation, apiTagKyc) diff --git a/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala b/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala index 4dc05128f0..09bda9b104 100644 --- a/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala +++ b/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala @@ -686,9 +686,9 @@ import net.liftweb.util.Helpers._ val privacyPolicyValue: String = getWebUiPropsValue("webui_privacy_policy", "") val termsAndConditionsValue: String = getWebUiPropsValue("webui_terms_and_conditions", "") // User Agreement table - UserAgreementProvider.userAgreementProvider.vend.createOrUpdateUserAgreement( + UserAgreementProvider.userAgreementProvider.vend.createUserAgreement( theUser.user.foreign.map(_.userId).getOrElse(""), "privacy_conditions", privacyPolicyValue) - UserAgreementProvider.userAgreementProvider.vend.createOrUpdateUserAgreement( + UserAgreementProvider.userAgreementProvider.vend.createUserAgreement( theUser.user.foreign.map(_.userId).getOrElse(""), "terms_and_conditions", termsAndConditionsValue) if (!skipEmailValidation) { sendValidationEmail(theUser) @@ -1041,13 +1041,13 @@ def restoreSomeSessions(): Unit = { def redirectUri(user: Box[ResourceUser]): String = { val userId = user.map(_.userId).getOrElse("") val hashedAgreementTextOfUser = - UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(userId, "terms_and_conditions") + UserAgreementProvider.userAgreementProvider.vend.getLastUserAgreement(userId, "terms_and_conditions") .map(_.agreementHash).getOrElse(HashUtil.Sha256Hash("not set")) val agreementText = getWebUiPropsValue("webui_terms_and_conditions", "not set") val hashedAgreementText = HashUtil.Sha256Hash(agreementText) if(hashedAgreementTextOfUser == hashedAgreementText) { // Check terms and conditions val hashedAgreementTextOfUser = - UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(userId, "privacy_conditions") + UserAgreementProvider.userAgreementProvider.vend.getLastUserAgreement(userId, "privacy_conditions") .map(_.agreementHash).getOrElse(HashUtil.Sha256Hash("not set")) val agreementText = getWebUiPropsValue("webui_privacy_policy", "not set") val hashedAgreementText = HashUtil.Sha256Hash(agreementText) diff --git a/obp-api/src/main/scala/code/snippet/PrivacyPolicy.scala b/obp-api/src/main/scala/code/snippet/PrivacyPolicy.scala index 1ff3fd0e71..b9549c9921 100644 --- a/obp-api/src/main/scala/code/snippet/PrivacyPolicy.scala +++ b/obp-api/src/main/scala/code/snippet/PrivacyPolicy.scala @@ -68,7 +68,7 @@ class PrivacyPolicy extends MdcLoggable { private def updateUserAgreement() = { if(AuthUser.currentUser.isDefined) { val agreementText = getWebUiPropsValue("webui_privacy_policy", "not set") - UserAgreementProvider.userAgreementProvider.vend.createOrUpdateUserAgreement( + UserAgreementProvider.userAgreementProvider.vend.createUserAgreement( AuthUser.currentUser.flatMap(_.user.foreign.map(_.userId)).getOrElse(""), "privacy_conditions", agreementText) S.redirectTo("/") } diff --git a/obp-api/src/main/scala/code/snippet/TermsAndConditions.scala b/obp-api/src/main/scala/code/snippet/TermsAndConditions.scala index 87e6393636..e66692cc71 100644 --- a/obp-api/src/main/scala/code/snippet/TermsAndConditions.scala +++ b/obp-api/src/main/scala/code/snippet/TermsAndConditions.scala @@ -69,7 +69,7 @@ class TermsAndConditions extends MdcLoggable { if(AuthUser.currentUser.isDefined) { val agreementText = getWebUiPropsValue("webui_terms_and_conditions", "not set") // val hashedAgreementText = HashUtil.Sha256Hash(agreementText) - UserAgreementProvider.userAgreementProvider.vend.createOrUpdateUserAgreement( + UserAgreementProvider.userAgreementProvider.vend.createUserAgreement( AuthUser.currentUser.flatMap(_.user.foreign.map(_.userId)).getOrElse(""), "terms_and_conditions", agreementText) diff --git a/obp-api/src/main/scala/code/snippet/UserInvitation.scala b/obp-api/src/main/scala/code/snippet/UserInvitation.scala index 700b754fa8..0833b5cad3 100644 --- a/obp-api/src/main/scala/code/snippet/UserInvitation.scala +++ b/obp-api/src/main/scala/code/snippet/UserInvitation.scala @@ -121,13 +121,13 @@ class UserInvitation extends MdcLoggable { showError(msg) case _ => // User Agreement table - UserAgreementProvider.userAgreementProvider.vend.createOrUpdateUserAgreement( + UserAgreementProvider.userAgreementProvider.vend.createUserAgreement( u.userId, "privacy_conditions", privacyConditionsValue) - UserAgreementProvider.userAgreementProvider.vend.createOrUpdateUserAgreement( + UserAgreementProvider.userAgreementProvider.vend.createUserAgreement( u.userId, "terms_and_conditions", termsAndConditionsValue) - UserAgreementProvider.userAgreementProvider.vend.createOrUpdateUserAgreement( + UserAgreementProvider.userAgreementProvider.vend.createUserAgreement( u.userId, "accept_marketing_info", marketingInfoCheckboxVar.is.toString) - UserAgreementProvider.userAgreementProvider.vend.createOrUpdateUserAgreement( + UserAgreementProvider.userAgreementProvider.vend.createUserAgreement( u.userId, "consent_for_collecting_personal_data", consentForCollectingCheckboxVar.is.toString) // Set the status of the user invitation to "FINISHED" UserInvitationProvider.userInvitationProvider.vend.updateStatusOfUserInvitation(userInvitation.map(_.userInvitationId).getOrElse(""), "FINISHED") @@ -187,9 +187,9 @@ class UserInvitation extends MdcLoggable { "#companyName" #> SHtml.text(companyVar.is, companyVar(_)) & "#devEmail" #> SHtml.text(devEmailVar, devEmailVar(_)) & "#username" #> SHtml.text(usernameVar, usernameVar(_)) & - "#privacy_checkbox" #> SHtml.checkbox(privacyCheckboxVar, privacyCheckboxVar(_)) & - "#terms_checkbox" #> SHtml.checkbox(termsCheckboxVar, termsCheckboxVar(_)) & - "#marketing_info_checkbox" #> SHtml.checkbox(marketingInfoCheckboxVar, marketingInfoCheckboxVar(_)) & + "#user_invitation_privacy_checkbox" #> SHtml.checkbox(privacyCheckboxVar, privacyCheckboxVar(_), "id" -> "user_invitation_privacy_checkbox") & + "#user_invitation_terms_checkbox" #> SHtml.checkbox(termsCheckboxVar, termsCheckboxVar(_), "id" -> "user_invitation_terms_checkbox") & + "#marketing_info_checkbox" #> SHtml.checkbox(marketingInfoCheckboxVar, marketingInfoCheckboxVar(_), "id" -> "marketing_info_checkbox") & "#consent_for_collecting_checkbox" #> SHtml.checkbox(consentForCollectingCheckboxVar, consentForCollectingCheckboxVar(_), "id" -> "consent_for_collecting_checkbox") & "#consent_for_collecting_mandatory" #> SHtml.checkbox(consentForCollectingMandatoryCheckboxVar, consentForCollectingMandatoryCheckboxVar(_), "id" -> "consent_for_collecting_mandatory", "hidden" -> "true") & "type=submit" #> SHtml.submit(s"$registrationConsumerButtonValue", () => submitButtonDefense) diff --git a/obp-api/src/main/scala/code/users/LiftUsers.scala b/obp-api/src/main/scala/code/users/LiftUsers.scala index f5a21983d0..39ae2f690a 100644 --- a/obp-api/src/main/scala/code/users/LiftUsers.scala +++ b/obp-api/src/main/scala/code/users/LiftUsers.scala @@ -133,9 +133,9 @@ object LiftUsers extends Users with MdcLoggable{ } private def getUserAgreements(user: ResourceUser) = { - val acceptMarketingInfo = UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(user.userId, "accept_marketing_info") - val termsAndConditions = UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(user.userId, "terms_and_conditions") - val privacyConditions = UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(user.userId, "privacy_conditions") + val acceptMarketingInfo = UserAgreementProvider.userAgreementProvider.vend.getLastUserAgreement(user.userId, "accept_marketing_info") + val termsAndConditions = UserAgreementProvider.userAgreementProvider.vend.getLastUserAgreement(user.userId, "terms_and_conditions") + val privacyConditions = UserAgreementProvider.userAgreementProvider.vend.getLastUserAgreement(user.userId, "privacy_conditions") val agreements = acceptMarketingInfo.toList ::: termsAndConditions.toList ::: privacyConditions.toList agreements } diff --git a/obp-api/src/main/scala/code/users/UserAgreement.scala b/obp-api/src/main/scala/code/users/UserAgreement.scala index ac51bfd865..e4deda7818 100644 --- a/obp-api/src/main/scala/code/users/UserAgreement.scala +++ b/obp-api/src/main/scala/code/users/UserAgreement.scala @@ -10,32 +10,6 @@ import net.liftweb.mapper._ import net.liftweb.common.Box.tryo object MappedUserAgreementProvider extends UserAgreementProvider { - // TODO Change the function name - override def createOrUpdateUserAgreement(userId: String, agreementType: String, agreementText: String): Box[UserAgreement] = { - UserAgreement.find( - By(UserAgreement.UserId, userId), - By(UserAgreement.AgreementType, agreementType) - ) match { - // TODO We should be adding an additional record. Not changing existing one. - case Full(existingUser) => - Full( - existingUser - .AgreementType(agreementType) - .AgreementText(agreementText) - .saveMe() - ) - case Empty => - Full( - UserAgreement.create - .UserId(userId) - .AgreementType(agreementType) - .AgreementText(agreementText) - .Date(new Date) - .saveMe() - ) - case everythingElse => everythingElse - } - } override def createUserAgreement(userId: String, agreementType: String, agreementText: String): Box[UserAgreement] = { Full( UserAgreement.create @@ -46,7 +20,7 @@ object MappedUserAgreementProvider extends UserAgreementProvider { .saveMe() ) } - override def getUserAgreement(userId: String, agreementType: String): Box[UserAgreement] = { + override def getLastUserAgreement(userId: String, agreementType: String): Box[UserAgreement] = { UserAgreement.findAll( By(UserAgreement.UserId, userId), By(UserAgreement.AgreementType, agreementType) diff --git a/obp-api/src/main/scala/code/users/UserAgreementProvider.scala b/obp-api/src/main/scala/code/users/UserAgreementProvider.scala index 3cade8c980..8381fdbe98 100644 --- a/obp-api/src/main/scala/code/users/UserAgreementProvider.scala +++ b/obp-api/src/main/scala/code/users/UserAgreementProvider.scala @@ -16,9 +16,8 @@ object UserAgreementProvider extends SimpleInjector { } trait UserAgreementProvider { - def createOrUpdateUserAgreement(userId: String, agreementType: String, agreementText: String): Box[UserAgreement] def createUserAgreement(userId: String, agreementType: String, agreementText: String): Box[UserAgreement] - def getUserAgreement(userId: String, agreementType: String): Box[UserAgreement] + def getLastUserAgreement(userId: String, agreementType: String): Box[UserAgreement] } trait UserAgreementTrait { diff --git a/obp-api/src/main/webapp/user-invitation.html b/obp-api/src/main/webapp/user-invitation.html index 55764e7378..c9ca142b76 100644 --- a/obp-api/src/main/webapp/user-invitation.html +++ b/obp-api/src/main/webapp/user-invitation.html @@ -74,8 +74,8 @@

Complete your user invitation

Privacy Policy
- - + +
@@ -83,8 +83,8 @@

Complete your user invitation

Terms and Conditions
- - + +

diff --git a/obp-api/src/test/scala/code/api/v4_0_0/UserInvitationApiAndGuiTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/UserInvitationApiAndGuiTest.scala new file mode 100644 index 0000000000..85003d8091 --- /dev/null +++ b/obp-api/src/test/scala/code/api/v4_0_0/UserInvitationApiAndGuiTest.scala @@ -0,0 +1,269 @@ +package code.api.v4_0_0 + +import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON +import code.api.util.APIUtil.OAuth._ +import code.api.util.ApiRole +import code.api.util.ApiRole.{CanCreateUserInvitation, CanGetUserInvitation} +import code.api.util.ErrorMessages.{CannotGetUserInvitation, UserHasMissingRoles, UserNotLoggedIn} +import code.api.v4_0_0.OBPAPI4_0_0.Implementations4_0_0 +import code.entitlement.Entitlement +import code.users.{UserInvitation, UserInvitationProvider} +import code.util.Helper.MdcLoggable +import com.github.dwickern.macros.NameOf.nameOf +import com.openbankproject.commons.model.ErrorMessage +import com.openbankproject.commons.util.ApiVersion +import net.liftweb.common.Box +import net.liftweb.json.Serialization.write +import net.liftweb.util.Helpers.tryo +import org.scalatest.Tag +import org.scalatestplus.selenium.HtmlUnit + +class UserInvitationApiAndGuiTest extends V400ServerSetup { + /** + * Test tags + * Example: To run tests with tag "getPermissions": + * mvn test -D tagsToInclude + * + * This is made possible by the scalatest maven plugin + */ + object VersionOfApi extends Tag(ApiVersion.v4_0_0.toString) + object ApiEndpoint1 extends Tag(nameOf(Implementations4_0_0.createUserInvitation)) + object ApiEndpoint2 extends Tag(nameOf(Implementations4_0_0.getUserInvitationAnonymous)) + object ApiEndpoint3 extends Tag(nameOf(Implementations4_0_0.getUserInvitation)) + object ApiEndpoint4 extends Tag(nameOf(Implementations4_0_0.getUserInvitations)) + + + case class UserInvitationAtBrowser() extends HtmlUnit with MdcLoggable { + implicit val driver = webDriver + + def closeAndQuit(): Unit = { + close() + quit() + } + + def checkPrepoulatedFields(loginPage: String, userInvitation: UserInvitation): Box[Boolean] = { + tryo { + go.to(loginPage) + val firstNameOk = textField("firstName").value == userInvitation.firstName + val lastNameOk = textField("lastName").value == userInvitation.lastName + val emailOk = textField("devEmail").value == userInvitation.email + val companyNameOk = textField("companyName").value == userInvitation.company + val countryOk = textField("country").value == userInvitation.country + + firstNameOk && lastNameOk && emailOk && companyNameOk && countryOk + } + } + def checkSubmitButtonDisabled(loginPage: String): Box[Boolean] = { + tryo { + go.to(loginPage) + val consentForCollectingCheckbox = checkbox("consent_for_collecting_checkbox").isSelected + val privacyCheckbox = checkbox("user_invitation_privacy_checkbox").isSelected + val termsCheckboxValue = checkbox("user_invitation_terms_checkbox").isSelected + val button = driver.findElementById("submit-button") + + ((termsCheckboxValue && privacyCheckbox && consentForCollectingCheckbox) == button.isEnabled) && + !button.isEnabled + } + } + def checkSubmitButtonDisabled2(loginPage: String): Box[Boolean] = { + tryo { + go.to(loginPage) + checkbox("user_invitation_privacy_checkbox").select + val consentForCollectingCheckbox = checkbox("consent_for_collecting_checkbox").isSelected + val privacyCheckbox = checkbox("user_invitation_privacy_checkbox").isSelected + val termsCheckboxValue = checkbox("user_invitation_terms_checkbox").isSelected + val button = driver.findElementById("submit-button") + + ((termsCheckboxValue && privacyCheckbox && consentForCollectingCheckbox) == button.isEnabled) && + !button.isEnabled + } + } + def checkSubmitButtonDisabled3(loginPage: String): Box[Boolean] = { + tryo { + go.to(loginPage) + checkbox("user_invitation_terms_checkbox").select + val consentForCollectingCheckbox = checkbox("consent_for_collecting_checkbox").isSelected + val privacyCheckbox = checkbox("user_invitation_privacy_checkbox").isSelected + val termsCheckboxValue = checkbox("user_invitation_terms_checkbox").isSelected + val button = driver.findElementById("submit-button") + + ((termsCheckboxValue && privacyCheckbox && consentForCollectingCheckbox) == button.isEnabled) && + !button.isEnabled + } + } + def checkSubmitButtonEnabled(loginPage: String): Box[Boolean] = { + tryo { + go.to(loginPage) + checkbox("consent_for_collecting_checkbox").select + checkbox("user_invitation_privacy_checkbox").select + checkbox("user_invitation_terms_checkbox").select + val consentForCollectingCheckbox = checkbox("consent_for_collecting_checkbox").isSelected + val privacyCheckbox = checkbox("user_invitation_privacy_checkbox").isSelected + val termsCheckboxValue = checkbox("user_invitation_terms_checkbox").isSelected + val button = driver.findElementById("submit-button") + + ((termsCheckboxValue && privacyCheckbox && consentForCollectingCheckbox) == button.isEnabled) && + button.isEnabled + } + } + def submitForm(loginPage: String, invitation: UserInvitation): Box[Boolean]= { + tryo { + go.to(loginPage) + checkbox("consent_for_collecting_checkbox").select + checkbox("user_invitation_privacy_checkbox").select + checkbox("user_invitation_terms_checkbox").select + click on XPathQuery("""//input[@type='submit']""") + val setPasswordUrl = currentUrl + // In case of successful submit the new URL looks like + // http://localhost:8016/user_mgt/reset_password/IXIU0TVDUCKOH3RWEJF0XSSHNKSEFTQT?action=set + if(setPasswordUrl.contains("user_mgt/reset_password") && setPasswordUrl.contains("action=set")) { + // Set a new password + val password = "Mjsjssj2odjd#" + val passwordField = IdQuery("password").webElement + passwordField.clear() + passwordField.sendKeys(password) + // Repeat the password value + val repeatPasswordField = IdQuery("repeatpassword").webElement + repeatPasswordField.clear() + repeatPasswordField.sendKeys(password) + // Submit the form + click on XPathQuery("""//input[@type='submit']""") + // After the password is set the page is redirected to the home page i.e. http://localhost:8016/ + // and the user is logged in + val span = driver.findElementById("loggedIn-username") + loginPage.contains(currentUrl) && + span.getText == s"${invitation.firstName.toLowerCase()}.${invitation.lastName.toLowerCase()}" + } else { + false + } + } + } + + + } + + + feature(s"test $ApiEndpoint1 version $VersionOfApi - Unauthorized access") { + scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) { + When("We make a request v4.0.0") + val request400 = (v4_0_0_Request / "banks" / testBankId1.value / "user-invitation").POST + val postJson = SwaggerDefinitionsJSON.userInvitationPostJsonV400 + val response400 = makePostRequest(request400, write(postJson)) + Then("We should get a 401") + response400.code should equal(401) + response400.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + } + } + feature(s"test $ApiEndpoint1 version $VersionOfApi - Authorized access") { + scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) { + When("We make a request v4.0.0") + val request400 = (v4_0_0_Request / "banks" / testBankId1.value / "user-invitation").POST <@(user1) + val postJson = SwaggerDefinitionsJSON.userInvitationPostJsonV400 + val response400 = makePostRequest(request400, write(postJson)) + Then("error should be " + UserHasMissingRoles + CanCreateUserInvitation) + response400.code should equal(403) + response400.body.extract[ErrorMessage].message should startWith(UserHasMissingRoles + CanCreateUserInvitation) + } + } + feature(s"test $ApiEndpoint1 and $ApiEndpoint4 version $VersionOfApi - Successful response") { + scenario("We will call the endpoint without user credentials", ApiEndpoint1, ApiEndpoint4, VersionOfApi) { + When("We add required entitlement") + Entitlement.entitlement.vend.addEntitlement(testBankId1.value, resourceUser1.userId, ApiRole.CanCreateUserInvitation.toString) + Then("We make a request v4.0.0") + val request400 = (v4_0_0_Request / "banks" / testBankId1.value / "user-invitation").POST <@(user1) + val postJson = SwaggerDefinitionsJSON.userInvitationPostJsonV400 + val response400 = makePostRequest(request400, write(postJson)) + Then("We get successful response") + response400.code should equal(201) + val userInvitation = response400.body.extract[UserInvitationJsonV400] + + When("We add required entitlement") + Entitlement.entitlement.vend.addEntitlement(testBankId1.value, resourceUser1.userId, ApiRole.CanGetUserInvitation.toString) + Then(s"We make a request $ApiEndpoint4") + val request = (v4_0_0_Request / "banks" / testBankId1.value / "user-invitations").GET <@ (user1) + val response = makeGetRequest(request) + Then("We get successful response") + response.code should equal(200) + val userInvitations = response.body.extract[UserInvitationsJsonV400] + userInvitations.user_invitations.exists(i => i.email == userInvitation.email) should equal(true) + + + val invitation: UserInvitation = UserInvitationProvider.userInvitationProvider.vend.getUserInvitations(testBankId1) + .getOrElse(Nil) + .filter(i => i.email == userInvitation.email) + .head + + val b = UserInvitationAtBrowser() + val pageUrl = (baseRequest / "user-invitation" <