mirror of
https://github.com/OpenBankProject/OBP-API.git
synced 2026-02-06 09:26:53 +00:00
feature/Remove Get API Key feature from code and delegate to OBP-Portal
This commit is contained in:
parent
0db2af0bd2
commit
45538d0393
@ -26,56 +26,57 @@ These properties can be:
|
|||||||
13. `webui_developer_user_invitation_email_text`
|
13. `webui_developer_user_invitation_email_text`
|
||||||
14. `webui_direct_login_documentation_url`
|
14. `webui_direct_login_documentation_url`
|
||||||
15. `webui_dummy_user_logins`
|
15. `webui_dummy_user_logins`
|
||||||
16. `webui_faq_data_text`
|
16. `webui_external_consumer_registration_url`
|
||||||
17. `webui_faq_email`
|
17. `webui_faq_data_text`
|
||||||
18. `webui_faq_url`
|
18. `webui_faq_email`
|
||||||
19. `webui_favicon_link_url`
|
19. `webui_faq_url`
|
||||||
20. `webui_featured_sdks_external_link`
|
20. `webui_favicon_link_url`
|
||||||
21. `webui_footer2_logo_left_url`
|
21. `webui_featured_sdks_external_link`
|
||||||
22. `webui_footer2_middle_text`
|
22. `webui_footer2_logo_left_url`
|
||||||
23. `webui_get_started_text`
|
23. `webui_footer2_middle_text`
|
||||||
24. `webui_header_logo_left_url`
|
24. `webui_get_started_text`
|
||||||
25. `webui_header_logo_right_url`
|
25. `webui_header_logo_left_url`
|
||||||
26. `webui_index_page_about_section_background_image_url`
|
26. `webui_header_logo_right_url`
|
||||||
27. `webui_index_page_about_section_text`
|
27. `webui_index_page_about_section_background_image_url`
|
||||||
28. `webui_legal_notice_html_text`
|
28. `webui_index_page_about_section_text`
|
||||||
29. `webui_login_button_text`
|
29. `webui_legal_notice_html_text`
|
||||||
30. `webui_login_page_instruction_title`
|
30. `webui_login_button_text`
|
||||||
31. `webui_login_page_special_instructions`
|
31. `webui_login_page_instruction_title`
|
||||||
32. `webui_main_faq_external_link`
|
32. `webui_login_page_special_instructions`
|
||||||
33. `webui_main_partners`
|
33. `webui_main_faq_external_link`
|
||||||
34. `webui_main_style_sheet`
|
34. `webui_main_partners`
|
||||||
35. `webui_oauth_1_documentation_url`
|
35. `webui_main_style_sheet`
|
||||||
36. `webui_oauth_2_documentation_url`
|
36. `webui_oauth_1_documentation_url`
|
||||||
37. `webui_obp_cli_url`
|
37. `webui_oauth_2_documentation_url`
|
||||||
38. `webui_override_style_sheet`
|
38. `webui_obp_cli_url`
|
||||||
39. `webui_page_title_prefix`
|
39. `webui_override_style_sheet`
|
||||||
40. `webui_post_consumer_registration_more_info_text`
|
40. `webui_page_title_prefix`
|
||||||
41. `webui_post_consumer_registration_more_info_url`
|
41. `webui_post_consumer_registration_more_info_text`
|
||||||
42. `webui_post_consumer_registration_submit_button_value`
|
42. `webui_post_consumer_registration_more_info_url`
|
||||||
43. `webui_post_user_invitation_submit_button_value`
|
43. `webui_post_consumer_registration_submit_button_value`
|
||||||
44. `webui_post_user_invitation_terms_and_conditions_checkbox_value`
|
44. `webui_post_user_invitation_submit_button_value`
|
||||||
45. `webui_privacy_policy`
|
45. `webui_post_user_invitation_terms_and_conditions_checkbox_value`
|
||||||
46. `webui_privacy_policy_url`
|
46. `webui_privacy_policy`
|
||||||
47. `webui_sandbox_introduction`
|
47. `webui_privacy_policy_url`
|
||||||
48. `webui_sdks_url`
|
48. `webui_sandbox_introduction`
|
||||||
49. `webui_show_dummy_user_tokens`
|
49. `webui_sdks_url`
|
||||||
50. `webui_signup_body_password_repeat_text`
|
50. `webui_show_dummy_user_tokens`
|
||||||
51. `webui_signup_form_submit_button_value`
|
51. `webui_signup_body_password_repeat_text`
|
||||||
52. `webui_signup_form_title_text`
|
52. `webui_signup_form_submit_button_value`
|
||||||
53. `webui_social_handle`
|
53. `webui_signup_form_title_text`
|
||||||
54. `webui_social_logo_url`
|
54. `webui_social_handle`
|
||||||
55. `webui_social_title`
|
55. `webui_social_logo_url`
|
||||||
56. `webui_social_url`
|
56. `webui_social_title`
|
||||||
57. `webui_subscriptions_button_text`
|
57. `webui_social_url`
|
||||||
58. `webui_subscriptions_invitation_text`
|
58. `webui_subscriptions_button_text`
|
||||||
59. `webui_subscriptions_url`
|
59. `webui_subscriptions_invitation_text`
|
||||||
60. `webui_support_email`
|
60. `webui_subscriptions_url`
|
||||||
61. `webui_support_platform_url`
|
61. `webui_support_email`
|
||||||
62. `webui_terms_and_conditions`
|
62. `webui_support_platform_url`
|
||||||
63. `webui_top_text`
|
63. `webui_terms_and_conditions`
|
||||||
64. `webui_user_invitation_notice_text`
|
64. `webui_top_text`
|
||||||
65. `webui_vendor_support_html_url`
|
65. `webui_user_invitation_notice_text`
|
||||||
|
66. `webui_vendor_support_html_url`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -101,6 +102,7 @@ These properties can be:
|
|||||||
- `webui_api_explorer_url`
|
- `webui_api_explorer_url`
|
||||||
- `webui_api_manager_url`
|
- `webui_api_manager_url`
|
||||||
- `webui_direct_login_documentation_url`
|
- `webui_direct_login_documentation_url`
|
||||||
|
- `webui_external_consumer_registration_url`
|
||||||
- `webui_faq_url`
|
- `webui_faq_url`
|
||||||
- `webui_featured_sdks_external_link`
|
- `webui_featured_sdks_external_link`
|
||||||
- `webui_main_faq_external_link`
|
- `webui_main_faq_external_link`
|
||||||
@ -143,6 +145,7 @@ These properties can be:
|
|||||||
- `webui_user_invitation_notice_text`
|
- `webui_user_invitation_notice_text`
|
||||||
|
|
||||||
### Consumer Registration
|
### Consumer Registration
|
||||||
|
- `webui_external_consumer_registration_url` (defaults to `webui_api_explorer_url` + `/consumers/register`)
|
||||||
- `webui_post_consumer_registration_more_info_text`
|
- `webui_post_consumer_registration_more_info_text`
|
||||||
- `webui_post_consumer_registration_more_info_url`
|
- `webui_post_consumer_registration_more_info_url`
|
||||||
- `webui_post_consumer_registration_submit_button_value`
|
- `webui_post_consumer_registration_submit_button_value`
|
||||||
|
|||||||
@ -586,7 +586,7 @@ class Boot extends MdcLoggable {
|
|||||||
Menu.i("debug-webui") / "debug" / "debug-webui",
|
Menu.i("debug-webui") / "debug" / "debug-webui",
|
||||||
Menu.i("Consumer Admin") / "admin" / "consumers" >> Admin.loginFirst >> LocGroup("admin")
|
Menu.i("Consumer Admin") / "admin" / "consumers" >> Admin.loginFirst >> LocGroup("admin")
|
||||||
submenus(Consumer.menus : _*),
|
submenus(Consumer.menus : _*),
|
||||||
Menu("Consumer Registration", Helper.i18n("consumer.registration.nav.name")) / "consumer-registration" >> AuthUser.loginFirst,
|
|
||||||
Menu("Consent Screen", Helper.i18n("consent.screen")) / "consent-screen" >> AuthUser.loginFirst,
|
Menu("Consent Screen", Helper.i18n("consent.screen")) / "consent-screen" >> AuthUser.loginFirst,
|
||||||
Menu("Dummy user tokens", "Get Dummy user tokens") / "dummy-user-tokens" >> AuthUser.loginFirst,
|
Menu("Dummy user tokens", "Get Dummy user tokens") / "dummy-user-tokens" >> AuthUser.loginFirst,
|
||||||
|
|
||||||
|
|||||||
@ -144,11 +144,17 @@ object Glossary extends MdcLoggable {
|
|||||||
|
|
||||||
// Note: this doesn't get / use an OBP version
|
// Note: this doesn't get / use an OBP version
|
||||||
def getApiExplorerLink(title: String, operationId: String) : String = {
|
def getApiExplorerLink(title: String, operationId: String) : String = {
|
||||||
val apiExplorerPrefix = APIUtil.getPropsValue("webui_api_explorer_url", "")
|
val apiExplorerPrefix = APIUtil.getPropsValue("webui_api_explorer_url", "http://localhost:5174")
|
||||||
// Note: This is hardcoded for API Explorer II
|
// Note: This is hardcoded for API Explorer II
|
||||||
s"""<a href="$apiExplorerPrefix/operationid/$operationId">$title</a>"""
|
s"""<a href="$apiExplorerPrefix/operationid/$operationId">$title</a>"""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Consumer registration URL helper
|
||||||
|
def getConsumerRegistrationUrl(): String = {
|
||||||
|
val apiExplorerUrl = APIUtil.getPropsValue("webui_api_explorer_url", "http://localhost:5174")
|
||||||
|
s"$apiExplorerUrl/consumers/register"
|
||||||
|
}
|
||||||
|
|
||||||
glossaryItems += GlossaryItem(
|
glossaryItems += GlossaryItem(
|
||||||
title = "Cheat Sheet",
|
title = "Cheat Sheet",
|
||||||
description =
|
description =
|
||||||
@ -590,7 +596,7 @@ object Glossary extends MdcLoggable {
|
|||||||
|
|
|
|
||||||
|Both standard entities (e.g. financial products and bank accounts in the OBP standard) and dynamic entities and endpoints (created by you or your organisation) can exist at the Bank level.
|
|Both standard entities (e.g. financial products and bank accounts in the OBP standard) and dynamic entities and endpoints (created by you or your organisation) can exist at the Bank level.
|
||||||
|
|
|
|
||||||
|For example see [Bank/Space level Dynamic Entities](/?version=OBPv4.0.0&operation_id=OBPv4_0_0-createBankLevelDynamicEntity) and [Bank/Space level Dynamic Endpoints](http://localhost:8082/?version=OBPv4.0.0&operation_id=OBPv4_0_0-createBankLevelDynamicEndpoint)
|
|For example see [Bank/Space level Dynamic Entities](/?version=OBPv4.0.0&operation_id=OBPv4_0_0-createBankLevelDynamicEntity) and [Bank/Space level Dynamic Endpoints](http://localhost:5174/?version=OBPv4.0.0&operation_id=OBPv4_0_0-createBankLevelDynamicEndpoint)
|
||||||
|
|
|
|
||||||
|The Bank is important because many Roles can be granted at the Bank level. In this way, it's possible to create segregated or partitioned sets of endpoints and data structures in a single OBP instance.
|
|The Bank is important because many Roles can be granted at the Bank level. In this way, it's possible to create segregated or partitioned sets of endpoints and data structures in a single OBP instance.
|
||||||
|
|
|
|
||||||
@ -1091,7 +1097,7 @@ object Glossary extends MdcLoggable {
|
|||||||
|
|
|
|
||||||
|[Sign up]($getServerUrl/user_mgt/sign_up) or [login]($getServerUrl/user_mgt/login) as a developer.
|
|[Sign up]($getServerUrl/user_mgt/sign_up) or [login]($getServerUrl/user_mgt/login) as a developer.
|
||||||
|
|
|
|
||||||
|Register your App key [HERE]($getServerUrl/consumer-registration)
|
|Register your App key [HERE](${getConsumerRegistrationUrl()})
|
||||||
|
|
|
|
||||||
|Copy and paste the consumer key for step two below.
|
|Copy and paste the consumer key for step two below.
|
||||||
|
|
|
|
||||||
@ -1182,7 +1188,7 @@ object Glossary extends MdcLoggable {
|
|||||||
|
|
|
|
||||||
| consumer_key
|
| consumer_key
|
||||||
| The application identifier. Generated on OBP side via
|
| The application identifier. Generated on OBP side via
|
||||||
| $getServerUrl/consumer-registration endpoint.
|
| ${getConsumerRegistrationUrl()} endpoint.
|
||||||
|
|
|
|
||||||
|
|
|
|
||||||
| Each parameter MUST NOT appear more than once per request.
|
| Each parameter MUST NOT appear more than once per request.
|
||||||
@ -2147,7 +2153,7 @@ object Glossary extends MdcLoggable {
|
|||||||
|
|
|
|
||||||
|[Sign up]($getServerUrl/user_mgt/sign_up) or [login]($getServerUrl/user_mgt/login) as a developer
|
|[Sign up]($getServerUrl/user_mgt/sign_up) or [login]($getServerUrl/user_mgt/login) as a developer
|
||||||
|
|
|
|
||||||
|Register your App key [HERE]($getServerUrl/consumer-registration)
|
|Register your App key [HERE](${getConsumerRegistrationUrl()})
|
||||||
|
|
|
|
||||||
|Copy and paste the CLIENT ID (AKA CONSUMER KEY), CLIENT SECRET (AKA CONSUMER SECRET) and REDIRECT_URL for the subsequent steps below.
|
|Copy and paste the CLIENT ID (AKA CONSUMER KEY), CLIENT SECRET (AKA CONSUMER SECRET) and REDIRECT_URL for the subsequent steps below.
|
||||||
|
|
|
|
||||||
@ -2796,9 +2802,9 @@ object Glossary extends MdcLoggable {
|
|||||||
|
|
|
|
||||||
|[Sign up]($getServerUrl/user_mgt/sign_up) or [login]($getServerUrl/user_mgt/login) as a developer.
|
|[Sign up]($getServerUrl/user_mgt/sign_up) or [login]($getServerUrl/user_mgt/login) as a developer.
|
||||||
|
|
|
|
||||||
|Register your App / Consumer [HERE]($getServerUrl/consumer-registration)
|
|Register your App / Consumer [HERE](${getConsumerRegistrationUrl()})
|
||||||
|
|
|
|
||||||
|Be sure to enter your Client Certificate in the above form. To create the user.crt file see [HERE](https://fardog.io/blog/2017/12/30/client-side-certificate-authentication-with-nginx/)
|
|Be sure to enter your Client Certificate in the registration form. To create the user.crt file see [HERE](https://fardog.io/blog/2017/12/30/client-side-certificate-authentication-with-nginx/)
|
||||||
|
|
|
|
||||||
|
|
|
|
||||||
|## Authenticate
|
|## Authenticate
|
||||||
|
|||||||
@ -1,532 +0,0 @@
|
|||||||
/**
|
|
||||||
Open Bank Project - API
|
|
||||||
Copyright (C) 2011-2019, TESOBE GmbH.
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
Email: contact@tesobe.com
|
|
||||||
TESOBE GmbH.
|
|
||||||
Osloer Strasse 16/17
|
|
||||||
Berlin 13359, Germany
|
|
||||||
|
|
||||||
This product includes software developed at
|
|
||||||
TESOBE (http://www.tesobe.com/)
|
|
||||||
|
|
||||||
*/
|
|
||||||
package code.snippet
|
|
||||||
|
|
||||||
import java.util
|
|
||||||
import code.api.{Constant, DirectLogin}
|
|
||||||
import code.api.util.{APIUtil, ErrorMessages, KeycloakAdmin, X509, CommonsEmailWrapper}
|
|
||||||
import code.consumer.Consumers
|
|
||||||
import code.model.dataAccess.AuthUser
|
|
||||||
import code.model.{Consumer, _}
|
|
||||||
import code.util.Helper.{MdcLoggable, ObpS}
|
|
||||||
import code.util.HydraUtil
|
|
||||||
import code.webuiprops.MappedWebUiPropsProvider.getWebUiPropsValue
|
|
||||||
import net.liftweb.common.{Box, Failure, Full}
|
|
||||||
import net.liftweb.http.{RequestVar, S, SHtml}
|
|
||||||
import net.liftweb.util.Helpers._
|
|
||||||
import net.liftweb.util.{CssSel, FieldError, Helpers}
|
|
||||||
import org.apache.commons.lang3.StringUtils
|
|
||||||
import org.codehaus.jackson.map.ObjectMapper
|
|
||||||
|
|
||||||
import scala.collection.immutable.{List, ListMap}
|
|
||||||
import scala.collection.JavaConverters._
|
|
||||||
import scala.xml.{Text, Unparsed}
|
|
||||||
|
|
||||||
class ConsumerRegistration extends MdcLoggable {
|
|
||||||
|
|
||||||
private object nameVar extends RequestVar("")
|
|
||||||
private object redirectionURLVar extends RequestVar("")
|
|
||||||
private object requestUriVar extends RequestVar("")
|
|
||||||
private object authenticationURLVar extends RequestVar("")
|
|
||||||
private object appTypeVar extends RequestVar[AppType](AppType.Confidential)
|
|
||||||
private object descriptionVar extends RequestVar("")
|
|
||||||
private object devEmailVar extends RequestVar("")
|
|
||||||
private object companyVar extends RequestVar("")
|
|
||||||
private object appType extends RequestVar("Public")
|
|
||||||
private object clientCertificateVar extends RequestVar("")
|
|
||||||
private object signingAlgVar extends RequestVar("")
|
|
||||||
private object oidcCheckboxVar extends RequestVar(false)
|
|
||||||
private object jwksUriVar extends RequestVar("")
|
|
||||||
private object jwksVar extends RequestVar("")
|
|
||||||
private object submitButtonDefenseFlag extends RequestVar("")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Can be used to show link to an online form to collect more information about the App / Startup
|
|
||||||
val registrationMoreInfoUrl = getWebUiPropsValue("webui_post_consumer_registration_more_info_url", "")
|
|
||||||
|
|
||||||
val registrationConsumerButtonValue = getWebUiPropsValue("webui_post_consumer_registration_submit_button_value", "Register consumer")
|
|
||||||
|
|
||||||
val registrationMoreInfoText : String = registrationMoreInfoUrl match {
|
|
||||||
case "" => ""
|
|
||||||
case _ => getWebUiPropsValue("webui_post_consumer_registration_more_info_text", "Please tell us more your Application and / or Startup using this link.")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def registerForm = {
|
|
||||||
|
|
||||||
val appTypes = List((AppType.Confidential.toString, AppType.Confidential.toString), (AppType.Public.toString, AppType.Public.toString))
|
|
||||||
val signingAlgs = List(
|
|
||||||
"ES256", "ES384", "ES512",
|
|
||||||
//Hydra support alg: RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384 and ES512
|
|
||||||
"RS256", "RS384", "RS512", "PS256", "PS384", "PS512"
|
|
||||||
).map(it => it -> it)
|
|
||||||
|
|
||||||
def submitButtonDefense: Unit = {
|
|
||||||
submitButtonDefenseFlag("true")
|
|
||||||
}
|
|
||||||
|
|
||||||
def registerWithoutWarnings =
|
|
||||||
register &
|
|
||||||
"#register-consumer-errors" #> ""
|
|
||||||
|
|
||||||
def displayAppType: Boolean = APIUtil.getPropsAsBoolValue("consumer_registration.display_app_type", true)
|
|
||||||
|
|
||||||
def register = {
|
|
||||||
"form" #> {
|
|
||||||
"#app-type-div [style] " #> {if(displayAppType) "display: block;" else "display: none"} &
|
|
||||||
"#appType" #> SHtml.select(appTypes, Box!! appType.is, appType(_)) &
|
|
||||||
"#appName" #> SHtml.text(nameVar.is, nameVar(_)) &
|
|
||||||
"#redirect_url_label *" #> {
|
|
||||||
if (HydraUtil.integrateWithHydra) "Redirect URL" else "Redirect URL (Optional)"
|
|
||||||
} &
|
|
||||||
"#appRedirectUrl" #> SHtml.text(redirectionURLVar, redirectionURLVar(_)) &
|
|
||||||
"#appDev" #> SHtml.text(devEmailVar, devEmailVar(_)) &
|
|
||||||
"#company" #> SHtml.text(companyVar, companyVar(_)) &
|
|
||||||
"#appDesc" #> SHtml.textarea(descriptionVar, descriptionVar (_)) &
|
|
||||||
"#appUserAuthenticationUrl" #> SHtml.text(authenticationURLVar.is, authenticationURLVar(_)) & {
|
|
||||||
if(HydraUtil.integrateWithHydra) {
|
|
||||||
"#app-client_certificate" #> SHtml.textarea(clientCertificateVar, clientCertificateVar (_))&
|
|
||||||
"#app-request_uri" #> SHtml.text(requestUriVar, requestUriVar(_)) &
|
|
||||||
"#oidc_checkbox" #> SHtml.checkbox(oidcCheckboxVar, oidcCheckboxVar(_)) &
|
|
||||||
"#app-signing_alg" #> SHtml.select(signingAlgs, Box!! signingAlgVar.is, signingAlgVar(_)) &
|
|
||||||
"#app-jwks_uri" #> SHtml.text(jwksUriVar, jwksUriVar(_)) &
|
|
||||||
"#app-jwks" #> SHtml.textarea(jwksVar, jwksVar(_))
|
|
||||||
} else {
|
|
||||||
".oauth2_fields" #> ""
|
|
||||||
}
|
|
||||||
} &
|
|
||||||
"type=submit" #> SHtml.submit(s"$registrationConsumerButtonValue", () => submitButtonDefense)
|
|
||||||
} &
|
|
||||||
"#register-consumer-success" #> ""
|
|
||||||
}
|
|
||||||
|
|
||||||
def showResults(consumer : Consumer) = {
|
|
||||||
val urlOAuthEndpoint = Constant.HostName + "/oauth/initiate"
|
|
||||||
val urlDirectLoginEndpoint = Constant.HostName + "/my/logins/direct"
|
|
||||||
val jwksUri = jwksUriVar.is
|
|
||||||
val jwks = jwksVar.is
|
|
||||||
val jwsAlg = signingAlgVar.is
|
|
||||||
var jwkPrivateKey: String = s"Please change this value to ${if(StringUtils.isNotBlank(jwksUri)) "jwks_uri" else "jwks"} corresponding private key"
|
|
||||||
// In case we use Hydra ORY as Identity Provider we create corresponding client at Hydra side a well
|
|
||||||
if(HydraUtil.integrateWithHydra) {
|
|
||||||
HydraUtil.createHydraClient(consumer, oAuth2Client => {
|
|
||||||
val signingAlg = signingAlgVar.is
|
|
||||||
|
|
||||||
if(oidcCheckboxVar.is == false) {
|
|
||||||
// TODO Set token_endpoint_auth_method in accordance to the Consumer.AppType value
|
|
||||||
// Consumer.AppType = Confidential => client_secret_post
|
|
||||||
// Consumer.AppType = Public => private_key_jwt
|
|
||||||
// Consumer.AppType = Unknown => private_key_jwt
|
|
||||||
oAuth2Client.setTokenEndpointAuthMethod(HydraUtil.hydraTokenEndpointAuthMethod)
|
|
||||||
} else {
|
|
||||||
oAuth2Client.setTokenEndpointAuthMethod(HydraUtil.clientSecretPost)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
oAuth2Client.setTokenEndpointAuthSigningAlg(signingAlg)
|
|
||||||
oAuth2Client.setRequestObjectSigningAlg(signingAlg)
|
|
||||||
|
|
||||||
def toJson(jwksJson: String) =
|
|
||||||
new ObjectMapper().readValue(jwksJson, classOf[util.Map[String, _]])
|
|
||||||
|
|
||||||
val requestUri = requestUriVar.is
|
|
||||||
if(StringUtils.isAllBlank(jwksUri, jwks)) {
|
|
||||||
val(privateKey, publicKey) = HydraUtil.createJwk(signingAlg)
|
|
||||||
jwkPrivateKey = privateKey
|
|
||||||
val jwksJson = s"""{"keys": [$publicKey]}"""
|
|
||||||
val jwksMap = toJson(jwksJson)
|
|
||||||
oAuth2Client.setJwks(jwksMap)
|
|
||||||
} else if(StringUtils.isNotBlank(jwks)){
|
|
||||||
val jwksMap = toJson(jwks)
|
|
||||||
oAuth2Client.setJwks(jwksMap)
|
|
||||||
} else if(StringUtils.isNotBlank(jwksUri)){
|
|
||||||
oAuth2Client.setJwksUri(jwksUri)
|
|
||||||
}
|
|
||||||
|
|
||||||
if(StringUtils.isNotBlank(requestUri)) {
|
|
||||||
oAuth2Client.setRequestUris(List(requestUri).asJava)
|
|
||||||
}
|
|
||||||
oAuth2Client
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// In case we use Keycloak as Identity Provider we create corresponding client at Keycloak side a well
|
|
||||||
if(KeycloakAdmin.integrateWithKeycloak) KeycloakAdmin.createKeycloakConsumer(consumer)
|
|
||||||
|
|
||||||
val registerConsumerSuccessMessageWebpage = getWebUiPropsValue(
|
|
||||||
"webui_register_consumer_success_message_webpage",
|
|
||||||
"Thanks for registering your consumer with the Open Bank Project API! Here is your developer information. Please save it in a secure location.")
|
|
||||||
//thanks for registering, here's your key, etc.
|
|
||||||
"#register-consumer-success-message *" #> registerConsumerSuccessMessageWebpage &
|
|
||||||
"#app-consumer_id *" #> consumer.consumerId.get &
|
|
||||||
"#app-name *" #> consumer.name.get &
|
|
||||||
"#app-redirect-url *" #> consumer.redirectURL &
|
|
||||||
"#app-user-authentication-url *" #> consumer.userAuthenticationURL &
|
|
||||||
"#app-type *" #> consumer.appType.get &
|
|
||||||
"#app-description *" #> consumer.description.get &
|
|
||||||
"#client_certificate *" #> {
|
|
||||||
if (StringUtils.isBlank(consumer.clientCertificate.get)) Text("None")
|
|
||||||
else Unparsed(consumer.clientCertificate.get)
|
|
||||||
} &
|
|
||||||
"#app-developer *" #> consumer.developerEmail.get &
|
|
||||||
"#auth-key *" #> consumer.key.get &
|
|
||||||
"#secret-key *" #> consumer.secret.get &
|
|
||||||
"#oauth-endpoint a *" #> urlOAuthEndpoint &
|
|
||||||
"#oauth-endpoint a [href]" #> urlOAuthEndpoint &
|
|
||||||
"#directlogin-endpoint a *" #> urlDirectLoginEndpoint &
|
|
||||||
"#directlogin-endpoint a [href]" #> urlDirectLoginEndpoint &
|
|
||||||
"#post-consumer-registration-more-info-link a *" #> registrationMoreInfoText &
|
|
||||||
"#post-consumer-registration-more-info-link a [href]" #> registrationMoreInfoUrl & {
|
|
||||||
if(HydraUtil.integrateWithHydra) {
|
|
||||||
"#hydra-client-info-title *" #>"OAuth2: " &
|
|
||||||
"#admin_url *" #> HydraUtil.hydraAdminUrl &
|
|
||||||
"#client_id *" #> {consumer.key.get} &
|
|
||||||
"#redirect_uri *" #> consumer.redirectURL.get &
|
|
||||||
{
|
|
||||||
val requestUri = requestUriVar.is
|
|
||||||
if(StringUtils.isBlank(requestUri)) "#oauth2_request_uri *" #> ""
|
|
||||||
else "#request_uri_value" #> requestUri
|
|
||||||
} &
|
|
||||||
"#client_scope" #> {
|
|
||||||
val lastIndex = HydraUtil.hydraConsents.length - 1
|
|
||||||
HydraUtil.hydraConsents.zipWithIndex.map { kv =>
|
|
||||||
".client-scope-value *" #> {
|
|
||||||
val (scope, index) = kv
|
|
||||||
if(index == lastIndex) {
|
|
||||||
scope
|
|
||||||
} else {
|
|
||||||
s"$scope,\\"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} &
|
|
||||||
"#client_jws_alg" #> Unparsed(jwsAlg) &
|
|
||||||
"#jwk_private_key" #> Unparsed(jwkPrivateKey)
|
|
||||||
} else {
|
|
||||||
"#hydra-client-info-title *" #> "" &
|
|
||||||
"#hydra-client-info *" #> ""
|
|
||||||
}
|
|
||||||
} &
|
|
||||||
"#register-consumer-input" #> "" & {
|
|
||||||
val hasDummyUsers = getWebUiPropsValue("webui_dummy_user_logins", "").nonEmpty
|
|
||||||
val isShowDummyUserTokens = getWebUiPropsValue("webui_show_dummy_user_tokens", "false").toBoolean
|
|
||||||
if(hasDummyUsers && isShowDummyUserTokens) {
|
|
||||||
"#create-directlogin a [href]" #> s"dummy-user-tokens?consumer_key=${consumer.key.get}"
|
|
||||||
} else {
|
|
||||||
"#dummy-user-tokens" #> ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def showRegistrationResults(result : Consumer) = {
|
|
||||||
|
|
||||||
notifyRegistrationOccurred(result)
|
|
||||||
sendEmailToDeveloper(result)
|
|
||||||
|
|
||||||
showResults(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
def showErrors(errors : List[FieldError]) = {
|
|
||||||
val errorsString = errors.map(_.msg.toString)
|
|
||||||
errorsString.map(errorMessage => S.error("register-consumer-errors", errorMessage))
|
|
||||||
register &
|
|
||||||
"#register-consumer-errors *" #> {
|
|
||||||
".error *" #>
|
|
||||||
errorsString.map({ e=>
|
|
||||||
".errorContent *" #> e
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def showUnknownErrors(errors : List[String]) = {
|
|
||||||
errors.map(errorMessage => S.error("register-consumer-errors", errorMessage))
|
|
||||||
register &
|
|
||||||
"#register-consumer-errors *" #> {
|
|
||||||
".error *" #>
|
|
||||||
errors.map({ e=>
|
|
||||||
".errorContent *" #> e
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
def showValidationErrors(errors : List[String]): CssSel = {
|
|
||||||
errors.filter(errorMessage => (errorMessage.contains("name") || errorMessage.contains("Name")) ).map(errorMessage => S.error("consumer-registration-app-name-error", errorMessage))
|
|
||||||
errors.filter(errorMessage => (errorMessage.contains("description") || errorMessage.contains("Description"))).map(errorMessage => S.error("consumer-registration-app-description-error", errorMessage))
|
|
||||||
errors.filter(errorMessage => (errorMessage.contains("email")|| errorMessage.contains("Email"))).map(errorMessage => S.error("consumer-registration-app-developer-error", errorMessage))
|
|
||||||
errors.filter(errorMessage => (errorMessage.contains("redirect")|| errorMessage.contains("Redirect"))).map(errorMessage => S.error("consumer-registration-app-redirect-url-error", errorMessage))
|
|
||||||
errors.filter(errorMessage => errorMessage.contains("request_uri")).map(errorMessage => S.error("consumer-registration-app-request_uri-error", errorMessage))
|
|
||||||
errors.filter(errorMessage => StringUtils.containsAny(errorMessage, "signing_alg", "jwks_uri", "jwks"))
|
|
||||||
.map(errorMessage => S.error("consumer-registration-app-signing_jwks-error", errorMessage))
|
|
||||||
errors.filter(errorMessage => errorMessage.contains("certificate")).map(errorMessage => S.error("consumer-registration-app-client_certificate-error", errorMessage))
|
|
||||||
//Here show not field related errors to the general part.
|
|
||||||
val unknownErrors: Seq[String] = errors
|
|
||||||
.filterNot(errorMessage => (errorMessage.contains("name") || errorMessage.contains("Name")))
|
|
||||||
.filterNot(errorMessage => (errorMessage.contains("description") || errorMessage.contains("Description")))
|
|
||||||
.filterNot(errorMessage => (errorMessage.contains("email") || errorMessage.contains("Email")))
|
|
||||||
.filterNot(errorMessage => (errorMessage.contains("redirect") || errorMessage.contains("Redirect")))
|
|
||||||
unknownErrors.map(errorMessage => S.error("register-consumer-errors", errorMessage))
|
|
||||||
register &
|
|
||||||
"#register-consumer-errors *" #> {
|
|
||||||
".error *" #>
|
|
||||||
unknownErrors.map({ e=>
|
|
||||||
".errorContent *" #> e
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO this should be used somewhere else, it is check the empty of description for the hack attack from GUI.
|
|
||||||
def showErrorsForDescription (descriptionError : String) = {
|
|
||||||
S.error("register-consumer-errors", descriptionError)
|
|
||||||
register &
|
|
||||||
"#register-consumer-errors *" #> {
|
|
||||||
".error *" #>
|
|
||||||
List(descriptionError).map({ e=>
|
|
||||||
".errorContent *" #> e
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def analyseResult = {
|
|
||||||
|
|
||||||
def withNameOpt(s: String): Option[AppType] = Some(AppType.valueOf(s))
|
|
||||||
|
|
||||||
val clientCertificate = clientCertificateVar.is
|
|
||||||
val requestUri = requestUriVar.is
|
|
||||||
val signingAlg = signingAlgVar.is
|
|
||||||
val jwksUri = jwksUriVar.is
|
|
||||||
val jwks = jwksVar.is
|
|
||||||
|
|
||||||
val appTypeSelected = withNameOpt(appType.is)
|
|
||||||
logger.debug("appTypeSelected: " + appTypeSelected)
|
|
||||||
nameVar.set(nameVar.is)
|
|
||||||
appTypeVar.set(appTypeSelected.get)
|
|
||||||
descriptionVar.set(descriptionVar.is)
|
|
||||||
devEmailVar.set(devEmailVar.is)
|
|
||||||
companyVar.set(companyVar.is)
|
|
||||||
redirectionURLVar.set(redirectionURLVar.is)
|
|
||||||
|
|
||||||
requestUriVar.set(requestUri)
|
|
||||||
clientCertificateVar.set(clientCertificate)
|
|
||||||
signingAlgVar.set(signingAlg)
|
|
||||||
jwksUriVar.set(jwksUri)
|
|
||||||
jwksVar.set(jwks)
|
|
||||||
|
|
||||||
val oauth2ParamError: CssSel = if(HydraUtil.integrateWithHydra) {
|
|
||||||
if(StringUtils.isBlank(redirectionURLVar.is) || Consumer.redirectURLRegex.findFirstIn(redirectionURLVar.is).isEmpty) {
|
|
||||||
showErrorsForDescription("The 'Redirect URL' should be a valid url !")
|
|
||||||
} else if(StringUtils.isNotBlank(requestUri) && !requestUri.matches("""^https?://(www.)?\S+?(:\d{2,6})?\S*$""")) {
|
|
||||||
showErrorsForDescription("The 'request_uri' should be a valid url !")
|
|
||||||
} else if(StringUtils.isNotBlank(jwksUri) && !jwksUri.matches("""^https?://(www.)?\S+?(:\d{2,6})?\S*$""")) {
|
|
||||||
showErrorsForDescription("The 'jwks_uri' should be a valid url !")
|
|
||||||
} else if(StringUtils.isBlank(signingAlg)) {
|
|
||||||
showErrorsForDescription("The 'signing_alg' should not be empty!")
|
|
||||||
} else if(StringUtils.isNoneBlank(jwksUri, jwks)) {
|
|
||||||
showErrorsForDescription("The 'jwks_uri' and 'jwks' should not have value at the same time!")
|
|
||||||
} else if (StringUtils.isNotBlank(clientCertificate) && X509.validate(clientCertificate) != Full(true)) {
|
|
||||||
showErrorsForDescription("The 'client certificate' should be a valid certificate, pleas copy whole crt file content !")
|
|
||||||
} else null
|
|
||||||
} else null
|
|
||||||
|
|
||||||
if(oauth2ParamError != null) {
|
|
||||||
oauth2ParamError
|
|
||||||
} else if(submitButtonDefenseFlag.isEmpty) {
|
|
||||||
showErrorsForDescription("The 'Register' button random name has been modified !")
|
|
||||||
} else{
|
|
||||||
val appType =
|
|
||||||
if(displayAppType) appTypeSelected
|
|
||||||
else Some(AppType.Unknown) // If Application Type is hidden from Consumer registration it defaults to Unknown
|
|
||||||
val consumer = Consumers.consumers.vend.createConsumer(
|
|
||||||
Some(Helpers.randomString(40).toLowerCase),
|
|
||||||
Some(Helpers.randomString(40).toLowerCase),
|
|
||||||
Some(true),
|
|
||||||
Some(nameVar.is),
|
|
||||||
appType,
|
|
||||||
Some(descriptionVar.is),
|
|
||||||
Some(devEmailVar.is),
|
|
||||||
Some(redirectionURLVar.is),
|
|
||||||
Some(AuthUser.getCurrentResourceUserUserId),
|
|
||||||
Some(clientCertificate),
|
|
||||||
company = Some(companyVar.is),
|
|
||||||
None
|
|
||||||
)
|
|
||||||
logger.debug("consumer: " + consumer)
|
|
||||||
consumer match {
|
|
||||||
case Full(x) =>
|
|
||||||
showRegistrationResults(x)
|
|
||||||
case Failure(msg, _, _) => showValidationErrors(msg.split(";").toList)
|
|
||||||
case _ => showUnknownErrors(List(ErrorMessages.UnknownError))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(S.post_?) analyseResult
|
|
||||||
else registerWithoutWarnings
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
def sendEmailToDeveloper(registered : Consumer) = {
|
|
||||||
import net.liftweb.util.Mailer
|
|
||||||
import net.liftweb.util.Mailer._
|
|
||||||
|
|
||||||
val mailSent = for {
|
|
||||||
send : String <- APIUtil.getPropsValue("mail.api.consumer.registered.notification.send") if send.equalsIgnoreCase("true")
|
|
||||||
from <- APIUtil.getPropsValue("mail.api.consumer.registered.sender.address") ?~ "Could not send mail: Missing props param for 'from'"
|
|
||||||
} yield {
|
|
||||||
|
|
||||||
// Only send consumer key / secret by email if we explicitly want that.
|
|
||||||
val sendSensitive : Boolean = APIUtil.getPropsAsBoolValue("mail.api.consumer.registered.notification.send.sensistive", false)
|
|
||||||
val consumerKeyOrMessage : String = if (sendSensitive) registered.key.get else "Configured so sensitive data is not sent by email (Consumer Key)."
|
|
||||||
val consumerSecretOrMessage : String = if (sendSensitive) registered.secret.get else "Configured so sensitive data is not sent by email (Consumer Secret)."
|
|
||||||
|
|
||||||
val thisApiInstance = Constant.HostName
|
|
||||||
val apiExplorerUrl = getWebUiPropsValue("webui_api_explorer_url", "unknown host")
|
|
||||||
val directLoginDocumentationUrl = getWebUiPropsValue("webui_direct_login_documentation_url", apiExplorerUrl + "/glossary#Direct-Login")
|
|
||||||
val oauthDocumentationUrl = getWebUiPropsValue("webui_oauth_1_documentation_url", apiExplorerUrl + "/glossary#OAuth-1.0a")
|
|
||||||
val oauthEndpointUrl = thisApiInstance + "/oauth/initiate"
|
|
||||||
|
|
||||||
val directLoginEndpointUrl = thisApiInstance + "/my/logins/direct"
|
|
||||||
val registrationMessage = s"Thank you for registering a Consumer on $thisApiInstance. \n" +
|
|
||||||
s"Email: ${registered.developerEmail.get} \n" +
|
|
||||||
s"App name: ${registered.name.get} \n" +
|
|
||||||
s"App type: ${registered.appType.get} \n" +
|
|
||||||
s"App description: ${registered.description.get} \n" +
|
|
||||||
s"App Redirect Url : ${registered.redirectURL} \n" +
|
|
||||||
s"Consumer Key: ${consumerKeyOrMessage} \n" +
|
|
||||||
s"Consumer Secret : ${consumerSecretOrMessage} \n" +
|
|
||||||
s"OAuth Endpoint: ${oauthEndpointUrl} \n" +
|
|
||||||
s"OAuth Documentation: ${directLoginDocumentationUrl} \n" +
|
|
||||||
s"Direct Login Endpoint: ${directLoginEndpointUrl} \n" +
|
|
||||||
s"Direct Login Documentation: ${oauthDocumentationUrl} \n" +
|
|
||||||
s"$registrationMoreInfoText: $registrationMoreInfoUrl"
|
|
||||||
|
|
||||||
val webuiRegisterConsumerSuccessMssageEmail : String = getWebUiPropsValue(
|
|
||||||
"webui_register_consumer_success_message_email",
|
|
||||||
"Thank you for registering to use the Open Bank Project API.")
|
|
||||||
|
|
||||||
val emailContent = CommonsEmailWrapper.EmailContent(
|
|
||||||
from = from,
|
|
||||||
to = List(registered.developerEmail.get),
|
|
||||||
subject = webuiRegisterConsumerSuccessMssageEmail,
|
|
||||||
textContent = Some(registrationMessage)
|
|
||||||
)
|
|
||||||
|
|
||||||
//this is an async call
|
|
||||||
CommonsEmailWrapper.sendTextEmail(emailContent)
|
|
||||||
}
|
|
||||||
|
|
||||||
if(mailSent.isEmpty)
|
|
||||||
this.logger.warn(s"Sending email with API consumer registration data is omitted: $mailSent")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is to let the system administrators / API managers know that someone has registered a consumer key.
|
|
||||||
def notifyRegistrationOccurred(registered : Consumer) = {
|
|
||||||
|
|
||||||
val mailSent = for {
|
|
||||||
// e.g mail.api.consumer.registered.sender.address=no-reply@example.com
|
|
||||||
from <- APIUtil.getPropsValue("mail.api.consumer.registered.sender.address") ?~ "Could not send mail: Missing props param for 'from'"
|
|
||||||
// no spaces, comma separated e.g. mail.api.consumer.registered.notification.addresses=notify@example.com,notify2@example.com,notify3@example.com
|
|
||||||
toAddressesString <- APIUtil.getPropsValue("mail.api.consumer.registered.notification.addresses") ?~ "Could not send mail: Missing props param for 'to'"
|
|
||||||
} yield {
|
|
||||||
|
|
||||||
val thisApiInstance = Constant.HostName
|
|
||||||
val registrationMessage = s"New user signed up for API keys on $thisApiInstance. \n" +
|
|
||||||
s"Email: ${registered.developerEmail.get} \n" +
|
|
||||||
s"App name: ${registered.name.get} \n" +
|
|
||||||
s"App type: ${registered.appType.get} \n" +
|
|
||||||
s"App description: ${registered.description.get} \n" +
|
|
||||||
s"App Redirect Url : ${registered.redirectURL}"
|
|
||||||
|
|
||||||
//technically doesn't work for all valid email addresses so this will mess up if someone tries to send emails to "foo,bar"@example.com
|
|
||||||
val to = toAddressesString.split(",").toList
|
|
||||||
|
|
||||||
val emailContent = CommonsEmailWrapper.EmailContent(
|
|
||||||
from = from,
|
|
||||||
to = to,
|
|
||||||
subject = s"New API user registered on $thisApiInstance",
|
|
||||||
textContent = Some(registrationMessage)
|
|
||||||
)
|
|
||||||
|
|
||||||
//this is an async call
|
|
||||||
CommonsEmailWrapper.sendTextEmail(emailContent)
|
|
||||||
}
|
|
||||||
|
|
||||||
if(mailSent.isEmpty)
|
|
||||||
this.logger.warn(s"API consumer registration failed: $mailSent")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
def showDummyCustomerTokens(): CssSel = {
|
|
||||||
val consumerKeyBox = ObpS.param("consumer_key")
|
|
||||||
// The following will check the login user and the user from the consumerkey. we do not want to share consumerkey to others.
|
|
||||||
val loginUserId = AuthUser.getCurrentUser.map(_.userId).openOr("")
|
|
||||||
val userCreatedByUserId = consumerKeyBox.map(Consumers.consumers.vend.getConsumerByConsumerKey(_)).flatten.map(_.createdByUserId.get).openOr("")
|
|
||||||
if(!loginUserId.equals(userCreatedByUserId))
|
|
||||||
return "#dummy-user-tokens ^" #> "The consumer key in the URL is not created by the current login user, please create consumer for this user first!"
|
|
||||||
|
|
||||||
val dummyUsersInfo = getWebUiPropsValue("webui_dummy_user_logins", "")
|
|
||||||
val isShowDummyUserTokens = getWebUiPropsValue("webui_show_dummy_user_tokens", "false").toBoolean
|
|
||||||
// (username, password) -> authHeader
|
|
||||||
val userNameToAuthInfo: Map[(String, String), String] = (isShowDummyUserTokens, consumerKeyBox, dummyUsersInfo) match {
|
|
||||||
case(true, Full(consumerKey), dummyCustomers) if dummyCustomers.nonEmpty => {
|
|
||||||
val regex = """(?s)\{.*?"user_name"\s*:\s*"(.+?)".+?"password"\s*:\s*"(.+?)".+?\}""".r
|
|
||||||
val matcher = regex.pattern.matcher(dummyCustomers)
|
|
||||||
var tokens = ListMap[(String, String), String]()
|
|
||||||
while(matcher.find()) {
|
|
||||||
val userName = matcher.group(1)
|
|
||||||
val password = matcher.group(2)
|
|
||||||
val (code, token, userId) = DirectLogin.createToken(Map(("username", userName), ("password", password), ("consumer_key", consumerKey)))
|
|
||||||
val authHeader = code match {
|
|
||||||
case 200 => (userName, password) -> s"""Authorization: DirectLogin token="$token""""
|
|
||||||
case _ => (userName, password) -> "username or password is invalid, generate token fail"
|
|
||||||
}
|
|
||||||
tokens += authHeader
|
|
||||||
}
|
|
||||||
tokens
|
|
||||||
}
|
|
||||||
case _ => Map.empty[(String, String), String]
|
|
||||||
}
|
|
||||||
|
|
||||||
val elements = userNameToAuthInfo.map{ pair =>
|
|
||||||
val ((userName, password), authHeader) = pair
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12 col-sm-4">
|
|
||||||
username: <br/>
|
|
||||||
{userName} <br/>
|
|
||||||
password: <br/>
|
|
||||||
{password}
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-12 col-sm-8">
|
|
||||||
{authHeader}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
"#dummy-user-tokens ^" #> elements
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -48,7 +48,7 @@ class Login {
|
|||||||
} else {
|
} else {
|
||||||
".logout [href]" #> {
|
".logout [href]" #> {
|
||||||
if(APIUtil.getPropsAsBoolValue("sso.enabled", false)) {
|
if(APIUtil.getPropsAsBoolValue("sso.enabled", false)) {
|
||||||
val apiExplorerUrl = getWebUiPropsValue("webui_api_explorer_url", "http://localhost:8082")
|
val apiExplorerUrl = getWebUiPropsValue("webui_api_explorer_url", "http://localhost:5174")
|
||||||
apiExplorerUrl + "/obp-api-logout"
|
apiExplorerUrl + "/obp-api-logout"
|
||||||
} else {
|
} else {
|
||||||
AuthUser.logoutPath.foldLeft("")(_ + "/" + _)
|
AuthUser.logoutPath.foldLeft("")(_ + "/" + _)
|
||||||
|
|||||||
@ -235,7 +235,7 @@ class WebUI extends MdcLoggable{
|
|||||||
val tags = S.attr("tags") openOr ""
|
val tags = S.attr("tags") openOr ""
|
||||||
val locale = S.locale.toString
|
val locale = S.locale.toString
|
||||||
// Note the Props value might contain a query parameter e.g. ?psd2=true
|
// Note the Props value might contain a query parameter e.g. ?psd2=true
|
||||||
val baseUrl = getWebUiPropsValue("webui_api_explorer_url", "")
|
val baseUrl = getWebUiPropsValue("webui_api_explorer_url", "http://localhost:5174")
|
||||||
// hack (we should use url operators instead) so we can add further query parameters if one is already included in the the baseUrl
|
// hack (we should use url operators instead) so we can add further query parameters if one is already included in the the baseUrl
|
||||||
val baseUrlWithQuery = baseUrl.contains("?") match {
|
val baseUrlWithQuery = baseUrl.contains("?") match {
|
||||||
case true => baseUrl + s"&tags=$tags${brandString}&locale=${locale}" // ? found so add & instead
|
case true => baseUrl + s"&tags=$tags${brandString}&locale=${locale}" // ? found so add & instead
|
||||||
@ -309,7 +309,19 @@ class WebUI extends MdcLoggable{
|
|||||||
".commit-id-link a [href]" #> s"https://github.com/OpenBankProject/OBP-API/commit/$commitId"
|
".commit-id-link a [href]" #> s"https://github.com/OpenBankProject/OBP-API/commit/$commitId"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// External Consumer Registration Link
|
||||||
|
// This replaces the internal Lift-based consumer registration functionality
|
||||||
|
// with a link to an external consumer registration service.
|
||||||
|
// Uses webui_api_explorer_url + /consumers/register as default.
|
||||||
|
// Configure webui_external_consumer_registration_url to override with a custom URL.
|
||||||
|
def externalConsumerRegistrationLink: CssSel = {
|
||||||
|
val apiExplorerUrl = getWebUiPropsValue("webui_api_explorer_url", "http://localhost:5174")
|
||||||
|
val defaultConsumerRegisterUrl = s"$apiExplorerUrl/consumers/register"
|
||||||
|
val externalUrl = getWebUiPropsValue("webui_external_consumer_registration_url", defaultConsumerRegisterUrl)
|
||||||
|
".get-api-key-link a [href]" #> scala.xml.Unparsed(externalUrl) &
|
||||||
|
".get-api-key-link a [target]" #> "_blank" &
|
||||||
|
".get-api-key-link a [rel]" #> "noopener"
|
||||||
|
}
|
||||||
|
|
||||||
// Social Finance (Sofi)
|
// Social Finance (Sofi)
|
||||||
def sofiLink: CssSel = {
|
def sofiLink: CssSel = {
|
||||||
@ -456,7 +468,7 @@ class WebUI extends MdcLoggable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// API Explorer URL from Props
|
// API Explorer URL from Props
|
||||||
val apiExplorerUrl = scala.xml.Unparsed(getWebUiPropsValue("webui_api_explorer_url", ""))
|
val apiExplorerUrl = scala.xml.Unparsed(getWebUiPropsValue("webui_api_explorer_url", "http://localhost:5174"))
|
||||||
|
|
||||||
// DirectLogin documentation url
|
// DirectLogin documentation url
|
||||||
def directLoginDocumentationUrl: CssSel = {
|
def directLoginDocumentationUrl: CssSel = {
|
||||||
@ -491,13 +503,13 @@ class WebUI extends MdcLoggable{
|
|||||||
|
|
||||||
|
|
||||||
def directLoginDocLink: CssSel = {
|
def directLoginDocLink: CssSel = {
|
||||||
val baseUrl = getWebUiPropsValue("webui_api_explorer_url", "")
|
val baseUrl = getWebUiPropsValue("webui_api_explorer_url", "http://localhost:5174")
|
||||||
val supportplatformlink = scala.xml.Unparsed(getWebUiPropsValue("webui_direct_login_documentation_url", s"${baseUrl}/glossary#Direct-Login"))
|
val supportplatformlink = scala.xml.Unparsed(getWebUiPropsValue("webui_direct_login_documentation_url", s"${baseUrl}/glossary#Direct-Login"))
|
||||||
"#direct-login-doc-link a [href]" #> supportplatformlink
|
"#direct-login-doc-link a [href]" #> supportplatformlink
|
||||||
}
|
}
|
||||||
|
|
||||||
def oauth1aLoginDocLink: CssSel = {
|
def oauth1aLoginDocLink: CssSel = {
|
||||||
val baseUrl = getWebUiPropsValue("webui_api_explorer_url", "")
|
val baseUrl = getWebUiPropsValue("webui_api_explorer_url", "http://localhost:5174")
|
||||||
val supportplatformlink = scala.xml.Unparsed(getWebUiPropsValue("webui_oauth_1_documentation_url", s"${baseUrl}/glossary#OAuth-1.0a"))
|
val supportplatformlink = scala.xml.Unparsed(getWebUiPropsValue("webui_oauth_1_documentation_url", s"${baseUrl}/glossary#OAuth-1.0a"))
|
||||||
"#oauth1a-doc-link a [href]" #> supportplatformlink
|
"#oauth1a-doc-link a [href]" #> supportplatformlink
|
||||||
}
|
}
|
||||||
|
|||||||
@ -216,7 +216,7 @@ object Helper extends Loggable {
|
|||||||
def isValidInternalRedirectUrl(url: String) : Boolean = {
|
def isValidInternalRedirectUrl(url: String) : Boolean = {
|
||||||
//set the default value is "/" and "/oauth/authorize"
|
//set the default value is "/" and "/oauth/authorize"
|
||||||
val internalRedirectUrlsWhiteList = List(
|
val internalRedirectUrlsWhiteList = List(
|
||||||
"/","/oauth/authorize","/consumer-registration",
|
"/","/oauth/authorize",
|
||||||
"/dummy-user-tokens","/create-sandbox-account",
|
"/dummy-user-tokens","/create-sandbox-account",
|
||||||
"/add-user-auth-context-update-request","/otp",
|
"/add-user-auth-context-update-request","/otp",
|
||||||
"/terms-and-conditions", "/privacy-policy",
|
"/terms-and-conditions", "/privacy-policy",
|
||||||
|
|||||||
@ -1,258 +0,0 @@
|
|||||||
<!--
|
|
||||||
Open Bank Project - API
|
|
||||||
Copyright (C) 2011-2017, TESOBE GmbH
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
Email: contact@tesobe.com
|
|
||||||
TESOBE GmbH
|
|
||||||
Osloerstrasse 16/17
|
|
||||||
Berlin 13359, Germany
|
|
||||||
|
|
||||||
This product includes software developed at
|
|
||||||
TESOBE (http://www.tesobe.com/)
|
|
||||||
by
|
|
||||||
Simon Redfern : simon AT tesobe DOT com
|
|
||||||
Sebastian Henschel : sebastian AT tesobe DOT com
|
|
||||||
-->
|
|
||||||
<div id="register-consumer" data-lift="surround?with=default;at=content" tabindex="-1">
|
|
||||||
<div data-lift="ConsumerRegistration.registerForm">
|
|
||||||
|
|
||||||
<div id="register-consumer-input" tabindex="-1">
|
|
||||||
<div id="register-consumer-explanation">
|
|
||||||
<h1>Register your consumer</h1>
|
|
||||||
<p>Please complete the information about your application below, so we can create your OAuth consumer key and secret.</p>
|
|
||||||
<p>All fields are required unless marked as 'optional'</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- <div id="register-consumer-errors" class="alert alert-danger">-->
|
|
||||||
<!-- <div class="error">-->
|
|
||||||
<!-- <span class="errorContent"></span>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
|
|
||||||
<form method="post">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12 col-sm-12">
|
|
||||||
<div class="form-group" id="app-type-div">
|
|
||||||
<label for="appType" id="appTypeLabel">Application type</label>
|
|
||||||
<select name="app-type" id="appType" class="form-control js-example-basic-single">
|
|
||||||
<option class="app-type-option"></option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="appName">Application name</label>
|
|
||||||
<input type="text" name="app-name" id="appName" class="form-control" aria-describedby="consumer-registration-app-name-error">
|
|
||||||
<div id = "consumer-registration-app-name-error-div" class="hide">
|
|
||||||
<span data-lift="Msg?id=consumer-registration-app-name-error"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="appRedirectUrl" id="redirect_url_label">Redirect URL (Optional)</label>
|
|
||||||
<img src="/media/icons/info-square.svg" alt="i" style="padding-left: 5px;" width="16" height="16" data-toggle="tooltip" data-placement="right" title="For use in OAuth flows: This is the URL served by your App that the OBP authorisation server will redirect to with an authorization code or access token as parameters in the URL following successful authorisation.">
|
|
||||||
<input type="text" name="app-redirect-url" id="appRedirectUrl" class="form-control" aria-describedby="consumer-registration-app-redirect-url-error">
|
|
||||||
<div id = "consumer-registration-app-redirect-url-error-div" class="hide">
|
|
||||||
<span data-lift="Msg?id=consumer-registration-app-redirect-url-error"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="appDev">Developer email</label>
|
|
||||||
<input type="text" name="app-developer" id="appDev" class="form-control" aria-describedby="consumer-registration-app-developer-error">
|
|
||||||
<div id = "consumer-registration-app-developer-error-div" class="hide">
|
|
||||||
<span data-lift="Msg?id=consumer-registration-app-developer-error"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="appDesc">Description of the application</label>
|
|
||||||
<textarea rows="4" name="app-description" id="appDesc" class="form-control" aria-describedby="consumer-registration-app-description-error"></textarea>
|
|
||||||
<div id = "consumer-registration-app-description-error-div" class="hide">
|
|
||||||
<span data-lift="Msg?id=consumer-registration-app-description-error"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="company">Company</label>
|
|
||||||
<input type="text" name="app-company" id="company" class="form-control" aria-describedby="consumer-registration-app-company-error">
|
|
||||||
<div id = "consumer-registration-app-company-error-div" class="hide">
|
|
||||||
<span data-lift="Msg?id=consumer-registration-app-company-error"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row oauth2_fields">
|
|
||||||
<hr>
|
|
||||||
<h2 style="text-align: center">OAuth2 related:</h2>
|
|
||||||
|
|
||||||
<div class="col-xs-12 col-sm-12">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="app-signing_alg" id="request_signing_alg">signing_alg,
|
|
||||||
<a role="button" data-toggle="collapse" href="#collapse_signing_alg" aria-expanded="false" aria-controls="collapse_signing_alg">
|
|
||||||
help
|
|
||||||
</a>
|
|
||||||
</label>
|
|
||||||
<div class="collapse" id="collapse_signing_alg">
|
|
||||||
<div class="well">
|
|
||||||
The signing algorithm name of request object and client_assertion.
|
|
||||||
Reference <a target="_blank" href="https://openid.net/specs/openid-connect-core-1_0.html#RequestObject" rel="noopener">6.1. Passing a Request Object by Value</a>
|
|
||||||
and <a target="_blank" href="https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication" rel="noopener">9. Client Authentication</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<select name="app-signing_alg" id="app-signing_alg" class="form-control js-example-basic-single">
|
|
||||||
<option value="">none</option>
|
|
||||||
<option value="RS256">RS256</option>
|
|
||||||
<option value="ES256">ES256</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="app-request_uri" id="request_uri_label">
|
|
||||||
request_uri (Optional), <a href="https://openid.net/specs/openid-connect-core-1_0.html#RequestUriParameter" target="_blank" rel="noopener">reference</a>
|
|
||||||
</label>
|
|
||||||
<input type="url" name="app-request_uri" id="app-request_uri" class="form-control">
|
|
||||||
<div id = "consumer-registration-app-request_uri-error-div" class="hide">
|
|
||||||
<span data-lift="Msg?id=consumer-registration-app-request_uri-error"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="app-jwks_uri" id="request_jwks_uri">
|
|
||||||
jwks_uri (Optional), <a href="https://openid.net/specs/openid-connect-core-1_0.html#RotateSigKeys" target="_blank" rel="noopener">reference</a>
|
|
||||||
</label>
|
|
||||||
<input type="url" name="app-jwks_uri" id="app-jwks_uri" class="form-control">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="app-jwks">jwks (Optional),
|
|
||||||
<a role="button" data-toggle="collapse" href="#collapse_jwks" aria-expanded="false" aria-controls="collapse_jwks">
|
|
||||||
help
|
|
||||||
</a>
|
|
||||||
</label>
|
|
||||||
<div class="collapse" id="collapse_jwks">
|
|
||||||
<div class="well">
|
|
||||||
Content of <strong>jwks_uri</strong>. <strong>jwks_uri</strong> and <strong>jwks</strong> should not both have value at the same time.
|
|
||||||
Reference <a target="_blank" href="https://openid.net/specs/openid-connect-core-1_0.html#RotateSigKeys" rel="noopener">10.1.1. Rotation of Asymmetric Signing Keys</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<textarea rows="4" name="app-jwks" id="app-jwks" class="form-control" placeholder="Content of jwks"></textarea>
|
|
||||||
<div id = "consumer-registration-app-signing_jwks-div" class="hide alert alert-danger">
|
|
||||||
<span data-lift="Msg?id=consumer-registration-app-signing_jwks-error"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="app-client_certificate">MTLS Client certificate (Optional)</label>
|
|
||||||
<textarea rows="4" name="app-client_certificate" id="app-client_certificate" class="form-control" placeholder="Whole content of crt file"></textarea>
|
|
||||||
<div id = "consumer-registration-app-client_certificate-error-div" class="hide alert alert-danger">
|
|
||||||
<span data-lift="Msg?id=consumer-registration-app-client_certificate-error"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<input type="checkbox" class="form-check-input" id="oidc_checkbox">
|
|
||||||
<label class="marketing-info-label" for="oidc_checkbox">OpenID Connect Client</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<input type="submit" value="Submit" class="btn btn-danger" aria-describedby="register-consumer-errors"/>
|
|
||||||
<div id = "register-consumer-errors-div" class="hide">
|
|
||||||
<span data-lift="Msg?id=register-consumer-errors"/>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div id="register-consumer-success" tabindex="-1">
|
|
||||||
<h1>Register your consumer</h1>
|
|
||||||
<div id="register-consumer-success-message">
|
|
||||||
<p>Thanks for registering your consumer with the Open Bank API! Here is your developer information. Please save it in a secure location.</p>
|
|
||||||
</div>
|
|
||||||
<br>
|
|
||||||
<p>Please save it in a secure location.</p>
|
|
||||||
<div style="cursor:pointer;text-align: right;" title="">
|
|
||||||
<div class="fa-solid fa-copy" onclick="copyConsumerRegistrationResultToClipboard(this)" id="register-consumer-success-copy-icon" title=""></div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12 col-sm-4">Consumer ID: </div>
|
|
||||||
<div class="col-xs-12 col-sm-8"><span id="app-consumer_id">123</span></div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12 col-sm-4">Application Type: </div>
|
|
||||||
<div class="col-xs-12 col-sm-8"><span id="app-type">web</span></div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12 col-sm-4">Application Name: </div>
|
|
||||||
<div class="col-xs-12 col-sm-8"><span id="app-name">ABC</span></div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12 col-sm-4">User redirect URL: </div>
|
|
||||||
<div class="col-xs-12 col-sm-8"><span id="app-redirect-url">ABC</span></div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12 col-sm-4">Developer Email: </div>
|
|
||||||
<div class="col-xs-12 col-sm-8"><span id="app-developer">abc@example.com</span></div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12 col-sm-4">App Description: </div>
|
|
||||||
<div class="col-xs-12 col-sm-8"><span id="app-description">ABCDEF</span></div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12 col-sm-4">Client certificate: </div>
|
|
||||||
<div class="col-xs-12 col-sm-8">
|
|
||||||
<span id="client_certificate" style="overflow-wrap: anywhere">ABCDEF</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12 col-sm-4">Consumer Key: </div>
|
|
||||||
<div class="col-xs-12 col-sm-8"><span id="auth-key">23432432432432</span></div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12 col-sm-4">Consumer Secret: </div>
|
|
||||||
<div class="col-xs-12 col-sm-8"><span id="secret-key">3334543543543</span></div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12 col-sm-4">OAuth 1.0a Endpoint: </div>
|
|
||||||
<div class="col-xs-12 col-sm-8"><span id="oauth-endpoint"><a href="#">endpoint</a></span></div>
|
|
||||||
</div>
|
|
||||||
<div class="row" data-lift="WebUI.oauth1aLoginDocLink">
|
|
||||||
<div class="col-xs-12 col-sm-4">OAuth 1.0a Documentation: </div>
|
|
||||||
<div class="col-xs-12 col-sm-8"><a id="oauth1a-doc-link" href="https://github.com/OpenBankProject/OBP-API/wiki/OAuth-1.0-Server" target="_blank">How to use OAuth for OpenBankProject</a></div>
|
|
||||||
</div>
|
|
||||||
<div class="row" id="dummy-user-tokens">
|
|
||||||
<div class="col-xs-12 col-sm-4">Dummy Users' Direct Login Tokens: </div>
|
|
||||||
<div class="col-xs-12 col-sm-8"><span id="create-directlogin"><a target="_blank" href="#" rel="noopener">Get dummy users' token</a></span></div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12 col-sm-4">Direct Login Endpoint: </div>
|
|
||||||
<div class="col-xs-12 col-sm-8"><span id="directlogin-endpoint"><a href="#">endpoint</a></span></div>
|
|
||||||
</div>
|
|
||||||
<div class="row" data-lift="WebUI.directLoginDocLink">
|
|
||||||
<div class="col-xs-12 col-sm-4">Direct Login Documentation: </div>
|
|
||||||
<div class="col-xs-12 col-sm-8"><a id="direct-login-doc-link" href="https://github.com/OpenBankProject/OBP-API/wiki/Direct-Login" target="_blank">How to use Direct Login</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12 col-sm-4"></div>
|
|
||||||
<div class="col-xs-12 col-sm-8"><span id="post-consumer-registration-more-info-link"><a href="#"></a></span></div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12 col-sm-4" id="hydra-client-info-title">OAuth2: </div>
|
|
||||||
<div class="col-xs-12 col-sm-8">
|
|
||||||
<span id="hydra-client-info" style="overflow-wrap: anywhere">
|
|
||||||
oauth2.client_id=<span id="client_id">auth-code-client</span><br>
|
|
||||||
oauth2.redirect_uri=<span id="redirect_uri">http://127.0.0.1:8081/main.html</span><br>
|
|
||||||
<span id="oauth2_request_uri">
|
|
||||||
oauth2.request_uri=<span id="request_uri_value">http://127.0.0.1:8081/request_object.json</span><br>
|
|
||||||
</span>
|
|
||||||
oauth2.client_scope=<span id="client_scope"><span class="client-scope-value">ReadAccountsBasic</span><br></span><br>
|
|
||||||
oauth2.jws_alg=<span id="client_jws_alg"></span><br>
|
|
||||||
oauth2.jwk_private_key=<span id="jwk_private_key">content of jwk key</span><br>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div><!-- success -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@ -37,7 +37,7 @@ Berlin 13359, Germany
|
|||||||
<span class="view_api_explorer">View API Explorer</span></a>
|
<span class="view_api_explorer">View API Explorer</span></a>
|
||||||
<a id="sandbox-introduction-link" class="btn btn-default" data-lift="WebUI.sandboxIntroductionLink" href="">
|
<a id="sandbox-introduction-link" class="btn btn-default" data-lift="WebUI.sandboxIntroductionLink" href="">
|
||||||
<span class="introduction">Introduction</span></a>
|
<span class="introduction">Introduction</span></a>
|
||||||
<!-- <a href="/consumer-registration" class="btn btn-default">Get API key</a>-->
|
<a class="btn btn-default get-api-key-link" data-lift="WebUI.externalConsumerRegistrationLink" href="#">Get API key</a>
|
||||||
<!-- <a class="sofi-link btn btn-default" data-lift="WebUI.sofiLink" href="">SOFIT</a>-->
|
<!-- <a class="sofi-link btn btn-default" data-lift="WebUI.sofiLink" href="">SOFIT</a>-->
|
||||||
<!-- <a id="sandbox-introduction-link" class="btn btn-default" data-lift="WebUI.sandboxIntroductionLink" href="">INTRODUCTION</a>-->
|
<!-- <a id="sandbox-introduction-link" class="btn btn-default" data-lift="WebUI.sandboxIntroductionLink" href="">INTRODUCTION</a>-->
|
||||||
<a class="btn btn-default subscriptions-button" data-lift="WebUI.subscriptionsButton" href="">
|
<a class="btn btn-default subscriptions-button" data-lift="WebUI.subscriptionsButton" href="">
|
||||||
|
|||||||
@ -119,8 +119,8 @@ Berlin 13359, Germany
|
|||||||
<span class="api_explorer">API Explorer</span>
|
<span class="api_explorer">API Explorer</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li data-lift="Nav.item?name=Consumer%20Registration&showEvenIfRestricted=true" class="navitem">
|
<li class="navitem get-api-key-link" data-lift="WebUI.externalConsumerRegistrationLink">
|
||||||
<a id ="get-api-key-link" class="navlink" href="#">Link name. Has class "selected" if it's the current page.</a>
|
<a id ="get-api-key-link" class="navlink" href="#">Get API Key</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="navitem">
|
<li class="navitem">
|
||||||
<a class="navlink" id="sandbox-introduction-link" data-lift="WebUI.sandboxIntroductionLink" href="">
|
<a class="navlink" id="sandbox-introduction-link" data-lift="WebUI.sandboxIntroductionLink" href="">
|
||||||
@ -181,8 +181,8 @@ Berlin 13359, Germany
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<div class="navitem" data-lift="Nav.item?name=Consumer%20Registration&showEvenIfRestricted=true" id="sideba-api-key-div">
|
<div class="navitem get-api-key-link" data-lift="WebUI.externalConsumerRegistrationLink" id="sideba-api-key-div">
|
||||||
<a id ="get-api-key-link" class="navlink" href="#">Link name. Has class "selected" if it's the current page.</a>
|
<a id ="get-api-key-link" class="navlink" href="#">Get API Key</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
|||||||
@ -124,8 +124,8 @@ Berlin 13359, Germany
|
|||||||
<lift:loc locid="api_explorer">API Explorer</lift:loc>
|
<lift:loc locid="api_explorer">API Explorer</lift:loc>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li data-lift="Nav.item?name=Consumer%20Registration&showEvenIfRestricted=true" class="navitem">
|
<li class="navitem get-api-key-link" data-lift="WebUI.externalConsumerRegistrationLink">
|
||||||
<a id ="get-api-key-link" class="navlink" href="#">Link name. Has class "selected" if it's the current page.</a>
|
<a id ="get-api-key-link" class="navlink" href="#">Get API Key</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="navitem">
|
<li class="navitem">
|
||||||
<a class="navlink" id="sandbox-introduction-link" data-lift="WebUI.sandboxIntroductionLink" href="">
|
<a class="navlink" id="sandbox-introduction-link" data-lift="WebUI.sandboxIntroductionLink" href="">
|
||||||
@ -191,8 +191,8 @@ Berlin 13359, Germany
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<div class="navitem" data-lift="Nav.item?name=Consumer%20Registration&showEvenIfRestricted=true" id="sideba-api-key-div">
|
<div class="navitem get-api-key-link" data-lift="WebUI.externalConsumerRegistrationLink" id="sideba-api-key-div">
|
||||||
<a id ="get-api-key-link" class="navlink" href="#">Link name. Has class "selected" if it's the current page.</a>
|
<a id ="get-api-key-link" class="navlink" href="#">Get API Key</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
|||||||
@ -119,8 +119,8 @@ Berlin 13359, Germany
|
|||||||
<lift:loc locid="api_explorer">API Explorer</lift:loc>
|
<lift:loc locid="api_explorer">API Explorer</lift:loc>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li data-lift="Nav.item?name=Consumer%20Registration&showEvenIfRestricted=true" class="navitem">
|
<li class="navitem get-api-key-link" data-lift="WebUI.externalConsumerRegistrationLink">
|
||||||
<a id ="get-api-key-link" class="navlink" href="#">Link name. Has class "selected" if it's the current page.</a>
|
<a id ="get-api-key-link" class="navlink" href="#">Get API Key</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="navitem">
|
<li class="navitem">
|
||||||
<a class="navlink" id="sandbox-introduction-link" data-lift="WebUI.sandboxIntroductionLink" href="">
|
<a class="navlink" id="sandbox-introduction-link" data-lift="WebUI.sandboxIntroductionLink" href="">
|
||||||
@ -181,8 +181,8 @@ Berlin 13359, Germany
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<div class="navitem" data-lift="Nav.item?name=Consumer%20Registration&showEvenIfRestricted=true" id="sideba-api-key-div">
|
<div class="navitem get-api-key-link" data-lift="WebUI.externalConsumerRegistrationLink" id="sideba-api-key-div">
|
||||||
<a id ="get-api-key-link" class="navlink" href="#">Link name. Has class "selected" if it's the current page.</a>
|
<a id ="get-api-key-link" class="navlink" href="#">Get API Key</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
|||||||
@ -119,8 +119,8 @@ Berlin 13359, Germany
|
|||||||
<lift:loc locid="api_explorer">API Explorer</lift:loc>
|
<lift:loc locid="api_explorer">API Explorer</lift:loc>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li data-lift="Nav.item?name=Consumer%20Registration&showEvenIfRestricted=true" class="navitem">
|
<li class="navitem get-api-key-link" data-lift="WebUI.externalConsumerRegistrationLink">
|
||||||
<a id ="get-api-key-link" class="navlink" href="#">Link name. Has class "selected" if it's the current page.</a>
|
<a id ="get-api-key-link" class="navlink" href="#">Get API Key</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="navitem">
|
<li class="navitem">
|
||||||
<a class="navlink" id="sandbox-introduction-link" data-lift="WebUI.sandboxIntroductionLink" href="">
|
<a class="navlink" id="sandbox-introduction-link" data-lift="WebUI.sandboxIntroductionLink" href="">
|
||||||
@ -181,8 +181,8 @@ Berlin 13359, Germany
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<div class="navitem" data-lift="Nav.item?name=Consumer%20Registration&showEvenIfRestricted=true" id="sideba-api-key-div">
|
<div class="navitem get-api-key-link" data-lift="WebUI.externalConsumerRegistrationLink" id="sideba-api-key-div">
|
||||||
<a id ="get-api-key-link" class="navlink" href="#">Link name. Has class "selected" if it's the current page.</a>
|
<a id ="get-api-key-link" class="navlink" href="#">Get API Key</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user