make signing_alg, jwks_uri and jwks cifiguable when register consumer.

This commit is contained in:
shuang 2020-11-08 21:46:14 +08:00
parent 28a9540532
commit 6ee383f3ed
5 changed files with 198 additions and 70 deletions

View File

@ -26,6 +26,8 @@ TESOBE (http://www.tesobe.com/)
*/
package code.snippet
import java.util
import code.api.DirectLogin
import code.api.util.{APIUtil, ErrorMessages, X509}
import code.consumer.Consumers
@ -39,8 +41,10 @@ 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.ListMap
import scala.collection.immutable.{List, ListMap}
import scala.jdk.CollectionConverters.seqAsJavaListConverter
import scala.xml.{Text, Unparsed}
@ -52,9 +56,12 @@ class ConsumerRegistration extends MdcLoggable {
private object authenticationURLVar extends RequestVar("")
private object appTypeVar extends RequestVar[AppType](AppType.Web)
private object descriptionVar extends RequestVar("")
private object clientCertificateVar extends RequestVar("")
private object devEmailVar extends RequestVar("")
private object appType extends RequestVar("Web")
private object clientCertificateVar extends RequestVar("")
private object signingAlgVar extends RequestVar("")
private object jwksUriVar extends RequestVar("")
private object jwksVar extends RequestVar("")
private object submitButtonDefenseFlag extends RequestVar("")
@ -74,6 +81,22 @@ class ConsumerRegistration extends MdcLoggable {
def registerForm = {
val appTypes = List((AppType.Web.toString, AppType.Web.toString), (AppType.Mobile.toString, AppType.Mobile.toString))
val signingAlgs = List(
"ES256",
"ES256K",
"ES512",
"ES384",
"EdDSA",
"RS256",
"RS512",
"RS38",
"HS256",
"HS384",
"HS512",
"PS256",
"PS384",
"PS512"
).map(it => it -> it)
def submitButtonDefense: Unit = {
submitButtonDefenseFlag("true")
@ -95,10 +118,13 @@ class ConsumerRegistration extends MdcLoggable {
"#appDesc" #> SHtml.textarea(descriptionVar, descriptionVar (_)) &
"#appUserAuthenticationUrl" #> SHtml.text(authenticationURLVar.is, authenticationURLVar(_)) & {
if(HydraUtil.mirrorConsumerInHydra) {
"#request_uri" #> SHtml.text(requestUriVar, requestUriVar(_)) &
"#appClientCertificate" #> SHtml.textarea(clientCertificateVar, clientCertificateVar (_))
"#app-client_certificate" #> SHtml.textarea(clientCertificateVar, clientCertificateVar (_))&
"#app-request_uri" #> SHtml.text(requestUriVar, requestUriVar(_)) &
"#app-signing_alg" #> SHtml.select(signingAlgs, Empty, signingAlgVar(_)) &
"#app-jwks_uri" #> SHtml.text(jwksUriVar, jwksUriVar(_)) &
"#app-jwks" #> SHtml.textarea(jwksVar, jwksVar(_))
} else {
".oauth2_field" #> ""
".oauth2_fields" #> ""
}
} &
"type=submit" #> SHtml.submit(s"$registrationConsumerButtonValue", () => submitButtonDefense)
@ -109,11 +135,39 @@ class ConsumerRegistration extends MdcLoggable {
def showResults(consumer : Consumer) = {
val urlOAuthEndpoint = APIUtil.getPropsValue("hostname", "") + "/oauth/initiate"
val urlDirectLoginEndpoint = APIUtil.getPropsValue("hostname", "") + "/my/logins/direct"
var jwkPrivateKey: String = ""
val jwksUri = jwksUriVar.is
val jwks = jwksVar.is
var jwkPrivateKey: String = s"Please change this value to ${if(StringUtils.isNotBlank(jwksUri)) "jwks_uri" else "jwks"} corresponding private key"
if(HydraUtil.mirrorConsumerInHydra) {
val(privateKey, publicKey) = HydraUtil.createJwk
jwkPrivateKey = privateKey
HydraUtil.createHydraClient(consumer, publicKey, requestUriVar.is)
HydraUtil.createHydraClient(consumer, oAuth2Client => {
val signingAlg = signingAlgVar.is
oAuth2Client.setTokenEndpointAuthMethod("private_key_jwt")
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
})
}
val registerConsumerSuccessMessageWebpage = getWebUiPropsValue(
"webui_register_consumer_success_message_webpage",
@ -126,7 +180,7 @@ class ConsumerRegistration extends MdcLoggable {
"#app-user-authentication-url *" #> consumer.userAuthenticationURL &
"#app-type *" #> consumer.appType.get &
"#app-description *" #> consumer.description.get &
"#app-client-certificate *" #> {
"#client_certificate *" #> {
if (StringUtils.isBlank(consumer.clientCertificate.get)) Text("None")
else Unparsed(consumer.clientCertificate.get)
} &
@ -212,10 +266,12 @@ class ConsumerRegistration extends MdcLoggable {
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("certificate")).map(errorMessage => S.error("consumer-registration-app-client-certificate-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")))
@ -249,25 +305,47 @@ class ConsumerRegistration extends MdcLoggable {
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)
clientCertificateVar.set(clientCertificate)
devEmailVar.set(devEmailVar.is)
redirectionURLVar.set(redirectionURLVar.is)
requestUriVar.set(requestUriVar.is)
if(submitButtonDefenseFlag.isEmpty) {
requestUriVar.set(requestUri)
clientCertificateVar.set(clientCertificate)
signingAlgVar.set(signingAlg)
jwksUriVar.set(jwksUri)
jwksVar.set(jwks)
val oauth2ParamError: CssSel = if(HydraUtil.mirrorConsumerInHydra) {
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.isNotBlank(jwksUri) && StringUtils.isBlank(signingAlg)) {
showErrorsForDescription("The 'signing_alg' should not be empty when request_uri have value!")
} else if(!StringUtils.isAllBlank(jwksUri, jwks) && StringUtils.isBlank(signingAlg)) {
showErrorsForDescription("The 'signing_alg' must have value when 'jwks_uri' or 'jwks' have value!")
} 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 if(HydraUtil.mirrorConsumerInHydra && (StringUtils.isBlank(redirectionURLVar.is) || Consumer.redirectURLRegex.findFirstIn(redirectionURLVar.is).isEmpty)) {
showErrorsForDescription("The 'Redirect URL' should be a valid url !")
} else if(HydraUtil.mirrorConsumerInHydra && (StringUtils.isNotBlank(requestUriVar.is) && !requestUriVar.is.matches("""^https?://(www.)?\S+?(:\d{2,6})?\S*$"""))) {
showErrorsForDescription("The 'request_uri' should be a valid url !")
} 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{
val consumer = Consumers.consumers.vend.createConsumer(
Some(Helpers.randomString(40).toLowerCase),
@ -319,7 +397,7 @@ class ConsumerRegistration extends MdcLoggable {
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.toString} \n" +
s"App type: ${registered.appType.get} \n" +
s"App description: ${registered.description.get} \n" +
s"Consumer Key: ${consumerKeyOrMessage} \n" +
s"Consumer Secret : ${consumerSecretOrMessage} \n" +
@ -364,7 +442,7 @@ class ConsumerRegistration extends MdcLoggable {
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.toString} \n" +
s"App type: ${registered.appType.get} \n" +
s"App description: ${registered.description.get}"
//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

View File

@ -5,11 +5,10 @@ import java.util.UUID
import code.api.util.APIUtil
import code.model.Consumer
import code.model.Consumer.redirectURLRegex
import com.nimbusds.jose.{Algorithm, JWSAlgorithm}
import com.nimbusds.jose.Algorithm
import com.nimbusds.jose.jwk.gen.ECKeyGenerator
import com.nimbusds.jose.jwk.{Curve, ECKey, KeyUse}
import org.apache.commons.lang3.StringUtils
import org.codehaus.jackson.map.ObjectMapper
import sh.ory.hydra.api.{AdminApi, PublicApi}
import sh.ory.hydra.model.OAuth2Client
import sh.ory.hydra.{ApiClient, Configuration}
@ -58,7 +57,7 @@ object HydraUtil {
* @param consumer
* @return created Hydra client or None
*/
def createHydraClient(consumer: Consumer, jwkPublicKey: String = null, requestUri: String = null): Option[OAuth2Client] = {
def createHydraClient(consumer: Consumer, fun: OAuth2Client => OAuth2Client = identity): Option[OAuth2Client] = {
val redirectUrl = consumer.redirectURL.get
if (StringUtils.isBlank(redirectUrl) || redirectURLRegex.findFirstIn(redirectUrl).isEmpty) {
return None
@ -79,32 +78,23 @@ object HydraUtil {
val clientMeta = Map("client_certificate" -> consumer.clientCertificate.get).asJava
oAuth2Client.setMetadata(clientMeta)
}
if(StringUtils.isBlank(jwkPublicKey)) {
oAuth2Client.setTokenEndpointAuthMethod("client_secret_post")
} else {
oAuth2Client.setTokenEndpointAuthMethod("private_key_jwt")
val jwks = s"""{"keys": [$jwkPublicKey]}"""
val jwksMap = new ObjectMapper().readValue(jwks, classOf[java.util.Map[String, _]])
oAuth2Client.setJwks(jwksMap)
oAuth2Client.setTokenEndpointAuthSigningAlg(JWSAlgorithm.ES256.getName)
oAuth2Client.setRequestObjectSigningAlg(JWSAlgorithm.ES256.getName)
}
if(StringUtils.isNotBlank(requestUri)) {
oAuth2Client.setRequestUris(List(requestUri).asJava)
}
Some(hydraAdmin.createOAuth2Client(oAuth2Client))
oAuth2Client.setTokenEndpointAuthMethod("client_secret_post")
val decoratedClient = fun(oAuth2Client)
Some(hydraAdmin.createOAuth2Client(decoratedClient))
}
/**
* create jwk
* @param signingAlg signing algorithm name
* @return private key json string to public key
*/
def createJwk: (String, String) = {
def createJwk(signingAlg: String): (String, String) = {
val jwk:ECKey = new ECKeyGenerator(Curve.P_256)
.keyUse(KeyUse.SIGNATURE) // indicate the intended use of the key
.keyID(UUID.randomUUID().toString()) // give the key a unique ID
.algorithm(new Algorithm("ES256"))
.algorithm(new Algorithm(signingAlg))
.generate()
jwk.toJSONString -> jwk.toPublicJWK().toJSONString

View File

@ -62,18 +62,6 @@ Berlin 13359, Germany
<span data-lift="Msg?id=consumer-registration-app-redirect-url-error"/>
</div>
</div>
<span class="oauth2_field">
<div class="form-group">
<label for="request_uri" id="request_uri_label">
<a href="https://tools.ietf.org/id/draft-lodderstedt-oauth-rar-03.html#security-considerations" target="_blank">The request_uri authorization request</a> parameter (Optional)
</label>
<input type="url" name="app-request_uri" id="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>
</span>
<div class="form-group">
<label for="appDev">Developer email</label>
<input type="text" name="app-developer" id="appDev" class="form-control">
@ -91,18 +79,74 @@ Berlin 13359, Germany
</div>
</div>
</div>
<span class="oauth2_field">
<div class="col-xs-12 col-sm-6">
<div class="form-group">
<label for="appClientCertificate">Client certificate, content of crt file (Optional)</label>
<textarea rows="4" name="app-description" id="appClientCertificate" class="form-control"></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="row oauth2_fields">
<hr>
<h2 style="text-align: center">OAuth2 related:</h2>
<div class="col-xs-12 col-sm-6">
<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">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>
</span>
<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>
<div class="col-xs-12 col-sm-6">
<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">
<span class="glyphicon glyphicon-menu-hamburger" aria-hidden="true"></span>
</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">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">9. Client Authentication</a>
</div>
</div>
<select name="app-signing_alg" id="app-signing_alg" class="form-control">
<option value="">none</option>
<option value="RS256">RS256</option>
<option value="ES256">ES256</option>
</select>
</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">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">
<span class="glyphicon glyphicon-menu-hamburger" aria-hidden="true"></span>
</a>
</label>
<div class="collapse" id="collapse_jwks">
<div class="well">
Content of <b>jwks_uri</b>. <b>jwks_uri</b> and <b>jwks</b> 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">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>
</div>
<input type="submit" value="Register consumer" class="btn btn-default" />
<div id = "register-consumer-errors-div" class="hide alert alert-danger">
@ -146,7 +190,7 @@ Berlin 13359, Germany
<div class="row">
<div class="col-xs-12 col-sm-4">Client certificate</div>
<div class="col-xs-12 col-sm-8">
<span id="app-client-certificate" style="overflow-wrap: anywhere">ABCDEF</span>
<span id="client_certificate" style="overflow-wrap: anywhere">ABCDEF</span>
</div>
</div>
<div class="row">

View File

@ -557,9 +557,16 @@ footer #copyright {
#register-consumer #appDesc {
height: 250px;
#register-consumer textarea {
height: 253px;
}
#register-consumer #app-jwks {
height: 180px;
}
#register-consumer .list-group-item, #register-consumer .well {
background-color: #2db6eb;
}
#register-consumer #register-consumer-errors {
margin-bottom: 20px;
}

View File

@ -138,8 +138,8 @@ $(document).ready(function() {
consumerRegistrationAppDescError.parent().addClass('hide');
}
var consumerRegistrationAppClientCertificateError = $('#register-consumer-input #consumer-registration-app-client-certificate-error');
var consumerRegistrationAppClientCertificateForm = $('#register-consumer-input #appClientCertificate');
var consumerRegistrationAppClientCertificateError = $('#register-consumer-input #consumer-registration-app-client_certificate-error');
var consumerRegistrationAppClientCertificateForm = $('#register-consumer-input #app-client_certificate');
if (consumerRegistrationAppClientCertificateError.length > 0 && consumerRegistrationAppClientCertificateError.html().length > 0) {
consumerRegistrationAppClientCertificateError.parent().removeClass('hide');
consumerRegistrationAppClientCertificateForm.addClass("error-border")
@ -148,14 +148,23 @@ $(document).ready(function() {
}
var consumerRegistrationAppRequestUriError = $('#register-consumer-input #consumer-registration-app-request_uri-error');
var consumerRegistrationAppRequestUriForm = $('#register-consumer-input #request_uri');
if (consumerRegistrationAppRequestUriError.length > 0 && consumerRegistrationAppRequestUriError.html().length > 0) {
consumerRegistrationAppRequestUriError.parent().removeClass('hide');
consumerRegistrationAppRequestUriForm.addClass("error-border")
$('#register-consumer-input #app-request_uri').addClass("error-border")
} else{
consumerRegistrationAppRequestUriError.parent().addClass('hide');
}
{
var consumerRegistrationJwksError = $('#register-consumer-input #consumer-registration-app-signing_jwks-error');
if (consumerRegistrationJwksError.length > 0 && consumerRegistrationJwksError.html().length > 0) {
consumerRegistrationJwksError.parent().removeClass('hide');
$('#register-consumer-input #app-jwks').addClass("error-border")
} else{
consumerRegistrationJwksError.parent().addClass('hide');
}
}
var consumerRegistrationAppRedirectUrlError = $('#register-consumer-input #consumer-registration-app-description-error');
var consumerRegistrationAppRedirectUrlForm = $('#register-consumer-input #appDesc');
if (consumerRegistrationAppRedirectUrlError.length > 0 && consumerRegistrationAppRedirectUrlError.html().length > 0) {